Compare commits

..
Author SHA1 Message Date
Stefan MaierandClaude Opus 5.5 c8dfd3773f Merge merge/upstream-agpl: OpenCE upstream under the AGPL 3.0 or later, Ringfall with it
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 18:29:39 +02:00
Stefan MaierandClaude Opus 5.5 856ca06a5d Merge OpenCE upstream under its new licence, the AGPL 3.0 or later
OpenCE moved from CC0 1.0 to AGPL-3.0-or-later (4b09168e7), and Ringfall:
Mjolnir VR moves with it: README's Legal section and THIRD_PARTY_NOTICES
say so (releases before this one stay CC0 1.0, LICENSES/CC0-1.0.txt).

Its disc image readers were written again from a description of the
format, extract-xiso's code and notice gone. The app's (XisoExtractor.java)
is OpenCE's new one with Ringfall's language folders (maps_es, maps_it)
added again; the Steam Frame's (steam_frame_xiso.py) is written anew the
same way, the language folders kept; the bundle and the release job no
longer carry extract-xiso's licence. Also in: bnunu/halo-1's latest
matches, the web build's WebRTC and loading fixes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 13:17:41 +02:00
MrBruh 7a1b683328 Merge pull request #415 from OpenCommunityEdition/testing
Switch OpenCE to the AGPL 3.0 or later, with the disc image readers w…
2026-10-19 20:58:05 +13:00
MrBruh 4b09168e7f Switch OpenCE to the AGPL 3.0 or later, with the disc image readers written again from a description of the format 2026-10-19 20:12:12 +13:00
MrBruh c208663539 Merge pull request #406 from OpenCommunityEdition/testing
Testing
2026-10-19 07:47:40 +13:00
Stefan MaierandClaude Opus 5.5 307d2a953c Merge fix/lan-join-bots: a lobby the bots fill is open to a person on the LAN too
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 19:09:46 +02:00
Stefan MaierandClaude Opus 5.5 ce8eba8088 Roadmap: bots make room on the LAN too
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 19:09:45 +02:00
Stefan MaierandClaude Opus 5.5 62dee62eb6 A lobby the bots fill is open to a person on the LAN too
The bots make room for a person who joins, and the server browser's
listing counted their slots as free, but the LAN advertisement and the
join's check still asked for a free slot: with 15 bots and the host a
lobby of 16 was advertised closed, and a Quest on the LAN was refused
("attempted to join a closed game"). Both now count a bot's slot in a
lobby as a person's, as the listing does (network_game_server_room_for_person).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 17:59:49 +02:00
Stefan MaierandClaude Opus 5.5 cf11cc86ce Merge merge/upstream-chat: OpenCE's text chat and streamer mode, the scores on the menu button held
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:15:13 +02:00
Stefan MaierandClaude Opus 5.5 40e4ff2cf8 Roadmap: the scores on the Quest, the text chat in VR
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:15:12 +02:00
Stefan MaierandClaude Opus 5.5 e901c92b41 VR: the menu button held shows the scores (the Quest's controllers had no way to them)
A press pauses as before, now as it is let go; held 0.4 s, the scores
show as long as it is, as the Xbox's Back. In the menus it is Start at
once. The Quest's controls page says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 14:42:41 +02:00
Stefan MaierandClaude Opus 5.5 dc52b7a592 Merge fixes: a brace the p2p.c merge lost; in VR the chat lines where the desktop has them
The chat's lines go at the top left on Android, where the touch controls
keep the bottom left free; VR has none, so it lays them out as the desktop
does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 14:34:36 +02:00
Stefan MaierandClaude Opus 5.5 93b67eb7d7 Merge OpenCE upstream: text chat in network games, streamer mode, faster map sharing, the web launcher
The touch controls stay off in VR alongside the chat's on-screen
keyboard (xinput_sdl.c); the join codes and the online list beside the
server browser's listed hosts (p2p.c).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 14:33:00 +02:00
MrBruh 81a768bc81 Export the game functions the web page calls, so its loading notice hides once the menus draw and rooms reach the game 2026-10-18 23:49:26 +13:00
MrBruh 1b61b52929 Keep a browser's WebRTC channel from stalling under loss by reporting every gap and giving up old ones, and let the test's lossless run tolerate a dropped burst 2026-10-18 23:36:08 +13:00
MrBruh 0110c16001 Merge bnunu/halo-1 main into testing, keeping OpenCE's code where both changed it and leaving out its matching tooling and logs 2026-10-18 22:31:23 +13:00
MrBruh 38ccb5f9a4 Merge pull request #401 from OpenCommunityEdition/testing
Merge Testing
2026-10-18 22:07:36 +13:00
MrBruh 54de4909ef Count the website's visits with Umami, its script loaded with CORS as the isolated page needs 2026-10-18 21:56:56 +13:00
MrBruh 1797ecd9ef Serve the web build's wasm from jsDelivr instead of GitHub Pages, checked against its SHA-256, with the site's copy as the fallback 2026-10-18 21:52:06 +13:00
MrBruh eb16cf4bdc Let a host share maps at up to 20 MB/s and a player helping it at up to 10 MB/s 2026-10-18 21:46:23 +13:00
MrBruh 52c1934dab Add sexual, hate and harassment words that Surge AI's list lacks to streamer mode 2026-10-18 21:35:30 +13:00
MrBruh bc37efd5da Redraw the web launcher in the menus' look, with a Ready to Play screen and Discord links on the page and in game 2026-10-18 21:11:01 +13:00
MrBruh f9bfca84b0 Merge pull request #400 from OpenCommunityEdition/testing
Merge Testing
2026-10-18 20:21:11 +13:00
MrBruh 453c24e6e0 Show the host's TEXT CHAT as OFF, and keep it from changing, while the game is listed in the server browser (#399) 2026-10-18 20:10:54 +13:00
MrBruh b1d71f070f Draw the text chat in team colors by default, with white as Custom Edition's an option (#399) 2026-10-18 20:01:04 +13:00
MrBruh 90ab2ff094 Move the text chat settings to Network Setup's Multiplayer HUD, put voice chat back on Audio Setup, and show Network Setup's in-game pages in the pause menu, co-op too (#399) 2026-10-18 19:57:16 +13:00
MrBruh 8488a161eb Fix the text chat's review findings: no | in lines, case-blind mutes, wrapped lines, per-view and hidden chat, stream-only host messages, no chat in browser-joined public games (#399) 2026-10-18 19:33:08 +13:00
MrBruh 4c624fcbed Merge pull request #399 from OpenCommunityEdition/ingame-chat-v2
Re-land the in-game text chat with the review's changes (#395, #397)
2026-10-18 18:26:36 +13:00
Jonas VolmanandClaude Opus 5.5 8652d565da Hide the touch controls while the chat's on-screen keyboard is up
On Android the touch controls' overlay keeps every finger while it shows
(the look, outside its controls), so a tap on the keyboard never reached
the game. While the keyboard is up the overlay now hides, as it does for a
menu or a cinematic, and lets the fingers through: a tap on a key types it,
and a tap elsewhere closes the keyboard, as B and the other touch controls
are hidden then.

A tap's place is a fraction of the screen, which the viewport fills, so the
keys' place is now kept as a fraction of the viewport, not of the HUD's
window inside it, which is smaller on Android.

Checked on the x86 Android emulator (with #396's fix for its ARM
translation, not part of this change): the touch D-pad's Up, held as a
finger holds it, opened the keyboard at the bottom of the screen and hid
the touch controls; taps on h and i typed "hi", a tap on SEND said it
("chat: Player: hi"), the keyboard closed and the controls came back; a
tap away from the keys closed it too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 21:38:15 -07:00
Jonas VolmanandClaude Opus 5.5 bc2344fb44 Let a controller or a touchscreen type a chat line on an on-screen keyboard
A controller's D-pad Up opens an on-screen keyboard for a line to everyone
(the D-pad only repeats the left stick's movement in the game). The stick,
or D-pad Left and Right, moves between its keys, A types one, X deletes, Y
switches capitals, D-pad Up and Down turn the line to everyone, the team or
the vehicle, Start sends it and B closes the keyboard; its last row has
Aa, #+= (the symbols' page), space, DEL and SEND. It is drawn at the bottom
right, clear of the lines above the motion sensor; on Android at the bottom
in the middle, between the touch controls, as the lines are at the top
left there.

The touch controls' D-pad Up opens it too, and a tap on a key types it
(touch_input.c hands the taps outside the menus to it). While it is up the
game gets none of the first controller (xinput_sdl.c), nor the touch
controls' stick and look.

The automated tests press the controller's buttons through
test_input_press: debug.network_test_chat now has each client type
"gg ez" on the keyboard and send it to the team. A drawn Linux client typed
it and the host received "chat (team): Player 2: gg ez"; the frames show
the keyboard over the game, the key picked lit, the typed line growing.
The harness gains three cases and six negative controls for the keys'
characters, moving between them and a tap's key (64 pass); CI's Linux test
step passes (493, 6 skipped).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 21:17:38 -07:00
Jonas VolmanandClaude Opus 5.5 7abd376b85 Mute a player's voice and text chat from the scoreboard
A player's scoreboard menu (right-click, then the player) had Mute voice,
this machine's own choice. It is now Mute voice and chat: this machine
plays none of their voice and shows none of their lines, and tells no one.
It is offered whether or not voice chat is on.

The host's menu adds Mute for everyone / Unmute for everyone. It is the
chat's /mute for 20 minutes (every machine told "Milenko muted for 20
minutes"), and it now silences the player's voice too: the host relays
none of a muted player's machine's voice (voice_host_relay), as it relays
none of their lines. /mute from the chat mutes their voice the same way.

debug.network_test_chat now also has the host mute the last player for
everyone (network_chat_host_mute, as its scoreboard does) and unmute them,
and the last player's machine mute the host's for itself. On three Linux
machines over system link, with debug.voice_test's tone as open mic:
- the host's mute: no machine showed the player's line, they alone were
  told the minutes left, and the host and the other client stopped hearing
  their voice until the unmute (one second heard at the edge);
- the client's own mute: only that machine stopped showing the host's lines
  and hearing the host's voice.
CI's Linux test step passes (484, 6 skipped).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 20:50:59 -07:00
Jonas VolmanandClaude Opus 5.5 e6d1e09bd7 Let the host turn the text chat off and refuse links, and players hide it
The host's TEXT CHAT row (Server Setup > Teamplay Options; in co-op, Voice
and Voting; network.text_chat) is ON (the default: lines with links are
refused), WITH LINKS or OFF: one row, as Teamplay Options has room for one
more. A game listed in the server browser (p2p_hosting_listed: a public
internet game) never has the text chat; a private game, joined by its
invite, and a LAN game have it as chosen.

The host sends its settings to every client in a new message,
_distributed_message_chat_config (89, one byte of flags), as they change
and every 3 seconds. A client without them, as from a host without the
chat, has the chat off. Pressing T where it is off says "Text chat is off
in this game". A line the settings refuse is refused on the sender's
machine and again by the host, whatever the client knew, which tells the
sender why: a changed build gets no further. Older builds drop the new
kind, so the network version stays 29.

Links are found by chat_text_has_link: a scheme, "www.", four numbers and
three dots, or a name and a common top-level domain ("discord.gg/x",
"example.com"), with the dots also written "(dot)", "[dot]", "(.)" or "[.]";
"1.5", "e.g.", "U.S.A" and "wait.comrades" are not links.

TEXT CHAT on Settings > Audio > Voice and Text Chat (display.text_chat)
HIDEs the chat for purists and streamers: no lines, and T and Y open
nothing. /mute's minutes are 20 when none are given.

debug.network_test_chat now also has each machine say a line with a link
(a client's sent past its own check, network_chat_say_unchecked, for the
host to refuse), then the host turns the chat off and on again around one
more line each. On three Linux machines over system link: every machine
showed "Links aren't allowed in this game" and "Text chat is off in this
game" once, the clients' from the host, and no refused line reached anyone.
The settings screens were drawn and the TEXT CHAT row's help follows its
choice. The harness gains four cases and six negative controls (55 pass);
CI's Linux test step passes (484, 6 skipped).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 20:39:29 -07:00
Jonas VolmanandClaude Opus 5.5 e94255072e Remove the duplication in network_chat.c
No change in behaviour:
- chat_message_new builds a line's message (header and line), which
  chat_host_send, chat_host_notice and chat_send each did by hand.
- chat_send_to_clients sends a message to every client machine.
- chat_line_text turns the typed line into wide characters too, in place
  of the drawing's own loop.
- chat_player_name_length is the one count of a name's characters, for the
  line's name and its ASCII copy.
- CHAT_DRAWN_SIZE and the comments about the chat's old filter are gone;
  testing's streamer mode keeps a word's length, so a line's buffers are
  CHAT_TEXT_SIZE.

The name match keeps its own lower-casing (chat_lower): the console's
network_game_server_name_begins_with is static in network_server_manager.c,
and the harness takes chat_name_match without the game.

Checked: the Linux debug build, the chat harness (45), and the three-machine
system-link chat test (lines, channels, /mute, /unmute) as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:55:22 -07:00
Jonas VolmanandClaude Opus 5.5 c527105b31 Use testing's streamer mode for the text chat
Testing's streamer mode (90dd65ca, text_filter.c) hides offensive words in
every string drawn, through draw_string.c, so it already covers the chat's
lines and players' names. The chat's own filter goes: chat_filter.c and
its harness tests, game.streamer_mode (which also shared
HALO_STREAMER_MODE with streamer.enabled) and the STREAMER MODE row on the
VOICE AND TEXT CHAT page. Settings > Network Setup > Streamer Mode is the
one toggle. The README's Streamer mode section now names the chat.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:45:37 -07:00
Jonas VolmanandClaude Opus 5.5 da38822634 Re-land the in-game text chat (revert #397)
This reverts 4ac609f0, the revert of #395, so the chat is back as it was
merged: T, Y and Tab, team and vehicle lines, the host's /mute, the
VOICE AND TEXT CHAT settings page and the Android placement.

The one conflict is source/text/draw_string.c, where #395's streamer mode
and testing's (90dd65ca, text_filter.c) both hook the drawn text; testing's
hook is kept. #395's own filter (chat_filter.c, game.streamer_mode) is left
in place, unhooked, for the next commit to take out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:44:03 -07:00
MrBruh 90dd65cab4 Add a streamer mode that hides slurs, swearing and insults in text from other players, maps and mods 2026-10-18 15:16:12 +13:00
MrBruh efabb0d056 Merge pull request #397 from OpenCommunityEdition/revert-395-ingame-chat
Revert "Add an in-game text chat for network games"
2026-10-18 15:03:57 +13:00
MrBruh 4ac609f0df Revert "Add an in-game text chat for network games" 2026-10-18 15:03:39 +13:00
bnunu be521e2621 Merge pull request #395 from OpenCommunityEdition/ingame-chat
Add an in-game text chat for network games
2026-10-17 22:03:27 -04:00
Jonas VolmanandClaude Opus 5.5 75b2393ccf Merge testing into ingame-chat
testing's SOUND PROCESSING page and the chat's VOICE AND TEXT CHAT page
both split rows off Audio Setup in tools/port_settings.py. Both are kept,
as two category rows, sound processing first. The settings screens and
menus.json are regenerated from it, so they match testing's apart from the
chat's page, its row on Audio Setup and Controls Setup's COMMUNICATION
group.

Checked after the merge: the Linux debug build, the network format check,
the CI Linux test step (467 passed, 6 skipped), and the three-machine
system-link chat test, /mute and /unmute included.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 18:37:12 -07:00
Jonas VolmanandClaude Opus 5.5 8592780212 Let the host mute a player's text chat with /mute
The host types "/mute <player> [minutes]" in the chat (5 minutes when it
gives none, 1440 at most), or "/unmute <player>". The player is found as
the console's kick finds one: the whole name in either case, else the one
player whose name begins with what was typed; none, or more than one, is
said to the host alone. Every machine is told "Milenko muted for 5
minutes" and "Milenko unmuted", as a notice in the chat: a down line with
the new _chat_line_notice_bit, its text alone, drawn in a pale gold
(white in the white style).

The host keeps a mute by the player's name in ASCII, so one who leaves and
joins again under it is still muted, and its minutes by its own clock. It
drops a muted player's lines as they come up and tells that machine alone
"You are muted for N more minutes". The mute ends by itself. A client's
/mute is shown "Only the host can mute players" and never sent, and the
host takes a line that came up as a line, never as a command. The wire
format and the network version (29) are unchanged.

debug.network_test_chat now has the host mute the last player for a
minute, each machine say a line, the host unmute them and each say
another. On three Linux machines over system link the muted player's line
reached no one, they alone got the minutes left, every machine showed the
mute and the unmute, and the clients' own /mute was refused. A drawn
client showed the notices in the chat. The harness gains ten cases and
eleven negative controls for the command's parsing, the name match and
the mute's time (412 pass).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 18:02:07 -07:00
Jonas VolmanandClaude Opus 5.5 861b8faca7 Draw the text chat at the top left on Android, the newest line highest
On Android the touch controls' Down button and Move stick are at the bottom
left, where the chat lines went (above the motion sensor). There the lines
now start under the weapon's panel at the top left, in the same column, and
go down: the line being typed first, then the newest, the older ones below
it. At most four show at once, the typed one among them: a fifth would
reach the tops of the Up and Fire buttons. The other builds draw them as
before.

Checked on the x86 emulator (host:bloodgulch: the test's three lines, two
typed with the keyboard, a third being typed): the typed one and the three
newest show, clear of the touch controls. Linux debug build and the
harness (391) pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 18:02:07 -07:00
Jonas VolmanandClaude Opus 5.5 f9676b2962 Replace bad words in players' names wherever streamer mode draws them
Streamer mode replaced the words of its list only in the chat's lines and
names, but a player named with a slur showed it on the scoreboard, above
their head, in the kills and in the lobby. Every text the game draws or
measures now goes through the filter at draw_string.c's start, both
draw_string and draw_unicode_string, so names are replaced wherever they
show and the text's width matches what is drawn. A text's format codes (a
| and the character after it) are kept, each run between them filtered on
its own, so a code never joins the words beside it ("|t" before "its").
The same strings are drawn every frame, so a cache of 48 keeps it cheap; a
text too long for its entries is drawn as it is rather than cut short.
The chat no longer filters its own lines (the draw does), so the line you
type shows replaced too. "hoe" now shows as "rake": "garden hoe" held the
word it replaced.

debug.network_test_name names a machine's player in an automated test
game, to test this.

Checked: tools/harness/tests/test_chat_filter.py (the format codes, the
cache, every replacement passed through the filter again unchanged, with
negative controls); a host on Xvfb, streamer mode on, named "fuckhead",
with a client named "n1gger": the chat showed "fudgehead" and the
scoreboard "nugget" and "fudgehead", while debug.txt kept the names as
they were; the CI Linux test step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 18:02:07 -07:00
Jonas VolmanandClaude Opus 5.5 0ca693e681 Let a chat line go to everyone, the team or the vehicle, by Tab
The PC version's chat could talk to everyone, the team or the vehicle.
While a line is typed, Tab now turns it to each in turn ("Say:", "Say to
team:", "Say to vehicle:"), as T and Y still start a line to everyone and
to the team. A vehicle's line goes to the players riding in the vehicle
the sender rides in: the host finds each player's outermost parent object
(a unit in a seat is its vehicle's child), so any map's vehicles count,
Custom Edition maps' too, and a sender on foot sees their line alone. The
host now always sends a line back to its sender's machine. The channel is
a second bit of the line's flags, so the wire format is unchanged.

debug.network_test_chat has the host seat the last two players in one
vehicle and every machine say a line to it.

Checked: tools/harness/tests/test_chat_logic.py (who hears a vehicle's
line, the channel through the flags, with negative controls); three
machines on system link, the two clients seated in one Warthog seeing
each other's vehicle lines and the host's, on foot, seeing only its own;
a host on Xvfb turning a line to the vehicle with Tab, and typing a line of
f's without meleeing; the CI Linux test step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 18:02:07 -07:00
Jonas VolmanandClaude Opus 5.5 9fc9f4e7a4 Let the text chat be drawn all white, as Custom Edition
display.chat_style (CHAT TEXT on Audio Setup's VOICE AND TEXT CHAT page)
picks the chat's colours: "default", the player's name in their team's
colour and the text a pale blue, as the PC version drew them, or "white",
all of it white, as Custom Edition. The line typed follows it too.

Checked on Xvfb: a host's lines drawn white, and the page with its new
row; the CI Linux test step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 18:02:07 -07:00
Jonas VolmanandClaude Opus 5.5 8a899e7650 Give Audio Setup a VOICE AND TEXT CHAT page with STREAMER MODE
Streamer mode needs a row a player reaches in a game, from the pause
menu's SETTINGS. Network Setup is not offered there and has no room on
desktop, and Audio Setup has none either, so its VOICE CHAT and VOICE
VOLUME rows move to a page of their own, VOICE AND TEXT CHAT, opened from
Audio Setup as Video Setup opens GRAPHICS, with STREAMER MODE under them.

The screens are port_settings.py's settings_files and menus.json lists the
new one, both written as tools/ce_menus.py writes them (it needs the PC
version's tags, so only these were written: the other screens come out
unchanged). Checked on Xvfb: both screens drawn, STREAMER MODE changed with
the keyboard and OK writing streamer_mode = true to config.toml; the CI
Linux test step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 18:02:07 -07:00
Jonas VolmanandClaude Opus 5.5 8413225a55 Add streamer mode to the text chat
A player who streams needs the chat on screen to stay clean whatever the
others type. game.streamer_mode (off by default) shows the slurs, swearing
and sexual words in the chat's names and lines as harmless, funny ones
(fuck: fudge, cunt: chop stick, faggot: flurry), keeping their case and
fitting their endings (fucking: fudging, faggots: flurries). Only what
this machine shows changes: the lines go to every machine as typed, so the
network is unchanged, and debug.txt keeps them so
(port/linux/game/chat_filter.c).

It finds the words in the spellings that dodge a plain list: digits and
signs for letters (n1gg3r, c00n, $hit), l for i, q for g, k for c, v for u,
ph for f and * for a vowel; letters and endings typed more than once; a
word spelled out with separators (n i g g e r); and a few words inside
others (sonofabitch, thundercunt). Each word may stand alone, with endings,
at a word's start or anywhere, and harmless words that hold one are kept
as typed (Scunthorpe, snigger, Niger, assassin, cocktail, rapper, FAQ).

Checked: tools/harness/tests/test_chat_filter.py (8 cases with 11
negative controls); every word of the repository's documents and of an
English dictionary of 370,000 words (dwyl/english-words) run through the
filter, the harmless ones it changed kept, and a community-submitted list
of 2,800 entries used to find gaps (neither list is in the repository: the
community one has no licence); a host on Xvfb with streamer mode on,
lines typed on its keyboard shown replaced while the client logged them
as typed; and the CI Linux test step.

No Settings row yet: Network Setup has no room on desktop and is hidden
from the pause menu's SETTINGS, so where the toggle goes is still to be
chosen; config.toml and HALO_STREAMER_MODE set it meanwhile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 18:02:07 -07:00
Jonas VolmanandClaude Opus 5.5 0792a3aecb Draw the chat lines with a soft shadow
The text drawing gives every string a hard shadow, a black copy a unit
down and right; the PC version's chat had a softer one, further out. The
chat lines now draw theirs as translucent black copies one to three units
down and right, which add up to a dark middle with a soft edge, and the
text's own shadow clear. Checked on a host drawn on Xvfb against the
reference screenshot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 18:02:07 -07:00
Jonas VolmanandClaude Opus 5.5 3efe79a38d Add a text chat to network games
Network games had voice chat but no way to type to the other players, as
the PC version's chat did. T (controls.chat) types a line to everyone and
Y (controls.team_chat) one to the team; Enter sends it, Escape cancels it.
The lines are drawn above the motion sensor with the HUD's font: the
player's name in their team's colour (blue without teams), then the text
in a pale blue, and fade after ten seconds (port/linux/game/network_chat.c).

A client sends its line to the host (_distributed_message_chat_up), which
takes it only from the client's own stream and one of that machine's
players, five at once and one each 1.5 seconds after, its text cut to 63
characters of printable ASCII, and sends it on to the machines that hear
it (_distributed_message_chat_down): all, or a team's line those with a
player of the team. Older builds drop both kinds as unknown, so the
network version stays 29, as for the vote kick; network_format.txt
records the two kinds.

While a line is typed the keyboard drives no player, as with the console
(xinput_sdl.c), nor voice chat's push to talk or the debug keys. Controls
Setup gets a COMMUNICATION group with PUSH TO TALK, CHAT and TEAM CHAT
(the controls screen's spinner strings changed by hand to what
port_settings.py writes: ce_menus.py needs the PC version's tags).

Checked: tools/harness/tests/test_chat_logic.py (the text's cleaning, the
host's rate, who hears a team's line, the fade, with negative controls);
the CI Linux test step; three machines on system link with
debug.network_test_chat (new), every machine showing the lines to all in
one order and a team's line only on its team's machines; and a host drawn
on Xvfb with lines typed on its keyboard through xdotool (sent on Enter,
not on Escape, the player still while typing).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 18:02:07 -07:00
MrBruh e393fbe716 Draw the main menu's MODS item in the redraws' own letters, spaced and glowing as the other items are 2026-10-18 12:24:15 +13:00
Stefan MaierandClaude Opus 5.5 5530d19769 Merge fix/vr-weapon-arm: each weapon held so that its hand lands on the real one, the scope's disc with it
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 18:00:14 +02:00
Stefan MaierandClaude Opus 5.5 603ad6b543 Roadmap: each weapon held so that its hand lands on the real one
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 18:00:13 +02:00
Stefan MaierandClaude Opus 5.5 322146f3cb Scope: the disc moves with the weapon from its old fixed place in the hand
The disc was placed for the weapons at the old fixed place (7 cm above
the hand, 22 cm ahead of it): with the pistol now held 20 cm further
back it sat at the muzzle. It moves as the weapon did (forward and up;
sideways the barrel places it, as before).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 17:54:43 +02:00
Stefan MaierandClaude Opus 5.5 a31c482d5a Hold each weapon so that its hand lands on the real one
The first-person model hung from a camera at one fixed place from the
weapon hand's controller (18 cm behind, 10 cm left, 14 cm above), but
each weapon's animations hold the hand elsewhere from that camera: the
pistol's wrist came out 15 cm before the controller (22 cm before where
the free hand's wrist is, 7 cm behind its grip), the plasma pistol's 11,
the assault rifle's 8 cm aside and 10 cm low; the weapon arm looked
longer, reaching forward. Measured on the Quest 3 (2026-10-17).

Now the wrist as the weapon is held ready is kept for each weapon
(eased, the last eight weapons), and the camera put so that it lands
where the free hand's does: 7 cm behind the grip, along the aim. The
model, its barrel, the laser and the shots move with the camera, as
before. A weapon not yet seen is held at the old place for its first
frame. vr.weapon_hand_forward/_right/_down (0 by default) move the hand
on the weapon; vr.weapon_forward/_right/_down, the old fixed place, are
gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 17:41:48 +02:00
Stefan MaierandClaude Opus 5.5 e543fe53e0 Merge merge/upstream-oct18: OpenCE's high-resolution Xbox HUD, pause menu artwork, client-side mods, resampler, unsteered vehicles
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 16:31:03 +02:00
Stefan MaierandClaude Opus 5.5 76fbf67334 Merge OpenCE upstream: the high-resolution Xbox HUD completed, pause menu artwork, client-side mods, the resampler setting, unsteered vehicles' netcode
README.md keeps Ringfall's; OpenCE's README is copied to
docs/OPENCE_README.md as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 14:54:00 +02:00
MrBruh 7524ba6168 Merge pull request #391 from OpenCommunityEdition/testing
Testing
2026-10-18 00:53:59 +13:00
MrBruh 5a1b8a4094 Set the resampler from a Sound Processing screen of Audio Setup (#245)
Audio Setup had no room for another row (nine on the desktop, as Video and
Network Setup), so Reverb moves with the new Resampler row (SINC, the
default, or LINEAR) to a SOUND PROCESSING screen opened from Audio Setup,
as Video Setup's categories are; the devices and volumes stay on Audio
Setup. The setting is read again when it changes, so the TODO saying it
could only be set in config.toml goes, and the help, the setting's comment
and the README say what linear does: it lets more of the images above a
sound's rate through, which is what sounds brighter.

The mixer's harness test takes the resampler from dsound_sdl.c, which now
calls config_string: it stubs it, and a new case checks linear mode against
linear interpolation worked out in double precision at every rate (with a
negative control). Checked: the harness and touch menu tests, test_web_build,
ninja linux, android and web, and both screens drawn on Xvfb with
HALO_AUDIO_RESAMPLER=linear; the menus regenerated with tools/ce_menus.py
change only the two audio screens.
2026-10-18 00:13:26 +13:00
MrBruh b718ad8468 Merge pull request #245 from WhiteI3ulL/resampler-linear-interpolation
Add linear resampler for brighter global audio
2026-10-18 00:03:17 +13:00
MrBruh af07c05d21 Say which shaders a mod's models can be drawn with
A mod's model can name a shader of any group the map has, but
shader_transparent_chicago_extended is Gearbox's addition for Halo PC and
the Xbox game has none: the note says to use a transparent chicago shader
instead, and that a Custom Edition map's chicago extended shaders are
converted to chicago shaders as the map loads.
2026-10-17 23:53:28 +13:00
MrBruh 9a8382f1ae Let mods replace models whose nodes, markers and regions match the map's 2026-10-17 23:49:30 +13:00
Stefan MaierandClaude Opus 5.5 7da699b035 Merge feature/disc-languages: the European disc in Spanish and Italian imported, in the headset's language
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 10:39:31 +02:00
Stefan MaierandClaude Opus 5.5 c5c604bb95 Import the European disc in Spanish and Italian, in the headset's language
That disc has no maps folder but one for each language, maps_es and
maps_it, and the disc image setup stopped: "The disc image has no maps
folder: it is not a Halo disc." The Quest's (XisoExtractor.java) and
the Steam Frame's (steam_frame_xiso.py, RINGFALL_LANGUAGE or the
system's) now take the folder of the device's language, else English's,
else the first, and write its language into maps/language.txt. The game
takes that as the Xbox's language (XGetLanguage) while game.language is
unset, so the menus speak it too. (Reported for ChupathingyCE, another
OpenCE fork, in its issue 145; written anew here.)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 10:39:30 +02:00
MrBruh 01797c0434 Merge pull request #388 from OpenCommunityEdition/testing
Merge testing
2026-10-17 17:54:12 +13:00
MrBruh 7059096e1b Merge main into testing, bringing in the pause menu and HUD fixes of #378 2026-10-17 17:43:40 +13:00
MrBruh 54c2d31c97 Show the Mods list as a resource pack list, the top mod winning 2026-10-17 16:19:49 +13:00
MrBruh dd16971b6c Add client-side mods with a Mods menu, load order and incompatibility checks 2026-10-17 16:19:49 +13:00
Guven 7c942e9a57 Merge pull request #384 from lukerb52/main
Added reference to AUR version to the README
2026-10-17 00:40:26 +01:00
bnunu 25697bbaa1 Merge pull request #378 from chestahh/opence-ui-fixes
Fix regression in pause menu & The Maw timer, restoring legacy Xbox behaviour. Fix incorrect Edit Gametypes positioning
2026-10-16 17:49:30 -04:00
bnunu a5e6102413 Merge pull request #372 from innerhat-dev/unsteered-vehicle-path
Carry a vehicle no player drives along the host's positions
2026-10-16 17:49:16 -04:00
chestahh b9e393c00c Apply high-resolution HUD and text settings without reloading maps
Recheck committed font settings before cached font selection, including when
high-resolution text was disabled at first use. At each frame boundary,
invalidate only cached HUD/title bitmaps affected by a changed setting so
both enabling and disabling rebuild the appropriate texture on next use.
Keep decoded replacements, font atlases, world textures and menu Apply/Cancel
semantics intact. This corrects shared cache behavior; it adds no MCC data,
map-family routing or networking/simulation changes.

Validation: Windows portable release build and 370 focused production-code,
HUD, menu and MCC checks passed. Tests cover repeated toggles, disabled text
at startup, font scale/cache reuse, selective bitmap refresh and Apply/Cancel.
Android and Web targets were attempted but are unavailable in this Windows
build graph. Live Custom Edition validation is limited by unavailable maps.

(cherry picked from commit ddeb650d82a73b570e182ff2bfd87e3cc3daa98d)
2026-10-16 21:42:21 +01:00
Constantine Luke Matthew Bubar c54185c088 punctuation 2026-10-16 16:30:26 -04:00
Constantine Luke Matthew Bubar 6b214c63a7 added reference to AUR version to the README 2026-10-16 16:27:22 -04:00
bnunu 19d84ce775 Merge pull request #383 from chestahh/opence-hud-fixes-2
Fixes to latest high-res HUD commit
2026-10-16 15:56:39 -04:00
chestahh 5f4beed534 Match high-resolution controller glyphs to the Xbox HUD
Match visible A/B/X/Y and supplied trigger/thumbstick artwork to the Xbox
bitmap bounds and center prompts around the visible glyph. Keep Black and
White unchanged. Preserve all three stick press frames and rename the
Menu Gems display label to Dark Gems without changing saved values.

Use the original HUD ONE/INVSRCALPHA blend for sticks and triggers, with
premultiplied asset edges, to avoid dimming them by prompt alpha twice.
Increase the existing high-res texture table so every new frame loads.
All changes remain in the existing high-res feature and menu wiring;
low-res fallbacks and unrelated icon indices retain their existing paths.

Checked: 358 focused HUD/menu/MCC tests on testing, Windows portable release
builds on testing and a clean upstream checkout, and hidden Xbox a10
captures for size, spacing and brightness. Android/Web targets are absent
from this Windows build graph. No protocol or simulation changes.

(cherry picked from commit c21ec87e6fbc6eb3370597dbdbc92cbe5464bb61)
2026-10-16 20:45:36 +01:00
bnunu 17a4c84a0a Merge pull request #326 from innerhat-dev/screen-fog-fov-testing
Screen fog stays on the canopy when the field of view is widened
2026-10-16 15:29:45 -04:00
bnunu 2c849ca30c Merge pull request #380 from chestahh/opence-hud-fixes
Fix low-res bitmaps overlap issue with High-Res HUD, as well as general high-res bitmap issues
2026-10-16 15:25:40 -04:00
chestahh c794406c20 Restore authored opacity for high-resolution HUD buttons (#380)
Xbox button definitions override prompt text with full-opacity colors.
Honor that existing override for Classic, Gems and Menu Gems while
retaining text alpha for icons without it. Embedded artwork supplies RGB.

Validated original Xbox a10/Blood Gulch definitions, opaque asset
interiors, Windows portable release compilation, and production-function
checks for all three styles, full/partial alpha, fallback, and cleanup.
Android/Web targets were unavailable in the Windows configuration.
No MCC/fork code, tests, documentation, assets or workflow changes included.

(cherry picked from commit ce6cf74623912bf442efcfb2e0d6849a84f86011)
2026-10-16 20:08:22 +01:00
Stefan MaierandClaude Opus 5.5 8662777f11 Merge feature/frame-updater: the Steam Frame's launcher updates the game
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:53:11 +02:00
chestahh f80bd15210 Add supplied Xbox Gems and retain Menu Gems as a third style (#380)
Use the supplied transparent A/B/X/Y artwork for Gems, fitted within the existing button bounds. Preserve the original menu redraws as Menu Gems and share their Black/White glyphs between both gem styles. Classic remains the default. Keep the source PNGs and deterministic asset conversion, and reserve enough placeholder entries for all styles across map changes.

Validated the Windows release build and 269 focused checks, including generated texture bounds, centering, repeatable output, all three native/MCC style transitions, disposal, and menu row spacing. Existing menu glyph PNGs remain unchanged.

(cherry picked from commit 5f66c51927f09c31095e224b0cbfbeb8aeef0bb8)
2026-10-16 19:44:57 +01:00
chestahh 4773c351d2 Add Classic and Gems high-resolution HUD button styles (#380)
Default high-resolution gameplay A/B/X/Y/Black/White buttons to plain Xbox faces made from the existing redraws. Retain the shaded faces as Gems and keep glyph geometry unchanged. Show Button Style below High-Res HUD only while enabled, following pending edits and closing the row gap when hidden. Recreate owned glyph placeholders on style changes and dispose them on map unload.

Validated the Windows release build, hidden menu captures with both styles and HUD off, isolated Xbox gameplay, and 268 focused production-code and menu checks including repeated visibility/focus and glyph lifecycle changes. Android and Web builds were attempted but their targets are unavailable in this Windows configuration.

(cherry picked from commit e8f36b8aa2b906a738856ac198efbd591cc9bcc0)
2026-10-16 19:44:33 +01:00
Stefan MaierandClaude Opus 5.5 e24df8ead9 The Steam Frame's launcher updates the game
The game's own updater (the Quest's) cannot on the Steam Frame: Lepton
installs the APK in the game's folder there at every start. So the
launcher does it: while the game runs it asks GitHub for the latest
release, fetches a newer one's Steam Frame bundle into update/, checks
it (its build.txt is the release's number), and puts the APK and the
launcher in place as the next start begins, before Lepton runs the APK.
update.auto = false in config.toml stops it, as on the Quest; a build
with no number (build.txt, which the bundle now carries and the
installer copies; 0 for local builds) never looks; without the internet,
or when anything fails, nothing changes. The in-game updater no longer
starts there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:40:26 +02:00
Stefan MaierandClaude Opus 5.5 7a10a413c8 Merge feature/vr-refresh-auto: the eyes drawn one by one (multiview's doubled pictures online), the refresh rate stepped down while frames come late
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 18:57:18 +02:00
chestahh e7d334c66a Match the legacy size and spacing of gameplay button glyphs
Increase the existing Xbox faces from 20 to 26 HUD units, centre them on the text capital midpoint and compensate for the text renderer's following-run inset. Preserve their original artwork and split-screen scale. Checked the portable Windows OpenCE build and hidden 1280x720 gameplay captures; the Press X to prompt has balanced visible gaps. Retain the repository's rocket reticle unchanged at the user's request.
2026-10-16 16:17:40 +01:00
chestahh f8b3eecb37 Complete the high-resolution Xbox HUD
Upscale the original vehicle/passenger labels, warnings, reload dots and
messaging sheets without changing their glyph layout or sprite coordinates.
Use the existing Xbox controller redraws for A/B/X/Y/Black/White prompts;
other controller symbols retain the original Xbox art through upscaling.
Match the existing high-resolution vehicle meter contour to remove the
double wrench. Restore the rounded diamond sniper reticle, contain the
flashlight fill and assign discrete thresholds to whole ammunition bullets.

The HUD module owns interpolation and tag selection. A narrow texture-cache
hook is needed to obtain decoded original pixels and invalidate generated
textures with their source; the messaging hook selects the existing button
art. Both respect the High-Res HUD setting. MCC's separate draw/texture
paths remain intact. No MCC bitmap data is embedded; label interpolation
was retained at the user's request, with its limited sharpness acknowledged.

Validated Windows portable release build; 246 focused HUD tests; 15,360
real GPU ammunition checks; hidden Xbox HUD captures enabled/disabled;
Xbox/MCC/Xbox transitions at 30 and 120 FPS with no exceptions. Player health
and sniper zoom-label pixels outside the targeted regions are unchanged.
Android/web targets are unavailable in this Windows build configuration;
Custom Edition gameplay data is unavailable, so that path was inspected.
No simulation or network format changes; network version stays unchanged.

(cherry picked from commit 5398553e4a95d7d68376acf023e6ca671066a614)
2026-10-16 16:07:49 +01:00
chestahh adccd47fe0 Restore online pause gradients and campaign Settings
Apply the existing campaign gradient to the three- and four-button online
pause artwork, retaining the translucent footer. Admit full-screen Xbox
network campaigns to the existing Settings insertion while keeping local
split-screen unchanged and preventing network Settings from pausing time.

Checked Windows release build, pause-art and campaign-context harnesses,
and hidden two-peer Xbox multiplayer and co-op sessions. The co-op client
can select and open Settings. Android/web builds are unavailable in this
Windows configuration; no wire-format or simulation contract changed.

(cherry picked from commit 04cd1e25e910c5234fee323033b1efb8c71e8e0a)
2026-10-16 14:35:52 +01:00
chestahh 5e228ea9db Restore pause menu artwork, timer digits and multiplayer menu spacing
Restore the pause header and footer bands while retaining Settings, the
campaign body gradient, and the Xbox footer alpha of 191/255. Initialize
the scripted timer scaling flags so stack data cannot shrink its digits.
Move Edit Gametypes and its divider inside the connected panel.

This implementation-only upstream contribution excludes MCC, regression
tests, documentation, optional regeneration commands and workflows. The
complete fixes and tests remain on GulchCE testing and the local validation
branch. Original cherry-picked changes below were combined and scoped to
OpenCE; authorship is retained.

Validated: portable release Windows builds on both bases; 25 upstream
art/timer tests and 52 GulchCE tests before upstream test exclusion; hidden
Xbox and MCC pause captures, both timer HUD settings, and multiplayer menu
captures. Final generator matches all committed pause SVGs. Android/web
targets are unavailable in this Windows configuration; online co-op was
not replayed.

(cherry picked from commit a0c20572f8bfa6af0d2f94c57abef32357750bf0)
(cherry picked from commit c76a3a596f7dbe8ced581b2e6c759aae0baababb)
(cherry picked from commit f0804e1e5c857db6dc84ff16c72d3f8b8abf95a2)
(cherry picked from commit bff920f6a5ec614caf98ff5e8555a0320b0a29f1)
(cherry picked from commit b1b59eb7237d901dba10aa0b5187b184f3f44a7b)
2026-10-16 04:43:57 +01:00
justinfilip 7cf24ae589 Stop carrying an unsteered vehicle once the host says it has stopped. 2026-10-15 12:35:57 -05:00
justinfilip e5e97c0526 Carry a vehicle no player drives along the host's positions. 2026-10-15 11:08:46 -05:00
Jonas Volman 2e376b992e Reconcile approved single-pixel S3TC decoder (+1 function, zero losses)
Apply only the Q-UP25 delta from de53e9bf, preserving both original-defect BUG comments and inferred length spelling. Fresh canonical checkpoint: +493 meaningful / +496 padded code and +76 data, zero losses; 1663 tests pass, 5 skipped, 159 subtests. Whole-board W3 unchanged at 4091; only s3tc.obj changes. Encoder and object admission remain held.
2026-10-13 17:02:57 -07:00
Jonas Volman ff2e07fac1 Replace parked stristr assembly with exact ordinary C (zero credit)
Use the natural C implementation from punpckhdq/halo 745c66b8. All 621 object fingerprints and inherited exact verdicts are unchanged; the single asm-implemented park is retired. Fresh full checkpoint: 1663 tests pass, 5 skipped, 159 subtests. Current scoring already counted the assembly: no additional function or byte credit.
2026-10-13 16:59:49 -07:00
justinfilip 521c44f862 Keep screen fog on the canopy when the field of view is widened. 2026-10-12 20:50:57 -05:00
WhiteBulL 0f3ceb9eab refactor: worked on comments a little bit more 2026-10-10 19:39:39 +03:00
WhiteBulL 39fcfdafaf feat: resampler linear mode
Fixed read past a buffer issue and
untied previous solution from reverb.
Also added config variable audio.resampler
(but no UI implementation since currently
there is no more space for that option).
2026-10-10 17:21:08 +03:00
WhiteBulL 4afb59660a Disable global audio muffling when reverb is false
previous incorrect implementation
2026-10-10 14:20:09 +03:00
Jonas Volman 1a98636fc0 Replace remaining BSS pragmas with verified zero initialization 2026-10-09 05:14:47 -07:00
Jonas Volman 14ec9fc4a2 Remove three no-op BSS pragma blocks without byte changes 2026-10-09 04:23:56 -07:00
Jonas Volman 73677cf26d Remove empty header stubs and unused shader-stage enums
Zero credit: remove 51 guard-only headers, 50 project-list entries, four useless includes and two unused enum definitions. Preserve source assertion locations and both repositories existing READMEs. Clean rebuild, unchanged 8252-row strict snapshot, unchanged code/data credit and admissions, 1663 tests plus 159 subtests pass. Disclose 76 compiler-local label renumberings and two warning source-line shifts; runtime sections and relocations remain identical. No private assets or scratch tooling published.
2026-10-09 02:58:59 -07:00
Jonas Volman b43c70168a Merge reviewed byte-inert cinematic cleanup into halo-1 main 2026-10-06 15:41:45 -07:00
Jonas Volman a71a4ac135 Apply approved byte-inert cinematic shadow cleanup; record upstream exclusions 2026-10-06 15:40:46 -07:00
Jonas Volman 1b25ea9a37 Merge three independently verified biped and avoidance function gains into halo-1 main 2026-10-02 17:05:13 -07:00
Jonas VolmanandClaude Opus 5.5 fccf773db3 Reconcile actor test avoidance match (+1 function, +743 meaningful bytes)
Independently verified donor c694a43d4e3343eedebac48fe6f2b6052f679778 under Q-UP21 after the separately gated helper restoration. Fresh clean full gate against 09255f0e: cumulative +3 functions, +1238 meaningful / +1264 padded bytes, zero exact losses; 7483 of 7573 Halo functions, 1603071 of 1770166 code bytes, data 3311591 and objects 396 unchanged, parks 59 valid. Tools tests: 1663 passed, 5 skipped, 159 subtests. Full /W3 and unit /W4 multisets unchanged. All twenty cast ablations and layout controls passed; helper identity, real-call schedule and 284 ordered provider-link probes passed. The disclosed residual caller changes 4144 to 4128 bytes.

Retain the narrow per-function parentheses ruling, inferred scopes and collision_result placement, S6 correction, single-variable alternative and compiler-version caveat. No general hold, object admission, research candidate, scorer/verifier or compiler flag change. Fresh canonical receipts and source-policy boundaries are recorded in docs/object_matching_logs/biped_avoidance_canonical_reconciliation_20261002.md. Unrelated README changes and research preserved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 17:04:04 -07:00
Jonas VolmanandClaude Opus 5.5 201442d377 Restore actor avoidance transform helper calls with the approved guard removal (zero credit)
Independently reconciled donor 41d3b992a5e5c0fbbb5f76c476d27f9c68031e82 without candidate F. Fresh clean full gate: no additional gains or losses, all prior function sections identical, one new uncredited 48-byte SELECT_ANY point_from_line3d copy identical to January, unchanged real-call schedule, warnings, data and object credit. Current-compiler cast ablation and layout controls pass, 284 ordered provider-link probes pass, 1663 tools tests pass. Six inferred casts and three byte-inert per-site parentheses are disclosed; all other actor_moving holds stay in force.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:49:29 -07:00
Jonas VolmanandClaude Opus 5.5 164026a7f1 Restore biped_jump helper call under the admitted two-site vector view (zero credit)
Independently reconciled donor 82ea0d5e9bcee8a1a48775aab6f0c351f5ee5e2a after the biped packet. Fresh clean full gate: zero additional gains or losses, all normalized objects unchanged, parks valid, warnings unchanged and 1663 tools tests passed. Current-compiler cast ablation is byte-inert; nine layout/alignment checks pass and a deliberately false control fails. Both current selected-provider link orders pass. Cast spelling remains inferred; no new parenthesis or general view-cast exception.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:47:08 -07:00
Jonas VolmanandClaude Opus 5.5 606d0a2458 Reconcile approved biped sight and placement matches (+2 functions, +495 meaningful bytes)
Independently verified donor edbdbf729e9b7c0c09557f1ccb45f94ad6c8a06a against canonical 09255f0e: fresh clean full build, strict whole-board sweep with zero losses, parks 61 to 59, unchanged data/object credit and warnings, 1663 tools tests passed. Folded helper identity and all selected-provider pair links passed. Per-site parentheses, descriptive constants and coupled jumping form remain narrowly disclosed as inferred; no general hold lifted. Unrelated canonical biped_accelerate shape and README edit preserved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:44:38 -07:00
Jonas Volman 862c393cd8 Merge five independently verified function gains into halo-1 main 2026-10-02 00:45:39 -07:00
Jonas Volman 09255f0e78 Reconcile main static storage: +1 exact function, zero losses
Approved coherent packet 41640b4d, independently clean-built after transport and plane-helper reconciliation. +538 meaningful / +544 padded bytes; main 95/95. Replace the invented packed aggregate with January's separate storage, add exactly eight private symbol rows and retire the frame-rate park. Names, six static scopes and exact main_game_render placement remain disclosed as inferred. Full stable sweep, parks, admission, fake scan and 1663 tools tests pass; no new warnings, data, objects or providers. Cumulative reconciliation +5 functions, +2121 meaningful / +2144 padded bytes, zero exact losses. Preserve dirty README and unrelated research. No PCH, scorer, verifier, status or held-source changes. Detailed independent receipts and limits recorded in docs/object_matching_logs/upstream_tmp_reconcile_20261002.md.
2026-10-02 00:44:12 -07:00
Jonas Volman 38fdd8cb24 Reconcile plane-helper pair: +2 exact functions, zero losses
Approved coherent packet 78294dd3 (Q-UP1), independently gated on canonical. Remove only outer helper parentheses, preserving /Od argument order, together with the attested float argument repair in item_accelerate. Items object remains identical. +1126 meaningful / +1136 padded bytes; data/object/provider sets unchanged. Fresh full build, stable sweep, admission, fake scan (26), parks and tests (1663 passed, 5 skipped, 159 subtests) pass; no new warnings. Retire only two exact parks. Remeasure four residuals on canonical, preserving previous measurements/rejection history: frustum 88.8082->90.11173; pill 97.21691->98.086395; polygon clip 80.76382->82.37186; lens flares 99.29274->99.198944 (disclosed decrease). Do not import unrelated lane fidelity changes or held parentheses/DD2/DD3/casts.
2026-10-02 00:34:46 -07:00
Jonas Volman 25f719236d Reconcile transport pair: +2 exact functions, zero losses
Independently reconciled lane e191c0f1 under Q-UP1. Header and definition return long together; January startup-store and cleanup topology retained. Retire only the two verified parks. Fresh clean build and stable sweep: +457 meaningful / +464 padded bytes, 0 losses, unchanged data and object status. Affected /W4 warning counts unchanged; inherited WSA narrowing moves to initialization. Admission/parks/fake scan pass; tools 1663 passed, 5 skipped, 159 subtests. No new providers or section/data changes beyond the two intended functions. README and unrelated research preserved.
2026-10-02 00:30:24 -07:00
Jonas Volman 356a8d412c Merge verified triangle and nav-point matches into halo-1 main
Preserve main README unchanged. Source/config/verification tree equals canonical 5e55d8f8; two exact Halo gains, zero regressions, +1998 meaningful bytes. No object or data-credit changes.
2026-10-01 19:41:01 -07:00
Jonas Volman 5e55d8f83a Reconcile exact triangle predicate and nav-point renderer (+1998 Halo bytes)
Cleaned punpck donor reconstructions; strict bytes and all relocations match January. Fresh cold whole-board gate: 2 gains, 0 losses; 1663 tests pass. Retire the two parks. Disclose six HUD /W3 C4244 narrowing warnings and six identical shared-inline helper copies; provider links pass both orders. No object or data credit changes; inherited README and research excluded.
2026-10-01 19:40:22 -07:00
322 changed files with 23662 additions and 3731 deletions

No files matched your search

+40 -1
View File
@@ -371,7 +371,6 @@ jobs:
with:
sparse-checkout: |
port/android
port/third_party/extract-xiso
tools
- uses: actions/download-artifact@v4
@@ -469,6 +468,7 @@ jobs:
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
contents: write
pages: write
id-token: write
concurrency:
@@ -483,6 +483,45 @@ jobs:
name: halo-web-release
path: site
# The game's wasm, most of a build, from jsDelivr rather than GitHub
# Pages, which serves 100 GB a month: the wasm alone in a commit of the
# web-wasm branch (pushed over the last, so the repository keeps one),
# which jsDelivr serves by its hash (with CORS; GitHub's release
# downloads have none, so a page cannot fetch them), and version.json
# names that copy and its SHA-256. The site's service worker takes the
# wasm from there, checked, and from the site only when it cannot
# (port/web/site/sw.js). A failure here leaves the site serving it.
- name: The wasm on jsDelivr
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
run: |
sha256="$(sha256sum site/halo.wasm | cut -d' ' -f1)"
mkdir wasm
cp site/halo.wasm wasm/
cd wasm
git init -q -b web-wasm
git -c user.name="github-actions[bot]" -c user.email="41898282+github-actions[bot]@users.noreply.github.com" \
add halo.wasm
git -c user.name="github-actions[bot]" -c user.email="41898282+github-actions[bot]@users.noreply.github.com" \
commit -q -m "The web build's wasm (build $GITHUB_RUN_NUMBER, $GITHUB_SHA)"
git push -q --force "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" HEAD:refs/heads/web-wasm
url="https://cdn.jsdelivr.net/gh/${GITHUB_REPOSITORY}@$(git rev-parse HEAD)/halo.wasm"
# (jsDelivr fetches it now, and it must be the same file: it serves
# files of at most 20 MB from GitHub)
curl -fsSL --retry 6 --retry-delay 10 --retry-all-errors -o served.wasm "$url"
echo "$sha256 served.wasm" | sha256sum -c -
cd ..
python3 - "$url" "$sha256" <<'EOF'
import json, sys
with open("site/version.json") as file:
stamp = json.load(file)
stamp["wasm"] = {"url": sys.argv[1], "sha256": sys.argv[2]}
with open("site/version.json", "w") as file:
json.dump(stamp, file, indent=1)
EOF
echo "the wasm from $url"
- uses: actions/configure-pages@v5
- uses: actions/upload-pages-artifact@v3
+18 -7
View File
@@ -15,8 +15,8 @@ end.
| Path | Contents |
| --- | --- |
| `source/` | The game: 466 C files of the decompilation, compiled as they are with few changes (each marked, below) |
| `port/linux/src/` | The platform layer the three ports share: Direct3D 8 on OpenGL (`d3d8_gl.c`), DirectSound and XInput on SDL3, files, threads, memory, sockets, settings (`port_config.c`), internet play (`p2p*.c`) |
| `port/linux/game/` | The port's game-side code, compiled as the game's sources are: the distributed netcode (`network_*.c`), co-op, map sharing (`map_share*.c`), the menus (`menu_tags.c`, `menu_functions.c`), Custom Edition maps (`custom_edition_*.c`, `cache_file_formats.c`), the tag validator (`tag_validate.c`, `tag_schema_*.c`), touch, FOV and more |
| `port/linux/src/` | The platform layer the three ports share: Direct3D 8 on OpenGL (`d3d8_gl.c`), DirectSound and XInput on SDL3, files, threads, memory, sockets, settings (`port_config.c`), the mods folder (`port_mods.c`), internet play (`p2p*.c`) |
| `port/linux/game/` | The port's game-side code, compiled as the game's sources are: the distributed netcode (`network_*.c`), co-op, map sharing (`map_share*.c`), the menus (`menu_tags.c`, `menu_functions.c`), Custom Edition maps (`custom_edition_*.c`, `cache_file_formats.c`), the tag validator (`tag_validate.c`, `tag_schema_*.c`), mods (`mods.c`, `mod_*.c`), streamer mode's word filter (`text_filter*.c`), touch, FOV and more |
| `port/linux/include/` | The port's headers: the limits (`halo_port_limits.h`, with `HALO_PORT_NETWORK_VERSION`), capacities (`halo_port_capacity.h`), the prefix header, `halo_linux_source_fixups.h` (port functions the game's sources call) |
| `port/include/xdk/` | Stand-ins for the Xbox SDK's headers (declarations only) |
| `port/windows/` | The Windows build's own files (`win32_*.c`, headers) |
@@ -26,7 +26,7 @@ end.
| `port/third_party/` | Vendored libraries, each with a README naming its upstream, version and checksum |
| `port/tools/` | Stand-alone tools (`cache_file_report.c`) |
| `tools/` | Build scripts (`linux_build.py`, `windows_build.py`, `android_build.py`, `web_build.py`, `ci_build.py`, `web_serve.py`), generators (`ce_menus.py`, `port_settings.py`, `hud_assets.py`, `title_assets.py`, `embed_assets.py`, `xdk_headers.py`) and tests |
| `docs/` | Longer design notes (`custom_edition_caches.md`) |
| `docs/` | Longer design notes (`custom_edition_caches.md`, `mods.md`) |
| `pgo/` | Profile-guided optimisation profiles |
| `assets/` | Game data for local runs (gitignored: `maps/`, `custom_maps/`) |
| `.github/workflows/` | CI: builds every platform on each push, publishes releases from `main` |
@@ -84,6 +84,7 @@ setting (`game.custom_edition`), checked at run time with
```sh
python -m pytest -q tools/harness tools/test_touch_menu.py # the engine's functions in a fake world
python -m pytest -q tools/test_linux_port.py # the build, the maps' checks (needs assets/maps)
python -m pytest -q tools/test_mods.py # mods played over a map (needs assets/maps)
python -m pytest -q tools/test_cache_file_formats.py tools/test_bmp_files.py tools/test_profile.py
python tools/test_touch_layout.py # the Android touch layout (JDK 17+)
python tools/test_light_storage.py
@@ -189,10 +190,18 @@ otherwise.
### Third-party code and licences
The repository is CC0. Vendored code goes in `port/third_party/<name>`,
copied unchanged, with a README naming the upstream, the version or commit,
the checksum and what uses it. GPL code (OpenSauce, Reclaimer) may be read
as documentation of file formats, never copied.
The repository is `AGPL-3.0-or-later` (`LICENSE`); the commits before the
change, and the releases built from them, stay CC0 (`LICENSES/CC0-1.0.txt`).
The README's "Licence" section says what each part is under. Vendored code
goes in `port/third_party/<name>`, copied unchanged, with a README naming
the upstream, the version or commit, the licence, the checksum and what uses
it. Its licence must let it be combined with the AGPL 3.0: MIT, BSD (without
an advertising clause), ISC, zlib, Apache 2.0, MPL 2.0, LGPL or GPL 3.0 or
later, public domain. GPL-2.0-only code and the 4-clause BSD licence cannot
be. Code taken from another project, GPL 3.0 code included, keeps its
notices and its licence, and says where it came from.
The decompilation's upstream, punpckhdq/halo, is AGPL 3.0 from 17 October
2026; bnunu/halo-1 is CC0.
### Commits and pull requests
@@ -216,6 +225,8 @@ as documentation of file formats, never copied.
| [port/web/README.md](port/web/README.md) | The web build, the page, the game data in the browser, limits, how it operates, hosting on GitHub Pages |
| [port/include/xdk/README.md](port/include/xdk/README.md) | How the SDK declarations were written and are checked |
| [docs/custom_edition_caches.md](docs/custom_edition_caches.md) | Loading and converting Custom Edition maps, and the evidence for each layout |
| [docs/mods.md](docs/mods.md) | Mods: folders of Halo PC tag files whose sounds, bitmaps, text and models play over a map's on one machine |
| [port/assets/text_filter/README.md](port/assets/text_filter/README.md) | Streamer mode's words: Surge AI's list, the review, the allowed words |
| [port/assets/menus/README.md](port/assets/menus/README.md) | The menu files' format and how to write them again |
| [port/assets/menus/NON_HANDDRAWN.md](port/assets/menus/NON_HANDDRAWN.md) | Menu pictures that are not redraws |
| [port/assets/menus/UNWIRED.md](port/assets/menus/UNWIRED.md) | Menu functions that do nothing yet |
+3
View File
@@ -18,3 +18,6 @@
[Lucide](https://lucide.dev) 1.53.0, copyright (c) 2026 Lucide Icons and
Contributors, under the ISC licence
(`port/assets/icons/lucide/LICENSE`).
- **The Mods menu's warning icons** (a mod out of its requested order, and
a mod that cannot be played): `triangle-alert` and `circle-x` from
[Lucide](https://lucide.dev) 1.53.0, under the same licence.
+661
View File
@@ -0,0 +1,661 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.
View File
File renamed without changes.
+31 -8
View File
@@ -68,7 +68,8 @@ of the game.
- 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.
works. The game plays in the disc's language; on the European disc in
Spanish and Italian it takes your headset's language (else the first).
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
@@ -116,8 +117,9 @@ No computer and no keyboard needed.
library and offers to switch back to Gaming Mode.
6. Start **Ringfall: Mjolnir VR** from your library (under *Non-Steam*).
To update, open the same file in Desktop Mode again; your settings,
saves and game data stay. More about the Steam Frame:
The game updates itself: when a new version is out, it is downloaded
while you play and used from the next start on. Your settings, saves and
game data stay. More about the Steam Frame:
[port/android/STEAM_FRAME.md](port/android/STEAM_FRAME.md).
## Playing
@@ -168,8 +170,9 @@ Ringfall: Mjolnir VR stands on the work of others:
- **[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.
- **[xboxdevwiki](https://xboxdevwiki.net/XDVDFS)**: its description of
the Xbox's disc file system, which the disc image readers are written
from.
- The libraries and fonts in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
Thank you all.
@@ -196,6 +199,26 @@ 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.
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).
### Licence
Ringfall: Mjolnir VR is free software: you can give it to others and change
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, version 3 of the License or (at your option)
any later version (`AGPL-3.0-or-later`), as OpenCE, which it builds on, is.
It comes with no warranty. The full text is in [LICENSE](LICENSE).
- **From build 312 on.** The AGPL applies from the commit that took in
OpenCE's change of licence (19 October 2026), and to each release built
from it or a later one. Earlier releases, and the source at every earlier
commit, were released under [CC0 1.0](LICENSES/CC0-1.0.txt) and stay
that way.
- **Hosts and servers.** If you change the game and let other persons play
with your version over a network (for example, as the host of an
internet game), the AGPL asks you to offer them the source of your
version. The game as released has its source here, and each release's
source is its tag (`build-<n>`).
- **Third-party code and fonts** keep their own licences: see
[THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
- **Halo is not covered.** The licence covers the work of this project's
and OpenCE's authors. It gives no rights in Halo, its maps or anything
else that Microsoft owns.
+10 -8
View File
@@ -1,14 +1,16 @@
# 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.
Ringfall: Mjolnir VR is released under the GNU Affero General Public
License, version 3 or later ([LICENSE](LICENSE)); releases before build 312
were released under [CC0 1.0](LICENSES/CC0-1.0.txt). 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) |
| [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 | AGPL 3.0 or later (OpenCE from 19 October 2026, punpckhdq/halo from 17 October 2026; CC0 1.0 before); bnunu/halo-1: CC0 1.0 | [LICENSE](LICENSE), [LICENSES/CC0-1.0.txt](LICENSES/CC0-1.0.txt) |
| [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`) |
@@ -22,11 +24,11 @@ license's full text is in the file named.
| [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
&lt;in@fishtank.com&gt;.*
The disc image readers (`XisoExtractor.java`, `steam_frame_xiso.py`,
`xiso.c`) are written from a description of the Xbox's disc file system
([xboxdevwiki's XDVDFS page](https://xboxdevwiki.net/XDVDFS)), not from
extract-xiso, which earlier releases followed.
## Fonts
+58 -14
View File
@@ -35,7 +35,7 @@ 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). The web site offers
[port/linux/README.md](port/linux/README.md#updates). The web site offers
each new build when it is out.
Each build of the `main` branch that passes on all four platforms is a new
@@ -43,9 +43,13 @@ release, and the web site then plays it. The [Releases](https://github.com/OpenC
page keeps the last five releases. If the latest build has a problem, get
an older build from that page.
### Arch-based Linux Distributions
The above Linux binary release is available on the [AUR](https://aur.archlinux.org/packages/opence-bin).
## Game data
The port does not include the game data. Make an Xbox disc image from your own copy
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,
@@ -57,10 +61,19 @@ so players of the two versions can play together.
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). On
data folder. Refer to [port/android/README.md](port/android/README.md). On
the web, the page copies `maps/` out of the disc image into the browser's
storage for the site; nothing is uploaded. Refer to
[port/web/README.md](../port/web/README.md).
[port/web/README.md](port/web/README.md).
## Mods
Mods are folders of Halo PC tag files, as the Halo Editing Kit and Invader
write them, in `mods/` next to `maps/`: texture, sound, text and model packs
that play over the maps' on your machine only, in any game. Each has a
`mod.toml`. Every mod in the folder is on; the main menu's Mods turns them
off and on and sets the order they load in.
Refer to [docs/mods.md](docs/mods.md).
## Platforms
@@ -68,10 +81,10 @@ 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) |
| Web (WebAssembly, WebGL 2, SDL3; desktop Chrome, Edge and Firefox) | [port/web/README.md](../port/web/README.md) |
| 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) |
| Web (WebAssembly, WebGL 2, SDL3; desktop Chrome, Edge and Firefox) | [port/web/README.md](port/web/README.md) |
The Linux README also gives the controls, the settings and the multiplayer
functions. These are almost the same on all platforms.
@@ -84,19 +97,19 @@ The game can play system link games on a local network and on the internet:
- Linux, Windows and Android machines and browsers can play in the same
game. On a local network, the native builds play system link; browsers
play each other in rooms, over WebRTC
([port/web/README.md](../port/web/README.md)).
([port/web/README.md](port/web/README.md)).
- An invite link, or the server browser, lets a machine join a game on the
internet, browsers too: a native build takes a browser's WebRTC
([port/linux/README.md](../port/linux/README.md), "Browsers"). No server of
([port/linux/README.md](port/linux/README.md), "Browsers"). 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).
[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).
the game uses. Refer to [port/include/xdk](port/include/xdk/README.md).
To build the game:
@@ -127,7 +140,7 @@ Give these options to `configure.py`:
| --- | --- |
| (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. The Linux build also operates on older distributions and on SteamOS: refer to "Portable build" in [port/linux/README.md](../port/linux/README.md#portable-build). Use this option for builds that you give to other persons. |
| `--portable` | The Linux and Windows builds operate on all x86-64 processors. The Linux build also operates on older distributions and on SteamOS: refer to "Portable build" in [port/linux/README.md](port/linux/README.md#portable-build). 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". |
@@ -187,10 +200,41 @@ file.
`debug.profile_memory` sets the memory of a recording in MB (default 256,
from 4 to 1024). A recording has no length limit:
the game writes a part each time the memory is full. Refer to "Settings" in
[port/linux/README.md](../port/linux/README.md).
[port/linux/README.md](port/linux/README.md).
On Android, the files are in `/sdcard/Android/data/com.halo.decomp/files/profiles/`.
Enter `adb pull` to copy the recording parts to a computer.
A profiling build plays with normal builds. It does not use
`--pgo=train`.
## Licence
OpenCE is free software: you can give it to others and change it under the
terms of the GNU Affero General Public License as published by the Free
Software Foundation, version 3 of the License or (at your option) any later
version (`AGPL-3.0-or-later`). It comes with no warranty. The full text is in
[LICENSE](LICENSE).
- **From the change.** The AGPL applies from the commit that made the change,
and to each release built from that commit or a later one. Earlier
releases, and the source at every earlier commit, were released under
[CC0 1.0](LICENSES/CC0-1.0.txt) and stay that way.
- **What OpenCE builds on.** The decompilation of
[punpckhdq/halo](https://github.com/punpckhdq/halo) is under the AGPL 3.0
from 17 October 2026, and under CC0 1.0 before that date.
[bnunu/halo-1](https://github.com/bnunu/halo-1) is under CC0 1.0.
- **Contributions** are under `AGPL-3.0-or-later`. A change taken from a
project under GPL 3.0 keeps that licence and its notices; the GPL 3.0 and
the AGPL 3.0 let the two be combined (section 13 of each).
- **Hosts and servers.** If you change the game and let other persons play
with your version over a network (for example, as the host of an internet
game, or on a web site), the AGPL asks you to offer them the source of
your version (section 13). The game as released has its source here, and
each release's source is its tag (`build-<n>`).
- **Third-party code, fonts and art** keep their own licences, which are
next to them (`port/third_party`, `port/assets/fonts`,
`port/assets/icons`, `port/assets/text_filter`). The release downloads and
the web site's `licenses.txt` carry their notices.
- **Halo is not covered.** The licence covers the work of OpenCE's authors.
It gives no rights in Halo, its maps or anything else that Microsoft owns.
+3 -3
View File
@@ -493,9 +493,9 @@ file the same as the host's; with `network.map_download` off, or a host with
but their model parts have its 0x68-byte layout and their shader types its
numbering, which makes them a check on what the conversion produces.
OpenSauce and Reclaimer are GPL-3.0 and this repository is CC0: no code from
either was copied. They were read as documentation of the file formats, and
every layout used was then checked against the sample maps.
No code from OpenSauce or Reclaimer (both GPL-3.0) was copied: they were
read as documentation of the file formats, and every layout used was then
checked against the sample maps.
### Documented facts (source, checked against the sample maps)
+329
View File
@@ -0,0 +1,329 @@
# Mods
A mod is a folder of Halo PC tag files that the game plays over a map's own
tags on your machine, as a resource pack does: sounds (a high-res audio
pack), bitmaps (a texture pack, a HUD redraw), text (a translation) and
models (higher-detail weapons, vehicles and scenery). Only the machine that
has a mod plays it. Nothing of a mod is sent to another machine, and a mod
changes nothing that the machines of a network game must agree on, so you
can play with mods in any game, with players who have other mods or none. (A host's mod sounds of other lengths change how long
the host's scripts and AI wait for dialogue to end; the host decides those
for every machine, so they still agree.)
The game's code is `port/linux/game/mods.c` (with `mod_sounds.c`,
`mod_bitmaps.c`, `mod_strings.c` and `mod_models.c`, one for each kind of
tag) and `port/linux/src/port_mods.c` (the mods folder and its files).
## A mod's folder
The mods are in the `mods` folder of the data root (the folder with
`maps/`; on Android the app's data folder, on the web the browser's storage
for the site). Each mod is a folder in it, with a `mod.toml` and a `tags`
folder:
```
mods/
hd_audio/
mod.toml
tags/
sound/sfx/weapons/assault rifle/fire.sound
sound/dialog/...
german/
mod.toml
tags/
ui/hud/hud_item_messages.unicode_string_list
```
The files in `tags` have the paths of the tags they replace: a tag's name,
then its group's extension, as the Halo Editing Kit and Invader keep them.
`tags/sound/sfx/weapons/assault rifle/fire.sound` replaces the sound tag
`sound\sfx\weapons\assault rifle\fire` of every map that has one. Names are
not case-sensitive. The game only looks up the names of the map's tags among
a mod's files: a mod cannot add tags, and no tag name of a map makes a path
outside the mod.
`mod.toml` says what the mod is. Every key is optional:
```toml
name = "High-res audio" # what the Mods menu shows (else the folder's name)
id = "hd_audio" # what other mods name it by (else the folder's name)
version = "1.0"
authors = ["Someone", "Someone else"] # or one string
description = "The game's sounds at 44 kHz"
maps = ["a10", "bloodgulch"] # the maps it is for, by their files' names (else every map)
load_after = ["hd_textures"] # mods it plays over: they load before it
load_before = ["fixes"] # mods that play over it: they load after it
incompatible = ["other_audio"] # mods it cannot be played with
format = 1 # the mods' format it is made for
```
`load_after`, `load_before` and `incompatible` name other mods by their `id`
or their folder's name, not case-sensitive. A mod they name that is not in
the mods folder is passed over. `maps = []` is every map.
A folder without `mod.toml` is not a mod. A mod made for a later `format`
than the build's, or whose `maps` names no map, is not played. The game
logs anything else wrong in `mod.toml` (an unknown key, a value of the
wrong type) and uses the rest; a `mod.toml` that is not TOML at all is
logged, and the mod played without what it says.
The game's log is its output: the terminal on Linux, `halo.log` beside
`halo.exe` on Windows, logcat on Android, and the page's log on the web.
Limits: 256 mods, folder names of 63 bytes at most, 262144 files in all, 24
folders deep under `tags`, and 64 maps in a mod's `maps`.
## The load order
Every mod in the mods folder is on until you turn it off. The mods load in
an order, and where two mods have a file for the same tag, the one loaded
later plays.
- The default order is the folders' names in alphabetical order (not
case-sensitive), except that the mods a mod must load after (those it
asks to load after, and those that ask to load before it) are taken to
just before it. Mods that ask in a ring (a after b, b after a) load
alphabetically, and the game's log says so.
- The main menu's **Mods** (in the PC version's menus, `display.menus =
"pc"`, the default) is laid out as the game lobby is: the mods on the
left as a resource pack list has them, the one at the top playing over
all those below it (the load order upside down: the mod loaded last is at
the top), and the chosen mod's details in the frame on the right (its
name, what is wrong with it, whether it is on, its priority, 1 the top,
and its version, authors, folder, maps and description). Choosing a mod
(A, or a click on the chosen one: the first click chooses it) turns it on
or off (ON, OFF); left and right, or **Move up** and **Move down**, move
the chosen mod (up: over one more mod). **OK** keeps the order and plays
it at once (on a map that is loaded, too); **Cancel** leaves it as it was.
- A warning sign beside a mod means it is on the wrong side of a mod it
asks to load before or after (both on): a mod that loads after another is
above it in the menu. A red cross means a mod that is on says it cannot be
played with this one, or that it cannot be played at all (made for a later
build, or its `maps` names none): it stays off. The details say which.
The menu keeps the order, and the mods turned off, in `mods/load_order.toml`,
by the mods' folders' names:
```toml
order = ["german", "hd_audio", "hd_textures"]
disabled = ["hd_textures"]
```
A mod added to the folder later is put where the default order puts it,
after the mod before it there (but on the side it asks for of the mods it
names), and is on. A mod gone from the folder is left out. When the file
cannot be written, the game says so in its log and keeps the order until it
quits.
### Mods that cannot be played together
When a mod that is on names another that is on as `incompatible`, the game
asks as it starts, before the window opens:
- **Play anyway** plays them all (closing the box does too);
- **Turn off ...** turns off the mods named as incompatible (if mod A says
it cannot be played with mod B, B is turned off), and keeps that in
`load_order.toml`;
- **Quit** closes the game.
The Mods menu turns them on again. On the web, **Quit** starts the page
again. A run nobody watches (`debug.hidden_window`, `debug.null_renderer`,
`debug.exit_after` or `debug.network_test`) is not asked: it plays them all,
and the game's log says which conflict.
At the console:
| Command | What it does |
| --- | --- |
| `mods_list` | Lists the mods in the mods folder in their load order, and which are on, then how many of the map's string lists, bitmaps, sounds and models the mods play. |
| `mods_reload` | Finds the mods again and reads their files again: a changed file is read again (one that now fails a check leaves what was played before), a new one is played, and the map's tag comes back for a file that is gone. If a sound changed, all sounds stop. It does nothing while `mods_enable false`. |
| `mods_enable false` | Plays the map's own tags until `mods_enable true` or the next map. |
These commands work in any game, a client's too: they change nothing of the
game.
### Getting a mod onto each platform
- **Linux, Windows**: put the mod's folder in `mods/` next to `maps/`.
- **Android**: put it in `mods/` in the app's data folder,
`/sdcard/Android/data/com.halo.decomp/files/mods`, for example with
`adb push hd_audio /sdcard/Android/data/com.halo.decomp/files/mods/`.
- **Web**: on the page, **Mods**, **Add a mod folder…** copies a mod's
`mod.toml` and `tags` folder into the browser's storage for the site.
**Remove** deletes it again.
## What a mod can replace
The game plays these groups from a mod. It does not read the files of any
other group (a `.weapon`, `.vehicle`, `.model_collision_geometry` or
`.scenario`, for example): they would change what the machines of a network
game must agree on.
| Group | Extension | What it is |
| --- | --- | --- |
| Sound | `.sound` | Sounds, music and dialogue |
| Bitmap | `.bitmap` | Textures, sprites, the HUD's pictures |
| Unicode string list | `.unicode_string_list` | The text the game shows |
| String list | `.string_list` | Other text |
| Model | `.gbxmodel` (Halo PC's), `.model` (the Xbox's) | What objects look like: weapons, first-person weapons and arms, vehicles, scenery, characters |
A file must be a Halo PC tag file of its group, as the Halo Editing Kit's
tools and Invader write them (a model also as `invader-extract` writes an
Xbox map's: `.model`). The game checks every count, size and offset
in it against the file, and every value against what the game reads, before
it uses the file. A file that fails a check is left out (the map's tag
plays), and `debug.txt` and the console say why.
### Sounds
- The samples can be 16-bit PCM, Xbox ADPCM or Ogg Vorbis, mono or stereo,
at 22 or 44 kHz. The game converts them to the Xbox ADPCM that it plays,
as it does for Custom Edition maps: mono at 22 kHz, stereo at its own rate.
- Empty values are filled in as a map's build fills them in: the defaults
of the sound's class, the playback rates and the bend.
- A sound must have permutations: a file with an empty pitch range (some
Halo PC translations have them) is left out.
- A permutation must fit in a block of the sound cache (one sound's samples:
`MAXIMUM_SOUND_DATA_SIZE`).
### Bitmaps
- A bitmap's types and formats are those the game draws: 2D, 3D and cube
maps; A8, Y8, AY8, A8Y8, R5G6B5, A1R5G5B5, A4R4G4B4, X8R8G8B8, A8R8G8B8,
DXT1, DXT3, DXT5 and P8 bump. Sides are 4096 at most (512 for a 3D
bitmap). A bitmap that is not a power of two on each side is drawn
linear, as a Custom Edition map's is: it must be an uncompressed 2D
bitmap.
- One bitmap's pixels may take a quarter of the texture cache: 64 MB on
Linux and Windows, 5.5 MB on Android and the web (a 2048 x 2048 DXT5 with
its mipmaps fits on all of them).
- A bitmap replaces the map's bitmap of its name, and the map's other tags
use it as the map's: it must have at least as many bitmaps, of the same
types, and as many sequences, each with at least as many sprites. It can
be larger: a redraw at twice the size is drawn at the map's size, sharper.
- The HUD draws a bitmap at its own size. So for each HUD element that draws
a mod's bitmap, the game scales the element by the map's size of what it
draws (its sprite, or with no sprites its bitmap) to the mod's, when that
is the same ratio for all of them. A bitmap the HUD draws that is not the
map's redrawn so (Halo PC's HUD bitmaps lay their sprites out to other
sizes) is left out. The HUD's digits can be the map's size or twice it.
- The menus draw a bitmap in their widgets' bounds, whatever its size.
- Halo PC keeps the channels of a model shader's multipurpose maps and of
the HUD's meters in another order than the Xbox. The game finds those
bitmaps among the mod's and draws them in the Xbox's order, as it does for
Custom Edition maps (`custom_edition_bitmaps.c`).
- The pixels are read from the file when the texture cache loads them, not
kept in memory.
### Text
- A list keeps the map's number of strings: a mod's strings past it are
left out, and the map's own strings stay where the mod's list is shorter.
- The game uses many strings as the format of what it prints
(`%s was killed by %s`). A mod's string replaces the map's only if it has
the same conversions in the same order (`%s`, `%d`, ...); otherwise the
map's string stays. A translation keeps the conversions of the original.
- Unicode strings are UTF-16, as the tool writes them.
- The main menu is the port's own (the PC version's menus, `port/assets/menus`),
whose text is in tags named `pc\...`. A mod's `ui\shell` lists change the
Xbox's menus (`display.menus = "xbox"`) and the text shown while playing
(HUD messages, the multiplayer messages, the pause menu).
### Models
A model is more than what is drawn: its nodes (its skeleton) are what its
animations move, its markers are where projectiles leave a weapon, where a
unit sits and where things attach, and its regions and permutations are what
damage and an object's variant choose among. The machines of a network game
agree on those, so a mod's model must have the map's:
- the same nodes: their names, hierarchy and default positions;
- the same markers: their names, regions, permutations, nodes, positions
and rotations (on the model's permutations, as the Halo Editing Kit keeps
them, or on the model, as a map does);
- the same regions and permutations: their names and flags.
Positions and rotations may differ from the map's by 0.001 at most (a model
exported again is not always bit for bit the map's). Only what is drawn is
the mod's: the geometries, their parts and vertices, which geometry each
permutation draws at each detail level, the detail levels' cutoffs, the
base map's scale, and which shaders each part is drawn with. A detail level
the map draws must draw one of the mod's geometries, and one the map draws
nothing at stays so. Every shader must be one the map has (a mod cannot add
tags); a mod's bitmaps can redraw them.
The model is checked against each map's own model of its name as that map
loads. On a map whose model of that name has other nodes, markers or
regions (a custom map's own assault rifle, say), or lacks a shader the mod
names, the mod's model is left out and the map's drawn; the mod's other
files still play there. `maps` in `mod.toml` keeps a mod to the maps it is
made for.
- Where a mod has both, its `.gbxmodel` plays; otherwise the file of the mod
loaded later, whichever its format.
- What the tool works out as it builds a map is worked out as it does: a
part's strip is its triangles block, its centroid's nodes are its most
weighted, its links of 0 to the parts drawn before and after it are none,
a base map scale of 0 is 1, and a model whose tag asks for it has the
normals of its vertices that share a position blended. The vertices are
compressed as the game draws them.
- A part's links to the parts drawn before and after it (for transparency)
must not go round.
- A model of 44 nodes or more (the renderer skins 43 at once; only Custom
Edition maps' have so many) is drawn a part's own nodes at a time, as a
Custom Edition map's is: a part's vertices may name 22 nodes at most.
- The collision geometry, the animations and the object's tag are the map's:
a larger weapon still hits and is hit where the map's does, and an object
is still left undrawn by the map's bounds of it (a much larger model may
vanish at the screen's edges).
- Every model made from a file is kept until the map changes, a file edited
and read again by `mods_reload` too, and counts against the mods' memory.
### Shaders
A model's parts can be drawn with a shader of every group Halo PC has but
one: `shader_transparent_chicago_extended`, which Gearbox added for Halo PC.
The Xbox game does not have it, so do not use it: use a
`shader_transparent_chicago` shader instead. (A Custom Edition map's
chicago extended shaders become chicago shaders as the map loads, keeping
only their four-stage maps: see
[custom_edition_caches.md](custom_edition_caches.md).) As for every
shader, it must be one the map has.
## Making a mod
1. Get the tags you want to change as tag files. `invader-extract` extracts
them from a map, and the Halo Editing Kit's tags are Halo PC's own.
2. Change them with the Halo Editing Kit or Invader (`invader-bitmap`,
`invader-sound` and `invader-model` build bitmaps, sounds and models from
images, audio files and JMS files). Keep each tag's name.
3. Put them in `tags/` of a folder in `mods/`, with a `mod.toml`.
4. Load a map (a new mod is on). `mods_reload` at the console reads the
files again as you change them, without loading the map again.
`debug.txt` has a line for every file the game leaves out, and why, and a
summary of what each kind of tag plays from the mods:
```
mods: 9 string lists from their files (9 new), 0 given back, 0 left out
mods: 66 HUD placements scaled to draw larger bitmaps at the map's size
mods: 354 bitmaps from their files (354 new), 0 given back, 6 left out
mods: 313 sounds from their files (313 new), 0 given back, 2 left out
mods: 4 models from their files (4 new), 0 given back, 1 left out
```
## How it operates
- When a map's tags load, at `mods_reload` and at the Mods menu's **OK**,
the game lists the files of the mods that are on, once, in a table of
their tag paths. Each of the map's sound, bitmap, string list and model
tags is looked up in it.
- A tag replaced by a mod takes the file's values in place, so everything
that holds the tag keeps it, and the map's own values come back when the
file goes. A sound playing or a texture in the cache is never changed
under the game: before a reload changes anything, the sounds stop and the
caches let go of what they hold.
- The mods' tags keep at most 512 MB in memory (192 MB on Android and the
web): their files, their sounds' samples, their bitmaps' tags and their
models' geometry.
- A Custom Edition map takes mods as an Xbox map does.
+2 -1
View File
@@ -98,6 +98,7 @@ To install the data from a computer:
| Item | Location in `/sdcard/Android/data/io.github.cesarus85.ringfall/files` |
| --- | --- |
| Saved games (`z:\` and `u:\`) | `save` |
| Mods (`docs/mods.md`) | `mods`: `adb push <mod folder> /sdcard/Android/data/com.halo.decomp/files/mods/`; the main menu's Mods orders them and turns them off |
| Log | `debug.txt` |
| Settings | `config.toml` |
@@ -160,7 +161,7 @@ controller for player 1. With the default buttons of the profile:
| Crouch, Zoom | left and right stick clicks |
| Light, Gren. type | white, black |
| Pause, Back | start, back |
| Up, Down, Left, Right | D-pad |
| Up, Down, Left, Right | D-pad (in a network game, Up opens the text chat's keyboard) |
The names on the buttons follow the profile's "Button layout" (Settings >
Gamepads): with "Swap triggers", the right trigger's button says "Grenade".
+5 -2
View File
@@ -16,8 +16,11 @@ On the Frame, in Desktop Mode (Steam menu > Power > Switch to Desktop):
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 game updates itself: while it runs, its launcher looks for a newer
release, fetches it and puts it in place as the next start begins
(`steam_frame_launch.sh`; `update.auto = false` in `config.toml` stops it;
`launcher.log` says what it did). Opening the same file again updates it
too (settings, saves and game data stay).
The desktop file (`steam_frame_installer.desktop`) downloads the latest
release's `ringfall-mjolnir-vr-steam-frame.zip` (`tools/steam_frame_bundle.py`, made by
+4 -2
View File
@@ -18,15 +18,17 @@ named where code is taken over.
| --- | --- |
| 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 | The eyes drawn one by one (both at once, multiview, only with `debug.multiview`: its eyes did not always fuse, see the open points); 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 |
| Aiming | The weapon in the hand, its shots from the muzzle, laser and crosshair options; each weapon held so that its model's hand lands on the real one, as the free hand's (it learns where each weapon's animations hold the hand; the pistol's had been 15 cm before the real hand, the assault rifle's 8 cm aside), the scope's disc moved with it; `vr.weapon_hand_*` to adjust (2026-10-17) |
| 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 |
| Vehicles | The Warthog's wheel turned with the hands, right trigger throttle, left brake; first-person seats; turrets with handles |
| Scores | The menu button held shows the scores on the Quest (a press pauses, as it is let go); the Steam Frame's View button as before; the text chat's lines where the desktop has them (2026-10-18) |
| Comfort | Snap or smooth turning, a comfort vignette in vehicles, VR options in the main and pause menus (Y), a page of every controller button |
| Menus | The PC version's menus in VR (`vr.menus`), clicked with a laser; text typed on the on-screen keyboard |
| Multiplayer | Up to date with OpenCE (network version 29, 2026-10-14): cross-play, its server browser, co-op campaign, password lobbies; join codes and the VR online list |
| Bots | Bots in multiplayer: on foot (paths, items, objectives), in vehicles and at turrets; they make room for a joining person |
| Bots | Bots in multiplayer: on foot (paths, items, objectives), in vehicles and at turrets; they make room for a joining person, on the LAN too (a lobby they fill is advertised open, 2026-10-18) |
| Voice chat | Push to talk on any button (`vr.button_talk`, BUTTONS page; the free hand's grip by default, sending nothing while it reaches for the foregrip or the wheel), open mic, off (2026-10-13) |
| Builds | The Android build's assembly conversion (`tools/android_asm_convert.py`) keeps string data as it is: strings and tables starting with "_" or holding \" lost a "_" (Opus's pulse cache among them: the Steam Frame's crashes in online games while talking), 2026-10-13 |
| Languages | The European disc in Spanish and Italian (a maps folder for each, `maps_es` and `maps_it`, none called `maps`) imported on the Quest and the Steam Frame: the headset's language's, its language the game's and the menus' (`maps/language.txt`, unless `game.language` is set), 2026-10-17; not yet tried with that disc |
| Controls | Left-handed mode (`vr.handedness`): the weapon in the left hand, the arms and grips mirrored, sticks and buttons mirrored on both controllers (`vr.mirror_controls`), the assault rifle's counter read the right way round (2026-10-06) |
| Graphics | Presets low / medium / high / max (`vr.graphics`), refresh rate and resolution in the VR options, all kept over a restart (2026-10-06) |
| Arms and hands | Both arms by IK from shoulders that follow the torso, the elbow led by the hand's roll; fingers from trigger, grip and touch; `vr.body` arms / hands / off, `vr.fingers` (2026-10-06) |
@@ -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/RingfallMjolnirVR";
static final String SHARED_DATA_ROOT = "/home/steamos/Documents/RingfallMjolnirVR";
private File dataRoot;
private TextView status;
@@ -36,7 +36,8 @@ import java.util.zip.ZipInputStream;
* the latest release when it starts, on a thread of its own, and if it is
* newer asks the player whether to update:
*
* - Yes: the release's app (halo-android-release.zip or -debug.zip) is
* - Yes: the release's app (ringfall-mjolnir-vr-android-release.zip or
* -debug.zip) is
* downloaded and handed to Android's package installer, which replaces the
* game (closing it) and offers to open the new version.
* - No: nothing, until the next start.
@@ -45,6 +46,9 @@ import java.util.zip.ZipInputStream;
*
* Every build of main is signed with the same key (the workflow's), which an
* app must keep for Android to install a new version over it.
*
* Not on the Steam Frame: Lepton installs the APK in the game's folder there
* at every start, so its launcher updates that (steam_frame_launch.sh).
*/
final class Updater {
/* the VR port's own releases: the original's, made without VR, must
@@ -63,7 +67,8 @@ final class Updater {
static void start(Activity activity) {
File config = configFile(activity);
if (BuildConfig.HALO_BUILD_NUMBER <= 0 || config == null || !autoUpdate(config))
if (BuildConfig.HALO_BUILD_NUMBER <= 0 || config == null || !autoUpdate(config) ||
new File(LauncherActivity.SHARED_DATA_ROOT).isDirectory())
return;
new Thread(() -> {
int latest = latestRelease();
@@ -4,94 +4,88 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import java.util.Locale;
/**
* Copies the maps folder out of an Xbox disc image (an "xiso", or a whole
* disc's ".iso"), as the desktop games do (port/linux/src/xiso.c, which this
* follows).
* Copies the maps folder out of the player's Xbox disc image of Halo, for
* LauncherActivity, as the desktop games do (port/linux/src/xiso.c; the web
* page's is port/web/site/xiso-worker.js). The image is a document the
* player picked, read through its FileChannel at absolute positions.
*
* The image's file system is XDVDFS, read as extract-xiso does
* (https://github.com/XboxDev/extract-xiso, extract-xiso.c, whose format
* handling this follows; its license is below and in
* port/third_party/extract-xiso/LICENSE.TXT): 2048-byte sectors; a volume
* descriptor at 0x10000 that starts and ends with "MICROSOFT*XBOX*MEDIA" and
* gives the root directory's sector and size; and directories whose entries
* form a binary tree (each entry: left and right subtree offsets in 4-byte
* units, the start sector, the size, attributes, the name's length and the
* name, 4-byte aligned). Images made from a whole disc put the game partition
* further in, at one of the offsets below.
* Written from a description of the Xbox's disc file system, XDVDFS
* (xboxdevwiki's XDVDFS page, https://xboxdevwiki.net/XDVDFS, and the format
* facts OpenCE's readers rely on). What this class relies on:
*
* The files are written to maps.partial first and moved into maps once they
* all are, so an interrupted extraction never leaves maps/ui.map behind.
* - Sectors are 2048 bytes and integers little-endian. Sector numbers count
* from the start of the game partition: an image of the partition alone
* has it at 0, an image of a whole disc further in (0x0FD90000, 0x02080000
* or 0x18300000 for XGD2, XGD3 and XGD1 discs). They are tried in that
* order.
* - The volume descriptor is the partition's sector 32 (at 0x10000). It
* holds "MICROSOFT*XBOX*MEDIA" at its start and again at 0x7EC, and the
* root directory table's start sector (u32 at 0x14) and size in bytes (u32
* at 0x18).
* - A directory table is a binary tree of entries whose root is the entry at
* offset 0. An entry is a left and a right subtree link (u16 each, in
* 4-byte units from the table's start, 0 for none), the start sector and
* the size of the file or of the subdirectory's table (u32 each), the
* attributes (u8, 0x10 for a directory), the name's length (u8) and the
* name. Unused bytes of a table are 0xFF, so a left link of 0xFFFF is
* padding, not an entry.
* - A file is one contiguous run of bytes from the start of its sector.
*
* Some parts of this code are copyright in@fishtank.com. This product
* includes software developed by in &lt;in@fishtank.com&gt;.
* The image is the player's file and nothing read from it is trusted. Only
* the root's table and the maps folder's are read, each whole and at most
* 4 MB; the walk of a table visits each offset once, goes at most 64 links
* deep and looks at most 4096 entries; and a file is written only under a
* plain name that no file before it had (ignoring case).
*
* Copyright (c) 2003 in &lt;in@fishtank.com&gt;
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
*
* This product includes software developed by in &lt;in@fishtank.com&gt;.
*
* 4. Neither the name of "in" nor the email address "in@fishtank.com"
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED `AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* The app starts the game once maps/ui.map exists, and makes the maps folder
* itself, so the copy is written to maps.partial (emptied first of what an
* earlier attempt left) and its files moved into maps once all are
* complete, ui.map last.
*/
final class XisoExtractor {
private static final int SECTOR_SIZE = 2048;
private static final long VOLUME_DESCRIPTOR_OFFSET = 0x10000;
private static final int ENTRY_HEADER_SIZE = 14;
private static final int DESCRIPTOR_OFFSET = 0x10000;
private static final byte[] MAGIC = "MICROSOFT*XBOX*MEDIA".getBytes(StandardCharsets.ISO_8859_1);
private static final int SECOND_MAGIC_OFFSET = 0x7EC;
private static final int ENTRY_FIXED_SIZE = 14;
private static final int ATTRIBUTE_DIRECTORY = 0x10;
/* directory tables are a few sectors; anything much larger is not one */
private static final long MAXIMUM_DIRECTORY_SIZE = 4 << 20;
private static final int MAXIMUM_FILES = 256;
private static final int COPY_BUFFER_SIZE = 1 << 20;
private static final byte[] VOLUME_MAGIC = "MICROSOFT*XBOX*MEDIA".getBytes(StandardCharsets.US_ASCII);
/* where the game partition starts: a plain image, and whole-disc images
(extract-xiso's GLOBAL, XGD3 and XGD1 offsets) */
private static final long[] PARTITION_OFFSETS = { 0, 0x0FD90000L, 0x02080000L, 0x18300000L };
private static final int PADDING_LINK = 0xFFFF;
/** where the game partition may start, in the order they are tried */
private static final long[] PARTITION_STARTS = {
0x00000000L, // an image of the game partition alone
0x0FD90000L, // a whole XGD2 disc
0x02080000L, // a whole XGD3 disc
0x18300000L, // a whole XGD1 disc (the original Xbox's)
};
/** directory tables are a few sectors; anything much larger is not one */
private static final int MAXIMUM_TABLE_SIZE = 4 * 1024 * 1024;
private static final int MAXIMUM_DEPTH = 64;
private static final int MAXIMUM_VISITS = 4096;
private static final int MAXIMUM_ENTRIES = 256;
private static final int COPY_CHUNK = 1024 * 1024;
private static final String PARTIAL_DIRECTORY = "maps.partial";
/** Told as the copy goes: the file being copied, and the bytes copied of all the files'. */
interface Progress {
void report(String file, long done, long total);
}
/** a reason the player can act on */
/** A reason the player can act on: its message is shown as it is. */
static final class ExtractException extends IOException {
ExtractException(String message) {
super(message);
}
}
/** a file or folder found in a directory table */
private static final class Entry {
final String name;
final long sector;
@@ -104,187 +98,327 @@ final class XisoExtractor {
}
}
private final FileChannel image;
private long partition;
private XisoExtractor(FileChannel image) {
this.image = image;
private XisoExtractor() {
}
/** copies the image's maps folder to destination/maps */
/** Copies the image's maps folder to destination/maps. */
static void extractMaps(FileChannel image, File destination, Progress progress) throws IOException {
new XisoExtractor(image).extract(destination, progress);
}
long imageSize = image.size();
long partition = -1;
long rootSector = 0, rootSize = 0;
ByteBuffer descriptor = ByteBuffer.allocate(SECTOR_SIZE);
private void readAt(long offset, ByteBuffer buffer) throws IOException {
while (buffer.hasRemaining()) {
int count = image.read(buffer, offset);
if (count <= 0)
throw new ExtractException("Could not read the disc image (is it complete?).");
offset += count;
}
buffer.flip();
}
private static boolean magicAt(ByteBuffer buffer, int offset) {
for (int index = 0; index < VOLUME_MAGIC.length; index++) {
if (buffer.get(offset + index) != VOLUME_MAGIC[index])
return false;
}
return true;
}
/** whether a name is one to write in the maps folder: no path, no "." or
* "..", and only printable ASCII that every file system takes as a plain
* name (a disc's maps folder holds .map files and loading.tga) */
static boolean isPlainName(String name) {
if (name.isEmpty() || name.equals(".") || name.equals(".."))
return false;
for (int index = 0; index < name.length(); index++) {
char c = name.charAt(index);
if (c < 0x20 || c > 0x7E || "/\\:*?\"<>|".indexOf(c) >= 0)
return false;
}
return true;
}
/** the partition's volume descriptor: the root directory's sector and size */
private long[] findVolume() throws IOException {
for (long offset : PARTITION_OFFSETS) {
ByteBuffer descriptor = ByteBuffer.allocate(SECTOR_SIZE).order(ByteOrder.LITTLE_ENDIAN);
try {
readAt(offset + VOLUME_DESCRIPTOR_OFFSET, descriptor);
} catch (IOException e) {
for (long start : PARTITION_STARTS) {
if (start + DESCRIPTOR_OFFSET + SECTOR_SIZE > imageSize)
continue;
}
if (!magicAt(descriptor, 0) || !magicAt(descriptor, 0x7EC))
descriptor.clear();
if (!readFully(image, descriptor, start + DESCRIPTOR_OFFSET))
continue;
partition = offset;
return new long[] {
descriptor.getInt(20) & 0xFFFFFFFFL, descriptor.getInt(24) & 0xFFFFFFFFL,
};
byte[] bytes = descriptor.array();
if (!hasMagic(bytes, 0) || !hasMagic(bytes, SECOND_MAGIC_OFFSET))
continue;
partition = start;
rootSector = u32(bytes, 0x14);
rootSize = u32(bytes, 0x18);
break;
}
throw new ExtractException("This is not an Xbox disc image.");
}
if (partition < 0)
throw new ExtractException("This is not an Xbox disc image.");
/** a directory's table, read whole */
private ByteBuffer readDirectory(long sector, long size, String what) throws IOException {
if (size <= 0 || size > MAXIMUM_DIRECTORY_SIZE)
throw new ExtractException(what);
ByteBuffer table = ByteBuffer.allocate((int) size).order(ByteOrder.LITTLE_ENDIAN);
readAt(partition + sector * SECTOR_SIZE, table);
return table;
}
/**
* collects the entries of the subtree at offset (4-byte units), files or
* directories as asked
*/
private static void walk(ByteBuffer table, long offset, int depth, boolean directories, List<Entry> out,
int[] visited) {
offset *= 4;
/* a malformed table must not loop or run off its end */
if (depth > 64 || ++visited[0] > 4096 || offset + ENTRY_HEADER_SIZE > table.limit())
return;
int at = (int) offset;
int left = table.getShort(at) & 0xFFFF;
int right = table.getShort(at + 2) & 0xFFFF;
/* 0xFFFF: padding, an empty directory */
if (left == 0xFFFF)
return;
int nameLength = table.get(at + 13) & 0xFF;
if (left != 0)
walk(table, left, depth + 1, directories, out, visited);
boolean isDirectory = (table.get(at + 12) & ATTRIBUTE_DIRECTORY) != 0;
if (at + ENTRY_HEADER_SIZE + nameLength <= table.limit() && nameLength > 0 && isDirectory == directories
&& out.size() < MAXIMUM_FILES) {
byte[] bytes = new byte[nameLength];
for (int index = 0; index < nameLength; index++)
bytes[index] = table.get(at + ENTRY_HEADER_SIZE + index);
String name = new String(bytes, StandardCharsets.ISO_8859_1);
/* (as extract-xiso refuses them: no name may leave the folder, and
none but a plain file name is written) */
if (isPlainName(name))
out.add(new Entry(name, table.getInt(at + 4) & 0xFFFFFFFFL, table.getInt(at + 8) & 0xFFFFFFFFL));
}
if (right != 0)
walk(table, right, depth + 1, directories, out, visited);
}
private void extract(File destination, Progress progress) throws IOException {
long[] root = findVolume();
/* the root's maps folder */
ByteBuffer table = readDirectory(root[0], root[1], "The disc image's file system is damaged.");
List<Entry> directories = new ArrayList<>();
walk(table, 0, 0, true, directories, new int[1]);
// the root's maps folder
byte[] root = readTable(image, partition, rootSector, rootSize, "The disc image's file system is damaged.");
Entry maps = null;
for (Entry entry : directories) {
if (entry.name.equalsIgnoreCase("maps"))
for (Entry entry : walkTable(root, true)) {
if (entry.name.equalsIgnoreCase("maps")) {
maps = entry;
break;
}
}
// (Ringfall: a European disc in two languages, Spanish and Italian,
// has no maps folder but one for each, maps_es and maps_it: the
// device's language's is taken, else English's, else the first, and
// its language written to maps/language.txt, which the game asks the
// Xbox for while game.language is unset: port/linux/src/xbox_xapi.c)
String language = null;
if (maps == null) {
String wanted = Locale.getDefault().getLanguage();
Entry english = null, first = null;
for (Entry entry : walkTable(root, true)) {
String code = mapsLanguage(entry.name);
if (code == null)
continue;
if (code.equals(wanted) && maps == null)
maps = entry;
if (code.equals("en") && english == null)
english = entry;
if (first == null)
first = entry;
}
if (maps == null)
maps = english != null ? english : first;
if (maps != null)
language = mapsLanguage(maps.name);
}
if (maps == null)
throw new ExtractException("The disc image has no maps folder: it is not a Halo disc.");
/* its files */
table = readDirectory(maps.sector, maps.size, "The disc image's maps folder is damaged.");
List<Entry> files = new ArrayList<>();
walk(table, 0, 0, false, files, new int[1]);
long total = 0;
boolean hasUi = false;
// its files
byte[] table = readTable(image, partition, maps.sector, maps.size, "The disc image's maps folder is damaged.");
List<Entry> files = walkTable(table, false);
Entry userInterface = null;
for (Entry file : files) {
total += file.size;
hasUi |= file.name.equalsIgnoreCase("ui.map");
if (file.name.equalsIgnoreCase("ui.map"))
userInterface = file;
}
if (!hasUi)
if (userInterface == null)
throw new ExtractException("The disc image's maps folder has no ui.map: it is not a Halo disc.");
File partial = new File(destination, "maps.partial");
File finished = new File(destination, "maps");
if (!partial.isDirectory() && !partial.mkdirs())
throw new ExtractException("Could not create " + partial + ".");
ByteBuffer buffer = ByteBuffer.allocateDirect(COPY_BUFFER_SIZE);
long done = 0;
// (an image that ends before a file does is refused before anything
// is written; an empty file's sector need not be anywhere)
long total = 0;
for (Entry file : files) {
File path = new File(partial, file.name);
long offset = partition + file.sector * SECTOR_SIZE;
long remaining = file.size;
if (file.size > 0 && partition + file.sector * SECTOR_SIZE + file.size > imageSize)
throw new ExtractException(readFailure(file));
total += file.size;
}
try (FileOutputStream out = new FileOutputStream(path)) {
FileChannel output = out.getChannel();
// the copy, to maps.partial
File partial = new File(destination, PARTIAL_DIRECTORY);
if (partial.isDirectory()) {
File[] leftovers = partial.listFiles();
if (leftovers != null) {
for (File leftover : leftovers)
leftover.delete();
}
} else {
partial.delete();
partial.mkdirs();
}
if (!partial.isDirectory())
throw new ExtractException("Could not create " + partial.getPath() + ".");
ByteBuffer buffer = ByteBuffer.allocate(COPY_CHUNK);
long done = 0;
for (Entry file : files)
done = copyFile(image, partition, file, new File(partial, file.name), buffer, done, total, progress);
while (remaining > 0) {
buffer.clear();
buffer.limit((int) Math.min(remaining, COPY_BUFFER_SIZE));
readAt(offset, buffer);
int count = buffer.remaining();
while (buffer.hasRemaining())
output.write(buffer);
offset += count;
remaining -= count;
done += count;
progress.report(file.name, done, total);
}
} catch (ExtractException e) {
throw new ExtractException("Could not read " + file.name + " from the disc image (is it complete?).");
// then into maps, ui.map (the app's sign that the data is there) last
File mapsFolder = new File(destination, "maps");
if (!mapsFolder.isDirectory() && !mapsFolder.mkdirs() && !mapsFolder.isDirectory())
throw new ExtractException("Could not create " + mapsFolder.getPath() + ".");
// (the language of a language's maps folder: before ui.map is there)
File languageFile = new File(mapsFolder, "language.txt");
languageFile.delete();
if (language != null) {
try (FileOutputStream out = new FileOutputStream(languageFile)) {
out.write((language + "\n").getBytes(StandardCharsets.US_ASCII));
} catch (IOException e) {
throw new ExtractException("Could not write " + path + " (is the storage full?).");
throw new ExtractException("Could not write " + languageFile.getPath() + " (is the storage full?).");
}
}
/* (the maps folder may be there, empty: the app makes it for adb) */
if (!finished.isDirectory() && !finished.mkdirs())
throw new ExtractException("Could not create " + finished + ".");
for (Entry file : files) {
File from = new File(partial, file.name);
File to = new File(finished, file.name);
to.delete();
if (!from.renameTo(to))
throw new ExtractException("Could not move " + file.name + " into " + finished + ".");
List<Entry> order = new ArrayList<>(files);
order.remove(userInterface);
order.add(userInterface);
for (Entry file : order) {
File target = new File(mapsFolder, file.name);
target.delete();
if (!new File(partial, file.name).renameTo(target))
throw new ExtractException("Could not move " + file.name + " into " + mapsFolder.getPath() + ".");
}
partial.delete();
}
/** a language's maps folder's language ("maps_it": "it"), else null */
static String mapsLanguage(String name) {
if (name.length() != 7 || !name.regionMatches(true, 0, "maps_", 0, 5))
return null;
String code = name.substring(5).toLowerCase(Locale.ROOT);
return Character.isLetter(code.charAt(0)) && Character.isLetter(code.charAt(1)) ? code : null;
}
/** No path, no . or .., and only printable ASCII that every file system takes as a plain name. */
static boolean isPlainName(String name) {
if (name == null || name.isEmpty() || name.equals(".") || name.equals(".."))
return false;
for (int index = 0; index < name.length(); index++) {
char character = name.charAt(index);
if (character < 0x20 || character > 0x7E || "/\\:*?\"<>|".indexOf(character) >= 0)
return false;
}
return true;
}
/* ---------- the image */
private static long u16(byte[] bytes, int offset) {
return (bytes[offset] & 0xFF) | (bytes[offset + 1] & 0xFF) << 8;
}
private static long u32(byte[] bytes, int offset) {
return u16(bytes, offset) | u16(bytes, offset + 2) << 16;
}
private static boolean hasMagic(byte[] bytes, int offset) {
for (int index = 0; index < MAGIC.length; index++) {
if (bytes[offset + index] != MAGIC[index])
return false;
}
return true;
}
/** Fills buffer from the image at position; false if the image ends first. */
private static boolean readFully(FileChannel image, ByteBuffer buffer, long position) throws IOException {
while (buffer.hasRemaining()) {
int count = image.read(buffer, position);
if (count <= 0)
return false;
position += count;
}
return true;
}
private static byte[] readTable(FileChannel image, long partition, long sector, long size, String damaged)
throws IOException {
if (size == 0 || size > MAXIMUM_TABLE_SIZE)
throw new ExtractException(damaged);
ByteBuffer table = ByteBuffer.allocate((int) size);
if (!readFully(image, table, partition + sector * SECTOR_SIZE))
throw new ExtractException("Could not read the disc image (is it complete?).");
return table.array();
}
/* ---------- directory tables */
private static final class Walk {
final byte[] table;
final boolean directories;
final BitSet visited = new BitSet();
final List<Entry> entries = new ArrayList<>();
int visits;
boolean stopped;
Walk(byte[] table, boolean directories) {
this.table = table;
this.directories = directories;
}
}
/** The folders (or the files) of a table with plain names, in the tree's order. */
private static List<Entry> walkTable(byte[] table, boolean directories) {
Walk walk = new Walk(table, directories);
walkEntry(walk, 0, 0);
return walk.entries;
}
/**
* The subtree whose root entry is at offset: its left subtree, the
* entry, its right subtree. A link to padding, past the table or back to
* an entry already visited ends that branch.
*/
private static void walkEntry(Walk walk, int offset, int depth) {
byte[] table = walk.table;
if (walk.stopped || depth > MAXIMUM_DEPTH)
return;
if (offset >= table.length || table.length - offset < ENTRY_FIXED_SIZE)
return;
int left = (int) u16(table, offset);
int right = (int) u16(table, offset + 2);
if (left == PADDING_LINK || walk.visited.get(offset / 4))
return;
walk.visited.set(offset / 4);
if (walk.visits >= MAXIMUM_VISITS) {
walk.stopped = true;
return;
}
walk.visits++;
if (left != 0)
walkEntry(walk, left * 4, depth + 1);
int nameLength = table[offset + 13] & 0xFF;
if (nameLength != 0 && nameLength <= table.length - offset - ENTRY_FIXED_SIZE)
collect(walk, offset, nameLength);
if (right != 0)
walkEntry(walk, right * 4, depth + 1);
}
private static void collect(Walk walk, int offset, int nameLength) {
byte[] table = walk.table;
boolean directory = (table[offset + 12] & ATTRIBUTE_DIRECTORY) != 0;
if (directory != walk.directories || walk.entries.size() >= MAXIMUM_ENTRIES)
return;
String name = new String(table, offset + ENTRY_FIXED_SIZE, nameLength, StandardCharsets.ISO_8859_1);
if (!isPlainName(name))
return;
// (the destination's file system may ignore case)
for (Entry entry : walk.entries) {
if (entry.name.equalsIgnoreCase(name))
return;
}
walk.entries.add(new Entry(name, u32(table, offset + 4), u32(table, offset + 8)));
}
/* ---------- the copy */
private static String readFailure(Entry file) {
return "Could not read " + file.name + " from the disc image (is it complete?).";
}
/** Copies one file of the image to output; returns the bytes copied of all the files so far. */
private static long copyFile(FileChannel image, long partition, Entry file, File output, ByteBuffer buffer,
long done, long total, Progress progress) throws IOException {
String writeFailure = "Could not write " + output.getPath() + " (is the storage full?).";
long position = partition + file.sector * SECTOR_SIZE;
long remaining = file.size;
FileOutputStream stream = null;
try {
stream = new FileOutputStream(output);
FileChannel channel = stream.getChannel();
while (remaining > 0) {
int count = (int) Math.min(remaining, COPY_CHUNK);
boolean read;
buffer.clear();
buffer.limit(count);
try {
read = readFully(image, buffer, position);
} catch (IOException exception) {
read = false;
}
if (!read)
throw new ExtractException(readFailure(file));
buffer.flip();
while (buffer.hasRemaining()) {
if (channel.write(buffer) <= 0)
throw new ExtractException(writeFailure);
}
position += count;
remaining -= count;
done += count;
if (progress != null)
progress.report(file.name, done, total);
}
FileOutputStream closing = stream;
stream = null;
closing.close();
} catch (ExtractException exception) {
throw exception;
} catch (IOException exception) {
throw new ExtractException(writeFailure);
} finally {
if (stream != null) {
try {
stream.close();
} catch (IOException ignored) {
// (the copy failed already)
}
}
}
return done;
}
}
+4
View File
@@ -75,6 +75,10 @@ void host_sdl_scancode_name(int scancode, char *buffer, unsigned int size);
int host_sdl_scancode_from_name(const char *name);
int host_sdl_show_toast(const char *message, int duration, int gravity, int x, int y);
int host_sdl_show_simple_message_box(unsigned int flags, const char *title, const char *message);
/* SDL_ShowMessageBox of up to three buttons (SDL_MessageBoxData has not the
guest's layout): the index of the one chosen, else -1 */
int host_sdl_show_message_box(unsigned int flags, const char *title, const char *message, int default_index,
const char *button0, const char *button1, const char *button2);
int host_sdl_get_gamepads(unsigned int *ids, int capacity);
unsigned int host_sdl_open_gamepad(unsigned int id);
unsigned int host_sdl_gamepad_from_id(unsigned int id);
+25
View File
@@ -95,6 +95,31 @@ bool SDL_ShowSimpleMessageBox(SDL_MessageBoxFlags flags, const char *title, cons
return host_sdl_show_simple_message_box((unsigned int)flags, title, message) != 0;
}
/* (up to three buttons, their ids those of the guest's) */
bool SDL_ShowMessageBox(const SDL_MessageBoxData *data, int *buttonid)
{
const char *labels[3] = { NULL, NULL, NULL };
int default_index = -1;
int index;
int chosen;
if (!data || data->numbuttons < 1 || data->numbuttons > 3)
return false;
for (index = 0; index < data->numbuttons; index++)
{
labels[index] = data->buttons[index].text;
if (data->buttons[index].flags & SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT)
default_index = index;
}
chosen = host_sdl_show_message_box((unsigned int)data->flags, data->title, data->message, default_index,
labels[0], labels[1], labels[2]);
if (chosen < 0 || chosen >= data->numbuttons)
return false;
if (buttonid)
*buttonid = data->buttons[chosen].buttonID;
return true;
}
void SDL_Delay(Uint32 milliseconds)
{
struct timespec duration;
+32
View File
@@ -514,3 +514,35 @@ int host_sdl_show_simple_message_box(uint32_t flags, const char *title, const ch
{
return SDL_ShowSimpleMessageBox((SDL_MessageBoxFlags)flags, title, message, NULL) ? 1 : 0;
}
/* ---------- a question for the player (the mods at startup: port_mods.c) */
int host_sdl_show_message_box(uint32_t flags, const char *title, const char *message, int default_index,
const char *button0, const char *button1, const char *button2)
{
const char *labels[3] = { button0, button1, button2 };
SDL_MessageBoxButtonData buttons[3];
SDL_MessageBoxData question;
int count = 0;
int answer = -1;
int index;
for (index = 0; index < 3; index++)
{
if (!labels[index])
continue;
buttons[count].flags = index == default_index ?
SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT | SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT : 0;
buttons[count].buttonID = index;
buttons[count].text = labels[index];
count++;
}
question.flags = (SDL_MessageBoxFlags)flags;
question.window = NULL;
question.title = title;
question.message = message;
question.numbuttons = count;
question.buttons = buttons;
question.colorScheme = NULL;
return SDL_ShowMessageBox(&question, &answer) ? answer : -1;
}
+1
View File
@@ -32,6 +32,7 @@ host_sdl_set_clipboard_text
host_sdl_get_clipboard_text
host_sdl_show_toast
host_sdl_show_simple_message_box
host_sdl_show_message_box
host_sdl_get_gamepads
host_sdl_open_gamepad
host_sdl_gamepad_from_id
+4
View File
@@ -237,6 +237,10 @@ cp "$BUNDLE/$LAUNCHER" "$GAME_DIR/$LAUNCHER.tmp" && chmod 755 "$GAME_DIR/$LAUNCH
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 build's number: the launcher updates from it, steam_frame_launch.sh;
# an update fetched for an older build is dropped)
cp "$BUNDLE/build.txt" "$GAME_DIR/build.txt" 2>/dev/null || rm -f "$GAME_DIR/build.txt"
rm -rf "$GAME_DIR/update"
# --- the game data
+72
View File
@@ -70,6 +70,75 @@ run()
child=0
}
# Updates (update.auto in config.toml; the game's own updater, the Quest's,
# does nothing here: Lepton installs the APK in this folder each start).
# While the game runs, the latest release is looked for; a newer one's
# bundle is fetched into update/, checked, and put in place as the next
# start begins, before Lepton runs the APK (it installs the newer one).
# A build that knows no number of its own (build.txt: a local one) never
# looks. Nothing changes without the internet, or when anything fails.
REPOSITORY="@REPOSITORY@"
UPDATE="$DATA/update"
BUILD_FILE="$DATA/build.txt"
SELF="$DATA/ringfall-mjolnir-vr.sh"
update_apply()
{
local new="$UPDATE/bundle" build
[ -f "$UPDATE/ready" ] || { rm -rf "$UPDATE"; return 0; }
build=$(cat "$new/build.txt" 2>/dev/null)
# (the launcher renamed over this one: bash goes on reading its own copy)
if cp "$new/ringfall-mjolnir-vr.sh" "$SELF.tmp" && chmod 755 "$SELF.tmp" &&
cp "$new/RingfallMjolnirVR.apk" "$APK.tmp" && chmod 666 "$APK.tmp" &&
mv -f "$SELF.tmp" "$SELF" && mv -f "$APK.tmp" "$APK" && touch "$APK" &&
cp "$new/build.txt" "$BUILD_FILE"
then
log "update: build $build put in place"
else
log "update: build $build could not be put in place"
rm -f "$SELF.tmp" "$APK.tmp"
fi
rm -rf "$UPDATE"
}
update_wanted()
{
# (update.auto = false in config.toml's [update]: never)
! awk '/^\[/ { section = $0 } section == "[update]" && /^[ \t]*auto[ \t]*=/ { print $3 }' \
"$DATA/config.toml" 2>/dev/null | grep -q false
}
update_check()
{
local installed latest
installed=$(cat "$BUILD_FILE" 2>/dev/null)
[[ "$installed" =~ ^[0-9]+$ ]] && (( installed > 0 )) || return 0
update_wanted || return 0
latest=$(curl -fsSL --max-time 20 -H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/$REPOSITORY/releases/latest" |
python3 -I -c 'import json, sys; print(json.load(sys.stdin)["tag_name"].rpartition("-")[2])' 2>/dev/null)
[[ "$latest" =~ ^[0-9]+$ ]] && (( latest > installed )) || return 0
log "update: build $latest found (this is $installed), fetching it"
rm -rf "$UPDATE" && mkdir -p "$UPDATE" || return 0
if curl -fsSL --retry 2 --max-time 900 -o "$UPDATE/bundle.zip" \
"https://github.com/$REPOSITORY/releases/download/build-$latest/ringfall-mjolnir-vr-steam-frame.zip" &&
python3 -I -m zipfile -e "$UPDATE/bundle.zip" "$UPDATE/bundle" &&
[ "$(cat "$UPDATE/bundle/build.txt" 2>/dev/null)" = "$latest" ] &&
[ -s "$UPDATE/bundle/RingfallMjolnirVR.apk" ] && [ -s "$UPDATE/bundle/ringfall-mjolnir-vr.sh" ]
then
rm -f "$UPDATE/bundle.zip"
touch "$UPDATE/ready"
log "update: build $latest fetched, put in place at the next start"
else
log "update: build $latest could not be fetched"
rm -rf "$UPDATE"
fi
}
update_apply
update_check </dev/null >/dev/null 2>&1 &
updater=$!
# (a new APK is only installed on its first start, not started; and a start
# right after a game that was ended hard can fail in SteamVR, which then
# needs a moment: again, up to three times, while it ends within 30 s)
@@ -83,4 +152,7 @@ for attempt in 1 2 3; do
sleep 5
done
rm -f "$QUIT"
# (a fetch still under way is given up: the next start looks again)
pkill -P "$updater" 2>/dev/null
kill "$updater" 2>/dev/null
log "end"
+172 -128
View File
@@ -1,66 +1,50 @@
#!/usr/bin/env python3
"""
Copies the maps folder out of an Xbox disc image (an "xiso", or a whole
disc's ".iso") on the Steam Frame's SteamOS side, for its installer
(steam_frame_install.sh). It reads the image as the app's own import does
Copies the maps folder out of the player's Xbox disc image of Halo (an
"xiso", or a whole disc's ".iso") on the Steam Frame's SteamOS side, for
its installer (steam_frame_install.sh), as the app's own import does
(app/src/main/java/com/halo/decomp/XisoExtractor.java, which this follows;
the desktop games' is port/linux/src/xiso.c).
steam_frame_xiso.py <image> <destination>
writes <destination>/maps (through maps.partial, so an interrupted copy
never leaves maps/ui.map behind) and prints its progress for zenity
--progress: a percentage on a line of its own, and "# <text>" lines. On
failure it prints "error: <reason the player can act on>" to stderr and
exits 1.
writes <destination>/maps (through maps.partial, ui.map moved in last, so
an interrupted copy never leaves maps/ui.map behind) and prints its
progress for zenity --progress: a percentage on a line of its own, and
"# <text>" lines. On failure it prints "error: <reason the player can act
on>" to stderr and exits 1.
The image's file system is XDVDFS, read as extract-xiso does
(https://github.com/XboxDev/extract-xiso, extract-xiso.c, whose format
handling this follows; its license is below and in
port/third_party/extract-xiso/LICENSE.TXT): 2048-byte sectors; a volume
descriptor at 0x10000 that starts and ends with "MICROSOFT*XBOX*MEDIA" and
gives the root directory's sector and size; and directories whose entries
form a binary tree (each entry: left and right subtree offsets in 4-byte
units, the start sector, the size, attributes, the name's length and the
name). Images made from a whole disc put the game partition further in, at
one of the offsets below.
Written from a description of the Xbox's disc file system, XDVDFS
(xboxdevwiki's XDVDFS page, https://xboxdevwiki.net/XDVDFS), as
XisoExtractor.java is:
Some parts of this code are copyright in@fishtank.com. This product
includes software developed by in <in@fishtank.com>.
- Sectors are 2048 bytes and integers little-endian. Sector numbers count
from the start of the game partition: an image of the partition alone has
it at 0, an image of a whole disc further in (PARTITION_STARTS, tried in
that order).
- The volume descriptor is the partition's sector 32 (at 0x10000). It holds
"MICROSOFT*XBOX*MEDIA" at its start and again at 0x7EC, and the root
directory table's start sector (u32 at 0x14) and size in bytes (u32 at
0x18).
- A directory table is a binary tree of entries whose root is the entry at
offset 0. An entry is a left and a right subtree link (u16 each, in 4-byte
units from the table's start, 0 for none), the start sector and the size
of the file or of the subdirectory's table (u32 each), the attributes (u8,
0x10 for a directory), the name's length (u8) and the name. Unused bytes
are 0xFF, so a left link of 0xFFFF is padding, not an entry.
- A file is one contiguous run of bytes from the start of its sector.
Copyright (c) 2003 in <in@fishtank.com>
All rights reserved.
The image is the player's file and nothing read from it is trusted: only
the root's table and the maps folder's are read, each whole and at most
4 MB; a table's walk visits each offset once, goes at most 64 links deep
and looks at most 4096 entries; and a file is written only under a plain
name that no file before it had (ignoring case).
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. All advertising materials mentioning features or use of this software
must display the following acknowledgement:
This product includes software developed by in <in@fishtank.com>.
4. Neither the name of "in" nor the email address "in@fishtank.com"
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED `AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
AUTHOR OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
A European disc in two languages (Spanish and Italian) has no maps folder
but one for each, maps_es and maps_it: the one for the language asked for
(RINGFALL_LANGUAGE, else the system's) is taken, else English's, else the
first, and its language written to maps/language.txt, which the game takes
as the Xbox's language while game.language is unset.
"""
import os
@@ -68,117 +52,170 @@ import struct
import sys
SECTOR_SIZE = 2048
VOLUME_DESCRIPTOR_OFFSET = 0x10000
ENTRY_HEADER_SIZE = 14
DESCRIPTOR_OFFSET = 0x10000
MAGIC = b"MICROSOFT*XBOX*MEDIA"
SECOND_MAGIC_OFFSET = 0x7EC
ENTRY_FIXED_SIZE = 14
ATTRIBUTE_DIRECTORY = 0x10
PADDING_LINK = 0xFFFF
# where the game partition may start: an image of the partition alone, a
# whole XGD2, XGD3 and XGD1 (the original Xbox's) disc
PARTITION_STARTS = (0x00000000, 0x0FD90000, 0x02080000, 0x18300000)
# directory tables are a few sectors; anything much larger is not one
MAXIMUM_DIRECTORY_SIZE = 4 << 20
MAXIMUM_FILES = 256
COPY_BUFFER_SIZE = 1 << 20
VOLUME_MAGIC = b"MICROSOFT*XBOX*MEDIA"
# where the game partition starts: a plain image, and whole-disc images
# (extract-xiso's GLOBAL, XGD3 and XGD1 offsets)
PARTITION_OFFSETS = (0, 0x0FD90000, 0x02080000, 0x18300000)
MAXIMUM_TABLE_SIZE = 4 << 20
MAXIMUM_DEPTH = 64
MAXIMUM_VISITS = 4096
MAXIMUM_ENTRIES = 256
COPY_CHUNK = 1 << 20
PARTIAL_DIRECTORY = "maps.partial"
class ExtractError(Exception):
"""a reason the player can act on"""
def read_at(image, offset, size):
image.seek(offset)
def read_exactly(image, position, size):
"""size bytes of the image from position; None if it ends first"""
image.seek(position)
data = image.read(size)
if len(data) != size:
raise ExtractError("Could not read the disc image (is it complete?).")
return data
return data if len(data) == size else None
def find_volume(image):
"""the game partition's offset, and the root directory's sector and size"""
for offset in PARTITION_OFFSETS:
try:
descriptor = read_at(image, offset + VOLUME_DESCRIPTOR_OFFSET, SECTOR_SIZE)
except ExtractError:
def find_partition(image, image_size):
"""the game partition's start, and the root table's sector and size"""
for start in PARTITION_STARTS:
if start + DESCRIPTOR_OFFSET + SECTOR_SIZE > image_size:
continue
if descriptor[:20] != VOLUME_MAGIC or descriptor[0x7EC:0x7EC + 20] != VOLUME_MAGIC:
descriptor = read_exactly(image, start + DESCRIPTOR_OFFSET, SECTOR_SIZE)
if (descriptor is None or descriptor[:len(MAGIC)] != MAGIC or
descriptor[SECOND_MAGIC_OFFSET:SECOND_MAGIC_OFFSET + len(MAGIC)] != MAGIC):
continue
sector, size = struct.unpack_from("<II", descriptor, 20)
return offset, sector, size
sector, size = struct.unpack_from("<II", descriptor, 0x14)
return start, sector, size
raise ExtractError("This is not an Xbox disc image.")
def read_directory(image, partition, sector, size, what):
if size <= 0 or size > MAXIMUM_DIRECTORY_SIZE:
raise ExtractError(what)
return read_at(image, partition + sector * SECTOR_SIZE, size)
def read_table(image, partition, sector, size, damaged):
if size == 0 or size > MAXIMUM_TABLE_SIZE:
raise ExtractError(damaged)
table = read_exactly(image, partition + sector * SECTOR_SIZE, size)
if table is None:
raise ExtractError("Could not read the disc image (is it complete?).")
return table
def walk(table, offset, depth, directories, out, visited):
"""collects the entries (name, sector, size) of the subtree at offset
(4-byte units), files or directories as asked"""
at = offset * 4
# a malformed table must not loop or run off its end
visited[0] += 1
if depth > 64 or visited[0] > 4096 or at + ENTRY_HEADER_SIZE > len(table):
return
left, right, sector, size, attributes, name_length = struct.unpack_from("<HHIIBB", table, at)
# 0xFFFF: padding, an empty directory
if left == 0xFFFF:
return
if left:
walk(table, left, depth + 1, directories, out, visited)
is_directory = bool(attributes & ATTRIBUTE_DIRECTORY)
if (at + ENTRY_HEADER_SIZE + name_length <= len(table) and name_length > 0
and is_directory == directories and len(out) < MAXIMUM_FILES):
name = table[at + ENTRY_HEADER_SIZE:at + ENTRY_HEADER_SIZE + name_length].decode("latin-1")
# (as extract-xiso refuses them: no name may leave the folder)
if name not in (".", "..") and "/" not in name and "\\" not in name:
out.append((name, sector, size))
if right:
walk(table, right, depth + 1, directories, out, visited)
def is_plain_name(name):
"""no path, no . or .., and only printable ASCII that every file system
takes as a plain name"""
if not name or name in (".", ".."):
return False
return all(0x20 <= ord(character) <= 0x7E and character not in '/\\:*?"<>|' for character in name)
def walk_table(table, directories):
"""the folders (or the files) of a table with plain names, as (name,
sector, size), in the tree's order: each entry's left subtree, the entry,
its right subtree; a link to padding, past the table or back to an entry
already visited ends that branch"""
entries, visited = [], set()
visits = [0]
def walk(offset, depth):
if depth > MAXIMUM_DEPTH or visits[0] >= MAXIMUM_VISITS:
return
if offset >= len(table) or len(table) - offset < ENTRY_FIXED_SIZE:
return
left, right = struct.unpack_from("<HH", table, offset)
if left == PADDING_LINK or offset // 4 in visited:
return
visited.add(offset // 4)
visits[0] += 1
if left:
walk(left * 4, depth + 1)
name_length = table[offset + 13]
if name_length and name_length <= len(table) - offset - ENTRY_FIXED_SIZE:
is_directory = bool(table[offset + 12] & ATTRIBUTE_DIRECTORY)
name = table[offset + ENTRY_FIXED_SIZE:offset + ENTRY_FIXED_SIZE + name_length].decode("latin-1")
# (the destination's file system may ignore case)
if (is_directory == directories and len(entries) < MAXIMUM_ENTRIES and is_plain_name(name) and
all(entry[0].lower() != name.lower() for entry in entries)):
sector, size = struct.unpack_from("<II", table, offset + 4)
entries.append((name, sector, size))
if right:
walk(right * 4, depth + 1)
walk(0, 0)
return entries
def maps_language(name):
"""a language's maps folder's language ("maps_it": "it"), else None"""
if len(name) == 7 and name[:5].lower() == "maps_" and name[5:].isalpha():
return name[5:].lower()
return None
def choose_maps(folders):
"""the maps folder, and its language (None for a plain "maps")"""
for folder in folders:
if folder[0].lower() == "maps":
return folder, None
languages = [(maps_language(folder[0]), folder) for folder in folders if maps_language(folder[0])]
wanted = (os.environ.get("RINGFALL_LANGUAGE") or os.environ.get("LC_ALL") or
os.environ.get("LC_MESSAGES") or os.environ.get("LANG") or "")[:2].lower()
for preferred in (wanted, "en"):
for language, folder in languages:
if language == preferred:
return folder, language
if languages:
return languages[0][1], languages[0][0]
raise ExtractError("The disc image has no maps folder: it is not a Halo disc.")
def extract(image_path, destination):
with open(image_path, "rb") as image:
partition, root_sector, root_size = find_volume(image)
image_size = os.fstat(image.fileno()).st_size
partition, root_sector, root_size = find_partition(image, image_size)
# the root's maps folder
table = read_directory(image, partition, root_sector, root_size,
"The disc image's file system is damaged.")
directories = []
walk(table, 0, 0, True, directories, [0])
maps = [entry for entry in directories if entry[0].lower() == "maps"]
if not maps:
raise ExtractError("The disc image has no maps folder: it is not a Halo disc.")
root = read_table(image, partition, root_sector, root_size, "The disc image's file system is damaged.")
(_, maps_sector, maps_size), language = choose_maps(walk_table(root, True))
# its files
table = read_directory(image, partition, maps[-1][1], maps[-1][2],
"The disc image's maps folder is damaged.")
files = []
walk(table, 0, 0, False, files, [0])
if not any(name.lower() == "ui.map" for name, _, _ in files):
table = read_table(image, partition, maps_sector, maps_size, "The disc image's maps folder is damaged.")
files = walk_table(table, False)
user_interface = next((file for file in files if file[0].lower() == "ui.map"), None)
if user_interface is None:
raise ExtractError("The disc image's maps folder has no ui.map: it is not a Halo disc.")
# (an image that ends before a file does is refused before anything
# is written; an empty file's sector need not be anywhere)
for name, sector, size in files:
if size and partition + sector * SECTOR_SIZE + size > image_size:
raise ExtractError("Could not read %s from the disc image (is it complete?)." % name)
total = sum(size for _, _, size in files) or 1
partial = os.path.join(destination, "maps.partial")
finished = os.path.join(destination, "maps")
# the copy, to maps.partial (emptied of what an earlier attempt left)
partial = os.path.join(destination, PARTIAL_DIRECTORY)
try:
os.makedirs(partial, exist_ok=True)
for leftover in os.listdir(partial):
os.remove(os.path.join(partial, leftover))
except OSError:
raise ExtractError("Could not create %s." % partial)
done = 0
shown = -1
done, shown = 0, -1
for name, sector, size in files:
path = os.path.join(partial, name)
offset = partition + sector * SECTOR_SIZE
remaining = size
position, remaining = partition + sector * SECTOR_SIZE, size
print("# Copying maps/%s" % name, flush=True)
try:
with open(path, "wb") as out:
while remaining > 0:
count = min(remaining, COPY_BUFFER_SIZE)
out.write(read_at(image, offset, count))
offset += count
count = min(remaining, COPY_CHUNK)
data = read_exactly(image, position, count)
if data is None:
raise ExtractError("Could not read %s from the disc image (is it complete?)." % name)
out.write(data)
position += count
remaining -= count
done += count
# (zenity closes at 100: that is left to the caller)
@@ -186,15 +223,22 @@ def extract(image_path, destination):
if percent != shown:
print(percent, flush=True)
shown = percent
except ExtractError:
raise ExtractError("Could not read %s from the disc image (is it complete?)." % name)
except OSError:
raise ExtractError("Could not write %s (is the storage full?)." % path)
# (the maps folder may be there, empty)
# then into maps (which may be there, empty), the language before and
# ui.map (the sign that the data is there) last
finished = os.path.join(destination, "maps")
try:
os.makedirs(finished, exist_ok=True)
for name, _, _ in files:
language_file = os.path.join(finished, "language.txt")
if os.path.exists(language_file):
os.remove(language_file)
if language:
with open(language_file, "w") as out:
out.write(language + "\n")
order = [file for file in files if file is not user_interface] + [user_interface]
for name, _, _ in order:
os.replace(os.path.join(partial, name), os.path.join(finished, name))
os.rmdir(partial)
except OSError as error:
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

+314
View File
@@ -207,6 +207,320 @@
]
}
]
},
{
"name": "black_butn__0",
"tag": "ui\\shell\\bitmaps\\black_butn",
"bitmap": 0,
"width": 32,
"height": 32,
"format": "dxt3",
"scale": 8,
"crc": 0,
"svg": "black_butn.svg",
"place": [
1,
0,
0
]
},
{
"name": "white_butn__0",
"tag": "ui\\shell\\bitmaps\\white_butn",
"bitmap": 0,
"width": 32,
"height": 32,
"format": "dxt3",
"scale": 8,
"crc": 0,
"svg": "white_butn.svg",
"place": [
1,
0,
0
]
},
{
"name": "classic_a__0",
"tag": "ui\\hud\\buttons\\classic_a",
"bitmap": 0,
"width": 32,
"height": 32,
"format": "dxt3",
"scale": 8,
"crc": 0,
"svg": "a_butn.svg",
"place": [
1,
0,
0
],
"classic": "#40eb40",
"letter": [
0.1685,
0.1119
]
},
{
"name": "classic_b__0",
"tag": "ui\\hud\\buttons\\classic_b",
"bitmap": 0,
"width": 32,
"height": 32,
"format": "dxt3",
"scale": 8,
"crc": 0,
"svg": "b_butn.svg",
"place": [
1,
0,
0
],
"classic": "#fa5946",
"letter": [
0.696,
0.1204
]
},
{
"name": "classic_x__0",
"tag": "ui\\hud\\buttons\\classic_x",
"bitmap": 0,
"width": 32,
"height": 32,
"format": "dxt3",
"scale": 8,
"crc": 0,
"svg": "x_butn.svg",
"place": [
1,
0,
0
],
"classic": "#3998fa",
"letter": [
0.3363,
2.4589
]
},
{
"name": "classic_y__0",
"tag": "ui\\hud\\buttons\\classic_y",
"bitmap": 0,
"width": 32,
"height": 32,
"format": "dxt3",
"scale": 8,
"crc": 0,
"svg": "y_butn.svg",
"place": [
1,
0,
0
],
"classic": "#f9e928",
"letter": [
-0.4376,
2.2898
]
},
{
"name": "classic_black__0",
"tag": "ui\\hud\\buttons\\classic_black",
"bitmap": 0,
"width": 32,
"height": 32,
"format": "dxt3",
"scale": 8,
"crc": 0,
"svg": "black_butn.svg",
"place": [
1,
0,
0
],
"classic": "#000000"
},
{
"name": "classic_white__0",
"tag": "ui\\hud\\buttons\\classic_white",
"bitmap": 0,
"width": 32,
"height": 32,
"format": "dxt3",
"scale": 8,
"crc": 0,
"svg": "white_butn.svg",
"place": [
1,
0,
0
],
"classic": "#ffffff"
},
{
"name": "gem_a__0",
"tag": "ui\\hud\\buttons\\gem_a",
"bitmap": 0,
"width": 32,
"height": 32,
"format": "dxt3",
"scale": 8,
"crc": 0,
"source_png": "source/a_gem.png"
},
{
"name": "gem_b__0",
"tag": "ui\\hud\\buttons\\gem_b",
"bitmap": 0,
"width": 32,
"height": 32,
"format": "dxt3",
"scale": 8,
"crc": 0,
"source_png": "source/b_gem.png"
},
{
"name": "gem_x__0",
"tag": "ui\\hud\\buttons\\gem_x",
"bitmap": 0,
"width": 32,
"height": 32,
"format": "dxt3",
"scale": 8,
"crc": 0,
"source_png": "source/x_gem.png"
},
{
"name": "gem_y__0",
"tag": "ui\\hud\\buttons\\gem_y",
"bitmap": 0,
"width": 32,
"height": 32,
"format": "dxt3",
"scale": 8,
"crc": 0,
"source_png": "source/y_gem.png"
},
{
"name": "trigger_left__0",
"tag": "ui\\hud\\buttons\\trigger_left",
"bitmap": 0,
"width": 64,
"height": 64,
"format": "a8r8g8b8",
"scale": 8,
"crc": 0,
"source_png": "source/trigger_left.png",
"control_cell": [
50,
35
]
},
{
"name": "trigger_right__0",
"tag": "ui\\hud\\buttons\\trigger_right",
"bitmap": 0,
"width": 64,
"height": 64,
"format": "a8r8g8b8",
"scale": 8,
"crc": 0,
"source_png": "source/trigger_right.png",
"control_cell": [
50,
35
]
},
{
"name": "thumbstick_left__0",
"tag": "ui\\hud\\buttons\\thumbstick_left",
"bitmap": 0,
"width": 64,
"height": 64,
"format": "a8r8g8b8",
"scale": 8,
"crc": 0,
"source_png": "source/thumbstick_left.png",
"control_cell": [
36,
40
]
},
{
"name": "thumbstick_right__0",
"tag": "ui\\hud\\buttons\\thumbstick_right",
"bitmap": 0,
"width": 64,
"height": 64,
"format": "a8r8g8b8",
"scale": 8,
"crc": 0,
"source_png": "source/thumbstick_right.png",
"control_cell": [
36,
40
]
},
{
"name": "thumbstick-halfway_left__0",
"tag": "ui\\hud\\buttons\\thumbstick-halfway_left",
"bitmap": 0,
"width": 64,
"height": 64,
"format": "a8r8g8b8",
"scale": 8,
"crc": 0,
"source_png": "source/thumbstick-halfway_left.png",
"control_cell": [
36,
40
]
},
{
"name": "thumbstick-halfway_right__0",
"tag": "ui\\hud\\buttons\\thumbstick-halfway_right",
"bitmap": 0,
"width": 64,
"height": 64,
"format": "a8r8g8b8",
"scale": 8,
"crc": 0,
"source_png": "source/thumbstick-halfway_right.png",
"control_cell": [
36,
40
]
},
{
"name": "thumbstick-full_left__0",
"tag": "ui\\hud\\buttons\\thumbstick-full_left",
"bitmap": 0,
"width": 64,
"height": 64,
"format": "a8r8g8b8",
"scale": 8,
"crc": 0,
"source_png": "source/thumbstick-full_left.png",
"control_cell": [
36,
40
]
},
{
"name": "thumbstick-full_right__0",
"tag": "ui\\hud\\buttons\\thumbstick-full_right",
"bitmap": 0,
"width": 64,
"height": 64,
"format": "a8r8g8b8",
"scale": 8,
"crc": 0,
"source_png": "source/thumbstick-full_right.png",
"control_cell": [
36,
40
]
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 522 KiB

+21
View File
@@ -0,0 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<!-- Original Xbox black button, derived from the OpenCE button redraw. -->
<defs>
<linearGradient id="body" gradientUnits="userSpaceOnUse" x1="0" y1="-12.932" x2="0" y2="12.932">
<stop offset="0" stop-color="#050505"/>
<stop offset="0.5" stop-color="#202020"/>
<stop offset="1" stop-color="#454545"/>
</linearGradient>
<linearGradient id="rim" gradientUnits="userSpaceOnUse" x1="0" y1="-12.932" x2="0" y2="12.932">
<stop offset="0" stop-color="#ffffff" stop-opacity="0.74"/>
<stop offset="1" stop-color="#ffffff" stop-opacity="0.73"/>
</linearGradient>
</defs>
<g transform="translate(10.341 12.879) rotate(-26.459)">
<ellipse id="oval" rx="8.659" ry="12.932" fill="url(#body)"/>
<ellipse rx="8.082" ry="12.355" fill="none" stroke="url(#rim)" stroke-width="1.154"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 866 B

+21
View File
@@ -0,0 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<!-- Original Xbox white button, derived from the OpenCE button redraw. -->
<defs>
<linearGradient id="body" gradientUnits="userSpaceOnUse" x1="0" y1="-12.932" x2="0" y2="12.932">
<stop offset="0" stop-color="#999999"/>
<stop offset="0.5" stop-color="#dddddd"/>
<stop offset="1" stop-color="#ffffff"/>
</linearGradient>
<linearGradient id="rim" gradientUnits="userSpaceOnUse" x1="0" y1="-12.932" x2="0" y2="12.932">
<stop offset="0" stop-color="#ffffff" stop-opacity="0.74"/>
<stop offset="1" stop-color="#ffffff" stop-opacity="0.73"/>
</linearGradient>
</defs>
<g transform="translate(10.341 12.879) rotate(-26.459)">
<ellipse id="oval" rx="8.659" ry="12.932" fill="url(#body)"/>
<ellipse rx="8.082" ry="12.355" fill="none" stroke="url(#rim)" stroke-width="1.154"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 866 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 369 KiB

After

Width:  |  Height:  |  Size: 330 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 183 KiB

After

Width:  |  Height:  |  Size: 170 KiB

+201 -7
View File
@@ -302,7 +302,70 @@
108
],
"kind": "meter",
"score": 0.683
"score": 0.683,
"thresholds": [
4,
8,
12,
16,
20,
24,
28,
32,
36,
40,
44,
48,
52,
56,
60,
64,
68,
72,
76,
80,
84,
88,
92,
96,
100,
104,
108,
112,
116,
120,
124,
128,
132,
136,
140,
144,
148,
152,
156,
160,
164,
168,
172,
176,
180,
184,
188,
192,
196,
200,
204,
208,
212,
216,
220,
224,
228,
232,
236,
240
],
"quantize_thresholds": true
},
{
"xbox": [
@@ -324,7 +387,22 @@
180
],
"kind": "meter",
"score": 0.676
"score": 0.676,
"thresholds": [
10,
30,
50,
70,
90,
110,
130,
150,
170,
190,
210,
230
],
"quantize_thresholds": true
},
{
"xbox": [
@@ -346,7 +424,22 @@
248
],
"kind": "meter",
"score": 0.747
"score": 0.747,
"thresholds": [
10,
30,
50,
70,
90,
110,
130,
150,
170,
190,
210,
230
],
"quantize_thresholds": true
},
{
"xbox": [
@@ -434,7 +527,30 @@
248
],
"kind": "meter",
"score": 0.721
"score": 0.721,
"thresholds": [
12,
24,
36,
48,
60,
72,
84,
96,
108,
120,
132,
144,
156,
168,
180,
192,
204,
216,
228,
240
],
"quantize_thresholds": true
},
{
"xbox": [
@@ -456,7 +572,14 @@
88
],
"kind": "meter",
"score": 0.852
"score": 0.852,
"thresholds": [
60,
120,
180,
240
],
"quantize_thresholds": true
},
{
"xbox": [
@@ -478,7 +601,12 @@
180
],
"kind": "meter",
"score": 0.82
"score": 0.82,
"thresholds": [
120,
240
],
"quantize_thresholds": true
}
]
},
@@ -3237,7 +3365,7 @@
50,
18
],
"svg": "hud2_left_reticles__reticle_18.svg",
"svg": "sniper_diamond.svg",
"source_scale": 1.365,
"source": [
-3.5831,
@@ -3355,6 +3483,72 @@
"score": 0.996
}
]
},
{
"name": "vehicle_wrench_outline__0",
"tag": "ui\\hud\\redraws\\vehicle_wrench_outline",
"bitmap": 0,
"width": 128,
"height": 32,
"format": "ay8",
"scale": 4,
"crc": 0,
"cells": [
{
"xbox": [
0,
0,
128,
32
],
"svg": "vehicle_wrench_outline.svg",
"source_scale": 1,
"source": [
0,
0
],
"clip": [
0,
0,
128,
32
],
"kind": "alpha"
}
]
},
{
"name": "passenger_health_outline__0",
"tag": "ui\\hud\\redraws\\passenger_health_outline",
"bitmap": 0,
"width": 128,
"height": 32,
"format": "ay8",
"scale": 4,
"crc": 0,
"cells": [
{
"xbox": [
0,
0,
128,
32
],
"svg": "passenger_health_outline.svg",
"source_scale": 1,
"source": [
0,
0
],
"clip": [
0,
0,
128,
32
],
"kind": "alpha"
}
]
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

@@ -28,12 +28,12 @@
<stop offset="0" stop-color="#ffffff"/><stop offset="1" stop-color="#000000"/>
</linearGradient>
<linearGradient id="ab0" gradientUnits="userSpaceOnUse" x1="0" y1="322.996" x2="0" y2="339.002"><stop offset="0" stop-color="#b9b9b9"/><stop offset="1" stop-color="#b3b3b3"/></linearGradient>
<mask id="mbx0" maskUnits="userSpaceOnUse" x="0" y="0" width="1024" height="512"><path d="M48.13,323 L416.49,323 L423.87,339 L55.51,339 Z" fill="url(#ab0)"/></mask>
<mask id="mbx0" maskUnits="userSpaceOnUse" x="0" y="0" width="1024" height="512"><path d="M53.5,327 L408.5,327 L412.2,335 L57.2,335 Z" fill="url(#ab0)"/></mask>
<linearGradient id="cb1" gradientUnits="userSpaceOnUse" x1="-88.616" y1="40.907" x2="242.068" y2="-111.744">
<stop offset="0" stop-color="#ffffff"/><stop offset="1" stop-color="#000000"/>
</linearGradient>
<linearGradient id="ab1" gradientUnits="userSpaceOnUse" x1="0" y1="363.996" x2="0" y2="392.003"><stop offset="0" stop-color="#b9b9b9"/><stop offset="1" stop-color="#aeaeae"/></linearGradient>
<mask id="mbx1" maskUnits="userSpaceOnUse" x="0" y="0" width="1024" height="512"><path d="M63.33,364 L459.69,364 L472.66,392 L76.29,392 Z" fill="url(#ab1)"/></mask>
<mask id="mbx1" maskUnits="userSpaceOnUse" x="0" y="0" width="1024" height="512"><path d="M68,368 L454,368 L463.3,388 L77.3,388 Z" fill="url(#ab1)"/></mask>
</defs>
<g transform="translate(0,0)" mask="url(#m1)"><rect x="564.89" y="6" width="393.11" height="60.03" fill="url(#c1)"/></g>
<g transform="translate(0,-1)" mask="url(#m4)"><rect x="49.21" y="143" width="435.41" height="112.02" fill="url(#c4)"/></g>

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="32" viewBox="0 0 128 32"><!-- Existing meter contour, aligned by the Xbox HUD tag offsets. --><path transform="translate(-.25,-55) scale(.25)" d="M327.21,274.16 L339.43,274.16 L334.08,285.74 L345.32,285.74 L340.46,296.25 L329.23,296.25 L323.88,307.82 L311.66,307.82 L317.01,296.25 L305.77,296.25 L310.63,285.74 L321.86,285.74 Z" fill="none" stroke="#fff" stroke-width="3.34"/></svg>

After

Width:  |  Height:  |  Size: 443 B

+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<path d="M8 2.7 C9.5 2.7 10.1 3.6 11.7 5.2 C13.8 7.3 13.8 8.7 11.7 10.8 C10.1 12.4 9.5 13.3 8 13.3 C6.5 13.3 5.9 12.4 4.3 10.8 C2.2 8.7 2.2 7.3 4.3 5.2 C5.9 3.6 6.5 2.7 8 2.7 Z" fill="none" stroke="#fff" stroke-width="1.3"/><circle cx="8" cy="8" r=".65" fill="#fff"/>
</svg>

After

Width:  |  Height:  |  Size: 359 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="32" viewBox="0 0 128 32"><!-- Existing meter contour, aligned by the Xbox HUD tag offsets. --><path transform="translate(-1.25,0) scale(.25)" d="M328.14,53.64 C327.98,53.7 327.27,53.81 326.57,53.89 C324.82,54.06 324.04,54.24 323.36,54.57 C323.04,54.74 322.43,54.92 322,54.99 C321.54,55.06 321,55.26 320.64,55.47 C320.32,55.69 319.77,55.91 319.41,56 C319.06,56.07 318.59,56.29 318.35,56.46 C318.12,56.62 317.66,56.91 317.31,57.07 C316.4,57.52 315.11,58.26 314.55,58.66 C313.98,59.07 313.38,59.49 312.69,59.95 C311.82,60.54 309.75,62.41 308.05,64.14 C305.23,67.03 304.94,66.88 313.35,66.88 C320.88,66.88 320.89,66.88 321.69,67.78 C322.69,68.88 322.07,70.7 320.11,72.45 C318.82,73.6 318.44,73.88 317.94,74 C317.66,74.08 317.21,74.21 316.94,74.31 C316.51,74.47 315.23,74.5 309.1,74.5 L301.76,74.5 L301.41,74.8 L301.06,75.1 L301.06,80.78 C301.06,86 301.07,86.47 301.29,86.76 C301.4,86.92 301.65,87.29 301.84,87.56 C302.36,88.36 303.52,89.61 303.99,89.91 C304.23,90.05 304.6,90.33 304.8,90.51 C305.01,90.69 305.41,90.91 305.69,91 C305.96,91.09 306.44,91.31 306.75,91.51 C307.14,91.75 307.61,91.91 308.25,92 C308.76,92.08 309.52,92.22 309.95,92.33 C310.85,92.56 315.31,92.53 315.9,92.28 C316.09,92.2 316.8,92.08 317.5,92.01 C318.32,91.92 319.09,91.75 319.73,91.51 C320.25,91.31 321.02,91.05 321.44,90.94 C321.85,90.83 322.32,90.62 322.5,90.51 C322.68,90.39 323.15,90.17 323.56,90.04 C323.98,89.9 324.59,89.61 324.93,89.39 C325.27,89.17 325.61,89 325.7,89 C325.77,89 326.12,88.81 326.46,88.58 C326.79,88.35 327.19,88.12 327.35,88.08 C327.5,88.03 327.85,87.79 328.14,87.55 C328.43,87.31 328.86,87.03 329.1,86.94 C329.34,86.84 329.71,86.6 329.94,86.41 C330.16,86.21 330.7,85.78 331.12,85.44 C331.55,85.09 332.27,84.5 332.73,84.11 C333.19,83.72 333.69,83.35 333.84,83.28 C334.29,83.03 356.52,83.06 357,83.31 C357.52,83.59 357.85,84.08 357.94,84.72 C358.05,85.56 358.25,86.14 358.56,86.56 C358.73,86.76 358.96,87.16 359.1,87.44 C359.59,88.41 362.02,90.66 362.93,90.96 C363.2,91.06 363.73,91.31 364.07,91.51 C364.48,91.74 365.02,91.91 365.59,92 C366.06,92.06 366.82,92.2 367.26,92.31 C368.39,92.58 372.21,92.58 373.09,92.31 C373.45,92.2 374.31,92.06 375.01,92 C375.98,91.91 376.49,91.79 377.11,91.5 C377.56,91.3 378.21,91.08 378.56,91 C378.9,90.92 379.46,90.7 379.81,90.5 C380.15,90.31 380.68,90.09 380.95,90.01 C381.24,89.94 381.69,89.72 381.95,89.54 C382.23,89.36 382.73,89.1 383.06,88.97 C383.41,88.84 383.8,88.62 383.94,88.5 C384.07,88.38 384.41,88.16 384.69,88.03 C385.29,87.72 386.39,87.03 387.06,86.5 C387.34,86.29 387.93,85.84 388.38,85.5 C390.16,84.12 390.45,83.89 391.68,82.69 C392.38,82 393.1,81.24 393.27,81 C393.45,80.76 393.9,80.21 394.26,79.79 C395.91,77.88 396.15,77.44 395.73,77.16 C395.64,77.11 392.55,77.09 388.88,77.1 C380.81,77.15 381.38,77.22 379.48,75.75 C378.15,74.72 380.57,71.14 383.59,69.69 L384.75,69.12 L392.12,69.12 C400.81,69.12 399.93,69.31 400.06,67.42 C400.29,64.56 400,61.07 399.49,60.36 C399.36,60.19 399.16,59.79 399.04,59.49 C398.38,57.86 396.06,55.59 394.45,54.96 C394.1,54.84 393.69,54.64 393.51,54.51 C393.05,54.19 392.45,54.04 391.35,53.94 C390.81,53.9 390.15,53.79 389.86,53.69 C389.12,53.42 385.86,53.42 385.11,53.69 C384.81,53.8 383.94,53.94 383.15,54 C382.01,54.09 381.56,54.19 380.9,54.49 C380.44,54.69 379.69,54.94 379.23,55.04 C378.76,55.15 378.16,55.39 377.88,55.56 C377.6,55.74 377.19,55.91 376.96,55.96 C376.5,56.05 375.94,56.31 375.51,56.64 C375.35,56.77 375,56.94 374.74,57 C374.48,57.07 374.1,57.27 373.9,57.44 C373.7,57.61 373.26,57.86 372.94,58 C372.6,58.14 372.15,58.42 371.91,58.62 C371.69,58.84 371.44,59 371.38,59 C371.3,59 371.04,59.2 370.76,59.44 C370.5,59.67 370.01,60 369.68,60.15 L369.06,60.44 L355.8,60.47 L342.54,60.5 L342.31,60.24 C342.18,60.1 341.96,59.8 341.81,59.56 C341.15,58.47 340.79,58 340.07,57.26 C339.18,56.34 337.55,55 337.31,55 C337.23,55 336.74,54.8 336.24,54.56 C335.55,54.24 334.98,54.09 334,53.95 C333.27,53.85 332.43,53.7 332.12,53.64 C331.43,53.47 328.54,53.47 328.14,53.64 Z M327.19,57.01 C326.71,57.07 326.04,57.21 325.69,57.3 C323.85,57.82 322.59,58.25 322.23,58.47 C322,58.61 321.48,58.82 321.06,58.94 C320.65,59.06 320,59.34 319.62,59.56 C319.25,59.79 318.51,60.21 318,60.5 C315.26,62.07 314.46,62.82 314.99,63.3 C315.18,63.48 315.61,63.5 317.89,63.5 C321.77,63.5 322.84,63.8 324.21,65.22 C325.81,66.88 325.89,70.24 324.4,72.69 C323.9,73.5 321.79,75.65 321.19,75.96 C320.99,76.08 320.61,76.31 320.38,76.5 C320.14,76.69 319.71,76.91 319.44,77 C319.16,77.09 318.6,77.3 318.19,77.49 L317.44,77.81 L310.88,77.84 C304.71,77.86 304.3,77.88 304,78.1 C302.6,79.12 303.02,83.61 304.74,85.89 C305.91,87.45 307.93,88.69 309.79,89.01 C311.21,89.25 316.19,89.09 317.31,88.78 C319.38,88.19 320.96,87.66 321.25,87.46 C321.43,87.35 321.81,87.17 322.12,87.06 C322.43,86.95 322.94,86.71 323.25,86.51 C323.56,86.33 324.01,86.09 324.25,86 C324.49,85.9 324.85,85.69 325.05,85.53 C325.26,85.36 325.71,85.1 326.07,84.94 C326.43,84.79 326.76,84.59 326.81,84.51 C326.85,84.42 327.11,84Line truncated

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

+10 -4
View File
@@ -1,11 +1,17 @@
# Lucide icons
Two icons of [Lucide](https://lucide.dev) 1.53.0 (ISC licence: `LICENSE`),
Four icons of [Lucide](https://lucide.dev) 1.53.0 (ISC licence: `LICENSE`),
as `icons/` has them at the `1.53.0` tag of
https://github.com/lucide-icons/lucide:
- `volume-2.svg`: voice chat's speaker, of a player who talks;
- `volume-x.svg`: ... of a player muted.
- `volume-x.svg`: ... of a player muted;
- `triangle-alert.svg`: the Mods menu's sign of a mod that loads on the
wrong side of a mod it asks to load before or after;
- `circle-x.svg`: ... of a mod that cannot be played, or that another mod
says it cannot be played with.
`tools/ce_menus.py` draws them, white, as the menus' bitmap `voice/speaker`
(port/linux/game/network_voice.c tints it). Credited in `AUTHORS.md`.
`tools/ce_menus.py` draws the first two, white, as the menus' bitmap
`voice/speaker` (port/linux/game/network_voice.c tints it), and the others,
amber and red, as `mods/status` and the smaller `mods/status_small`
(`tools/port_settings.py`). Credited in `AUTHORS.md`.
+15
View File
@@ -0,0 +1,15 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="12" cy="12" r="10" />
<path d="m15 9-6 6" />
<path d="m9 9 6 6" />
</svg>

After

Width:  |  Height:  |  Size: 293 B

@@ -0,0 +1,15 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" />
<path d="M12 9v4" />
<path d="M12 17h.01" />
</svg>

After

Width:  |  Height:  |  Size: 345 B

+1 -1
View File
@@ -62,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 164 frames are placeholders.
13 of 177 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.
+2 -2
View File
@@ -10,7 +10,7 @@ do (save a setting, join a game) does not happen; the screens open and close as
| Function | Uses | |
| --- | --- | --- |
| `common button bar update` | 27 | PC function |
| `common button bar update` | 28 | PC function |
| `direct ip connect init` | 1 | PC function |
| `direct ip connect update` | 1 | PC function |
| `direct ip edit field` | 4 | PC function |
@@ -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` | 82 | PC function |
| `mouse spinner 1wide click` | 84 | 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 |
+26
View File
@@ -314,6 +314,9 @@
<bitmap name="main_menu/solo_level_select/header_load_level">
<frame png="ce/shell/main_menu/solo_level_select/header_load_level__0.png" width="512" height="64"/>
</bitmap>
<bitmap name="main_menu/mods/header_mods">
<frame png="ce/port/main_menu/mods/header_mods.png" width="512" height="64"/>
</bitmap>
<bitmap name="main_menu/multiplayer_type_select/coop/header_player_2">
<frame png="ce/port/main_menu/multiplayer_type_select/coop/header_player_2.png" width="512" height="64"/>
</bitmap>
@@ -356,10 +359,33 @@
<frame png="ce/port/pause/pausebox_right__1.png" width="16" height="256"/>
<frame png="ce/port/pause/pausebox_right__2.png" width="16" height="256"/>
</bitmap>
<bitmap name="pause/objectives_left">
<frame png="ce/port/pause/objectives_left__0.png" width="16" height="256"/>
</bitmap>
<bitmap name="pause/objectives_center">
<frame png="ce/port/pause/objectives_center__0.png" width="4" height="256"/>
</bitmap>
<bitmap name="pause/objectives_right">
<frame png="ce/port/pause/objectives_right__0.png" width="16" height="256"/>
</bitmap>
<bitmap name="voice/speaker">
<frame png="ce/port/voice/speaker__0.png" width="24" height="24"/>
<frame png="ce/port/voice/speaker__1.png" width="24" height="24"/>
</bitmap>
<bitmap name="main_menu/menu_mods">
<frame png="ce/port/main_menu/menu_mods__0.png" width="256" height="64"/>
<frame png="ce/port/main_menu/menu_mods__1.png" width="256" height="64"/>
</bitmap>
<bitmap name="mods/status">
<frame png="ce/port/mods/status__0.png" width="24" height="24"/>
<frame png="ce/port/mods/status__1.png" width="24" height="24"/>
<frame png="ce/port/mods/status__2.png" width="24" height="24"/>
</bitmap>
<bitmap name="mods/status_small">
<frame png="ce/port/mods/status_small__0.png" width="16" height="16"/>
<frame png="ce/port/mods/status_small__1.png" width="16" height="16"/>
<frame png="ce/port/mods/status_small__2.png" width="16" height="16"/>
</bitmap>
<bitmap name="blank">
<frame png="ce/port/blank__0.png" width="4" height="4"/>
</bitmap>
+183
View File
@@ -0,0 +1,183 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- The port's Mods screen, in the PC version's style (tools/port_settings.py) -->
<menus>
<widget name="main_menu/main_menu_item_mods" type="text" width="256" height="33" bitmap="main_menu/menu_mods">
<on event="a" open="main_menu/mods/mods_screen"/>
<on event="start" open="main_menu/mods/mods_screen"/>
<on event="left_mouse" run="mouse emit accept event"/>
</widget>
<widget name="main_menu/mods/mods_screen" width="640" height="480" flags="pass_unhandled_to_focused_child pause_game" bitmap="bitmaps/gradient">
<on event="b" back="true"/>
<on event="back" back="true"/>
<child widget="main_menu/new_select/sel_list_desc_bkd"/>
<child widget="main_menu/mods/mods_list"/>
<child widget="main_menu/mods/header_mods"/>
</widget>
<widget name="main_menu/mods/header_mods" controller="1" left="35" top="11" width="363" height="59" bitmap="main_menu/mods/header_mods">
</widget>
<widget name="main_menu/mods/mods_list" type="column_list" width="640" height="480" flags="pass_unhandled_to_focused_child up_down_tabs_children" description="main_menu/mods/mod_details">
<data input="port mods update"/>
<on event="created" run="port mods init"/>
<on event="deleted" run="port mods close"/>
<child widget="main_menu/mods/op_mod_1" x="20" y="73"/>
<child widget="main_menu/mods/op_mod_2" x="20" y="103"/>
<child widget="main_menu/mods/op_mod_3" x="20" y="133"/>
<child widget="main_menu/mods/op_mod_4" x="20" y="163"/>
<child widget="main_menu/mods/op_mod_5" x="20" y="193"/>
<child widget="main_menu/mods/op_mod_6" x="20" y="223"/>
<child widget="main_menu/mods/op_mod_7" x="20" y="253"/>
<child widget="main_menu/mods/op_mod_8" x="20" y="283"/>
<child widget="main_menu/mods/op_mod_9" x="20" y="313"/>
<child widget="main_menu/mods/op_mod_10" x="20" y="343"/>
<child widget="main_menu/mods/op_mod_11" x="20" y="373"/>
<child widget="main_menu/mods/button_bar" y="414"/>
</widget>
<widget name="main_menu/mods/op_mod_1" width="390" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/sel_list_item_bkd_top" color="#FF2896FF">
<on event="a" run="port mods toggle"/>
<on event="start" run="port mods toggle"/>
<on event="left" run="port mods move up"/>
<on event="right" run="port mods move down"/>
<on event="left_mouse" run="mouse emit accept event"/>
<child widget="main_menu/mods/mod_status" x="8" y="2"/>
<child widget="main_menu/mods/mod_name" x="34"/>
<child widget="main_menu/mods/mod_state" x="318"/>
</widget>
<widget name="main_menu/mods/op_mod_2" width="390" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/sel_list_item_bkd" color="#FF2896FF">
<on event="a" run="port mods toggle"/>
<on event="start" run="port mods toggle"/>
<on event="left" run="port mods move up"/>
<on event="right" run="port mods move down"/>
<on event="left_mouse" run="mouse emit accept event"/>
<child widget="main_menu/mods/mod_status" x="8" y="2"/>
<child widget="main_menu/mods/mod_name" x="34"/>
<child widget="main_menu/mods/mod_state" x="318"/>
</widget>
<widget name="main_menu/mods/op_mod_3" width="390" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/sel_list_item_bkd" color="#FF2896FF">
<on event="a" run="port mods toggle"/>
<on event="start" run="port mods toggle"/>
<on event="left" run="port mods move up"/>
<on event="right" run="port mods move down"/>
<on event="left_mouse" run="mouse emit accept event"/>
<child widget="main_menu/mods/mod_status" x="8" y="2"/>
<child widget="main_menu/mods/mod_name" x="34"/>
<child widget="main_menu/mods/mod_state" x="318"/>
</widget>
<widget name="main_menu/mods/op_mod_4" width="390" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/sel_list_item_bkd" color="#FF2896FF">
<on event="a" run="port mods toggle"/>
<on event="start" run="port mods toggle"/>
<on event="left" run="port mods move up"/>
<on event="right" run="port mods move down"/>
<on event="left_mouse" run="mouse emit accept event"/>
<child widget="main_menu/mods/mod_status" x="8" y="2"/>
<child widget="main_menu/mods/mod_name" x="34"/>
<child widget="main_menu/mods/mod_state" x="318"/>
</widget>
<widget name="main_menu/mods/op_mod_5" width="390" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/sel_list_item_bkd" color="#FF2896FF">
<on event="a" run="port mods toggle"/>
<on event="start" run="port mods toggle"/>
<on event="left" run="port mods move up"/>
<on event="right" run="port mods move down"/>
<on event="left_mouse" run="mouse emit accept event"/>
<child widget="main_menu/mods/mod_status" x="8" y="2"/>
<child widget="main_menu/mods/mod_name" x="34"/>
<child widget="main_menu/mods/mod_state" x="318"/>
</widget>
<widget name="main_menu/mods/op_mod_6" width="390" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/sel_list_item_bkd" color="#FF2896FF">
<on event="a" run="port mods toggle"/>
<on event="start" run="port mods toggle"/>
<on event="left" run="port mods move up"/>
<on event="right" run="port mods move down"/>
<on event="left_mouse" run="mouse emit accept event"/>
<child widget="main_menu/mods/mod_status" x="8" y="2"/>
<child widget="main_menu/mods/mod_name" x="34"/>
<child widget="main_menu/mods/mod_state" x="318"/>
</widget>
<widget name="main_menu/mods/op_mod_7" width="390" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/sel_list_item_bkd" color="#FF2896FF">
<on event="a" run="port mods toggle"/>
<on event="start" run="port mods toggle"/>
<on event="left" run="port mods move up"/>
<on event="right" run="port mods move down"/>
<on event="left_mouse" run="mouse emit accept event"/>
<child widget="main_menu/mods/mod_status" x="8" y="2"/>
<child widget="main_menu/mods/mod_name" x="34"/>
<child widget="main_menu/mods/mod_state" x="318"/>
</widget>
<widget name="main_menu/mods/op_mod_8" width="390" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/sel_list_item_bkd" color="#FF2896FF">
<on event="a" run="port mods toggle"/>
<on event="start" run="port mods toggle"/>
<on event="left" run="port mods move up"/>
<on event="right" run="port mods move down"/>
<on event="left_mouse" run="mouse emit accept event"/>
<child widget="main_menu/mods/mod_status" x="8" y="2"/>
<child widget="main_menu/mods/mod_name" x="34"/>
<child widget="main_menu/mods/mod_state" x="318"/>
</widget>
<widget name="main_menu/mods/op_mod_9" width="390" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/sel_list_item_bkd" color="#FF2896FF">
<on event="a" run="port mods toggle"/>
<on event="start" run="port mods toggle"/>
<on event="left" run="port mods move up"/>
<on event="right" run="port mods move down"/>
<on event="left_mouse" run="mouse emit accept event"/>
<child widget="main_menu/mods/mod_status" x="8" y="2"/>
<child widget="main_menu/mods/mod_name" x="34"/>
<child widget="main_menu/mods/mod_state" x="318"/>
</widget>
<widget name="main_menu/mods/op_mod_10" width="390" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/sel_list_item_bkd" color="#FF2896FF">
<on event="a" run="port mods toggle"/>
<on event="start" run="port mods toggle"/>
<on event="left" run="port mods move up"/>
<on event="right" run="port mods move down"/>
<on event="left_mouse" run="mouse emit accept event"/>
<child widget="main_menu/mods/mod_status" x="8" y="2"/>
<child widget="main_menu/mods/mod_name" x="34"/>
<child widget="main_menu/mods/mod_state" x="318"/>
</widget>
<widget name="main_menu/mods/op_mod_11" width="390" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/sel_list_item_bkd" color="#FF2896FF">
<on event="a" run="port mods toggle"/>
<on event="start" run="port mods toggle"/>
<on event="left" run="port mods move up"/>
<on event="right" run="port mods move down"/>
<on event="left_mouse" run="mouse emit accept event"/>
<child widget="main_menu/mods/mod_status" x="8" y="2"/>
<child widget="main_menu/mods/mod_name" x="34"/>
<child widget="main_menu/mods/mod_state" x="318"/>
</widget>
<widget name="main_menu/mods/mod_status" controller="1" width="24" height="24" bitmap="mods/status">
</widget>
<widget name="main_menu/mods/mod_name" type="text" controller="1" width="284" height="22" font="ui\large_ui" color="#FF2896FF" text_y="4">
</widget>
<widget name="main_menu/mods/mod_state" type="text" controller="1" width="66" height="22" font="ui\large_ui" color="#FF2896FF" align="center" text_y="4" text_flags="no_focus_test">
</widget>
<widget name="main_menu/mods/mod_details" width="640" height="480">
<child widget="main_menu/mods/mod_details_name"/>
<child widget="main_menu/mods/mod_details_status"/>
<child widget="main_menu/mods/mod_details_text"/>
</widget>
<widget name="main_menu/mods/mod_details_name" type="text" controller="1" left="412" top="84" width="200" height="44" font="ui\large_ui" color="#FFFFFFFF">
</widget>
<widget name="main_menu/mods/mod_details_status" controller="1" left="411" top="132" width="16" height="16" bitmap="mods/status_small">
</widget>
<widget name="main_menu/mods/mod_details_text" type="text" controller="1" left="412" top="130" width="200" height="272" font="ui\small_ui" color="#FF2896FF">
</widget>
<widget name="main_menu/mods/button_bar" type="column_list" width="640" height="28" flags="pass_unhandled_to_focused_child left_right_tabs_items">
<child widget="main_menu/mods/button_up" y="1"/>
<child widget="main_menu/mods/button_down" x="130" y="1"/>
<child widget="main_menu/mods/button_ok" x="380" y="1"/>
<child widget="common_button_cancel" x="510" y="1"/>
</widget>
<widget name="main_menu/mods/button_up" type="text" width="128" height="24" bitmap="bitmaps/text_button_background" text="MOVE UP" font="ui\small_ui" color="#FFFFFFFF" align="center" text_y="2">
<on event="a" run="port mods move up"/>
<on event="start" run="port mods move up"/>
<on event="left_mouse" run="mouse emit accept event"/>
</widget>
<widget name="main_menu/mods/button_down" type="text" width="128" height="24" bitmap="bitmaps/text_button_background" text="MOVE DOWN" font="ui\small_ui" color="#FFFFFFFF" align="center" text_y="2">
<on event="a" run="port mods move down"/>
<on event="start" run="port mods move down"/>
<on event="left_mouse" run="mouse emit accept event"/>
</widget>
<widget name="main_menu/mods/button_ok" type="text" width="128" height="24" bitmap="bitmaps/text_button_background" string_list="strings/common_button_captions" string_index="1" font="ui\small_ui" color="#FFFFFFFF" align="center" text_y="2">
<on event="a" run="port mods save" back="true"/>
<on event="start" run="port mods save" back="true"/>
<on event="left_mouse" run="mouse emit accept event"/>
</widget>
</menus>
@@ -219,7 +219,7 @@
<string text="Anyone can see and join your game: it is listed\nin everyone's Server Browser."/>
<string text="Only players with your invite link can join."/>
<string text="Friendly fire, the enemies and player collisions,\nfor this game."/>
<string text="Voice chat and votes to kick a player, for this\ngame."/>
<string text="Voice chat, text chat and votes to kick a player,\nfor this game."/>
<string text="Players from the Server Browser must type it to\njoin (an invite link needs none). Enter sets it."/>
<string text="Sending players who join without the game's\nCustom Edition map the map, and how fast."/>
</strings>
@@ -369,6 +369,7 @@
<child widget="main_menu/multiplayer_type_select/server_settings/voice_options/op_voice_kbps" x="54" y="133"/>
<child widget="main_menu/multiplayer_type_select/server_settings/voice_options/op_voice_proximity" x="54" y="163"/>
<child widget="main_menu/multiplayer_type_select/server_settings/voice_options/op_votekick" x="54" y="193"/>
<child widget="main_menu/multiplayer_type_select/server_settings/voice_options/op_text_chat" x="54" y="223"/>
<child widget="main_menu/multiplayer_type_select/server_settings/voice_options/voice_options_button_bar" y="414"/>
</widget>
<widget name="main_menu/multiplayer_type_select/server_settings/voice_options/voice_options_button_bar" type="column_list" width="640" height="28" flags="pass_unhandled_to_focused_child left_right_tabs_items">
@@ -458,12 +459,27 @@
<string text="ON"/>
<string text="OFF"/>
</strings>
<widget name="main_menu/multiplayer_type_select/server_settings/voice_options/op_text_chat" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
<child widget="main_menu/multiplayer_type_select/server_settings/voice_options/text_chat_label"/>
<child widget="main_menu/multiplayer_type_select/server_settings/voice_options/text_chat_spinner" x="286" y="1"/>
</widget>
<widget name="main_menu/multiplayer_type_select/server_settings/voice_options/text_chat_label" type="text" controller="1" width="300" height="22" string_list="main_menu/multiplayer_type_select/server_settings/voice_options/voice_options_labels" string_index="5" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/multiplayer_type_select/server_settings/voice_options/text_chat_spinner" type="spinner" top="2" width="206" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" string_list="main_menu/multiplayer_type_select/server_settings/voice_options/var_text_chat" font="ui\large_ui" color="#FF2896FF" align="center" text_y="4" list_flags="items_from_strings" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -13 19 -7" footer_bounds="7 208 19 214">
<on event="left_mouse" run="mouse spinner 1wide click"/>
</widget>
<strings name="main_menu/multiplayer_type_select/server_settings/voice_options/var_text_chat">
<string text="ON"/>
<string text="WITH LINKS"/>
<string text="OFF"/>
</strings>
<strings name="main_menu/multiplayer_type_select/server_settings/voice_options/voice_options_labels">
<string text="VOICE CHAT:"/>
<string text="LOBBY VOICE CHAT:"/>
<string text="VOICE QUALITY:"/>
<string text="VOICE NEAR DISTANCE:"/>
<string text="VOTE KICK:"/>
<string text="TEXT CHAT:"/>
</strings>
<strings name="main_menu/multiplayer_type_select/server_settings/voice_options/cap_voice_options">
<string text="No voice chat during the game."/>
@@ -488,6 +504,9 @@
<string text="Players 150 metres apart or less are near\nfor voice chat."/>
<string text="Players can vote to kick a player, from the\nscoreboard."/>
<string text="Players cannot vote to kick anyone."/>
<string text="Players can type to each other; lines with links\nare refused. Off in a public internet game."/>
<string text="Players can type to each other, links too. Off\nin a public internet game."/>
<string text="No text chat in the game."/>
</strings>
<widget name="main_menu/multiplayer_type_select/server_settings/map_share_options/map_share_options_screen" width="640" height="480" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/gradient">
<child widget="main_menu/multiplayer_type_select/server_settings/map_share_options/header_map_share_options"/>
@@ -543,6 +562,8 @@
<string text="2 MB/S"/>
<string text="4 MB/S"/>
<string text="8 MB/S"/>
<string text="16 MB/S"/>
<string text="20 MB/S"/>
</strings>
<strings name="main_menu/multiplayer_type_select/server_settings/map_share_options/map_share_options_labels">
<string text="SHARE MAPS:"/>
@@ -557,5 +578,7 @@
<string text="The most you send of maps, so that the game\nkeeps the rest of your upload."/>
<string text="The most you send of maps, so that the game\nkeeps the rest of your upload."/>
<string text="The most you send of maps, so that the game\nkeeps the rest of your upload."/>
<string text="The most you send of maps, so that the game\nkeeps the rest of your upload."/>
<string text="The most you send of maps, so that the game\nkeeps the rest of your upload."/>
</strings>
</menus>
@@ -60,13 +60,13 @@
<child widget="main_menu/multiplayer_type_select/multiplayer_type_create_internet_item" y="243"/>
<child widget="main_menu/multiplayer_type_select/multiplayer_type_create_lan_item" y="276"/>
<child widget="main_menu/multiplayer_type_select/multiplayer_type_coop_item" y="309"/>
<child widget="main_menu/multiplayer_type_select/multiplayer_type_gametypes_item" y="375"/>
<child widget="main_menu/multiplayer_type_select/multiplayer_type_gametypes_item" y="358"/>
<child widget="main_menu/multiplayer_type_select/multiplayer_type_button_bar" y="414"/>
</widget>
<widget name="main_menu/multiplayer_type_select/multiplayer_type_select_screen" width="640" height="480" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/gradient">
<on event="created" run="mp type menu initialize"/>
<child widget="main_menu/multiplayer_type_select/header_multiplayer"/>
<child widget="main_menu/multiplayer_type_select/multiplayer_type_select_list"/>
<child widget="main_menu/blueline" x="64" y="358"/>
<child widget="main_menu/blueline" x="64" y="349"/>
</widget>
</menus>
@@ -78,4 +78,18 @@
<string text="ON"/>
<string text="OFF"/>
</strings>
<widget name="main_menu/settings_select/multiplayer_setup/teamplay_options_edit/op_text_chat" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
<child widget="main_menu/settings_select/multiplayer_setup/teamplay_options_edit/text_chat_label"/>
<child widget="main_menu/settings_select/multiplayer_setup/teamplay_options_edit/text_chat_spinner" x="286" y="1"/>
</widget>
<widget name="main_menu/settings_select/multiplayer_setup/teamplay_options_edit/text_chat_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/multiplayer_setup/teamplay_options_edit/teamplay_options_labels" string_index="8" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/multiplayer_setup/teamplay_options_edit/text_chat_spinner" type="spinner" top="2" width="206" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" string_list="main_menu/settings_select/multiplayer_setup/teamplay_options_edit/var_text_chat" font="ui\large_ui" color="#FF2896FF" align="center" text_y="4" list_flags="items_from_strings" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -13 19 -7" footer_bounds="7 208 19 214">
<on event="left_mouse" run="mouse spinner 1wide click"/>
</widget>
<strings name="main_menu/settings_select/multiplayer_setup/teamplay_options_edit/var_text_chat">
<string text="ON"/>
<string text="WITH LINKS"/>
<string text="OFF"/>
</strings>
</menus>
@@ -53,6 +53,7 @@
<child widget="main_menu/settings_select/multiplayer_setup/teamplay_options_edit/op_voice_kbps" x="54" y="223"/>
<child widget="main_menu/settings_select/multiplayer_setup/teamplay_options_edit/op_voice_proximity" x="54" y="253"/>
<child widget="main_menu/settings_select/multiplayer_setup/teamplay_options_edit/op_votekick" x="54" y="283"/>
<child widget="main_menu/settings_select/multiplayer_setup/teamplay_options_edit/op_text_chat" x="54" y="313"/>
<child widget="main_menu/settings_select/multiplayer_setup/teamplay_options_edit/teamplay_button_bar" y="414"/>
</widget>
<widget name="main_menu/settings_select/multiplayer_setup/teamplay_options_edit/teamplay_options_screen" width="640" height="480" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/gradient">
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- The port's settings screen, in the PC version's style (tools/port_settings.py) -->
<menus>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/sound_processing/sound_processing_settings_screen" width="640" height="480" flags="pass_unhandled_to_focused_child pause_game" bitmap="bitmaps/gradient">
<on event="b" back="true"/>
<on event="back" back="true"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/sound_processing/header_profile_audio_settings"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/sound_processing/options_menu"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/sound_processing/header_profile_audio_settings" controller="1" left="35" top="11" width="605" height="59" bitmap="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/header_profile_audio_settings">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/sound_processing/help" type="text" controller="1" left="68" top="350" width="482" height="60" string_list="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/sound_processing/help_strings" font="ui\large_ui" color="#FFFFFFFF">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/sound_processing/options_menu" type="column_list" width="640" height="480" flags="pass_unhandled_to_focused_child up_down_tabs_children" description="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/sound_processing/help">
<data input="port settings help"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/sound_processing/op_reverb" x="54" y="73"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/sound_processing/op_resampler" x="54" y="103"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/sound_processing/button_bar" y="414"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/sound_processing/button_bar" type="column_list" width="640" height="28" flags="pass_unhandled_to_focused_child left_right_tabs_items">
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/sound_processing/button_defaults" y="1"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/sound_processing/button_ok" x="380" y="1"/>
<child widget="common_button_cancel" x="510" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/sound_processing/op_reverb" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/sound_processing/reverb_label"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/sound_processing/reverb_spinner" x="320" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/sound_processing/reverb_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/sound_processing/labels" string_index="0" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/sound_processing/reverb_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="ON|OFF" setting="audio.reverb" values="true|false" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="created" run="port setting load"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/sound_processing/op_resampler" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/sound_processing/resampler_label"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/sound_processing/resampler_spinner" x="320" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/sound_processing/resampler_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/sound_processing/labels" string_index="1" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/sound_processing/resampler_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="SINC|LINEAR" setting="audio.resampler" values="sinc|linear" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="created" run="port setting load"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/sound_processing/button_defaults" type="text" width="128" height="24" bitmap="bitmaps/text_button_background" string_list="strings/common_button_captions" string_index="3" font="ui\small_ui" color="#FFFFFFFF" align="center" text_y="2">
<on event="a" run="port settings defaults"/>
<on event="start" run="port settings defaults"/>
<on event="left_mouse" run="mouse emit accept event"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/sound_processing/button_ok" type="text" width="128" height="24" bitmap="bitmaps/text_button_background" string_list="strings/common_button_captions" string_index="1" font="ui\small_ui" color="#FFFFFFFF" align="center" text_y="2">
<on event="a" run="port settings save" back="true"/>
<on event="start" run="port settings save" back="true"/>
<on event="left_mouse" run="mouse emit accept event"/>
</widget>
<strings name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/sound_processing/labels">
<string text="REVERB:"/>
<string text="RESAMPLER:"/>
</strings>
<strings name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/sound_processing/help_strings">
<string text=""/>
<string text="Echo sounds as the place you are in does, and\nmuffle those behind walls, as the Xbox did."/>
<string text="How sounds are converted to the output's rate:\nsinc is clean; linear is brighter, with aliasing."/>
</strings>
</menus>
@@ -21,14 +21,14 @@
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/op_music_volume" x="54" y="103" platform="android web"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/op_effects_volume" x="54" y="193" platform="desktop"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/op_effects_volume" x="54" y="133" platform="android web"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/op_reverb" x="54" y="223" platform="desktop"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/op_reverb" x="54" y="163" platform="android web"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/op_enabled" x="54" y="253" platform="desktop"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/op_enabled" x="54" y="193" platform="android web"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/op_voice_chat" x="54" y="283" platform="desktop"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/op_voice_chat" x="54" y="223" platform="android web"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/op_voice_volume" x="54" y="313" platform="desktop"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/op_voice_volume" x="54" y="253" platform="android web"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/op_enabled" x="54" y="223" platform="desktop"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/op_enabled" x="54" y="163" platform="android web"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/op_voice_chat" x="54" y="253" platform="desktop"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/op_voice_chat" x="54" y="193" platform="android web"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/op_voice_volume" x="54" y="283" platform="desktop"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/op_voice_volume" x="54" y="223" platform="android web"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/op_sound_processing" x="54" y="313" platform="desktop"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/op_sound_processing" x="54" y="253" platform="android web"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/button_bar" y="414"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/button_bar" type="column_list" width="640" height="28" flags="pass_unhandled_to_focused_child left_right_tabs_items">
@@ -81,20 +81,11 @@
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/effects_volume_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="0|1|2|3|4|5|6|7|8|9|10" setting="audio.effects_volume" values="0|0.1|0.2|0.3|0.4|0.5|0.6|0.7|0.8|0.9|1" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="created" run="port setting load"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/op_reverb" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/reverb_label"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/reverb_spinner" x="320" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/reverb_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/labels" string_index="5" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/reverb_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="ON|OFF" setting="audio.reverb" values="true|false" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="created" run="port setting load"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/op_enabled" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/enabled_label"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/enabled_spinner" x="320" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/enabled_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/labels" string_index="6" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/enabled_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/labels" string_index="5" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/enabled_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="ON|OFF" setting="audio.enabled" values="true|false" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="created" run="port setting load"/>
@@ -103,7 +94,7 @@
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/voice_chat_label"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/voice_chat_spinner" x="320" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/voice_chat_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/labels" string_index="7" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/voice_chat_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/labels" string_index="6" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/voice_chat_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="PUSH TO TALK|OPEN MIC|OFF" setting="audio.voice_chat" values="push_to_talk|open_mic|off" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="created" run="port setting load"/>
@@ -112,11 +103,19 @@
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/voice_volume_label"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/voice_volume_spinner" x="320" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/voice_volume_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/labels" string_index="8" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/voice_volume_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/labels" string_index="7" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/voice_volume_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="0|1|2|3|4|5|6|7|8|9|10" setting="audio.voice_volume" values="0|0.1|0.2|0.3|0.4|0.5|0.6|0.7|0.8|0.9|1" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="created" run="port setting load"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/op_sound_processing" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
<on event="a" open="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/sound_processing/sound_processing_settings_screen"/>
<on event="start" open="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/sound_processing/sound_processing_settings_screen"/>
<on event="left_mouse" run="mouse emit accept event"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/sound_processing_label"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/sound_processing_label" type="text" controller="1" width="512" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/labels" string_index="8" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/button_defaults" type="text" width="128" height="24" bitmap="bitmaps/text_button_background" string_list="strings/common_button_captions" string_index="3" font="ui\small_ui" color="#FFFFFFFF" align="center" text_y="2">
<on event="a" run="port settings defaults"/>
<on event="start" run="port settings defaults"/>
@@ -133,10 +132,10 @@
<string text="MASTER VOLUME:"/>
<string text="MUSIC VOLUME:"/>
<string text="EFFECTS VOLUME:"/>
<string text="REVERB:"/>
<string text="SOUND:"/>
<string text="VOICE CHAT:"/>
<string text="VOICE VOLUME:"/>
<string text="SOUND PROCESSING:"/>
</strings>
<strings name="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/help_strings">
<string text=""/>
@@ -145,9 +144,9 @@
<string text="The volume of everything."/>
<string text="The music's volume."/>
<string text="The volume of every other sound: effects and\nspeech."/>
<string text="Echo sounds as the place you are in does, and\nmuffle those behind walls, as the Xbox did."/>
<string text="Play sound at all; from the next time the game\nstarts."/>
<string text="Talk in network games: while PUSH TO TALK is held\n(Controls), whenever you speak, or never."/>
<string text="The other players' voices."/>
<string text="The reverb, and how sounds are converted to the\noutput's rate."/>
</strings>
</menus>
@@ -35,7 +35,7 @@
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/controls_setup/group_label" type="text" controller="1" width="200" height="22" text="SHOW:" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/controls_setup/group_spinner" type="spinner" left="3" top="2" width="297" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="MOVEMENT|WEAPONS|ACTIONS" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 300 19 306">
<widget name="main_menu/settings_select/player_setup/player_profile_edit/controls_setup/group_spinner" type="spinner" left="3" top="2" width="297" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="MOVEMENT|WEAPONS|ACTIONS|COMMUNICATION" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 300 19 306">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/controls_setup/op_command_1" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
<on event="a" run="controls begin binding"/>
@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- The port's settings screen, in the PC version's style (tools/port_settings.py) -->
<menus>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/in_game/in_game_network_settings_screen" width="640" height="480" flags="pass_unhandled_to_focused_child pause_game" bitmap="bitmaps/gradient">
<on event="b" back="true"/>
<on event="back" back="true"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/in_game/header_profile_network_settings"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/in_game/options_menu"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/in_game/header_profile_network_settings" controller="1" left="35" top="11" width="605" height="59" bitmap="main_menu/settings_select/player_setup/player_profile_edit/network_setup/header_profile_network_settings">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/in_game/help" type="text" controller="1" left="68" top="350" width="482" height="60" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/in_game/help_strings" font="ui\large_ui" color="#FFFFFFFF">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/in_game/options_menu" type="column_list" width="640" height="480" flags="pass_unhandled_to_focused_child up_down_tabs_children" description="main_menu/settings_select/player_setup/player_profile_edit/network_setup/in_game/help">
<data input="port settings help"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/in_game/op_map_sharing" x="54" y="73" platform="desktop android"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/in_game/op_multiplayer_hud" x="54" y="103" platform="desktop android"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/in_game/op_multiplayer_hud" x="54" y="73" platform="web"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/in_game/op_streamer_mode" x="54" y="133" platform="desktop android"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/in_game/op_streamer_mode" x="54" y="103" platform="web"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/in_game/button_bar" y="414"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/in_game/button_bar" type="column_list" width="640" height="28" flags="pass_unhandled_to_focused_child left_right_tabs_items">
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/in_game/button_defaults" y="1"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/in_game/button_ok" x="380" y="1"/>
<child widget="common_button_cancel" x="510" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/in_game/op_map_sharing" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF" platform="desktop android">
<on event="a" open="main_menu/settings_select/player_setup/player_profile_edit/network_setup/map_sharing/map_sharing_settings_screen"/>
<on event="start" open="main_menu/settings_select/player_setup/player_profile_edit/network_setup/map_sharing/map_sharing_settings_screen"/>
<on event="left_mouse" run="mouse emit accept event"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/in_game/map_sharing_label"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/in_game/map_sharing_label" type="text" controller="1" width="512" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/in_game/labels" string_index="0" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/in_game/op_multiplayer_hud" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
<on event="a" open="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/multiplayer_hud_settings_screen"/>
<on event="start" open="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/multiplayer_hud_settings_screen"/>
<on event="left_mouse" run="mouse emit accept event"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/in_game/multiplayer_hud_label"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/in_game/multiplayer_hud_label" type="text" controller="1" width="512" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/in_game/labels" string_index="1" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/in_game/op_streamer_mode" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
<on event="a" open="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/streamer_mode_settings_screen"/>
<on event="start" open="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/streamer_mode_settings_screen"/>
<on event="left_mouse" run="mouse emit accept event"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/in_game/streamer_mode_label"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/in_game/streamer_mode_label" type="text" controller="1" width="512" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/in_game/labels" string_index="2" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/in_game/button_defaults" type="text" width="128" height="24" bitmap="bitmaps/text_button_background" string_list="strings/common_button_captions" string_index="3" font="ui\small_ui" color="#FFFFFFFF" align="center" text_y="2">
<on event="a" run="port settings defaults"/>
<on event="start" run="port settings defaults"/>
<on event="left_mouse" run="mouse emit accept event"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/in_game/button_ok" type="text" width="128" height="24" bitmap="bitmaps/text_button_background" string_list="strings/common_button_captions" string_index="1" font="ui\small_ui" color="#FFFFFFFF" align="center" text_y="2">
<on event="a" run="port settings save" back="true"/>
<on event="start" run="port settings save" back="true"/>
<on event="left_mouse" run="mouse emit accept event"/>
</widget>
<strings name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/in_game/labels">
<string text="MAP SHARING:"/>
<string text="MULTIPLAYER HUD:"/>
<string text="STREAMER MODE:"/>
</strings>
<strings name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/in_game/help_strings">
<string text=""/>
<string text="Downloading Custom Edition maps you do not have,\nand helping send them to other players."/>
<string text="Players' names above their heads, the\nscoreboard, and the text chat."/>
<string text="Hide offensive words in names and text from\nother players, maps and mods, as stars."/>
</strings>
</menus>
@@ -47,7 +47,7 @@
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/map_sharing/map_seed_upload_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/map_sharing/labels" string_index="2" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/map_sharing/map_seed_upload_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="64 KB/S|128 KB/S|256 KB/S|512 KB/S|1 MB/S|2 MB/S" setting="network.map_seed_upload" values="64|128|256|512|1024|2048" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/map_sharing/map_seed_upload_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="64 KB/S|128 KB/S|256 KB/S|512 KB/S|1 MB/S|2 MB/S|4 MB/S|8 MB/S|10 MB/S" setting="network.map_seed_upload" values="64|128|256|512|1024|2048|4096|8192|10240" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="created" run="port setting load"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/map_sharing/button_defaults" type="text" width="128" height="24" bitmap="bitmaps/text_button_background" string_list="strings/common_button_captions" string_index="3" font="ui\small_ui" color="#FFFFFFFF" align="center" text_y="2">
@@ -0,0 +1,110 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- The port's settings screen, in the PC version's style (tools/port_settings.py) -->
<menus>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/multiplayer_hud_settings_screen" width="640" height="480" flags="pass_unhandled_to_focused_child pause_game" bitmap="bitmaps/gradient">
<on event="b" back="true"/>
<on event="back" back="true"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/header_profile_network_settings"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/options_menu"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/header_profile_network_settings" controller="1" left="35" top="11" width="605" height="59" bitmap="main_menu/settings_select/player_setup/player_profile_edit/network_setup/header_profile_network_settings">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/help" type="text" controller="1" left="68" top="350" width="482" height="60" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/help_strings" font="ui\large_ui" color="#FFFFFFFF">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/options_menu" type="column_list" width="640" height="480" flags="pass_unhandled_to_focused_child up_down_tabs_children" description="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/help">
<data input="port settings help"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/op_player_names" x="54" y="73"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/op_player_name_scale" x="54" y="103"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/op_scoreboard_team_layout" x="54" y="133"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/op_scoreboard_background" x="54" y="163"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/op_text_chat" x="54" y="193"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/op_chat_style" x="54" y="223"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/button_bar" y="414"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/button_bar" type="column_list" width="640" height="28" flags="pass_unhandled_to_focused_child left_right_tabs_items">
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/button_defaults" y="1"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/button_ok" x="380" y="1"/>
<child widget="common_button_cancel" x="510" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/op_player_names" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/player_names_label"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/player_names_spinner" x="320" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/player_names_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/labels" string_index="0" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/player_names_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="ALL|ALLIES|ENEMIES|NONE" setting="display.player_names" values="all|allies|enemies|none" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="created" run="port setting load"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/op_player_name_scale" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/player_name_scale_label"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/player_name_scale_spinner" x="320" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/player_name_scale_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/labels" string_index="1" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/player_name_scale_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="0.5x|0.75x|1x|1.25x|1.5x|2x" setting="display.player_name_scale" values="0.5|0.75|1|1.25|1.5|2" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="created" run="port setting load"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/op_scoreboard_team_layout" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/scoreboard_team_layout_label"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/scoreboard_team_layout_spinner" x="320" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/scoreboard_team_layout_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/labels" string_index="2" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/scoreboard_team_layout_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="TEAMS|BY SCORE" setting="display.scoreboard_team_layout" values="teams|score" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="created" run="port setting load"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/op_scoreboard_background" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/scoreboard_background_label"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/scoreboard_background_spinner" x="320" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/scoreboard_background_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/labels" string_index="3" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/scoreboard_background_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="ON|OFF" setting="display.scoreboard_background" values="true|false" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="created" run="port setting load"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/op_text_chat" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/text_chat_label"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/text_chat_spinner" x="320" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/text_chat_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/labels" string_index="4" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/text_chat_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="SHOW|HIDE" setting="display.text_chat" values="true|false" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="created" run="port setting load"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/op_chat_style" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/chat_style_label"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/chat_style_spinner" x="320" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/chat_style_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/labels" string_index="5" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/chat_style_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="COLORS|WHITE" setting="display.chat_style" values="colors|white" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="created" run="port setting load"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/button_defaults" type="text" width="128" height="24" bitmap="bitmaps/text_button_background" string_list="strings/common_button_captions" string_index="3" font="ui\small_ui" color="#FFFFFFFF" align="center" text_y="2">
<on event="a" run="port settings defaults"/>
<on event="start" run="port settings defaults"/>
<on event="left_mouse" run="mouse emit accept event"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/button_ok" type="text" width="128" height="24" bitmap="bitmaps/text_button_background" string_list="strings/common_button_captions" string_index="1" font="ui\small_ui" color="#FFFFFFFF" align="center" text_y="2">
<on event="a" run="port settings save" back="true"/>
<on event="start" run="port settings save" back="true"/>
<on event="left_mouse" run="mouse emit accept event"/>
</widget>
<strings name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/labels">
<string text="PLAYER NAMES:"/>
<string text="PLAYER NAME SIZE:"/>
<string text="SCOREBOARD LAYOUT:"/>
<string text="SCOREBOARD PANEL:"/>
<string text="TEXT CHAT:"/>
<string text="CHAT TEXT:"/>
</strings>
<strings name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/help_strings">
<string text=""/>
<string text="In multiplayer, whose names are drawn above their\nheads."/>
<string text="How large the players' names are drawn."/>
<string text="A team game's scoreboard: a column for each team,\nor every player in order of score."/>
<string text="Draw a panel behind the scoreboard, for clearer\ntext."/>
<string text="SHOW the text chat, or HIDE it: no lines, and\nits keys and D-pad Up open nothing."/>
<string text="The text chat's colors: COLORS, names in their\nteam's color; WHITE, as Custom Edition."/>
</strings>
</menus>
@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- The port's settings screen, in the PC version's style (tools/port_settings.py) -->
<menus>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/streamer_mode_settings_screen" width="640" height="480" flags="pass_unhandled_to_focused_child pause_game" bitmap="bitmaps/gradient">
<on event="b" back="true"/>
<on event="back" back="true"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/header_profile_network_settings"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/options_menu"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/header_profile_network_settings" controller="1" left="35" top="11" width="605" height="59" bitmap="main_menu/settings_select/player_setup/player_profile_edit/network_setup/header_profile_network_settings">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/help" type="text" controller="1" left="68" top="350" width="482" height="60" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/help_strings" font="ui\large_ui" color="#FFFFFFFF">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/options_menu" type="column_list" width="640" height="480" flags="pass_unhandled_to_focused_child up_down_tabs_children" description="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/help">
<data input="port settings help"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/op_enabled" x="54" y="73"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/op_slurs" x="54" y="103"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/op_swearing" x="54" y="133"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/op_insults" x="54" y="163"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/button_bar" y="414"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/button_bar" type="column_list" width="640" height="28" flags="pass_unhandled_to_focused_child left_right_tabs_items">
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/button_defaults" y="1"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/button_ok" x="380" y="1"/>
<child widget="common_button_cancel" x="510" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/op_enabled" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/enabled_label"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/enabled_spinner" x="320" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/enabled_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/labels" string_index="0" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/enabled_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="ON|OFF" setting="streamer.enabled" values="true|false" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="created" run="port setting load"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/op_slurs" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/slurs_label"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/slurs_spinner" x="286" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/slurs_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/labels" string_index="1" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/slurs_spinner" type="spinner" top="2" width="206" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="OFF|SEVERE|STRONG AND WORSE|MILD AND WORSE" setting="streamer.slurs" values="off|severe|strong|mild" font="ui\large_ui" color="#FF2896FF" align="center" text_y="4" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -13 19 -7" footer_bounds="7 208 19 214">
<on event="created" run="port setting load"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/op_swearing" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/swearing_label"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/swearing_spinner" x="286" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/swearing_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/labels" string_index="2" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/swearing_spinner" type="spinner" top="2" width="206" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="OFF|SEVERE|STRONG AND WORSE|MILD AND WORSE" setting="streamer.swearing" values="off|severe|strong|mild" font="ui\large_ui" color="#FF2896FF" align="center" text_y="4" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -13 19 -7" footer_bounds="7 208 19 214">
<on event="created" run="port setting load"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/op_insults" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/insults_label"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/insults_spinner" x="286" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/insults_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/labels" string_index="3" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/insults_spinner" type="spinner" top="2" width="206" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="OFF|SEVERE|STRONG AND WORSE|MILD AND WORSE" setting="streamer.insults" values="off|severe|strong|mild" font="ui\large_ui" color="#FF2896FF" align="center" text_y="4" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -13 19 -7" footer_bounds="7 208 19 214">
<on event="created" run="port setting load"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/button_defaults" type="text" width="128" height="24" bitmap="bitmaps/text_button_background" string_list="strings/common_button_captions" string_index="3" font="ui\small_ui" color="#FFFFFFFF" align="center" text_y="2">
<on event="a" run="port settings defaults"/>
<on event="start" run="port settings defaults"/>
<on event="left_mouse" run="mouse emit accept event"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/button_ok" type="text" width="128" height="24" bitmap="bitmaps/text_button_background" string_list="strings/common_button_captions" string_index="1" font="ui\small_ui" color="#FFFFFFFF" align="center" text_y="2">
<on event="a" run="port settings save" back="true"/>
<on event="start" run="port settings save" back="true"/>
<on event="left_mouse" run="mouse emit accept event"/>
</widget>
<strings name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/labels">
<string text="STREAMER MODE:"/>
<string text="CENSOR SLURS:"/>
<string text="CENSOR SWEARING:"/>
<string text="CENSOR INSULTS:"/>
</strings>
<strings name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/help_strings">
<string text=""/>
<string text="Hide offensive words in names and text from\nother players, maps and mods, as stars."/>
<string text="Slurs about race, sexuality, gender and\ndisability."/>
<string text="Swearing and sexual words (STRONG AND WORSE\nleaves the mildest showing)."/>
<string text="Name-calling and insults that are not slurs."/>
</strings>
</menus>
@@ -17,15 +17,11 @@
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_allow_upnp" x="54" y="103" platform="desktop android"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_join_from_clipboard" x="54" y="133" platform="desktop android"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_auto" x="54" y="163" platform="desktop android"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_player_names" x="54" y="193" platform="desktop android"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_player_names" x="54" y="103" platform="web"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_player_name_scale" x="54" y="223" platform="desktop android"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_player_name_scale" x="54" y="133" platform="web"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_scoreboard_team_layout" x="54" y="253" platform="desktop android"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_scoreboard_team_layout" x="54" y="163" platform="web"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_scoreboard_background" x="54" y="283" platform="desktop android"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_scoreboard_background" x="54" y="193" platform="web"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_map_sharing" x="54" y="313" platform="desktop android"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_map_sharing" x="54" y="193" platform="desktop android"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_multiplayer_hud" x="54" y="223" platform="desktop android"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_multiplayer_hud" x="54" y="103" platform="web"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_streamer_mode" x="54" y="253" platform="desktop android"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_streamer_mode" x="54" y="133" platform="web"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/button_bar" y="414"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/button_bar" type="column_list" width="640" height="28" flags="pass_unhandled_to_focused_child left_right_tabs_items">
@@ -69,49 +65,29 @@
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/auto_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="ON|OFF" setting="update.auto" values="true|false" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="created" run="port setting load"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_player_names" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/player_names_label"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/player_names_spinner" x="320" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/player_names_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/labels" string_index="4" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/player_names_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="ALL|ALLIES|ENEMIES|NONE" setting="display.player_names" values="all|allies|enemies|none" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="created" run="port setting load"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_player_name_scale" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/player_name_scale_label"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/player_name_scale_spinner" x="320" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/player_name_scale_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/labels" string_index="5" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/player_name_scale_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="0.5x|0.75x|1x|1.25x|1.5x|2x" setting="display.player_name_scale" values="0.5|0.75|1|1.25|1.5|2" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="created" run="port setting load"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_scoreboard_team_layout" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/scoreboard_team_layout_label"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/scoreboard_team_layout_spinner" x="320" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/scoreboard_team_layout_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/labels" string_index="6" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/scoreboard_team_layout_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="TEAMS|BY SCORE" setting="display.scoreboard_team_layout" values="teams|score" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="created" run="port setting load"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_scoreboard_background" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/scoreboard_background_label"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/scoreboard_background_spinner" x="320" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/scoreboard_background_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/labels" string_index="7" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/scoreboard_background_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="ON|OFF" setting="display.scoreboard_background" values="true|false" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="created" run="port setting load"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_map_sharing" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF" platform="desktop android">
<on event="a" open="main_menu/settings_select/player_setup/player_profile_edit/network_setup/map_sharing/map_sharing_settings_screen"/>
<on event="start" open="main_menu/settings_select/player_setup/player_profile_edit/network_setup/map_sharing/map_sharing_settings_screen"/>
<on event="left_mouse" run="mouse emit accept event"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/map_sharing_label"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/map_sharing_label" type="text" controller="1" width="512" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/labels" string_index="8" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/map_sharing_label" type="text" controller="1" width="512" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/labels" string_index="4" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_multiplayer_hud" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
<on event="a" open="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/multiplayer_hud_settings_screen"/>
<on event="start" open="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/multiplayer_hud_settings_screen"/>
<on event="left_mouse" run="mouse emit accept event"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud_label"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud_label" type="text" controller="1" width="512" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/labels" string_index="5" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_streamer_mode" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
<on event="a" open="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/streamer_mode_settings_screen"/>
<on event="start" open="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/streamer_mode_settings_screen"/>
<on event="left_mouse" run="mouse emit accept event"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode_label"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode_label" type="text" controller="1" width="512" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/labels" string_index="6" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/button_defaults" type="text" width="128" height="24" bitmap="bitmaps/text_button_background" string_list="strings/common_button_captions" string_index="3" font="ui\small_ui" color="#FFFFFFFF" align="center" text_y="2">
<on event="a" run="port settings defaults"/>
@@ -128,11 +104,9 @@
<string text="UPNP PORT FORWARDING:"/>
<string text="JOIN FROM CLIPBOARD:"/>
<string text="CHECK FOR UPDATES:"/>
<string text="PLAYER NAMES:"/>
<string text="PLAYER NAME SIZE:"/>
<string text="SCOREBOARD LAYOUT:"/>
<string text="SCOREBOARD PANEL:"/>
<string text="MAP SHARING:"/>
<string text="MULTIPLAYER HUD:"/>
<string text="STREAMER MODE:"/>
</strings>
<strings name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/help_strings">
<string text=""/>
@@ -140,10 +114,8 @@
<string text="Let internet play ask the router to forward its\nport, for networks that stop connections."/>
<string text="Join the game of an invite link copied before\nswitching to the game."/>
<string text="Look for a new version when the game starts."/>
<string text="In multiplayer, whose names are drawn above their\nheads."/>
<string text="How large the players' names are drawn."/>
<string text="A team game's scoreboard: a column for each team,\nor every player in order of score."/>
<string text="Draw a panel behind the scoreboard, for clearer\ntext."/>
<string text="Downloading Custom Edition maps you do not have,\nand helping send them to other players."/>
<string text="Players' names above their heads, the\nscoreboard, and the text chat."/>
<string text="Hide offensive words in names and text from\nother players, maps and mods, as stars."/>
</strings>
</menus>
@@ -14,13 +14,14 @@
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/options_menu" type="column_list" width="640" height="480" flags="pass_unhandled_to_focused_child up_down_tabs_children" description="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/help">
<data input="port settings help"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/op_high_res_hud" x="54" y="73"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/op_high_res_text" x="54" y="103"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/op_anti_aliasing" x="54" y="133" platform="desktop"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/op_anti_aliasing_android" x="54" y="133" platform="android web"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/op_shadow_resolution" x="54" y="163"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/op_per_pixel_lighting" x="54" y="193"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/op_shield_glow" x="54" y="223"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/op_shield_glow_intensity" x="54" y="253"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/op_hud_button_style" x="54" y="103"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/op_high_res_text" x="54" y="133"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/op_anti_aliasing" x="54" y="163" platform="desktop"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/op_anti_aliasing_android" x="54" y="163" platform="android web"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/op_shadow_resolution" x="54" y="193"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/op_per_pixel_lighting" x="54" y="223"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/op_shield_glow" x="54" y="253"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/op_shield_glow_intensity" x="54" y="283"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/button_bar" y="414"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/button_bar" type="column_list" width="640" height="28" flags="pass_unhandled_to_focused_child left_right_tabs_items">
@@ -37,11 +38,20 @@
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/high_res_hud_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="ON|OFF" setting="display.high_res_hud" values="true|false" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="created" run="port setting load"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/op_hud_button_style" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/hud_button_style_label"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/hud_button_style_spinner" x="320" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/hud_button_style_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/labels" string_index="1" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/hud_button_style_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="CLASSIC|GEMS|DARK GEMS" setting="display.hud_button_style" values="classic|gems|menu_gems" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="created" run="port setting load"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/op_high_res_text" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/high_res_text_label"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/high_res_text_spinner" x="320" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/high_res_text_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/labels" string_index="1" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/high_res_text_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/labels" string_index="2" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/high_res_text_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="ON|OFF" setting="display.high_res_text" values="true|false" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="created" run="port setting load"/>
@@ -50,7 +60,7 @@
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/anti_aliasing_label"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/anti_aliasing_spinner" x="320" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/anti_aliasing_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/labels" string_index="2" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/anti_aliasing_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/labels" string_index="3" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/anti_aliasing_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="OFF|FXAA|SMAA|SSAA 2X|MSAA 2X|MSAA 4X|MSAA 8X" setting="display.anti_aliasing" values="off|fxaa|smaa|ssaa2x|msaa2x|msaa4x|msaa8x" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="created" run="port setting load"/>
@@ -59,7 +69,7 @@
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/anti_aliasing_android_label"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/anti_aliasing_android_spinner" x="320" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/anti_aliasing_android_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/labels" string_index="3" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/anti_aliasing_android_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/labels" string_index="4" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/anti_aliasing_android_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="OFF|FXAA|MSAA 2X|MSAA 4X" setting="display.anti_aliasing" values="off|fxaa|msaa2x|msaa4x" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="created" run="port setting load"/>
@@ -68,7 +78,7 @@
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/shadow_resolution_label"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/shadow_resolution_spinner" x="320" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/shadow_resolution_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/labels" string_index="4" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/shadow_resolution_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/labels" string_index="5" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/shadow_resolution_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="128|256|512|1024" setting="display.shadow_resolution" values="128|256|512|1024" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="created" run="port setting load"/>
@@ -77,7 +87,7 @@
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/per_pixel_lighting_label"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/per_pixel_lighting_spinner" x="320" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/per_pixel_lighting_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/labels" string_index="5" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/per_pixel_lighting_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/labels" string_index="6" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/per_pixel_lighting_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="ON|OFF" setting="display.per_pixel_lighting" values="true|false" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="created" run="port setting load"/>
@@ -86,7 +96,7 @@
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/shield_glow_label"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/shield_glow_spinner" x="320" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/shield_glow_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/labels" string_index="6" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/shield_glow_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/labels" string_index="7" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/shield_glow_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="ON|OFF" setting="display.shield_glow" values="true|false" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="created" run="port setting load"/>
@@ -95,7 +105,7 @@
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/shield_glow_intensity_label"/>
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/shield_glow_intensity_spinner" x="320" y="1"/>
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/shield_glow_intensity_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/labels" string_index="7" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/shield_glow_intensity_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/labels" string_index="8" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
</widget>
<widget name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/shield_glow_intensity_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="DEFAULT|LIGHT SHOW" setting="display.shield_glow_intensity" values="default|light_show" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
<on event="created" run="port setting load"/>
@@ -112,6 +122,7 @@
</widget>
<strings name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/labels">
<string text="HIGH-RES HUD:"/>
<string text="BUTTON STYLE:"/>
<string text="HIGH-RES TEXT:"/>
<string text="ANTI-ALIASING:"/>
<string text="ANTI-ALIASING:"/>
@@ -123,6 +134,7 @@
<strings name="main_menu/settings_select/player_setup/player_profile_edit/video_settings/graphics/help_strings">
<string text=""/>
<string text="Draw the HUD from the high-res redraws; off\ndraws the game's own pictures."/>
<string text="Classic: plain Xbox buttons. Gems: shaded Xbox\nbuttons. Dark Gems: the menu's button artwork."/>
<string text="Draw text and titles with high-res fonts; off\ndraws the game's own."/>
<string text="Smooth jagged edges, which the Xbox did not. FXAA\nand SMAA are cheap; SSAA and MSAA are sharper."/>
<string text="Smooth jagged edges, which the Xbox did not. FXAA\nis cheap; MSAA is sharper."/>
+2 -1
View File
@@ -56,7 +56,8 @@
<child widget="main_menu/main_menu_item_multiplayer" x="192" y="283"/>
<child widget="main_menu/main_menu_item_profile" x="192" y="319"/>
<child widget="main_menu/main_menu_item_settings" x="192" y="355"/>
<child widget="main_menu/main_menu_item_quit_game" x="192" y="391"/>
<child widget="main_menu/main_menu_item_mods" x="192" y="391"/>
<child widget="main_menu/main_menu_item_quit_game" x="192" y="427"/>
</widget>
<widget name="main_menu/new_campaign_creating_profile" width="640" height="480" flags="pass_unhandled_to_focused_child no_history" auto_close="1">
<on event="deleted" run="new campaign decision" open="main_menu/new_campaign_saving_profile"/>
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Loaded 100 of 322 files, more files were not shown because too many files have changed in this diff. Show more