Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1b2d34fce | ||
|
|
448a5d5353 | ||
|
|
76b1898ee1 | ||
|
|
f876a526df | ||
|
|
43dc11f224 | ||
|
|
eb74c9f792 | ||
|
|
5f1abf161c | ||
|
|
07302a866f | ||
|
|
e7d8b9fbe3 | ||
|
|
929ae2d5af | ||
|
|
93846725e5 | ||
|
|
633492e4d0 | ||
|
|
e4c83398f2 | ||
|
|
54447c7877 | ||
|
|
28dabab5ba | ||
|
|
c69c8101ed | ||
|
|
54abd34493 | ||
|
|
d7efd3934a | ||
|
|
c3e8220792 | ||
|
|
7e07506327 | ||
|
|
2670b3589f | ||
|
|
a950de4b2b | ||
|
|
b214dc1728 | ||
|
|
1984996d04 | ||
|
|
f20689db44 | ||
|
|
4d35b9cb10 | ||
|
|
9d8e471f78 | ||
|
|
95bf07a774 | ||
|
|
93b96cf5b8 | ||
|
|
531e48ce13 | ||
|
|
0c9f2fcd81 | ||
|
|
f68db3b03e | ||
|
|
bd08baacac | ||
|
|
f39126e019 | ||
|
|
f649bd1a3e | ||
|
|
719563a48c | ||
|
|
32cc1cb206 | ||
|
|
adf220d344 | ||
|
|
32ad9ed6b0 | ||
|
|
cf19bcfdfa | ||
|
|
3b5796f1a0 | ||
|
|
68135a712d | ||
|
|
1d3b8184c3 | ||
|
|
ef6ea26fd8 | ||
|
|
9c6ed2481e | ||
|
|
bf4d5d8faf | ||
|
|
76addf661f | ||
|
|
7ea610770c | ||
|
|
22249cf192 |
No files matched your search
@@ -0,0 +1,38 @@
|
||||
name: Problem report
|
||||
description: Something does not work as it should
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for reporting! Please do **not** ask for, link to or attach game files or disc images; such issues are closed and deleted.
|
||||
- type: dropdown
|
||||
id: headset
|
||||
attributes:
|
||||
label: Headset
|
||||
options:
|
||||
- Meta Quest 3
|
||||
- Meta Quest 3S
|
||||
- Steam Frame
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: build
|
||||
attributes:
|
||||
label: Build number
|
||||
description: The release you installed, e.g. "build 180".
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: what
|
||||
attributes:
|
||||
label: What happened
|
||||
description: What you did, what you expected and what happened instead.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: log
|
||||
attributes:
|
||||
label: Log
|
||||
description: |
|
||||
Attach `debug.txt`. Quest: `Android/data/io.github.cesarus85.ringfall/files/debug.txt` (SideQuest's file manager). Steam Frame: `Documents/RingfallMjolnirVR/debug.txt`, and `logcat.txt` and `install.log` beside it.
|
||||
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: How to install
|
||||
url: https://github.com/Cesarus85/ringfall-mjolnir-vr#install-on-meta-quest-3--3s
|
||||
about: Step-by-step installation for the Quest 3 / 3S and the Steam Frame.
|
||||
@@ -220,8 +220,22 @@ jobs:
|
||||
path: dist/halo-windows-release
|
||||
retention-days: 3
|
||||
|
||||
# the PDBs, for crash reports' backtraces (tools/ci_build.py)
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: halo-windows-debug-symbols
|
||||
path: dist/halo-windows-debug-symbols
|
||||
retention-days: 3
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: halo-windows-release-symbols
|
||||
path: dist/halo-windows-release-symbols
|
||||
retention-days: 3
|
||||
|
||||
release:
|
||||
# The six builds as a release, tagged build-<run number> and marked latest
|
||||
# The six builds (and the Windows builds' PDBs) as a release, tagged
|
||||
# build-<run number> and marked latest
|
||||
# (every file keeps its name from release to release, for the README's
|
||||
# latest/download links); then all but the newest five build-* releases
|
||||
# go, tags and all. Only for main, and only when every platform built.
|
||||
@@ -252,13 +266,18 @@ jobs:
|
||||
- name: Package
|
||||
run: |
|
||||
mkdir release
|
||||
for build in artifacts/*/; do
|
||||
name="$(basename "$build")"
|
||||
(cd "$build" && zip -qr "$GITHUB_WORKSPACE/release/$name.zip" .)
|
||||
# (Ringfall: Mjolnir VR publishes its Android builds only, under
|
||||
# its own name: Updater.java looks for them; the desktop ports are
|
||||
# built to keep them building)
|
||||
for config in debug release; do
|
||||
(cd "artifacts/halo-android-$config" &&
|
||||
zip -qr "$GITHUB_WORKSPACE/release/ringfall-mjolnir-vr-android-$config.zip" .)
|
||||
done
|
||||
# the Steam Frame's installer (port/android/STEAM_FRAME.md): its
|
||||
# bundle with the release APK, and the desktop file that fetches it
|
||||
python3 tools/steam_frame_bundle.py artifacts/halo-android-release/app-release.apk release "$GH_REPO"
|
||||
# the APK itself, for sideloading on the Quest (SideQuest, adb)
|
||||
cp artifacts/halo-android-release/app-release.apk release/RingfallMjolnirVR.apk
|
||||
ls -l release
|
||||
|
||||
- name: Publish
|
||||
@@ -270,12 +289,50 @@ jobs:
|
||||
run: |
|
||||
tag="build-$GITHUB_RUN_NUMBER"
|
||||
subject="$(printf '%s' "${MESSAGE:-manual run}" | head -n 1)"
|
||||
printf '%s\n\nCommit: %s\nBuild: %s\n\nThe release builds are for playing; the debug builds stop at the first failed assertion and log it.\n' \
|
||||
"$subject" "$COMMIT_URL" "$RUN_URL" > notes.md
|
||||
readme="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY"
|
||||
{
|
||||
printf '%s\n\n' "$subject"
|
||||
printf '**Ringfall: Mjolnir VR** is an unofficial VR mod for Halo: Combat Evolved. It needs a disc image of your own, purchased Xbox copy of the game; no game files are included.\n\n'
|
||||
printf '| Headset | Download | How to install |\n| --- | --- | --- |\n'
|
||||
printf '| Meta Quest 3 / 3S | `RingfallMjolnirVR.apk` | [Install on Quest](%s#install-on-meta-quest-3--3s) |\n' "$readme"
|
||||
printf '| Steam Frame | `Install-Ringfall-Mjolnir-VR.desktop` (open it in Desktop Mode) | [Install on Steam Frame](%s#install-on-steam-frame) |\n\n' "$readme"
|
||||
printf 'The other files are for the built-in updater and the Steam Frame installer.\n\n'
|
||||
printf 'Commit: %s\nBuild: %s\n' "$COMMIT_URL" "$RUN_URL"
|
||||
} > notes.md
|
||||
gh release create "$tag" release/* --target "$GITHUB_SHA" --latest \
|
||||
--title "Build $GITHUB_RUN_NUMBER (${GITHUB_SHA:0:7})" --notes-file notes.md
|
||||
--title "Ringfall: Mjolnir VR, build $GITHUB_RUN_NUMBER (${GITHUB_SHA:0:7})" --notes-file notes.md
|
||||
echo "tag=$tag" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# The Windows builds' executables and PDBs to Sentry, which turns the
|
||||
# crash reports of these builds (port/windows/src/win32_crash.c) into
|
||||
# backtraces with function names and lines. Needs the SENTRY_AUTH_TOKEN
|
||||
# secret: an organization auth token of the Sentry organization, which
|
||||
# names the organization and its region itself. The SENTRY_PROJECT
|
||||
# variable can name the project (its slug), else the id in the DSN of
|
||||
# win32_crash.c; SENTRY_ORG and SENTRY_URL (https://de.sentry.io) are
|
||||
# for a personal token instead. A failed upload does not stop the
|
||||
# release.
|
||||
- name: Upload Windows symbols to Sentry
|
||||
continue-on-error: true
|
||||
env:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT || '4512207917744208' }}
|
||||
ORG: ${{ vars.SENTRY_ORG }}
|
||||
URL: ${{ vars.SENTRY_URL }}
|
||||
SENTRY_CLI_VERSION: 2.46.0
|
||||
run: |
|
||||
if [ -z "$SENTRY_AUTH_TOKEN" ]; then
|
||||
echo "::warning::no SENTRY_AUTH_TOKEN secret: crash reports of this build will have no function names"
|
||||
exit 0
|
||||
fi
|
||||
if [ -n "$ORG" ]; then export SENTRY_ORG="$ORG"; fi
|
||||
if [ -n "$URL" ]; then export SENTRY_URL="$URL"; fi
|
||||
curl -fsSL -o sentry-cli "https://github.com/getsentry/sentry-cli/releases/download/$SENTRY_CLI_VERSION/sentry-cli-Linux-x86_64"
|
||||
chmod +x sentry-cli
|
||||
./sentry-cli debug-files upload --wait \
|
||||
artifacts/halo-windows-release artifacts/halo-windows-release-symbols \
|
||||
artifacts/halo-windows-debug artifacts/halo-windows-debug-symbols
|
||||
|
||||
- name: Keep the last five
|
||||
run: |
|
||||
gh release list --limit 100 --json tagName,createdAt \
|
||||
@@ -328,7 +385,7 @@ jobs:
|
||||
title="Build failed"; color=15548997
|
||||
fi
|
||||
latest="$SERVER/$REPOSITORY/releases/latest"
|
||||
downloads="[Linux]($latest/download/halo-linux-release.zip) · [Windows]($latest/download/halo-windows-release.zip) · [Android]($latest/download/halo-android-release.zip) ([all builds]($latest))"
|
||||
downloads="[Quest]($latest/download/ringfall-mjolnir-vr-android-release.zip) · [Steam Frame]($latest/download/Install-Ringfall-Mjolnir-VR.desktop) ([all builds]($latest))"
|
||||
if [ -n "$RELEASE_TAG" ]; then
|
||||
release="[$RELEASE_TAG]($SERVER/$REPOSITORY/releases/tag/$RELEASE_TAG)"
|
||||
else
|
||||
|
||||
@@ -1,140 +1,198 @@
|
||||
# Halo: Combat Evolved for Linux, Windows and Android
|
||||
<p align="center">
|
||||
<img src="port/android/steam_frame_grid/library.png" width="640" alt="Ringfall: Mjolnir VR">
|
||||
</p>
|
||||
|
||||
[](https://discord.gg/9gqcHyr5km)
|
||||
# Ringfall: Mjolnir VR
|
||||
|
||||
This project is a port of the Halo: Combat Evolved decompilation to Linux,
|
||||
Windows and Android. The decompilation is of the Xbox build 2342
|
||||
(`cachebeta.exe`, SHA-256
|
||||
`4cc87b45f721270392a96f1674ed2b5cd4a7bb4355faeab4531d1cf1884d9520`).
|
||||
**A VR mod for Halo: Combat Evolved** (the original Xbox game) on the
|
||||
**Meta Quest 3 / 3S** and the **Steam Frame**. Play the campaign and
|
||||
multiplayer in full 6DoF virtual reality, with your hands in the game.
|
||||
|
||||
<img width="1289" height="995" alt="The game on Linux" src="https://github.com/user-attachments/assets/0d3ad50f-f8b8-46cf-aef8-e3661da2a7d7" />
|
||||
Ringfall: Mjolnir VR runs on your headset by itself, without a PC. It
|
||||
brings no game files: it uses a disc image of **your own, purchased copy**
|
||||
of the game.
|
||||
|
||||
The port starts from the decompilation of [bnunu/halo-1](https://github.com/bnunu/halo-1).
|
||||
That project is a fork of [punpckhdq/halo](https://github.com/punpckhdq/halo).
|
||||
> **Unofficial fan project.** Ringfall: Mjolnir VR is a free,
|
||||
> non-commercial fan project. It is not affiliated with, endorsed,
|
||||
> sponsored or approved by Microsoft, Xbox Game Studios, Halo Studios or
|
||||
> Bungie. See [Legal](#legal).
|
||||
|
||||
## Download
|
||||
## Contents
|
||||
|
||||
GitHub Actions builds the game for each commit. These links download the
|
||||
builds of the latest release:
|
||||
- [What you get](#what-you-get)
|
||||
- [What you can play](#what-you-can-play)
|
||||
- [What you need](#what-you-need)
|
||||
- [Install on Meta Quest 3 / 3S](#install-on-meta-quest-3--3s)
|
||||
- [Install on Steam Frame](#install-on-steam-frame)
|
||||
- [Playing](#playing)
|
||||
- [Problems and questions](#problems-and-questions)
|
||||
- [Credits](#credits)
|
||||
- [Legal](#legal)
|
||||
|
||||
| Platform | Release | Debug |
|
||||
| --- | --- | --- |
|
||||
| Linux | [halo-linux-release.zip](https://github.com/OpenCommunityEdition/OpenCE/releases/latest/download/halo-linux-release.zip) | [halo-linux-debug.zip](https://github.com/OpenCommunityEdition/OpenCE/releases/latest/download/halo-linux-debug.zip) |
|
||||
| Windows | [halo-windows-release.zip](https://github.com/OpenCommunityEdition/OpenCE/releases/latest/download/halo-windows-release.zip) | [halo-windows-debug.zip](https://github.com/OpenCommunityEdition/OpenCE/releases/latest/download/halo-windows-debug.zip) |
|
||||
| Android | [halo-android-release.zip](https://github.com/OpenCommunityEdition/OpenCE/releases/latest/download/halo-android-release.zip) | [halo-android-debug.zip](https://github.com/OpenCommunityEdition/OpenCE/releases/latest/download/halo-android-debug.zip) |
|
||||
## What you get
|
||||
|
||||
Use the release build to play. The debug build stops at the first failed
|
||||
assertion and writes it to the log. Use the debug build to find and report
|
||||
problems.
|
||||
- **Full VR**: stereo 6DoF at your headset's refresh rate (72 to 120 Hz);
|
||||
the HUD, menus, scope and crosshair float as layers of their own.
|
||||
- **Your hands in the game**: the weapon sits in your hand and fires
|
||||
where you point it; arms and fingers follow your controllers;
|
||||
two-handed weapons, melee by swinging, grenades thrown with your hand,
|
||||
crouching and leaning with your body.
|
||||
- **Vehicles**: steer the Warthog by turning its wheel with both hands;
|
||||
turrets with handles.
|
||||
- **Comfort**: snap or smooth turning in fine steps, comfort vignettes,
|
||||
left-handed mode, buttons remapped freely, graphics presets, refresh
|
||||
rate and resolution, all in the in-game VR options (**Y**).
|
||||
- **The PC version's menus**, clicked with a laser pointer, and an
|
||||
on-screen keyboard for names, codes and passwords.
|
||||
|
||||
The game updates itself. At start-up it looks for a newer release, and asks
|
||||
if you want to install it. Refer to "Updates" in
|
||||
[port/linux/README.md](port/linux/README.md#updates).
|
||||
## What you can play
|
||||
|
||||
Each build of the `main` branch that passes on all three platforms is a new
|
||||
release. The [Releases](https://github.com/OpenCommunityEdition/OpenCE/releases)
|
||||
page keeps the last five releases. If the latest build has a problem, get
|
||||
an older build from that page.
|
||||
- **The whole campaign**: all ten missions, on every difficulty, saved as
|
||||
you go.
|
||||
- **Co-op campaign** with friends, online or on your local network, with
|
||||
settings for friendly fire and extra enemies per player.
|
||||
- **LAN multiplayer** (system link) with everyone on your network.
|
||||
- **Online multiplayer**, no server of your own needed: host a game and
|
||||
share its join code, find public games in the server browser, or protect
|
||||
a lobby with a password. Up to 128 players.
|
||||
- **Bots** to fill a game, on foot, in vehicles and at turrets; they make
|
||||
room when a person joins.
|
||||
- **All 13 multiplayer maps** and the Xbox game's game types: Slayer,
|
||||
Capture the Flag, King of the Hill, Oddball and Race.
|
||||
- **Cross-play**: VR players on the Quest and the Steam Frame, together with
|
||||
players of the PC and phone builds of
|
||||
[OpenCE](https://github.com/OpenCommunityEdition/OpenCE).
|
||||
|
||||
## Game data
|
||||
## What you need
|
||||
|
||||
The port does not include the game data. Download an Xbox disc image
|
||||
(`.xiso` or `.iso`) of Halo: Combat Evolved. All versions of the game
|
||||
operate. The maps of the European (PAL) version were made for a slower
|
||||
console. The port changes them to play as the North American (NTSC) maps do,
|
||||
so players of the two versions can play together.
|
||||
- A **Meta Quest 3 / 3S** or a **Steam Frame**.
|
||||
- A **disc image** (`.iso` or `.xiso`) of **Halo: Combat Evolved for the
|
||||
original Xbox**, made from **a disc you own**. Any region and version
|
||||
works.
|
||||
|
||||
1. Start the game.
|
||||
2. At the first start, the game asks for the disc image. Select it.
|
||||
3. The game extracts the `maps/` folder. Then the game starts.
|
||||
This project does not include, link to or help find game files. Please
|
||||
do not ask for or share disc images in issues or discussions; such posts
|
||||
are deleted.
|
||||
|
||||
On Linux and Windows, the game puts `maps/` next to the executable. On
|
||||
Android, copy the disc image to the phone first. The app puts `maps/` in its
|
||||
data folder. Refer to [port/android/README.md](port/android/README.md).
|
||||
## Install on Meta Quest 3 / 3S
|
||||
|
||||
## Platforms
|
||||
You install the game once with a computer (Windows, macOS or Linux); after
|
||||
that it updates itself.
|
||||
|
||||
Each platform has its own instructions:
|
||||
1. **Turn on Developer Mode** for your Quest, once: in the Meta Horizon
|
||||
app on your phone, open your headset's settings, then *Developer
|
||||
Mode* (Meta asks you to create a free developer account first).
|
||||
2. **Install [SideQuest](https://sidequestvr.com/setup-howto)** (the
|
||||
"Advanced Installer") on your computer and connect the Quest with a
|
||||
USB cable. In the headset, allow USB debugging for this computer.
|
||||
3. **Download `RingfallMjolnirVR.apk`** from the
|
||||
[latest release](../../releases/latest) and drag it onto the SideQuest
|
||||
window. It installs the game.
|
||||
4. **Copy your disc image** to the Quest's `Download` folder: in
|
||||
SideQuest, open the file manager (the folder icon), go to `Download`
|
||||
and drag the `.iso` file in.
|
||||
5. **In the headset**, open your *Library*, choose *Unknown Sources* and
|
||||
start **Ringfall: Mjolnir VR**. Select **Choose disc image** and pick
|
||||
your file in `Download`. The game copies what it needs (about 1.8 GB,
|
||||
a minute or two) and starts.
|
||||
|
||||
| Platform | Instructions |
|
||||
| --- | --- |
|
||||
| Linux (32-bit x86 executable, OpenGL 4.5, SDL3) | [port/linux/README.md](port/linux/README.md) |
|
||||
| Windows (32-bit x86 executable, OpenGL 4.5, SDL3) | [port/windows/README.md](port/windows/README.md) |
|
||||
| Android (arm64 app, OpenGL ES 3, SDL3) | [port/android/README.md](port/android/README.md) |
|
||||
You can delete the disc image from `Download` afterwards. When a new
|
||||
version comes out, the game asks you at start whether to update.
|
||||
|
||||
The Linux README also gives the controls, the settings and the multiplayer
|
||||
functions. These are almost the same on all platforms.
|
||||
## Install on Steam Frame
|
||||
|
||||
## Multiplayer
|
||||
No computer and no keyboard needed.
|
||||
|
||||
The game can play system link games on a local network and on the internet:
|
||||
1. **Switch to Desktop Mode**: press the Steam button, then *Power* >
|
||||
*Switch to Desktop*.
|
||||
2. **Put your disc image** in the `Downloads` folder (for example from a
|
||||
USB stick or memory card with the file manager, Dolphin).
|
||||
3. **Download `Install-Ringfall-Mjolnir-VR.desktop`** from the
|
||||
[latest release](../../releases/latest) with the web browser.
|
||||
4. **Open it**: in Dolphin, open `Downloads` and double-click the file.
|
||||
If you are asked whether to run it, choose to continue.
|
||||
5. **Answer the dialogs.** The installer finds your disc image, copies
|
||||
what the game needs, adds **Ringfall: Mjolnir VR** to your Steam
|
||||
library and offers to switch back to Gaming Mode.
|
||||
6. Start **Ringfall: Mjolnir VR** from your library (under *Non-Steam*).
|
||||
|
||||
- A system link game can have up to 128 players on up to 128 machines.
|
||||
- Linux, Windows and Android machines can play in the same game.
|
||||
- An invite link lets a machine join a game on the internet. No server of
|
||||
this project is necessary.
|
||||
- The netcode is new. Each machine moves its own player at once,
|
||||
and the host makes the decisions for the game. Refer to
|
||||
[port/linux/NETCODE.md](port/linux/NETCODE.md).
|
||||
To update, open the same file in Desktop Mode again; your settings,
|
||||
saves and game data stay. More about the Steam Frame:
|
||||
[port/android/STEAM_FRAME.md](port/android/STEAM_FRAME.md).
|
||||
|
||||
## Build the game
|
||||
## Playing
|
||||
|
||||
You do not need the Xbox SDK. The port supplies the SDK declarations that
|
||||
the game uses. Refer to [port/include/xdk](port/include/xdk/README.md).
|
||||
- **Y** opens the VR options in the main and pause menus: turning,
|
||||
comfort, weapon hand, buttons, graphics, refresh rate and resolution.
|
||||
*Quit the game* is on its last page.
|
||||
- In the menus, point with a controller and pull the trigger to click.
|
||||
- The controller layout is on the VR options' *Controller buttons* page.
|
||||
|
||||
To build the game:
|
||||
## Problems and questions
|
||||
|
||||
1. Install Python and [ninja](https://ninja-build.org/).
|
||||
2. Install the tools for your platform. Refer to the README for the
|
||||
platform.
|
||||
3. In the root folder of the repository, enter `python configure.py`.
|
||||
4. Enter `ninja` with the target for the platform:
|
||||
Please open an [issue](../../issues) and include:
|
||||
|
||||
| Target | Result |
|
||||
| --- | --- |
|
||||
| `ninja linux` | `build/linux/halo` |
|
||||
| `ninja windows` (on Windows) | `build/windows/halo.exe` and `SDL3.dll` |
|
||||
| `ninja android_apk` | `port/android/app/build/outputs/apk/debug/app-debug.apk` |
|
||||
- your headset and the build number of the release you installed;
|
||||
- what you did and what happened;
|
||||
- the game's log, `debug.txt`:
|
||||
- Quest: `Android/data/io.github.cesarus85.ringfall/files/debug.txt`
|
||||
(SideQuest's file manager);
|
||||
- Steam Frame: `Documents/RingfallMjolnirVR/debug.txt` (and
|
||||
`logcat.txt` and `install.log` beside it).
|
||||
|
||||
If you enter `ninja` without a target, ninja builds the game for the
|
||||
computer that you use.
|
||||
What is done and what comes next: [port/android/VR_ROADMAP.md](port/android/VR_ROADMAP.md).
|
||||
|
||||
`tools/ci_build.py` makes the same builds as GitHub Actions. For example,
|
||||
enter `python tools/ci_build.py linux release`.
|
||||
**Network use.** The game connects to the internet only for what you
|
||||
use: the update check (GitHub), online games (to other players
|
||||
directly), and join codes and lobby lists through public MQTT brokers
|
||||
(listed in [brokers.txt](port/assets/network/brokers.txt)). There is no
|
||||
telemetry.
|
||||
|
||||
### Build options
|
||||
## Credits
|
||||
|
||||
Give these options to `configure.py`:
|
||||
Ringfall: Mjolnir VR stands on the work of others:
|
||||
|
||||
| Option | Result |
|
||||
| --- | --- |
|
||||
| (none) | A debug build. A failed assertion stops the game. |
|
||||
| `--release` | A release build. The game does not examine assertions, as in the retail game. |
|
||||
| `--portable` | The Linux and Windows builds operate on all x86-64 processors. Use this option for builds that you give to other persons. |
|
||||
| `--lto=thin`, `--lto=off` | Less link-time optimization. The link is faster. |
|
||||
| `--pgo=off` | No profile-guided optimization. |
|
||||
| `--pgo=train` | Records a new optimization profile. Refer to "Optimization profiles". |
|
||||
- **[OpenCE](https://github.com/OpenCommunityEdition/OpenCE)** (Open
|
||||
Community Edition): the port of the decompiled game to Linux, Windows
|
||||
and Android, with its renderer, netcode, PC menus and multiplayer, that
|
||||
this project builds on and follows.
|
||||
- **The decompilation**: [bnunu/halo-1](https://github.com/bnunu/halo-1),
|
||||
a fork of [punpckhdq/halo](https://github.com/punpckhdq/halo).
|
||||
- **[HaloCE-Quest-VR](https://github.com/moistman42069/HaloCE-Quest-VR)**
|
||||
by moistman42069 (CC0): the arm IK, the forearm's twist and the
|
||||
fingers' poses are taken from it (`port/linux/game/vr_arms.c`); the
|
||||
left-handed mode and the graphics presets follow its ideas.
|
||||
- **[HaloCE-VR](https://github.com/astromaddie/HaloCE-VR)** by astromaddie
|
||||
(CC0): the Steam Frame work, whose findings (such as quieting Mesa's
|
||||
trace markers) are used here.
|
||||
- **[extract-xiso](https://github.com/XboxDev/extract-xiso)**: the disc
|
||||
image readers follow its format handling.
|
||||
- The libraries and fonts in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
|
||||
|
||||
Without `--portable`, the Linux and Windows builds use all the instructions
|
||||
of the processor that builds them (`-march=native`). Such a build does not
|
||||
always start on a different computer.
|
||||
Thank you all.
|
||||
|
||||
### Optimization profiles
|
||||
## Building it
|
||||
|
||||
The builds use profiles of the game to optimize the code:
|
||||
`python3 configure.py --release`, then `ninja android_apk` (Android SDK
|
||||
and NDK needed). See [port/android/README.md](port/android/README.md) for
|
||||
the details and [docs/OPENCE_README.md](docs/OPENCE_README.md) for the
|
||||
port it is built on.
|
||||
|
||||
- `pgo/halo_linux.profdata` for Linux and Android.
|
||||
- `pgo/halo_windows.profdata` for Windows.
|
||||
## Legal
|
||||
|
||||
The profiles need clang 22 or later. With an older clang, the builds do not
|
||||
use the profiles.
|
||||
Ringfall: Mjolnir VR is an unofficial, free, non-commercial fan project.
|
||||
It is not affiliated with, endorsed, sponsored or approved by Microsoft
|
||||
Corporation, Xbox Game Studios, Halo Studios or Bungie. Halo, Halo: Combat
|
||||
Evolved and Xbox are trademarks of Microsoft Corporation; they are named
|
||||
here only to say which game this mod works with. Meta Quest is a trademark
|
||||
of Meta Platforms, Inc.; Steam and Steam Frame are trademarks of Valve
|
||||
Corporation.
|
||||
|
||||
To record a new profile:
|
||||
No game data, art, music or other content of the game is included or
|
||||
distributed. You need your own, legally purchased copy of the game. If
|
||||
you enjoy this mod, please support the creators of the original by buying
|
||||
their games.
|
||||
|
||||
1. Delete the profile.
|
||||
2. Enter `python configure.py --pgo=train`.
|
||||
3. Enter `ninja linux` or `ninja windows`.
|
||||
|
||||
The build then plays the main menu and the first minute of each campaign
|
||||
level. This procedure continues for approximately 15 minutes. The game
|
||||
data must be in `assets/`.
|
||||
This project's own code is released under [CC0 1.0](LICENSE.md), as the
|
||||
decompilation it builds on. Third-party code and fonts keep their own
|
||||
licenses: see [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
|
||||
@@ -0,0 +1,46 @@
|
||||
# Third-party notices
|
||||
|
||||
Ringfall: Mjolnir VR's own code is released under [CC0 1.0](LICENSE.md).
|
||||
It includes or builds on the following, under their own licenses; each
|
||||
license's full text is in the file named.
|
||||
|
||||
## Code
|
||||
|
||||
| Component | Used for | License | Text |
|
||||
| --- | --- | --- | --- |
|
||||
| [OpenCE](https://github.com/OpenCommunityEdition/OpenCE) and the decompilation ([bnunu/halo-1](https://github.com/bnunu/halo-1), [punpckhdq/halo](https://github.com/punpckhdq/halo)) | the game's code, the port | CC0 1.0 | [LICENSE.md](LICENSE.md) |
|
||||
| [HaloCE-Quest-VR](https://github.com/moistman42069/HaloCE-Quest-VR) | the arm IK, forearm twist and finger poses (`port/linux/game/vr_arms.c`) | CC0 1.0 | (its repository) |
|
||||
| [HaloCE-VR](https://github.com/astromaddie/HaloCE-VR) | Steam Frame findings | CC0 1.0 | (its repository) |
|
||||
| [SDL 3](https://libsdl.org) | windows, input, sound | zlib | (in SDL's source, `LICENSE.txt`) |
|
||||
| [Khronos OpenXR loader](https://github.com/KhronosGroup/OpenXR-SDK) | VR runtime access | Apache 2.0 | (in the loader's package) |
|
||||
| [musl](https://musl.libc.org) | the C library of the game's code on Android, its maths | MIT | [port/third_party/musl-math](port/third_party/musl-math/README.md) |
|
||||
| [Expat](https://libexpat.github.io) | the PC menus' definitions | MIT | [port/third_party/expat/COPYING](port/third_party/expat/COPYING) |
|
||||
| [Mbed TLS](https://www.trustedfirmware.org/projects/mbed-tls/) | HTTPS for the desktop builds' updater (not in the Android app) | Apache 2.0 | [port/third_party/mbedtls/LICENSE](port/third_party/mbedtls/LICENSE) |
|
||||
| [miniupnpc](https://miniupnp.tuxfamily.org) | router port forwarding for online games | BSD 3-Clause | [port/third_party/miniupnpc/LICENSE](port/third_party/miniupnpc/LICENSE) |
|
||||
| [KCP](https://github.com/skywind3000/kcp) | reliable transport for online games | MIT | [port/third_party/kcp/LICENSE](port/third_party/kcp/LICENSE) |
|
||||
| [Monocypher](https://monocypher.org) | signing public games' listings (Ed25519); online play's other cryptography is the port's own | CC0 1.0 or BSD 2-Clause | [port/third_party/monocypher/LICENCE.md](port/third_party/monocypher/LICENCE.md) |
|
||||
| [tomlc17](https://github.com/cktan/tomlc17) | reading `config.toml` | MIT | [port/third_party/tomlc17/LICENSE](port/third_party/tomlc17/LICENSE) |
|
||||
| [stb](https://github.com/nothings/stb) | drawing the game's text sharply (stb_truetype) | MIT or public domain | [port/third_party/stb/LICENSE](port/third_party/stb/LICENSE) |
|
||||
| [SMAA](https://www.iryoku.com/smaa/) | anti-aliasing (flat screens; off in VR) | MIT | [port/third_party/smaa/LICENSE](port/third_party/smaa/LICENSE) |
|
||||
| [extract-xiso](https://github.com/XboxDev/extract-xiso) | reading disc images (followed by `XisoExtractor.java`, `steam_frame_xiso.py`, `xiso.c`) | BSD-style (in@fishtank.com) | [port/third_party/extract-xiso/LICENSE.TXT](port/third_party/extract-xiso/LICENSE.TXT) |
|
||||
|
||||
The disc image readers include this acknowledgement, as extract-xiso's
|
||||
license asks: *This product includes software developed by in
|
||||
<in@fishtank.com>.*
|
||||
|
||||
## Fonts
|
||||
|
||||
| Font | Used for | License | Text |
|
||||
| --- | --- | --- | --- |
|
||||
| Overpass (Red Hat, Delve Withrington) | the game's text, the library pictures | SIL Open Font License 1.1 | [port/assets/fonts/Overpass-OFL.txt](port/assets/fonts/Overpass-OFL.txt) |
|
||||
| OpenCE (after Newtown) | the menus' titles | SIL Open Font License 1.1 | [port/assets/fonts/OpenCE-OFL.txt](port/assets/fonts/OpenCE-OFL.txt) |
|
||||
| Newtown (Roger White) | OpenCE's source | public domain | [port/assets/fonts/Newtown-LICENSE.txt](port/assets/fonts/Newtown-LICENSE.txt) |
|
||||
|
||||
See [port/assets/fonts/README.md](port/assets/fonts/README.md) for where
|
||||
each font is used.
|
||||
|
||||
## Not included
|
||||
|
||||
No file of Halo: Combat Evolved (maps, sounds, art, music, fonts or
|
||||
executables) is part of this repository or its releases. The game reads
|
||||
them from the player's own disc image.
|
||||
@@ -0,0 +1,145 @@
|
||||
> This is the README of [OpenCE](https://github.com/OpenCommunityEdition/OpenCE),
|
||||
> the port Ringfall: Mjolnir VR is built on, kept for its build and
|
||||
> development notes. Its download links are OpenCE's. For the VR mod, see
|
||||
> the [main README](../README.md).
|
||||
|
||||
# Halo: Combat Evolved for Linux, Windows and Android
|
||||
|
||||
[](https://discord.gg/9gqcHyr5km)
|
||||
|
||||
This project is a port of the Halo: Combat Evolved decompilation to Linux,
|
||||
Windows and Android. The decompilation is of the Xbox build 2342
|
||||
(`cachebeta.exe`, SHA-256
|
||||
`4cc87b45f721270392a96f1674ed2b5cd4a7bb4355faeab4531d1cf1884d9520`).
|
||||
|
||||
<img width="1289" height="995" alt="The game on Linux" src="https://github.com/user-attachments/assets/0d3ad50f-f8b8-46cf-aef8-e3661da2a7d7" />
|
||||
|
||||
The port starts from the decompilation of [bnunu/halo-1](https://github.com/bnunu/halo-1).
|
||||
That project is a fork of [punpckhdq/halo](https://github.com/punpckhdq/halo).
|
||||
|
||||
## Download
|
||||
|
||||
GitHub Actions builds the game for each commit. These links download the
|
||||
builds of the latest release:
|
||||
|
||||
| Platform | Release | Debug |
|
||||
| --- | --- | --- |
|
||||
| Linux | [halo-linux-release.zip](https://github.com/OpenCommunityEdition/OpenCE/releases/latest/download/halo-linux-release.zip) | [halo-linux-debug.zip](https://github.com/OpenCommunityEdition/OpenCE/releases/latest/download/halo-linux-debug.zip) |
|
||||
| Windows | [halo-windows-release.zip](https://github.com/OpenCommunityEdition/OpenCE/releases/latest/download/halo-windows-release.zip) | [halo-windows-debug.zip](https://github.com/OpenCommunityEdition/OpenCE/releases/latest/download/halo-windows-debug.zip) |
|
||||
| Android | [halo-android-release.zip](https://github.com/OpenCommunityEdition/OpenCE/releases/latest/download/halo-android-release.zip) | [halo-android-debug.zip](https://github.com/OpenCommunityEdition/OpenCE/releases/latest/download/halo-android-debug.zip) |
|
||||
|
||||
Use the release build to play. The debug build stops at the first failed
|
||||
assertion and writes it to the log. Use the debug build to find and report
|
||||
problems.
|
||||
|
||||
The game updates itself. At start-up it looks for a newer release, and asks
|
||||
if you want to install it. Refer to "Updates" in
|
||||
[port/linux/README.md](../port/linux/README.md#updates).
|
||||
|
||||
Each build of the `main` branch that passes on all three platforms is a new
|
||||
release. The [Releases](https://github.com/OpenCommunityEdition/OpenCE/releases)
|
||||
page keeps the last five releases. If the latest build has a problem, get
|
||||
an older build from that page.
|
||||
|
||||
## Game data
|
||||
|
||||
The port does not include the game data. It needs an Xbox disc image
|
||||
(`.xiso` or `.iso`) made from your own copy of Halo: Combat Evolved. All versions of the game
|
||||
operate. The maps of the European (PAL) version were made for a slower
|
||||
console. The port changes them to play as the North American (NTSC) maps do,
|
||||
so players of the two versions can play together.
|
||||
|
||||
1. Start the game.
|
||||
2. At the first start, the game asks for the disc image. Select it.
|
||||
3. The game extracts the `maps/` folder. Then the game starts.
|
||||
|
||||
On Linux and Windows, the game puts `maps/` next to the executable. On
|
||||
Android, copy the disc image to the phone first. The app puts `maps/` in its
|
||||
data folder. Refer to [port/android/README.md](../port/android/README.md).
|
||||
|
||||
## Platforms
|
||||
|
||||
Each platform has its own instructions:
|
||||
|
||||
| Platform | Instructions |
|
||||
| --- | --- |
|
||||
| Linux (32-bit x86 executable, OpenGL 4.5, SDL3) | [port/linux/README.md](../port/linux/README.md) |
|
||||
| Windows (32-bit x86 executable, OpenGL 4.5, SDL3) | [port/windows/README.md](../port/windows/README.md) |
|
||||
| Android (arm64 app, OpenGL ES 3, SDL3) | [port/android/README.md](../port/android/README.md) |
|
||||
|
||||
The Linux README also gives the controls, the settings and the multiplayer
|
||||
functions. These are almost the same on all platforms.
|
||||
|
||||
## Multiplayer
|
||||
|
||||
The game can play system link games on a local network and on the internet:
|
||||
|
||||
- A system link game can have up to 128 players on up to 128 machines.
|
||||
- Linux, Windows and Android machines can play in the same game.
|
||||
- An invite link lets a machine join a game on the internet. No server of
|
||||
this project is necessary.
|
||||
- The netcode is new. Each machine moves its own player at once,
|
||||
and the host makes the decisions for the game. Refer to
|
||||
[port/linux/NETCODE.md](../port/linux/NETCODE.md).
|
||||
|
||||
## Build the game
|
||||
|
||||
You do not need the Xbox SDK. The port supplies the SDK declarations that
|
||||
the game uses. Refer to [port/include/xdk](../port/include/xdk/README.md).
|
||||
|
||||
To build the game:
|
||||
|
||||
1. Install Python and [ninja](https://ninja-build.org/).
|
||||
2. Install the tools for your platform. Refer to the README for the
|
||||
platform.
|
||||
3. In the root folder of the repository, enter `python configure.py`.
|
||||
4. Enter `ninja` with the target for the platform:
|
||||
|
||||
| Target | Result |
|
||||
| --- | --- |
|
||||
| `ninja linux` | `build/linux/halo` |
|
||||
| `ninja windows` (on Windows) | `build/windows/halo.exe` and `SDL3.dll` |
|
||||
| `ninja android_apk` | `port/android/app/build/outputs/apk/debug/app-debug.apk` |
|
||||
|
||||
If you enter `ninja` without a target, ninja builds the game for the
|
||||
computer that you use.
|
||||
|
||||
`tools/ci_build.py` makes the same builds as GitHub Actions. For example,
|
||||
enter `python tools/ci_build.py linux release`.
|
||||
|
||||
### Build options
|
||||
|
||||
Give these options to `configure.py`:
|
||||
|
||||
| Option | Result |
|
||||
| --- | --- |
|
||||
| (none) | A debug build. A failed assertion stops the game. |
|
||||
| `--release` | A release build. The game does not examine assertions, as in the retail game. |
|
||||
| `--portable` | The Linux and Windows builds operate on all x86-64 processors. Use this option for builds that you give to other persons. |
|
||||
| `--lto=thin`, `--lto=off` | Less link-time optimization. The link is faster. |
|
||||
| `--pgo=off` | No profile-guided optimization. |
|
||||
| `--pgo=train` | Records a new optimization profile. Refer to "Optimization profiles". |
|
||||
|
||||
Without `--portable`, the Linux and Windows builds use all the instructions
|
||||
of the processor that builds them (`-march=native`). Such a build does not
|
||||
always start on a different computer.
|
||||
|
||||
### Optimization profiles
|
||||
|
||||
The builds use profiles of the game to optimize the code:
|
||||
|
||||
- `pgo/halo_linux.profdata` for Linux and Android.
|
||||
- `pgo/halo_windows.profdata` for Windows.
|
||||
|
||||
The profiles need clang 22 or later. With an older clang, the builds do not
|
||||
use the profiles.
|
||||
|
||||
To record a new profile:
|
||||
|
||||
1. Delete the profile.
|
||||
2. Enter `python configure.py --pgo=train`.
|
||||
3. Enter `ninja linux` or `ninja windows`.
|
||||
|
||||
The build then plays the main menu and the first minute of each campaign
|
||||
level. This procedure continues for approximately 15 minutes. The game
|
||||
data must be in `assets/`.
|
||||
@@ -1,5 +1,7 @@
|
||||
# Android
|
||||
|
||||
The app of Ringfall: Mjolnir VR (application id `io.github.cesarus85.ringfall`;
|
||||
its Java classes keep the decompilation's package, `com.halo.decomp`).
|
||||
`ninja android` builds the game for 64-bit ARM Android (arm64-v8a).
|
||||
`ninja android_apk` makes an app from it:
|
||||
`port/android/app/build/outputs/apk/debug/app-debug.apk`.
|
||||
@@ -41,8 +43,8 @@ ninja) and these items:
|
||||
## Game data
|
||||
|
||||
The game needs the `maps/` folder from an Xbox disc image (`.xiso` or
|
||||
`.iso`) of any version of the game. The app extracts `maps/` from the disc
|
||||
image. The app keeps the data in `/sdcard/Android/data/com.halo.decomp/files`.
|
||||
`.iso`) made from your own copy of the game, any version. The app extracts `maps/` from the disc
|
||||
image. The app keeps the data in `/sdcard/Android/data/io.github.cesarus85.ringfall/files`.
|
||||
|
||||
To install the data with the app:
|
||||
|
||||
@@ -57,16 +59,16 @@ To install the data with the app:
|
||||
To install the data from a computer:
|
||||
|
||||
1. Start the app one time. The app makes its folders.
|
||||
2. Enter `adb push <folder>/. /sdcard/Android/data/com.halo.decomp/files/`.
|
||||
2. Enter `adb push <folder>/. /sdcard/Android/data/io.github.cesarus85.ringfall/files/`.
|
||||
|
||||
| Item | Location in `/sdcard/Android/data/com.halo.decomp/files` |
|
||||
| Item | Location in `/sdcard/Android/data/io.github.cesarus85.ringfall/files` |
|
||||
| --- | --- |
|
||||
| Saved games (`z:\` and `u:\`) | `save` |
|
||||
| Log | `debug.txt` |
|
||||
| Settings | `config.toml` |
|
||||
|
||||
To make a copy of the saved games, enter
|
||||
`adb pull /sdcard/Android/data/com.halo.decomp/files/save`.
|
||||
`adb pull /sdcard/Android/data/io.github.cesarus85.ringfall/files/save`.
|
||||
|
||||
## Controls
|
||||
|
||||
@@ -100,9 +102,9 @@ not accept touch input.
|
||||
The settings are in `config.toml` in the data folder of the app. To change
|
||||
them:
|
||||
|
||||
1. Enter `adb pull /sdcard/Android/data/com.halo.decomp/files/config.toml`.
|
||||
1. Enter `adb pull /sdcard/Android/data/io.github.cesarus85.ringfall/files/config.toml`.
|
||||
2. Change the file.
|
||||
3. Enter `adb push config.toml /sdcard/Android/data/com.halo.decomp/files/`.
|
||||
3. Enter `adb push config.toml /sdcard/Android/data/io.github.cesarus85.ringfall/files/`.
|
||||
|
||||
At the first start, the game writes the file with the default values. To
|
||||
get the default values again, delete the file.
|
||||
@@ -148,7 +150,7 @@ select "Yes":
|
||||
|
||||
1. The app downloads the new version.
|
||||
2. The package installer of Android opens. At the first update, Android asks
|
||||
you to let Halo install apps. Allow it.
|
||||
you to let the game install apps. Allow it.
|
||||
3. Select "Update". Android replaces the app.
|
||||
4. Select "Open" to start the new version.
|
||||
|
||||
@@ -158,9 +160,9 @@ signature. GitHub Actions signs each build with the key in the
|
||||
repository. A computer that builds for its own devices signs with the same
|
||||
key when `port/android/signing.properties` (not in the repository) names it:
|
||||
|
||||
storeFile=/path/to/halo-vr-release.jks
|
||||
storeFile=/path/to/ringfall-release.jks
|
||||
password=<the keystore's password>
|
||||
keyAlias=halo
|
||||
keyAlias=ringfall
|
||||
|
||||
Keep the keystore and its password safe: a lost key means every player must
|
||||
remove the app before the next version. If you installed a build that has a different signature, remove
|
||||
@@ -190,7 +192,7 @@ instead.
|
||||
|
||||
Controls in play (in the menus the buttons are the Xbox's by position):
|
||||
|
||||
| Controller | Halo |
|
||||
| Controller | Game |
|
||||
| --- | --- |
|
||||
| right trigger / left trigger | fire / throw grenade |
|
||||
| right grip / left grip | melee / switch weapon |
|
||||
@@ -238,7 +240,7 @@ through a shader that decodes to linear, and the stored bytes are the
|
||||
game's. Camera shake is off in VR.
|
||||
|
||||
A file named `xr_test` in the data folder
|
||||
(`adb shell touch /sdcard/Android/data/com.halo.decomp/files/xr_test`)
|
||||
(`adb shell touch /sdcard/Android/data/io.github.cesarus85.ringfall/files/xr_test`)
|
||||
starts a self-test instead of the game, which needs no game data: each eye
|
||||
in its own colour (red while the right trigger is held), a checkerboard
|
||||
screen in the room (its top-left square red), and the controllers' buttons
|
||||
|
||||
@@ -7,28 +7,36 @@ What differs is found at run time, not by a build of its own.
|
||||
## Installing it (players)
|
||||
|
||||
On the Frame, in Desktop Mode (Steam menu > Power > Switch to Desktop):
|
||||
1. Put a disc image of Halo: Combat Evolved for the Xbox (`.iso` or
|
||||
1. Put your own disc image of Halo: Combat Evolved for the original Xbox (`.iso` or
|
||||
`.xiso`, any version) in Downloads (or Documents, the Desktop, a memory
|
||||
card).
|
||||
2. Download `Install-Halo-VR.desktop` from the latest release and open it
|
||||
2. Download `Install-Ringfall-Mjolnir-VR.desktop` from the latest release and open it
|
||||
(double-click; confirm running it).
|
||||
3. Answer the dialogs: the installer finds the disc image (or asks for it),
|
||||
copies the game data out of it, adds "Halo VR" to the Steam library and
|
||||
switches back to Gaming Mode. Start Halo VR from the library there.
|
||||
copies the game data out of it, adds "Ringfall: Mjolnir VR" to the Steam library
|
||||
and switches back to Gaming Mode. Start it from the library there.
|
||||
|
||||
Opening the same file again updates the game (settings, saves and game data
|
||||
stay).
|
||||
|
||||
The desktop file (`steam_frame_installer.desktop`) downloads the latest
|
||||
release's `halo-vr-steam-frame.zip` (`tools/steam_frame_bundle.py`, made by
|
||||
release's `ringfall-mjolnir-vr-steam-frame.zip` (`tools/steam_frame_bundle.py`, made by
|
||||
the release job of `.github/workflows/build.yml`) and runs its
|
||||
`install.sh` (`steam_frame_install.sh`), with zenity dialogs:
|
||||
- `~/Documents/HaloCE`: the launcher (`halo-vr.sh`) and the release APK;
|
||||
- `~/Documents/RingfallMjolnirVR`: the launcher (`ringfall-mjolnir-vr.sh`)
|
||||
and the release APK (`RingfallMjolnirVR.apk`); the folder of the builds
|
||||
before the name, `~/Documents/HaloCE`, renamed to it, and their "Halo VR"
|
||||
shortcut replaced;
|
||||
- the maps folder out of the disc image (`steam_frame_xiso.py`, the app's
|
||||
`XisoExtractor.java` in Python), unless `maps/ui.map` is there;
|
||||
- the Steam shortcut (`shortcuts.vdf` as below, with `OpenVR` set, and the
|
||||
library pictures), unless it is there: Steam is closed for that, as it
|
||||
writes its shortcuts back when it quits.
|
||||
- the Steam shortcut, unless it is there: added by Steam itself
|
||||
(`steam://addnonsteamgame`, as SteamOS's "Add to Steam" does) from
|
||||
`Ringfall Mjolnir VR.desktop` beside the game, then the library pictures
|
||||
under the id Steam gave it. Steam is not closed: on the Frame, Desktop
|
||||
Mode runs inside Gaming Mode's session, and closing Steam ends it (and the
|
||||
installer with it). An old "Halo VR" shortcut is left for the player to
|
||||
remove (Steam would write it back);
|
||||
- what it did in `install.log` beside the game.
|
||||
|
||||
`HALO_VR_UNATTENDED=1` (and `HALO_VR_IMAGE=<image>`) runs it without
|
||||
dialogs, for testing over adb; it leaves Steam and its shortcuts alone.
|
||||
@@ -44,7 +52,7 @@ dialogs, for testing over adb; it leaves Steam and its shortcuts alone.
|
||||
connected as well.
|
||||
- Install with `adb install -r --no-streaming`. It prints "Lepton post
|
||||
installation failed due to APP_PATH being unset", but installs. Start the
|
||||
game with `am start -n com.halo.decomp/.LauncherActivity`.
|
||||
game with `am start -n io.github.cesarus85.ringfall/com.halo.decomp.LauncherActivity`.
|
||||
- Lepton empties `/sdcard/Android/data` whenever it starts again: the game's
|
||||
data must live elsewhere (stage 2). `/sdcard/Documents` and
|
||||
`/sdcard/Download` are the SteamOS user's folders and are kept.
|
||||
@@ -62,7 +70,7 @@ dialogs, for testing over adb; it leaves Steam and its shortcuts alone.
|
||||
| # | Stage | State |
|
||||
| --- | --- | --- |
|
||||
| 1 | Base: the fixes above on the current main | done |
|
||||
| 2 | The game's data where Lepton keeps it | done: `/home/steamos/Documents/HaloCE` |
|
||||
| 2 | The game's data where Lepton keeps it | done: `/home/steamos/Documents/RingfallMjolnirVR` |
|
||||
| 3 | Menus, HUD and text visible (quad layers) | done: their alpha from their colour |
|
||||
| 4 | The freeze at the cryo bay's calibration | not seen again (2026-10-04) |
|
||||
| 5 | The Frame's controllers | done: their own profile, the Xbox's layout |
|
||||
@@ -72,10 +80,10 @@ dialogs, for testing over adb; it leaves Steam and its shortcuts alone.
|
||||
|
||||
## Starting it on the Frame
|
||||
|
||||
A button in the Steam library ("Halo VR", a non-Steam game) runs
|
||||
`~/Documents/HaloCE/halo-vr.sh` (`port/android/steam_frame_launch.sh`),
|
||||
A button in the Steam library ("Ringfall: Mjolnir VR", a non-Steam game)
|
||||
runs `~/Documents/RingfallMjolnirVR/ringfall-mjolnir-vr.sh` (`port/android/steam_frame_launch.sh`),
|
||||
which starts the game as Steam starts an Android title of its own:
|
||||
`lepton waitforexitandrun -- ~/Documents/HaloCE/HaloVR.apk`, in a container
|
||||
`lepton waitforexitandrun -- ~/Documents/RingfallMjolnirVR/RingfallMjolnirVR.apk`, in a container
|
||||
of its own (`steamlaunch-4290000001`) that Lepton installs the APK into
|
||||
anew whenever the file is newer. Updating the game is replacing that APK.
|
||||
|
||||
@@ -90,7 +98,7 @@ What the script does that Steam would for an Android title:
|
||||
quit (it writes `quit_requested.txt`).
|
||||
|
||||
Steam's "Exit game" does not reach the game (its process is the container's,
|
||||
not Steam's): quit it with the VR options' "Quit Halo" (Y in the main or
|
||||
not Steam's): quit it with the VR options' "Quit the game" (Y in the main or
|
||||
pause menu, twice). A container ended within 30 s of its start makes
|
||||
Lepton drop the app's install, so the next start installs it again.
|
||||
|
||||
@@ -105,14 +113,14 @@ beside Lepton's `<frame-ip>:5555`.
|
||||
Lepton uninstalls the app and empties its folders whenever it starts again.
|
||||
The game's data lives in the SteamOS user's Documents instead: when
|
||||
`/home/steamos/Documents` can be written, the launcher makes
|
||||
`/home/steamos/Documents/HaloCE` and the launcher and the game take it as
|
||||
`/home/steamos/Documents/RingfallMjolnirVR` and the launcher and the game take it as
|
||||
their data folder (config.toml, saves and debug.txt go there too), so a
|
||||
disc image imported in the app stays as well. The installer above fills it;
|
||||
by hand, once:
|
||||
|
||||
adb -s <frame-ip>:5555 shell mkdir -p /home/steamos/Documents/HaloCE/maps
|
||||
adb -s <frame-ip>:5555 push assets/maps/. /home/steamos/Documents/HaloCE/maps/
|
||||
adb -s <frame-ip>:5555 shell chmod -R 777 /home/steamos/Documents/HaloCE
|
||||
adb -s <frame-ip>:5555 shell mkdir -p /home/steamos/Documents/RingfallMjolnirVR/maps
|
||||
adb -s <frame-ip>:5555 push assets/maps/. /home/steamos/Documents/RingfallMjolnirVR/maps/
|
||||
adb -s <frame-ip>:5555 shell chmod -R 777 /home/steamos/Documents/RingfallMjolnirVR
|
||||
|
||||
After a Lepton restart only the APK is installed again. (Use the path under
|
||||
/home, not /sdcard/Documents: Android 11's scoped storage stands between the
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# VR roadmap
|
||||
|
||||
What the VR port (Quest 3, Steam Frame) has, and what comes next, in the
|
||||
What Ringfall: Mjolnir VR (Quest 3, Steam Frame) has, and what comes next, in the
|
||||
agreed order. Updated with every step: a step is **done** once the user has
|
||||
tried it in the headset and it is merged into `main`; **built** means in a
|
||||
branch, not yet tried in the headset.
|
||||
@@ -16,7 +16,7 @@ named where code is taken over.
|
||||
|
||||
| Area | What |
|
||||
| --- | --- |
|
||||
| Headsets | Quest 3; Steam Frame (Lepton): its controllers' layout, a Steam library tile that installs, starts, updates and quits the game, data kept in `~/Documents/HaloCE` |
|
||||
| Headsets | Quest 3; Steam Frame (Lepton): its controllers' layout, a Steam library tile that installs, starts, updates and quits the game, data kept in `~/Documents/RingfallMjolnirVR` |
|
||||
| Rendering | Both eyes in one pass (multiview) on the Quest; one by one on SteamVR, where multiview's eyes did not fuse; lens flares at their depth in stereo; HUD, menus, scope, crosshair and names as layers of their own |
|
||||
| Aiming | The weapon in the hand, its shots from the muzzle, laser and crosshair options |
|
||||
| Body | Physical crouch (`vr.crouch_limit`) beside the crouch button, leaning (`vr.lean_limit`), melee by swinging, grenades thrown with the left hand, two-handed weapons |
|
||||
@@ -35,11 +35,12 @@ named where code is taken over.
|
||||
| --- | --- | --- |
|
||||
| 1 | Steam Frame CPU time: Mesa's trace markers off (after astromaddie/HaloCE-VR), no `glGet` stalls in the visibility counters' copies | **done** (in `main` 2026-10-06): markers quieted, no problems; the frame's work about unchanged (11-12.5 ms of 13.9 in a bot game) |
|
||||
| 3a | Visible arms and hands: the arms by IK to the controllers, fingers by trigger and grip | **done** (in `main` 2026-10-06; Quest 3 and Steam Frame good; 0.02 ms a frame) |
|
||||
| 2 | Controls and comfort: left-handed mode, buttons remapped, flashlight by tapping the head, finer turning and vignette choices, graphics presets and refresh rate in the VR options | first part **done** (in `main` 2026-10-06; Quest 3 and Steam Frame good): left-handed mode, graphics presets, refresh rate and resolution in the VR options. Left: buttons remapped, flashlight by tapping the head, finer turning and vignette choices |
|
||||
| 2 | Controls and comfort: left-handed mode, buttons remapped, flashlight by tapping the head, finer turning and vignette choices, graphics presets and refresh rate in the VR options | first part **done** (in `main` 2026-10-06; Quest 3 and Steam Frame good): left-handed mode, graphics presets, refresh rate and resolution in the VR options. Second part **done** (in `main` 2026-10-06; Quest 3 good): buttons remapped, finer turning and vignette choices; the flashlight by tapping the head taken out (did not work) |
|
||||
| 3b | The whole body: torso from head and hands, legs and feet following in the room; full / arms only / hands only | |
|
||||
| 3c | Weapons held physically, holsters at shoulder and hip (the buttons stay) | |
|
||||
| 3d | Other VR players shown with their heads, hands and bodies (today their bodies move as flat players' do: only their aim and throws come from their hands) | |
|
||||
| 4 | Later: disc image import in the launcher, sprint by arm swing (offline) | import **built** (branch `feature/release-install`): the app's import (Quest; on the Frame now kept in `~/Documents/HaloCE`), a Steam Frame installer from Desktop Mode (`STEAM_FRAME.md`), a release signing key; test pending |
|
||||
| 4 | Later: disc image import in the launcher, sprint by arm swing (offline) | import **done** (in `main` 2026-10-06): the app's import (tried on the Quest 3: all 24 maps; on the Frame kept in `~/Documents/RingfallMjolnirVR`), a Steam Frame installer from Desktop Mode (`STEAM_FRAME.md`; tried on the Frame: the old folder renamed, the shortcut added by Steam), a release signing key (CI and local builds the same). Sprint by arm swing still open |
|
||||
| R | Release as **Ringfall: Mjolnir VR** (no "Halo" in the product's name): app name, id `io.github.cesarus85.ringfall`, icon and library pictures (`tools/ringfall_art.py`), Frame folder and installer, release assets, README; the repository renamed | **done** (in `main` 2026-10-06; Quest 3 and Steam Frame good) |
|
||||
|
||||
## Step 2, first part (done)
|
||||
|
||||
@@ -106,7 +107,7 @@ the GPU's fill.
|
||||
|
||||
VR options (Y): two pages, PLAY (the comfort rows, "Weapon hand",
|
||||
"Mirrored controls") and MORE ("Graphics", "Refresh rate", "Resolution",
|
||||
the controller buttons page, Quit). The refresh rate steps through what
|
||||
the controller buttons page, Quit); four since the second part (below). The refresh rate steps through what
|
||||
the runtime offers and changes at once; on the Steam Frame the row shows
|
||||
SteamOS's rate. The resolution (`vr.resolution_scale`, 70-120%) takes a
|
||||
restart. The controller buttons page shows the layout as set.
|
||||
@@ -136,6 +137,81 @@ To try in the headset (Quest 3 and Steam Frame):
|
||||
in a bot game; Max on the Quest at 90 Hz; refresh rates 72/80/90/120
|
||||
on the Quest; the Frame's row.
|
||||
|
||||
## Step 2, second part (done)
|
||||
|
||||
In `main` 2026-10-06; tried on the Quest 3 (with the rename): good. The flashlight by tapping the head (after HaloCE-Quest-VR)
|
||||
did not work in the headset and was taken out again; the button stays.
|
||||
The VR
|
||||
options (Y) have four pages: PLAY ("Weapon hand", "Mirrored controls",
|
||||
laser, crosshair, motion tracker, body, fingers, the Warthog's wheel),
|
||||
COMFORT (turning, the vignettes), BUTTONS (the actions' buttons,
|
||||
"Reset buttons to defaults", the controller buttons page) and MORE (graphics, refresh rate, resolution, Quit).
|
||||
|
||||
Buttons remapped (BUTTONS page; `vr.c` `controls_remapped`):
|
||||
- Jump, the flashlight (held: switch grenades), action/reload, switch
|
||||
weapon, crouch, zoom and melee each go on one of seven buttons: A, B,
|
||||
X, Y, the move stick's click, the turning stick's, the weapon hand's
|
||||
grip. Moving an action to a button swaps it with the action there, so
|
||||
none is ever left without one.
|
||||
- The triggers, the free hand's grip, the menu button, the Steam Frame's
|
||||
bumpers, D-pad and View button keep theirs; the menus keep the buttons
|
||||
as they are.
|
||||
- Applied after the left-handed mirroring, on the gamepad's sides: the
|
||||
same on the Touch and the Frame controllers, and a left-handed player's
|
||||
buttons are the mirror image of the right-handed ones (on the Frame,
|
||||
mirrored, the left D-pad's). The defaults are the old layout, the
|
||||
host's bindings and the ABI unchanged.
|
||||
- The melee's aim and jab follow its button; the grips on a Warthog's
|
||||
wheel press nothing, whatever the grip does.
|
||||
- The controller buttons page shows each action where its button is.
|
||||
|
||||
Comfort (COMFORT page): snap turns 10 to 45 degrees by 5, 60, 75, 90;
|
||||
smooth turning 30 to 150 degrees a second by 15, then to 300 by 30; the
|
||||
vehicle vignette Off, 15 to 100%; the on-foot vignette (off by default)
|
||||
while the stick turns smoothly, or also while the move stick moves, as
|
||||
far as the sticks are pushed, with its own strength. The old values are
|
||||
among the steps; defaults unchanged.
|
||||
|
||||
Transparent geometry: room for 2048 groups a window (the Xbox's 384,
|
||||
below).
|
||||
|
||||
| Setting | Values |
|
||||
| --- | --- |
|
||||
| `vr.button_jump` | `"a"` (default), `"b"`, `"x"`, `"y"`, `"move_stick"`, `"turn_stick"`, `"grip"`: as right-handed, mirrored with `vr.mirror_controls` |
|
||||
| `vr.button_light` | the flashlight, held switch grenades; `"b"` (default) |
|
||||
| `vr.button_action` | action, reload; `"x"` (default) |
|
||||
| `vr.button_weapon` | switch weapon; `"y"` (default) |
|
||||
| `vr.button_crouch` | `"move_stick"` (default) |
|
||||
| `vr.button_zoom` | `"turn_stick"` (default) |
|
||||
| `vr.button_melee` | `"grip"` (default) |
|
||||
| `vr.foot_vignette` | `"off"` (default), `"turning"` (smooth turns), `"moving"` (and moving) |
|
||||
| `vr.foot_vignette_strength` | 0.15 to 1, `0.7` (default) |
|
||||
| `debug.vr_options` | `false` (default); `true` shows the VR options off the headset (the emulator) |
|
||||
|
||||
Checked in the emulator (`debug.vr_options`): the four pages without
|
||||
overlaps, each row's values, swaps and reset, the controller buttons page
|
||||
right- and left-handed after swaps, the settings kept over a restart, a
|
||||
15-bot game as before.
|
||||
|
||||
To try in the headset (Quest 3 and Steam Frame):
|
||||
1. Defaults: every button as before (jump, light/held grenades, action,
|
||||
switch weapon, crouch, zoom, melee by grip and by swing), right- and
|
||||
left-handed, mirrored and not; the menus' A and B.
|
||||
2. BUTTONS: swap jump and melee (jump on the grip, melee on A): both work,
|
||||
the melee jabs and aims where the head looks; light on a stick click
|
||||
(tap: light, hold: grenades); the controller buttons page says so.
|
||||
At the Warthog's wheel with jump on the grip: no jump while holding
|
||||
the wheel. Reset: all back.
|
||||
3. The same left-handed with mirrored controls (on the Frame the left
|
||||
D-pad), and with `Mirrored controls: Off`.
|
||||
4. COMFORT: snap 10 and 90, smooth 30 and 300; vehicle vignette 15% and
|
||||
100%; on-foot vignette "Smooth turns" (nothing with snap turns),
|
||||
"Moving, turning", strength 15% and 100%; no vignette in a vehicle
|
||||
from the on-foot one, none in the menus.
|
||||
5. A game of 30 and more players: effects, glass and water stay; the log
|
||||
(`adb logcat -s halo`) says `rasterizer: N transparent geometry groups
|
||||
in a window` once a window passes 384; frame time as before.
|
||||
|
||||
## Step 3a (done)
|
||||
|
||||
Tried on the Quest 3 and the Steam Frame, in `main`. The arm IK,
|
||||
@@ -194,10 +270,11 @@ the left hand animated through reloads.
|
||||
- Multiview on the Steam Frame: why the eyes do not fuse there (suspected
|
||||
Mesa zink/Turnip); tools in branch `frame/doubled-images` (86335828),
|
||||
not merged.
|
||||
- Transparent geometry groups: the Xbox's 384 a window overflow in games
|
||||
of 30 and more players.
|
||||
- The VR on-screen keyboard shows a password in the clear while it is
|
||||
typed.
|
||||
- Transparent geometry groups: the Xbox's 384 a window overflowed in
|
||||
games of 30 and more players (the last submitted, glass, water,
|
||||
particles, contrails, vanished). Raised to 2048 (320 KB) on
|
||||
`feature/vr-comfort`, to check in a large game (step 2, second part,
|
||||
test 6); upstream still has 384.
|
||||
|
||||
## Headset tests (2026-10-06, all three in `main`)
|
||||
|
||||
|
||||
@@ -20,14 +20,14 @@ def buildNumber = buildNumberText.isInteger() ? buildNumberText.toInteger() : 0
|
||||
// debug key of the computer that builds
|
||||
def signingKey = System.getenv("HALO_ANDROID_KEYSTORE")
|
||||
def signingPassword = System.getenv("HALO_ANDROID_KEYSTORE_PASSWORD")
|
||||
def signingAlias = System.getenv("HALO_ANDROID_KEY_ALIAS") ?: "halo"
|
||||
def signingAlias = System.getenv("HALO_ANDROID_KEY_ALIAS") ?: "ringfall"
|
||||
def signingProperties = new File(rootDir, "signing.properties")
|
||||
if (!signingKey && signingProperties.isFile()) {
|
||||
def properties = new Properties()
|
||||
signingProperties.withInputStream { properties.load(it) }
|
||||
signingKey = properties.getProperty("storeFile")
|
||||
signingPassword = properties.getProperty("password")
|
||||
signingAlias = properties.getProperty("keyAlias", "halo")
|
||||
signingAlias = properties.getProperty("keyAlias", "ringfall")
|
||||
}
|
||||
|
||||
android {
|
||||
@@ -35,7 +35,8 @@ android {
|
||||
compileSdk 35
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.halo.decomp"
|
||||
// (the app's own id; the Java classes keep the decompilation's package)
|
||||
applicationId "io.github.cesarus85.ringfall"
|
||||
minSdk 28
|
||||
targetSdk 35
|
||||
versionCode Math.max(1, buildNumber)
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
<!-- the downloaded new version, for the package installer (Updater) -->
|
||||
<provider
|
||||
android:name=".UpdateProvider"
|
||||
android:authorities="com.halo.decomp.update"
|
||||
android:authorities="${applicationId}.update"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true" />
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ import java.nio.channels.FileChannel;
|
||||
* Starts the game once its data is in place.
|
||||
*
|
||||
* The game reads the Xbox game data (the folder holding maps/) from the
|
||||
* app's external files directory, /sdcard/Android/data/com.halo.decomp/files.
|
||||
* app's external files directory, /sdcard/Android/data/<application id>/files.
|
||||
* If it is missing, this screen lets the player pick an Xbox disc image of
|
||||
* the game (.xiso or .iso, any version) with the system file picker, and
|
||||
* copies its maps folder there (XisoExtractor), as the desktop games do; or
|
||||
@@ -35,7 +35,7 @@ import java.nio.channels.FileChannel;
|
||||
*/
|
||||
public class LauncherActivity extends Activity {
|
||||
private static final int PICK_IMAGE = 1;
|
||||
private static final String SHARED_DATA_ROOT = "/home/steamos/Documents/HaloCE";
|
||||
private static final String SHARED_DATA_ROOT = "/home/steamos/Documents/RingfallMjolnirVR";
|
||||
|
||||
private File dataRoot;
|
||||
private TextView status;
|
||||
@@ -147,15 +147,15 @@ public class LauncherActivity extends Activity {
|
||||
layout.setBackgroundColor(Color.rgb(12, 16, 20));
|
||||
|
||||
TextView title = new TextView(this);
|
||||
title.setText("Halo needs its game data");
|
||||
title.setText("Ringfall: Mjolnir VR needs game data");
|
||||
title.setTextColor(Color.WHITE);
|
||||
title.setTextSize(TypedValue.COMPLEX_UNIT_SP, 24);
|
||||
title.setGravity(Gravity.CENTER);
|
||||
layout.addView(title);
|
||||
|
||||
TextView message = new TextView(this);
|
||||
message.setText("Choose an Xbox disc image of Halo: Combat Evolved (an .iso or .xiso file, any "
|
||||
+ "version) on this device. Its maps folder is copied into the app's storage (about 1.8 GB), "
|
||||
message.setText("Choose your own disc image of Halo: Combat Evolved for the original Xbox (an .iso or "
|
||||
+ ".xiso file, any version) on this device. Its maps folder is copied into the app's storage (about 1.8 GB), "
|
||||
+ "and you can delete the image afterwards.\n\n"
|
||||
+ "You can also copy a maps folder from a computer:\n"
|
||||
+ "adb push <folder with maps>/. " + (dataRoot != null ? dataRoot.getAbsolutePath() : "") + "/");
|
||||
|
||||
@@ -13,13 +13,13 @@ import java.io.FileNotFoundException;
|
||||
|
||||
/**
|
||||
* Hands Android's package installer the downloaded new version of the app
|
||||
* (Updater), read-only, the one file content://com.halo.decomp.update/halo.apk.
|
||||
* (Updater), read-only, the one file content://<application id>.update/update.apk.
|
||||
* (The app has no AndroidX, whose FileProvider does the same.)
|
||||
*/
|
||||
public class UpdateProvider extends ContentProvider {
|
||||
static final String AUTHORITY = "com.halo.decomp.update";
|
||||
static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".update";
|
||||
static final String DIRECTORY = "update";
|
||||
static final String APK = "halo.apk";
|
||||
static final String APK = "update.apk";
|
||||
|
||||
private File apk() {
|
||||
return new File(new File(getContext().getCacheDir(), DIRECTORY), APK);
|
||||
|
||||
@@ -49,7 +49,8 @@ import java.util.zip.ZipInputStream;
|
||||
final class Updater {
|
||||
/* the VR port's own releases: the original's, made without VR, must
|
||||
never replace it (they share the package name) */
|
||||
private static final String REPOSITORY = "Cesarus85/halo-ce-vr";
|
||||
private static final String APP_NAME = "Ringfall: Mjolnir VR";
|
||||
private static final String REPOSITORY = "Cesarus85/ringfall-mjolnir-vr";
|
||||
private static final String USER_AGENT = "halo-ce-universal-updater";
|
||||
private static final int TIMEOUT_MILLISECONDS = 20000;
|
||||
|
||||
@@ -202,8 +203,8 @@ final class Updater {
|
||||
if (activity.isFinishing())
|
||||
return;
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle("Halo: new version")
|
||||
.setMessage("A new version of Halo was detected (build " + latest + "; this is build "
|
||||
.setTitle(APP_NAME + ": new version")
|
||||
.setMessage("A new version of " + APP_NAME + " was detected (build " + latest + "; this is build "
|
||||
+ BuildConfig.HALO_BUILD_NUMBER + ").\n\nDo you want to update? The game will close and start "
|
||||
+ "the new version.")
|
||||
.setCancelable(false)
|
||||
@@ -215,7 +216,7 @@ final class Updater {
|
||||
|
||||
private static void confirmNever(Activity activity) {
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle("Halo: new version")
|
||||
.setTitle(APP_NAME + ": new version")
|
||||
.setMessage("Stop asking about new versions?\n\nTo ask again, set auto = true in the [update] section "
|
||||
+ "of config.toml.")
|
||||
.setCancelable(false)
|
||||
@@ -232,7 +233,7 @@ final class Updater {
|
||||
/* ---------- updating */
|
||||
|
||||
private static void update(Activity activity, int latest) {
|
||||
String asset = "halo-android-" + (BuildConfig.DEBUG ? "debug" : "release") + ".zip";
|
||||
String asset = "ringfall-mjolnir-vr-android-" + (BuildConfig.DEBUG ? "debug" : "release") + ".zip";
|
||||
File directory = new File(activity.getCacheDir(), UpdateProvider.DIRECTORY);
|
||||
LinearLayout layout = new LinearLayout(activity);
|
||||
TextView status = new TextView(activity);
|
||||
@@ -247,7 +248,7 @@ final class Updater {
|
||||
layout.addView(status);
|
||||
layout.addView(bar);
|
||||
AlertDialog progress = new AlertDialog.Builder(activity)
|
||||
.setTitle("Halo: new version")
|
||||
.setTitle(APP_NAME + ": new version")
|
||||
.setView(layout)
|
||||
.setCancelable(false)
|
||||
.show();
|
||||
@@ -275,7 +276,7 @@ final class Updater {
|
||||
activity.runOnUiThread(() -> {
|
||||
progress.dismiss();
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle("Halo: new version")
|
||||
.setTitle(APP_NAME + ": new version")
|
||||
.setMessage("The update failed:\n\n" + e.getMessage())
|
||||
.setPositiveButton("OK", null)
|
||||
.show();
|
||||
|
||||
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 9.3 KiB |
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Halo</string>
|
||||
<string name="app_name">Ringfall: Mjolnir VR</string>
|
||||
</resources>
|
||||
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 21 KiB |
@@ -63,7 +63,7 @@ void host_fatal(const char *format, ...)
|
||||
vsnprintf(message, sizeof(message), format, arguments);
|
||||
va_end(arguments);
|
||||
__android_log_write(ANDROID_LOG_FATAL, "halo", message);
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Halo", message, NULL);
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Ringfall: Mjolnir VR", message, NULL);
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ int host_errno(void)
|
||||
static char data_root[512];
|
||||
static char save_root[512];
|
||||
/* where a device that empties the app's folders keeps the game's data */
|
||||
#define SHARED_DATA_ROOT "/home/steamos/Documents/HaloCE"
|
||||
#define SHARED_DATA_ROOT "/home/steamos/Documents/RingfallMjolnirVR"
|
||||
|
||||
void host_android_path(int which, char *buffer, uint32_t size)
|
||||
{
|
||||
@@ -292,7 +292,7 @@ static void *game_main(void *unused)
|
||||
}
|
||||
if (!directory_has_maps(data_root))
|
||||
{
|
||||
host_fatal("The Halo game data was not found.\n\nCopy the PAL game data (build 01.01.14.2342), "
|
||||
host_fatal("The game data was not found.\n\nCopy the PAL game data (build 01.01.14.2342), "
|
||||
"the folder that contains maps, into\n%s\nor import it from the launcher screen.", data_root);
|
||||
}
|
||||
|
||||
@@ -400,7 +400,7 @@ int main(int argc, char *argv[])
|
||||
{
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
host_logf(HOST_LOG_INFO, "Halo for Android starting");
|
||||
host_logf(HOST_LOG_INFO, "Ringfall: Mjolnir VR starting");
|
||||
host_install_signal_handlers();
|
||||
if (host_native_thread_create(game_main, NULL, MAIN_STACK_SIZE) != 0)
|
||||
host_fatal("cannot start the game thread");
|
||||
|
||||
@@ -1232,7 +1232,7 @@ int host_xr_probe(float resolution_scale, int *eye_width, int *eye_height)
|
||||
android.applicationVM = machine;
|
||||
android.applicationActivity = activity;
|
||||
instance.next = &android;
|
||||
strcpy(instance.applicationInfo.applicationName, "Halo VR");
|
||||
strcpy(instance.applicationInfo.applicationName, "Ringfall Mjolnir VR");
|
||||
instance.applicationInfo.applicationVersion = 1;
|
||||
strcpy(instance.applicationInfo.engineName, "halo-ce-universal");
|
||||
instance.applicationInfo.engineVersion = 1;
|
||||
@@ -2167,7 +2167,7 @@ void host_xr_test(void)
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
|
||||
window = SDL_CreateWindow("Halo", 640, 480, SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN);
|
||||
window = SDL_CreateWindow("Ringfall: Mjolnir VR", 640, 480, SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN);
|
||||
context = window ? SDL_GL_CreateContext(window) : NULL;
|
||||
if (!context || !SDL_GL_MakeCurrent(window, context))
|
||||
host_fatal("VR test: no OpenGL ES context: %s", SDL_GetError());
|
||||
|
||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 71 KiB |
@@ -1,31 +1,65 @@
|
||||
#!/bin/bash
|
||||
# Halo VR's installer for the Steam Frame, run in its Desktop Mode from the
|
||||
# bundle the release's "Install Halo VR" desktop file downloads
|
||||
# (steam_frame_installer.desktop; the bundle: this script, HaloVR.apk,
|
||||
# halo-vr.sh = steam_frame_launch.sh, steam_frame_xiso.py and the library
|
||||
# pictures in grid/). Everything is answered with dialogs, no keyboard:
|
||||
# - the game in ~/Documents/HaloCE (port/android/STEAM_FRAME.md), its APK
|
||||
# replaced (Lepton installs a newer one at the next start), so running the
|
||||
# installer again updates the game;
|
||||
# - the game data: the maps folder copied out of a disc image of Halo
|
||||
# (.iso or .xiso) found in Downloads, Documents, the Desktop or on a
|
||||
# memory card, or chosen, unless it is there already;
|
||||
# - "Halo VR" in the Steam library, with its pictures, unless it is there
|
||||
# already: Steam is closed for that (it keeps its shortcuts in memory and
|
||||
# writes them back when it quits), and started again in Gaming Mode.
|
||||
# HALO_VR_UNATTENDED=1 answers every question with its default and prints
|
||||
# instead of showing dialogs (HALO_VR_IMAGE: the disc image to use), for
|
||||
# Ringfall: Mjolnir VR's installer for the Steam Frame, run in its Desktop
|
||||
# Mode from the bundle the release's "Install Ringfall: Mjolnir VR" desktop
|
||||
# file downloads (steam_frame_installer.desktop; the bundle: this script,
|
||||
# RingfallMjolnirVR.apk, ringfall-mjolnir-vr.sh = steam_frame_launch.sh,
|
||||
# steam_frame_xiso.py and the library pictures in grid/). Everything is
|
||||
# answered with dialogs, no keyboard:
|
||||
# - the game in ~/Documents/RingfallMjolnirVR (port/android/STEAM_FRAME.md),
|
||||
# its APK replaced (Lepton installs a newer one at the next start), so
|
||||
# running the installer again updates the game; the folder of the builds
|
||||
# before the name, ~/Documents/HaloCE, becomes it;
|
||||
# - the game data: the maps folder copied out of a disc image of the
|
||||
# original Xbox game (.iso or .xiso) found in Downloads, Documents, the
|
||||
# Desktop or on a memory card, or chosen, unless it is there already;
|
||||
# - the game in the Steam library, with its pictures, unless it is there
|
||||
# already: added by Steam itself (steam://addnonsteamgame, as SteamOS's
|
||||
# "Add to Steam" does), from a desktop file beside the game. Steam is not
|
||||
# closed: it keeps its shortcuts in memory and writes them back, and on the
|
||||
# Steam Frame Desktop Mode runs inside Gaming Mode's session, which ends
|
||||
# with Steam. The shortcut of the builds before the name ("Halo VR") is
|
||||
# left for the player to remove (Steam would write it back).
|
||||
# RINGFALL_UNATTENDED=1 answers every question with its default and prints
|
||||
# instead of showing dialogs (RINGFALL_IMAGE: the disc image to use), for
|
||||
# testing over adb; it never closes Steam.
|
||||
|
||||
BUNDLE="$(cd "$(dirname "$0")" && pwd)"
|
||||
HALO_DIR="$HOME/Documents/HaloCE"
|
||||
GAME_DIR="$HOME/Documents/RingfallMjolnirVR"
|
||||
STEAM_DIR="$HOME/.local/share/Steam"
|
||||
LEPTON="$STEAM_DIR/steamapps/common/Lepton/lepton"
|
||||
TITLE="Halo VR"
|
||||
NAME="Halo VR"
|
||||
EXE="\"$HALO_DIR/halo-vr.sh\""
|
||||
TITLE="Ringfall: Mjolnir VR"
|
||||
NAME="Ringfall: Mjolnir VR"
|
||||
APK_NAME="RingfallMjolnirVR.apk"
|
||||
LAUNCHER="ringfall-mjolnir-vr.sh"
|
||||
EXE="\"$GAME_DIR/$LAUNCHER\""
|
||||
# (the desktop file Steam adds the game from)
|
||||
NAME_FILE="Ringfall Mjolnir VR"
|
||||
# the builds before the name: their folder and Steam shortcut
|
||||
OLD_DIR="$HOME/Documents/HaloCE"
|
||||
OLD_NAME="Halo VR"
|
||||
|
||||
unattended() { [ "${HALO_VR_UNATTENDED:-0}" = 1 ] || ! command -v zenity >/dev/null; }
|
||||
# what the installer did, for looking into problems: written to /tmp while
|
||||
# it runs, then kept beside the game as install.log
|
||||
LOG="/tmp/ringfall-mjolnir-vr-install.log"
|
||||
: > "$LOG"
|
||||
log() { echo "$(date '+%F %T') $*" >> "$LOG"; }
|
||||
keep_log() { [ -d "$GAME_DIR" ] && cp -f "$LOG" "$GAME_DIR/install.log" 2>/dev/null; }
|
||||
trap keep_log EXIT
|
||||
log "installer from $BUNDLE"
|
||||
|
||||
# whether Steam runs: its pid file (~/.steam/steam.pid), or its client's
|
||||
# process (ubuntu12_32/steam: the process is not always named "steam")
|
||||
steam_running()
|
||||
{
|
||||
local pid
|
||||
pid="$(cat "$HOME/.steam/steam.pid" 2>/dev/null)"
|
||||
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
pgrep -f 'ubuntu12_32/steam( |$)' >/dev/null || pgrep -x steam >/dev/null
|
||||
}
|
||||
|
||||
unattended() { [ "${RINGFALL_UNATTENDED:-0}" = 1 ] || ! command -v zenity >/dev/null; }
|
||||
|
||||
info()
|
||||
{
|
||||
@@ -34,6 +68,7 @@ info()
|
||||
|
||||
fail()
|
||||
{
|
||||
log "error: $1"
|
||||
if unattended; then printf "error: %b\n" "$1" >&2; else zenity --error --title="$TITLE" --width=460 --text="$1"; fi
|
||||
exit 1
|
||||
}
|
||||
@@ -49,12 +84,12 @@ ask()
|
||||
choose_image()
|
||||
{
|
||||
if unattended; then return 1; fi
|
||||
zenity --file-selection --title="Choose a disc image of Halo: Combat Evolved (.iso or .xiso)" \
|
||||
zenity --file-selection --title="Choose a disc image of Halo: Combat Evolved for the Xbox (.iso or .xiso)" \
|
||||
--filename="$HOME/Downloads/" --file-filter="Disc images | *.iso *.ISO *.xiso *.XISO" \
|
||||
--file-filter="All files | *"
|
||||
}
|
||||
|
||||
# the disc images near at hand, largest first (a Halo image is a few GB;
|
||||
# the disc images near at hand, largest first (the game's image is a few GB;
|
||||
# anything under 500 MB is not one)
|
||||
find_images()
|
||||
{
|
||||
@@ -68,10 +103,10 @@ extract_maps()
|
||||
local image="$1" log
|
||||
log="$(mktemp)"
|
||||
if unattended; then
|
||||
python3 "$BUNDLE/steam_frame_xiso.py" "$image" "$HALO_DIR" 2>"$log" | grep '^#' | tail -n 1
|
||||
python3 "$BUNDLE/steam_frame_xiso.py" "$image" "$GAME_DIR" 2>"$log" | grep '^#' | tail -n 1
|
||||
status=${PIPESTATUS[0]}
|
||||
else
|
||||
python3 "$BUNDLE/steam_frame_xiso.py" "$image" "$HALO_DIR" 2>"$log" |
|
||||
python3 "$BUNDLE/steam_frame_xiso.py" "$image" "$GAME_DIR" 2>"$log" |
|
||||
zenity --progress --title="$TITLE" --width=460 --auto-close --no-cancel \
|
||||
--text="Copying the game data out of the disc image..."
|
||||
status=${PIPESTATUS[0]}
|
||||
@@ -84,39 +119,36 @@ extract_maps()
|
||||
rm -f "$log"
|
||||
}
|
||||
|
||||
# the shortcut's id as Steam makes it for a non-Steam game: its pictures in
|
||||
# config/grid are named after it
|
||||
# the Steam users' shortcut files (userdata/0 is no user)
|
||||
shortcut_files()
|
||||
{
|
||||
local config
|
||||
for config in "$STEAM_DIR"/userdata/*/config; do
|
||||
case "$config" in */userdata/0/*) continue ;; esac
|
||||
[ -f "$config/shortcuts.vdf" ] && echo "$config/shortcuts.vdf"
|
||||
done
|
||||
}
|
||||
|
||||
# whether a Steam user's library holds the launcher given
|
||||
have_shortcut_to()
|
||||
{
|
||||
local file
|
||||
while read -r file; do
|
||||
grep -qaF "$1" "$file" && return 0
|
||||
done < <(shortcut_files)
|
||||
return 1
|
||||
}
|
||||
|
||||
have_shortcut() { have_shortcut_to "$GAME_DIR/$LAUNCHER"; }
|
||||
|
||||
# the id Steam gave the shortcut whose Exe is $2, in shortcuts.vdf $1 (Steam
|
||||
# picks it; its library pictures in config/grid are named after it)
|
||||
shortcut_id()
|
||||
{
|
||||
python3 -c 'import sys, zlib; print(zlib.crc32((sys.argv[1] + sys.argv[2]).encode()) | 0x80000000)' "$EXE" "$NAME"
|
||||
}
|
||||
python3 - "$1" "$2" <<'PYTHON'
|
||||
import struct, sys
|
||||
|
||||
# whether every Steam user's shortcuts.vdf already holds the game
|
||||
have_shortcut()
|
||||
{
|
||||
local found=1 config
|
||||
for config in "$STEAM_DIR"/userdata/*/config; do
|
||||
[ -d "$config" ] || continue
|
||||
case "$config" in */userdata/0/*) continue ;; esac
|
||||
grep -qaF "$HALO_DIR/halo-vr.sh" "$config/shortcuts.vdf" 2>/dev/null || return 1
|
||||
found=0
|
||||
done
|
||||
return $found
|
||||
}
|
||||
|
||||
# "Halo VR" added to every Steam user's shortcuts.vdf (Steam closed), as a VR
|
||||
# title, with its pictures
|
||||
add_shortcut()
|
||||
{
|
||||
local id config
|
||||
id="$(shortcut_id)"
|
||||
for config in "$STEAM_DIR"/userdata/*/config; do
|
||||
[ -d "$config" ] || continue
|
||||
case "$config" in */userdata/0/*) continue ;; esac
|
||||
python3 - "$config/shortcuts.vdf" "$id" "$NAME" "$EXE" "\"$HALO_DIR/\"" <<'PYTHON' || return 1
|
||||
import os, struct, sys
|
||||
|
||||
path, appid, name, exe, start = sys.argv[1], int(sys.argv[2]), sys.argv[3], sys.argv[4], sys.argv[5]
|
||||
path, exe = sys.argv[1], sys.argv[2].encode()
|
||||
|
||||
# Steam's binary VDF: 0x00 a map, 0x01 a string, 0x02 an int32, 0x08 a map's end
|
||||
def read_map(data, at):
|
||||
@@ -125,7 +157,7 @@ def read_map(data, at):
|
||||
kind = data[at]; at += 1
|
||||
if kind == 0x08:
|
||||
return items, at
|
||||
end = data.index(b"\0", at); key = data[at:end]; at = end + 1
|
||||
end = data.index(b"\0", at); key = data[at:end].lower(); at = end + 1
|
||||
if kind == 0x00:
|
||||
value, at = read_map(data, at)
|
||||
elif kind == 0x01:
|
||||
@@ -134,88 +166,82 @@ def read_map(data, at):
|
||||
value = struct.unpack_from("<I", data, at)[0]; at += 4
|
||||
else:
|
||||
raise ValueError("unknown VDF type %d" % kind)
|
||||
items.append((kind, key, value))
|
||||
items.append((key, value))
|
||||
|
||||
def write_map(items):
|
||||
out = b""
|
||||
for kind, key, value in items:
|
||||
out += bytes([kind]) + key + b"\0"
|
||||
if kind == 0x00:
|
||||
out += write_map(value)
|
||||
elif kind == 0x01:
|
||||
out += value + b"\0"
|
||||
else:
|
||||
out += struct.pack("<I", value)
|
||||
return out + b"\x08"
|
||||
|
||||
shortcuts = []
|
||||
if os.path.exists(path):
|
||||
with open(path, "rb") as file:
|
||||
data = file.read()
|
||||
if data:
|
||||
top, _ = read_map(data, 0)
|
||||
for kind, key, value in top:
|
||||
if key.lower() == b"shortcuts" and kind == 0x00:
|
||||
shortcuts = [entry for entry in value if entry[0] == 0x00]
|
||||
# (an older entry of the game's replaced)
|
||||
shortcuts = [entry for entry in shortcuts
|
||||
if not any(k.lower() == b"exe" and v == exe.encode() for _, k, v in entry[2])]
|
||||
|
||||
def text(key, value):
|
||||
return (0x01, key.encode(), value.encode())
|
||||
|
||||
def number(key, value):
|
||||
return (0x02, key.encode(), value)
|
||||
|
||||
entry = [
|
||||
number("appid", appid), text("AppName", name), text("Exe", exe), text("StartDir", start),
|
||||
text("icon", ""), text("ShortcutPath", ""), text("LaunchOptions", ""), number("IsHidden", 0),
|
||||
number("AllowDesktopConfig", 1), number("AllowOverlay", 1), number("OpenVR", 1), number("Devkit", 0),
|
||||
text("DevkitGameID", ""), number("DevkitOverrideAppID", 0), number("LastPlayTime", 0),
|
||||
text("FlatpakAppID", ""), text("sortas", ""), (0x00, b"tags", []),
|
||||
]
|
||||
shortcuts.append((0x00, b"", entry))
|
||||
shortcuts = [(0x00, str(index).encode(), value) for index, (_, _, value) in enumerate(shortcuts)]
|
||||
data = write_map([(0x00, b"shortcuts", shortcuts)])
|
||||
with open(path + ".tmp", "wb") as file:
|
||||
file.write(data)
|
||||
os.replace(path + ".tmp", path)
|
||||
with open(path, "rb") as file:
|
||||
top, _ = read_map(file.read(), 0)
|
||||
for key, shortcuts in top:
|
||||
if key == b"shortcuts":
|
||||
for _, entry in shortcuts:
|
||||
fields = dict(entry)
|
||||
if fields.get(b"exe") == exe and b"appid" in fields:
|
||||
print(fields[b"appid"])
|
||||
sys.exit(0)
|
||||
sys.exit(1)
|
||||
PYTHON
|
||||
mkdir -p "$config/grid"
|
||||
cp "$BUNDLE/grid/library.png" "$config/grid/${id}.png"
|
||||
cp "$BUNDLE/grid/libraryp.png" "$config/grid/${id}p.png"
|
||||
cp "$BUNDLE/grid/library_hero.png" "$config/grid/${id}_hero.png"
|
||||
cp "$BUNDLE/grid/library_logo.png" "$config/grid/${id}_logo.png"
|
||||
done
|
||||
}
|
||||
|
||||
# the game added to the library of the Steam user logged in, by Steam (it
|
||||
# must run), from a desktop file beside the game; then its pictures
|
||||
add_shortcut()
|
||||
{
|
||||
local desktop="$GAME_DIR/$NAME_FILE.desktop" url file id
|
||||
printf '[Desktop Entry]\nType=Application\nName=%s\nExec=%s\nPath=%s\nIcon=steam\nTerminal=false\n' \
|
||||
"$NAME" "$GAME_DIR/$LAUNCHER" "$GAME_DIR" > "$desktop" && chmod 755 "$desktop" || return 1
|
||||
url="steam://addnonsteamgame/$(python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=""))' "$desktop")"
|
||||
log "asking Steam: $url"
|
||||
# (Steam's own "Add to Steam" touches this first: steamos-add-to-steam)
|
||||
touch /tmp/addnonsteamgamefile
|
||||
steam "$url" >/dev/null 2>&1 &
|
||||
for _ in $(seq 30); do have_shortcut && break; sleep 1; done
|
||||
have_shortcut || return 1
|
||||
while read -r file; do
|
||||
id="$(shortcut_id "$file" "$EXE")" || continue
|
||||
log "shortcut $id in $file"
|
||||
mkdir -p "$(dirname "$file")/grid"
|
||||
cp "$BUNDLE/grid/library.png" "$(dirname "$file")/grid/${id}.png"
|
||||
cp "$BUNDLE/grid/libraryp.png" "$(dirname "$file")/grid/${id}p.png"
|
||||
cp "$BUNDLE/grid/library_hero.png" "$(dirname "$file")/grid/${id}_hero.png"
|
||||
cp "$BUNDLE/grid/library_logo.png" "$(dirname "$file")/grid/${id}_logo.png"
|
||||
done < <(shortcut_files)
|
||||
return 0
|
||||
}
|
||||
|
||||
# --- the game
|
||||
|
||||
[ -x "$LEPTON" ] || fail "Lepton, which runs Android games on the Steam Frame, was not found.\n\nStart any Android game from the Steam store once, then run this installer again."
|
||||
for file in HaloVR.apk halo-vr.sh steam_frame_xiso.py grid/library.png; do
|
||||
for file in "$APK_NAME" "$LAUNCHER" steam_frame_xiso.py grid/library.png; do
|
||||
[ -f "$BUNDLE/$file" ] || fail "The installer is incomplete ($file is missing). Download it again."
|
||||
done
|
||||
|
||||
if [ -f "$HALO_DIR/HaloVR.apk" ]; then
|
||||
ask "Halo VR is installed already. Update it to this version?\n\n(Your settings, saves and game data stay.)" yes || exit 0
|
||||
else
|
||||
ask "This installs Halo VR on your Steam Frame.\n\nYou need your own disc image of Halo: Combat Evolved for the Xbox (an .iso or .xiso file, any version), for example in Downloads.\n\nContinue?" yes || exit 0
|
||||
# the builds before the name: their folder becomes the game's (settings,
|
||||
# saves and game data kept), without their APK and launcher
|
||||
if [ -d "$OLD_DIR" ] && [ ! -e "$GAME_DIR" ]; then
|
||||
log "renaming $OLD_DIR to $GAME_DIR"
|
||||
mv "$OLD_DIR" "$GAME_DIR" || fail "Could not rename $OLD_DIR to $GAME_DIR."
|
||||
rm -f "$GAME_DIR/HaloVR.apk" "$GAME_DIR/halo-vr.sh"
|
||||
fi
|
||||
|
||||
mkdir -p "$HALO_DIR/maps" || fail "Could not create $HALO_DIR."
|
||||
if [ -f "$GAME_DIR/$APK_NAME" ] || [ -f "$GAME_DIR/maps/ui.map" ]; then
|
||||
ask "$NAME is installed already. Update it to this version?\n\n(Your settings, saves and game data stay.)" yes || exit 0
|
||||
else
|
||||
ask "This installs $NAME on your Steam Frame.\n\nYou need your own disc image of Halo: Combat Evolved for the original Xbox (an .iso or .xiso file, any version), for example in Downloads.\n\nContinue?" yes || exit 0
|
||||
fi
|
||||
|
||||
mkdir -p "$GAME_DIR/maps" || fail "Could not create $GAME_DIR."
|
||||
# (the game runs in Lepton's container as a user of its own)
|
||||
chmod 777 "$HALO_DIR" "$HALO_DIR/maps"
|
||||
cp "$BUNDLE/halo-vr.sh" "$HALO_DIR/halo-vr.sh.tmp" && chmod 755 "$HALO_DIR/halo-vr.sh.tmp" &&
|
||||
mv -f "$HALO_DIR/halo-vr.sh.tmp" "$HALO_DIR/halo-vr.sh" || fail "Could not write $HALO_DIR/halo-vr.sh."
|
||||
chmod 777 "$GAME_DIR" "$GAME_DIR/maps"
|
||||
cp "$BUNDLE/$LAUNCHER" "$GAME_DIR/$LAUNCHER.tmp" && chmod 755 "$GAME_DIR/$LAUNCHER.tmp" &&
|
||||
mv -f "$GAME_DIR/$LAUNCHER.tmp" "$GAME_DIR/$LAUNCHER" || fail "Could not write $GAME_DIR/$LAUNCHER."
|
||||
# (newer than the installed one: Lepton installs it at the next start)
|
||||
cp "$BUNDLE/HaloVR.apk" "$HALO_DIR/HaloVR.apk.tmp" && chmod 666 "$HALO_DIR/HaloVR.apk.tmp" &&
|
||||
mv -f "$HALO_DIR/HaloVR.apk.tmp" "$HALO_DIR/HaloVR.apk" && touch "$HALO_DIR/HaloVR.apk" ||
|
||||
fail "Could not write $HALO_DIR/HaloVR.apk (is the storage full?)."
|
||||
cp "$BUNDLE/$APK_NAME" "$GAME_DIR/$APK_NAME.tmp" && chmod 666 "$GAME_DIR/$APK_NAME.tmp" &&
|
||||
mv -f "$GAME_DIR/$APK_NAME.tmp" "$GAME_DIR/$APK_NAME" && touch "$GAME_DIR/$APK_NAME" ||
|
||||
fail "Could not write $GAME_DIR/$APK_NAME (is the storage full?)."
|
||||
|
||||
# --- the game data
|
||||
|
||||
if [ ! -f "$HALO_DIR/maps/ui.map" ]; then
|
||||
image="${HALO_VR_IMAGE:-}"
|
||||
if [ ! -f "$GAME_DIR/maps/ui.map" ]; then
|
||||
image="${RINGFALL_IMAGE:-}"
|
||||
if [ -z "$image" ]; then
|
||||
mapfile -t images < <(find_images)
|
||||
for candidate in "${images[@]}"; do
|
||||
@@ -231,36 +257,32 @@ if [ ! -f "$HALO_DIR/maps/ui.map" ]; then
|
||||
fi
|
||||
image="$(choose_image)" || fail "No disc image was chosen. Run the installer again once it is on the Frame (for example in Downloads)."
|
||||
fi
|
||||
log "extracting $image"
|
||||
extract_maps "$image"
|
||||
chmod -R a+rwX "$HALO_DIR/maps"
|
||||
chmod -R a+rwX "$GAME_DIR/maps"
|
||||
image_note="\n\nThe disc image itself is no longer needed:\n$image"
|
||||
fi
|
||||
|
||||
# --- Steam
|
||||
|
||||
steam_closed=0
|
||||
if ! have_shortcut; then
|
||||
if unattended; then
|
||||
echo "info: the Steam shortcut is missing (not added unattended)"
|
||||
else
|
||||
info "Halo VR is now added to your Steam library. Steam closes for a moment for that."
|
||||
if pgrep -x steam >/dev/null; then
|
||||
steam -shutdown >/dev/null 2>&1 &
|
||||
for _ in $(seq 60); do pgrep -x steam >/dev/null || break; sleep 1; done
|
||||
pgrep -x steam >/dev/null && fail "Steam did not close. Close it (Steam menu > Exit) and run the installer again."
|
||||
fi
|
||||
steam_closed=1
|
||||
add_shortcut || fail "Could not add Halo VR to the Steam library."
|
||||
fi
|
||||
if have_shortcut; then
|
||||
log "the Steam shortcut is there"
|
||||
elif unattended; then
|
||||
echo "info: the Steam shortcut is missing (not added unattended)"
|
||||
elif ! steam_running; then
|
||||
info "Steam is not running, so $NAME could not be added to your library.\n\nStart Steam and run the installer again."
|
||||
else
|
||||
add_shortcut || fail "Steam did not add $NAME to your library. Run the installer again; if it happens again, add\n$GAME_DIR/$NAME_FILE.desktop\nwith Steam's \"Add a Non-Steam Game\"."
|
||||
fi
|
||||
|
||||
if unattended; then
|
||||
echo "info: installed in $HALO_DIR"
|
||||
echo "info: installed in $GAME_DIR"
|
||||
exit 0
|
||||
fi
|
||||
if ask "Halo VR is installed.${image_note:-}\n\nSwitch to Gaming Mode now? Start Halo VR from your library there." yes; then
|
||||
if have_shortcut_to "$OLD_DIR/halo-vr.sh"; then
|
||||
info "The library still shows the old \"$OLD_NAME\" from before the game's new name. It no longer starts: remove it in Steam (its gear or Manage > Remove non-Steam game)."
|
||||
fi
|
||||
if ask "$NAME is installed.${image_note:-}\n\nSwitch to Gaming Mode now? Start $NAME from your library there (its pictures may show once Steam starts again)." yes; then
|
||||
steamos-session-select gamescope >/dev/null 2>&1 ||
|
||||
info "Switch to Gaming Mode yourself (the \"Return to Gaming Mode\" icon or the power menu)."
|
||||
elif [ "$steam_closed" = 1 ]; then
|
||||
setsid steam >/dev/null 2>&1 < /dev/null &
|
||||
fi
|
||||
@@ -1,13 +1,14 @@
|
||||
[Desktop Entry]
|
||||
# Halo VR's installer for the Steam Frame (port/android/STEAM_FRAME.md): a
|
||||
# release asset, "Install-Halo-VR.desktop", opened in Desktop Mode. It
|
||||
# downloads the latest release's bundle (tools/steam_frame_bundle.py, which
|
||||
# fills in the repository) and runs its installer, steam_frame_install.sh.
|
||||
# Ringfall: Mjolnir VR's installer for the Steam Frame
|
||||
# (port/android/STEAM_FRAME.md): a release asset,
|
||||
# "Install-Ringfall-Mjolnir-VR.desktop", opened in Desktop Mode. It downloads
|
||||
# the latest release's bundle (tools/steam_frame_bundle.py, which fills in
|
||||
# the repository) and runs its installer, steam_frame_install.sh.
|
||||
# (no "$", "\" or "%" in Exec: the desktop entry format would need them
|
||||
# escaped)
|
||||
Type=Application
|
||||
Name=Install Halo VR
|
||||
Comment=Installs Halo VR for the Steam Frame, or updates it
|
||||
Name=Install Ringfall: Mjolnir VR
|
||||
Comment=Installs Ringfall: Mjolnir VR for the Steam Frame, or updates it
|
||||
Icon=steam
|
||||
Terminal=false
|
||||
Exec=bash -c "if curl -fL --retry 3 -o /tmp/halo-vr-steam-frame.zip https://github.com/@REPOSITORY@/releases/latest/download/halo-vr-steam-frame.zip && rm -rf /tmp/halo-vr-install && python3 -m zipfile -e /tmp/halo-vr-steam-frame.zip /tmp/halo-vr-install; then exec bash /tmp/halo-vr-install/install.sh; else zenity --error --width=460 --title='Halo VR' --text='Halo VR could not be downloaded. Is the Steam Frame online?'; fi"
|
||||
Exec=bash -c "if curl -fL --retry 3 -o /tmp/ringfall-mjolnir-vr-steam-frame.zip https://github.com/@REPOSITORY@/releases/latest/download/ringfall-mjolnir-vr-steam-frame.zip && rm -rf /tmp/ringfall-mjolnir-vr-install && python3 -m zipfile -e /tmp/ringfall-mjolnir-vr-steam-frame.zip /tmp/ringfall-mjolnir-vr-install; then exec bash /tmp/ringfall-mjolnir-vr-install/install.sh; else zenity --error --width=460 --title='Ringfall: Mjolnir VR' --text='Ringfall: Mjolnir VR could not be downloaded. Is the Steam Frame online?'; fi"
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
# Halo VR on the Steam Frame: Lepton runs the APK in a container of its own,
|
||||
# Ringfall: Mjolnir VR on the Steam Frame: Lepton runs the APK in a container of its own,
|
||||
# and installs it anew whenever the APK is newer than the installed one
|
||||
# (port/android/STEAM_FRAME.md). The game's data: ~/Documents/HaloCE.
|
||||
# (port/android/STEAM_FRAME.md). The game's data: ~/Documents/RingfallMjolnirVR.
|
||||
# (a container of its own, not the shortcut's id: with that, SteamVR's
|
||||
# client in the container waited on xrCreateInstance)
|
||||
export SteamAppId="${HALO_LEPTON_APP_ID:-4290000001}"
|
||||
@@ -14,10 +14,11 @@ export STEAM_COMPAT_SHADER_PATH="$HOME/.local/share/Steam/steamapps/shadercache/
|
||||
mkdir -p "$STEAM_COMPAT_DATA_PATH/internal" "$STEAM_COMPAT_SHADER_PATH"
|
||||
unset STEAM_FOSSILIZE_DUMP_PATH LD_PRELOAD
|
||||
LEPTON="$HOME/.local/share/Steam/steamapps/common/Lepton/lepton"
|
||||
APK="$HOME/Documents/HaloCE/HaloVR.apk"
|
||||
LOG="$HOME/Documents/HaloCE/launcher.log"
|
||||
DATA="$HOME/Documents/RingfallMjolnirVR"
|
||||
APK="$DATA/RingfallMjolnirVR.apk"
|
||||
LOG="$DATA/launcher.log"
|
||||
# (written by the game when the player quits it: no start again)
|
||||
QUIT="$HOME/Documents/HaloCE/quit_requested.txt"
|
||||
QUIT="$DATA/quit_requested.txt"
|
||||
rm -f "$QUIT"
|
||||
CONTEXT="steamlaunch-$SteamAppId"
|
||||
|
||||
@@ -49,7 +50,7 @@ setsid bash -c '
|
||||
|
||||
# the game's log while it runs (Lepton's logcat of its container), for
|
||||
# looking into problems: logcat.txt, anew each start
|
||||
LOGCAT="$HOME/Documents/HaloCE/logcat.txt"
|
||||
LOGCAT="$DATA/logcat.txt"
|
||||
: > "$LOGCAT"
|
||||
setsid bash -c '
|
||||
while kill -0 '"$script"' 2>/dev/null; do
|
||||
|
||||
@@ -30,6 +30,17 @@ the same picture, or for the profile settings the Xbox's picture for the same th
|
||||
| `shell/main_menu/settings_select/player_setup/player_profile_edit/profile_options__2.png` | `ui\shell\main_menu\settings_select\player_setup\player_profile_edit\profile_options` frame 1 |
|
||||
| `shell/main_menu/settings_select/player_setup/player_profile_edit/profile_options__7.png` | `ui\shell\main_menu\settings_select\player_setup\player_profile_edit\profile_options` frame 3 |
|
||||
|
||||
These frames are added after a bitmap's, drawn scaled from the Xbox map's (the profile settings'
|
||||
picture of Gamepad Setup: the Xbox's Controller Setup's pictures of the button settings).
|
||||
|
||||
| Our bitmap | The Xbox's frame |
|
||||
| --- | --- |
|
||||
| `main_menu/settings_select/player_setup/player_profile_edit/profile_options` | `ui\shell\main_menu\settings_select\player_setup\player_profile_edit\controller_edit\config_controller` frame 4 |
|
||||
| `main_menu/settings_select/player_setup/player_profile_edit/profile_options` | `ui\shell\main_menu\settings_select\player_setup\player_profile_edit\controller_edit\config_controller` frame 5 |
|
||||
| `main_menu/settings_select/player_setup/player_profile_edit/profile_options` | `ui\shell\main_menu\settings_select\player_setup\player_profile_edit\controller_edit\config_controller` frame 6 |
|
||||
| `main_menu/settings_select/player_setup/player_profile_edit/profile_options` | `ui\shell\main_menu\settings_select\player_setup\player_profile_edit\controller_edit\config_controller` frame 7 |
|
||||
| `main_menu/settings_select/player_setup/player_profile_edit/profile_options` | `ui\shell\main_menu\settings_select\player_setup\player_profile_edit\controller_edit\config_controller` frame 8 |
|
||||
|
||||
## Placeholders, to be redrawn
|
||||
|
||||
The Xbox's map has none of these (or other pictures under the name), so each frame is a placeholder
|
||||
@@ -51,7 +62,7 @@ until it is redrawn.
|
||||
| `ce/shell/main_menu/settings_select/player_setup/player_profile_edit/profile_options__6.png` | 512x256 | `shell/main_menu/settings_select/player_setup/player_profile_edit/profile_options__6.png` |
|
||||
| `ce/shell/main_menu/settings_select/player_setup/player_profile_edit/profile_options__8.png` | 512x256 | `shell/main_menu/settings_select/player_setup/player_profile_edit/profile_options__8.png` |
|
||||
|
||||
13 of 156 frames are placeholders.
|
||||
13 of 157 frames are placeholders.
|
||||
|
||||
The PC version's `ui\gamespy` and `ui\ticker` fonts are drawn with `ui\small_ui`, which
|
||||
the Xbox's map has.
|
||||
@@ -52,6 +52,8 @@ the 640x480 menu screen (the PNG is a whole multiple of it), or a frame of a
|
||||
bitmap of the map: `<frame map="ui\shell\...\profile_options" index="1"/>`. (Or
|
||||
`frames="a.png b.png"` with one `width` and `height` for all.) A widget draws
|
||||
its bitmap from the top left, one unit to a texel, cut to the widget's size.
|
||||
A map's frame with a `width` and `height` is drawn scaled to that size
|
||||
instead, from `x` and `y` in the widget (0 if not given).
|
||||
A list's item shows frame 1 while it has the focus and frame 0 otherwise, if
|
||||
the bitmap has exactly two frames; the game's functions choose others.
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ do (save a setting, join a game) does not happen; the screens open and close as
|
||||
| `gamespy update filter settings` | 2 | PC function |
|
||||
| `gt edit list update` | 1 | PC function |
|
||||
| `gt select list update` | 1 | PC function |
|
||||
| `mouse spinner 1wide click` | 66 | PC function |
|
||||
| `mouse spinner 1wide click` | 72 | PC function |
|
||||
| `mp map list update` | 1 | PC function |
|
||||
| `mp prof init teamplay options` | 1 | PC function |
|
||||
| `mp prof init vehicle options` | 1 | PC function |
|
||||
@@ -33,4 +33,4 @@ do (save a setting, join a game) does not happen; the screens open and close as
|
||||
| `ss edit server name` | 2 | PC function |
|
||||
| `ss start game` | 2 | PC function |
|
||||
|
||||
Wired: `campaign menu continue`, `campaign menu init`, `controls back handler`, `controls begin binding`, `controls screen change set`, `controls screen defaults`, `controls screen init`, `controls update menu`, `difficulty item select`, `direct ip connect go`, `emit custom activation event`, `gamespy back handler`, `gamespy dismiss error`, `gamespy dismiss filters`, `gamespy screen dispose`, `gamespy screen init`, `load game list update`, `load game menu activated`, `load game menu delete finish`, `load game menu delete request`, `load game menu dispose`, `load game menu init`, `main menu quit game`, `mouse emit accept event`, `mouse emit back event`, `mouse emit x event`, `mp type set mode`, `profile manager select`, `profile set edit begin`, `single prev cl item activated`, `solo map list update`.
|
||||
Wired: `campaign menu continue`, `campaign menu init`, `controls back handler`, `controls begin binding`, `controls screen change set`, `controls screen defaults`, `controls screen init`, `controls update menu`, `difficulty item select`, `direct ip connect go`, `emit custom activation event`, `gamespy back handler`, `gamespy dismiss error`, `gamespy dismiss filters`, `gamespy screen dispose`, `gamespy screen init`, `load game list update`, `load game menu activated`, `load game menu delete finish`, `load game menu delete request`, `load game menu dispose`, `load game menu init`, `main menu quit game`, `mouse emit accept event`, `mouse emit back event`, `mouse emit x event`, `mp type set mode`, `profile manager select`, `profile set edit begin`, `single prev cl item activated`, `solo map list update`, `ss edit server password`.
|
||||
@@ -302,6 +302,11 @@
|
||||
<frame png="ce/shell/main_menu/settings_select/player_setup/player_profile_edit/profile_options__6.png" width="512" height="256"/>
|
||||
<frame map="ui\shell\main_menu\settings_select\player_setup\player_profile_edit\profile_options" index="3"/>
|
||||
<frame png="ce/shell/main_menu/settings_select/player_setup/player_profile_edit/profile_options__8.png" width="512" height="256"/>
|
||||
<frame map="ui\shell\main_menu\settings_select\player_setup\player_profile_edit\controller_edit\config_controller" index="4" width="279" height="279" y="34"/>
|
||||
<frame map="ui\shell\main_menu\settings_select\player_setup\player_profile_edit\controller_edit\config_controller" index="5" width="279" height="279" y="34"/>
|
||||
<frame map="ui\shell\main_menu\settings_select\player_setup\player_profile_edit\controller_edit\config_controller" index="6" width="279" height="279" y="34"/>
|
||||
<frame map="ui\shell\main_menu\settings_select\player_setup\player_profile_edit\controller_edit\config_controller" index="7" width="279" height="279" y="34"/>
|
||||
<frame map="ui\shell\main_menu\settings_select\player_setup\player_profile_edit\controller_edit\config_controller" index="8" width="279" height="279" y="34"/>
|
||||
</bitmap>
|
||||
<bitmap name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/header_profile_video_settings">
|
||||
<frame png="ce/shell/main_menu/settings_select/player_setup/player_profile_edit/video_settings/header_profile_video_settings__0.png" width="512" height="64"/>
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
<on event="left_mouse" run="mouse emit accept event"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/profile_edit_extended_desc_pic" controller="1" left="288" top="85" width="279" height="202" bitmap="main_menu/settings_select/player_setup/player_profile_edit/profile_options">
|
||||
<data input="port gamepad layout preview"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/profile_edit_extended_desc_text" type="text" controller="1" left="294" top="290" width="296" height="110" string_list="main_menu/settings_select/player_setup/player_profile_edit/profile_edit_descriptions" font="ui\large_ui" color="#FF2896FF">
|
||||
<replace search="__BUILD__" function="build_number"/>
|
||||
|
||||
@@ -52,14 +52,13 @@ with ideas from VALORANT's netcode articles, keeping the 30 Hz tick:
|
||||
Until a client has the host's, the host takes none of its players'
|
||||
movement and none of its loading zones, and after any switch no loading
|
||||
zone switches again until every machine has the new BSP (ten seconds at
|
||||
most). A loading zone into a
|
||||
BSP the team hasn't been in brings every player to whoever crossed it,
|
||||
as split screen does; one back into a BSP it has been in switches only
|
||||
with two thirds of the living players at it (in the trigger, or within
|
||||
15 world units of the player in it, about 45 metres), so one player can't
|
||||
drag the team back through the level; a player held back is told how
|
||||
many are there and how many it needs. A player outside the loaded BSP
|
||||
and falling for two seconds is brought back beside a teammate. A dead
|
||||
most). Only the host's crossing of a loading zone switches the BSP, and
|
||||
it brings every player to the host, however far behind, so no one
|
||||
running ahead or doubling back drags the team through the level (while
|
||||
none of the host's players are alive, anyone's crossing does); a client
|
||||
standing on a loading zone is told it waits for the host. A player
|
||||
outside the loaded BSP and falling for two seconds is brought back beside
|
||||
the host, else a teammate. A dead
|
||||
player watches a living teammate (`coop_spectate.c`) and comes back
|
||||
beside one once it is safe. With everyone dead they come back where they
|
||||
were at the last checkpoint, without a revert. A mission the scripts fail
|
||||
@@ -173,7 +172,10 @@ the Elite major's and commander's armor); version 19 sends with the game's
|
||||
settings whether co-op's players collide with each other (Server Setup's PLAYER
|
||||
COLLISIONS: each machine's players then pass through the others'); version 20
|
||||
lists a public game with a password with its invite's token sealed with the
|
||||
password's key (`p2p_lobby.c`), a listing of another layout.
|
||||
password's key (`p2p_lobby.c`), a listing of another layout; version 21
|
||||
sends each killing blow again reliably and an object come to rest three
|
||||
times (a client waits for a player's blow before its body dies without one),
|
||||
and switches co-op's BSP on the host's crossing alone.
|
||||
|
||||
A client plays by its host's rules: in another's game (searching for it,
|
||||
in its lobby, or playing it) the developer console, the telnet console
|
||||
@@ -195,18 +197,20 @@ only ever jumps forward to it when behind, so an honest one is never ahead
|
||||
while going faster (one that caught up, or a host that stalled, is one or
|
||||
the other, not both). One more than a tenth faster and half a second ahead
|
||||
has its players' predictions refused at once (the host's copies go as its
|
||||
own ticks have them), and after ten seconds of it is dropped, and every
|
||||
machine is told who, in red on its console and in its `debug.txt`. Its
|
||||
address is kept out of the host's games while the host runs only when a
|
||||
message that came over its connection's stream was that far ahead too: a
|
||||
datagram is known to be the machine's only by the address it came from,
|
||||
which another machine can send one as, so on its datagrams alone it is
|
||||
dropped, logged, and may join again
|
||||
own ticks have them). After ten seconds of it, if a message that came over
|
||||
its connection's stream was that far ahead too, it is dropped, every
|
||||
machine is told who, in red on its console and in its `debug.txt`, and its
|
||||
address is kept out of the host's games while the host runs. A datagram is
|
||||
known to be the machine's only by the address it came from, which another
|
||||
machine can send one as, so on its datagrams alone it is not dropped: its
|
||||
players' predictions stay refused while it goes on, and it is logged once,
|
||||
as unverified (dropped as above if its stream says so later)
|
||||
(`distributed_note_client_clock`, `network_game_server_kick_machine`,
|
||||
`_distributed_message_notice`). The host also adds a line to
|
||||
`cheaters.txt` beside its `debug.txt`: when, the player's address (an
|
||||
internet play peer's real one), their Discord user and their players'
|
||||
names, and why. A client tells the host its Discord user as the Discord
|
||||
names, and why (an unverified one says so). The Discord user is marked
|
||||
`(self-reported)` there and in `bans.txt`. A client tells the host its Discord user as the Discord
|
||||
client signed in on its machine says (its id and name, none without one:
|
||||
not running, or internet play off), once it is in the game and again
|
||||
when it changes; it says what it likes, so the host keeps of it only digits
|
||||
@@ -335,9 +339,12 @@ a pregame keep-alive every five seconds from the host
|
||||
4. (Done) Corrections: the host sends each client where its moving objects
|
||||
are (vehicles, items, bodies) as often as they are near that client's
|
||||
nearest player (every tick within 25 world units, every second within
|
||||
60, every third within 120, every fourth further off), once more to
|
||||
every client as an object comes to rest, and a few of those at rest,
|
||||
round them all. A client puts its copies there, and the difference is
|
||||
60, every third within 120, every fourth further off), to every client
|
||||
three times over half a second as an object comes to rest (one lost
|
||||
would leave a falling body hanging until its turn round all of them),
|
||||
and a few of those at rest, round them all. A client takes the host's
|
||||
word on whether each is at rest even when its copy is close enough to
|
||||
leave where it is. A client puts its copies there, and the difference is
|
||||
drawn fading over a few ticks (`render_interpolation.c`) instead of a
|
||||
jump. A client drives its own player's vehicle and sends where it is,
|
||||
which the host takes within a tolerance, as it does its own player's
|
||||
@@ -437,9 +444,11 @@ a pregame keep-alive every five seconds from the host
|
||||
player's screen effects to that player's machine alone, but for a
|
||||
weapon's own shake of the player firing it (no one's damage), which
|
||||
that player's machine shows itself at once. The killing blow is sent
|
||||
unreliably: an actor's body the host says is dead (the objects' states
|
||||
say so) that is still alive half a second on is killed with nothing
|
||||
to show (a player's the units' states kill).
|
||||
with the tick's other damage and once more reliably (a client replays
|
||||
one blow of a unit only), so every body falls as the host's did: a
|
||||
body the host says is dead (the objects' states say so of an actor's,
|
||||
the units' states of a player's) that is still alive half a second on
|
||||
is killed with nothing to show.
|
||||
- A client's own projectiles respond to what they hit as the game has
|
||||
them: the host's shields and health, which the client has, say
|
||||
whether the shield or the body took the hit, and how much is left of
|
||||
@@ -451,6 +460,14 @@ What reaches the other machines, and how, decides how the game feels over
|
||||
a real network as much as the model does (compared with Quake III, Source,
|
||||
Unity's Netcode for Entities, lightyear, netfox and the Ares source):
|
||||
|
||||
- **Datagrams carry only what is theirs.** A datagram is known to be a
|
||||
machine's only by the address it came from, which anyone can send one
|
||||
as. So a client takes from a datagram only what the host sends in one: its
|
||||
game's advertisement, its answer to a ping, and the distributed netcode's
|
||||
messages (checked as the host's below); the game's own messages (a player
|
||||
added or removed, the game begun or over, its settings), which the host
|
||||
sends over its connection, are ignored in a datagram
|
||||
(`network_client_message_handler.c`).
|
||||
- **Nothing held back.** The game's connections (the reliable messages:
|
||||
objects made and deleted, the game type's state, hits, pickups) send each
|
||||
write at once (`TCP_NODELAY`, in `xnet.c` for the game's sockets and in
|
||||
@@ -548,7 +565,16 @@ shortens the game, to test the next (`host:<map>:<variant>,<variant>...`
|
||||
plays the variants in turn, the next once a game is over, as the host's
|
||||
button on the scores does). `debug.network_latency` and
|
||||
`debug.network_loss` hold back what a machine receives and drop some of its
|
||||
datagrams, to test as over the internet.
|
||||
datagrams, to test as over the internet. `debug.network_corrupt` damages
|
||||
that share of the datagrams a machine receives at random (bytes changed,
|
||||
cut short, stretched to a full datagram, or replaced throughout: `xnet.c`),
|
||||
and `debug.network_corrupt_stream` that share of its reads of connections
|
||||
(a damaged connection is closed, so a little goes a long way), from
|
||||
`debug.network_corrupt_after` seconds after the start (what a host sends its
|
||||
own client over the loopback is damaged too, so the game is set up and
|
||||
started first), to test that nothing another machine sends can crash the
|
||||
game: a host and a client with a third of their datagrams damaged must play
|
||||
on, their logs noting what they refused.
|
||||
|
||||
The host logs to `debug.txt` when a player on another machine presses the
|
||||
action button where the host has nothing for them to pick up, with where it
|
||||
|
||||
@@ -274,6 +274,7 @@ the setting for one start of the game. It has priority over the file.
|
||||
| `network.stun_servers` | Google and Cloudflare | `HALO_NET_STUN` | The public STUN servers (`host:port`, with commas between them) that give the internet address of a machine. |
|
||||
| `discord.application_id` | the application of the project | `HALO_DISCORD_APPLICATION` | The Discord application for invites. Empty: no Discord. |
|
||||
| `update.auto` | `true` | `HALO_UPDATE_AUTO` | `true`: at start-up, the game looks for a new version. Refer to "Updates". `false`: the game does not look. |
|
||||
| `crash_reports.upload` | `"ask"` | `HALO_CRASH_REPORTS` | Windows only. `"yes"`: the game sends a report of each crash to the developers. `"no"`: the game sends no reports. `"ask"`: the game asks at the next crash and writes the answer here. Refer to "Crash reports" in [port/windows/README.md](../windows/README.md#crash-reports). |
|
||||
| `debug.update_answer` | `""` | `HALO_UPDATE_ANSWER` | The answer to the update question, for automatic tests: `yes`, `no` or `never`. Empty: the game asks. |
|
||||
| `debug.exit_after` | `0.0` | `HALO_EXIT_AFTER` | The game stops after this number of seconds. `0`: never. |
|
||||
| `debug.screenshot_directory`, `debug.screenshot_every` | `""`, `0` | `HALO_SCREENSHOT_DIR`, `HALO_SCREENSHOT_EVERY` | The game writes each Nth frame to this folder as a BMP file. |
|
||||
@@ -282,7 +283,7 @@ the setting for one start of the game. It has priority over the file.
|
||||
| `debug.menu_open` | `""` | `HALO_MENU_OPEN` | Start on this screen of the menus (`main_menu/settings_select/...`, as `port/assets/menus` names it), a player profile being edited, to look at it. |
|
||||
| `debug.gpu_skip_vertex_shaders`, `debug.gpu_debug_expression`, `debug.gpu_debug_flat`, `debug.gpu_debug_texture0` | off | `HALO_GPU_SKIP_VS`, `HALO_GPU_DEBUG_EXPR`, `HALO_GPU_DEBUG_FLAT`, `HALO_GPU_DEBUG_T0` | Tools to find problems in the graphics: skip the draws of a vertex shader, or replace the output of all pixel shaders with a GLSL expression (for example `t0.rgb`). |
|
||||
| `debug.network_test`, `debug.network_test_start`, `debug.network_test_kill`, `debug.network_test_score`, `debug.network_test_shoot`, `debug.network_test_vehicle`, `debug.network_test_pickup`, `debug.network_test_pickup_weapon`, `debug.test_input` | off | `HALO_NETWORK_TEST`, `HALO_NETWORK_TEST_START`, `HALO_NETWORK_TEST_KILL`, `HALO_NETWORK_TEST_SCORE`, `HALO_NETWORK_TEST_SHOOT`, `HALO_NETWORK_TEST_VEHICLE`, `HALO_NETWORK_TEST_PICKUP`, `HALO_NETWORK_TEST_PICKUP_WEAPON`, `HALO_TEST_INPUT` | Automatic tests of system link (`game/network_test.c`). Refer to `NETCODE.md`. |
|
||||
| `debug.network_latency`, `debug.network_loss` | `0` | `HALO_NETWORK_LATENCY`, `HALO_NETWORK_LOSS` | The game holds all the data that it receives for this number of milliseconds, and ignores this percentage of the datagrams. Use these settings to test the netcode as on the internet. |
|
||||
| `debug.network_latency`, `debug.network_loss`, `debug.network_corrupt`, `debug.network_corrupt_stream`, `debug.network_corrupt_after` | `0` | `HALO_NETWORK_LATENCY`, `HALO_NETWORK_LOSS`, `HALO_NETWORK_CORRUPT`, `HALO_NETWORK_CORRUPT_STREAM`, `HALO_NETWORK_CORRUPT_AFTER` | The game holds all the data that it receives for this number of milliseconds, ignores this percentage of the datagrams, and damages this percentage of the datagrams it receives, and this percentage of its reads of streams, at random (bytes changed, cut short, stretched or replaced), from this many seconds after the start. Use the first two to test the netcode as on the internet, and the others to test that nothing another machine sends can crash the game (a damaged stream is closed, so a little goes a long way; a host's messages to its own client are damaged too, so start damaging once the game has started). |
|
||||
| `debug.telnet_console`, `debug.telnet_console_port` | `false`, `2323` | `HALO_TELNET_CONSOLE`, `HALO_TELNET_CONSOLE_PORT` | The game listens on 127.0.0.1, on this port, for a script console (connect with telnet). The console has no password, so only this computer can reach it. |
|
||||
|
||||
With Mesa drivers, the game sends its GL calls through the GL thread of
|
||||
@@ -566,16 +567,17 @@ Only machines with the invite can find the game:
|
||||
last 256 keys. Thus the proof of a player does not need more key work. A
|
||||
flood of requests can make players join more slowly. A player asks again
|
||||
for 90 seconds.
|
||||
- The host drops a player whose game runs faster than time (a speed hack)
|
||||
for ten seconds. Each player sees who in red on the console. The host
|
||||
adds a line to `cheaters.txt` (beside `debug.txt`) with the address and
|
||||
hardware id of the player, and the Discord name and id that the game of
|
||||
the player told it (a player can change these). If the messages on the
|
||||
player's connection were also ahead, the host keeps that address out of
|
||||
its games and bans the player: it adds the line to `bans.txt`, and refuses
|
||||
a machine whose address or hardware id is in it. If only the player's
|
||||
datagrams were ahead, the player can join again: another machine can send
|
||||
datagrams with the player's address.
|
||||
- The host refuses the predicted movement of a player whose game runs
|
||||
faster than time (a speed hack). If the messages on the player's
|
||||
connection were also ahead for ten seconds, the host drops and bans the
|
||||
player: each player sees who in red on the console, and the host adds a
|
||||
line to `cheaters.txt` and `bans.txt` (beside `debug.txt`) with the
|
||||
address and hardware id of the player, and the Discord name and id that
|
||||
the game of the player told it, marked `(self-reported)` (a player can
|
||||
change these). The host refuses a machine whose address or hardware id is
|
||||
in `bans.txt`. If only the player's datagrams were ahead, the host does
|
||||
not drop the player, because another machine can send datagrams with the
|
||||
player's address: it adds an `unverified` line to `cheaters.txt`.
|
||||
- The host can ban a player with `ban <player name>` in the developer
|
||||
console (Tab completes the name). Remove a line from `bans.txt` to unban.
|
||||
Refer to `NETCODE.md`. `kick <player name>` drops the player the same
|
||||
@@ -643,6 +645,55 @@ the invite with the invite button of Discord. When a person accepts it, that
|
||||
person joins the game. If the game does not operate, Discord starts it.
|
||||
The game sends the activity only to a Discord client of the same user.
|
||||
|
||||
## Map checks
|
||||
|
||||
The game reads a map's tags straight into memory and uses them as its own
|
||||
structures: every pointer, count, index and enum in them is the map's, and
|
||||
the game writes values into tags as it runs. So before anything reads a
|
||||
map's tags, the port checks every tag against a schema of its group
|
||||
(`game/tag_schema_*.c`, read by `game/tag_validate.c`), and each structure
|
||||
BSP as it loads:
|
||||
|
||||
- Every block and every piece of data must lie in the tags (or the BSP) and
|
||||
overlap no other. Otherwise the game refuses the map.
|
||||
- A block with more elements than the game has room for is cut to the
|
||||
maximum. A tag reference that is not a tag of the right group becomes
|
||||
none. So do an index past its block and an enum past its values (or they
|
||||
become 0, where the game cannot take none). A string gets its terminator.
|
||||
Values that the game sets as it runs are reset.
|
||||
- Checks that the schema cannot express run last: the BSPs' and the models'
|
||||
graphs, vertex and index buffers, and indices into other tags.
|
||||
|
||||
Each correction goes to `debug.txt`. The game's own maps need none.
|
||||
`build/linux/map_validate [--strict] map.map...` runs the same checks on map
|
||||
files without the game, and `tools/test_linux_port.py` runs it on the maps
|
||||
in `assets/maps`. `map_validate --fuzz <runs> map.map` changes a few words
|
||||
of the tags at random in each run. The checks must not crash or hang, and a
|
||||
map that they let through must need no more corrections.
|
||||
|
||||
A map's scripts can call only the script functions that a map needs (the
|
||||
allowlist in `hs/hs.c`). They cannot call the functions for files, the
|
||||
saved state of the game, the console, debugging or cheats. A script that
|
||||
calls one does not run. The developer console can call every function.
|
||||
|
||||
Defensive checks stay in the game code too. An index into a tag block, the
|
||||
tags or a tag's data that is out of range gets zeros (`tag_empty_data` in
|
||||
`tag_files/tag_groups.c`), not other memory.
|
||||
|
||||
A map's name must be its file's: the cache file slots are found by the name
|
||||
in the map's header, so a map file whose header names another map (a
|
||||
renamed one) is refused, not copied again for ever. The `loading.tga` a map
|
||||
pack may put in the maps folder is read only if it is an uncompressed 24-bit
|
||||
picture of 320 by 240, the loading screen's texture.
|
||||
|
||||
A checkpoint (`savegame.bin`, in the profile's folder) and a core are
|
||||
images of the game state's memory: with the data arrays' pointers to their
|
||||
elements, the objects' memory pool's blocks and the references to them, and
|
||||
the caches' procedures. Before one is taken, each of those is checked
|
||||
against what the game made at startup (`game_state_image_accept` in
|
||||
`saved games/game_state.c`): an image that does not match (a damaged or
|
||||
crafted file) is refused, and the level starts over.
|
||||
|
||||
## What operates
|
||||
|
||||
| Area | Status |
|
||||
|
||||
@@ -32,6 +32,7 @@ fires for whoever walks into it, and what it starts is sent to the clients
|
||||
#include "hs/object_lists.h"
|
||||
#include "objects/objects.h"
|
||||
#include "objects/object_types.h"
|
||||
#include "scenario/scenario.h"
|
||||
#include "units/bipeds.h"
|
||||
#include "units/units.h"
|
||||
|
||||
@@ -98,6 +99,35 @@ boolean coop_scripts_any_player_will_do(
|
||||
return any;
|
||||
}
|
||||
|
||||
void coop_scripts_gather_in_volume(
|
||||
short trigger_volume_index,
|
||||
long object_list_index)
|
||||
{
|
||||
long reference_index;
|
||||
long object_index;
|
||||
long inside_index = NONE;
|
||||
|
||||
for (object_index = object_list_get_first(object_list_index, &reference_index);
|
||||
object_index != NONE && inside_index == NONE;
|
||||
object_index = object_list_get_next(object_list_index, &reference_index))
|
||||
{
|
||||
if (scenario_trigger_volume_test_object(trigger_volume_index, object_index))
|
||||
inside_index = object_index;
|
||||
}
|
||||
if (inside_index == NONE)
|
||||
return;
|
||||
for (object_index = object_list_get_first(object_list_index, &reference_index);
|
||||
object_index != NONE;
|
||||
object_index = object_list_get_next(object_list_index, &reference_index))
|
||||
{
|
||||
if (!scenario_trigger_volume_test_object(trigger_volume_index, object_index))
|
||||
{
|
||||
player_teleport(player_index_from_unit_index(object_index), inside_index,
|
||||
&object_get(inside_index)->object.bounding_sphere_center);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* the units of the players following player0, if `unit_index` is
|
||||
player0's; how many */
|
||||
static short players_following(
|
||||
|
||||
@@ -14,6 +14,12 @@ Campaign scripts made to work for every player in network co-op
|
||||
list as inside once any of it is, because every object in it is a player */
|
||||
boolean coop_scripts_any_player_will_do(
|
||||
long object_list_index);
|
||||
/* and when a script waits on it and one player is inside, the players of
|
||||
the list outside come beside that one, so a gate (the Maw's lift, the run
|
||||
to its bridge, a30's Pelicans) leaves no one behind */
|
||||
void coop_scripts_gather_in_volume(
|
||||
short trigger_volume_index,
|
||||
long object_list_index);
|
||||
|
||||
/* What the scripts do to player0, done to the players they can't name too
|
||||
(the "followers"). Each does nothing unless `unit_index` is player0's in a
|
||||
|
||||
@@ -1438,6 +1438,32 @@ static boolean color_choose(void)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* ---------- Edit Profile Settings: its picture of Gamepad Setup, the
|
||||
profile's button settings as the Xbox's Controller Setup shows them */
|
||||
|
||||
/* the picture's frames (the bitmap's: tools/port_settings.py's
|
||||
BITMAP_FRAMES): Gamepad Setup's row's, as the list numbers its rows
|
||||
(player_profile_edit_select_menu_update_extended_description), and after
|
||||
the PC version's nine the Xbox's five of the button settings */
|
||||
#define PROFILE_GAMEPAD_FRAME 2
|
||||
#define PROFILE_FIRST_LAYOUT_FRAME 9
|
||||
|
||||
/* "port gamepad layout preview" (the picture's own): on Gamepad Setup's
|
||||
row, the edited profile's button settings */
|
||||
static void profile_gamepad_layout(struct widget_instance *picture)
|
||||
{
|
||||
struct player_profile *profile = player_ui_get_edit_player_profile();
|
||||
short frame = picture->animation.current_frame_index;
|
||||
|
||||
if (frame == PROFILE_GAMEPAD_FRAME ||
|
||||
(frame >= PROFILE_FIRST_LAYOUT_FRAME && frame < PROFILE_FIRST_LAYOUT_FRAME + NUMBER_OF_BUTTON_PRESETS))
|
||||
{
|
||||
picture->animation.current_frame_index = (short)(PROFILE_FIRST_LAYOUT_FRAME +
|
||||
(profile && profile->controller_settings.button_preset < NUMBER_OF_BUTTON_PRESETS ?
|
||||
profile->controller_settings.button_preset : _button_preset_standard));
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- Profiles: the player profiles, and a row to make one (as the
|
||||
PC version's list has them, but by the Xbox's names for its spinner's
|
||||
functions); choosing one makes it player 1's, the profile the campaign and
|
||||
@@ -5392,4 +5418,6 @@ void pc_menu_game_data_function_invoke(
|
||||
profile_list_update(widget);
|
||||
else if (!strcmp(name, "load game list update"))
|
||||
saved_game_list_update(widget);
|
||||
else if (!strcmp(name, "port gamepad layout preview"))
|
||||
profile_gamepad_layout(widget);
|
||||
}
|
||||
@@ -65,6 +65,7 @@ char const *pc_menus_root_name(void);
|
||||
char const *pc_menu_function_name(long function_index);
|
||||
char const *pc_menu_game_data_input_name(long function_index);
|
||||
boolean pc_menu_tag(long tag_index);
|
||||
boolean pc_menu_frame_placement(struct bitmap_data const *bitmap, short *x, short *y, short *width, short *height);
|
||||
|
||||
/* ---------- constants */
|
||||
|
||||
@@ -240,6 +241,15 @@ typedef char verify_ui_widget_definition_conditional_widgets_offset[
|
||||
typedef char verify_ui_widget_definition_size[
|
||||
sizeof(struct ui_widget_definition) == 0x3EC ? 1 : -1];
|
||||
|
||||
/* a frame of ui.map's that a menu file scales: where in its widget, and at
|
||||
what size, it is drawn (pc_menu_frame_placement) */
|
||||
struct menu_frame_placement
|
||||
{
|
||||
struct bitmap_data const *bitmap;
|
||||
short x, y;
|
||||
short width, height;
|
||||
};
|
||||
|
||||
/* a spinner bound to a setting (menu_functions.c) */
|
||||
struct pc_menu_setting
|
||||
{
|
||||
@@ -381,6 +391,9 @@ static char const *const port_game_data_input_names[] =
|
||||
row; the Xbox's read only player_ui's gametype, not Server Setup's) */
|
||||
"game settings lists text update", "get edit game settings name", "mp edit profile set rule text",
|
||||
"port password update",
|
||||
/* (the profile settings' picture: the Xbox's of the button settings,
|
||||
on Gamepad Setup's row) */
|
||||
"port gamepad layout preview",
|
||||
};
|
||||
|
||||
static struct
|
||||
@@ -390,6 +403,8 @@ static struct
|
||||
long block_count;
|
||||
struct bitmap_data **bitmaps;
|
||||
long bitmap_count;
|
||||
struct menu_frame_placement *placements;
|
||||
long placement_count;
|
||||
struct cache_file_tag_instance *original_instances;
|
||||
long original_count;
|
||||
struct pc_menu_setting *settings;
|
||||
@@ -431,7 +446,17 @@ static void *allocate(long size)
|
||||
return NULL;
|
||||
}
|
||||
memset(block, 0, size > 0 ? size : 1);
|
||||
menu_tags.blocks = realloc(menu_tags.blocks, (menu_tags.block_count + 1) * sizeof(*menu_tags.blocks));
|
||||
{
|
||||
void **blocks = realloc(menu_tags.blocks, (menu_tags.block_count + 1) * sizeof(*menu_tags.blocks));
|
||||
|
||||
if (!blocks)
|
||||
{
|
||||
free(block);
|
||||
build.failed = TRUE;
|
||||
return NULL;
|
||||
}
|
||||
menu_tags.blocks = blocks;
|
||||
}
|
||||
menu_tags.blocks[menu_tags.block_count++] = block;
|
||||
return block;
|
||||
}
|
||||
@@ -793,6 +818,24 @@ static void *bitmap_build(struct halo_menu_bitmap const *source, long tag_index)
|
||||
memcpy(bitmap, (struct bitmap_data *)group_source->bitmaps.address + data->index, sizeof(*bitmap));
|
||||
bitmap->cache_block_index = NONE;
|
||||
bitmap->base_address = NULL;
|
||||
/* (scaled to a size of the file's: the texture stays the map's,
|
||||
drawn smaller or larger, ui_widget.c) */
|
||||
if (data->width > 0 && data->height > 0 && data->width <= 2048 && data->height <= 2048)
|
||||
{
|
||||
struct menu_frame_placement *placements = realloc(menu_tags.placements,
|
||||
(menu_tags.placement_count + 1) * sizeof(*menu_tags.placements));
|
||||
|
||||
if (placements)
|
||||
{
|
||||
menu_tags.placements = placements;
|
||||
placements[menu_tags.placement_count].bitmap = bitmap;
|
||||
placements[menu_tags.placement_count].x = (short)PIN(data->x, -2048, 2048);
|
||||
placements[menu_tags.placement_count].y = (short)PIN(data->y, -2048, 2048);
|
||||
placements[menu_tags.placement_count].width = (short)data->width;
|
||||
placements[menu_tags.placement_count].height = (short)data->height;
|
||||
menu_tags.placement_count++;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1307,7 +1350,9 @@ static void instance_set(struct cache_file_tag_instance *instances, long group_t
|
||||
instance->parent_group_tags[0] = NONE;
|
||||
instance->parent_group_tags[1] = NONE;
|
||||
instance->tag_index = tag_index;
|
||||
instance->name = copy;
|
||||
/* (the name is read by tag_loaded: of none, when the copy failed, which
|
||||
fails the build) */
|
||||
instance->name = copy ? copy : "";
|
||||
instance->base_address = definition;
|
||||
}
|
||||
|
||||
@@ -1330,6 +1375,8 @@ static void menu_tags_release(void)
|
||||
free(menu_tags.blocks);
|
||||
if (menu_tags.bitmaps)
|
||||
free(menu_tags.bitmaps);
|
||||
if (menu_tags.placements)
|
||||
free(menu_tags.placements);
|
||||
if (menu_tags.settings)
|
||||
free(menu_tags.settings);
|
||||
memset(&menu_tags, 0, sizeof(menu_tags));
|
||||
@@ -1642,6 +1689,8 @@ void menu_tags_loaded(
|
||||
build.spinner_tags = malloc((widget_count + 1) * sizeof(long));
|
||||
build.bitmap_tags = malloc((menus->bitmap_count + 1) * sizeof(long));
|
||||
build.strings_tags = malloc((menus->string_list_count + 1) * sizeof(long));
|
||||
if (!build.widget_tags || !build.text_tags || !build.spinner_tags || !build.bitmap_tags || !build.strings_tags)
|
||||
goto failed;
|
||||
for (index = 0; index < widget_count; index++)
|
||||
{
|
||||
own_lists += (menus->widgets[index].text != NULL) + (menus->widgets[index].strings != NULL);
|
||||
@@ -1796,6 +1845,32 @@ boolean pc_menu_tag(
|
||||
DATUM_INDEX_TO_ABSOLUTE_INDEX(tag_index) >= menu_tags.original_count;
|
||||
}
|
||||
|
||||
/* where in its widget, and how large, a frame of ui.map's that a menu file
|
||||
scales is drawn (<frame map=... width= height= x= y=>); FALSE for any other
|
||||
bitmap, drawn as it is (ui_widget.c) */
|
||||
boolean pc_menu_frame_placement(
|
||||
struct bitmap_data const *bitmap,
|
||||
short *x,
|
||||
short *y,
|
||||
short *width,
|
||||
short *height)
|
||||
{
|
||||
long index;
|
||||
|
||||
for (index = 0; index < menu_tags.placement_count; index++)
|
||||
{
|
||||
if (menu_tags.placements[index].bitmap == bitmap)
|
||||
{
|
||||
*x = menu_tags.placements[index].x;
|
||||
*y = menu_tags.placements[index].y;
|
||||
*width = menu_tags.placements[index].width;
|
||||
*height = menu_tags.placements[index].height;
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
char const *pc_menu_function_name(
|
||||
long function_index)
|
||||
{
|
||||
|
||||
@@ -522,7 +522,8 @@ static boolean actor_state_apply(
|
||||
SET_FLAG(unit->unit.flags, _unit_super_camouflaged_bit,
|
||||
TEST_FLAG(state->flags, _distributed_actor_super_camouflaged_bit));
|
||||
unit->unit.active_camouflage = (real)state->active_camouflage / 255.0f;
|
||||
if (state->team != NO_TEAM)
|
||||
/* (the game's 10 teams, game_allegiance.c) */
|
||||
if (state->team != NO_TEAM && state->team < 10)
|
||||
unit->object.owner_team_index = state->team;
|
||||
SET_FLAG(unit->unit.flags, _unit_running_blindly_bit,
|
||||
TEST_FLAG(state->flags, _distributed_actor_running_blindly_bit));
|
||||
|
||||
@@ -155,7 +155,8 @@ enum
|
||||
/* each event is sent in this many ticks' messages */
|
||||
EVENT_SENDS = 3,
|
||||
|
||||
/* the size of devices.c's device group array */
|
||||
/* the size of devices.c's device group array (devices_initialize's;
|
||||
every index into the arrays below is checked against this too) */
|
||||
MAXIMUM_DEVICE_GROUPS = 1024,
|
||||
/* a changed group is sent in this many ticks' messages */
|
||||
DEVICE_GROUP_SENDS = 3,
|
||||
@@ -179,6 +180,9 @@ enum
|
||||
MAXIMUM_DROPPED_VEHICLES = 5,
|
||||
};
|
||||
|
||||
/* (devices.c's literal count: a change there is one here) */
|
||||
typedef char device_groups_count_assert[MAXIMUM_DEVICE_GROUPS == 1024 ? 1 : -1];
|
||||
|
||||
/* distributed_coop_event.kind */
|
||||
enum
|
||||
{
|
||||
@@ -829,6 +833,7 @@ static short device_group_find(
|
||||
{
|
||||
group_index = entry->group_index;
|
||||
return group_index >= 0 && group_index < global_scenario_get()->device_groups.count &&
|
||||
group_index < MAXIMUM_DEVICE_GROUPS &&
|
||||
device_group_network_get(group_index, &value, &flags, &runtime) && !runtime ? group_index : NONE;
|
||||
}
|
||||
device_index = object_find(entry->name_index, entry->object_index, entry->definition_index, _object_mask_device);
|
||||
@@ -1747,7 +1752,8 @@ static void client_apply_attach(
|
||||
struct distributed_coop_event const *event)
|
||||
{
|
||||
long parent_index = object_find(event->name_index, event->object_index, event->definition_index, _object_mask_all);
|
||||
long child_index = object_find((short)event->reals[0], event->target, event->tag_index, _object_mask_all);
|
||||
long child_index = object_find((short)PIN(event->reals[0], -1.0f, 32767.0f), event->target, event->tag_index,
|
||||
_object_mask_all);
|
||||
long ancestor_index;
|
||||
|
||||
if (parent_index == NONE || child_index == NONE || parent_index == child_index)
|
||||
|
||||
@@ -2093,6 +2093,38 @@ static void distributed_log_reports(
|
||||
logged_rejected = damage_rejected_reports;
|
||||
}
|
||||
|
||||
/* the tick's killing blows once more, reliably, to every client: sent with
|
||||
the rest they may be lost, and a body killed without its blow falls without
|
||||
the death the host's had (a client replays one blow of a unit only) */
|
||||
static void distributed_send_kills_reliably(
|
||||
void)
|
||||
{
|
||||
struct distributed_damage_event_message message;
|
||||
short limit = MIN(MAXIMUM_ENTRIES_PER_MESSAGE, RELIABLE_ENTRIES(struct distributed_damage_event));
|
||||
short count = 0;
|
||||
short index;
|
||||
|
||||
for (index = 0; index < damage_event_count; index++)
|
||||
{
|
||||
if (damage_events[index].kind != _damage_event_kill)
|
||||
continue;
|
||||
message.events[count++] = damage_events[index];
|
||||
if (count == limit)
|
||||
{
|
||||
distributed_send(&message, _distributed_message_damage_events, count,
|
||||
(word)(sizeof(message.header) + count * sizeof(struct distributed_damage_event)),
|
||||
_distributed_to_clients_reliably);
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
if (count)
|
||||
{
|
||||
distributed_send(&message, _distributed_message_damage_events, count,
|
||||
(word)(sizeof(message.header) + count * sizeof(struct distributed_damage_event)),
|
||||
_distributed_to_clients_reliably);
|
||||
}
|
||||
}
|
||||
|
||||
void network_damage_host_tick(
|
||||
void)
|
||||
{
|
||||
@@ -2131,6 +2163,7 @@ void network_damage_host_tick(
|
||||
(word)(sizeof(message.header) + count * sizeof(struct distributed_damage_event)));
|
||||
}
|
||||
}
|
||||
distributed_send_kills_reliably();
|
||||
damage_event_count = 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -163,6 +163,10 @@ enum
|
||||
(or the other way round) before it is put where the host has it: its
|
||||
own prediction reaches the host and comes back in about a round trip */
|
||||
SEAT_DISAGREEMENT_TICKS = 15,
|
||||
/* how long a client waits for a player's killing blow once the host's
|
||||
states say the player died, before the unit dies without it (an
|
||||
actor's waits as long: network_objects.c) */
|
||||
DEATH_BLOW_WAIT_TICKS = TICKS_PER_SECOND / 2,
|
||||
/* the machines, and the host: a client's messages' sender */
|
||||
MAXIMUM_SENDERS = HALO_PORT_MAXIMUM_NETWORK_MACHINES + 1,
|
||||
HOST_SENDER = HALO_PORT_MAXIMUM_NETWORK_MACHINES,
|
||||
@@ -462,6 +466,10 @@ static struct distributed_death
|
||||
short killing_player_index;
|
||||
boolean friendly_fire;
|
||||
boolean killed_by_vehicle;
|
||||
/* a client: when the host's states first said the player's living unit
|
||||
here was dead (NONE: not), to wait for the killing blow
|
||||
(DEATH_BLOW_WAIT_TICKS) */
|
||||
long dead_since;
|
||||
} distributed_deaths[MAXIMUM_TRACKED_PLAYERS];
|
||||
/* the host: a kill this tick, whose statistics the clients should have
|
||||
with it */
|
||||
@@ -606,6 +614,8 @@ static struct distributed_client_clock
|
||||
short fast_windows;
|
||||
boolean fast;
|
||||
boolean ahead_on_stream;
|
||||
/* (its datagrams' word logged, unverified, while they go on saying so) */
|
||||
boolean logged_unverified;
|
||||
} distributed_client_clocks[HALO_PORT_MAXIMUM_NETWORK_MACHINES];
|
||||
/* the host: whether the message being handled came over its machine's
|
||||
stream (network_distributed_handle_stream_message) */
|
||||
@@ -2151,12 +2161,22 @@ static void distributed_handle_unit_state(
|
||||
unit_index = distributed_living_unit(player);
|
||||
if (!alive)
|
||||
{
|
||||
/* died on the host (who counts it; the damage that killed it,
|
||||
network_damage.c, usually kills it here first) */
|
||||
if (unit_index != NONE)
|
||||
unit_kill_no_statistics(unit_index);
|
||||
/* died on the host, who counts it. Its killing blow (network_damage.c)
|
||||
kills it here with the death the host's had, so it is given a while
|
||||
to come before the unit dies without one, as it falls. */
|
||||
if (unit_index != NONE && state->player_index < MAXIMUM_TRACKED_PLAYERS)
|
||||
{
|
||||
long *dead_since = &distributed_deaths[state->player_index].dead_since;
|
||||
|
||||
if (*dead_since == NONE)
|
||||
*dead_since = game_time_get();
|
||||
else if (game_time_get() - *dead_since >= DEATH_BLOW_WAIT_TICKS)
|
||||
unit_kill_no_statistics(unit_index);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (state->player_index < MAXIMUM_TRACKED_PLAYERS)
|
||||
distributed_deaths[state->player_index].dead_since = NONE;
|
||||
/* spawned on the host: the host's unit is the player's here too, once
|
||||
this machine has it (network_objects.c) */
|
||||
if (state->unit_index == NONE || !network_objects_client_has(state->unit_index) ||
|
||||
@@ -2178,7 +2198,8 @@ static void distributed_handle_unit_state(
|
||||
unit_index = state->unit_index;
|
||||
/* the seat it rides: a client's own player's, once it has ridden
|
||||
otherwise for longer than its prediction takes to reach the host and
|
||||
come back */
|
||||
come back (a player within the tracked ones, as players.c's datums are) */
|
||||
if (state->player_index < MAXIMUM_TRACKED_PLAYERS)
|
||||
{
|
||||
struct unit_datum *unit = unit_get(unit_index);
|
||||
long vehicle_index = unit->object.parent_object_index != NONE && unit->unit.parent_seat_index != NONE ?
|
||||
@@ -2811,8 +2832,8 @@ static void distributed_handle_actions(
|
||||
action.control_flags = relayed.control_flags[0];
|
||||
action.desired_facing.yaw = distributed_angle_unpack(relayed.yaw, FALSE);
|
||||
action.desired_facing.pitch = distributed_angle_unpack(relayed.pitch, TRUE);
|
||||
action.throttle.i = (real)relayed.throttle_i / 127.0f;
|
||||
action.throttle.j = (real)relayed.throttle_j / 127.0f;
|
||||
action.throttle.i = PIN((real)relayed.throttle_i / 127.0f, -1.0f, 1.0f);
|
||||
action.throttle.j = PIN((real)relayed.throttle_j / 127.0f, -1.0f, 1.0f);
|
||||
action.primary_trigger = (real)relayed.primary_trigger / 255.0f;
|
||||
action.desired_weapon_index = relayed.desired_weapon_index;
|
||||
action.desired_grenade_index = relayed.desired_grenade_index;
|
||||
@@ -3238,6 +3259,7 @@ void network_distributed_new_game(
|
||||
distributed_host_update_number = NONE;
|
||||
for (player_index = 0; player_index < MAXIMUM_TRACKED_PLAYERS; player_index++)
|
||||
{
|
||||
distributed_deaths[player_index].dead_since = NONE;
|
||||
distributed_sent_units[player_index].flags = 0;
|
||||
distributed_sent_units[player_index].unit_index = NONE;
|
||||
distributed_sent_units[player_index].vehicle_index = NONE;
|
||||
@@ -3582,9 +3604,13 @@ static void distributed_write_player_record(
|
||||
error(_error_log, "could not open %s to add a player to it", file_name);
|
||||
return;
|
||||
}
|
||||
fprintf(file, "%s\tip=%s\thwid=%s\tdiscord_username=%s\tdiscord_id=%s\tplayers=%s\treason=%s\n", when,
|
||||
kept_address, hardware_id[0] ? hardware_id : "none", discord_name[0] ? discord_name : "none",
|
||||
discord_id[0] ? discord_id : "none", kept_names, kept_reason);
|
||||
/* (the Discord user as the machine told it, which it may say is anyone's:
|
||||
marked so) */
|
||||
fprintf(file, "%s\tip=%s\thwid=%s\tdiscord_username=%s%s\tdiscord_id=%s%s\tplayers=%s\treason=%s\n", when,
|
||||
kept_address, hardware_id[0] ? hardware_id : "none",
|
||||
discord_name[0] ? discord_name : "none", discord_name[0] ? " (self-reported)" : "",
|
||||
discord_id[0] ? discord_id : "none", discord_id[0] ? " (self-reported)" : "",
|
||||
kept_names, kept_reason);
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
@@ -3749,13 +3775,14 @@ static void distributed_note_client_clock(
|
||||
{
|
||||
clock->fast_windows = 0;
|
||||
clock->ahead_on_stream = FALSE;
|
||||
clock->logged_unverified = FALSE;
|
||||
}
|
||||
else if (++clock->fast_windows == 1)
|
||||
{
|
||||
error(_error_log, "machine #%ld's game runs %.2f times as fast as this host's (%ld ticks ahead): "
|
||||
"its players' predictions refused", machine_index, rate, ahead);
|
||||
}
|
||||
else if (clock->fast_windows >= CLIENT_CLOCK_FAST_WINDOWS)
|
||||
else if (clock->fast_windows >= CLIENT_CLOCK_FAST_WINDOWS && clock->ahead_on_stream)
|
||||
{
|
||||
error(_error_log, "machine #%ld's game ran %.2f times as fast as this host's for %d seconds "
|
||||
"(%ld ticks ahead): dropped", machine_index, rate,
|
||||
@@ -3775,31 +3802,41 @@ static void distributed_note_client_clock(
|
||||
distributed_client_identities[machine_index].discord_name, 1);
|
||||
if (discord_id[0] || discord_name[0])
|
||||
snprintf(discord, sizeof(discord), " (Discord: %s, %s)", discord_name, discord_id);
|
||||
snprintf(reason, sizeof(reason), "speed hack (game ran %.2f times as fast)%s", rate,
|
||||
clock->ahead_on_stream ? "" : " (its datagrams only: not banned)");
|
||||
snprintf(reason, sizeof(reason), "speed hack (game ran %.2f times as fast)", rate);
|
||||
snprintf(text, sizeof(text), "%s%s kicked by the host: their game ran %.2f times as fast (a speed hack)",
|
||||
names, discord, rate);
|
||||
distributed_send_notice(text);
|
||||
/* banned, and kept out, only when its stream said so
|
||||
too; on its datagrams' word alone (which another machine
|
||||
could have sent as from its address), logged and dropped,
|
||||
and it may join again */
|
||||
if (clock->ahead_on_stream)
|
||||
{
|
||||
distributed_log_cheater(machine_index, names, reason);
|
||||
}
|
||||
else
|
||||
{
|
||||
char address[32];
|
||||
|
||||
distributed_machine_address_text(machine_index, address, sizeof(address));
|
||||
distributed_write_player_record(CHEATERS_FILE, address, machine_index, names, reason);
|
||||
}
|
||||
distributed_log_cheater(machine_index, names, reason);
|
||||
}
|
||||
network_game_server_kick_machine(machine_index, clock->ahead_on_stream);
|
||||
network_game_server_kick_machine(machine_index, TRUE);
|
||||
clock->fast_windows = 0;
|
||||
clock->ahead_on_stream = FALSE;
|
||||
}
|
||||
/* dropped, banned and kept out only when its stream said so too; on
|
||||
its datagrams' word alone (which another machine could have sent as
|
||||
from its address, to have it dropped and logged as a cheater) it
|
||||
stays, its players' predictions refused while they say so, and is
|
||||
logged once, unverified (dropped as above if its stream says so later) */
|
||||
else if (clock->fast_windows >= CLIENT_CLOCK_FAST_WINDOWS)
|
||||
{
|
||||
clock->fast_windows = CLIENT_CLOCK_FAST_WINDOWS;
|
||||
if (!clock->logged_unverified)
|
||||
{
|
||||
char names[64];
|
||||
char reason[96];
|
||||
char address[32];
|
||||
|
||||
error(_error_log, "machine #%ld's datagrams said its game ran %.2f times as fast as this host's "
|
||||
"for %d seconds (%ld ticks ahead), its stream not: not dropped (unverified)", machine_index, rate,
|
||||
CLIENT_CLOCK_FAST_WINDOWS * CLIENT_CLOCK_WINDOW_MILLISECONDS / 1000, ahead);
|
||||
distributed_machine_player_names(machine_index, names, sizeof(names));
|
||||
snprintf(reason, sizeof(reason), "unverified speed hack (its datagrams only, not dropped: "
|
||||
"game ran %.2f times as fast)", rate);
|
||||
distributed_machine_address_text(machine_index, address, sizeof(address));
|
||||
distributed_write_player_record(CHEATERS_FILE, address, machine_index, names, reason);
|
||||
clock->logged_unverified = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
clock->window_tick = clock->latest_tick;
|
||||
clock->window_time = now;
|
||||
@@ -3814,6 +3851,12 @@ boolean distributed_machine_clock_fast(
|
||||
distributed_client_clocks[machine_index].fast;
|
||||
}
|
||||
|
||||
/* whether the messages handled now came in a batch (the unreliable ones;
|
||||
one sent reliably comes on its own): a killing blow sent again reliably is
|
||||
not overtaken by the newer damage sent with the ticks since
|
||||
(distributed_message_stale) */
|
||||
static boolean distributed_handling_batch;
|
||||
|
||||
/* a message of the distributed kind; machine_index is the sender's on the
|
||||
host, NONE on a client */
|
||||
void network_distributed_handle_message(
|
||||
@@ -3852,7 +3895,11 @@ void network_distributed_handle_message(
|
||||
offset += length;
|
||||
/* (no batch in a batch) */
|
||||
if (((struct distributed_message_header const *)buffer)->type != _distributed_message_batch)
|
||||
{
|
||||
distributed_handling_batch = TRUE;
|
||||
network_distributed_handle_message(machine_index, buffer, (word)(sizeof(message_header) + length));
|
||||
distributed_handling_batch = FALSE;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -3944,8 +3991,11 @@ void network_distributed_handle_message(
|
||||
distributed_host_time = header.game_time;
|
||||
break;
|
||||
}
|
||||
if (distributed_message_stale(machine_index, &header))
|
||||
if ((distributed_handling_batch || header.type != _distributed_message_damage_events) &&
|
||||
distributed_message_stale(machine_index, &header))
|
||||
{
|
||||
return;
|
||||
}
|
||||
/* (the host: a client's clock, by its messages' ticks; and its players'
|
||||
predictions not taken while its game runs fast) */
|
||||
if (machine_index != NONE)
|
||||
|
||||
@@ -116,6 +116,11 @@ enum
|
||||
EMPTY_INVENTORY_TICKS = 3 * INVENTORY_REFRESH_TICKS,
|
||||
/* the host's objects at rest sent each tick, round them all */
|
||||
RESTING_STATES_PER_TICK = 4,
|
||||
/* an object come to rest is sent to every client this many times, this
|
||||
many ticks apart: one lost would leave a falling body hanging until its
|
||||
turn round all of them, which in a large game is many seconds */
|
||||
REST_STATE_REPEATS = 3,
|
||||
REST_STATE_REPEAT_TICKS = 8,
|
||||
/* a client asks for the host's objects again until it has them (a host
|
||||
still loading misses the asking), after this, twice as long each time
|
||||
up to the most */
|
||||
@@ -321,9 +326,11 @@ static long objects_host_told[MAXIMUM_TRACKED_OBJECTS];
|
||||
static long objects_host_resting_cursor;
|
||||
/* ... past the highest of them */
|
||||
static long objects_host_told_count;
|
||||
/* ... whether each was moving at the last tick (one come to rest is sent
|
||||
once more, to every client) */
|
||||
/* ... whether each was moving at the last tick, and when each last came to
|
||||
rest (NONE: not since it was told of), to send it to every client
|
||||
(REST_STATE_REPEATS) */
|
||||
static boolean objects_host_state_moving[MAXIMUM_TRACKED_OBJECTS];
|
||||
static long objects_host_rest_times[MAXIMUM_TRACKED_OBJECTS];
|
||||
/* ... what each unit's inventory was last sent as (in all, and its weapons
|
||||
and grenades), when to every client, and when its ammunition alone last
|
||||
changed (NONE: not since); when it last carried anything (NONE: never) */
|
||||
@@ -1353,12 +1360,22 @@ enum
|
||||
_host_state_to_all,
|
||||
};
|
||||
|
||||
/* whether an object at rest is due to every client again: it came to rest
|
||||
this tick, or REST_STATE_REPEAT_TICKS after one of its sends */
|
||||
static boolean distributed_host_rest_state_due(
|
||||
long absolute_index)
|
||||
{
|
||||
long since = game_time_get() - objects_host_rest_times[absolute_index];
|
||||
|
||||
return objects_host_rest_times[absolute_index] != NONE &&
|
||||
since < REST_STATE_REPEATS * REST_STATE_REPEAT_TICKS && since % REST_STATE_REPEAT_TICKS == 0;
|
||||
}
|
||||
|
||||
/* where the moving objects are, to each client those near its players
|
||||
every tick, those further less often, and the vehicle it drives itself
|
||||
(which it has already) every few ticks, with which of its ticks the host
|
||||
has it at; to every client each as it comes to rest, and a few at rest,
|
||||
round them all (one whose last move was lost is put right when its turn
|
||||
comes) */
|
||||
has it at; to every client each as it comes to rest (REST_STATE_REPEATS
|
||||
times), and a few at rest, round them all */
|
||||
static void distributed_host_send_states(
|
||||
void)
|
||||
{
|
||||
@@ -1386,7 +1403,9 @@ static void distributed_host_send_states(
|
||||
continue;
|
||||
at_rest = TEST_FLAG(object_get(object_index)->object.flags, _object_at_rest_bit);
|
||||
objects_host_state_moving[absolute_index] = !at_rest;
|
||||
if (at_rest && !was_moving)
|
||||
if (at_rest && was_moving)
|
||||
objects_host_rest_times[absolute_index] = game_time_get();
|
||||
if (at_rest && !distributed_host_rest_state_due(absolute_index))
|
||||
continue;
|
||||
distributed_state_from_object(object_index, &states[state_count]);
|
||||
kinds[state_count++] = at_rest ? _host_state_to_all : _host_state_moving;
|
||||
@@ -2133,6 +2152,12 @@ static boolean distributed_client_change_valid(
|
||||
team_count = game_engine_get_variant()->game_engine_index == game_engine_ctf ? CTF_FLAG_TEAMS : ODDBALL_TEAMS;
|
||||
if (change->owner_team_index != NONE && (change->owner_team_index < 0 || change->owner_team_index >= team_count))
|
||||
return FALSE;
|
||||
/* (a flag or ball of no team would index the game type's arrays at -1) */
|
||||
if (type == _object_type_weapon && ((weapon_definition_get(change->definition_index)->weapon.flags >> 3) & 1) &&
|
||||
change->owner_team_index == NONE)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
/* (its colors numbers) */
|
||||
{
|
||||
short color_index;
|
||||
@@ -2459,6 +2484,8 @@ void network_objects_handle_states(
|
||||
real blend_distance = REMOTE_BLEND_DISTANCE;
|
||||
real_vector3d forward, up, velocity, angular_velocity;
|
||||
real dx, dy, dz;
|
||||
boolean own_vehicle = FALSE;
|
||||
boolean at_rest;
|
||||
|
||||
if (!distributed_object_index_valid(state->object_index) || !network_objects_client_has(state->object_index))
|
||||
continue;
|
||||
@@ -2517,8 +2544,15 @@ void network_objects_handle_states(
|
||||
/* (turned as it drives it) */
|
||||
angle_tolerance = -1.0f;
|
||||
blend_distance = 0.0f;
|
||||
own_vehicle = TRUE;
|
||||
}
|
||||
}
|
||||
/* (at rest as the host has it, however close: a copy left at rest
|
||||
while the host's falls hangs there; but not one this machine drives,
|
||||
which the host's word, a round trip old, would stop as it sets off) */
|
||||
at_rest = TEST_FLAG(state->flags, _distributed_object_at_rest_bit) || distributed_vehicle_unsteered(state->object_index);
|
||||
if (!own_vehicle)
|
||||
SET_FLAG(object->object.flags, _object_at_rest_bit, at_rest);
|
||||
dx = state->position.x - object->object.position.x;
|
||||
dy = state->position.y - object->object.position.y;
|
||||
dz = state->position.z - object->object.position.z;
|
||||
@@ -2535,8 +2569,9 @@ void network_objects_handle_states(
|
||||
{
|
||||
distributed_count_correction();
|
||||
}
|
||||
SET_FLAG(object->object.flags, _object_at_rest_bit, TEST_FLAG(state->flags, _distributed_object_at_rest_bit) ||
|
||||
distributed_vehicle_unsteered(state->object_index));
|
||||
/* (one it drives, put where the host has it, is as the host has it) */
|
||||
if (own_vehicle)
|
||||
SET_FLAG(object->object.flags, _object_at_rest_bit, at_rest);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2846,6 +2881,24 @@ void network_objects_set_seat(
|
||||
{
|
||||
return;
|
||||
}
|
||||
/* (and not one the unit carries, however far down: a ring of
|
||||
parents would turn every walk up them forever) */
|
||||
{
|
||||
long ancestor_index = vehicle->object.parent_object_index;
|
||||
long steps;
|
||||
|
||||
for (steps = 0; ancestor_index != NONE; steps++)
|
||||
{
|
||||
struct object_header_datum const *ancestor;
|
||||
|
||||
if (ancestor_index == unit_index || steps >= 16)
|
||||
return;
|
||||
ancestor = (struct object_header_datum const *)datum_try_and_get(object_header_data, ancestor_index);
|
||||
if (!ancestor || !ancestor->datum)
|
||||
return;
|
||||
ancestor_index = ancestor->datum->object.parent_object_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (unit->object.parent_object_index != NONE && unit->unit.parent_seat_index != NONE)
|
||||
unit_exit_seat_end(unit_index);
|
||||
@@ -3053,6 +3106,7 @@ void network_objects_new_game(
|
||||
{
|
||||
objects_host_told[absolute_index] = NONE;
|
||||
objects_host_state_moving[absolute_index] = FALSE;
|
||||
objects_host_rest_times[absolute_index] = NONE;
|
||||
objects_host_inventories[absolute_index].carried_time = NONE;
|
||||
objects_host_inventories[absolute_index].ammunition_time = NONE;
|
||||
objects_client_has[absolute_index] = NONE;
|
||||
|
||||
@@ -0,0 +1,333 @@
|
||||
/*
|
||||
TAG_SCHEMA.H
|
||||
|
||||
What a map's tags are checked by before the game uses them (tag_validate.c):
|
||||
a schema of each tag group, written with the game's own structures so that
|
||||
every offset and size is the compiler's.
|
||||
|
||||
A definition is a structure (a tag's root, a tag block's element or a
|
||||
structure inside one) and its fields: only the fields that hold a pointer, a
|
||||
count, an index, an enum, a tag or a runtime value, which are what the game
|
||||
trusts. Plain numbers and flags are not listed. A field names its place with
|
||||
TAG_SCHEMA_OFFSET, which fails to compile if the field is not of the type the
|
||||
schema says it is.
|
||||
|
||||
What happens to a field that is wrong:
|
||||
- a block or data whose bytes leave the tags, or overlap other tags' (so that
|
||||
a runtime value written to one would land in another): the map is refused,
|
||||
as nothing after it can be trusted;
|
||||
- a block with more elements than the game has room for: its count is cut to
|
||||
that maximum;
|
||||
- a tag reference or tag index that is not a tag of the right group: NONE;
|
||||
- a block index past its block: NONE where the game takes NONE, otherwise 0
|
||||
(NONE if the block is empty);
|
||||
- an enum past its values: its first value (NONE where the game takes NONE);
|
||||
- a string without its terminator: terminated;
|
||||
- a runtime value (one the game sets as it runs): reset, always.
|
||||
Each correction is logged. The retail maps need none (tools/map_validate.c).
|
||||
*/
|
||||
|
||||
#ifndef __TAG_SCHEMA_H
|
||||
#define __TAG_SCHEMA_H
|
||||
#pragma once
|
||||
|
||||
/* ---------- headers */
|
||||
|
||||
#include "tag_files/tag_groups.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
/* ---------- constants */
|
||||
|
||||
enum tag_schema_field_type
|
||||
{
|
||||
/* a struct tag_block: its elements are definition's */
|
||||
_tag_schema_block,
|
||||
/* a struct tag_data in the tags: its bytes are at most maximum */
|
||||
_tag_schema_data,
|
||||
/* a struct tag_data whose bytes are in the map file (a bitmap's pixels, a
|
||||
sound's samples), at its file offset: they are at most maximum, in the
|
||||
file */
|
||||
_tag_schema_file_data,
|
||||
/* a struct tag_reference to a tag of one of groups */
|
||||
_tag_schema_reference,
|
||||
/* a long tag index alone, of one of groups */
|
||||
_tag_schema_tag_index,
|
||||
/* an index into a block (see target_level) */
|
||||
_tag_schema_block_index,
|
||||
/* an enum of maximum values */
|
||||
_tag_schema_enum,
|
||||
/* a string of size bytes, terminated */
|
||||
_tag_schema_string,
|
||||
/* a runtime value of size bytes, reset to value */
|
||||
_tag_schema_reset,
|
||||
/* a structure inline: definition's */
|
||||
_tag_schema_struct,
|
||||
/* a check the schema cannot say: check is called on the element once
|
||||
every tag has been through the schema, so it may look at other tags */
|
||||
_tag_schema_check,
|
||||
_tag_schema_terminator,
|
||||
NUMBER_OF_TAG_SCHEMA_FIELD_TYPES
|
||||
};
|
||||
|
||||
enum tag_schema_field_flags
|
||||
{
|
||||
/* an index, enum, tag or tag index that may be NONE */
|
||||
_tag_schema_none_bit = 0,
|
||||
/* (set by the macros) an index or enum of an unsigned type, whose NONE
|
||||
is all ones */
|
||||
_tag_schema_unsigned_bit,
|
||||
NUMBER_OF_TAG_SCHEMA_FIELD_FLAGS
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
/* a block index's block is in the tag's root */
|
||||
TAG_SCHEMA_ROOT = -1,
|
||||
/* a block index's block is in the structure (_tag_schema_struct) the
|
||||
index is in */
|
||||
TAG_SCHEMA_STRUCTURE = -2,
|
||||
/* (0 is the block element the index is in, 1 the one its block is in,
|
||||
and so on) */
|
||||
|
||||
MAXIMUM_TAG_SCHEMA_GROUPS_PER_FIELD = 15,
|
||||
};
|
||||
|
||||
/* ---------- macros */
|
||||
|
||||
/* where field is in type, if it is a field_type (it fails to compile
|
||||
otherwise) */
|
||||
#define TAG_SCHEMA_OFFSET(type, field, field_type) \
|
||||
(offsetof(type, field) + \
|
||||
0 * sizeof(char[__builtin_types_compatible_p(__typeof__(((type *)0)->field), field_type) ? 1 : -1]))
|
||||
|
||||
/* where field is in type, if it is an integer (of 1, 2 or 4 bytes) */
|
||||
#define TAG_SCHEMA_INTEGER_OFFSET(type, field) \
|
||||
(offsetof(type, field) + \
|
||||
0 * sizeof(char[(sizeof(((type *)0)->field) == 1 || sizeof(((type *)0)->field) == 2 || \
|
||||
sizeof(((type *)0)->field) == 4) ? 1 : -1]))
|
||||
|
||||
#define TAG_SCHEMA_FIELD_SIZE(type, field) ((short)sizeof(((type *)0)->field))
|
||||
|
||||
/* _tag_schema_unsigned_bit if field is of an unsigned type */
|
||||
#define TAG_SCHEMA_UNSIGNED(type, field) \
|
||||
((__typeof__(((type *)0)->field))-1 > 0 ? FLAG(_tag_schema_unsigned_bit) : 0)
|
||||
|
||||
/* a definition: its name, its structure and its fields, which end with
|
||||
TAG_SCHEMA_END */
|
||||
#define TAG_SCHEMA_DEFINITION(name, type, fields) \
|
||||
{ #name, sizeof(type), (fields) }
|
||||
|
||||
#define TAG_SCHEMA_BLOCK(type, field, definition, maximum) \
|
||||
{ _tag_schema_block, sizeof(struct tag_block), 1, 0, \
|
||||
TAG_SCHEMA_OFFSET(type, field, struct tag_block), (maximum), 0, 0, &(definition), NULL, #field }
|
||||
|
||||
#define TAG_SCHEMA_DATA(type, field, maximum) \
|
||||
{ _tag_schema_data, sizeof(struct tag_data), 1, 0, \
|
||||
TAG_SCHEMA_OFFSET(type, field, struct tag_data), (maximum), 0, 0, NULL, NULL, #field }
|
||||
|
||||
#define TAG_SCHEMA_FILE_DATA(type, field, maximum) \
|
||||
{ _tag_schema_file_data, sizeof(struct tag_data), 1, 0, \
|
||||
TAG_SCHEMA_OFFSET(type, field, struct tag_data), (maximum), 0, 0, NULL, NULL, #field }
|
||||
|
||||
/* groups: a list of group tags ending with 0, e.g. TAG_SCHEMA_GROUPS('obje') */
|
||||
#define TAG_SCHEMA_GROUPS(...) ((unsigned long const []){ __VA_ARGS__, 0 })
|
||||
|
||||
/* a reference may always be NONE */
|
||||
#define TAG_SCHEMA_REFERENCE(type, field, groups) \
|
||||
{ _tag_schema_reference, sizeof(struct tag_reference), 1, FLAG(_tag_schema_none_bit), \
|
||||
TAG_SCHEMA_OFFSET(type, field, struct tag_reference), 0, 0, 0, (groups), NULL, #field }
|
||||
|
||||
#define TAG_SCHEMA_REFERENCE_ARRAY(type, field, groups) \
|
||||
{ _tag_schema_reference, sizeof(struct tag_reference), \
|
||||
(short)(sizeof(((type *)0)->field) / sizeof(struct tag_reference)), FLAG(_tag_schema_none_bit), \
|
||||
TAG_SCHEMA_OFFSET(type, field[0], struct tag_reference), 0, 0, 0, (groups), NULL, #field }
|
||||
|
||||
#define TAG_SCHEMA_TAG_INDEX(type, field, groups) \
|
||||
{ _tag_schema_tag_index, 4, 1, FLAG(_tag_schema_none_bit), \
|
||||
TAG_SCHEMA_OFFSET(type, field, long), 0, 0, 0, (groups), NULL, #field }
|
||||
|
||||
/* an index into the block at target_offset in the element or structure
|
||||
target_level names; flags are _tag_schema_none_bit's or 0 */
|
||||
#define TAG_SCHEMA_BLOCK_INDEX(type, field, target_level, target_offset, flags) \
|
||||
{ _tag_schema_block_index, TAG_SCHEMA_FIELD_SIZE(type, field), 1, (flags) | TAG_SCHEMA_UNSIGNED(type, field), \
|
||||
TAG_SCHEMA_INTEGER_OFFSET(type, field), 0, (target_level), (target_offset), NULL, NULL, #field }
|
||||
|
||||
/* an array of them */
|
||||
#define TAG_SCHEMA_BLOCK_INDEX_ARRAY(type, field, target_level, target_offset, flags) \
|
||||
{ _tag_schema_block_index, TAG_SCHEMA_FIELD_SIZE(type, field[0]), \
|
||||
(short)(sizeof(((type *)0)->field) / sizeof(((type *)0)->field[0])), \
|
||||
(flags) | TAG_SCHEMA_UNSIGNED(type, field[0]), \
|
||||
TAG_SCHEMA_INTEGER_OFFSET(type, field[0]), 0, (target_level), (target_offset), NULL, NULL, #field }
|
||||
|
||||
#define TAG_SCHEMA_ENUM(type, field, count, flags) \
|
||||
{ _tag_schema_enum, TAG_SCHEMA_FIELD_SIZE(type, field), 1, (flags) | TAG_SCHEMA_UNSIGNED(type, field), \
|
||||
TAG_SCHEMA_INTEGER_OFFSET(type, field), (count), 0, 0, NULL, NULL, #field }
|
||||
|
||||
#define TAG_SCHEMA_ENUM_ARRAY(type, field, count, flags) \
|
||||
{ _tag_schema_enum, TAG_SCHEMA_FIELD_SIZE(type, field[0]), \
|
||||
(short)(sizeof(((type *)0)->field) / sizeof(((type *)0)->field[0])), \
|
||||
(flags) | TAG_SCHEMA_UNSIGNED(type, field[0]), \
|
||||
TAG_SCHEMA_INTEGER_OFFSET(type, field[0]), (count), 0, 0, NULL, NULL, #field }
|
||||
|
||||
#define TAG_SCHEMA_STRING(type, field) \
|
||||
{ _tag_schema_string, TAG_SCHEMA_FIELD_SIZE(type, field), 1, 0, \
|
||||
offsetof(type, field), 0, 0, 0, NULL, NULL, #field }
|
||||
|
||||
/* a runtime value of the field's size (up to 4 bytes) reset to value */
|
||||
#define TAG_SCHEMA_RESET(type, field, value) \
|
||||
{ _tag_schema_reset, TAG_SCHEMA_FIELD_SIZE(type, field), 1, 0, \
|
||||
TAG_SCHEMA_INTEGER_OFFSET(type, field), 0, 0, (value), NULL, NULL, #field }
|
||||
|
||||
/* a runtime structure (any size) zeroed */
|
||||
#define TAG_SCHEMA_ZERO(type, field) \
|
||||
{ _tag_schema_reset, TAG_SCHEMA_FIELD_SIZE(type, field), 1, 0, \
|
||||
offsetof(type, field), 0, 0, 0, NULL, NULL, #field }
|
||||
|
||||
#define TAG_SCHEMA_STRUCT(type, field, definition) \
|
||||
{ _tag_schema_struct, TAG_SCHEMA_FIELD_SIZE(type, field), 1, 0, \
|
||||
offsetof(type, field), 0, 0, 0, &(definition), NULL, #field }
|
||||
|
||||
#define TAG_SCHEMA_STRUCT_ARRAY(type, field, definition) \
|
||||
{ _tag_schema_struct, TAG_SCHEMA_FIELD_SIZE(type, field[0]), \
|
||||
(short)(sizeof(((type *)0)->field) / sizeof(((type *)0)->field[0])), 0, \
|
||||
offsetof(type, field[0]), 0, 0, 0, &(definition), NULL, #field }
|
||||
|
||||
#define TAG_SCHEMA_CHECK(check) \
|
||||
{ _tag_schema_check, 0, 1, 0, 0, 0, 0, 0, NULL, (check), #check }
|
||||
|
||||
#define TAG_SCHEMA_END \
|
||||
{ _tag_schema_terminator, 0, 0, 0, 0, 0, 0, 0, NULL, NULL, NULL }
|
||||
|
||||
/* ---------- structures */
|
||||
|
||||
struct tag_validation;
|
||||
struct tag_schema_definition;
|
||||
|
||||
/* a check on one element (base) of the definition it is in: FALSE refuses
|
||||
the map (tag_validate_refuse says why) */
|
||||
typedef boolean (*tag_schema_check_proc)(
|
||||
struct tag_validation *validation,
|
||||
void *base);
|
||||
|
||||
struct tag_schema_field
|
||||
{
|
||||
short type;
|
||||
/* the integer's bytes; a block's, reference's or structure's stride */
|
||||
short size;
|
||||
/* how many there are, one after another (an array) */
|
||||
short count;
|
||||
short flags;
|
||||
long offset;
|
||||
/* a block's most elements, data's most bytes, an enum's values */
|
||||
long maximum;
|
||||
/* a block index's block: where it is (TAG_SCHEMA_ROOT,
|
||||
TAG_SCHEMA_STRUCTURE, or how many elements up) and its offset there */
|
||||
long target_level;
|
||||
/* a block index's block's offset; a reset's value */
|
||||
long target_offset;
|
||||
/* a block's or structure's definition; a reference's or tag index's
|
||||
groups */
|
||||
void const *definition;
|
||||
tag_schema_check_proc check;
|
||||
char const *name;
|
||||
};
|
||||
|
||||
struct tag_schema_definition
|
||||
{
|
||||
char const *name;
|
||||
long size;
|
||||
struct tag_schema_field const *fields;
|
||||
};
|
||||
|
||||
struct tag_schema_group
|
||||
{
|
||||
unsigned long group_tag;
|
||||
unsigned long parent_group_tags[2];
|
||||
struct tag_schema_definition const *definition;
|
||||
};
|
||||
|
||||
/* ---------- prototypes/TAG_SCHEMA_*.C */
|
||||
|
||||
/* the groups the validator knows: lists of them (each ending with a 0 group
|
||||
tag), one for each tag_schema_*.c, ending with NULL */
|
||||
extern struct tag_schema_group const *const tag_schema_group_lists[];
|
||||
|
||||
extern struct tag_schema_group const tag_schema_object_groups[];
|
||||
extern struct tag_schema_group const tag_schema_model_groups[];
|
||||
extern struct tag_schema_group const tag_schema_collision_groups[];
|
||||
extern struct tag_schema_group const tag_schema_render_groups[];
|
||||
extern struct tag_schema_group const tag_schema_effect_groups[];
|
||||
extern struct tag_schema_group const tag_schema_scenario_groups[];
|
||||
|
||||
/* ---------- prototypes/TAG_VALIDATE.C */
|
||||
|
||||
/* the tags at tag_header (tag_data_size bytes read from a map file of
|
||||
file_length bytes): FALSE if they cannot be used. Before anything else
|
||||
reads them; corrections are made in place */
|
||||
boolean tag_validate_tags(
|
||||
void *tag_header,
|
||||
long tag_data_size,
|
||||
long file_length,
|
||||
char const *map_name);
|
||||
|
||||
/* a structure bsp just read (size bytes at base, its header there) as tag
|
||||
tag_index: FALSE if it cannot be used */
|
||||
boolean tag_validate_structure_bsp(
|
||||
long tag_index,
|
||||
void *base,
|
||||
long size);
|
||||
|
||||
/* how many corrections the last validation made (none for a retail map) */
|
||||
long tag_validate_corrections(
|
||||
void);
|
||||
/* whether the last validation found address to be in a tag's root, block or
|
||||
data (tools/map_validate.c) */
|
||||
boolean tag_validate_claimed(
|
||||
void const *address);
|
||||
|
||||
/* for checks: */
|
||||
|
||||
/* the map is refused: why, printf style */
|
||||
void tag_validate_refuse(
|
||||
struct tag_validation *validation,
|
||||
char const *format,
|
||||
...);
|
||||
/* a correction was made: what, printf style */
|
||||
void tag_validate_correct(
|
||||
struct tag_validation *validation,
|
||||
char const *format,
|
||||
...);
|
||||
/* the map file's length, which data in the file (_tag_schema_file_data)
|
||||
lies within */
|
||||
long tag_validate_file_length(
|
||||
struct tag_validation *validation);
|
||||
/* whether size bytes at address are in the tags (or the bsp) being checked */
|
||||
boolean tag_validate_contains(
|
||||
struct tag_validation *validation,
|
||||
void const *address,
|
||||
unsigned long size);
|
||||
/* the root of tag tag_index if it is a tag of group (or inherits from it),
|
||||
otherwise NULL */
|
||||
void *tag_validate_tag_get(
|
||||
struct tag_validation *validation,
|
||||
long tag_index,
|
||||
unsigned long group_tag);
|
||||
/* the vertex or index buffer (D3DVertexBuffer, D3DIndexBuffer) a model's or
|
||||
bsp's buffer points at, if it is one of its header's: its data's address,
|
||||
otherwise NULL. A bsp's "index buffers" are its lightmaps' vertex buffers
|
||||
(the second array of its header). The data is only read: it is not a block
|
||||
of the tags, and may lie where another tag is */
|
||||
void *tag_validate_vertex_buffer_data(
|
||||
struct tag_validation *validation,
|
||||
void const *buffer);
|
||||
void *tag_validate_index_buffer_data(
|
||||
struct tag_validation *validation,
|
||||
void const *buffer);
|
||||
|
||||
/* ---------- prototypes/where the validator's messages go (tag_validate_report.c) */
|
||||
|
||||
void tag_validate_report(
|
||||
char const *message);
|
||||
|
||||
#endif // __TAG_SCHEMA_H
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
TAG_SCHEMA_GROUPS.C
|
||||
|
||||
The tag groups' schemas (tag_schema.h), from each tag_schema_*.c.
|
||||
*/
|
||||
|
||||
/* ---------- headers */
|
||||
|
||||
#include "cseries.h"
|
||||
#include "tag_schema.h"
|
||||
|
||||
/* ---------- globals */
|
||||
|
||||
struct tag_schema_group const *const tag_schema_group_lists[] =
|
||||
{
|
||||
tag_schema_object_groups,
|
||||
tag_schema_model_groups,
|
||||
tag_schema_collision_groups,
|
||||
tag_schema_render_groups,
|
||||
tag_schema_effect_groups,
|
||||
tag_schema_scenario_groups,
|
||||
NULL
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
TAG_VALIDATE_REPORT.C
|
||||
|
||||
Where the tag validator's messages go in the game (tag_validate.c): the
|
||||
debug log. (tools/map_validate.c prints them.)
|
||||
*/
|
||||
|
||||
/* ---------- headers */
|
||||
|
||||
#include "cseries.h"
|
||||
#include "errors.h"
|
||||
#include "tag_schema.h"
|
||||
|
||||
/* ---------- public code */
|
||||
|
||||
void tag_validate_report(
|
||||
char const *message)
|
||||
{
|
||||
error(_error_silent, "%s", message);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -25,13 +25,16 @@ numbered from here, clear of the Xbox's (0 to 101 and 0 to 40) and the PC's
|
||||
#define PC_MENU_FUNCTION_BASE 256
|
||||
|
||||
/* a frame: a PNG drawn at width by height, or the map's bitmap's frame
|
||||
(map: its bitmap group tag, index: the bitmap's number in it) */
|
||||
(map: its bitmap group tag, index: the bitmap's number in it), drawn as the
|
||||
map has it or, with a width and height, scaled to that size, x and y into
|
||||
the widget */
|
||||
struct halo_menu_frame
|
||||
{
|
||||
char const *png;
|
||||
char const *map;
|
||||
long index;
|
||||
long width, height;
|
||||
long x, y;
|
||||
};
|
||||
|
||||
struct halo_menu_bitmap
|
||||
|
||||
@@ -66,7 +66,7 @@ is newer (network_client_manager.c). A host advertises it, with its netcode,
|
||||
in its game's advertisement's reserved bytes (network_server_message_handler.c),
|
||||
which hosts built before there was a version send as zeros: version 0.
|
||||
Raise it with any change to what the machines send each other. */
|
||||
#define HALO_PORT_NETWORK_VERSION 20
|
||||
#define HALO_PORT_NETWORK_VERSION 21
|
||||
/* ... the advertisement's reserved bytes: the version (a little-endian word),
|
||||
then flags */
|
||||
#define HALO_PORT_ADVERTISED_VERSION_OFFSET 0
|
||||
|
||||
@@ -252,6 +252,39 @@ int halo_vr_controller_layout(void);
|
||||
/* the hand that holds the weapon (HALO_XR_HAND_*: 1 the right, 0 the left,
|
||||
vr.handedness) */
|
||||
int halo_vr_weapon_hand(void);
|
||||
/* The game's actions on the controllers' buttons (vr.button_*; the VR
|
||||
options' BUTTONS page): each action on one of seven buttons, named as
|
||||
right-handed (A and B on the right controller, X and Y, the Quest's, on
|
||||
the left; the move stick's click, the turning stick's, the weapon hand's
|
||||
grip); left-handed with vr.mirror_controls, their mirror images. The
|
||||
actions are numbered by the button each has by default. The triggers, the
|
||||
free hand's grip, the menu button and the Steam Frame's bumpers, D-pad and
|
||||
View button keep theirs; the menus keep the buttons as they are. */
|
||||
enum
|
||||
{
|
||||
HALO_VR_INPUT_A = 0,
|
||||
HALO_VR_INPUT_B,
|
||||
HALO_VR_INPUT_X,
|
||||
HALO_VR_INPUT_Y,
|
||||
HALO_VR_INPUT_MOVE_STICK,
|
||||
HALO_VR_INPUT_TURN_STICK,
|
||||
HALO_VR_INPUT_GRIP,
|
||||
HALO_VR_INPUT_COUNT,
|
||||
|
||||
HALO_VR_ACTION_JUMP = HALO_VR_INPUT_A,
|
||||
HALO_VR_ACTION_LIGHT = HALO_VR_INPUT_B, /* a tap the flashlight; held, switch grenades */
|
||||
HALO_VR_ACTION_ACTION = HALO_VR_INPUT_X, /* action, reload */
|
||||
HALO_VR_ACTION_WEAPON = HALO_VR_INPUT_Y, /* switch weapon */
|
||||
HALO_VR_ACTION_CROUCH = HALO_VR_INPUT_MOVE_STICK,
|
||||
HALO_VR_ACTION_ZOOM = HALO_VR_INPUT_TURN_STICK,
|
||||
HALO_VR_ACTION_MELEE = HALO_VR_INPUT_GRIP,
|
||||
HALO_VR_ACTION_COUNT = HALO_VR_INPUT_COUNT,
|
||||
};
|
||||
/* an action's setting ("vr.button_jump"), a button's value in it ("a"),
|
||||
and the button an action is on, as set */
|
||||
const char *halo_vr_action_setting(int action);
|
||||
const char *halo_vr_input_value(int input);
|
||||
int halo_vr_action_input(int action);
|
||||
/* the graphics preset (vr.graphics; HALO_VR_GRAPHICS_HIGH outside VR): which
|
||||
effects are drawn (port/linux/game/vr_graphics.c), and with "max" the
|
||||
models lit for each pixel and larger shadows (d3d8_gl.c) */
|
||||
|
||||
@@ -352,6 +352,12 @@ struct vertex_shader_object
|
||||
/* a shader lit for each pixel failed to compile or link: lit as the
|
||||
vertex shader lights it from then on */
|
||||
BOOL lighting_failed;
|
||||
#ifndef HALO_ANDROID
|
||||
/* the vertex array its draws last used, and the streams they had
|
||||
(setup_streams: the layout follows from the two) */
|
||||
struct vertex_array_entry *vertex_array;
|
||||
unsigned long vertex_array_streams;
|
||||
#endif
|
||||
};
|
||||
|
||||
/* ---------- programs */
|
||||
@@ -552,6 +558,13 @@ struct gl_device
|
||||
float query_area[VISIBILITY_TEST_SLOTS];
|
||||
GLuint active_query;
|
||||
BOOL visibility_test_active;
|
||||
/* (queries read on the CPU) each slot's latest count known, and whether
|
||||
its query has yet to be read: the game spins on a result it is told is
|
||||
incomplete, so a query is read only once it says it is available, and
|
||||
until then the slot's earlier count stands. A frame of The Library's
|
||||
lights makes hundreds of tests. */
|
||||
GLuint visibility_known[VISIBILITY_TEST_SLOTS];
|
||||
BOOL visibility_unread[VISIBILITY_TEST_SLOTS];
|
||||
#ifdef HALO_ANDROID
|
||||
/* with atomic counters: one counter per test, used as a ring; the
|
||||
counter a test ended in, per result slot */
|
||||
@@ -676,6 +689,30 @@ struct vertex_binding
|
||||
|
||||
/* a binding per stream (setup_streams); GL has at least 16 */
|
||||
#define VERTEX_BINDING_COUNT 16
|
||||
|
||||
/* a vertex array object for each vertex layout (the attributes a draw
|
||||
enables, and each one's format and binding), made once: a draw binds its
|
||||
layout's and points the bindings at its streams, rather than enabling,
|
||||
formatting and binding each attribute again (setup_streams) */
|
||||
struct vertex_layout
|
||||
{
|
||||
unsigned long enabled;
|
||||
struct attribute_format formats[XGPU_VERTEX_ATTRIBUTE_COUNT];
|
||||
};
|
||||
|
||||
struct vertex_array_entry
|
||||
{
|
||||
struct vertex_array_entry *next;
|
||||
unsigned long hash;
|
||||
struct vertex_layout layout;
|
||||
GLuint vertex_array;
|
||||
/* its bindings, as last pointed (nothing else changes them) */
|
||||
struct vertex_binding bindings[VERTEX_BINDING_COUNT];
|
||||
};
|
||||
|
||||
#define VERTEX_ARRAY_BUCKET_COUNT 64
|
||||
static struct vertex_array_entry *vertex_array_buckets[VERTEX_ARRAY_BUCKET_COUNT];
|
||||
static struct vertex_array_entry *current_vertex_array;
|
||||
#endif
|
||||
|
||||
static struct
|
||||
@@ -710,8 +747,7 @@ static struct
|
||||
#ifdef HALO_ANDROID
|
||||
struct attribute_pointer attribute_pointers[XGPU_VERTEX_ATTRIBUTE_COUNT];
|
||||
#else
|
||||
struct attribute_format attribute_formats[XGPU_VERTEX_ATTRIBUTE_COUNT];
|
||||
struct vertex_binding vertex_bindings[VERTEX_BINDING_COUNT];
|
||||
GLuint vertex_array;
|
||||
#endif
|
||||
/* a disabled attribute's value; kind 1 is the integer zero */
|
||||
unsigned char attribute_value_kind[XGPU_VERTEX_ATTRIBUTE_COUNT];
|
||||
@@ -754,9 +790,16 @@ static void state_framebuffer(GLuint framebuffer)
|
||||
}
|
||||
}
|
||||
|
||||
/* gl_state.textures' slot of a target */
|
||||
static int texture_slot(GLenum target)
|
||||
{
|
||||
return target == GL_TEXTURE_CUBE_MAP ? 1 : target == GL_TEXTURE_3D ? 2 : 0;
|
||||
}
|
||||
|
||||
#ifdef HALO_ANDROID
|
||||
static void state_texture(int unit, GLenum target, GLuint texture)
|
||||
{
|
||||
int slot = target == GL_TEXTURE_CUBE_MAP ? 1 : target == GL_TEXTURE_3D ? 2 : 0;
|
||||
int slot = texture_slot(target);
|
||||
|
||||
if (gl_state.textures[unit][slot] == texture)
|
||||
return;
|
||||
@@ -768,6 +811,7 @@ static void state_texture(int unit, GLenum target, GLuint texture)
|
||||
gl_state.textures[unit][slot] = texture;
|
||||
glBindTexture(target, texture);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void state_sampler(int unit, GLuint sampler)
|
||||
{
|
||||
@@ -796,15 +840,13 @@ static void state_element_array_buffer(GLuint buffer)
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HALO_ANDROID
|
||||
/* enables the attribute, reading size elements of type from buffer: each
|
||||
vertex is stride bytes on from the one before it, starting at
|
||||
buffer_offset, with the attribute relative_offset bytes into it.
|
||||
Attributes given the same binding share the buffer, its offset and its
|
||||
stride (on desktop GL; ES points each attribute on its own). */
|
||||
buffer_offset, with the attribute relative_offset bytes into it */
|
||||
static void state_attribute_stream(GLuint index, GLuint binding, GLuint buffer, GLint size, GLenum type,
|
||||
GLboolean normalized, BOOL integer, GLsizei stride, unsigned long buffer_offset, unsigned long relative_offset)
|
||||
{
|
||||
#ifdef HALO_ANDROID
|
||||
struct attribute_pointer *pointer = &gl_state.attribute_pointers[index];
|
||||
unsigned long offset = buffer_offset + relative_offset;
|
||||
|
||||
@@ -832,53 +874,106 @@ static void state_attribute_stream(GLuint index, GLuint binding, GLuint buffer,
|
||||
pointer->integer = integer ? GL_TRUE : GL_FALSE;
|
||||
pointer->stride = stride;
|
||||
pointer->offset = offset;
|
||||
}
|
||||
#else
|
||||
struct attribute_format *format = &gl_state.attribute_formats[index];
|
||||
struct vertex_binding *vertex_binding = &gl_state.vertex_bindings[binding];
|
||||
/* the vertex array of a layout, made the first time (its attributes enabled,
|
||||
formatted and bound to their bindings) */
|
||||
static struct vertex_array_entry *vertex_array_get(const struct vertex_layout *layout)
|
||||
{
|
||||
const unsigned char *bytes = (const unsigned char *)layout;
|
||||
unsigned long hash = 2166136261UL, index;
|
||||
struct vertex_array_entry **bucket, *entry;
|
||||
|
||||
if (gl_state.attribute_enabled[index] != 1)
|
||||
for (index = 0; index < sizeof(*layout); index++)
|
||||
hash = (hash ^ bytes[index]) * 16777619UL;
|
||||
bucket = &vertex_array_buckets[hash % VERTEX_ARRAY_BUCKET_COUNT];
|
||||
for (entry = *bucket; entry; entry = entry->next)
|
||||
{
|
||||
gl_state.attribute_enabled[index] = 1;
|
||||
glEnableVertexAttribArray(index);
|
||||
if (entry->hash == hash && !memcmp(&entry->layout, layout, sizeof(*layout)))
|
||||
return entry;
|
||||
}
|
||||
if (format->size != size || format->type != type || format->normalized != normalized ||
|
||||
format->integer != (integer ? GL_TRUE : GL_FALSE) || format->relative_offset != relative_offset)
|
||||
entry = calloc(1, sizeof(*entry));
|
||||
entry->hash = hash;
|
||||
entry->layout = *layout;
|
||||
memset(entry->bindings, 0xff, sizeof(entry->bindings));
|
||||
glGenVertexArrays(1, &entry->vertex_array);
|
||||
glBindVertexArray(entry->vertex_array);
|
||||
gl_state.vertex_array = entry->vertex_array;
|
||||
/* (the index buffer's binding is the vertex array's) */
|
||||
gl_state.element_array_buffer = (GLuint)-1;
|
||||
for (index = 0; index < XGPU_VERTEX_ATTRIBUTE_COUNT; index++)
|
||||
{
|
||||
if (integer)
|
||||
glVertexAttribIFormat(index, size, type, (GLuint)relative_offset);
|
||||
const struct attribute_format *format = &layout->formats[index];
|
||||
|
||||
if (!(layout->enabled & (1UL << index)))
|
||||
continue;
|
||||
glEnableVertexAttribArray((GLuint)index);
|
||||
if (format->integer)
|
||||
glVertexAttribIFormat((GLuint)index, format->size, format->type, format->relative_offset);
|
||||
else
|
||||
glVertexAttribFormat(index, size, type, normalized, (GLuint)relative_offset);
|
||||
format->size = size;
|
||||
format->type = type;
|
||||
format->normalized = normalized;
|
||||
format->integer = integer ? GL_TRUE : GL_FALSE;
|
||||
format->relative_offset = (GLuint)relative_offset;
|
||||
glVertexAttribFormat((GLuint)index, format->size, format->type, format->normalized, format->relative_offset);
|
||||
glVertexAttribBinding((GLuint)index, format->binding);
|
||||
}
|
||||
if (format->binding != binding)
|
||||
entry->next = *bucket;
|
||||
*bucket = entry;
|
||||
return entry;
|
||||
}
|
||||
|
||||
static void state_vertex_array(struct vertex_array_entry *entry)
|
||||
{
|
||||
if (gl_state.vertex_array != entry->vertex_array)
|
||||
{
|
||||
glVertexAttribBinding(index, binding);
|
||||
format->binding = binding;
|
||||
gl_state.vertex_array = entry->vertex_array;
|
||||
glBindVertexArray(entry->vertex_array);
|
||||
/* (the index buffer's binding is the vertex array's) */
|
||||
gl_state.element_array_buffer = (GLuint)-1;
|
||||
}
|
||||
if (vertex_binding->buffer != buffer || vertex_binding->offset != buffer_offset || vertex_binding->stride != stride)
|
||||
current_vertex_array = entry;
|
||||
}
|
||||
|
||||
/* points the bound vertex array's binding at buffer: each vertex is stride
|
||||
bytes on from the one before it, starting at offset */
|
||||
static void state_vertex_buffer(GLuint binding, GLuint buffer, unsigned long offset, GLsizei stride)
|
||||
{
|
||||
struct vertex_binding *vertex_binding = ¤t_vertex_array->bindings[binding];
|
||||
|
||||
if (vertex_binding->buffer != buffer || vertex_binding->offset != offset || vertex_binding->stride != stride)
|
||||
{
|
||||
glBindVertexBuffer(binding, buffer, (GLintptr)buffer_offset, stride);
|
||||
glBindVertexBuffer(binding, buffer, (GLintptr)offset, stride);
|
||||
vertex_binding->buffer = buffer;
|
||||
vertex_binding->offset = buffer_offset;
|
||||
vertex_binding->offset = offset;
|
||||
vertex_binding->stride = stride;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* an attribute of a layout */
|
||||
static void layout_attribute(struct vertex_layout *layout, unsigned long index, GLuint binding, GLint size,
|
||||
GLenum type, GLboolean normalized, BOOL integer, unsigned long relative_offset)
|
||||
{
|
||||
struct attribute_format *format = &layout->formats[index];
|
||||
|
||||
format->size = size;
|
||||
format->type = type;
|
||||
format->normalized = normalized;
|
||||
format->integer = integer ? GL_TRUE : GL_FALSE;
|
||||
format->relative_offset = (GLuint)relative_offset;
|
||||
format->binding = binding;
|
||||
layout->enabled |= 1UL << index;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* disables the attribute, which then reads value, or the integer zero */
|
||||
static void state_attribute_value(GLuint index, const float *value)
|
||||
{
|
||||
unsigned char kind = value ? 0 : 1;
|
||||
|
||||
#ifdef HALO_ANDROID
|
||||
if (gl_state.attribute_enabled[index] != 0)
|
||||
{
|
||||
gl_state.attribute_enabled[index] = 0;
|
||||
glDisableVertexAttribArray(index);
|
||||
}
|
||||
#endif
|
||||
if (gl_state.attribute_value_kind[index] == kind &&
|
||||
(!value || !memcmp(gl_state.attribute_values[index], value, sizeof(gl_state.attribute_values[index]))))
|
||||
{
|
||||
@@ -1491,6 +1586,9 @@ static void render_target_multisample(struct xgpu_render_target *target, int sam
|
||||
xgpu_gl_state_invalidate();
|
||||
}
|
||||
|
||||
/* counts draws and clears into render targets (xgpu_render_target.written) */
|
||||
static unsigned long render_target_write_serial;
|
||||
|
||||
/* the pixels per unit of the bound targets (render_target_get) */
|
||||
static float target_scale[2] = { 1.0f, 1.0f };
|
||||
|
||||
@@ -1528,7 +1626,10 @@ static BOOL bind_targets(BOOL *has_depth)
|
||||
if (!color && !depth)
|
||||
return FALSE;
|
||||
if (color)
|
||||
{
|
||||
color->last_rendered = device.frame + 1;
|
||||
color->target.written = ++render_target_write_serial;
|
||||
}
|
||||
/* viewports and clears are in the targets' units (render_target_get) */
|
||||
target_scale[0] = color ? color->target.scale[0] : depth->target.scale[0];
|
||||
target_scale[1] = color ? color->target.scale[1] : depth->target.scale[1];
|
||||
@@ -2239,6 +2340,7 @@ HRESULT WINAPI D3DDevice_EndVisibilityTest(DWORD index)
|
||||
device.queries[0] = device.queries[index];
|
||||
device.queries[index] = scratch;
|
||||
device.query_pending[index] = TRUE;
|
||||
device.visibility_unread[index] = TRUE;
|
||||
#ifndef HALO_ANDROID
|
||||
if (device.visibility_results)
|
||||
{
|
||||
@@ -2330,21 +2432,30 @@ HRESULT WINAPI D3DDevice_GetVisibilityTestResult(DWORD index, UINT *result, ULON
|
||||
return S_OK;
|
||||
}
|
||||
#endif
|
||||
glGetQueryObjectuiv(device.queries[index], GL_QUERY_RESULT_AVAILABLE, &available);
|
||||
if (!available)
|
||||
return D3DERR_TESTINCOMPLETE;
|
||||
glGetQueryObjectuiv(device.queries[index], GL_QUERY_RESULT, &samples);
|
||||
/* the latest count known: from this test, or while the GPU is still
|
||||
behind, from the slot's earlier ones */
|
||||
if (device.visibility_unread[index])
|
||||
{
|
||||
glGetQueryObjectuiv(device.queries[index], GL_QUERY_RESULT_AVAILABLE, &available);
|
||||
if (available)
|
||||
{
|
||||
glGetQueryObjectuiv(device.queries[index], GL_QUERY_RESULT, &samples);
|
||||
#ifdef HALO_ANDROID
|
||||
/* ES only says whether any sample passed. The game divides the count by
|
||||
the test's area (lens flare brightness, rasterizer_lights.c): report
|
||||
more than any test covers, well below what would overflow there. */
|
||||
if (samples)
|
||||
samples = VISIBILITY_ALL_SAMPLES;
|
||||
/* ES only says whether any sample passed. The game divides the
|
||||
count by the test's area (lens flare brightness,
|
||||
rasterizer_lights.c): report more than any test covers, well
|
||||
below what would overflow there. */
|
||||
if (samples)
|
||||
samples = VISIBILITY_ALL_SAMPLES;
|
||||
#else
|
||||
samples = visibility_unscaled(samples, index);
|
||||
samples = visibility_unscaled(samples, index);
|
||||
#endif
|
||||
device.visibility_known[index] = samples;
|
||||
device.visibility_unread[index] = FALSE;
|
||||
}
|
||||
}
|
||||
if (result)
|
||||
*result = samples;
|
||||
*result = device.visibility_known[index];
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
@@ -2891,40 +3002,30 @@ static GLenum address_mode(DWORD mode)
|
||||
}
|
||||
}
|
||||
|
||||
/* hires: a high-res HUD texture (hud_hires.h), drawn smaller than it is, so
|
||||
filtered and from its mip levels whatever the game asks: the HUD's meters are
|
||||
point sampled for one player, to keep the Xbox bitmaps' texels sharp */
|
||||
static void configure_sampler(int stage, BOOL mipmapped, BOOL hires)
|
||||
/* ---------- sampler objects
|
||||
|
||||
A sampler object for each sampler state the game uses, made once: a draw
|
||||
binds the one its state needs, rather than changing a sampler's parameters,
|
||||
which costs a GL call each, and on Zink a new Vulkan sampler. The game uses
|
||||
a few dozen states. */
|
||||
|
||||
#define SAMPLER_STATE_WORDS 11
|
||||
#define SAMPLER_CACHE_SIZE 512
|
||||
|
||||
static struct
|
||||
{
|
||||
/* the texture stage state each sampler was last configured from */
|
||||
static DWORD configured[D3DTSS_MAXSTAGES][11];
|
||||
static BOOL configured_valid[D3DTSS_MAXSTAGES];
|
||||
GLuint sampler = device.samplers[stage];
|
||||
DWORD *state = D3D__TextureState[stage];
|
||||
DWORD min_filter = hires ? D3DTEXF_LINEAR : state[D3DTSS_MINFILTER];
|
||||
DWORD mip_filter = hires ? D3DTEXF_LINEAR : mipmapped ? state[D3DTSS_MIPFILTER] : D3DTEXF_NONE;
|
||||
DWORD mag_filter = hires ? D3DTEXF_LINEAR : state[D3DTSS_MAGFILTER];
|
||||
DWORD maximum_mip_level = hires ? 0 : state[D3DTSS_MAXMIPLEVEL];
|
||||
DWORD lod_bias = hires ? 0 : state[D3DTSS_MIPMAPLODBIAS];
|
||||
DWORD inputs[SAMPLER_STATE_WORDS];
|
||||
GLuint sampler;
|
||||
} sampler_cache[SAMPLER_CACHE_SIZE];
|
||||
static unsigned long sampler_cache_count;
|
||||
|
||||
/* a sampler's parameters from its state (configure_sampler's inputs) */
|
||||
static void sampler_parameters(GLuint sampler, const DWORD *inputs)
|
||||
{
|
||||
DWORD min_filter = inputs[0], mip_filter = inputs[1], mag_filter = inputs[2];
|
||||
DWORD lod_bias = inputs[6], maximum_mip_level = inputs[7], anisotropy = inputs[8];
|
||||
GLenum minification;
|
||||
float border[4];
|
||||
DWORD inputs[11];
|
||||
|
||||
inputs[0] = min_filter;
|
||||
inputs[1] = mip_filter;
|
||||
inputs[2] = mag_filter;
|
||||
inputs[3] = state[D3DTSS_ADDRESSU];
|
||||
inputs[4] = state[D3DTSS_ADDRESSV];
|
||||
inputs[5] = state[D3DTSS_ADDRESSW];
|
||||
inputs[6] = lod_bias;
|
||||
inputs[7] = maximum_mip_level;
|
||||
inputs[8] = state[D3DTSS_MAXANISOTROPY];
|
||||
inputs[9] = state[D3DTSS_BORDERCOLOR];
|
||||
inputs[10] = hires;
|
||||
if (configured_valid[stage] && !memcmp(configured[stage], inputs, sizeof(inputs)))
|
||||
return;
|
||||
memcpy(configured[stage], inputs, sizeof(inputs));
|
||||
configured_valid[stage] = TRUE;
|
||||
|
||||
if (min_filter == D3DTEXF_POINT)
|
||||
minification = mip_filter == D3DTEXF_NONE ? GL_NEAREST :
|
||||
@@ -2934,44 +3035,115 @@ static void configure_sampler(int stage, BOOL mipmapped, BOOL hires)
|
||||
mip_filter == D3DTEXF_POINT ? GL_LINEAR_MIPMAP_NEAREST : GL_LINEAR_MIPMAP_LINEAR;
|
||||
glSamplerParameteri(sampler, GL_TEXTURE_MIN_FILTER, (GLint)minification);
|
||||
glSamplerParameteri(sampler, GL_TEXTURE_MAG_FILTER, mag_filter == D3DTEXF_POINT ? GL_NEAREST : GL_LINEAR);
|
||||
glSamplerParameteri(sampler, GL_TEXTURE_WRAP_S, (GLint)address_mode(state[D3DTSS_ADDRESSU]));
|
||||
glSamplerParameteri(sampler, GL_TEXTURE_WRAP_T, (GLint)address_mode(state[D3DTSS_ADDRESSV]));
|
||||
glSamplerParameteri(sampler, GL_TEXTURE_WRAP_R, (GLint)address_mode(state[D3DTSS_ADDRESSW]));
|
||||
glSamplerParameteri(sampler, GL_TEXTURE_WRAP_S, (GLint)address_mode(inputs[3]));
|
||||
glSamplerParameteri(sampler, GL_TEXTURE_WRAP_T, (GLint)address_mode(inputs[4]));
|
||||
glSamplerParameteri(sampler, GL_TEXTURE_WRAP_R, (GLint)address_mode(inputs[5]));
|
||||
#ifdef HALO_ANDROID
|
||||
/* ES has no sampler LOD bias; the pixel shader applies it
|
||||
(texture_lod_bias) */
|
||||
(void)lod_bias;
|
||||
glSamplerParameterf(sampler, GL_TEXTURE_MIN_LOD, (float)maximum_mip_level);
|
||||
if (xgpu_capabilities.anisotropy)
|
||||
glSamplerParameterf(sampler, GL_TEXTURE_MAX_ANISOTROPY_EXT,
|
||||
(min_filter == D3DTEXF_ANISOTROPIC && state[D3DTSS_MAXANISOTROPY] > 1) ? (float)state[D3DTSS_MAXANISOTROPY] : 1.0f);
|
||||
(min_filter == D3DTEXF_ANISOTROPIC && anisotropy > 1) ? (float)anisotropy : 1.0f);
|
||||
if (xgpu_capabilities.border_clamp)
|
||||
{
|
||||
color_to_vec4(state[D3DTSS_BORDERCOLOR], border);
|
||||
color_to_vec4(inputs[9], border);
|
||||
glSamplerParameterfv(sampler, GL_TEXTURE_BORDER_COLOR, border);
|
||||
}
|
||||
#else
|
||||
glSamplerParameterf(sampler, GL_TEXTURE_LOD_BIAS, dword_to_float(lod_bias));
|
||||
glSamplerParameterf(sampler, GL_TEXTURE_MIN_LOD, (float)maximum_mip_level);
|
||||
glSamplerParameterf(sampler, GL_TEXTURE_MAX_ANISOTROPY,
|
||||
(min_filter == D3DTEXF_ANISOTROPIC && state[D3DTSS_MAXANISOTROPY] > 1) ? (float)state[D3DTSS_MAXANISOTROPY] : 1.0f);
|
||||
color_to_vec4(state[D3DTSS_BORDERCOLOR], border);
|
||||
(min_filter == D3DTEXF_ANISOTROPIC && anisotropy > 1) ? (float)anisotropy : 1.0f);
|
||||
color_to_vec4(inputs[9], border);
|
||||
glSamplerParameterfv(sampler, GL_TEXTURE_BORDER_COLOR, border);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* the sampler object of a sampler state, made the first time; with the
|
||||
cache full (never seen), the stage's own sampler set to it */
|
||||
static GLuint sampler_get(int stage, const DWORD *inputs)
|
||||
{
|
||||
unsigned long hash = 2166136261UL, index, probe;
|
||||
GLuint sampler;
|
||||
|
||||
for (index = 0; index < SAMPLER_STATE_WORDS; index++)
|
||||
hash = (hash ^ inputs[index]) * 16777619UL;
|
||||
for (probe = 0; probe < SAMPLER_CACHE_SIZE; probe++)
|
||||
{
|
||||
index = (hash + probe) % SAMPLER_CACHE_SIZE;
|
||||
if (!sampler_cache[index].sampler)
|
||||
break;
|
||||
if (!memcmp(sampler_cache[index].inputs, inputs, sizeof(sampler_cache[index].inputs)))
|
||||
return sampler_cache[index].sampler;
|
||||
}
|
||||
if (probe == SAMPLER_CACHE_SIZE || sampler_cache_count >= SAMPLER_CACHE_SIZE * 3 / 4)
|
||||
{
|
||||
sampler_parameters(device.samplers[stage], inputs);
|
||||
return device.samplers[stage];
|
||||
}
|
||||
glGenSamplers(1, &sampler);
|
||||
sampler_parameters(sampler, inputs);
|
||||
memcpy(sampler_cache[index].inputs, inputs, sizeof(sampler_cache[index].inputs));
|
||||
sampler_cache[index].sampler = sampler;
|
||||
sampler_cache_count++;
|
||||
return sampler;
|
||||
}
|
||||
|
||||
/* hires: a high-res HUD texture (hud_hires.h), drawn smaller than it is, so
|
||||
filtered and from its mip levels whatever the game asks: the HUD's meters are
|
||||
point sampled for one player, to keep the Xbox bitmaps' texels sharp */
|
||||
static void configure_sampler(int stage, BOOL mipmapped, BOOL hires)
|
||||
{
|
||||
/* the texture stage state each stage's sampler was last chosen by */
|
||||
static DWORD configured[D3DTSS_MAXSTAGES][SAMPLER_STATE_WORDS];
|
||||
static GLuint configured_sampler[D3DTSS_MAXSTAGES];
|
||||
DWORD *state = D3D__TextureState[stage];
|
||||
DWORD inputs[SAMPLER_STATE_WORDS];
|
||||
|
||||
inputs[0] = hires ? D3DTEXF_LINEAR : state[D3DTSS_MINFILTER];
|
||||
inputs[1] = hires ? D3DTEXF_LINEAR : mipmapped ? state[D3DTSS_MIPFILTER] : D3DTEXF_NONE;
|
||||
inputs[2] = hires ? D3DTEXF_LINEAR : state[D3DTSS_MAGFILTER];
|
||||
inputs[3] = state[D3DTSS_ADDRESSU];
|
||||
inputs[4] = state[D3DTSS_ADDRESSV];
|
||||
inputs[5] = state[D3DTSS_ADDRESSW];
|
||||
inputs[6] = hires ? 0 : state[D3DTSS_MIPMAPLODBIAS];
|
||||
inputs[7] = hires ? 0 : state[D3DTSS_MAXMIPLEVEL];
|
||||
inputs[8] = state[D3DTSS_MAXANISOTROPY];
|
||||
inputs[9] = state[D3DTSS_BORDERCOLOR];
|
||||
inputs[10] = hires;
|
||||
if (!configured_sampler[stage] || memcmp(configured[stage], inputs, sizeof(inputs)))
|
||||
{
|
||||
memcpy(configured[stage], inputs, sizeof(inputs));
|
||||
configured_sampler[stage] = sampler_get(stage, inputs);
|
||||
}
|
||||
state_sampler(stage, configured_sampler[stage]);
|
||||
}
|
||||
|
||||
|
||||
/* ---------- render targets sampled with their mip chain
|
||||
|
||||
The game renders some textures one mip level at a time, each level being a
|
||||
surface of its own (the water's ripple map). Sampling such a texture needs
|
||||
every level in one GL texture, so the levels' render targets are copied into
|
||||
a mipmapped composite whenever it is bound. */
|
||||
a mipmapped composite. Each draw of the water binds it, some maps (a30) more
|
||||
than once a frame, so the copy (and the mipmaps of the levels the game did not
|
||||
render) is redone only once a level's target has been drawn into since the
|
||||
last one. */
|
||||
|
||||
#define MIP_COMPOSITE_LEVELS 16
|
||||
|
||||
struct mip_composite
|
||||
{
|
||||
struct mip_composite *next;
|
||||
unsigned long data, width, height, levels;
|
||||
GLuint texture;
|
||||
/* the levels last copied, and each one's target's texture and written
|
||||
serial then */
|
||||
unsigned long rendered_levels;
|
||||
GLuint level_sources[MIP_COMPOSITE_LEVELS];
|
||||
unsigned long level_written[MIP_COMPOSITE_LEVELS];
|
||||
};
|
||||
|
||||
static struct mip_composite *mip_composites;
|
||||
@@ -3000,7 +3172,9 @@ static void copy_level_by_blit(GLuint source, GLuint destination, GLint level, G
|
||||
static GLuint mip_composite_get(const struct xgpu_texture_description *description, unsigned long data)
|
||||
{
|
||||
struct mip_composite *composite;
|
||||
struct xgpu_render_target *targets[MIP_COMPOSITE_LEVELS];
|
||||
unsigned long level, rendered_levels = 0;
|
||||
BOOL changed;
|
||||
|
||||
for (composite = mip_composites; composite; composite = composite->next)
|
||||
{
|
||||
@@ -3028,10 +3202,11 @@ static GLuint mip_composite_get(const struct xgpu_texture_description *descripti
|
||||
|
||||
glTexImage2D(GL_TEXTURE_2D, (GLint)level, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
|
||||
}
|
||||
composite->rendered_levels = ~0UL;
|
||||
composite->next = mip_composites;
|
||||
mip_composites = composite;
|
||||
}
|
||||
for (level = 0; level < description->levels; level++)
|
||||
for (level = 0; level < description->levels && level < MIP_COMPOSITE_LEVELS; level++)
|
||||
{
|
||||
unsigned long width = description->width >> level ? description->width >> level : 1;
|
||||
unsigned long height = description->height >> level ? description->height >> level : 1;
|
||||
@@ -3041,17 +3216,35 @@ static GLuint mip_composite_get(const struct xgpu_texture_description *descripti
|
||||
if (!target || target->width != width || target->height != height ||
|
||||
target->gl_width != width || target->gl_height != height)
|
||||
break;
|
||||
targets[level] = target;
|
||||
rendered_levels++;
|
||||
}
|
||||
changed = rendered_levels != composite->rendered_levels;
|
||||
for (level = 0; level < rendered_levels && !changed; level++)
|
||||
{
|
||||
changed = targets[level]->texture != composite->level_sources[level] ||
|
||||
targets[level]->written != composite->level_written[level];
|
||||
}
|
||||
if (!changed)
|
||||
return composite->texture;
|
||||
composite->rendered_levels = rendered_levels;
|
||||
for (level = 0; level < rendered_levels; level++)
|
||||
{
|
||||
struct xgpu_render_target *target = targets[level];
|
||||
GLsizei width = (GLsizei)target->width, height = (GLsizei)target->height;
|
||||
|
||||
composite->level_sources[level] = target->texture;
|
||||
composite->level_written[level] = target->written;
|
||||
render_target_resolve(target);
|
||||
#ifdef HALO_ANDROID
|
||||
if (!xgpu_capabilities.copy_image)
|
||||
{
|
||||
copy_level_by_blit(target->texture, composite->texture, (GLint)level, (GLsizei)width, (GLsizei)height);
|
||||
copy_level_by_blit(target->texture, composite->texture, (GLint)level, width, height);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
glCopyImageSubData(target->texture, GL_TEXTURE_2D, 0, 0, 0, 0,
|
||||
composite->texture, GL_TEXTURE_2D, (GLint)level, 0, 0, 0, (GLsizei)width, (GLsizei)height, 1);
|
||||
rendered_levels++;
|
||||
composite->texture, GL_TEXTURE_2D, (GLint)level, 0, 0, 0, width, height, 1);
|
||||
}
|
||||
glBindTexture(GL_TEXTURE_2D, composite->texture);
|
||||
/* levels the game did not render come from the ones it did */
|
||||
@@ -3143,7 +3336,6 @@ static void bind_textures(struct nv2a_pixel_shader_key *key, float texture_scale
|
||||
}
|
||||
gl_targets[stage] = gl_target;
|
||||
gl_textures[stage] = gl_texture;
|
||||
state_sampler(stage, device.samplers[stage]);
|
||||
configure_sampler(stage, description.levels > 1, description.hires);
|
||||
if (stage == 0)
|
||||
key->coverage_alpha = description.hires_coverage != FALSE;
|
||||
@@ -3151,8 +3343,38 @@ static void bind_textures(struct nv2a_pixel_shader_key *key, float texture_scale
|
||||
gl_target == GL_TEXTURE_3D ? _xgpu_sampler_3d : _xgpu_sampler_2d;
|
||||
}
|
||||
}
|
||||
#ifdef HALO_ANDROID
|
||||
for (stage = 0; stage < D3DTSS_MAXSTAGES; stage++)
|
||||
state_texture(stage, gl_targets[stage], gl_textures[stage]);
|
||||
#else
|
||||
{
|
||||
/* the units whose texture changes, bound in one call (GL 4.4's
|
||||
multi-bind) rather than selecting and binding each unit; binding no
|
||||
texture unbinds all of the unit's targets */
|
||||
int first = -1, last = -1;
|
||||
|
||||
for (stage = 0; stage < D3DTSS_MAXSTAGES; stage++)
|
||||
{
|
||||
if (gl_state.textures[stage][texture_slot(gl_targets[stage])] != gl_textures[stage])
|
||||
{
|
||||
if (first < 0)
|
||||
first = stage;
|
||||
last = stage;
|
||||
}
|
||||
}
|
||||
if (first >= 0)
|
||||
{
|
||||
glBindTextures((GLuint)first, (GLsizei)(last - first + 1), &gl_textures[first]);
|
||||
for (stage = first; stage <= last; stage++)
|
||||
{
|
||||
if (gl_textures[stage])
|
||||
gl_state.textures[stage][texture_slot(gl_targets[stage])] = gl_textures[stage];
|
||||
else
|
||||
memset(gl_state.textures[stage], 0, sizeof(gl_state.textures[stage]));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static GLenum stencil_operation(DWORD operation)
|
||||
@@ -4315,6 +4537,13 @@ static void setup_streams(unsigned long first, unsigned long count)
|
||||
BOOL placed[16] = { FALSE };
|
||||
BOOL enabled[XGPU_VERTEX_ATTRIBUTE_COUNT] = { FALSE };
|
||||
unsigned long index, total = 0;
|
||||
#ifndef HALO_ANDROID
|
||||
struct vertex_layout layout;
|
||||
unsigned long streams_used = 0;
|
||||
|
||||
/* (zeroed: layouts are compared and hashed whole) */
|
||||
memset(&layout, 0, sizeof(layout));
|
||||
#endif
|
||||
|
||||
/* the mirror first; then one reservation for everything streamed */
|
||||
for (index = 0; index < declaration->element_count; index++)
|
||||
@@ -4363,6 +4592,7 @@ static void setup_streams(unsigned long first, unsigned long count)
|
||||
stream_buffers[stream] = device.stream_buffer;
|
||||
stats.streamed_bytes += bytes;
|
||||
}
|
||||
#ifdef HALO_ANDROID
|
||||
if (element->type == D3DVSDT_NORMPACKED3)
|
||||
{
|
||||
state_attribute_stream(element->reg, (GLuint)stream, stream_buffers[stream], 1, GL_UNSIGNED_INT, GL_FALSE,
|
||||
@@ -4374,8 +4604,36 @@ static void setup_streams(unsigned long first, unsigned long count)
|
||||
state_attribute_stream(element->reg, (GLuint)stream, stream_buffers[stream], size, type, normalized,
|
||||
FALSE, (GLsizei)stride, stream_offsets[stream], element->offset);
|
||||
}
|
||||
#else
|
||||
if (element->type == D3DVSDT_NORMPACKED3)
|
||||
{
|
||||
layout_attribute(&layout, element->reg, (GLuint)stream, 1, GL_UNSIGNED_INT, GL_FALSE, TRUE, element->offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
attribute_format(element, &size, &type, &normalized);
|
||||
layout_attribute(&layout, element->reg, (GLuint)stream, size, type, normalized, FALSE, element->offset);
|
||||
}
|
||||
streams_used |= 1UL << stream;
|
||||
#endif
|
||||
enabled[element->reg] = TRUE;
|
||||
}
|
||||
#ifndef HALO_ANDROID
|
||||
/* (the layout follows from the declaration and the streams it has, so
|
||||
the same two have the same vertex array: no need to look it up) */
|
||||
if (!declaration->vertex_array || declaration->vertex_array_streams != streams_used)
|
||||
{
|
||||
declaration->vertex_array = vertex_array_get(&layout);
|
||||
declaration->vertex_array_streams = streams_used;
|
||||
}
|
||||
state_vertex_array(declaration->vertex_array);
|
||||
for (index = 0; index < 16; index++)
|
||||
{
|
||||
if (streams_used & (1UL << index))
|
||||
state_vertex_buffer((GLuint)index, stream_buffers[index], stream_offsets[index],
|
||||
(GLsizei)device.streams[index].stride);
|
||||
}
|
||||
#endif
|
||||
for (index = 0; index < XGPU_VERTEX_ATTRIBUTE_COUNT; index++)
|
||||
{
|
||||
if (!enabled[index])
|
||||
@@ -4550,11 +4808,30 @@ void WINAPI D3DDevice_End(void)
|
||||
return;
|
||||
trace_draw("immediate", type, count, device.immediate_vertices);
|
||||
offset = stream_upload(device.immediate_vertices, count * stride);
|
||||
#ifdef HALO_ANDROID
|
||||
for (index = 0; index < XGPU_VERTEX_ATTRIBUTE_COUNT; index++)
|
||||
{
|
||||
state_attribute_stream(index, 0, device.stream_buffer, 4, GL_FLOAT, GL_FALSE, FALSE, (GLsizei)stride,
|
||||
offset, index * 4 * sizeof(float));
|
||||
}
|
||||
#else
|
||||
{
|
||||
/* every attribute four floats, one after another */
|
||||
static struct vertex_array_entry *immediate_array;
|
||||
|
||||
if (!immediate_array)
|
||||
{
|
||||
struct vertex_layout layout;
|
||||
|
||||
memset(&layout, 0, sizeof(layout));
|
||||
for (index = 0; index < XGPU_VERTEX_ATTRIBUTE_COUNT; index++)
|
||||
layout_attribute(&layout, index, 0, 4, GL_FLOAT, GL_FALSE, FALSE, index * 4 * sizeof(float));
|
||||
immediate_array = vertex_array_get(&layout);
|
||||
}
|
||||
state_vertex_array(immediate_array);
|
||||
state_vertex_buffer(0, device.stream_buffer, offset, (GLsizei)stride);
|
||||
}
|
||||
#endif
|
||||
if (type == D3DPT_QUADLIST)
|
||||
{
|
||||
unsigned long index_count;
|
||||
|
||||
@@ -221,6 +221,7 @@ this list to generate the guest's entry points */
|
||||
X(glVertexAttribIFormat) \
|
||||
X(glVertexAttribBinding) \
|
||||
X(glBindVertexBuffer) \
|
||||
X(glBindTextures) \
|
||||
X(glGetQueryBufferObjectuiv) \
|
||||
X(glVertexAttrib4fv) \
|
||||
X(glVertexAttribI4ui) \
|
||||
@@ -452,6 +453,7 @@ pointers, sees the declarations without these aliases */
|
||||
#define glVertexAttribIFormat halo_glVertexAttribIFormat
|
||||
#define glVertexAttribBinding halo_glVertexAttribBinding
|
||||
#define glBindVertexBuffer halo_glBindVertexBuffer
|
||||
#define glBindTextures halo_glBindTextures
|
||||
#define glGetQueryBufferObjectuiv halo_glGetQueryBufferObjectuiv
|
||||
#define glVertexAttrib4fv halo_glVertexAttrib4fv
|
||||
#define glVertexAttribI4ui halo_glVertexAttribI4ui
|
||||
|
||||
@@ -15,7 +15,7 @@ configure_sampler), as they are larger than they appear.
|
||||
|
||||
The PNGs are the ones tools/hud_assets.py and title_assets.py write, so only
|
||||
what they write is read: 8-bit RGBA, not interlaced, its data inflated with
|
||||
the game's zlib.
|
||||
the port's zlib (port/third_party/zlib: a menus folder's PNGs are anyone's).
|
||||
*/
|
||||
|
||||
#include "hud_hires.h"
|
||||
@@ -23,7 +23,7 @@ the game's zlib.
|
||||
#include "port_config.h"
|
||||
#include "xgpu.h"
|
||||
|
||||
#include "memory/zlib/zlib.h"
|
||||
#include "zlib_prefixed.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -32,6 +32,10 @@ the game's zlib.
|
||||
long hud_hires_asset_at(unsigned long address, long width, long height);
|
||||
|
||||
#define MAXIMUM_TEXTURES 128
|
||||
/* a PNG's inflated rows and its texels, which are held at once: 128 MB for
|
||||
a 4096 by 4096 sheet (the largest shipped, 2048 by 2048, takes 32 MB), and
|
||||
no more for a small file that names a large size (a menus folder's) */
|
||||
#define MAXIMUM_DECODED_SIZE (192UL << 20)
|
||||
|
||||
static struct
|
||||
{
|
||||
@@ -141,6 +145,12 @@ static unsigned char *png_decode(const unsigned char *data, unsigned long size,
|
||||
!width || !height || width > 8192 || height > 8192 ||
|
||||
data[24] != 8 || data[25] != 6 || data[28] != 0)
|
||||
return NULL;
|
||||
if (filtered_size + stride * height > MAXIMUM_DECODED_SIZE)
|
||||
{
|
||||
platform_log("png: %lux%lu is too large to decode (more than %lu MB)", width, height,
|
||||
MAXIMUM_DECODED_SIZE >> 20);
|
||||
return NULL;
|
||||
}
|
||||
*png_width = width;
|
||||
*png_height = height;
|
||||
compressed = malloc(size);
|
||||
|
||||
@@ -62,15 +62,30 @@ static long file_find(const char *path)
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void file_add(const char *path, const unsigned char *data, unsigned long size, int loaded)
|
||||
/* the file's index, or -1 when out of memory (and a loaded file's data is
|
||||
freed) */
|
||||
static long file_add(const char *path, const unsigned char *data, unsigned long size, int loaded)
|
||||
{
|
||||
long index = file_find(path);
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
files = realloc(files, (file_count + 1) * sizeof(*files));
|
||||
struct menu_file *grown = realloc(files, (file_count + 1) * sizeof(*files));
|
||||
char *name = strdup(path);
|
||||
|
||||
if (!grown || !name)
|
||||
{
|
||||
platform_log("menus: out of memory for %s", path);
|
||||
if (grown)
|
||||
files = grown;
|
||||
free(name);
|
||||
if (loaded)
|
||||
free((void *)data);
|
||||
return -1;
|
||||
}
|
||||
files = grown;
|
||||
index = file_count++;
|
||||
files[index].path = strdup(path);
|
||||
files[index].path = name;
|
||||
}
|
||||
else if (files[index].loaded)
|
||||
{
|
||||
@@ -79,6 +94,7 @@ static void file_add(const char *path, const unsigned char *data, unsigned long
|
||||
files[index].data = data;
|
||||
files[index].size = size;
|
||||
files[index].loaded = loaded;
|
||||
return index;
|
||||
}
|
||||
|
||||
static unsigned char *file_read(const char *path, unsigned long *size)
|
||||
@@ -182,12 +198,39 @@ static void files_gather(void)
|
||||
#endif
|
||||
}
|
||||
|
||||
/* whether a bitmap's path stays inside the menus folder: relative, with no
|
||||
drive (c:) and no .. (nor Windows' spellings of it, such as ".. ") */
|
||||
static int path_inside_folder(const char *path)
|
||||
{
|
||||
const char *component = path;
|
||||
|
||||
if (!*path || *path == '/' || *path == '\\' || strchr(path, ':'))
|
||||
return 0;
|
||||
while (*component)
|
||||
{
|
||||
size_t length = strcspn(component, "/\\");
|
||||
|
||||
/* (only dots and spaces, but a lone .) */
|
||||
if (length && strspn(component, ". ") == length && !(length == 1 && *component == '.'))
|
||||
return 0;
|
||||
component += length;
|
||||
component += strspn(component, "/\\");
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* a file's data (a bitmap's PNG, by the path its <bitmap> gives): the
|
||||
menus folder's, else the embedded one */
|
||||
static const unsigned char *file_data(const char *path, unsigned long *size)
|
||||
{
|
||||
long index = file_find(path);
|
||||
long index;
|
||||
|
||||
if (!path_inside_folder(path))
|
||||
{
|
||||
platform_log("menus: %s is not a path inside the menus folder", path);
|
||||
return NULL;
|
||||
}
|
||||
index = file_find(path);
|
||||
if (index < 0)
|
||||
{
|
||||
char full[1200];
|
||||
@@ -197,8 +240,9 @@ static const unsigned char *file_data(const char *path, unsigned long *size)
|
||||
data = file_read(full, size);
|
||||
if (!data)
|
||||
return NULL;
|
||||
file_add(path, data, *size, 1);
|
||||
index = file_find(path);
|
||||
index = file_add(path, data, *size, 1);
|
||||
if (index < 0)
|
||||
return NULL;
|
||||
}
|
||||
*size = files[index].size;
|
||||
return files[index].data;
|
||||
@@ -324,7 +368,11 @@ static void read_attributes(struct reader *reader, const char *element, const XM
|
||||
if (strcmp(name, table[field].name))
|
||||
continue;
|
||||
if (table[field].text)
|
||||
{
|
||||
*table[field].text = copy(value);
|
||||
if (!*table[field].text)
|
||||
reader_error(reader, "out of memory");
|
||||
}
|
||||
else if (parse_long(reader, name, value, table[field].number) && table[field].given)
|
||||
*table[field].given = 1;
|
||||
break;
|
||||
@@ -365,19 +413,39 @@ static int for_this_platform(struct reader *reader, const XML_Char **attributes)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* room for one more element in an array of count */
|
||||
static void *grow(void *array, long count, size_t size)
|
||||
/* room for one more element in an array of count; NULL when out of memory,
|
||||
which fails the reading (the array is then as it was) */
|
||||
static void *grow(struct reader *reader, void *array, long count, size_t size)
|
||||
{
|
||||
return realloc(array, (size_t)(count + 1) * size);
|
||||
void *grown = realloc(array, (size_t)(count + 1) * size);
|
||||
|
||||
if (!grown)
|
||||
reader_error(reader, "out of memory");
|
||||
return grown;
|
||||
}
|
||||
|
||||
static long *grow_last(long *array, long count)
|
||||
static int grow_last(struct reader *reader, long **array, long count)
|
||||
{
|
||||
array = grow(array, count, sizeof(*array));
|
||||
array[count] = HALO_MENU_NONE;
|
||||
return array;
|
||||
long *grown = grow(reader, *array, count, sizeof(**array));
|
||||
|
||||
if (!grown)
|
||||
return 0;
|
||||
grown[count] = HALO_MENU_NONE;
|
||||
*array = grown;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* room in array for one more of its count elements, else the reading
|
||||
function returns (out of memory: the reading has failed) */
|
||||
#define GROW(reader, array, count) \
|
||||
do \
|
||||
{ \
|
||||
void *grown = grow((reader), (array), (count), sizeof(*(array))); \
|
||||
if (!grown) \
|
||||
return; \
|
||||
(array) = grown; \
|
||||
} while (0)
|
||||
|
||||
/* adds index to a widget's list (first, and each item's next) */
|
||||
#define LIST_ADD(reader, owner, first, last, items, index) \
|
||||
do \
|
||||
@@ -409,15 +477,30 @@ static void read_bitmap(struct reader *reader, const XML_Char **attributes)
|
||||
/* (frames="a.png b.png", all width by height, or <frame>s) */
|
||||
while (frames && *frames && !reader->failed)
|
||||
{
|
||||
struct halo_menu_frame *grown;
|
||||
size_t length;
|
||||
|
||||
frames += strspn(frames, " \t\r\n");
|
||||
length = strcspn(frames, " \t\r\n");
|
||||
if (!length)
|
||||
break;
|
||||
menus->frames = grow(menus->frames, menus->frame_count, sizeof(*menus->frames));
|
||||
/* (a bitmap's count of frames is a short of the game's) */
|
||||
if (bitmap.frame_count >= 32767)
|
||||
{
|
||||
reader_error(reader, "a bitmap has too many frames");
|
||||
break;
|
||||
}
|
||||
grown = grow(reader, menus->frames, menus->frame_count, sizeof(*menus->frames));
|
||||
if (!grown)
|
||||
break;
|
||||
menus->frames = grown;
|
||||
memset(&menus->frames[menus->frame_count], 0, sizeof(*menus->frames));
|
||||
menus->frames[menus->frame_count].png = copy_length(frames, length);
|
||||
if (!menus->frames[menus->frame_count].png)
|
||||
{
|
||||
reader_error(reader, "out of memory");
|
||||
break;
|
||||
}
|
||||
menus->frames[menus->frame_count].width = width;
|
||||
menus->frames[menus->frame_count].height = height;
|
||||
menus->frame_count++;
|
||||
@@ -426,7 +509,7 @@ static void read_bitmap(struct reader *reader, const XML_Char **attributes)
|
||||
}
|
||||
if (!reader->failed && !bitmap.name)
|
||||
reader_error(reader, "a <bitmap> needs a name");
|
||||
menus->bitmaps = grow(menus->bitmaps, menus->bitmap_count, sizeof(*menus->bitmaps));
|
||||
GROW(reader, menus->bitmaps, menus->bitmap_count);
|
||||
menus->bitmaps[menus->bitmap_count++] = bitmap;
|
||||
}
|
||||
|
||||
@@ -438,6 +521,7 @@ static void read_frame(struct reader *reader, const XML_Char **attributes, long
|
||||
{
|
||||
{ "png", &frame.png }, { "map", &frame.map }, { "index", NULL, &frame.index },
|
||||
{ "width", NULL, &frame.width }, { "height", NULL, &frame.height },
|
||||
{ "x", NULL, &frame.x }, { "y", NULL, &frame.y },
|
||||
};
|
||||
|
||||
memset(&frame, 0, sizeof(frame));
|
||||
@@ -449,11 +533,20 @@ static void read_frame(struct reader *reader, const XML_Char **attributes, long
|
||||
reader_error(reader, "a <frame> with a png needs width and height");
|
||||
else if (!reader->failed && frame.map && frame.index < 0)
|
||||
reader_error(reader, "a <frame> of a map bitmap needs its index");
|
||||
else if (!reader->failed && frame.map && (frame.width < 0 || frame.height < 0 || !frame.width != !frame.height))
|
||||
reader_error(reader, "a <frame> of a map bitmap is scaled to a width and a height, or neither");
|
||||
else if (!reader->failed && (frame.x || frame.y) && (!frame.map || !frame.width))
|
||||
reader_error(reader, "only a scaled <frame> of a map bitmap is placed at an x and y");
|
||||
if (!reader->failed && reader->bitmap_frames_attribute)
|
||||
reader_error(reader, "a bitmap's <frame>s and frames= cannot be mixed");
|
||||
if (frame.index < 0)
|
||||
frame.index = 0;
|
||||
menus->frames = grow(menus->frames, menus->frame_count, sizeof(*menus->frames));
|
||||
if (menus->bitmaps[owner].frame_count >= 32767)
|
||||
{
|
||||
reader_error(reader, "a bitmap has too many frames");
|
||||
return;
|
||||
}
|
||||
GROW(reader, menus->frames, menus->frame_count);
|
||||
menus->frames[menus->frame_count++] = frame;
|
||||
menus->bitmaps[owner].frame_count++;
|
||||
}
|
||||
@@ -471,7 +564,7 @@ static void read_strings(struct reader *reader, const XML_Char **attributes)
|
||||
read_attributes(reader, "strings", attributes, table, sizeof(table) / sizeof(*table));
|
||||
if (!reader->failed && !strings.name)
|
||||
reader_error(reader, "a <strings> needs a name");
|
||||
menus->string_lists = grow(menus->string_lists, menus->string_list_count, sizeof(*menus->string_lists));
|
||||
GROW(reader, menus->string_lists, menus->string_list_count);
|
||||
menus->string_lists[menus->string_list_count++] = strings;
|
||||
}
|
||||
|
||||
@@ -484,7 +577,7 @@ static void read_string(struct reader *reader, const XML_Char **attributes, long
|
||||
read_attributes(reader, "string", attributes, table, sizeof(table) / sizeof(*table));
|
||||
if (!reader->failed && !text)
|
||||
reader_error(reader, "a <string> needs text");
|
||||
menus->strings = grow(menus->strings, menus->string_count, sizeof(*menus->strings));
|
||||
GROW(reader, menus->strings, menus->string_count);
|
||||
menus->strings[menus->string_count++] = text ? text : "";
|
||||
menus->string_lists[owner].count++;
|
||||
}
|
||||
@@ -505,7 +598,7 @@ static void child_add(struct reader *reader, long parent, long nested, const cha
|
||||
child.next = HALO_MENU_NONE;
|
||||
child.file = reader->file;
|
||||
child.line = current_line(reader);
|
||||
menus->children = grow(menus->children, menus->child_count, sizeof(*menus->children));
|
||||
GROW(reader, menus->children, menus->child_count);
|
||||
menus->children[menus->child_count++] = child;
|
||||
LIST_ADD(reader, parent, first_child, last_child, children, index);
|
||||
}
|
||||
@@ -543,14 +636,19 @@ static void read_widget(struct reader *reader, const XML_Char **attributes, long
|
||||
read_attributes(reader, "widget", attributes, table, sizeof(table) / sizeof(*table));
|
||||
if (!reader->failed && !widget.name)
|
||||
reader_error(reader, "a <widget> needs a name");
|
||||
menus->widgets = grow(menus->widgets, index, sizeof(*menus->widgets));
|
||||
GROW(reader, menus->widgets, index);
|
||||
menus->widgets[index] = widget;
|
||||
menus->widget_count++;
|
||||
reader->last_child = grow_last(reader->last_child, index);
|
||||
reader->last_handler = grow_last(reader->last_handler, index);
|
||||
reader->last_input = grow_last(reader->last_input, index);
|
||||
reader->last_conditional = grow_last(reader->last_conditional, index);
|
||||
reader->last_replace = grow_last(reader->last_replace, index);
|
||||
if (!grow_last(reader, &reader->last_child, index))
|
||||
return;
|
||||
if (!grow_last(reader, &reader->last_handler, index))
|
||||
return;
|
||||
if (!grow_last(reader, &reader->last_input, index))
|
||||
return;
|
||||
if (!grow_last(reader, &reader->last_conditional, index))
|
||||
return;
|
||||
if (!grow_last(reader, &reader->last_replace, index))
|
||||
return;
|
||||
if (parent != HALO_MENU_NONE)
|
||||
child_add(reader, parent, index, NULL, widget.x, widget.y, child_controller);
|
||||
}
|
||||
@@ -597,7 +695,7 @@ static void read_handler(struct reader *reader, const XML_Char **attributes, lon
|
||||
handler.branch = branch && !strcmp(branch, "true");
|
||||
if (!reader->failed && !handler.event)
|
||||
reader_error(reader, "an <on> needs an event");
|
||||
menus->handlers = grow(menus->handlers, index, sizeof(*menus->handlers));
|
||||
GROW(reader, menus->handlers, index);
|
||||
menus->handlers[index] = handler;
|
||||
menus->handler_count++;
|
||||
LIST_ADD(reader, owner, first_handler, last_handler, handlers, index);
|
||||
@@ -617,7 +715,7 @@ static void read_input(struct reader *reader, const XML_Char **attributes, long
|
||||
read_attributes(reader, "data", attributes, table, sizeof(table) / sizeof(*table));
|
||||
if (!reader->failed && !input.input)
|
||||
reader_error(reader, "a <data> needs an input");
|
||||
menus->inputs = grow(menus->inputs, index, sizeof(*menus->inputs));
|
||||
GROW(reader, menus->inputs, index);
|
||||
menus->inputs[index] = input;
|
||||
menus->input_count++;
|
||||
LIST_ADD(reader, owner, first_input, last_input, inputs, index);
|
||||
@@ -641,7 +739,7 @@ static void read_conditional(struct reader *reader, const XML_Char **attributes,
|
||||
conditional.if_failed = if_failed && !strcmp(if_failed, "true");
|
||||
if (!reader->failed && !conditional.widget)
|
||||
reader_error(reader, "a <conditional> needs a widget");
|
||||
menus->conditionals = grow(menus->conditionals, index, sizeof(*menus->conditionals));
|
||||
GROW(reader, menus->conditionals, index);
|
||||
menus->conditionals[index] = conditional;
|
||||
menus->conditional_count++;
|
||||
LIST_ADD(reader, owner, first_conditional, last_conditional, conditionals, index);
|
||||
@@ -661,7 +759,7 @@ static void read_replace(struct reader *reader, const XML_Char **attributes, lon
|
||||
read_attributes(reader, "replace", attributes, table, sizeof(table) / sizeof(*table));
|
||||
if (!reader->failed && !replace.search)
|
||||
reader_error(reader, "a <replace> needs a search");
|
||||
menus->replaces = grow(menus->replaces, index, sizeof(*menus->replaces));
|
||||
GROW(reader, menus->replaces, index);
|
||||
menus->replaces[index] = replace;
|
||||
menus->replace_count++;
|
||||
LIST_ADD(reader, owner, first_replace, last_replace, replaces, index);
|
||||
@@ -980,7 +1078,8 @@ unsigned int menu_art_texture(unsigned long data, unsigned long *levels)
|
||||
if (art[index].data == data)
|
||||
break;
|
||||
}
|
||||
if (index == art_count || art[index].failed)
|
||||
/* (no png: out of memory registering it) */
|
||||
if (index == art_count || art[index].failed || !art[index].png)
|
||||
return 0;
|
||||
if (!art[index].texture)
|
||||
{
|
||||
|
||||
@@ -1114,6 +1114,10 @@ static void update_peers(void)
|
||||
{
|
||||
int candidate;
|
||||
|
||||
/* (every address it gave, until a packet sealed by it arrives
|
||||
from one: then only that one, above. The addresses are anyone's
|
||||
who has the invite to give, so they are sent to no longer than
|
||||
this) */
|
||||
for (candidate = 0; candidate < peer->candidate_count; candidate++)
|
||||
peer_ping(peer, &peer->candidates[candidate]);
|
||||
peer->sent_time = p2p_now();
|
||||
@@ -1305,7 +1309,10 @@ int p2p_local_candidates(struct p2p_candidate *candidates, int maximum_count)
|
||||
candidates[count].address = lan;
|
||||
candidates[count++].port = p2p.tunnel_port;
|
||||
}
|
||||
/* (the port the router forwards here, UPnP) */
|
||||
/* (the port the router forwards here, UPnP; STUN's addresses are offered
|
||||
too, even where the router names another: then either may be the one
|
||||
that works, and a machine on the LAN that answers as the router cannot
|
||||
take the real address's place) */
|
||||
if (p2p.upnp_forwarded && count < maximum_count)
|
||||
candidates[count++] = p2p.upnp_candidate;
|
||||
for (index = 0; index < p2p.stun_count && count < maximum_count; index++)
|
||||
|
||||
@@ -50,6 +50,8 @@ enum
|
||||
_platform_desktop = 1,
|
||||
_platform_android = 2,
|
||||
_platform_all = _platform_desktop | _platform_android,
|
||||
/* (of the desktop builds, only Windows) */
|
||||
_platform_windows = 4,
|
||||
};
|
||||
|
||||
struct config_setting
|
||||
@@ -201,6 +203,28 @@ static const struct config_setting config_settings[] =
|
||||
"and the left one turns, X jumps and A reloads (on the Steam Frame the\n"
|
||||
"D-pad takes A, B, X and Y as their mirror image); false keeps them as\n"
|
||||
"right-handed. The menus keep their buttons either way." },
|
||||
{ "vr.button_jump", _config_string, "\"a\"", "HALO_VR_BUTTON_JUMP", _environment_value, _platform_android,
|
||||
"The button each of the game's actions is on (the VR options' BUTTONS\n"
|
||||
"page swaps them): \"a\", \"b\", \"x\", \"y\" (A and B on the right\n"
|
||||
"controller, X and Y the Quest's on the left; the Steam Frame has all\n"
|
||||
"four on the right), \"move_stick\" and \"turn_stick\" (their clicks),\n"
|
||||
"\"grip\" (the weapon hand's), named as right-handed: left-handed with\n"
|
||||
"mirror_controls, their mirror images. Jump," },
|
||||
{ "vr.button_light", _config_string, "\"b\"", "HALO_VR_BUTTON_LIGHT", _environment_value, _platform_android,
|
||||
"the flashlight (held: switch grenades)," },
|
||||
{ "vr.button_action", _config_string, "\"x\"", "HALO_VR_BUTTON_ACTION", _environment_value, _platform_android,
|
||||
"action and reload," },
|
||||
{ "vr.button_weapon", _config_string, "\"y\"", "HALO_VR_BUTTON_WEAPON", _environment_value, _platform_android,
|
||||
"switch weapon," },
|
||||
{ "vr.button_crouch", _config_string, "\"move_stick\"", "HALO_VR_BUTTON_CROUCH", _environment_value,
|
||||
_platform_android,
|
||||
"crouch," },
|
||||
{ "vr.button_zoom", _config_string, "\"turn_stick\"", "HALO_VR_BUTTON_ZOOM", _environment_value,
|
||||
_platform_android,
|
||||
"zoom" },
|
||||
{ "vr.button_melee", _config_string, "\"grip\"", "HALO_VR_BUTTON_MELEE", _environment_value, _platform_android,
|
||||
"and melee. The triggers, the free hand's grip and the menu button keep\n"
|
||||
"theirs, as do the menus." },
|
||||
{ "vr.laser", _config_boolean, "false", "HALO_VR_LASER", _environment_value, _platform_android,
|
||||
"A laser from the weapon to what it aims at (with aim_with = \"controller\")." },
|
||||
{ "vr.crosshair", _config_boolean, "true", "HALO_VR_CROSSHAIR", _environment_value, _platform_android,
|
||||
@@ -304,6 +328,13 @@ static const struct config_setting config_settings[] =
|
||||
{ "vr.comfort_vignette", _config_real, "0.7", "HALO_VR_COMFORT_VIGNETTE", _environment_value, _platform_android,
|
||||
"How far the view darkens from its edges while a vehicle turns you\n"
|
||||
"(0 never, 1 to a small window)." },
|
||||
{ "vr.foot_vignette", _config_string, "\"off\"", "HALO_VR_FOOT_VIGNETTE", _environment_value, _platform_android,
|
||||
"On foot, the view darkens from its edges too: \"turning\" while the stick\n"
|
||||
"turns you smoothly (smooth_turn), \"moving\" while a stick turns or moves\n"
|
||||
"you, as far as it is pushed; \"off\" never." },
|
||||
{ "vr.foot_vignette_strength", _config_real, "0.7", "HALO_VR_FOOT_VIGNETTE_STRENGTH", _environment_value,
|
||||
_platform_android,
|
||||
"How far it darkens then (as comfort_vignette)." },
|
||||
{ "vr.motion_tracker_on_wrist", _config_boolean, "true", "HALO_VR_MOTION_TRACKER_WRIST", _environment_value,
|
||||
_platform_android,
|
||||
"Show the motion tracker on the back of the left wrist, like a watch (turn\n"
|
||||
@@ -324,9 +355,11 @@ static const struct config_setting config_settings[] =
|
||||
{ "vr.smooth_turn", _config_boolean, "false", "HALO_VR_SMOOTH_TURN", _environment_value, _platform_android,
|
||||
"The right stick turns smoothly; false turns in steps (snap_turn_degrees)." },
|
||||
{ "vr.snap_turn_degrees", _config_real, "30.0", "HALO_VR_SNAP_TURN", _environment_value, _platform_android,
|
||||
"How far a flick of the right stick turns." },
|
||||
"How far a flick of the right stick turns, in degrees (the VR options step\n"
|
||||
"through 10 to 45 by 5, 60, 75 and 90)." },
|
||||
{ "vr.smooth_turn_speed", _config_real, "120.0", "HALO_VR_SMOOTH_TURN_SPEED", _environment_value, _platform_android,
|
||||
"Degrees per second the right stick turns at, fully pushed, with smooth_turn." },
|
||||
"Degrees per second the right stick turns at, fully pushed, with smooth_turn\n"
|
||||
"(the VR options step through 30 to 150 by 15, then to 300 by 30)." },
|
||||
{ "vr.cinematics_in_3d", _config_boolean, "false", "HALO_VR_CINEMATICS_3D", _environment_value, _platform_android,
|
||||
"Show cinematics around you, from the cinematic camera; false shows them\n"
|
||||
"on the screen in front of you (easier on the stomach)." },
|
||||
@@ -523,6 +556,10 @@ static const struct config_setting config_settings[] =
|
||||
{ "update.auto", _config_boolean, "true", "HALO_UPDATE_AUTO", _environment_value, _platform_all,
|
||||
"Look for a new version when the game starts, and offer to update to it;\n"
|
||||
"false never looks (the game's \"Do not ask again\" writes false here)." },
|
||||
{ "crash_reports.upload", _config_string, "\"ask\"", "HALO_CRASH_REPORTS", _environment_value, _platform_windows,
|
||||
"Send a report of each crash (a minidump and halo.log) to the developers'\n"
|
||||
"Sentry project (port/windows/src/win32_crash.c): \"yes\" sends them, \"no\"\n"
|
||||
"never does, \"ask\" asks at the next crash and writes the answer here." },
|
||||
|
||||
{ "debug.network_test", _config_string, "\"\"", "HALO_NETWORK_TEST", _environment_value, _platform_all,
|
||||
"Automated system link sessions for testing (port/linux/game/network_test.c):\n"
|
||||
@@ -573,10 +610,25 @@ static const struct config_setting config_settings[] =
|
||||
"The headset's layer copies: 1 deferred (no waits for the GL thread or the\n"
|
||||
"GPU in the middle of the frame), 0 each one restored and released at once,\n"
|
||||
"-1 as the runtime needs (deferred on SteamVR). Read when the app starts." },
|
||||
{ "debug.vr_options", _config_boolean, "false", "HALO_DEBUG_VR_OPTIONS", _environment_value, _platform_android,
|
||||
"Off the headset (the emulator), show the VR options panel on the main\n"
|
||||
"and pause menus all the same, for checking its pages." },
|
||||
{ "debug.mirror_first_person", _config_boolean, "false", "HALO_MIRROR_FIRST_PERSON", _environment_value,
|
||||
_platform_android,
|
||||
"Off the headset (flat), the first-person weapon drawn mirrored, as the\n"
|
||||
"left hand's is in VR: a test aid for what reads mirrored there. Read once." },
|
||||
{ "debug.network_corrupt", _config_real, "0.0", "HALO_NETWORK_CORRUPT", _environment_value, _platform_all,
|
||||
"Percent of the datagrams received that are damaged at random, to test\n"
|
||||
"that nothing a machine sends can crash the game; 0 none." },
|
||||
{ "debug.network_corrupt_stream", _config_real, "0.0", "HALO_NETWORK_CORRUPT_STREAM", _environment_value,
|
||||
_platform_all,
|
||||
"Percent of the reads of streams that are damaged at random, for the\n"
|
||||
"same (a damaged stream is closed, so a little goes a long way); 0 none." },
|
||||
{ "debug.network_corrupt_after", _config_real, "0.0", "HALO_NETWORK_CORRUPT_AFTER", _environment_value,
|
||||
_platform_all,
|
||||
"Seconds after the start before anything is damaged, so that a game can\n"
|
||||
"be set up and started first (a host's messages to its own client are\n"
|
||||
"damaged too)." },
|
||||
{ "debug.test_input", _config_string, "\"\"", "HALO_TEST_INPUT", _environment_value, _platform_all,
|
||||
"\"bot:<seed>\" plays controller 1 with a scripted pattern (automated\n"
|
||||
"network tests); \"look:<seed>\" stands still, only turning and looking\n"
|
||||
@@ -638,6 +690,8 @@ static const struct config_setting config_settings[] =
|
||||
|
||||
#ifdef HALO_ANDROID
|
||||
#define CONFIG_PLATFORM _platform_android
|
||||
#elif defined(_WIN32)
|
||||
#define CONFIG_PLATFORM (_platform_desktop | _platform_windows)
|
||||
#else
|
||||
#define CONFIG_PLATFORM _platform_desktop
|
||||
#endif
|
||||
|
||||
@@ -8,9 +8,10 @@ replaced and the game started again.
|
||||
The download is a plain HTTP/1.1 GET over TLS 1.2 or 1.3, following
|
||||
redirects (GitHub sends release downloads to its file host). The server's
|
||||
certificate must chain to one of the system's certificate authorities (the
|
||||
bundle the distribution keeps for OpenSSL, curl and the rest, or the file
|
||||
SSL_CERT_FILE names) and name the host; nothing is sent before that is
|
||||
checked.
|
||||
bundle the distribution keeps for OpenSSL, curl and the rest) and name the
|
||||
host; nothing is sent before that is checked. The file SSL_CERT_FILE names
|
||||
is used only on a system with no bundle, with a warning on stderr: whatever
|
||||
sets the environment would otherwise choose whom updates are trusted from.
|
||||
|
||||
Built with the host's ABI, as the other posix_*.c.
|
||||
*/
|
||||
@@ -62,12 +63,6 @@ static void load_certificates(void)
|
||||
|
||||
crypto_ready = psa_crypto_init() == PSA_SUCCESS;
|
||||
mbedtls_x509_crt_init(&certificates);
|
||||
if (environment && *environment && mbedtls_x509_crt_parse_file(&certificates, environment) >= 0 &&
|
||||
certificates.version)
|
||||
{
|
||||
certificates_loaded = 1;
|
||||
return;
|
||||
}
|
||||
for (index = 0; index < sizeof(certificate_bundles) / sizeof(*certificate_bundles); index++)
|
||||
{
|
||||
/* (a bundle's certificates that do not parse are left out: the
|
||||
@@ -76,9 +71,23 @@ static void load_certificates(void)
|
||||
mbedtls_x509_crt_parse_file(&certificates, certificate_bundles[index]) >= 0 && certificates.version)
|
||||
{
|
||||
certificates_loaded = 1;
|
||||
if (environment && *environment)
|
||||
{
|
||||
fprintf(stderr, "halo-linux: update: SSL_CERT_FILE is ignored; the update server is checked "
|
||||
"against %s\n", certificate_bundles[index]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
/* no bundle of the system's: the environment's, said loudly, since it
|
||||
decides which servers updates are taken from */
|
||||
if (environment && *environment && mbedtls_x509_crt_parse_file(&certificates, environment) >= 0 &&
|
||||
certificates.version)
|
||||
{
|
||||
certificates_loaded = 1;
|
||||
fprintf(stderr, "halo-linux: update: WARNING: no system certificate authorities were found; the update "
|
||||
"server is checked against SSL_CERT_FILE (%s) instead\n", environment);
|
||||
}
|
||||
}
|
||||
|
||||
static void set_error(char *error, int error_size, const char *what, int code)
|
||||
@@ -305,8 +314,13 @@ struct download
|
||||
unsigned long long received, total;
|
||||
};
|
||||
|
||||
/* (a release is tens of megabytes: a body past this fills no disk) */
|
||||
#define MAXIMUM_DOWNLOAD_SIZE (512ull * 1024 * 1024)
|
||||
|
||||
static int body_write(struct download *download, const unsigned char *data, size_t size)
|
||||
{
|
||||
if (download->received + size > MAXIMUM_DOWNLOAD_SIZE)
|
||||
return 0;
|
||||
if (fwrite(data, 1, size, download->file) != size)
|
||||
return 0;
|
||||
download->received += size;
|
||||
@@ -426,6 +440,12 @@ static int https_get(const char *url, struct download *download, char *location,
|
||||
{
|
||||
length = strtoull(value, NULL, 10);
|
||||
have_length = 1;
|
||||
if (length > MAXIMUM_DOWNLOAD_SIZE)
|
||||
{
|
||||
snprintf(error, (size_t)error_size, "the download from %s is too large", host);
|
||||
connection_free(&connection);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else if (!strcasecmp(line, "Transfer-Encoding") && strcasestr(value, "chunked"))
|
||||
{
|
||||
|
||||
@@ -31,7 +31,7 @@ update.h's: posix_update.c on Linux, win32_update.c on Windows.
|
||||
|
||||
#ifndef HALO_ANDROID
|
||||
|
||||
#include "memory/zlib/zlib.h"
|
||||
#include "zlib_prefixed.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <stdio.h>
|
||||
@@ -165,6 +165,13 @@ static int zip_extract_entry(SDL_IOStream *zip, unsigned long local_offset, int
|
||||
if (result != Z_OK && result != Z_STREAM_END)
|
||||
break;
|
||||
produced = sizeof(output) - stream.avail_out;
|
||||
/* (no more than the entry says, before it is on the disk) */
|
||||
if (written + (unsigned long)produced > size)
|
||||
{
|
||||
snprintf(reason, reason_size, "it unpacks to more than %lu bytes", size);
|
||||
result = Z_ERRNO;
|
||||
break;
|
||||
}
|
||||
if (SDL_WriteIO(file, output, produced) != produced)
|
||||
{
|
||||
snprintf(reason, reason_size, "could not write %s after %lu bytes (%s)", path, written, SDL_GetError());
|
||||
@@ -186,9 +193,9 @@ static int zip_extract_entry(SDL_IOStream *zip, unsigned long local_offset, int
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* (the game's zlib is 1.1, which can want a byte past the end of a
|
||||
raw stream before it says the stream has ended: all of the input
|
||||
unpacked is enough, as the size and the CRC are checked) */
|
||||
/* (all of the input unpacked is enough, as the size and the CRC are
|
||||
checked: the game's zlib 1.1, used here before, could want a byte past
|
||||
the end of a raw stream before it said the stream had ended) */
|
||||
if (ended || !remaining)
|
||||
{
|
||||
if (written != size)
|
||||
|
||||
@@ -17,6 +17,28 @@ yaw plus the head's.
|
||||
|
||||
#include "halo_vr.h"
|
||||
|
||||
/* the actions' settings and the buttons' values in them (halo_vr.h's
|
||||
HALO_VR_ACTION_* and HALO_VR_INPUT_*), by number */
|
||||
static const char *const vr_action_settings[HALO_VR_ACTION_COUNT] =
|
||||
{
|
||||
"vr.button_jump", "vr.button_light", "vr.button_action", "vr.button_weapon", "vr.button_crouch",
|
||||
"vr.button_zoom", "vr.button_melee",
|
||||
};
|
||||
static const char *const vr_input_values[HALO_VR_INPUT_COUNT] =
|
||||
{
|
||||
"a", "b", "x", "y", "move_stick", "turn_stick", "grip",
|
||||
};
|
||||
|
||||
const char *halo_vr_action_setting(int action)
|
||||
{
|
||||
return action >= 0 && action < HALO_VR_ACTION_COUNT ? vr_action_settings[action] : "";
|
||||
}
|
||||
|
||||
const char *halo_vr_input_value(int input)
|
||||
{
|
||||
return input >= 0 && input < HALO_VR_INPUT_COUNT ? vr_input_values[input] : "";
|
||||
}
|
||||
|
||||
#ifdef HALO_ANDROID
|
||||
|
||||
#include "platform.h"
|
||||
@@ -36,6 +58,14 @@ yaw plus the head's.
|
||||
/* the facing the game changes itself by more than this (a respawn, a
|
||||
teleport, a vehicle's seat) turns the body with it */
|
||||
#define FACING_JUMP (10.0f * PI / 180.0f)
|
||||
/* vr.foot_vignette: none on foot, while the stick turns smoothly, and
|
||||
while a stick turns smoothly or moves */
|
||||
enum
|
||||
{
|
||||
FOOT_VIGNETTE_OFF = 0,
|
||||
FOOT_VIGNETTE_TURNING,
|
||||
FOOT_VIGNETTE_MOVING,
|
||||
};
|
||||
|
||||
static struct
|
||||
{
|
||||
@@ -133,9 +163,13 @@ static struct
|
||||
float vehicle_steer_smoothing; /* seconds */
|
||||
float steer[2]; /* the steering as smoothed: aside, up (-1..1) */
|
||||
unsigned long long steer_last_us;
|
||||
/* the comfort vignette: how strong at most, now, and the heading it
|
||||
last saw */
|
||||
/* the comfort vignette: how strong at most in a vehicle, on foot
|
||||
(vr.foot_vignette: FOOT_VIGNETTE_*) and how strong there, now, and the
|
||||
heading it last saw */
|
||||
float comfort_vignette;
|
||||
int foot_vignette;
|
||||
float foot_vignette_strength;
|
||||
int seated; /* in a vehicle's seat, whatever the view */
|
||||
float vignette;
|
||||
float vignette_last_yaw;
|
||||
int vignette_yaw_valid;
|
||||
@@ -220,9 +254,9 @@ static struct
|
||||
/* until when a melee attack aims where the head looks: its damage is
|
||||
dealt some ticks into the attack, along the unit's aim */
|
||||
unsigned long long melee_aim_until_us;
|
||||
int melee_grip_down;
|
||||
int melee_button_down;
|
||||
float melee_strength; /* 0..1, the last attack's swing */
|
||||
unsigned long long melee_jab_start_us; /* the weapon hand's grip's attack: the weapon jabs */
|
||||
unsigned long long melee_jab_start_us; /* the melee button's attack: the weapon jabs */
|
||||
/* the laser along the barrel, for the frame it was set in */
|
||||
unsigned long frame_number;
|
||||
unsigned long laser_frame;
|
||||
@@ -250,6 +284,9 @@ static struct
|
||||
vr.mirror_controls the sticks and face buttons mirrored too */
|
||||
int left_handed;
|
||||
int mirror_controls;
|
||||
/* the button each action is on (vr.button_*: HALO_VR_INPUT_* by
|
||||
HALO_VR_ACTION_*) */
|
||||
int action_inputs[HALO_VR_ACTION_COUNT];
|
||||
/* vr.refresh_rate as last asked for (the VR options change it), and
|
||||
vr.resolution_scale as the app started with it */
|
||||
float refresh_rate_wanted;
|
||||
@@ -307,6 +344,7 @@ static int name_pose(const float point[3], struct halo_xr_pose *pose, float *wid
|
||||
static int weapon_hand_tracked(short local_player_index);
|
||||
static void controller_axes(float forward[3], float up[3], float right[3]);
|
||||
static void hand_offset(float out[3]);
|
||||
static void controls_in_play(struct halo_xr_hand hands[HALO_XR_HAND_COUNT]);
|
||||
|
||||
/* the hand that holds the weapon, the free one (the grenade, the
|
||||
foregrip), and the one whose stick turns the body and steers */
|
||||
@@ -494,6 +532,13 @@ static void vr_read_live_settings(void)
|
||||
vr.laser = config_boolean("vr.laser");
|
||||
vr.vehicle_wheel = config_boolean("vr.vehicle_wheel");
|
||||
vr.comfort_vignette = (float)config_real("vr.comfort_vignette");
|
||||
{
|
||||
const char *foot = config_string("vr.foot_vignette");
|
||||
|
||||
vr.foot_vignette = !strcmp(foot, "moving") ? FOOT_VIGNETTE_MOVING :
|
||||
!strcmp(foot, "turning") ? FOOT_VIGNETTE_TURNING : FOOT_VIGNETTE_OFF;
|
||||
}
|
||||
vr.foot_vignette_strength = (float)config_real("vr.foot_vignette_strength");
|
||||
vr.fingers = config_boolean("vr.fingers");
|
||||
vr.arm_ik = config_boolean("vr.arm_ik");
|
||||
{
|
||||
@@ -517,6 +562,12 @@ static void vr_read_live_settings(void)
|
||||
vr.left_handed = left_handed;
|
||||
vr.mirror_controls = config_boolean("vr.mirror_controls");
|
||||
}
|
||||
{
|
||||
int action;
|
||||
|
||||
for (action = 0; action < HALO_VR_ACTION_COUNT; action++)
|
||||
vr.action_inputs[action] = halo_vr_action_input(action);
|
||||
}
|
||||
{
|
||||
/* (the refresh rate changes at once where the runtime lets it) */
|
||||
float refresh_rate = (float)config_real("vr.refresh_rate");
|
||||
@@ -619,6 +670,22 @@ int halo_vr_weapon_hand(void)
|
||||
return weapon_hand();
|
||||
}
|
||||
|
||||
int halo_vr_action_input(int action)
|
||||
{
|
||||
const char *value;
|
||||
int input;
|
||||
|
||||
if (action < 0 || action >= HALO_VR_ACTION_COUNT)
|
||||
return HALO_VR_INPUT_A;
|
||||
value = config_string(vr_action_settings[action]);
|
||||
for (input = 0; input < HALO_VR_INPUT_COUNT; input++)
|
||||
{
|
||||
if (!strcmp(value, vr_input_values[input]))
|
||||
return input;
|
||||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
int halo_vr_refresh_rates(float *rates, int maximum, float *current)
|
||||
{
|
||||
*current = 0.0f;
|
||||
@@ -980,18 +1047,21 @@ static void melee_update(void)
|
||||
|
||||
vr.hand_last_us = now;
|
||||
{
|
||||
/* the weapon hand's grip's melee attack (xinput_sdl.c) aims the
|
||||
same way */
|
||||
int grip_down = vr.frame.hands[weapon_hand()].active &&
|
||||
(vr.frame.hands[weapon_hand()].buttons & HALO_XR_BUTTON_SQUEEZE);
|
||||
/* the melee button's attack (xinput_sdl.c; the weapon hand's grip
|
||||
unless vr.button_melee moves it) aims the same way */
|
||||
struct halo_xr_hand hands[HALO_XR_HAND_COUNT];
|
||||
int button_down;
|
||||
|
||||
if (grip_down && !vr.melee_grip_down)
|
||||
memcpy(hands, vr.frame.hands, sizeof(hands));
|
||||
controls_in_play(hands);
|
||||
button_down = (hands[HALO_XR_HAND_RIGHT].buttons & HALO_XR_BUTTON_SQUEEZE) != 0;
|
||||
if (button_down && !vr.melee_button_down)
|
||||
{
|
||||
vr.melee_aim_until_us = now + 700000;
|
||||
vr.melee_strength = 0.75f;
|
||||
vr.melee_jab_start_us = now;
|
||||
}
|
||||
vr.melee_grip_down = grip_down;
|
||||
vr.melee_button_down = button_down;
|
||||
}
|
||||
quaternion_rotate(vr.frame.head.orientation, look, facing);
|
||||
facing[1] = 0.0f;
|
||||
@@ -1378,13 +1448,45 @@ int halo_vr_wheel_held(void)
|
||||
}
|
||||
|
||||
/* The comfort vignette: while a vehicle turns the player (from 15 degrees a
|
||||
second, fully at 60), the eye images darken from their edges; it comes in
|
||||
quickly and goes slowly. */
|
||||
second, fully at 60), the eye images darken from their edges; on foot
|
||||
(vr.foot_vignette), while the turning stick turns smoothly and, with
|
||||
"moving", the move stick moves, as far as they are pushed (fully at three
|
||||
quarters). It comes in quickly and goes slowly. */
|
||||
static float foot_vignette_target(void)
|
||||
{
|
||||
const struct halo_xr_hand *turn = &vr.frame.hands[turn_hand()];
|
||||
const struct halo_xr_hand *move = &vr.frame.hands[turn_hand() == HALO_XR_HAND_RIGHT ? HALO_XR_HAND_LEFT :
|
||||
HALO_XR_HAND_RIGHT];
|
||||
float push = 0.0f;
|
||||
|
||||
if (vr.foot_vignette == FOOT_VIGNETTE_OFF || vr.foot_vignette_strength <= 0.0f || vr.seated ||
|
||||
!vr.frame.focused || vr.last_overlay_mode == HALO_XR_OVERLAY_SCREEN)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
if (vr.smooth_turn && turn->active)
|
||||
push = fabsf(turn->thumbstick[0]);
|
||||
if (vr.foot_vignette == FOOT_VIGNETTE_MOVING && move->active)
|
||||
{
|
||||
float moving = sqrtf(move->thumbstick[0] * move->thumbstick[0] + move->thumbstick[1] * move->thumbstick[1]);
|
||||
|
||||
if (moving > push)
|
||||
push = moving;
|
||||
}
|
||||
/* (beyond the sticks' dead zone, 0.15) */
|
||||
push = (push - 0.15f) / 0.6f;
|
||||
if (push < 0.0f)
|
||||
push = 0.0f;
|
||||
if (push > 1.0f)
|
||||
push = 1.0f;
|
||||
return push * (vr.foot_vignette_strength > 1.0f ? 1.0f : vr.foot_vignette_strength);
|
||||
}
|
||||
|
||||
static void vignette_update(void)
|
||||
{
|
||||
unsigned long long now = now_us();
|
||||
float seconds = vr.vignette_last_us ? (float)(now - vr.vignette_last_us) * 1e-6f : 0.0f;
|
||||
float target = 0.0f, rate;
|
||||
float target = foot_vignette_target(), rate;
|
||||
|
||||
vr.vignette_last_us = now;
|
||||
if (seconds > 0.1f)
|
||||
@@ -1396,13 +1498,15 @@ static void vignette_update(void)
|
||||
if (vr.vignette_yaw_valid && seconds > 0.0f)
|
||||
{
|
||||
float turning = fabsf(wrap_angle(yaw - vr.vignette_last_yaw)) / seconds;
|
||||
float vehicle = (turning - 15.0f * PI / 180.0f) / (45.0f * PI / 180.0f);
|
||||
|
||||
target = (turning - 15.0f * PI / 180.0f) / (45.0f * PI / 180.0f);
|
||||
if (target < 0.0f)
|
||||
target = 0.0f;
|
||||
if (target > 1.0f)
|
||||
target = 1.0f;
|
||||
target *= vr.comfort_vignette;
|
||||
if (vehicle < 0.0f)
|
||||
vehicle = 0.0f;
|
||||
if (vehicle > 1.0f)
|
||||
vehicle = 1.0f;
|
||||
vehicle *= vr.comfort_vignette;
|
||||
if (vehicle > target)
|
||||
target = vehicle;
|
||||
}
|
||||
vr.vignette_last_yaw = yaw;
|
||||
vr.vignette_yaw_valid = 1;
|
||||
@@ -1423,6 +1527,7 @@ void halo_vr_set_seat(short local_player_index, int seat, int armed, float vehic
|
||||
{
|
||||
if (!vr.active || local_player_index != 0)
|
||||
return;
|
||||
vr.seated = seat != HALO_VR_SEAT_NONE;
|
||||
if (!vr.vehicle_first_person)
|
||||
seat = HALO_VR_SEAT_NONE;
|
||||
vr.seat_yaw = vehicle_yaw;
|
||||
@@ -3046,6 +3151,51 @@ static void controllers_mirrored(struct halo_xr_hand hands[HALO_XR_HAND_COUNT])
|
||||
}
|
||||
}
|
||||
|
||||
/* The buttons the actions are on (vr.button_*), as the gamepad's sides
|
||||
have them once mirrored: A and B the right hand's, X and Y the left's
|
||||
A and B, the move stick's click the left's, the turning stick's the
|
||||
right's, the grip the right's (the weapon hand's). Each action's button
|
||||
is read and handed on as the button the action has by default, which
|
||||
xinput_sdl.c turns into it; with the defaults nothing changes. The grips
|
||||
holding a Warthog's wheel press nothing. */
|
||||
static const int input_hands[HALO_VR_INPUT_COUNT] =
|
||||
{
|
||||
HALO_XR_HAND_RIGHT, HALO_XR_HAND_RIGHT, HALO_XR_HAND_LEFT, HALO_XR_HAND_LEFT, HALO_XR_HAND_LEFT,
|
||||
HALO_XR_HAND_RIGHT, HALO_XR_HAND_RIGHT,
|
||||
};
|
||||
static const int input_buttons[HALO_VR_INPUT_COUNT] =
|
||||
{
|
||||
HALO_XR_BUTTON_A, HALO_XR_BUTTON_B, HALO_XR_BUTTON_A, HALO_XR_BUTTON_B, HALO_XR_BUTTON_THUMBSTICK,
|
||||
HALO_XR_BUTTON_THUMBSTICK, HALO_XR_BUTTON_SQUEEZE,
|
||||
};
|
||||
|
||||
static void controls_remapped(struct halo_xr_hand hands[HALO_XR_HAND_COUNT])
|
||||
{
|
||||
int down[HALO_VR_INPUT_COUNT];
|
||||
int input, action;
|
||||
|
||||
for (input = 0; input < HALO_VR_INPUT_COUNT; input++)
|
||||
down[input] = (hands[input_hands[input]].buttons & input_buttons[input]) != 0;
|
||||
if (vr.wheel_hands)
|
||||
down[HALO_VR_INPUT_GRIP] = 0;
|
||||
for (input = 0; input < HALO_VR_INPUT_COUNT; input++)
|
||||
hands[input_hands[input]].buttons &= ~input_buttons[input];
|
||||
for (action = 0; action < HALO_VR_ACTION_COUNT; action++)
|
||||
{
|
||||
if (down[vr.action_inputs[action]])
|
||||
hands[input_hands[action]].buttons |= input_buttons[action];
|
||||
}
|
||||
}
|
||||
|
||||
/* the controllers as the game is played with them: mirrored (left-handed)
|
||||
and the actions on their buttons */
|
||||
static void controls_in_play(struct halo_xr_hand hands[HALO_XR_HAND_COUNT])
|
||||
{
|
||||
if (vr.left_handed)
|
||||
controllers_mirrored(hands);
|
||||
controls_remapped(hands);
|
||||
}
|
||||
|
||||
/* the controllers, for the gamepad the game reads (xinput_sdl.c) */
|
||||
int halo_vr_controllers(struct halo_xr_hand hands[HALO_XR_HAND_COUNT], int *menus)
|
||||
{
|
||||
@@ -3053,8 +3203,8 @@ int halo_vr_controllers(struct halo_xr_hand hands[HALO_XR_HAND_COUNT], int *menu
|
||||
return 0;
|
||||
memcpy(hands, vr.frame.hands, sizeof(vr.frame.hands));
|
||||
*menus = vr.last_overlay_mode == HALO_XR_OVERLAY_SCREEN;
|
||||
if (vr.left_handed && !*menus)
|
||||
controllers_mirrored(hands);
|
||||
if (!*menus)
|
||||
controls_in_play(hands);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -3062,6 +3212,7 @@ int halo_vr_controllers(struct halo_xr_hand hands[HALO_XR_HAND_COUNT], int *menu
|
||||
|
||||
int halo_vr_controller_layout(void) { return 0; }
|
||||
int halo_vr_weapon_hand(void) { return 1; }
|
||||
int halo_vr_action_input(int action) { return action; }
|
||||
int halo_vr_graphics(void) { return HALO_VR_GRAPHICS_HIGH; }
|
||||
int halo_vr_refresh_rates(float *rates, int maximum, float *current)
|
||||
{
|
||||
|
||||
@@ -210,7 +210,10 @@ void platform_translate_path(const char *xbox_path, char *host_path, unsigned lo
|
||||
while (*cursor && *cursor != '\\' && *cursor != '/' && component_length + 1 < sizeof(component))
|
||||
component[component_length++] = *cursor++;
|
||||
component[component_length] = '\0';
|
||||
if (!component_length || !strcmp(component, "."))
|
||||
/* . and .. (and Windows' spellings of them, with trailing dots and
|
||||
spaces) are skipped: FATX names are never only dots, and .. would
|
||||
leave the root */
|
||||
if (!component_length || strspn(component, ". ") == component_length)
|
||||
continue;
|
||||
|
||||
if (posix_find_entry_case_insensitive(resolved, component, on_disk, sizeof(on_disk)))
|
||||
|
||||
@@ -210,6 +210,15 @@ unsigned long xgpu_texture_face_size(const struct xgpu_texture_description *desc
|
||||
return size;
|
||||
}
|
||||
|
||||
/* whether the size is one D3DDevice_GetDeviceCaps allows (d3d8_gl.c): up
|
||||
to 4096 by 4096, and 512 each way for a volume */
|
||||
static BOOL texture_size_supported(const struct xgpu_texture_description *description)
|
||||
{
|
||||
if (description->depth > 1)
|
||||
return description->width <= 512 && description->height <= 512 && description->depth <= 512;
|
||||
return description->width <= 4096 && description->height <= 4096;
|
||||
}
|
||||
|
||||
unsigned long xgpu_texture_level_pitch(const struct xgpu_texture_description *description, unsigned long level)
|
||||
{
|
||||
struct format_information information = format_information(description->format);
|
||||
@@ -303,20 +312,22 @@ static unsigned long yuv_to_argb(long y, long u, long v)
|
||||
clamp_byte((298 * c + 516 * d + 128) >> 8));
|
||||
}
|
||||
|
||||
/* a texel's 16 and 32 bits, read only for the kinds that have them (the last
|
||||
texel of a 1-byte texture read 3 bytes past it) */
|
||||
#define TEXEL16(source) ((unsigned long)(source)[0] | ((unsigned long)(source)[1] << 8))
|
||||
#define TEXEL32(source) (TEXEL16(source) | ((unsigned long)(source)[2] << 16) | ((unsigned long)(source)[3] << 24))
|
||||
|
||||
static unsigned long convert_texel(unsigned char kind, const unsigned char *source, const D3DCOLOR *palette,
|
||||
unsigned long x, const unsigned char *row)
|
||||
{
|
||||
unsigned long v16 = source[0] | ((unsigned long)source[1] << 8);
|
||||
unsigned long v32 = v16 | ((unsigned long)source[2] << 16) | ((unsigned long)source[3] << 24);
|
||||
|
||||
switch (kind)
|
||||
{
|
||||
case _texel_a8r8g8b8: return v32;
|
||||
case _texel_x8r8g8b8: return v32 | 0xff000000UL;
|
||||
case _texel_r5g6b5: return argb(255, expand5(v16 >> 11), expand6((v16 >> 5) & 0x3f), expand5(v16 & 0x1f));
|
||||
case _texel_a1r5g5b5: return argb((v16 & 0x8000) ? 255 : 0, expand5((v16 >> 10) & 0x1f), expand5((v16 >> 5) & 0x1f), expand5(v16 & 0x1f));
|
||||
case _texel_x1r5g5b5: return argb(255, expand5((v16 >> 10) & 0x1f), expand5((v16 >> 5) & 0x1f), expand5(v16 & 0x1f));
|
||||
case _texel_a4r4g4b4: return argb(expand4(v16 >> 12), expand4((v16 >> 8) & 0xf), expand4((v16 >> 4) & 0xf), expand4(v16 & 0xf));
|
||||
case _texel_a8r8g8b8: return TEXEL32(source);
|
||||
case _texel_x8r8g8b8: return TEXEL32(source) | 0xff000000UL;
|
||||
case _texel_r5g6b5: return argb(255, expand5(TEXEL16(source) >> 11), expand6((TEXEL16(source) >> 5) & 0x3f), expand5(TEXEL16(source) & 0x1f));
|
||||
case _texel_a1r5g5b5: return argb((TEXEL16(source) & 0x8000) ? 255 : 0, expand5((TEXEL16(source) >> 10) & 0x1f), expand5((TEXEL16(source) >> 5) & 0x1f), expand5(TEXEL16(source) & 0x1f));
|
||||
case _texel_x1r5g5b5: return argb(255, expand5((TEXEL16(source) >> 10) & 0x1f), expand5((TEXEL16(source) >> 5) & 0x1f), expand5(TEXEL16(source) & 0x1f));
|
||||
case _texel_a4r4g4b4: return argb(expand4(TEXEL16(source) >> 12), expand4((TEXEL16(source) >> 8) & 0xf), expand4((TEXEL16(source) >> 4) & 0xf), expand4(TEXEL16(source) & 0xf));
|
||||
case _texel_l8: return argb(255, source[0], source[0], source[0]);
|
||||
case _texel_al8: return argb(source[0], source[0], source[0], source[0]);
|
||||
case _texel_a8: return argb(source[0], 255, 255, 255);
|
||||
@@ -325,14 +336,14 @@ static unsigned long convert_texel(unsigned char kind, const unsigned char *sour
|
||||
/* V8U8 shares this format: U (the low byte) reads as red, V as green */
|
||||
case _texel_g8b8: return argb(255, source[0], source[1], 0);
|
||||
case _texel_r8b8: return argb(255, source[1], 0, source[0]);
|
||||
case _texel_r6g5b5: return argb(255, expand6(v16 >> 10), expand5((v16 >> 5) & 0x1f), expand5(v16 & 0x1f));
|
||||
case _texel_r6g5b5: return argb(255, expand6(TEXEL16(source) >> 10), expand5((TEXEL16(source) >> 5) & 0x1f), expand5(TEXEL16(source) & 0x1f));
|
||||
case _texel_l16: return argb(255, source[1], source[1], source[1]);
|
||||
case _texel_v16u16: return argb(255, source[1], source[3], 0);
|
||||
case _texel_a8b8g8r8: return argb(source[3], source[0], source[1], source[2]);
|
||||
case _texel_b8g8r8a8: return argb(source[0], source[1], source[2], source[3]);
|
||||
case _texel_r8g8b8a8: return argb(source[0], source[3], source[2], source[1]);
|
||||
case _texel_r5g5b5a1: return argb((v16 & 1) ? 255 : 0, expand5(v16 >> 11), expand5((v16 >> 6) & 0x1f), expand5((v16 >> 1) & 0x1f));
|
||||
case _texel_r4g4b4a4: return argb(expand4(v16 & 0xf), expand4(v16 >> 12), expand4((v16 >> 8) & 0xf), expand4((v16 >> 4) & 0xf));
|
||||
case _texel_r5g5b5a1: return argb((TEXEL16(source) & 1) ? 255 : 0, expand5(TEXEL16(source) >> 11), expand5((TEXEL16(source) >> 6) & 0x1f), expand5((TEXEL16(source) >> 1) & 0x1f));
|
||||
case _texel_r4g4b4a4: return argb(expand4(TEXEL16(source) & 0xf), expand4(TEXEL16(source) >> 12), expand4((TEXEL16(source) >> 8) & 0xf), expand4((TEXEL16(source) >> 4) & 0xf));
|
||||
case _texel_yuy2:
|
||||
{
|
||||
const unsigned char *pair = row + (x & ~1UL) * 2;
|
||||
@@ -347,12 +358,13 @@ static unsigned long convert_texel(unsigned char kind, const unsigned char *sour
|
||||
}
|
||||
case _texel_d24s8: return argb(255, source[3], source[3], source[3]);
|
||||
case _texel_d16: return argb(255, source[1], source[1], source[1]);
|
||||
default: return v32;
|
||||
default: return TEXEL32(source);
|
||||
}
|
||||
}
|
||||
|
||||
/* one level (or 3D slice set) of an uncompressed texture into BGRA */
|
||||
static void decode_level(const struct xgpu_texture_description *description, unsigned long level,
|
||||
/* one level (or 3D slice set) of an uncompressed texture into BGRA; FALSE
|
||||
when out of memory */
|
||||
static BOOL decode_level(const struct xgpu_texture_description *description, unsigned long level,
|
||||
const unsigned char *source, const D3DCOLOR *palette, unsigned long *destination)
|
||||
{
|
||||
struct format_information information = format_information(description->format);
|
||||
@@ -363,19 +375,30 @@ static void decode_level(const struct xgpu_texture_description *description, uns
|
||||
|
||||
if (description->linear)
|
||||
{
|
||||
/* only the texels a row's pitch holds: a Size word whose pitch is
|
||||
narrower than its width (a map's bitmap) read past the texture's
|
||||
pitch * height bytes; the rest of such a row is black (a YUV texel
|
||||
reads its pair's four bytes) */
|
||||
unsigned long row_texels = information.bytes ? description->pitch / information.bytes : 0;
|
||||
|
||||
if (information.kind == _texel_yuy2 || information.kind == _texel_uyvy)
|
||||
row_texels &= ~1UL;
|
||||
for (y = 0; y < height; y++)
|
||||
{
|
||||
const unsigned char *row = source + y * description->pitch;
|
||||
|
||||
for (x = 0; x < width; x++)
|
||||
destination[y * width + x] = convert_texel(information.kind, row + x * information.bytes, palette, x, row);
|
||||
destination[y * width + x] = x < row_texels ?
|
||||
convert_texel(information.kind, row + x * information.bytes, palette, x, row) : 0;
|
||||
}
|
||||
return;
|
||||
return TRUE;
|
||||
}
|
||||
{
|
||||
struct swizzle_masks masks = swizzle_masks(width, height, depth);
|
||||
unsigned long *x_offsets = malloc(width * sizeof(unsigned long));
|
||||
|
||||
if (!x_offsets)
|
||||
return FALSE;
|
||||
for (x = 0; x < width; x++)
|
||||
x_offsets[x] = spread(masks.x, x);
|
||||
for (z = 0; z < depth; z++)
|
||||
@@ -396,6 +419,7 @@ static void decode_level(const struct xgpu_texture_description *description, uns
|
||||
}
|
||||
free(x_offsets);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
#ifdef HALO_ANDROID
|
||||
@@ -556,6 +580,8 @@ static void texture_dump(GLenum target, const struct xgpu_texture_description *d
|
||||
if (!directory || target != GL_TEXTURE_2D)
|
||||
return;
|
||||
pixels = malloc(width * height * 4);
|
||||
if (!pixels)
|
||||
return;
|
||||
glGetTexImage(GL_TEXTURE_2D, 0, GL_BGRA, GL_UNSIGNED_BYTE, pixels);
|
||||
snprintf(path, sizeof(path), "%s/tex%05lu_fmt%02x_%lux%lu.tga", directory, dump_index++,
|
||||
(unsigned)description->format, width, height);
|
||||
@@ -590,6 +616,12 @@ static void upload(GLuint texture, GLenum target, const struct xgpu_texture_desc
|
||||
decode_compressed = description->compressed && !xgpu_capabilities.s3tc;
|
||||
#endif
|
||||
converted = description->compressed && !decode_compressed ? NULL : malloc(largest * sizeof(unsigned long));
|
||||
if (!converted && !(description->compressed && !decode_compressed))
|
||||
{
|
||||
platform_log("textures: no memory to convert a %lux%lux%lu texture; it is not drawn",
|
||||
description->width, description->height, description->depth);
|
||||
return;
|
||||
}
|
||||
glBindTexture(target, texture);
|
||||
xgpu_gl_state_invalidate();
|
||||
#ifdef HALO_ANDROID
|
||||
@@ -629,7 +661,13 @@ static void upload(GLuint texture, GLenum target, const struct xgpu_texture_desc
|
||||
(unsigned long)depth, converted);
|
||||
else
|
||||
#endif
|
||||
decode_level(description, level, source, palette, converted);
|
||||
if (!decode_level(description, level, source, palette, converted))
|
||||
{
|
||||
platform_log("textures: no memory to convert a %lux%lux%lu texture; it is not drawn",
|
||||
description->width, description->height, description->depth);
|
||||
free(converted);
|
||||
return;
|
||||
}
|
||||
if (target == GL_TEXTURE_3D)
|
||||
glTexImage3D(image_target, (GLint)level, GL_RGBA8, width, height, depth, 0, GL_BGRA, GL_UNSIGNED_BYTE, converted);
|
||||
else
|
||||
@@ -805,6 +843,12 @@ GLuint xgpu_texture_get(const DWORD *resource, const D3DCOLOR *palette, GLenum *
|
||||
if (!entry)
|
||||
{
|
||||
entry = calloc(1, sizeof(*entry));
|
||||
if (!entry)
|
||||
{
|
||||
xgpu_texture_describe(format_word, size_word, description);
|
||||
*target = GL_TEXTURE_2D;
|
||||
return 0;
|
||||
}
|
||||
entry->data = data;
|
||||
entry->format_word = format_word;
|
||||
entry->size_word = size_word;
|
||||
@@ -813,7 +857,15 @@ GLuint xgpu_texture_get(const DWORD *resource, const D3DCOLOR *palette, GLenum *
|
||||
entry->target = entry->description.cube_map ? GL_TEXTURE_CUBE_MAP :
|
||||
entry->description.depth > 1 ? GL_TEXTURE_3D : GL_TEXTURE_2D;
|
||||
entry->address = (unsigned long)PLATFORM_PHYSICAL_TO_VIRTUAL(data);
|
||||
entry->size = xgpu_texture_face_size(&entry->description) * (entry->description.cube_map ? 6 : 1);
|
||||
/* (a size beyond D3DDevice_GetDeviceCaps' is never uploaded: its
|
||||
byte counts would not fit in 32 bits) */
|
||||
entry->size = texture_size_supported(&entry->description) ?
|
||||
xgpu_texture_face_size(&entry->description) * (entry->description.cube_map ? 6 : 1) : 0;
|
||||
if (!entry->size)
|
||||
{
|
||||
platform_log("textures: a %lux%lux%lu texture is larger than the device takes; it is not drawn",
|
||||
entry->description.width, entry->description.height, entry->description.depth);
|
||||
}
|
||||
entry->generation = 0;
|
||||
entry->override = -1;
|
||||
glGenTextures(1, &entry->texture);
|
||||
@@ -843,7 +895,7 @@ GLuint xgpu_texture_get(const DWORD *resource, const D3DCOLOR *palette, GLenum *
|
||||
entry->generation = 1;
|
||||
/* (which bitmap is here may have changed with the pixels) */
|
||||
entry->override = -1;
|
||||
if (!palettized && !entry->description.cube_map && entry->description.depth == 1)
|
||||
if (entry->size && !palettized && !entry->description.cube_map && entry->description.depth == 1)
|
||||
{
|
||||
unsigned long levels;
|
||||
|
||||
@@ -853,7 +905,7 @@ GLuint xgpu_texture_get(const DWORD *resource, const D3DCOLOR *palette, GLenum *
|
||||
if (entry->override >= 0 && !hud_hires_override_texture(entry->override, &levels))
|
||||
entry->override = -1;
|
||||
}
|
||||
if (entry->override < 0 && platform_is_contiguous((void *)entry->address) &&
|
||||
if (entry->override < 0 && entry->size && platform_is_contiguous((void *)entry->address) &&
|
||||
platform_is_contiguous((void *)(entry->address + entry->size - 1)))
|
||||
{
|
||||
if (config_boolean("debug.texture_log"))
|
||||
|
||||
@@ -159,11 +159,16 @@ static void delete_tree(const char *xbox_directory)
|
||||
do
|
||||
{
|
||||
char child[MAX_PATH * 2];
|
||||
int directory = (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
|
||||
|
||||
snprintf(child, sizeof(child), "%s%s", xbox_directory, data.cFileName);
|
||||
if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
||||
/* (a path that does not fit is left) */
|
||||
if (snprintf(child, sizeof(child), "%s%s%s", xbox_directory, data.cFileName, directory ? "\\" : "") >=
|
||||
(int)sizeof(child))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (directory)
|
||||
{
|
||||
strcat(child, "\\");
|
||||
delete_tree(child);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -226,6 +226,9 @@ struct xgpu_render_target
|
||||
GLuint multisample;
|
||||
int samples;
|
||||
BOOL unresolved;
|
||||
/* changes whenever the target is drawn into or cleared (d3d8_gl.c,
|
||||
bind_targets) */
|
||||
unsigned long written;
|
||||
};
|
||||
|
||||
/* the GL texture holding a render target with this physical address, or 0 */
|
||||
|
||||
@@ -25,7 +25,7 @@ anti_aliasing_values).
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifndef HALO_ANDROID
|
||||
#include "memory/zlib/zlib.h"
|
||||
#include "zlib_prefixed.h"
|
||||
|
||||
/* port/third_party/smaa, embedded by tools/embed_assets.py: the shader's
|
||||
text (ending in a NUL), and the lookup textures' bytes compressed with
|
||||
@@ -372,8 +372,8 @@ static GLuint lookup_texture(const unsigned int *stream, unsigned long stream_si
|
||||
if (!texels)
|
||||
return 0;
|
||||
result = uncompress(texels, &size, (const Bytef *)stream, (uLong)stream_size);
|
||||
/* (the game's zlib is 1.1, which can stop short of saying the stream
|
||||
has ended: the size says whether it is whole) */
|
||||
/* (the size says whether it is whole, as with the game's zlib 1.1,
|
||||
which could stop short of saying the stream had ended) */
|
||||
if ((result == Z_OK || result == Z_BUF_ERROR) && size == (uLongf)(width * height * texel_size))
|
||||
{
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
|
||||
@@ -859,6 +859,11 @@ face buttons too (the right stick moves, X jumps, A reloads; on the Steam
|
||||
Frame the left D-pad takes A, B, X and Y). Here "right" and "left" are the
|
||||
sides so handed over.
|
||||
|
||||
The VR options' BUTTONS page (vr.button_*) moves jump, the flashlight,
|
||||
action, switch weapon, crouch, zoom and melee between A, B, X, Y, the
|
||||
stick clicks and the weapon hand's grip: vr.c hands each action's button
|
||||
over as the one above, so "A" here is the jump wherever it is.
|
||||
|
||||
In the menus the buttons are the Xbox's by position: A accepts, B goes back. */
|
||||
|
||||
#include "guest_host.h"
|
||||
@@ -937,9 +942,8 @@ static void vr_gamepad_state(XINPUT_GAMEPAD *pad)
|
||||
}
|
||||
merge_button(pad, XINPUT_GAMEPAD_A, (right->buttons & HALO_XR_BUTTON_A) != 0);
|
||||
merge_button(pad, XINPUT_GAMEPAD_X, (left->buttons & HALO_XR_BUTTON_A) != 0);
|
||||
/* (the grips holding a Warthog's wheel are not a blow) */
|
||||
merge_button(pad, XINPUT_GAMEPAD_B,
|
||||
((right->buttons & HALO_XR_BUTTON_SQUEEZE) != 0 && !halo_vr_wheel_held()) || halo_vr_melee());
|
||||
/* (the grips holding a Warthog's wheel are not a blow: vr.c) */
|
||||
merge_button(pad, XINPUT_GAMEPAD_B, (right->buttons & HALO_XR_BUTTON_SQUEEZE) != 0 || halo_vr_melee());
|
||||
merge_button(pad, XINPUT_GAMEPAD_Y, (left->buttons & HALO_XR_BUTTON_B) != 0);
|
||||
/* (the Steam Frame's bumpers: the left switches grenades, the right is
|
||||
the flashlight) */
|
||||
|
||||
@@ -611,7 +611,13 @@ int WSAAPI halo_ws_sendto(SOCKET socket, const char *buffer, int length, int fla
|
||||
/* debug.network_latency and debug.network_loss: what this machine receives
|
||||
is held back that many milliseconds (both ways between two machines: a
|
||||
round trip of twice it) and that share of its datagrams lost, to test the
|
||||
netcode as over the internet */
|
||||
netcode as over the internet. debug.network_corrupt: that share of the
|
||||
datagrams it receives, and debug.network_corrupt_stream that share of its
|
||||
reads of streams, is damaged at random (bytes changed, cut short,
|
||||
stretched, or replaced) from debug.network_corrupt_after seconds after the
|
||||
start (so that the game can be set up and started: what a host sends its
|
||||
own client over the loopback is damaged too), to test that nothing another
|
||||
machine sends can crash the game */
|
||||
#define DELAYED_PACKET_SIZE 1500
|
||||
#define MAXIMUM_DELAYED_PACKETS 4096
|
||||
|
||||
@@ -632,6 +638,11 @@ static struct
|
||||
int checked;
|
||||
DWORD latency;
|
||||
int loss_percent;
|
||||
int corrupt_percent;
|
||||
int corrupt_stream_percent;
|
||||
DWORD corrupt_after;
|
||||
DWORD started_time;
|
||||
unsigned corrupt_seed;
|
||||
struct delayed_packet *packets;
|
||||
int first;
|
||||
int count;
|
||||
@@ -647,23 +658,109 @@ static int delayed_enabled(void)
|
||||
{
|
||||
double latency = config_real("debug.network_latency");
|
||||
double loss = config_real("debug.network_loss");
|
||||
double corrupt = config_real("debug.network_corrupt");
|
||||
double corrupt_stream = config_real("debug.network_corrupt_stream");
|
||||
double corrupt_after = config_real("debug.network_corrupt_after");
|
||||
|
||||
/* up to ten seconds, and a share */
|
||||
/* up to ten seconds, and shares */
|
||||
latency = !(latency > 0.0) ? 0.0 : latency > 10000.0 ? 10000.0 : latency;
|
||||
loss = !(loss > 0.0) ? 0.0 : loss > 100.0 ? 100.0 : loss;
|
||||
corrupt = !(corrupt > 0.0) ? 0.0 : corrupt > 100.0 ? 100.0 : corrupt;
|
||||
corrupt_stream = !(corrupt_stream > 0.0) ? 0.0 : corrupt_stream > 100.0 ? 100.0 : corrupt_stream;
|
||||
corrupt_after = !(corrupt_after > 0.0) ? 0.0 : corrupt_after > 86400.0 ? 86400.0 : corrupt_after;
|
||||
delayed.latency = (DWORD)latency;
|
||||
delayed.loss_percent = (int)loss;
|
||||
delayed.corrupt_percent = (int)corrupt;
|
||||
delayed.corrupt_stream_percent = (int)corrupt_stream;
|
||||
delayed.corrupt_after = (DWORD)(corrupt_after * 1000.0);
|
||||
delayed.started_time = GetTickCount();
|
||||
delayed.corrupt_seed = (unsigned)GetTickCount() ^ (unsigned)(size_t)&delayed.checked;
|
||||
delayed.checked = 1;
|
||||
if (delayed.latency || delayed.loss_percent)
|
||||
if (delayed.latency || delayed.loss_percent || delayed.corrupt_percent || delayed.corrupt_stream_percent)
|
||||
{
|
||||
delayed.packets = calloc(MAXIMUM_DELAYED_PACKETS, sizeof(*delayed.packets));
|
||||
platform_log("network: receiving %lu ms late, losing %d%% of datagrams (testing)",
|
||||
(unsigned long)delayed.latency, delayed.loss_percent);
|
||||
platform_log("network: receiving %lu ms late, losing %d%% of datagrams, damaging %d%% of them and %d%% of stream reads after %lu s (testing)",
|
||||
(unsigned long)delayed.latency, delayed.loss_percent, delayed.corrupt_percent,
|
||||
delayed.corrupt_stream_percent, (unsigned long)(delayed.corrupt_after / 1000));
|
||||
}
|
||||
}
|
||||
return delayed.packets != NULL;
|
||||
}
|
||||
|
||||
/* (the test's own numbers, so that the game's rand() goes as without it) */
|
||||
static unsigned delayed_random(void)
|
||||
{
|
||||
delayed.corrupt_seed = delayed.corrupt_seed * 1103515245u + 12345u;
|
||||
return delayed.corrupt_seed >> 8;
|
||||
}
|
||||
|
||||
/* damages a packet at random: a few bytes changed, cut short, stretched
|
||||
with random bytes (a datagram, up to the largest), or replaced throughout;
|
||||
the length it has after */
|
||||
static int delayed_corrupt(char *data, int length, int datagram)
|
||||
{
|
||||
int count, index;
|
||||
|
||||
switch (delayed_random() % 8)
|
||||
{
|
||||
case 0:
|
||||
/* (cut short, a datagram to nothing at times; a stream's read of
|
||||
nothing would be the stream closing) */
|
||||
if (length > 0)
|
||||
length = datagram ? (int)(delayed_random() % (unsigned)(length + 1)) :
|
||||
1 + (int)(delayed_random() % (unsigned)length);
|
||||
break;
|
||||
case 1:
|
||||
if (datagram)
|
||||
{
|
||||
int stretched = (int)(delayed_random() % (unsigned)(DELAYED_PACKET_SIZE + 1));
|
||||
|
||||
for (index = length; index < stretched; index++)
|
||||
data[index] = (char)delayed_random();
|
||||
if (stretched > length)
|
||||
length = stretched;
|
||||
}
|
||||
else if (length > 0)
|
||||
{
|
||||
data[delayed_random() % (unsigned)length] = (char)delayed_random();
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
for (index = 0; index < length; index++)
|
||||
data[index] = (char)delayed_random();
|
||||
break;
|
||||
case 3:
|
||||
/* (a run of bytes: a field, with its neighbours) */
|
||||
if (length > 0)
|
||||
{
|
||||
int start = (int)(delayed_random() % (unsigned)length);
|
||||
int run = 1 + (int)(delayed_random() % 16u);
|
||||
|
||||
for (index = start; index < length && index < start + run; index++)
|
||||
data[index] = (char)delayed_random();
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
/* (the extremes a size or an index is checked against) */
|
||||
if (length > 0)
|
||||
{
|
||||
static const unsigned char extremes[] = { 0x00, 0xFF, 0x7F, 0x80, 0x01, 0xFE };
|
||||
|
||||
count = 1 + (int)(delayed_random() % 4u);
|
||||
for (index = 0; index < count; index++)
|
||||
data[delayed_random() % (unsigned)length] = (char)extremes[delayed_random() % sizeof(extremes)];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* (a few bits) */
|
||||
count = 1 + (int)(delayed_random() % 8u);
|
||||
for (index = 0; index < count && length > 0; index++)
|
||||
data[delayed_random() % (unsigned)length] ^= (char)(1u << (delayed_random() % 8u));
|
||||
break;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
/* drops what is held back for socket, and what has been read (socket -1),
|
||||
keeping the rest in order */
|
||||
static void delayed_drop(int socket)
|
||||
@@ -756,6 +853,14 @@ static int delayed_receive_locked(SOCKET socket, char *buffer, int length, int f
|
||||
return 0;
|
||||
if (datagram && rand() % 100 < delayed.loss_percent)
|
||||
continue;
|
||||
/* (a stream's reads by their own share: a damaged stream is dropped,
|
||||
and the game must go on for its datagrams to be tested) */
|
||||
if ((datagram ? delayed.corrupt_percent : delayed.corrupt_stream_percent) &&
|
||||
GetTickCount() - delayed.started_time >= delayed.corrupt_after &&
|
||||
(int)(delayed_random() % 100u) < (datagram ? delayed.corrupt_percent : delayed.corrupt_stream_percent))
|
||||
{
|
||||
result = delayed_corrupt(packet->data, result, datagram);
|
||||
}
|
||||
packet->socket = (int)socket;
|
||||
packet->length = result;
|
||||
packet->offset = 0;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
Copyright notice:
|
||||
|
||||
(C) 1995-2026 Jean-loup Gailly and Mark Adler
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
Jean-loup Gailly Mark Adler
|
||||
jloup@gzip.org madler@alumni.caltech.edu
|
||||
@@ -0,0 +1,115 @@
|
||||
ZLIB DATA COMPRESSION LIBRARY
|
||||
|
||||
zlib 1.3.2 is a general purpose data compression library. All the code is
|
||||
thread safe (though see the FAQ for caveats). The data format used by the zlib
|
||||
library is described by RFCs (Request for Comments) 1950 to 1952 at
|
||||
https://datatracker.ietf.org/doc/html/rfc1950 (zlib format), rfc1951 (deflate
|
||||
format) and rfc1952 (gzip format).
|
||||
|
||||
All functions of the compression library are documented in the file zlib.h
|
||||
(volunteer to write man pages welcome, contact zlib@gzip.org). A usage example
|
||||
of the library is given in the file test/example.c which also tests that
|
||||
the library is working correctly. Another example is given in the file
|
||||
test/minigzip.c. The compression library itself is composed of all source
|
||||
files in the root directory.
|
||||
|
||||
To compile all files and run the test program, follow the instructions given at
|
||||
the top of Makefile.in. In short "./configure; make test", and if that goes
|
||||
well, "make install" should work for most flavors of Unix. For Windows, use
|
||||
one of the special makefiles in win32/ or contrib/vstudio/ . For VMS, use
|
||||
make_vms.com.
|
||||
|
||||
Questions about zlib should be sent to <zlib@gzip.org>, or to Gilles Vollant
|
||||
<info@winimage.com> for the Windows DLL version. The zlib home page is
|
||||
https://zlib.net/ . Before reporting a problem, please check this site to
|
||||
verify that you have the latest version of zlib; otherwise get the latest
|
||||
version and check whether the problem still exists or not.
|
||||
|
||||
PLEASE read the zlib FAQ https://zlib.net/zlib_faq.html before asking for help.
|
||||
|
||||
Mark Nelson <markn@ieee.org> wrote an article about zlib for the Jan. 1997
|
||||
issue of Dr. Dobb's Journal; a copy of the article is available at
|
||||
https://zlib.net/nelson/ .
|
||||
|
||||
The changes made in version 1.3.2 are documented in the file ChangeLog.
|
||||
|
||||
Unsupported third party contributions are provided in directory contrib/ .
|
||||
|
||||
zlib is available in Java using the java.util.zip package. Follow the API
|
||||
Documentation link at: https://docs.oracle.com/search/?q=java.util.zip .
|
||||
|
||||
A Perl interface to zlib and bzip2 written by Paul Marquess <pmqs@cpan.org>
|
||||
can be found at https://github.com/pmqs/IO-Compress .
|
||||
|
||||
A Python interface to zlib written by A.M. Kuchling <amk@amk.ca> is
|
||||
available in Python 1.5 and later versions, see
|
||||
https://docs.python.org/3/library/zlib.html .
|
||||
|
||||
zlib is built into tcl: https://wiki.tcl-lang.org/page/zlib .
|
||||
|
||||
An experimental package to read and write files in .zip format, written on top
|
||||
of zlib by Gilles Vollant <info@winimage.com>, is available in the
|
||||
contrib/minizip directory of zlib.
|
||||
|
||||
|
||||
Notes for some targets:
|
||||
|
||||
- For Windows DLL versions, please see win32/DLL_FAQ.txt
|
||||
|
||||
- For 64-bit Irix, deflate.c must be compiled without any optimization. With
|
||||
-O, one libpng test fails. The test works in 32 bit mode (with the -n32
|
||||
compiler flag). The compiler bug has been reported to SGI.
|
||||
|
||||
- zlib doesn't work with gcc 2.6.3 on a DEC 3000/300LX under OSF/1 2.1 it works
|
||||
when compiled with cc.
|
||||
|
||||
- On Digital Unix 4.0D (formerly OSF/1) on AlphaServer, the cc option -std1 is
|
||||
necessary to get gzprintf working correctly. This is done by configure.
|
||||
|
||||
- zlib doesn't work on HP-UX 9.05 with some versions of /bin/cc. It works with
|
||||
other compilers. Use "make test" to check your compiler.
|
||||
|
||||
- For PalmOs, see https://palmzlib.sourceforge.net/
|
||||
|
||||
|
||||
Acknowledgments:
|
||||
|
||||
The deflate format used by zlib was defined by Phil Katz. The deflate and
|
||||
zlib specifications were written by L. Peter Deutsch. Thanks to all the
|
||||
people who reported problems and suggested various improvements in zlib; they
|
||||
are too numerous to cite here.
|
||||
|
||||
Copyright notice:
|
||||
|
||||
(C) 1995-2026 Jean-loup Gailly and Mark Adler
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
Jean-loup Gailly Mark Adler
|
||||
jloup@gzip.org madler@alumni.caltech.edu
|
||||
|
||||
If you use the zlib library in a product, we would appreciate *not* receiving
|
||||
lengthy legal documents to sign. The sources are provided for free but without
|
||||
warranty of any kind. The library has been entirely written by Jean-loup
|
||||
Gailly and Mark Adler; it does not include third-party code. We make all
|
||||
contributions to and distributions of this project solely in our personal
|
||||
capacity, and are not conveying any rights to any intellectual property of
|
||||
any third parties.
|
||||
|
||||
If you redistribute modified sources, we would appreciate that you include in
|
||||
the file ChangeLog history information documenting your changes. Please read
|
||||
the FAQ for more information on the distribution of modified source versions.
|
||||
@@ -0,0 +1,164 @@
|
||||
/* adler32.c -- compute the Adler-32 checksum of a data stream
|
||||
* Copyright (C) 1995-2011, 2016 Mark Adler
|
||||
* For conditions of distribution and use, see copyright notice in zlib.h
|
||||
*/
|
||||
|
||||
/* @(#) $Id$ */
|
||||
|
||||
#include "zutil.h"
|
||||
|
||||
#define BASE 65521U /* largest prime smaller than 65536 */
|
||||
#define NMAX 5552
|
||||
/* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */
|
||||
|
||||
#define DO1(buf,i) {adler += (buf)[i]; sum2 += adler;}
|
||||
#define DO2(buf,i) DO1(buf,i); DO1(buf,i+1);
|
||||
#define DO4(buf,i) DO2(buf,i); DO2(buf,i+2);
|
||||
#define DO8(buf,i) DO4(buf,i); DO4(buf,i+4);
|
||||
#define DO16(buf) DO8(buf,0); DO8(buf,8);
|
||||
|
||||
/* use NO_DIVIDE if your processor does not do division in hardware --
|
||||
try it both ways to see which is faster */
|
||||
#ifdef NO_DIVIDE
|
||||
/* note that this assumes BASE is 65521, where 65536 % 65521 == 15
|
||||
(thank you to John Reiser for pointing this out) */
|
||||
# define CHOP(a) \
|
||||
do { \
|
||||
unsigned long tmp = a >> 16; \
|
||||
a &= 0xffffUL; \
|
||||
a += (tmp << 4) - tmp; \
|
||||
} while (0)
|
||||
# define MOD28(a) \
|
||||
do { \
|
||||
CHOP(a); \
|
||||
if (a >= BASE) a -= BASE; \
|
||||
} while (0)
|
||||
# define MOD(a) \
|
||||
do { \
|
||||
CHOP(a); \
|
||||
MOD28(a); \
|
||||
} while (0)
|
||||
# define MOD63(a) \
|
||||
do { /* this assumes a is not negative */ \
|
||||
z_off64_t tmp = a >> 32; \
|
||||
a &= 0xffffffffL; \
|
||||
a += (tmp << 8) - (tmp << 5) + tmp; \
|
||||
tmp = a >> 16; \
|
||||
a &= 0xffffL; \
|
||||
a += (tmp << 4) - tmp; \
|
||||
tmp = a >> 16; \
|
||||
a &= 0xffffL; \
|
||||
a += (tmp << 4) - tmp; \
|
||||
if (a >= BASE) a -= BASE; \
|
||||
} while (0)
|
||||
#else
|
||||
# define MOD(a) a %= BASE
|
||||
# define MOD28(a) a %= BASE
|
||||
# define MOD63(a) a %= BASE
|
||||
#endif
|
||||
|
||||
/* ========================================================================= */
|
||||
uLong ZEXPORT adler32_z(uLong adler, const Bytef *buf, z_size_t len) {
|
||||
unsigned long sum2;
|
||||
unsigned n;
|
||||
|
||||
/* split Adler-32 into component sums */
|
||||
sum2 = (adler >> 16) & 0xffff;
|
||||
adler &= 0xffff;
|
||||
|
||||
/* in case user likes doing a byte at a time, keep it fast */
|
||||
if (len == 1) {
|
||||
adler += buf[0];
|
||||
if (adler >= BASE)
|
||||
adler -= BASE;
|
||||
sum2 += adler;
|
||||
if (sum2 >= BASE)
|
||||
sum2 -= BASE;
|
||||
return adler | (sum2 << 16);
|
||||
}
|
||||
|
||||
/* initial Adler-32 value (deferred check for len == 1 speed) */
|
||||
if (buf == Z_NULL)
|
||||
return 1L;
|
||||
|
||||
/* in case short lengths are provided, keep it somewhat fast */
|
||||
if (len < 16) {
|
||||
while (len--) {
|
||||
adler += *buf++;
|
||||
sum2 += adler;
|
||||
}
|
||||
if (adler >= BASE)
|
||||
adler -= BASE;
|
||||
MOD28(sum2); /* only added so many BASE's */
|
||||
return adler | (sum2 << 16);
|
||||
}
|
||||
|
||||
/* do length NMAX blocks -- requires just one modulo operation */
|
||||
while (len >= NMAX) {
|
||||
len -= NMAX;
|
||||
n = NMAX / 16; /* NMAX is divisible by 16 */
|
||||
do {
|
||||
DO16(buf); /* 16 sums unrolled */
|
||||
buf += 16;
|
||||
} while (--n);
|
||||
MOD(adler);
|
||||
MOD(sum2);
|
||||
}
|
||||
|
||||
/* do remaining bytes (less than NMAX, still just one modulo) */
|
||||
if (len) { /* avoid modulos if none remaining */
|
||||
while (len >= 16) {
|
||||
len -= 16;
|
||||
DO16(buf);
|
||||
buf += 16;
|
||||
}
|
||||
while (len--) {
|
||||
adler += *buf++;
|
||||
sum2 += adler;
|
||||
}
|
||||
MOD(adler);
|
||||
MOD(sum2);
|
||||
}
|
||||
|
||||
/* return recombined sums */
|
||||
return adler | (sum2 << 16);
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
uLong ZEXPORT adler32(uLong adler, const Bytef *buf, uInt len) {
|
||||
return adler32_z(adler, buf, len);
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
local uLong adler32_combine_(uLong adler1, uLong adler2, z_off64_t len2) {
|
||||
unsigned long sum1;
|
||||
unsigned long sum2;
|
||||
unsigned rem;
|
||||
|
||||
/* for negative len, return invalid adler32 as a clue for debugging */
|
||||
if (len2 < 0)
|
||||
return 0xffffffffUL;
|
||||
|
||||
/* the derivation of this formula is left as an exercise for the reader */
|
||||
MOD63(len2); /* assumes len2 >= 0 */
|
||||
rem = (unsigned)len2;
|
||||
sum1 = adler1 & 0xffff;
|
||||
sum2 = rem * sum1;
|
||||
MOD(sum2);
|
||||
sum1 += (adler2 & 0xffff) + BASE - 1;
|
||||
sum2 += ((adler1 >> 16) & 0xffff) + ((adler2 >> 16) & 0xffff) + BASE - rem;
|
||||
if (sum1 >= BASE) sum1 -= BASE;
|
||||
if (sum1 >= BASE) sum1 -= BASE;
|
||||
if (sum2 >= ((unsigned long)BASE << 1)) sum2 -= ((unsigned long)BASE << 1);
|
||||
if (sum2 >= BASE) sum2 -= BASE;
|
||||
return sum1 | (sum2 << 16);
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
uLong ZEXPORT adler32_combine(uLong adler1, uLong adler2, z_off_t len2) {
|
||||
return adler32_combine_(adler1, adler2, len2);
|
||||
}
|
||||
|
||||
uLong ZEXPORT adler32_combine64(uLong adler1, uLong adler2, z_off64_t len2) {
|
||||
return adler32_combine_(adler1, adler2, len2);
|
||||
}
|
||||
@@ -0,0 +1,983 @@
|
||||
/* crc32.c -- compute the CRC-32 of a data stream
|
||||
* Copyright (C) 1995-2026 Mark Adler
|
||||
* For conditions of distribution and use, see copyright notice in zlib.h
|
||||
*
|
||||
* This interleaved implementation of a CRC makes use of pipelined multiple
|
||||
* arithmetic-logic units, commonly found in modern CPU cores. It is due to
|
||||
* Kadatch and Jenkins (2010). See doc/crc-doc.1.0.pdf in this distribution.
|
||||
*/
|
||||
|
||||
/* @(#) $Id$ */
|
||||
|
||||
/*
|
||||
Note on the use of DYNAMIC_CRC_TABLE: there is no mutex or semaphore
|
||||
protection on the static variables used to control the first-use generation
|
||||
of the crc tables. Therefore, if you #define DYNAMIC_CRC_TABLE, you should
|
||||
first call get_crc_table() to initialize the tables before allowing more than
|
||||
one thread to use crc32().
|
||||
|
||||
MAKECRCH can be #defined to write out crc32.h. A main() routine is also
|
||||
produced, so that this one source file can be compiled to an executable.
|
||||
*/
|
||||
|
||||
#ifdef MAKECRCH
|
||||
# include <stdio.h>
|
||||
# ifndef DYNAMIC_CRC_TABLE
|
||||
# define DYNAMIC_CRC_TABLE
|
||||
# endif
|
||||
#endif
|
||||
#ifdef DYNAMIC_CRC_TABLE
|
||||
# define Z_ONCE
|
||||
#endif
|
||||
|
||||
#include "zutil.h" /* for Z_U4, Z_U8, z_crc_t, and FAR definitions */
|
||||
|
||||
#ifdef HAVE_S390X_VX
|
||||
# include "contrib/crc32vx/crc32_vx_hooks.h"
|
||||
#endif
|
||||
|
||||
/*
|
||||
A CRC of a message is computed on N braids of words in the message, where
|
||||
each word consists of W bytes (4 or 8). If N is 3, for example, then three
|
||||
running sparse CRCs are calculated respectively on each braid, at these
|
||||
indices in the array of words: 0, 3, 6, ..., 1, 4, 7, ..., and 2, 5, 8, ...
|
||||
This is done starting at a word boundary, and continues until as many blocks
|
||||
of N * W bytes as are available have been processed. The results are combined
|
||||
into a single CRC at the end. For this code, N must be in the range 1..6 and
|
||||
W must be 4 or 8. The upper limit on N can be increased if desired by adding
|
||||
more #if blocks, extending the patterns apparent in the code. In addition,
|
||||
crc32.h would need to be regenerated, if the maximum N value is increased.
|
||||
|
||||
N and W are chosen empirically by benchmarking the execution time on a given
|
||||
processor. The choices for N and W below were based on testing on Intel Kaby
|
||||
Lake i7, AMD Ryzen 7, ARM Cortex-A57, Sparc64-VII, PowerPC POWER9, and MIPS64
|
||||
Octeon II processors. The Intel, AMD, and ARM processors were all fastest
|
||||
with N=5, W=8. The Sparc, PowerPC, and MIPS64 were all fastest at N=5, W=4.
|
||||
They were all tested with either gcc or clang, all using the -O3 optimization
|
||||
level. Your mileage may vary.
|
||||
*/
|
||||
|
||||
/* Define N */
|
||||
#ifdef Z_TESTN
|
||||
# define N Z_TESTN
|
||||
#else
|
||||
# define N 5
|
||||
#endif
|
||||
#if N < 1 || N > 6
|
||||
# error N must be in 1..6
|
||||
#endif
|
||||
|
||||
/*
|
||||
z_crc_t must be at least 32 bits. z_word_t must be at least as long as
|
||||
z_crc_t. It is assumed here that z_word_t is either 32 bits or 64 bits, and
|
||||
that bytes are eight bits.
|
||||
*/
|
||||
|
||||
/*
|
||||
Define W and the associated z_word_t type. If W is not defined, then a
|
||||
braided calculation is not used, and the associated tables and code are not
|
||||
compiled.
|
||||
*/
|
||||
#ifdef Z_TESTW
|
||||
# if Z_TESTW-1 != -1
|
||||
# define W Z_TESTW
|
||||
# endif
|
||||
#else
|
||||
# ifdef MAKECRCH
|
||||
# define W 8 /* required for MAKECRCH */
|
||||
# else
|
||||
# if defined(__x86_64__) || defined(__aarch64__)
|
||||
# define W 8
|
||||
# else
|
||||
# define W 4
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
#ifdef W
|
||||
# if W == 8 && defined(Z_U8)
|
||||
typedef Z_U8 z_word_t;
|
||||
# elif defined(Z_U4)
|
||||
# undef W
|
||||
# define W 4
|
||||
typedef Z_U4 z_word_t;
|
||||
# else
|
||||
# undef W
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/* If available, use the ARM processor CRC32 instruction. */
|
||||
#if defined(__aarch64__) && defined(__ARM_FEATURE_CRC32) && \
|
||||
defined(W) && W == 8
|
||||
# define ARMCRC32
|
||||
#endif
|
||||
|
||||
#if defined(W) && (!defined(ARMCRC32) || defined(DYNAMIC_CRC_TABLE))
|
||||
/*
|
||||
Swap the bytes in a z_word_t to convert between little and big endian. Any
|
||||
self-respecting compiler will optimize this to a single machine byte-swap
|
||||
instruction, if one is available. This assumes that word_t is either 32 bits
|
||||
or 64 bits.
|
||||
*/
|
||||
local z_word_t byte_swap(z_word_t word) {
|
||||
# if W == 8
|
||||
return
|
||||
(word & 0xff00000000000000) >> 56 |
|
||||
(word & 0xff000000000000) >> 40 |
|
||||
(word & 0xff0000000000) >> 24 |
|
||||
(word & 0xff00000000) >> 8 |
|
||||
(word & 0xff000000) << 8 |
|
||||
(word & 0xff0000) << 24 |
|
||||
(word & 0xff00) << 40 |
|
||||
(word & 0xff) << 56;
|
||||
# else /* W == 4 */
|
||||
return
|
||||
(word & 0xff000000) >> 24 |
|
||||
(word & 0xff0000) >> 8 |
|
||||
(word & 0xff00) << 8 |
|
||||
(word & 0xff) << 24;
|
||||
# endif
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef DYNAMIC_CRC_TABLE
|
||||
/* =========================================================================
|
||||
* Table of powers of x for combining CRC-32s, filled in by make_crc_table()
|
||||
* below.
|
||||
*/
|
||||
local z_crc_t FAR x2n_table[32];
|
||||
#else
|
||||
/* =========================================================================
|
||||
* Tables for byte-wise and braided CRC-32 calculations, and a table of powers
|
||||
* of x for combining CRC-32s, all made by make_crc_table().
|
||||
*/
|
||||
# include "crc32.h"
|
||||
#endif
|
||||
|
||||
/* CRC polynomial. */
|
||||
#define POLY 0xedb88320 /* p(x) reflected, with x^32 implied */
|
||||
|
||||
/*
|
||||
Return a(x) multiplied by b(x) modulo p(x), where p(x) is the CRC polynomial,
|
||||
reflected. For speed, this requires that a not be zero.
|
||||
*/
|
||||
local uLong multmodp(uLong a, uLong b) {
|
||||
uLong m, p;
|
||||
|
||||
m = (uLong)1 << 31;
|
||||
p = 0;
|
||||
for (;;) {
|
||||
if (a & m) {
|
||||
p ^= b;
|
||||
if ((a & (m - 1)) == 0)
|
||||
break;
|
||||
}
|
||||
m >>= 1;
|
||||
b = b & 1 ? (b >> 1) ^ POLY : b >> 1;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
/*
|
||||
Return x^(n * 2^k) modulo p(x). Requires that x2n_table[] has been
|
||||
initialized. n must not be negative.
|
||||
*/
|
||||
local uLong x2nmodp(z_off64_t n, unsigned k) {
|
||||
uLong p;
|
||||
|
||||
p = (uLong)1 << 31; /* x^0 == 1 */
|
||||
while (n) {
|
||||
if (n & 1)
|
||||
p = multmodp(x2n_table[k & 31], p);
|
||||
n >>= 1;
|
||||
k++;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
#ifdef DYNAMIC_CRC_TABLE
|
||||
/* =========================================================================
|
||||
* Build the tables for byte-wise and braided CRC-32 calculations, and a table
|
||||
* of powers of x for combining CRC-32s.
|
||||
*/
|
||||
local z_crc_t FAR crc_table[256];
|
||||
#ifdef W
|
||||
local z_word_t FAR crc_big_table[256];
|
||||
local z_crc_t FAR crc_braid_table[W][256];
|
||||
local z_word_t FAR crc_braid_big_table[W][256];
|
||||
local void braid(z_crc_t [][256], z_word_t [][256], int, int);
|
||||
#endif
|
||||
#ifdef MAKECRCH
|
||||
local void write_table(FILE *, const z_crc_t FAR *, int);
|
||||
local void write_table32hi(FILE *, const z_word_t FAR *, int);
|
||||
local void write_table64(FILE *, const z_word_t FAR *, int);
|
||||
#endif /* MAKECRCH */
|
||||
|
||||
/* State for once(). */
|
||||
local z_once_t made = Z_ONCE_INIT;
|
||||
|
||||
/*
|
||||
Generate tables for a byte-wise 32-bit CRC calculation on the polynomial:
|
||||
x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1.
|
||||
|
||||
Polynomials over GF(2) are represented in binary, one bit per coefficient,
|
||||
with the lowest powers in the most significant bit. Then adding polynomials
|
||||
is just exclusive-or, and multiplying a polynomial by x is a right shift by
|
||||
one. If we call the above polynomial p, and represent a byte as the
|
||||
polynomial q, also with the lowest power in the most significant bit (so the
|
||||
byte 0xb1 is the polynomial x^7+x^3+x^2+1), then the CRC is (q*x^32) mod p,
|
||||
where a mod b means the remainder after dividing a by b.
|
||||
|
||||
This calculation is done using the shift-register method of multiplying and
|
||||
taking the remainder. The register is initialized to zero, and for each
|
||||
incoming bit, x^32 is added mod p to the register if the bit is a one (where
|
||||
x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by x
|
||||
(which is shifting right by one and adding x^32 mod p if the bit shifted out
|
||||
is a one). We start with the highest power (least significant bit) of q and
|
||||
repeat for all eight bits of q.
|
||||
|
||||
The table is simply the CRC of all possible eight bit values. This is all the
|
||||
information needed to generate CRCs on data a byte at a time for all
|
||||
combinations of CRC register values and incoming bytes.
|
||||
*/
|
||||
|
||||
local void make_crc_table(void) {
|
||||
unsigned i, j, n;
|
||||
z_crc_t p;
|
||||
|
||||
/* initialize the CRC of bytes tables */
|
||||
for (i = 0; i < 256; i++) {
|
||||
p = i;
|
||||
for (j = 0; j < 8; j++)
|
||||
p = p & 1 ? (p >> 1) ^ POLY : p >> 1;
|
||||
crc_table[i] = p;
|
||||
#ifdef W
|
||||
crc_big_table[i] = byte_swap(p);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* initialize the x^2^n mod p(x) table */
|
||||
p = (z_crc_t)1 << 30; /* x^1 */
|
||||
x2n_table[0] = p;
|
||||
for (n = 1; n < 32; n++)
|
||||
x2n_table[n] = p = (z_crc_t)multmodp(p, p);
|
||||
|
||||
#ifdef W
|
||||
/* initialize the braiding tables -- needs x2n_table[] */
|
||||
braid(crc_braid_table, crc_braid_big_table, N, W);
|
||||
#endif
|
||||
|
||||
#ifdef MAKECRCH
|
||||
{
|
||||
/*
|
||||
The crc32.h header file contains tables for both 32-bit and 64-bit
|
||||
z_word_t's, and so requires a 64-bit type be available. In that case,
|
||||
z_word_t must be defined to be 64-bits. This code then also generates
|
||||
and writes out the tables for the case that z_word_t is 32 bits.
|
||||
*/
|
||||
#if !defined(W) || W != 8
|
||||
# error Need a 64-bit integer type in order to generate crc32.h.
|
||||
#endif
|
||||
FILE *out;
|
||||
int k, n;
|
||||
z_crc_t ltl[8][256];
|
||||
z_word_t big[8][256];
|
||||
|
||||
out = fopen("crc32.h", "w");
|
||||
if (out == NULL) return;
|
||||
|
||||
/* write out little-endian CRC table to crc32.h */
|
||||
fprintf(out,
|
||||
"/* crc32.h -- tables for rapid CRC calculation\n"
|
||||
" * Generated automatically by crc32.c\n */\n"
|
||||
"\n"
|
||||
"local const z_crc_t FAR crc_table[] = {\n"
|
||||
" ");
|
||||
write_table(out, crc_table, 256);
|
||||
fprintf(out,
|
||||
"};\n");
|
||||
|
||||
/* write out big-endian CRC table for 64-bit z_word_t to crc32.h */
|
||||
fprintf(out,
|
||||
"\n"
|
||||
"#ifdef W\n"
|
||||
"\n"
|
||||
"#if W == 8\n"
|
||||
"\n"
|
||||
"local const z_word_t FAR crc_big_table[] = {\n"
|
||||
" ");
|
||||
write_table64(out, crc_big_table, 256);
|
||||
fprintf(out,
|
||||
"};\n");
|
||||
|
||||
/* write out big-endian CRC table for 32-bit z_word_t to crc32.h */
|
||||
fprintf(out,
|
||||
"\n"
|
||||
"#else /* W == 4 */\n"
|
||||
"\n"
|
||||
"local const z_word_t FAR crc_big_table[] = {\n"
|
||||
" ");
|
||||
write_table32hi(out, crc_big_table, 256);
|
||||
fprintf(out,
|
||||
"};\n"
|
||||
"\n"
|
||||
"#endif\n");
|
||||
|
||||
/* write out braid tables for each value of N */
|
||||
for (n = 1; n <= 6; n++) {
|
||||
fprintf(out,
|
||||
"\n"
|
||||
"#if N == %d\n", n);
|
||||
|
||||
/* compute braid tables for this N and 64-bit word_t */
|
||||
braid(ltl, big, n, 8);
|
||||
|
||||
/* write out braid tables for 64-bit z_word_t to crc32.h */
|
||||
fprintf(out,
|
||||
"\n"
|
||||
"#if W == 8\n"
|
||||
"\n"
|
||||
"local const z_crc_t FAR crc_braid_table[][256] = {\n");
|
||||
for (k = 0; k < 8; k++) {
|
||||
fprintf(out, " {");
|
||||
write_table(out, ltl[k], 256);
|
||||
fprintf(out, "}%s", k < 7 ? ",\n" : "");
|
||||
}
|
||||
fprintf(out,
|
||||
"};\n"
|
||||
"\n"
|
||||
"local const z_word_t FAR crc_braid_big_table[][256] = {\n");
|
||||
for (k = 0; k < 8; k++) {
|
||||
fprintf(out, " {");
|
||||
write_table64(out, big[k], 256);
|
||||
fprintf(out, "}%s", k < 7 ? ",\n" : "");
|
||||
}
|
||||
fprintf(out,
|
||||
"};\n");
|
||||
|
||||
/* compute braid tables for this N and 32-bit word_t */
|
||||
braid(ltl, big, n, 4);
|
||||
|
||||
/* write out braid tables for 32-bit z_word_t to crc32.h */
|
||||
fprintf(out,
|
||||
"\n"
|
||||
"#else /* W == 4 */\n"
|
||||
"\n"
|
||||
"local const z_crc_t FAR crc_braid_table[][256] = {\n");
|
||||
for (k = 0; k < 4; k++) {
|
||||
fprintf(out, " {");
|
||||
write_table(out, ltl[k], 256);
|
||||
fprintf(out, "}%s", k < 3 ? ",\n" : "");
|
||||
}
|
||||
fprintf(out,
|
||||
"};\n"
|
||||
"\n"
|
||||
"local const z_word_t FAR crc_braid_big_table[][256] = {\n");
|
||||
for (k = 0; k < 4; k++) {
|
||||
fprintf(out, " {");
|
||||
write_table32hi(out, big[k], 256);
|
||||
fprintf(out, "}%s", k < 3 ? ",\n" : "");
|
||||
}
|
||||
fprintf(out,
|
||||
"};\n"
|
||||
"\n"
|
||||
"#endif\n"
|
||||
"\n"
|
||||
"#endif\n");
|
||||
}
|
||||
fprintf(out,
|
||||
"\n"
|
||||
"#endif\n");
|
||||
|
||||
/* write out zeros operator table to crc32.h */
|
||||
fprintf(out,
|
||||
"\n"
|
||||
"local const z_crc_t FAR x2n_table[] = {\n"
|
||||
" ");
|
||||
write_table(out, x2n_table, 32);
|
||||
fprintf(out,
|
||||
"};\n");
|
||||
fclose(out);
|
||||
}
|
||||
#endif /* MAKECRCH */
|
||||
}
|
||||
|
||||
#ifdef MAKECRCH
|
||||
|
||||
/*
|
||||
Write the 32-bit values in table[0..k-1] to out, five per line in
|
||||
hexadecimal separated by commas.
|
||||
*/
|
||||
local void write_table(FILE *out, const z_crc_t FAR *table, int k) {
|
||||
int n;
|
||||
|
||||
for (n = 0; n < k; n++)
|
||||
fprintf(out, "%s0x%08lx%s", n == 0 || n % 5 ? "" : " ",
|
||||
(unsigned long)(table[n]),
|
||||
n == k - 1 ? "" : (n % 5 == 4 ? ",\n" : ", "));
|
||||
}
|
||||
|
||||
/*
|
||||
Write the high 32-bits of each value in table[0..k-1] to out, five per line
|
||||
in hexadecimal separated by commas.
|
||||
*/
|
||||
local void write_table32hi(FILE *out, const z_word_t FAR *table, int k) {
|
||||
int n;
|
||||
|
||||
for (n = 0; n < k; n++)
|
||||
fprintf(out, "%s0x%08lx%s", n == 0 || n % 5 ? "" : " ",
|
||||
(unsigned long)(table[n] >> 32),
|
||||
n == k - 1 ? "" : (n % 5 == 4 ? ",\n" : ", "));
|
||||
}
|
||||
|
||||
/*
|
||||
Write the 64-bit values in table[0..k-1] to out, three per line in
|
||||
hexadecimal separated by commas. This assumes that if there is a 64-bit
|
||||
type, then there is also a long long integer type, and it is at least 64
|
||||
bits. If not, then the type cast and format string can be adjusted
|
||||
accordingly.
|
||||
*/
|
||||
local void write_table64(FILE *out, const z_word_t FAR *table, int k) {
|
||||
int n;
|
||||
|
||||
for (n = 0; n < k; n++)
|
||||
fprintf(out, "%s0x%016llx%s", n == 0 || n % 3 ? "" : " ",
|
||||
(unsigned long long)(table[n]),
|
||||
n == k - 1 ? "" : (n % 3 == 2 ? ",\n" : ", "));
|
||||
}
|
||||
|
||||
/* Actually do the deed. */
|
||||
int main(void) {
|
||||
make_crc_table();
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* MAKECRCH */
|
||||
|
||||
#ifdef W
|
||||
/*
|
||||
Generate the little and big-endian braid tables for the given n and z_word_t
|
||||
size w. Each array must have room for w blocks of 256 elements.
|
||||
*/
|
||||
local void braid(z_crc_t ltl[][256], z_word_t big[][256], int n, int w) {
|
||||
int k;
|
||||
z_crc_t i, p, q;
|
||||
for (k = 0; k < w; k++) {
|
||||
p = (z_crc_t)x2nmodp((n * w + 3 - k) << 3, 0);
|
||||
ltl[k][0] = 0;
|
||||
big[w - 1 - k][0] = 0;
|
||||
for (i = 1; i < 256; i++) {
|
||||
ltl[k][i] = q = (z_crc_t)multmodp(i << 24, p);
|
||||
big[w - 1 - k][i] = byte_swap(q);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* DYNAMIC_CRC_TABLE */
|
||||
|
||||
/* =========================================================================
|
||||
* This function can be used by asm versions of crc32(), and to force the
|
||||
* generation of the CRC tables in a threaded application.
|
||||
*/
|
||||
const z_crc_t FAR * ZEXPORT get_crc_table(void) {
|
||||
#ifdef DYNAMIC_CRC_TABLE
|
||||
z_once(&made, make_crc_table);
|
||||
#endif /* DYNAMIC_CRC_TABLE */
|
||||
return (const z_crc_t FAR *)crc_table;
|
||||
}
|
||||
|
||||
/* =========================================================================
|
||||
* Use ARM machine instructions if available. This will compute the CRC about
|
||||
* ten times faster than the braided calculation. This code does not check for
|
||||
* the presence of the CRC instruction at run time. __ARM_FEATURE_CRC32 will
|
||||
* only be defined if the compilation specifies an ARM processor architecture
|
||||
* that has the instructions. For example, compiling with -march=armv8.1-a or
|
||||
* -march=armv8-a+crc, or -march=native if the compile machine has the crc32
|
||||
* instructions.
|
||||
*/
|
||||
#ifdef ARMCRC32
|
||||
|
||||
/*
|
||||
Constants empirically determined to maximize speed. These values are from
|
||||
measurements on a Cortex-A57. Your mileage may vary.
|
||||
*/
|
||||
#define Z_BATCH 3990 /* number of words in a batch */
|
||||
#define Z_BATCH_ZEROS 0xa10d3d0c /* computed from Z_BATCH = 3990 */
|
||||
#define Z_BATCH_MIN 800 /* fewest words in a final batch */
|
||||
|
||||
uLong ZEXPORT crc32_z(uLong crc, const unsigned char FAR *buf, z_size_t len) {
|
||||
uLong val;
|
||||
z_word_t crc1, crc2;
|
||||
const z_word_t *word;
|
||||
z_word_t val0, val1, val2;
|
||||
z_size_t last, last2, i;
|
||||
z_size_t num;
|
||||
|
||||
/* Return initial CRC, if requested. */
|
||||
if (buf == Z_NULL) return 0;
|
||||
|
||||
#ifdef DYNAMIC_CRC_TABLE
|
||||
z_once(&made, make_crc_table);
|
||||
#endif /* DYNAMIC_CRC_TABLE */
|
||||
|
||||
/* Pre-condition the CRC */
|
||||
crc = (~crc) & 0xffffffff;
|
||||
|
||||
/* Compute the CRC up to a word boundary. */
|
||||
while (len && ((z_size_t)buf & 7) != 0) {
|
||||
len--;
|
||||
val = *buf++;
|
||||
__asm__ volatile("crc32b %w0, %w0, %w1" : "+r"(crc) : "r"(val));
|
||||
}
|
||||
|
||||
/* Prepare to compute the CRC on full 64-bit words word[0..num-1]. */
|
||||
word = (z_word_t const *)buf;
|
||||
num = len >> 3;
|
||||
len &= 7;
|
||||
|
||||
/* Do three interleaved CRCs to realize the throughput of one crc32x
|
||||
instruction per cycle. Each CRC is calculated on Z_BATCH words. The
|
||||
three CRCs are combined into a single CRC after each set of batches. */
|
||||
while (num >= 3 * Z_BATCH) {
|
||||
crc1 = 0;
|
||||
crc2 = 0;
|
||||
for (i = 0; i < Z_BATCH; i++) {
|
||||
val0 = word[i];
|
||||
val1 = word[i + Z_BATCH];
|
||||
val2 = word[i + 2 * Z_BATCH];
|
||||
__asm__ volatile("crc32x %w0, %w0, %x1" : "+r"(crc) : "r"(val0));
|
||||
__asm__ volatile("crc32x %w0, %w0, %x1" : "+r"(crc1) : "r"(val1));
|
||||
__asm__ volatile("crc32x %w0, %w0, %x1" : "+r"(crc2) : "r"(val2));
|
||||
}
|
||||
word += 3 * Z_BATCH;
|
||||
num -= 3 * Z_BATCH;
|
||||
crc = multmodp(Z_BATCH_ZEROS, crc) ^ crc1;
|
||||
crc = multmodp(Z_BATCH_ZEROS, crc) ^ crc2;
|
||||
}
|
||||
|
||||
/* Do one last smaller batch with the remaining words, if there are enough
|
||||
to pay for the combination of CRCs. */
|
||||
last = num / 3;
|
||||
if (last >= Z_BATCH_MIN) {
|
||||
last2 = last << 1;
|
||||
crc1 = 0;
|
||||
crc2 = 0;
|
||||
for (i = 0; i < last; i++) {
|
||||
val0 = word[i];
|
||||
val1 = word[i + last];
|
||||
val2 = word[i + last2];
|
||||
__asm__ volatile("crc32x %w0, %w0, %x1" : "+r"(crc) : "r"(val0));
|
||||
__asm__ volatile("crc32x %w0, %w0, %x1" : "+r"(crc1) : "r"(val1));
|
||||
__asm__ volatile("crc32x %w0, %w0, %x1" : "+r"(crc2) : "r"(val2));
|
||||
}
|
||||
word += 3 * last;
|
||||
num -= 3 * last;
|
||||
val = x2nmodp((int)last, 6);
|
||||
crc = multmodp(val, crc) ^ crc1;
|
||||
crc = multmodp(val, crc) ^ crc2;
|
||||
}
|
||||
|
||||
/* Compute the CRC on any remaining words. */
|
||||
for (i = 0; i < num; i++) {
|
||||
val0 = word[i];
|
||||
__asm__ volatile("crc32x %w0, %w0, %x1" : "+r"(crc) : "r"(val0));
|
||||
}
|
||||
word += num;
|
||||
|
||||
/* Complete the CRC on any remaining bytes. */
|
||||
buf = (const unsigned char FAR *)word;
|
||||
while (len) {
|
||||
len--;
|
||||
val = *buf++;
|
||||
__asm__ volatile("crc32b %w0, %w0, %w1" : "+r"(crc) : "r"(val));
|
||||
}
|
||||
|
||||
/* Return the CRC, post-conditioned. */
|
||||
return crc ^ 0xffffffff;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#ifdef W
|
||||
|
||||
/*
|
||||
Return the CRC of the W bytes in the word_t data, taking the
|
||||
least-significant byte of the word as the first byte of data, without any pre
|
||||
or post conditioning. This is used to combine the CRCs of each braid.
|
||||
*/
|
||||
local z_crc_t crc_word(z_word_t data) {
|
||||
int k;
|
||||
for (k = 0; k < W; k++)
|
||||
data = (data >> 8) ^ crc_table[data & 0xff];
|
||||
return (z_crc_t)data;
|
||||
}
|
||||
|
||||
local z_word_t crc_word_big(z_word_t data) {
|
||||
int k;
|
||||
for (k = 0; k < W; k++)
|
||||
data = (data << 8) ^
|
||||
crc_big_table[(data >> ((W - 1) << 3)) & 0xff];
|
||||
return data;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/* ========================================================================= */
|
||||
uLong ZEXPORT crc32_z(uLong crc, const unsigned char FAR *buf, z_size_t len) {
|
||||
/* Return initial CRC, if requested. */
|
||||
if (buf == Z_NULL) return 0;
|
||||
|
||||
#ifdef DYNAMIC_CRC_TABLE
|
||||
z_once(&made, make_crc_table);
|
||||
#endif /* DYNAMIC_CRC_TABLE */
|
||||
|
||||
/* Pre-condition the CRC */
|
||||
crc = (~crc) & 0xffffffff;
|
||||
|
||||
#ifdef W
|
||||
|
||||
/* If provided enough bytes, do a braided CRC calculation. */
|
||||
if (len >= N * W + W - 1) {
|
||||
z_size_t blks;
|
||||
z_word_t const *words;
|
||||
unsigned endian;
|
||||
int k;
|
||||
|
||||
/* Compute the CRC up to a z_word_t boundary. */
|
||||
while (len && ((z_size_t)buf & (W - 1)) != 0) {
|
||||
len--;
|
||||
crc = (crc >> 8) ^ crc_table[(crc ^ *buf++) & 0xff];
|
||||
}
|
||||
|
||||
/* Compute the CRC on as many N z_word_t blocks as are available. */
|
||||
blks = len / (N * W);
|
||||
len -= blks * N * W;
|
||||
words = (z_word_t const *)buf;
|
||||
|
||||
/* Do endian check at execution time instead of compile time, since ARM
|
||||
processors can change the endianness at execution time. If the
|
||||
compiler knows what the endianness will be, it can optimize out the
|
||||
check and the unused branch. */
|
||||
endian = 1;
|
||||
if (*(unsigned char *)&endian) {
|
||||
/* Little endian. */
|
||||
|
||||
z_crc_t crc0;
|
||||
z_word_t word0;
|
||||
#if N > 1
|
||||
z_crc_t crc1;
|
||||
z_word_t word1;
|
||||
#if N > 2
|
||||
z_crc_t crc2;
|
||||
z_word_t word2;
|
||||
#if N > 3
|
||||
z_crc_t crc3;
|
||||
z_word_t word3;
|
||||
#if N > 4
|
||||
z_crc_t crc4;
|
||||
z_word_t word4;
|
||||
#if N > 5
|
||||
z_crc_t crc5;
|
||||
z_word_t word5;
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* Initialize the CRC for each braid. */
|
||||
crc0 = crc;
|
||||
#if N > 1
|
||||
crc1 = 0;
|
||||
#if N > 2
|
||||
crc2 = 0;
|
||||
#if N > 3
|
||||
crc3 = 0;
|
||||
#if N > 4
|
||||
crc4 = 0;
|
||||
#if N > 5
|
||||
crc5 = 0;
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/*
|
||||
Process the first blks-1 blocks, computing the CRCs on each braid
|
||||
independently.
|
||||
*/
|
||||
while (--blks) {
|
||||
/* Load the word for each braid into registers. */
|
||||
word0 = crc0 ^ words[0];
|
||||
#if N > 1
|
||||
word1 = crc1 ^ words[1];
|
||||
#if N > 2
|
||||
word2 = crc2 ^ words[2];
|
||||
#if N > 3
|
||||
word3 = crc3 ^ words[3];
|
||||
#if N > 4
|
||||
word4 = crc4 ^ words[4];
|
||||
#if N > 5
|
||||
word5 = crc5 ^ words[5];
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
words += N;
|
||||
|
||||
/* Compute and update the CRC for each word. The loop should
|
||||
get unrolled. */
|
||||
crc0 = crc_braid_table[0][word0 & 0xff];
|
||||
#if N > 1
|
||||
crc1 = crc_braid_table[0][word1 & 0xff];
|
||||
#if N > 2
|
||||
crc2 = crc_braid_table[0][word2 & 0xff];
|
||||
#if N > 3
|
||||
crc3 = crc_braid_table[0][word3 & 0xff];
|
||||
#if N > 4
|
||||
crc4 = crc_braid_table[0][word4 & 0xff];
|
||||
#if N > 5
|
||||
crc5 = crc_braid_table[0][word5 & 0xff];
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
for (k = 1; k < W; k++) {
|
||||
crc0 ^= crc_braid_table[k][(word0 >> (k << 3)) & 0xff];
|
||||
#if N > 1
|
||||
crc1 ^= crc_braid_table[k][(word1 >> (k << 3)) & 0xff];
|
||||
#if N > 2
|
||||
crc2 ^= crc_braid_table[k][(word2 >> (k << 3)) & 0xff];
|
||||
#if N > 3
|
||||
crc3 ^= crc_braid_table[k][(word3 >> (k << 3)) & 0xff];
|
||||
#if N > 4
|
||||
crc4 ^= crc_braid_table[k][(word4 >> (k << 3)) & 0xff];
|
||||
#if N > 5
|
||||
crc5 ^= crc_braid_table[k][(word5 >> (k << 3)) & 0xff];
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Process the last block, combining the CRCs of the N braids at the
|
||||
same time.
|
||||
*/
|
||||
crc = crc_word(crc0 ^ words[0]);
|
||||
#if N > 1
|
||||
crc = crc_word(crc1 ^ words[1] ^ crc);
|
||||
#if N > 2
|
||||
crc = crc_word(crc2 ^ words[2] ^ crc);
|
||||
#if N > 3
|
||||
crc = crc_word(crc3 ^ words[3] ^ crc);
|
||||
#if N > 4
|
||||
crc = crc_word(crc4 ^ words[4] ^ crc);
|
||||
#if N > 5
|
||||
crc = crc_word(crc5 ^ words[5] ^ crc);
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
words += N;
|
||||
}
|
||||
else {
|
||||
/* Big endian. */
|
||||
|
||||
z_word_t crc0, word0, comb;
|
||||
#if N > 1
|
||||
z_word_t crc1, word1;
|
||||
#if N > 2
|
||||
z_word_t crc2, word2;
|
||||
#if N > 3
|
||||
z_word_t crc3, word3;
|
||||
#if N > 4
|
||||
z_word_t crc4, word4;
|
||||
#if N > 5
|
||||
z_word_t crc5, word5;
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* Initialize the CRC for each braid. */
|
||||
crc0 = byte_swap(crc);
|
||||
#if N > 1
|
||||
crc1 = 0;
|
||||
#if N > 2
|
||||
crc2 = 0;
|
||||
#if N > 3
|
||||
crc3 = 0;
|
||||
#if N > 4
|
||||
crc4 = 0;
|
||||
#if N > 5
|
||||
crc5 = 0;
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/*
|
||||
Process the first blks-1 blocks, computing the CRCs on each braid
|
||||
independently.
|
||||
*/
|
||||
while (--blks) {
|
||||
/* Load the word for each braid into registers. */
|
||||
word0 = crc0 ^ words[0];
|
||||
#if N > 1
|
||||
word1 = crc1 ^ words[1];
|
||||
#if N > 2
|
||||
word2 = crc2 ^ words[2];
|
||||
#if N > 3
|
||||
word3 = crc3 ^ words[3];
|
||||
#if N > 4
|
||||
word4 = crc4 ^ words[4];
|
||||
#if N > 5
|
||||
word5 = crc5 ^ words[5];
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
words += N;
|
||||
|
||||
/* Compute and update the CRC for each word. The loop should
|
||||
get unrolled. */
|
||||
crc0 = crc_braid_big_table[0][word0 & 0xff];
|
||||
#if N > 1
|
||||
crc1 = crc_braid_big_table[0][word1 & 0xff];
|
||||
#if N > 2
|
||||
crc2 = crc_braid_big_table[0][word2 & 0xff];
|
||||
#if N > 3
|
||||
crc3 = crc_braid_big_table[0][word3 & 0xff];
|
||||
#if N > 4
|
||||
crc4 = crc_braid_big_table[0][word4 & 0xff];
|
||||
#if N > 5
|
||||
crc5 = crc_braid_big_table[0][word5 & 0xff];
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
for (k = 1; k < W; k++) {
|
||||
crc0 ^= crc_braid_big_table[k][(word0 >> (k << 3)) & 0xff];
|
||||
#if N > 1
|
||||
crc1 ^= crc_braid_big_table[k][(word1 >> (k << 3)) & 0xff];
|
||||
#if N > 2
|
||||
crc2 ^= crc_braid_big_table[k][(word2 >> (k << 3)) & 0xff];
|
||||
#if N > 3
|
||||
crc3 ^= crc_braid_big_table[k][(word3 >> (k << 3)) & 0xff];
|
||||
#if N > 4
|
||||
crc4 ^= crc_braid_big_table[k][(word4 >> (k << 3)) & 0xff];
|
||||
#if N > 5
|
||||
crc5 ^= crc_braid_big_table[k][(word5 >> (k << 3)) & 0xff];
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Process the last block, combining the CRCs of the N braids at the
|
||||
same time.
|
||||
*/
|
||||
comb = crc_word_big(crc0 ^ words[0]);
|
||||
#if N > 1
|
||||
comb = crc_word_big(crc1 ^ words[1] ^ comb);
|
||||
#if N > 2
|
||||
comb = crc_word_big(crc2 ^ words[2] ^ comb);
|
||||
#if N > 3
|
||||
comb = crc_word_big(crc3 ^ words[3] ^ comb);
|
||||
#if N > 4
|
||||
comb = crc_word_big(crc4 ^ words[4] ^ comb);
|
||||
#if N > 5
|
||||
comb = crc_word_big(crc5 ^ words[5] ^ comb);
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
words += N;
|
||||
crc = byte_swap(comb);
|
||||
}
|
||||
|
||||
/*
|
||||
Update the pointer to the remaining bytes to process.
|
||||
*/
|
||||
buf = (unsigned char const *)words;
|
||||
}
|
||||
|
||||
#endif /* W */
|
||||
|
||||
/* Complete the computation of the CRC on any remaining bytes. */
|
||||
while (len >= 8) {
|
||||
len -= 8;
|
||||
crc = (crc >> 8) ^ crc_table[(crc ^ *buf++) & 0xff];
|
||||
crc = (crc >> 8) ^ crc_table[(crc ^ *buf++) & 0xff];
|
||||
crc = (crc >> 8) ^ crc_table[(crc ^ *buf++) & 0xff];
|
||||
crc = (crc >> 8) ^ crc_table[(crc ^ *buf++) & 0xff];
|
||||
crc = (crc >> 8) ^ crc_table[(crc ^ *buf++) & 0xff];
|
||||
crc = (crc >> 8) ^ crc_table[(crc ^ *buf++) & 0xff];
|
||||
crc = (crc >> 8) ^ crc_table[(crc ^ *buf++) & 0xff];
|
||||
crc = (crc >> 8) ^ crc_table[(crc ^ *buf++) & 0xff];
|
||||
}
|
||||
while (len) {
|
||||
len--;
|
||||
crc = (crc >> 8) ^ crc_table[(crc ^ *buf++) & 0xff];
|
||||
}
|
||||
|
||||
/* Return the CRC, post-conditioned. */
|
||||
return crc ^ 0xffffffff;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/* ========================================================================= */
|
||||
uLong ZEXPORT crc32(uLong crc, const unsigned char FAR *buf, uInt len) {
|
||||
#ifdef HAVE_S390X_VX
|
||||
return crc32_z_hook(crc, buf, len);
|
||||
#endif
|
||||
return crc32_z(crc, buf, len);
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
uLong ZEXPORT crc32_combine_gen64(z_off64_t len2) {
|
||||
if (len2 < 0)
|
||||
return 0;
|
||||
#ifdef DYNAMIC_CRC_TABLE
|
||||
z_once(&made, make_crc_table);
|
||||
#endif /* DYNAMIC_CRC_TABLE */
|
||||
return x2nmodp(len2, 3);
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
uLong ZEXPORT crc32_combine_gen(z_off_t len2) {
|
||||
return crc32_combine_gen64((z_off64_t)len2);
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
uLong ZEXPORT crc32_combine_op(uLong crc1, uLong crc2, uLong op) {
|
||||
if (op == 0)
|
||||
return 0;
|
||||
return multmodp(op, crc1 & 0xffffffff) ^ (crc2 & 0xffffffff);
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
uLong ZEXPORT crc32_combine64(uLong crc1, uLong crc2, z_off64_t len2) {
|
||||
return crc32_combine_op(crc1, crc2, crc32_combine_gen64(len2));
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
uLong ZEXPORT crc32_combine(uLong crc1, uLong crc2, z_off_t len2) {
|
||||
return crc32_combine64(crc1, crc2, (z_off64_t)len2);
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
/* gzguts.h -- zlib internal header definitions for gz* operations
|
||||
* Copyright (C) 2004-2026 Mark Adler
|
||||
* For conditions of distribution and use, see copyright notice in zlib.h
|
||||
*/
|
||||
|
||||
#ifdef _LARGEFILE64_SOURCE
|
||||
# ifndef _LARGEFILE_SOURCE
|
||||
# define _LARGEFILE_SOURCE 1
|
||||
# endif
|
||||
# undef _FILE_OFFSET_BITS
|
||||
# undef _TIME_BITS
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_HIDDEN
|
||||
# define ZLIB_INTERNAL __attribute__((visibility ("hidden")))
|
||||
#else
|
||||
# define ZLIB_INTERNAL
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
# ifndef WIN32_LEAN_AND_MEAN
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# endif
|
||||
# ifndef _CRT_SECURE_NO_WARNINGS
|
||||
# define _CRT_SECURE_NO_WARNINGS
|
||||
# endif
|
||||
# ifndef _CRT_NONSTDC_NO_DEPRECATE
|
||||
# define _CRT_NONSTDC_NO_DEPRECATE
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include "zlib.h"
|
||||
#ifdef STDC
|
||||
# include <string.h>
|
||||
# include <stdlib.h>
|
||||
# include <limits.h>
|
||||
#endif
|
||||
|
||||
#ifndef _POSIX_C_SOURCE
|
||||
# define _POSIX_C_SOURCE 200112L
|
||||
#endif
|
||||
#include <fcntl.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <stddef.h>
|
||||
#endif
|
||||
|
||||
#if defined(__TURBOC__) || defined(_MSC_VER) || defined(_WIN32)
|
||||
# include <io.h>
|
||||
# include <sys/stat.h>
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32) && !defined(WIDECHAR)
|
||||
# define WIDECHAR
|
||||
#endif
|
||||
|
||||
#ifdef NO_DEFLATE /* for compatibility with old definition */
|
||||
# define NO_GZCOMPRESS
|
||||
#endif
|
||||
|
||||
#if defined(STDC99) || (defined(__TURBOC__) && __TURBOC__ >= 0x550)
|
||||
# ifndef HAVE_VSNPRINTF
|
||||
# define HAVE_VSNPRINTF
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if defined(__CYGWIN__)
|
||||
# ifndef HAVE_VSNPRINTF
|
||||
# define HAVE_VSNPRINTF
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if defined(MSDOS) && defined(__BORLANDC__) && (BORLANDC > 0x410)
|
||||
# ifndef HAVE_VSNPRINTF
|
||||
# define HAVE_VSNPRINTF
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_VSNPRINTF
|
||||
# if !defined(NO_vsnprintf) && \
|
||||
(defined(MSDOS) || defined(__TURBOC__) || defined(__SASC) || \
|
||||
defined(VMS) || defined(__OS400) || defined(__MVS__))
|
||||
/* vsnprintf may exist on some MS-DOS compilers (DJGPP?),
|
||||
but for now we just assume it doesn't. */
|
||||
# define NO_vsnprintf
|
||||
# endif
|
||||
# ifdef WIN32
|
||||
/* In Win32, vsnprintf is available as the "non-ANSI" _vsnprintf. */
|
||||
# if !defined(_MSC_VER) || ( defined(_MSC_VER) && _MSC_VER < 1500 )
|
||||
# ifndef vsnprintf
|
||||
# define vsnprintf _vsnprintf
|
||||
# endif
|
||||
# endif
|
||||
# elif !defined(__STDC_VERSION__) || __STDC_VERSION__-0 < 199901L
|
||||
/* Otherwise if C89/90, assume no C99 snprintf() or vsnprintf() */
|
||||
# ifndef NO_snprintf
|
||||
# define NO_snprintf
|
||||
# endif
|
||||
# ifndef NO_vsnprintf
|
||||
# define NO_vsnprintf
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/* unlike snprintf (which is required in C99), _snprintf does not guarantee
|
||||
null termination of the result -- however this is only used in gzlib.c where
|
||||
the result is assured to fit in the space provided */
|
||||
#if defined(_MSC_VER) && _MSC_VER < 1900
|
||||
# define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
#ifndef local
|
||||
# define local static
|
||||
#endif
|
||||
/* since "static" is used to mean two completely different things in C, we
|
||||
define "local" for the non-static meaning of "static", for readability
|
||||
(compile with -Dlocal if your debugger can't find static symbols) */
|
||||
|
||||
/* gz* functions always use library allocation functions */
|
||||
#ifndef STDC
|
||||
extern voidp malloc(uInt size);
|
||||
extern void free(voidpf ptr);
|
||||
#endif
|
||||
|
||||
/* get errno and strerror definition */
|
||||
#if defined UNDER_CE
|
||||
# include <windows.h>
|
||||
# define zstrerror() gz_strwinerror((DWORD)GetLastError())
|
||||
#else
|
||||
# ifndef NO_STRERROR
|
||||
# include <errno.h>
|
||||
# define zstrerror() strerror(errno)
|
||||
# else
|
||||
# define zstrerror() "stdio error (consult errno)"
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/* provide prototypes for these when building zlib without LFS */
|
||||
#if !defined(_LARGEFILE64_SOURCE) || _LFS64_LARGEFILE-0 == 0
|
||||
ZEXTERN gzFile ZEXPORT gzopen64(const char *, const char *);
|
||||
ZEXTERN z_off64_t ZEXPORT gzseek64(gzFile, z_off64_t, int);
|
||||
ZEXTERN z_off64_t ZEXPORT gztell64(gzFile);
|
||||
ZEXTERN z_off64_t ZEXPORT gzoffset64(gzFile);
|
||||
#endif
|
||||
|
||||
/* default memLevel */
|
||||
#if MAX_MEM_LEVEL >= 8
|
||||
# define DEF_MEM_LEVEL 8
|
||||
#else
|
||||
# define DEF_MEM_LEVEL MAX_MEM_LEVEL
|
||||
#endif
|
||||
|
||||
/* default i/o buffer size -- double this for output when reading (this and
|
||||
twice this must be able to fit in an unsigned type) */
|
||||
#define GZBUFSIZE 8192
|
||||
|
||||
/* gzip modes, also provide a little integrity check on the passed structure */
|
||||
#define GZ_NONE 0
|
||||
#define GZ_READ 7247
|
||||
#define GZ_WRITE 31153
|
||||
#define GZ_APPEND 1 /* mode set to GZ_WRITE after the file is opened */
|
||||
|
||||
/* values for gz_state how */
|
||||
#define LOOK 0 /* look for a gzip header */
|
||||
#define COPY 1 /* copy input directly */
|
||||
#define GZIP 2 /* decompress a gzip stream */
|
||||
|
||||
/* internal gzip file state data structure */
|
||||
typedef struct {
|
||||
/* exposed contents for gzgetc() macro */
|
||||
struct gzFile_s x; /* "x" for exposed */
|
||||
/* x.have: number of bytes available at x.next */
|
||||
/* x.next: next output data to deliver or write */
|
||||
/* x.pos: current position in uncompressed data */
|
||||
/* used for both reading and writing */
|
||||
int mode; /* see gzip modes above */
|
||||
int fd; /* file descriptor */
|
||||
char *path; /* path or fd for error messages */
|
||||
unsigned size; /* buffer size, zero if not allocated yet */
|
||||
unsigned want; /* requested buffer size, default is GZBUFSIZE */
|
||||
unsigned char *in; /* input buffer (double-sized when writing) */
|
||||
unsigned char *out; /* output buffer (double-sized when reading) */
|
||||
int direct; /* 0 if processing gzip, 1 if transparent */
|
||||
/* just for reading */
|
||||
int junk; /* -1 = start, 1 = junk candidate, 0 = in gzip */
|
||||
int how; /* 0: get header, 1: copy, 2: decompress */
|
||||
int again; /* true if EAGAIN or EWOULDBLOCK on last i/o */
|
||||
z_off64_t start; /* where the gzip data started, for rewinding */
|
||||
int eof; /* true if end of input file reached */
|
||||
int past; /* true if read requested past end */
|
||||
/* just for writing */
|
||||
int level; /* compression level */
|
||||
int strategy; /* compression strategy */
|
||||
int reset; /* true if a reset is pending after a Z_FINISH */
|
||||
/* seek request */
|
||||
z_off64_t skip; /* amount to skip (already rewound if backwards) */
|
||||
/* error information */
|
||||
int err; /* error code */
|
||||
char *msg; /* error message */
|
||||
/* zlib inflate or deflate stream */
|
||||
z_stream strm; /* stream structure in-place (not a pointer) */
|
||||
} gz_state;
|
||||
typedef gz_state FAR *gz_statep;
|
||||
|
||||
/* shared functions */
|
||||
void ZLIB_INTERNAL gz_error(gz_statep, int, const char *);
|
||||
#if defined UNDER_CE
|
||||
char ZLIB_INTERNAL *gz_strwinerror(DWORD error);
|
||||
#endif
|
||||
|
||||
/* GT_OFF(x), where x is an unsigned value, is true if x > maximum z_off64_t
|
||||
value -- needed when comparing unsigned to z_off64_t, which is signed
|
||||
(possible z_off64_t types off_t, off64_t, and long are all signed) */
|
||||
unsigned ZLIB_INTERNAL gz_intmax(void);
|
||||
#define GT_OFF(x) (sizeof(int) == sizeof(z_off64_t) && (x) > gz_intmax())
|
||||
@@ -0,0 +1,321 @@
|
||||
/* inffast.c -- fast decoding
|
||||
* Copyright (C) 1995-2026 Mark Adler
|
||||
* For conditions of distribution and use, see copyright notice in zlib.h
|
||||
*/
|
||||
|
||||
#include "zutil.h"
|
||||
#include "inftrees.h"
|
||||
#include "inflate.h"
|
||||
#include "inffast.h"
|
||||
|
||||
#ifdef ASMINF
|
||||
# pragma message("Assembler code may have bugs -- use at your own risk")
|
||||
#else
|
||||
|
||||
/*
|
||||
Decode literal, length, and distance codes and write out the resulting
|
||||
literal and match bytes until either not enough input or output is
|
||||
available, an end-of-block is encountered, or a data error is encountered.
|
||||
When large enough input and output buffers are supplied to inflate(), for
|
||||
example, a 16K input buffer and a 64K output buffer, more than 95% of the
|
||||
inflate execution time is spent in this routine.
|
||||
|
||||
Entry assumptions:
|
||||
|
||||
state->mode == LEN
|
||||
strm->avail_in >= 6
|
||||
strm->avail_out >= 258
|
||||
start >= strm->avail_out
|
||||
state->bits < 8
|
||||
|
||||
On return, state->mode is one of:
|
||||
|
||||
LEN -- ran out of enough output space or enough available input
|
||||
TYPE -- reached end of block code, inflate() to interpret next block
|
||||
BAD -- error in block data
|
||||
|
||||
Notes:
|
||||
|
||||
- The maximum input bits used by a length/distance pair is 15 bits for the
|
||||
length code, 5 bits for the length extra, 15 bits for the distance code,
|
||||
and 13 bits for the distance extra. This totals 48 bits, or six bytes.
|
||||
Therefore if strm->avail_in >= 6, then there is enough input to avoid
|
||||
checking for available input while decoding.
|
||||
|
||||
- The maximum bytes that a single length/distance pair can output is 258
|
||||
bytes, which is the maximum length that can be coded. inflate_fast()
|
||||
requires strm->avail_out >= 258 for each loop to avoid checking for
|
||||
output space.
|
||||
*/
|
||||
void ZLIB_INTERNAL inflate_fast(z_streamp strm, unsigned start) {
|
||||
struct inflate_state FAR *state;
|
||||
z_const unsigned char FAR *in; /* local strm->next_in */
|
||||
z_const unsigned char FAR *last; /* have enough input while in < last */
|
||||
unsigned char FAR *out; /* local strm->next_out */
|
||||
unsigned char FAR *beg; /* inflate()'s initial strm->next_out */
|
||||
unsigned char FAR *end; /* while out < end, enough space available */
|
||||
#ifdef INFLATE_STRICT
|
||||
unsigned dmax; /* maximum distance from zlib header */
|
||||
#endif
|
||||
unsigned wsize; /* window size or zero if not using window */
|
||||
unsigned whave; /* valid bytes in the window */
|
||||
unsigned wnext; /* window write index */
|
||||
unsigned char FAR *window; /* allocated sliding window, if wsize != 0 */
|
||||
unsigned long hold; /* local strm->hold */
|
||||
unsigned bits; /* local strm->bits */
|
||||
code const FAR *lcode; /* local strm->lencode */
|
||||
code const FAR *dcode; /* local strm->distcode */
|
||||
unsigned lmask; /* mask for first level of length codes */
|
||||
unsigned dmask; /* mask for first level of distance codes */
|
||||
code const *here; /* retrieved table entry */
|
||||
unsigned op; /* code bits, operation, extra bits, or */
|
||||
/* window position, window bytes to copy */
|
||||
unsigned len; /* match length, unused bytes */
|
||||
unsigned dist; /* match distance */
|
||||
unsigned char FAR *from; /* where to copy match from */
|
||||
|
||||
/* copy state to local variables */
|
||||
state = (struct inflate_state FAR *)strm->state;
|
||||
in = strm->next_in;
|
||||
last = in + (strm->avail_in - 5);
|
||||
out = strm->next_out;
|
||||
beg = out - (start - strm->avail_out);
|
||||
end = out + (strm->avail_out - 257);
|
||||
#ifdef INFLATE_STRICT
|
||||
dmax = state->dmax;
|
||||
#endif
|
||||
wsize = state->wsize;
|
||||
whave = state->whave;
|
||||
wnext = state->wnext;
|
||||
window = state->window;
|
||||
hold = state->hold;
|
||||
bits = state->bits;
|
||||
lcode = state->lencode;
|
||||
dcode = state->distcode;
|
||||
lmask = (1U << state->lenbits) - 1;
|
||||
dmask = (1U << state->distbits) - 1;
|
||||
|
||||
/* decode literals and length/distances until end-of-block or not enough
|
||||
input data or output space */
|
||||
do {
|
||||
if (bits < 15) {
|
||||
hold += (unsigned long)(*in++) << bits;
|
||||
bits += 8;
|
||||
hold += (unsigned long)(*in++) << bits;
|
||||
bits += 8;
|
||||
}
|
||||
here = lcode + (hold & lmask);
|
||||
dolen:
|
||||
op = (unsigned)(here->bits);
|
||||
hold >>= op;
|
||||
bits -= op;
|
||||
op = (unsigned)(here->op);
|
||||
if (op == 0) { /* literal */
|
||||
Tracevv((stderr, here->val >= 0x20 && here->val < 0x7f ?
|
||||
"inflate: literal '%c'\n" :
|
||||
"inflate: literal 0x%02x\n", here->val));
|
||||
*out++ = (unsigned char)(here->val);
|
||||
}
|
||||
else if (op & 16) { /* length base */
|
||||
len = (unsigned)(here->val);
|
||||
op &= 15; /* number of extra bits */
|
||||
if (op) {
|
||||
if (bits < op) {
|
||||
hold += (unsigned long)(*in++) << bits;
|
||||
bits += 8;
|
||||
}
|
||||
len += (unsigned)hold & ((1U << op) - 1);
|
||||
hold >>= op;
|
||||
bits -= op;
|
||||
}
|
||||
Tracevv((stderr, "inflate: length %u\n", len));
|
||||
if (bits < 15) {
|
||||
hold += (unsigned long)(*in++) << bits;
|
||||
bits += 8;
|
||||
hold += (unsigned long)(*in++) << bits;
|
||||
bits += 8;
|
||||
}
|
||||
here = dcode + (hold & dmask);
|
||||
dodist:
|
||||
op = (unsigned)(here->bits);
|
||||
hold >>= op;
|
||||
bits -= op;
|
||||
op = (unsigned)(here->op);
|
||||
if (op & 16) { /* distance base */
|
||||
dist = (unsigned)(here->val);
|
||||
op &= 15; /* number of extra bits */
|
||||
if (bits < op) {
|
||||
hold += (unsigned long)(*in++) << bits;
|
||||
bits += 8;
|
||||
if (bits < op) {
|
||||
hold += (unsigned long)(*in++) << bits;
|
||||
bits += 8;
|
||||
}
|
||||
}
|
||||
dist += (unsigned)hold & ((1U << op) - 1);
|
||||
#ifdef INFLATE_STRICT
|
||||
if (dist > dmax) {
|
||||
strm->msg = (z_const char *)
|
||||
"invalid distance too far back";
|
||||
state->mode = BAD;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
hold >>= op;
|
||||
bits -= op;
|
||||
Tracevv((stderr, "inflate: distance %u\n", dist));
|
||||
op = (unsigned)(out - beg); /* max distance in output */
|
||||
if (dist > op) { /* see if copy from window */
|
||||
op = dist - op; /* distance back in window */
|
||||
if (op > whave) {
|
||||
if (state->sane) {
|
||||
strm->msg = (z_const char *)
|
||||
"invalid distance too far back";
|
||||
state->mode = BAD;
|
||||
break;
|
||||
}
|
||||
#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
|
||||
if (len <= op - whave) {
|
||||
do {
|
||||
*out++ = 0;
|
||||
} while (--len);
|
||||
continue;
|
||||
}
|
||||
len -= op - whave;
|
||||
do {
|
||||
*out++ = 0;
|
||||
} while (--op > whave);
|
||||
if (op == 0) {
|
||||
from = out - dist;
|
||||
do {
|
||||
*out++ = *from++;
|
||||
} while (--len);
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
from = window;
|
||||
if (wnext == 0) { /* very common case */
|
||||
from += wsize - op;
|
||||
if (op < len) { /* some from window */
|
||||
len -= op;
|
||||
do {
|
||||
*out++ = *from++;
|
||||
} while (--op);
|
||||
from = out - dist; /* rest from output */
|
||||
}
|
||||
}
|
||||
else if (wnext < op) { /* wrap around window */
|
||||
from += wsize + wnext - op;
|
||||
op -= wnext;
|
||||
if (op < len) { /* some from end of window */
|
||||
len -= op;
|
||||
do {
|
||||
*out++ = *from++;
|
||||
} while (--op);
|
||||
from = window;
|
||||
if (wnext < len) { /* some from start of window */
|
||||
op = wnext;
|
||||
len -= op;
|
||||
do {
|
||||
*out++ = *from++;
|
||||
} while (--op);
|
||||
from = out - dist; /* rest from output */
|
||||
}
|
||||
}
|
||||
}
|
||||
else { /* contiguous in window */
|
||||
from += wnext - op;
|
||||
if (op < len) { /* some from window */
|
||||
len -= op;
|
||||
do {
|
||||
*out++ = *from++;
|
||||
} while (--op);
|
||||
from = out - dist; /* rest from output */
|
||||
}
|
||||
}
|
||||
while (len > 2) {
|
||||
*out++ = *from++;
|
||||
*out++ = *from++;
|
||||
*out++ = *from++;
|
||||
len -= 3;
|
||||
}
|
||||
if (len) {
|
||||
*out++ = *from++;
|
||||
if (len > 1)
|
||||
*out++ = *from++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
from = out - dist; /* copy direct from output */
|
||||
do { /* minimum length is three */
|
||||
*out++ = *from++;
|
||||
*out++ = *from++;
|
||||
*out++ = *from++;
|
||||
len -= 3;
|
||||
} while (len > 2);
|
||||
if (len) {
|
||||
*out++ = *from++;
|
||||
if (len > 1)
|
||||
*out++ = *from++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ((op & 64) == 0) { /* 2nd level distance code */
|
||||
here = dcode + here->val + (hold & ((1U << op) - 1));
|
||||
goto dodist;
|
||||
}
|
||||
else {
|
||||
strm->msg = (z_const char *)"invalid distance code";
|
||||
state->mode = BAD;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if ((op & 64) == 0) { /* 2nd level length code */
|
||||
here = lcode + here->val + (hold & ((1U << op) - 1));
|
||||
goto dolen;
|
||||
}
|
||||
else if (op & 32) { /* end-of-block */
|
||||
Tracevv((stderr, "inflate: end of block\n"));
|
||||
state->mode = TYPE;
|
||||
break;
|
||||
}
|
||||
else {
|
||||
strm->msg = (z_const char *)"invalid literal/length code";
|
||||
state->mode = BAD;
|
||||
break;
|
||||
}
|
||||
} while (in < last && out < end);
|
||||
|
||||
/* return unused bytes (on entry, bits < 8, so in won't go too far back) */
|
||||
len = bits >> 3;
|
||||
in -= len;
|
||||
bits -= len << 3;
|
||||
hold &= (1U << bits) - 1;
|
||||
|
||||
/* update state and return */
|
||||
strm->next_in = in;
|
||||
strm->next_out = out;
|
||||
strm->avail_in = (unsigned)(in < last ? 5 + (last - in) : 5 - (in - last));
|
||||
strm->avail_out = (unsigned)(out < end ?
|
||||
257 + (end - out) : 257 - (out - end));
|
||||
state->hold = hold;
|
||||
state->bits = bits;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
inflate_fast() speedups that turned out slower (on a PowerPC G3 750CXe):
|
||||
- Using bit fields for code structure
|
||||
- Different op definition to avoid & for extra bits (do & for table bits)
|
||||
- Three separate decoding do-loops for direct, window, and wnext == 0
|
||||
- Special case for distance > 1 copies to do overlapped load and store copy
|
||||
- Explicit branch predictions (based on measured branch probabilities)
|
||||
- Deferring match copy and interspersed it with decoding subsequent codes
|
||||
- Swapping literal/length else
|
||||
- Swapping window/direct else
|
||||
- Larger unrolled copy loops (three is about right)
|
||||
- Moving len -= 3 statement into middle of loop
|
||||
*/
|
||||
|
||||
#endif /* !ASMINF */
|
||||
@@ -0,0 +1,11 @@
|
||||
/* inffast.h -- header to use inffast.c
|
||||
* Copyright (C) 1995-2003, 2010 Mark Adler
|
||||
* For conditions of distribution and use, see copyright notice in zlib.h
|
||||
*/
|
||||
|
||||
/* WARNING: this file should *not* be used by applications. It is
|
||||
part of the implementation of the compression library and is
|
||||
subject to change. Applications should only use zlib.h.
|
||||
*/
|
||||
|
||||
void ZLIB_INTERNAL inflate_fast(z_streamp strm, unsigned start);
|
||||
@@ -0,0 +1,94 @@
|
||||
/* inffixed.h -- table for decoding fixed codes
|
||||
* Generated automatically by makefixed().
|
||||
*/
|
||||
|
||||
/* WARNING: this file should *not* be used by applications.
|
||||
It is part of the implementation of this library and is
|
||||
subject to change. Applications should only use zlib.h.
|
||||
*/
|
||||
|
||||
static const code lenfix[512] = {
|
||||
{96,7,0},{0,8,80},{0,8,16},{20,8,115},{18,7,31},{0,8,112},{0,8,48},
|
||||
{0,9,192},{16,7,10},{0,8,96},{0,8,32},{0,9,160},{0,8,0},{0,8,128},
|
||||
{0,8,64},{0,9,224},{16,7,6},{0,8,88},{0,8,24},{0,9,144},{19,7,59},
|
||||
{0,8,120},{0,8,56},{0,9,208},{17,7,17},{0,8,104},{0,8,40},{0,9,176},
|
||||
{0,8,8},{0,8,136},{0,8,72},{0,9,240},{16,7,4},{0,8,84},{0,8,20},
|
||||
{21,8,227},{19,7,43},{0,8,116},{0,8,52},{0,9,200},{17,7,13},{0,8,100},
|
||||
{0,8,36},{0,9,168},{0,8,4},{0,8,132},{0,8,68},{0,9,232},{16,7,8},
|
||||
{0,8,92},{0,8,28},{0,9,152},{20,7,83},{0,8,124},{0,8,60},{0,9,216},
|
||||
{18,7,23},{0,8,108},{0,8,44},{0,9,184},{0,8,12},{0,8,140},{0,8,76},
|
||||
{0,9,248},{16,7,3},{0,8,82},{0,8,18},{21,8,163},{19,7,35},{0,8,114},
|
||||
{0,8,50},{0,9,196},{17,7,11},{0,8,98},{0,8,34},{0,9,164},{0,8,2},
|
||||
{0,8,130},{0,8,66},{0,9,228},{16,7,7},{0,8,90},{0,8,26},{0,9,148},
|
||||
{20,7,67},{0,8,122},{0,8,58},{0,9,212},{18,7,19},{0,8,106},{0,8,42},
|
||||
{0,9,180},{0,8,10},{0,8,138},{0,8,74},{0,9,244},{16,7,5},{0,8,86},
|
||||
{0,8,22},{64,8,0},{19,7,51},{0,8,118},{0,8,54},{0,9,204},{17,7,15},
|
||||
{0,8,102},{0,8,38},{0,9,172},{0,8,6},{0,8,134},{0,8,70},{0,9,236},
|
||||
{16,7,9},{0,8,94},{0,8,30},{0,9,156},{20,7,99},{0,8,126},{0,8,62},
|
||||
{0,9,220},{18,7,27},{0,8,110},{0,8,46},{0,9,188},{0,8,14},{0,8,142},
|
||||
{0,8,78},{0,9,252},{96,7,0},{0,8,81},{0,8,17},{21,8,131},{18,7,31},
|
||||
{0,8,113},{0,8,49},{0,9,194},{16,7,10},{0,8,97},{0,8,33},{0,9,162},
|
||||
{0,8,1},{0,8,129},{0,8,65},{0,9,226},{16,7,6},{0,8,89},{0,8,25},
|
||||
{0,9,146},{19,7,59},{0,8,121},{0,8,57},{0,9,210},{17,7,17},{0,8,105},
|
||||
{0,8,41},{0,9,178},{0,8,9},{0,8,137},{0,8,73},{0,9,242},{16,7,4},
|
||||
{0,8,85},{0,8,21},{16,8,258},{19,7,43},{0,8,117},{0,8,53},{0,9,202},
|
||||
{17,7,13},{0,8,101},{0,8,37},{0,9,170},{0,8,5},{0,8,133},{0,8,69},
|
||||
{0,9,234},{16,7,8},{0,8,93},{0,8,29},{0,9,154},{20,7,83},{0,8,125},
|
||||
{0,8,61},{0,9,218},{18,7,23},{0,8,109},{0,8,45},{0,9,186},{0,8,13},
|
||||
{0,8,141},{0,8,77},{0,9,250},{16,7,3},{0,8,83},{0,8,19},{21,8,195},
|
||||
{19,7,35},{0,8,115},{0,8,51},{0,9,198},{17,7,11},{0,8,99},{0,8,35},
|
||||
{0,9,166},{0,8,3},{0,8,131},{0,8,67},{0,9,230},{16,7,7},{0,8,91},
|
||||
{0,8,27},{0,9,150},{20,7,67},{0,8,123},{0,8,59},{0,9,214},{18,7,19},
|
||||
{0,8,107},{0,8,43},{0,9,182},{0,8,11},{0,8,139},{0,8,75},{0,9,246},
|
||||
{16,7,5},{0,8,87},{0,8,23},{64,8,0},{19,7,51},{0,8,119},{0,8,55},
|
||||
{0,9,206},{17,7,15},{0,8,103},{0,8,39},{0,9,174},{0,8,7},{0,8,135},
|
||||
{0,8,71},{0,9,238},{16,7,9},{0,8,95},{0,8,31},{0,9,158},{20,7,99},
|
||||
{0,8,127},{0,8,63},{0,9,222},{18,7,27},{0,8,111},{0,8,47},{0,9,190},
|
||||
{0,8,15},{0,8,143},{0,8,79},{0,9,254},{96,7,0},{0,8,80},{0,8,16},
|
||||
{20,8,115},{18,7,31},{0,8,112},{0,8,48},{0,9,193},{16,7,10},{0,8,96},
|
||||
{0,8,32},{0,9,161},{0,8,0},{0,8,128},{0,8,64},{0,9,225},{16,7,6},
|
||||
{0,8,88},{0,8,24},{0,9,145},{19,7,59},{0,8,120},{0,8,56},{0,9,209},
|
||||
{17,7,17},{0,8,104},{0,8,40},{0,9,177},{0,8,8},{0,8,136},{0,8,72},
|
||||
{0,9,241},{16,7,4},{0,8,84},{0,8,20},{21,8,227},{19,7,43},{0,8,116},
|
||||
{0,8,52},{0,9,201},{17,7,13},{0,8,100},{0,8,36},{0,9,169},{0,8,4},
|
||||
{0,8,132},{0,8,68},{0,9,233},{16,7,8},{0,8,92},{0,8,28},{0,9,153},
|
||||
{20,7,83},{0,8,124},{0,8,60},{0,9,217},{18,7,23},{0,8,108},{0,8,44},
|
||||
{0,9,185},{0,8,12},{0,8,140},{0,8,76},{0,9,249},{16,7,3},{0,8,82},
|
||||
{0,8,18},{21,8,163},{19,7,35},{0,8,114},{0,8,50},{0,9,197},{17,7,11},
|
||||
{0,8,98},{0,8,34},{0,9,165},{0,8,2},{0,8,130},{0,8,66},{0,9,229},
|
||||
{16,7,7},{0,8,90},{0,8,26},{0,9,149},{20,7,67},{0,8,122},{0,8,58},
|
||||
{0,9,213},{18,7,19},{0,8,106},{0,8,42},{0,9,181},{0,8,10},{0,8,138},
|
||||
{0,8,74},{0,9,245},{16,7,5},{0,8,86},{0,8,22},{64,8,0},{19,7,51},
|
||||
{0,8,118},{0,8,54},{0,9,205},{17,7,15},{0,8,102},{0,8,38},{0,9,173},
|
||||
{0,8,6},{0,8,134},{0,8,70},{0,9,237},{16,7,9},{0,8,94},{0,8,30},
|
||||
{0,9,157},{20,7,99},{0,8,126},{0,8,62},{0,9,221},{18,7,27},{0,8,110},
|
||||
{0,8,46},{0,9,189},{0,8,14},{0,8,142},{0,8,78},{0,9,253},{96,7,0},
|
||||
{0,8,81},{0,8,17},{21,8,131},{18,7,31},{0,8,113},{0,8,49},{0,9,195},
|
||||
{16,7,10},{0,8,97},{0,8,33},{0,9,163},{0,8,1},{0,8,129},{0,8,65},
|
||||
{0,9,227},{16,7,6},{0,8,89},{0,8,25},{0,9,147},{19,7,59},{0,8,121},
|
||||
{0,8,57},{0,9,211},{17,7,17},{0,8,105},{0,8,41},{0,9,179},{0,8,9},
|
||||
{0,8,137},{0,8,73},{0,9,243},{16,7,4},{0,8,85},{0,8,21},{16,8,258},
|
||||
{19,7,43},{0,8,117},{0,8,53},{0,9,203},{17,7,13},{0,8,101},{0,8,37},
|
||||
{0,9,171},{0,8,5},{0,8,133},{0,8,69},{0,9,235},{16,7,8},{0,8,93},
|
||||
{0,8,29},{0,9,155},{20,7,83},{0,8,125},{0,8,61},{0,9,219},{18,7,23},
|
||||
{0,8,109},{0,8,45},{0,9,187},{0,8,13},{0,8,141},{0,8,77},{0,9,251},
|
||||
{16,7,3},{0,8,83},{0,8,19},{21,8,195},{19,7,35},{0,8,115},{0,8,51},
|
||||
{0,9,199},{17,7,11},{0,8,99},{0,8,35},{0,9,167},{0,8,3},{0,8,131},
|
||||
{0,8,67},{0,9,231},{16,7,7},{0,8,91},{0,8,27},{0,9,151},{20,7,67},
|
||||
{0,8,123},{0,8,59},{0,9,215},{18,7,19},{0,8,107},{0,8,43},{0,9,183},
|
||||
{0,8,11},{0,8,139},{0,8,75},{0,9,247},{16,7,5},{0,8,87},{0,8,23},
|
||||
{64,8,0},{19,7,51},{0,8,119},{0,8,55},{0,9,207},{17,7,15},{0,8,103},
|
||||
{0,8,39},{0,9,175},{0,8,7},{0,8,135},{0,8,71},{0,9,239},{16,7,9},
|
||||
{0,8,95},{0,8,31},{0,9,159},{20,7,99},{0,8,127},{0,8,63},{0,9,223},
|
||||
{18,7,27},{0,8,111},{0,8,47},{0,9,191},{0,8,15},{0,8,143},{0,8,79},
|
||||
{0,9,255}
|
||||
};
|
||||
|
||||
static const code distfix[32] = {
|
||||
{16,5,1},{23,5,257},{19,5,17},{27,5,4097},{17,5,5},{25,5,1025},
|
||||
{21,5,65},{29,5,16385},{16,5,3},{24,5,513},{20,5,33},{28,5,8193},
|
||||
{18,5,9},{26,5,2049},{22,5,129},{64,5,0},{16,5,2},{23,5,385},
|
||||
{19,5,25},{27,5,6145},{17,5,7},{25,5,1537},{21,5,97},{29,5,24577},
|
||||
{16,5,4},{24,5,769},{20,5,49},{28,5,12289},{18,5,13},{26,5,3073},
|
||||
{22,5,193},{64,5,0}
|
||||
};
|
||||
@@ -0,0 +1,126 @@
|
||||
/* inflate.h -- internal inflate state definition
|
||||
* Copyright (C) 1995-2019 Mark Adler
|
||||
* For conditions of distribution and use, see copyright notice in zlib.h
|
||||
*/
|
||||
|
||||
/* WARNING: this file should *not* be used by applications. It is
|
||||
part of the implementation of the compression library and is
|
||||
subject to change. Applications should only use zlib.h.
|
||||
*/
|
||||
|
||||
/* define NO_GZIP when compiling if you want to disable gzip header and
|
||||
trailer decoding by inflate(). NO_GZIP would be used to avoid linking in
|
||||
the crc code when it is not needed. For shared libraries, gzip decoding
|
||||
should be left enabled. */
|
||||
#ifndef NO_GZIP
|
||||
# define GUNZIP
|
||||
#endif
|
||||
|
||||
/* Possible inflate modes between inflate() calls */
|
||||
typedef enum {
|
||||
HEAD = 16180, /* i: waiting for magic header */
|
||||
FLAGS, /* i: waiting for method and flags (gzip) */
|
||||
TIME, /* i: waiting for modification time (gzip) */
|
||||
OS, /* i: waiting for extra flags and operating system (gzip) */
|
||||
EXLEN, /* i: waiting for extra length (gzip) */
|
||||
EXTRA, /* i: waiting for extra bytes (gzip) */
|
||||
NAME, /* i: waiting for end of file name (gzip) */
|
||||
COMMENT, /* i: waiting for end of comment (gzip) */
|
||||
HCRC, /* i: waiting for header crc (gzip) */
|
||||
DICTID, /* i: waiting for dictionary check value */
|
||||
DICT, /* waiting for inflateSetDictionary() call */
|
||||
TYPE, /* i: waiting for type bits, including last-flag bit */
|
||||
TYPEDO, /* i: same, but skip check to exit inflate on new block */
|
||||
STORED, /* i: waiting for stored size (length and complement) */
|
||||
COPY_, /* i/o: same as COPY below, but only first time in */
|
||||
COPY, /* i/o: waiting for input or output to copy stored block */
|
||||
TABLE, /* i: waiting for dynamic block table lengths */
|
||||
LENLENS, /* i: waiting for code length code lengths */
|
||||
CODELENS, /* i: waiting for length/lit and distance code lengths */
|
||||
LEN_, /* i: same as LEN below, but only first time in */
|
||||
LEN, /* i: waiting for length/lit/eob code */
|
||||
LENEXT, /* i: waiting for length extra bits */
|
||||
DIST, /* i: waiting for distance code */
|
||||
DISTEXT, /* i: waiting for distance extra bits */
|
||||
MATCH, /* o: waiting for output space to copy string */
|
||||
LIT, /* o: waiting for output space to write literal */
|
||||
CHECK, /* i: waiting for 32-bit check value */
|
||||
LENGTH, /* i: waiting for 32-bit length (gzip) */
|
||||
DONE, /* finished check, done -- remain here until reset */
|
||||
BAD, /* got a data error -- remain here until reset */
|
||||
MEM, /* got an inflate() memory error -- remain here until reset */
|
||||
SYNC /* looking for synchronization bytes to restart inflate() */
|
||||
} inflate_mode;
|
||||
|
||||
/*
|
||||
State transitions between above modes -
|
||||
|
||||
(most modes can go to BAD or MEM on error -- not shown for clarity)
|
||||
|
||||
Process header:
|
||||
HEAD -> (gzip) or (zlib) or (raw)
|
||||
(gzip) -> FLAGS -> TIME -> OS -> EXLEN -> EXTRA -> NAME -> COMMENT ->
|
||||
HCRC -> TYPE
|
||||
(zlib) -> DICTID or TYPE
|
||||
DICTID -> DICT -> TYPE
|
||||
(raw) -> TYPEDO
|
||||
Read deflate blocks:
|
||||
TYPE -> TYPEDO -> STORED or TABLE or LEN_ or CHECK
|
||||
STORED -> COPY_ -> COPY -> TYPE
|
||||
TABLE -> LENLENS -> CODELENS -> LEN_
|
||||
LEN_ -> LEN
|
||||
Read deflate codes in fixed or dynamic block:
|
||||
LEN -> LENEXT or LIT or TYPE
|
||||
LENEXT -> DIST -> DISTEXT -> MATCH -> LEN
|
||||
LIT -> LEN
|
||||
Process trailer:
|
||||
CHECK -> LENGTH -> DONE
|
||||
*/
|
||||
|
||||
/* State maintained between inflate() calls -- approximately 7K bytes, not
|
||||
including the allocated sliding window, which is up to 32K bytes. */
|
||||
struct inflate_state {
|
||||
z_streamp strm; /* pointer back to this zlib stream */
|
||||
inflate_mode mode; /* current inflate mode */
|
||||
int last; /* true if processing last block */
|
||||
int wrap; /* bit 0 true for zlib, bit 1 true for gzip,
|
||||
bit 2 true to validate check value */
|
||||
int havedict; /* true if dictionary provided */
|
||||
int flags; /* gzip header method and flags, 0 if zlib, or
|
||||
-1 if raw or no header yet */
|
||||
unsigned dmax; /* zlib header max distance (INFLATE_STRICT) */
|
||||
unsigned long check; /* protected copy of check value */
|
||||
unsigned long total; /* protected copy of output count */
|
||||
gz_headerp head; /* where to save gzip header information */
|
||||
/* sliding window */
|
||||
unsigned wbits; /* log base 2 of requested window size */
|
||||
unsigned wsize; /* window size or zero if not using window */
|
||||
unsigned whave; /* valid bytes in the window */
|
||||
unsigned wnext; /* window write index */
|
||||
unsigned char FAR *window; /* allocated sliding window, if needed */
|
||||
/* bit accumulator */
|
||||
unsigned long hold; /* input bit accumulator */
|
||||
unsigned bits; /* number of bits in hold */
|
||||
/* for string and stored block copying */
|
||||
unsigned length; /* literal or length of data to copy */
|
||||
unsigned offset; /* distance back to copy string from */
|
||||
/* for table and code decoding */
|
||||
unsigned extra; /* extra bits needed */
|
||||
/* fixed and dynamic code tables */
|
||||
code const FAR *lencode; /* starting table for length/literal codes */
|
||||
code const FAR *distcode; /* starting table for distance codes */
|
||||
unsigned lenbits; /* index bits for lencode */
|
||||
unsigned distbits; /* index bits for distcode */
|
||||
/* dynamic table building */
|
||||
unsigned ncode; /* number of code length code lengths */
|
||||
unsigned nlen; /* number of length code lengths */
|
||||
unsigned ndist; /* number of distance code lengths */
|
||||
unsigned have; /* number of code lengths in lens[] */
|
||||
code FAR *next; /* next available space in codes[] */
|
||||
unsigned short lens[320]; /* temporary storage for code lengths */
|
||||
unsigned short work[288]; /* work area for code table building */
|
||||
code codes[ENOUGH]; /* space for code tables */
|
||||
int sane; /* if false, allow invalid distance too far */
|
||||
int back; /* bits back of last unprocessed length/lit */
|
||||
unsigned was; /* initial length of match */
|
||||
};
|
||||
@@ -0,0 +1,424 @@
|
||||
/* inftrees.c -- generate Huffman trees for efficient decoding
|
||||
* Copyright (C) 1995-2026 Mark Adler
|
||||
* For conditions of distribution and use, see copyright notice in zlib.h
|
||||
*/
|
||||
|
||||
#ifdef MAKEFIXED
|
||||
# ifndef BUILDFIXED
|
||||
# define BUILDFIXED
|
||||
# endif
|
||||
#endif
|
||||
#ifdef BUILDFIXED
|
||||
# define Z_ONCE
|
||||
#endif
|
||||
|
||||
#include "zutil.h"
|
||||
#include "inftrees.h"
|
||||
#include "inflate.h"
|
||||
|
||||
#ifndef NULL
|
||||
# define NULL 0
|
||||
#endif
|
||||
|
||||
#define MAXBITS 15
|
||||
|
||||
const char inflate_copyright[] =
|
||||
" inflate 1.3.2 Copyright 1995-2026 Mark Adler ";
|
||||
/*
|
||||
If you use the zlib library in a product, an acknowledgment is welcome
|
||||
in the documentation of your product. If for some reason you cannot
|
||||
include such an acknowledgment, I would appreciate that you keep this
|
||||
copyright string in the executable of your product.
|
||||
*/
|
||||
|
||||
/*
|
||||
Build a set of tables to decode the provided canonical Huffman code.
|
||||
The code lengths are lens[0..codes-1]. The result starts at *table,
|
||||
whose indices are 0..2^bits-1. work is a writable array of at least
|
||||
lens shorts, which is used as a work area. type is the type of code
|
||||
to be generated, CODES, LENS, or DISTS. On return, zero is success,
|
||||
-1 is an invalid code, and +1 means that ENOUGH isn't enough. table
|
||||
on return points to the next available entry's address. bits is the
|
||||
requested root table index bits, and on return it is the actual root
|
||||
table index bits. It will differ if the request is greater than the
|
||||
longest code or if it is less than the shortest code.
|
||||
*/
|
||||
int ZLIB_INTERNAL inflate_table(codetype type, unsigned short FAR *lens,
|
||||
unsigned codes, code FAR * FAR *table,
|
||||
unsigned FAR *bits, unsigned short FAR *work) {
|
||||
unsigned len; /* a code's length in bits */
|
||||
unsigned sym; /* index of code symbols */
|
||||
unsigned min, max; /* minimum and maximum code lengths */
|
||||
unsigned root; /* number of index bits for root table */
|
||||
unsigned curr; /* number of index bits for current table */
|
||||
unsigned drop; /* code bits to drop for sub-table */
|
||||
int left; /* number of prefix codes available */
|
||||
unsigned used; /* code entries in table used */
|
||||
unsigned huff; /* Huffman code */
|
||||
unsigned incr; /* for incrementing code, index */
|
||||
unsigned fill; /* index for replicating entries */
|
||||
unsigned low; /* low bits for current root entry */
|
||||
unsigned mask; /* mask for low root bits */
|
||||
code here; /* table entry for duplication */
|
||||
code FAR *next; /* next available space in table */
|
||||
const unsigned short FAR *base = NULL; /* base value table to use */
|
||||
const unsigned short FAR *extra = NULL; /* extra bits table to use */
|
||||
unsigned match = 0; /* use base and extra for symbol >= match */
|
||||
unsigned short count[MAXBITS+1]; /* number of codes of each length */
|
||||
unsigned short offs[MAXBITS+1]; /* offsets in table for each length */
|
||||
static const unsigned short lbase[31] = { /* Length codes 257..285 base */
|
||||
3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
|
||||
35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0};
|
||||
static const unsigned short lext[31] = { /* Length codes 257..285 extra */
|
||||
16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18,
|
||||
19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 199, 75};
|
||||
static const unsigned short dbase[32] = { /* Distance codes 0..29 base */
|
||||
1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
|
||||
257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
|
||||
8193, 12289, 16385, 24577, 0, 0};
|
||||
static const unsigned short dext[32] = { /* Distance codes 0..29 extra */
|
||||
16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22,
|
||||
23, 23, 24, 24, 25, 25, 26, 26, 27, 27,
|
||||
28, 28, 29, 29, 64, 64};
|
||||
|
||||
/*
|
||||
Process a set of code lengths to create a canonical Huffman code. The
|
||||
code lengths are lens[0..codes-1]. Each length corresponds to the
|
||||
symbols 0..codes-1. The Huffman code is generated by first sorting the
|
||||
symbols by length from short to long, and retaining the symbol order
|
||||
for codes with equal lengths. Then the code starts with all zero bits
|
||||
for the first code of the shortest length, and the codes are integer
|
||||
increments for the same length, and zeros are appended as the length
|
||||
increases. For the deflate format, these bits are stored backwards
|
||||
from their more natural integer increment ordering, and so when the
|
||||
decoding tables are built in the large loop below, the integer codes
|
||||
are incremented backwards.
|
||||
|
||||
This routine assumes, but does not check, that all of the entries in
|
||||
lens[] are in the range 0..MAXBITS. The caller must assure this.
|
||||
1..MAXBITS is interpreted as that code length. zero means that that
|
||||
symbol does not occur in this code.
|
||||
|
||||
The codes are sorted by computing a count of codes for each length,
|
||||
creating from that a table of starting indices for each length in the
|
||||
sorted table, and then entering the symbols in order in the sorted
|
||||
table. The sorted table is work[], with that space being provided by
|
||||
the caller.
|
||||
|
||||
The length counts are used for other purposes as well, i.e. finding
|
||||
the minimum and maximum length codes, determining if there are any
|
||||
codes at all, checking for a valid set of lengths, and looking ahead
|
||||
at length counts to determine sub-table sizes when building the
|
||||
decoding tables.
|
||||
*/
|
||||
|
||||
/* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */
|
||||
for (len = 0; len <= MAXBITS; len++)
|
||||
count[len] = 0;
|
||||
for (sym = 0; sym < codes; sym++)
|
||||
count[lens[sym]]++;
|
||||
|
||||
/* bound code lengths, force root to be within code lengths */
|
||||
root = *bits;
|
||||
for (max = MAXBITS; max >= 1; max--)
|
||||
if (count[max] != 0) break;
|
||||
if (root > max) root = max;
|
||||
if (max == 0) { /* no symbols to code at all */
|
||||
here.op = (unsigned char)64; /* invalid code marker */
|
||||
here.bits = (unsigned char)1;
|
||||
here.val = (unsigned short)0;
|
||||
*(*table)++ = here; /* make a table to force an error */
|
||||
*(*table)++ = here;
|
||||
*bits = 1;
|
||||
return 0; /* no symbols, but wait for decoding to report error */
|
||||
}
|
||||
for (min = 1; min < max; min++)
|
||||
if (count[min] != 0) break;
|
||||
if (root < min) root = min;
|
||||
|
||||
/* check for an over-subscribed or incomplete set of lengths */
|
||||
left = 1;
|
||||
for (len = 1; len <= MAXBITS; len++) {
|
||||
left <<= 1;
|
||||
left -= count[len];
|
||||
if (left < 0) return -1; /* over-subscribed */
|
||||
}
|
||||
if (left > 0 && (type == CODES || max != 1))
|
||||
return -1; /* incomplete set */
|
||||
|
||||
/* generate offsets into symbol table for each length for sorting */
|
||||
offs[1] = 0;
|
||||
for (len = 1; len < MAXBITS; len++)
|
||||
offs[len + 1] = offs[len] + count[len];
|
||||
|
||||
/* sort symbols by length, by symbol order within each length */
|
||||
for (sym = 0; sym < codes; sym++)
|
||||
if (lens[sym] != 0) work[offs[lens[sym]]++] = (unsigned short)sym;
|
||||
|
||||
/*
|
||||
Create and fill in decoding tables. In this loop, the table being
|
||||
filled is at next and has curr index bits. The code being used is huff
|
||||
with length len. That code is converted to an index by dropping drop
|
||||
bits off of the bottom. For codes where len is less than drop + curr,
|
||||
those top drop + curr - len bits are incremented through all values to
|
||||
fill the table with replicated entries.
|
||||
|
||||
root is the number of index bits for the root table. When len exceeds
|
||||
root, sub-tables are created pointed to by the root entry with an index
|
||||
of the low root bits of huff. This is saved in low to check for when a
|
||||
new sub-table should be started. drop is zero when the root table is
|
||||
being filled, and drop is root when sub-tables are being filled.
|
||||
|
||||
When a new sub-table is needed, it is necessary to look ahead in the
|
||||
code lengths to determine what size sub-table is needed. The length
|
||||
counts are used for this, and so count[] is decremented as codes are
|
||||
entered in the tables.
|
||||
|
||||
used keeps track of how many table entries have been allocated from the
|
||||
provided *table space. It is checked for LENS and DIST tables against
|
||||
the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in
|
||||
the initial root table size constants. See the comments in inftrees.h
|
||||
for more information.
|
||||
|
||||
sym increments through all symbols, and the loop terminates when
|
||||
all codes of length max, i.e. all codes, have been processed. This
|
||||
routine permits incomplete codes, so another loop after this one fills
|
||||
in the rest of the decoding tables with invalid code markers.
|
||||
*/
|
||||
|
||||
/* set up for code type */
|
||||
switch (type) {
|
||||
case CODES:
|
||||
match = 20;
|
||||
break;
|
||||
case LENS:
|
||||
base = lbase;
|
||||
extra = lext;
|
||||
match = 257;
|
||||
break;
|
||||
case DISTS:
|
||||
base = dbase;
|
||||
extra = dext;
|
||||
}
|
||||
|
||||
/* initialize state for loop */
|
||||
huff = 0; /* starting code */
|
||||
sym = 0; /* starting code symbol */
|
||||
len = min; /* starting code length */
|
||||
next = *table; /* current table to fill in */
|
||||
curr = root; /* current table index bits */
|
||||
drop = 0; /* current bits to drop from code for index */
|
||||
low = (unsigned)(-1); /* trigger new sub-table when len > root */
|
||||
used = 1U << root; /* use root table entries */
|
||||
mask = used - 1; /* mask for comparing low */
|
||||
|
||||
/* check available table space */
|
||||
if ((type == LENS && used > ENOUGH_LENS) ||
|
||||
(type == DISTS && used > ENOUGH_DISTS))
|
||||
return 1;
|
||||
|
||||
/* process all codes and make table entries */
|
||||
for (;;) {
|
||||
/* create table entry */
|
||||
here.bits = (unsigned char)(len - drop);
|
||||
if (work[sym] + 1U < match) {
|
||||
here.op = (unsigned char)0;
|
||||
here.val = work[sym];
|
||||
}
|
||||
else if (work[sym] >= match) {
|
||||
here.op = (unsigned char)(extra[work[sym] - match]);
|
||||
here.val = base[work[sym] - match];
|
||||
}
|
||||
else {
|
||||
here.op = (unsigned char)(32 + 64); /* end of block */
|
||||
here.val = 0;
|
||||
}
|
||||
|
||||
/* replicate for those indices with low len bits equal to huff */
|
||||
incr = 1U << (len - drop);
|
||||
fill = 1U << curr;
|
||||
min = fill; /* save offset to next table */
|
||||
do {
|
||||
fill -= incr;
|
||||
next[(huff >> drop) + fill] = here;
|
||||
} while (fill != 0);
|
||||
|
||||
/* backwards increment the len-bit code huff */
|
||||
incr = 1U << (len - 1);
|
||||
while (huff & incr)
|
||||
incr >>= 1;
|
||||
if (incr != 0) {
|
||||
huff &= incr - 1;
|
||||
huff += incr;
|
||||
}
|
||||
else
|
||||
huff = 0;
|
||||
|
||||
/* go to next symbol, update count, len */
|
||||
sym++;
|
||||
if (--(count[len]) == 0) {
|
||||
if (len == max) break;
|
||||
len = lens[work[sym]];
|
||||
}
|
||||
|
||||
/* create new sub-table if needed */
|
||||
if (len > root && (huff & mask) != low) {
|
||||
/* if first time, transition to sub-tables */
|
||||
if (drop == 0)
|
||||
drop = root;
|
||||
|
||||
/* increment past last table */
|
||||
next += min; /* here min is 1 << curr */
|
||||
|
||||
/* determine length of next table */
|
||||
curr = len - drop;
|
||||
left = (int)(1 << curr);
|
||||
while (curr + drop < max) {
|
||||
left -= count[curr + drop];
|
||||
if (left <= 0) break;
|
||||
curr++;
|
||||
left <<= 1;
|
||||
}
|
||||
|
||||
/* check for enough space */
|
||||
used += 1U << curr;
|
||||
if ((type == LENS && used > ENOUGH_LENS) ||
|
||||
(type == DISTS && used > ENOUGH_DISTS))
|
||||
return 1;
|
||||
|
||||
/* point entry in root table to sub-table */
|
||||
low = huff & mask;
|
||||
(*table)[low].op = (unsigned char)curr;
|
||||
(*table)[low].bits = (unsigned char)root;
|
||||
(*table)[low].val = (unsigned short)(next - *table);
|
||||
}
|
||||
}
|
||||
|
||||
/* fill in remaining table entry if code is incomplete (guaranteed to have
|
||||
at most one remaining entry, since if the code is incomplete, the
|
||||
maximum code length that was allowed to get this far is one bit) */
|
||||
if (huff != 0) {
|
||||
here.op = (unsigned char)64; /* invalid code marker */
|
||||
here.bits = (unsigned char)(len - drop);
|
||||
here.val = (unsigned short)0;
|
||||
next[huff] = here;
|
||||
}
|
||||
|
||||
/* set return parameters */
|
||||
*table += used;
|
||||
*bits = root;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef BUILDFIXED
|
||||
/*
|
||||
If this is compiled with BUILDFIXED defined, and if inflate will be used in
|
||||
multiple threads, and if atomics are not available, then inflate() must be
|
||||
called with a fixed block (e.g. 0x03 0x00) to initialize the tables and must
|
||||
return before any other threads are allowed to call inflate.
|
||||
*/
|
||||
|
||||
static code *lenfix, *distfix;
|
||||
static code fixed[544];
|
||||
|
||||
/* State for z_once(). */
|
||||
local z_once_t built = Z_ONCE_INIT;
|
||||
|
||||
local void buildtables(void) {
|
||||
unsigned sym, bits;
|
||||
static code *next;
|
||||
unsigned short lens[288], work[288];
|
||||
|
||||
/* literal/length table */
|
||||
sym = 0;
|
||||
while (sym < 144) lens[sym++] = 8;
|
||||
while (sym < 256) lens[sym++] = 9;
|
||||
while (sym < 280) lens[sym++] = 7;
|
||||
while (sym < 288) lens[sym++] = 8;
|
||||
next = fixed;
|
||||
lenfix = next;
|
||||
bits = 9;
|
||||
inflate_table(LENS, lens, 288, &(next), &(bits), work);
|
||||
|
||||
/* distance table */
|
||||
sym = 0;
|
||||
while (sym < 32) lens[sym++] = 5;
|
||||
distfix = next;
|
||||
bits = 5;
|
||||
inflate_table(DISTS, lens, 32, &(next), &(bits), work);
|
||||
}
|
||||
#else /* !BUILDFIXED */
|
||||
# include "inffixed.h"
|
||||
#endif /* BUILDFIXED */
|
||||
|
||||
/*
|
||||
Return state with length and distance decoding tables and index sizes set to
|
||||
fixed code decoding. Normally this returns fixed tables from inffixed.h.
|
||||
If BUILDFIXED is defined, then instead this routine builds the tables the
|
||||
first time it's called, and returns those tables the first time and
|
||||
thereafter. This reduces the size of the code by about 2K bytes, in
|
||||
exchange for a little execution time. However, BUILDFIXED should not be
|
||||
used for threaded applications if atomics are not available, as it will
|
||||
not be thread-safe.
|
||||
*/
|
||||
void inflate_fixed(struct inflate_state FAR *state) {
|
||||
#ifdef BUILDFIXED
|
||||
z_once(&built, buildtables);
|
||||
#endif /* BUILDFIXED */
|
||||
state->lencode = lenfix;
|
||||
state->lenbits = 9;
|
||||
state->distcode = distfix;
|
||||
state->distbits = 5;
|
||||
}
|
||||
|
||||
#ifdef MAKEFIXED
|
||||
#include <stdio.h>
|
||||
|
||||
/*
|
||||
Write out the inffixed.h that will be #include'd above. Defining MAKEFIXED
|
||||
also defines BUILDFIXED, so the tables are built on the fly. main() writes
|
||||
those tables to stdout, which would directed to inffixed.h. Compile this
|
||||
along with zutil.c:
|
||||
|
||||
cc -DMAKEFIXED -o fix inftrees.c zutil.c
|
||||
./fix > inffixed.h
|
||||
*/
|
||||
int main(void) {
|
||||
unsigned low, size;
|
||||
struct inflate_state state;
|
||||
|
||||
inflate_fixed(&state);
|
||||
puts("/* inffixed.h -- table for decoding fixed codes");
|
||||
puts(" * Generated automatically by makefixed().");
|
||||
puts(" */");
|
||||
puts("");
|
||||
puts("/* WARNING: this file should *not* be used by applications.");
|
||||
puts(" It is part of the implementation of this library and is");
|
||||
puts(" subject to change. Applications should only use zlib.h.");
|
||||
puts(" */");
|
||||
puts("");
|
||||
size = 1U << 9;
|
||||
printf("static const code lenfix[%u] = {", size);
|
||||
low = 0;
|
||||
for (;;) {
|
||||
if ((low % 7) == 0) printf("\n ");
|
||||
printf("{%u,%u,%d}", (low & 127) == 99 ? 64 : state.lencode[low].op,
|
||||
state.lencode[low].bits, state.lencode[low].val);
|
||||
if (++low == size) break;
|
||||
putchar(',');
|
||||
}
|
||||
puts("\n};");
|
||||
size = 1U << 5;
|
||||
printf("\nstatic const code distfix[%u] = {", size);
|
||||
low = 0;
|
||||
for (;;) {
|
||||
if ((low % 6) == 0) printf("\n ");
|
||||
printf("{%u,%u,%d}", state.distcode[low].op, state.distcode[low].bits,
|
||||
state.distcode[low].val);
|
||||
if (++low == size) break;
|
||||
putchar(',');
|
||||
}
|
||||
puts("\n};");
|
||||
return 0;
|
||||
}
|
||||
#endif /* MAKEFIXED */
|
||||
@@ -0,0 +1,64 @@
|
||||
/* inftrees.h -- header to use inftrees.c
|
||||
* Copyright (C) 1995-2026 Mark Adler
|
||||
* For conditions of distribution and use, see copyright notice in zlib.h
|
||||
*/
|
||||
|
||||
/* WARNING: this file should *not* be used by applications. It is
|
||||
part of the implementation of the compression library and is
|
||||
subject to change. Applications should only use zlib.h.
|
||||
*/
|
||||
|
||||
/* Structure for decoding tables. Each entry provides either the
|
||||
information needed to do the operation requested by the code that
|
||||
indexed that table entry, or it provides a pointer to another
|
||||
table that indexes more bits of the code. op indicates whether
|
||||
the entry is a pointer to another table, a literal, a length or
|
||||
distance, an end-of-block, or an invalid code. For a table
|
||||
pointer, the low four bits of op is the number of index bits of
|
||||
that table. For a length or distance, the low four bits of op
|
||||
is the number of extra bits to get after the code. bits is
|
||||
the number of bits in this code or part of the code to drop off
|
||||
of the bit buffer. val is the actual byte to output in the case
|
||||
of a literal, the base length or distance, or the offset from
|
||||
the current table to the next table. Each entry is four bytes. */
|
||||
typedef struct {
|
||||
unsigned char op; /* operation, extra bits, table bits */
|
||||
unsigned char bits; /* bits in this part of the code */
|
||||
unsigned short val; /* offset in table or code value */
|
||||
} code;
|
||||
|
||||
/* op values as set by inflate_table():
|
||||
00000000 - literal
|
||||
0000tttt - table link, tttt != 0 is the number of table index bits
|
||||
0001eeee - length or distance, eeee is the number of extra bits
|
||||
01100000 - end of block
|
||||
01000000 - invalid code
|
||||
*/
|
||||
|
||||
/* Maximum size of the dynamic table. The maximum number of code structures is
|
||||
1444, which is the sum of 852 for literal/length codes and 592 for distance
|
||||
codes. These values were found by exhaustive searches using the program
|
||||
examples/enough.c found in the zlib distribution. The arguments to that
|
||||
program are the number of symbols, the initial root table size, and the
|
||||
maximum bit length of a code. "enough 286 9 15" for literal/length codes
|
||||
returns 852, and "enough 30 6 15" for distance codes returns 592. The
|
||||
initial root table size (9 or 6) is found in the fifth argument of the
|
||||
inflate_table() calls in inflate.c and infback.c. If the root table size is
|
||||
changed, then these maximum sizes would be need to be recalculated and
|
||||
updated. */
|
||||
#define ENOUGH_LENS 852
|
||||
#define ENOUGH_DISTS 592
|
||||
#define ENOUGH (ENOUGH_LENS+ENOUGH_DISTS)
|
||||
|
||||
/* Type of code to build for inflate_table() */
|
||||
typedef enum {
|
||||
CODES,
|
||||
LENS,
|
||||
DISTS
|
||||
} codetype;
|
||||
|
||||
int ZLIB_INTERNAL inflate_table(codetype type, unsigned short FAR *lens,
|
||||
unsigned codes, code FAR * FAR *table,
|
||||
unsigned FAR *bits, unsigned short FAR *work);
|
||||
struct inflate_state;
|
||||
void ZLIB_INTERNAL inflate_fixed(struct inflate_state FAR *state);
|
||||
@@ -0,0 +1,101 @@
|
||||
/* uncompr.c -- decompress a memory buffer
|
||||
* Copyright (C) 1995-2026 Jean-loup Gailly, Mark Adler
|
||||
* For conditions of distribution and use, see copyright notice in zlib.h
|
||||
*/
|
||||
|
||||
/* @(#) $Id$ */
|
||||
|
||||
#define ZLIB_INTERNAL
|
||||
#include "zlib.h"
|
||||
|
||||
/* ===========================================================================
|
||||
Decompresses the source buffer into the destination buffer. *sourceLen is
|
||||
the byte length of the source buffer. Upon entry, *destLen is the total size
|
||||
of the destination buffer, which must be large enough to hold the entire
|
||||
uncompressed data. (The size of the uncompressed data must have been saved
|
||||
previously by the compressor and transmitted to the decompressor by some
|
||||
mechanism outside the scope of this compression library.) Upon exit,
|
||||
*destLen is the size of the decompressed data and *sourceLen is the number
|
||||
of source bytes consumed. Upon return, source + *sourceLen points to the
|
||||
first unused input byte.
|
||||
|
||||
uncompress returns Z_OK if success, Z_MEM_ERROR if there was not enough
|
||||
memory, Z_BUF_ERROR if there was not enough room in the output buffer, or
|
||||
Z_DATA_ERROR if the input data was corrupted, including if the input data is
|
||||
an incomplete zlib stream.
|
||||
|
||||
The _z versions of the functions take size_t length arguments.
|
||||
*/
|
||||
int ZEXPORT uncompress2_z(Bytef *dest, z_size_t *destLen, const Bytef *source,
|
||||
z_size_t *sourceLen) {
|
||||
z_stream stream;
|
||||
int err;
|
||||
const uInt max = (uInt)-1;
|
||||
z_size_t len, left;
|
||||
|
||||
if (sourceLen == NULL || (*sourceLen > 0 && source == NULL) ||
|
||||
destLen == NULL || (*destLen > 0 && dest == NULL))
|
||||
return Z_STREAM_ERROR;
|
||||
|
||||
len = *sourceLen;
|
||||
left = *destLen;
|
||||
if (left == 0 && dest == Z_NULL)
|
||||
dest = (Bytef *)&stream.reserved; /* next_out cannot be NULL */
|
||||
|
||||
stream.next_in = (z_const Bytef *)source;
|
||||
stream.avail_in = 0;
|
||||
stream.zalloc = (alloc_func)0;
|
||||
stream.zfree = (free_func)0;
|
||||
stream.opaque = (voidpf)0;
|
||||
|
||||
err = inflateInit(&stream);
|
||||
if (err != Z_OK) return err;
|
||||
|
||||
stream.next_out = dest;
|
||||
stream.avail_out = 0;
|
||||
|
||||
do {
|
||||
if (stream.avail_out == 0) {
|
||||
stream.avail_out = left > (z_size_t)max ? max : (uInt)left;
|
||||
left -= stream.avail_out;
|
||||
}
|
||||
if (stream.avail_in == 0) {
|
||||
stream.avail_in = len > (z_size_t)max ? max : (uInt)len;
|
||||
len -= stream.avail_in;
|
||||
}
|
||||
err = inflate(&stream, Z_NO_FLUSH);
|
||||
} while (err == Z_OK);
|
||||
|
||||
/* Set len and left to the unused input data and unused output space. Set
|
||||
*sourceLen to the amount of input consumed. Set *destLen to the amount
|
||||
of data produced. */
|
||||
len += stream.avail_in;
|
||||
left += stream.avail_out;
|
||||
*sourceLen -= len;
|
||||
*destLen -= left;
|
||||
|
||||
inflateEnd(&stream);
|
||||
return err == Z_STREAM_END ? Z_OK :
|
||||
err == Z_NEED_DICT ? Z_DATA_ERROR :
|
||||
err == Z_BUF_ERROR && len == 0 ? Z_DATA_ERROR :
|
||||
err;
|
||||
}
|
||||
int ZEXPORT uncompress2(Bytef *dest, uLongf *destLen, const Bytef *source,
|
||||
uLong *sourceLen) {
|
||||
int ret;
|
||||
z_size_t got = *destLen, used = *sourceLen;
|
||||
ret = uncompress2_z(dest, &got, source, &used);
|
||||
*sourceLen = (uLong)used;
|
||||
*destLen = (uLong)got;
|
||||
return ret;
|
||||
}
|
||||
int ZEXPORT uncompress_z(Bytef *dest, z_size_t *destLen, const Bytef *source,
|
||||
z_size_t sourceLen) {
|
||||
z_size_t used = sourceLen;
|
||||
return uncompress2_z(dest, destLen, source, &used);
|
||||
}
|
||||
int ZEXPORT uncompress(Bytef *dest, uLongf *destLen, const Bytef *source,
|
||||
uLong sourceLen) {
|
||||
uLong used = sourceLen;
|
||||
return uncompress2(dest, destLen, source, &used);
|
||||
}
|
||||
@@ -0,0 +1,551 @@
|
||||
/* zconf.h -- configuration of the zlib compression library
|
||||
* Copyright (C) 1995-2026 Jean-loup Gailly, Mark Adler
|
||||
* For conditions of distribution and use, see copyright notice in zlib.h
|
||||
*/
|
||||
|
||||
/* @(#) $Id$ */
|
||||
|
||||
#ifndef ZCONF_H
|
||||
#define ZCONF_H
|
||||
|
||||
/*
|
||||
* If you *really* need a unique prefix for all types and library functions,
|
||||
* compile with -DZ_PREFIX. The "standard" zlib should be compiled without it.
|
||||
* Even better than compiling with -DZ_PREFIX would be to use configure to set
|
||||
* this permanently in zconf.h using "./configure --zprefix".
|
||||
*/
|
||||
#ifdef Z_PREFIX /* may be set to #if 1 by ./configure */
|
||||
# define Z_PREFIX_SET
|
||||
|
||||
/* all linked symbols and init macros */
|
||||
# define _dist_code z__dist_code
|
||||
# define _length_code z__length_code
|
||||
# define _tr_align z__tr_align
|
||||
# define _tr_flush_bits z__tr_flush_bits
|
||||
# define _tr_flush_block z__tr_flush_block
|
||||
# define _tr_init z__tr_init
|
||||
# define _tr_stored_block z__tr_stored_block
|
||||
# define _tr_tally z__tr_tally
|
||||
# define adler32 z_adler32
|
||||
# define adler32_combine z_adler32_combine
|
||||
# define adler32_combine64 z_adler32_combine64
|
||||
# define adler32_z z_adler32_z
|
||||
# ifndef Z_SOLO
|
||||
# define compress z_compress
|
||||
# define compress2 z_compress2
|
||||
# define compress_z z_compress_z
|
||||
# define compress2_z z_compress2_z
|
||||
# define compressBound z_compressBound
|
||||
# define compressBound_z z_compressBound_z
|
||||
# endif
|
||||
# define crc32 z_crc32
|
||||
# define crc32_combine z_crc32_combine
|
||||
# define crc32_combine64 z_crc32_combine64
|
||||
# define crc32_combine_gen z_crc32_combine_gen
|
||||
# define crc32_combine_gen64 z_crc32_combine_gen64
|
||||
# define crc32_combine_op z_crc32_combine_op
|
||||
# define crc32_z z_crc32_z
|
||||
# define deflate z_deflate
|
||||
# define deflateBound z_deflateBound
|
||||
# define deflateBound_z z_deflateBound_z
|
||||
# define deflateCopy z_deflateCopy
|
||||
# define deflateEnd z_deflateEnd
|
||||
# define deflateGetDictionary z_deflateGetDictionary
|
||||
# define deflateInit z_deflateInit
|
||||
# define deflateInit2 z_deflateInit2
|
||||
# define deflateInit2_ z_deflateInit2_
|
||||
# define deflateInit_ z_deflateInit_
|
||||
# define deflateParams z_deflateParams
|
||||
# define deflatePending z_deflatePending
|
||||
# define deflatePrime z_deflatePrime
|
||||
# define deflateReset z_deflateReset
|
||||
# define deflateResetKeep z_deflateResetKeep
|
||||
# define deflateSetDictionary z_deflateSetDictionary
|
||||
# define deflateSetHeader z_deflateSetHeader
|
||||
# define deflateTune z_deflateTune
|
||||
# define deflateUsed z_deflateUsed
|
||||
# define deflate_copyright z_deflate_copyright
|
||||
# define get_crc_table z_get_crc_table
|
||||
# ifndef Z_SOLO
|
||||
# define gz_error z_gz_error
|
||||
# define gz_intmax z_gz_intmax
|
||||
# define gz_strwinerror z_gz_strwinerror
|
||||
# define gzbuffer z_gzbuffer
|
||||
# define gzclearerr z_gzclearerr
|
||||
# define gzclose z_gzclose
|
||||
# define gzclose_r z_gzclose_r
|
||||
# define gzclose_w z_gzclose_w
|
||||
# define gzdirect z_gzdirect
|
||||
# define gzdopen z_gzdopen
|
||||
# define gzeof z_gzeof
|
||||
# define gzerror z_gzerror
|
||||
# define gzflush z_gzflush
|
||||
# define gzfread z_gzfread
|
||||
# define gzfwrite z_gzfwrite
|
||||
# define gzgetc z_gzgetc
|
||||
# define gzgetc_ z_gzgetc_
|
||||
# define gzgets z_gzgets
|
||||
# define gzoffset z_gzoffset
|
||||
# define gzoffset64 z_gzoffset64
|
||||
# define gzopen z_gzopen
|
||||
# define gzopen64 z_gzopen64
|
||||
# ifdef _WIN32
|
||||
# define gzopen_w z_gzopen_w
|
||||
# endif
|
||||
# define gzprintf z_gzprintf
|
||||
# define gzputc z_gzputc
|
||||
# define gzputs z_gzputs
|
||||
# define gzread z_gzread
|
||||
# define gzrewind z_gzrewind
|
||||
# define gzseek z_gzseek
|
||||
# define gzseek64 z_gzseek64
|
||||
# define gzsetparams z_gzsetparams
|
||||
# define gztell z_gztell
|
||||
# define gztell64 z_gztell64
|
||||
# define gzungetc z_gzungetc
|
||||
# define gzvprintf z_gzvprintf
|
||||
# define gzwrite z_gzwrite
|
||||
# endif
|
||||
# define inflate z_inflate
|
||||
# define inflateBack z_inflateBack
|
||||
# define inflateBackEnd z_inflateBackEnd
|
||||
# define inflateBackInit z_inflateBackInit
|
||||
# define inflateBackInit_ z_inflateBackInit_
|
||||
# define inflateCodesUsed z_inflateCodesUsed
|
||||
# define inflateCopy z_inflateCopy
|
||||
# define inflateEnd z_inflateEnd
|
||||
# define inflateGetDictionary z_inflateGetDictionary
|
||||
# define inflateGetHeader z_inflateGetHeader
|
||||
# define inflateInit z_inflateInit
|
||||
# define inflateInit2 z_inflateInit2
|
||||
# define inflateInit2_ z_inflateInit2_
|
||||
# define inflateInit_ z_inflateInit_
|
||||
# define inflateMark z_inflateMark
|
||||
# define inflatePrime z_inflatePrime
|
||||
# define inflateReset z_inflateReset
|
||||
# define inflateReset2 z_inflateReset2
|
||||
# define inflateResetKeep z_inflateResetKeep
|
||||
# define inflateSetDictionary z_inflateSetDictionary
|
||||
# define inflateSync z_inflateSync
|
||||
# define inflateSyncPoint z_inflateSyncPoint
|
||||
# define inflateUndermine z_inflateUndermine
|
||||
# define inflateValidate z_inflateValidate
|
||||
# define inflate_copyright z_inflate_copyright
|
||||
# define inflate_fast z_inflate_fast
|
||||
# define inflate_table z_inflate_table
|
||||
# define inflate_fixed z_inflate_fixed
|
||||
# ifndef Z_SOLO
|
||||
# define uncompress z_uncompress
|
||||
# define uncompress2 z_uncompress2
|
||||
# define uncompress_z z_uncompress_z
|
||||
# define uncompress2_z z_uncompress2_z
|
||||
# endif
|
||||
# define zError z_zError
|
||||
# ifndef Z_SOLO
|
||||
# define zcalloc z_zcalloc
|
||||
# define zcfree z_zcfree
|
||||
# endif
|
||||
# define zlibCompileFlags z_zlibCompileFlags
|
||||
# define zlibVersion z_zlibVersion
|
||||
|
||||
/* all zlib typedefs in zlib.h and zconf.h */
|
||||
# define Byte z_Byte
|
||||
# define Bytef z_Bytef
|
||||
# define alloc_func z_alloc_func
|
||||
# define charf z_charf
|
||||
# define free_func z_free_func
|
||||
# ifndef Z_SOLO
|
||||
# define gzFile z_gzFile
|
||||
# endif
|
||||
# define gz_header z_gz_header
|
||||
# define gz_headerp z_gz_headerp
|
||||
# define in_func z_in_func
|
||||
# define intf z_intf
|
||||
# define out_func z_out_func
|
||||
# define uInt z_uInt
|
||||
# define uIntf z_uIntf
|
||||
# define uLong z_uLong
|
||||
# define uLongf z_uLongf
|
||||
# define voidp z_voidp
|
||||
# define voidpc z_voidpc
|
||||
# define voidpf z_voidpf
|
||||
|
||||
/* all zlib structs in zlib.h and zconf.h */
|
||||
# define gz_header_s z_gz_header_s
|
||||
# define internal_state z_internal_state
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(__MSDOS__) && !defined(MSDOS)
|
||||
# define MSDOS
|
||||
#endif
|
||||
#if (defined(OS_2) || defined(__OS2__)) && !defined(OS2)
|
||||
# define OS2
|
||||
#endif
|
||||
#if defined(_WINDOWS) && !defined(WINDOWS)
|
||||
# define WINDOWS
|
||||
#endif
|
||||
#if defined(_WIN32) || defined(_WIN32_WCE) || defined(__WIN32__)
|
||||
# ifndef WIN32
|
||||
# define WIN32
|
||||
# endif
|
||||
#endif
|
||||
#if (defined(MSDOS) || defined(OS2) || defined(WINDOWS)) && !defined(WIN32)
|
||||
# if !defined(__GNUC__) && !defined(__FLAT__) && !defined(__386__)
|
||||
# ifndef SYS16BIT
|
||||
# define SYS16BIT
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Compile with -DMAXSEG_64K if the alloc function cannot allocate more
|
||||
* than 64k bytes at a time (needed on systems with 16-bit int).
|
||||
*/
|
||||
#ifdef SYS16BIT
|
||||
# define MAXSEG_64K
|
||||
#endif
|
||||
#ifdef MSDOS
|
||||
# define UNALIGNED_OK
|
||||
#endif
|
||||
|
||||
#ifdef __STDC_VERSION__
|
||||
# ifndef STDC
|
||||
# define STDC
|
||||
# endif
|
||||
# if __STDC_VERSION__ >= 199901L
|
||||
# ifndef STDC99
|
||||
# define STDC99
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
#if !defined(STDC) && (defined(__STDC__) || defined(__cplusplus))
|
||||
# define STDC
|
||||
#endif
|
||||
#if !defined(STDC) && (defined(__GNUC__) || defined(__BORLANDC__))
|
||||
# define STDC
|
||||
#endif
|
||||
#if !defined(STDC) && (defined(MSDOS) || defined(WINDOWS) || defined(WIN32))
|
||||
# define STDC
|
||||
#endif
|
||||
#if !defined(STDC) && (defined(OS2) || defined(__HOS_AIX__))
|
||||
# define STDC
|
||||
#endif
|
||||
|
||||
#if defined(__OS400__) && !defined(STDC) /* iSeries (formerly AS/400). */
|
||||
# define STDC
|
||||
#endif
|
||||
|
||||
#ifndef STDC
|
||||
# ifndef const /* cannot use !defined(STDC) && !defined(const) on Mac */
|
||||
# define const /* note: need a more gentle solution here */
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifndef z_const
|
||||
# ifdef ZLIB_CONST
|
||||
# define z_const const
|
||||
# else
|
||||
# define z_const
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef Z_SOLO
|
||||
# ifdef _WIN64
|
||||
typedef unsigned long long z_size_t;
|
||||
# else
|
||||
typedef unsigned long z_size_t;
|
||||
# endif
|
||||
#else
|
||||
# define z_longlong long long
|
||||
# if defined(NO_SIZE_T)
|
||||
typedef unsigned NO_SIZE_T z_size_t;
|
||||
# elif defined(STDC)
|
||||
# include <stddef.h>
|
||||
typedef size_t z_size_t;
|
||||
# else
|
||||
typedef unsigned long z_size_t;
|
||||
# endif
|
||||
# undef z_longlong
|
||||
#endif
|
||||
|
||||
/* Maximum value for memLevel in deflateInit2 */
|
||||
#ifndef MAX_MEM_LEVEL
|
||||
# ifdef MAXSEG_64K
|
||||
# define MAX_MEM_LEVEL 8
|
||||
# else
|
||||
# define MAX_MEM_LEVEL 9
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/* Maximum value for windowBits in deflateInit2 and inflateInit2.
|
||||
* WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files
|
||||
* created by gzip. (Files created by minigzip can still be extracted by
|
||||
* gzip.)
|
||||
*/
|
||||
#ifndef MAX_WBITS
|
||||
# define MAX_WBITS 15 /* 32K LZ77 window */
|
||||
#endif
|
||||
|
||||
/* The memory requirements for deflate are (in bytes):
|
||||
(1 << (windowBits+2)) + (1 << (memLevel+9))
|
||||
that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values)
|
||||
plus a few kilobytes for small objects. For example, if you want to reduce
|
||||
the default memory requirements from 256K to 128K, compile with
|
||||
make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7"
|
||||
Of course this will generally degrade compression (there's no free lunch).
|
||||
|
||||
The memory requirements for inflate are (in bytes) 1 << windowBits
|
||||
that is, 32K for windowBits=15 (default value) plus about 7 kilobytes
|
||||
for small objects.
|
||||
*/
|
||||
|
||||
/* Type declarations */
|
||||
|
||||
#ifndef OF /* function prototypes */
|
||||
# ifdef STDC
|
||||
# define OF(args) args
|
||||
# else
|
||||
# define OF(args) ()
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/* The following definitions for FAR are needed only for MSDOS mixed
|
||||
* model programming (small or medium model with some far allocations).
|
||||
* This was tested only with MSC; for other MSDOS compilers you may have
|
||||
* to define NO_MEMCPY in zutil.h. If you don't need the mixed model,
|
||||
* just define FAR to be empty.
|
||||
*/
|
||||
#ifdef SYS16BIT
|
||||
# if defined(M_I86SM) || defined(M_I86MM)
|
||||
/* MSC small or medium model */
|
||||
# define SMALL_MEDIUM
|
||||
# ifdef _MSC_VER
|
||||
# define FAR _far
|
||||
# else
|
||||
# define FAR far
|
||||
# endif
|
||||
# endif
|
||||
# if (defined(__SMALL__) || defined(__MEDIUM__))
|
||||
/* Turbo C small or medium model */
|
||||
# define SMALL_MEDIUM
|
||||
# ifdef __BORLANDC__
|
||||
# define FAR _far
|
||||
# else
|
||||
# define FAR far
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if defined(WINDOWS) || defined(WIN32)
|
||||
/* If building or using zlib as a DLL, define ZLIB_DLL.
|
||||
* This is not mandatory, but it offers a little performance increase.
|
||||
*/
|
||||
# ifdef ZLIB_DLL
|
||||
# if defined(WIN32) && (!defined(__BORLANDC__) || (__BORLANDC__ >= 0x500))
|
||||
# ifdef ZLIB_INTERNAL
|
||||
# define ZEXTERN extern __declspec(dllexport)
|
||||
# else
|
||||
# define ZEXTERN extern __declspec(dllimport)
|
||||
# endif
|
||||
# endif
|
||||
# endif /* ZLIB_DLL */
|
||||
/* If building or using zlib with the WINAPI/WINAPIV calling convention,
|
||||
* define ZLIB_WINAPI.
|
||||
* Caution: the standard ZLIB1.DLL is NOT compiled using ZLIB_WINAPI.
|
||||
*/
|
||||
# ifdef ZLIB_WINAPI
|
||||
# ifdef FAR
|
||||
# undef FAR
|
||||
# endif
|
||||
# ifndef WIN32_LEAN_AND_MEAN
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# endif
|
||||
# include <windows.h>
|
||||
/* No need for _export, use ZLIB.DEF instead. */
|
||||
/* For complete Windows compatibility, use WINAPI, not __stdcall. */
|
||||
# define ZEXPORT WINAPI
|
||||
# ifdef WIN32
|
||||
# define ZEXPORTVA WINAPIV
|
||||
# else
|
||||
# define ZEXPORTVA FAR CDECL
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if defined (__BEOS__)
|
||||
# ifdef ZLIB_DLL
|
||||
# ifdef ZLIB_INTERNAL
|
||||
# define ZEXPORT __declspec(dllexport)
|
||||
# define ZEXPORTVA __declspec(dllexport)
|
||||
# else
|
||||
# define ZEXPORT __declspec(dllimport)
|
||||
# define ZEXPORTVA __declspec(dllimport)
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifndef ZEXTERN
|
||||
# define ZEXTERN extern
|
||||
#endif
|
||||
#ifndef ZEXPORT
|
||||
# define ZEXPORT
|
||||
#endif
|
||||
#ifndef ZEXPORTVA
|
||||
# define ZEXPORTVA
|
||||
#endif
|
||||
|
||||
#ifndef FAR
|
||||
# define FAR
|
||||
#endif
|
||||
|
||||
#if !defined(__MACTYPES__)
|
||||
typedef unsigned char Byte; /* 8 bits */
|
||||
#endif
|
||||
typedef unsigned int uInt; /* 16 bits or more */
|
||||
typedef unsigned long uLong; /* 32 bits or more */
|
||||
|
||||
#ifdef SMALL_MEDIUM
|
||||
/* Borland C/C++ and some old MSC versions ignore FAR inside typedef */
|
||||
# define Bytef Byte FAR
|
||||
#else
|
||||
typedef Byte FAR Bytef;
|
||||
#endif
|
||||
typedef char FAR charf;
|
||||
typedef int FAR intf;
|
||||
typedef uInt FAR uIntf;
|
||||
typedef uLong FAR uLongf;
|
||||
|
||||
#ifdef STDC
|
||||
typedef void const *voidpc;
|
||||
typedef void FAR *voidpf;
|
||||
typedef void *voidp;
|
||||
#else
|
||||
typedef Byte const *voidpc;
|
||||
typedef Byte FAR *voidpf;
|
||||
typedef Byte *voidp;
|
||||
#endif
|
||||
|
||||
#if !defined(Z_U4) && !defined(Z_SOLO) && defined(STDC)
|
||||
# include <limits.h>
|
||||
# if (UINT_MAX == 0xffffffffUL)
|
||||
# define Z_U4 unsigned
|
||||
# elif (ULONG_MAX == 0xffffffffUL)
|
||||
# define Z_U4 unsigned long
|
||||
# elif (USHRT_MAX == 0xffffffffUL)
|
||||
# define Z_U4 unsigned short
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef Z_U4
|
||||
typedef Z_U4 z_crc_t;
|
||||
#else
|
||||
typedef unsigned long z_crc_t;
|
||||
#endif
|
||||
|
||||
#if HAVE_UNISTD_H-0 /* may be set to #if 1 by ./configure */
|
||||
# define Z_HAVE_UNISTD_H
|
||||
#endif
|
||||
|
||||
#if HAVE_STDARG_H-0 /* may be set to #if 1 by ./configure */
|
||||
# define Z_HAVE_STDARG_H
|
||||
#endif
|
||||
|
||||
#ifdef STDC
|
||||
# ifndef Z_SOLO
|
||||
# include <sys/types.h> /* for off_t */
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if defined(STDC) || defined(Z_HAVE_STDARG_H)
|
||||
# ifndef Z_SOLO
|
||||
# include <stdarg.h> /* for va_list */
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
# ifndef Z_SOLO
|
||||
# include <stddef.h> /* for wchar_t */
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/* a little trick to accommodate both "#define _LARGEFILE64_SOURCE" and
|
||||
* "#define _LARGEFILE64_SOURCE 1" as requesting 64-bit operations, (even
|
||||
* though the former does not conform to the LFS document), but considering
|
||||
* both "#undef _LARGEFILE64_SOURCE" and "#define _LARGEFILE64_SOURCE 0" as
|
||||
* equivalently requesting no 64-bit operations
|
||||
*/
|
||||
#if defined(_LARGEFILE64_SOURCE) && -_LARGEFILE64_SOURCE - -1 == 1
|
||||
# undef _LARGEFILE64_SOURCE
|
||||
#endif
|
||||
|
||||
#ifndef Z_HAVE_UNISTD_H
|
||||
# if defined(__WATCOMC__) || defined(__GO32__) || \
|
||||
(defined(_LARGEFILE64_SOURCE) && !defined(_WIN32))
|
||||
# define Z_HAVE_UNISTD_H
|
||||
# endif
|
||||
#endif
|
||||
#ifndef Z_SOLO
|
||||
# if defined(Z_HAVE_UNISTD_H)
|
||||
# include <unistd.h> /* for SEEK_*, off_t, and _LFS64_LARGEFILE */
|
||||
# ifdef VMS
|
||||
# include <unixio.h> /* for off_t */
|
||||
# endif
|
||||
# ifndef z_off_t
|
||||
# define z_off_t off_t
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if defined(_LFS64_LARGEFILE) && _LFS64_LARGEFILE-0
|
||||
# define Z_LFS64
|
||||
#endif
|
||||
|
||||
#if defined(_LARGEFILE64_SOURCE) && defined(Z_LFS64)
|
||||
# define Z_LARGE64
|
||||
#endif
|
||||
|
||||
#if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS-0 == 64 && defined(Z_LFS64)
|
||||
# define Z_WANT64
|
||||
#endif
|
||||
|
||||
#if !defined(SEEK_SET) && !defined(Z_SOLO)
|
||||
# define SEEK_SET 0 /* Seek from beginning of file. */
|
||||
# define SEEK_CUR 1 /* Seek from current position. */
|
||||
# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */
|
||||
#endif
|
||||
|
||||
#ifndef z_off_t
|
||||
# define z_off_t long long
|
||||
#endif
|
||||
|
||||
#if !defined(_WIN32) && defined(Z_LARGE64)
|
||||
# define z_off64_t off64_t
|
||||
#elif defined(__MINGW32__)
|
||||
# define z_off64_t long long
|
||||
#elif defined(_WIN32) && !defined(__GNUC__)
|
||||
# define z_off64_t __int64
|
||||
#elif defined(__GO32__)
|
||||
# define z_off64_t offset_t
|
||||
#else
|
||||
# define z_off64_t z_off_t
|
||||
#endif
|
||||
|
||||
/* MVS linker does not support external names larger than 8 bytes */
|
||||
#if defined(__MVS__)
|
||||
#pragma map(deflateInit_,"DEIN")
|
||||
#pragma map(deflateInit2_,"DEIN2")
|
||||
#pragma map(deflateEnd,"DEEND")
|
||||
#pragma map(deflateBound,"DEBND")
|
||||
#pragma map(inflateInit_,"ININ")
|
||||
#pragma map(inflateInit2_,"ININ2")
|
||||
#pragma map(inflateEnd,"INEND")
|
||||
#pragma map(inflateSync,"INSY")
|
||||
#pragma map(inflateSetDictionary,"INSEDI")
|
||||
#pragma map(compressBound,"CMBND")
|
||||
#pragma map(inflate_table,"INTABL")
|
||||
#pragma map(inflate_fast,"INFA")
|
||||
#pragma map(inflate_copyright,"INCOPY")
|
||||
#endif
|
||||
|
||||
#endif /* ZCONF_H */
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
ZLIB_PREFIXED.H
|
||||
|
||||
The port's zlib (1.3.2, its inflate only: what the port inflates is other
|
||||
people's data, the maps, the menus' and the HUD's PNGs, updates), beside
|
||||
the game's own (source/memory/zlib, 1.1.3): its names begin z_, so the two
|
||||
never meet. Include this, not zlib.h, so that the names always match.
|
||||
|
||||
Taken unchanged from the release (zlib.net, signed by Mark Adler): adler32.c,
|
||||
crc32.c, crc32.h, gzguts.h, inffast.c, inffast.h, inffixed.h, inflate.c,
|
||||
inflate.h, inftrees.c, inftrees.h, uncompr.c, zconf.h, zlib.h, zutil.c,
|
||||
zutil.h, LICENSE and README. To update, copy those from a newer release.
|
||||
*/
|
||||
|
||||
#ifndef ZLIB_PREFIXED_H
|
||||
#define ZLIB_PREFIXED_H
|
||||
|
||||
#ifndef Z_PREFIX
|
||||
#define Z_PREFIX
|
||||
#endif
|
||||
#include "zlib.h"
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,313 @@
|
||||
/* zutil.c -- target dependent utility functions for the compression library
|
||||
* Copyright (C) 1995-2026 Jean-loup Gailly
|
||||
* For conditions of distribution and use, see copyright notice in zlib.h
|
||||
*/
|
||||
|
||||
/* @(#) $Id$ */
|
||||
|
||||
#include "zutil.h"
|
||||
#ifndef Z_SOLO
|
||||
# include "gzguts.h"
|
||||
#endif
|
||||
|
||||
z_const char * const z_errmsg[10] = {
|
||||
(z_const char *)"need dictionary", /* Z_NEED_DICT 2 */
|
||||
(z_const char *)"stream end", /* Z_STREAM_END 1 */
|
||||
(z_const char *)"", /* Z_OK 0 */
|
||||
(z_const char *)"file error", /* Z_ERRNO (-1) */
|
||||
(z_const char *)"stream error", /* Z_STREAM_ERROR (-2) */
|
||||
(z_const char *)"data error", /* Z_DATA_ERROR (-3) */
|
||||
(z_const char *)"insufficient memory", /* Z_MEM_ERROR (-4) */
|
||||
(z_const char *)"buffer error", /* Z_BUF_ERROR (-5) */
|
||||
(z_const char *)"incompatible version",/* Z_VERSION_ERROR (-6) */
|
||||
(z_const char *)""
|
||||
};
|
||||
|
||||
|
||||
const char * ZEXPORT zlibVersion(void) {
|
||||
return ZLIB_VERSION;
|
||||
}
|
||||
|
||||
uLong ZEXPORT zlibCompileFlags(void) {
|
||||
uLong flags;
|
||||
|
||||
flags = 0;
|
||||
switch ((int)(sizeof(uInt))) {
|
||||
case 2: break;
|
||||
case 4: flags += 1; break;
|
||||
case 8: flags += 2; break;
|
||||
default: flags += 3;
|
||||
}
|
||||
switch ((int)(sizeof(uLong))) {
|
||||
case 2: break;
|
||||
case 4: flags += 1 << 2; break;
|
||||
case 8: flags += 2 << 2; break;
|
||||
default: flags += 3 << 2;
|
||||
}
|
||||
switch ((int)(sizeof(voidpf))) {
|
||||
case 2: break;
|
||||
case 4: flags += 1 << 4; break;
|
||||
case 8: flags += 2 << 4; break;
|
||||
default: flags += 3 << 4;
|
||||
}
|
||||
switch ((int)(sizeof(z_off_t))) {
|
||||
case 2: break;
|
||||
case 4: flags += 1 << 6; break;
|
||||
case 8: flags += 2 << 6; break;
|
||||
default: flags += 3 << 6;
|
||||
}
|
||||
#ifdef ZLIB_DEBUG
|
||||
flags += 1 << 8;
|
||||
#endif
|
||||
/*
|
||||
#if defined(ASMV) || defined(ASMINF)
|
||||
flags += 1 << 9;
|
||||
#endif
|
||||
*/
|
||||
#ifdef ZLIB_WINAPI
|
||||
flags += 1 << 10;
|
||||
#endif
|
||||
#ifdef BUILDFIXED
|
||||
flags += 1 << 12;
|
||||
#endif
|
||||
#ifdef DYNAMIC_CRC_TABLE
|
||||
flags += 1 << 13;
|
||||
#endif
|
||||
#ifdef NO_GZCOMPRESS
|
||||
flags += 1L << 16;
|
||||
#endif
|
||||
#ifdef NO_GZIP
|
||||
flags += 1L << 17;
|
||||
#endif
|
||||
#ifdef PKZIP_BUG_WORKAROUND
|
||||
flags += 1L << 20;
|
||||
#endif
|
||||
#ifdef FASTEST
|
||||
flags += 1L << 21;
|
||||
#endif
|
||||
#if defined(STDC) || defined(Z_HAVE_STDARG_H)
|
||||
# ifdef NO_vsnprintf
|
||||
# ifdef ZLIB_INSECURE
|
||||
flags += 1L << 25;
|
||||
# else
|
||||
flags += 1L << 27;
|
||||
# endif
|
||||
# ifdef HAS_vsprintf_void
|
||||
flags += 1L << 26;
|
||||
# endif
|
||||
# else
|
||||
# ifdef HAS_vsnprintf_void
|
||||
flags += 1L << 26;
|
||||
# endif
|
||||
# endif
|
||||
#else
|
||||
flags += 1L << 24;
|
||||
# ifdef NO_snprintf
|
||||
# ifdef ZLIB_INSECURE
|
||||
flags += 1L << 25;
|
||||
# else
|
||||
flags += 1L << 27;
|
||||
# endif
|
||||
# ifdef HAS_sprintf_void
|
||||
flags += 1L << 26;
|
||||
# endif
|
||||
# else
|
||||
# ifdef HAS_snprintf_void
|
||||
flags += 1L << 26;
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
return flags;
|
||||
}
|
||||
|
||||
#ifdef ZLIB_DEBUG
|
||||
#include <stdlib.h>
|
||||
# ifndef verbose
|
||||
# define verbose 0
|
||||
# endif
|
||||
int ZLIB_INTERNAL z_verbose = verbose;
|
||||
|
||||
void ZLIB_INTERNAL z_error(char *m) {
|
||||
fprintf(stderr, "%s\n", m);
|
||||
exit(1);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* exported to allow conversion of error code to string for compress() and
|
||||
* uncompress()
|
||||
*/
|
||||
const char * ZEXPORT zError(int err) {
|
||||
return ERR_MSG(err);
|
||||
}
|
||||
|
||||
#if defined(_WIN32_WCE) && _WIN32_WCE < 0x800
|
||||
/* The older Microsoft C Run-Time Library for Windows CE doesn't have
|
||||
* errno. We define it as a global variable to simplify porting.
|
||||
* Its value is always 0 and should not be used.
|
||||
*/
|
||||
int errno = 0;
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_MEMCPY
|
||||
|
||||
void ZLIB_INTERNAL zmemcpy(void FAR *dst, const void FAR *src, z_size_t n) {
|
||||
uchf *p = dst;
|
||||
const uchf *q = src;
|
||||
while (n) {
|
||||
*p++ = *q++;
|
||||
n--;
|
||||
}
|
||||
}
|
||||
|
||||
int ZLIB_INTERNAL zmemcmp(const void FAR *s1, const void FAR *s2, z_size_t n) {
|
||||
const uchf *p = s1, *q = s2;
|
||||
while (n) {
|
||||
if (*p++ != *q++)
|
||||
return (int)p[-1] - (int)q[-1];
|
||||
n--;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ZLIB_INTERNAL zmemzero(void FAR *b, z_size_t len) {
|
||||
uchf *p = b;
|
||||
if (len == 0) return;
|
||||
while (len) {
|
||||
*p++ = 0;
|
||||
len--;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef Z_SOLO
|
||||
|
||||
#ifdef SYS16BIT
|
||||
|
||||
#ifdef __TURBOC__
|
||||
/* Turbo C in 16-bit mode */
|
||||
|
||||
# define MY_ZCALLOC
|
||||
|
||||
/* Turbo C malloc() does not allow dynamic allocation of 64K bytes
|
||||
* and farmalloc(64K) returns a pointer with an offset of 8, so we
|
||||
* must fix the pointer. Warning: the pointer must be put back to its
|
||||
* original form in order to free it, use zcfree().
|
||||
*/
|
||||
|
||||
#define MAX_PTR 10
|
||||
/* 10*64K = 640K */
|
||||
|
||||
local int next_ptr = 0;
|
||||
|
||||
typedef struct ptr_table_s {
|
||||
voidpf org_ptr;
|
||||
voidpf new_ptr;
|
||||
} ptr_table;
|
||||
|
||||
local ptr_table table[MAX_PTR];
|
||||
/* This table is used to remember the original form of pointers
|
||||
* to large buffers (64K). Such pointers are normalized with a zero offset.
|
||||
* Since MSDOS is not a preemptive multitasking OS, this table is not
|
||||
* protected from concurrent access. This hack doesn't work anyway on
|
||||
* a protected system like OS/2. Use Microsoft C instead.
|
||||
*/
|
||||
|
||||
voidpf ZLIB_INTERNAL zcalloc(voidpf opaque, unsigned items, unsigned size) {
|
||||
voidpf buf;
|
||||
ulg bsize = (ulg)items*size;
|
||||
|
||||
(void)opaque;
|
||||
|
||||
/* If we allocate less than 65520 bytes, we assume that farmalloc
|
||||
* will return a usable pointer which doesn't have to be normalized.
|
||||
*/
|
||||
if (bsize < 65520L) {
|
||||
buf = farmalloc(bsize);
|
||||
if (*(ush*)&buf != 0) return buf;
|
||||
} else {
|
||||
buf = farmalloc(bsize + 16L);
|
||||
}
|
||||
if (buf == NULL || next_ptr >= MAX_PTR) return NULL;
|
||||
table[next_ptr].org_ptr = buf;
|
||||
|
||||
/* Normalize the pointer to seg:0 */
|
||||
*((ush*)&buf+1) += ((ush)((uch*)buf-0) + 15) >> 4;
|
||||
*(ush*)&buf = 0;
|
||||
table[next_ptr++].new_ptr = buf;
|
||||
return buf;
|
||||
}
|
||||
|
||||
void ZLIB_INTERNAL zcfree(voidpf opaque, voidpf ptr) {
|
||||
int n;
|
||||
|
||||
(void)opaque;
|
||||
|
||||
if (*(ush*)&ptr != 0) { /* object < 64K */
|
||||
farfree(ptr);
|
||||
return;
|
||||
}
|
||||
/* Find the original pointer */
|
||||
for (n = 0; n < next_ptr; n++) {
|
||||
if (ptr != table[n].new_ptr) continue;
|
||||
|
||||
farfree(table[n].org_ptr);
|
||||
while (++n < next_ptr) {
|
||||
table[n-1] = table[n];
|
||||
}
|
||||
next_ptr--;
|
||||
return;
|
||||
}
|
||||
Assert(0, "zcfree: ptr not found");
|
||||
}
|
||||
|
||||
#endif /* __TURBOC__ */
|
||||
|
||||
|
||||
#ifdef M_I86
|
||||
/* Microsoft C in 16-bit mode */
|
||||
|
||||
# define MY_ZCALLOC
|
||||
|
||||
#if (!defined(_MSC_VER) || (_MSC_VER <= 600))
|
||||
# define _halloc halloc
|
||||
# define _hfree hfree
|
||||
#endif
|
||||
|
||||
voidpf ZLIB_INTERNAL zcalloc(voidpf opaque, uInt items, uInt size) {
|
||||
(void)opaque;
|
||||
return _halloc((long)items, size);
|
||||
}
|
||||
|
||||
void ZLIB_INTERNAL zcfree(voidpf opaque, voidpf ptr) {
|
||||
(void)opaque;
|
||||
_hfree(ptr);
|
||||
}
|
||||
|
||||
#endif /* M_I86 */
|
||||
|
||||
#endif /* SYS16BIT */
|
||||
|
||||
|
||||
#ifndef MY_ZCALLOC /* Any system without a special alloc function */
|
||||
|
||||
#ifndef STDC
|
||||
extern voidp malloc(uInt size);
|
||||
extern voidp calloc(uInt items, uInt size);
|
||||
extern void free(voidpf ptr);
|
||||
#endif
|
||||
|
||||
voidpf ZLIB_INTERNAL zcalloc(voidpf opaque, unsigned items, unsigned size) {
|
||||
(void)opaque;
|
||||
return sizeof(uInt) > 2 ? (voidpf)malloc(items * size) :
|
||||
(voidpf)calloc(items, size);
|
||||
}
|
||||
|
||||
void ZLIB_INTERNAL zcfree(voidpf opaque, voidpf ptr) {
|
||||
(void)opaque;
|
||||
free(ptr);
|
||||
}
|
||||
|
||||
#endif /* MY_ZCALLOC */
|
||||
|
||||
#endif /* !Z_SOLO */
|
||||
@@ -0,0 +1,331 @@
|
||||
/* zutil.h -- internal interface and configuration of the compression library
|
||||
* Copyright (C) 1995-2026 Jean-loup Gailly, Mark Adler
|
||||
* For conditions of distribution and use, see copyright notice in zlib.h
|
||||
*/
|
||||
|
||||
/* WARNING: this file should *not* be used by applications. It is
|
||||
part of the implementation of the compression library and is
|
||||
subject to change. Applications should only use zlib.h.
|
||||
*/
|
||||
|
||||
/* @(#) $Id$ */
|
||||
|
||||
#ifndef ZUTIL_H
|
||||
#define ZUTIL_H
|
||||
|
||||
#ifdef HAVE_HIDDEN
|
||||
# define ZLIB_INTERNAL __attribute__((visibility ("hidden")))
|
||||
#else
|
||||
# define ZLIB_INTERNAL
|
||||
#endif
|
||||
|
||||
#include "zlib.h"
|
||||
|
||||
#if defined(STDC) && !defined(Z_SOLO)
|
||||
# if !(defined(_WIN32_WCE) && defined(_MSC_VER))
|
||||
# include <stddef.h>
|
||||
# endif
|
||||
# include <string.h>
|
||||
# include <stdlib.h>
|
||||
#endif
|
||||
|
||||
#ifndef local
|
||||
# define local static
|
||||
#endif
|
||||
/* since "static" is used to mean two completely different things in C, we
|
||||
define "local" for the non-static meaning of "static", for readability
|
||||
(compile with -Dlocal if your debugger can't find static symbols) */
|
||||
|
||||
extern const char deflate_copyright[];
|
||||
extern const char inflate_copyright[];
|
||||
extern const char inflate9_copyright[];
|
||||
|
||||
typedef unsigned char uch;
|
||||
typedef uch FAR uchf;
|
||||
typedef unsigned short ush;
|
||||
typedef ush FAR ushf;
|
||||
typedef unsigned long ulg;
|
||||
|
||||
#if !defined(Z_U8) && !defined(Z_SOLO) && defined(STDC)
|
||||
# include <limits.h>
|
||||
# if (ULONG_MAX == 0xffffffffffffffff)
|
||||
# define Z_U8 unsigned long
|
||||
# elif (ULLONG_MAX == 0xffffffffffffffff)
|
||||
# define Z_U8 unsigned long long
|
||||
# elif (ULONG_LONG_MAX == 0xffffffffffffffff)
|
||||
# define Z_U8 unsigned long long
|
||||
# elif (UINT_MAX == 0xffffffffffffffff)
|
||||
# define Z_U8 unsigned
|
||||
# endif
|
||||
#endif
|
||||
|
||||
extern z_const char * const z_errmsg[10]; /* indexed by 2-zlib_error */
|
||||
/* (size given to avoid silly warnings with Visual C++) */
|
||||
|
||||
#define ERR_MSG(err) z_errmsg[(err) < -6 || (err) > 2 ? 9 : 2 - (err)]
|
||||
|
||||
#define ERR_RETURN(strm,err) \
|
||||
return (strm->msg = ERR_MSG(err), (err))
|
||||
/* To be used only when the state is known to be valid */
|
||||
|
||||
/* common constants */
|
||||
#if MAX_WBITS < 9 || MAX_WBITS > 15
|
||||
# error MAX_WBITS must be in 9..15
|
||||
#endif
|
||||
#ifndef DEF_WBITS
|
||||
# define DEF_WBITS MAX_WBITS
|
||||
#endif
|
||||
/* default windowBits for decompression. MAX_WBITS is for compression only */
|
||||
|
||||
#if MAX_MEM_LEVEL >= 8
|
||||
# define DEF_MEM_LEVEL 8
|
||||
#else
|
||||
# define DEF_MEM_LEVEL MAX_MEM_LEVEL
|
||||
#endif
|
||||
/* default memLevel */
|
||||
|
||||
#define STORED_BLOCK 0
|
||||
#define STATIC_TREES 1
|
||||
#define DYN_TREES 2
|
||||
/* The three kinds of block type */
|
||||
|
||||
#define MIN_MATCH 3
|
||||
#define MAX_MATCH 258
|
||||
/* The minimum and maximum match lengths */
|
||||
|
||||
#define PRESET_DICT 0x20 /* preset dictionary flag in zlib header */
|
||||
|
||||
/* target dependencies */
|
||||
|
||||
#if defined(MSDOS) || (defined(WINDOWS) && !defined(WIN32))
|
||||
# define OS_CODE 0x00
|
||||
# ifndef Z_SOLO
|
||||
# if defined(__TURBOC__) || defined(__BORLANDC__)
|
||||
# if (__STDC__ == 1) && (defined(__LARGE__) || defined(__COMPACT__))
|
||||
/* Allow compilation with ANSI keywords only enabled */
|
||||
void _Cdecl farfree( void *block );
|
||||
void *_Cdecl farmalloc( unsigned long nbytes );
|
||||
# else
|
||||
# include <alloc.h>
|
||||
# endif
|
||||
# else /* MSC or DJGPP */
|
||||
# include <malloc.h>
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef AMIGA
|
||||
# define OS_CODE 1
|
||||
#endif
|
||||
|
||||
#if defined(VAXC) || defined(VMS)
|
||||
# define OS_CODE 2
|
||||
# define F_OPEN(name, mode) \
|
||||
fopen((name), (mode), "mbc=60", "ctx=stm", "rfm=fix", "mrs=512")
|
||||
#endif
|
||||
|
||||
#ifdef __370__
|
||||
# if __TARGET_LIB__ < 0x20000000
|
||||
# define OS_CODE 4
|
||||
# elif __TARGET_LIB__ < 0x40000000
|
||||
# define OS_CODE 11
|
||||
# else
|
||||
# define OS_CODE 8
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if defined(ATARI) || defined(atarist)
|
||||
# define OS_CODE 5
|
||||
#endif
|
||||
|
||||
#ifdef OS2
|
||||
# define OS_CODE 6
|
||||
# if defined(M_I86) && !defined(Z_SOLO)
|
||||
# include <malloc.h>
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if defined(MACOS)
|
||||
# define OS_CODE 7
|
||||
#endif
|
||||
|
||||
#if defined(__acorn) || defined(__riscos)
|
||||
# define OS_CODE 13
|
||||
#endif
|
||||
|
||||
#if defined(WIN32) && !defined(__CYGWIN__)
|
||||
# define OS_CODE 10
|
||||
#endif
|
||||
|
||||
#ifdef _BEOS_
|
||||
# define OS_CODE 16
|
||||
#endif
|
||||
|
||||
#ifdef __TOS_OS400__
|
||||
# define OS_CODE 18
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
# define OS_CODE 19
|
||||
#endif
|
||||
|
||||
#if defined(__BORLANDC__) && !defined(MSDOS)
|
||||
#pragma warn -8004
|
||||
#pragma warn -8008
|
||||
#pragma warn -8066
|
||||
#endif
|
||||
|
||||
/* provide prototypes for these when building zlib without LFS */
|
||||
#ifndef Z_LARGE64
|
||||
ZEXTERN uLong ZEXPORT adler32_combine64(uLong, uLong, z_off64_t);
|
||||
ZEXTERN uLong ZEXPORT crc32_combine64(uLong, uLong, z_off64_t);
|
||||
ZEXTERN uLong ZEXPORT crc32_combine_gen64(z_off64_t);
|
||||
#endif
|
||||
|
||||
/* common defaults */
|
||||
|
||||
#ifndef OS_CODE
|
||||
# define OS_CODE 3 /* assume Unix */
|
||||
#endif
|
||||
|
||||
#ifndef F_OPEN
|
||||
# define F_OPEN(name, mode) fopen((name), (mode))
|
||||
#endif
|
||||
|
||||
/* functions */
|
||||
|
||||
#if defined(pyr) || defined(Z_SOLO)
|
||||
# define NO_MEMCPY
|
||||
#endif
|
||||
#if defined(SMALL_MEDIUM) && !defined(_MSC_VER) && !defined(__SC__)
|
||||
/* Use our own functions for small and medium model with MSC <= 5.0.
|
||||
* You may have to use the same strategy for Borland C (untested).
|
||||
* The __SC__ check is for Symantec.
|
||||
*/
|
||||
# define NO_MEMCPY
|
||||
#endif
|
||||
#if defined(STDC) && !defined(HAVE_MEMCPY) && !defined(NO_MEMCPY)
|
||||
# define HAVE_MEMCPY
|
||||
#endif
|
||||
#ifdef HAVE_MEMCPY
|
||||
# ifdef SMALL_MEDIUM /* MSDOS small or medium model */
|
||||
# define zmemcpy _fmemcpy
|
||||
# define zmemcmp _fmemcmp
|
||||
# define zmemzero(dest, len) _fmemset(dest, 0, len)
|
||||
# else
|
||||
# define zmemcpy memcpy
|
||||
# define zmemcmp memcmp
|
||||
# define zmemzero(dest, len) memset(dest, 0, len)
|
||||
# endif
|
||||
#else
|
||||
void ZLIB_INTERNAL zmemcpy(void FAR *, const void FAR *, z_size_t);
|
||||
int ZLIB_INTERNAL zmemcmp(const void FAR *, const void FAR *, z_size_t);
|
||||
void ZLIB_INTERNAL zmemzero(void FAR *, z_size_t);
|
||||
#endif
|
||||
|
||||
/* Diagnostic functions */
|
||||
#ifdef ZLIB_DEBUG
|
||||
# include <stdio.h>
|
||||
extern int ZLIB_INTERNAL z_verbose;
|
||||
extern void ZLIB_INTERNAL z_error(char *m);
|
||||
# define Assert(cond,msg) {if(!(cond)) z_error(msg);}
|
||||
# define Trace(x) {if (z_verbose>=0) fprintf x ;}
|
||||
# define Tracev(x) {if (z_verbose>0) fprintf x ;}
|
||||
# define Tracevv(x) {if (z_verbose>1) fprintf x ;}
|
||||
# define Tracec(c,x) {if (z_verbose>0 && (c)) fprintf x ;}
|
||||
# define Tracecv(c,x) {if (z_verbose>1 && (c)) fprintf x ;}
|
||||
#else
|
||||
# define Assert(cond,msg)
|
||||
# define Trace(x)
|
||||
# define Tracev(x)
|
||||
# define Tracevv(x)
|
||||
# define Tracec(c,x)
|
||||
# define Tracecv(c,x)
|
||||
#endif
|
||||
|
||||
#ifndef Z_SOLO
|
||||
voidpf ZLIB_INTERNAL zcalloc(voidpf opaque, unsigned items,
|
||||
unsigned size);
|
||||
void ZLIB_INTERNAL zcfree(voidpf opaque, voidpf ptr);
|
||||
#endif
|
||||
|
||||
#define ZALLOC(strm, items, size) \
|
||||
(*((strm)->zalloc))((strm)->opaque, (items), (size))
|
||||
#define ZFREE(strm, addr) (*((strm)->zfree))((strm)->opaque, (voidpf)(addr))
|
||||
#define TRY_FREE(s, p) {if (p) ZFREE(s, p);}
|
||||
|
||||
/* Reverse the bytes in a 32-bit value */
|
||||
#define ZSWAP32(q) ((((q) >> 24) & 0xff) + (((q) >> 8) & 0xff00) + \
|
||||
(((q) & 0xff00) << 8) + (((q) & 0xff) << 24))
|
||||
|
||||
#ifdef Z_ONCE
|
||||
/*
|
||||
Create a local z_once() function depending on the availability of atomics.
|
||||
*/
|
||||
|
||||
/* Check for the availability of atomics. */
|
||||
#if defined(__STDC__) && __STDC_VERSION__ >= 201112L && \
|
||||
!defined(__STDC_NO_ATOMICS__)
|
||||
|
||||
#include <stdatomic.h>
|
||||
typedef struct {
|
||||
atomic_flag begun;
|
||||
atomic_int done;
|
||||
} z_once_t;
|
||||
#define Z_ONCE_INIT {ATOMIC_FLAG_INIT, 0}
|
||||
|
||||
/*
|
||||
Run the provided init() function exactly once, even if multiple threads
|
||||
invoke once() at the same time. The state must be a once_t initialized with
|
||||
Z_ONCE_INIT.
|
||||
*/
|
||||
local void z_once(z_once_t *state, void (*init)(void)) {
|
||||
if (!atomic_load(&state->done)) {
|
||||
if (atomic_flag_test_and_set(&state->begun))
|
||||
while (!atomic_load(&state->done))
|
||||
;
|
||||
else {
|
||||
init();
|
||||
atomic_store(&state->done, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#else /* no atomics */
|
||||
|
||||
#warning zlib not thread-safe
|
||||
|
||||
typedef struct z_once_s {
|
||||
volatile int begun;
|
||||
volatile int done;
|
||||
} z_once_t;
|
||||
#define Z_ONCE_INIT {0, 0}
|
||||
|
||||
/* Test and set. Alas, not atomic, but tries to limit the period of
|
||||
vulnerability. */
|
||||
local int test_and_set(int volatile *flag) {
|
||||
int was;
|
||||
|
||||
was = *flag;
|
||||
*flag = 1;
|
||||
return was;
|
||||
}
|
||||
|
||||
/* Run the provided init() function once. This is not thread-safe. */
|
||||
local void z_once(z_once_t *state, void (*init)(void)) {
|
||||
if (!state->done) {
|
||||
if (test_and_set(&state->begun))
|
||||
while (!state->done)
|
||||
;
|
||||
else {
|
||||
init();
|
||||
state->done = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* ?atomics */
|
||||
|
||||
#endif /* Z_ONCE */
|
||||
|
||||
#endif /* ZUTIL_H */
|
||||
@@ -53,6 +53,7 @@ The game finds the game data as on Linux. Refer to "Start the game" in
|
||||
| Saved games | `%APPDATA%\halo`, or `paths.saves` in `config.toml` |
|
||||
| Log | `debug.txt` in the data root (the folder that contains `maps\`) |
|
||||
| Log of the port | The console. The release build has no console: `halo.log` next to `halo.exe` |
|
||||
| Crash reports | `crashes\` next to `halo.exe`, until the game sends them |
|
||||
|
||||
## How the port operates
|
||||
|
||||
@@ -87,7 +88,8 @@ These files use only the Windows SDK:
|
||||
| --- | --- |
|
||||
| `src/win32_files.c`, `src/win32_net.c` | The file and socket functions of `port/linux/src/posix.h`. |
|
||||
| `src/win32_posix.c` | The POSIX functions on Windows threads, critical sections, condition variables, `VirtualAlloc` and the performance counter. |
|
||||
| `src/win32_memory_watch.c` | The write tracking of textures, with a vectored exception handler. It also writes reports of crashes. |
|
||||
| `src/win32_memory_watch.c` | The write tracking of textures, with a vectored exception handler. |
|
||||
| `src/win32_crash.c` | The crash reports. Refer to "Crash reports". |
|
||||
|
||||
`port.json` gives the Linux files that these files replace, and the Windows
|
||||
libraries of the link.
|
||||
@@ -109,6 +111,57 @@ MSVC gives file scope to a structure tag in a prototype. clang does not.
|
||||
Thus the build also includes the declarations of the Linux build
|
||||
(`build/windows/halo_msvc_tags.h`).
|
||||
|
||||
## Crash reports
|
||||
|
||||
A crash writes the faulting address and the calls that led to it to
|
||||
`debug.txt` and to the log. The builds of the workflow (the releases)
|
||||
also send a crash report to the Sentry project of the developers:
|
||||
|
||||
1. The game starts a second copy of `halo.exe` (`halo.exe --crash-report`).
|
||||
This copy writes a minidump of the game to `crashes\` next to
|
||||
`halo.exe`, and the game stops. A minidump contains the stacks and the
|
||||
registers of the threads, and the list of the modules. It does not
|
||||
contain the other memory of the game.
|
||||
2. At the first crash, the copy asks the player whether to send crash
|
||||
reports. `crash_reports.upload` in `config.toml` keeps the answer:
|
||||
`"yes"` sends this report and all the reports after it, and `"no"` sends
|
||||
no reports and does not ask again. The answer `"no"` deletes the
|
||||
minidump.
|
||||
3. The copy sends the minidump and a copy of `halo.log` to Sentry, then
|
||||
deletes them. If it cannot send them (for example, with no network
|
||||
connection), the game sends them when it starts the next time
|
||||
(`halo.exe --crash-upload`, in the background). `crashes\` keeps at
|
||||
most 8 reports.
|
||||
|
||||
Sentry finds the function names and the source lines of the minidump in
|
||||
the PDBs (`halo.pdb`, `SDL3.pdb`). The `release` job of the workflow
|
||||
uploads the PDBs and the executables of each build of main to Sentry. This
|
||||
needs the `SENTRY_AUTH_TOKEN` secret of the repository: an organization
|
||||
auth token from the settings of the Sentry organization (the token gives
|
||||
the organization and its region). The `SENTRY_PROJECT` variable of the
|
||||
repository can give the slug of the project; without it, the job uses the
|
||||
id of the project in the DSN. For a personal token instead, also set the
|
||||
`SENTRY_ORG` variable (the slug of the organization) and the `SENTRY_URL`
|
||||
variable (`https://de.sentry.io`).
|
||||
|
||||
Each release also has the PDBs as `halo-windows-<configuration>-symbols.zip`.
|
||||
`tools/symbolize_crash.py` uses them to add the function names and the
|
||||
source lines to the crash lines of a `debug.txt` or a `halo.log`:
|
||||
|
||||
```
|
||||
python tools/symbolize_crash.py debug.txt halo.exe
|
||||
```
|
||||
|
||||
Give the `halo.exe` of the build that crashed, with its `halo.pdb` next to
|
||||
it. The tool needs `llvm-symbolizer` (LLVM).
|
||||
|
||||
Builds without a build number (a local build, or a build of a branch other
|
||||
than main) send no crash reports. To test the crash reports with such a
|
||||
build, set the `HALO_CRASH_REPORTS_ANY_BUILD` environment variable.
|
||||
|
||||
The crash reports also include the crashes of `abort()` and of an invalid
|
||||
argument to a function of the C runtime.
|
||||
|
||||
## Limits
|
||||
|
||||
- Bink video is not available. The game skips the movies.
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"winmm",
|
||||
"user32",
|
||||
"advapi32",
|
||||
"winhttp"
|
||||
"winhttp",
|
||||
"dbghelp"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,926 @@
|
||||
/*
|
||||
WIN32_CRASH.C
|
||||
|
||||
Crash reports. The game's own __try handler cannot catch a crash
|
||||
(port/windows/include/halo_windows_prefix.h); an unhandled exception filter
|
||||
does instead. It writes a backtrace of the EBP frame chain into debug.txt and
|
||||
the log, as before, and then has a second copy of halo.exe, the crash
|
||||
reporter ("halo.exe --crash-report <process> <thread> <exception pointers>"),
|
||||
write a minidump of the crashed game. A crashed process cannot be trusted to
|
||||
dump itself (its stack, heap or locks may be what broke), so the reporter
|
||||
reads it from outside, as breakpad and crashpad do. The game waits for the
|
||||
dump and then ends, without Windows's own crash dialog.
|
||||
|
||||
The reporter waits for the game to close, then, at the first crash, asks the
|
||||
player whether to send crash reports. config.toml keeps the answer
|
||||
(crash_reports.upload): "yes" sends this report and every later one without
|
||||
asking, "no" sends none and never asks again. A report is the minidump and
|
||||
a copy of halo.log, sent to the minidump endpoint of the project's Sentry.
|
||||
Sentry turns the minidump into a backtrace with function names and lines
|
||||
from halo.pdb, which the workflow uploads for each build of main
|
||||
(.github/workflows/build.yml). Reports wait in crashes\ beside halo.exe until
|
||||
they are sent; one that could not be sent (no network) goes the next time
|
||||
the game starts ("halo.exe --crash-upload", in the background).
|
||||
|
||||
Only numbered builds (the workflow's builds of main, whose symbols Sentry
|
||||
has) report crashes; the HALO_CRASH_REPORTS_ANY_BUILD environment variable
|
||||
makes any build report (for testing). Abort() and the C runtime's invalid
|
||||
parameter checks, which end the process without an exception, raise one
|
||||
here, so they are reported too.
|
||||
*/
|
||||
|
||||
#include <windows.h>
|
||||
#include <dbghelp.h>
|
||||
#include <winhttp.h>
|
||||
#include <signal.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#include "port_config.h"
|
||||
|
||||
/* (tools/windows_build.py gives them, as to updater.c) */
|
||||
#ifndef HALO_BUILD_NUMBER
|
||||
#define HALO_BUILD_NUMBER 0
|
||||
#endif
|
||||
#ifndef HALO_BUILD_FLAVOR
|
||||
#define HALO_BUILD_FLAVOR "release"
|
||||
#endif
|
||||
|
||||
/* the minidump endpoint of the project's DSN,
|
||||
https://e656c596e8f90402b0f47a2613b69e50@o4512207906603008.ingest.de.sentry.io/4512207917744208
|
||||
(a DSN is public: it only lets a client send events) */
|
||||
#define SENTRY_HOST L"o4512207906603008.ingest.de.sentry.io"
|
||||
#define SENTRY_MINIDUMP_PATH L"/api/4512207917744208/minidump/?sentry_key=e656c596e8f90402b0f47a2613b69e50"
|
||||
#define SENTRY_USER_AGENT L"halo-ce-universal-crash-reporter"
|
||||
|
||||
#define CRASH_REPORT_OPTION L"--crash-report"
|
||||
#define CRASH_UPLOAD_OPTION L"--crash-upload"
|
||||
#define CRASH_FOLDER L"crashes"
|
||||
#define CRASH_UPLOAD_LOCK L"Local\\halo-crash-upload"
|
||||
|
||||
/* the exceptions abort() and the invalid parameter checks raise (bit 29:
|
||||
an application's own code) */
|
||||
#define CRASH_ABORT_EXCEPTION 0xE0000001UL
|
||||
#define CRASH_INVALID_PARAMETER_EXCEPTION 0xE0000002UL
|
||||
|
||||
#define MAXIMUM_PENDING_REPORTS 8
|
||||
#define MAXIMUM_LOG_SIZE (512 * 1024)
|
||||
#define MAXIMUM_DUMP_SIZE (32 * 1024 * 1024)
|
||||
#define MAXIMUM_REPORTER_LOG_SIZE (64 * 1024)
|
||||
#define DUMP_WAIT_MILLISECONDS 60000
|
||||
#define EXIT_WAIT_MILLISECONDS 10000
|
||||
#define CONSENT_WAIT_MILLISECONDS (10 * 60 * 1000)
|
||||
#define UPLOAD_LOCK_WAIT_MILLISECONDS (5 * 60 * 1000)
|
||||
#define UPLOAD_TIMEOUT_MILLISECONDS 30000
|
||||
#define PATH_SIZE (MAX_PATH * 2)
|
||||
|
||||
/* (WinHTTP's TLS 1.3 flag, missing from older SDKs) */
|
||||
#ifndef WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_3
|
||||
#define WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_3 0x00002000
|
||||
#endif
|
||||
|
||||
void platform_log(const char *format, ...);
|
||||
/* errors.c's: debug.txt, whose first lines (its reference address) place
|
||||
the addresses here in the build */
|
||||
void write_to_error_file(char *string, unsigned char date);
|
||||
|
||||
/* ---------- files */
|
||||
|
||||
/* the folder of halo.exe (and of config.toml), with its separator */
|
||||
static int crash_executable_folder(wchar_t *path, size_t size)
|
||||
{
|
||||
DWORD length = GetModuleFileNameW(NULL, path, (DWORD)size);
|
||||
wchar_t *slash;
|
||||
|
||||
if (!length || length >= size || !(slash = wcsrchr(path, L'\\')))
|
||||
return 0;
|
||||
slash[1] = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* crashes\ beside halo.exe, with its separator */
|
||||
static int crash_folder(wchar_t *path, size_t size)
|
||||
{
|
||||
if (!crash_executable_folder(path, size) || wcslen(path) + wcslen(CRASH_FOLDER) + 2 > size)
|
||||
return 0;
|
||||
wcscat(path, CRASH_FOLDER L"\\");
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int crash_path(wchar_t *path, size_t size, const wchar_t *folder, const wchar_t *name, const wchar_t *extension)
|
||||
{
|
||||
int length = _snwprintf(path, size, L"%ls%ls%ls", folder, name, extension);
|
||||
|
||||
return length > 0 && (size_t)length < size;
|
||||
}
|
||||
|
||||
/* a whole file, or at most its last maximum bytes; NULL if there is none.
|
||||
free() it */
|
||||
static char *crash_read_file(const wchar_t *path, size_t maximum, DWORD *size)
|
||||
{
|
||||
HANDLE file = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
|
||||
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
LARGE_INTEGER length, offset;
|
||||
char *data = NULL;
|
||||
DWORD read = 0;
|
||||
|
||||
if (file == INVALID_HANDLE_VALUE)
|
||||
return NULL;
|
||||
if (GetFileSizeEx(file, &length) && length.QuadPart >= 0)
|
||||
{
|
||||
offset.QuadPart = length.QuadPart > (LONGLONG)maximum ? length.QuadPart - (LONGLONG)maximum : 0;
|
||||
length.QuadPart -= offset.QuadPart;
|
||||
data = malloc((size_t)length.QuadPart + 1);
|
||||
if (data && (!SetFilePointerEx(file, offset, NULL, FILE_BEGIN) ||
|
||||
!ReadFile(file, data, (DWORD)length.QuadPart, &read, NULL) || read != (DWORD)length.QuadPart))
|
||||
{
|
||||
free(data);
|
||||
data = NULL;
|
||||
}
|
||||
}
|
||||
CloseHandle(file);
|
||||
if (data)
|
||||
{
|
||||
data[read] = 0;
|
||||
*size = read;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
static int crash_write_file(const wchar_t *path, const char *data, DWORD size)
|
||||
{
|
||||
HANDLE file = CreateFileW(path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
DWORD written = 0;
|
||||
int succeeded;
|
||||
|
||||
if (file == INVALID_HANDLE_VALUE)
|
||||
return 0;
|
||||
succeeded = WriteFile(file, data, size, &written, NULL) && written == size;
|
||||
CloseHandle(file);
|
||||
if (!succeeded)
|
||||
DeleteFileW(path);
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
/* the reports waiting in the folder: the names of their minidumps without
|
||||
".dmp", at most count of them; how many there are */
|
||||
static int crash_pending_reports(const wchar_t *folder, wchar_t names[][MAX_PATH], int count)
|
||||
{
|
||||
wchar_t pattern[PATH_SIZE];
|
||||
WIN32_FIND_DATAW found;
|
||||
HANDLE search;
|
||||
int pending = 0;
|
||||
|
||||
if (!crash_path(pattern, PATH_SIZE, folder, L"*", L".dmp"))
|
||||
return 0;
|
||||
search = FindFirstFileW(pattern, &found);
|
||||
if (search == INVALID_HANDLE_VALUE)
|
||||
return 0;
|
||||
do
|
||||
{
|
||||
size_t length = wcslen(found.cFileName);
|
||||
|
||||
if ((found.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) || length <= 4 || length >= MAX_PATH)
|
||||
continue;
|
||||
if (names && pending < count)
|
||||
{
|
||||
wcscpy(names[pending], found.cFileName);
|
||||
names[pending][length - 4] = 0;
|
||||
}
|
||||
pending++;
|
||||
}
|
||||
while (FindNextFileW(search, &found));
|
||||
FindClose(search);
|
||||
return pending;
|
||||
}
|
||||
|
||||
static void crash_delete_report(const wchar_t *folder, const wchar_t *name)
|
||||
{
|
||||
wchar_t path[PATH_SIZE];
|
||||
|
||||
if (crash_path(path, PATH_SIZE, folder, name, L".dmp"))
|
||||
DeleteFileW(path);
|
||||
if (crash_path(path, PATH_SIZE, folder, name, L".log"))
|
||||
DeleteFileW(path);
|
||||
}
|
||||
|
||||
/* ---------- the upload */
|
||||
|
||||
struct crash_body
|
||||
{
|
||||
char *data;
|
||||
size_t length;
|
||||
size_t size;
|
||||
int failed;
|
||||
};
|
||||
|
||||
#define CRASH_BOUNDARY "halo-crash-report-3f9c1e7a52d8b406"
|
||||
|
||||
static void body_append(struct crash_body *body, const void *data, size_t length)
|
||||
{
|
||||
if (body->failed)
|
||||
return;
|
||||
if (body->length + length > body->size)
|
||||
{
|
||||
size_t size = (body->length + length) * 2 + 4096;
|
||||
char *grown = realloc(body->data, size);
|
||||
|
||||
if (!grown)
|
||||
{
|
||||
body->failed = 1;
|
||||
return;
|
||||
}
|
||||
body->data = grown;
|
||||
body->size = size;
|
||||
}
|
||||
memcpy(body->data + body->length, data, length);
|
||||
body->length += length;
|
||||
}
|
||||
|
||||
static void body_text(struct crash_body *body, const char *text)
|
||||
{
|
||||
body_append(body, text, strlen(text));
|
||||
}
|
||||
|
||||
static void body_field(struct crash_body *body, const char *name, const char *value)
|
||||
{
|
||||
body_text(body, "--" CRASH_BOUNDARY "\r\nContent-Disposition: form-data; name=\"");
|
||||
body_text(body, name);
|
||||
body_text(body, "\"\r\n\r\n");
|
||||
body_text(body, value);
|
||||
body_text(body, "\r\n");
|
||||
}
|
||||
|
||||
static void body_file(struct crash_body *body, const char *name, const char *file_name, const char *type,
|
||||
const char *data, size_t length)
|
||||
{
|
||||
body_text(body, "--" CRASH_BOUNDARY "\r\nContent-Disposition: form-data; name=\"");
|
||||
body_text(body, name);
|
||||
body_text(body, "\"; filename=\"");
|
||||
body_text(body, file_name);
|
||||
body_text(body, "\"\r\nContent-Type: ");
|
||||
body_text(body, type);
|
||||
body_text(body, "\r\n\r\n");
|
||||
body_append(body, data, length);
|
||||
body_text(body, "\r\n");
|
||||
}
|
||||
|
||||
/* POSTs the body to Sentry: the HTTP status (0 for no answer), and the
|
||||
answer's start (the event's id) */
|
||||
static DWORD crash_post(const struct crash_body *body, char *answer, DWORD answer_size)
|
||||
{
|
||||
HINTERNET session, connection = NULL, request = NULL;
|
||||
DWORD status = 0, status_size = sizeof(status), protocols, received = 0;
|
||||
|
||||
answer[0] = 0;
|
||||
session = WinHttpOpen(SENTRY_USER_AGENT, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME,
|
||||
WINHTTP_NO_PROXY_BYPASS, 0);
|
||||
if (!session)
|
||||
return 0;
|
||||
/* (TLS 1.2 and 1.3; Windows versions without 1.3 take 1.2) */
|
||||
protocols = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_3;
|
||||
if (!WinHttpSetOption(session, WINHTTP_OPTION_SECURE_PROTOCOLS, &protocols, sizeof(protocols)))
|
||||
{
|
||||
protocols = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2;
|
||||
WinHttpSetOption(session, WINHTTP_OPTION_SECURE_PROTOCOLS, &protocols, sizeof(protocols));
|
||||
}
|
||||
WinHttpSetTimeouts(session, UPLOAD_TIMEOUT_MILLISECONDS, UPLOAD_TIMEOUT_MILLISECONDS,
|
||||
UPLOAD_TIMEOUT_MILLISECONDS, UPLOAD_TIMEOUT_MILLISECONDS);
|
||||
connection = WinHttpConnect(session, SENTRY_HOST, INTERNET_DEFAULT_HTTPS_PORT, 0);
|
||||
if (connection)
|
||||
{
|
||||
request = WinHttpOpenRequest(connection, L"POST", SENTRY_MINIDUMP_PATH, NULL, WINHTTP_NO_REFERER,
|
||||
WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE);
|
||||
}
|
||||
if (request &&
|
||||
WinHttpSendRequest(request, L"Content-Type: multipart/form-data; boundary=" CRASH_BOUNDARY, (DWORD)-1L,
|
||||
body->data, (DWORD)body->length, (DWORD)body->length, 0) &&
|
||||
WinHttpReceiveResponse(request, NULL) &&
|
||||
WinHttpQueryHeaders(request, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
|
||||
WINHTTP_HEADER_NAME_BY_INDEX, &status, &status_size, WINHTTP_NO_HEADER_INDEX))
|
||||
{
|
||||
DWORD count = 0;
|
||||
|
||||
while (received + 1 < answer_size &&
|
||||
WinHttpReadData(request, answer + received, answer_size - 1 - received, &count) && count)
|
||||
{
|
||||
received += count;
|
||||
}
|
||||
answer[received] = 0;
|
||||
answer[strcspn(answer, "\r\n")] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
platform_log("crash report: could not reach %ls (error %lu)", SENTRY_HOST, (unsigned long)GetLastError());
|
||||
}
|
||||
if (request)
|
||||
WinHttpCloseHandle(request);
|
||||
if (connection)
|
||||
WinHttpCloseHandle(connection);
|
||||
WinHttpCloseHandle(session);
|
||||
return status;
|
||||
}
|
||||
|
||||
/* sends one report; 1 when it is done with (sent, or refused for good) */
|
||||
static int crash_upload(const wchar_t *folder, const wchar_t *name)
|
||||
{
|
||||
typedef const char *(CDECL *wine_get_version_proc)(void);
|
||||
wchar_t path[PATH_SIZE];
|
||||
const wchar_t *build_text = wcsstr(name, L"-build");
|
||||
char release[32] = "build-0", environment[16] = "release", file_name[MAX_PATH * 3], answer[256];
|
||||
struct crash_body body = { 0 };
|
||||
char *dump, *log = NULL;
|
||||
DWORD dump_size = 0, log_size = 0, status;
|
||||
wine_get_version_proc wine_get_version;
|
||||
int number = 0;
|
||||
wchar_t flavor[16] = L"";
|
||||
|
||||
/* the build that crashed, from the name: <time>-<process>-build<number>-<flavor> */
|
||||
if (build_text && swscanf(build_text, L"-build%d-%15ls", &number, flavor) >= 1)
|
||||
{
|
||||
snprintf(release, sizeof(release), "build-%d", number);
|
||||
if (flavor[0])
|
||||
snprintf(environment, sizeof(environment), "%ls", flavor);
|
||||
}
|
||||
if (!crash_path(path, PATH_SIZE, folder, name, L".dmp") || !(dump = crash_read_file(path, MAXIMUM_DUMP_SIZE + 1,
|
||||
&dump_size)))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
if (dump_size > MAXIMUM_DUMP_SIZE)
|
||||
{
|
||||
platform_log("crash report: %ls.dmp is too large to send", name);
|
||||
free(dump);
|
||||
return 1;
|
||||
}
|
||||
if (crash_path(path, PATH_SIZE, folder, name, L".log"))
|
||||
log = crash_read_file(path, MAXIMUM_LOG_SIZE, &log_size);
|
||||
snprintf(file_name, sizeof(file_name), "%ls.dmp", name);
|
||||
|
||||
body_field(&body, "sentry[release]", release);
|
||||
body_field(&body, "sentry[environment]", environment);
|
||||
/* (Proton and Wine players' crashes are often Wine's) */
|
||||
wine_get_version = (wine_get_version_proc)(void *)GetProcAddress(GetModuleHandleW(L"ntdll.dll"),
|
||||
"wine_get_version");
|
||||
body_field(&body, "sentry[tags][wine]", wine_get_version ? wine_get_version() : "no");
|
||||
body_file(&body, "upload_file_minidump", file_name, "application/octet-stream", dump, dump_size);
|
||||
/* (another file of the form is an attachment of the event) */
|
||||
if (log)
|
||||
body_file(&body, "halo.log", "halo.log", "text/plain", log, log_size);
|
||||
body_text(&body, "--" CRASH_BOUNDARY "--\r\n");
|
||||
free(dump);
|
||||
free(log);
|
||||
if (body.failed)
|
||||
{
|
||||
free(body.data);
|
||||
return 0;
|
||||
}
|
||||
status = crash_post(&body, answer, sizeof(answer));
|
||||
free(body.data);
|
||||
if (status == 200)
|
||||
{
|
||||
platform_log("crash report: sent %ls (%s, %s): %s", name, release, environment, answer);
|
||||
return 1;
|
||||
}
|
||||
platform_log("crash report: Sentry answered %lu to %ls: %s", (unsigned long)status, name, answer);
|
||||
/* (no answer, a server error or too many reports: again later; any other
|
||||
refusal would be the same the next time) */
|
||||
return status >= 400 && status < 500 && status != 408 && status != 429;
|
||||
}
|
||||
|
||||
/* sends the reports waiting in the folder, one process at a time */
|
||||
static void crash_upload_pending(const wchar_t *folder)
|
||||
{
|
||||
wchar_t names[MAXIMUM_PENDING_REPORTS * 2][MAX_PATH];
|
||||
HANDLE lock = CreateMutexW(NULL, FALSE, CRASH_UPLOAD_LOCK);
|
||||
int count, index;
|
||||
|
||||
if (lock)
|
||||
WaitForSingleObject(lock, UPLOAD_LOCK_WAIT_MILLISECONDS);
|
||||
count = crash_pending_reports(folder, names, MAXIMUM_PENDING_REPORTS * 2);
|
||||
if (count > MAXIMUM_PENDING_REPORTS * 2)
|
||||
count = MAXIMUM_PENDING_REPORTS * 2;
|
||||
for (index = 0; index < count; index++)
|
||||
{
|
||||
if (crash_upload(folder, names[index]))
|
||||
crash_delete_report(folder, names[index]);
|
||||
}
|
||||
if (lock)
|
||||
{
|
||||
ReleaseMutex(lock);
|
||||
CloseHandle(lock);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- the reporter */
|
||||
|
||||
/* a module's CodeView record (its PDB's id and name), from its image in the
|
||||
crashed game; its size, or 0 */
|
||||
static DWORD crash_codeview_record(HANDLE process, ULONG64 base, char *record, DWORD size)
|
||||
{
|
||||
IMAGE_DOS_HEADER dos;
|
||||
IMAGE_NT_HEADERS32 headers;
|
||||
IMAGE_DATA_DIRECTORY debug;
|
||||
DWORD index;
|
||||
|
||||
if (!ReadProcessMemory(process, (void *)(ULONG_PTR)base, &dos, sizeof(dos), NULL) ||
|
||||
dos.e_magic != IMAGE_DOS_SIGNATURE ||
|
||||
!ReadProcessMemory(process, (void *)(ULONG_PTR)(base + (ULONG64)dos.e_lfanew), &headers, sizeof(headers),
|
||||
NULL) ||
|
||||
headers.Signature != IMAGE_NT_SIGNATURE || headers.OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC ||
|
||||
headers.OptionalHeader.NumberOfRvaAndSizes <= IMAGE_DIRECTORY_ENTRY_DEBUG)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
debug = headers.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG];
|
||||
for (index = 0; index < debug.Size / sizeof(IMAGE_DEBUG_DIRECTORY) && index < 16; index++)
|
||||
{
|
||||
IMAGE_DEBUG_DIRECTORY entry;
|
||||
|
||||
if (!ReadProcessMemory(process, (void *)(ULONG_PTR)(base + debug.VirtualAddress +
|
||||
index * sizeof(IMAGE_DEBUG_DIRECTORY)), &entry, sizeof(entry), NULL))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
/* (an "RSDS" record: signature, GUID, age and the PDB's name) */
|
||||
if (entry.Type == IMAGE_DEBUG_TYPE_CODEVIEW && entry.AddressOfRawData && entry.SizeOfData >= 25 &&
|
||||
entry.SizeOfData <= size &&
|
||||
ReadProcessMemory(process, (void *)(ULONG_PTR)(base + entry.AddressOfRawData), record, entry.SizeOfData,
|
||||
NULL) &&
|
||||
!memcmp(record, "RSDS", 4))
|
||||
{
|
||||
return entry.SizeOfData;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Wine's MiniDumpWriteDump leaves out the modules' CodeView records, which
|
||||
Sentry finds their symbols by (Windows's own writes them): the minidump
|
||||
gets them from the crashed game's images */
|
||||
static void crash_add_codeview_records(HANDLE process, const wchar_t *path)
|
||||
{
|
||||
struct crash_body dump = { 0 };
|
||||
MINIDUMP_HEADER header;
|
||||
DWORD size = 0, stream;
|
||||
char *data = crash_read_file(path, MAXIMUM_DUMP_SIZE, &size);
|
||||
int added = 0;
|
||||
|
||||
if (!data)
|
||||
return;
|
||||
body_append(&dump, data, size);
|
||||
free(data);
|
||||
if (dump.failed || size < sizeof(header))
|
||||
goto done;
|
||||
memcpy(&header, dump.data, sizeof(header));
|
||||
if (header.Signature != MINIDUMP_SIGNATURE || header.NumberOfStreams > 256 ||
|
||||
header.StreamDirectoryRva > size || header.NumberOfStreams * sizeof(MINIDUMP_DIRECTORY) >
|
||||
size - header.StreamDirectoryRva)
|
||||
{
|
||||
goto done;
|
||||
}
|
||||
for (stream = 0; stream < header.NumberOfStreams; stream++)
|
||||
{
|
||||
MINIDUMP_DIRECTORY directory;
|
||||
ULONG32 count, module;
|
||||
|
||||
memcpy(&directory, dump.data + header.StreamDirectoryRva + stream * sizeof(directory), sizeof(directory));
|
||||
if (directory.StreamType != ModuleListStream || directory.Location.Rva > size - sizeof(count))
|
||||
continue;
|
||||
memcpy(&count, dump.data + directory.Location.Rva, sizeof(count));
|
||||
if (count > (size - directory.Location.Rva - sizeof(count)) / sizeof(MINIDUMP_MODULE))
|
||||
continue;
|
||||
for (module = 0; module < count; module++)
|
||||
{
|
||||
size_t offset = directory.Location.Rva + sizeof(count) + module * sizeof(MINIDUMP_MODULE);
|
||||
MINIDUMP_MODULE entry;
|
||||
char record[1024];
|
||||
DWORD length;
|
||||
|
||||
memcpy(&entry, dump.data + offset, sizeof(entry));
|
||||
if (entry.CvRecord.DataSize ||
|
||||
!(length = crash_codeview_record(process, entry.BaseOfImage, record, sizeof(record))))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
entry.CvRecord.Rva = (RVA)dump.length;
|
||||
entry.CvRecord.DataSize = length;
|
||||
body_append(&dump, record, length);
|
||||
body_append(&dump, "\0\0\0", (4 - length % 4) % 4);
|
||||
if (dump.failed)
|
||||
goto done;
|
||||
memcpy(dump.data + offset, &entry, sizeof(entry));
|
||||
added++;
|
||||
}
|
||||
}
|
||||
if (added && crash_write_file(path, dump.data, (DWORD)dump.length))
|
||||
platform_log("crash report: added the CodeView records of %d modules", added);
|
||||
|
||||
done:
|
||||
free(dump.data);
|
||||
}
|
||||
|
||||
static const char *crash_consent(void)
|
||||
{
|
||||
return config_string("crash_reports.upload");
|
||||
}
|
||||
|
||||
/* asks the player: 1 yes, 0 no, -1 no answer (the box went unanswered, as
|
||||
it can behind a fullscreen window under gamescope) */
|
||||
static int crash_ask(void)
|
||||
{
|
||||
typedef int (WINAPI *message_box_timeout_proc)(HWND, LPCWSTR, LPCWSTR, UINT, WORD, DWORD);
|
||||
static const wchar_t text[] =
|
||||
L"Halo crashed.\n\n"
|
||||
L"Do you want to send crash reports to the developers? They help us find and fix crashes.\n\n"
|
||||
L"A report holds the state of the game when it crashed (the call stacks and registers of its threads, "
|
||||
L"and the list of its program files) and its log, halo.log. Reports go to Sentry (sentry.io).\n\n"
|
||||
L"The answer is kept in config.toml (crash_reports.upload): Yes sends the report of this crash and of "
|
||||
L"every later one, No never sends one.";
|
||||
/* (user32's MessageBoxTimeoutW, which Windows and Wine have and do not
|
||||
document) */
|
||||
message_box_timeout_proc message_box_timeout =
|
||||
(message_box_timeout_proc)(void *)GetProcAddress(GetModuleHandleW(L"user32.dll"), "MessageBoxTimeoutW");
|
||||
UINT style = MB_YESNO | MB_ICONERROR | MB_TOPMOST | MB_SETFOREGROUND;
|
||||
int answer = message_box_timeout ? message_box_timeout(NULL, text, L"Halo crashed", style, 0,
|
||||
CONSENT_WAIT_MILLISECONDS) : MessageBoxW(NULL, text, L"Halo crashed", style);
|
||||
|
||||
return answer == IDYES ? 1 : answer == IDNO ? 0 : -1;
|
||||
}
|
||||
|
||||
/* the reporter's own log, crashes\reporter.log (the game's goes to halo.log,
|
||||
which this process must leave as the game wrote it) */
|
||||
static void crash_reporter_log(const wchar_t *folder)
|
||||
{
|
||||
wchar_t path[PATH_SIZE];
|
||||
WIN32_FILE_ATTRIBUTE_DATA attributes;
|
||||
int full;
|
||||
|
||||
if (!crash_path(path, PATH_SIZE, folder, L"reporter", L".log"))
|
||||
return;
|
||||
/* (added to, until it grows past its limit) */
|
||||
full = GetFileAttributesExW(path, GetFileExInfoStandard, &attributes) &&
|
||||
(attributes.nFileSizeHigh || attributes.nFileSizeLow > MAXIMUM_REPORTER_LOG_SIZE);
|
||||
if (_wfreopen(path, full ? L"w" : L"a", stderr))
|
||||
setvbuf(stderr, NULL, _IONBF, 0);
|
||||
}
|
||||
|
||||
/* a copy of the crashed game's halo.log beside its minidump, if the game
|
||||
wrote it (a debug build logs to its console instead, and leaves an older
|
||||
halo.log) */
|
||||
static void crash_copy_log(HANDLE process, const wchar_t *report_log)
|
||||
{
|
||||
wchar_t path[PATH_SIZE];
|
||||
FILETIME created, exited, kernel, user;
|
||||
WIN32_FILE_ATTRIBUTE_DATA attributes;
|
||||
DWORD size = 0;
|
||||
char *log;
|
||||
|
||||
if (!crash_executable_folder(path, PATH_SIZE) || wcslen(path) + sizeof("halo.log") > PATH_SIZE)
|
||||
return;
|
||||
wcscat(path, L"halo.log");
|
||||
if (!GetProcessTimes(process, &created, &exited, &kernel, &user) ||
|
||||
!GetFileAttributesExW(path, GetFileExInfoStandard, &attributes) ||
|
||||
CompareFileTime(&attributes.ftLastWriteTime, &created) < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
log = crash_read_file(path, MAXIMUM_LOG_SIZE, &size);
|
||||
if (log)
|
||||
{
|
||||
crash_write_file(report_log, log, size);
|
||||
free(log);
|
||||
}
|
||||
}
|
||||
|
||||
static void crash_signal_dumped(DWORD process_id)
|
||||
{
|
||||
wchar_t name[64];
|
||||
HANDLE event;
|
||||
|
||||
_snwprintf(name, 64, L"Local\\halo-crash-dumped-%lu", (unsigned long)process_id);
|
||||
event = OpenEventW(EVENT_MODIFY_STATE, FALSE, name);
|
||||
if (event)
|
||||
{
|
||||
SetEvent(event);
|
||||
CloseHandle(event);
|
||||
}
|
||||
}
|
||||
|
||||
/* "halo.exe --crash-report": dumps the crashed game, asks the first time,
|
||||
and sends */
|
||||
static void crash_reporter(DWORD process_id, DWORD thread_id, ULONG_PTR exception_pointers)
|
||||
{
|
||||
wchar_t folder[PATH_SIZE], name[MAX_PATH], dump[PATH_SIZE], log[PATH_SIZE];
|
||||
MINIDUMP_EXCEPTION_INFORMATION exception;
|
||||
SYSTEMTIME now;
|
||||
HANDLE process, file;
|
||||
const char *consent;
|
||||
BOOL dumped;
|
||||
int answer;
|
||||
|
||||
if (!crash_folder(folder, PATH_SIZE))
|
||||
return;
|
||||
CreateDirectoryW(folder, NULL);
|
||||
crash_reporter_log(folder);
|
||||
consent = crash_consent();
|
||||
if (!strcmp(consent, "no"))
|
||||
return;
|
||||
if (crash_pending_reports(folder, NULL, 0) >= MAXIMUM_PENDING_REPORTS)
|
||||
{
|
||||
platform_log("crash report: %d reports are waiting to be sent already; no new one", MAXIMUM_PENDING_REPORTS);
|
||||
return;
|
||||
}
|
||||
process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_DUP_HANDLE | SYNCHRONIZE, FALSE,
|
||||
process_id);
|
||||
if (!process)
|
||||
{
|
||||
platform_log("crash report: cannot open the game's process (error %lu)", (unsigned long)GetLastError());
|
||||
return;
|
||||
}
|
||||
GetSystemTime(&now);
|
||||
_snwprintf(name, MAX_PATH, L"%04u%02u%02u-%02u%02u%02u-%lu-build%d-%hs", now.wYear, now.wMonth, now.wDay,
|
||||
now.wHour, now.wMinute, now.wSecond, (unsigned long)process_id, HALO_BUILD_NUMBER, HALO_BUILD_FLAVOR);
|
||||
name[MAX_PATH - 1] = 0;
|
||||
if (!crash_path(dump, PATH_SIZE, folder, name, L".dmp") || !crash_path(log, PATH_SIZE, folder, name, L".log"))
|
||||
{
|
||||
CloseHandle(process);
|
||||
return;
|
||||
}
|
||||
file = CreateFileW(dump, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
if (file == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
platform_log("crash report: cannot write %ls (error %lu)", dump, (unsigned long)GetLastError());
|
||||
CloseHandle(process);
|
||||
return;
|
||||
}
|
||||
exception.ThreadId = thread_id;
|
||||
exception.ExceptionPointers = (EXCEPTION_POINTERS *)exception_pointers;
|
||||
/* (the pointers are the crashed game's) */
|
||||
exception.ClientPointers = TRUE;
|
||||
/* the threads' stacks and registers and the modules (with the ids of their
|
||||
PDBs, for Sentry to find symbols by), not the game's memory */
|
||||
dumped = MiniDumpWriteDump(process, process_id, file,
|
||||
MiniDumpNormal | MiniDumpWithThreadInfo | MiniDumpWithUnloadedModules, &exception, NULL, NULL);
|
||||
CloseHandle(file);
|
||||
if (!dumped)
|
||||
{
|
||||
platform_log("crash report: the minidump failed (error %lu)", (unsigned long)GetLastError());
|
||||
DeleteFileW(dump);
|
||||
CloseHandle(process);
|
||||
return;
|
||||
}
|
||||
crash_add_codeview_records(process, dump);
|
||||
crash_signal_dumped(process_id);
|
||||
/* (the game's fullscreen window would cover the question) */
|
||||
WaitForSingleObject(process, EXIT_WAIT_MILLISECONDS);
|
||||
crash_copy_log(process, log);
|
||||
CloseHandle(process);
|
||||
platform_log("crash report: wrote %ls", dump);
|
||||
|
||||
if (strcmp(consent, "yes"))
|
||||
{
|
||||
answer = crash_ask();
|
||||
if (answer < 0)
|
||||
{
|
||||
platform_log("crash report: no answer; nothing sent");
|
||||
crash_delete_report(folder, name);
|
||||
return;
|
||||
}
|
||||
config_write("crash_reports.upload", answer ? "yes" : "no");
|
||||
if (!answer)
|
||||
{
|
||||
platform_log("crash report: the player declined; none will be sent");
|
||||
crash_delete_report(folder, name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
crash_upload_pending(folder);
|
||||
}
|
||||
|
||||
/* "halo.exe --crash-upload": the reports a crash left unsent */
|
||||
static void crash_uploader(void)
|
||||
{
|
||||
wchar_t folder[PATH_SIZE], names[MAXIMUM_PENDING_REPORTS * 2][MAX_PATH];
|
||||
const char *consent;
|
||||
int count, index;
|
||||
|
||||
if (!crash_folder(folder, PATH_SIZE))
|
||||
return;
|
||||
crash_reporter_log(folder);
|
||||
consent = crash_consent();
|
||||
if (!strcmp(consent, "yes"))
|
||||
{
|
||||
crash_upload_pending(folder);
|
||||
}
|
||||
else if (!strcmp(consent, "no"))
|
||||
{
|
||||
count = crash_pending_reports(folder, names, MAXIMUM_PENDING_REPORTS * 2);
|
||||
if (count > MAXIMUM_PENDING_REPORTS * 2)
|
||||
count = MAXIMUM_PENDING_REPORTS * 2;
|
||||
for (index = 0; index < count; index++)
|
||||
crash_delete_report(folder, names[index]);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- the crashed game */
|
||||
|
||||
static int crash_reports_enabled(void)
|
||||
{
|
||||
return HALO_BUILD_NUMBER > 0 || GetEnvironmentVariableW(L"HALO_CRASH_REPORTS_ANY_BUILD", NULL, 0) > 0;
|
||||
}
|
||||
|
||||
/* starts this executable again with the option and its arguments, with no
|
||||
console and none of this process's handles (its sockets hold the game's
|
||||
ports) */
|
||||
static HANDLE crash_start_reporter(const wchar_t *arguments)
|
||||
{
|
||||
wchar_t executable[PATH_SIZE], command[PATH_SIZE + 128];
|
||||
STARTUPINFOW startup;
|
||||
PROCESS_INFORMATION process;
|
||||
DWORD length = GetModuleFileNameW(NULL, executable, PATH_SIZE);
|
||||
|
||||
if (!length || length >= PATH_SIZE)
|
||||
return NULL;
|
||||
_snwprintf(command, PATH_SIZE + 128, L"\"%ls\" %ls", executable, arguments);
|
||||
command[PATH_SIZE + 127] = 0;
|
||||
memset(&startup, 0, sizeof(startup));
|
||||
startup.cb = sizeof(startup);
|
||||
if (!CreateProcessW(executable, command, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &startup, &process))
|
||||
return NULL;
|
||||
CloseHandle(process.hThread);
|
||||
return process.hProcess;
|
||||
}
|
||||
|
||||
/* has the reporter dump this process; 1 once the minidump is written */
|
||||
static int crash_dump(EXCEPTION_POINTERS *exception)
|
||||
{
|
||||
wchar_t name[64], arguments[128];
|
||||
HANDLE event, waits[2];
|
||||
DWORD result;
|
||||
|
||||
_snwprintf(name, 64, L"Local\\halo-crash-dumped-%lu", (unsigned long)GetCurrentProcessId());
|
||||
event = CreateEventW(NULL, TRUE, FALSE, name);
|
||||
if (!event)
|
||||
return 0;
|
||||
_snwprintf(arguments, 128, CRASH_REPORT_OPTION L" %lu %lu %lx", (unsigned long)GetCurrentProcessId(),
|
||||
(unsigned long)GetCurrentThreadId(), (unsigned long)(ULONG_PTR)exception);
|
||||
waits[0] = event;
|
||||
waits[1] = crash_start_reporter(arguments);
|
||||
if (!waits[1])
|
||||
{
|
||||
CloseHandle(event);
|
||||
return 0;
|
||||
}
|
||||
/* (until the dump is written, or the reporter ends without one: the
|
||||
player said no, say) */
|
||||
result = WaitForMultipleObjects(2, waits, FALSE, DUMP_WAIT_MILLISECONDS);
|
||||
CloseHandle(waits[1]);
|
||||
CloseHandle(event);
|
||||
return result == WAIT_OBJECT_0;
|
||||
}
|
||||
|
||||
/* a line of the report, to the log and to debug.txt (a player sends
|
||||
debug.txt; the console closes with the game) */
|
||||
static void crash_line(const char *format, ...)
|
||||
{
|
||||
char line[256];
|
||||
va_list arguments;
|
||||
size_t length;
|
||||
|
||||
va_start(arguments, format);
|
||||
vsnprintf(line, sizeof(line) - 2, format, arguments);
|
||||
va_end(arguments);
|
||||
platform_log("%s", line);
|
||||
length = strlen(line);
|
||||
line[length] = '\r';
|
||||
line[length + 1] = '\n';
|
||||
line[length + 2] = 0;
|
||||
write_to_error_file(line, 1);
|
||||
}
|
||||
|
||||
static LONG WINAPI crash_filter(EXCEPTION_POINTERS *exception)
|
||||
{
|
||||
static volatile LONG crashed_thread = 0;
|
||||
EXCEPTION_RECORD *record = exception->ExceptionRecord;
|
||||
CONTEXT *context = exception->ContextRecord;
|
||||
const DWORD *stack = (const DWORD *)context->Esp;
|
||||
LONG thread = (LONG)GetCurrentThreadId();
|
||||
LONG first = InterlockedCompareExchange(&crashed_thread, thread, 0);
|
||||
int dumped;
|
||||
|
||||
/* one report: a crash in this report goes on to Windows, and another
|
||||
thread's crash waits for the end the first one brings */
|
||||
if (first == thread)
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
if (first)
|
||||
Sleep(INFINITE);
|
||||
/* the minidump first, before anything here can fail */
|
||||
dumped = crash_reports_enabled() && crash_dump(exception);
|
||||
/* (where halo.exe is: tools/symbolize_crash.py finds the lines of the
|
||||
addresses below from it and halo.pdb) */
|
||||
crash_line("crash: halo.exe at %p, build %d (%s)", (void *)GetModuleHandleW(NULL), HALO_BUILD_NUMBER,
|
||||
HALO_BUILD_FLAVOR);
|
||||
crash_line("crash: exception %08lx at %p (accessing %p), eip %08lx ebp %08lx esp %08lx",
|
||||
record->ExceptionCode, record->ExceptionAddress,
|
||||
record->NumberParameters >= 2 ? (void *)record->ExceptionInformation[1] : NULL,
|
||||
context->Eip, context->Ebp, context->Esp);
|
||||
if (!IsBadReadPtr(stack, 6 * sizeof(DWORD)))
|
||||
{
|
||||
crash_line("crash: stack %08lx %08lx %08lx %08lx %08lx %08lx",
|
||||
stack[0], stack[1], stack[2], stack[3], stack[4], stack[5]);
|
||||
}
|
||||
{
|
||||
/* the EBP frame chain (the game keeps frame pointers) */
|
||||
const DWORD *frame = (const DWORD *)context->Ebp;
|
||||
int depth;
|
||||
|
||||
for (depth = 0; depth < 32 && frame && !IsBadReadPtr(frame, 2 * sizeof(DWORD)); depth++)
|
||||
{
|
||||
crash_line("crash: called from %08lx", frame[1]);
|
||||
if ((const DWORD *)frame[0] <= frame)
|
||||
break;
|
||||
frame = (const DWORD *)frame[0];
|
||||
}
|
||||
}
|
||||
fflush(stderr);
|
||||
/* (the reporter has it: the game ends without Windows's crash dialog) */
|
||||
return dumped ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
/* abort() and a bad argument to the C runtime end the process at once
|
||||
(__fastfail), past the filter: an exception of their own first */
|
||||
static void crash_abort(int signal_number)
|
||||
{
|
||||
(void)signal_number;
|
||||
RaiseException(CRASH_ABORT_EXCEPTION, EXCEPTION_NONCONTINUABLE, 0, NULL);
|
||||
}
|
||||
|
||||
static void crash_invalid_parameter(const wchar_t *expression, const wchar_t *function, const wchar_t *file,
|
||||
unsigned int line, uintptr_t reserved)
|
||||
{
|
||||
(void)expression;
|
||||
(void)function;
|
||||
(void)file;
|
||||
(void)line;
|
||||
(void)reserved;
|
||||
RaiseException(CRASH_INVALID_PARAMETER_EXCEPTION, EXCEPTION_NONCONTINUABLE, 0, NULL);
|
||||
}
|
||||
|
||||
/* ---------- start-up */
|
||||
|
||||
/* the reporter's option on the command line, or NULL */
|
||||
static const wchar_t *crash_option(const wchar_t *option)
|
||||
{
|
||||
const wchar_t *command = GetCommandLineW();
|
||||
const wchar_t *found = command ? wcsstr(command, option) : NULL;
|
||||
|
||||
return found && found > command && found[-1] == L' ' ? found + wcslen(option) : NULL;
|
||||
}
|
||||
|
||||
/* whether this process is a reporter (win32_posix.c then leaves halo.log
|
||||
to the game) */
|
||||
int crash_reporter_process(void)
|
||||
{
|
||||
return crash_option(CRASH_REPORT_OPTION) || crash_option(CRASH_UPLOAD_OPTION);
|
||||
}
|
||||
|
||||
__attribute__((constructor))
|
||||
static void crash_reports_install(void)
|
||||
{
|
||||
const wchar_t *arguments;
|
||||
ULONG guarantee = 64 * 1024;
|
||||
wchar_t folder[PATH_SIZE];
|
||||
|
||||
if ((arguments = crash_option(CRASH_REPORT_OPTION)) != NULL)
|
||||
{
|
||||
unsigned long process_id = 0, thread_id = 0, exception_pointers = 0;
|
||||
|
||||
if (swscanf(arguments, L" %lu %lu %lx", &process_id, &thread_id, &exception_pointers) == 3)
|
||||
crash_reporter((DWORD)process_id, (DWORD)thread_id, (ULONG_PTR)exception_pointers);
|
||||
ExitProcess(0);
|
||||
}
|
||||
if (crash_option(CRASH_UPLOAD_OPTION))
|
||||
{
|
||||
crash_uploader();
|
||||
ExitProcess(0);
|
||||
}
|
||||
|
||||
SetUnhandledExceptionFilter(crash_filter);
|
||||
/* (room on the main thread's stack for the filter after a stack overflow) */
|
||||
SetThreadStackGuarantee(&guarantee);
|
||||
signal(SIGABRT, crash_abort);
|
||||
_set_invalid_parameter_handler(crash_invalid_parameter);
|
||||
/* reports an earlier crash could not send */
|
||||
if (crash_reports_enabled() && crash_folder(folder, PATH_SIZE) && crash_pending_reports(folder, NULL, 0))
|
||||
{
|
||||
HANDLE uploader = crash_start_reporter(CRASH_UPLOAD_OPTION);
|
||||
|
||||
if (uploader)
|
||||
CloseHandle(uploader);
|
||||
}
|
||||
}
|
||||
@@ -6,15 +6,9 @@ of port/linux/src/memory_watch.c (see there for the design). The pages behind
|
||||
a cached texture are made read-only; a vectored exception handler catches
|
||||
the first write, records a new generation for the page and makes it
|
||||
writable again.
|
||||
|
||||
This file also reports crashes, which the game's own __try handler cannot
|
||||
(port/windows/include/halo_windows_prefix.h).
|
||||
*/
|
||||
|
||||
#include <windows.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
/* the Xbox memory window (port/linux/src/platform.h) */
|
||||
#define PLATFORM_CONTIGUOUS_BASE 0x80000000UL
|
||||
@@ -161,66 +155,3 @@ void memory_watch_forget(void *address, unsigned long size)
|
||||
page_generation[page] = InterlockedIncrement(¤t_generation);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- crash reports */
|
||||
|
||||
/* errors.c's: debug.txt, whose first lines (its reference address) place
|
||||
the addresses here in the build */
|
||||
void write_to_error_file(char *string, unsigned char date);
|
||||
|
||||
/* a line of the report, to the console and to debug.txt (a player sends
|
||||
debug.txt; the console closes with the game) */
|
||||
static void crash_line(const char *format, ...)
|
||||
{
|
||||
char line[256];
|
||||
va_list arguments;
|
||||
size_t length;
|
||||
|
||||
va_start(arguments, format);
|
||||
vsnprintf(line, sizeof(line) - 2, format, arguments);
|
||||
va_end(arguments);
|
||||
platform_log("%s", line);
|
||||
length = strlen(line);
|
||||
line[length] = '\r';
|
||||
line[length + 1] = '\n';
|
||||
line[length + 2] = 0;
|
||||
write_to_error_file(line, 1);
|
||||
}
|
||||
|
||||
static LONG WINAPI crash_filter(EXCEPTION_POINTERS *exception)
|
||||
{
|
||||
EXCEPTION_RECORD *record = exception->ExceptionRecord;
|
||||
CONTEXT *context = exception->ContextRecord;
|
||||
const DWORD *stack = (const DWORD *)context->Esp;
|
||||
|
||||
crash_line("crash: exception %08lx at %p (accessing %p), eip %08lx ebp %08lx esp %08lx",
|
||||
record->ExceptionCode, record->ExceptionAddress,
|
||||
record->NumberParameters >= 2 ? (void *)record->ExceptionInformation[1] : NULL,
|
||||
context->Eip, context->Ebp, context->Esp);
|
||||
if (!IsBadReadPtr(stack, 6 * sizeof(DWORD)))
|
||||
{
|
||||
crash_line("crash: stack %08lx %08lx %08lx %08lx %08lx %08lx",
|
||||
stack[0], stack[1], stack[2], stack[3], stack[4], stack[5]);
|
||||
}
|
||||
{
|
||||
/* the EBP frame chain (the game keeps frame pointers) */
|
||||
const DWORD *frame = (const DWORD *)context->Ebp;
|
||||
int depth;
|
||||
|
||||
for (depth = 0; depth < 32 && frame && !IsBadReadPtr(frame, 2 * sizeof(DWORD)); depth++)
|
||||
{
|
||||
crash_line("crash: called from %08lx", frame[1]);
|
||||
if ((const DWORD *)frame[0] <= frame)
|
||||
break;
|
||||
frame = (const DWORD *)frame[0];
|
||||
}
|
||||
}
|
||||
fflush(stderr);
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
__attribute__((constructor))
|
||||
static void crash_reports_install(void)
|
||||
{
|
||||
SetUnhandledExceptionFilter(crash_filter);
|
||||
}
|
||||
@@ -28,6 +28,10 @@ graphics) run a program on the integrated one unless it exports these */
|
||||
__declspec(dllexport) DWORD NvOptimusEnablement = 1;
|
||||
__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
|
||||
|
||||
/* win32_crash.c's: whether this process is a crash reporter, which leaves
|
||||
halo.log to the game it reports */
|
||||
int crash_reporter_process(void);
|
||||
|
||||
__attribute__((constructor))
|
||||
static void windows_startup(void)
|
||||
{
|
||||
@@ -41,7 +45,7 @@ static void windows_startup(void)
|
||||
layer's log (platform_log's stderr: invite links, the data root, crash
|
||||
reports) goes to halo.log next to halo.exe, unless stderr already goes
|
||||
somewhere (a pipe or a file the game was started with) */
|
||||
if (_fileno(stderr) < 0)
|
||||
if (_fileno(stderr) < 0 && !crash_reporter_process())
|
||||
{
|
||||
char path[MAX_PATH];
|
||||
DWORD length = GetModuleFileNameA(NULL, path, sizeof(path));
|
||||
|
||||
@@ -43,6 +43,7 @@ symbols in this file:
|
||||
/* ---------- headers */
|
||||
|
||||
#include "cseries.h"
|
||||
#include "cseries/errors.h"
|
||||
#include "actions.h"
|
||||
#include "math/real_math.h"
|
||||
|
||||
@@ -250,6 +251,7 @@ static short action_alert_next_position(
|
||||
unsigned long unavailable_positions[BIT_VECTOR_SIZE_IN_LONGS(32)];
|
||||
boolean any_position_available = FALSE;
|
||||
short position_index;
|
||||
short move_position_count;
|
||||
|
||||
csmemset(
|
||||
unavailable_positions,
|
||||
@@ -257,7 +259,23 @@ static short action_alert_next_position(
|
||||
sizeof(unavailable_positions));
|
||||
position_index = 0;
|
||||
move_positions = &squad->move_positions;
|
||||
while (position_index < move_positions->count)
|
||||
/* port: no more positions than unavailable_positions holds (a
|
||||
map's count): the squad moves among its first ones */
|
||||
move_position_count = (short)PIN(move_positions->count, 0, (long)SIZEOF_BITS(unavailable_positions));
|
||||
if (move_position_count < move_positions->count)
|
||||
{
|
||||
static boolean reported = FALSE;
|
||||
|
||||
if (!reported)
|
||||
{
|
||||
error(_error_silent, "squad %s has %ld move positions (only %d are used)",
|
||||
squad->name,
|
||||
move_positions->count,
|
||||
move_position_count);
|
||||
reported = TRUE;
|
||||
}
|
||||
}
|
||||
while (position_index < move_position_count)
|
||||
{
|
||||
struct move_position_definition *move_position =
|
||||
TAG_BLOCK_GET_ELEMENT(
|
||||
@@ -320,7 +338,7 @@ static short action_alert_next_position(
|
||||
result = choose_random_array_element(
|
||||
squad->move_positions.address,
|
||||
sizeof(struct move_position_definition),
|
||||
move_positions->count,
|
||||
move_position_count,
|
||||
offsetof(struct move_position_definition, weight),
|
||||
unavailable_positions);
|
||||
}
|
||||
@@ -329,7 +347,7 @@ static short action_alert_next_position(
|
||||
short next_position_index;
|
||||
|
||||
if (current_position_index < 0 ||
|
||||
current_position_index >= move_positions->count)
|
||||
current_position_index >= move_position_count)
|
||||
next_position_index = 0;
|
||||
else
|
||||
next_position_index = current_position_index;
|
||||
@@ -346,7 +364,7 @@ static short action_alert_next_position(
|
||||
case _move_position_order_loop_back_and_forth:
|
||||
if (next_position_index == 0)
|
||||
increasing = TRUE;
|
||||
else if (next_position_index == move_positions->count - 1)
|
||||
else if (next_position_index == move_position_count - 1)
|
||||
increasing = FALSE;
|
||||
else if (direction_increasing)
|
||||
increasing = *direction_increasing;
|
||||
@@ -364,14 +382,14 @@ static short action_alert_next_position(
|
||||
if (increasing)
|
||||
{
|
||||
next_position_index++;
|
||||
if (next_position_index >= move_positions->count)
|
||||
if (next_position_index >= move_position_count)
|
||||
next_position_index = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
next_position_index--;
|
||||
if (next_position_index < 0)
|
||||
next_position_index = (short)(move_positions->count - 1);
|
||||
next_position_index = (short)(move_position_count - 1);
|
||||
}
|
||||
}
|
||||
while (BIT_VECTOR_TEST_FLAG(unavailable_positions, next_position_index));
|
||||
|
||||