From 6f7ff49c61cfbb36c57e8726b1c051339a9a1585 Mon Sep 17 00:00:00 2001 From: Jeffie Date: Tue, 6 Oct 2026 23:19:20 -0500 Subject: [PATCH 01/20] gfx: enable shaderDemoteToHelperInvocation, which the shaders need The fragment shaders' discard compiles to OpDemoteToHelperInvocation, which requires the Vulkan 1.3 feature shaderDemoteToHelperInvocation. The device did not enable it, so the validation layer reported it and the graphics tests that count validation messages (TexDecode, GfxMisc) failed. It is a core 1.3 feature, which the renderer already requires. The test fixture also set the deprecated VK_LAYER_ENABLES, which the layer reports as a message of its own; use VK_LAYER_VALIDATE_SYNC instead. Co-Authored-By: Claude Sonnet 5.5 --- port/src/gfx/renderer.cpp | 2 ++ port/src/tests/gfx_fixture.hpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/port/src/gfx/renderer.cpp b/port/src/gfx/renderer.cpp index 69b128af2..655ddbe10 100644 --- a/port/src/gfx/renderer.cpp +++ b/port/src/gfx/renderer.cpp @@ -303,6 +303,8 @@ void CreateDevice() { features13.pNext = g.dynamic_color_write_mask ? &dynamic3 : dynamic3.pNext; features13.dynamicRendering = VK_TRUE; features13.synchronization2 = VK_TRUE; + // The fragment shaders' discard compiles to OpDemoteToHelperInvocation, which needs this on. + features13.shaderDemoteToHelperInvocation = VK_TRUE; VkPhysicalDeviceVulkan12Features features12 = {}; features12.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; diff --git a/port/src/tests/gfx_fixture.hpp b/port/src/tests/gfx_fixture.hpp index cf140e706..d8637ab2d 100644 --- a/port/src/tests/gfx_fixture.hpp +++ b/port/src/tests/gfx_fixture.hpp @@ -39,7 +39,7 @@ struct GfxFixture { GfxFixture(int width, int height, float render_scale, const GfxOptions &options) { // Synchronization validation too, unless the environment already chose layer features. - dc::test::SetEnv("VK_LAYER_ENABLES", "VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXT", 0); + dc::test::SetEnv("VK_LAYER_VALIDATE_SYNC", "1", 0); WindowConfig window{width, height, true}; window.vulkan = !options.offscreen; WindowInit(window); From 1d7cbcedf21432737f960fce857171980552c761 Mon Sep 17 00:00:00 2001 From: Jeffie Date: Tue, 6 Oct 2026 23:43:57 -0500 Subject: [PATCH 02/20] Localization: game text from JSON, TrueType message font, Language option Game text can come from JSON files, with the disc's own message files as the fallback, and message text can be drawn from a TrueType font so it is sharp at any window size. Nothing from the disc is added to the repository. - The files are laid out as Minecraft's language files are: one flat JSON file per language (en_gb.json, fr_fr.json...), each a "dotted.key": "text" object with the same keys in every language. A key is the message file's key (its path with the language and the .mes taken out, slashes made dots) and the message id: "dun.script.d01.d01.12". - localize.{hpp,cpp} reads /lang and /lang and lays the text over a message file as the game loads it (LoadFile2, LoadFileBG and the pack lookup GetPackFile). A missing message, file, bad JSON or a text that does not fit keeps the disc's text and says so once on stderr. patches/*.json make small edits to the disc's text (replace pairs, or whole text, guarded by a hash). - darkcloud --export-text DIR, run once automatically for a save folder with no export, writes one file per language from the user's own data (855 message files on the PAL disc, each reading back to the disc's codes), and the hashes patches check. The export is gitignored. - The Options screen's own words are looked up by key too (options..label, .help, .choice., options.page.), with the English in the code as the fallback; port/lang/ ships French, German, Italian and Spanish for them, copied beside the executable by the build. - lang/font.ttf draws message text with stb_truetype at the screen's resolution, in the game's own cells; letters the font lacks (accents, cedilla, inverted marks, oe) are built from the font's own glyphs. - A Language row on the Game page and game.language in config.json skip the start-up language screen; changing it reloads the town or title once Options closes so images, menus and messages all change together. - scripts/port/compare_text.py compares the languages message by message. - docs/LOCALIZATION.md describes the formats and limits. Japanese is not decoded: its text is glyph indices into the game's own font, so ja_jp.json keeps them as {N} codes, which still round-trip. Co-Authored-By: Claude Sonnet 5.5 --- .gitignore | 4 + docs/LOCALIZATION.md | 292 ++++++++++ docs/PC.md | 10 +- port/CMakeLists.txt | 16 +- port/lang/de_de.json | 142 +++++ port/lang/es_es.json | 142 +++++ port/lang/fr_fr.json | 142 +++++ port/lang/it_it.json | 142 +++++ port/src/clsmes.cpp | 214 ++++++++ port/src/dataread.cpp | 50 ++ port/src/gameloop.cpp | 22 + port/src/gametext.cpp | 177 +++++- port/src/gametext.hpp | 9 +- port/src/localize.cpp | 900 +++++++++++++++++++++++++++++++ port/src/localize.hpp | 123 +++++ port/src/main.cpp | 22 + port/src/menu_option.hpp | 7 + port/src/platform/config.cpp | 21 + port/src/platform/config.hpp | 3 + port/src/platform/ttffont.cpp | 433 +++++++++++++++ port/src/platform/ttffont.hpp | 48 ++ port/src/tests/gametext_test.cpp | 47 +- port/src/tests/localize_test.cpp | 712 ++++++++++++++++++++++++ port/src/tests/ttffont_test.cpp | 98 ++++ scripts/port/compare_text.py | 91 ++++ 25 files changed, 3855 insertions(+), 12 deletions(-) create mode 100644 docs/LOCALIZATION.md create mode 100644 port/lang/de_de.json create mode 100644 port/lang/es_es.json create mode 100644 port/lang/fr_fr.json create mode 100644 port/lang/it_it.json create mode 100644 port/src/localize.cpp create mode 100644 port/src/localize.hpp create mode 100644 port/src/platform/ttffont.cpp create mode 100644 port/src/platform/ttffont.hpp create mode 100644 port/src/tests/localize_test.cpp create mode 100644 port/src/tests/ttffont_test.cpp create mode 100644 scripts/port/compare_text.py diff --git a/.gitignore b/.gitignore index 73291ef66..cde05ba31 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,10 @@ AGENTS.md /save/ /soundtrack/ +# The game's message files exported as JSON (darkcloud --export-text, docs/LOCALIZATION.md): the +# disc's own text, never committed. +/lang-export/ + # Flatpak builds (docs/FLATPAK.md) /.flatpak-builder/ /build-dir/ diff --git a/docs/LOCALIZATION.md b/docs/LOCALIZATION.md new file mode 100644 index 000000000..0e8780fca --- /dev/null +++ b/docs/LOCALIZATION.md @@ -0,0 +1,292 @@ +# Localization + +Game text can come from JSON files instead of the disc's `.mes` message files. A JSON file holds only the +messages it changes; every message it leaves out, and every file it does not mention, is read from the +disc as before. Nothing from the disc is shipped: a translator starts from an export of their own +extracted data (below), and the files the port distributes hold only text written for the port. + +The files are laid out as Minecraft's language files are: one flat JSON file per language, named +`language_COUNTRY.json`, each an object of `"dotted.key": "text"`, with the same keys in every language. + +## Where the files go + +One file per language, named by the language the game is set to: + +| Language | `LanguageCode` | File | +|---|---|---| +| Japanese | 0 | `ja_jp.json` | +| English (US) | 1 | `en_us.json` | +| English (UK) | 2 | `en_gb.json` | +| Francais | 3 | `fr_fr.json` | +| Deutsch | 4 | `de_de.json` | +| Italiano | 5 | `it_it.json` | +| Espanol | 6 | `es_es.json` | + +The port looks in two folders, in this order, and a later file's messages replace an earlier one's: + +1. `lang/` beside the executable, +2. `lang/` in the save folder (`--save`, or `DC_SAVE`). + +The language is looked up whenever the game asks for a file, so a change of language takes effect on the +next file the game loads; the JSON itself is read once per language, so restart the game after editing a +file. + +## Format + +```json +{ + "dun.script.d01.d01.0": "Bienvenue.", + "dun.script.d01.d01.12": "Il y a {cyan}trois{/color} coffres ici.\nPrenez-les !", + "commenu.dungeon.dunmenu5.pak.allmenu.11": "\"Objet\"\nUtiliser un objet ou\nregler les objets." +} +``` + +- A key is **the message file's key, a dot, and the message id**. The id is the number the game gives the + message, an s16 (`-32768` to `32767`); an id the disc's file does not have is added. +- The message file's key is the same in every language, so `fr_fr.json` and `de_de.json` have the same keys + for the same messages and can be set side by side. It is the path the game loads with the language taken + out, the `.mes` taken off and the slashes made dots: + + | The game loads | Key | + |---|---| + | `dun/script/d01/d01_3.mes` (and `d01_2.mes`, `d01_4.mes`...) | `dun.script.d01.d01` | + | `meswin/system14_2.mes` | `meswin.system14` | + | `gedit/e01/e01talk_3.mes` | `gedit.e01.e01talk` | + | `commenu/a_fre/dungeon/dunmenu5.pak/allmenu.mes` (`a_eng/`, `a_ger/`...) | `commenu.dungeon.dunmenu5.pak.allmenu` | + | `noda_w/dun00_eng.mes` (no language in its name) | `noda_w.dun00_eng` | + + A message file inside a pack (`.pak`, `.pac`) is the pack's path and the entry's name, as in the last + rows. The "language" is the last number of a file's name (`_0` to `_6`) or a menu folder named `a_xxx/`. +- A key that does not end in a message id is one of **the port's own strings**, the ones it draws itself + (the Options screen). They are in the same files, under `options.`; see "The port's own text" below. +- A text is UTF-8. `\n` is a line break and `{{` is a `{`. What is not a letter, a space or a line break is a + **control code**, written in braces by its name (below). The game stores them as numbers; a number in + braces, `{-253}`, is the same code and is read too, but the export always writes the name. + +### Control codes + +| Written | Is | +|---|---| +| `{page}` | wait for a button, then the next page | +| `{wait 12}` | pause for 12 more frames before the next letter | +| `{white}` `{yellow}` `{cyan}` `{green}` `{dark}` `{gold}` `{grey}` `{highlight}` | switch the text colour; `{/color}` goes back to the window's own. `{color 9}` is a colour with no name | +| `{value}` `{value1}` ... `{value8}` | a number the game puts in (a price, a damage, an amount) | +| `{name1}` ... `{name6}` | a character's name | +| `{insert1}` ... `{insert10}` | a message the game picks while it runs (an item or place name) | +| `{gap 8}` `{spacing 10}` `{justify 3}` | width of a gap, of the spaces, and the justified area, in the units the game uses | +| `{bubble 5}` | the shape of the speech bubble | +| `{select}` `{start}` `{L1}` `{R1}` `{L2}` `{R2}` `{circle}` `{triangle}` `{cross}` `{square}` | the pad's buttons | +| `{dpad}` `{dpad-updown}` `{dpad-sides}` `{up}` `{right}` `{down}` `{left}` | the d-pad and arrows | +| `{heart}` `{note}` `{alert}` `{hand}` `{bait}` `{monster}` `{word-button}` `{red-x}` `{red-slash}` `{yellow-arrow}` `{orange-arrow}` | other symbols the game draws as one character | +| `{icon-sword}` `{icon-orb}` `{icon-georama}` `{icon-jar}` | the item-kind pictures | +| `{icon 32}` | a symbol with no name | + +Names are not case sensitive. A name the game has no code for is read as the text it is, and a code that is in +none of these tables (there are none in the PAL text) is written as its number. Keep the codes in a +translation as the English has them; where a word is moved, its code moves with it. Every code the PAL +languages' text uses is in the table: no number is left in the English, French, German, Italian or Spanish +export. + +## The port's own text + +The Options screen is the port's, not the disc's, so its words are not in the disc's message files. They are +in the language files too, under keys the code derives from each setting, so a new row gets its keys without +any other change: + +| Key | Is | +|---|---| +| `options.page.game` (`display`, `audio`, `controls`) | a page's name | +| `options..label` | the row's name; `` is the name in `config.json`, `game.map`, `video.aspect`... | +| `options..help` | the help window's text for a setting the port describes itself | +| `options..choice.` | the n-th choice of the setting, counting from 0 (`On`, `Off`...) | +| `options..value.` | a value the row works out itself, by its English (`Desktop`, `Unlimited`) | +| `options.shortcuts`, `options.help.*`, `options.display.*` | the hints at the bottom and the warning texts | + +A text may hold `%1`, `%2`... where the program puts a number or a name, and the pad's button glyphs as +their codes (`{square}`, `{triangle}`, `{circle}`, `{cross}`, `{L1}`, `{R1}`); keep both as the English has them. Help windows hold four lines of about 22 characters, and a +label about 20; a longer one runs past its window. + +The settings the game describes itself (the help for Message Speed, Clock...) are the disc's messages and +come from the disc's language files as before. + +`port/lang/` ships these strings in French, German, Italian and Spanish; the build copies it to `lang/` +beside the executable, where the game reads it, so they need no export. English is in the code, and is what +shows where a language has no text for a key. The export lists every one of them with the text the language +would show now, so a translator starts from the shipped text. A translation written in `/lang/` takes +the place of the shipped one, key by key. The translations are first drafts: a native speaker should look +at them. + +## Choosing the language + +The language screen at the start of the game sets the language, as retail does. To skip it, and to change +the language at any time, set it in `config.json` or on the Options screen (Game page, Language row): + +```json +{"game": {"language": "francais"}} +``` + +`ask` (the default) shows the language screen; `english`, `francais`, `deutsch`, `italiano` and `espanol` +start in that language without asking, going straight to the attract movie where the language screen would +have led. Changing the row on the Options screen switches the game to that language once the Options screen +closes: the town or title it is in is loaded again, so every message, menu and image is the new language. +A dungeon floor is not restarted for it; the language takes hold when the next area loads. `ask` leaves the +current language alone. + +## Exporting the disc's text + +The port does this for you: the first start with data in place (right after the first-run extraction, or +any `dcdata extract`) writes the export to `/lang-export/`, unless `en_gb.json` is already there. +Delete that folder to have it made again. The same by hand: + +``` +darkcloud --data --export-text +``` + +writes one flat file per language into ``, from every message file under the data folder (loose +`.mes` files and the `.mes` entries of `.pak` and `.pac` packs; 855 files on the PAL disc): + +| File | Holds | +|---|---| +| `en_gb.json`, `fr_fr.json`, `de_de.json`, `it_it.json`, `es_es.json`, `en_us.json`, `ja_jp.json` | the messages of that language's files, all keyed as above | +| `hashes/.json` | the same keys, each with the hash of the message's text, for patches | + +A file that names no language and is not Japanese (`noda_w/dun00_eng.mes`) is in every language's file. +The menu packs left in `commenu/` that are Japanese go to `ja_jp.json`. Where a file's path names its +language it wins over one that does not. Two files of one language that come to the same key are told on +stderr (there are none on the PAL disc). + +The export checks as it goes that every message encodes back to the disc's own codes, and prints how many +files do not (none, on the PAL disc). Messages that have no end code in the disc's file (one unused id in +`allmenu.mes`) are left out. + +The export is the disc's own text. It is the user's data and must not be committed or shared: +`/lang-export/` is in `.gitignore`, and anything you put in a `lang/` folder next to a build you +distribute should be your own text, not an export. + +## Translating + +1. Export, then copy the language's file to `/lang/` (`fr_fr.json`...). +2. Delete every message you are not changing; what stays is the part you have written. A file with all of + the disc's messages in it works, but it hides what you changed. +3. Edit the texts. Keep every `{code}` and line break the disc's text has unless you know what it does. +4. Start the game in that language and look at each screen. Check stderr for `localize:` lines (overlong + texts, characters the font lacks). + +## Comparing the languages + +``` +python scripts/port/compare_text.py /lang-export [] +``` + +writes one CSV per language (fr_fr, de_de, it_it, es_es) with `en_gb.json` as the reference, one row per +message that differs in a way worth looking at: `missing` (English has it, this language does not), `extra`, +`empty`, `same` (the English text, often untranslated, and every message of a file that names no +language), and `long` (more than twice or under half English's length, where a window may not fit it), and +a `summary.txt` of the counts. The CSVs hold the disc's own text: keep them out of git. + +## Patches + +A patch changes a message the disc has without carrying the disc's text. Patch files are `patches/*.json` +in either `lang/` folder, read in name order for every language. They are applied to the disc's text each +time the file loads, so updating a patch needs no re-export: + +```json +{"patches": [ + {"file": "dun/script/d01/d01_3.mes", "id": 12, "replace": [["teh", "the"], ["recieve", "receive"]]}, + {"file": "commenu.emenu.pak.allmenu", "id": 11, "original": "0a1b2c3d4e5f6a7b", + "text": "\"Item\"\nUse or equip items."} +]} +``` + +- `file` is the path the game loads or the message file's key. +- `replace` is a list of `[find, with]` pairs; every occurrence of `find` becomes `with`. A pair whose `find` + is not in the message is skipped and reported; if none match, the patch is. +- `text` replaces the message whole. Give exactly one of `replace` and `text`. +- `original` (optional) is the hash of the disc's whole message (a 64-bit FNV-1a of the UTF-8 text, as 16 hex + digits; the export lists them in `hashes/`). The patch applies only when it matches, so a different disc, + or a message already changed, is left alone. +- A message that the language's own file also has is not patched: the explicit text wins. +- Patches in one file chain in order, so a later `find` sees an earlier patch's result. +- The shipped patches hold only the words that change, so the corrected text exists only on the user's + machine, built from their own disc. + +## File encoding + +Every file the port writes is UTF-8 without a byte-order mark, and letters are written as themselves, never +as `\uXXXX` escapes, so `Crème` and `Jäger-Ohrring` read as they are in any editor. The port reads UTF-8 +files (a BOM is not expected). + +**Japanese is the exception, for now.** The Japanese messages are not text but numbers: each letter is the index +(0 to 784) of a glyph in the game's own Japanese font, which is not Unicode, so `ja_jp.json` has them as +`{237}{9}{2}...`. (These are font positions, not control codes; the control codes in Japanese text are named like any other.) Writing them as real Japanese needs a table from those indices to characters, which the +port does not have yet. Until then they stay as the codes, which still round-trip exactly, and the other +languages are unaffected. + +## What the game font can draw + +The PAL font has the 88 letters, digits and marks of its `gaiji.img` and the accented letters +`ÀÁÂÄÈÉÊËÌÍÎÏÑÒÓÔÖÙÚÛÜàáâäèéêëìíîïñòóôöùúûü`, `œ Œ ß Ç ç ¡ ¿`, and the curly quotes and dashes become the +plain ones. Anything else (`;`, `_`, `~`, a Cyrillic or Greek letter) is drawn as `?`, and the port tells you +once per file how many characters did that. The full table is in [PC.md](PC.md#game-text). + +## Crisp text from a TrueType font + +The game draws its text from 14x20 bitmaps, which blur when the picture is scaled up. Put a TrueType file +called `font.ttf` in a `lang/` folder (the save folder's, then the executable's) and the port draws every +character the font has from it instead, rasterized at the screen's own resolution, in the cell the game laid +the bitmap character out in (size, colour, outline and the cursor row's flashing are the game's own). A +character the font lacks, and the icons and pad buttons, still draw as bitmaps; a font that has blank +placeholders for the accented letters, the cedilla, `¡`, `¿`, `œ` and `Œ` gets them built from its own +letters and marks. The port logs `font: message text from ` when it loads one. + +- It works for every message the game draws through its message windows, whatever its source: the disc's + text, the JSON's, item and monster names. +- It does not touch text that is a picture: the large menu words (ITEM, WEAPON...), the title screen's and + the like are textures, and stay as they are. +- A word keeps the cells the game gave it: its letters are spaced by the font inside the word and the word + is centred on its cells, so rows, indents and the icons between words stay where the game put them. +- The font is not shipped with the port: each user adds a file they have the right to use. + +## Limits, and what happens when they are passed + +A text that does not fit is left as the disc has it, and the port says so once on stderr (`localize: ...`): + +- a window shows at most 720 characters (`{value}`, `{name1}` and `{insert1}` count as what they expand to); +- a message file's offsets are s16, so a file is at most about 32,000 characters in all; +- a message file the game reads from a **loose file** (`dun/script/...`, `gedit/...`, `meswin/...`) is read + into a buffer sized for the disc's file, rounded up to whole 2048-byte sectors. A translated file that + comes to more than that keeps the disc's text for the whole file. Translate longer by shortening + elsewhere in the file, or put the text in a message file the game reads out of a pack, which has no such + limit. + +Bad JSON (not JSON, a key that is not a file key and an id, a value that is not a string) is reported once +and the file is ignored; the game runs on disc text. + +## Memory + +The JSON for the current language (the language's file, from both folders) is held parsed in memory: about +100 bytes of bookkeeping per message plus the text. A translation that changes some messages is a few +hundred KB; a full export of the disc's English is about 3.5 MB. A message file taken from a pack is rebuilt +once and kept (about 80 KB each, about 2 MB for every pack). Loose message files are rebuilt each time the +game loads them and not kept. Reading a file costs a transient 3 to 4 times its size. With `font.ttf`, the +glyph atlas is one 2048 x 2048 texture (16 MB of video memory). + +Set `DC_LOCALIZE_TRACE=1` to log every message file the game loads and whether the JSON has an entry for it. + +## Not done + +- Japanese as characters (above). +- Other text the port may draw itself later: a screen that adds rows only has to follow the key scheme above. +- Reloading a JSON file without restarting the game. + +## In the code + +`port/src/localize.{hpp,cpp}`: `LocalizeText` and `LocalizeFormat` give the port's own strings, +`LocalizeMessageKey` makes a file's key, `LocalizeParse` reads a language's +file, `LocalizeMessages` lays texts over a disc message file (through `GameTextFile`), `LocalizeFile` and +`LocalizePack` are the hooks, and `LocalizeExport` is `--export-text`. The Options screen asks +`LocalizeText` for each of its words (`port/src/menu_option.cpp`; `OptionStrings()` lists them for the export), +and lays its text out again when it is opened in another language. The hooks sit in +`port/src/dataread.cpp`: `LoadFile2` and `LoadFileBG` pass each file they read to `LocalizeLoaded` and +`LocalizeFile`, and the port's `GetPackFile` gives a message entry through `LocalizePack`. Tests: +`port/src/tests/localize_test.cpp`. diff --git a/docs/PC.md b/docs/PC.md index 579c8e78f..280f34edd 100644 --- a/docs/PC.md +++ b/docs/PC.md @@ -1197,8 +1197,9 @@ characters in it, and its own accents only. `GameTextEncode` turns UTF-8 into codes. Curly quotes and dashes become the font's own; any other character the font lacks, and any byte that is not -UTF-8, becomes `?` and is counted. `{N}` writes code N as it is, for control -codes and icons, and `{{` a `{`; `GameTextDecode` writes the same form, so +UTF-8, becomes `?` and is counted. A control code or icon is written in braces by its name (`{page}`, `{cyan}`, `{L1}`, +`{value3}`; the list is in LOCALIZATION.md) or by its number, `{N}`, and `{{` is a `{`; +`GameTextDecode` writes the name where there is one, else the number, so any message reads back and encodes to the same codes. `GameTextFile` lays texts out as a message file for `ClsMes::SetBuff`. `GameText` is one piece of text drawn as the menus draw their help line (`InitMenuMesSet`'s @@ -1207,7 +1208,7 @@ the text, `SetColour` a `FontColor`, then `Draw(x, y, alpha)` from a menu's draw function; `Width` gives its width for aligning a value. Both have retail's fixed sizes. A window lays out at most `MES_WIN_LINE_MAX` -(720) characters, counting the end and what a `{N}` name, value or system +(720) characters, counting the end and what a `{name1}`, `{value}` or `{insert1}` name, value or system message code expands to; retail's `SetMesWinTbl` writes on past the table, so the port's (`port/src/clsmes.cpp`) takes no more, and `GameText::Set` gives back -1, draws nothing and has a width of 0 for a text that did not fit. A line past @@ -1217,6 +1218,9 @@ without one does. A message file is s16 throughout: an id is -0x8000 to the last message by id may extend beyond that range. `GameTextFile::Set` gives back -1 and leaves the file as it was where either would not hold. +The text of retail's message files can be replaced from JSON, one file per language, with the +disc's text as the fallback: see [LOCALIZATION.md](LOCALIZATION.md). + ## The Options screen The game's Options screen, from the title, the town menu and the dungeon menu, diff --git a/port/CMakeLists.txt b/port/CMakeLists.txt index ae862254e..89e0be88a 100644 --- a/port/CMakeLists.txt +++ b/port/CMakeLists.txt @@ -49,6 +49,10 @@ else() file(DOWNLOAD https://raw.githubusercontent.com/nothings/stb/2c980bb59875b0d32144a71867fbdebb2f77cd20/stb_vorbis.c ${AUDIO_DECODER_DIR}/stb_vorbis.c EXPECTED_HASH SHA256=4c7cb2ff1f7011e9d67950446b7eb9ca044f2e464d76bfbb0b84dd2e23e65636) endif() +# The message font's rasterizer (platform/ttffont.cpp): a single-file library, public domain or MIT, fetched at a pinned commit. +set(TTF_DIR ${CMAKE_BINARY_DIR}/stb_truetype) +file(DOWNLOAD https://raw.githubusercontent.com/nothings/stb/2c980bb59875b0d32144a71867fbdebb2f77cd20/stb_truetype.h + ${TTF_DIR}/stb_truetype.h EXPECTED_HASH SHA256=ecd30b05e0dd4fea3a13c26810dd9e1992dc379049482c393d5a19e6b5090aab) set(PS2_DIR ${CMAKE_SOURCE_DIR}/ps2/src) set(PORT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src) @@ -213,12 +217,12 @@ file(CONFIGURE OUTPUT ${SHADER_HEADER_DIR}/options/credits.hpp CONTENT "#pragma once\ninline constexpr const char *kCredits[] = {${credit_names}};\n") add_library(dc_host OBJECT ${HOST_SOURCES} ${SHADER_HEADERS}) -target_include_directories(dc_host PRIVATE ${PORT_DIR} ${SHADER_HEADER_DIR} ${AUDIO_DECODER_DIR}) +target_include_directories(dc_host PRIVATE ${PORT_DIR} ${SHADER_HEADER_DIR} ${AUDIO_DECODER_DIR} ${TTF_DIR}) target_link_libraries(dc_host PRIVATE SDL3::SDL3 Vulkan::Vulkan nlohmann_json::nlohmann_json) target_compile_options(dc_host PRIVATE -Wall -ffunction-sections -fdata-sections) add_library(dc_port OBJECT ${PORT_LIB_SOURCES}) -target_link_libraries(dc_port PRIVATE dc_options SDL3::SDL3 Vulkan::Vulkan) +target_link_libraries(dc_port PRIVATE dc_options SDL3::SDL3 Vulkan::Vulkan nlohmann_json::nlohmann_json) # Replacement units reach the host side as "gfx/gfx.hpp", "platform/window.hpp". target_include_directories(dc_port PRIVATE ${PORT_DIR} ${SHADER_HEADER_DIR}) # The game's headers trip a few warnings that only describe how MWCC's source @@ -269,6 +273,14 @@ endif() include(GNUInstallDirs) install(TARGETS darkcloud RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +# The strings the port draws itself, translated (docs/LOCALIZATION.md): lang/ beside the executable, where the +# game reads its language files. +add_custom_target(dc_lang + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/lang $/lang + COMMENT "Copying the port's translated strings") +add_dependencies(darkcloud dc_lang) +install(DIRECTORY lang/ DESTINATION ${CMAKE_INSTALL_BINDIR}/lang) + # The tests use an installed GoogleTest, or fetch one at configure time; a build with neither turns them off. option(DC_BUILD_TESTS "Build darkcloud_tests" ON) if(DC_BUILD_TESTS) diff --git a/port/lang/de_de.json b/port/lang/de_de.json new file mode 100644 index 000000000..3896493ad --- /dev/null +++ b/port/lang/de_de.json @@ -0,0 +1,142 @@ +{ + "options.shortcuts": "{square} Standard\n{triangle} Verwerfen\n{circle} Verlassen", + "options.help.save_failed": "Speichern misslang.\nÄnderungen gelten.\nVerlassen: erneut\nconfig.json schreiben.", + "options.help.display_kept": "Die Anzeige konnte\nnicht wechseln und\nbleibt im alten Modus.\nAnderen Modus wählen.", + "options.help.tabs": "\"Optionen\"\n{L1} {R1} oder links/\nrechts: Seite wechseln.", + "options.help.exit": "{cross} Verlassen\n{square} Seitenstandard\n{triangle} Alles verwerfen\n{circle} Verlassen (Zeile)", + "options.help.display_now": "Die Anzeige konnte\nnicht wechseln. Sie ist\njetzt %1", + "options.display.fullscreen_now": "Vollbild.", + "options.display.window_now": "ein Fenster\nvon %1 x %2.", + "options.page.game": "Spiel", + "options.game.save_cursor_position.label": "Cursor merken", + "options.game.save_cursor_position.choice.0": "An", + "options.game.save_cursor_position.choice.1": "Aus", + "options.game.message_speed.label": "Textgeschwindigkeit", + "options.game.message_speed.choice.0": "Normal", + "options.game.message_speed.choice.1": "Schnell", + "options.game.clock.label": "Uhr", + "options.game.clock.choice.0": "An", + "options.game.clock.choice.1": "Aus", + "options.game.time_speed.label": "Zeitgeschwindigkeit", + "options.game.time_speed.choice.0": "Normal", + "options.game.time_speed.choice.1": "Schnell", + "options.game.map.label": "Dungeon-Karte", + "options.game.map.choice.0": "1", + "options.game.map.choice.1": "2", + "options.game.map.choice.2": "3", + "options.game.map.choice.3": "Aus", + "options.game.enemy_damage.label": "Gegnerschaden", + "options.game.enemy_damage.choice.0": "An", + "options.game.enemy_damage.choice.1": "Aus", + "options.game.player_damage.label": "Gruppenschaden", + "options.game.player_damage.choice.0": "An", + "options.game.player_damage.choice.1": "Aus", + "options.game.enemy_hp.label": "Gegner-LP", + "options.game.enemy_hp.choice.0": "An", + "options.game.enemy_hp.choice.1": "Aus", + "options.game.names.label": "Namen", + "options.game.names.choice.0": "An", + "options.game.names.choice.1": "Aus", + "options.discord.rich_presence.label": "Discord aktivieren", + "options.discord.rich_presence.help": "\"Discord Rich Presence\"\nZeigt auf Discord,\nwas du spielst.", + "options.discord.rich_presence.choice.0": "An", + "options.discord.rich_presence.choice.1": "Aus", + "options.game.language.label": "Sprache", + "options.game.language.help": "\"Sprache\"\nSprache des Spiels.\nFragen: Auswahl beim\nStart anzeigen.", + "options.game.language.choice.0": "Fragen", + "options.game.language.choice.1": "English", + "options.game.language.choice.2": "Français", + "options.game.language.choice.3": "Deutsch", + "options.game.language.choice.4": "Italiano", + "options.game.language.choice.5": "Español", + "options.page.display": "Anzeige", + "options.video.fullscreen.label": "Fenstermodus", + "options.video.fullscreen.help": "\"Fenstermodus\"\nIm Fenster oder im\nVollbild spielen.", + "options.video.fullscreen.choice.0": "Fenster", + "options.video.fullscreen.choice.1": "Vollbild", + "options.video.width.label": "Auflösung", + "options.video.width.help": "\"Auflösung\"\nGröße des Fensters.\nVollbild und Desktop\nnutzen den Monitor.", + "options.video.present_mode.label": "V-Sync", + "options.video.present_mode.help": "\"V-Sync\"\nAn: kein Tearing.\nSchnell: weniger\nLatenz. Aus: Tearing.", + "options.video.present_mode.choice.0": "An", + "options.video.present_mode.choice.1": "Schnell", + "options.video.present_mode.choice.2": "Aus", + "options.video.max_fps.label": "Bildratenlimit", + "options.video.max_fps.help": "\"Bildratenlimit\"\nDie meisten Bilder\npro Sekunde.", + "options.video.aspect.label": "Seitenverhältnis", + "options.video.aspect.help": "\"Seitenverhältnis\"\nBreit: füllt das\nFenster. 4:3: das Bild\nder PS2.", + "options.video.aspect.choice.0": "Breit", + "options.video.aspect.choice.1": "4:3", + "options.video.ui_scale.label": "Menügröße", + "options.video.ui_scale.help": "\"Menügröße\"\nGröße von HUD und\nMenüs, nach dem\nVerlassen der Optionen.", + "options.video.interpolation.label": "Weiche Bewegung", + "options.video.interpolation.help": "\"Weiche Bewegung\"\nZeichnet Bilder\nzwischen den Schritten.", + "options.video.interpolation.choice.0": "An", + "options.video.interpolation.choice.1": "Aus", + "options.video.show_fps.label": "FPS-Anzeige", + "options.video.show_fps.help": "\"FPS-Anzeige\"\nZeigt die Bildrate\nin der Ecke.", + "options.video.show_fps.choice.0": "An", + "options.video.show_fps.choice.1": "Aus", + "options.video.soft_focus.label": "Weichzeichner", + "options.video.soft_focus.choice.0": "An", + "options.video.soft_focus.choice.1": "Aus", + "options.page.audio": "Audio", + "options.audio.master_volume.label": "Lautstärke", + "options.audio.master_volume.help": "\"Lautstärke\"\nWie laut das Spiel ist.", + "options.audio.sound.label": "Ton", + "options.audio.sound.choice.0": "Stereo", + "options.audio.sound.choice.1": "Mono", + "options.page.controls": "Steuerung", + "options.input.vibration.label": "Vibration", + "options.input.vibration.choice.0": "An", + "options.input.vibration.choice.1": "Aus", + "options.input.mouse_sensitivity.label": "Mausempfindlichkeit", + "options.input.mouse_sensitivity.help": "\"Mausempfindlichkeit\"\nWie schnell die Maus\ndie Kamera dreht.", + "options.input.mouse_invert_y.label": "Maus Y umkehren", + "options.input.mouse_invert_y.help": "\"Maus Y umkehren\"\nMaus nach oben:\nBlick nach unten.", + "options.input.mouse_invert_y.choice.0": "An", + "options.input.mouse_invert_y.choice.1": "Aus", + "options.input.stick_sensitivity.label": "Stick-Empfindlichk.", + "options.input.stick_sensitivity.help": "\"Stick-Empfindlichk.\"\nWie weit der Stick\nkippen muss.", + "options.input.stick_invert_x.label": "Stick X umkehren", + "options.input.stick_invert_x.help": "\"Stick X umkehren\"\nKehrt Kamera links\nund rechts um.", + "options.input.stick_invert_x.choice.0": "An", + "options.input.stick_invert_x.choice.1": "Aus", + "options.input.stick_invert_y.label": "Stick Y umkehren", + "options.input.stick_invert_y.help": "\"Stick Y umkehren\"\nKehrt Kamera oben\nund unten um.", + "options.input.stick_invert_y.choice.0": "An", + "options.input.stick_invert_y.choice.1": "Aus", + "options.input.gyro.label": "Gyro", + "options.input.gyro.help": "\"Gyro\"\nWenn das Neigen des\nPads die Kamera dreht.", + "options.input.gyro.choice.0": "Aus", + "options.input.gyro.choice.1": "Immer", + "options.input.gyro.choice.2": "Ego-Sicht", + "options.input.gyro.choice.3": "Beim Halten", + "options.input.gyro_sensitivity.label": "Gyro-Empfindlichk.", + "options.input.gyro_sensitivity.help": "\"Gyro-Empfindlichk.\"\nWie schnell Neigen\ndie Kamera dreht.", + "options.input.gyro_invert_x.label": "Gyro X umkehren", + "options.input.gyro_invert_x.help": "\"Gyro X umkehren\"\nKehrt Gyro links\nund rechts um.", + "options.input.gyro_invert_x.choice.0": "An", + "options.input.gyro_invert_x.choice.1": "Aus", + "options.input.gyro_invert_y.label": "Gyro Y umkehren", + "options.input.gyro_invert_y.help": "\"Gyro Y umkehren\"\nKehrt Gyro oben\nund unten um.", + "options.input.gyro_invert_y.choice.0": "An", + "options.input.gyro_invert_y.choice.1": "Aus", + "options.video.width.value.Desktop": "Desktop", + "options.video.max_fps.value.Unlimited": "Unbegrenzt", + "options.game.qte_always_win.label": "QTEs immer gewinnen", + "options.game.qte_always_win.help": "\"QTEs immer gewinnen\"\nTastenabfragen enden\nimmer perfekt.", + "options.game.qte_always_win.choice.0": "An", + "options.game.qte_always_win.choice.1": "Aus", + "options.video.fps_detail.label": "FPS-Info", + "options.video.fps_detail.help": "\"FPS-Info\"\nWas der Zähler zeigt:\nnur FPS, mit Ticks\noder alles.", + "options.video.fps_detail.choice.0": "FPS", + "options.video.fps_detail.choice.1": "FPS+Ticks", + "options.video.fps_detail.choice.2": "Alles", + "options.input.mouse_zoom.label": "Mausrad-Zoom", + "options.input.mouse_zoom.help": "\"Mausrad-Zoom\"\nScrollen, um näher\noder weiter weg von der\nFigur zu gehen.", + "options.input.mouse_zoom.choice.0": "An", + "options.input.mouse_zoom.choice.1": "Aus", + "options.input.bindings.zoom_reset.label": "Zoom zurücksetzen", + "options.input.bindings.zoom_reset.help": "\"Zoom zurücksetzen\"\nStellt normalen\nKameraabstand her." +} diff --git a/port/lang/es_es.json b/port/lang/es_es.json new file mode 100644 index 000000000..c7f1c137e --- /dev/null +++ b/port/lang/es_es.json @@ -0,0 +1,142 @@ +{ + "options.shortcuts": "{square} Predeterm.\n{triangle} Deshacer\n{circle} Cerrar", + "options.help.save_failed": "No se pudo guardar.\nLos cambios se aplican.\nCierra para reintentar\nescribir config.json.", + "options.help.display_kept": "La pantalla no pudo\ncambiar y mantiene\nsu modo. Prueba con\notro modo o tamaño.", + "options.help.tabs": "\"Opciones\"\n{L1} {R1} o izq./\nder.: cambiar página.", + "options.help.exit": "{cross} Cerrar\n{square} Valores de la página\n{triangle} Deshacer todo\n{circle} Cerrar (toda fila)", + "options.help.display_now": "La pantalla no pudo\ncambiar. Ahora es\n%1", + "options.display.fullscreen_now": "pantalla completa.", + "options.display.window_now": "una ventana\nde %1 x %2.", + "options.page.game": "Juego", + "options.game.save_cursor_position.label": "Guardar cursor", + "options.game.save_cursor_position.choice.0": "Sí", + "options.game.save_cursor_position.choice.1": "No", + "options.game.message_speed.label": "Velocidad mensajes", + "options.game.message_speed.choice.0": "Normal", + "options.game.message_speed.choice.1": "Rápido", + "options.game.clock.label": "Reloj", + "options.game.clock.choice.0": "Sí", + "options.game.clock.choice.1": "No", + "options.game.time_speed.label": "Velocidad del tiempo", + "options.game.time_speed.choice.0": "Normal", + "options.game.time_speed.choice.1": "Rápido", + "options.game.map.label": "Mapa de mazmorra", + "options.game.map.choice.0": "1", + "options.game.map.choice.1": "2", + "options.game.map.choice.2": "3", + "options.game.map.choice.3": "No", + "options.game.enemy_damage.label": "Daño a enemigos", + "options.game.enemy_damage.choice.0": "Sí", + "options.game.enemy_damage.choice.1": "No", + "options.game.player_damage.label": "Daño al grupo", + "options.game.player_damage.choice.0": "Sí", + "options.game.player_damage.choice.1": "No", + "options.game.enemy_hp.label": "PV enemigos", + "options.game.enemy_hp.choice.0": "Sí", + "options.game.enemy_hp.choice.1": "No", + "options.game.names.label": "Nombres", + "options.game.names.choice.0": "Sí", + "options.game.names.choice.1": "No", + "options.discord.rich_presence.label": "Activar Discord", + "options.discord.rich_presence.help": "\"Discord Rich Presence\"\nMuestra en Discord a\nqué estás jugando.", + "options.discord.rich_presence.choice.0": "Sí", + "options.discord.rich_presence.choice.1": "No", + "options.game.language.label": "Idioma", + "options.game.language.help": "\"Idioma\"\nEl idioma del juego.\nPreguntar: muestra la\nelección al iniciar.", + "options.game.language.choice.0": "Preguntar", + "options.game.language.choice.1": "English", + "options.game.language.choice.2": "Français", + "options.game.language.choice.3": "Deutsch", + "options.game.language.choice.4": "Italiano", + "options.game.language.choice.5": "Español", + "options.page.display": "Pantalla", + "options.video.fullscreen.label": "Modo de ventana", + "options.video.fullscreen.help": "\"Modo de ventana\"\nJuega en ventana o\nen pantalla completa.", + "options.video.fullscreen.choice.0": "Ventana", + "options.video.fullscreen.choice.1": "Completa", + "options.video.width.label": "Resolución", + "options.video.width.help": "\"Resolución\"\nTamaño de la ventana.\nCompleta y Escritorio\nusan todo el monitor.", + "options.video.present_mode.label": "V-Sync", + "options.video.present_mode.help": "\"V-Sync\"\nSí: sin cortes.\nRápido: menos retraso.\nNo: puede haber cortes.", + "options.video.present_mode.choice.0": "Sí", + "options.video.present_mode.choice.1": "Rápido", + "options.video.present_mode.choice.2": "No", + "options.video.max_fps.label": "Límite de FPS", + "options.video.max_fps.help": "\"Límite de FPS\"\nLos máximos cuadros\npor segundo.", + "options.video.aspect.label": "Relación aspecto", + "options.video.aspect.help": "\"Relación aspecto\"\nAncho: llena la\nventana. 4:3: la imagen\nde PS2.", + "options.video.aspect.choice.0": "Ancho", + "options.video.aspect.choice.1": "4:3", + "options.video.ui_scale.label": "Tamaño interfaz", + "options.video.ui_scale.help": "\"Tamaño interfaz\"\nTamaño del HUD y los\nmenús, al cerrar las\nopciones.", + "options.video.interpolation.label": "Movimiento suave", + "options.video.interpolation.help": "\"Movimiento suave\"\nDibuja cuadros entre\nlos pasos del juego.", + "options.video.interpolation.choice.0": "Sí", + "options.video.interpolation.choice.1": "No", + "options.video.show_fps.label": "Contador de FPS", + "options.video.show_fps.help": "\"Contador de FPS\"\nMuestra los cuadros\npor segundo arriba.", + "options.video.show_fps.choice.0": "Sí", + "options.video.show_fps.choice.1": "No", + "options.video.soft_focus.label": "Enfoque suave", + "options.video.soft_focus.choice.0": "Sí", + "options.video.soft_focus.choice.1": "No", + "options.page.audio": "Audio", + "options.audio.master_volume.label": "Volumen", + "options.audio.master_volume.help": "\"Volumen\"\nQué tan alto suena.", + "options.audio.sound.label": "Sonido", + "options.audio.sound.choice.0": "Estéreo", + "options.audio.sound.choice.1": "Mono", + "options.page.controls": "Controles", + "options.input.vibration.label": "Vibración", + "options.input.vibration.choice.0": "Sí", + "options.input.vibration.choice.1": "No", + "options.input.mouse_sensitivity.label": "Sensibilidad ratón", + "options.input.mouse_sensitivity.help": "\"Sensibilidad ratón\"\nQué tan rápido gira\nla cámara el ratón.", + "options.input.mouse_invert_y.label": "Invertir ratón Y", + "options.input.mouse_invert_y.help": "\"Invertir ratón Y\"\nRatón hacia arriba:\nmira hacia abajo.", + "options.input.mouse_invert_y.choice.0": "Sí", + "options.input.mouse_invert_y.choice.1": "No", + "options.input.stick_sensitivity.label": "Sensibilidad stick", + "options.input.stick_sensitivity.help": "\"Sensibilidad stick\"\nCuánto hay que\ninclinar el stick.", + "options.input.stick_invert_x.label": "Invertir stick X", + "options.input.stick_invert_x.help": "\"Invertir stick X\"\nInvierte la cámara a\nizquierda y derecha.", + "options.input.stick_invert_x.choice.0": "Sí", + "options.input.stick_invert_x.choice.1": "No", + "options.input.stick_invert_y.label": "Invertir stick Y", + "options.input.stick_invert_y.help": "\"Invertir stick Y\"\nInvierte la cámara\narriba y abajo.", + "options.input.stick_invert_y.choice.0": "Sí", + "options.input.stick_invert_y.choice.1": "No", + "options.input.gyro.label": "Giroscopio", + "options.input.gyro.help": "\"Giroscopio\"\nCuando inclinar el\nmando gira la cámara.", + "options.input.gyro.choice.0": "No", + "options.input.gyro.choice.1": "Siempre", + "options.input.gyro.choice.2": "Primera persona", + "options.input.gyro.choice.3": "Al mantener", + "options.input.gyro_sensitivity.label": "Sensib. giroscopio", + "options.input.gyro_sensitivity.help": "\"Sensib. giroscopio\"\nQué tan rápido gira\nal inclinar.", + "options.input.gyro_invert_x.label": "Invertir giroscopio X", + "options.input.gyro_invert_x.help": "\"Invertir giroscopio X\"\nInvierte el giroscopio\na izquierda y derecha.", + "options.input.gyro_invert_x.choice.0": "Sí", + "options.input.gyro_invert_x.choice.1": "No", + "options.input.gyro_invert_y.label": "Invertir giroscopio Y", + "options.input.gyro_invert_y.help": "\"Invertir giroscopio Y\"\nInvierte el giroscopio\narriba y abajo.", + "options.input.gyro_invert_y.choice.0": "Sí", + "options.input.gyro_invert_y.choice.1": "No", + "options.video.width.value.Desktop": "Escritorio", + "options.video.max_fps.value.Unlimited": "Sin límite", + "options.game.qte_always_win.label": "QTE siempre ganados", + "options.game.qte_always_win.help": "\"QTE siempre ganados\"\nLos botones a tiempo\nterminan en perfecto.", + "options.game.qte_always_win.choice.0": "Sí", + "options.game.qte_always_win.choice.1": "No", + "options.video.fps_detail.label": "Info de FPS", + "options.video.fps_detail.help": "\"Info de FPS\"\nQué muestra el\ncontador: solo FPS,\ncon ticks, o todo.", + "options.video.fps_detail.choice.0": "FPS", + "options.video.fps_detail.choice.1": "FPS+Ticks", + "options.video.fps_detail.choice.2": "Todo", + "options.input.mouse_zoom.label": "Zoom con rueda", + "options.input.mouse_zoom.help": "\"Zoom con rueda\"\nDesplázate para\nacercarte o alejarte\ndel personaje.", + "options.input.mouse_zoom.choice.0": "Sí", + "options.input.mouse_zoom.choice.1": "No", + "options.input.bindings.zoom_reset.label": "Restablecer zoom", + "options.input.bindings.zoom_reset.help": "\"Restablecer zoom\"\nRestaura la distancia\nnormal de la cámara." +} diff --git a/port/lang/fr_fr.json b/port/lang/fr_fr.json new file mode 100644 index 000000000..719ed0696 --- /dev/null +++ b/port/lang/fr_fr.json @@ -0,0 +1,142 @@ +{ + "options.shortcuts": "{square} Défauts\n{triangle} Annuler\n{circle} Fermer", + "options.help.save_failed": "Sauvegarde impossible.\nChangements actifs.\nFermez pour réessayer\nconfig.json.", + "options.help.display_kept": "L'écran n'a pas pu\nchanger et garde son\nancien mode. Essayez\nun autre mode.", + "options.help.tabs": "\"Options\"\n{L1} {R1} ou gauche/\ndroite: changer page.", + "options.help.exit": "{cross} Fermer\n{square} Défauts de la page\n{triangle} Tout annuler\n{circle} Fermer (toute ligne)", + "options.help.display_now": "L'écran n'a pas pu\nchanger. Il est\ndésormais %1", + "options.display.fullscreen_now": "plein écran.", + "options.display.window_now": "une fenêtre\nde %1 x %2.", + "options.page.game": "Jeu", + "options.game.save_cursor_position.label": "Mémoriser curseur", + "options.game.save_cursor_position.choice.0": "Activé", + "options.game.save_cursor_position.choice.1": "Désactivé", + "options.game.message_speed.label": "Vitesse messages", + "options.game.message_speed.choice.0": "Normal", + "options.game.message_speed.choice.1": "Rapide", + "options.game.clock.label": "Horloge", + "options.game.clock.choice.0": "Activé", + "options.game.clock.choice.1": "Désactivé", + "options.game.time_speed.label": "Vitesse du temps", + "options.game.time_speed.choice.0": "Normal", + "options.game.time_speed.choice.1": "Rapide", + "options.game.map.label": "Carte du donjon", + "options.game.map.choice.0": "1", + "options.game.map.choice.1": "2", + "options.game.map.choice.2": "3", + "options.game.map.choice.3": "Désactivé", + "options.game.enemy_damage.label": "Dégâts ennemis", + "options.game.enemy_damage.choice.0": "Activé", + "options.game.enemy_damage.choice.1": "Désactivé", + "options.game.player_damage.label": "Dégâts équipe", + "options.game.player_damage.choice.0": "Activé", + "options.game.player_damage.choice.1": "Désactivé", + "options.game.enemy_hp.label": "PV ennemis", + "options.game.enemy_hp.choice.0": "Activé", + "options.game.enemy_hp.choice.1": "Désactivé", + "options.game.names.label": "Noms", + "options.game.names.choice.0": "Activé", + "options.game.names.choice.1": "Désactivé", + "options.discord.rich_presence.label": "Activer Discord", + "options.discord.rich_presence.help": "\"Discord Rich Presence\"\nMontre à quoi vous\njouez sur Discord.", + "options.discord.rich_presence.choice.0": "Activé", + "options.discord.rich_presence.choice.1": "Désactivé", + "options.game.language.label": "Langue", + "options.game.language.help": "\"Langue\"\nLangue du jeu.\nDemander: écran de\nchoix au démarrage.", + "options.game.language.choice.0": "Demander", + "options.game.language.choice.1": "English", + "options.game.language.choice.2": "Français", + "options.game.language.choice.3": "Deutsch", + "options.game.language.choice.4": "Italiano", + "options.game.language.choice.5": "Español", + "options.page.display": "Affichage", + "options.video.fullscreen.label": "Mode d'affichage", + "options.video.fullscreen.help": "\"Mode d'affichage\"\nJouer en fenêtre ou\nen plein écran.", + "options.video.fullscreen.choice.0": "Fenêtré", + "options.video.fullscreen.choice.1": "Plein écran", + "options.video.width.label": "Résolution", + "options.video.width.help": "\"Résolution\"\nTaille de la fenêtre.\nPlein écran et Bureau\noccupent tout l'écran.", + "options.video.present_mode.label": "V-Sync", + "options.video.present_mode.help": "\"V-Sync\"\nActivé: sans déchirure\nRapide: moins de délai\nDésactivé: déchirure", + "options.video.present_mode.choice.0": "Activé", + "options.video.present_mode.choice.1": "Rapide", + "options.video.present_mode.choice.2": "Désactivé", + "options.video.max_fps.label": "Limite d'images", + "options.video.max_fps.help": "\"Limite d'images\"\nNombre maximal\nd'images par seconde.", + "options.video.aspect.label": "Format d'image", + "options.video.aspect.help": "\"Format d'image\"\nLarge: remplit la\nfenêtre. 4:3: l'image\nde la PS2.", + "options.video.aspect.choice.0": "Large", + "options.video.aspect.choice.1": "4:3", + "options.video.ui_scale.label": "Taille interface", + "options.video.ui_scale.help": "\"Taille interface\"\nTaille du HUD et des\nmenus, appliquée à la\nfermeture des options.", + "options.video.interpolation.label": "Fluidité", + "options.video.interpolation.help": "\"Fluidité\"\nAffiche des images\nentre les étapes.", + "options.video.interpolation.choice.0": "Activé", + "options.video.interpolation.choice.1": "Désactivé", + "options.video.show_fps.label": "Compteur d'IPS", + "options.video.show_fps.help": "\"Compteur d'IPS\"\nAffiche la cadence\nd'images dans le coin.", + "options.video.show_fps.choice.0": "Activé", + "options.video.show_fps.choice.1": "Désactivé", + "options.video.soft_focus.label": "Flou doux", + "options.video.soft_focus.choice.0": "Activé", + "options.video.soft_focus.choice.1": "Désactivé", + "options.page.audio": "Audio", + "options.audio.master_volume.label": "Volume", + "options.audio.master_volume.help": "\"Volume\"\nLe volume du jeu.", + "options.audio.sound.label": "Son", + "options.audio.sound.choice.0": "Stéréo", + "options.audio.sound.choice.1": "Mono", + "options.page.controls": "Commandes", + "options.input.vibration.label": "Vibration", + "options.input.vibration.choice.0": "Activé", + "options.input.vibration.choice.1": "Désactivé", + "options.input.mouse_sensitivity.label": "Sensibilité souris", + "options.input.mouse_sensitivity.help": "\"Sensibilité souris\"\nVitesse de la souris\npour la caméra.", + "options.input.mouse_invert_y.label": "Inverser souris Y", + "options.input.mouse_invert_y.help": "\"Inverser souris Y\"\nSouris vers le haut:\nregard vers le bas.", + "options.input.mouse_invert_y.choice.0": "Activé", + "options.input.mouse_invert_y.choice.1": "Désactivé", + "options.input.stick_sensitivity.label": "Sensibilité stick", + "options.input.stick_sensitivity.help": "\"Sensibilité stick\"\nInclinaison du stick\nnécessaire.", + "options.input.stick_invert_x.label": "Inverser stick X", + "options.input.stick_invert_x.help": "\"Inverser stick X\"\nInverse la caméra\ngauche et droite.", + "options.input.stick_invert_x.choice.0": "Activé", + "options.input.stick_invert_x.choice.1": "Désactivé", + "options.input.stick_invert_y.label": "Inverser stick Y", + "options.input.stick_invert_y.help": "\"Inverser stick Y\"\nInverse la caméra\nhaut et bas.", + "options.input.stick_invert_y.choice.0": "Activé", + "options.input.stick_invert_y.choice.1": "Désactivé", + "options.input.gyro.label": "Gyroscope", + "options.input.gyro.help": "\"Gyroscope\"\nL'inclinaison de la\nmanette oriente la\ncaméra.", + "options.input.gyro.choice.0": "Désactivé", + "options.input.gyro.choice.1": "Toujours", + "options.input.gyro.choice.2": "Vue subjective", + "options.input.gyro.choice.3": "Si maintenu", + "options.input.gyro_sensitivity.label": "Sensibilité gyro", + "options.input.gyro_sensitivity.help": "\"Sensibilité gyro\"\nVitesse du gyroscope\npour la caméra.", + "options.input.gyro_invert_x.label": "Inverser gyro X", + "options.input.gyro_invert_x.help": "\"Inverser gyro X\"\nInverse la gauche et\nla droite du gyro.", + "options.input.gyro_invert_x.choice.0": "Activé", + "options.input.gyro_invert_x.choice.1": "Désactivé", + "options.input.gyro_invert_y.label": "Inverser gyro Y", + "options.input.gyro_invert_y.help": "\"Inverser gyro Y\"\nInverse le haut et\nle bas du gyro.", + "options.input.gyro_invert_y.choice.0": "Activé", + "options.input.gyro_invert_y.choice.1": "Désactivé", + "options.video.width.value.Desktop": "Bureau", + "options.video.max_fps.value.Unlimited": "Illimitée", + "options.game.qte_always_win.label": "QTE toujours gagnés", + "options.game.qte_always_win.help": "\"QTE toujours gagnés\"\nLes invites de touches\nfinissent en parfait.", + "options.game.qte_always_win.choice.0": "Activé", + "options.game.qte_always_win.choice.1": "Désactivé", + "options.video.fps_detail.label": "Infos IPS", + "options.video.fps_detail.help": "\"Infos IPS\"\nCe que montre le\ncompteur: IPS seuls,\navec ticks, ou tout.", + "options.video.fps_detail.choice.0": "IPS", + "options.video.fps_detail.choice.1": "IPS+Ticks", + "options.video.fps_detail.choice.2": "Tout", + "options.input.mouse_zoom.label": "Zoom molette", + "options.input.mouse_zoom.help": "\"Zoom molette\"\nFaites défiler pour\nvous rapprocher ou\nvous éloigner du héros.", + "options.input.mouse_zoom.choice.0": "Activé", + "options.input.mouse_zoom.choice.1": "Désactivé", + "options.input.bindings.zoom_reset.label": "Réinit. zoom", + "options.input.bindings.zoom_reset.help": "\"Réinit. zoom\"\nRétablit la distance\nnormale de la caméra." +} diff --git a/port/lang/it_it.json b/port/lang/it_it.json new file mode 100644 index 000000000..004c7ca88 --- /dev/null +++ b/port/lang/it_it.json @@ -0,0 +1,142 @@ +{ + "options.shortcuts": "{square} Predefiniti\n{triangle} Annulla\n{circle} Chiudi", + "options.help.save_failed": "Salvataggio fallito.\nLe modifiche valgono.\nChiudi per riprovare\nscrivere config.json.", + "options.help.display_kept": "Lo schermo non ha\npotuto cambiare e ha\nmantenuto la modalità.\nProva un'altra.", + "options.help.tabs": "\"Opzioni\"\n{L1} {R1} o sinistra/\ndestra: cambia pagina.", + "options.help.exit": "{cross} Chiudi\n{square} Predefiniti pagina\n{triangle} Annulla tutto\n{circle} Chiudi da ogni riga", + "options.help.display_now": "Lo schermo non ha\npotuto cambiare.\nOra è %1", + "options.display.fullscreen_now": "schermo intero.", + "options.display.window_now": "una finestra\ndi %1 x %2.", + "options.page.game": "Gioco", + "options.game.save_cursor_position.label": "Salva cursore", + "options.game.save_cursor_position.choice.0": "Sì", + "options.game.save_cursor_position.choice.1": "No", + "options.game.message_speed.label": "Velocità messaggi", + "options.game.message_speed.choice.0": "Normale", + "options.game.message_speed.choice.1": "Veloce", + "options.game.clock.label": "Orologio", + "options.game.clock.choice.0": "Sì", + "options.game.clock.choice.1": "No", + "options.game.time_speed.label": "Velocità del tempo", + "options.game.time_speed.choice.0": "Normale", + "options.game.time_speed.choice.1": "Veloce", + "options.game.map.label": "Mappa dungeon", + "options.game.map.choice.0": "1", + "options.game.map.choice.1": "2", + "options.game.map.choice.2": "3", + "options.game.map.choice.3": "No", + "options.game.enemy_damage.label": "Danni nemici", + "options.game.enemy_damage.choice.0": "Sì", + "options.game.enemy_damage.choice.1": "No", + "options.game.player_damage.label": "Danni squadra", + "options.game.player_damage.choice.0": "Sì", + "options.game.player_damage.choice.1": "No", + "options.game.enemy_hp.label": "PS nemici", + "options.game.enemy_hp.choice.0": "Sì", + "options.game.enemy_hp.choice.1": "No", + "options.game.names.label": "Nomi", + "options.game.names.choice.0": "Sì", + "options.game.names.choice.1": "No", + "options.discord.rich_presence.label": "Attiva Discord", + "options.discord.rich_presence.help": "\"Discord Rich Presence\"\nMostra su Discord a\ncosa stai giocando.", + "options.discord.rich_presence.choice.0": "Sì", + "options.discord.rich_presence.choice.1": "No", + "options.game.language.label": "Lingua", + "options.game.language.help": "\"Lingua\"\nLa lingua del gioco.\nChiedi: mostra la\nscelta all'avvio.", + "options.game.language.choice.0": "Chiedi", + "options.game.language.choice.1": "English", + "options.game.language.choice.2": "Français", + "options.game.language.choice.3": "Deutsch", + "options.game.language.choice.4": "Italiano", + "options.game.language.choice.5": "Español", + "options.page.display": "Schermo", + "options.video.fullscreen.label": "Modo finestra", + "options.video.fullscreen.help": "\"Modo finestra\"\nGioca in una finestra\no a schermo intero.", + "options.video.fullscreen.choice.0": "Finestra", + "options.video.fullscreen.choice.1": "Schermo intero", + "options.video.width.label": "Risoluzione", + "options.video.width.help": "\"Risoluzione\"\nDimensione finestra.\nIntero e Desktop usano\ntutto il monitor.", + "options.video.present_mode.label": "V-Sync", + "options.video.present_mode.help": "\"V-Sync\"\nSì: niente tearing.\nVeloce: meno ritardo.\nNo: può avere tearing.", + "options.video.present_mode.choice.0": "Sì", + "options.video.present_mode.choice.1": "Veloce", + "options.video.present_mode.choice.2": "No", + "options.video.max_fps.label": "Limite fotogrammi", + "options.video.max_fps.help": "\"Limite fotogrammi\"\nIl massimo di\nfotogrammi al secondo.", + "options.video.aspect.label": "Proporzioni", + "options.video.aspect.help": "\"Proporzioni\"\nAmpio: riempie la\nfinestra. 4:3: come\nsu PS2.", + "options.video.aspect.choice.0": "Ampio", + "options.video.aspect.choice.1": "4:3", + "options.video.ui_scale.label": "Dimensione menu", + "options.video.ui_scale.help": "\"Dimensione menu\"\nDimensione di HUD e\nmenu, dopo la chiusura\ndelle opzioni.", + "options.video.interpolation.label": "Moto fluido", + "options.video.interpolation.help": "\"Moto fluido\"\nDisegna fotogrammi\ntra i passi del gioco.", + "options.video.interpolation.choice.0": "Sì", + "options.video.interpolation.choice.1": "No", + "options.video.show_fps.label": "Contatore FPS", + "options.video.show_fps.help": "\"Contatore FPS\"\nMostra i fotogrammi\nal secondo nell'angolo.", + "options.video.show_fps.choice.0": "Sì", + "options.video.show_fps.choice.1": "No", + "options.video.soft_focus.label": "Fuoco morbido", + "options.video.soft_focus.choice.0": "Sì", + "options.video.soft_focus.choice.1": "No", + "options.page.audio": "Audio", + "options.audio.master_volume.label": "Volume", + "options.audio.master_volume.help": "\"Volume\"\nQuanto forte è il\ngioco.", + "options.audio.sound.label": "Suono", + "options.audio.sound.choice.0": "Stereo", + "options.audio.sound.choice.1": "Mono", + "options.page.controls": "Comandi", + "options.input.vibration.label": "Vibrazione", + "options.input.vibration.choice.0": "Sì", + "options.input.vibration.choice.1": "No", + "options.input.mouse_sensitivity.label": "Sensibilità mouse", + "options.input.mouse_sensitivity.help": "\"Sensibilità mouse\"\nVelocità con cui il\nmouse gira la camera.", + "options.input.mouse_invert_y.label": "Inverti mouse Y", + "options.input.mouse_invert_y.help": "\"Inverti mouse Y\"\nMouse in alto:\nsguardo in basso.", + "options.input.mouse_invert_y.choice.0": "Sì", + "options.input.mouse_invert_y.choice.1": "No", + "options.input.stick_sensitivity.label": "Sensibilità stick", + "options.input.stick_sensitivity.help": "\"Sensibilità stick\"\nQuanto va inclinato\nlo stick.", + "options.input.stick_invert_x.label": "Inverti stick X", + "options.input.stick_invert_x.help": "\"Inverti stick X\"\nInverte la camera a\nsinistra e a destra.", + "options.input.stick_invert_x.choice.0": "Sì", + "options.input.stick_invert_x.choice.1": "No", + "options.input.stick_invert_y.label": "Inverti stick Y", + "options.input.stick_invert_y.help": "\"Inverti stick Y\"\nInverte la camera in\nalto e in basso.", + "options.input.stick_invert_y.choice.0": "Sì", + "options.input.stick_invert_y.choice.1": "No", + "options.input.gyro.label": "Giroscopio", + "options.input.gyro.help": "\"Giroscopio\"\nQuando inclinare il\npad gira la camera.", + "options.input.gyro.choice.0": "No", + "options.input.gyro.choice.1": "Sempre", + "options.input.gyro.choice.2": "Prima persona", + "options.input.gyro.choice.3": "Se premuto", + "options.input.gyro_sensitivity.label": "Sensibilità giroscopio", + "options.input.gyro_sensitivity.help": "\"Sensib. giroscopio\"\nVelocità con cui\nl'inclinazione gira.", + "options.input.gyro_invert_x.label": "Inverti giroscopio X", + "options.input.gyro_invert_x.help": "\"Inverti giroscopio X\"\nInverte il giroscopio\na sinistra e a destra.", + "options.input.gyro_invert_x.choice.0": "Sì", + "options.input.gyro_invert_x.choice.1": "No", + "options.input.gyro_invert_y.label": "Inverti giroscopio Y", + "options.input.gyro_invert_y.help": "\"Inverti giroscopio Y\"\nInverte il giroscopio\nin alto e in basso.", + "options.input.gyro_invert_y.choice.0": "Sì", + "options.input.gyro_invert_y.choice.1": "No", + "options.video.width.value.Desktop": "Desktop", + "options.video.max_fps.value.Unlimited": "Illimitato", + "options.game.qte_always_win.label": "QTE sempre vinti", + "options.game.qte_always_win.help": "\"QTE sempre vinti\"\nI comandi a tempo\nfiniscono sempre in\nperfetto.", + "options.game.qte_always_win.choice.0": "Sì", + "options.game.qte_always_win.choice.1": "No", + "options.video.fps_detail.label": "Info FPS", + "options.video.fps_detail.help": "\"Info FPS\"\nCosa mostra il\ncontatore: solo FPS,\ncon tick, o tutto.", + "options.video.fps_detail.choice.0": "FPS", + "options.video.fps_detail.choice.1": "FPS+Tick", + "options.video.fps_detail.choice.2": "Tutto", + "options.input.mouse_zoom.label": "Zoom rotella", + "options.input.mouse_zoom.help": "\"Zoom rotella\"\nScorri per avvicinarti\no allontanarti dal\npersonaggio.", + "options.input.mouse_zoom.choice.0": "Sì", + "options.input.mouse_zoom.choice.1": "No", + "options.input.bindings.zoom_reset.label": "Azzera zoom", + "options.input.bindings.zoom_reset.help": "\"Azzera zoom\"\nRipristina la distanza\nnormale della camera." +} diff --git a/port/src/clsmes.cpp b/port/src/clsmes.cpp index db8d4e50d..ef5338d02 100644 --- a/port/src/clsmes.cpp +++ b/port/src/clsmes.cpp @@ -7,18 +7,212 @@ #include #include "draw2d_port.hpp" +#include "gametext.hpp" #include "gameutil.hpp" +#include "localize.hpp" #include "mglib.hpp" #include "mglib_port.hpp" +#include "platform/ttffont.hpp" #include "rect.hpp" #include "snd.hpp" #include "texture.hpp" extern float RandTbl[64]; extern float RandTbl2[64]; +extern MES_FUCHI FuchiTbl_E[]; namespace { +// ---- Message text from a TrueType font ---- +// With font.ttf in a lang folder (the save's or the executable's), a character the font has is drawn +// from it at the screen's own resolution, in the cell the game laid the bitmap character out in, +// instead of from the 14x20 bitmaps of gaiji.img. Icons and characters the font lacks draw as before. + +bool TtfReady() { + static bool tried = false; + if (!tried) { + tried = true; + std::filesystem::path file = LocalizeFindFile("font.ttf"); + if (!file.empty() && ttffont::Load(file)) { + std::fprintf(stderr, "font: message text from %s\n", file.string().c_str()); + } + } + return ttffont::Loaded(); +} + +// What DrawGaijiFont tints a character with: the colour table's entry, the flashing cursor row's, +// and double for a lit row. +void GlyphTint(int clut, int dark, int wide, int &r, int &g, int &b, int &alpha) { + alpha = 0x80; + switch (FontColorTbl[clut]) { + case FONT_COLOR_WHITE: r = g = b = 0x5F; break; + case FONT_COLOR_BROWN: r = 0x22; g = 0x20; b = 0x18; break; + case FONT_COLOR_YELLOW: r = 0x5E; g = 0x5E; b = 0x20; break; + case FONT_COLOR_CYAN: r = 0x20; g = 0x5E; b = 0x5E; break; + case FONT_COLOR_GREEN: r = 0x20; g = 0x5E; b = 0x20; break; + case FONT_COLOR_BROWN_OPAQUE: r = 0x22; g = 0x20; b = 0x18; break; + case FONT_COLOR_GOLD: r = 0x73; g = 0x67; b = 0x33; break; + case FONT_COLOR_GREY: r = g = b = 0x47; break; + case FONT_COLOR_MAGENTA: r = 0x5F; g = 0x1F; b = 0x5F; break; + default: r = 0x28; g = 0x28; b = 0x20; break; + } + if (clut == 0xFF) { + if (dark != 0) { + r = 0x7F; g = 0x22; b = 0x22; + } else { + r = 0x40; g = 0x11; b = 0x11; + } + } + if (wide == 1) { + r *= 2; + g *= 2; + b *= 2; + } +} + +void TtfQuad(std::vector &out, const ttffont::Glyph &glyph, float x, float y, float width, + float height, int r, int g, int b, int alpha) { + auto clamp = [](int v) { return static_cast(v < 0 ? 0 : v > 255 ? 255 : v); }; + const u_char red = clamp(r), green = clamp(g), blue = clamp(b), a = clamp(alpha); + out.push_back(draw2d::Vertex(x, y, 0.0f, glyph.u0, glyph.v0, red, green, blue, a)); + out.push_back(draw2d::Vertex(x + width, y, 0.0f, glyph.u1, glyph.v0, red, green, blue, a)); + out.push_back(draw2d::Vertex(x + width, y + height, 0.0f, glyph.u1, glyph.v1, red, green, blue, a)); + out.push_back(draw2d::Vertex(x, y + height, 0.0f, glyph.u0, glyph.v1, red, green, blue, a)); +} + +// The em the font is drawn at: its line fits 82% of the cell's height and its widest glyph the cell's +// width, so a glyph never leaves the cell the game sized the window for. +float TtfEm(const ClsMes &mes, const ttffont::Metrics &metrics) { + return std::min(mes.char_height * 0.82f, mes.char_width * 0.95f / metrics.advance); +} + +// Where each laid-out character of a window starts, in the window's own coordinates. A word (a run of +// characters the font draws) keeps the place the game gave it: its letters go one after another by +// their own widths and the word is centred on the cells the game gave it, so rows, indents, centring and +// the icons between words stay where the game put them. A glyph is never given more than its cell, so +// a word is never wider than the game's. +void TtfLayoutRows(ClsMes &mes, std::vector &xs) { + const int count = mes.win_line_num; + xs.assign(static_cast(count), 0.0f); + for (int i = 0; i < count; i++) { + xs[static_cast(i)] = static_cast(mes.win_line[i].x); + } + if (mes.char_height <= 0 || mes.char_width <= 0) { + return; + } + const gfx::LogicalMapping mapping = gfx::GetUiMapping(gfx::CurrentRenderTarget()); + const ttffont::Metrics metrics = ttffont::GetMetrics(); + const float em = TtfEm(mes, metrics); + const int em_px = static_cast(em * mapping.scale_x + 0.5f); + const float gap = em * 0.1f; + + auto glyph_of = [&](int i, ttffont::Glyph &glyph) { + const int code = mes.win_line[i].code; + const char32_t ch = code >= -0x2DF && code < -0x251 ? GameTextChar(static_cast(code)) : 0; + return ch != 0 && ttffont::GetGlyph(ch, em_px, glyph); + }; + auto cell_of = [&](int i) { + const bool next = i + 1 < count && mes.win_line[i + 1].y == mes.win_line[i].y; + return next ? static_cast(mes.win_line[i + 1].x - mes.win_line[i].x) : static_cast(mes.char_width); + }; + + int i = 0; + while (i < count) { + ttffont::Glyph glyph; + if (!glyph_of(i, glyph)) { + i++; + continue; + } + int last = i; + float natural = 0.0f; + std::vector pen; + for (; last < count && mes.win_line[last].y == mes.win_line[i].y && glyph_of(last, glyph); last++) { + pen.push_back(natural); + natural += std::min(glyph.width / mapping.scale_x + gap, cell_of(last)); + } + const float span = static_cast(mes.win_line[last - 1].x - mes.win_line[i].x) + cell_of(last - 1); + const float start = static_cast(mes.win_line[i].x) + (span - (natural - gap)) / 2; + for (int k = i; k < last; k++) { + xs[static_cast(k)] = start + pen[static_cast(k - i)]; + } + i = last; + } +} + +// Queues the font's glyph for win_line[index]: an edge pass per outline and one fill. False where +// the font has none, and the bitmap character is drawn. +bool QueueTtfGlyph(ClsMes &mes, int index, int dark, int offset_x, int offset_y, const std::vector &xs, + std::vector &edges, std::vector &fills) { + const MES_WIN_LINE &line = mes.win_line[index]; + const char32_t ch = GameTextChar(line.code); + if (ch == 0 || mes.char_height <= 0 || mes.char_width <= 0 || static_cast(index) >= xs.size()) { + return false; + } + const gfx::LogicalMapping mapping = gfx::GetUiMapping(gfx::CurrentRenderTarget()); + const float rows = draw2d::RowScale(); + const ttffont::Metrics metrics = ttffont::GetMetrics(); + const float em = TtfEm(mes, metrics); + const int em_px = static_cast(em * mapping.scale_x + 0.5f); + ttffont::Glyph glyph; + if (!ttffont::GetGlyph(ch, em_px, glyph)) { + return false; + } + + // The window's origin for this row, as the bitmap characters take it. + const int row = line.y / mes.char_height; + float origin_x, cell_y; + if (row >= static_cast(std::size(mes.line_pos)) || mes.line_pos[row].x < 0 || mes.line_pos[row].y < 0) { + origin_x = static_cast(offset_x + mes.text_x); + cell_y = static_cast(offset_y + line.y + mes.text_y); + } else { + origin_x = static_cast(mes.line_pos[row].x); + cell_y = static_cast(line.y + mes.line_pos[row].y - row * mes.char_height); + } + + // The font's line is centred in the cell's height. The bitmap's pixels are screen pixels, so a quad + // is as many logical units as they come to. + const float width = glyph.width / mapping.scale_x; + const float height = glyph.height / mapping.scale_y * rows; + const float base = cell_y + (mes.char_height - em * (metrics.ascent + metrics.descent)) / 2 + em * metrics.ascent; + float x = origin_x + xs[static_cast(index)]; + float y = (base * rows) + glyph.y_offset / mapping.scale_y; + + // On whole pixels, so the edges of the strokes are not smeared across two. + auto snap = [](float value, float scale, float offset) { return (std::round(value * scale + offset) - offset) / scale; }; + x = snap(x, mapping.scale_x, mapping.offset_x); + y = snap(y, mapping.scale_y, mapping.offset_y); + + int wide = 0; + if (mes.cursor_row >= 0) { + wide = mes.cursor_lit == 1 ? (row == mes.cursor_row ? 1 : 0) : (row == mes.cursor_row ? 0 : 1); + } + int r, g, b, alpha; + GlyphTint(line.clut, dark, wide, r, g, b, alpha); + + // The bitmaps' outlines were soft; a crisp glyph's reads thick at the same offsets, so they are + // brought in to 60%. + const int cap = mes.edge_alpha; + auto edge = [&](float dx, float dy, int er, int eg, int eb, int ea) { + TtfQuad(edges, glyph, x + dx * 0.6f, y + dy * 0.6f * rows, width, height, er, eg, eb, std::min(cap, ea)); + }; + switch (mes.style) { + case MES_EDGE_WHITE: edge(1, 1, 0xFF, 0xFF, 0xFF, 0x40); break; + case MES_EDGE_BLACK: edge(1, 1, 0, 0, 0, 0x40); break; + case MES_EDGE_TABLE: + for (int i = 0; FuchiTbl_E[i].alpha > 0; i++) { + edge(static_cast(FuchiTbl_E[i].x), static_cast(FuchiTbl_E[i].y), FuchiTbl_E[i].r, + FuchiTbl_E[i].g, FuchiTbl_E[i].b, FuchiTbl_E[i].alpha); + } + break; + case MES_EDGE_DOUBLE: + edge(1, 1, 0x40, 0x40, 0x40, 0x80); + edge(2, 2, 0, 0, 0, 0x80); + break; + } + TtfQuad(fills, glyph, x, y, width, height, r, g, b, std::min(cap, alpha)); + return true; +} + enum { GAIJI_CODE, GAIJI_U, @@ -797,6 +991,13 @@ PC_OVERRIDE void ClsMes::DrawMesWin() { texture = draw2d::Get().find_texture("gaiji"); setbilinear(0); set2DSprite_Start(Vif1Packet, texture); + const bool ttf = TtfReady(); + std::vector ttf_edges; + std::vector ttf_fills; + std::vector ttf_xs; + if (ttf) { + TtfLayoutRows(*this, ttf_xs); + } for (index = this->text_from; index < this->text_no; index++) { if (MesAbsDrawOff != 0) { @@ -809,6 +1010,10 @@ PC_OVERRIDE void ClsMes::DrawMesWin() { continue; } + if (ttf && code >= -0x2DF && QueueTtfGlyph(*this, index, dark, offset_x, offset_y, ttf_xs, ttf_edges, ttf_fills)) { + continue; + } + int gaiji_index = code + 0x300; int u = GaijiDataTbl[gaiji_index][GAIJI_U]; @@ -911,6 +1116,15 @@ PC_OVERRIDE void ClsMes::DrawMesWin() { set2DSprite_End(Vif1Packet, texture); + if (!ttf_fills.empty()) { + const gfx::TextureBinding binding = ttffont::Binding(); + if (!ttf_edges.empty()) { + gfx::Draw2D(gfx::Primitive::Quads, ttf_edges, binding, draw2d::SpriteState()); + } + gfx::Draw2D(gfx::Primitive::Quads, ttf_fills, binding, draw2d::SpriteState()); + draw2d::RestoreTestZbuf(); + } + if (this->page_arrow != 0) { if ((this->text_no >= this->text_len && this->cursor_row < 0) || this->waiting != 0) { if ((this->blink >> 4) % 2 != 0) { diff --git a/port/src/dataread.cpp b/port/src/dataread.cpp index b1583eeab..d6736f8d9 100644 --- a/port/src/dataread.cpp +++ b/port/src/dataread.cpp @@ -11,6 +11,7 @@ #include #include "../../tools/dcdata/dcdata.hpp" +#include "localize.hpp" #include "platform/paths.hpp" namespace fs = std::filesystem; @@ -85,6 +86,28 @@ int ReadWhole(const fs::path &file, void *buffer) { return static_cast(size); } +// One file inside a pack, as ps2/src/dataread.cpp has it: offsets are bytes from the entry, and a +// first byte of zero ends the table. +struct PackEntry { + char name[64]; + int offset; + int size; + int next; +}; + +bool SameName(const char *name, std::string_view other) { + std::size_t length = std::strlen(name); + return length == other.size() && dcdata::FoldPath(std::string_view(name, length)) == dcdata::FoldPath(other); +} + +// Hands a file just read to the localization layer: a message file may be rewritten from the +// language's JSON in place (it fits the whole sectors ReadWhole zeroed), and a pack is noted so +// its message entries can be told from GetPackFile. +void Localized(std::string_view path, void *buffer, int *size) { + LocalizeLoaded(path, buffer); + LocalizeFile(path, buffer, dcdata::SectorsFor(static_cast(*size)) * dcdata::kSector, size); +} + } // namespace PC_OVERRIDE void InitCDFile() { @@ -142,6 +165,7 @@ PC_OVERRIDE int LoadFile2(char *path, void *buffer, int *out_size, int mode) { return 0; } int size = ReadWhole(*file, buffer); + Localized(path, buffer, &size); if (out_size) { *out_size = size; } @@ -189,6 +213,7 @@ PC_OVERRIDE int LoadFileBG(char *name, u_long128 *buffer, int *out_size) { return 0; } int size = ReadWhole(*file, buffer); + Localized(name, buffer, &size); std::snprintf(info->name, sizeof info->name, "%s", name); info->busy = true; info->id = 1; @@ -245,3 +270,28 @@ PC_OVERRIDE int WriteFile(char *path, void *buffer, int size) { stream.write(static_cast(buffer), size); return stream ? 1 : 0; } + +// A pack is looked up by the last component of a path, as retail does; a message file in it is +// given as the language's JSON has it. +PC_OVERRIDE u_int *GetPackFile(u_int *pack, char *name, int *out_size) { + if (!pack) { + return 0; + } + std::string_view base = name; + if (std::size_t slash = base.rfind('/'); slash != std::string_view::npos) { + base = base.substr(slash + 1); + } + for (PackEntry *entry = reinterpret_cast(pack); entry->name[0]; + entry = reinterpret_cast(reinterpret_cast(entry) + entry->next)) { + if (SameName(entry->name, base)) { + u_int *data = reinterpret_cast(reinterpret_cast(entry) + entry->offset); + int size = entry->size; + data = const_cast(LocalizePack(pack, base, data, &size)); + if (out_size) { + *out_size = size; + } + return data; + } + } + return 0; +} diff --git a/port/src/gameloop.cpp b/port/src/gameloop.cpp index 045a866f0..56a004eaf 100644 --- a/port/src/gameloop.cpp +++ b/port/src/gameloop.cpp @@ -28,6 +28,7 @@ #include "mglib_port.hpp" #include "nowload.hpp" #include "platform/clock.hpp" +#include "localize.hpp" #include "platform/config.hpp" #include "platform/input.hpp" #include "platform/overlay.hpp" @@ -820,8 +821,18 @@ int RunGame(int argc, char **argv) { MapNo = -1; mode = GAME_MODE_LANGUAGE; GamePad.KeyLock2(1); + if (const int language = ConfigGet().language; language >= LANG_ENGLISH_UK && language <= LANG_SPANISH) { + // game.language is set: start in it without asking, where the language screen would have. + LanguageCode = language; + MapNo = 801; + mode = GAME_MODE_RUSH_MOVIE; + } } if (g_jump.set) { + // A jump skips the language screen, so the language chosen in config.json is the one it starts in. + if (const int language = ConfigGet().language; language >= LANG_ENGLISH_UK && language <= LANG_SPANISH) { + LanguageCode = language; + } ApplyJump(); } @@ -830,6 +841,8 @@ int RunGame(int argc, char **argv) { bool data_loaded = false; bool skip_title = false; for (;;) { + // A language chosen on the Options screen takes hold here, where the next area is about to load. + LocalizeApplyPendingLanguage(); if (mode != GAME_MODE_UNUSED_12 && !data_loaded) { initialize_data(); data_loaded = true; @@ -893,6 +906,15 @@ int RunGame(int argc, char **argv) { result = ModeLoop(skip_title); GameApplyLoopResult(old_main_mode, result); + if (result == 0 && LocalizeLanguagePending() && !MenuOptionOpen() && + (mode == GAME_MODE_EDIT || mode == GAME_MODE_TITLE)) { + // The language was changed in this town or title and the Options screen has closed: load + // the same map again, now in the new language. A dungeon floor is not restarted for it; + // the language takes hold when the next area loads. + NextMapNo = MapNo; + StartEventNo = -1; + result = 1; + } InputSetLookOnLeftStick((mode == GAME_MODE_DUNGEON && gameTask == GAME_TASK_EYE_CAMERA) || (mode == GAME_MODE_EDIT && viewMode != 0 && EdInteriorFlag == 0)); diff --git a/port/src/gametext.cpp b/port/src/gametext.cpp index 5edc04200..d309e6959 100644 --- a/port/src/gametext.cpp +++ b/port/src/gametext.cpp @@ -106,8 +106,180 @@ void AppendUtf8(std::string &out, char32_t ch) { } } -// "{N}" at utf8[at]: N into code and at past the '}'. +// The control codes that are not characters, by the names the text is written with. Codes in a family +// (a colour, a value, a name) are written with their number: "{value3}", "{wait 12}". +struct Named { + s16 code; + const char *name; +}; + +constexpr Named kNamed[] = { + {-0xFD, "page" }, + {-0x400, "/color" }, + {-0x3FF, "white" }, + {-0x3FE, "yellow" }, + {-0x3FD, "cyan" }, + {-0x3FC, "green" }, + {-0x3FB, "dark" }, + {-0x3FA, "gold" }, + {-0x3F9, "grey" }, + {-0x301, "highlight" }, + {-0x401, "value" }, + {-0x300, "select" }, + {-0x2FF, "start" }, + {-0x2FE, "L1" }, + {-0x2FD, "R1" }, + {-0x2FC, "L2" }, + {-0x2FB, "R2" }, + {-0x2FA, "circle" }, + {-0x2F9, "triangle" }, + {-0x2F8, "cross" }, + {-0x2F7, "square" }, + {-0x2F6, "dpad" }, + {-0x2F5, "dpad-updown" }, + {-0x2F4, "dpad-sides" }, + {-0x2F3, "red-slash" }, + {-0x2F2, "heart" }, + {-0x2F1, "note" }, + {-0x2F0, "red-x" }, + {-0x2EF, "yellow-arrow"}, + {-0x2EE, "icon-sword" }, + {-0x2ED, "icon-orb" }, + {-0x2EC, "icon-georama"}, + {-0x2EB, "icon-jar" }, + {-0x2EA, "orange-arrow"}, + {-0x2E9, "bait" }, + {-0x2E8, "word-button" }, + {-0x2E7, "monster" }, + {-0x2E6, "alert" }, + {-0x2E5, "up" }, + {-0x2E4, "right" }, + {-0x2E3, "down" }, + {-0x2E2, "left" }, + {-0x2E1, "hand" }, +}; + +// The families: a name, the lowest code, how many, and where the number starts. A code is written as +// the name and the number (code - first + origin), joined by a space where the name ends in a letter +// that a digit would be read as part of ("wait 12") or directly ("value3"). +struct Family { + const char *name; + int first; + int last; + int origin; + bool spaced; +}; + +constexpr Family kFamilies[] = { + {"wait", -0x200, -0x101, 0, true }, + {"color", -0x3FF, -0x302, 2, true }, + {"gap", -0x700, -0x601, 0, true }, + {"spacing", -0x800, -0x701, 0, true }, + {"justify", -0x900, -0x801, 0, true }, + {"bubble", -0xA00, -0x901, 0, true }, + {"icon", -0x300, -0x2E0, 0, true }, + {"name", -0x506, -0x501, 1, false}, + {"value", -0x40D, -0x406, 1, false}, +}; + +// "insert" runs over two stretches of codes. +constexpr int kInsertFirst[] = {-0x402, -0x40E}; +constexpr int kInsertCount[] = {4, 6}; + +std::string Lower(std::string_view text) { + std::string out(text); + for (char &ch : out) { + if (ch >= 'A' && ch <= 'Z') { + ch = static_cast(ch - 'A' + 'a'); + } + } + return out; +} + +// "12" as a number up to 4 digits, or -1. +int ParseNumber(std::string_view text) { + if (text.empty() || text.size() > 4) { + return -1; + } + int value = 0; + for (char ch : text) { + if (ch < '0' || ch > '9') { + return -1; + } + value = value * 10 + (ch - '0'); + } + return value; +} + +// The name of a control code, or empty where it has none (a character, or a code the game does not use). +std::string ControlName(s16 code) { + for (const Named &named : kNamed) { + if (named.code == code) { + return named.name; + } + } + for (const Family &family : kFamilies) { + if (code >= family.first && code <= family.last) { + // values count upwards in number while their codes run downwards. + const bool downwards = std::string_view(family.name) == "value"; + const int number = downwards ? family.last - code + family.origin : code - family.first + family.origin; + return std::string(family.name) + (family.spaced ? " " : "") + std::to_string(number); + } + } + for (int stretch = 0; stretch < 2; stretch++) { + const int first = kInsertFirst[stretch]; + if (code <= first && code > first - kInsertCount[stretch]) { + return "insert" + std::to_string((stretch == 0 ? 0 : kInsertCount[0]) + (first - code) + 1); + } + } + return {}; +} + +// The code a name stands for; false where it is none. +bool ControlFromName(std::string_view token, s16 &code) { + const std::string name = Lower(token); + for (const Named &named : kNamed) { + if (name == Lower(named.name)) { + code = named.code; + return true; + } + } + for (const Family &family : kFamilies) { + const std::string_view prefix = family.name; + if (name.size() <= prefix.size() || name.compare(0, prefix.size(), prefix) != 0) { + continue; + } + std::string_view rest = std::string_view(name).substr(prefix.size()); + if (family.spaced != (rest.front() == ' ')) { + continue; + } + const int number = ParseNumber(family.spaced ? rest.substr(1) : rest); + const bool downwards = prefix == "value"; + const int value = downwards ? family.last - (number - family.origin) : family.first + (number - family.origin); + if (number >= 0 && value >= family.first && value <= family.last) { + code = static_cast(value); + return true; + } + } + if (name.size() > 6 && name.compare(0, 6, "insert") == 0) { + const int number = ParseNumber(std::string_view(name).substr(6)); + if (number >= 1 && number <= kInsertCount[0] + kInsertCount[1]) { + const int index = number - 1; + code = static_cast(index < kInsertCount[0] ? kInsertFirst[0] - index + : kInsertFirst[1] - (index - kInsertCount[0])); + return true; + } + } + return false; +} + +// "{N}" or "{name}" at utf8[at]: the code into code and at past the '}'. bool ReadEscape(std::string_view utf8, size_t &at, s16 &code) { + if (const size_t close = utf8.find('}', at); close != std::string_view::npos && close - at <= 24 && + ControlFromName(utf8.substr(at + 1, close - at - 1), code)) { + at = close + 1; + return true; + } size_t i = at + 1; bool negative = false; long value = 0; @@ -226,7 +398,8 @@ std::string GameTextDecode(const s16 *codes) { const char32_t ch = GameTextChar(*codes); if (ch == 0) { - text += '{' + std::to_string(*codes) + '}'; + const std::string name = ControlName(*codes); + text += '{' + (name.empty() ? std::to_string(*codes) : name) + '}'; } else if (ch == U'{') { text += "{{"; } else { diff --git a/port/src/gametext.hpp b/port/src/gametext.hpp index fa7f84a7c..0403d8190 100644 --- a/port/src/gametext.hpp +++ b/port/src/gametext.hpp @@ -20,14 +20,15 @@ s16 GameTextCode(char32_t ch); // The character code draws, or 0 where code is not one (a control code, an icon). char32_t GameTextChar(s16 code); -// Appends utf8's codes and MES_CODE_END to out. "{N}" puts the code N (decimal, as the game's -// s16) in as it is, for control codes and icons; "{{" is a '{'. Typographic quotes and dashes +// Appends utf8's codes and MES_CODE_END to out. A control code, one that is not a character, is +// written in braces by its name ("{page}", "{cyan}...{/color}", "{L1}", "{value3}", "{name1}", "{wait 12}", +// "{icon 32}"; docs/LOCALIZATION.md lists them) or by its number, "{-253}", the s16 the game stores. "{{" is a '{'. Typographic quotes and dashes // become the font's own. A character the font lacks, or a byte that is not UTF-8, becomes '?'; // gives back how many did. int GameTextEncode(std::string_view utf8, std::vector &out); -// The text of codes up to MES_CODE_END, as GameTextEncode reads it back: a code that is not a -// character is written as "{N}" and '{' as "{{". +// The text of codes up to MES_CODE_END, as GameTextEncode reads it back: a control code is written by +// its name where it has one, else as "{N}", and '{' as "{{". std::string GameTextDecode(const s16 *codes); // A message file of port text, laid out as ClsMes::SetBuff reads one: the count, then each diff --git a/port/src/localize.cpp b/port/src/localize.cpp new file mode 100644 index 000000000..4ab849b06 --- /dev/null +++ b/port/src/localize.cpp @@ -0,0 +1,900 @@ +// nlohmann/json before the game headers, whose one-letter macros it cannot stand. +#include "localize.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../tools/dcdata/dcdata.hpp" +#include "dataread.hpp" +#include "mainselect.hpp" +#include "menu_option.hpp" +#include "platform/config.hpp" +#include "platform/paths.hpp" + +namespace fs = std::filesystem; + +namespace { + +using Json = nlohmann::ordered_json; + +// The 76-byte entry of dataread.cpp's pack format, for walking a pack to its message files. +constexpr size_t kPackName = 64; +constexpr size_t kPackEntry = kPackName + 12; + +struct State { + std::vector directories; + bool directories_set = false; + int language = -1; + LocalizeTable table; + std::set reported; + std::unordered_map packs; + // Packs hand out pointers that callers keep, so what was built is never freed. + std::map>> built; +}; + +State &Get() { + static State state; + return state; +} + +// Each problem is told once, so a file loaded every frame does not fill the log. +void Report(const std::string &what) { + if (Get().reported.insert(what).second) { + std::fprintf(stderr, "localize: %s\n", what.c_str()); + } +} + +// The file each LanguageCode reads and the export writes, named as Minecraft's language files are +// (language_COUNTRY.json), and the folder its menu packs are in. +constexpr const char *kLanguageFiles[7] = {"ja_jp", "en_us", "en_gb", "fr_fr", "de_de", "it_it", "es_es"}; +constexpr const char *kLanguageDirs[7] = {"a_jpn", "a_usa", "a_eng", "a_fre", "a_ger", "a_ita", "a_spa"}; + +// The file suffix of a language code, or null for a code that is none. +const char *LanguageFile(int code) { + return code >= 0 && code < 7 ? kLanguageFiles[code] : nullptr; +} + +std::vector Directories() { + State &state = Get(); + if (state.directories_set) { + return state.directories; + } + std::vector directories; + fs::path exe = PathsExecutable(); + if (!exe.empty()) { + directories.push_back(exe.parent_path() / "lang"); + } + directories.push_back(PathsSaveRoot() / "lang"); + return directories; +} + +bool ReadText(const fs::path &file, std::string &text) { + std::ifstream stream(file, std::ios::binary); + if (!stream) { + return false; + } + text.assign(std::istreambuf_iterator(stream), std::istreambuf_iterator()); + return true; +} + +// One language's files (.json, the save folder's over the executable's), without patches. +LocalizeTable LoadLanguage(int code) { + LocalizeTable table; + const char *name = LanguageFile(code); + if (name == nullptr) { + return table; + } + for (const fs::path &directory : Directories()) { + fs::path file = directory / (std::string(name) + ".json"); + std::string text; + std::error_code error; + if (!fs::is_regular_file(file, error) || !ReadText(file, text)) { + continue; + } + LocalizeTable read; + std::string why; + if (LocalizeParse(text, read, why)) { + LocalizeMerge(table, read); + } else { + Report(PathsDisplay(file) + ": " + why + "; using retail text"); + } + } + return table; +} + +// The table for the current LanguageCode, read when the code first or newly is asked for. +const LocalizeTable &Table() { + State &state = Get(); + if (state.language == LanguageCode) { + return state.table; + } + state.language = LanguageCode; + state.table = LoadLanguage(LanguageCode); + if (LanguageFile(LanguageCode) == nullptr) { + return state.table; + } + // Patches: every .json in /patches, by name, whatever the language. + for (const fs::path &directory : Directories()) { + std::error_code error; + std::vector names; + for (fs::directory_iterator it(directory / "patches", error), end; !error && it != end; it.increment(error)) { + if (it->path().extension() == ".json") { + names.push_back(it->path()); + } + } + std::sort(names.begin(), names.end()); + for (const fs::path &file : names) { + std::string text; + if (!ReadText(file, text)) { + continue; + } + LocalizeTable read; + std::string why; + if (LocalizeParsePatches(text, read, why)) { + LocalizeMerge(state.table, read); + } else { + Report(PathsDisplay(file) + ": " + why + "; patches in it ignored"); + } + } + } + return state.table; +} + +// DC_LOCALIZE_TRACE=1 names every message file the game loads, as the JSON would key it, with +// whether the table has an entry for it: how to see what a play-through touches. +void Trace(const std::string &key, bool has_entry) { + static const bool on = std::getenv("DC_LOCALIZE_TRACE") != nullptr; + if (on && Get().reported.insert("trace " + key).second) { + std::fprintf(stderr, "localize-trace: %s %s\n", key.c_str(), has_entry ? "json" : "retail"); + } +} + +bool IsPackName(std::string_view path) { + auto ends = [&](std::string_view suffix) { + return path.size() >= suffix.size() && + std::equal(suffix.begin(), suffix.end(), path.end() - suffix.size(), [](char a, char b) { + return a == dcdata::FoldChar(b); + }); + }; + return ends(".pak") || ends(".pac"); +} + +bool IsMessageName(std::string_view path) { + return path.size() >= 4 && std::equal(path.end() - 4, path.end(), ".mes", [](char a, char b) { + return dcdata::FoldChar(a) == b; + }); +} + +std::string FileKey(std::string_view path) { + if (size_t colon = path.find(':'); colon != std::string_view::npos) { + path = path.substr(colon + 1); + } + return dcdata::FoldPath(path); +} + +// A retail message file as the game reads it: s16 words, whose bytes the loader has swapped for +// the host's order already. +struct Message { + int id; + std::vector codes; +}; + +bool ReadRetail(const s16 *file, size_t words, std::vector &out) { + if (words < 2 || file[0] < 0) { + return false; + } + const size_t count = static_cast(file[0]); + if (2 + count * 2 > words) { + return false; + } + const size_t base = 1 + count; + for (size_t i = 0; i < count; i++) { + const int id = file[2 + i * 2]; + const long start = static_cast(base) + file[3 + i * 2]; + if (start < 0 || static_cast(start) >= words) { + continue; + } + Message message{id, {}}; + size_t at = static_cast(start); + while (at < words && file[at] != MES_CODE_END) { + message.codes.push_back(file[at++]); + } + if (at >= words) { + continue; + } + message.codes.push_back(MES_CODE_END); + out.push_back(std::move(message)); + } + return true; +} + +} // namespace + +std::string LocalizeTextHash(std::string_view text) { + std::uint64_t hash = 0xCBF29CE484222325ULL; + for (unsigned char c : text) { + hash ^= c; + hash *= 0x100000001B3ULL; + } + char digits[17]; + std::snprintf(digits, sizeof digits, "%016llx", static_cast(hash)); + return digits; +} + +std::string LocalizeMessageKey(std::string_view path) { + std::string key = FileKey(path); + // The folder a language's menu packs are in, a_fre/ and the like, is not part of the key. + for (const char *dir : kLanguageDirs) { + const std::string folder = std::string(dir) + "/"; + if (key.compare(0, folder.size(), folder) == 0) { + key.erase(0, folder.size()); + } else if (size_t at = key.find("/" + folder); at != std::string::npos) { + key.erase(at + 1, folder.size()); + } + } + if (key.size() >= 4 && key.compare(key.size() - 4, 4, ".mes") == 0) { + key.erase(key.size() - 4); + // The language is the last number of the name: d01_3, steve07_1_4. + if (key.size() >= 2 && key[key.size() - 2] == '_' && key.back() >= '0' && key.back() <= '6') { + key.erase(key.size() - 2); + } + } + std::replace(key.begin(), key.end(), '/', '.'); + return key; +} + +namespace { + +int ParseId(const std::string &key, bool &ok) { + char *end = nullptr; + long id = std::strtol(key.c_str(), &end, 10); + ok = !key.empty() && *end == '\0' && id >= INT16_MIN && id <= INT16_MAX; + return static_cast(id); +} + +bool ParsePatches(const Json &patches, LocalizeTable &read, std::string &error) { + if (!patches.is_array()) { + error = "\"patches\" is not an array"; + return false; + } + int number = 0; + for (const Json &item : patches) { + number++; + const std::string where = "patch " + std::to_string(number); + if (!item.is_object() || !item.contains("file") || !item["file"].is_string() || !item.contains("id")) { + error = where + " needs a \"file\" and an \"id\""; + return false; + } + LocalizePatch patch; + bool ok = false; + if (item["id"].is_number_integer()) { + patch.id = ParseId(std::to_string(item["id"].get()), ok); + } + if (!ok) { + error = where + ": \"id\" is not a message id (an s16)"; + return false; + } + if (item.contains("original")) { + if (!item["original"].is_string()) { + error = where + ": \"original\" is not a string"; + return false; + } + patch.original = item["original"].get(); + } + const bool has_text = item.contains("text"); + const bool has_replace = item.contains("replace"); + if (has_text == has_replace) { + error = where + " needs exactly one of \"text\" and \"replace\""; + return false; + } + if (has_text) { + if (!item["text"].is_string()) { + error = where + ": \"text\" is not a string"; + return false; + } + patch.text = item["text"].get(); + } else { + if (!item["replace"].is_array() || item["replace"].empty()) { + error = where + ": \"replace\" is not a list of [find, with] pairs"; + return false; + } + for (const Json &pair : item["replace"]) { + if (!pair.is_array() || pair.size() != 2 || !pair[0].is_string() || !pair[1].is_string() || + pair[0].get().empty()) { + error = where + ": \"replace\" is not a list of [find, with] pairs"; + return false; + } + patch.replace.emplace_back(pair[0].get(), pair[1].get()); + } + } + // A patch names its file by the path the game loads or by its message key. + const std::string file = item["file"].get(); + read.patches[IsMessageName(file) || file.find_first_of("/\\") != std::string::npos ? LocalizeMessageKey(file) + : file] + .push_back(std::move(patch)); + } + return true; +} + +} // namespace + +bool LocalizeParse(std::string_view json, LocalizeTable &table, std::string &error) { + LocalizeTable read; + try { + Json root = Json::parse(json); + if (!root.is_object()) { + error = "the top level is not an object"; + return false; + } + for (const auto &[key, text] : root.items()) { + if (!text.is_string()) { + error = "\"" + key + "\" is not a string"; + return false; + } + // "dun.script.d01.d01.12" is message 12 of a message file; any other key is a string of the port's. + const size_t dot = key.rfind('.'); + const std::string last = dot == std::string::npos ? key : key.substr(dot + 1); + const bool number = !last.empty() && last.find_first_not_of("-0123456789") == std::string::npos && + last != "-"; + if (!number) { + read.strings[key] = text.get(); + continue; + } + bool ok = false; + const int id = ParseId(last, ok); + if (!ok || dot == 0 || dot == std::string::npos) { + error = "\"" + key + "\" is not a message key (a file key, a dot and an s16 id)"; + return false; + } + read.files[key.substr(0, dot)][id] = text.get(); + } + } catch (const Json::exception &failure) { + error = failure.what(); + return false; + } + table = std::move(read); + return true; +} + +bool LocalizeParsePatches(std::string_view json, LocalizeTable &table, std::string &error) { + LocalizeTable read; + try { + Json root = Json::parse(json); + auto patches = root.is_object() ? root.find("patches") : root.end(); + if (!root.is_object() || patches == root.end()) { + error = "no \"patches\" list"; + return false; + } + if (!ParsePatches(*patches, read, error)) { + return false; + } + } catch (const Json::exception &failure) { + error = failure.what(); + return false; + } + table = std::move(read); + return true; +} + +void LocalizeMerge(LocalizeTable &base, const LocalizeTable &over) { + for (const auto &[file, messages] : over.files) { + for (const auto &[id, text] : messages) { + base.files[file][id] = text; + } + } + for (const auto &[file, patches] : over.patches) { + auto &target = base.patches[file]; + target.insert(target.end(), patches.begin(), patches.end()); + } + for (const auto &[key, text] : over.strings) { + base.strings[key] = text; + } +} + +bool LocalizeReadMessages(const s16 *file, size_t words, std::map &out) { + std::vector messages; + if (!ReadRetail(file, words, messages)) { + return false; + } + for (const Message &message : messages) { + out.emplace(message.id, GameTextDecode(message.codes.data())); + } + return true; +} + +bool LocalizeMessages(const s16 *retail, size_t words, const std::map &texts, + std::vector &out, LocalizeStats &stats) { + std::vector messages; + if (texts.empty() || !ReadRetail(retail, words, messages)) { + return false; + } + // Retail's own texts go in as codes, so none is changed by being read back as text. + std::map first; + for (const Message &message : messages) { + first.emplace(message.id, &message); + } + GameTextFile file; + for (const auto &[id, message] : first) { + std::string text = GameTextDecode(message->codes.data()); + if (file.Set(id, text) < 0) { + return false; + } + } + LocalizeStats made; + for (const auto &[id, text] : texts) { + const int missing = file.Set(id, text); + if (missing < 0) { + made.rejected++; + } else { + made.replaced++; + made.missing += missing; + } + } + stats = made; + if (made.replaced == 0) { + return false; + } + // GameTextFile does not say how long the file is; the last message by offset ends it. + const s16 *data = file.Data(); + const size_t base = 1 + static_cast(data[0]); + size_t end = 2 + static_cast(data[0]) * 2; + for (int i = 0; i < data[0]; i++) { + size_t at = base + static_cast(data[3 + i * 2]); + while (data[at] != MES_CODE_END) { + at++; + } + end = std::max(end, at + 1); + } + out.assign(data, data + end); + return true; +} + +namespace { + +// What the JSON says for one message file: the patches applied to the disc's messages, then the +// "files" texts, which win. A patch that does not apply is told once and the message stays as it was. +std::map Wanted(const LocalizeTable &table, const std::string &key, const s16 *retail, + size_t words) { + std::map texts; + if (auto found = table.files.find(key); found != table.files.end()) { + texts = found->second; + } + auto patches = table.patches.find(key); + if (patches == table.patches.end()) { + return texts; + } + std::map current; + if (!LocalizeReadMessages(retail, words, current)) { + return texts; + } + for (const LocalizePatch &patch : patches->second) { + const std::string name = key + " message " + std::to_string(patch.id); + if (table.files.count(key) != 0 && table.files.at(key).count(patch.id) != 0) { + continue; + } + auto message = current.find(patch.id); + if (message == current.end()) { + Report(name + ": not in the disc's file; patch skipped"); + continue; + } + if (!patch.original.empty() && patch.original != LocalizeTextHash(message->second)) { + Report(name + ": the disc's text is not what the patch was written for; patch skipped"); + continue; + } + std::string text; + if (patch.text) { + text = *patch.text; + } else { + text = message->second; + int applied = 0; + for (const auto &[find, with] : patch.replace) { + size_t at = text.find(find); + if (at == std::string::npos) { + Report(name + ": \"" + find + "\" is not in the disc's text; that replacement skipped"); + continue; + } + applied++; + for (; at != std::string::npos; at = text.find(find, at + with.size())) { + text.replace(at, find.size(), with); + } + } + if (applied == 0) { + continue; + } + } + message->second = text; + texts[patch.id] = text; + } + return texts; +} + +} // namespace + +void LocalizeLoaded(std::string_view path, const void *buffer) { + if (IsPackName(path)) { + Get().packs[buffer] = FileKey(path); + } else { + Get().packs.erase(buffer); + } +} + +bool LocalizeFile(std::string_view path, void *buffer, size_t capacity, int *size) { + if (!IsMessageName(path)) { + return false; + } + const LocalizeTable &table = Table(); + const std::string key = LocalizeMessageKey(path); + const bool known = table.files.count(key) != 0 || table.patches.count(key) != 0; + Trace(key, known); + if (!known || size == nullptr || *size < 4) { + return false; + } + std::vector out; + LocalizeStats stats; + const size_t words = static_cast(*size) / 2; + const auto texts = Wanted(table, key, static_cast(buffer), words); + if (!LocalizeMessages(static_cast(buffer), words, texts, out, stats)) { + if (stats.rejected > 0) { + Report(key + ": " + std::to_string(stats.rejected) + " texts did not fit; retail text kept"); + } + return false; + } + if (out.size() * 2 > capacity) { + Report(key + ": the translated file is " + std::to_string(out.size() * 2) + " bytes, past the " + + std::to_string(capacity) + " the game reads it into; retail text kept"); + return false; + } + if (stats.rejected > 0) { + Report(key + ": " + std::to_string(stats.rejected) + " texts did not fit; retail text kept for them"); + } + if (stats.missing > 0) { + Report(key + ": " + std::to_string(stats.missing) + " characters the game font lacks became '?'"); + } + std::memcpy(buffer, out.data(), out.size() * 2); + *size = static_cast(out.size() * 2); + return true; +} + +const u_int *LocalizePack(const u_int *pack, std::string_view name, const u_int *data, int *size) { + State &state = Get(); + if (data == nullptr || size == nullptr || !IsMessageName(name)) { + return data; + } + auto from = state.packs.find(pack); + if (from == state.packs.end()) { + return data; + } + const LocalizeTable &table = Table(); + const std::string key = LocalizeMessageKey(from->second + "/" + std::string(name)); + const bool known = table.files.count(key) != 0 || table.patches.count(key) != 0; + Trace(key, known); + if (!known || *size < 4) { + return data; + } + // The same pack read again hands out the same text again, as long as the retail bytes and the + // language are the same. + const size_t bytes = static_cast(*size); + std::string tag = key + "#" + std::to_string(LanguageCode) + "#" + + std::to_string(std::hash{}( + std::string_view(reinterpret_cast(data), bytes))); + if (auto cached = state.built.find(tag); cached != state.built.end()) { + *size = static_cast(cached->second->size() * 2); + return reinterpret_cast(cached->second->data()); + } + std::vector out; + LocalizeStats stats; + const auto texts = Wanted(table, key, reinterpret_cast(data), bytes / 2); + if (!LocalizeMessages(reinterpret_cast(data), bytes / 2, texts, out, stats)) { + if (stats.rejected > 0) { + Report(key + ": " + std::to_string(stats.rejected) + " texts did not fit; retail text kept"); + } + return data; + } + if (stats.rejected > 0) { + Report(key + ": " + std::to_string(stats.rejected) + " texts did not fit; retail text kept for them"); + } + if (stats.missing > 0) { + Report(key + ": " + std::to_string(stats.missing) + " characters the game font lacks became '?'"); + } + // The game reads whole quadwords off a message file's end, as it does off the disc's. + out.resize((out.size() + 7) & ~size_t{7}, MES_CODE_END); + auto kept = std::make_unique>(std::move(out)); + auto *result = reinterpret_cast(kept->data()); + *size = static_cast(kept->size() * 2); + state.built[tag] = std::move(kept); + return result; +} + +std::string LocalizeText(std::string_view english_key, std::string_view english) { + const LocalizeTable &table = Table(); + const std::string key(english_key); + if (auto found = table.strings.find(key); found != table.strings.end()) { + return found->second; + } + // A key that ends in a number is read as a message id (options.game.map.choice.0); it is the same entry. + if (size_t dot = key.rfind('.'); dot != std::string::npos) { + bool ok = false; + const int id = ParseId(key.substr(dot + 1), ok); + if (auto file = ok ? table.files.find(key.substr(0, dot)) : table.files.end(); file != table.files.end()) { + if (auto text = file->second.find(id); text != file->second.end()) { + return text->second; + } + } + } + return std::string(english); +} + +std::string LocalizeFormat(std::string text, std::initializer_list arguments) { + int number = 1; + for (const std::string &argument : arguments) { + const std::string placeholder = "%" + std::to_string(number++); + for (size_t at = text.find(placeholder); at != std::string::npos; at = text.find(placeholder, at + argument.size())) { + text.replace(at, placeholder.size(), argument); + } + } + return text; +} + +std::filesystem::path LocalizeFindFile(std::string_view name) { + std::vector directories = Directories(); + for (auto directory = directories.rbegin(); directory != directories.rend(); ++directory) { + fs::path file = *directory / std::string(name); + std::error_code error; + if (fs::is_regular_file(file, error)) { + return file; + } + } + return {}; +} + +namespace { + +// 0: none waiting. +int g_pending_language = 0; + +} // namespace + +void LocalizeConfigChanged(const Config &before, const Config &after) { + if (after.language == before.language) { + return; + } + // "ask" and the language the game is already in leave nothing to switch to. + g_pending_language = after.language >= 2 && after.language <= 6 && after.language != LanguageCode ? after.language : 0; +} + +bool LocalizeLanguagePending() { + return g_pending_language != 0; +} + +void LocalizeApplyPendingLanguage() { + if (g_pending_language == 0) { + return; + } + LanguageCode = g_pending_language; + g_pending_language = 0; + if (SystemMes != nullptr) { + LoadSystemMessage(); + } +} + +void LocalizeSetDirectories(std::vector directories) { + State &state = Get(); + state.directories = std::move(directories); + state.directories_set = true; + state.language = -1; +} + +void LocalizeReset() { + State &state = Get(); + state.language = -1; + state.table = {}; + state.reported.clear(); + state.packs.clear(); +} + +namespace { + +// Which language a message file is for, from where the game keeps it: "_N.mes", "img_N" or an +// "a_xxx" directory. -1 where its path says none. +int FileLanguage(const std::string &key) { + static const std::pair dirs[] = { + {"a_jpn/", 0}, + {"a_usa/", 1}, + {"a_eng/", 2}, + {"a_fre/", 3}, + {"a_ger/", 4}, + {"a_ita/", 5}, + {"a_spa/", 6} + }; + for (const auto &[dir, code] : dirs) { + if (key.find(dir) != std::string::npos) { + return code; + } + } + std::string_view name = key; + if (size_t slash = name.rfind('/'); slash != std::string_view::npos) { + name = name.substr(slash + 1); + } + if (name.size() >= 6 && name[name.size() - 6] == '_' && name[name.size() - 5] >= '0' && + name[name.size() - 5] <= '6' && name.substr(name.size() - 4) == ".mes") { + return name[name.size() - 5] - '0'; + } + return -1; +} + +struct Exported { + std::string key; + std::map messages; + int language; + // The language came from what the text is, not from the path. + bool derived = false; +}; + +// Japanese text is not text but codes from 0 up, which GameTextDecode writes as "{N}"; every other +// language's codes are negative. +bool IsJapanese(const std::map &messages) { + for (const auto &[id, text] : messages) { + for (size_t at = text.find('{'); at != std::string::npos; at = text.find('{', at + 1)) { + if (at + 1 < text.size() && text[at + 1] >= '0' && text[at + 1] <= '9') { + return true; + } + } + } + return false; +} + +} // namespace + +int LocalizeExport(const fs::path &out, bool verify) { + const fs::path &root = PathsDataRoot(); + std::error_code error; + if (!fs::is_directory(root, error)) { + std::fprintf(stderr, "export: %s is not a directory\n", PathsDisplay(root).c_str()); + return -1; + } + std::vector found; + int mismatched = 0; + auto add = [&](const std::string &key, const s16 *words, size_t count) { + Exported item; + item.key = key; + item.language = FileLanguage(key); + if (!LocalizeReadMessages(words, count, item.messages) || item.messages.empty()) { + return; + } + // Japanese text is Japanese wherever the disc keeps it: the menu packs left in commenu/ name no language, + // and two in a_usa/ are the Japanese ones. A file that names Japanese itself still wins a clash. + if (item.language != 0 && IsJapanese(item.messages)) { + item.language = 0; + item.derived = true; + } + if (verify) { + std::map again; + LocalizeStats stats; + std::vector rebuilt; + std::map same = item.messages; + if (LocalizeMessages(words, count, same, rebuilt, stats)) { + std::map back; + if (!LocalizeReadMessages(rebuilt.data(), rebuilt.size(), back) || back != item.messages || + stats.missing != 0 || stats.rejected != 0) { + mismatched++; + std::fprintf(stderr, "export: %s does not read back as it was (missing %d, rejected %d)\n", + key.c_str(), stats.missing, stats.rejected); + } + } else { + mismatched++; + std::fprintf(stderr, "export: %s could not be rebuilt\n", key.c_str()); + } + } + found.push_back(std::move(item)); + }; + + auto options = fs::directory_options::follow_directory_symlink | fs::directory_options::skip_permission_denied; + for (fs::recursive_directory_iterator it(root, options, error), end; !error && it != end; it.increment(error)) { + std::error_code entry_error; + if (!it->is_regular_file(entry_error)) { + continue; + } + const std::string key = dcdata::FoldPath(it->path().lexically_relative(root).generic_string()); + const bool mes = IsMessageName(key); + if (!mes && !IsPackName(key)) { + continue; + } + std::string bytes; + if (!ReadText(it->path(), bytes)) { + continue; + } + if (mes) { + std::vector words(bytes.size() / 2); + std::memcpy(words.data(), bytes.data(), words.size() * 2); + add(key, words.data(), words.size()); + continue; + } + for (size_t at = 0; at + kPackEntry <= bytes.size();) { + const char *entry = bytes.data() + at; + if (entry[0] == 0) { + break; + } + int offset, size, next; + std::memcpy(&offset, entry + kPackName, 4); + std::memcpy(&size, entry + kPackName + 4, 4); + std::memcpy(&next, entry + kPackName + 8, 4); + std::string name(entry, std::find(entry, entry + kPackName, '\0') - entry); + if (offset < 0 || size < 0 || next <= 0 || at + static_cast(offset) + size > bytes.size()) { + break; + } + if (IsMessageName(name)) { + std::vector words(static_cast(size) / 2); + std::memcpy(words.data(), bytes.data() + at + offset, words.size() * 2); + add(key + "/" + dcdata::FoldPath(name), words.data(), words.size()); + } + at += static_cast(next); + } + } + + // One flat file per language, as Minecraft's are (".": text), with the files that name + // no language in every one. Where a file's path names its language it wins over one that does not. + // Two files of one language that come to the same key would be one file to the loader, so that is told. + std::error_code made; + fs::create_directories(out / "hashes", made); + int collisions = 0; + for (int code = 0; code < 7; code++) { + std::map chosen; + std::map> keyed; + auto rank = [](const Exported &item) { return item.language == -1 ? 0 : item.derived ? 1 + : 2; }; + for (const Exported &item : found) { + if (item.language != code && item.language != -1) { + continue; + } + const std::string key = LocalizeMessageKey(item.key); + auto at = chosen.find(key); + if (at == chosen.end() || rank(item) > rank(*at->second)) { + chosen[key] = &item; + } else if (rank(item) == rank(*at->second) && at->second->key != item.key) { + collisions++; + std::fprintf(stderr, "export: %s and %s are both \"%s\"\n", at->second->key.c_str(), + item.key.c_str(), key.c_str()); + } + } + for (const auto &[key, item] : chosen) { + keyed[key] = item->messages; + } + Json texts = Json::object(); + Json hashes = Json::object(); + for (const auto &[key, messages] : keyed) { + for (const auto &[id, text] : messages) { + texts[key + "." + std::to_string(id)] = text; + hashes[key + "." + std::to_string(id)] = LocalizeTextHash(text); + } + } + // The port's own strings (the Options screen's), as this language shows them now: the shipped + // translation if there is one, else the English the code has. + const LocalizeTable language = LoadLanguage(code); + for (const auto &[key, english] : OptionStrings()) { + auto text = language.strings.find(key); + texts[key] = text != language.strings.end() ? text->second : english; + } + std::ofstream(out / (std::string(kLanguageFiles[code]) + ".json"), std::ios::binary | std::ios::trunc) + << texts.dump(4, ' ', false, Json::error_handler_t::replace) << '\n'; + std::ofstream(out / "hashes" / (std::string(kLanguageFiles[code]) + ".json"), std::ios::binary | std::ios::trunc) + << hashes.dump(4, ' ', false, Json::error_handler_t::replace) << '\n'; + } + if (collisions != 0) { + std::fprintf(stderr, "export: %d message files share a key with another\n", collisions); + } + if (verify) { + std::fprintf(stderr, "export: %d of %zu message files do not read back as they were\n", mismatched, + found.size()); + } + return static_cast(found.size()); +} diff --git a/port/src/localize.hpp b/port/src/localize.hpp new file mode 100644 index 000000000..2301e100d --- /dev/null +++ b/port/src/localize.hpp @@ -0,0 +1,123 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gametext.hpp" + +// Game text from JSON the port ships, laid over the retail message files (docs/LOCALIZATION.md). Each +// language is one flat file, as Minecraft's language files are, named for it (en_gb.json, fr_fr.json...) and +// read from /lang/, then /lang/ over it: +// +// {"dun.script.d01.d01.12": "There are three chests here.", ...} +// +// A key is the message file's key (LocalizeMessageKey), a dot, and the message id. The keys are the same in +// every language, so the files can be set side by side. A message the JSON does not have, and a file it +// does not have, stay as retail has them. + +// One edit to a message the disc has (docs/LOCALIZATION.md, "Patches"): either text that takes its +// place whole, or pairs of a piece of the disc's text and what stands for it. The edit applies only +// where the disc's message still has what it expects: every "find" occurs in it, and "original", +// when given, is the LocalizeTextHash of the whole message. +struct LocalizePatch { + int id = 0; + std::string original; + std::optional text; + std::vector> replace; +}; + +struct LocalizeTable { + // Message file key > message id > text. + std::map> files; + // Patches by message file key, in the order they were read; a message a "files" entry has is not patched. + std::map> patches; + // The port's own text, the Options screen's: key (a dotted name that does not end in a message id) > text. + std::map strings; +}; + +// The key of a message file, the same in every language: the path the game loads (or "/" +// for a file in a pack) with the language taken out, the ".mes" taken off and the slashes made dots. +// dun/script/d01/d01_3.mes is "dun.script.d01.d01", and commenu/a_fre/dungeon/dunmenu5.pak/allmenu.mes is +// "commenu.dungeon.dunmenu5.pak.allmenu". +std::string LocalizeMessageKey(std::string_view path); + +// A 64-bit FNV-1a of a message's text as 16 hex digits: what "original" in a patch is checked against, +// and what the export lists in hashes/.json for the person writing one. +std::string LocalizeTextHash(std::string_view text); + +// What a replaced file cost: texts that went in, characters the font lacks (shown as '?'), and +// texts left as retail because they did not fit. +struct LocalizeStats { + int replaced = 0; + int missing = 0; + int rejected = 0; +}; + +// Reads one language's file: a flat object of ".": text for the disc's messages, and +// "": text for the port's own (a key whose last part is not a number). Gives back false with the +// reason in error for text that is not JSON, a number that is not an s16 id, or a value that is not a +// string; table is untouched then. +bool LocalizeParse(std::string_view json, LocalizeTable &table, std::string &error); + +// Reads a patch file, {"patches": [...]}, in the same way. +bool LocalizeParsePatches(std::string_view json, LocalizeTable &table, std::string &error); + +// Table entries of over replace those of base. +void LocalizeMerge(LocalizeTable &base, const LocalizeTable &over); + +// The messages of a retail message file of words s16, as GameTextDecode reads them; the first of +// an id the file repeats, as the game's lookup finds it. False for a file that is not one. +bool LocalizeReadMessages(const s16 *file, size_t words, std::map &out); + +// retail with texts laid over it, as a message file in out. False, leaving out as it was, where +// retail is not a message file or nothing changed. +bool LocalizeMessages(const s16 *retail, size_t words, const std::map &texts, + std::vector &out, LocalizeStats &stats); + +// The text of a string the port draws itself (the Options screen's labels, help and choices): the current +// language's text for key if its JSON has one, else english. A "%1", "%2"... in a text stands for what +// LocalizeFormat is given. +std::string LocalizeText(std::string_view key, std::string_view english); +std::string LocalizeFormat(std::string text, std::initializer_list arguments); + +// Host hooks, called from the data loaders. LocalizeLoaded notes where a pack came from (only +// .pak and .pac are kept). LocalizeFile rewrites a message file just read into buffer, which holds +// capacity bytes, and sets *size; a file that would not fit stays as retail. LocalizePack gives +// back the data of pack entry name as the JSON has it (a buffer that lives on), or data itself. +void LocalizeLoaded(std::string_view path, const void *buffer); +bool LocalizeFile(std::string_view path, void *buffer, size_t capacity, int *size); +const u_int *LocalizePack(const u_int *pack, std::string_view name, const u_int *data, int *size); + +// Writes every message file under the data root as one flat file per language into out (en_gb.json, +// fr_fr.json...; the files that name no language go in every one) and each message's hash into +// out/hashes/ the same way. Gives back how many message files it read, or -1 where the data cannot be +// read; verify counts, on stderr, texts that do not encode back to the retail codes. +int LocalizeExport(const std::filesystem::path &out, bool verify); + +// The file called name in the language folders (the save folder's before the executable's), or an +// empty path where neither has one. +std::filesystem::path LocalizeFindFile(std::string_view name); + +struct Config; + +// A ConfigChangeHook: when game.language is set to a language other than the current one, the switch +// is held until the game can make it whole. A language is not one setting but everything an area loads +// (its message files, menu packs and images), so changing it under a loaded area leaves half of it in +// each; the game applies it between areas, and reloads the town or title it is in once the Options +// screen has closed. +void LocalizeConfigChanged(const Config &before, const Config &after); + +// Whether a language is waiting to be switched to, and the switch: LanguageCode changes and the system +// messages (names, places) are read again. The game loop calls it where a new area is about to load. +bool LocalizeLanguagePending(); +void LocalizeApplyPendingLanguage(); + +// For tests: where the language files are, and forgetting what was read. +void LocalizeSetDirectories(std::vector directories); +void LocalizeReset(); diff --git a/port/src/main.cpp b/port/src/main.cpp index 05b3acdd5..d94efffbf 100644 --- a/port/src/main.cpp +++ b/port/src/main.cpp @@ -17,6 +17,7 @@ #include "gamemode.hpp" #include "gfx/gfx.hpp" #include "langset.hpp" +#include "localize.hpp" #include "menu_option.hpp" #include "menu_save.hpp" #include "mglib.hpp" @@ -57,6 +58,7 @@ struct Options { bool fast_load = false; bool show_fps = false; bool screenshot_fps = false; + const char *export_text = nullptr; }; Options g_options; @@ -81,6 +83,7 @@ Options g_options; " --screenshot-fps with --screenshot and --show-fps: the image as a window shows it, the\n" " FPS counter over it\n" " --display-per-tick N headless: also render N interpolated display frames per tick\n" + " --export-text DIR write the game's message files to DIR as language JSON (docs/LOCALIZATION.md)\n" " --show-fps draw the FPS counter on presented frames when headless too\n" "test hooks:\n" " --jump MODE[:MAP] start in edit:, dungeon:<0-6>, title, rush, opening or menu,\n" @@ -127,6 +130,8 @@ Options ParseOptions(int argc, const char **argv) { options.jump = value(); } else if (arg == "--fast-load") { options.fast_load = true; + } else if (arg == "--export-text") { + options.export_text = value(); } else if (arg == "--show-fps") { options.show_fps = true; } else if (arg == "--screenshot-fps") { @@ -360,6 +365,21 @@ int Run(int argc, const char **argv) { const char *fast_load = std::getenv("DC_FAST_LOAD"); GameSetFastLoad(options.fast_load || (fast_load != nullptr && *fast_load != '\0' && *fast_load != '0')); RequireData(); + if (options.export_text == nullptr) { + // Once per save folder: the extracted data's text as JSON for translators (docs/LOCALIZATION.md). + std::filesystem::path seed = PathsSaveRoot() / "lang-export"; + std::error_code error; + if (!std::filesystem::exists(seed / "en_gb.json", error)) { + std::fprintf(stderr, "exporting the game text to %s\n", PathsDisplay(seed).c_str()); + if (LocalizeExport(seed, false) < 0) { + std::fprintf(stderr, "the game text could not be exported\n"); + } + } + } + if (options.export_text != nullptr) { + // The game's message files as language JSON for translators; no window, no game. + return LocalizeExport(options.export_text, true) >= 0 ? kExitOk : kExitUsage; + } LoadInputScript(options.input); ConfigLoad(); @@ -394,6 +414,7 @@ int Run(int argc, const char **argv) { ConfigAddChangeHook(ApplyConfigChange); ConfigAddChangeHook(DisplayChanged); ConfigAddChangeHook(GameOptionsChanged); + ConfigAddChangeHook(LocalizeConfigChanged); int status = RunGame(argc, const_cast(argv)); if (status == kExitOk && options.screenshot != nullptr) { @@ -401,6 +422,7 @@ int Run(int argc, const char **argv) { } ReportPresentStats(); + ConfigRemoveChangeHook(LocalizeConfigChanged); ConfigRemoveChangeHook(GameOptionsChanged); ConfigRemoveChangeHook(DisplayChanged); ConfigRemoveChangeHook(ApplyConfigChange); diff --git a/port/src/menu_option.hpp b/port/src/menu_option.hpp index 1f0fd6b23..3949727de 100644 --- a/port/src/menu_option.hpp +++ b/port/src/menu_option.hpp @@ -1,6 +1,8 @@ #pragma once #include +#include +#include #include #include "platform/config.hpp" @@ -12,6 +14,11 @@ class CSaveData; // The host keeps the interface at 100% meanwhile: video.ui_scale applies from when it closes. bool MenuOptionOpen(); +// Every string the Options screen draws that is its own, not the game's: key and English, as +// localize.hpp's LocalizeText takes them (options..label, .help, .choice., options.page....). +// The export lists them in every language's file; port/lang/ ships their translations. +std::vector> OptionStrings(); + // Puts the game's options from config.json into the save, where the game reads them (its // configuration words, the dungeon map's status and the menu cursors' reset flag), and sets the // sound's stereo mode. A loaded save brings its own copy, so this runs before every mode starts diff --git a/port/src/platform/config.cpp b/port/src/platform/config.cpp index e57352ad6..40244ad12 100644 --- a/port/src/platform/config.cpp +++ b/port/src/platform/config.cpp @@ -150,6 +150,23 @@ bool ReadGyro(const Json &value, ConfigGyro &out) { return false; } +constexpr const char *kLanguageNames[] = {"ask", "", "english", "francais", "deutsch", "italiano", "espanol"}; + +// "ask" or a language's name, as config.json spells them, becoming LanguageCode 0 or 2 to 6. +bool ReadLanguage(const Json &value, int &out) { + if (!value.is_string()) { + return false; + } + std::string language = Lower(value.get()); + for (std::size_t i = 0; i < std::size(kLanguageNames); ++i) { + if (kLanguageNames[i][0] != '\0' && language == kLanguageNames[i]) { + out = static_cast(i); + return true; + } + } + return false; +} + bool ReadList(const Json &value, std::vector &out) { std::vector items; if (value.is_string()) { @@ -330,6 +347,9 @@ bool Apply(Config &config, std::string_view name, const Json &value) { if (name == "game.debug_mode") { return ReadBool(value, config.debug_mode); } + if (name == "game.language") { + return ReadLanguage(value, config.language); + } if (name == "video.present_mode") { return ReadPresentMode(value, config.present_mode); } @@ -488,6 +508,7 @@ std::string ConfigSerialize(const Config &config) { root["game"]["tick_rate"] = config.tick_rate; root["game"]["debug_mode"] = config.debug_mode; root["game"]["qte_always_win"] = config.qte_always_win; + root["game"]["language"] = kLanguageNames[config.language >= 2 && config.language <= 6 ? config.language : 0]; const ConfigGameOptions &options = config.options; root["game"]["save_cursor_position"] = options.save_cursor_position; root["game"]["message_speed"] = options.fast_messages ? "fast" : "normal"; diff --git a/port/src/platform/config.hpp b/port/src/platform/config.hpp index 17a3daa75..0f7643943 100644 --- a/port/src/platform/config.hpp +++ b/port/src/platform/config.hpp @@ -96,6 +96,9 @@ struct Config { std::vector mouse_release_keys; ConfigGameOptions options; bool discord_rich_presence = true; + // game.language: 0 asks at start-up, as retail does; 2 to 6 is the language to start in + // (LanguageCode: English, Francais, Deutsch, Italiano, Espanol) and skips the language screen. + int language = 0; bool operator==(const Config &) const = default; }; diff --git a/port/src/platform/ttffont.cpp b/port/src/platform/ttffont.cpp new file mode 100644 index 000000000..832c4a075 --- /dev/null +++ b/port/src/platform/ttffont.cpp @@ -0,0 +1,433 @@ +#include "platform/ttffont.hpp" + +#include +#include +#include +#include +#include + +#define STB_TRUETYPE_IMPLEMENTATION +#define STBTT_STATIC +#include "stb_truetype.h" + +#include "platform/paths.hpp" + +namespace ttffont { + +namespace { + +constexpr int kAtlasSize = 2048; +// Space round each bitmap, so linear sampling at a quad's edge reads transparent texels. +constexpr int kPad = 1; + +struct Font { + std::vector data; + stbtt_fontinfo info = {}; + bool loaded = false; +}; + +struct Atlas { + gfx::TextureHandle texture = gfx::kNullTexture; + int x = 0; + int y = 0; + int row_height = 0; + std::map, Glyph> glyphs; + std::map, bool> missing; +}; + +Font g_font; +Atlas g_atlas; + +void ResetAtlas() { + if (g_atlas.texture != gfx::kNullTexture) { + gfx::DestroyTexture(g_atlas.texture); + } + g_atlas = {}; +} + +bool EnsureAtlas() { + if (g_atlas.texture == gfx::kNullTexture) { + gfx::TextureDesc desc; + desc.width = kAtlasSize; + desc.height = kAtlasSize; + desc.format = gfx::TextureFormat::Rgba8; + g_atlas.texture = gfx::CreateTexture(desc); + } + return g_atlas.texture != gfx::kNullTexture; +} + +// ---- Glyph bitmaps, and letters the font lacks built from ones it has ---- + +// Coverage, with its top-left corner (x0, y0) in pixels from the pen on the baseline (y down). +struct Bitmap { + int x0 = 0; + int y0 = 0; + int w = 0; + int h = 0; + std::vector alpha; +}; + +// The glyph index of ch when the font draws something for it. A blank placeholder (a font that maps +// a letter it does not have to an empty outline) counts as missing. +int DrawnGlyph(char32_t ch) { + const int index = stbtt_FindGlyphIndex(&g_font.info, static_cast(ch)); + return index != 0 && !stbtt_IsGlyphEmpty(&g_font.info, index) ? index : 0; +} + +Bitmap Raster(int index, float scale_x, float scale_y) { + Bitmap bitmap; + int x1, y1; + stbtt_GetGlyphBitmapBox(&g_font.info, index, scale_x, scale_y, &bitmap.x0, &bitmap.y0, &x1, &y1); + bitmap.w = x1 - bitmap.x0; + bitmap.h = y1 - bitmap.y0; + if (bitmap.w > 0 && bitmap.h > 0) { + bitmap.alpha.assign(static_cast(bitmap.w) * bitmap.h, 0); + stbtt_MakeGlyphBitmap(&g_font.info, bitmap.alpha.data(), bitmap.w, bitmap.h, bitmap.w, scale_x, scale_y, index); + } + return bitmap; +} + +struct Part { + const Bitmap *bitmap; + int dx; + int dy; +}; + +// The parts laid over each other, each moved by its own offset; the strongest coverage wins. +Bitmap Combine(const std::vector &parts) { + int x0 = INT32_MAX, y0 = INT32_MAX, x1 = INT32_MIN, y1 = INT32_MIN; + for (const Part &part : parts) { + if (part.bitmap->w <= 0 || part.bitmap->h <= 0) { + continue; + } + x0 = std::min(x0, part.bitmap->x0 + part.dx); + y0 = std::min(y0, part.bitmap->y0 + part.dy); + x1 = std::max(x1, part.bitmap->x0 + part.dx + part.bitmap->w); + y1 = std::max(y1, part.bitmap->y0 + part.dy + part.bitmap->h); + } + Bitmap out; + if (x0 >= x1 || y0 >= y1) { + return out; + } + out.x0 = x0; + out.y0 = y0; + out.w = x1 - x0; + out.h = y1 - y0; + out.alpha.assign(static_cast(out.w) * out.h, 0); + for (const Part &part : parts) { + for (int row = 0; row < part.bitmap->h; row++) { + for (int col = 0; col < part.bitmap->w; col++) { + unsigned char &to = out.alpha[static_cast(part.bitmap->y0 + part.dy + row - y0) * out.w + + (part.bitmap->x0 + part.dx + col - x0)]; + to = std::max(to, part.bitmap->alpha[static_cast(row) * part.bitmap->w + col]); + } + } + } + return out; +} + +enum class Mark { + Grave, + Acute, + Circumflex, + Diaeresis, + Tilde, + Cedilla +}; + +// A character the PAL font draws as a letter with a mark on it, or under it. +struct Accented { + char32_t ch; + char32_t base; + Mark mark; +}; + +constexpr Accented kAccented[] = { + {U'À', U'A', Mark::Grave }, + {U'Á', U'A', Mark::Acute }, + {U'Â', U'A', Mark::Circumflex}, + {U'Ä', U'A', Mark::Diaeresis }, + {U'È', U'E', Mark::Grave }, + {U'É', U'E', Mark::Acute }, + {U'Ê', U'E', Mark::Circumflex}, + {U'Ë', U'E', Mark::Diaeresis }, + {U'Ì', U'I', Mark::Grave }, + {U'Í', U'I', Mark::Acute }, + {U'Î', U'I', Mark::Circumflex}, + {U'Ï', U'I', Mark::Diaeresis }, + {U'Ñ', U'N', Mark::Tilde }, + {U'Ò', U'O', Mark::Grave }, + {U'Ó', U'O', Mark::Acute }, + {U'Ô', U'O', Mark::Circumflex}, + {U'Ö', U'O', Mark::Diaeresis }, + {U'Ù', U'U', Mark::Grave }, + {U'Ú', U'U', Mark::Acute }, + {U'Û', U'U', Mark::Circumflex}, + {U'Ü', U'U', Mark::Diaeresis }, + {U'Ç', U'C', Mark::Cedilla }, + {U'à', U'a', Mark::Grave }, + {U'á', U'a', Mark::Acute }, + {U'â', U'a', Mark::Circumflex}, + {U'ä', U'a', Mark::Diaeresis }, + {U'è', U'e', Mark::Grave }, + {U'é', U'e', Mark::Acute }, + {U'ê', U'e', Mark::Circumflex}, + {U'ë', U'e', Mark::Diaeresis }, + {U'ì', U'i', Mark::Grave }, + {U'í', U'i', Mark::Acute }, + {U'î', U'i', Mark::Circumflex}, + {U'ï', U'i', Mark::Diaeresis }, + {U'ñ', U'n', Mark::Tilde }, + {U'ò', U'o', Mark::Grave }, + {U'ó', U'o', Mark::Acute }, + {U'ô', U'o', Mark::Circumflex}, + {U'ö', U'o', Mark::Diaeresis }, + {U'ù', U'u', Mark::Grave }, + {U'ú', U'u', Mark::Acute }, + {U'û', U'u', Mark::Circumflex}, + {U'ü', U'u', Mark::Diaeresis }, + {U'ç', U'c', Mark::Cedilla }, +}; + +// The plain character whose glyph stands for a mark, for the fonts that have no accent characters of +// their own: a tick for the acute, a pair of them for the diaeresis, a comma for the cedilla. +char32_t MarkGlyph(Mark mark) { + switch (mark) { + case Mark::Grave: + return U'`'; + case Mark::Acute: + return U'\''; + case Mark::Circumflex: + return U'^'; + case Mark::Diaeresis: + return U'"'; + case Mark::Tilde: + return U'~'; + case Mark::Cedilla: + return U','; + } + return 0; +} + +// Builds ch from the font's own glyphs when the font draws nothing for it; false for a character this +// does not know how to build (the caller then draws the game's bitmap). scale is pixels per font unit. +bool Compose(char32_t ch, float scale, int em_px, Bitmap &out) { + const int gap = std::max(1, em_px / 25); + + for (const Accented &accented : kAccented) { + if (accented.ch != ch) { + continue; + } + const int base_index = DrawnGlyph(accented.base); + const int mark_index = DrawnGlyph(MarkGlyph(accented.mark)); + if (base_index == 0 || mark_index == 0) { + return false; + } + const bool upper = accented.base >= U'A' && accented.base <= U'Z'; + const float k = accented.mark == Mark::Cedilla ? 0.9f : upper ? 0.7f + : 0.85f; + const Bitmap base = Raster(base_index, scale, scale); + const Bitmap mark = Raster(mark_index, scale * k, scale * k); + if (base.w <= 0 || mark.w <= 0) { + return false; + } + const int dx = (base.x0 + base.w / 2) - (mark.x0 + mark.w / 2); + int dy; + if (accented.mark == Mark::Cedilla) { + // The comma's top just inside the letter's bottom, its tail below the baseline. + dy = (base.y0 + base.h - 2) - mark.y0; + } else { + // The mark's bottom a little above the letter's top (a lower-case i's dot included). + dy = base.y0 - gap - (mark.y0 + mark.h); + } + out = Combine({ + {&base, 0, 0 }, + {&mark, dx, dy} + }); + return out.w > 0; + } + + if (ch == U'¡' || ch == U'¿') { + const int index = DrawnGlyph(ch == U'¡' ? U'!' : U'?'); + if (index == 0) { + return false; + } + out = Raster(index, scale, scale); + // Turned over inside its own box, so it keeps the height and the depth the original had. + for (int row = 0; row < out.h / 2; row++) { + std::swap_ranges(out.alpha.begin() + static_cast(row) * out.w, + out.alpha.begin() + static_cast(row + 1) * out.w, + out.alpha.begin() + static_cast(out.h - 1 - row) * out.w); + } + return out.w > 0; + } + + if (ch == U'œ' || ch == U'Œ') { + const int first = DrawnGlyph(ch == U'œ' ? U'o' : U'O'); + const int second = DrawnGlyph(ch == U'œ' ? U'e' : U'E'); + if (first == 0 || second == 0) { + return false; + } + // Two letters, each squeezed to 65% of its width, the second tucked against the first. + const Bitmap left = Raster(first, scale * 0.65f, scale); + const Bitmap right = Raster(second, scale * 0.65f, scale); + out = Combine({ + {&left, 0, 0}, + {&right, left.x0 + left.w - right.x0 - 1, 0} + }); + return out.w > 0; + } + return false; +} + +} // namespace + +bool Load(const std::filesystem::path &file) { + std::ifstream stream(file, std::ios::binary); + if (!stream) { + std::fprintf(stderr, "font: cannot read %s\n", PathsDisplay(file).c_str()); + return false; + } + Font font; + font.data.assign(std::istreambuf_iterator(stream), std::istreambuf_iterator()); + const unsigned char *bytes = font.data.data(); + int offset = stbtt_GetFontOffsetForIndex(bytes, 0); + if (offset < 0 || !stbtt_InitFont(&font.info, bytes, offset)) { + std::fprintf(stderr, "font: %s is not a TrueType font\n", PathsDisplay(file).c_str()); + return false; + } + font.loaded = true; + // The font's data moves with the vector it is in, so the info is built on the final place. + g_font = std::move(font); + stbtt_InitFont(&g_font.info, g_font.data.data(), stbtt_GetFontOffsetForIndex(g_font.data.data(), 0)); + ResetAtlas(); + return true; +} + +bool Loaded() { + return g_font.loaded; +} + +bool GetGlyph(char32_t ch, int em_px, Glyph &out) { + if (!g_font.loaded || em_px < 4 || em_px > 512) { + return false; + } + const auto key = std::make_pair(ch, em_px); + if (auto found = g_atlas.glyphs.find(key); found != g_atlas.glyphs.end()) { + out = found->second; + return true; + } + if (g_atlas.missing.count(key) != 0) { + return false; + } + if (!EnsureAtlas()) { + g_atlas.missing[key] = true; + return false; + } + + // An em of em_px pixels: stb scales by the font's units per em. + const float scale = stbtt_ScaleForMappingEmToPixels(&g_font.info, static_cast(em_px)); + Bitmap bitmap; + if (const int index = DrawnGlyph(ch); index != 0) { + bitmap = Raster(index, scale, scale); + } else if (!Compose(ch, scale, em_px, bitmap)) { + g_atlas.missing[key] = true; + return false; + } + const int width = bitmap.w; + const int height = bitmap.h; + + if (width > 0 && height > 0) { + if (g_atlas.x + width + 2 * kPad > kAtlasSize) { + g_atlas.x = 0; + g_atlas.y += g_atlas.row_height; + g_atlas.row_height = 0; + } + if (g_atlas.y + height + 2 * kPad > kAtlasSize) { + // Full: start again. Glyphs queued this frame keep their old coordinates, which now + // show whatever lands there; one frame of it, and the next fills the atlas afresh. + std::fprintf(stderr, "font: atlas full, starting again\n"); + gfx::TextureHandle keep = g_atlas.texture; + g_atlas = {}; + g_atlas.texture = keep; + std::vector blank(static_cast(kAtlasSize) * kAtlasSize * 4, 0); + gfx::UpdateTexture(keep, 0, 0, 0, kAtlasSize, kAtlasSize, blank.data()); + } + } + + Glyph glyph = {}; + glyph.x_offset = static_cast(bitmap.x0); + glyph.y_offset = static_cast(bitmap.y0); + glyph.width = static_cast(width); + glyph.height = static_cast(height); + + if (width > 0 && height > 0) { + // White with the coverage as alpha, so the draw's vertex colour tints it. + const int w = width + 2 * kPad; + const int h = height + 2 * kPad; + std::vector pixels(static_cast(w) * h * 4, 0); + for (int row = 0; row < height; row++) { + for (int col = 0; col < width; col++) { + unsigned char *texel = &pixels[(static_cast(row + kPad) * w + col + kPad) * 4]; + texel[0] = texel[1] = texel[2] = 255; + texel[3] = bitmap.alpha[static_cast(row) * width + col]; + } + } + gfx::UpdateTexture(g_atlas.texture, 0, static_cast(g_atlas.x), static_cast(g_atlas.y), + static_cast(w), static_cast(h), pixels.data()); + glyph.u0 = static_cast(g_atlas.x + kPad); + glyph.v0 = static_cast(g_atlas.y + kPad); + glyph.u1 = static_cast(g_atlas.x + kPad + width); + glyph.v1 = static_cast(g_atlas.y + kPad + height); + g_atlas.x += w; + g_atlas.row_height = std::max(g_atlas.row_height, h); + } + + g_atlas.glyphs[key] = glyph; + out = glyph; + return true; +} + +bool RenderGlyph(char32_t ch, int em_px, std::vector &alpha, int &w, int &h, int &x0, int &y0) { + if (!g_font.loaded || em_px < 4 || em_px > 512) { + return false; + } + const float scale = stbtt_ScaleForMappingEmToPixels(&g_font.info, static_cast(em_px)); + Bitmap bitmap; + if (const int index = DrawnGlyph(ch); index != 0) { + bitmap = Raster(index, scale, scale); + } else if (!Compose(ch, scale, em_px, bitmap)) { + return false; + } + alpha = std::move(bitmap.alpha); + w = bitmap.w; + h = bitmap.h; + x0 = bitmap.x0; + y0 = bitmap.y0; + return true; +} + +gfx::TextureBinding Binding() { + gfx::TextureBinding binding; + binding.texture = g_atlas.texture; + binding.filter = gfx::Filter::Linear; + return binding; +} + +Metrics GetMetrics() { + Metrics metrics = {0.8f, 0.2f, 0.6f}; + if (!g_font.loaded) { + return metrics; + } + int ascent, descent, gap; + stbtt_GetFontVMetrics(&g_font.info, &ascent, &descent, &gap); + const float em = 1.0f / stbtt_ScaleForMappingEmToPixels(&g_font.info, 1.0f); + metrics.ascent = static_cast(ascent) / em; + metrics.descent = static_cast(-descent) / em; + int advance, bearing; + stbtt_GetCodepointHMetrics(&g_font.info, '0', &advance, &bearing); + metrics.advance = static_cast(advance) / em; + return metrics; +} + +} // namespace ttffont diff --git a/port/src/platform/ttffont.hpp b/port/src/platform/ttffont.hpp new file mode 100644 index 000000000..99c86fc3c --- /dev/null +++ b/port/src/platform/ttffont.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include +#include + +#include "gfx/gfx.hpp" + +// A TrueType font for the game's message text, drawn at the screen's own resolution instead of +// from the 14x20 bitmaps of gaiji.img. Host side: plain types only. +namespace ttffont { + +// Reads the font file; false (with a message on stderr) for one that is missing or not TrueType. +// Loading again replaces the font. +bool Load(const std::filesystem::path &file); + +bool Loaded(); + +// One glyph in the atlas. u and v are texel coordinates of the bitmap in the atlas; the bitmap is +// width x height pixels, its top-left x_offset pixels right of the pen and y_offset pixels below +// the baseline (negative above it). +struct Glyph { + float u0, v0, u1, v1; + float x_offset, y_offset; + float width, height; +}; + +// The glyph of ch at an em of em_px pixels, rasterized into the atlas when first asked for. False +// where the font has no glyph for ch, so the caller draws what it drew before, or the atlas is full. +bool GetGlyph(char32_t ch, int em_px, Glyph &out); + +// The glyph of ch at an em of em_px pixels as plain coverage (alpha, w x h, 0 to 255) with its top-left +// corner x0, y0 pixels from the pen on the baseline, y down; what GetGlyph puts in the atlas, and what +// the tests look at. False where GetGlyph would say so. +bool RenderGlyph(char32_t ch, int em_px, std::vector &alpha, int &w, int &h, int &x0, int &y0); + +// The atlas, to draw GetGlyph's coordinates from. +gfx::TextureBinding Binding(); + +// The font's own proportions, as fractions of an em. +struct Metrics { + float ascent; + float descent; // positive, below the baseline + float advance; // of the digit 0 +}; + +Metrics GetMetrics(); + +} // namespace ttffont diff --git a/port/src/tests/gametext_test.cpp b/port/src/tests/gametext_test.cpp index 1079db6f7..83a67f2d7 100644 --- a/port/src/tests/gametext_test.cpp +++ b/port/src/tests/gametext_test.cpp @@ -69,7 +69,7 @@ TEST(GameText, ReplacesWhatTheFontLacks) { TEST(GameText, EscapesRoundTrip) { const std::vector codes = {-0x2DF, MES_CODE_NEWLINE, -0x2F0, MES_CODE_PAGE, -0x299, -0x298, -0x2DE, MES_CODE_END}; const std::string text = GameTextDecode(codes.data()); - EXPECT_EQ(text, "A\n{-752}{-253}{{}B"); + EXPECT_EQ(text, "A\n{red-x}{page}{{}B"); EXPECT_EQ(Encode(text), codes); EXPECT_EQ(Encode("{x}"), Encode("{{x}")); } @@ -132,3 +132,48 @@ TEST(GameText, FileLimits) { EXPECT_EQ(GameTextDecode(mes.GetTextLineDataTop(0x7FFF)), "high"); EXPECT_EQ(GameTextDecode(mes.GetTextLineDataTop(0)).size(), 0x7FF6U); } + +// A control code is written by its name where it has one, and by its number where it does not; either way +// the same code comes back. +TEST(GameText, ControlCodesHaveNames) { + EXPECT_EQ(GameTextDecode(Encode("{page}{/color}{cyan}{L1}{R1}{cross}{circle}{square}{triangle}").data()), + "{page}{/color}{cyan}{L1}{R1}{cross}{circle}{square}{triangle}"); + EXPECT_EQ(Encode("{-253}{-1024}{-1021}{-766}{-760}"), Encode("{page}{/color}{cyan}{L1}{cross}")); + EXPECT_EQ(Encode("{value}{value1}{value8}{insert1}{insert10}{name1}{name6}"), + (std::vector{-0x401, -0x406, -0x40D, -0x402, -0x413, -0x506, -0x501, MES_CODE_END})); + EXPECT_EQ(Encode("{wait 12}{gap 8}{spacing 10}{justify 3}{bubble 5}{color 9}{icon 32}"), + (std::vector{-0x200 + 12, -0x700 + 8, -0x800 + 10, -0x900 + 3, -0xA00 + 5, -0x401 + 9, -0x2E0, MES_CODE_END})); + // Names are not case sensitive, and a name the game has no code for is left as the text it is. + EXPECT_EQ(Encode("{PAGE}{l1}"), Encode("{page}{L1}")); + int missing = 0; + EXPECT_EQ(Encode("{nonsense}", &missing).size(), 11U); + EXPECT_EQ(Encode("{value9}{insert11}{name7}{wait 300}", &missing).front(), Encode("{").front()); +} + +TEST(GameText, EveryCodeSurvivesBeingWrittenAndReadBack) { + int named = 0; + for (int code = INT16_MIN; code <= INT16_MAX; code++) { + if (code == MES_CODE_END) { + continue; + } + const std::vector codes = {static_cast(code), MES_CODE_END}; + const std::string text = GameTextDecode(codes.data()); + ASSERT_EQ(Encode(text), codes) << code << " written as " << text; + if (text.size() > 2 && text[0] == '{' && text[1] != '-' && !(text[1] >= '0' && text[1] <= '9') && text[1] != '{') { + named++; + } + } + // Every code the PAL game's text uses, and more, has a name: 33 icons less the one unnamed, the families, + // the colours and the page break. + EXPECT_GT(named, 300); +} + +// No control code the game's text uses is left as a number. +TEST(GameText, TheCodesThePalTextUsesAreAllNamed) { + for (int code : {-0xFD, -0x400, -0x3FF, -0x3FE, -0x3FD, -0x3FC, -0x3FB, -0x3FA, -0x301, -0x401, -0x402, -0x405, -0x406, -0x40D, + -0x506, -0x501, -0x6F8, -0x7F6, -0x2F8, -0x2EE, -0x2E1, -0x1FF}) { + const std::vector codes = {static_cast(code), MES_CODE_END}; + const std::string text = GameTextDecode(codes.data()); + EXPECT_FALSE(text.size() > 2 && (text[1] == '-' || (text[1] >= '0' && text[1] <= '9'))) << code << " is " << text; + } +} diff --git a/port/src/tests/localize_test.cpp b/port/src/tests/localize_test.cpp new file mode 100644 index 000000000..a0718a69c --- /dev/null +++ b/port/src/tests/localize_test.cpp @@ -0,0 +1,712 @@ +// nlohmann/json before the game headers, whose one-letter macros it cannot stand. +#include +#include + +#include +#include +#include +#include +#include + +#include "clsmes.hpp" +#include "data_fixture.hpp" +#include "gametext.hpp" +#include "localize.hpp" +#include "mainselect.hpp" + +namespace { + +namespace fs = std::filesystem; + +// A retail-shaped message file of the given texts, as the loaders hand it over. +std::vector File(const std::map &texts) { + GameTextFile file; + for (const auto &[id, text] : texts) { + EXPECT_EQ(file.Set(id, text), 0); + } + const s16 *data = file.Data(); + // The last message by offset ends the file. + size_t end = 2 + static_cast(data[0]) * 2; + for (int i = 0; i < data[0]; i++) { + size_t at = 1 + static_cast(data[0]) + static_cast(data[3 + i * 2]); + while (data[at] != MES_CODE_END) { + at++; + } + end = std::max(end, at + 1); + } + return std::vector(data, data + end); +} + +std::string Text(std::vector &file, int id) { + ClsMes mes; + mes.SetBuff(file.data()); + const s16 *top = mes.GetTextLineDataTop(id); + return top == nullptr ? "" : GameTextDecode(top); +} + +void Write(const fs::path &path, const std::string &text) { + fs::create_directories(path.parent_path()); + std::ofstream(path, std::ios::binary) << text; +} + +struct LangDir { + fs::path dir = datafix::TempDir("localize"); + + LangDir() { + LocalizeReset(); + LocalizeSetDirectories({dir}); + LanguageCode = 3; + } + + ~LangDir() { + LocalizeReset(); + LocalizeSetDirectories({}); + std::error_code error; + fs::remove_all(dir, error); + } +}; + +// A buffer as LoadFile2 leaves one: the file, then zeros to whole sectors. +struct Loaded { + alignas(64) unsigned char bytes[4096] = {}; + int size = 0; + + explicit Loaded(const std::vector &file) { + size = static_cast(file.size() * 2); + std::memcpy(bytes, file.data(), static_cast(size)); + } +}; + +} // namespace + +TEST(Localize, ParsesTheFormat) { + LocalizeTable table; + std::string error; + ASSERT_TRUE(LocalizeParse(R"({"dun.script.d01.d01.0": "a", "dun.script.d01.d01.-5": "b{{", "meswin.system14.12": "c"})", + table, error)) + << error; + ASSERT_EQ(table.files.count("dun.script.d01.d01"), 1U); + EXPECT_EQ(table.files["dun.script.d01.d01"].at(0), "a"); + EXPECT_EQ(table.files["dun.script.d01.d01"].at(-5), "b{{"); + EXPECT_EQ(table.files["meswin.system14"].at(12), "c"); +} + +TEST(Localize, MessageKeysAreTheSameInEveryLanguage) { + EXPECT_EQ(LocalizeMessageKey("dun/script/d01/d01_3.mes"), "dun.script.d01.d01"); + EXPECT_EQ(LocalizeMessageKey("dun/script/d01/d01_6.mes"), "dun.script.d01.d01"); + EXPECT_EQ(LocalizeMessageKey("cdrom0:\\DUN\\SCRIPT\\D01\\D01_3.MES"), "dun.script.d01.d01"); + EXPECT_EQ(LocalizeMessageKey("dun/message/ww_mes/steve07_1_4.mes"), "dun.message.ww_mes.steve07_1"); + EXPECT_EQ(LocalizeMessageKey("commenu/a_fre/dungeon/dunmenu5.pak/allmenu.mes"), "commenu.dungeon.dunmenu5.pak.allmenu"); + EXPECT_EQ(LocalizeMessageKey("commenu/a_eng/dungeon/dunmenu5.pak/allmenu.mes"), "commenu.dungeon.dunmenu5.pak.allmenu"); + EXPECT_EQ(LocalizeMessageKey("meswin/system14_2.mes"), "meswin.system14"); + // A file that names no language keeps its name. + EXPECT_EQ(LocalizeMessageKey("noda_w/dun00_eng.mes"), "noda_w.dun00_eng"); + EXPECT_EQ(LocalizeMessageKey("commenu/emenu.pak/allmenu.mes"), "commenu.emenu.pak.allmenu"); +} + +TEST(Localize, RejectsBadJson) { + LocalizeTable table; + std::string error; + EXPECT_FALSE(LocalizeParse("{", table, error)); + EXPECT_FALSE(LocalizeParse("[]", table, error)); + EXPECT_FALSE(LocalizeParse(R"({"a.b.40000": "t"})", table, error)); + EXPECT_NE(error.find("not a message key"), std::string::npos); + EXPECT_FALSE(LocalizeParse(R"({".1": "t"})", table, error)); + EXPECT_FALSE(LocalizeParse(R"({"a.b.1": 5})", table, error)); + EXPECT_FALSE(LocalizeParse(R"({"options.x.label": 5})", table, error)); + EXPECT_TRUE(table.files.empty()); + EXPECT_TRUE(table.strings.empty()); +} + +// A key that does not end in a message id is one of the port's own strings. +TEST(Localize, PortStringsAreKeysThatDoNotEndInAnId) { + LocalizeTable table; + std::string error; + ASSERT_TRUE(LocalizeParse(R"({"options.game.map.label": "Carte", "options.game.map.choice.3": "Non", + "dun.script.d01.d01.1": "msg"})", + table, error)) + << error; + EXPECT_EQ(table.strings.at("options.game.map.label"), "Carte"); + EXPECT_EQ(table.strings.count("options.game.map.choice.3"), 0U); + EXPECT_EQ(table.files.at("options.game.map.choice").at(3), "Non"); + EXPECT_EQ(table.files.at("dun.script.d01.d01").at(1), "msg"); +} + +TEST(Localize, TextComesFromTheLanguageJsonOrTheEnglishGiven) { + LangDir lang; + Write(lang.dir / "fr_fr.json", R"({"options.game.map.label": "Carte du donjon", "options.game.map.choice.3": "Non"})"); + EXPECT_EQ(LocalizeText("options.game.map.label", "Dungeon Map"), "Carte du donjon"); + EXPECT_EQ(LocalizeText("options.game.map.choice.3", "Off"), "Non"); + EXPECT_EQ(LocalizeText("options.game.map.choice.2", "3"), "3") << "a key the file lacks gives the English"; + LanguageCode = 4; + EXPECT_EQ(LocalizeText("options.game.map.label", "Dungeon Map"), "Dungeon Map") << "no file for German"; +} + +TEST(Localize, FormatFillsThePlaceholders) { + EXPECT_EQ(LocalizeFormat("a %1 x %2\nwindow.", {"1280", "720"}), "a 1280 x 720\nwindow."); + EXPECT_EQ(LocalizeFormat("%2 then %1, %1 again", {"x", "y"}), "y then x, x again"); + EXPECT_EQ(LocalizeFormat("100%", {"x"}), "100%"); +} + +TEST(Localize, ReplacesById) { + std::vector retail = File({ + {1, "one" }, + {2, "two" }, + {3, "three"} + }); + std::vector out; + LocalizeStats stats; + ASSERT_TRUE(LocalizeMessages(retail.data(), retail.size(), { + {2, "deux {-752}" }, + {9, "neuf\nlignes"} + }, + out, stats)); + EXPECT_EQ(stats.replaced, 2); + EXPECT_EQ(stats.missing, 0); + EXPECT_EQ(stats.rejected, 0); + EXPECT_EQ(Text(out, 1), "one"); + EXPECT_EQ(Text(out, 2), "deux {red-x}"); + EXPECT_EQ(Text(out, 3), "three"); + EXPECT_EQ(Text(out, 9), "neuf\nlignes"); + EXPECT_EQ(Text(out, 4), ""); +} + +TEST(Localize, CountsWhatTheFontLacks) { + std::vector retail = File({ + {1, "one"} + }); + std::vector out; + LocalizeStats stats; + ASSERT_TRUE(LocalizeMessages(retail.data(), retail.size(), { + {1, "a~b;"} + }, + out, stats)); + EXPECT_EQ(stats.missing, 2); + EXPECT_EQ(Text(out, 1), "a?b?"); +} + +TEST(Localize, KeepsRetailWhereATextDoesNotFit) { + std::vector retail = File({ + {1, "one"}, + {2, "two"} + }); + std::vector out; + LocalizeStats stats; + // The first is past what a message file's s16 offsets reach, the last past its s16 ids. + ASSERT_TRUE(LocalizeMessages(retail.data(), retail.size(), + { + {1, std::string(0x8000, 'A')}, + {2, "deux"}, + {40000, "no"} + }, + out, stats)); + EXPECT_EQ(stats.rejected, 2); + EXPECT_EQ(stats.replaced, 1); + EXPECT_EQ(Text(out, 1), "one"); + EXPECT_EQ(Text(out, 2), "deux"); + + EXPECT_FALSE(LocalizeMessages(retail.data(), retail.size(), { + {1, std::string(0x8000, 'A')} + }, + out, stats)); + EXPECT_EQ(stats.rejected, 1); +} + +TEST(Localize, RetailRoundTrips) { + std::vector retail = File({ + {1, "Norune Village" }, + {2, "{cyan}x\n{/color}"}, + {3, "Tani\xC3\xA8re" } + }); + std::map read; + ASSERT_TRUE(LocalizeReadMessages(retail.data(), retail.size(), read)); + EXPECT_EQ(read, (std::map{ + {1, "Norune Village" }, + {2, "{cyan}x\n{/color}"}, + {3, "Tani\xC3\xA8re" } + })); + + std::vector out; + LocalizeStats stats; + ASSERT_TRUE(LocalizeMessages(retail.data(), retail.size(), read, out, stats)); + EXPECT_EQ(out, retail); + + s16 junk[2] = {-1, 0}; + EXPECT_FALSE(LocalizeReadMessages(junk, 2, read)); +} + +// A message that never ends (as the last id of the PAL allmenu.mes does) is left out, not fatal. +TEST(Localize, SkipsAMessageWithoutAnEnd) { + std::vector retail = File({ + {1, "one"}, + {2, "two"} + }); + retail.pop_back(); + std::map read; + ASSERT_TRUE(LocalizeReadMessages(retail.data(), retail.size(), read)); + EXPECT_EQ(read.size(), 1U); +} + +TEST(Localize, LoadedFileComesFromTheLanguageJson) { + LangDir lang; + std::vector retail = File({ + {1, "one"}, + {2, "two"} + }); + Write(lang.dir / "fr_fr.json", R"({"dun.script.d01.d01.2": "deux"})"); + + Loaded loaded(retail); + int size = loaded.size; + EXPECT_TRUE(LocalizeFile("cdrom0:\\DUN\\SCRIPT\\D01\\D01_3.MES", loaded.bytes, 2048, &size)); + std::vector got(reinterpret_cast(loaded.bytes), reinterpret_cast(loaded.bytes) + size / 2); + EXPECT_EQ(Text(got, 1), "one"); + EXPECT_EQ(Text(got, 2), "deux"); + + // Another file, and a name that is no message file, stay as they were. + Loaded other(retail); + size = other.size; + EXPECT_FALSE(LocalizeFile("dun/script/d02/d02_3.mes", other.bytes, 2048, &size)); + EXPECT_FALSE(LocalizeFile("dun/script/d01/d01_3.bin", other.bytes, 2048, &size)); + EXPECT_EQ(size, other.size); + + // A language with no file of its own has nothing to say. + LanguageCode = 4; + EXPECT_FALSE(LocalizeFile("dun/script/d01/d01_4.mes", other.bytes, 2048, &size)); + EXPECT_EQ(size, other.size); +} + +TEST(Localize, TheSaveFoldersFileLaysOverTheExecutables) { + LangDir lang; + fs::path second = lang.dir / "second"; + LocalizeSetDirectories({lang.dir, second}); + std::vector retail = File({ + {1, "one" }, + {2, "two" }, + {3, "three"} + }); + Write(lang.dir / "fr_fr.json", R"({"a.1": "exe", "a.2": "exe"})"); + Write(second / "fr_fr.json", R"({"a.2": "save"})"); + + Loaded loaded(retail); + int size = loaded.size; + ASSERT_TRUE(LocalizeFile("a_3.mes", loaded.bytes, 2048, &size)); + std::vector got(reinterpret_cast(loaded.bytes), reinterpret_cast(loaded.bytes) + size / 2); + EXPECT_EQ(Text(got, 1), "exe"); + EXPECT_EQ(Text(got, 2), "save"); + EXPECT_EQ(Text(got, 3), "three"); +} + +TEST(Localize, BadJsonFallsBackToRetail) { + LangDir lang; + std::vector retail = File({ + {1, "one"} + }); + Write(lang.dir / "fr_fr.json", "{ not json"); + + Loaded loaded(retail); + int size = loaded.size; + testing::internal::CaptureStderr(); + EXPECT_FALSE(LocalizeFile("a.mes", loaded.bytes, 2048, &size)); + EXPECT_FALSE(LocalizeFile("a.mes", loaded.bytes, 2048, &size)); + std::string log = testing::internal::GetCapturedStderr(); + EXPECT_NE(log.find("fr_fr.json"), std::string::npos); + // Told once, not on every load. + EXPECT_EQ(log.find("fr_fr.json", log.find("fr_fr.json") + 1), std::string::npos); + EXPECT_EQ(size, loaded.size); +} + +TEST(Localize, AFileThatWouldNotFitStaysRetail) { + LangDir lang; + std::vector retail = File({ + {1, "one"} + }); + Write(lang.dir / "fr_fr.json", R"({"a.1": "a text far longer than the file it replaces"})"); + + Loaded loaded(retail); + int size = loaded.size; + testing::internal::CaptureStderr(); + EXPECT_FALSE(LocalizeFile("a.mes", loaded.bytes, static_cast(size) + 4, &size)); + EXPECT_NE(testing::internal::GetCapturedStderr().find("retail text kept"), std::string::npos); + EXPECT_EQ(size, loaded.size); + EXPECT_TRUE(LocalizeFile("a.mes", loaded.bytes, 2048, &size)); + EXPECT_GT(size, loaded.size); +} + +TEST(Localize, PackEntryComesFromTheJson) { + LangDir lang; + std::vector retail = File({ + {1, "one"}, + {2, "two"} + }); + Write(lang.dir / "fr_fr.json", R"({"commenu.emenu.pak.allmenu.1": "un"})"); + + alignas(64) static unsigned char pack[4096]; + std::memcpy(pack, retail.data(), retail.size() * 2); + int size = static_cast(retail.size() * 2); + const auto *data = reinterpret_cast(pack); + + // A pack nobody said where it came from is left alone. + EXPECT_EQ(LocalizePack(reinterpret_cast(pack), "allmenu.mes", data, &size), data); + + LocalizeLoaded("cdrom0:/COMMENU/A_FRE/EMENU.PAK;1", pack); + LocalizeLoaded("commenu/a_fre/emenu.pak", pack); + int replaced = size; + const u_int *got = LocalizePack(reinterpret_cast(pack), "allmenu.mes", data, &replaced); + ASSERT_NE(got, data); + std::vector words(reinterpret_cast(got), reinterpret_cast(got) + replaced / 2); + EXPECT_EQ(Text(words, 1), "un"); + EXPECT_EQ(Text(words, 2), "two"); + + // The same entry read again is the same text, at the same place. + int again = size; + EXPECT_EQ(LocalizePack(reinterpret_cast(pack), "allmenu.mes", data, &again), got); + EXPECT_EQ(again, replaced); + + // An entry that is not a message file, and a buffer another file has taken, are not touched. + int other = size; + EXPECT_EQ(LocalizePack(reinterpret_cast(pack), "option.img", data, &other), data); + LocalizeLoaded("commenu/a_fre/other.img", pack); + EXPECT_EQ(LocalizePack(reinterpret_cast(pack), "allmenu.mes", data, &other), data); +} + +// ---- Patches ---- + +namespace { + +std::vector Patched(LangDir &lang, const std::vector &retail, const std::string &patches, + const std::string &files = "") { + Write(lang.dir / "patches" / "a.json", patches); + if (!files.empty()) { + Write(lang.dir / "fr_fr.json", files); + } + Loaded loaded(retail); + int size = loaded.size; + if (!LocalizeFile("a.mes", loaded.bytes, 2048, &size)) { + return {}; + } + return std::vector(reinterpret_cast(loaded.bytes), reinterpret_cast(loaded.bytes) + size / 2); +} + +} // namespace + +TEST(Localize, ParsesPatches) { + LocalizeTable table; + std::string error; + ASSERT_TRUE(LocalizeParsePatches(R"({"patches": [ + {"file": "A.MES", "id": 3, "replace": [["teh", "the"], ["recieve", "receive"]]}, + {"file": "a.mes", "id": 4, "original": "0123456789abcdef", "text": "new"}]})", + table, error)) + << error; + ASSERT_EQ(table.patches["a"].size(), 2U); + EXPECT_EQ(table.patches["a"][0].replace.size(), 2U); + EXPECT_EQ(table.patches["a"][1].original, "0123456789abcdef"); + EXPECT_EQ(*table.patches["a"][1].text, "new"); + + for (const char *bad : {R"({"patches": 1})", R"({"patches": [{"id": 1, "text": "x"}]})", + R"({"patches": [{"file": "a", "id": 1}]})", + R"({"patches": [{"file": "a", "id": 1, "text": "x", "replace": [["a", "b"]]}]})", + R"({"patches": [{"file": "a", "id": 1, "replace": [["", "b"]]}]})", + R"({"patches": [{"file": "a", "id": 40000, "text": "x"}]})"}) { + EXPECT_FALSE(LocalizeParsePatches(bad, table, error)) << bad; + } +} + +TEST(Localize, TextHashIsStable) { + EXPECT_EQ(LocalizeTextHash(""), "cbf29ce484222325"); + EXPECT_EQ(LocalizeTextHash("a"), "af63dc4c8601ec8c"); + EXPECT_NE(LocalizeTextHash("one"), LocalizeTextHash("One")); +} + +TEST(Localize, ReplacePatchEditsTheDiscsText) { + LangDir lang; + std::vector retail = File({ + {1, "one" }, + {2, "teh cat and teh dog"}, + {3, "three" } + }); + std::vector got = Patched(lang, retail, R"({"patches": [{"file": "a.mes", "id": 2, "replace": [["teh", "the"]]}]})"); + ASSERT_FALSE(got.empty()); + EXPECT_EQ(Text(got, 1), "one"); + EXPECT_EQ(Text(got, 2), "the cat and the dog"); + EXPECT_EQ(Text(got, 3), "three"); +} + +TEST(Localize, APatchThatDoesNotMatchIsSkippedAndTold) { + LangDir lang; + std::vector retail = File({ + {1, "one"}, + {2, "two"} + }); + testing::internal::CaptureStderr(); + std::vector got = Patched(lang, retail, R"({"patches": [ + {"file": "a.mes", "id": 2, "replace": [["zzz", "y"]]}, + {"file": "a.mes", "id": 9, "text": "nine"}, + {"file": "a.mes", "id": 1, "original": "0000000000000000", "text": "uno"}]})"); + std::string log = testing::internal::GetCapturedStderr(); + EXPECT_TRUE(got.empty()); + EXPECT_NE(log.find("\"zzz\" is not in the disc's text"), std::string::npos); + EXPECT_NE(log.find("message 9: not in the disc's file"), std::string::npos); + EXPECT_NE(log.find("message 1: the disc's text is not what the patch was written for"), std::string::npos); +} + +TEST(Localize, TextPatchWithTheRightHashApplies) { + LangDir lang; + std::vector retail = File({ + {1, "one"}, + {2, "two"} + }); + std::vector got = Patched(lang, retail, + R"({"patches": [{"file": "a.mes", "id": 1, "original": ")" + LocalizeTextHash("one") + + R"(", "text": "uno"}]})"); + ASSERT_FALSE(got.empty()); + EXPECT_EQ(Text(got, 1), "uno"); + EXPECT_EQ(Text(got, 2), "two"); +} + +TEST(Localize, PatchesChainAndAFilesTextWins) { + LangDir lang; + std::vector retail = File({ + {1, "aa"}, + {2, "bb"} + }); + std::vector got = Patched(lang, retail, + R"({"patches": [{"file": "a.mes", "id": 1, "replace": [["a", "b"]]}, + {"file": "a.mes", "id": 1, "replace": [["bb", "c"]]}, + {"file": "a.mes", "id": 2, "replace": [["b", "x"]]}]})", + R"({"a.2": "mine"})"); + ASSERT_FALSE(got.empty()); + EXPECT_EQ(Text(got, 1), "c"); + EXPECT_EQ(Text(got, 2), "mine"); +} + +// ---- game.language ---- + +#include "platform/config.hpp" + +TEST(LocalizeLanguage, ConfigReadsAndWritesTheLanguage) { + EXPECT_EQ(ConfigParse("").language, 0); + EXPECT_EQ(ConfigParse(R"({"game": {"language": "francais"}})").language, 3); + EXPECT_EQ(ConfigParse(R"({"game": {"language": "Espanol"}})").language, 6); + EXPECT_EQ(ConfigParse(R"({"game": {"language": "english"}})").language, 2); + EXPECT_EQ(ConfigParse(R"({"game": {"language": "ask"}})").language, 0); + + testing::internal::CaptureStderr(); + EXPECT_EQ(ConfigParse(R"({"game": {"language": "klingon"}})").language, 0); + EXPECT_EQ(ConfigParse(R"({"game": {"language": 3}})").language, 0); + testing::internal::GetCapturedStderr(); + + Config config; + config.language = 4; + EXPECT_EQ(ConfigParse(ConfigSerialize(config)).language, 4); + EXPECT_FALSE(ConfigAppliesOnRestart("game.language")); +} + +// ---- the language files and the export ---- + +#include "platform/paths.hpp" + +namespace { + +void WriteMes(const fs::path &path, const std::vector &file) { + fs::create_directories(path.parent_path()); + std::ofstream(path, std::ios::binary).write(reinterpret_cast(file.data()), static_cast(file.size() * 2)); +} + +} // namespace + +TEST(LocalizeLanguages, ExportWritesOneFlatFilePerLanguageWithTheSameKeys) { + fs::path root = datafix::TempDir("langexport"); + WriteMes(root / "data/dun/script/d01/d01_2.mes", File({ + {1, "Hello"}, + {2, "Bye" } + })); + WriteMes(root / "data/dun/script/d01/d01_3.mes", File({ + {1, "Bonjour" }, + {2, "Au revoir \xC3\xA0 la Cr\xC3\xA8me"} + })); + WriteMes(root / "data/commenu/a_ger/dunmenu.pak.mes", File({ + {5, "Gegenstand"} + })); + WriteMes(root / "data/noda_w/dun00_eng.mes", File({ + {9, "Only one"} + })); + PathsSetDataRoot(root / "data"); + + ASSERT_GE(LocalizeExport(root / "out", true), 4); + auto load = [&](const char *name) { + std::ifstream stream(root / "out" / name); + return nlohmann::ordered_json::parse(stream); + }; + const auto en = load("en_gb.json"); + const auto fr = load("fr_fr.json"); + const auto de = load("de_de.json"); + // Flat ".": text, the same keys for the same message in every language. + EXPECT_EQ(en["dun.script.d01.d01.1"], "Hello"); + EXPECT_EQ(fr["dun.script.d01.d01.1"], "Bonjour"); + EXPECT_EQ(fr["dun.script.d01.d01.2"], "Au revoir \xC3\xA0 la Cr\xC3\xA8me"); + EXPECT_EQ(de["commenu.dunmenu.pak.5"], "Gegenstand"); + EXPECT_FALSE(en.contains("commenu.dunmenu.pak.5")); + EXPECT_TRUE(en.contains("dun.script.d01.d01.2")); + // A file that names no language is in every language's file. + for (const auto *language : {&en, &fr, &de}) { + EXPECT_EQ((*language)["noda_w.dun00_eng.9"], "Only one"); + } + EXPECT_TRUE(fs::exists(root / "out" / "hashes" / "fr_fr.json")); + EXPECT_EQ(load("hashes/fr_fr.json")["dun.script.d01.d01.1"], LocalizeTextHash("Bonjour")); + + // The file is UTF-8 as it stands, with no \u escapes standing in for letters. + std::ifstream raw(root / "out" / "fr_fr.json", std::ios::binary); + std::string bytes((std::istreambuf_iterator(raw)), std::istreambuf_iterator()); + EXPECT_NE(bytes.find("Cr\xC3\xA8me"), std::string::npos); + EXPECT_EQ(bytes.find("\\u00"), std::string::npos); + raw.close(); + std::error_code removed; + fs::remove_all(root, removed); +} + +// The PAL disc keeps Japanese text in two of its a_usa packs; it is Japanese, so it goes in ja_jp.json and en_us.json +// holds only English. +TEST(LocalizeLanguages, JapaneseTextGoesToTheJapaneseFileWhereverItIsKept) { + fs::path root = datafix::TempDir("langjp"); + WriteMes(root / "data/commenu/a_usa/pack.pak.mes", File({{1, "{11}{41}{82}"}})); + WriteMes(root / "data/commenu/a_usa/other.pak.mes", File({{1, "English"}})); + WriteMes(root / "data/commenu/a_jpn/pack.pak.mes", File({{1, "{11}{41}{82}"}})); + PathsSetDataRoot(root / "data"); + + testing::internal::CaptureStderr(); + LocalizeExport(root / "out", false); + const std::string log = testing::internal::GetCapturedStderr(); + EXPECT_EQ(log.find("share a key"), std::string::npos) << log; + auto load = [&](const char *name) { + std::ifstream stream(root / "out" / name); + return nlohmann::ordered_json::parse(stream); + }; + const auto us = load("en_us.json"); + const auto jp = load("ja_jp.json"); + EXPECT_FALSE(us.contains("commenu.pack.pak.1")); + EXPECT_EQ(us["commenu.other.pak.1"], "English"); + EXPECT_EQ(jp["commenu.pack.pak.1"], "{11}{41}{82}"); + std::error_code removed; + fs::remove_all(root, removed); +} + +TEST(LocalizeLanguages, ExportSaysWhenTwoFilesShareAKey) { + fs::path root = datafix::TempDir("langcollide"); + WriteMes(root / "data/x/m_3.mes", File({ + {1, "one"} + })); + WriteMes(root / "data/x/a_fre/m.mes", File({ + {1, "uno"} + })); + PathsSetDataRoot(root / "data"); + + testing::internal::CaptureStderr(); + LocalizeExport(root / "out", false); + EXPECT_NE(testing::internal::GetCapturedStderr().find("share a key"), std::string::npos); + std::error_code removed; + fs::remove_all(root, removed); +} + +TEST(LocalizeLanguages, ChangingTheLanguageSettingWaitsForTheGameToSwitch) { + LanguageCode = 2; + Config before; + Config after; + after.language = 5; + LocalizeConfigChanged(before, after); + EXPECT_TRUE(LocalizeLanguagePending()); + EXPECT_EQ(LanguageCode, 2) << "nothing changes under a loaded area"; + LocalizeApplyPendingLanguage(); + EXPECT_EQ(LanguageCode, 5); + EXPECT_FALSE(LocalizeLanguagePending()); + + // Changing back before the game switched cancels it; "ask", the current language and a setting + // that did not change leave nothing waiting. + before = after; + after.language = 3; + LocalizeConfigChanged(before, after); + EXPECT_TRUE(LocalizeLanguagePending()); + before = after; + after.language = 5; + LocalizeConfigChanged(before, after); + EXPECT_FALSE(LocalizeLanguagePending()); + before = after; + after.language = 0; + LocalizeConfigChanged(before, after); + EXPECT_FALSE(LocalizeLanguagePending()); + LocalizeApplyPendingLanguage(); + EXPECT_EQ(LanguageCode, 5); +} + +// ---- the Options screen's own text ---- + +#include "menu_option.hpp" + +// port/lang/ ships the Options screen's strings in each language: the same keys as the code has, nothing +// else, and the same placeholders and pad glyph codes as the English. +TEST(LocalizeOptions, ShippedTranslationsHaveExactlyTheCodesKeys) { + const fs::path lang = fs::path(__FILE__).parent_path().parent_path().parent_path() / "lang"; + ASSERT_TRUE(fs::is_directory(lang)) << lang; + + std::map english; + for (const auto &[key, text] : OptionStrings()) { + EXPECT_TRUE(english.emplace(key, text).second) << "two strings called " << key; + } + EXPECT_GT(english.size(), 100U); + + // What a translation must keep as the English has it: every {code} and every %N. + auto tokens = [](const std::string &text) { + std::string out; + for (size_t at = 0; at < text.size(); at++) { + if (text[at] == '%' && at + 1 < text.size() && isdigit(static_cast(text[at + 1]))) { + out += text.substr(at, 2); + } else if (text[at] == '{' && text.find('}', at) != std::string::npos) { + out += text.substr(at, text.find('}', at) - at + 1); + } + } + return out; + }; + + int files = 0; + for (const char *name : {"fr_fr", "de_de", "it_it", "es_es"}) { + std::ifstream stream(lang / (std::string(name) + ".json")); + ASSERT_TRUE(stream) << name; + const auto shipped = nlohmann::ordered_json::parse(stream); + files++; + for (const auto &[key, text] : english) { + ASSERT_TRUE(shipped.contains(key)) << name << " lacks " << key; + const std::string translated = shipped[key]; + EXPECT_FALSE(translated.empty()) << name << " " << key; + EXPECT_EQ(tokens(translated), tokens(text)) << name << " " << key << ": placeholders and pad glyphs differ"; + } + for (const auto &item : shipped.items()) { + EXPECT_TRUE(english.count(item.key()) != 0) << name << " has " << item.key() << " that the code does not"; + } + } + EXPECT_EQ(files, 4); +} + +TEST(LocalizeOptions, TheExportListsTheOptionStringsWithTheShippedText) { + fs::path root = datafix::TempDir("langopt"); + WriteMes(root / "data/dun/script/d01/d01_3.mes", File({ + {1, "Bonjour"} + })); + PathsSetDataRoot(root / "data"); + const fs::path lang = fs::path(__FILE__).parent_path().parent_path().parent_path() / "lang"; + LocalizeReset(); + LocalizeSetDirectories({lang}); + + ASSERT_GE(LocalizeExport(root / "out", false), 1); + auto load = [&](const char *name) { + std::ifstream stream(root / "out" / name); + return nlohmann::ordered_json::parse(stream); + }; + const auto en = load("en_gb.json"); + const auto fr = load("fr_fr.json"); + EXPECT_EQ(en["options.game.map.label"], "Dungeon Map"); + EXPECT_EQ(fr["options.game.map.label"], "Carte du donjon"); + EXPECT_EQ(fr["dun.script.d01.d01.1"], "Bonjour"); + LocalizeSetDirectories({}); + LocalizeReset(); + std::error_code removed; + fs::remove_all(root, removed); +} diff --git a/port/src/tests/ttffont_test.cpp b/port/src/tests/ttffont_test.cpp new file mode 100644 index 000000000..243db74e6 --- /dev/null +++ b/port/src/tests/ttffont_test.cpp @@ -0,0 +1,98 @@ +#include + +#include +#include +#include +#include + +#include "platform/ttffont.hpp" + +// The tests that need a font read one from DC_TEST_FONT (a TrueType file); without it they skip, as no +// font is shipped with the port. DC_TEST_FONT_DUMP=1 also prints each glyph as text. + +namespace { + +struct Rendered { + std::vector alpha; + int w = 0; + int h = 0; + int x0 = 0; + int y0 = 0; +}; + +bool LoadTestFont() { + const char *path = std::getenv("DC_TEST_FONT"); + return path != nullptr && *path != '\0' && ttffont::Load(path); +} + +Rendered Render(char32_t ch, int em_px = 40) { + Rendered out; + EXPECT_TRUE(ttffont::RenderGlyph(ch, em_px, out.alpha, out.w, out.h, out.x0, out.y0)) << static_cast(ch); + if (std::getenv("DC_TEST_FONT_DUMP") != nullptr) { + std::printf("U+%04X %dx%d at (%d,%d)\n", static_cast(ch), out.w, out.h, out.x0, out.y0); + for (int row = 0; row < out.h; row++) { + for (int col = 0; col < out.w; col++) { + const unsigned char a = out.alpha[static_cast(row) * out.w + col]; + std::putchar(a > 160 ? '#' : a > 64 ? '+' + : ' '); + } + std::putchar('\n'); + } + } + return out; +} + +} // namespace + +// An accented letter is its base with a mark above it, in the same place across the letters. +TEST(TtfFont, AccentedLettersAreBuiltFromTheFontsOwnGlyphs) { + if (!LoadTestFont()) { + GTEST_SKIP() << "DC_TEST_FONT is not set"; + } + const Rendered e = Render(U'e'); + for (char32_t accented : {U'é', U'è', U'ê', U'ë'}) { + const Rendered r = Render(accented); + EXPECT_GT(r.w, 0); + EXPECT_LT(r.y0, e.y0) << "the mark stands above the letter"; + EXPECT_EQ(r.y0 + r.h, e.y0 + e.h) << "the letter keeps its bottom"; + EXPECT_NEAR(r.x0 + r.w / 2.0, e.x0 + e.w / 2.0, 2.0) << "the mark is centred on the letter"; + } + const Rendered c = Render(U'c'); + const Rendered cedilla = Render(U'ç'); + EXPECT_GT(cedilla.y0 + cedilla.h, c.y0 + c.h) << "the cedilla hangs below the baseline"; + EXPECT_EQ(cedilla.y0, c.y0); + + const Rendered upper = Render(U'É'); + const Rendered plain_upper = Render(U'E'); + EXPECT_LT(upper.y0, plain_upper.y0); +} + +TEST(TtfFont, InvertedMarksAndLigaturesAreBuilt) { + if (!LoadTestFont()) { + GTEST_SKIP() << "DC_TEST_FONT is not set"; + } + const Rendered bang = Render(U'!'); + const Rendered inverted = Render(U'¡'); + EXPECT_EQ(inverted.w, bang.w); + EXPECT_EQ(inverted.h, bang.h); + // Turned over: what was at the top is now at the bottom. + EXPECT_EQ(inverted.alpha.front(), bang.alpha[bang.alpha.size() - static_cast(bang.w)]); + + const Rendered oe = Render(U'œ'); + const Rendered o = Render(U'o'); + EXPECT_GT(oe.w, o.w); + EXPECT_LT(oe.w, 2 * o.w); + Render(U'Œ'); + Render(U'Ñ'); + Render(U'ñ'); + Render(U'ä'); +} + +TEST(TtfFont, WhatItCannotBuildIsLeftToTheBitmap) { + if (!LoadTestFont()) { + GTEST_SKIP() << "DC_TEST_FONT is not set"; + } + Rendered out; + EXPECT_FALSE(ttffont::RenderGlyph(U'ß', 40, out.alpha, out.w, out.h, out.x0, out.y0)); + EXPECT_FALSE(ttffont::RenderGlyph(U'~' + 0x1000, 40, out.alpha, out.w, out.h, out.x0, out.y0)); +} diff --git a/scripts/port/compare_text.py b/scripts/port/compare_text.py new file mode 100644 index 000000000..ff3cbbfb5 --- /dev/null +++ b/scripts/port/compare_text.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +"""Compares the game's text across languages, message by message. + +Reads the language files that `darkcloud --export-text` writes (by default /lang-export, see +docs/LOCALIZATION.md): en_gb.json, fr_fr.json, de_de.json, it_it.json, es_es.json. Each is a flat +object of ".": text, with the same keys in every language, so the +languages are matched key to key. Writes one CSV per language into (default: compare/ beside the +export), with British English as the reference: + + key, english, text, flag + +Flags: + missing English has the message, this language's file does not + extra this language has a message English does not + same the text is the English text (often an untranslated line; skip it for names and numbers) + empty this language's text is empty where English's is not + long the text is more than twice, or less than half, the English length (a window may not fit it) + +The export is the disc's own text: keep what this writes out of git, as the export is. + + python scripts/port/compare_text.py [] +""" + +import csv +import json +import re +import sys +from pathlib import Path + +REFERENCE = "en_gb" +LANGUAGES = ["fr_fr", "de_de", "it_it", "es_es"] + + +def load(path): + with open(path, encoding="utf-8") as stream: + return json.load(stream) + + +def informative(text): + """False for a text that is the same in every language: only codes, digits and punctuation.""" + stripped = re.sub(r"\{-?\d+\}", "", text) + return re.search(r"[A-Za-z]{3,}", stripped) is not None + + +def sort_key(key): + """Keys by message file, then message id as a number.""" + file, _, id_ = key.rpartition(".") + return (file, int(id_) if id_.lstrip("-").isdigit() else 0) + + +def main(argv): + if len(argv) < 2: + print(__doc__) + return 2 + export = Path(argv[1]) + out = Path(argv[2]) if len(argv) > 2 else export.parent / "compare" + out.mkdir(parents=True, exist_ok=True) + english = load(export / (REFERENCE + ".json")) + lines = ["Messages against English (%s.json), by language" % REFERENCE, ""] + for name in LANGUAGES: + other = load(export / (name + ".json")) + counts = {"missing": 0, "extra": 0, "same": 0, "empty": 0, "long": 0} + with open(out / (name + ".csv"), "w", encoding="utf-8", newline="") as stream: + writer = csv.writer(stream) + writer.writerow(["key", "english", "text", "flag"]) + for key in sorted(set(english) | set(other), key=sort_key): + en = english.get(key) + text = other.get(key) + flag = "" + if en is not None and text is None: + flag = "missing" + elif en is None: + flag = "extra" + elif en and not text: + flag = "empty" + elif text == en and informative(en): + flag = "same" + elif len(en) > 20 and not (0.5 <= len(text) / len(en) <= 2.0): + flag = "long" + if flag: + counts[flag] += 1 + writer.writerow([key, en or "", text or "", flag]) + lines.append("%s: %d messages, %s" % (name, len(other), ", ".join("%d %s" % (n, k) for k, n in counts.items()))) + (out / "summary.txt").write_text("\n".join(lines) + "\n", encoding="utf-8") + print("\n".join(lines)) + print("written to", out) + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) From 4b26dd566e4a848d0517bcd545e6308b399cb4de Mon Sep 17 00:00:00 2001 From: Jeff Date: Wed, 7 Oct 2026 06:32:47 -0500 Subject: [PATCH 03/20] Localization: TrueType text keeps the bitmaps' outline and shadow The outline and drop shadow were drawn at 60% of the bitmaps' offsets and faintly, so they vanished under a thin glyph. They now sit at the full offsets and the outline is laid down nine times, which reads as dark as the bitmaps' text. --- port/src/clsmes.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/port/src/clsmes.cpp b/port/src/clsmes.cpp index ef5338d02..49409fd46 100644 --- a/port/src/clsmes.cpp +++ b/port/src/clsmes.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -189,11 +190,15 @@ bool QueueTtfGlyph(ClsMes &mes, int index, int dark, int offset_x, int offset_y, int r, g, b, alpha; GlyphTint(line.clut, dark, wide, r, g, b, alpha); - // The bitmaps' outlines were soft; a crisp glyph's reads thick at the same offsets, so they are - // brought in to 60%. + // The outline and the drop shadow sit at the offsets the bitmaps' use: the shadow is part of the + // look (it lifts the text off the picture), and at a fraction of the offset it vanishes under the glyph. const int cap = mes.edge_alpha; - auto edge = [&](float dx, float dy, int er, int eg, int eb, int ea) { - TtfQuad(edges, glyph, x + dx * 0.6f, y + dy * 0.6f * rows, width, height, er, eg, eb, std::min(cap, ea)); + // A thin stroke's outline covers little of a pixel, so it is laid down nine times to read as dark as the bitmaps'. + constexpr int passes = 9; + auto edge = [&](float dx, float dy, int er, int eg, int eb, int ea) { + for (int pass = 0; pass < passes; ++pass) { + TtfQuad(edges, glyph, x + dx, y + dy * rows, width, height, er, eg, eb, std::min(cap, ea)); + } }; switch (mes.style) { case MES_EDGE_WHITE: edge(1, 1, 0xFF, 0xFF, 0xFF, 0x40); break; From cbc74990d9e7876b612217266aec1fedfd6c16c6 Mon Sep 17 00:00:00 2001 From: Jeff Date: Wed, 7 Oct 2026 06:54:46 -0500 Subject: [PATCH 04/20] wip: Options text through the modular options/ screen --- port/src/options/rows.cpp | 82 ++++++++++++++++++++++++++++++++- port/src/options/rows.hpp | 8 ++++ port/src/options/screen.cpp | 91 +++++++++++++++++++++++++++++-------- 3 files changed, 161 insertions(+), 20 deletions(-) diff --git a/port/src/options/rows.cpp b/port/src/options/rows.cpp index 2ce0b545a..bbb7c2f85 100644 --- a/port/src/options/rows.cpp +++ b/port/src/options/rows.cpp @@ -8,6 +8,7 @@ #include #include +#include "localize.hpp" #include "menu_option.hpp" #include "platform/window.hpp" @@ -466,6 +467,19 @@ void RestoreGyroSensitivity(Config &config, const Config &defaults) { config.gyro_sensitivity = defaults.gyro_sensitivity; } +// Ask, then the five languages of the language screen (LanguageCode 2 to 6). +int LanguageChoice(const Config &config) { + return config.language >= 2 && config.language <= 6 ? config.language - 1 : 0; +} + +void SetLanguage(Config &config, int choice) { + config.language = choice == 0 ? 0 : choice + 1; +} + +int LanguageCount(const Config &) { + return 6; +} + const Row kGameRows[] = { GameRow<&ConfigGameOptions::save_cursor_position, true>("game.save_cursor_position", "Save Cursor Position", "On|Off", 0x15E), @@ -557,6 +571,13 @@ const Row kControlRows[] = { const Row kAccessibilityRows[] = { OnOffRow<&Config::qte_always_win>("game.qte_always_win", "Always Win QTEs", "\"Always Win QTEs\"\nButton prompts always\nend in a perfect."), + Row{.key = "game.language", + .label = "Language", + .help = "\"Language\"\nThe language of the game;\nAsk shows the language\nscreen at start-up.", + .count = LanguageCount, + .get = LanguageChoice, + .set = SetLanguage, + .names = "Ask|English|Francais|Deutsch|Italiano|Espanol"}, }; } // namespace @@ -572,8 +593,67 @@ std::span Pages() { return pages; } +// The Options screen's own text is looked up by key, in the language the game is in (localize.hpp); the +// English here is what it shows where the language has none. A setting's strings are options..label, .help and .choice., and a page's is options.page.. +namespace { + +// A value a row works out itself ("Desktop", "Unlimited") by its English: options..value.. +std::string ValueText(const Row &row, const std::string &english) { + return LocalizeText("options." + std::string(row.key) + ".value." + english, english); +} + +std::string ChoiceText(const Row &row, int choice) { + return LocalizeText("options." + std::string(row.key) + ".choice." + std::to_string(choice), + ChoiceName(row.names, choice)); +} + +} // namespace + +std::string PageKey(const char *name) { + std::string key = "options.page."; + for (const char *c = name; *c != '\0'; ++c) { + key += static_cast(*c >= 'A' && *c <= 'Z' ? *c - 'A' + 'a' : *c); + } + return key; +} + +std::vector> RowStrings() { + std::vector> out; + // Rows may share a key (the key-binding rows share one help); the same text twice is listed once, and two + // texts under one key are both kept for the tests to find. + auto add = [&](std::string key, std::string english) { + for (const auto &[have_key, have_english] : out) { + if (have_key == key && have_english == english) { + return; + } + } + out.emplace_back(std::move(key), std::move(english)); + }; + for (const Page &page : Pages()) { + add(PageKey(page.name), page.name); + add(PageKey(page.name) + ".help", page.help); + for (const Row &row : page.rows) { + const std::string prefix = "options." + std::string(row.key); + add(prefix + ".label", row.label); + if (row.help != nullptr) { + add(prefix + ".help", row.help); + } + if (row.names != nullptr) { + const int count = row.count(ConfigGet()); + for (int choice = 0; choice < count; ++choice) { + add(prefix + ".choice." + std::to_string(choice), ChoiceName(row.names, choice)); + } + } + } + } + add("options.video.width.value.Desktop", "Desktop"); + add("options.video.max_fps.value.Unlimited", "Unlimited"); + return out; +} + std::string RowValue(const Row &row, const Config &config) { - std::string value = row.text != nullptr ? row.text(config) : ChoiceName(row.names, row.get(config)); + std::string value = row.text != nullptr ? ValueText(row, row.text(config)) : ChoiceText(row, row.get(config)); if (ConfigAppliesOnRestart(row.key)) { value += " *"; } diff --git a/port/src/options/rows.hpp b/port/src/options/rows.hpp index e5eb70163..7c41d5929 100644 --- a/port/src/options/rows.hpp +++ b/port/src/options/rows.hpp @@ -2,6 +2,8 @@ #include #include +#include +#include #include "platform/config.hpp" @@ -28,6 +30,12 @@ struct Page { std::span Pages(); +// The key of a page's name: options.page.; its help is that key with .help. +std::string PageKey(const char *name); + +// Every string the settings rows draw that is their own: key and English, as LocalizeText takes them. +std::vector> RowStrings(); + // The value a setting row shows, with " *" when it applies at the next start. std::string RowValue(const Row &row, const Config &config); diff --git a/port/src/options/screen.cpp b/port/src/options/screen.cpp index 4a6230a78..d807f38fd 100644 --- a/port/src/options/screen.cpp +++ b/port/src/options/screen.cpp @@ -2,12 +2,15 @@ #include #include +#include #include #include "clsmes.hpp" #include "dataread.hpp" #include "gamepad.hpp" #include "gameutil.hpp" +#include "localize.hpp" +#include "mainselect.hpp" #include "memcard.hpp" #include "menu_draw.hpp" #include "menuetc.hpp" @@ -18,12 +21,12 @@ #include "sound.hpp" #include "texture.hpp" -#define PAD_GLYPH_L1 "{-766}" -#define PAD_GLYPH_R1 "{-765}" -#define PAD_GLYPH_CIRCLE "{-762}" -#define PAD_GLYPH_TRIANGLE "{-761}" -#define PAD_GLYPH_CROSS "{-760}" -#define PAD_GLYPH_SQUARE "{-759}" +#define PAD_GLYPH_L1 "{L1}" +#define PAD_GLYPH_R1 "{R1}" +#define PAD_GLYPH_CIRCLE "{circle}" +#define PAD_GLYPH_TRIANGLE "{triangle}" +#define PAD_GLYPH_CROSS "{cross}" +#define PAD_GLYPH_SQUARE "{square}" namespace options { @@ -45,6 +48,18 @@ constexpr int kExitHelp = 999; constexpr int kPageHelp = 900; constexpr int kRowHelp = 1000; +// The screen's fixed texts in English, with the keys their translations have. +constexpr const char *kShortcutsText = PAD_GLYPH_SQUARE " Reset page\n" PAD_GLYPH_TRIANGLE " Undo changes\n" PAD_GLYPH_CIRCLE + " Close"; +constexpr const char *kSaveHelpText = "Could not save.\nChanges apply now.\nClose to retry writing\nconfig.json."; +constexpr const char *kDisplayHelpText = "The display could not\nchange, and kept the\nmode it had. Try\nanother mode or size."; +constexpr const char *kTurnPageText = PAD_GLYPH_L1 " " PAD_GLYPH_R1 " turn the page."; +constexpr const char *kExitHelpText = PAD_GLYPH_CROSS " Close\n" PAD_GLYPH_SQUARE " This page's defaults\n" PAD_GLYPH_TRIANGLE + " Undo every change\n" PAD_GLYPH_CIRCLE " Close from any row"; +constexpr const char *kDisplayNowText = "The display could not\nchange as asked. It is\nnow %1"; +constexpr const char *kFullscreenNowText = "fullscreen."; +constexpr const char *kWindowNowText = "a %1 x %2\nwindow."; + int HelpIndex(int page, int row) { int index = row; for (int p = 0; p < page; ++p) { @@ -126,10 +141,13 @@ void ShowHelp() { message = kDisplayHelp; } else if (DisplayGetWarning() == DisplayWarning::Changed) { WindowMode mode = DisplayShownMode(); - std::string now = mode.fullscreen ? "fullscreen." : std::format("a {} x {}\nwindow.", mode.width, mode.height); + std::string now = mode.fullscreen ? LocalizeText("options.display.fullscreen_now", kFullscreenNowText) + : LocalizeFormat(LocalizeText("options.display.window_now", kWindowNowText), + {std::to_string(mode.width), std::to_string(mode.height)}); if (now != g_screen.display_now) { g_screen.display_now = now; - GetTexts().help.Set(kDisplayNowHelp, "The display could not\nchange as asked. It is\nnow " + now); + GetTexts().help.Set(kDisplayNowHelp, + LocalizeFormat(LocalizeText("options.help.display_now", kDisplayNowText), {now})); buffer = GetTexts().help.Data(); mes.mes_made = -1; } @@ -387,32 +405,49 @@ Texts::Texts() { right.Set(">"); l1.Set(PAD_GLYPH_L1); r1.Set(PAD_GLYPH_R1); - shortcuts.Set(PAD_GLYPH_SQUARE " Reset page\n" PAD_GLYPH_TRIANGLE " Undo changes\n" PAD_GLYPH_CIRCLE " Close"); - help.Set(kSaveHelp, "Could not save.\nChanges apply now.\nClose to retry writing\nconfig.json."); - help.Set(kDisplayHelp, "The display could not\nchange, and kept the\nmode it had. Try\nanother mode or size."); - help.Set(kExitHelp, PAD_GLYPH_CROSS " Close\n" PAD_GLYPH_SQUARE " This page's defaults\n" PAD_GLYPH_TRIANGLE - " Undo every change\n" PAD_GLYPH_CIRCLE " Close from any row"); + shortcuts.Set(LocalizeText("options.shortcuts", kShortcutsText)); + help.Set(kSaveHelp, LocalizeText("options.help.save_failed", kSaveHelpText)); + help.Set(kDisplayHelp, LocalizeText("options.help.display_kept", kDisplayHelpText)); + help.Set(kExitHelp, LocalizeText("options.help.exit", kExitHelpText)); int index = 0; for (int p = 0; p < static_cast(Pages().size()); ++p) { const Page &page = Pages()[p]; - tabs.emplace_back().Set(page.name); - help.Set(kPageHelp + p, std::string(page.help) + "\n" PAD_GLYPH_L1 " " PAD_GLYPH_R1 " turn the page."); + tabs.emplace_back().Set(LocalizeText(PageKey(page.name), page.name)); + help.Set(kPageHelp + p, LocalizeText(PageKey(page.name) + ".help", page.help) + "\n" + + LocalizeText("options.help.turn_page", kTurnPageText)); labels.emplace_back(); values.emplace_back(); for (const Row &row : page.rows) { - labels.back().emplace_back().Set(row.label); + labels.back().emplace_back().Set(LocalizeText("options." + std::string(row.key) + ".label", row.label)); values.back().emplace_back(); if (row.help != nullptr) { - help.Set(kRowHelp + index, row.help); + help.Set(kRowHelp + index, LocalizeText("options." + std::string(row.key) + ".help", row.help)); } ++index; } } } +namespace { + +std::unique_ptr g_texts; +int g_texts_language = -1; + +} // namespace + Texts &GetTexts() { - static Texts texts; - return texts; + if (!g_texts) { + g_texts = std::make_unique(); + g_texts_language = LanguageCode; + } + return *g_texts; +} + +// The screen's text is laid out for a language; opening the screen in another lays it out again. +void RefreshTextsForLanguage() { + if (g_texts && g_texts_language != LanguageCode) { + g_texts.reset(); + } } const Page &CurrentPage() { @@ -472,6 +507,7 @@ int Open(int mode, int block_no, u_long128 *buffer) { return 0; } + RefreshTextsForLanguage(); g_screen = Screen{}; g_screen.open = true; g_screen.mode = mode; @@ -571,3 +607,20 @@ int Run() { } } // namespace options + +std::vector> OptionStrings() { + std::vector> out = { + {"options.shortcuts", options::kShortcutsText }, + {"options.help.save_failed", options::kSaveHelpText }, + {"options.help.display_kept", options::kDisplayHelpText }, + {"options.help.turn_page", options::kTurnPageText }, + {"options.help.exit", options::kExitHelpText }, + {"options.help.display_now", options::kDisplayNowText }, + {"options.display.fullscreen_now", options::kFullscreenNowText}, + {"options.display.window_now", options::kWindowNowText }, + }; + for (auto &entry : options::RowStrings()) { + out.push_back(std::move(entry)); + } + return out; +} From 5f7b5f46106a709784cad51f4dea6bcaed114a29 Mon Sep 17 00:00:00 2001 From: Jeff Date: Wed, 7 Oct 2026 07:02:59 -0500 Subject: [PATCH 05/20] wip: cast shadow taps --- port/src/clsmes.cpp | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/port/src/clsmes.cpp b/port/src/clsmes.cpp index 49409fd46..ef0262ed6 100644 --- a/port/src/clsmes.cpp +++ b/port/src/clsmes.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -209,10 +210,39 @@ bool QueueTtfGlyph(ClsMes &mes, int index, int dark, int offset_x, int offset_y, FuchiTbl_E[i].g, FuchiTbl_E[i].b, FuchiTbl_E[i].alpha); } break; - case MES_EDGE_DOUBLE: - edge(1, 1, 0x40, 0x40, 0x40, 0x80); - edge(2, 2, 0, 0, 0, 0x80); + case MES_EDGE_DOUBLE: { + // Retail's shadow is cast down and to the right and falls off softly, because its bitmaps are soft. + // A crisp glyph stacked at two offsets reads as a hard outline instead, so the shadow is laid in + // as taps at half-pixel steps along the light's direction, which the texture filter blends. + static const int mode = [] { + const char *text = std::getenv("DC_TTF_SHADOW"); + return text != nullptr ? std::atoi(text) : 1; + }(); + struct Tap { + float dx, dy; + int grey, alpha; + }; + static const Tap soft[] = { + {0.8f, 0.8f, 0x40, 0x70}, {1.4f, 1.4f, 0x10, 0x78}, {2.0f, 2.0f, 0, 0x78}, {2.6f, 2.6f, 0, 0x60}, + {3.2f, 3.2f, 0, 0x40}, {1.6f, 0.8f, 0, 0x40}, {0.8f, 1.6f, 0, 0x40}, {2.4f, 1.6f, 0, 0x30}, + {1.6f, 2.4f, 0, 0x30}, + }; + static const Tap deep[] = { + {0.8f, 0.8f, 0x40, 0x78}, {1.4f, 1.4f, 0x10, 0x78}, {2.0f, 2.0f, 0, 0x78}, {2.6f, 2.6f, 0, 0x78}, + {3.2f, 3.2f, 0, 0x60}, {3.8f, 3.8f, 0, 0x40}, {1.6f, 0.8f, 0, 0x50}, {0.8f, 1.6f, 0, 0x50}, + {2.4f, 1.6f, 0, 0x48}, {1.6f, 2.4f, 0, 0x48}, {3.2f, 2.4f, 0, 0x30}, {2.4f, 3.2f, 0, 0x30}, + }; + if (mode == 0) { + edge(1, 1, 0x40, 0x40, 0x40, 0x80); + edge(2, 2, 0, 0, 0, 0x80); + } else { + for (const Tap &tap : mode == 1 ? std::span(soft) : std::span(deep)) { + TtfQuad(edges, glyph, x + tap.dx, y + tap.dy * rows, width, height, tap.grey, tap.grey, tap.grey, + std::min(cap, tap.alpha)); + } + } break; + } } TtfQuad(fills, glyph, x, y, width, height, r, g, b, std::min(cap, alpha)); return true; From 6b1dd60663890c91b88e47d24924b21fcc1084d8 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 07:04:49 -0500 Subject: [PATCH 06/20] lang: add keys for new settings, drop stale tab help --- port/lang/de_de.json | 20 ++++++++++++++++++-- port/lang/es_es.json | 20 ++++++++++++++++++-- port/lang/fr_fr.json | 20 ++++++++++++++++++-- port/lang/it_it.json | 20 ++++++++++++++++++-- 4 files changed, 72 insertions(+), 8 deletions(-) diff --git a/port/lang/de_de.json b/port/lang/de_de.json index 3896493ad..da67c31b3 100644 --- a/port/lang/de_de.json +++ b/port/lang/de_de.json @@ -2,7 +2,6 @@ "options.shortcuts": "{square} Standard\n{triangle} Verwerfen\n{circle} Verlassen", "options.help.save_failed": "Speichern misslang.\nÄnderungen gelten.\nVerlassen: erneut\nconfig.json schreiben.", "options.help.display_kept": "Die Anzeige konnte\nnicht wechseln und\nbleibt im alten Modus.\nAnderen Modus wählen.", - "options.help.tabs": "\"Optionen\"\n{L1} {R1} oder links/\nrechts: Seite wechseln.", "options.help.exit": "{cross} Verlassen\n{square} Seitenstandard\n{triangle} Alles verwerfen\n{circle} Verlassen (Zeile)", "options.help.display_now": "Die Anzeige konnte\nnicht wechseln. Sie ist\njetzt %1", "options.display.fullscreen_now": "Vollbild.", @@ -138,5 +137,22 @@ "options.input.mouse_zoom.choice.0": "An", "options.input.mouse_zoom.choice.1": "Aus", "options.input.bindings.zoom_reset.label": "Zoom zurücksetzen", - "options.input.bindings.zoom_reset.help": "\"Zoom zurücksetzen\"\nStellt normalen\nKameraabstand her." + "options.input.bindings.zoom_reset.help": "\"Zoom zurücksetzen\"\nStellt normalen\nKameraabstand her.", + "options.help.turn_page": "{L1} {R1} blättern.", + "options.page.game.help": "\"Spiel\"\nWie das Spiel abläuft.", + "options.page.display.help": "\"Anzeige\"\nFenster und Bild.", + "options.page.audio.help": "\"Audio\"\nTon und Musik.", + "options.page.controls.help": "\"Steuerung\"\nMaus, Gamepad und Gyro.", + "options.page.accessibility": "Bedienhilfen", + "options.page.accessibility.help": "\"Bedienhilfen\"\nHilfe bei den schwereren\nStellen des Spiels.", + "options.audio.soundtrack.label": "Soundtrack", + "options.audio.soundtrack.help": "\"Soundtrack\"\nPS2: die Musik des Spiels.\nEigen: Ihre eigenen\nAufnahmen, ab dem\nnächsten Lied.", + "options.audio.soundtrack.choice.0": "PS2", + "options.audio.soundtrack.choice.1": "Eigen", + "options.audio.surround.label": "Surround", + "options.audio.surround.help": "\"Surround\"\nVerteilt den Ton auf\n5.1-Lautsprecher.", + "options.audio.surround.choice.0": "An", + "options.audio.surround.choice.1": "Aus", + "options.input.mouse_camera_return.label": "Vertikale Rückkehr", + "options.input.mouse_camera_return.help": "\"Vertikale Rückkehr\"\nWie schnell die Kamera\nbei ruhender Maus auf\nnormale Höhe zurückkehrt." } diff --git a/port/lang/es_es.json b/port/lang/es_es.json index c7f1c137e..f6807719b 100644 --- a/port/lang/es_es.json +++ b/port/lang/es_es.json @@ -2,7 +2,6 @@ "options.shortcuts": "{square} Predeterm.\n{triangle} Deshacer\n{circle} Cerrar", "options.help.save_failed": "No se pudo guardar.\nLos cambios se aplican.\nCierra para reintentar\nescribir config.json.", "options.help.display_kept": "La pantalla no pudo\ncambiar y mantiene\nsu modo. Prueba con\notro modo o tamaño.", - "options.help.tabs": "\"Opciones\"\n{L1} {R1} o izq./\nder.: cambiar página.", "options.help.exit": "{cross} Cerrar\n{square} Valores de la página\n{triangle} Deshacer todo\n{circle} Cerrar (toda fila)", "options.help.display_now": "La pantalla no pudo\ncambiar. Ahora es\n%1", "options.display.fullscreen_now": "pantalla completa.", @@ -138,5 +137,22 @@ "options.input.mouse_zoom.choice.0": "Sí", "options.input.mouse_zoom.choice.1": "No", "options.input.bindings.zoom_reset.label": "Restablecer zoom", - "options.input.bindings.zoom_reset.help": "\"Restablecer zoom\"\nRestaura la distancia\nnormal de la cámara." + "options.input.bindings.zoom_reset.help": "\"Restablecer zoom\"\nRestaura la distancia\nnormal de la cámara.", + "options.help.turn_page": "{L1} {R1} cambian de página.", + "options.page.game.help": "\"Juego\"\nCómo se juega.", + "options.page.display.help": "\"Pantalla\"\nLa ventana y la imagen.", + "options.page.audio.help": "\"Audio\"\nSonido y música.", + "options.page.controls.help": "\"Controles\"\nRatón, mando y giroscopio.", + "options.page.accessibility": "Accesibilidad", + "options.page.accessibility.help": "\"Accesibilidad\"\nAyuda con las partes\nmás difíciles del juego.", + "options.audio.soundtrack.label": "Banda sonora", + "options.audio.soundtrack.help": "\"Banda sonora\"\nPS2: la música del juego.\nPersonal: tus propias\ngrabaciones, desde la\nsiguiente canción.", + "options.audio.soundtrack.choice.0": "PS2", + "options.audio.soundtrack.choice.1": "Personal", + "options.audio.surround.label": "Envolvente", + "options.audio.surround.help": "\"Envolvente\"\nReparte el sonido en\naltavoces 5.1.", + "options.audio.surround.choice.0": "Sí", + "options.audio.surround.choice.1": "No", + "options.input.mouse_camera_return.label": "Retorno vertical", + "options.input.mouse_camera_return.help": "\"Retorno vertical\"\nRapidez con que la cámara\nvuelve a la altura normal\ncuando el ratón se detiene." } diff --git a/port/lang/fr_fr.json b/port/lang/fr_fr.json index 719ed0696..c80d426f4 100644 --- a/port/lang/fr_fr.json +++ b/port/lang/fr_fr.json @@ -2,7 +2,6 @@ "options.shortcuts": "{square} Défauts\n{triangle} Annuler\n{circle} Fermer", "options.help.save_failed": "Sauvegarde impossible.\nChangements actifs.\nFermez pour réessayer\nconfig.json.", "options.help.display_kept": "L'écran n'a pas pu\nchanger et garde son\nancien mode. Essayez\nun autre mode.", - "options.help.tabs": "\"Options\"\n{L1} {R1} ou gauche/\ndroite: changer page.", "options.help.exit": "{cross} Fermer\n{square} Défauts de la page\n{triangle} Tout annuler\n{circle} Fermer (toute ligne)", "options.help.display_now": "L'écran n'a pas pu\nchanger. Il est\ndésormais %1", "options.display.fullscreen_now": "plein écran.", @@ -138,5 +137,22 @@ "options.input.mouse_zoom.choice.0": "Activé", "options.input.mouse_zoom.choice.1": "Désactivé", "options.input.bindings.zoom_reset.label": "Réinit. zoom", - "options.input.bindings.zoom_reset.help": "\"Réinit. zoom\"\nRétablit la distance\nnormale de la caméra." + "options.input.bindings.zoom_reset.help": "\"Réinit. zoom\"\nRétablit la distance\nnormale de la caméra.", + "options.help.turn_page": "{L1} {R1} changent de page.", + "options.page.game.help": "\"Jeu\"\nLe déroulement du jeu.", + "options.page.display.help": "\"Affichage\"\nLa fenêtre et l'image.", + "options.page.audio.help": "\"Audio\"\nSon et musique.", + "options.page.controls.help": "\"Commandes\"\nSouris, manette et gyro.", + "options.page.accessibility": "Accessibilité", + "options.page.accessibility.help": "\"Accessibilité\"\nAide pour les passages\nles plus difficiles.", + "options.audio.soundtrack.label": "Bande-son", + "options.audio.soundtrack.help": "\"Bande-son\"\nMusique du jeu PS2.\nPerso: vos propres\nenregistrements, dès la\nchanson suivante.", + "options.audio.soundtrack.choice.0": "PS2", + "options.audio.soundtrack.choice.1": "Perso", + "options.audio.surround.label": "Surround", + "options.audio.surround.help": "\"Surround\"\nDiffuse le son sur\nenceintes 5.1.", + "options.audio.surround.choice.0": "Oui", + "options.audio.surround.choice.1": "Non", + "options.input.mouse_camera_return.label": "Retour vertical", + "options.input.mouse_camera_return.help": "\"Retour vertical\"\nVitesse de retour de la\ncaméra à hauteur normale\nquand la souris s'arrête." } diff --git a/port/lang/it_it.json b/port/lang/it_it.json index 004c7ca88..92cd6431f 100644 --- a/port/lang/it_it.json +++ b/port/lang/it_it.json @@ -2,7 +2,6 @@ "options.shortcuts": "{square} Predefiniti\n{triangle} Annulla\n{circle} Chiudi", "options.help.save_failed": "Salvataggio fallito.\nLe modifiche valgono.\nChiudi per riprovare\nscrivere config.json.", "options.help.display_kept": "Lo schermo non ha\npotuto cambiare e ha\nmantenuto la modalità.\nProva un'altra.", - "options.help.tabs": "\"Opzioni\"\n{L1} {R1} o sinistra/\ndestra: cambia pagina.", "options.help.exit": "{cross} Chiudi\n{square} Predefiniti pagina\n{triangle} Annulla tutto\n{circle} Chiudi da ogni riga", "options.help.display_now": "Lo schermo non ha\npotuto cambiare.\nOra è %1", "options.display.fullscreen_now": "schermo intero.", @@ -138,5 +137,22 @@ "options.input.mouse_zoom.choice.0": "Sì", "options.input.mouse_zoom.choice.1": "No", "options.input.bindings.zoom_reset.label": "Azzera zoom", - "options.input.bindings.zoom_reset.help": "\"Azzera zoom\"\nRipristina la distanza\nnormale della camera." + "options.input.bindings.zoom_reset.help": "\"Azzera zoom\"\nRipristina la distanza\nnormale della camera.", + "options.help.turn_page": "{L1} {R1} cambiano pagina.", + "options.page.game.help": "\"Gioco\"\nCome si svolge il gioco.", + "options.page.display.help": "\"Schermo\"\nLa finestra e l'immagine.", + "options.page.audio.help": "\"Audio\"\nSuono e musica.", + "options.page.controls.help": "\"Comandi\"\nMouse, gamepad e giroscopio.", + "options.page.accessibility": "Accessibilità", + "options.page.accessibility.help": "\"Accessibilità\"\nAiuto per le parti più\ndifficili del gioco.", + "options.audio.soundtrack.label": "Colonna sonora", + "options.audio.soundtrack.help": "\"Colonna sonora\"\nPS2: la musica del gioco.\nPersonale: le tue\nregistrazioni, dalla\nprossima canzone.", + "options.audio.soundtrack.choice.0": "PS2", + "options.audio.soundtrack.choice.1": "Personale", + "options.audio.surround.label": "Surround", + "options.audio.surround.help": "\"Surround\"\nDistribuisce il suono su\naltoparlanti 5.1.", + "options.audio.surround.choice.0": "Sì", + "options.audio.surround.choice.1": "No", + "options.input.mouse_camera_return.label": "Ritorno verticale", + "options.input.mouse_camera_return.help": "\"Ritorno verticale\"\nQuanto in fretta la camera\ntorna all'altezza normale\nquando il mouse si ferma." } From 6afc60149435349feb47418af42e9a7717ceeef9 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 07:06:05 -0500 Subject: [PATCH 07/20] wip: cast shadow also for the system text style --- port/src/clsmes.cpp | 168 ++++++++++++++++++++++++++++++-------------- 1 file changed, 116 insertions(+), 52 deletions(-) diff --git a/port/src/clsmes.cpp b/port/src/clsmes.cpp index ef0262ed6..af807d304 100644 --- a/port/src/clsmes.cpp +++ b/port/src/clsmes.cpp @@ -4,8 +4,8 @@ #include #include #include -#include #include +#include #include #include "draw2d_port.hpp" @@ -19,8 +19,8 @@ #include "snd.hpp" #include "texture.hpp" -extern float RandTbl[64]; -extern float RandTbl2[64]; +extern float RandTbl[64]; +extern float RandTbl2[64]; extern MES_FUCHI FuchiTbl_E[]; namespace { @@ -47,22 +47,62 @@ bool TtfReady() { void GlyphTint(int clut, int dark, int wide, int &r, int &g, int &b, int &alpha) { alpha = 0x80; switch (FontColorTbl[clut]) { - case FONT_COLOR_WHITE: r = g = b = 0x5F; break; - case FONT_COLOR_BROWN: r = 0x22; g = 0x20; b = 0x18; break; - case FONT_COLOR_YELLOW: r = 0x5E; g = 0x5E; b = 0x20; break; - case FONT_COLOR_CYAN: r = 0x20; g = 0x5E; b = 0x5E; break; - case FONT_COLOR_GREEN: r = 0x20; g = 0x5E; b = 0x20; break; - case FONT_COLOR_BROWN_OPAQUE: r = 0x22; g = 0x20; b = 0x18; break; - case FONT_COLOR_GOLD: r = 0x73; g = 0x67; b = 0x33; break; - case FONT_COLOR_GREY: r = g = b = 0x47; break; - case FONT_COLOR_MAGENTA: r = 0x5F; g = 0x1F; b = 0x5F; break; - default: r = 0x28; g = 0x28; b = 0x20; break; + case FONT_COLOR_WHITE: + r = g = b = 0x5F; + break; + case FONT_COLOR_BROWN: + r = 0x22; + g = 0x20; + b = 0x18; + break; + case FONT_COLOR_YELLOW: + r = 0x5E; + g = 0x5E; + b = 0x20; + break; + case FONT_COLOR_CYAN: + r = 0x20; + g = 0x5E; + b = 0x5E; + break; + case FONT_COLOR_GREEN: + r = 0x20; + g = 0x5E; + b = 0x20; + break; + case FONT_COLOR_BROWN_OPAQUE: + r = 0x22; + g = 0x20; + b = 0x18; + break; + case FONT_COLOR_GOLD: + r = 0x73; + g = 0x67; + b = 0x33; + break; + case FONT_COLOR_GREY: + r = g = b = 0x47; + break; + case FONT_COLOR_MAGENTA: + r = 0x5F; + g = 0x1F; + b = 0x5F; + break; + default: + r = 0x28; + g = 0x28; + b = 0x20; + break; } if (clut == 0xFF) { if (dark != 0) { - r = 0x7F; g = 0x22; b = 0x22; + r = 0x7F; + g = 0x22; + b = 0x22; } else { - r = 0x40; g = 0x11; b = 0x11; + r = 0x40; + g = 0x11; + b = 0x11; } } if (wide == 1) { @@ -74,7 +114,8 @@ void GlyphTint(int clut, int dark, int wide, int &r, int &g, int &b, int &alpha) void TtfQuad(std::vector &out, const ttffont::Glyph &glyph, float x, float y, float width, float height, int r, int g, int b, int alpha) { - auto clamp = [](int v) { return static_cast(v < 0 ? 0 : v > 255 ? 255 : v); }; + auto clamp = [](int v) { return static_cast(v < 0 ? 0 : v > 255 ? 255 + : v); }; const u_char red = clamp(r), green = clamp(g), blue = clamp(b), a = clamp(alpha); out.push_back(draw2d::Vertex(x, y, 0.0f, glyph.u0, glyph.v0, red, green, blue, a)); out.push_back(draw2d::Vertex(x + width, y, 0.0f, glyph.u1, glyph.v0, red, green, blue, a)); @@ -109,7 +150,7 @@ void TtfLayoutRows(ClsMes &mes, std::vector &xs) { const float gap = em * 0.1f; auto glyph_of = [&](int i, ttffont::Glyph &glyph) { - const int code = mes.win_line[i].code; + const int code = mes.win_line[i].code; const char32_t ch = code >= -0x2DF && code < -0x251 ? GameTextChar(static_cast(code)) : 0; return ch != 0 && ttffont::GetGlyph(ch, em_px, glyph); }; @@ -196,53 +237,76 @@ bool QueueTtfGlyph(ClsMes &mes, int index, int dark, int offset_x, int offset_y, const int cap = mes.edge_alpha; // A thin stroke's outline covers little of a pixel, so it is laid down nine times to read as dark as the bitmaps'. constexpr int passes = 9; - auto edge = [&](float dx, float dy, int er, int eg, int eb, int ea) { + auto edge = [&](float dx, float dy, int er, int eg, int eb, int ea) { for (int pass = 0; pass < passes; ++pass) { TtfQuad(edges, glyph, x + dx, y + dy * rows, width, height, er, eg, eb, std::min(cap, ea)); } }; + // Retail's shadow is cast down and to the right and falls off softly, because its bitmaps are soft. + // A crisp glyph stacked at two offsets reads as a hard outline instead, so the shadow is laid in + // as taps at half-pixel steps along the light's direction, which the texture filter blends. + static const int mode = [] { + const char *text = std::getenv("DC_TTF_SHADOW"); + return text != nullptr ? std::atoi(text) : 1; + }(); + + struct Tap { + float dx, dy; + int grey, alpha; + }; + + static const Tap soft[] = { + {0.8f, 0.8f, 0x40, 0x70}, + {1.4f, 1.4f, 0x10, 0x78}, + {2.0f, 2.0f, 0, 0x78}, + {2.6f, 2.6f, 0, 0x60}, + {3.2f, 3.2f, 0, 0x40}, + {1.6f, 0.8f, 0, 0x40}, + {0.8f, 1.6f, 0, 0x40}, + {2.4f, 1.6f, 0, 0x30}, + {1.6f, 2.4f, 0, 0x30}, + }; + static const Tap deep[] = { + {0.8f, 0.8f, 0x40, 0x78}, + {1.4f, 1.4f, 0x10, 0x78}, + {2.0f, 2.0f, 0, 0x78}, + {2.6f, 2.6f, 0, 0x78}, + {3.2f, 3.2f, 0, 0x60}, + {3.8f, 3.8f, 0, 0x40}, + {1.6f, 0.8f, 0, 0x50}, + {0.8f, 1.6f, 0, 0x50}, + {2.4f, 1.6f, 0, 0x48}, + {1.6f, 2.4f, 0, 0x48}, + {3.2f, 2.4f, 0, 0x30}, + {2.4f, 3.2f, 0, 0x30}, + }; + auto cast = [&] { + if (mode == 0) { + edge(1, 1, 0x40, 0x40, 0x40, 0x80); + edge(2, 2, 0, 0, 0, 0x80); + } else { + for (const Tap &tap : mode == 1 ? std::span(soft) : std::span(deep)) { + TtfQuad(edges, glyph, x + tap.dx, y + tap.dy * rows, width, height, tap.grey, tap.grey, tap.grey, + std::min(cap, tap.alpha)); + } + } + }; switch (mes.style) { - case MES_EDGE_WHITE: edge(1, 1, 0xFF, 0xFF, 0xFF, 0x40); break; - case MES_EDGE_BLACK: edge(1, 1, 0, 0, 0, 0x40); break; + case MES_EDGE_WHITE: + edge(1, 1, 0xFF, 0xFF, 0xFF, 0x40); + break; + case MES_EDGE_BLACK: + cast(); + break; case MES_EDGE_TABLE: for (int i = 0; FuchiTbl_E[i].alpha > 0; i++) { edge(static_cast(FuchiTbl_E[i].x), static_cast(FuchiTbl_E[i].y), FuchiTbl_E[i].r, FuchiTbl_E[i].g, FuchiTbl_E[i].b, FuchiTbl_E[i].alpha); } break; - case MES_EDGE_DOUBLE: { - // Retail's shadow is cast down and to the right and falls off softly, because its bitmaps are soft. - // A crisp glyph stacked at two offsets reads as a hard outline instead, so the shadow is laid in - // as taps at half-pixel steps along the light's direction, which the texture filter blends. - static const int mode = [] { - const char *text = std::getenv("DC_TTF_SHADOW"); - return text != nullptr ? std::atoi(text) : 1; - }(); - struct Tap { - float dx, dy; - int grey, alpha; - }; - static const Tap soft[] = { - {0.8f, 0.8f, 0x40, 0x70}, {1.4f, 1.4f, 0x10, 0x78}, {2.0f, 2.0f, 0, 0x78}, {2.6f, 2.6f, 0, 0x60}, - {3.2f, 3.2f, 0, 0x40}, {1.6f, 0.8f, 0, 0x40}, {0.8f, 1.6f, 0, 0x40}, {2.4f, 1.6f, 0, 0x30}, - {1.6f, 2.4f, 0, 0x30}, - }; - static const Tap deep[] = { - {0.8f, 0.8f, 0x40, 0x78}, {1.4f, 1.4f, 0x10, 0x78}, {2.0f, 2.0f, 0, 0x78}, {2.6f, 2.6f, 0, 0x78}, - {3.2f, 3.2f, 0, 0x60}, {3.8f, 3.8f, 0, 0x40}, {1.6f, 0.8f, 0, 0x50}, {0.8f, 1.6f, 0, 0x50}, - {2.4f, 1.6f, 0, 0x48}, {1.6f, 2.4f, 0, 0x48}, {3.2f, 2.4f, 0, 0x30}, {2.4f, 3.2f, 0, 0x30}, - }; - if (mode == 0) { - edge(1, 1, 0x40, 0x40, 0x40, 0x80); - edge(2, 2, 0, 0, 0, 0x80); - } else { - for (const Tap &tap : mode == 1 ? std::span(soft) : std::span(deep)) { - TtfQuad(edges, glyph, x + tap.dx, y + tap.dy * rows, width, height, tap.grey, tap.grey, tap.grey, - std::min(cap, tap.alpha)); - } - } + case MES_EDGE_DOUBLE: + cast(); break; - } } TtfQuad(fills, glyph, x, y, width, height, r, g, b, std::min(cap, alpha)); return true; From 88db9b55dece0d0b5163d7392df13a4fa49bd907 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 07:08:25 -0500 Subject: [PATCH 08/20] Localization: TrueType shadow is cast down and right with a soft fall-off --- port/src/clsmes.cpp | 31 +++---------------------------- 1 file changed, 3 insertions(+), 28 deletions(-) diff --git a/port/src/clsmes.cpp b/port/src/clsmes.cpp index af807d304..7275f2664 100644 --- a/port/src/clsmes.cpp +++ b/port/src/clsmes.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include "draw2d_port.hpp" @@ -245,11 +244,6 @@ bool QueueTtfGlyph(ClsMes &mes, int index, int dark, int offset_x, int offset_y, // Retail's shadow is cast down and to the right and falls off softly, because its bitmaps are soft. // A crisp glyph stacked at two offsets reads as a hard outline instead, so the shadow is laid in // as taps at half-pixel steps along the light's direction, which the texture filter blends. - static const int mode = [] { - const char *text = std::getenv("DC_TTF_SHADOW"); - return text != nullptr ? std::atoi(text) : 1; - }(); - struct Tap { float dx, dy; int grey, alpha; @@ -266,29 +260,10 @@ bool QueueTtfGlyph(ClsMes &mes, int index, int dark, int offset_x, int offset_y, {2.4f, 1.6f, 0, 0x30}, {1.6f, 2.4f, 0, 0x30}, }; - static const Tap deep[] = { - {0.8f, 0.8f, 0x40, 0x78}, - {1.4f, 1.4f, 0x10, 0x78}, - {2.0f, 2.0f, 0, 0x78}, - {2.6f, 2.6f, 0, 0x78}, - {3.2f, 3.2f, 0, 0x60}, - {3.8f, 3.8f, 0, 0x40}, - {1.6f, 0.8f, 0, 0x50}, - {0.8f, 1.6f, 0, 0x50}, - {2.4f, 1.6f, 0, 0x48}, - {1.6f, 2.4f, 0, 0x48}, - {3.2f, 2.4f, 0, 0x30}, - {2.4f, 3.2f, 0, 0x30}, - }; auto cast = [&] { - if (mode == 0) { - edge(1, 1, 0x40, 0x40, 0x40, 0x80); - edge(2, 2, 0, 0, 0, 0x80); - } else { - for (const Tap &tap : mode == 1 ? std::span(soft) : std::span(deep)) { - TtfQuad(edges, glyph, x + tap.dx, y + tap.dy * rows, width, height, tap.grey, tap.grey, tap.grey, - std::min(cap, tap.alpha)); - } + for (const Tap &tap : soft) { + TtfQuad(edges, glyph, x + tap.dx, y + tap.dy * rows, width, height, tap.grey, tap.grey, tap.grey, + std::min(cap, tap.alpha)); } }; switch (mes.style) { From 1c18b60b15f3f83f14ab2d673d308d51223c25cc Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 07:18:26 -0500 Subject: [PATCH 09/20] Localization: Text Shadow setting (Soft or Deep) on the Display page --- port/lang/de_de.json | 6 +++++- port/lang/es_es.json | 6 +++++- port/lang/fr_fr.json | 6 +++++- port/lang/it_it.json | 6 +++++- port/src/clsmes.cpp | 19 ++++++++++++++++++- port/src/options/rows.cpp | 7 +++++++ port/src/platform/config.cpp | 12 ++++++++++++ port/src/platform/config.hpp | 2 ++ 8 files changed, 59 insertions(+), 5 deletions(-) diff --git a/port/lang/de_de.json b/port/lang/de_de.json index da67c31b3..61a59232b 100644 --- a/port/lang/de_de.json +++ b/port/lang/de_de.json @@ -154,5 +154,9 @@ "options.audio.surround.choice.0": "An", "options.audio.surround.choice.1": "Aus", "options.input.mouse_camera_return.label": "Vertikale Rückkehr", - "options.input.mouse_camera_return.help": "\"Vertikale Rückkehr\"\nWie schnell die Kamera\nbei ruhender Maus auf\nnormale Höhe zurückkehrt." + "options.input.mouse_camera_return.help": "\"Vertikale Rückkehr\"\nWie schnell die Kamera\nbei ruhender Maus auf\nnormale Höhe zurückkehrt.", + "options.video.text_shadow.label": "Textschatten", + "options.video.text_shadow.help": "\"Textschatten\"\nWie dunkel der Schatten\nunter dem Text ist (bei\nTrueType-Schrift).", + "options.video.text_shadow.choice.0": "Weich", + "options.video.text_shadow.choice.1": "Tief" } diff --git a/port/lang/es_es.json b/port/lang/es_es.json index f6807719b..3de669b99 100644 --- a/port/lang/es_es.json +++ b/port/lang/es_es.json @@ -154,5 +154,9 @@ "options.audio.surround.choice.0": "Sí", "options.audio.surround.choice.1": "No", "options.input.mouse_camera_return.label": "Retorno vertical", - "options.input.mouse_camera_return.help": "\"Retorno vertical\"\nRapidez con que la cámara\nvuelve a la altura normal\ncuando el ratón se detiene." + "options.input.mouse_camera_return.help": "\"Retorno vertical\"\nRapidez con que la cámara\nvuelve a la altura normal\ncuando el ratón se detiene.", + "options.video.text_shadow.label": "Sombra del texto", + "options.video.text_shadow.help": "\"Sombra del texto\"\nQué tan oscura es la\nsombra bajo el texto con\nfuente TrueType.", + "options.video.text_shadow.choice.0": "Suave", + "options.video.text_shadow.choice.1": "Profunda" } diff --git a/port/lang/fr_fr.json b/port/lang/fr_fr.json index c80d426f4..baf3a5df6 100644 --- a/port/lang/fr_fr.json +++ b/port/lang/fr_fr.json @@ -154,5 +154,9 @@ "options.audio.surround.choice.0": "Oui", "options.audio.surround.choice.1": "Non", "options.input.mouse_camera_return.label": "Retour vertical", - "options.input.mouse_camera_return.help": "\"Retour vertical\"\nVitesse de retour de la\ncaméra à hauteur normale\nquand la souris s'arrête." + "options.input.mouse_camera_return.help": "\"Retour vertical\"\nVitesse de retour de la\ncaméra à hauteur normale\nquand la souris s'arrête.", + "options.video.text_shadow.label": "Ombre du texte", + "options.video.text_shadow.help": "\"Ombre du texte\"\nOmbre sous le texte, avec\nune police TrueType.", + "options.video.text_shadow.choice.0": "Légère", + "options.video.text_shadow.choice.1": "Marquée" } diff --git a/port/lang/it_it.json b/port/lang/it_it.json index 92cd6431f..727145129 100644 --- a/port/lang/it_it.json +++ b/port/lang/it_it.json @@ -154,5 +154,9 @@ "options.audio.surround.choice.0": "Sì", "options.audio.surround.choice.1": "No", "options.input.mouse_camera_return.label": "Ritorno verticale", - "options.input.mouse_camera_return.help": "\"Ritorno verticale\"\nQuanto in fretta la camera\ntorna all'altezza normale\nquando il mouse si ferma." + "options.input.mouse_camera_return.help": "\"Ritorno verticale\"\nQuanto in fretta la camera\ntorna all'altezza normale\nquando il mouse si ferma.", + "options.video.text_shadow.label": "Ombra del testo", + "options.video.text_shadow.help": "\"Ombra del testo\"\nQuanto è scura l'ombra\nsotto il testo con\ncarattere TrueType.", + "options.video.text_shadow.choice.0": "Leggera", + "options.video.text_shadow.choice.1": "Intensa" } diff --git a/port/src/clsmes.cpp b/port/src/clsmes.cpp index 7275f2664..c7765c3d6 100644 --- a/port/src/clsmes.cpp +++ b/port/src/clsmes.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "draw2d_port.hpp" @@ -13,6 +14,7 @@ #include "localize.hpp" #include "mglib.hpp" #include "mglib_port.hpp" +#include "platform/config.hpp" #include "platform/ttffont.hpp" #include "rect.hpp" #include "snd.hpp" @@ -260,8 +262,23 @@ bool QueueTtfGlyph(ClsMes &mes, int index, int dark, int offset_x, int offset_y, {2.4f, 1.6f, 0, 0x30}, {1.6f, 2.4f, 0, 0x30}, }; + static const Tap deep[] = { + {0.8f, 0.8f, 0x40, 0x78}, + {1.4f, 1.4f, 0x10, 0x78}, + {2.0f, 2.0f, 0, 0x78}, + {2.6f, 2.6f, 0, 0x78}, + {3.2f, 3.2f, 0, 0x60}, + {3.8f, 3.8f, 0, 0x40}, + {1.6f, 0.8f, 0, 0x50}, + {0.8f, 1.6f, 0, 0x50}, + {2.4f, 1.6f, 0, 0x48}, + {1.6f, 2.4f, 0, 0x48}, + {3.2f, 2.4f, 0, 0x30}, + {2.4f, 3.2f, 0, 0x30}, + }; auto cast = [&] { - for (const Tap &tap : soft) { + const bool deeper = ConfigGet().text_shadow == 1; + for (const Tap &tap : deeper ? std::span(deep) : std::span(soft)) { TtfQuad(edges, glyph, x + tap.dx, y + tap.dy * rows, width, height, tap.grey, tap.grey, tap.grey, std::min(cap, tap.alpha)); } diff --git a/port/src/options/rows.cpp b/port/src/options/rows.cpp index bbb7c2f85..c50c5b4ca 100644 --- a/port/src/options/rows.cpp +++ b/port/src/options/rows.cpp @@ -523,6 +523,13 @@ const Row kDisplayRows[] = { SettingRow("video.fps_detail", "FPS Info", "\"FPS Info\"\nWhat the counter shows:\nthe frame rate alone,\nwith ticks, or all.", FpsDetailCount, FpsDetailChoice, SetFpsDetail, nullptr, "FPS|FPS+Ticks|All"), GameRow<&ConfigGameOptions::soft_focus, true>("video.soft_focus", "Soft Focus", "On|Off", 0x169), + Row{.key = "video.text_shadow", + .label = "Text Shadow", + .help = "\"Text Shadow\"\nHow dark the shadow\nunder the text is, when\nthe text is a TrueType\nfont.", + .count = Two, + .get = [](const Config &config) { return config.text_shadow; }, + .set = [](Config &config, int choice) { config.text_shadow = choice; }, + .names = "Soft|Deep"}, }; const Row kAudioRows[] = { diff --git a/port/src/platform/config.cpp b/port/src/platform/config.cpp index 40244ad12..950d672a3 100644 --- a/port/src/platform/config.cpp +++ b/port/src/platform/config.cpp @@ -347,6 +347,17 @@ bool Apply(Config &config, std::string_view name, const Json &value) { if (name == "game.debug_mode") { return ReadBool(value, config.debug_mode); } + if (name == "video.text_shadow") { + if (!value.is_string()) { + return false; + } + const std::string shadow = Lower(value.get()); + if (shadow != "soft" && shadow != "deep") { + return false; + } + config.text_shadow = shadow == "deep" ? 1 : 0; + return true; + } if (name == "game.language") { return ReadLanguage(value, config.language); } @@ -519,6 +530,7 @@ std::string ConfigSerialize(const Config &config) { root["game"]["player_damage"] = options.player_damage; root["game"]["enemy_hp"] = options.enemy_hp; root["game"]["names"] = options.names; + root["video"]["text_shadow"] = config.text_shadow == 1 ? "deep" : "soft"; root["video"]["present_mode"] = PresentModeName(config.present_mode); root["video"]["interpolation"] = config.interpolation; root["video"]["max_fps"] = config.max_fps; diff --git a/port/src/platform/config.hpp b/port/src/platform/config.hpp index 0f7643943..e1f2faadc 100644 --- a/port/src/platform/config.hpp +++ b/port/src/platform/config.hpp @@ -99,6 +99,8 @@ struct Config { // game.language: 0 asks at start-up, as retail does; 2 to 6 is the language to start in // (LanguageCode: English, Francais, Deutsch, Italiano, Espanol) and skips the language screen. int language = 0; + // video.text_shadow: 0 is the soft shadow the TrueType message font casts, 1 a deeper one. + int text_shadow = 0; bool operator==(const Config &) const = default; }; From 499707a2068bb2451b2e52f929cb23b8ab7c6e2c Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 07:21:29 -0500 Subject: [PATCH 10/20] Localization: soft shadow is a blurred, denser cast --- port/src/clsmes.cpp | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/port/src/clsmes.cpp b/port/src/clsmes.cpp index c7765c3d6..ce390885e 100644 --- a/port/src/clsmes.cpp +++ b/port/src/clsmes.cpp @@ -252,15 +252,27 @@ bool QueueTtfGlyph(ClsMes &mes, int index, int dark, int offset_x, int offset_y, }; static const Tap soft[] = { - {0.8f, 0.8f, 0x40, 0x70}, - {1.4f, 1.4f, 0x10, 0x78}, - {2.0f, 2.0f, 0, 0x78}, - {2.6f, 2.6f, 0, 0x60}, - {3.2f, 3.2f, 0, 0x40}, - {1.6f, 0.8f, 0, 0x40}, - {0.8f, 1.6f, 0, 0x40}, - {2.4f, 1.6f, 0, 0x30}, - {1.6f, 2.4f, 0, 0x30}, + {0.6f, 1.4f, 0, 0x17}, + {0.6f, 2.2f, 0, 0x1C}, + {0.6f, 3.0f, 0, 0x17}, + {1.4f, 0.6f, 0, 0x17}, + {1.4f, 1.4f, 0, 0x2C}, + {1.4f, 2.2f, 0, 0x38}, + {1.4f, 3.0f, 0, 0x2C}, + {1.4f, 3.8f, 0, 0x17}, + {2.2f, 0.6f, 0, 0x1C}, + {2.2f, 1.4f, 0, 0x38}, + {2.2f, 2.2f, 0, 0x46}, + {2.2f, 3.0f, 0, 0x38}, + {2.2f, 3.8f, 0, 0x1C}, + {3.0f, 0.6f, 0, 0x17}, + {3.0f, 1.4f, 0, 0x2C}, + {3.0f, 2.2f, 0, 0x38}, + {3.0f, 3.0f, 0, 0x2C}, + {3.0f, 3.8f, 0, 0x17}, + {3.8f, 1.4f, 0, 0x17}, + {3.8f, 2.2f, 0, 0x1C}, + {3.8f, 3.0f, 0, 0x17}, }; static const Tap deep[] = { {0.8f, 0.8f, 0x40, 0x78}, From 5985e0fa166c7f6e6789c3cc0c7367152e020848 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 07:33:23 -0500 Subject: [PATCH 11/20] Localization: soft shadow is more opaque --- port/src/clsmes.cpp | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/port/src/clsmes.cpp b/port/src/clsmes.cpp index ce390885e..a9c941e5b 100644 --- a/port/src/clsmes.cpp +++ b/port/src/clsmes.cpp @@ -252,27 +252,27 @@ bool QueueTtfGlyph(ClsMes &mes, int index, int dark, int offset_x, int offset_y, }; static const Tap soft[] = { - {0.6f, 1.4f, 0, 0x17}, - {0.6f, 2.2f, 0, 0x1C}, - {0.6f, 3.0f, 0, 0x17}, - {1.4f, 0.6f, 0, 0x17}, - {1.4f, 1.4f, 0, 0x2C}, - {1.4f, 2.2f, 0, 0x38}, - {1.4f, 3.0f, 0, 0x2C}, - {1.4f, 3.8f, 0, 0x17}, - {2.2f, 0.6f, 0, 0x1C}, - {2.2f, 1.4f, 0, 0x38}, - {2.2f, 2.2f, 0, 0x46}, - {2.2f, 3.0f, 0, 0x38}, - {2.2f, 3.8f, 0, 0x1C}, - {3.0f, 0.6f, 0, 0x17}, - {3.0f, 1.4f, 0, 0x2C}, - {3.0f, 2.2f, 0, 0x38}, - {3.0f, 3.0f, 0, 0x2C}, - {3.0f, 3.8f, 0, 0x17}, - {3.8f, 1.4f, 0, 0x17}, - {3.8f, 2.2f, 0, 0x1C}, - {3.8f, 3.0f, 0, 0x17}, + {0.6f, 1.4f, 0, 0x2C}, + {0.6f, 2.2f, 0, 0x35}, + {0.6f, 3.0f, 0, 0x2C}, + {1.4f, 0.6f, 0, 0x2C}, + {1.4f, 1.4f, 0, 0x54}, + {1.4f, 2.2f, 0, 0x6A}, + {1.4f, 3.0f, 0, 0x54}, + {1.4f, 3.8f, 0, 0x2C}, + {2.2f, 0.6f, 0, 0x35}, + {2.2f, 1.4f, 0, 0x6A}, + {2.2f, 2.2f, 0, 0x78}, + {2.2f, 3.0f, 0, 0x6A}, + {2.2f, 3.8f, 0, 0x35}, + {3.0f, 0.6f, 0, 0x2C}, + {3.0f, 1.4f, 0, 0x54}, + {3.0f, 2.2f, 0, 0x6A}, + {3.0f, 3.0f, 0, 0x54}, + {3.0f, 3.8f, 0, 0x2C}, + {3.8f, 1.4f, 0, 0x2C}, + {3.8f, 2.2f, 0, 0x35}, + {3.8f, 3.0f, 0, 0x2C}, }; static const Tap deep[] = { {0.8f, 0.8f, 0x40, 0x78}, From 01e446b61030270a0288a9155bac09c417d02ce6 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 10:39:07 -0500 Subject: [PATCH 12/20] Localization: soft shadow is more transparent --- port/src/clsmes.cpp | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/port/src/clsmes.cpp b/port/src/clsmes.cpp index a9c941e5b..73f39c8d7 100644 --- a/port/src/clsmes.cpp +++ b/port/src/clsmes.cpp @@ -252,27 +252,27 @@ bool QueueTtfGlyph(ClsMes &mes, int index, int dark, int offset_x, int offset_y, }; static const Tap soft[] = { - {0.6f, 1.4f, 0, 0x2C}, - {0.6f, 2.2f, 0, 0x35}, - {0.6f, 3.0f, 0, 0x2C}, - {1.4f, 0.6f, 0, 0x2C}, - {1.4f, 1.4f, 0, 0x54}, - {1.4f, 2.2f, 0, 0x6A}, - {1.4f, 3.0f, 0, 0x54}, - {1.4f, 3.8f, 0, 0x2C}, - {2.2f, 0.6f, 0, 0x35}, - {2.2f, 1.4f, 0, 0x6A}, - {2.2f, 2.2f, 0, 0x78}, - {2.2f, 3.0f, 0, 0x6A}, - {2.2f, 3.8f, 0, 0x35}, - {3.0f, 0.6f, 0, 0x2C}, - {3.0f, 1.4f, 0, 0x54}, - {3.0f, 2.2f, 0, 0x6A}, - {3.0f, 3.0f, 0, 0x54}, - {3.0f, 3.8f, 0, 0x2C}, - {3.8f, 1.4f, 0, 0x2C}, - {3.8f, 2.2f, 0, 0x35}, - {3.8f, 3.0f, 0, 0x2C}, + {0.6f, 1.4f, 0, 0x0C}, + {0.6f, 2.2f, 0, 0x0E}, + {0.6f, 3.0f, 0, 0x0C}, + {1.4f, 0.6f, 0, 0x0C}, + {1.4f, 1.4f, 0, 0x16}, + {1.4f, 2.2f, 0, 0x1C}, + {1.4f, 3.0f, 0, 0x16}, + {1.4f, 3.8f, 0, 0x0C}, + {2.2f, 0.6f, 0, 0x0E}, + {2.2f, 1.4f, 0, 0x1C}, + {2.2f, 2.2f, 0, 0x20}, + {2.2f, 3.0f, 0, 0x1C}, + {2.2f, 3.8f, 0, 0x0E}, + {3.0f, 0.6f, 0, 0x0C}, + {3.0f, 1.4f, 0, 0x16}, + {3.0f, 2.2f, 0, 0x1C}, + {3.0f, 3.0f, 0, 0x16}, + {3.0f, 3.8f, 0, 0x0C}, + {3.8f, 1.4f, 0, 0x0C}, + {3.8f, 2.2f, 0, 0x0E}, + {3.8f, 3.0f, 0, 0x0C}, }; static const Tap deep[] = { {0.8f, 0.8f, 0x40, 0x78}, From 30f048bbe1fa5576bb32e81cea02a028d7baabc1 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 16:19:41 -0500 Subject: [PATCH 13/20] Localization: glyphs are uploaded at once, not through the recorded display list --- port/src/gfx/gfx.hpp | 6 ++++++ port/src/gfx/resources.cpp | 19 ++++++++++++++++--- port/src/platform/ttffont.cpp | 4 ++-- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/port/src/gfx/gfx.hpp b/port/src/gfx/gfx.hpp index 17bab2e6b..d18b556fc 100644 --- a/port/src/gfx/gfx.hpp +++ b/port/src/gfx/gfx.hpp @@ -202,6 +202,12 @@ void DestroyTexture(TextureHandle texture); // index byte. Alpha is in the renderer's units: 0xFF is GS 0x80; ConvertPs2Alpha converts. bool UpdateTexture(TextureHandle texture, uint32_t mip, uint32_t x, uint32_t y, uint32_t w, uint32_t h, const void *pixels, uint32_t row_length = 0); +// UpdateTexture that is never recorded into a display list: it happens at once, even between +// BeginRecording and EndRecording. For a texture that is filled once and kept (the TrueType +// message font's atlas): a list that is recorded and never drawn, as the lists of ticks the window +// skips are, would take its upload with it, and the texture would stay without it for good. +bool UpdateTextureNow(TextureHandle texture, uint32_t mip, uint32_t x, uint32_t y, uint32_t w, uint32_t h, + const void *pixels, uint32_t row_length = 0); bool UpdatePalette(TextureHandle palette, const uint32_t *rgba, uint32_t first = 0, uint32_t count = 256); std::optional GetTextureInfo(TextureHandle texture); diff --git a/port/src/gfx/resources.cpp b/port/src/gfx/resources.cpp index cc755cc52..c7c3e9a71 100644 --- a/port/src/gfx/resources.cpp +++ b/port/src/gfx/resources.cpp @@ -641,8 +641,9 @@ void DestroyTexture(TextureHandle handle) { DestroyDoomedTexture(handle); } -bool UpdateTexture(TextureHandle handle, uint32_t mip, uint32_t x, uint32_t y, uint32_t w, uint32_t h, - const void *pixels, uint32_t row_length) { +namespace { +bool UpdateTextureImpl(TextureHandle handle, uint32_t mip, uint32_t x, uint32_t y, uint32_t w, uint32_t h, + const void *pixels, uint32_t row_length, bool record) { Texture *texture = LookupTexture(handle); if (texture == nullptr || pixels == nullptr) { Error("UpdateTexture: no texture %#x", handle); @@ -659,7 +660,7 @@ bool UpdateTexture(TextureHandle handle, uint32_t mip, uint32_t x, uint32_t y, u uint32_t texel = TexelSize(texture->desc.format); uint32_t stride = (row_length ? row_length : w) * texel; uint32_t row = w * texel; - if (RecordingCalls()) { + if (record && RecordingCalls()) { UpdateTextureEntry entry{handle, mip, x, y, w, h, std::vector(static_cast(row) * h)}; for (uint32_t i = 0; i < h; i++) { std::memcpy(entry.pixels.data() + static_cast(i) * row, @@ -688,6 +689,18 @@ bool UpdateTexture(TextureHandle handle, uint32_t mip, uint32_t x, uint32_t y, u return true; } +} // namespace + +bool UpdateTexture(TextureHandle handle, uint32_t mip, uint32_t x, uint32_t y, uint32_t w, uint32_t h, + const void *pixels, uint32_t row_length) { + return UpdateTextureImpl(handle, mip, x, y, w, h, pixels, row_length, true); +} + +bool UpdateTextureNow(TextureHandle handle, uint32_t mip, uint32_t x, uint32_t y, uint32_t w, uint32_t h, + const void *pixels, uint32_t row_length) { + return UpdateTextureImpl(handle, mip, x, y, w, h, pixels, row_length, false); +} + bool UpdatePalette(TextureHandle palette, const uint32_t *rgba, uint32_t first, uint32_t count) { if (first + count > 256) { Error("UpdatePalette: entries %u..%u are past 256", first, first + count); diff --git a/port/src/platform/ttffont.cpp b/port/src/platform/ttffont.cpp index 832c4a075..62fbfe61f 100644 --- a/port/src/platform/ttffont.cpp +++ b/port/src/platform/ttffont.cpp @@ -351,7 +351,7 @@ bool GetGlyph(char32_t ch, int em_px, Glyph &out) { g_atlas = {}; g_atlas.texture = keep; std::vector blank(static_cast(kAtlasSize) * kAtlasSize * 4, 0); - gfx::UpdateTexture(keep, 0, 0, 0, kAtlasSize, kAtlasSize, blank.data()); + gfx::UpdateTextureNow(keep, 0, 0, 0, kAtlasSize, kAtlasSize, blank.data()); } } @@ -373,7 +373,7 @@ bool GetGlyph(char32_t ch, int em_px, Glyph &out) { texel[3] = bitmap.alpha[static_cast(row) * width + col]; } } - gfx::UpdateTexture(g_atlas.texture, 0, static_cast(g_atlas.x), static_cast(g_atlas.y), + gfx::UpdateTextureNow(g_atlas.texture, 0, static_cast(g_atlas.x), static_cast(g_atlas.y), static_cast(w), static_cast(h), pixels.data()); glyph.u0 = static_cast(g_atlas.x + kPad); glyph.v0 = static_cast(g_atlas.y + kPad); From 1f735fb1ba9a9f7e959a47938ae1b446e446f554 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 16:27:53 -0500 Subject: [PATCH 14/20] Flatpak: stb_truetype.h is a build source, as the audio decoders are, since the sandbox has no network --- port/CMakeLists.txt | 13 +++++++++---- port/flatpak/org.themoonpeople.Chronicle.yml | 4 ++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/port/CMakeLists.txt b/port/CMakeLists.txt index 89e0be88a..4c635199c 100644 --- a/port/CMakeLists.txt +++ b/port/CMakeLists.txt @@ -49,10 +49,15 @@ else() file(DOWNLOAD https://raw.githubusercontent.com/nothings/stb/2c980bb59875b0d32144a71867fbdebb2f77cd20/stb_vorbis.c ${AUDIO_DECODER_DIR}/stb_vorbis.c EXPECTED_HASH SHA256=4c7cb2ff1f7011e9d67950446b7eb9ca044f2e464d76bfbb0b84dd2e23e65636) endif() -# The message font's rasterizer (platform/ttffont.cpp): a single-file library, public domain or MIT, fetched at a pinned commit. -set(TTF_DIR ${CMAKE_BINARY_DIR}/stb_truetype) -file(DOWNLOAD https://raw.githubusercontent.com/nothings/stb/2c980bb59875b0d32144a71867fbdebb2f77cd20/stb_truetype.h - ${TTF_DIR}/stb_truetype.h EXPECTED_HASH SHA256=ecd30b05e0dd4fea3a13c26810dd9e1992dc379049482c393d5a19e6b5090aab) +# The message font's rasterizer (platform/ttffont.cpp): a single-file library, public domain or MIT, at a pinned +# commit. Flatpak has it as a source, since its build sandbox has no network; other builds fetch the same file. +if(EXISTS ${CMAKE_SOURCE_DIR}/stb_truetype/stb_truetype.h) + set(TTF_DIR ${CMAKE_SOURCE_DIR}/stb_truetype) +else() + set(TTF_DIR ${CMAKE_BINARY_DIR}/stb_truetype) + file(DOWNLOAD https://raw.githubusercontent.com/nothings/stb/2c980bb59875b0d32144a71867fbdebb2f77cd20/stb_truetype.h + ${TTF_DIR}/stb_truetype.h EXPECTED_HASH SHA256=ecd30b05e0dd4fea3a13c26810dd9e1992dc379049482c393d5a19e6b5090aab) +endif() set(PS2_DIR ${CMAKE_SOURCE_DIR}/ps2/src) set(PORT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src) diff --git a/port/flatpak/org.themoonpeople.Chronicle.yml b/port/flatpak/org.themoonpeople.Chronicle.yml index da3e704c3..709a748aa 100644 --- a/port/flatpak/org.themoonpeople.Chronicle.yml +++ b/port/flatpak/org.themoonpeople.Chronicle.yml @@ -95,3 +95,7 @@ modules: url: https://raw.githubusercontent.com/nothings/stb/2c980bb59875b0d32144a71867fbdebb2f77cd20/stb_vorbis.c sha256: 4c7cb2ff1f7011e9d67950446b7eb9ca044f2e464d76bfbb0b84dd2e23e65636 dest: audio_decoders + - type: file + url: https://raw.githubusercontent.com/nothings/stb/2c980bb59875b0d32144a71867fbdebb2f77cd20/stb_truetype.h + sha256: ecd30b05e0dd4fea3a13c26810dd9e1992dc379049482c393d5a19e6b5090aab + dest: stb_truetype From a99cf8bb8799fdfd44958e77f3a7c7b8a80d507a Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 16:43:06 -0500 Subject: [PATCH 15/20] Localization: the table outline is the same cast shadow as the other text; refresh the truncation report's line numbers --- docs/port/truncations.md | 2 +- port/src/clsmes.cpp | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/docs/port/truncations.md b/docs/port/truncations.md index 50c42702a..297117df9 100644 --- a/docs/port/truncations.md +++ b/docs/port/truncations.md @@ -60,7 +60,7 @@ recovered, as `CCharacter::ClothStep` does with `PortImagePointer`) fixes the ro | site | function | state | class | expression | origin | use | |---|---|---|---|---|---|---| -| port/src/dataread.cpp:184 | LoadFileBG | port | low-bits | `reinterpret_cast < std :: uintptr_t >(buffer)` | parameter | test | +| port/src/dataread.cpp:208 | LoadFileBG | port | low-bits | `reinterpret_cast < std :: uintptr_t >(buffer)` | parameter | test | | port/src/edit_in.cpp:1761 | LoadData | port | low-bits | `(intptr_t)func_point[i].parts` | field | index: InteriorParts[(int)(intptr_t)func_point[i].parts] | | port/src/editloop_init.cpp:405 | EditInit | port | low-bits | `reinterpret_cast < std :: intptr_t >(EdNPCReadBuffer)` | global pointer | store: read_misalign | | port/src/texture_buffer.cpp:14 | CTextureManager::SetBuffer | port | low-bits | `reinterpret_cast < std :: uintptr_t >(this->buffer)` | field | store: misalignment | diff --git a/port/src/clsmes.cpp b/port/src/clsmes.cpp index 73f39c8d7..75cb1f713 100644 --- a/port/src/clsmes.cpp +++ b/port/src/clsmes.cpp @@ -22,7 +22,6 @@ extern float RandTbl[64]; extern float RandTbl2[64]; -extern MES_FUCHI FuchiTbl_E[]; namespace { @@ -303,10 +302,7 @@ bool QueueTtfGlyph(ClsMes &mes, int index, int dark, int offset_x, int offset_y, cast(); break; case MES_EDGE_TABLE: - for (int i = 0; FuchiTbl_E[i].alpha > 0; i++) { - edge(static_cast(FuchiTbl_E[i].x), static_cast(FuchiTbl_E[i].y), FuchiTbl_E[i].r, - FuchiTbl_E[i].g, FuchiTbl_E[i].b, FuchiTbl_E[i].alpha); - } + cast(); break; case MES_EDGE_DOUBLE: cast(); From 9ffdb15c49869fb8df610304671ba85cb3c013d2 Mon Sep 17 00:00:00 2001 From: Jeffie Date: Wed, 7 Oct 2026 20:01:41 -0500 Subject: [PATCH 16/20] Button symbols: PS4/PS5/Xbox/Switch/keyboard glyph atlases for the font's icons and the textures' painted buttons Settings input.glyphs (new/original) and input.glyph_device (auto/ps4/ps5/xbox/switch/keyboard) with two Options rows (translated in fr/de/es/it), the symbols drawn with a cast shadow like the font's, stb_image as a pinned download / Flatpak source, tests, docs/GLYPHS.md. Co-Authored-By: Claude Sonnet 5.5 --- docs/GLYPHS.md | 35 + port/CMakeLists.txt | 13 + port/flatpak/org.themoonpeople.Chronicle.yml | 4 + port/glyphs/glyphs.json | 1086 ++++++++++++++++++ port/glyphs/keyboard.png | Bin 0 -> 163401 bytes port/glyphs/ps4.png | Bin 0 -> 54436 bytes port/glyphs/ps5.png | Bin 0 -> 83206 bytes port/glyphs/switch.png | Bin 0 -> 56109 bytes port/glyphs/xbox.png | Bin 0 -> 51781 bytes port/lang/de_de.json | 14 +- port/lang/es_es.json | 14 +- port/lang/fr_fr.json | 14 +- port/lang/it_it.json | 14 +- port/src/clsmes.cpp | 30 + port/src/gameutil_sprite.cpp | 15 + port/src/glyph_draw.cpp | 126 ++ port/src/glyph_draw.hpp | 23 + port/src/options/rows.cpp | 18 + port/src/platform/config.cpp | 28 + port/src/platform/config.hpp | 14 + port/src/platform/glyphs.cpp | 268 +++++ port/src/platform/glyphs.hpp | 65 ++ port/src/platform/input.cpp | 62 + port/src/platform/input.hpp | 19 + port/src/snd.cpp | 19 + port/src/tests/glyphs_test.cpp | 123 ++ port/src/texture.cpp | 85 ++ tools/glyphs/build_glyphs.py | 219 ++++ 28 files changed, 2304 insertions(+), 4 deletions(-) create mode 100644 docs/GLYPHS.md create mode 100644 port/glyphs/glyphs.json create mode 100644 port/glyphs/keyboard.png create mode 100644 port/glyphs/ps4.png create mode 100644 port/glyphs/ps5.png create mode 100644 port/glyphs/switch.png create mode 100644 port/glyphs/xbox.png create mode 100644 port/src/glyph_draw.cpp create mode 100644 port/src/glyph_draw.hpp create mode 100644 port/src/platform/glyphs.cpp create mode 100644 port/src/platform/glyphs.hpp create mode 100644 port/src/tests/glyphs_test.cpp create mode 100644 tools/glyphs/build_glyphs.py diff --git a/docs/GLYPHS.md b/docs/GLYPHS.md new file mode 100644 index 000000000..d5301643c --- /dev/null +++ b/docs/GLYPHS.md @@ -0,0 +1,35 @@ +# Button symbols + +The game draws pad symbols in its text (the font's icons) and in a few textures. With `input.glyphs` `"new"` +(the default) the port draws redrawn symbols for the device in use instead; `"original"` keeps the PS2's art. +Options > Controls has both settings. + +| config.json | values | meaning | +|---|---|---| +| `input.glyphs` | `new`, `original` | the redrawn symbols, or the game's own | +| `input.glyph_device` | `auto`, `ps4`, `ps5`, `xbox`, `switch`, `keyboard` | whose symbols: `auto` follows the device touched last (keyboard or mouse, otherwise the first gamepad's make; PS4 until something is touched); the others force one | + +The keyboard style shows the key or mouse button the player bound to the action (`input.bindings`), the PS4 symbol +where the art has none for that key. Face buttons follow their position on the pad (the game's Cross is the bottom +button: A on Xbox, B on Switch). + +## Where they are drawn + +- Font icons (`ClsMes::DrawGaijiFont`, `port/src/clsmes.cpp`): the pad codes -0x300 to -0x2F4 (`{select}`, `{start}`, + `{L1}` to `{R2}`, `{circle}`, `{triangle}`, `{cross}`, `{square}`, `{dpad}`...). Every message and menu that names a + button uses them. +- Symbols painted into textures (`port/src/glyph_draw.cpp`, table `kBaked`): the name entry's L1/R1 hints (`nametemp`), the + character board's tab hints (`perbrd`) and the cutscene skip prompt (`pause_e`, `skip_bord`). Draws of these rectangles + (`RectSprite`, `port/src/snd.cpp`) are replaced by, or overlaid with, the symbol. +- Symbols on other textures the port has not found yet are still the game's: `DC_SPRITE_TRACE=1` prints each texture + rectangle a sprite draws (stderr) and `DC_TEXTURE_DUMP=` writes every texture the game loads as a PNG, which is + how the table was made. Add the box to `kBaked`. + +## Files + +`glyphs/` beside the executable (or in the save folder, which wins): `glyphs.json` and one atlas PNG per style. The +json gives each symbol's rectangle in its atlas; `reference` is the atlas size of a round face button. A symbol is +drawn scaled so a round button fills the height of its slot. `tools/glyphs/build_glyphs.py --src ` builds +them from the source art (P4Gamepad/Retro, P5Gamepad/Retro, XGamepad/Alt 2, SGamepad/Retro, Keyboard_Mouse/Dark); +Xbox's Menu button and the keyboard style's cursor-key clusters are composed by the tool. The atlases are read with +stb_image (a pinned download, as stb_truetype is). diff --git a/port/CMakeLists.txt b/port/CMakeLists.txt index 4c635199c..a04bbf2d1 100644 --- a/port/CMakeLists.txt +++ b/port/CMakeLists.txt @@ -58,6 +58,12 @@ else() file(DOWNLOAD https://raw.githubusercontent.com/nothings/stb/2c980bb59875b0d32144a71867fbdebb2f77cd20/stb_truetype.h ${TTF_DIR}/stb_truetype.h EXPECTED_HASH SHA256=ecd30b05e0dd4fea3a13c26810dd9e1992dc379049482c393d5a19e6b5090aab) endif() +# The button symbols' PNG atlases (platform/glyphs.cpp): the same library family at the same pinned commit, a +# Flatpak source beside stb_truetype.h, or fetched. +if(NOT EXISTS ${TTF_DIR}/stb_image.h) + file(DOWNLOAD https://raw.githubusercontent.com/nothings/stb/2c980bb59875b0d32144a71867fbdebb2f77cd20/stb_image.h + ${TTF_DIR}/stb_image.h EXPECTED_HASH SHA256=594c2fe35d49488b4382dbfaec8f98366defca819d916ac95becf3e75f4200b3) +endif() set(PS2_DIR ${CMAKE_SOURCE_DIR}/ps2/src) set(PORT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src) @@ -286,6 +292,13 @@ add_custom_target(dc_lang add_dependencies(darkcloud dc_lang) install(DIRECTORY lang/ DESTINATION ${CMAKE_INSTALL_BINDIR}/lang) +# The button symbols (docs/GLYPHS.md): glyphs/ beside the executable. Rebuilt from art by tools/glyphs/build_glyphs.py. +add_custom_target(dc_glyphs + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/glyphs $/glyphs + COMMENT "Copying the button symbols") +add_dependencies(darkcloud dc_glyphs) +install(DIRECTORY glyphs/ DESTINATION ${CMAKE_INSTALL_BINDIR}/glyphs) + # The tests use an installed GoogleTest, or fetch one at configure time; a build with neither turns them off. option(DC_BUILD_TESTS "Build darkcloud_tests" ON) if(DC_BUILD_TESTS) diff --git a/port/flatpak/org.themoonpeople.Chronicle.yml b/port/flatpak/org.themoonpeople.Chronicle.yml index 709a748aa..df294e0ab 100644 --- a/port/flatpak/org.themoonpeople.Chronicle.yml +++ b/port/flatpak/org.themoonpeople.Chronicle.yml @@ -99,3 +99,7 @@ modules: url: https://raw.githubusercontent.com/nothings/stb/2c980bb59875b0d32144a71867fbdebb2f77cd20/stb_truetype.h sha256: ecd30b05e0dd4fea3a13c26810dd9e1992dc379049482c393d5a19e6b5090aab dest: stb_truetype + - type: file + url: https://raw.githubusercontent.com/nothings/stb/2c980bb59875b0d32144a71867fbdebb2f77cd20/stb_image.h + sha256: 594c2fe35d49488b4382dbfaec8f98366defca819d916ac95becf3e75f4200b3 + dest: stb_truetype diff --git a/port/glyphs/glyphs.json b/port/glyphs/glyphs.json new file mode 100644 index 000000000..1a6db4efa --- /dev/null +++ b/port/glyphs/glyphs.json @@ -0,0 +1,1086 @@ +{ + "reference": 64, + "styles": { + "keyboard": { + "atlas": "keyboard.png", + "glyphs": { + "'": [ + 551, + 2, + 69, + 69 + ], + "-": [ + 624, + 2, + 69, + 69 + ], + "/": [ + 697, + 2, + 69, + 69 + ], + "0": [ + 770, + 2, + 69, + 69 + ], + "1": [ + 843, + 2, + 69, + 69 + ], + "2": [ + 916, + 2, + 69, + 69 + ], + "3": [ + 2, + 91, + 69, + 69 + ], + "5": [ + 75, + 91, + 69, + 69 + ], + "6": [ + 148, + 91, + 69, + 69 + ], + "7": [ + 221, + 91, + 69, + 69 + ], + "8": [ + 294, + 91, + 69, + 69 + ], + "9": [ + 367, + 91, + 69, + 69 + ], + ";": [ + 440, + 91, + 69, + 69 + ], + "=": [ + 513, + 91, + 69, + 69 + ], + "[": [ + 586, + 91, + 69, + 69 + ], + "]": [ + 659, + 91, + 69, + 69 + ], + "a": [ + 732, + 91, + 69, + 69 + ], + "b": [ + 805, + 91, + 69, + 69 + ], + "backspace": [ + 780, + 383, + 80, + 49 + ], + "c": [ + 878, + 91, + 69, + 69 + ], + "capslock": [ + 864, + 383, + 80, + 49 + ], + "d": [ + 951, + 91, + 69, + 69 + ], + "delete": [ + 2, + 164, + 69, + 69 + ], + "down": [ + 75, + 164, + 69, + 69 + ], + "dpad": [ + 513, + 383, + 85, + 56 + ], + "dpad_lr": [ + 679, + 456, + 85, + 41 + ], + "dpad_ud": [ + 2, + 2, + 41, + 85 + ], + "e": [ + 148, + 164, + 69, + 69 + ], + "end": [ + 221, + 164, + 69, + 69 + ], + "escape": [ + 294, + 164, + 69, + 69 + ], + "f": [ + 367, + 164, + 69, + 69 + ], + "f1": [ + 440, + 164, + 69, + 69 + ], + "f10": [ + 513, + 164, + 69, + 69 + ], + "f11": [ + 586, + 164, + 69, + 69 + ], + "f12": [ + 659, + 164, + 69, + 69 + ], + "f2": [ + 732, + 164, + 69, + 69 + ], + "f3": [ + 805, + 164, + 69, + 69 + ], + "f4": [ + 878, + 164, + 69, + 69 + ], + "f5": [ + 951, + 164, + 69, + 69 + ], + "f6": [ + 2, + 237, + 69, + 69 + ], + "f7": [ + 75, + 237, + 69, + 69 + ], + "f8": [ + 148, + 237, + 69, + 69 + ], + "f9": [ + 221, + 237, + 69, + 69 + ], + "g": [ + 294, + 237, + 69, + 69 + ], + "grave": [ + 367, + 237, + 69, + 69 + ], + "h": [ + 440, + 237, + 69, + 69 + ], + "home": [ + 513, + 237, + 69, + 69 + ], + "i": [ + 586, + 237, + 69, + 69 + ], + "insert": [ + 659, + 237, + 69, + 69 + ], + "j": [ + 732, + 237, + 69, + 69 + ], + "k": [ + 805, + 237, + 69, + 69 + ], + "keypad*": [ + 878, + 237, + 69, + 69 + ], + "keypadenter": [ + 401, + 2, + 71, + 71 + ], + "l": [ + 951, + 237, + 69, + 69 + ], + "left": [ + 2, + 310, + 69, + 69 + ], + "leftalt": [ + 2, + 456, + 80, + 49 + ], + "leftctrl": [ + 86, + 456, + 80, + 49 + ], + "leftshift": [ + 170, + 456, + 80, + 49 + ], + "lstick": [ + 602, + 383, + 85, + 56 + ], + "m": [ + 75, + 310, + 69, + 69 + ], + "mouse1": [ + 47, + 2, + 51, + 85 + ], + "mouse2": [ + 102, + 2, + 51, + 85 + ], + "mouse3": [ + 157, + 2, + 51, + 85 + ], + "mouse4": [ + 212, + 2, + 51, + 85 + ], + "mouse5": [ + 267, + 2, + 51, + 85 + ], + "mouse_xy": [ + 322, + 2, + 75, + 83 + ], + "n": [ + 148, + 310, + 69, + 69 + ], + "numlock": [ + 221, + 310, + 69, + 69 + ], + "o": [ + 294, + 310, + 69, + 69 + ], + "p": [ + 367, + 310, + 69, + 69 + ], + "pagedown": [ + 440, + 310, + 69, + 69 + ], + "pageup": [ + 513, + 310, + 69, + 69 + ], + "printscreen": [ + 586, + 310, + 69, + 69 + ], + "q": [ + 659, + 310, + 69, + 69 + ], + "r": [ + 732, + 310, + 69, + 69 + ], + "return": [ + 476, + 2, + 71, + 71 + ], + "right": [ + 805, + 310, + 69, + 69 + ], + "rightalt": [ + 254, + 456, + 80, + 49 + ], + "rightctrl": [ + 338, + 456, + 80, + 49 + ], + "rightshift": [ + 422, + 456, + 80, + 49 + ], + "rstick": [ + 691, + 383, + 85, + 56 + ], + "s": [ + 878, + 310, + 69, + 69 + ], + "space": [ + 590, + 456, + 85, + 45 + ], + "t": [ + 951, + 310, + 69, + 69 + ], + "tab": [ + 506, + 456, + 80, + 49 + ], + "u": [ + 2, + 383, + 69, + 69 + ], + "up": [ + 75, + 383, + 69, + 69 + ], + "v": [ + 148, + 383, + 69, + 69 + ], + "w": [ + 221, + 383, + 69, + 69 + ], + "x": [ + 294, + 383, + 69, + 69 + ], + "y": [ + 367, + 383, + 69, + 69 + ], + "z": [ + 440, + 383, + 69, + 69 + ] + } + }, + "ps4": { + "atlas": "ps4.png", + "glyphs": { + "circle": [ + 889, + 2, + 64, + 64 + ], + "cross": [ + 957, + 2, + 64, + 64 + ], + "down": [ + 170, + 2, + 77, + 77 + ], + "dpad": [ + 251, + 2, + 77, + 77 + ], + "dpad_lr": [ + 332, + 2, + 77, + 77 + ], + "dpad_ud": [ + 413, + 2, + 77, + 77 + ], + "l1": [ + 138, + 91, + 85, + 54 + ], + "l2": [ + 2, + 2, + 80, + 85 + ], + "l3": [ + 316, + 91, + 53, + 53 + ], + "left": [ + 494, + 2, + 77, + 77 + ], + "lstick": [ + 373, + 91, + 53, + 53 + ], + "r1": [ + 227, + 91, + 85, + 54 + ], + "r2": [ + 86, + 2, + 80, + 85 + ], + "r3": [ + 430, + 91, + 53, + 53 + ], + "right": [ + 575, + 2, + 77, + 77 + ], + "rstick": [ + 487, + 91, + 53, + 53 + ], + "select": [ + 737, + 2, + 63, + 65 + ], + "square": [ + 2, + 91, + 64, + 64 + ], + "start": [ + 804, + 2, + 81, + 65 + ], + "triangle": [ + 70, + 91, + 64, + 64 + ], + "up": [ + 656, + 2, + 77, + 77 + ] + } + }, + "ps5": { + "atlas": "ps5.png", + "glyphs": { + "circle": [ + 689, + 109, + 64, + 64 + ], + "cross": [ + 757, + 109, + 64, + 64 + ], + "down": [ + 108, + 2, + 102, + 102 + ], + "dpad": [ + 214, + 2, + 102, + 102 + ], + "dpad_lr": [ + 320, + 2, + 102, + 102 + ], + "dpad_ud": [ + 426, + 2, + 102, + 102 + ], + "l1": [ + 185, + 109, + 102, + 76 + ], + "l2": [ + 2, + 2, + 102, + 103 + ], + "l3": [ + 397, + 109, + 69, + 68 + ], + "left": [ + 532, + 2, + 102, + 102 + ], + "lstick": [ + 470, + 109, + 69, + 68 + ], + "r1": [ + 291, + 109, + 102, + 76 + ], + "r2": [ + 638, + 2, + 101, + 102 + ], + "r3": [ + 543, + 109, + 69, + 68 + ], + "right": [ + 743, + 2, + 102, + 102 + ], + "rstick": [ + 616, + 109, + 69, + 68 + ], + "select": [ + 2, + 109, + 79, + 80 + ], + "square": [ + 825, + 109, + 64, + 64 + ], + "start": [ + 85, + 109, + 96, + 80 + ], + "triangle": [ + 893, + 109, + 64, + 64 + ], + "up": [ + 849, + 2, + 102, + 102 + ] + } + }, + "switch": { + "atlas": "switch.png", + "glyphs": { + "circle": [ + 73, + 90, + 64, + 64 + ], + "cross": [ + 141, + 90, + 64, + 64 + ], + "down": [ + 433, + 2, + 75, + 75 + ], + "dpad": [ + 354, + 2, + 75, + 79 + ], + "dpad_lr": [ + 512, + 2, + 75, + 75 + ], + "dpad_ud": [ + 591, + 2, + 75, + 75 + ], + "l1": [ + 481, + 90, + 69, + 56 + ], + "l2": [ + 670, + 2, + 74, + 75 + ], + "l3": [ + 2, + 2, + 84, + 84 + ], + "left": [ + 748, + 2, + 75, + 75 + ], + "lstick": [ + 90, + 2, + 84, + 84 + ], + "r1": [ + 554, + 90, + 69, + 56 + ], + "r2": [ + 2, + 90, + 67, + 69 + ], + "r3": [ + 178, + 2, + 84, + 84 + ], + "right": [ + 827, + 2, + 75, + 75 + ], + "rstick": [ + 266, + 2, + 84, + 84 + ], + "select": [ + 209, + 90, + 64, + 64 + ], + "square": [ + 277, + 90, + 64, + 64 + ], + "start": [ + 345, + 90, + 64, + 64 + ], + "triangle": [ + 413, + 90, + 64, + 64 + ], + "up": [ + 906, + 2, + 75, + 75 + ] + } + }, + "xbox": { + "atlas": "xbox.png", + "glyphs": { + "circle": [ + 909, + 2, + 64, + 64 + ], + "cross": [ + 2, + 87, + 64, + 64 + ], + "down": [ + 2, + 2, + 80, + 81 + ], + "dpad": [ + 86, + 2, + 80, + 81 + ], + "dpad_lr": [ + 338, + 2, + 81, + 80 + ], + "dpad_ud": [ + 170, + 2, + 80, + 81 + ], + "l1": [ + 342, + 87, + 85, + 56 + ], + "l2": [ + 423, + 2, + 67, + 80 + ], + "l3": [ + 565, + 2, + 85, + 79 + ], + "left": [ + 654, + 2, + 79, + 79 + ], + "lstick": [ + 520, + 87, + 53, + 53 + ], + "r1": [ + 431, + 87, + 85, + 56 + ], + "r2": [ + 494, + 2, + 67, + 80 + ], + "r3": [ + 737, + 2, + 85, + 79 + ], + "right": [ + 826, + 2, + 79, + 79 + ], + "rstick": [ + 577, + 87, + 53, + 53 + ], + "select": [ + 70, + 87, + 64, + 64 + ], + "square": [ + 138, + 87, + 64, + 64 + ], + "start": [ + 206, + 87, + 64, + 64 + ], + "triangle": [ + 274, + 87, + 64, + 64 + ], + "up": [ + 254, + 2, + 80, + 81 + ] + } + } + } +} diff --git a/port/glyphs/keyboard.png b/port/glyphs/keyboard.png new file mode 100644 index 0000000000000000000000000000000000000000..413bf2da5cc0f3aeb44a5d289e00d6216846c1b6 GIT binary patch literal 163401 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͕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�+O;&VorY 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: every texture the game enters is written there as .png (index +// textures through their palette), for finding what the game draws where (button icons). +void DumpTexture(const char *name, const PortDecodedTexture &texture) { + static const char *dir = std::getenv("DC_TEXTURE_DUMP"); + if (dir == nullptr || texture.levels.empty()) { + return; + } + const unsigned width = texture.width; + const unsigned height = texture.height; + std::vector rgba(static_cast(width) * height * 4); + for (size_t i = 0; i < static_cast(width) * height; i++) { + if (texture.format == gfx::TextureFormat::Index8) { + uint32_t c = texture.palette[texture.levels[0][i]]; + std::memcpy(&rgba[i * 4], &c, 4); + } else { + std::memcpy(&rgba[i * 4], &texture.levels[0][i * 4], 4); + } + } + // A PNG with stored (uncompressed) deflate blocks. + auto crc = [](const uint8_t *data, size_t size, uint32_t crc_in) { + uint32_t c = ~crc_in; + for (size_t i = 0; i < size; i++) { + c ^= data[i]; + for (int k = 0; k < 8; k++) { + c = (c >> 1) ^ (0xEDB88320u & (0u - (c & 1))); + } + } + return ~c; + }; + std::vector out = {0x89, 'P', 'N', 'G', 0x0D, 0x0A, 0x1A, 0x0A}; + auto put32 = [&](std::vector &v, uint32_t x) { + for (int s = 24; s >= 0; s -= 8) { + v.push_back(static_cast(x >> s)); + } + }; + auto chunk = [&](const char *type, const std::vector &body) { + put32(out, static_cast(body.size())); + std::vector typed(type, type + 4); + typed.insert(typed.end(), body.begin(), body.end()); + out.insert(out.end(), typed.begin(), typed.end()); + put32(out, crc(typed.data(), typed.size(), 0)); + }; + std::vector ihdr; + put32(ihdr, width); + put32(ihdr, height); + ihdr.insert(ihdr.end(), {8, 6, 0, 0, 0}); + chunk("IHDR", ihdr); + std::vector raw; + for (unsigned y = 0; y < height; y++) { + raw.push_back(0); + raw.insert(raw.end(), rgba.begin() + static_cast(y) * width * 4, + rgba.begin() + static_cast(y + 1) * width * 4); + } + std::vector z = {0x78, 0x01}; + uint32_t a = 1; + uint32_t b = 0; + for (uint8_t v : raw) { + a = (a + v) % 65521; + b = (b + a) % 65521; + } + for (size_t pos = 0; pos < raw.size() || pos == 0; pos += 65535) { + size_t n = std::min(65535, raw.size() - pos); + z.push_back(pos + n >= raw.size() ? 1 : 0); + z.push_back(static_cast(n)); + z.push_back(static_cast(n >> 8)); + z.push_back(static_cast(~n)); + z.push_back(static_cast((~n) >> 8)); + z.insert(z.end(), raw.begin() + pos, raw.begin() + pos + n); + } + put32(z, (b << 16) | a); + chunk("IDAT", z); + chunk("IEND", {}); + std::filesystem::create_directories(dir); + std::string path = std::string(dir) + "/" + name + ".png"; + if (FILE *file = std::fopen(path.c_str(), "wb")) { + std::fwrite(out.data(), 1, out.size(), file); + std::fclose(file); + } +} + // Ground models repeat these images across UV islands and cells. Their source edges differ, so // blend a narrow indexed border into a periodic image before uploading it. void MakeGroundPeriodic(const char *name, PortDecodedTexture &texture) { @@ -266,6 +349,7 @@ void Enter(CTextureManager &manager, EnterMode mode, int block, char *name, u_ch return; } MakeGroundPeriodic(name, decoded); + DumpTexture(name, decoded); tbp = PortCreateTexture(decoded, PortTextureOwner::Manager, &cbp); } tex->tex0 = Tex0(tbp, tbw, psm, tw, th, cbp, indexed ? 1 : 0); @@ -366,6 +450,7 @@ PC_OVERRIDE void CTextureManager::EnterFixTextureZ(u_char *buffer) { unsigned tbp = 0; unsigned cbp = 0; if (PortDecodeTexture(1, width, height, levels, 1, clut, 256, false, decoded)) { + DumpTexture(name, decoded); tbp = PortCreateTexture(decoded, PortTextureOwner::Manager, &cbp); } tex->tex0 = SCE_GS_SET_TEX0(tbp, 10, kPsmT8H, 10, 8, 1, 0, cbp, 0, 0, 0, 1); diff --git a/tools/glyphs/build_glyphs.py b/tools/glyphs/build_glyphs.py new file mode 100644 index 000000000..3ce07d83f --- /dev/null +++ b/tools/glyphs/build_glyphs.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python3 +"""Packs controller / keyboard button art into port/glyphs/ (one atlas PNG per style + glyphs.json). + + python tools/glyphs/build_glyphs.py --src "C:/path/To be used" [--out port/glyphs] + +--src holds the art as downloaded: P4Gamepad/Retro, P5Gamepad/Retro, XGamepad/Alt 2, SGamepad/Retro and +Keyboard_Mouse/Dark, one 128x128 PNG per button. Each glyph is cropped to its pixels and scaled by one +factor per style (REFERENCE source pixels become CELL atlas pixels), so the symbols keep their relative +sizes; glyphs.json gives every glyph's rectangle in its style's atlas. +""" +import argparse +import json +import os +import re +import sys + +from PIL import Image, ImageDraw + +CELL = 64 # atlas pixels a REFERENCE-pixel-wide glyph is drawn at +REFERENCE = 96 # source pixels of a round face button +ATLAS_WIDTH = 1024 +PAD = 2 # transparent border around each packed glyph + +# game button name -> file stem per gamepad style. Face buttons follow the position on the pad +# (cross is the bottom button: A on Xbox, B on Switch). +PAD_STYLES = { + "ps4": ("P4Gamepad/Retro", { + "cross": "T_P4_Cross_Color_Retro", "circle": "T_P4_Circle_Color_Retro", + "square": "T_P4_Square_Color_Retro", "triangle": "T_P4_Triangle_Color_Retro", + "l1": "T_P4_L1_Retro", "r1": "T_P4_R1_Retro", "l2": "T_P4_L2_Retro", "r2": "T_P4_R2_Retro", + "start": "T_P4_Options_Retro", "select": "T_P4_Share_Retro", + "dpad": "T_P4_Dpad_Retro", "dpad_ud": "T_P4_Dpad_Y_Retro", "dpad_lr": "T_P4_Dpad_X_Retro", + "up": "T_P4_Dpad_UP_Retro", "down": "T_P4_Dpad_Down_Retro", "left": "T_P4_Dpad_Left_Retro", + "right": "T_P4_Dpad_Right_Retro", "l3": "T_P4_L3_Retro", "r3": "T_P4_R3_Retro", + "lstick": "T_P4_L_Retro", "rstick": "T_P4_R_Retro"}), + "ps5": ("P5Gamepad/Retro", { + "cross": "T_P5_Cross_Color_Retro", "circle": "T_P5_Circle_Color_Retro", + "square": "T_P5_Square_Color_Retro", "triangle": "T_P5_Triangle_Color_Retro", + "l1": "T_P5_L1_Retro", "r1": "T_P5_R1_Retro", "l2": "T_P5_L2_Retro", "r2": "T_P5_R2_Retro", + "start": "T_P5_Options_Retro", "select": "T_P5_Share_Retro", + "dpad": "T_P5_Dpad_Retro", "dpad_ud": "T_P5_Dpad_Y_Retro", "dpad_lr": "T_P5_Dpad_X_Retro", + "up": "T_P5_Dpad_UP_Retro", "down": "T_P5_Dpad_Down_Retro", "left": "T_P5_Dpad_Left_Retro", + "right": "T_P5_Dpad_Right_Retro", "l3": "T_P5_L3_Retro", "r3": "T_P5_R3_Retro", + "lstick": "T_P5_L_Retro", "rstick": "T_P5_R_Retro"}), + "xbox": ("XGamepad/Alt 2", { + "cross": "T_X_A_Color_Alt_2", "circle": "T_X_B_Color_Alt_2", + "square": "T_X_X_Color_Alt_2", "triangle": "T_X_Y_Color_Alt_2", + "l1": "T_X_LB_Alt_2", "r1": "T_X_RB_Alt_2", "l2": "T_X_LT_Alt_2", "r2": "T_X_RT_Alt_2", + "start": "@xbox_menu", "select": "T_X_Share_Alt_2", + "dpad": "T_X_Dpad_Alt_2", "dpad_ud": "T_X_Dpad_Y_Alt_2", "dpad_lr": "T_X_Dpad_X_Alt_2", + "up": "T_X_Dpad_Up_Alt_2", "down": "T_X_Dpad_Down_Alt_2", "left": "T_X_Dpad_Left_Alt_2", + "right": "T_X_Dpad_Right_Alt_2", "l3": "T_X_Left_Stick_Click_Alt_2", "r3": "T_X_Right_Stick_Click_Alt_2", + "lstick": "T_X_L_Alt_2", "rstick": "T_X_R_Alt_2"}), + "switch": ("SGamepad/Retro", { + "cross": "T_S_B_Retro", "circle": "T_S_A_Retro", "square": "T_S_Y_Retro", "triangle": "T_S_X_Retro", + "l1": "T_S_LB_Retro", "r1": "T_S_RB_Retro", "l2": "T_S_LT_Retro", "r2": "T_S_RT_Retro", + "start": "T_S_Plus_Retro", "select": "T_S_Minus_Retro", + "dpad": "T_S_Dpad_Retro", "dpad_ud": "T_S_Dpad_Y_Retro", "dpad_lr": "T_S_Dpad_X_Retro", + "up": "T_S_Dpad_Up_Retro", "down": "T_S_Dpad_Down_Retro", "left": "T_S_Dpad_Left_Retro", + "right": "T_S_Dpad_Right_Retro", "l3": "T_S_L_Retro", "r3": "T_S_R_Retro", + "lstick": "T_S_L_Retro", "rstick": "T_S_R_Retro"}), +} + +# Keyboard file stem (between T_ and _Key_Dark) -> the names the game looks keys up by: SDL's scancode +# name, lower-case, spaces dropped (input.hpp, InputPrimaryBindingName), or mouse1..mouse5. +KEY_ALIASES = { + "Esc": ["escape"], "BackSpace": ["backspace"], "Backspace_Alt": [], "Enter": ["return", "keypadenter"], + "Enter_Alt": [], "Enter_Tall": [], "Space": ["space"], "Tab": ["tab"], "Shift": ["leftshift", "rightshift"], + "Crtl": ["leftctrl", "rightctrl"], "Alt": ["leftalt", "rightalt"], "CapsLock": ["capslock"], + "Tilde": ["grave"], "Del": ["delete"], "Ins": ["insert"], "Home": ["home"], "End": ["end"], + "PageUp": ["pageup"], "PageDown": ["pagedown"], "PrtScrn": ["printscreen"], "NumLock": ["numlock"], + "Up": ["up"], "Down": ["down"], "Left": ["left"], "Right": ["right"], "Minus": ["-"], "Plus": ["="], + "Asterisk": ["keypad*"], "Brackets_L": ["["], "Brackets_R": ["]"], "Semicolon": [";"], "Slash": ["/"], + "Quotation": ["'"], "Question_Mark": [], "Cursor": [], + "Mouse_Left": ["mouse1"], "Mouse_Right": ["mouse2"], "Mouse_Middle": ["mouse3"], + "Mouse_Simple": ["mouse4", "mouse5"], "Mouse_XY": ["mouse_xy"], + "Mouse_Scroll_Down": ["wheeldown"], "Mouse_Scroll_Up": ["wheelup"], "Mouse_Scroll": ["wheel"], +} + + +def load(path): + return Image.open(path).convert("RGBA") + + +def make_xbox_menu(src_dir): + """The art has no Xbox Menu button: the View button with its icon swapped for three bars.""" + view = load(os.path.join(src_dir, "T_X_Share_Alt_2.png")) + px = view.load() + body = px[28, 64] if px[28, 64][3] > 200 else (20, 18, 28, 255) + out = view.copy() + d = ImageDraw.Draw(out) + d.ellipse((22, 22, 106, 106), fill=body) # wipe the icon, keep the rim + for y in (50, 64, 78): + d.rounded_rectangle((42, y - 3, 86, y + 3), radius=3, fill=(255, 255, 255, 255)) + return out + + +def pack(images): + """Shelf-packs {name: RGBA image}, each cropped to its pixels and scaled by CELL/REFERENCE. + Returns (atlas, {name: [x, y, w, h]}).""" + scale = CELL / REFERENCE + crops = {} + for name, im in images.items(): + box = im.getchannel("A").getbbox() + if box is None: + continue + c = im.crop(box) + w = max(1, round(c.width * scale)) + h = max(1, round(c.height * scale)) + if w > ATLAS_WIDTH // 2: + h = max(1, round(h * (ATLAS_WIDTH // 2) / w)) + w = ATLAS_WIDTH // 2 + # resize in premultiplied space so transparent pixels do not bleed their colour in + c = c.convert("RGBa").resize((w, h), Image.LANCZOS).convert("RGBA") + crops[name] = c + order = sorted(crops, key=lambda n: (-crops[n].height, n)) + x = y = row_h = 0 + placed = {} + for name in order: + c = crops[name] + if x + c.width + 2 * PAD > ATLAS_WIDTH: + x, y, row_h = 0, y + row_h, 0 + placed[name] = (x + PAD, y + PAD) + x += c.width + 2 * PAD + row_h = max(row_h, c.height + 2 * PAD) + height = 1 + while height < y + row_h: + height *= 2 + atlas = Image.new("RGBA", (ATLAS_WIDTH, height), (0, 0, 0, 0)) + rects = {} + for name, (px, py) in placed.items(): + atlas.paste(crops[name], (px, py)) + rects[name] = [px, py, crops[name].width, crops[name].height] + return atlas, rects + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("--src", required=True) + ap.add_argument("--out", default=os.path.join(os.path.dirname(__file__), "..", "..", "port", "glyphs")) + args = ap.parse_args() + os.makedirs(args.out, exist_ok=True) + index = {"reference": CELL, "styles": {}} + missing = [] + + for style, (folder, table) in PAD_STYLES.items(): + src_dir = os.path.join(args.src, folder) + images = {} + for name, stem in table.items(): + if stem == "@xbox_menu": + images[name] = make_xbox_menu(src_dir) + continue + path = os.path.join(src_dir, stem + ".png") + if os.path.exists(path): + images[name] = load(path) + else: + missing.append(path) + atlas, rects = pack(images) + atlas.save(os.path.join(args.out, style + ".png"), optimize=True) + index["styles"][style] = {"atlas": style + ".png", "glyphs": rects} + + key_dir = os.path.join(args.src, "Keyboard_Mouse", "Dark") + images = {} + for file in sorted(os.listdir(key_dir)): + m = re.fullmatch(r"T_(.+?)_Key(?:_Dark)?(?:-\d+)?\.png", file) + if not m or re.search(r"-\d+\.png$", file): + continue + stem = m.group(1) + if stem in KEY_ALIASES: + names = KEY_ALIASES[stem] + elif re.fullmatch(r"[A-Z0-9]|F\d{1,2}", stem): + names = [stem.lower()] + else: + continue + for n in names: + images[n] = load(os.path.join(key_dir, file)) + # the pad's directional symbols as clusters of the keys that are bound to them + def cluster(layout, keys): + """layout: list of rows, each a list of key names or None (a gap); keys are drawn 48 px wide.""" + size = 56 + cols = max(len(r) for r in layout) + canvas = Image.new("RGBA", (cols * size, len(layout) * size), (0, 0, 0, 0)) + for ry, row in enumerate(layout): + offset = (cols - len(row)) * size // 2 + for cx, k in enumerate(row): + if k is None or k not in keys: + continue + im = keys[k] + box = im.getchannel("A").getbbox() + im = im.crop(box) + f = (size - 4) / max(im.width, im.height) + im = im.resize((max(1, round(im.width * f)), max(1, round(im.height * f))), Image.LANCZOS) + canvas.alpha_composite(im, (offset + cx * size + (size - im.width) // 2, + ry * size + (size - im.height) // 2)) + # the cluster is drawn about as big as one round button: scale it up to REFERENCE + scale = REFERENCE * 1.35 / max(canvas.width, canvas.height) + return canvas.resize((round(canvas.width * scale), round(canvas.height * scale)), Image.LANCZOS) + images["dpad"] = cluster([[None, "up", None], ["left", "down", "right"]], images) + images["dpad_ud"] = cluster([["up"], ["down"]], images) + images["dpad_lr"] = cluster([["left", "right"]], images) + images["lstick"] = cluster([[None, "w", None], ["a", "s", "d"]], images) + images["rstick"] = cluster([[None, "i", None], ["j", "k", "l"]], images) + images.pop("cursor", None) + atlas, rects = pack(images) + atlas.save(os.path.join(args.out, "keyboard.png"), optimize=True) + index["styles"]["keyboard"] = {"atlas": "keyboard.png", "glyphs": rects} + + with open(os.path.join(args.out, "glyphs.json"), "w", encoding="utf-8") as f: + json.dump(index, f, indent=1, sort_keys=True) + f.write("\n") + for m in missing: + print("missing:", m, file=sys.stderr) + for style, data in index["styles"].items(): + print(style, len(data["glyphs"]), "glyphs") + return 1 if missing else 0 + + +if __name__ == "__main__": + sys.exit(main()) From 272646bd0661a3c33822ba79dba3080982cadbf4 Mon Sep 17 00:00:00 2001 From: Jeffie Date: Wed, 7 Oct 2026 21:10:42 -0500 Subject: [PATCH 17/20] Localization: the message font and the language files work out of the box The build copies tools/font/DarkCloudCompendium.ttf, which the repository already holds, to lang/font.ttf beside the executable, so message text is drawn from it as built (Dayuppy confirmed on 2026-10-06 that the community font may be used here; credited in CREDITS and tools/font/README.md). Its accented letters, cedilla, inverted marks and oe are blank in the file and are built from its own glyphs, which covers every language the game has. --font FILE names another font, a font in the save folder's lang/ wins over the bundled one, and a missing font is said once instead of silently falling back to the bitmaps. The language JSON is written on the first start with the game data in place; docs/LOCALIZATION.md and the README now start with that quick start. The font tests run on the repository's own font, so they no longer skip. Co-Authored-By: Claude Sonnet 5.5 --- CREDITS | 4 ++++ README.md | 6 ++++++ docs/LOCALIZATION.md | 18 +++++++++++++++++- port/CMakeLists.txt | 5 ++++- port/src/clsmes.cpp | 5 +++++ port/src/localize.cpp | 17 +++++++++++++++++ port/src/localize.hpp | 4 ++++ port/src/main.cpp | 7 +++++++ port/src/tests/ttffont_test.cpp | 10 +++++++--- tools/font/README.md | 12 ++++++++++++ 10 files changed, 83 insertions(+), 5 deletions(-) create mode 100644 tools/font/README.md diff --git a/CREDITS b/CREDITS index 5be13f022..bc39e89c3 100644 --- a/CREDITS +++ b/CREDITS @@ -9,3 +9,7 @@ drkm9743 [Artists] Carbon + +[Message font] +Dayuppy +Moonbunny diff --git a/README.md b/README.md index ffddb58b3..4bc7f82f4 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,12 @@ and `JOBS=N` controls parallelism on every platform. `chronicle.flatpak`; `docs/FLATPAK.md` covers installing it. It needs your own PAL disc: the first start asks for the disc image and extracts the game's files from it. +### Translations and the message font + +Game text can come from JSON language files (one per language, laid out as Minecraft's are) and be drawn from a +TrueType font so it stays sharp at any window size. The first start writes the language files from your own +game data; the Dark Cloud community font is bundled. See [docs/LOCALIZATION.md](docs/LOCALIZATION.md). + ## Repository layout - `ps2/src`, `ps2/include`: matching game sources and headers. diff --git a/docs/LOCALIZATION.md b/docs/LOCALIZATION.md index 0e8780fca..d39e3fdaa 100644 --- a/docs/LOCALIZATION.md +++ b/docs/LOCALIZATION.md @@ -8,6 +8,21 @@ extracted data (below), and the files the port distributes hold only text writte The files are laid out as Minecraft's language files are: one flat JSON file per language, named `language_COUNTRY.json`, each an object of `"dotted.key": "text"`, with the same keys in every language. +## Quick start + +1. **The language files.** Build the game and start it once with the game data in place. The first start writes + every language's JSON to `/lang-export/` (`en_gb.json`, `fr_fr.json`...; the save folder is + `--save`, else `./save`). They come from your own disc, so the build cannot make them and the repository + does not hold them. To write them again, or anywhere else: + `darkcloud --data --export-text `. Copy the file you want to change to + `/lang/` and edit it. +2. **Sharp text.** The port draws message text from a TrueType font instead of the game's blurry bitmaps. The + Dark Cloud Compendium Community Font (by Dayuppy and Moonbunny, used with their permission) is in + `tools/font/`, and the build copies it to `lang/font.ttf` beside the executable, so it works as built. To use another font, put it at `/lang/font.ttf` (that wins), or start with + `--font `; delete `lang/font.ttf` to go back to the game's bitmaps. The port says + `font: message text from ...` when it found one, and `font: no font.ttf ...` when it did not. +3. **The language.** Options, Game page, Language row (or `game.language` in `config.json`). + ## Where the files go One file per language, named by the language the game is set to: @@ -245,7 +260,8 @@ letters and marks. The port logs `font: message text from ` when it loads the like are textures, and stay as they are. - A word keeps the cells the game gave it: its letters are spaced by the font inside the word and the word is centred on its cells, so rows, indents and the icons between words stay where the game put them. -- The font is not shipped with the port: each user adds a file they have the right to use. +- The bundled font is the community font above (credited in `CREDITS`, see `tools/font/README.md`); a font you add + is yours to license. ## Limits, and what happens when they are passed diff --git a/port/CMakeLists.txt b/port/CMakeLists.txt index a04bbf2d1..519ea0347 100644 --- a/port/CMakeLists.txt +++ b/port/CMakeLists.txt @@ -288,9 +288,12 @@ install(TARGETS darkcloud RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) # game reads its language files. add_custom_target(dc_lang COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/lang $/lang - COMMENT "Copying the port's translated strings") + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/tools/font/DarkCloudCompendium.ttf + $/lang/font.ttf + COMMENT "Copying the port's translated strings and message font") add_dependencies(darkcloud dc_lang) install(DIRECTORY lang/ DESTINATION ${CMAKE_INSTALL_BINDIR}/lang) +install(FILES ${CMAKE_SOURCE_DIR}/tools/font/DarkCloudCompendium.ttf DESTINATION ${CMAKE_INSTALL_BINDIR}/lang RENAME font.ttf) # The button symbols (docs/GLYPHS.md): glyphs/ beside the executable. Rebuilt from art by tools/glyphs/build_glyphs.py. add_custom_target(dc_glyphs diff --git a/port/src/clsmes.cpp b/port/src/clsmes.cpp index 9f77dc20b..7db25b7b8 100644 --- a/port/src/clsmes.cpp +++ b/port/src/clsmes.cpp @@ -39,6 +39,11 @@ bool TtfReady() { std::filesystem::path file = LocalizeFindFile("font.ttf"); if (!file.empty() && ttffont::Load(file)) { std::fprintf(stderr, "font: message text from %s\n", file.string().c_str()); + } else if (file.empty()) { + // The community font ships in lang/; this is someone who removed it, so say where one goes. + std::fprintf(stderr, + "font: no font.ttf in lang/ beside the executable or in the save folder, and no --font; message " + "text uses the game's bitmaps (docs/LOCALIZATION.md)\n"); } } return ttffont::Loaded(); diff --git a/port/src/localize.cpp b/port/src/localize.cpp index 4ab849b06..273227763 100644 --- a/port/src/localize.cpp +++ b/port/src/localize.cpp @@ -645,7 +645,24 @@ std::string LocalizeFormat(std::string text, std::initializer_list return text; } +namespace { + +fs::path g_font_path; + +} // namespace + +void LocalizeSetFontPath(const std::filesystem::path &file) { + g_font_path = file; +} + std::filesystem::path LocalizeFindFile(std::string_view name) { + if (name == "font.ttf" && !g_font_path.empty()) { + std::error_code error; + if (fs::is_regular_file(g_font_path, error)) { + return g_font_path; + } + std::fprintf(stderr, "font: --font %s is not a file\n", PathsDisplay(g_font_path).c_str()); + } std::vector directories = Directories(); for (auto directory = directories.rbegin(); directory != directories.rend(); ++directory) { fs::path file = *directory / std::string(name); diff --git a/port/src/localize.hpp b/port/src/localize.hpp index 2301e100d..57bf526e2 100644 --- a/port/src/localize.hpp +++ b/port/src/localize.hpp @@ -100,6 +100,10 @@ const u_int *LocalizePack(const u_int *pack, std::string_view name, const u_int // read; verify counts, on stderr, texts that do not encode back to the retail codes. int LocalizeExport(const std::filesystem::path &out, bool verify); +// A font file named on the command line (--font): LocalizeFindFile("font.ttf") gives it before the language +// folders' own. +void LocalizeSetFontPath(const std::filesystem::path &file); + // The file called name in the language folders (the save folder's before the executable's), or an // empty path where neither has one. std::filesystem::path LocalizeFindFile(std::string_view name); diff --git a/port/src/main.cpp b/port/src/main.cpp index d94efffbf..7cc150261 100644 --- a/port/src/main.cpp +++ b/port/src/main.cpp @@ -59,6 +59,7 @@ struct Options { bool show_fps = false; bool screenshot_fps = false; const char *export_text = nullptr; + const char *font = nullptr; }; Options g_options; @@ -84,6 +85,7 @@ Options g_options; " FPS counter over it\n" " --display-per-tick N headless: also render N interpolated display frames per tick\n" " --export-text DIR write the game's message files to DIR as language JSON (docs/LOCALIZATION.md)\n" + " --font FILE draw message text from this TrueType font (else lang/font.ttf)\n" " --show-fps draw the FPS counter on presented frames when headless too\n" "test hooks:\n" " --jump MODE[:MAP] start in edit:, dungeon:<0-6>, title, rush, opening or menu,\n" @@ -132,6 +134,8 @@ Options ParseOptions(int argc, const char **argv) { options.fast_load = true; } else if (arg == "--export-text") { options.export_text = value(); + } else if (arg == "--font") { + options.font = value(); } else if (arg == "--show-fps") { options.show_fps = true; } else if (arg == "--screenshot-fps") { @@ -365,6 +369,9 @@ int Run(int argc, const char **argv) { const char *fast_load = std::getenv("DC_FAST_LOAD"); GameSetFastLoad(options.fast_load || (fast_load != nullptr && *fast_load != '\0' && *fast_load != '0')); RequireData(); + if (options.font != nullptr) { + LocalizeSetFontPath(PathsFromUtf8(options.font)); + } if (options.export_text == nullptr) { // Once per save folder: the extracted data's text as JSON for translators (docs/LOCALIZATION.md). std::filesystem::path seed = PathsSaveRoot() / "lang-export"; diff --git a/port/src/tests/ttffont_test.cpp b/port/src/tests/ttffont_test.cpp index 243db74e6..b5ea09cae 100644 --- a/port/src/tests/ttffont_test.cpp +++ b/port/src/tests/ttffont_test.cpp @@ -7,8 +7,8 @@ #include "platform/ttffont.hpp" -// The tests that need a font read one from DC_TEST_FONT (a TrueType file); without it they skip, as no -// font is shipped with the port. DC_TEST_FONT_DUMP=1 also prints each glyph as text. +// The tests run on the font the repository holds (tools/font/DarkCloudCompendium.ttf), or the one DC_TEST_FONT names. +// DC_TEST_FONT_DUMP=1 also prints each glyph as text. namespace { @@ -22,7 +22,11 @@ struct Rendered { bool LoadTestFont() { const char *path = std::getenv("DC_TEST_FONT"); - return path != nullptr && *path != '\0' && ttffont::Load(path); + if (path != nullptr && *path != '\0') { + return ttffont::Load(path); + } + return ttffont::Load(std::filesystem::path(__FILE__).parent_path().parent_path().parent_path().parent_path() / "tools" / "font" / + "DarkCloudCompendium.ttf"); } Rendered Render(char32_t ch, int em_px = 40) { diff --git a/tools/font/README.md b/tools/font/README.md new file mode 100644 index 000000000..840b53731 --- /dev/null +++ b/tools/font/README.md @@ -0,0 +1,12 @@ +# DarkCloudCompendium.ttf + +The Dark Cloud Compendium Community Font ("Dark Cloud Speech", version 001.000), by Dayuppy and Moonbunny, +copyright (c) 2020 Dayuppy & Moonbunny. It was released to the community to use as they wish, and Dayuppy +confirmed on 2026-10-06 that it may be used in this port. + +It draws the FPS counter (`gen_overlay_font.py`) and the game's message text (docs/LOCALIZATION.md): the build +copies it to `lang/font.ttf` beside the executable. Its accented letters, the cedilla, `¡`, `¿`, `œ` and `Œ` +are blank in the file; the port builds them from the font's own letters and marks when it draws. + +To use another font for message text, put it at `lang/font.ttf` in the save folder (that wins over this +one) or start the game with `--font `. From aedcaee20419ed88f0d10dbbbe99df1a39ba3a1b Mon Sep 17 00:00:00 2001 From: Jeffie Date: Wed, 7 Oct 2026 21:16:52 -0500 Subject: [PATCH 18/20] docs: refer to LOCALIZATION.md in backticks, as the other docs are Doxygen reads only README.md and docs/PC.md, so a markdown link to docs/LOCALIZATION.md cannot resolve and its warning fails the documentation build. The rest of the README and PC.md name other docs in backticks; do the same. --- README.md | 2 +- docs/PC.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4bc7f82f4..c89b7441c 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ start asks for the disc image and extracts the game's files from it. Game text can come from JSON language files (one per language, laid out as Minecraft's are) and be drawn from a TrueType font so it stays sharp at any window size. The first start writes the language files from your own -game data; the Dark Cloud community font is bundled. See [docs/LOCALIZATION.md](docs/LOCALIZATION.md). +game data; the Dark Cloud community font is bundled. See `docs/LOCALIZATION.md`. ## Repository layout diff --git a/docs/PC.md b/docs/PC.md index 280f34edd..5d8e5b4c8 100644 --- a/docs/PC.md +++ b/docs/PC.md @@ -1219,7 +1219,7 @@ the last message by id may extend beyond that range. `GameTextFile::Set` gives back -1 and leaves the file as it was where either would not hold. The text of retail's message files can be replaced from JSON, one file per language, with the -disc's text as the fallback: see [LOCALIZATION.md](LOCALIZATION.md). +disc's text as the fallback: see `docs/LOCALIZATION.md`. ## The Options screen From 084e822aad0013987308b91e4cebbaf781d5cd24 Mon Sep 17 00:00:00 2001 From: Jeffie Date: Wed, 7 Oct 2026 21:59:11 -0500 Subject: [PATCH 19/20] Button symbols: the PS5 style is drawn from the new button pack build_glyphs.py --ps5-pack rebuilds only the PS5 atlas and its glyphs.json entry from a flat folder of 64 px buttons. --- docs/GLYPHS.md | 4 + port/glyphs/glyphs.json | 138 +++++++++++++++++------------------ port/glyphs/ps5.png | Bin 83206 -> 50248 bytes tools/glyphs/build_glyphs.py | 50 ++++++++++++- 4 files changed, 119 insertions(+), 73 deletions(-) diff --git a/docs/GLYPHS.md b/docs/GLYPHS.md index d5301643c..d2a132871 100644 --- a/docs/GLYPHS.md +++ b/docs/GLYPHS.md @@ -33,3 +33,7 @@ drawn scaled so a round button fills the height of its slot. `tools/glyphs/build them from the source art (P4Gamepad/Retro, P5Gamepad/Retro, XGamepad/Alt 2, SGamepad/Retro, Keyboard_Mouse/Dark); Xbox's Menu button and the keyboard style's cursor-key clusters are composed by the tool. The atlases are read with stb_image (a pinned download, as stb_truetype is). + +The PS5 style is drawn from a flat pack of 64 px buttons (`ps5_a_butt.png`, `ps5_lstick_all.png`...): +`tools/glyphs/build_glyphs.py --ps5-pack ` rebuilds only `ps5.png` and its entry in `glyphs.json`, leaving the +other styles. The pack's A, B, X and Y are Cross, Circle, Square and Triangle; L3 and R3 are its plain stick icons. diff --git a/port/glyphs/glyphs.json b/port/glyphs/glyphs.json index 1a6db4efa..8ad1ec5d4 100644 --- a/port/glyphs/glyphs.json +++ b/port/glyphs/glyphs.json @@ -693,130 +693,130 @@ "atlas": "ps5.png", "glyphs": { "circle": [ - 689, - 109, + 822, + 2, 64, 64 ], "cross": [ - 757, - 109, + 890, + 2, 64, 64 ], "down": [ - 108, 2, - 102, - 102 + 2, + 66, + 66 ], "dpad": [ - 214, + 72, 2, - 102, - 102 + 66, + 66 ], "dpad_lr": [ - 320, + 142, 2, - 102, - 102 + 66, + 66 ], "dpad_ud": [ - 426, + 212, 2, - 102, - 102 + 66, + 66 ], "l1": [ - 185, - 109, - 102, - 76 + 206, + 72, + 64, + 41 ], "l2": [ - 2, - 2, - 102, - 103 + 70, + 72, + 64, + 61 ], "l3": [ - 397, - 109, - 69, - 68 + 282, + 2, + 67, + 66 ], "left": [ - 532, + 353, 2, - 102, - 102 + 66, + 66 ], "lstick": [ - 470, - 109, - 69, - 68 + 423, + 2, + 67, + 66 ], "r1": [ - 291, - 109, - 102, - 76 + 274, + 72, + 64, + 41 ], "r2": [ - 638, - 2, - 101, - 102 + 138, + 72, + 64, + 61 ], "r3": [ - 543, - 109, - 69, - 68 + 494, + 2, + 67, + 66 ], "right": [ - 743, + 565, 2, - 102, - 102 + 66, + 66 ], "rstick": [ - 616, - 109, - 69, - 68 + 635, + 2, + 67, + 66 ], "select": [ - 2, - 109, - 79, - 80 + 342, + 72, + 64, + 37 ], "square": [ - 825, - 109, + 958, + 2, 64, 64 ], "start": [ - 85, - 109, - 96, - 80 + 706, + 2, + 42, + 66 ], "triangle": [ - 893, - 109, + 2, + 72, 64, 64 ], "up": [ - 849, + 752, 2, - 102, - 102 + 66, + 66 ] } }, diff --git a/port/glyphs/ps5.png b/port/glyphs/ps5.png index 9293f31c1139393fbe5757a456f35351f7a43511..ac50a8bd2731876b3d59b7654470032469cb8e09 100644 GIT binary patch literal 50248 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 literal 83206 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�+O;&VorY z68|Fv84wKUbW}u#-}Bzg%b(B<2vxEIZF2Ta{IGfa(1x)tVSZ9rTbtXJ6JVUdkkDsx zhE4ao@;mmn5@AS)JXmu(+a%!o);U}RD~}d@Yb8u-c2h=xpL!dgK`pc2Z?sbDe-$Mc zCJ1z^W&cwDsIhll33L53dF9jwr%Vgc)__miBwC-VX${gJMl5if4S`G2&`Ali<3ll+ zayr5j$I|mbC1e-Ed2Yz>_vV_Aq_eYgdSWhOsn^tlCN!x`52lC z`^xzkA`bj_Q5najwA!o7T~Qp4iy~TYd`#XkaHX^Qc$}}E+s_@(qd!8nG99S}MKDrL zB&NXI*>M5MOfrmJf#<;scIOd$Sy>Qa*fSOOcR}0^3!#S zP2}Qgylyw=GVi{gX!+O+ExO2;zX3POok)PdlFFh`tGw5_SG!MV+ZGg;8D*O7(F4&D z;kiKQGw14iCdUp%U@D**C;l3*5v-0JajYemK4><@l4YG|&?$HEJFw~~TIf{ICC3Hk z+LVYa@eh3Viv*e4| zg`Ff8d{sd5;is&z!eX}F^>LRT#oyAvFPe_8uJ)d=D*GT_3IH;*07Rb%#ghE`@uw@ zUyUi$NkD0D@}oIr@_^1(t_lGsX-opBEsPdIPb!IUQI6VUi9?yUiJoL?XZ5z)e|4TK7#DLxh(Vl4BF0^|8+Y507V*~_* ze^?Qm$f{f4wdSn*mj9Fv8mh)o2u9gV=7_^@uL65{^F?dKC>y_=vJl6XG}x2~@1%5?tAq^D1bmEh?KZ!CRwq!Mn58vYz+| zc3H0Uj0)_n!M6fruj%keY*$V^Sm{8iD67aWk@Qdr$o+6d{W#75M^=WptP|gQM~#NE zMW&(PG;Edc!H_t#@&$b{B5kP@di2D5$<&b2l}s-c^hL&Hj= zK<`qt5+65n=NqAhQ!?NQ2x>s8A}3lHwNb0%G+AbJh?+QFQAm|OYMq1wk)y+-ei|lK zRpnM!yR(`xz{N>WL0{|Pr}+Nu>f zJ~DU#Wk@w4?#Sj|bz+=G_7ahavgX;D|HR!BYYI+flBQ4T75!*6U_QKwc7y*(IgdSk z*Qx?!mRVWC0p0c02)U$GMKe0`=)jOUS1n>lJLYG#-fHht%YQ_!y)Szb*8uPk#28N7 zCfpozU}kEoM*;!#hS}t9r-+~GWYN=1AUWHKb9irWD0v`tg;BhTP_}s&)oGX-F{0D( z00&Np41x9;;2C&XA%)%kczd|(d_lrUD<~pTKn0jx+fCKf@I8&vNMl-Pe{6vpWLUFC zKBie&YNlvJ_TSX%9Uw@IL0YcE>bKHOyT}!Xhe?pInF_fIEMg)rhs*>OAqm z4UYN*@s*>y=NkUw18>u4Jpx-Q9QB2AJ=bfH@BNyw$`0RsSBgx1T@}zh{gr^X=Yt$` zdlrlY(b|~GVZ0{wIrA!}lMB0?09hf$r-&{%Dl9T5i3~dgsQnxOM38%=`E3`Z zp72+DIj>x{nX($f0;}y`tJ`iI|+4vTbf|?weq2y>u(9UDZ~5 zM;jKW&89f0@54^X4gXdKiFll~o99*Pfc^Bu={vfMv+>C8?$EoT}Tdo2<6I!#`jHTHAbb+z_$(85C2?VX*3C?fXmOo~rDMhf_4 zwX&>6EimDaeeIu_Ce#nmuM;|KE>@og-<&;Uoxt+MTO%2FcV6ex7eK7wCh_%pqH2PO zQWSct=fh#sWtv%RJbUvQ9ugFkqONb39u2$Kjy=hWumi6;^Gi!(0SR~aYPZtH@^W=C z@T>V(6dw;qybTNtl#x+TEFQQ+PWl*cdrvk7WNdA1XZps+3KXkf>DAB)3kj(>I*2W6 zE9l1H=#Mo=!hhN}RtKNn;cD0DFnZpp6Ejllr3`*#XhM-Lo$4*8N*H-<1Z6-PxZUC< zs3b5uf18!-oLspR=^Lu6-z##b^15!DEL03aBJ$hY=~Tpy1@5kHmq=<6jlako>5ya% zGvE|lYxE!LKT6$Q4+nPao=2Ztqf+;7x(Ha023_@iDwSHhL)MuO)?V+NDekMu>4T;C zBemkwTVzFZnpXA=7yuE zXRS>lRTAGCj5W{k*d4c#lS#_AQBZ0G%l7?y7tU^TD=Wt05`!<7myXJ0uB(kB7h1Og zs>~(0!RRchd6s-xW%X(1pU5H17_~&9oTWCTl@;|dj?LAg6&v+2&FAOmuN@uP9qsgG z8XApofH7{S7uRG(O$P%@yFQZs`nEw$EKzj zg@lEzFp8W62EoyEzd|*&zXH$#+1;>tbcLLZoFHA zC0t;XQgO6W_@Y$iZ*Qmlqpct1aK*`|w8HJi?Nr55T~s&)lA6}h$WQ(k1Ah-yp`6-R zi>m;1#bvisN-D>HZ*c(*innCn4G*}EMyP>=q%gfgS&a^QtMWh;Dcw7qB}~2=vEWVW z_83f0Dad!cf-TMAXecUB`+ENlAHpdh@S*kL-mt0W+}*rF(3-R}p(_C9-py?N?zg~8 z)Met2l`7&$p7Y-P*#RE-A{3Vda&V^x04XUc0EM}Y$&WdV(lYjK&}LN;Lr^2(>sSa( z_xJZV%ZrS>zL@;Pmfz-JjdeJb+W$dn@|YEyYc}|3f4TJm@i*t6$H~KN-@DJTg@uJ^ zKz?iB_l?u*_c?y+hmRtg>z+nC$CL9A=5w3tvsdpoxe`mUdFG$z)=2iI*JBC`)f(b0 z-D%~FY@ME6Tx0;&o|ovlfXh(&R*t3G8U{pNmJSy_A@{34M)jeMA05n>HUXC=Y@L+) zM=kr*={?awqa!1bh0~F>*eCN*?f_{E1Qo(nttOrA-Cg1GZ{K#LWJFaytYs43iYDQu z69vknHRs7YBWHG=%jer$`JY%dCauw#nd_R>KfixV7HG^A~ zt#;YypOv6G8UZ0j-{+`3NRQa8}>wy?K-DtF%Q~WC|IEp$pgg{f8Tp zbBM`s9HKcz9&;#Y(7jJ2!0^H2UAXsEm-13iN8xMX9YtBz2P1!bmeT8sz!jtNRN4G| zPK8m9GK!qr_2*uP^;WSjBwO}l8;?p311PI`Th#B6)=~hT(v!(4JxeL6ke}mT3|Vk$ zNtRkn?WlPdnYvczB=`29Y0ITpU)cPb?h! zroO`C>wL3kCH{TEYb|Ex^XJcrFJHcNC`y`CZhcAE+-JTc{N~LYaV+GJ#e)Nfu{q&N z0eZY=uID=wXT?R?3auyomAp|zWmn~;yF@BP{7R9hM_z$}j}0#zn>Z&XCS)91t~XEO z7^zWxe9J9|Nbe&Vv;PL3PrMz`t60xn((D(3#%VfNk)aG!3@Xf>bvxn%0v;? z*E%jaOd`Qov6rsf1f~?}e!cvl*+POACg*R|;yIg-<*G(ufz4G&6qg5>&zb349x0c8 z&8|&B(FaUo+5G}dop9b7+}n$s@15f4L#K5|H00D?A3JVkg?tx#d{$@B{UIULZ-%ie zTZGs_4iwtssbVPY~K93K@ZOezRdSlJ!j#r55O{^!c?>-XZIvIewj zUa>2`H^jtbzud*^a34R~!%@3w#SjCCspP>ov!psHyxU4&Z*JJ&bF19Dli)cTla+@RQ)Ee z!zSgaKkA#Uwk=`I_WClrc03Focf$B#PbB>z2u}G1K$Q%hXRIGgGJi zEM+?F#y>COq_FCDhxnK3;BiQx34Oj237Y+BHkLW98!1LcUVZebd!U|~y+2p02UNAx zB^2EoWLst~CTf5dH3fiahg$4ah%46jv!ASKDPad$qE|z&O6pE3jk?hveV7jwpGQ6e zU8_!Y25hjZ3mtQA-6B~5m%_+!?{l4cQ{9JlQZr1~&KUl+(ViR#2xvPyFSUG{!=@HL zqRpQaiLYG--kpBcP~8&Fz_x}gFNeepZFbWm&M&k__8D^sINa8dO#jHl7Yltd3l06W z*Dy0e06eN>Qk>v$7+<{)dl=Ala|sfaZ zqc-*Zp6!qEZaGaWq_oi`B(fatEexM_BiCk=3R_Od6#Q&^;0SZt44b;RxWJm4n2=PE z2b)swHxrO0Fq6D(g?O*z=YA?IY@{f8#>}Aoaq^W+A0{yyT;0qR6<+ju9AFn|CWl%% z{Tx5J_9KO!Rs5d4Y4ApO;C5T{8W_~oKuwz=$SU<6D2PalgRITtY4neTc5Dqwv0Bay z+XfZ8eN$>hE1HSaG%})W1Wkt5AD>5L^$-hl9g4I8sF~d%Xzlb=(iRry{Ndr@r3-b= zYfe-+63WU$(aTjX8ww?Pd9?nw`?XVL28|bdogA@5!FnD)lT@afM@0})ktM82?+3X@ zM7YbUt0yYvvQm;ox=c9(c8ZH`2ww^Po!p-j7EiBbk2C#bV|(48Fnpcqxp4mJ5^USw zI+T@`UVtO&|LWcg6_uEZ@pfhcG`O~^YB)E-aWnFaZjQ1REb=+wc?{VTYc2koS3Yua&4Vm%&ekxfgNiFHN%0|4W)?a*z}Hs}@lc=hZ5jnYHlt zo6i9){Zd9NE>yD6A;diL6j8Z@_Vt+Fgitb5`sLw{8X{NVF9>5ixiVTQQ zc&guvr<)@~nVLB9A_Z_um{)#-$_J>9tRB|^xwc7uy$lo7Dao{u4_`c@Zavm3Y|>`@ z5LI+wjq?X(Sw9cH9A2ECV_sffD&~yHhKGCTfUdC3NR$T;TcO03j9FZ~r$lj$@d|aT zeW5gtXqMzmy{JU*(fru8Md982eg(n~3~Zf_O4Gy(o%F8lyddJK-D&U$w#d*&E43Jx zcN&1z_~OH)bQt0xINfL8&{Vrn0Myo{vg)d;I8dU@H#NDCoNE(WrN02!M(fPqnmXz9m| z+?&qu{=J{qN%~g##>CVf9z5sHO$H&)mxi%t!1`lNZaMW*cDfA`Wh^IVl7`*fV&uxo zA5{3(a=u`n+nb-2MV^0Ps86#t3yY0>zdDgQZ+SS^hbXIPlw6qqQ(&H|l4B7#%z*l{ zj+-5aTaiaH#LFPbBk zEZ;sv<}yPNzntW%ShsNST~%Oh=>bJTdz|Os^cuB5>&j1TWbJbC;oeT@T)CSEB9Xu1 zq1P}+%af|t@bYC?Aqk;HG#d3y#ElP|@6G5fpZ1$K-Vq*qKfitdUM)?-*(BoTQd#DG zyu@Q5o~@Tck7zY>_A>?{UE9|=hnBSFN=exbzR8qxKm3^Q4i6`$Y&6U@$cdDjc(ks$$GipHC7QsS!cv5dq8F(z8EP zQ-Q5TMO&FkT<~h?jyZHPiz^B6afAilFw^o>HsQ@QGd?!Gn!($%aa9)Y45SmDxpN9? zL~vG*)JjW?(SPF;)s&^1p3-$Zskg8uf#M~?_P4>39T`wI^J-qhyt}}zT?q`lmAyMW zJn1P5I7OLv+0AhtLBKY1p+|!+w25t?uu@J^DBvWiIMcwBu$uC0=+Qr-4sOq#93AES z@qb&DwkkI`DvuZG9igCi-_6stk-hWbdCcde)s(q|Hb=*1|3S6z0gK!(UralYnsDI3 z8vUEc9z)w8S3F)KFc7|>a6ewshYC73>l4o;?K>RlP(Br-BHtA~eG z66+0$@VYUW!wBYS%;_vns-9Vh^`n^9iT+edY-DN5(ed4sSsnFtXhwscUHkda#K(c4 zu~UBgaCeVC9PNdfx@#NCrBI90L*D{v7)C+lc{^1lzkR zxuq8WTf;TCtCfgEyc48pkCzZ1KCJZ`Y?=f#UIhFSXxJ`C$^5f1v`HwzMxNOUeS>CceGFrtm)-&n}Es>0C>!x%SaHOZJBwqw&{G=GD&ylYcPsR(@lSXZK zU$DN=*7^ZZai@F#*|ecbEE!c37&3&tx3zr+Xld2GJ#^sVsjkQ~-E#XToC^Ht@83}^ z7%vt#@ZGO21-nWs?Na^vw*s#~ozP*n%Esw*b1PQ2#qWA4ity2H=T}+2ny)Y~qoR|7 zuB+MFm*>E`Nd&oe%?TpZZnjFEO}FCL#_2>r<@fKq!TM~MUvZigQ5Q!9mlBj19xqoS z=7t5%eoGyKXFXcHL_m&|(yLj&3?4NC4RbUSEtt!53n}Sp!621Xs?s!yuCDAYD?a|* z{WgX)XGH9qy55>pSWl%D*Izf%&$QZKN^30t)E6Xqtqrj?;Vq%n4ot7PPW}c!rxZAh z3(pevOaR@uf(v%*8wXNPvZsfe8^*(~(FmpMAN9|z+=V3-;}6MC*)i*$O`4XC0bOlr zk+A^qPJy$79R59~>*jr$-+pZneXsU<+Sw(6K*mtx&h8>AMs-|jdXzl|!i9xv4A52_ zE=Ly(uBRvxT)w86*8F?AtkUX?sVh+`9b~y)`4n1#b&<|a=DK;dI(_7^?ZL0C5yyTk z?XT9kY)tD|cm}xYO|)R+b?YCN;1^j81L~Wnwoc#RKoFO|`<;9iJ^-99aeH$7J`6{H z40>2Oc+2nj7szej`cAAwr<60&gBQ6UIZ@Pfd-d_-jyPvOp$_2|ByDgo|0F0?zJknk zc;&c`Gl>itoN(^LFLlDeAtWlioID9aft$?(dzQ8BE}iDz^Y2a>1p?qmyuzdtc^BR2 zDmX%;#?H}AX)!RKLm9uJRJEKn2}j~RblstL3&!rhjovDH)ON!ncGdpr^5*{GBgnu- zSJzLkU*biEdV_dUiTQ$$uEuW0#gEGAD2K@c{i3_wMFX>PhE<>KZHte_kvyq0Bs4BT zqix@V9SI6z>ojwM1&b;;C+Ywno=v~*2n+BOzxkUNp`}Iq9F!8v4p>U-z92w1FK<~) z%hN#Cqo3WcbohMi0d)V#;M!i#t$A^UyD@}xMPdCq>BN%RKFA_~Hzsg&Km4_3zT3-?!7tp50iL7rD8XI69M@sBys;jF z!1Ks6Q*Hc;Zc#BZ8s`CJ#87bj8f3J1=0E>%Yh=9fu`_NUJ=e`eENGd*CEY}hIo)D| zbzwv3yK^6PEt~jzdo!B&-m`N!iH>hzr|_+;%xXmZLTztb_P~2*V(5j(pNR?YAfLR^ z{hH^>G~R=KTP3GHwhv0hHBi@KB>r^&2YFeNR|6d=?DfFqk~^p#owTk&!(!wx=w+p~ zdkVsE;URJ~p+Gzxs>CkX8(`^GXzp%F`#o3L2-B33LWt=y-9#dSRpLmDo4+4`WuZWr zm@KC6Ru!+#h+1RO#f~_C87?MxzUb0Ar#6B9#OU;&3?mA-nryw>V2>duCr@u^XaFpCLJ&$td+Xmr4%8^!M$+?e$mJ?vQtgf@%8+%fIhnfU%s#%4%VKjA@N zFP=Zk?#v~}iikX|ueHEOZgUdCq^1+QSy5D5`%Y5bxvk%O!N2&cZJCNkp`5*ij`~`V zT%pjZ3x&O1{ZPpC7yPK8r%+b+zUKpiL#?m5ZO51I2^zt{ZwPZ17sFr|r0Xsh)OK@8 zerqwNb3Er=txt?tnUImUupS+nSG7-Z0jg`cNt?R^s2IWvS%Z_y(TRmzFom`L0Wqj% zr>!h8iN%{4awg6mV#Wc=`Ev0dr6m1CJ*mEk>oJ5Po)gDGu8l$x#J*H#b9wI9|WF zT%Q&c0&S=dL2!PDvf}NG5BIjySJJ|UzRd{O)%L7^D)z$&$4;UMr)J%ECl~(A&ntld z1Uh}waEdZANM}b!;{!I+t%9Qn$**2?S9yD*M2$7i7~n#dDKAAn7&IeXEiK(Yqr4q_ z^W;^S8)43j4mtQU#P;67VRG&G3Km}ko=$iNg3@Z;TC8iCVe_mlzTxqrA;^_wvimU@ zx04*72EGk|(pLC0_V;;vWDry}2#&rFs>9XDxxnQf_ud{B7rd`!s*cz~%d_YT8*2F% zpqCGYy#+}1Kl*a#PEg!_=dONt@Tt~pfTgV6B0BXUr|dpes{zO@Dk@rj6hapljA=C7 z*pUJ|@kH>P#2Es%h|^I=7|O$v!(5f8@IP?kA%Xo#v%~_Te>|ohd%@h?-6L;qZqVRY z<27cPk1L&zb3Q3xV$nPIwPrV@rN;rIY0+)t!Agb?%`I2;=>}6t)}SL@RcD zXe8x1!X_MKzf&h~LY=7+j6OUF>r3Ahx=hoQvO()hqG?J>=DeJJ-MdHhcarq zor9Ke4{r0ve7b9^RKEHJ2T_7xRxFR_1wNoBk$|z2;~U&N6W77L_Gj^Rr-zrzQk1u3 z4~Lf}KUtq#y$yvk%mcSP#8h>2`(l^JJ^ZkR@$p2F^*E{Sp5l7l8$U7lws&Bw!<$rb z<8Psr0$@l*l-hhPJy|U|$hvwxaDMfNs5MAA)2NCZze1KV3TKP}D*|0`-c>%E&tcxW z`MCRqc3@!Z--83~6CSBX?#@Aj2Lces&Rio;d?^j#Ug^Eu~&`VL)@jt75QYf zF}fYI%C*7BXIgGULNO!dYd)e0xWCl~DQY=UbVc11<@x&I8~k=-8~_$Z3EB`;3%N7D z?D>7zwA9trm50;mA@|frFR;sgf+WzhARl7`o^DiA9pPPqqkw!s(SShWX6jBs!y~9I z6B#(MwOkaae)>AZEFHt#Srg0e)EOh*W3I~-)+^o-ZCz1^-eyjzZ#5iM2M;>Dh8P7B z6bfyqp!5Y~UD8shl6;w630+Y~m6VT+;O~qqXWvqG6uk9AZoOUGH@?2f@TbvAhII~F zpic{_jY#hz!5S$l;ef+EJ$tNdH*R6fb_m+s{;=< z)!V(#c~#uA-_f>=Q`)!^=rqIF0=#ApaPvnOdn_IRwe=p1X@W~?dc;#+oh(R zD9ZfWQK9c{V<2@GAZspK2i-^kuT(8QXqx0T^m>QmEd0u*sIOJzRA8R`fwi*7t=gGh zlojn>B;3O+aGsA}zMk8an>3TjY(9tt58RE6JP>sj6EGD_>(w!Tmcw8;e(1eDN@n-_ zc61kq`$B-!$78>v_VF}Au4vrYE}Gm7LPbp-N~eJeO$ZlBhD+oN;{N!dyy#Wt7czIl zNc(ps|3sBkP^BeB06-@erA*V`%Ud{!K|M7i`kQRri9tXePwp<(R?QI8r-c58-qC-Rl*gP-i-30(UB$`Vt+p2(jAC0fPFMX!N zpe9CZ(pzn^VqVm-I@R!$w|j5uc6OH}O`TAyd3ncEgs24~a9|Kr(5(&Bxfu`vzh`Ec z-3?NMBqW5UBqcbBiHb%R#Qo;QOg3cC_7C754t$h7j+ks+?MyWc zHTxupmagOhL1KYR#aZmA23sy^8r&a7t%igwfnl10t8~$#_=7li50i7wm;Tu8Zw-*7 zrrHF1FphH60O|2*3g>rK*71nONA`fLsd!`gm^?4RxrrMHms#uIli>3#!!% zr9;_HJ;TXEmh$n7nkV4N+SZgJuCB;mrsbsPg0z%0BBF%EfC$16(gKpwHGniIA)>U>B`Ga~(p{26 zcbtd){m)rvueH~bwOMYM_j&K14Z)N z*^dg|-oA36c*0HFlC=kK;qb=eb{mv#VK8B7) z3&p5weX^Q2mh2RC@R_GeFI|4=vm?wL+V5)bvqtxHSmEKr_R7KYQ93G;ZAHifC=K46 zz8Sw88m_nG20_B;6Q;WOjdhm=tJmxlf!Fh{w=2c1k~SG3-%1=?n=Aw{ zWe7Hyl7RsS)CFW66XWA#QOXG2R!)*c$<(F_+q zYTMtlQG>TLIjya&VMB+1pF0xsn{H@hf6hT^)KOSRvZe{g{jzpC4rfUcFcVAB;MO%T z$h&?Ozs7-wrUu+r@o{^u_08 z9E94P(Vo{e#J<)zr)Yli4Syg310F9i&~@iCt`=lA8Z3xQ`oZZt7O)**}>$_e**`6`Ikb+ zR=uF<1qJj}fwKl7uU;6%k?VPfgb_4H<$P~jRz%A?Vt-v~7&1K%J$=^p3>OTqj_@CQOnU#G z!wME6@0mY7USbA;Z5uAAEdQ%3!;j@EW9D@02st8)QA`}E>wIDrOm78eC3x`pu$R^D)s`v|gxj@qO)UKgcveMnT6uFm53plQ@}hQ^jXwoMc9#=zu3gQ0|%D`wJO{ zh05t{)Sc4~}F!o>*<=6vj`D6u4PY-8Nz(;ONK}H$&{sJ78C|NXNs&%iUz>gyL|ySr>xt?MZ22KO!ZbZMs}Y13tl-wsJ+1 zj@elp#d zsq=YnWPiTpHOFG*$}4MIaZeSFydV~zi|fo(LgcxZP)nioK3GrFoSi1iMrfOz#3}Uo z<-}z*>n|%JZMuSO3{kJE? zbpMlg*@83RngCHB8G{*0LBHYvtyC_V$kagVJ(Ql6kP&U|JB-hIkHlG|e#?m}r@GC~ z47!3S>#DnId*F;YV8D>Lza6zptdd)K>)z(|q*H+v++)q6!mBc> z`@oPUIo(Ud3fc^F&NbUrev=WTraN@kqY$Sh75?^|cY$Rm?a7DfA;QYxao`}lx^F{o z`Li5PXm!!%hjdk0QBIEh(b6c%rZ!F0(Lm3Cw3YjUkCdI3$Od*8aRjlu$mMVsO+Hm*+6QxdlEs2P`vG|nM*uh z9D?bA8@LydzCOO7N#F$PT}c@iHL0MrT!`BJD(qE>J)!g ztOAUba@S@`@aNX6sa-B9n4t3#dys$6Qpa`7=C3LiS02{vtD|6 z+c2tGzj*hv(e18AmAx@X<7}K&D}lCP5mRv&1HdL?Wua} z`Y^#V13jUE*jxv zzEd}CLV!CbP@wGYx4J)g3flaCMn-IkpM4K+1ha7?h#CTYz5`J1GC*@jXHzK}A#dE> z!Eh%2DPNL_xAh|Z)aGT555+iir(vZEf9i!k+D~8dNY^SUftj zRZ+qJ`1|FO=!mgPNxrR$)(`D6NW3FsOD)FQ}{107B(_tKK~JO^=W~49k)V*lE&z=AA&GZW0ctnfXl)L`H@_YY`r6W162! zpRXCEf9hUuFLAsiPhz5=owJn8>anh>7 zpX;qS_2Atdq1-<-!<6PzK~ClfGFG1ky1$;UG=%ZxSYydxpjN11r`jcZk%*#*4Ul_( zA%yRLFJ$g9;Uj8+pQI&@bS^srK^4b-lxDSF4=!&_U}rh5MfjzimhTtxXP~en{$;8| z$}ylQGyBpsG~Gy~P;X8d(e>cr0}hBEMvE|aHG;>+3N5Jv*h&{!zRIk1 zH4N+YYvuhpn2#ts5_tY&qXh^l8Dl!RqV%f}zx^)O&2rs-#fPV+)~>s4jvp9EQ9>4k zH=k?Kpw`x2eSP3d4Cc{9uj}&%KfZ^Ww*l${+AVD*;q~?$51g(W-D)vD^eQo4EbQH% z=Ke}Z0G+9RT-vGDT|M}@{!Q-TL)=$(uVNXr=KfA>;p8%;;L5?OXOT6=$J=={W1Ia; zRMb~=!+%b<8?KJ6&36Vyw}vzw1TNhV4(B4w>{>@XzkrZ{a34$VPl&g}8D{SB?k5T%74E!VL2%z{ z+!y+4OaIEw2TZJoqzHq^$iAp45cD8i)P&P~iJ_O;G3Z?tY|pyOCDQZeBGv$pg~yew1WGU^0)5a?Ky#^$MFI^$H? z*`Zv$)dC`hWqo@^4jlZIztenA`22;?y)u4R7o_QLK#eJTt*K1~3M%*<&TTFsL6!?& zzc-4Ct$vu%vV~6y?-}wUqt(=fSf600KabVZC=_>+`JN!es^q-VpHYH!QoHS(RDjl! z(wqsLK2zjRu6xrOLQK3=eAErvsbH9+XTwP1pP9L8-^xiCiZI&t;=LBUw=IwMyoijd zo{|1>oewblw2M*X$(a7+`^zRazpT_2%Xhcc&S~pYlfYo0zgBosL|xc$Y?uxlWoyKzt$N!UI^O?oAS6 zR*%t~W6ael$vM8*L`YzihYlUW!=;nN=VX1RgSX^0uvH*(qV2UipWavK-r=>jD0^y4 zl;VVb%CHp-#*{#T@^oZ5l)7(O+q;M9?z-p8&^3@ zJp#YN*U@^pF5u&j>!I$WG?@eoL@Pi*V`fUu;UFp&*NmL zlQ{;s-j9Idspm;1m|GW4fukA*3crQi2mcz5t`!J=uKUwK69E{J*q%Hz4M4+xeK)_m z0SS*{`2+69{Xnzx(jtzJ|t0x!0w`l4*PT zgMbV^A?5_if)a6)``S^qFi4_#>}GkJt(lDxH7=BIYe#FT1Ox=%3Q>EJCnhQO)P;}n z3VVm6H8_Ry<5fBJB@RD)+HPGRS!iAkc59$zG|;D-sU^<3{3*VDP9zX;FC=FXnQ87y z8fqKeqQl3&DB=5K3NKcIyzS!?YD$D@mo8j2BZI>KQoVCInSGlFmWUF%m-zBuuH77W zU=bN&IK_6EY2x7T;n9PubG8g1oHhtk3MetI(VGAdLr3~@cZZ0Y@AyVur3~hn{DIk- z+UE;>%7Et~=3g)Vfr7kA_PAxBA;kQtZ&MZt3CS}e8vd_-yDd_VpxpC?6mIG$r&!Y} zxEsFUWVn@Yceuww5Oi?-_uFRJ^;8KU8bEy$YHt-oX#ZmtOpq&;EY5WPMLD9{M$bxcfQZ77?m%Xqb$X z?YEaU;}hnx^J==bp~{(?4F9FpUh0luh4I zlE;|1PrbSIyvjke#{iRzC=3|Le35 zptC=c#t%vd5gdrYxfV>8h-UdOJ&g^dawB z4RM@$K`r;so$=kDoL|g2Y=%*Sgj#3WXlGQ^!T`Pi@lX&t+=__UL1gb)@9mTJxWcrcGSE*@@V^?GAmWrKd0d z`_<^v>*;z=B)BqosRd+?V=t0m! z)(m_r*}0;CwAn@Y^Z2B$TNhRm41X_AN18~$<7;}OT4aMP`-$~s$+@P}7o1R2pKB%e z(`ux7dyhbq^fw7m;XDVLCD((TP^QzP5wjV$LNW^|Tck<ssrxR68zPu1P$USPoU}`uM4D1;ne%5W`jib zJFRcJ{}lO#?fGJ-T~A@>cT7F=!NGSyO&6+59v3WTQ{_w#11> zFJQ|sMbL;Y1Y(+?m9J8_Fb`HV(#2bLGTpP@?e$Ai+C6Eqo6N9+dt?@AVMqQ>h;=Lw z3s@Srz9Uc>Fp=CoV7$v(TUYlD2vz^M56Zuj`tO2uodB)S0wM^Ghyy)G5%3M6hFLya z@I-?@el!&PyveNAoDUrEnw3nT{8mmiR^)CS8)#q-zF`F9D3#FYHf>*zyEj{D6NSV~ zX5LOPy3dVcreMi-?(}J743p*As~sOqptbdN&AOX%8u7o%U(~nG3BIq}ekPW1viIrgd?6Szh9ul7&XeYa*O5a*wvYas zvEO&yt+g(xIwUbc8zTGfIxJTjbBdCX69x7x;O4b`e2jKZ+1F-jC^(1!1N!2|Mj68F zugLos`{?hfsj0n&O{afm<5ZYVSn?|WnpO;L(CY$+6mVr`Mm_)|o*WE~487a{;&5DdN8jBR!I^#?tP&ONzmLzvB?3Bk9 z+-XQ2D>ZM$@-8~kD_ra|b^Nme63-$qvfkJ?mFz+DwH1CSO9w)%R8?B^5bmtLSvCFU zfMZDKCjFn9upw!u8R8KYr`@p57`1v0cx#M9EnU<3=C{v>UM-tLk9y`m7ZW{159fw_gOvsXE3a;vBPNbfv{Y8cJm{&(W0ek1vs+V!_!IF&$M8eS_Y z8RY1kdLOHo?wSm^wBDC}J>OD=B0hDtg}K&FX6n607@E{JeAOQ%#UhZIEMzBUYPdY@AvH%`o1qSxLp8;Q*oZHuCC4#c%=$1 z;?BfZ^v2dx@`XES(Zn<<#fs>gUu;7%d#o_uZ)uhcthf+wj+z6O?hjjZ{xHV(s1CQt zkois8CeGe3$rgx3L~i?9f5(xaD7&$~{sJpp{9l>hFJ0AtcU+Kjm5lTNdBz%$ADQSF82P0Y zuku#6OIGzd5i49u{)p|lsR%1{>aXe1-zofVqwG5h`f@-mbXO{Us}3X6=(3_UpsT|8 z-4lu2KCQo@kka==+62E(;K|U@ctqm#wY@3R_jHx6eRV0>Oe{v`^yLhwi7}oFQT>l3 zRwke?ntKg#=aLVv)=N4|NJCq#+QV-lxU@l9mwPQfo^eHM5}#!seGL!q=1usO!wkH` z<-^`feA*RMT=TgD;7bJ>Na1s-th)7`JQ|$p6T>D&v9#}j_-u^6@OKexnD%qeH-Ztz z*g<5Uw>@+WRx=()h_g!Y^#mb|67DL$X7>HX931kH&dh1w zdyrve<`Fk2?8V)%ei<(C?a!({RGM8s6D=owahIOxh&z*$a%3 z8lfbUQV;K^Mh>hh!V8f7g+50f?EZSo1kkUoEBzKpZdGe(j`GeYtZu_o}i0Ta*pUUY!h_>T5|Zk=8me+&f_`B`5dBgKZr=@hxv^63~7A z+`SJ(dLGI!9FQ&x6%`e|)s}G{xlk!4$TBq$zHdmUQBr$S-&6lu=&5DZm6MMXW z`Q@U{ZjZLC^N29S^QE@#+vy9Pae4ZJy>=?E;Vm->tU+Mb)zA5Ex6WHcavs=G$;Q`H zDN!Zjf*m+(oHWZ&qd^!_7hshC%Oo>gI}`bYy=*S)+sH7eU$D7((}BPDt4lwD0r>h- zBJN*F(buo1rKtFcI@8=m=^(1_gU**)oG**oCH?Nk(bn&k_wb(`%v@a_ZFDE{PNj)E z`aVZ`ftgR=3&DR<|FQ$O{Ql*HYRS&OcQ-LLTJ;oQU6>W=xY)R4Ow9X{Os0^lbQR4w z(Nu3g)@?(7`!+8CZp`sy|(Rt>_2ITdvn+^1lbK`TsS5 zI&>^30Y!1mwtk37emEm+KeD2)AzbtWMZr{cTcBZ=e(Sum;e9=3Qej=DGln9-vgP~) zd}%znUtvZw9(*g|V`36%WHREusImvP`q=yO!LoDdp7Lf_IBgzE=fVPNvU@e3TSO$n zKtdEs(*xg}On&&w_<;4l+fQSZQ}pnzY&ayxPLTsLBe63-Rm|EeZN?`4U71$o1)bi{ zUPN3^E~l{^O){j^beU&MGD&77M3?^N5+5)L&1vtSp5h^=C2i)%4-8w&`Cg9iTT?o* zHgmT!JD%}29(~Fh=YTx_J}n4RL!0Qi*M!9mbt8mr9-t48&Ojsw&AQsJI^67l$NWhD z87BkaM~suo-ql*Qb8z6y0OR+R=O8g5hPt}d?@$mBme9~L;mtmL7|h<b_zKMTS+HiuT8?#*s$+k_+d>gGWa4{^qgW>M2)y zf-u&2vOBs4!5T4$L*QM<*5uEA1d%-)RfUA;qwX6MCwRR)qDco-^+)U!DT*X!`ZL4z zi!rsu8O@pzMCN72_i-hS=ui}o(dN|;Sb|{PuBkm%#WTo(tl~oO(QjZd0#fO_f^U|A znw9h~Yz(5z+`(;jnKgqWo8AMjbd?cs)Q z>fpmd`ZNJJ&kfS2{$>|V%s&lHh*@zzSD*zrV6fBEvumJc_@<-l&rLVBv_5lr%NxE2 zXyzBDZy)4Rp=;4wC}!y$SE@z z_Xawq;fyzbYo8OhzL91*94DC0&}1cmOpS~#zQ3OM@vA+8#*dc__IUs3=u=hI$k%V* z-eAdrVM9LqkQUb;GxrrOp)U^H1COHsKLI?2}{1o*!j z8yh#`Iy0Ua8cZOIitRdG(DTMmuO-$dJe<6hL2^i<8V!076Jh-|Th4DcKcM>)w5P8| z9I-xsE-4Y@y54xz#(i54gUT6 z!(C$2_v-VL{Gun{oSVQX1i_)LjsXLyC9fE;4+01Vz$`TUh2Z;3-x!=)f?ihv<1Kl0 zTVmG7RN_C9^%O=#1;u`x{oC=#wjB>L;|~Ir!B4vq$4=QTPcr7}W9-t8yACUV&O`9d z&kBm04a~0Gu9r}5RJ2BjU*R76WqIsBPtEcd$__lIXL|s4Lw#NUVh@qjO zA-&$4O(jPE-=RQX`-CGP|BPpf;WSr$eSKdmDumzms@arSqcXZBL&>NU1GHbsYr|T$ zk%UsUwFM7uu%z`->$Tb$`0%EYXd-CB7o?O;yuOo*HqPS{!&&(4*KZ1IXPGl9z6I_Dgyn( zf<_gLk5|u0#5xs$CI*USg8t2PV`YPHk;xjUU5J*d1ZvTXZb9QtqLy#cMOTe9eo%J4 z?Jh16eaUp=#Q{ilNNQQZxj8sI48Od*{3$wmq^|=E#0NN#7gqK^(~UDWlwL~Aq5kMm zaDF}~&|8vc8-d9fbwz(SfYEO>_oCQN&u(bB$@M<-nsj<*pm|Ypv-M3qVwsQin-Aj$ zl=q+Fgy)>S_Nla6gxz-|>TVAF>CALC#Vn9s9^k;!@!=7fb_C z8z`Vp66rZ$f^SJL&I(D4-o+~2M>^X)L~Qbt=_#ty9=dI~%=YQ@Y-X)R=E)U5gbp8HFDdJDYBq|9nb9)GEm~+JiqT zT)juo_!)n)_x{d^nFQmG#0_j|s@*WqoW{<12sqfIkBkWmseZ8XocCjn0$sE~R-*!; zM@Lmbt@#^#P!JF~xdmyvfsm}MYreoQB_;LCsgto2C^;p7TK>&R04|X)K{HD4AKC^* zP(BvPz{C_nCVg-O%MMj(XBWOC{!*g4;6Gr;_Mc(FfF{B}>bCeZ5IA@H z(+u5f7%j}LQq=-stU)gPQw}jlgNlJGkRXkK>4xA|cX2U4m@5+OzHx>dq65g-_fz;&*O4zFLBuTR7`!!qz;7-wA0?cGTvL$F*B61umP5?+VNBZ*@}`- z@Zj_Y#Kc|1_Ucfvy9+I|skNYq&l{(zW1|IbYHfR=`$EJ<(0^1d3C$Z?uQX&J!TG{6 zEEa$}yy?H>WZlo zErS7(aAK%Yk`UKK)-6Dwxx+xx17a{>Oaf$jJTUd)1j=K{c{G?}$>CtX`-uVVuG|Z6 zqy4&o@^akA;K50Za7W8tdMKhRm*2jsmkK)@eq$n(4|Y7&7#HjCTA8H{znSvv%UJ(W zVgR&qR9oc!d$k{sx)7P$T4rg+o$GN3i&gr~^=;oUeV0pbWBz1$xzsqWeD;r@8-`qO zW)8T;1?l{H?#ktEKo$`5Vho5vgsexp!;HN=j-8-qM??a!x&&NEqQgTy>DbmM^%2Dlr^34^V4?NAEHpJ599r8kTjeE57+b`C}h6Lp-`S1Fm3M@a zPK=S3OxJqp=%^w{8`N<2d2?-g=l!hDdop$IKiQMDWub^=mgHUCi7o*pr1cqZX(lSVU#-R5h!X&@G|{AkMe8!e&Ay?W{ft=7{@LcQ8GSEaBG49M%|^*P`-%^y zIJ|uM!iz;mwV~p9$OchL`)9cT6SlyYBG33RJMX!jk%6Iwa540SB6Mttj=2zV)7N-3Og_Z$>Qr+;>I0 z(Orq@8u#sdy^e)L8*xW_$nEoz>b#XnKH8S}pE`l?5otU|lRy)l-7zW;@g~2c!d5`$ ztezGq*bVp?f{VBVb5t?(BLa;o|7xYSDjl5g?0n!QZV8VKq|dm<0lV8<1jB-^0*Hc_YEgH?OW)8j+DnJy52i z^E9H0q7oDR#^&?!`*#CK&d#o=G%B(5QxXM~{uwNNI4BMFUs!`iqX5^x9ZJ+;Ufca2XGkLj=|WBB&vIQEe{V_(he zWH~q30yT4ytT)PFK+L^_@5KP+p7ok2%WrJJ>jH(RJ<5w$TFD4y+(VTW)_%z~bz7MwfNP$>jhA3#wT}bNJNf+E#*`ewf@M z!u9H|BG{!^AQ|yreqv3oWd%+epq|Bn%NIyaE32vk?{TiLubZTaPoABR3$ry1@$$&P zIjXIzKcr~#HWrZ6L7p#z5eiS~Ip`%^=u{quhsXSQ;4$+Z9d=0S((9pKVb64GZ`UDa;*OF&rH4*{KL6dp`yr_0PQ+{vdJdfIn+6^!JC5%xM|WSvx-{ z_D1eqT{4M^{yFKT#s)-s0l(cFWKd1I_R8BWDd;$wQ7UaDB=Vgs1 zj+ZZd(ep0wJff6hq8_0_D(dM>Ez41iXO=PrwzYhUat%W5ET%yi&mo*VEzWnt|Pun$RK!Amf&KoeDDc^uyN;pS*Y6ZX79MVGgcp=c#7jfcVb z0`}2!Kcp_BV|*7cBs{{He_0WtbZ>I#dmPoGOPx>5k9V%lZk3Pm(ONES1zqFoU9kR_ zS`rWPGK&mqe4=c>^fKGAtKWYdu^&l_`KtcIkt6 z*B;OHzuh3|!=o5%D2QyHPZcaYyL~-H7K2U^G&1t2{V6ZeNnu>RmKZ|9rS7Khb4Cauu55Ex|sfH|7 zR-87suPh8ICINPG_In(h!Z{Is=_})}#!NwEAGa^#GH&f6wDM%iDxQPPB%M)MDD;M& zcblzH$lOX(BW|H9o}uX>7GT;z%!9-41$>t+k9#t%niI5T@k^>KhOyhn%GgOPqW~27 z@iJH+SFl)kbFoljebDyqlYc8yE6zqx{qxMX$22v@hcZ#NsCylv$u`lH*c7mq&c(rb z>;BbCRss&1`t=}T1>lP|@{{;_iyW(;9iIKa9P~P^*J=Cl9x#IL#KHVo0F`G^Q!|G! zYBh@$r3z>5wWoFwUy7KB@RM|#YRXqc6U)Q$0wP-M`_IpLODc~>Mn=|w`W2**H~jgu z65!U2lUXVv^bf(u4V(bxE($L`W(e9KTjsuP&!Y`}tFuV}7IjI3?~wU@kF4vx4YyXy zVf3!rAH_nEPi0Q2;n;sgiQm4x03ZOrkct86TU-p@HvwAyXQzxaJQv5I_GD!6&Fc*n zX78Jb>sqLS>3^prM_LV@2beS5<3^}=a?oWlzpk0twe(duti>d&jL!}~4`o}+D$@GE zCJ>^)cAn&8Brvxw1be1ZbywZIOK{}&#b@|5lBSG^&eri_|M%Ira6>vc)VAPpOSx~c z_w@zxQAaG1m@dgi+m8QEcllML#!!E>E)=;D+Q7x^AMc~S5!T5ZfdN1Hy^oX846Q9PIBJV9X}yzsF+|ChD~HIVai2LN%e3`l$~yjs z@3dUVH5YKycw6@3q~!Nh1yP83Wa>ILrO=orQ#>>A|F70}MuDw?pYb!7{ z=38A?JG4xC9!m3(Y#Y=#DlBBNZns<2c<;h-NuF(Ve*axE|2s;1o((t%08e0&X*A!K z8Xl`_#t|`;x$HADxPwgP83;mA@Fx=qybfEqy?#a6`Aw1@?67$agb~O(V=_zbeIK4& zh8UCUbsRjm|M!vP@3L>!TF^x)hds!GSU|o5gOXI)dqIa4olawm^EDF-96c1bE$I{HTI_^ z8B(Sv$4Pge13?5<^X8Nv78IwdgdGc*7!Q@7^aq%{BUk1Dy8s#_lTp~fX*QQy;)~UX z&xa#udJ?F_nJ~5>nPDGZBh%4p>nJS*=P(X==e_0FT2)eq1t@iM^qnjl)(V{0uh7{4 zGCt=-F0Owf(PnqBU?wAygD>psSAIAGnI)Lp>#D;tiz<06qMt{FN@Uzrg9=o`UPXA1- zf7ypKNG$G}{G3eTeZDOpK>5_Kudc`r(M9B~udVH8W-@IZoiQq#0Cb^3S|lJhjKwqQ zGZ?TxjQr%CSflek^3gqcWH9KEN2>-hE)>C_(J`v_7T(^u=~8qEC7}0syr9Jc7P}*( zzKX<%A+wL4`8B|^PTl+Orm^+wEX+kDgRt7}^Y9KC>^;aXf5!?Oi2~3NeP3><>tB-pqNzV@w z7{f@48XHYP23Y}hMMsmE{8nT2z^TW3jwceZ4>PK7ZsOwT8=|AE9 z{kgrp{kfd2Q{J}An?*|}xc6g?Pbcyts=Gl|R8iM@@WXEG%n}RRo+^=q{Pj71^uKc+ zz5Cd804$JhA2X+dtfZMj!TS#;0!a-Y-pINQY>E>X#4}SVxCwU9A~fh7N(N1~q;*I6 zNN`l)&3B0=%MW?7<7Q5harD&hAh03OA||>!5njjt-%gsj+zzQH*n-gtAMF0U=?3oqlR5`#`Md zTT4Bdcw!QgfMlemDx5I<8_?KnuLJT{j~)|I?;Hq(5-lsYZTSv&#$0MG7PHKa6vDBt z`(a<5{_BTHcI&HA`y;))0ASyI4@*Ldy&jM}&U_kE3z+Kn1|CRNWg(s|&%pD7gp@^F zTTL=~3hO4JuBUt7XZTNXSAv7DGS%g{ytF>&zavu*CJR!OA%tp+iR!fI-TV=L;~WO% zxvrT%30z`ubmHTYnqX}TnC3nMGq$yCgLAFq373G`bi3M!)Uo|cc&yFtDh)OTh2A1_ z7oCx+)CxOJuPI(Wdy8! z0JTXNn6Ci+E|KlM{`sc1(XD~s9l8|>Jxkos*q_As!-~byz|w|`aAW=mDP0OH zluD)pa8pY?`VNdrdM7uFHia?;cyPnOuDw@urqdMklhJvVYyy4zGi(f~N!6;h)oam&Wgbl(I=IJfD0>e( zu$Q3%Y$2kjgsE z)r=s?E$At~xQ7jY@+K!rD8EiZ*x1o-wp@b8E=3aQAde){+qN|rN8T|G@_$7y(u&)< zc`LQ<=aB1-NLv&Dyld=**>j#vM;%=(h2KpRcPJ?;+B-Ztdj4>EqKVCnw78)~K&f@BZEU=We9GvP3ZUDkHsMS<9{ng;X zsG%=kzls3AlRhv3RYCuN^1OwS?Mp|d6KJVfGm46;@wW@46zhJm?L~S6NE)vRlfVaa zf3o`_`qn*4x-}XyE*;o;RH<_zbB91Y4EsWtl)oB+NGUYtKLgxbz!I6O6qj&Xw^pD- zr1sye>U=D*ndK#r!PF>;ITOSq(_ej%cLZ9G3@Z&_j}V}=NMiYtgYLC%vl;je)4(jx z3kbM8HZEnaT&w+NBkg%E6j{H@=dWs@iNDlIPi}hz&+Ys~*2u$ef9Fo-P`QUU1pgzV zi4;ygy77&nI;G;u@uyIq8se?9!!1V7Cs*@S|K3XOMqK&ibF&Uxq$=Nam_A+;zHRCV zhW?UT6%CKnKmPuAS$=Hz-}_=qq7wI>u`H)OQyu&izpo)AbCG)`uAV%89W@+zC_Q9^ zv#?FV(yx?P?s1Mack<9)&UjDyaZbwdz$pTI8G#Ea@)De)H7)%3bn~`IDT;d-KjU;L7pxKFh>xZe9$9-{NI5uCR{k z6wxdK?h%P_)A1)x{5|_0B*y%zw2a+sI?rne#cOE3cPnS zUIQ9=@rJgkmqsRrUfQb-4G${?k%8Tewig!{ zw$OQLYmC{G8P{emQBA4m=>0(U7gZezv3ky`SgaCpdW&^B1Zv9G{1Zfep`K>lzfmh^ zXy0%gZZtbWgG@bp6|V*J9f7=mvjbNf_mj z<1TP_hi#^j$NLS*r)T@41n=4vQTeXcVlA%>?9*| z^LuIZSQfuv%kOdVI3Fi7{K6EU<|^axB`&oJ&cy^ByaXQUJLE zEQg(0{>Q`}HCBCv-A+DX_nzC1TgPY5cUHW?b^{*JqM*_`3rFkP@c;R8ORGW=!-xbw zE4ZV;BmD{Y8*5Wr;jrv=S(OKg($g#y&c%f!w3?^FSXJ3*eF?AKy3dHpl!qVa<&S0D zv$uBg-XdUCP$!7kBd5!(2+-k^HH;L(QzeKkXzEQ&d?zymH2<&%h>XNJHIAgA$B3H8v%l?RyLTlIVWB9IgCBn^uDQHRl>|+}+6hmDN{$swVn)czNYv?+`{n1h2qvLxS}$QE#JbsrR*_qT=g;2S<}3 zq`upMj8F(xbuz!n7ss)34#UZ$t`u!=cLnaaX<;o8>Yjlmb@cw99UO`Y_?|TyyVDiu zbwANnAdL?a!FHBGMB1jB&Q5mShVHSyGlitsX(oaYKZuDr#o97wbeahc4(@w--_kk@ zp)4#Z83BKg)xvlVc)Nm0(sbI5-9>vc$uWp+-N-E&MU7F``;SSegeOPhZ*vmI#s8(a zNbDW=Hk8CdZ~o#|qAe4R0>m z6QMuG^g%K`HJ)dbq?4Y)PzCV9g4yp6^~|XG;HLj}!hv_t9i8u7en8gmnAQsirA@P$TQwg)#1q_^L%T%U+aN1fA^fh^|2|o)2K<; zhb!hLe@zATfO**C9m!mFirH9anBKk%>okfJL#w*sRp#FL*95$cMvxH%78hWw?N#>= z4#L2Id_GtfuxO#(+%QWeHw5`AbIQ_h!2*38xJ3zLoG;Z)JRrxWFLzo{Ch?!x3k5=d zpE)C&jK5FKnqD-HtbZGP^DJ-uez{S^it}pA^z^%T#l^*qye0MJlyX013b5pWw}f%3 z(l!C~(>OZ9g|<4CU7Om01Vo-v;d+4zxdfxf>`-tbAhy&x^t!J;3x*0UB7b%}^Aj*v zYRJ4mRJ;{ zpI`zg=!bw#g0JUe5~57$4r}ZT?!cJrgPtdhZ?g-^YfUY;UVW_KiK~!~Uw)fsf#+o1 zNh5(nYfkb1@b(p4RYqI1heo=)yQGnn&LbcqD%~mF-CY85=xz}ur8}ghOFBeAx*Oi+ z-uvE9cw@j&271o-eP{2r*P3&#xzzEn;E>Vue*}(18RG`^2d&64(zciuWc|kO+;)z+ zdo*ppV<`%VPy3d(Tx+!v{2D3r8hbYpNhqobXvXOE65qQ%<~#b#bx#9C1imM_!7Y6u;q+?Nc5j!j!OjZ`@IZZW1;E4 zM$=Pi{hB%RM)eenLb0$AT5tG42FguqNEiPf8ch9>f-G5tg{(2k_x z1OZjyINgLeuMlc3S->g&HL$b4Hh4GR{5ZgnG(9aTjk;c=qlo%O>GMD#;>jUOfNa+Y z1W<25wj3iu{6NV5K_zcNtf1>?4Pe9-MY5QHCorJq!7N-jQTE)UY;Y^{lk|$V-*Y`5E7GrgJ~QAw zyVVfD6Ib$q0J}h7)y$CKesXfUbBfD=$4Hnf?%nNlc69AiDUl;VOk3Uiu+Y%~2^>Y) z@)6I$e#~j*imS*MxqTGh8XjJ%!Rx|%_G)oGOdnB$`yhXArpU3>&9k{;#_pqzKw)&P zM04x+X*2nh=QA;S0X4%oS=rXTC;;c*%g=OCNCr!OM{w z-;qq`?EPDu1kkF=1Yu!g8}z#{-Dw?NQZ;}xDT?CJ`>0*Nx4j(?ggJvM*hvBc0%#B* z5+~SM7s!4qb9$)mH#{`7b8q+NzgGJ7sU-urLrNN_Lo3+AyyXS3Fw(ld+iLDewi``C)YSuUT#X&U8 z&<|aBxjTXGfRA^Ngf+jJDgY8-Xl7=HBMBUF=&VD}e6bKvZ|T^(2nh+7+w`gV?vkC| zH`?0L>Rw?5njXXGmED&|v$HW8`2;rU#&M3*aKK!|7eNIK0es5N<6l z?&TBr>?In+<^?zN-@Cd_PMN%NBJ+%jC<&RGn)29Xl=vHF@0;{a*=KivCP3|A1w4?v zyo=KcDRy@DVDJO&D_HzU{@Hax$aTk~7^?YDd= ze0wF@`gr>d7|k`!JBT(MWOVQwgpby`jNkq^y}gaFU^%(OUXnPzy&1&H2yb2)Td(27 zioEuYH=*S!X~_g{H^ln%7l!~(>ig1yMqD^DWD4eBhf9-#Qw`(`Q`?3b*DbXSW}QR* zFp<9UPnX%9I)Yd=`eN}(h2VmpK4=eWIXDd5_02=&bo60)3D6zi;oZaVY)b`}{K#8~64Vaof-1HjCx|kf_O5U!#2ISZ*6@ zT*T?|8T#*aJw1<>%w8jYNlvZY(KLR=s-v0s?0O)!!#)@O5IG(`7qfOT@WwJG)0s?0 z*QDWeGfWEGi2ze3%1x3Ca&oBtpM@y%N)rTOH}XN6Rr0A?+HdlWVZlGw zn+FA(A%C{?+1Tr#ZOqPLuvsf)hhO*0O& zRMNupw9-?%Cc;p>&6T46Jj6E2`1eJ+)saU123I2lyNe^q8)l=FiZ$)nlUlWl?Jv8%Ev5u)BjN#meZfdgD-2O4l9%cXEhxy-!hSgHLq1GEf z>2O{_e{aoiAnxpTN{Xhbn)>o`;@y4U)uiTqpHW}Gs>GWWtl0|#X7^Shc!f6)dV}q+ zz;a*k?rnP2ChPJ5)Z^ZiKOgc zD;B1!@1^_TOS{z3kC_9Co7~euJjG&6+SF}+%Fa2~8Xh;M{OV%i&77P15|@u_2ksJs z#tpk|hns8u?%@skPs`qjYm|NY6bF(PEE*Pnduz)%Y69zHi5q9p(bJ;@Eg;3<$XxUN z06Y)=jI%1R=d(Sd!2Hrp!$T>XD9J)cvwDyosGb$2ff8uTFa|CCLyqyoT!#nP)?~Re zwKxMO^b-(t-x(NioqGH^?%z0G?%23Dg!C^NT>mT12Y@hZQKjhs9`LBXd`&~s?slL` z0k#byjdy0da&&VS<&QZ3yuB>GOEQQCxSZ>Yl{M|=^rJ9tv_E7V7fl#|cCsA!%;?u* znR=@(z8EHA7k5kn9Ux=OhMkh?`;!{;BzRK^E4O!RPBT$;(ZEvr$~UXJUt;8uY;B&2 zCr)a{hPQdjHeQZ8nWjWPVwuQ%8ux4=bvq^5Ya=4JF<`dm-U|xpd=K$SAX)kjJuB`NJy2{O{5&%^ z$I?ry>M3>to`456e;s-Diz3(w^4;DsD94`OL3~p}$7DiJ(gfT}He(v(D#?Y8&WX4W zQ4$x|nebXF`T!h2kX+DFW-8&`2cS?q$Y1>xQR-9QaJ{Jpit-nGYUuk_U)y*3_`L6L zZoF`CpKDb!T#BcapaSu7XIx4B=ucvUVO=p2pN>(iQxvl%h&FqTO%># ze-62pdB7=tI4|5+rI%$L7gA|LfEJd3f{QB(*<3rEv}ss*g6_A-rKuhqgRp~NVy+QV z*Q79Ul_1EddK<4E&rUPt?=={F_|T`Tyq5TEdS-@~Ze85-s?Rrn4P_spdeMfeKuq2< zrdKy%4Wa>nE+NljGnAa_tLh4Q!SnW?3}miL4eA^7&w4bKQ5Wi->&zvy+74;dS+Onf zCe*Ml46+z$O;mHxhT!yg;#$n>s!UcejPcEVd@-GQnP5MY={s-+8aK!Tr1dDtnT0mN zQcLJVxJC`g6SF@eaGFIA1p--ZP(ft~J2Sy`0O)FLkwRTEk@%|=Fi8fkng4)WW^wVC zucdIHe0T@Cdmv9e0^oN7aFT3t0%m5_RgU@aWUCS=5Wt+?9k`bfjD3OR*3}UK&z8K0 z2fwJk4M+e$55SvbeS{AP^sgo=084)cW3>jI=*!A;J?OETB9J%n3kYC(9i;JD41(V# z?Gi5H$EDd%{L=E6h5MqQqJjy!ib^^?Uc7p|pPiag^Y@qVeEmKP3g9jEWggH*+{T^u z@x9*5E2?z3tM-prp&G7ia=#9+!D|<(v!XrM%r$p&ReCKy(%M`4^=mk3SYC^Xb^e>p zff{f(AUSjaEp(s5Er84kK0Zkh@=d3nv+Roy@4a4KI}KIIKaHrn%MYT?#Wt3fYNC-- zbM#sm6H&-5-!$I7l>vq2*W%(ZNN!b?V+7yN(qg8sEi3EC9(6g`uFA2u!H>6~yybuK z!u*li1<V;K%RA3 zj2LIsg?XByn`oSwM^4{{yh*;cy>{*`F{=OUYD|de%IMo3Uk(whTo@Gqe$&drCkP%*36+0BsI077uC@i{ak26q|pY8+eKBscUQd-6`qy zCSTB(rObXy)X;!;A3mIdMeEI+r3D%%r(EY}0>3_(x>D8V;ROHV7&Ys~VhK460$58~>CMaaCZ3~)NRYh2Av zscmXX7ZemkCvIN6`?pjj@)egta6WXr{G!B2ow;{?kwo6A5PYP|zklxxIDSFOnWsdM z1i89_s`uxS1afU%9i3O~5Gp5vm?hFaR7iB+21gQf99MyYRz5e=u*K8re^K<}dtF_! zAU}UZdYr$Q;=;Gra1BzEXOZe?imbM*j454AMj7$a7(#wxMP{ZQq5;jfeC2`GB0{qS zCdxP)WIREY`>IGy!lw4On_w(w=JCPvI0dRWF{%yxxXWm`fR>gy3ihg*u`xsV4x^GB zeeL3;VT;UB#J5tcrqm(2Gc}&T4<`Bft4xP~p-Eme4k4l?4vba{HE|CcW)RTtQ9Hi4 zXp`+Q6B@o_N&)}*1*Dwdh4^j%5>S6QelIN@$gbbcFMwZ7f`q_&g!0;bt%r2VGSchd zC6(U2>*PThO{0g!2;HK}_YmOn3?!(#eQm91b-f2o^mv;J)Re+56GqO2k?;Y(p*-+5 zI6OavS1*Ji7qi?~|Yo7~9Yx)}velZf(D6*t6=+ufoPPOaAl?pRy8&Y`j zI})%J6{^Pw)zgL`cZ3!5Fe2u{3ctnn{0xd9A#km4yI*Gq&5l+Op@*xxK?p!m)!aJpK z3P&Kxim3)4g@^W$Pw@vExxLo=lUU*$9|v^E5ILXNCL)9kSwYHFiL@^oJ_V##Z>S1d zYnEMk;sv&JMZ)#gF4CfAOqS{ZhHl`&U#~Ewjbsy;*&z}WA@8*e_ zIYWM=pj%1Etx{VI-|DdjDAE48_g&IyJG(iJtLM^nImT`%_Kk*6sw$=%fBNg8V7?$~ zzymd3>=(Yp!{wjNErh6}eY9m8zcu2v3k^y@1|1+QWL=AeB=kDa+U4YR1B^Y4k>8F` z=zN;lVC`p}I+&mx`hp;EqM;)PU>sJRXg06^9Pjk3=-PvwP(|(PUi9b~!i^xR3vWQ> zV-JQY8l!&o^Xped9HqN~D>!d{b7L4rIhxPcd1y-|TNhG(hndpXc3n#&5YiWi;EgIx zJ}~jSrI%afpv*}H7zbm&(Fw(Ae=of}D&U6?+IScV0|_D;VCpS)B~6?LH$DCza$GLt zN=O1%?eiSCVNQym;Rt8ligmLlz1SDV^Foicvhz;*>s#sN&jWMqe#hfs`{nD#Tm6#z z^8C>tp_mrC=XZt;YKwT{-V#jCnW;sv2L$Klo?Od|O1({>Q-`|p#8SO3N~~|Y+HNbkjK%f?S& z$`HUqOBqlr*g_TdG){hR<+L1XVSM7pv+J)Oqz%^CP_!EQPg;)K8-F;cwfZHI*x*D< z{!T%6@>PiKZz!vs4k_OIUe~d`eJk0(2&qYg)uud2<&R`rU3K-WjO|NTt1JH4MW_f# z#7|jh|3;PnvBx6`#3xZ9xqmQ=e>ikY&ndb0Y{7LY6*#n_GZJFRe2^^+fx!k+39v#9 z5i#bHkrs&J2NNQD(=fscIL23eV}A@Y%Z#soD69k&@MkD)VD1hXY5@25oWzlKXisVy zPfZgit_C*&(~z=6Nx+CCjf)>s)@NzOzoR_I#Uzu}IB3YYXyHaWL?OqLG7{(Ybybt4hZU|l$Ci-VevD6Sf(2^g1goK0GUxH0$Mjj!M2c8F+ z7BLs1vay%74TF>cgU9jd4VW!+9kR+;P&z#NdpD>kDuB#GifE+ybCR%$Ay zr--DIF8(+YQ0qv#FGR92wYP5u+ifRu*)dL-7R~G9{X;t)@skH50-V8>PEMc5HQ&7>bf(XYN&lImhpJ8; zYFt7VD5;3*9kBOCsfa)+NPXfmoeyVGgb&d;CZYpmF<6tssWf86Fpw#U+KX?<(|fX- zPY9ZSp&b-CsJ|9^ib!JeCI)O5Q`q_sCcQt(iYjS~@T4^tUree=ceqM>TBo#1-L(XF zhdN_w)1Z#HO35z%a({T31nQo!R!wB#{~pWNb#D2qv@XvX#)23mNb`J$YMuZ8Je5C3AZwT316^$6?WvTvssD?^M9OG?`cR< zU7{=Ud5QDsZhY9Zr_o~isQat=S{CNvdZ0pfknKWLgi;a1tNiUWmn|bu*3I`K@${HRBSs76w|R(53DgZSd*7{297wOkmy)Xc&P*H?3mYT-UA6@_=yL0v&CH@AtJZB7EoZAV|d zEMzh`BIFt?ZHvf7d{3gHjkd|MZG5C!>wH3E1Te$&I(=cl0IYptl8)VJbV8l)mHh|P)5t0yNJ|qBy5Ye zmL3Ok&X&;zN|{Yhp>9fX`Uip`#-B@tx2ktz)mFWet>&RnrXs`ZKvVDzF^*E1){xJ2 zw%Mn9GVT%^PJ(m7u0;x_kp3YnpCK!LD+(D6iX+pH@M_GQO;@@ylkGz!S^Xs8WifN= zXvKvLtcXgi6t1#gDOFO-;SzCCHgs`KqIftX6-{)5afouK_`2`}pE9>Nh$OJB%LPi0 zE7<4z{FbLu9+Uxx#T36fd@1eufmbJU7a-J-@_dQFD0<8OK{i-qk20am!s z8TH3>9@;cz%@0$4l8=7~iRsCnVT*W-lL~+-?tH&LQ$M2FSLjEZd=jI0TCXDYwte^5 z5=sL<1_(%4H~-W^SuHzTgjCZdP=480bjrjDNW=Az9%~G$^OaD`8axd5Rc~=|WdzKo zR2=DVR4U<9godgvpofgcFZiDdUjgIchvf!=p&-mbGQDd-_0-f5AO-&ka%=`I@fVs zLlpUAw)Abf!ijPMm6>8oQrbdPGU_W$*}jkHxR5=rmy}CdIgaO=sJ^^L)OG4KZYo<7 ze;B=eD8uNc(VKMqEw=>+`6O|{a&t0$-cr{50gRbv#MNX<%(Eg)JkI-R0fFd$^uo8V z(CSkay+(A~f0xtOQ#~OyD`d9jqwBDo<}%6USJr;@Z}+{C(fD}Olpvh0j+Peww~C5- zrq-fBmZ+(zPoeEdnMq{JHR@y(zkoLp&ON+fHK@?*yDXj`~_Y6Nh zM`s(VVd<9d*YfiB0I6B_E^{j{_`B%>>9^`Z4hOuxjcoXCWQfI>u+uWi;47vczYbF0 zAfEDPM(nIbICZ~5Z9l4`vasp~<*wPx3>uLq!%J(8n2|D%2yZl%r)o@$fB2CYd7NlX zWN_o3reUxY{aU=C3gnsGuKzUFiaipz_WO4uJ{pjio%|P$>Lr<;=ArxS%w?c~bMSLT z;HASSvo!elMQ9OC2>G+G3=PHf1fS?|FoK*Z>umSwLwf@|c_(qC<8d*Z7wSU5nn!)h zB;-Dx|0?newyP_K_n=4if0IX_1v$hH!|K$pY?z?JM2fcF2qW_0O;eV)R?U1R_`ES4 zl6$>B?Qu0w1VDyVaa%T`tT(|E>&7k9EocT;@|5KbS29<_eO#Skbt+eb)8}q%CRXp6 z&J$zOJO$pX-Ceq{vF?_)e$J{pLTtJCt8+DS=^qwjCA~orX^NL~J;!(jwu=lZd3teP zNj-MX&Z#G#oIw*er#O`rR4(oSbN%ot>{m_HK>O=0NDX&a4Q99G4x(1Sm&ar$Ta8 zZWJ=zXIKq0yX$NT2xJ}qvNF>W#jc;5u^;uGB#rT7c1l9H+HOR;)^OS$vnuk+n#pIU zmSSd;zjtVrJnv9({eS=wskiH{;cAJx_@w$yyb7k8=}l3r%!UG)dTcjX?+nlB3VFtO zwME%3&5Z*wp}-b9VX(W~u|*9HqM>tfyK!E04L%)h_yP<(0n7A??0!*MA9FTdcs^M2lQ zuJ%la-M*VLt1k)fDF%kD76+{~dP>SONdjz0PJ-l!v&wfn4IIj;trn8ip=?Gv!){FO z%-UAie|Gg!95K`F0-RNXP<1C$3D#}d6Yu6H6b^kozEmw)bSyDDJNyq*^vR{4!p%DQ zS9EFVb>y?~!j`efycpksj)Gz=Xbn|5fux}~uke(j)D$nW?ZEdXcd#R-JU2XDH88Zd zVRt?RDa<|{R}t@jGniYnxJy4185O$nlQ;l>e9aV@PSZpzCY8^n$r|iOI^8IQbroM= zEglsZtXH_NgojgE_r&>K=-mCiXz!{+GXIqsCiB&t#zfmNCXfd?1qmm{sss@}j~xa~ z4tVnS#D6nj6@lE>@!!BH9LqP|RwA8x_dFV+pDn^Q3ChJC!b93^126lQE+2>`8aE%0 z78V}EZougw_^|f^w5DG z%CF@O5=L-FnoG`)@9!XnT5p|q6Asy8jl);i*{%6=2ha$@Uyt$o*%5cxxLo(QZ$_wp zSX1@6Zx+m7TX_tH;W7V;HgWPK^>f>$KHimBjC{QRMfvn^^D88t^^)j&oMYbI%}A`z zJT{|CG@rYPfje*@CLR&ySm+J;Z0xf$`2hl1w(=!5csz)`>ikvzMdI;Zzv;N7zO=&s zQXXt)Fy>wSwCdFxb#+8qM4=7C7z8yz1R*#Y8 zjm&8VHBY3)QHI0=<}14!LiHVfG-IA&(F04U1#b+AN}3vJDwZVh0I#ALp62|Ma1T`; z5%R+-%GaGFz>N(2ih5lq+NrhdrHV@!h7sKTU2A1*Y&8X8--b2~(7Y*=mWmW999q)) z0!jb=X@9h`8sN~?xMkJDUVa@zX|+)NJS0|Gq%$N*UIn?ImP&=OQe*gLpYRFi1K{v`0J<*UdzCV`og{isLUR5K8&q{i1ZZQ$ zfA!p-IJENWcw-Km;@vTQpSG4s|6U`=QmU#(+%<`mm2qO=fi?w?sv*;|KXyDDBOQ?y zk+&2Prk-w_RtL@kA+zHm2{%}pcH1qZ$t4z*_d;<}-^R-@lyQjMF?NW;)@|WsZTB)Z zmJ5j8V|T=cb+&;*8QqO;**Gt_dvZrfXp=@(`@69_VJDF^bc=pl635UJ`9Cb{%z&d-P^ydX1$(msMR{Y~I818^+i#Ig^^dOP=(0gV%$Xegq60i5?YCf~@Wf zSRecQH0MUmzM1^ z61%(N8$m~3T}_+BjSP9COCs~e2m}1r7~u#c1ULf1-b@p86Vz~Nge!btNZwo3M-doY ztQu&$aCN$J_)~}dL%}y2csY6_mY#~Z^=0lDw?BCOSP1`n^3ZLAg+{S=wSyP7cUsb%9Xv`Bv`GqZr<=Ra)l@~fl~W%4>rev)h})S1yH z{$r+9<}7@kr!mJJ6EesJ?fyVgEg&n?sl3V0|5r%{zX1yeflT(-dYq5^ zR^Xx6@Mf9|9&}y}ZOj!R^d~+JYFOUXs^XfdKef%mpg6j-AX0}0niA`{3A&5ci@Ii= z6tjXpB1P0^L*K1NAeBoVbd0v96VF(R8XREh_R}0~XMB>u4%xPk>s6yAU&N>mF~uSz z>mbjb#jH69&3LrfV?rJl9|3s@=o-APfobC6ZJ0Vvp{adqt2R$Bx5>QJ)!Mm1U!-$f zaJh9n)Oh!Tv!!nDc}uI;bAZ;MsBk=~pF2^b6j=}L9~%yt=D{JJ>2PNQ%uYZW5+#@{ zXae#BNWjJ?VZ66YDc$@kY9pu_1F4F|>WW2^+dKQ=dDbj!5mKzcIMjw!{tqnzWZg!7 zlSe=_HfT`>PA4{jd7$RTRLTT5G8n*b7PCB$!%o~kaMppv<&q2%*W zG;65!f#w)Ku+MUu@0$1w2hs2n>wq zMWv-JqA0}hT-kbHN(#x_c?yZSkd}p+PsOt-W%*swscFL}8r6p}E=>2jMpCrPnll^= zq9VDluC;JVj0K|Kj;j)nz7%CM-{gM6lp0QIsL3PPE(S7q!rTRGgByEe$|~+SMkEe7 z5E0-|Y2!yskN%z{X*Mq`DZ@7O-ZVP0z^q z6}mO0Z;uG?3T4ZKeKu$M3A4>>XdpmBk~>lloGc|6RyJ2o3ynloLwnEmY-pIfD7TPc z0u`~nX7$^=HF&bTNl5~WKLf6>WG!Ppczeb?CD!xkB&&=A;IBsV)jNB)GgkJU%ge-_G} zzuvt)PW)Q>C4-05AX7UGTbZTnM=l)lRzqRDVSyh_5Z;S#E}t0)h`Zj)?iHtF`9`&P8c|s}ld|3=Ux^#zgoH_oE z3KK0fH&xmET<~bLB28|)Vym;;4Jx)WJc!oQe;<`|eVUp)#%jiGwI1?~7ZqN4 zva9D=QL6LeI3$4Bz!#y^g^YdKPRiau0?qi%aGEcH_R3O2H$svMixgNN5wgqOUy0vaJ?6RyrXP-}>YPSGBT%+HM1rzsrdUp;AS0q+X=qLT4~RLv#cR)>X^Q`YefEyU2o8--&583wKJIp1uz zSy`TOo<}LSq%{t=+yAF7Jy+<6x#rN%I=z*Azq_*|^T8b(Lq>tda8)LH6wyR=qRnpy_%j-5#T@WK%`Rw5~wC~V+y|89PLVsgkNg7qVz76v& zlZv#K5>F6ZH|wurTx~%cFd?S>Q_jPxsL=i&{3@SKvc(I3_s&;H@#?@Hb1n3PeqYXm z8@9ODz_rj`yjH{7Kdu6a1j*Bd=N{+h^sK(o|<3+&|a9Rd}nt0=YQ$py0g5 zm+zX4Pnm2;o>au({4wh^m1-<+a z#Od1FCgtdykBcn0T|{n7-9@&IgUUEnUB(8$OXdZs3{#qHemn8-y9bq`_yn}+H5yfp znA~EPu&4m6V4iK+v3}M(3oxwa|%h|Sy%=m^ehqc?n%ptlR)ymsmmL;!( z??b~i@Onrl1`vS)!p-^p;(c$JZrR?-!Gf2Q!iFLFl%QOsrmh?EmT0(d z+5q$&Un zT4@U!mT%#`yl#)@GuV#?t%q_696;Z9*OWvhM^6u{v-}}~U zUL!zSrp}P5yjpO_6&cDtDyQjw>c+|r4Yv42;_?~^>5ZZV-sdowU-SQCW1 zs~gEUs~@T3UyVrYyUM}cdvxZ^Wwe=IJLYO)m`q0bTC+|$-%N}9`CDO6iS5$TvPoDW zrOCCk{7+5(pptCgXO}O}gdnT|d zY;yGTwZW&y)*Ub1vphL)P^wS5+J%@HAm)D1(C83&kIBaVBl^gG(^FK?_TzjkQVQtw zV_{7Tu$0Q{PBBcY93HNGZ*$_P`^V1v748!C?6B>`y*7}bka;ovIh1aUSQDDGGd{THQeMn-R-TS`B?pv9Y#aS#g)36 zfHc{XFV)5B>(GGopePrAZOj43;2TRf7q&R+O1AzdQd$I7Rvxz3G1g+Zb-xNLZBztR zmDTn(B!iE7x2pQO2gX$yZ{BDLlP6g6aIn{)W8WW)IASCxN%3n((SH(|E|tu4%O^S@ z77K{SMXW*mW{M}JqM*iiu~r;c^T2cEOKK%yUL1PxPis5J6~_xZD=@p;T=JDgE=g=- zmE-79$1NXx$EkUhuD%L;oGxLP576rMtb zE_V@59g~9EfamXp&TASR=tQ?|7p?5oVF??HNu0&q&ynM?<&9 zT?q3yD=oSj;PQ`C?#nmuCK8oo-4K zqBKP(Zz?xqCj_&QIIfL2@P7U-c~0S?H_3l|H&n5f(%vXzm5v;wUKf9eGtOZUu@v#8 zi(_cuk(Gr-@{Li9nKF8M`7gU~74@%0n(LxSP?Q*>qtS#RrX1wOW@b5hQk~o`plW~Y zFbW^;!40Pj;_|z35<9(P7iIC)ovb|j$<%T;&r3km!)aTIB`_?+fLeJ+dC~SQitc>h?24LRORb@Y-Nhv1X5B)Z`^2$3T6H6 z<8pjG!*}QF_Rm@~R|p9}F~Kh`1aJ15P@ylvyp#Vs?IjNe{X%%9z0g)_oT0`(#}6N| zL8~q7t;{$+tdTVGbJ=+4e)4QjqJc> zG^d1jci8MC`}=RJV6QoaOK*FxJ`Vg>im2Zi0qIAI{y3C7RloFAb$FyuiS^kMD&2d{ z>H_9O)E!g!d*7Fa4uEWO80azfaXLXHu%;j_8naz6ly#sGM2le|XN?yq>~c(J!9gT2 zWJeV0MtK$OU@S%8?rPJwq(ea;tjf>pUZ`F?U#|nn*+n0 zn;w?!C>;M-2_655uJT(V_xRJf5U+YJGZ{m9(tas>U2lt@=Rm9aa@Su?c$Y*LE$Vpi z^bvXM=IC@pyhnyDG~UQi>DN!SNP-a9oC4v?n(W6Pwre`rkGNI~6e4(rITvw(myBz2 z(lM3Tuk>VYtxjoNF`;mj0)6xoz}lCHWEV}LIZ{C!sbdq;FAdr+ronI;LP+dxuR=qy zT#~I)H#dm|2I*uF42(x_^|)zFIXc20H5}hz-UusekI4^$r*I#}Rb*2CL`!S3zB#)r z``Vt`MbCY^1qO&TFncr+~fGC-Vg^A)M&UKR` z@hBR0h}*j}?7#Q?C`~->-fa(@_-ri6nwpFm>#Ue_H(S0CV3F-liZtGzOW{#&#fyVl ztt@gq^YvXt{ZpHPwvNtsAPKd}o(Cp>444e7WmzGAHQ zSX{=toH@l6XU{fmoGeoz<5@P%2MC zIC8j+;H|;T%oO-Vt1o|m-$nH}xq33*7@Pq7SE?d{t^^iiB)Ly)e`jQO_V?WySi;`0 zsZ2`$oEk_am*_2(T+^ONQeVy7C@KaHi{sCHv9(fPiWO;Wzh+S5G2{I^lJ?T{sIRPo zOQtXR`xt+ld*rb(!hIVMcr8*m&udv;dn8Vlm;WS@5lQU*`zye=?G+4lgJU{xwH`-m-#~o`V_=o?;eph22(4@^* zo1p_t2ye`kO2cnwI+NTv6sN0Bx!f`Nonm!QoZ9yOj?_nqKOg4Jkz*XOw(E=#x0okN zD*#sD-(FqKO9IyKa2j6?E2|%HjQYLxKYn9|1gJ)eIqqbvQ7S&>;UKLes z_WV{<&OuA&k%HWOEjGG!oM!R=`)+OxM{$sckN*IR$`b*CtZ51tUfTvNtpS@knC0Nn z%m2<(i`7r}@wqZ*PK4;ABeh-YJtqNS4-OXS>Ft<5CpXk40zutHkDUJ)l;oO5aPmLi zDFJf?W}RfD*wa0Dp#;VC!bu`Scb0s;bx0o1uV+h!3DbH=RpD`iN>SApH9liecQ4G%(- z23_k^$nI^n&A>q=!BPKQYz3qrx@ZYIhr8FmPha2dDwJe;59~X^+9b=ZEWJ#GZ}6WH z$QCplwbUHnKf0qw@a7qEYbK7E#t)btB}9u$<{9Fsqw#b4KI_coQ7k2prh88!Hwn`8 zr-_4ooUJ%DapH8MxRP)y552?=gF&7iCR%)LH~Z8y1gJvUjw&)kqU2)>v*7?ybg0jX ze=G7C(_~ts+e7eKfD;iE(9wjX^2{QlaFToKb;;2#(*WOA8(n!ekHCe653ix z_n$|?ZoB%^S^@k2j3@~mk1R|{FzB

I9hm;5L7m_V-L9Eh09#@5=?LI+Okc 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 z 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 diff --git a/tools/glyphs/build_glyphs.py b/tools/glyphs/build_glyphs.py index 3ce07d83f..029462075 100644 --- a/tools/glyphs/build_glyphs.py +++ b/tools/glyphs/build_glyphs.py @@ -61,6 +61,18 @@ "lstick": "T_S_L_Retro", "rstick": "T_S_R_Retro"}), } +# The PS5 style from a flat folder of one 64 px PNG per button ("ps5_a_butt.png"): the pack's A, B, X and Y +# are the pad's bottom, right, left and top buttons (Cross, Circle, Square, Triangle). It has no L3/R3 click +# art but the stick icons, so L3 and R3 are the plain stick icons and the stick symbols the all-directions ones. +PS5_PACK_REFERENCE = 60 # source pixels of its round face button +PS5_PACK = { + "cross": "a_butt", "circle": "b_butt", "square": "x_butt", "triangle": "y_butt", + "l1": "lb_butt", "r1": "rb_butt", "l2": "lt_butt", "r2": "rt_butt", + "start": "start_butt", "select": "back_butt", + "dpad": "dpad_all", "dpad_ud": "dpad_updown", "dpad_lr": "dpad_leftright", + "up": "dpad_up", "down": "dpad_down", "left": "dpad_left", "right": "dpad_right", + "l3": "lstick_none", "r3": "rstick_none", "lstick": "lstick_all", "rstick": "rstick_all"} + # Keyboard file stem (between T_ and _Key_Dark) -> the names the game looks keys up by: SDL's scancode # name, lower-case, spaces dropped (input.hpp, InputPrimaryBindingName), or mouse1..mouse5. KEY_ALIASES = { @@ -95,10 +107,10 @@ def make_xbox_menu(src_dir): return out -def pack(images): - """Shelf-packs {name: RGBA image}, each cropped to its pixels and scaled by CELL/REFERENCE. +def pack(images, reference=REFERENCE): + """Shelf-packs {name: RGBA image}, each cropped to its pixels and scaled by CELL/reference. Returns (atlas, {name: [x, y, w, h]}).""" - scale = CELL / REFERENCE + scale = CELL / reference crops = {} for name, im in images.items(): box = im.getchannel("A").getbbox() @@ -134,12 +146,42 @@ def pack(images): return atlas, rects +def build_ps5_pack(folder, out): + images = {} + missing = [] + for name, stem in PS5_PACK.items(): + path = os.path.join(folder, "ps5_" + stem + ".png") + if os.path.exists(path): + images[name] = load(path) + else: + missing.append(path) + atlas, rects = pack(images, PS5_PACK_REFERENCE) + atlas.save(os.path.join(out, "ps5.png"), optimize=True) + index_path = os.path.join(out, "glyphs.json") + with open(index_path, encoding="utf-8") as f: + index = json.load(f) + index["styles"]["ps5"] = {"atlas": "ps5.png", "glyphs": rects} + with open(index_path, "w", encoding="utf-8") as f: + json.dump(index, f, indent=1, sort_keys=True) + f.write("\n") + for m in missing: + print("missing:", m, file=sys.stderr) + print("ps5", len(rects), "glyphs") + return 1 if missing else 0 + + def main(): ap = argparse.ArgumentParser() - ap.add_argument("--src", required=True) + ap.add_argument("--src") + ap.add_argument("--ps5-pack", help="folder of ps5_*.png buttons: rebuilds only the ps5 atlas from it, " + "keeping the other styles in the existing glyphs.json") ap.add_argument("--out", default=os.path.join(os.path.dirname(__file__), "..", "..", "port", "glyphs")) args = ap.parse_args() os.makedirs(args.out, exist_ok=True) + if args.ps5_pack: + return build_ps5_pack(args.ps5_pack, args.out) + if not args.src: + ap.error("--src or --ps5-pack is required") index = {"reference": CELL, "styles": {}} missing = [] From 7113bb49b523c0d9dfc17c2a070d46c24cf37d8f Mon Sep 17 00:00:00 2001 From: Jeffie Date: Wed, 7 Oct 2026 22:07:43 -0500 Subject: [PATCH 20/20] Text and symbol shadow: two sliders from none to dark Text Shadow and a new Symbol Shadow are percents in steps of 5: 0 casts none, 50 is the soft shadow (the letters' default), 100 the deep one. The button symbols default to 25, a lighter shadow, since a solid shape stacks it where a thin stroke does not. The old soft and deep config values still read as 50 and 100. --- port/lang/de_de.json | 4 +-- port/lang/es_es.json | 4 +-- port/lang/fr_fr.json | 4 +-- port/lang/it_it.json | 4 +-- port/src/clsmes.cpp | 53 +++------------------------- port/src/glyph_draw.cpp | 12 +++++-- port/src/options/rows.cpp | 41 ++++++++++++++++++---- port/src/platform/config.cpp | 22 ++++++++---- port/src/platform/config.hpp | 8 +++-- port/src/text_shadow.hpp | 68 ++++++++++++++++++++++++++++++++++++ 10 files changed, 145 insertions(+), 75 deletions(-) create mode 100644 port/src/text_shadow.hpp diff --git a/port/lang/de_de.json b/port/lang/de_de.json index 5333c9180..05953e77b 100644 --- a/port/lang/de_de.json +++ b/port/lang/de_de.json @@ -157,8 +157,8 @@ "options.input.mouse_camera_return.help": "\"Vertikale Rückkehr\"\nWie schnell die Kamera\nbei ruhender Maus auf\nnormale Höhe zurückkehrt.", "options.video.text_shadow.label": "Textschatten", "options.video.text_shadow.help": "\"Textschatten\"\nWie dunkel der Schatten\nunter dem Text ist (bei\nTrueType-Schrift).", - "options.video.text_shadow.choice.0": "Weich", - "options.video.text_shadow.choice.1": "Tief", + "options.video.glyph_shadow.label": "Symbolschatten", + "options.video.glyph_shadow.help": "\"Symbolschatten\"\nWie dunkel der Schatten\nunter den Tastensymbolen\nist.", "options.input.glyphs.label": "Tastensymbole", "options.input.glyphs.help": "\"Tastensymbole\"\nNeu: neu gezeichnete\nSymbole für dein Gerät.\nOriginal: die der PS2.", "options.input.glyphs.choice.0": "Original", diff --git a/port/lang/es_es.json b/port/lang/es_es.json index 888f977cd..2d7e891f1 100644 --- a/port/lang/es_es.json +++ b/port/lang/es_es.json @@ -157,8 +157,8 @@ "options.input.mouse_camera_return.help": "\"Retorno vertical\"\nRapidez con que la cámara\nvuelve a la altura normal\ncuando el ratón se detiene.", "options.video.text_shadow.label": "Sombra del texto", "options.video.text_shadow.help": "\"Sombra del texto\"\nQué tan oscura es la\nsombra bajo el texto con\nfuente TrueType.", - "options.video.text_shadow.choice.0": "Suave", - "options.video.text_shadow.choice.1": "Profunda", + "options.video.glyph_shadow.label": "Sombra de símbolos", + "options.video.glyph_shadow.help": "\"Sombra de símbolos\"\nQué tan oscura es la\nsombra bajo los símbolos\nde botones.", "options.input.glyphs.label": "Símbolos de botones", "options.input.glyphs.help": "\"Símbolos de botones\"\nNuevos: símbolos\nrediseñados para tu mando.\nOriginales: los de PS2.", "options.input.glyphs.choice.0": "Originales", diff --git a/port/lang/fr_fr.json b/port/lang/fr_fr.json index 7ac2071c6..07029f681 100644 --- a/port/lang/fr_fr.json +++ b/port/lang/fr_fr.json @@ -157,8 +157,8 @@ "options.input.mouse_camera_return.help": "\"Retour vertical\"\nVitesse de retour de la\ncaméra à hauteur normale\nquand la souris s'arrête.", "options.video.text_shadow.label": "Ombre du texte", "options.video.text_shadow.help": "\"Ombre du texte\"\nOmbre sous le texte, avec\nune police TrueType.", - "options.video.text_shadow.choice.0": "Légère", - "options.video.text_shadow.choice.1": "Marquée", + "options.video.glyph_shadow.label": "Ombre des symboles", + "options.video.glyph_shadow.help": "\"Ombre des symboles\"\nOmbre sous les symboles\nde boutons.", "options.input.glyphs.label": "Symboles boutons", "options.input.glyphs.help": "\"Symboles boutons\"\nNouveau: symboles refaits\npour votre manette.\nOriginal: ceux de la PS2.", "options.input.glyphs.choice.0": "Original", diff --git a/port/lang/it_it.json b/port/lang/it_it.json index 5a8fda644..f72a7074f 100644 --- a/port/lang/it_it.json +++ b/port/lang/it_it.json @@ -157,8 +157,8 @@ "options.input.mouse_camera_return.help": "\"Ritorno verticale\"\nQuanto in fretta la camera\ntorna all'altezza normale\nquando il mouse si ferma.", "options.video.text_shadow.label": "Ombra del testo", "options.video.text_shadow.help": "\"Ombra del testo\"\nQuanto è scura l'ombra\nsotto il testo con\ncarattere TrueType.", - "options.video.text_shadow.choice.0": "Leggera", - "options.video.text_shadow.choice.1": "Intensa", + "options.video.glyph_shadow.label": "Ombra dei simboli", + "options.video.glyph_shadow.help": "\"Ombra dei simboli\"\nQuanto è scura l'ombra\nsotto i simboli dei\npulsanti.", "options.input.glyphs.label": "Simboli tasti", "options.input.glyphs.help": "\"Simboli tasti\"\nNuovi: simboli ridisegnati\nper il tuo controller.\nOriginali: quelli PS2.", "options.input.glyphs.choice.0": "Originali", diff --git a/port/src/clsmes.cpp b/port/src/clsmes.cpp index 7db25b7b8..1758c651f 100644 --- a/port/src/clsmes.cpp +++ b/port/src/clsmes.cpp @@ -20,6 +20,7 @@ #include "platform/ttffont.hpp" #include "rect.hpp" #include "snd.hpp" +#include "text_shadow.hpp" #include "texture.hpp" extern float RandTbl[64]; @@ -249,57 +250,13 @@ bool QueueTtfGlyph(ClsMes &mes, int index, int dark, int offset_x, int offset_y, TtfQuad(edges, glyph, x + dx, y + dy * rows, width, height, er, eg, eb, std::min(cap, ea)); } }; - // Retail's shadow is cast down and to the right and falls off softly, because its bitmaps are soft. - // A crisp glyph stacked at two offsets reads as a hard outline instead, so the shadow is laid in - // as taps at half-pixel steps along the light's direction, which the texture filter blends. - struct Tap { - float dx, dy; - int grey, alpha; - }; - - static const Tap soft[] = { - {0.6f, 1.4f, 0, 0x0C}, - {0.6f, 2.2f, 0, 0x0E}, - {0.6f, 3.0f, 0, 0x0C}, - {1.4f, 0.6f, 0, 0x0C}, - {1.4f, 1.4f, 0, 0x16}, - {1.4f, 2.2f, 0, 0x1C}, - {1.4f, 3.0f, 0, 0x16}, - {1.4f, 3.8f, 0, 0x0C}, - {2.2f, 0.6f, 0, 0x0E}, - {2.2f, 1.4f, 0, 0x1C}, - {2.2f, 2.2f, 0, 0x20}, - {2.2f, 3.0f, 0, 0x1C}, - {2.2f, 3.8f, 0, 0x0E}, - {3.0f, 0.6f, 0, 0x0C}, - {3.0f, 1.4f, 0, 0x16}, - {3.0f, 2.2f, 0, 0x1C}, - {3.0f, 3.0f, 0, 0x16}, - {3.0f, 3.8f, 0, 0x0C}, - {3.8f, 1.4f, 0, 0x0C}, - {3.8f, 2.2f, 0, 0x0E}, - {3.8f, 3.0f, 0, 0x0C}, - }; - static const Tap deep[] = { - {0.8f, 0.8f, 0x40, 0x78}, - {1.4f, 1.4f, 0x10, 0x78}, - {2.0f, 2.0f, 0, 0x78}, - {2.6f, 2.6f, 0, 0x78}, - {3.2f, 3.2f, 0, 0x60}, - {3.8f, 3.8f, 0, 0x40}, - {1.6f, 0.8f, 0, 0x50}, - {0.8f, 1.6f, 0, 0x50}, - {2.4f, 1.6f, 0, 0x48}, - {1.6f, 2.4f, 0, 0x48}, - {3.2f, 2.4f, 0, 0x30}, - {2.4f, 3.2f, 0, 0x30}, - }; auto cast = [&] { - const bool deeper = ConfigGet().text_shadow == 1; - for (const Tap &tap : deeper ? std::span(deep) : std::span(soft)) { + // Retail's shadow falls off softly because its bitmaps are soft. A crisp glyph stacked at two + // offsets reads as a hard outline instead, so it is laid in as taps the texture filter blends. + TextShadowEach(ConfigGet().text_shadow, [&](const TextShadowTap &tap) { TtfQuad(edges, glyph, x + tap.dx, y + tap.dy * rows, width, height, tap.grey, tap.grey, tap.grey, std::min(cap, tap.alpha)); - } + }); }; switch (mes.style) { case MES_EDGE_WHITE: diff --git a/port/src/glyph_draw.cpp b/port/src/glyph_draw.cpp index 2c70deaa7..18e405b0d 100644 --- a/port/src/glyph_draw.cpp +++ b/port/src/glyph_draw.cpp @@ -6,6 +6,8 @@ #include #include "draw2d_port.hpp" +#include "platform/config.hpp" +#include "text_shadow.hpp" namespace { @@ -68,8 +70,8 @@ bool GlyphDrawSlot(glyphs::Button button, const CRect_i_ &slot, int alpha, float float x = static_cast(slot.x) - grow + (slot_w - width) * 0.5f; float y = static_cast(slot.y) - grow + (slot_h - height) * 0.5f; auto a = static_cast(alpha); - // In text the symbol casts the font's shadow: its own shape, black, half as strong, one pixel down and - // right. Painted over art it has none (the art has its own). + // In text the symbol casts a shadow (text_shadow.hpp) in its own shape, so it lifts off the + // picture like the words beside it; config glyph_shadow sets how strong, apart from the letters'. Painted over art it has none (the art has its own). std::vector quad; auto add = [&](float ox, float oy, u_char r, u_char g, u_char b, u_char alpha_byte) { quad.push_back(draw2d::Vertex(x + ox, y + oy, 0.0f, image.u0, image.v0, r, g, b, alpha_byte)); @@ -78,7 +80,11 @@ bool GlyphDrawSlot(glyphs::Button button, const CRect_i_ &slot, int alpha, float quad.push_back(draw2d::Vertex(x + ox, y + oy + height, 0.0f, image.u0, image.v1, r, g, b, alpha_byte)); }; if (grow <= 0.0f) { - add(1.0f, 1.0f, 0, 0, 0, static_cast(a / 2)); + const float rows = draw2d::RowScale(); + TextShadowEach(ConfigGet().glyph_shadow, [&](const TextShadowTap &tap) { + add(tap.dx, tap.dy * rows, static_cast(tap.grey), static_cast(tap.grey), + static_cast(tap.grey), static_cast(std::min(alpha, tap.alpha))); + }); } add(0.0f, 0.0f, 0x80, 0x80, 0x80, a); gfx::Draw2D(gfx::Primitive::Quads, quad, image.binding, draw2d::SpriteState()); diff --git a/port/src/options/rows.cpp b/port/src/options/rows.cpp index 1e1fe9ba2..809c50779 100644 --- a/port/src/options/rows.cpp +++ b/port/src/options/rows.cpp @@ -492,6 +492,34 @@ int LanguageCount(const Config &) { return 6; } +int ShadowCount(const Config &) { + return 21; +} + +int TextShadowChoice(const Config &config) { + return std::clamp(config.text_shadow / 5, 0, 20); +} + +void SetTextShadow(Config &config, int choice) { + config.text_shadow = choice * 5; +} + +std::string TextShadowText(const Config &config) { + return std::format("{}%", config.text_shadow); +} + +int GlyphShadowChoice(const Config &config) { + return std::clamp(config.glyph_shadow / 5, 0, 20); +} + +void SetGlyphShadow(Config &config, int choice) { + config.glyph_shadow = choice * 5; +} + +std::string GlyphShadowText(const Config &config) { + return std::format("{}%", config.glyph_shadow); +} + const Row kGameRows[] = { GameRow<&ConfigGameOptions::save_cursor_position, true>("game.save_cursor_position", "Save Cursor Position", "On|Off", 0x15E), @@ -535,13 +563,12 @@ const Row kDisplayRows[] = { SettingRow("video.fps_detail", "FPS Info", "\"FPS Info\"\nWhat the counter shows:\nthe frame rate alone,\nwith ticks, or all.", FpsDetailCount, FpsDetailChoice, SetFpsDetail, nullptr, "FPS|FPS+Ticks|All"), GameRow<&ConfigGameOptions::soft_focus, true>("video.soft_focus", "Soft Focus", "On|Off", 0x169), - Row{.key = "video.text_shadow", - .label = "Text Shadow", - .help = "\"Text Shadow\"\nHow dark the shadow\nunder the text is, when\nthe text is a TrueType\nfont.", - .count = Two, - .get = [](const Config &config) { return config.text_shadow; }, - .set = [](Config &config, int choice) { config.text_shadow = choice; }, - .names = "Soft|Deep"}, + SettingRow("video.text_shadow", "Text Shadow", + "\"Text Shadow\"\nHow dark the shadow\nunder the letters is.\n50% is the soft one.", ShadowCount, + TextShadowChoice, SetTextShadow, TextShadowText), + SettingRow("video.glyph_shadow", "Symbol Shadow", + "\"Symbol Shadow\"\nHow dark the shadow\nunder the button\nsymbols is.", ShadowCount, + GlyphShadowChoice, SetGlyphShadow, GlyphShadowText), }; const Row kAudioRows[] = { diff --git a/port/src/platform/config.cpp b/port/src/platform/config.cpp index 5e6db2f9b..c4bcb748b 100644 --- a/port/src/platform/config.cpp +++ b/port/src/platform/config.cpp @@ -373,15 +373,22 @@ bool Apply(Config &config, std::string_view name, const Json &value) { if (name == "game.debug_mode") { return ReadBool(value, config.debug_mode); } - if (name == "video.text_shadow") { - if (!value.is_string()) { - return false; + if (name == "video.text_shadow" || name == "video.glyph_shadow") { + int &target = name == "video.text_shadow" ? config.text_shadow : config.glyph_shadow; + if (value.is_string()) { + // the earlier two-choice setting + const std::string shadow = Lower(value.get()); + if (shadow != "soft" && shadow != "deep") { + return false; + } + target = shadow == "deep" ? 100 : 50; + return true; } - const std::string shadow = Lower(value.get()); - if (shadow != "soft" && shadow != "deep") { + double percent = 0.0; + if (!ReadNumber(value, percent) || !std::isfinite(percent)) { return false; } - config.text_shadow = shadow == "deep" ? 1 : 0; + target = std::clamp(static_cast(std::lround(percent / 5.0)) * 5, 0, 100); return true; } if (name == "game.language") { @@ -556,7 +563,8 @@ std::string ConfigSerialize(const Config &config) { root["game"]["player_damage"] = options.player_damage; root["game"]["enemy_hp"] = options.enemy_hp; root["game"]["names"] = options.names; - root["video"]["text_shadow"] = config.text_shadow == 1 ? "deep" : "soft"; + root["video"]["text_shadow"] = config.text_shadow; + root["video"]["glyph_shadow"] = config.glyph_shadow; root["video"]["present_mode"] = PresentModeName(config.present_mode); root["video"]["interpolation"] = config.interpolation; root["video"]["max_fps"] = config.max_fps; diff --git a/port/src/platform/config.hpp b/port/src/platform/config.hpp index 545c30583..178d6e368 100644 --- a/port/src/platform/config.hpp +++ b/port/src/platform/config.hpp @@ -113,8 +113,12 @@ struct Config { // game.language: 0 asks at start-up, as retail does; 2 to 6 is the language to start in // (LanguageCode: English, Francais, Deutsch, Italiano, Espanol) and skips the language screen. int language = 0; - // video.text_shadow: 0 is the soft shadow the TrueType message font casts, 1 a deeper one. - int text_shadow = 0; + // video.text_shadow and video.glyph_shadow: how strong the shadow under the TrueType message text and + // under the button symbols in it is, in percent in steps of 5. 0 casts none, 50 is the soft shadow + // (the letters' default), 100 the deep one. The symbols' is lighter, because a solid shape stacks + // the shadow where a thin stroke does not. + int text_shadow = 50; + int glyph_shadow = 25; bool operator==(const Config &) const = default; }; diff --git a/port/src/text_shadow.hpp b/port/src/text_shadow.hpp new file mode 100644 index 000000000..8d55ac91e --- /dev/null +++ b/port/src/text_shadow.hpp @@ -0,0 +1,68 @@ +#pragma once + +#include + +// The drop shadow the message text casts, as taps at half-pixel steps along the light's direction (down +// and right) that the texture filter blends. Message text (clsmes.cpp) and the button symbols drawn in +// it (glyph_draw.cpp) share it, so a symbol sits on the picture the way the letters beside it do. +// Offsets are in logical units, in the shadow's own colour `grey`; `alpha` is capped by the caller. +struct TextShadowTap { + float dx, dy; + int grey, alpha; +}; + +// Calls `draw(tap)` for each tap of the shadow at `percent` strength: 0 casts none, 50 is the soft +// shadow, 100 the deep one, and in between the soft one fades out or gives way to the deep one. +template +void TextShadowEach(int percent, Draw &&draw) { + static constexpr TextShadowTap soft[] = { + {0.6f, 1.4f, 0, 0x0C}, + {0.6f, 2.2f, 0, 0x0E}, + {0.6f, 3.0f, 0, 0x0C}, + {1.4f, 0.6f, 0, 0x0C}, + {1.4f, 1.4f, 0, 0x16}, + {1.4f, 2.2f, 0, 0x1C}, + {1.4f, 3.0f, 0, 0x16}, + {1.4f, 3.8f, 0, 0x0C}, + {2.2f, 0.6f, 0, 0x0E}, + {2.2f, 1.4f, 0, 0x1C}, + {2.2f, 2.2f, 0, 0x20}, + {2.2f, 3.0f, 0, 0x1C}, + {2.2f, 3.8f, 0, 0x0E}, + {3.0f, 0.6f, 0, 0x0C}, + {3.0f, 1.4f, 0, 0x16}, + {3.0f, 2.2f, 0, 0x1C}, + {3.0f, 3.0f, 0, 0x16}, + {3.0f, 3.8f, 0, 0x0C}, + {3.8f, 1.4f, 0, 0x0C}, + {3.8f, 2.2f, 0, 0x0E}, + {3.8f, 3.0f, 0, 0x0C}, + }; + static constexpr TextShadowTap deep[] = { + {0.8f, 0.8f, 0x40, 0x78}, + {1.4f, 1.4f, 0x10, 0x78}, + {2.0f, 2.0f, 0, 0x78}, + {2.6f, 2.6f, 0, 0x78}, + {3.2f, 3.2f, 0, 0x60}, + {3.8f, 3.8f, 0, 0x40}, + {1.6f, 0.8f, 0, 0x50}, + {0.8f, 1.6f, 0, 0x50}, + {2.4f, 1.6f, 0, 0x48}, + {1.6f, 2.4f, 0, 0x48}, + {3.2f, 2.4f, 0, 0x30}, + {2.4f, 3.2f, 0, 0x30}, + }; + percent = percent < 0 ? 0 : (percent > 100 ? 100 : percent); + const float to_deep = percent > 50 ? static_cast(percent - 50) / 50.0f : 0.0f; + const float soft_scale = percent >= 50 ? 1.0f - to_deep : static_cast(percent) / 50.0f; + auto cast = [&](std::span taps, float scale) { + for (TextShadowTap tap : taps) { + tap.alpha = static_cast(static_cast(tap.alpha) * scale + 0.5f); + if (tap.alpha > 0) { + draw(tap); + } + } + }; + cast(soft, soft_scale); + cast(deep, to_deep); +}

I9hm;5L7m_V-L9Eh09#@5=?LI+Okc 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 z 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 literal 0 HcmV?d00001 diff --git a/port/glyphs/switch.png b/port/glyphs/switch.png new file mode 100644 index 0000000000000000000000000000000000000000..73c568c687ff5a0b4c6f7f686af9c7f4c8cf0101 GIT binary patch literal 56109 zcmZ^Lby!pH8~13CMx`4`=^TxMfJ0Hb8>C}|NFyK}BB3HBIO%3|rxHUHHo7J`Qabk@ zzVGk-^Sv%E*&l44=RD7S-=8{*d#SBKeuwc62m~U3s;Q<60^tKMaX`d`z%L5xy7hL>Z7JP`!>|q~7+W`Oww0JOzD~&0wU$eBWr! znQ`t{9w}dZJXbP0(@;l)8u#fRs@J&157;k-r-RzH403!z!{$oAY(eH>t)6@9LAimy zH&+*0JVVyAJwuecVyU^*6Y0fl`~JT##E;*7?|JT05f^)8;+S(B0MlO^VDh}u4+@gU zY;3$-qh<^y?;x~_^n4!FZrSX;Y71JNFzx(GaoKr-o&N?`>MSe6#4Uw<=XwqLH8T?^ zO`TH;Un}fS7E`apEcjz88gCXQ`z!42vR}_QlgFv8Jmz#E=u$5WGIf2ThFM4CTrTG1 z*4Inm7CzUUB;GUNjnGi-#0W!FHBFD;H00NWM*deMo|s&!?bw{ z(Dg_SJY!;OT06n7aO7P#j2ws+v~M;rCF0p}zUHWUTF8qIba!>Fd%ZgsfJ5CL7dy3K zVB!jDbP)eMj6EAvUi*#?lnR>PIF+k79>xj2+r@v^U8&*3&C5!TQn0YgT@2h zL{}MBS%!e^wyQw(x?6qZOwtPDNaTQ=ygV!*5x6l`-KZtAkSePh+x|SEVN*0X8MiCe zsh+5@RxqKta&~fZ5^kF%S4$S|;_lua?sH=?Kp%hNMr>Yi-C=b6mg*6NKSP(!*U#@- z^i7_#lhwxkhlauP4Ld0u?OXkcFzC!5bGZe< z-Nxm-u-R8iv-sF^d0uKzmX)FF4zjF*D_IiPe2UFak3a@axk5)gbBVKkH2K+#AR&`h zdubk3WXSRO`wwr1Ys>fEwr}|6#;2Cj?pQ*q?b@7^!y}^{&!T z6aDINo35RZ^tsWZHP+G^Rsdl)!O0V*T3Datwcn<$M*Of?CkrjDgnjZAXPEz!bt@TM zUy}p*-wzoF3Omdf`8-gjMLeyCi@+w~I4z1<)Y|sPd-B0@UnO3@BtGCloZF5arej)k z`C%X@&fi>9!vlZ+MMlmXDx}0FQ$4=Qhrw2TK))6i5Im_1x{i)T=B=;Yal1PCVd==3 zhsGlVQ>`3SM8&xUK=h#Y z*E=))AXfbM2c62qOW&A0nwFnpB|ho?-;?9IUqJEs^^0TOb^S~xNU=tOpYsOGx*j!c#TtL^1wVOlwO^Wu6+t{8F})b>jSxtg>wJetacrxqQ;PN)JLEeiN(| zcEsX8cTg~m8L_0B#!%o*gn+(~@>vYFCxhTj)KAL zaO|-LjuX~Dk^X;eDCyB+S1f5|6U~W}tozxi-H=#;vf|vAt784LJm`27`w6fEnQAD>#7R7Q(lLVbNTs!J8<(ijP|?LbP3Qe zT>F+1YKAloE(5~X?YB#E!a<-6%&7!Y-aq+Msu_W7RvTg~?fXcD!!d~n=n&Bu%j=vNmEHYaji zJRnhAXHQSZjz?CUzc+SwPp*D!5D5-vt)9LX13lx10WS%b@K{(_T!u4+Jos8vfkoJs zx)uMgvB`hN!&???OdX={&lqCtZIa7f7PwpJXh%2Idyn1kc(x_Wu-q7$<6yD_;TMAG zXZv`V(27aJ8$I&W^e@{Ux`LK6%{*-MECxqffL{A%#n=r`+&EDkpXbx$eUO}-9BwU{ zphe3>mW=0u0;gQO*z5T8e~Oiut$jWkPTNY13usU2a=VW0Kw-~@qc3f?(K8Hsd!7FN zjldCRVdmM`-qx3>zUp0a-IoIT&%=}BkHhwZ3_MT;9Q73moALa(pYIJSCHWnxVh$wF zCL_4N44IjzJVxpB-3xU$L4FR!N<1eTRAvUTbN18QK}sS)*4EbH-+u=t<=!=QjVdbq zbnJ4{QUkZ62RnW*4J#!8MU0qfXvEwJ%v;!xmT8-VW;=jPak915VsJDsLSWDYjt#p} zP*c3vcUU^2E#NTMyzbSgJIEtdcL6m$eQrE=X`AJ!_1{FT>h=de@PjhA@pDH}eNPXe zSBJ_5C)vnIzN9JA(?NrW6m5>G#3%1ls|w4+>I>{(!yq&*(z3~Q3$^7Eieo+uc80$e zm7k+jZ3XNvdWCFOBYr*#p8{9Cs$z0|cKA-gW2Tzg6Yb!8bVI{Ru^Kl6?rJziHlo`PtDRxFCQI?@Ub{$13sbI%=MZG zHsL8RCl?qQ1RSEwKVrW2|J`u%j$&P$mk25ntQ92Anox3Gp#j&LhqVbhc^CSL5B5yG ze845WSYo-w)*Sf69mRLqRum_^@zWIcQftkWpMam+ZC1U8)D!C{X%Q*&s_M(9_GF*8 zZ06To!Dn1-eKMU;Zl$YK%Eu6sTD`<);uPWe6Ai7cvu40P*tnu9ugj@cLOJ@z1cw79 z)~~Eleu`nqu0LI>-ohG=d@A6E5NDJh4Aa7Dw+$MuT@Z{q^F*Ah0yp7&E=$Ul0s~Y~ z@M|xnq^V#3Zx5$Ta+*}a(`j@eRMZM%4+$)ywM-@FpJopc=%wp=F+0XSV*dF2%TG1q z)i>N%2Oiw*39kkd`{cW)x7j<+M?F{e!m#p{9T%5BUe$rVtN$vgs7Uhn^(|f<{unk; zTP|~cDIv0cMU=;%qiwg)7Vt?is6i(RYFsv+cWy_ng^?0YbKjQ;vS`o22JN*{TAdG} z#@~NVs0v=iE{lYnGlA*e>oUF`S2hsIypDXCrJA(Z$-{ zS6fD%J;;h#ucfy{E5Id^h8`s3p%wQ6MAAQ`1?Dus;RgtWb~7-v4LMt;4i!l93l2r1 zRP=l#XyWkc?KUtNiRs^BB?2}M0k#ov#w$T$dLl>7`iQP=Kj7qttVFyYTOWu1{8;UZ z@)*fgByBz2?tYSYMizQ{d0g#2yp8wi!F>?UmxmxYFqozJSFaz(ih#fqhYDjNi`<+m zHn>$yPyWi|U0P%mB;L5*0eyobD=~p;zQqnEc@By5&A0Cg8`w=BIbry}BWce`VtQOf z1w7lSjNVmhb1b!?5ke=P3`9TViLh2NXV`c)`rbesQD984+7DHSr-mS~z&6lbt@uk7U`*y~O$~K2I=`fbL}sy` z#K;^_A9U!*aq-_4fqR9Q@Q81e64B3IKj>i?1g~?H`$IpCS*OvfktOSO`FCC_YL-}= zjEJPI{#ZXeJ8A5U^bEm#QQ}|v`(=qb8?Quv>oMY-q!I%`$fjTzD6_F9b&izF4>osp za<=|FB?d7vG9KCQnWe~Gj)$J?PcxGXe#wuU#)v|a(>iaCtIm)zoss%-1_DD9riEX> z;#gg*7}a>MdS`p}9O%Vg;{U0@&2Mkd0hTu;F{a={Ks_%Ubw)XM_-#EvPWTI*8&4&s z$8TQuUJX)E&pL{5!1c_pAnb)&Vr)~ot`JhhGavzcWp)lj$;s#sv=#2Keqag1JxhuK8q%*alZ?t(DZHp^3Pa>U&ZyEYgIe# zY=gV1Tn?*vyikrd`nETO-W=w=7>Z}XGQR|1K-Di(?8kK7yVMz6|7afP2O!Ho9E!ly z#unPWSR99TADOy>lhZ)q1f;YZB`*;a`4gOmh@VMi9TGt~(-?KV)d>_TAth{EVyecD zNKh+(is6oCNf&{}ix-qiH#IK`J3HANcvKk}&>#@Jv9S*5#xVxXRG*)PZ-9anim@zl)ElAkX$c_X_+iD3j4)|W0+*@*5mg4KW^l@&ypY( zZWCF-+&uqGS$szWw)Aops`}qw=LJaHE|?2kcB{cpUqlU&cv@L7`}C+Aj5 zdi*t9vDLr#a}Oe{*j5v-3t3@yZtvlL=S2+3NlTMU+dgA*F28662xu2yDU=TS(wZ=p zL8(9O^V5Zsz}x~`Hma(49-Y_1z?8E4B=EQBle!RS;+FEs$^8X-q8xag?()l*JDQCv z;@;@53f08yNofT?B{r4ZqH-<%G16M>>d@;r;>W~Il?iUr3EN9c=~k+#*KgbRdq|x@2z#_d*A*z-*OcScuL^}csUAv@K?`Fbw?>slP(F#nC&x?HA&W$n9Do{ zXcpm%e}cr;uK=3#E$@ll_z%DKx&S!jWi$i*#d57x1svdNq#VMQt*@wn|6}ob1KP-}Evqh>{^dS#B9*o=e$me-A5PHedcREo>}uB+eVq4?9Ucu2LMU zyOwzJe1NuS|AuZ?C&e(MPquwLvu-68a=W<7*URSCz>t*M^HU!vA;Nz zlHlIlCnh4jXc$i|mS?1kVM#^p8KeZf&T@-y@WcNpGx2=l8~h)P9Q3n<9%1rv*(;^2 zd1Py$Ts1vY_*nI-ro_Hyl3N9DQ}~wc@6kn?Ij9;zs(lQ|e zOl(o9=6AVM@5ofZ*Ff7PzX)GeS_m@Ji!zJ7swz9REP|H?GOUL&gAYmSJ^n>tFm{$*L;*0cq!_&MTpi4a75qP}i?+-Mn%uHG_om1!kK`*p(aTIxD zwH}i>Fn;=e=0J-)o8x2oX2Y6Ea7AI;4n=4NUZ8~Ve>}LQwY41%Kqr7E z{XA5-?>J{Py!Bk4-=3SxiXR4EU(C`x@%&$@=WKlL&!Cub3MdrYK9Sifc$D6>b!WsY zq6ney~_~k2{}Ds9ix{YSvUUb;w}TgveL`Rxj6+6Iem^Pp#fWQ6ulQMnMv7d zY3>kpK~_6r+iw*WovT}wz%YcP0x*>=U{+o$j2(t{(Yx*|ym%KBSjKSrs8dTI12^{l zm++4KCDwOT%l!lCx)9>=AF|lxp^S%CljGwxvobe>S_%KFM*qiaiYVLq{_Eft9}pQz zG`WmeR2{1DlfINHW%RGNF-&qG;*U$YWurRpnbUA>e!6a=n2xrx!dUOH<%zht@Awn$ z$JK#4f56%}bQVWX{~n%0xyTbRUnr%7B|x;{J@$Fwx^dN&zwsIVE4%x6k#mEEH--2~ z6TJU1MP?B5;CZF{Y(k`tC7k~BZEcYR15l&H9PBVPrH^0U3+j4R;l$}#{|(CJ1!Sdi zct-&sOymGf;1{~@%Kd*wzz+4yNcA;mGIRCZ-uWWxPcwlE|C7}~$>HOGOo@Lh)j5dn zyZqemP%oODi8GF>X?}h}ga1sMeixID=e^TvbeSw#_B!vUJxvT-=ox*vvkClN(Sm`K zR9mLvI1XqK8tYRzTrs)vEv@OgTz3hOB3_-y)uEm!ugCG`YGMzSmAio#`dK5A7wccU zn!8jfugNjU_ECJ8Iuppr)zuD=_7-kc#WA%bphp{(L96Pcx$zmlWWCpX0Ey2TeWi+K z;Yq%FxY16M7|ajjjXFd5%7s_}fYbBpz{7W00YcH|yA4MI|ylXOQ|^ z5N&yH*jvuc1Q{wMZpU&K7o6PExySv{{4r=Fn#tq6sjH#0g7GK(hl~m(x;BqW^oQI5 z<_w%TJDLX%I4y?1rPXxpj!2B9FJf;djoZ$=!UTCINBKferXBZlPuq-e*s<3G&-Y?C zR&Pwdx?Bd1I~&H5B{7`1la03NIk!18KeFngq^f6+nw}j5gSuQwc$gB7A!-u~3)+oy z-U)Q;Olqgq`vEUYYWY{rY61C%5{XgU_OtWTu5WDo((Ym3Gcz+&4MnT3yINf`RyM8N z8Of0sxtGsHW%J`lU&tB3Da)cuZ*y0rMeF`Mk>I$SgOsqY#&J>gKhodLaii4#U6vVD zKU=l_*8{hLUBkti!XE0YQwBYYgl>geYoY= zTj~3eyqu-22m(wIP{;a+^H&w{9U|po^Us0iAa;-Ii*E@K;% z25|LVe%Vj;GC88?y?;4Xz#L5G{N~3N-k_7L7>5N*{~PQ%&1W6|GODs?CnkK`FfAob zr{vY75mY$JkEEns5wgw|C~w3fk&07=jR|cAW?9gvf!;CyjIiP)Y>gk*QI>vDh@82z z=r43?!hKfYHrwIf8%-W0u(_D_LdgX6@AvuPdY!qjKiT?KO!lFk;+S58d!6@TWlUia zgukis;XA6i!?sb-!e`9~xr@y{j`K;ELS_w32Fz*ap~x2~{$W*MFD0|Se6a$q@5rL! zSfK(=aa1{%y>Ek`yA+VSmYt@%1)XicVt_h-Q{3Lp^3mKoj+L2NjVF!4T{;Hm$KCN< zm&*m?%^dex<&pKnqK5kV;<2zBpOJ>U`wIS=CsL-(ckM?DKG)cqRD@&Xnzn0){|Ytw z9;$KHm_THA0~>Ao@6d(El9UcKfS7%MJ?d4$izTkath`sH=+;oap#G-otX%fv*WzMI zg75`CYtT0ILE2AUL#US*m1n@P*=_$I%3bena0i@#WwXT#Lkq}U-!K7ai3n6$ zfszG%2<@`&!$=wP0vAu>=j&^|GXo!#P4>BOw@nO7r+vJQy12IrdIriDEg=c$~ewe$4{X6avy2bg@}9HfAr*nKyAhEGd}mbgxmIOLzM$=&e!*ktFX#*ekjG#l9D)uu~}I91-{+(E0o_(U?}0(;lxhk z;*Fqa$U_kLKPDa-yP?e~_YzXlOW1*FsnIEUriuqJyJ%*8UC7bNNe}(HZLTvoR$1QgKtHOAl~o{r&FA?Le5@tJX{Km8zfLNYMr z8quZNKEwg&t;h>WL~}*@??kb9A7hvEV24`yU#6}D^!%_3IPz3%K%qivvVchHy_5^+ z86=<#c~J&v_2)la?Lm_`)nP>MD>XnW{3HWS9l(G(%P_J`ovyKSOdXN9*H)V0B%Un! z(DZqq>|j?H$nr1?MHadr$(A0&@|ynqR1{KZ3=QAG|LYIBVc`^b7Pa79;V1mD5agxD7)sy2|*El{{Z5K@eDoua$!#Y@mI*NHlI+Ff-oAbN+mw8tY=B36Ah(#*5>MX0w>gXbd7 zY{N1*nc{mA9}M^RTIs0~`r0*YtiA3fU+@MnR)X&Qd5@=Z8Gj?p(bqk`ZE0>*Wq4=P z23*vQmt^;xMFh9ndd{=2puPjM`WC|o32CoZPj{|=hzCI}#1mQi+ryYxvnMe?aiZ`d+Keaq@N?M_wF_qfd!e4zn%r5tcgSw{X`~|>Fme=L zXlN)#W0S?C`=I_P5o=Z=?M) zbc)`Rao_VN#nLt$yZ%vBFCiRXMoSypR+(G&dng?vmdrzp^B6$(pc^bmfi&Nz;Ha{> znW1sMfniMXRN>}O8H-nx7b@pA+tby>j_VP>FL z@TN+PcK!nS*k?*y&0c4ez^lWtZs1JjA1m=>+9|qHqNs#&*U3x5$*yAd z*9B$n7YwbVLi0bwV-z=u9#-|OjeemB5%lP0e$CpRu44i=1(!AKN=#wD z3-8U^XUF$H?W5t#z}chhQ1b=kDv5zN^?z$wuS3<;Y*xN{Cu2m7u`$7;^pWnf-_i?A z_oUp|4KA5r_f8dFNDOGlo*%RVllaktjt__8AGSyg@5Lh;Dt-5sm!87eW0YTg2S+}unV)TFAUCkSFRL28>GPU?Ktf2ks zy3@Mcv(Kqp*-sR5;)7W1%KM?r_Zzr|s2L4cw)kSI_I`Xc@RYTfG~I$gwq zZYAAb3VAXjwqPpYb;2{Ar0#A(LPEm*3B4D~B}KUdxr(hEFaW3Uw`&{keM`SQNbSo( zXQ)eN&gL->j;kHL=#u<`OLYXl!?7?zFR#fj_27u!K?7+TDG~ks`*)7;HBy43)05r7 z4n}6L(a}-xR&gQKA4;Qm@g$?~a(s;>9^+L9CfflE7@Vs)NBNJ(+d0x_+KR!EGn`Na584MnmF# zNlm5wd?Z+pGwZ~g=dh(GQ(|`<$z4YC2@#NHAHhm{KkyYHm)KI7fyRW~-B}d(KQm|B zGTvx(Fvs^J>4ln?Q7e*XEp=|*2o6|Gv(Nep7u&h0681b{8BEc0pATLgJGji2?wS|4 zr5n72$42~V#d)#(O{t@FW(IU~=ckdd#x_?f+aN2Qruw6H{0%-1L{E?>473vbO@qC?ZpVcb5 zQVl<*=mz~%WLCyB=!fHm(W<7oMxA<@0nM}3c5OP7$0l=@GUkrWGpux>f_lOHCC5Mv(aDk@rKel2U&-ehyI62O`*i zBH*2R)CD5!AOPTAJ7>LYH!mBG!rG9d^v~9NGzCyAz73xg-Mh}$^8gIq0)TT)a|G+_ z?>(+kjK-DGd!akw_rH^*n`s@t{#AJSthoy%8i{ns`N`ja(m9$olItC&CN?s~TiyP= zWW}MzFG~Y|(Ubf}3Q=c@`+OOd`-6JoE`u|EwMeT!(6l<9@gFmGeoG+t6Yyg$!!t+q zFD5s!R$#)Obf|M32I?@!*m!~~HJ`ejR#$i0#7#t(&5eiN>KH)Rf2y@aYNFYkxG zTFy)Q%bvJkv_G?2`76(2MmxyjFSu}!>`xXz zDF1r9%fKkaAHNX4sAHQe;$E!pTEFcEn1I=5L=22h{IlHmY^kK)5M6reLasfS(Pczh zjf|XVoHJqaXB~w1=*y244O@STJffefxhbBe3z==8Y;eD< z7nLrgj@&!ym-#qs1DG&V4en)O?!9_16yLQ6ora51a2m${qm^)A%@kI=@0b@NeMbyG znQ-xQq^EhF?_4)6{oQfZ`Z(4H6LJfD;2(t)PbHT`O%l@}3^`6n zcDFtPBkN*)P9~-#sCdU+i+ax`iFD zD0$|KEHCN(qO^M8Usi$ZaH%D-jNF}tTt03uUEtB(i%{efjSq={bKnD*{w}p_l1;QZ#S$Hdo4O$3 z;%1EJdyXK0B_7nIgplR(F27(0IO-#wv;pWKOTyt;7~89{Tnho$^|p=b26+2@nK>^! zLL#E!WCMvaZL8oyhIA($j^LLBF}5)8P8X9;E{HI5xQg{JwHfvkE|bKEq-10?9DSka zp#rraJDY6%hZczq0_lIy)jD)i-Ybb|PUpv7kf&(V7c0AW?dEFxo4fA5P)IJTY`K%7 z@Q)9W*1mxM{V0sCUrXzv@#tqVU*Qdl6iiqZ(($>PmqnwRKEvn$@*z9bX#K~sa?+DZ z>k0GbaR25mll?v4DU86uTQw5ONNb(c)(3CBh2BCd-i=#7tH4NCKVSKD-Fx!%C?IA} zvG^sauZS?al!X9}aT23dNWLL zbfGkg>3p& zBnwd^1MKoXN`RE>5Cj&Po^)=g(E43|>@-ffU*URCmv>DAdHDD6UDgFsaSX6-2UhwM zwA?}P@-(M>u&KC}E047KJT2mCKsNr^>(wv;j>1?Z8mx+Z>6_^8OP*nuo+lh+Aw*F< z7O#w72Qi3%Ha*EZ_u5GVYr{-S$s@uF-GejSN;Vlm>>x0Zg`ylc6-gFv(YB?XY|+pS zfmw%|%nw@yZo&xs*xkg=w0(jjH9g)~I&-FW0hHjN3y*SX?TaU_O6qU!rTtz?4d1oW zZvR(HOfq^hGemR_<0%i1QzTA?Gq{r{h-3c|q{}lGj-Tv@#As!p2eir8t1I4-e$9f? z_*bnC@E{{B%)iUTn^lCohtu;OOHJYxpSJHsP61v%n67YOY-7G}hN{6G z9m#eD`tWn&{pIyCWmqho>u-ke=%<51XWL2+iCn(iAhuAfbeEF_rzbb2Vq#+azkS^_hr1{|m}e{v z;7giCdS^GMRo(wGrJ8mdJF7mIk8I5U6_01I18nrO6zrKlGZ4Xi|NF3Wtj4A{sp(*V z+{w-DlM4(R_;<&NtnIHgM^+QJ)_K^fVA$I*2m*0MO z>Rf&^g*k_ZG^4&~J{LDui?;W|2!YDo0AXJ+m9{`*Ropb(R z-rR2gXf%_XS2B8l@mVE?A=r_=1FGn{tje=dQLaB^mTX*ZUNWyqe(cTgwR)LWDu0Mt zpg+k^P$t*;+v-%Wl=ey>V;O;xIt@6EabPkr4XoiVn3!f=zosQ}jkkEYFgexgFOrp& zr9L9qUImHaN~NDBMmWV)0u`!B>l!k;vWH7bC)l^h+CrIk{Y?H=JF37ehT0x^Z0BIp zwB@KV$O_WY=j(57O&|b6qjge(a@bS3{Dy!(P1cg{i0V|nkP$Q%^)$Y3x$HP5OGM3) zTHQVMW0rRX>2Q`iM1WtgZl0eq4pM2KeI235EezK2Xg914P!CK=gI1N<=S4+EK;`aN z1eQ*48f%q=RsC>*@n+q?};0l@=1RHenGP{r|TFV9)l zD{U7r?pD}|mQkhoc?AG7?d0X<9e$DS*u{!7=tb_4^~!_>vpi&})nE;<-&U6w6@A&5 z`sBY8%>AL-I@iD1%@RJc?DPtqESaV&4AP0)uAU<xk|cr6UgGObgC}^xwUcMqzC7{=3=w zIN>#IDJq_Hg2v(4>+=*S@Bz0Uv>ZV5c;9BJr78zeE2#Vfjf52x>i%k+Gc8SjQgw2e zI%no9_f$4H2g0?p6*p-=gDIWnwTb96Wj=2@`R>y8go~lXeE1TnOG4#t!Ax#*@QoC7 z#-K=`Qeg_;`T@A#p)q1eATjz*E&YIP=!hX4O!g$6IsUKmzU2DVW6muPclT=q#`3g< z3#SO)SWT5UgN)K$?P|g3dWSzMKdpT;~Y)dr937U+Y7ZP{Ec+_Og;SihFTQ~yfFVtg%LuJ|=adWMdGWMSQ)UUwhOTqzSCh28{6>>lx^3=IvQ7Fo3fBh$fbLG=ln)LYr z{;!yp4`R|r#0OStN6EnB6IYGv0Y*TEQi1u}0$@qKs9M31EGZ`4Kvjz7=x9P9aC{GY zQH70ITU*mz+X@oh1WDaGw`uZ~L)Wz6-cC(!_tJ5fuD5I?P&CX7H z?Gn|u-ap+0ARYDgX8 zCZacs@&-gg56X0x9BQ={N*|Y(X&9yQq-s9cPff9m&0G}%prvqkl8gubI$5p}Q(73* zC8FnKXZCCQf@iwDB9JMvtoop5EIeiCTy8DYy|sTykSCQ5O{pA**kM;J!w%WSHKvu|a#-~p{ z*C3pIZ`>1my#7lNNenR3+p*auw%e@^WmqjXLD3G&i&AF@=HSSYX^t4?M9*L}hI^%1 zCF_f$&B^xk%|MMv1RpGliZ=9e7PJ3bON1lsXQG^{`+g4N8S=Lly_8{(`V%1{^XAWg zZPsB~Z|Rc4jn1|WDA$*69;O~dEf&(Eed-6EHsf{9`7o3~brIdAlg2E`Q1O{<=n|Pz z1yD&<{Q&z&0nWV3To^Xe$;G8|`WJ0a4}kLD?#woRjPQgr4%iHer)n+%_gP)~%M+fo zlFdUYDJk3Wf_G<2F$*cAB3B~DnFO%Eu>#hK3rQ&p8Lez%4++05T<`jy8<^AF+Q}=I ziz1mfC_B%*d{8*6JGaHJjzahVe^GYS;&rGZz&%>ePBIo%_4HInyIahKV=BxVIRPOE z(Y!ojm*P5}@aLoVqwW1^Q&O`lFSAzysW$3$9Z=W&Y>z>Mg(qW}k~UO70e<0Knw}Fi z$@7<_9^>mTU00vL^B+q#(U8|hW62kNrnF0=`_sulRO2A(QNfvWoe^~9kt3w!Sz^zB z6E@QUUENFZkO`Jj+eT`$3_$5FKh$-laC-tU8zMa`qAjLuDC8GcDNv>t`@LKQXtK5q zM>2|T$fKC1@~779_){_Dkzm|f$aE$E;B_1Yny~p1oTdyq2~e8QJX7-4{k^C>3-iye z*-#pHz@xI!@kHC_h2G1ldJ(+B>9%-7Ln~}foF90?)phljXjdaFOc@Z(wd}xU6@^X@ zV;xN1Tx>e-&-skqdKo27_vo}fO5I8qjqV)`055W~sJl_$8!+5k+4o{5TF>zpP6n_1 zc)6*({37h)E}-A%KE+&7;*plp$t3I20E-1wXi;QEP=fMXRU^So%I_qA;&=BA&=zfG zUzhw&I^3%Ji?@x2bPuP_^&6BuDRU{fpjBcj*pp&zPrw*aA`P2rVdk{0bNES4uCu<= zn9|hK{%PJ-(|DI2Pt{TJSbZMJZ`5qep@IC!Ei&s zn*ISxk3x=@LsOg203T(rpRTbG&=DWUiuu1J??@wBG_5Pv!BY}$)pZ^7lEv#Z|6Wb| zA=hQQLnlUgu8RINK;2Z`T-fej$e8z@*(jKVDH?8X*zMKeTTSf6lz9b|f2vr;`y; zte1;l4di}->m8-E3cEgLJ5kuXH+EQwTMei`N@>G?hBL~~qRh=*+h_ge%M`}s9nb?w zY#ql)Gq#h#P>!K|kNq5zGa4~s8}ep)bSfSdkiQrNtlhY=;5CVjot-C~sl^G@cKbPU zXNH)|^1pv$C@YsU7czYw6c7bYh{nq&YV>q4g`|MFME+$3ep{W+p#S1pAsH)Y4e$f6 zZScBkVxBjJu{o(aw}vCZhIzUbr@-*alhtz{l$_}2>MdiZcY$eiEqY@v{$)Eg(G#@kEh-Gelrw?KmEekb;t z?L7o$i?X_xvhNsA`1K?;-TCS-FE)}zzM;2vyA<}bP;?=2;d%7}K{<9WXz|i(zdJ?+ zQgsji1~3rgh*;lHZTbfO&bUd*$5k;R0VRHVP@Q$7#otXo2#6_OlLm|L&`p;PZs;Sq z7WG1tD{sesuO>->O~-bLYA8mubaqwzC)dx+7x4DVO#CMkuVa-OO$DL~z9m2n?q!_J zHYs{EDG0;ynbp7KciAt2h!d}t4jbX{8UzAS0a6M^nm8(Is9{Cs!r~cpb1f@V2)c@- zJ?75b^TC`IKrHp#u4AaZ-ZzzmA=l_Spw_mng? z5&??L0N^Ml+}z-Z4!nQH6XV5+Z-s(*>kotbK2blbjj$;K{Ro^D)^+g`o?5&)F)|NW zZ5&?kH@6Cq_5R_-<_^dshhxeqz;iO*=H5?RJzQLnx`9h=l_rdv^7aIU{(dO=3Db*3 z6e}i*M3njb&km%a@a0>5Db5xtnNKE=^H(?Y*kN$uckC+3f-Vpz0qckay@& zT2&l>1g~3hbGkOQfmWH%L~kJcn*4BNRuq-N=d@wPPVc?t7U6nimJecz_zxwt@K?be z8jLWqy*ct2uFVX{Eh=N+g(rH%L+?mZQ?F-7j1-~4UKW%9^8^E#jYw3-iR6Q{r+HA5 zN4F#~2Cy7}pPQS|+ffWWb2b17v^WS^w^`xq%JqOi@bpIrcY@d=3V0N9>`=Pz)3;8X zU$_&U4XHlXhijuvSO13ntgP6iBqh`RnpTLv2~e96s(*7ZX*xv6Pr^Q04k%r3z8Ift zKHRz%a{~!?RZ0XJy=Ye*HNgP?>yrf9Wxyo%jGNCDi|+2mF%LaQRR^8!>N@=N!uwUC zU%IvUCRa@Q;bRES>6}lBhnw5C zjlPfI+D3@rCiJcO*daS?LjNe!xt>d~Q2BI>O_M*2`tWek3t<);ORixgV8Ecp0z8}f zDp5z}flSg#i}G~C^^4qf18%W$sV>?QmkC{nP*ncD+(Vh@`m7u(xaZ?QqSF1P-bbGy zh%jct2KO{Hc>WIu@Wlfg8yoL$>H^YZ|EZ;ysu4>eqeClRuM$r;yKXrggjwz@?2&+g zcy~}=Zq$FOpj#9W27aM@Es3nc-oL?gDuF8aB{{XM=Ub$KKu3F_w>EGV?k)nIx~j^! zW`&>Alq9^=0Ef&+@KX`oQaH+(6E=~%zT=qRmtltNdC5xe2dbxvP#6nRozST zp1exDpRVga=RGXcnb@4t*jgEM-_FflmP-hsl0=%@#{gGTGj#9z#mt&>mFO!Z1Y|?d?TQHSqBa7pCG{|eV0geXWt57LX zpU_9-A4a-?@*PKAUI8)Vpv4#4?Fl?1k`>^?PX;P!Y{R9pW{rvb>(l@n4Xg$)yBDr!JXn5>1$H)6Io4 zI@<@)4Fov!ZJj4gZ8m@F0pFr<=W6ps*khR6=Wc(~l``O4365Mrovfs8hLz^BsKd5A zZ^YOKT>;p=1g@Kh8a;Pqq6PI0i@@t6hiy#scGwOmdSD)XJ|_drF?%z!SoKOf*^)s1~RUz>9| zzoy_fiv$I{tG+fiVs&2Q8ni+hHpjhs3oDn*)EvKf!yz^qQ*K?4@WHT3Jjx zA<>n#<5du(IXQ44wbQhM4VOh`xxYK!*>3w;b4mlWJu!HEYust!u^%~sDYbqgDl$EJ zU#NJz1Lj@S+??UooJIX0TO-QBao=vnh_dEi}H!1kJYgPoFN>w1kN2x4MCoz-{Q=-ArJG zAWnWbp5Xam0E+|?|I?F~)@k-bpYGN5D zkTC(h5q*CfiY&t0mV=~JP%MxX2rgT}cD?(9mO;>~U}?D!83KBfrP0oSE@9N}?{lC2 zb>io5#hPb*u{2({=N)nJBOV*x(&TThZ0RY?>#hoPH=gOq!js>>AFP64&Z=?5M|bjD zJh+B$&$qHqpQI`#BpD6-U&V}XY=C-*BnU6%{8xD_-)IVKj}8shs%eaHv^=?peU9tF z^OfwOQ<}?Olwm81<;Bm*!xGse^%l_6@^E)Qu)kGFGFWe^2R$Co-xIM%PhP*GePA~Y zBrdxa?fz%zijkS2t`A&e{-hl8^Vhyl{?;y1O@Z*~xR~2*AR%gaF=KFoD4cjlk|^qH zDA(34C;{vz!FJO`IW=RCn2F6M-*}Fq$|f;SLNrgcVt|8IGo$Ar8l+6SWLa&bBx1>M zvDs!(`Dv%6Glj=65FF+hL__%Y+5^?+{eMt;$|m==j&Q&|vP;8bhvT}V%wUEvf8>4n zrys{=Z^BE!vi%Z;`CXhJ9Srdrk0ytvu>Uv-_?vNt&j?D@#`7eQkIJvAOlaQ5)2%-_ zJWB%B0Xh2afVh9CiSpiea;}0-GClH=oLRCmRd(#W2G^0-^q3m_dH3#S2dF#*Z$&&) z4KiDP(Zoi{ZxlVzG5n}PJ;YTyI=$X4n+^wSu@X-;xw6dfr@Fg*W{P-?waJPv@k_VN zteLpuv{ztsYRX|RTjBJTmzUq%+>tZWOpRUi@ywkDR7%zY+u2$!it5T$D(+%85CLqX zCjjJt$3_6wxk94bMBP5YLN%vEKKQ_|fR+8Q!4Qb#uX4}FIWjXd?a$D(Xje6@qggnk ztJW!*c?p0Y;dIbmR_Rvk=nFPvT+&_0xyym*zArEiI!n zGp5uqkVj{nPLNQK9HbM5ET@+a3_0SH;2GRmJ1zF7g@=3Ze-OIyw{e9qR}-9lzh}q5_dwvEJ>Pq6dYFhkwR? zxiyc?It$)ONOOPG=hn6}RUK;h)vQ0Zh|jQvJ~Z~uHm0b8cGC1FJH@LHAg^kytLteG zl-OF1q#aHA2Zc7H*TbK$T<$^|wcn20-pE+YW>2SEIL!=Qd@nd`wmZz36`)nBVps zT)%RaAUx>=4YD@<5ircW^6m1b{jbi2&CShEF@G*fUG3cuFb(vmca>ntD5dREz6fT( zM|6w6T>xcErjoE9u|B4bvAxljadO~y$4M;tKyE2(Q{y{5ceSJ8055lORlvpZGsnCk zk2V%&P_PE2h#)NsIZ1T5J6Jle7J=l7;fHaG8mkIT6=M*CXFrYmOoOIA-;=&C2A5_j z6OOtv6mtZF7WT(2&y@#Qetd!_e|PQ|)V{$IhpJx8+}rQmdZf`NA-!2;Vb`wkTN}%9 z+|HbZ?fpmj9du~czT>iuVQJ;a7Ym%-@w=ewbYepcqNa+e$tmEsu)NwZG-~nS;^lcr zosA9wsb0rQoYYQFD1mIGhorf_Kz^r}@3ItmgkzQh1>F(p_IjCUxAdXNyEqs;PcKTy zqW>2RH4#*MRfRLMgkGDQRl2aL7V4>hUKS{YX*&)+ZJ8$`0#=1SCtt0sY;YAuX5S!| z-bctu7*9NBW1e!`I{s00@790)HRULRBRY8{?S1N1e2hKq1f>oMgh=4c-Dl`yNYT>~ z?z5oZ!K2<$D-mC9ctHcCC|LZ>zurC+XaW9m3?R8Q(pF8`$~o>=Z+&0iUZIgoq>^y| z?y2@e|4^(Ikkm-p9t%LAkdYi~#$5~w6MMi&jh+h%5+Gm>_{PMYB71y%JVARa6R`B> z*dUB^qfL%RsB%LN!kBU@h6)dFj{EaI*!_bBaT7^C{(7G5_%H(-v^xE>`xE0N@M9f6 zrtN)sMgPeCmE+l520QRi8@m;d7xXl}xi2EK_vi$IGx$CQQXQp*TXe*rM8}|~xw^8n z;GU|@FHDTH;cTftC35C+UH2Pwd~SsTwc{x@e%Dy|{Kc0FUS3`!4#qF=+P`Biz_-1= z|MNh}_btkxNQPE#?69Y~hnLp{OE#9tZntEL#t*GJaAHrK#$_lFBeynn+2q-l3xp%? z7-uJ2~S`=utbWa=wS5AwHSPqC~?I% zYx`+5WFc0I!tUy8Sn;;NERU_~Dker6(tX0lR^V8iOyM$2kf4_u49eke>gL?=(LwDbQ-sw|KX7k&)RKn z6^!(8nOp9O-bS{Z}197I!c)V0$$CA~LFOZGs=a^=tGqn;I=#cEZCZk&-0sJM&d zy#p^@1ee`-cx;o)nI~c1M~@-1m8ms4cJ<#K!*(|;^&=ck<*!FV)~MsI7yc4D9++wy zjVxcsUSDgKNq15{2Fi|hTjPmFv&jH4t~B|R|2bbt?xRqOpRRu03$drP9}N?S`fPcr*`NK67m6!4Wl-7NuXk{AfX9rz z7?$w$^BoJA!2ogmo8k@;P{sZtx_TUz=b!Fw@mvb0J%TFB^`5Vo#PI8keVBAU1b znM3T1m9osgSJ)i4jP3Y5ykL#PLDcXH_G!q8_|a`pTaVxV?&E_lZBm7f)tU8_i@Go@ zV-ls=VlEI8ZwURknDugy0luIRA$7GiZ$#br7XUB`pihG;$vpRQa~|wT6b5*8o}h*n zDLz#lN*;`xxT8wzDsFMLq5o$VXT%2h!U6&UiXFjMf!0%L3-_NNtAUQ0lPvSA4IL6hhC7wYJ3~soQv@T9!>I39ZzgWl7W6`^J@k=W>C7<}JoVcO%=2_L%u$H9-YpZw)oMvYT)69VFDNfO4 z-)+=S&Hi#9E!zB(E7B|ZN-j=}UKF9d@nj zO8CzknF>AoiYTdUfV#H2pTDz2E`aD9^E`r*4HXr&>DSwlrF5IP*U34~MkGatms@C5 zeZF$1q<3Erpckl!d>C4r8~pysSMqu#>u55`dh3bD9QqFzYkRqu4FPo>b6QA2zc~kD z(%>Wp!*hm+lqE?jskcOPfGr2gtWBAb!;dwNE@(Q7?(T;^kp9_0>K^}9;5!vagf4;c z%3amZ@4`Fn_%CwDGIX~onH3-ixZzDXBf^QtRB0Bxk~YN1oD2R|fDw{+c4h#wXy4=G z<=lA}@?n{qxdc7@FCSt%rv$Z97WF0C?xeM=90{1+qrp?;Gi!3ez=VMYC0lLBW{Zot z7ju>o%A$5(wG%>Dcy@1^h;2Cg%he5l6*z}5o1qhm=?R&nI2<<%*-~u-C?ud3ECX}o z0Xx@fgandT1L~y=wKFDp`AZd>HN+2x*;VOu}}2l?epE~TA6FHXk4H|~Pw z+xmsK>jbVQpgt=u0GVKQnonMJl{e1&-UqMW-Py^hax?f3ZL+E6Hw&Ytty#+XHTn+neTM3_;Msm-uNhQnckha70MM>+bwio^0H6wmniPV z;PEt!eKI*|aACjuJ`gMG(rW8jQ)>{4@KuY}@j^GNQ}Lb3?xr__OLuA5TSX4GhAa## zp-1Tn&9v68Rr7cR(1sWW;i_s-HH~xYpSK{#~UsDERL^sGA z6C*MzY(5(RPzuGJ@PU_YM-$N;18M^QJ&0x~YefnFmUtLmlXxNjX^tF*?vNR3xPoK2 zSNo>i-hNsKE#H97iL14e(*+Pve$`Gto0<~Pr=$xe;}eJjuqPH+7Q1iBrn)krn2tK2lySAJ9sin|c+>;a9zDdRp_+#troDMl8Gxz8V8TrUw)HOQX`tqcJYuAS1uf39X>rqg+ze%szj5=fdAxz zEZXkp5@g7qCX%XJv|MENn#1HJ?W4`G+60jlRI8}Xxjg5lPjM^WENyqfL()c&ZNSWp zhmY^uY&JZAJg~^u-MbUsm9XGx?)CZZyA!(Y)kX~>BD~iqOusBq(`{F8#$26T9q*bv zCeI={!d(Oo{-u~6ezO zYe|i_l=Lt)Wp3Ng8ySvUFb!X;ECScAI^Zihe{d`(>;0%9S)A58YU3Zh=Y58d*8R5c zCP69gfn3O0>Nmq-s0e-QI;X7d?Ut98&d$4DFx#5xvg4CM1*o~S&SGUawCX@EsUP;P z?WO+_zuF!pGRPklT5_VC0Via3SSeCyd$$cdQ5_dk?Y6B?{w0mslh&tIrhuUMl(X-R zqv&1%6>ayLblYJC=z`aPY8Xe|MCHke=?4W>H7>j63Qbo@;rqvifR|bB*-%-zTVmSy zvP~@Mi!G3jyK|;q`ROOV$4k7|zDEkxE`W1^SDX_ZB`$uHZJFu7pR4=h+c##Aao)=Y zUepqvVgNkp7!r7f{WZrZE8p_vgh#ou!B~3&QWK*H z|L-v#q4W%W=ZnZC>=Ge^E)IO2l?wvtx4*1hQY^8#W4u#cV=ZyUcP(eBj4of*+jR4?{)3#dGP(s*r$vt3lOPlV@ECCz%ojhGP6!0 z`8QXeYHQsw;Xx7ygCaqwi#VxvbgT9U(vUR&1`XerSQb|zyxVb`R5!|$2g>f+>o@Xf-^1CQ1^CqfUy*pT4Tl#sCgDe1G zs2pUO*mRmO@5SAhK0GmT9FiGfDfH6U-t_>dWDw9!MD!}9 z46Hf;#_ucHTpcy87ad1xX|AmfxU}LQg!I*|`-l2Xg!+mIyEIF_?|^1pZf;CbY1|4S zkII0fNu|Ps=f24d<;!jiMPb+^E_x{DqM4bNTXAe~Q&6L%2P!8trEeyFidd!^&51${6P3e4zGAh9!zfl4hN4zw z{iF9L?7#&_CVW_Vn0>kUEh(T4zzML1&xRBu$tOMOn2A?+q$*@8W9GS-i8V?o0CWPI z=AP4($Pjn$poN%kn`w_kP4pZ(0ZotwL3^{%4AR0-(5pWU*=&)C5z2Ju z7e|sKXP?9v#|TA7@(wq%g;jlh^I~Qx=tc4PF+f3-?q1fedo*Fld{VBXvFfnG#kIn< zA`Rkl%R&K1@Y_vETVwL+h6ky3+g{jAM(jyT(A7(7$(zo1Hn$I7n0(&g{N7l(hsq{P zsP9cA2@xjZYKw8wVJ`r>db+l z`qQURGUZ2x#mPRy%|lVbw&f^`8eI5rk-o1LKStlxQ`(>{Zr;AVm0UpS1h?QbRtGPy z6HUFZ7cjxUP+}Kz3gAqw06j>66#-I8z29qTL5{@23~BB0Cbc;5A?d# zP*W3!;M~Cw7>u=)kzjYLFq^Q4i8Ed@UZEF#oGqF6vcS)#*byJbQTE8y+n59a?MZ3D}}HJ4*K;$mto?q z7Q1C-^7B2O-zqBt-g;kWaKgKs8=abmwZGSnBL9s5Em|jdcDWz8xFN z>nmFTtlzF>4QE3D-vtE1Pyk2l2*NONTpwAxKK|!HrYxzUMyvms+uga^;{VRK6;a z#24WlIsW_VzfZ_A=JEfxbjo>`An3jc=I0v;Lb(8@wUi@W)-S^8)c+CZ5n3?H^0ix5 z3lLQm#?pw%0-&e_G7Dv2;AS(|8QlL5p}hg$Sf=OyOY48!f5JK7>9VT(3ll57qMLWp zPYHK2^m>;@EA~H(y%jgKL6DjqQ} zZmg|w#?DBt#E9SnvpR=Exz4BwLuGv+IuYIb`j<~*it8p?=yzrAjG1hfP}&6PYIwf# z^qdP0&?o8xeh&;I4*5-p%D)mvjXrW5`_2{?`Ysv@UAaO8%r_C^sgQ2LX=95#LL1$Q zV-X$WstWT@9z3uNzQw`;3+;5Q!1Sicd-gV7{yQ~pHls-+L3~L|f>TyJNnI$5+9DdH z>KNXgSB-_>eWG@1MJ`%Jf_R13ilC%7;HPKBiTAk^PwVncR{GG_rA~q5!Aogy{7!@Z z)ce}n+Avj3P0hFNfhYiI6C^{2wI03qju>sWgqV6*#O6Ho zvExCk-){Sbn9ia$iI@Pdp`2u#>RY_m<*A+em{TqyMOrab8(MSV44{;c7F}ni_g(OX zIY_|-9KI1Ktz+I@tM(6EOuV@o*}m8_B51ee@lcHzi!Uu=aG1pH52=dN@LudUSND#{ zfy7ne)g*bYqIm?UQ5TqzYc{Z%VDq)T^ zZwEy83}C*+m;q)aMujMgic05Vc~73+bGxy`LBnIj`OzJ$DznIsSGEv-nQFkei%HSr zCjX69gud?h4|l;+JgcuRf+h4q|NisCMGQ&r2qlgiioF{9YvnmDR)`9-chEI%^WC_OUSNAF{Lr6H0b!#NC|Kqx*VLr3dn)d$ScuXvUo)+E{x4A&tmFvEVCa#4)vgZ zVoYYozRW}<|A5dCNf1_;be&s1gEbaa3iVQD>YAL@;QWX9rx>^Y_+7piAm<>Ju# zr?r-@v=%>m5EtDQOh+vW>Bs7!jD()hGHylFtus@aJd@{gD9*1(34?UlqC50C<(rA) z3HNMtRwgmCVoEeqFyT39!EmOyxxWUQa6)@jZ#g2mFA^LJ>Zp*;<~O&3fLAPdW9y3x zi-5Ug9}^tOSo}z%R28}e=K(-+@04&nP6f=o+?&l_RqozljHRodHMK-eS)s02g=;1iY8nbi^5d}5|lu)6kx1ox4Ds<3l8FXWr>(i3{40E#yjao&HJtNRT z(VnBb#q)mujh|_J82B%zfaM{vO23z{A^|;lA7ZYOSMqi^!Lra7WH2eIx)aD=;SH{4T%v?{&S^PPWm!b4(O)%o&4zCCRXt1G611#4#qz}D~mR93PyK`d*Gjpc|aE4GLZj$r@ zOF^v3F^Xr?@b|7w{DTiAe|EZs!*^>1Pe{;ktzHoCQC<;@5P)E>)TUN8;5}+ zSNVGc;`il?a)<0wA#6Db)j(TTd_r?vNPhiEF?>`06R`$aQh>BItdvwxJknm~O>WB_ zLt5`UuP}n)*iJZB!8t&$$h({s^e*ye7$K&24u$PmQ7+k&;5i1fA0AR3&x(`xd7Q;< zaBnA@v#qev}ZxoG4)It5>PlTBb|H_nhm^)vWZc!+WEkW*X`FjphZ8T%VS+^ zrp_tJBZG#lr{$UPeqrQ>Bd)kv2J`^5w0Lpo|$)@F0!`wy-|K+~-PVUEa z?O|slvq?OsNHar2L-xyB;Aj$u(1;T4jKA+6VBS-Am3WRVfBNN3h10!@N}zx~c}0RI z_%|S3p@TT7iU9NMhGY25kjh5$@@)^7W1d6#QcDE}WeBMlnQKH;GtgUQ~-OCd@KbCfs%TiyuTAC)58tG7?+*mugS zqF=Pe4{`l^>CUWnMf&#B`5rwA|I5!-3j74({L;e+l6#!i7sZ#JP>*MD2r4P90~b>g zHr4=&o6puGYRj1mlnUt$C(=XTJweP)Z#7+ol@sR#0f9k_eG^C5-Pr#Jm+#^Plu#cx5FkZHCquCm&f7p6d`^h z=R&UM!AI|G=+rMyOIaT+$&7jky~V>>{0$rVgAk9X!Zvtf$rYro@O)aiE0N{Ks~D#V z9pNsToLohW(uJ~rmJL6r##q-d^L_-|oIKFGuSoax+SJ74H#SpXw?t4j(x)O9?;l?o zRfEM3fc#&gJF7iKs$q3L|FS%hzkeSv>vU0eeC8i0AC^AIyf?RN zpV3$NEA!`!0v(<1?v9y1xBV84K}a?e9o{OJdX^e1b*7Ji^!B#l@E%mn_E;uI7 z(1*}n*&$OU-Tw7jJ<*!c|5kuT+<<_ROTF}|$gVB9`@t`kPfozD#KUk3-4Bm^ug9rp-H;j7%RRlWKCr7v;aL?9-y|&7_-_nJjh_o(f#HMuukj-ydu0Ao$OCfDWO(t#D5vodo4h8rqxlPy-t1F;9#98Bui$=xBRj|SOC^KK$ zy$LL$gJ!|%o$dzkqv@51#1?5hv*d%?YivRtR6-(#v_7tul>%{w(cn~+MJ@<^vBr?m z%k^-6Q~j8NaBKUFhhL|}b<7HX&+M};igeQP0CB-RsaP4tZ|E2p;G}$Z78X2>!k_6U z%^x>^VN~T?Y1X{&Hhgv;R!SkD@g_y4sZV&olzo6Tdf*)>We(hq>-v*kt>dUGU*$FL z9N#*tQ58Mg8j@<_mf566+_Za0@n_Gpxu24tfZ4h=%`gF-?+Y_OG1^FC+SMYTju~y6 zS0RHOu3s z-aL!I{O)`N6DYS@Z1~L2$u=`Y4_uafe|8J69#FJ&767&Dk_T+^MJN!k>QFz#L3s3V zj{oz%eu5u)nIg8}f3Og9_HX4ug&QRW`;Eu#kgwsh%J5qtUvrEQ&#-ug%9GfhvmjFU zcV@PgFQDx2n0#aiVMs@ndq(2Dku$!0oKQ#lK7)%0oECuzlfingGKy?u_Q?vrl)_&7 zXiv!KlIJxS&Ysd!AB!h$@61qSU=>TX_efH&22owf+C{U%@i8h;$z3F2B>03t@x(s= z>D}c6Df%8)-gqzK{K^~!C^y3#48CY61u+Frn0UV{?K;g_mb=AQGZ@^?7!cev97*@j zT!mWs211hGYI4?zIio741v4n4E8KShq12c5cpL1{af zIcyq>7aEU$i+c54H8a6*lKF=VK87zF^!ky~*coP2S*{7{Jyhbdrs>tb^mhmb5foV* zmm1nLh1glR;y>#Lr%NpTOkd`)3)+d$V4pqb;!RU~uMFz0h9_V5sQ^+ZF7{W-&KSh! zN;JlxZ8o#XHY`L?I>*bu35V`3IUM-eq0yM?8#2W@@i>-`5;rRu8C^w`hWLF@0L5=n zpU+n{*dE#hb!do8h2&$p0Cae80;Eceg6%S8wc;5+%BhXvz%6X^$HHM6O|oBUKg|o6 zWS(P=Zd>(44a>bfUEN~Os{Mrd7Upjt!kL1h2#3^3FUa0P#49JGdBL{S=|9k(KnpWC z5}&YI^#7{#@hMP3XR!om1;z+e}dt8|en3|E6&f)y`6WgGM_Aah}0O);(gYg7)2S;Ms<`Dty zjTU=o^TBWH;n`-9Uhy#sRNl}n9cl&pBa|FY$(t%A;~*SdY%DB>ohQdZ{_6f@5RE(u z7Rq89d=l%G0qWby0*gbxH2KZw&Z5{{QL&{mnYKM%*!(Q+l(oFm!k@sAX5_$WMwTb(%lHF1{${k6t0z@vidU=|PwiJRllv)~=Hv;<$x5o9 z^pfYa!e{{FURdK5yAx==`DO6@qOP zRCTz{NQ!7ix~wWO`Nh8t!K+SEF2wc=Ru&K^c=!0;|5?31+2XBJ`V@{6*J(kp-dCHD zipcTB*w%FWc{UtUkDShd&uPK`C;Pwh|TFyKx)JUNM9^T&&Slf}P+wi*18GJeTy zg-;RdpJYA7NFQ*0^!eZY8=HILK%iZFJKCq&|7<^qxj;vQOH<{U1kw^Kc-sAUUTHJc z_3Tf*FB_s{CYH2EB{K;vbySDHEga%e3#!+wfJ2Uun6RodLiFo%2k#Xr<1*WPQ;1q& z5xy3F{4{vjhq814dvr6p_f(WwdLooWVb_<0KYK1lY)~QxS8ZM5@N0+2~C*b?jk@ zr$afhzo%<8W#*O9ib-oiLrH`;xPSe^1_QLf=V}Zz=&Sj!V;`>f%f(48e^UaYM(N38hV;j_?>J8C%b9bQ7W>+FNg?x*)Xva2$%w1 zq8<{|z)N8BXv`mNu|=k=@+dDxg2Qmo-ytG)P}T${YQN$#o+}{fBDM%ZNAB^4zK{_w z51su34ZnDfAULnx%j?X{luspNW>Gt5DtRAZxMxqH&B&O8b9hJ*E%Hv7(C@-^z98w3 zQhDc_*Ac3MJZ<&$M`i;xfs15(FF+ZHh^TI&>IDsWn`9TwN}a;Yiq6ggHK?dIUym(# z{*V%>4>to%K`XFzF~r&8`_K694SOc+ZcAU9N%aOT{*F@URenuGf>td0wgHj#8<#g) ziAx48UZ&0X(Eh-Rq&}B0PbRi2@0*Fz&u8p+Q1@FM=Z|}CAH?`D6qIU|?`4L|{U|!T z0xo4`=7(trJ((la&Aa`QZ%;cL+!J4(!}g5{iX`-g(5%TWu3i}M9yNwgvF8t!^`m0U zGADiM5q(A>mSC3q`n57Qi{O+W{jlkN_c!~GjDTP5ogNv7k;gYs;D89nfsJr{rdiMU zD$x(XYf-<*s0l-CtgIj)q)S@P!cT%UxW?w@%tf#l9R$Cb*Z_P#P;qx_m;fX6laU-5 z(0hBMi~>-Avurjr#fM)Yb&ck2FuqV?BE9NwvEOza22Zf|ZMHL}Xs+P#aixE_)%k4Z z-qQ;e^h=8$ZP)qYKfdBP;z4L(K0%?L%n?({2q)}3Y~_aCO*g`~gw=;rB-@t}46Uen z1SS*`r-v5k&HLg_1P!QVsP&J{FvA>XZeEi#cd;`Mrq)bZI#1sfzlmdj($V1J_@gtJ z+d5=3JY)v^(o_WJ?u$QM)aGvOH3dkY%t(g@y*sIPL@Tr=T&zws;M(J|^XyjH119Ek zKR=G)jLy+S6EIBiM)pL5%MjQcS@HORzZ-x>%xXZ->663`*txDZs;~YG- zJMIo8%J&kC&uQQ4+2ugvG-Q#>6FoRm;xX|VYUyW>4Mfu(2Qq6g2KO~8zBpv!2t2p| za%0_%acdF;#(ht0lyN0FjW8d%^NTmo3zRV@HRG*HidTtv!ky6>LFD<|cF?(W-ee}) z1tzQl)z+_GF4aY#sFoN>cf$B{hKhR;_`srMpr@EW$d0m}F|73fyqD|Qs-*&RVi>Yn zjrHwR)sl+;&4nD8pq4>O1|)zA8Xy;QnR(HO=XeL9Cio=A8b~K|&j#&p={8ClmA}V= z0eoPp%})#vlCgpEgbK8GP?59^FXFjUVPIG>KKNeq*nc1OBN()BW_Vk=CwxdjboWAA zmM8NeU;tYFUN{*2 z#t*DyP@9xAvV1O*IGyJ1n1?3yLsnquC;*e(pL8EKJQo?4K+TS>1RXMxcWWTpMCJAhyy=MLs;h7)GnprgYaF{`$x*b1nIk6yhi_o&hM$<=rN zZC;TIyPfq*_os3ZsX|@y6P-CZo#z6OOxOMU@U%%z+hPq{arc@H{06VR!8Wm*!qH|l zYLYSr1*r+UH1jAh?!m4jS1K+PkgHuM&AnK%wI_QYQe_9$i7XOBrs(*~lPq&h#M6Oq z91M+thmv#Zf9#YQouiYC2yjt%dd0lkDSqM^@8Wsk^ zoZF_S_|dq&YCXpFC35r6LGBGLm=Ue%X=>Wt`}yDIE7d(rc`ywaBk#@HwqoS8?1AV< zQMdk@D3XKyX4>POc^SLxn<|+3_9DAH(OU+MP=jOCZy43A4p`rV%q+t(&65rYAQb9* zq`aI{*JS=fn^RcjFjfV%5F@$8yKrqyrlUAdcx%ydPnyZvlF~Y4A+SLBuwUeQ~5ZtMvu zsInPWGL5op)QG?TR`YovS-s0r7lBZpfo~26&}=e#XtEL)2kyL2%o%t(HZvo&J`pAt zn&Xp$G*0z20a3FQ^ck21J_OC+=M|zpCvRq`dFxc1l%i+Uqx?26MTOhiTHF0;hu$ld zu+Fm3oM43nUB>(q^Hh3I2+k{0GdT`dBjn8#;^2{ydQf8h@;6;}cEa}UI{4=V*as1S zW4oND=T|)7NjKFH`{U!JJk~=o4tKCSpf$#Dr7{Ah42w5g@URpsHP&dm8})|D1-ap~ z-N$e>(l3Cl*(g|m$GEm0-=3QCo=^1S-2^q1S5ZupyG%b^sFlAWkTpwM+?viHW!$(-2mMmPz(lv0%IU0V zQ{VcTiEtOVYlWO=b@K_%k)>(w8vPpeNU?xvduj1Ls`w!sM(exCh%fb?;ecWlhR$gp zw2)6OFZ?WTG33H#LMo?)W|C;BnEPM>8*Tz@VV58&de*_3;ySGEh&Rw62px@o!_w7F7FQj0{+n?g!p`< zL?WrSq#8dQVUYX!5K_S{)e?F~eR!$r#fUPx8|*7X+j68Ldf0~b)^EdZkzSyH^34Lgi(wR0!MxHk+TQ=Az7K8}jOD#M zp}Jv$&KT7$iHGt5TQmszG6J0upyxKz(o-jpFNNaarGKBn{4oXf-7l@nt2tmm(R_wd zd-vYF4`KU3EgNSOZ4N6|MW^u?7Ec4}+SSj9dW;6AY)l;@-h7v}0vN5kH^I+gUoa+Y zp1_>)X_pa&dcO#l?hWI3naW@qbuMg=$s@JD5X{>HV>C7TU1d4L(!rxGXVoIAoL;`< zk9~!s85DFAnbFqo9w6AKrcny!P7{#+!IE{KT`03$=$k0tOeS9oWmb2;V_do=YTtv6 z*0uGURD5A_h2A?#=F0dGkCaiEdY-X=-tTE}327boxxqIsg8*K$dK~VN=Rr->Rt<`~ zO0iRc-~Gc_9Jxna7Bd?{*+^}JA6Fb7bD&1DvT*P`u2C55hwJwgl9aiJ92J@fc<@aR z{TU@F!|aw+bGTAqrTNV`QAlg>hX$ zzn&Wf#`-PHzeDkZkL;-FtX#;5Q&HJY-0R&u+2OkY&WD+MwPpX1ll1r#MfqJ|Cj*>c z%*Dg+meFA7pI~37gn|Y^t^G)+53-E{yb^hW^HLi#=C7Uv>mNfLd7weEk7;`oiSiU# zbKn-bf3J^Hxf;#m5yb=Iu3ER{_Y$yGgI$G^W4Mz)y(^wR_bZQSn>l4KLEBgD zCvk&rRT#fHwkC5*g2Qtc!B3@dk8j;LQo>E`7li)HA1nJjgM6tXDNseb(OI+6c=3PT z8BImBv=JU~*ck&2p*p~+Ph7V=r`aCRZA(_XLM5as|6d;Q(Ux}VQo!7P-6%v&QQ zUFE0q1?!_jwW69HCN4+VD3FsJXWABD1;$&EJT6yn_hbygC?J5O%}vv@j{MZ0l8UAi+B!#L!Bw&2SG1M>{rSNRmUCW649cAr&_l zfw&p}*xfQ9{U1e>SEqHK)UjH<0RI%?ShB=Uok!W&;2m%_3KnBS`wj+UbZmX{(J{<4*TT>%9!9V^eu z$?p59afKIa>huCN8^Ap1o)|#-uzv*zpt=OuRx)~y0j`sSpXegHahH~?43hXf(d~@=R9+I@z$NdUu+bWKYxO&7K*_iVEX)AS z6Sk;~Hc$h2BY9VU9w~6$c7$bRkm`=VN>#q8cAesS{krG_R;>FO)jXDr%WQIh%MLl;D8JkWXB8=I;qJ zSl(4?a@)z>?rF#1u-69geM+z@Ee7ZXAC?Kf{~^bY5s7B=P5-NQbvU}WirnjBhNArO znS^257)6CnZ8t)mxHCt>UU_&W&S!3`M$})c?MNkPR+?VMj?Nd!|Jnr;<{-G*S*ve! zx)oq_nYIb>V1HW;ttw(oa6niGUC8lz9|wBB!q6fUZ1{;6%&@+%X?t@*VM8@>D!W^%R>C1VlnqtB zOt-lLnRMk-@9q-L(1C$aw>fgOL@;6EgL1F59(LuFFhX=6Jucf%&)eD$gFxmU&COJX ziZBxso0ThG4cqkzf$HL+e!~jeR#QA~Mmx83> z7vWpA>RUc}*asG$D7gC-^9R$ToJ_6vN=1+7Cvji$oW^A98h6bao-lh_<@>G&(bAL4 zXMN^#zLN$=5s%QsKWT8kas} z2I6rM-#^xSV)gBQ!!uYz&anlYQq8a4?k3{UU>Z&srl^g&b+xw9a;LnUK1S>O+p)&* zt0p}TFlVv*b>`kbVr|&&zX!vUd;2eLy-2aKc%Bblrtb&+)ocor(?b0h5$~Snx70J% zJWv(~*ri)03a+CC|A(sGpRZ5==wakfA;Xo}Z{R{^8tnN|a|7wh??1x%e@HsZsHpm{ z4bKeSAzec}A{~;_T_VyTT?zuyT{D1miwa1WbV*A$NJ)2h4&5;C@&A5>wOF$_bI$qg zz3+Wp!qd`KE$K{3shbdyi~IH`|7URYr&B*Fy+=2!V=pHLO=SWVqR;BPw3Vl+ zcwuWOh&l8l%_Am=`zhZ$L464V%-CFykq$dh_jaor}0NZSX)*ddlnM3kk zB#=VFF0VG;qo4@aI1+|k;bqYa6Kk$F_Tti|E}`0WGlZS36EuHe1#z8BV@FnVr39f% zGe1jE3exhOZX?^Htz^K!eEC|vF*}Kdz8?%U)B4x>keBqaubMID(m=WXn=UYzEg7~y zZZts~X*<&XWjds$7>;3iAiQH>$Jy1v`~3N@IgT_La|6{sCww#G$a)SsqXBou4t~%; zng8TXh9*TnS-htaTs=kVD`<U*sx0f~wprpY>1_lUm?@`z^d)krg@m zS?6HInqDZbK+TKE1L{QcSycZaL-(YVWc$gIoea6<`8PoqG)`p(iGtp6ZnxIpWu$Y7 zAfr26T)d7+*-zFr2Bp4|eo~g7=`sAeH5C5>p40VA1D&qMi zKPTw_>nBY#5Qv5t74(NP8_Y{jMBT&iQ$;WSC{A>;W|{(!dIHVuySuw$6Tm%Fe(*gU zJIqo^PgEO#&MGV8`_{z$0Z{6m z#klWajyDTD_@nVo1PE1<90T%Q9F<=V=D70#N`Oa9V=ze_U$|Vg&JQAUb^h7W7?kv0 zY4pmecRcAzAp5|AwEcJ))#K<5I7eTHhenD6jJXnP`T6|;HdqYb3~($ zzcK{V0oP32U#JGLnW+00c;rcVN}4?Vy^Pnz&LiRHbsFthEk!xG8E{0a38i}zu5)0K ziFy4kZuX_0wQn%5=ueDBO`lZL9|u1{^zy&a;YP?^`cvOTO2m)_1%0MfDk#Q?6PfkT|!$kuJzn;iSa zh1QkIoV>UQW_A5fDXKO)1Zq$?BpL`JK6AK)Ym9+hjnQXvi9~(Ag9f+ZeqcrNt92WY z>5?7CFv~cJoCP|H5mR~4Vtx?(mJr9Hwze~v&wuc z(}>%BRW@f_KdNx|{#AIi-q3cKFpKAs1dd52=q*{v`x-^1A$K06yRUl186|VLcWmfR zVeYxf_^u-9Bj}l;%%3ieew0os0lFSGIE@d1QBYgv9k;OgIYCy#$^9*s>TltzSUYE6 z!OMyG4d|mjN|Vz?izG%OS6-#GL9RZ(oY$@ZLdM}ry5|rkPDfr(f&H$10E)NNCJ8)V zOO;s1{{Dr_;=i&|QU`EvZC~U6KX|CX{0RgP-|i!Ni#AdlK!=?izNlJCp-xJ02dZRxM#{#l)^Mt>BL*0%x5HMFQ-#lkVz~#jif+%VquW0 z-tpGN?63|B-Vu(qiWYbkTMeZ%*v~yAarAJ}e5qlDgvR)MktC(}HlwRHT?#g?A3}Tn zkXKx$G5gVS=BT>T=BRwV284H#_pfEK>)QYpV~zR-MYT~*uB{=sqNx{_hsOkTI7xG? z94LRNVm^xey|2hanbkYwtK;xJOU9PtbzI3F##a&S0aZR16ygYyJ{wY07UBm|^wYyC zYx5@@SP%H^GI(YLUK2!u#eeLNuDE>7ZvUwf1UiZIg$2Ii7KgkTTTOa$)xQOcIj>V! zxt`kX?>syMFMilvU^0LA#tEl_2MjODTw-Q)8W{s9I>6f4+Fw~ybXqoPpY@17;70_5 zzxJ=@RMFu7i@Z@rVZ)1cKM1DMUNMl+d^wn|T?0e`th8UEodJIU?LVJon41VKHg-2< zMh_fCqjBB^Z=o(;Y3QFL$?0OOZ*|x);8}U=ovr<{pXkdJ)j4`8PEOR(Z|;l zH3s4=R%ym1miY*SyK?w{6r#$H5)&rDtTw`UakLnN7e@OTy=av;v~4IeSYT8<&gU)G zbyfmKJh(mUE$E~=J8O+3-f=;V*i+9Nb*%7oeS{wyUyitziqktuBsU%cKAcQmy3YuQq*0fZDKTK3_fqnAVe@40`y%}?;< z!urLZ73u#ZP9T`OR@h;Y3Ljj(!a6+6(7NE%_z(6KU$JZL-H^$=q5bp1L|B0s-kuE) zm{8mJf~mbxon^9S=VkA|5_)h;J;qBQbr_$WkM(n5N`EQYBYe65{($#+$`OU3EIZ=l zqy&jl?IDIOI)gKOsL9?wNY{$R&1r(Zw(T@KEs1b_ijWq5b-^JFiOamJpdT%>BzOk? zgVroll^L#;7LW{rn{r8#rWut9wF2H1T8Lkw{GaZmlh|BdJ6 z{~`-oFF@*vd~xPi!Q1EFX-=J|Ue`@4q$1rEK0ZDt05Y9IQw8wTx&Mv8R6&bP#>l@2 z*!ayc$aYGsv={gU^Yx@-)^lHuK;CD#+8yao6N^%G)3wt_^Txhkn>CBuAwQv?+xX1T zi9f>UAtUC29?(|xakINB?7wH!eM7jjC~D1h;cGjjsg>AOmSn+%Uxv8;Vnp#G5yS*im`O-X%f0L{99%Z4;by!7DAHdMVwU4G2deud_h|UeciBZAl9>q-$<=#kdVLy+cESWJ}$v3Ac*&Q z1P={#2sX@laTOHsG0acn*F98{bqB4FM&k3;ErTDg16251523qMBZLA6i>bYv5@bo6 zx%hQ}zxd~9B=!72tI(H8hw$+Kgj)K9yp8+rl{N&L$VLb*;ltiTFi)t7q5sKlG(N#$ zGmPsjOxygNZUK=JY+y>oOsn@r4^wsC{AoPh`9g+NI+XJG!_M2f4;=OeQDnkk!%@YN9VfH0wDFBE`t`}`b5 zAy$zD(%XB)U?3pbE!D}U;u_cOifAS&Sn;QA_6ld7(I86mK_EZ3cg+ncU6W`KIHHt_ zT0ieJdZEtVJ7r?va_S;gOGDlo&e)#1;k^)u;sc(f2u8ypTP>|#4vh1+N5dDfSu2c* z;WRYq%Z-!xy%2-J$e4rj0c=*xKI-fQH3Q(wHjW5YkK2v(2Oz$VH7eO=t{LdvjJbLW zdm4S?1@Qb8-0=vB008p;Fz5FRqwVjk)4wf-ivXEK0Q~&T>vl`8();{CDgWn8tYV<- zV>zB=O&xZw#p9xb{pkBKVKbWg^&5`V;IiLwaa-$dN-T|8-5Na3XifJ$oFolAF-HlEkMmU$*CXsCe@B;Vg^eNg$yIyfNFLZ*(qxf| z>HSa>1*D@0Lm<>!rqzN6kruFtUsT6&69zI@&SoQwh#HC-vJaSylL9Q0t2m}Nj({q2 zBGD40Wl%?TT^)@yt+E;G_~-2r&euuF&Svo2phkWMXpeSrip9))l3Bw1?}ihnxr!jR zxOuvr2YsAfX`c?9FD4`vlRt%dVbp?MdT7=Al?;EejL@bc`* z@)|(I=&k$YSmOpZtBWQgB5FY+2mSngZ^}XDJold6dHNj!evMy@ZO3{1`#|mC&{{I{ z0>euD=+oofrPpqD*cT?0fXT32S9~C;+6%Niqk#q|YEV04q|D~|r-t1e9Ot82SlBDq z?dS#N)S<9qNR*#u+V5}mB?_cEs5isX=kXp~{F}Zml-{hVYs5yZq8I7naCJ)r+(}7Je7E zHi>SxOjrf%GG3mdJ4PL7^(um!&7PX>)qP%t8JJ=dcVB{3=W`L+uPE+5VNCX*ZG)xk zf)>r+4~bfX_6^R{dRto1`6v1n2=0+|9&^VfuAAhL+%%&rYp1iAH zjnB=60Iq|Hf1FPZ4zd?%Oo{1GIG4;L#9}CV6d5;)s@^U?>(z@rwD5o{f^H&9VwITw zG^xa2U=d$TT6rF?Rk=5`6<-PYG!^s>lkj;F(ju{%5#L5a(q>vcPxB+723UA;X|gbd z&tt;>`|0lB^uQGPqX{V|cQI`Us+`Wz=U4~!z{vpoyd8@DN-Lhw;z`*@oxWkK zm;F2E8)P7=YSh;AbXafNt0iCu&r&@mt62DUGqK8PjgcfO#962!QDg>t=-Szgqpb8P zGid$Wm7sm{Lde(NDdfcR8m(a+Q}@mK9ucnE!dRGStO$t-Dyz;zc@s|R!kKWhz8q%C z*t1gRCZuvcnsUI>lAD+q;q3P?&y8S?7Y$DsFWv};C-7l11se&<7;=5S4pQNi^7O^a zyy?fe*$rt6p5`gGeX!CgcDA=rO>6jap>XJMrnmF9E;cm$zdj4Ko|T8;75b#XqwA=4 zz*!OnFe`2RE=!R9^@_w)E3SB=&BJ}NiKnwTXpB?fqH@ghq~q>`dM>IY26fH?MKUxw z66}uVY79a*Ip;OWk3H*HggWkdvS6#t$${u?j4#F)ma(rsY})i{$C|uh?lKb*fQpEU z0{A&nlmi{Js538wO)2UQu+bsNg zv?r*fuNIJOGmMJGK7b~-UNYB>ASVN*y^jJt6EC=vF|l-3BMKf)HSLNKS`G)Mq4j%@ zSU4bJAbDhQr9fp&5eiUMyG{Xg+70E55 zi(e|!eD~=Ilcdr9bNLk&gu1|@1bf+cn(QI2gsxK&xq-;iQJ(jG^GV7Gb<*~fQLg18 zkIf9On)oU+#okG$BFcF!1Qrf(?0HH%8g1Gnv`GOhF&)0hF1?(_ zn*CT-awqvxnwC)4la{K)Q-sh;DsjKHx-C<$I*iw2)4ymSY!!ef1Os>Us57GFIvdD7 zZB>MYAj89LrmaE1Rxu07i8buXTKH8)Kk6J7l@Z~cgPx)feeESVD<4i4yj`1h(7IJEzg`*?`&Ah_xJVhAdGM#`4+y8X9wHn4auEhEBDyn zTU}opp^o%?TjdQ|Li(I4(;B)}qBXrrQT?#_XmRb!9&)pIyV&Sj1%OG!kB>((n%vBQ zqY;ba2qW7MWA_C&J^8^bzV4uWedu`ycCsGD8ZZwt46gJKNMQAoQxB(K)kSn(H^kaI6b55SMBtDvV- zXj>=`E;Vps`N0Xuk7>A??6>;wwNQm`R^27VB~RH3$Z7DixjW?0+qnpUYS@&?q)I%H3;f;Z#^)!R zdZI2GpZ`+0>DUST9(dH)VWNakKCAS?oF`nn7IN%qqjkqjP z(c;jqo$=P?31)=Te4{tT3~OTx8frB_D1%`Y*p><2G^b$2Wc(C#y?;Tuv@p`iQ$3$IDem1{d#o-v^b6IOK$^AaL zeNmVe7Jw8?G$dE=9?flQgQ?6hJR2sEDxny_{o5iSo+TKv0lf#4HD8g~&c!yUSjrAc zS@R{=yVH_MJ~4^iFC%e_!wTc;ra&fxyQxE#jlR@6XjcqWI zkYgfdG^CZARIc(1IPJ#$Crc?@vVD`~^)t5qJete#anVC3X2+6x z98Hs>FzCcF4+4ZRKHXw^Q_G+>T&~2*a6Ma-tnkBknsp_U(rg-RG)8`(79zpxZam!p zn}M{_pqDlKC~LS5)zs=!39|GXI~Z1=FVSJgOPuO>cp`5(%vJ}~wCg|67v-IpvM6yE zW}kP<7~NFl;}AGU3vJ`&5{6&%r9zi|0ZS>sr>Qu}xaN2h%loGGG5q)nO|`5<>ae`& zxEJW#0Z7^(!y&B01mU<+@6BE4+qTYSCuJu-30pm;3!lv`XbjfHVLvH~JTGKatzQe@ zLEl{ia?GaLSK|L>thj|o9P{C9462g2+k1XSjk-N4v8L;Epq0g5~clWd=_ zCCJIO{Lq1%J@>Nt68I0+rKR=?4FGtCxe}G)Ra*9WTwD_jZ3cUnfXVL2-ak@Z7Do0Y zVhqdozVc#H@{XfzM0?dwaB(~PfB4(0}49Q_@|oz&7IE?#b9envH?&r$aaNL*Rr z2C}DZcZjJb02gnUNMTXJtG_G$#ld9ufD)8aNKXn0Dat2wQ;5b%NJ5<$}{4rRdsg-(B=9=Pl%2sIfRxdtVu7?MHN2xIJk? zh?X*9`L{cDG%scsrq&*PrBD!(UMX9h#R5opz9AoAHYil`D6AK}EjNrw4%v5~6h8jO z0g8z#O|6k>iB>rmQb@dIZ^b_F4K&N{z(+jh+O2^;&xXIm`(g#Sp$;PDJPiiUw6X(=u%V@ zE+a&&C;mVj1Y<5vsD+=#5VD_&v(dw)CSzy_!WTf;(OPLIC!S+%CtjZ$)!Ii_S>U#Q z5>}IFavM?Myzpy7?*-n$I5GjNTK-nW92ObD)E-#^@n{UF9|#d}jfis-GN@do2dLfT zYX_!n_Uv zwzX72^b|7=Dmoe^Q%-k#1sh*pb1Adk_QI|n&JTb!bGwaLsH#XAnb!cMKt9=9EEhDc zUYfcPQufJPzm5H-u6UOw)wea(67>>6UT9$`5%y*&T{yoDTFdH&a-w`7NqJ5KTygBV zMSC$w)P(Ili^ru2oH+^)ySoSnWF-Ihd&~)Wl34I1x5rwDo@l%ZYcy!;M^8%}XXjqQ z@uDR<{Q49kwb8bbNNZxb5j6Uy_3$~-t6|@Y;@=ACjRF+*pV~}O zL&L(HHe!VSk=%fHxu0X*vdJX^U*!`EDfb+~}=`SSDB;7ple81ibRl_%earPcrs*p~1_ z7Klk^ad*s4tOsg;Jc7hPde`&992D+X4+`I}FTqHIwQJ#cuO%Y2Yd`10?zKM8d4yj( zNmHdia&L5pwFa+bc*^^oCVpgoVe{pQ+p@gyl9chI&*LftgkPEyvVwfNuCf~VlEBzk z94UDde7#{Uc~5ELy`^LIGUa=VAQuSu%%z5)!h$EL_hy2Aqsx1j;6lPG7BZZM1 z`Wy^NgTpt$K-$||cr#u$Veklh^PtEZkP!<5I31_c9weBSHvL567@MB4;iwJ?Q-~s< zyYs_FpUTS&OqXsZsz;pvD-YGNAV6cl1XV+}iXmNws8wo1ds>sN-7;!>W9W&tFgzsP z$XE9BUyXNoUpTP6q=IioXM}FYceGfuG(&s{D4OC*AcA5uch3k)JN3{6E4+}6si&7P z3A7Ydyk(s6k8$p8sVI7+vt~jV6KI%SW2s*lPm#bZT~ms)^Ntx2xRalOBNCDR+>R3% zbva#5>1|w0th6rd@84UHnED=(+ArQT{Y-zwjCmX{_lq#$^37ePq+@9y&W4;*!XuyL zm5Sk?A2NN-=GtH{okU(IzBFizrP!3+hZgmB)v%u?m8Aw{YTi@jCY8VD1l&Z59H#>{&BBKL_ST@5(W(l%jt$q$sLtWF!kqF-cq z<$MfT9w48=$|x_`6a^Kf96puwQ5SJ!qzYjuY*;=1;Jpdf6Wo|ge=H2A)q8M!xF*V* zTpN?esoef6Z&M*ph&DZX)Ei45<+rJjzPFP8;TFA|_dSJ|#bKsmWI;@KQ+5JM5XlGS z>uoOu=&vl{Vpq7-m{79xCYmm@2}m6@Rq3tUt|Tog82}I)jbYrXKOLKj|IMs5i`GX( z5sL^RFTfAixt`{GC$2*)NjDk;2c#I}zmiHL^(pushZK1Xr= z#zgq-yc&4&N{hX_*@#=4HguGi$h4aa)A#l5bCCG@*w2+zdrA}t2r|eX5J-QNUvq~; zWb~^-Bw4a#fb59nWUWGm~Qjd&OSNKvq|;K~5z%f@l8-jLcal+%)buv}V3Fj~Fs$dci!Ju2efIlhBBaI1MqGcJP z7=M~atzOR7?0McyRXFrPpVmU~j$-k2OZ8)4Wcs;S1#gxKD*y*6W@8XfZr*)@#hw?AgWtdPB~GoF%9IOmP4X@GTqR!ea~U_LcnA zt(cRpyPkjZ$Mm-`^j?*JQEN@}Uz7b{djtGMk(ROQ4Zv30{-DQwH)Zg+;uPf#$VBNO8 z=k!(9tPx%4S|Ufv`dzIE74Z?iy0J(9Y1Hv^#CyYWHJ8O_gyP(f5Y_tD>sRUk`m%2| zQ}6OV{%Xthv_WohYBcXgFVp*=o#Oq>QLC-e!mDjeFElt#D!IT#!W}&mb7}w2kt>(M zZ47)knZra1)F$(s1|1_qQ4mIE`;9U#tWZYl9nIy1Er-{okW&#yw`d_RrtN!s(RJKr zk97?CT1`zkQbKa`iFZgM*X3FGNIG{%3=LZU+A29+JJIL^O+}iA`8cWBm!>6+GVkm! z{oZ+e=mJZhgBPyu0br>!xSPOl6-B?!elE}RJl(ZYu>GhWNjzhG9pii{^V2OQqi3dN zV8q{!Q@5fO4H$-S0iD)Ndl|2P3Mquo%&P`Os{qhmr+7!>Rr0SU9X9#!^W*J5X~f^T zxmU!SQJAd&@6-}#s&aCFuKqCWuix^Uyngx4^5Lcr83}uwf%TNtKT-UXRX1^;a@ zNRdc)+BgcLEIyu^O*I&sZK4X6U`7QsK_w_sqJw7|*!(teV@2F3irV|Q{etF=zwuJG z4IBi!Iv=1Rssz1{a{q0sp%HoWq!gS1KHE8CU3f1m8MfI`%k{FTgIKv~Rk>LS75_I*eTHI(}lCG2R~n~XbI@?7>N@fA}H0@hwlc%L0rTQPV@OX8?MM#Zl40s zqcU&wgj4DExNLF=r;LQA+ctA=`7}bdiaWp=qhPm%NV3{2%rAq09R8!I*X9>ot z+{O97Eq!91D#X$(u~CJZR1yKSk9;GY)7h0sGwAYy!C< zK0AwGSh=wdkYzgg!(Or4C;4=m>=6ilx`{(l0H+!!t{-R45ki9i?qV#}9-L+3eAkJC zB>eXvW)ceO)h{*nl?(u?TLE!b$;4E2ggAf(7kmKfVSvpDfWMjB5TB(Z4-<@fLGUAt{{g_0L_{J57EF~?!soE*>2}kd z#Bd#0dr&Oc1rE2gbBT2djK-u)FyGr{ zEUS&4pbsdTIEm9s6oz10c&Ba&;coW^c=dbUuiyTBs9w)VGgZijORr5jdD5K3RR1gz z>xySpa!sO$K2>1cwJPgM<$=tM-}Dr@1gN?MwD)rsegm~Lma4U}1!ECvhdG}G3o+<$ zO2yDNE=PKLJq?jkNbKq>>30++8urTAqFvBFdD#2i!=wnmXIrd8N_rX7{oWd~_Z{7P zcyhe9nmI-`U}>Udk(tY{`Xd4!E8j}sY{1=+%}nX8)ehE85?q!@(UfZ`pp>EYe4wtg z_onUDPqumj9CcVX!IECkKU-&C$w41B{#{8!KX5xmuuEg~zcMw+#Ai=w3TDaSal9CK zP|-$bPN8-u>&6B&OXuh3ftqG)nk>^ejnA6vxcPkLWWS{1Kk*^)@jYCwmJ7|ii6Sh$ zu@|Nwo+}YH0v|J3s=o%r>}q9V+BB%*bxvRzOY;`SU+9YXq)tvnxEOr-2y*BP97qylz8+oG}Qv4ai%C)vMcsRpIHz^5MnXrur7 z7mcCcTUKTAMj?+68m#ZMOUTS^6zH~G;71G9UYn& zl?@y#mIrqoouwidw=rO#WzkcxrUDL+;3>i%YSK(`*2Csr_Ci9R2z(Gi;o)};E7Z&O zO-%!clJLS}2|)iKzUC!1({d_(Icuhsqmj3Kk0OjCMXJ%9%chYG3J!!B|x1>;;tx&h%sSxH*5jqio&sRoUDWqMZqg^tUFavPuUp)E%*(iXH845g* z3kGJst8*m*V;-lc+nwC%YW4xuuP8pz;o+E$OH@FoiX}3B_`@kV>~yDpXJNI4TDc9m zEz}kSvzYGl`eXJ0;o+10PiI-)(Q+vgg*jdS96m=-`5vV}(5qW7($L;1xzqRpeEt~gWqtR+r{kxe<(N9YQ>}`S0#?*TA zt~4$yrm(Nqv%&?>Dz@hm!o1r zjWR{d& zA+GOiYw&1)sw5W>z#@F?j%GS}F0_iSVo_{7^>q9RQ3WG&QUMtDmF<}zg*aVHWl0i) zNcoH@)GDY#9t~lPiAqTmUxw_o)hrDddT>gRNx#b)p0)j)+IE`#W@>BDT(4sLVRq@E z{mdll{UD>?LE}NffInVHKpYF|xX1Vrqa5&P?^;ZPc8vk=bn~AmKTR%1Ls;CP(bgl6 zR%b`dY@6iPSPk0vqv`%wBTqf1vR|NX&6x2$yl;`HqWGeuC}B@@>g*6pLzKwwBBW&$ zQu&L-W}0I8QV!X2G}6V0bkG-g#XH@;*wW! zCWf+pTU)cHvyX^IDt7(N(yXH3$gA=;q%*ABsdD$F%Xfhr*f2ocYQp+=I&WGW3*T(_Aq&xe#(8Sc&PZ*3rBgBrWKxioWx|hUssV%Qoxh+LH_STkcSJXLaOAC6B)q z>oxrNO>e2He^wDvDGAx>`?`%W-k6zXG#jejo27e}yh*B~oh=xnT18M$`siVFQW1bo z4R0VE<-QfRO~;TnYFH@9wxBnsLj+gDAmJ=Ie2;+bVlFJzgN0WtIjD{ zgu545xgP(ALOZgeBNckoiZ{?Y0J7<^ET#k%NAloWAEjK~mBThHMgDBPRLLLjzh)te zavEu4?fq9r=iq>PDR2o0EP-|Lo0Bl)WB;2hUNQjP)VYFul%&U%1%8*S&Z4>pG{Szi zV5(R28Ww5)U1*{**Qb7mD!iAMc+x>&!gEZsti5nh3QB`qp=z66NI9a%^WZ1H_*TPK zT-dq3CXYj3e8R|*524}XiO$7F`)Xd$e7|zzA4f^kn-JW^@)P1k#DYpd>qp2|xv`J^ zHEUztP1)o%3ABhD&A}k@PYGyfb_oX3uY#!gn(w5=k{f?sHbN&3l`48!Z3)sKL2N6U ztm-Iep2@GfJ+-rGNB7#f2-r%6%)$(+*5q|A&?$+?S##!1#L%;ziG2VP3)830 zMcth1Em{du-J**Zxq|`Mwut?$4~NIdr(~!vU${-rL;F4{ev9pCW~>j<@+IT97#wiu z)&MEWSFfbH7g`oH;_s(tACYwbo)Sd{ zyifM$L`Lzwi<}2e53$bGKpk{Qty>8CVzUR7XTFq7jifJh)C+B5H2X-3CZfc^2-6rY%cLeju50p6T?-B+E zDF0$r`Kj|hq))A_683Yy-p@muM_vKp3a^VX#ZzE1Z!z%2v%T>K2~U?cLID5+ z(+cbdv_>M9Z)gR-3&w%>P92?}7GwBXFBLXDypMX0MsQ?L##-QWNRGI6^J7eUe{ol3 zo>@UP=&uv*>XGAx`9zvpUQ1Y+ck}F2Y=;|_aV-@Msvy>rou$P^UNxBQ)Q(r)2CDw3 zuwmz5>-~~W-~k9~KnL@xva~}#j1y9;GTmsd#)mwo@tmZ$6$rL?4!?#Vp3fyDB{;H% zM**;kz)Ph9u~-Rviar|N&(Br_;fdtemK`}Nei_*>rB58;Dy$BVFD%)SqFLI3AI5n? z81JHc==v-#_opOM3}MvrV|~%QIBBl7(FrM54Q5dC-5)HqqLGYmJUs8Cc~@-whTrrt zr9cJVAURd0;?P*D>5IMaG!?r4L1S=zn!D7f4SBaxHB}nv8>!fEBsC> ztzd?tI(e6yao1)uLB-*-N2I;56Ki!7J*7HdYD2PgA)U>r?*Z*wGz?vs8_bj_ZRW=l3V)^7x)|kPuc?X&N zmL=WiP$z%}5AH~_#X~xcb~&3jHI&e3j^uteGw47n(Ue4Db4c8*mw2tPrz*DAhtVI2 zi=i^Vx;Fh`7MMd4_xmkaFeqT_I`Bvwp0I@4JW!>;6jZj5Dfd<|>L3iqsfRddB$l-m zT_X!U&GYI&-)EZab9RRlZ%DQURfr~>W-vslfb{$~Tk?XAyvPNHrRjL*pRk@;WU(%26S%rtkdc zq85C8%yi4Q3t2jk|MVwL<+;RLVU3csUYKQ0r&bIM(D|%JyvS(( zhwR8pa`?;=`S0}uXNnc3*>dq#fHus~b7AT$+=`;@qR>jIgZ;dT6~U!!E?hFxjS0v) zAJc|Y-yLJrR2qW{84a6XTLy0*E5NOImGY}2oQ1{(iD~3BMkqGi_MDxb=r~?k)wI%$ z5L33dNEUc)r2U9_M-X=GD<8~)M-%9X-#z}rQ&Zi#|7+8}`|qu>E3g^eH0L|DG#3jf zzC^=k)oE$mEQZxc2;TeuWV7a?%5?*?WzH$GjL>WaZej&zH7Pwq7o5}gH+wAs+8f9W z*nDVx7P^KaO^7=3N^-$aWNM|WsZse-nsAl}7jcL{v^o5miP4$qMgu2s1r!*JW4!dH z{w#pVEq3(p3lGJG-&q<^f*=h)a&#{?R(K(DIt0+7xApI+bsp2Fl1v-R3ot%R1!O=k zBR5lp9D7$DmsWuF3hqW-MJFzqGO~5R!`}X>ekl}aE98evw+KkW?bd_fV}Cu>xy0Z` zig1IT+`58X5O&g^&U_a2BI&9d|TT#H7x;|4>Qet z(BNE(Xw@s1jiV}sEVGHuU&?#@tZ@ZOmR(Qx4 zOqFH(Cc~9PeJ#zx2plC}k4(JFo1i@WSWuerbO)@N682bti?kmEaL8yr1D;)ZbPT`uqj~dMg6Oui{pdxm$roXa2P7BA|g|3G8Q9%#-V-AUu#3 zs6KN%;`-aVb=KA7d z_aC=dbL#Djy}!gg{wF+!p4rUU$6t(1-$YJdf^QaoyC2v#1*rJ?b=@Rpge;#R?OCxl zC(L`U=cMvF@Z9dn8a9-r*vb-YW^K6F*(b?X3-shi3F%f*V2M((fN=( z7s~Vd?72brK2043nE_P5sMe`@V}m9%G}OcJMZ0gDyv{`R9gggJEbL1hnN3js%Q<^rFMF?b0b0?rz2zDnq1{tmj9`p zqqcnyol;r;un?_8e~-jtf_pmuflY)&Y%ape4s}qulv1-ao%H;XXGs|b5;!}G%!wT* zCp>_;O~JTrBgZPy4uIioW9XKh?dn94p8e;b?@2oU+fk~KFk8%iY?_tl{yvh;nG;0b z%ZeOB)zc=2d<&9^mKCAS(r?Dj6y0IHxH1TD5a%!&4E+7f2!WkY1Gitxa*h5e0=&ZI zw4MlGA)3!?0&-{V3ye`&lGuIo-+lsaM_{qza!udSaI!%&V{nU^v^feBXh-$CmK7{+ zo!??bnF?g|83OI$3<;xrp`v&suFH%E3QPb~4jog30rP5tEcCwx2OZE>-aM>&RT82O z?*waL5aP`8@GP-MBMNah|Wy4FQ|4w1eJ_^(C2da>= zeA}>C+6aPm%OgXh;8BF`?u-;Py-9ENL%A=mI?MHWTTYdx%@=)vLB1Vd%8+S@IYoq$ zSy{SA)u8TD^zpHn=LTNMwS-E(rgWL^{TU$GZi6=z+}HHx)(ahHokUFanWWnB!E{&~ z`t_e#&Eu+Sp1wn~&gud2@8zzw@jpm!3 zkX7T)ev0t*ze%U2%a>@tr2$M4eWyS-|DA@M3nL@&-QsQ`NpV(;xT`n_w0}JRr6c`G z6U!7VXE$A{3|ut9e;3Wh9Uac*K`k}P!E$;M@IV9yf=SLt_sqV}7H*6VSUovayQcF5 zvOrtxnbIE4iPiyXyxj<7z}p9#IJLS}HQD#b&eB1DYP z9n!&bG2NuzA~KMbd2DX=jnqkbAdr2<3lg>Cw$YjX7+!~v`@4*uXP+j*Kr9&zhAcQq zwveOe8ASvmS>`Vhu_gDUh=(rLoN_MIVA+Pt8($}PO_rr$dhrQxJP-rj`2*|~ajF$e zkWbsz_WMF7JuMWW#tD>5pVb5_7bbiQv!CmHRml(H!F4nS?x-oIOOAs30Ojp4vx#q+ zhvFX2bzLU@O^9>?3%9c!)`g-KA}bHG z=`?;0qfP}iT_)?9{fgkV9)$EQ%fuxkb69Y@ z%p5;p37{7yrU*SltLMY}@gf zCV89+GlR^#( zWC~wGKz4BYaHs;va$_ZP1m1v&*(H1vw>Z0jn%kCV5G4xs!kAeK$x`p;<_~$j_4;H! z@>I!U+M>#<==v$FH<=*Rd^d;Xmoy=N3vVfguO-yiwXaQizWz0EXOYOaoh+OFDCHjZ z5-V>+57PO&*tR;JeY8dpTzYHvepZy#Cv}86S*rK(Sue-})dKviU+M6Lg2D`JM!e4r zTgo_X8)1u$@W@m>06x<-9oK|IknP+fW6V&#vN+~Q9N8#=k!y_Elt3hmvYA9QN>Yv) zDNTY-tI|c*c=g{iP;QcLj4~I6;5$N|rU1vu#pF%YQ;oC{(-MFt1Kf7qQ;fj>OSHxl zgM|PIpV^hAn#%0VgXLP)Xi};ODiPf*SLadREvO0Rs z(LW{r-EW+d5GVS`9GmedOf50UUoS^i$gXVi0%f+BtLSEqmnainFiV3V0TBF-=^|zCwUIZ7OT$P`GZ{OC1oX zmf1B7^v-MwK^P$eI}D9O+SuI4aYv62~nPBT|7?}=g-KA zDv%3Xwu{GaQCmw;Je)A=$rwwG&I11Z-6#hhdASQ<&2pE6EK11Gjyp1^KO&sBH@w#4 zOUu&xMYR(-q12c-))ANvFdWXerwFb8KDhNQPV(u->`|b`CE#IfChq8K?C!SevWYgk z4@NucJ0|I$1FH5n@1lyhX?mg{7XO)Wyo3``UMr~U6r%W%U_2}zWpkRntj{S{1SUAE z6Gy$eVM}E?*1F|SlQ?NmU*~}TM0X-9Bqy;dgqT*GiB!-q3Z(_PmEQLm5xHOZMd(GZ zqk*bUL%L`0iis4+Nyb96B(WaSQJNRtkOyt9li>4z+mj}IfED|UC8PfOnEA@l`J}Pl#-+9GKSoc{ou>~#Nd@ET*ww)6zPU;{ypSaw(nM5zv|Clo%lNHnqY_Q#aslAF)rmE%ZovmVB z;hS^*p|`{y4u10`E;ce(Q?wxcxOWG9Fu5~u#b zj{X+G*I2tYV%qDt8GG0wI21L|Y>ukT+y0|-YEqeAuphJ2#qFrg=<5I4yYhdi*Eal} zF=L;Q-H5RqWStgUw!zq=>?*RSWT#_7W)X=Il14>jIT*6l6vi%Fgc636rI8m|PZ(sG zcY6CX-gEu%{PaB65BCqxeLv52-JknMwlAwB=$U>h4FheBOYa>&J)u$P0&@T6H6k(* z*YtO5Dc5NR0a3TiiI=^h{VB|B)XE*@{zwv5rYm+cvyW|*PN}D_sPq^_%d#NZ77WY` zv}08QKWqc$pxdHkSJ48JN>qTJmuVZ!gaXM6qnaR(IP0{wK1IN?DNzICE9y^`j88#E z`I9Gsis8J@;r=1BZEI+;;quaZAGHqhw7(6MA&2-qO;e`S! zYkwu&A|%Uea39k%v-c0484`UcAPSZgOBypl=LOeruc!)z?a@o>9fS)Fkg+nm)l3~2 z(6HXJUv;c=BLdJ%K-5gfikfBGy;a=6QHc}G;}sxeMpA6gd#&`Lc>>}r zzd1+6{-BR~_jw!}%`4aP^(d~oyd~k1HGZZYZvM0x^l-1B>V=%7!cLOC^Ic~%$~R4J zO>2rc&uE_aM~@>7Qgd91GQ_vNC`Yy zF4^#mOZIfUzx|!2InU75ag5oND&g3{8SFIeWG zHh32ox%L3TS(%(1Imo(Q6|XEt{1tGuNa@F*uA=P2TosQZ^1{rooxgrWx)+*A)5xok z#U=b6=|W3Xk>e&luxl#weTt@wylSkgT8y-_!RO`Th3-dJT4EC`Zhy9(wh&$?CKg93 zaH@pZ%Kzs8yZ6mToAHjv6j4a7J;hA&5^PEZ{LY|i?*>clE)NLoGCo=q6*kw@`OG}9XmhR-r3@_1U)vMpTApTk);6ai77X6VmDc*4#^ z4FCWJxbpRynR^T~@hfEv@ zls%qHIpF6IL)5TqsZo{hJ)QDIWw@i7Oq-Oy={Iqp)q=Qtzu~RDd73=ap2k6$+bb{U zo(uktKO=tDXoBIq?+ZfQH~J=l`fj{4)hn5uYIv{J}D+0|)6@isp z1ub98UV#H~2+~2G^}zyM?LfPtO=Bi?(i|~p=_Vy@KYX}(qJ)uEkx3vSKTE& z+n8I?Zv;t3m2jig#rXuI<7_5d`*MH_9{Pyy!epsHRYIJWRDA^WyS`S(>@$04tNtDv z&27litwl0kBX1{vtq-QkdW|csA9evQDZO0X?>_Kn|_&3wxl%EqAtZjw$KprQRY3WzkOG600YI zLDsxNVVZpFpYH=mSa@h>8Jr2KqtyVR&t^A22Q_bz*gooSeV4sHm5d>{ZK<=SJFz5Lv^zkvFlIVUx~>jEaovfy zSb(ak{yv?GuQX$vXRE9kj+E`CYguX+Buk_m?I~oUnmxR5^|oH1bCRh1`-pVzFdLEV zdyn6p0KfRhb7~!reZ}Hf!+X3@6;G#T8qnzHm0Tc%NZItRRoy64`dIvQj~VkI2Xx8` zzR}ig!9fH%p}cI1HgDd z*EA7n0UKE8>-kKh_BIWX#ie69_Bd0JDx?O55C=Afm2n_^kno}nccp9Zh)CTHV7}B) zNbz_9YuplaV;7FNgP-Wz4RfYCg6yIN?1b81D7z`Of$jYeI9DeS zFF;dnvoWtRbkGlmJp1OQf9p#)wc9k-yH-;9G_|LQm0nqWzjhKsZmlePUMR~=q6oOD z%9gU}*gNwF|C0QSA}wIx!nq}R56%7& z5=#~s%j3PFpTO*4Hr0IiIlx7+6yXplH4QH$ODYo*FJoKZIUiPlc`=JF0!_up<+Pur zT{*U)ypyFNtv-&FTK0CUA(I{7Mx%wE*X(GR-Bq=s!CB^1^<>7{IS@J3du3_k2(5H1 zanS@Nf_Ngm(Zbyepx=NoT;}7Q{UgY0g0GiPx`(^pa)J>8NGdK|BZt4zEf2u@P?Uym zq#xc9m{=b2^;Bg!`~QbkjrJV+ro&8Pz8zv_8d(9`}|e4=-984m4z5bbs)L7IssA7(z>&6pci0w zihEYSYw!QLk8#C1XvlDa1T5s{1@yRUBYtW{0ja@gn>eiGOt` literal 0 HcmV?d00001 diff --git a/port/glyphs/xbox.png b/port/glyphs/xbox.png new file mode 100644 index 0000000000000000000000000000000000000000..7cbfc9e35794926be0b1b8fb7871c0491f08314e GIT binary patch literal 51781 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 RpdAE!-o8win_line[index].code; if (code >= -0x300 && code < -0x2DF) { + if (DrawPadGlyph(code, screen, this->edge_alpha < 0x80 ? this->edge_alpha : 0x80)) { + return; + } set2DSprite_Core(Vif1Packet, texture, screen, texel, 0x80, 0x80, 0x80, this->edge_alpha < 0x80 ? this->edge_alpha : 0x80); return; diff --git a/port/src/gameutil_sprite.cpp b/port/src/gameutil_sprite.cpp index 09f3c0895..503afbc06 100644 --- a/port/src/gameutil_sprite.cpp +++ b/port/src/gameutil_sprite.cpp @@ -1,3 +1,9 @@ +#include +#include +#include +#include +#include +#include #include #include "draw2d_port.hpp" @@ -34,6 +40,15 @@ PC_OVERRIDE void set2DSprite_Core(sceVif1Packet *packet, CTexture *texture, cons return; } + // DC_SPRITE_TRACE=1: each distinct (texture, texel rect) a sprite draws, once, for finding the art + // a draw site uses (button symbols baked into textures). + static const bool trace = std::getenv("DC_SPRITE_TRACE") != nullptr; + static std::set> seen; + if (trace && seen.emplace(texture->name, texel.x, texel.y, texel.width, texel.height).second) { + std::fprintf(stderr, "sprite %s texel %d %d %d %d screen %d %d %d %d\n", texture->name, texel.x, texel.y, + texel.width, texel.height, screen.x, screen.y, screen.width, screen.height); + } + const float x0 = static_cast(screen.x); const float y0 = static_cast(screen.y); const float x1 = static_cast(screen.x + screen.width); diff --git a/port/src/glyph_draw.cpp b/port/src/glyph_draw.cpp new file mode 100644 index 000000000..2c70deaa7 --- /dev/null +++ b/port/src/glyph_draw.cpp @@ -0,0 +1,126 @@ +#include "glyph_draw.hpp" + +#include +#include +#include +#include + +#include "draw2d_port.hpp" + +namespace { + +struct Baked { + const char *texture; + int x, y, w, h; // the art's box in the texture + glyphs::Button button; +}; + +// Boxes found by dumping the textures (DC_TEXTURE_DUMP) and tracing the draws (DC_SPRITE_TRACE). +constexpr Baked kBaked[] = { + // name entry: the L1/R1 hints beside the name (the strip also holds L2 and R2) + {"nametemp", 0, 472, 24, 24, glyphs::Button::L2}, + {"nametemp", 24, 472, 24, 24, glyphs::Button::L1}, + {"nametemp", 49, 472, 24, 24, glyphs::Button::R1}, + {"nametemp", 73, 472, 24, 24, glyphs::Button::R2}, + // the character board's tab hints + {"perbrd", 72, 20, 24, 20, glyphs::Button::L2}, + {"perbrd", 98, 20, 24, 20, glyphs::Button::L1}, + {"perbrd", 124, 20, 24, 20, glyphs::Button::R1}, + {"perbrd", 150, 20, 26, 20, glyphs::Button::R2}, + // the cutscene skip prompt: PAL (YES cross, NO circle) and the other layout + {"pause_e", 46, 35, 16, 16, glyphs::Button::Cross}, + {"pause_e", 80, 52, 16, 16, glyphs::Button::Circle}, + {"skip_bord", 62, 77, 16, 16, glyphs::Button::Circle}, + {"skip_bord", 86, 94, 16, 16, glyphs::Button::Cross}, +}; + +int Overlap(const Baked &b, const CRect_i_ &texel) { + int x0 = std::max(b.x, texel.x); + int y0 = std::max(b.y, texel.y); + int x1 = std::min(b.x + b.w, texel.x + texel.width); + int y1 = std::min(b.y + b.h, texel.y + texel.height); + return x1 > x0 && y1 > y0 ? (x1 - x0) * (y1 - y0) : 0; +} + +} // namespace + +bool GlyphDrawSlot(glyphs::Button button, const CRect_i_ &slot, int alpha, float grow) { + glyphs::Image image; + if (!glyphs::Find(button, image)) { + return false; + } + float slot_w = static_cast(slot.width) + 2.0f * grow; + float slot_h = static_cast(slot.height) + 2.0f * grow; + // In text the symbol sits inside its slot with a margin, so it never touches the letters beside it; + // painted over art (grow > 0) it must cover the art instead. + // Round buttons are sized by their diameter (the atlas keeps their proportions to the shoulder buttons + // and the d-pad); keycaps and the mouse by their height, so a short wide key (Tab, Space) is as tall + // as a letter key. A key may reach into the spaces beside it, not further. + float scale = slot_h * (image.key ? (grow > 0.0f ? 1.3f : 0.92f) : (grow > 0.0f ? 1.0f : 0.84f)) / + (image.key ? image.height : glyphs::kReference); + float width = image.width * scale; + float height = image.height * scale; + const float max_w = slot_w * (grow > 0.0f ? 2.2f : (image.key ? 1.7f : 0.96f)); + if (width > max_w) { + height *= max_w / width; + width = max_w; + } + float x = static_cast(slot.x) - grow + (slot_w - width) * 0.5f; + float y = static_cast(slot.y) - grow + (slot_h - height) * 0.5f; + auto a = static_cast(alpha); + // In text the symbol casts the font's shadow: its own shape, black, half as strong, one pixel down and + // right. Painted over art it has none (the art has its own). + std::vector quad; + auto add = [&](float ox, float oy, u_char r, u_char g, u_char b, u_char alpha_byte) { + quad.push_back(draw2d::Vertex(x + ox, y + oy, 0.0f, image.u0, image.v0, r, g, b, alpha_byte)); + quad.push_back(draw2d::Vertex(x + ox + width, y + oy, 0.0f, image.u1, image.v0, r, g, b, alpha_byte)); + quad.push_back(draw2d::Vertex(x + ox + width, y + oy + height, 0.0f, image.u1, image.v1, r, g, b, alpha_byte)); + quad.push_back(draw2d::Vertex(x + ox, y + oy + height, 0.0f, image.u0, image.v1, r, g, b, alpha_byte)); + }; + if (grow <= 0.0f) { + add(1.0f, 1.0f, 0, 0, 0, static_cast(a / 2)); + } + add(0.0f, 0.0f, 0x80, 0x80, 0x80, a); + gfx::Draw2D(gfx::Primitive::Quads, quad, image.binding, draw2d::SpriteState()); + draw2d::RestoreTestZbuf(); + return true; +} + +bool GlyphReplaceSprite(const char *texture, const CRect_i_ &screen, const CRect_i_ &texel, int alpha) { + if (texture == nullptr || texel.width <= 0 || texel.height <= 0) { + return false; + } + for (const Baked &b : kBaked) { + if (std::strcmp(b.texture, texture) != 0) { + continue; + } + int overlap = Overlap(b, texel); + // the drawn rectangle is the art: most of it is inside the box, and the box is mostly inside it + if (overlap * 10 >= texel.width * texel.height * 6 && overlap * 10 >= b.w * b.h * 6) { + return GlyphDrawSlot(b.button, screen, alpha); + } + } + return false; +} + +void GlyphOverlaySprite(const char *texture, const CRect_i_ &screen, const CRect_i_ &texel, int alpha) { + if (texture == nullptr || texel.width <= 0 || texel.height <= 0) { + return; + } + const float sx = static_cast(screen.width) / static_cast(texel.width); + const float sy = static_cast(screen.height) / static_cast(texel.height); + for (const Baked &b : kBaked) { + if (std::strcmp(b.texture, texture) != 0) { + continue; + } + // only a box wholly inside a larger rectangle: whole-rectangle matches were replaced + if (b.x < texel.x || b.y < texel.y || b.x + b.w > texel.x + texel.width || + b.y + b.h > texel.y + texel.height || b.w * b.h * 2 > texel.width * texel.height) { + continue; + } + CRect_i_ slot(screen.x + static_cast(static_cast(b.x - texel.x) * sx), + screen.y + static_cast(static_cast(b.y - texel.y) * sy), + static_cast(static_cast(b.w) * sx), static_cast(static_cast(b.h) * sy)); + GlyphDrawSlot(b.button, slot, alpha, 1.0f); + } +} diff --git a/port/src/glyph_draw.hpp b/port/src/glyph_draw.hpp new file mode 100644 index 000000000..163ba6c52 --- /dev/null +++ b/port/src/glyph_draw.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "platform/glyphs.hpp" +#include "rect.hpp" + +// Draws the button symbols of platform/glyphs.hpp into the game's 2D screen space. + +// The symbol of a pad button in a slot: the art keeps its proportions, scaled so a round face +// button fills the slot's height, and sits in the slot's middle; a wide one (a key) may reach out +// of the slot up to twice its width. grow adds pixels on every side. False (nothing drawn) when +// the game's own art should be used (see glyphs::Find). +bool GlyphDrawSlot(glyphs::Button button, const CRect_i_ &slot, int alpha, float grow = 0.0f); + +// Button symbols painted into the game's textures (the skip prompt's two buttons, the shoulder +// button labels of the name entry and the character board), by texture name and the box of the +// art in the texture's texels. The sprite draws (RectSprite, snd.cpp) call these for every +// textured rectangle: +// - Replace: the drawn texel rectangle is (about) one of the boxes; the symbol is drawn in its +// place and true is returned, so the caller skips the rectangle. +// - Overlay: the drawn rectangle holds a box among other art; call after drawing the rectangle, +// and the symbol is drawn over the painted one. +bool GlyphReplaceSprite(const char *texture, const CRect_i_ &screen, const CRect_i_ &texel, int alpha); +void GlyphOverlaySprite(const char *texture, const CRect_i_ &screen, const CRect_i_ &texel, int alpha); diff --git a/port/src/options/rows.cpp b/port/src/options/rows.cpp index c50c5b4ca..1e1fe9ba2 100644 --- a/port/src/options/rows.cpp +++ b/port/src/options/rows.cpp @@ -446,6 +446,18 @@ void SetGyro(Config &config, int choice) { config.gyro = static_cast(choice); } +int GlyphDeviceCount(const Config &) { + return 6; +} + +int GlyphDeviceChoice(const Config &config) { + return static_cast(config.glyph_device); +} + +void SetGlyphDevice(Config &config, int choice) { + config.glyph_device = static_cast(choice); +} + // 0.10 to 2.00 in twentieths. int GyroSensitivityCount(const Config &) { return 39; @@ -545,6 +557,12 @@ const Row kAudioRows[] = { const Row kControlRows[] = { GameRow<&ConfigGameOptions::vibration, true>("input.vibration", "Vibration", "On|Off", 0x15F), + NamedRow<&Config::glyphs_new>("input.glyphs", "Button Symbols", + "\"Button Symbols\"\nNew: redrawn symbols\nfor your controller.\nOriginal: the PS2's.", + "Original|New"), + SettingRow("input.glyph_device", "Symbols Shown", + "\"Symbols Shown\"\nAuto: the device you\nuse. Or always show one\nof the others.", GlyphDeviceCount, + GlyphDeviceChoice, SetGlyphDevice, nullptr, "Auto|PS4|PS5|Xbox|Switch|Keyboard"), SettingRow("input.mouse_sensitivity", "Mouse Sensitivity", "\"Mouse Sensitivity\"\nHow fast the mouse\nturns the camera.", MouseSensitivityCount, MouseSensitivityChoice, SetMouseSensitivity, MouseSensitivityText, nullptr, RestoreMouseSensitivity), diff --git a/port/src/platform/config.cpp b/port/src/platform/config.cpp index 950d672a3..5e6db2f9b 100644 --- a/port/src/platform/config.cpp +++ b/port/src/platform/config.cpp @@ -134,6 +134,22 @@ bool ReadAspect(const Json &value, ConfigAspect &out) { return true; } +constexpr const char *kGlyphDeviceNames[] = {"auto", "ps4", "ps5", "xbox", "switch", "keyboard"}; + +bool ReadGlyphDevice(const Json &value, ConfigGlyphDevice &out) { + if (!value.is_string()) { + return false; + } + const std::string &name = value.get_ref(); + for (std::size_t i = 0; i < std::size(kGlyphDeviceNames); ++i) { + if (name == kGlyphDeviceNames[i]) { + out = static_cast(i); + return true; + } + } + return false; +} + constexpr const char *kGyroNames[] = {"off", "always", "first_person", "held"}; bool ReadGyro(const Json &value, ConfigGyro &out) { @@ -316,6 +332,16 @@ bool Apply(Config &config, std::string_view name, const Json &value) { if (name == "input.mouse_capture") { return ReadBool(value, config.mouse_capture); } + if (name == "input.glyphs") { + if (!value.is_string() || (value.get() != "new" && value.get() != "original")) { + return false; + } + config.glyphs_new = value.get() == "new"; + return true; + } + if (name == "input.glyph_device") { + return ReadGlyphDevice(value, config.glyph_device); + } if (name == "input.mouse_zoom") { return ReadBool(value, config.mouse_zoom); } @@ -559,6 +585,8 @@ std::string ConfigSerialize(const Config &config) { root["input"]["mouse_invert_y"] = config.mouse_invert_y; root["input"]["mouse_capture"] = config.mouse_capture; root["input"]["mouse_zoom"] = config.mouse_zoom; + root["input"]["glyphs"] = config.glyphs_new ? "new" : "original"; + root["input"]["glyph_device"] = kGlyphDeviceNames[static_cast(config.glyph_device)]; root["input"]["mouse_camera_return"] = Shortest(config.mouse_camera_return); root["input"]["mouse_release"] = config.mouse_release_keys; root["input"]["vibration"] = options.vibration; diff --git a/port/src/platform/config.hpp b/port/src/platform/config.hpp index e1f2faadc..545c30583 100644 --- a/port/src/platform/config.hpp +++ b/port/src/platform/config.hpp @@ -30,6 +30,17 @@ enum class ConfigGyro { Held, }; +// input.glyph_device: whose button symbols the game draws. Auto follows the device in use (the +// last of keyboard/mouse or gamepad to be touched; a gamepad's own family), PS4 until one is. +enum class ConfigGlyphDevice { + Auto, + Ps4, + Ps5, + Xbox, + Switch, + Keyboard, +}; + struct ConfigKeyBinding { std::string action; std::vector keys; @@ -91,6 +102,9 @@ struct Config { bool mouse_invert_y = false; bool mouse_capture = true; bool mouse_zoom = false; + // input.glyphs "new": the button symbols of glyphs/ (see glyphs.hpp); "original": the game's own. + bool glyphs_new = true; + ConfigGlyphDevice glyph_device = ConfigGlyphDevice::Auto; // Third-person vertical return after mouse input: 0 holds height, 1 is retail's rate. float mouse_camera_return = 0.2f; std::vector mouse_release_keys; diff --git a/port/src/platform/glyphs.cpp b/port/src/platform/glyphs.cpp new file mode 100644 index 000000000..736816e25 --- /dev/null +++ b/port/src/platform/glyphs.cpp @@ -0,0 +1,268 @@ +#include "platform/glyphs.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#define STB_IMAGE_IMPLEMENTATION +#define STBI_ONLY_PNG +#define STBI_NO_STDIO +#include "stb_image.h" + +#include "platform/config.hpp" +#include "platform/input.hpp" +#include "platform/paths.hpp" + +namespace glyphs { + +namespace { + +namespace fs = std::filesystem; + +struct Rect { + int x = 0; + int y = 0; + int w = 0; + int h = 0; +}; + +struct Style { + std::map glyphs; + fs::path file; + gfx::TextureHandle texture = gfx::kNullTexture; + int width = 0; + int height = 0; + bool failed = false; + bool keyboard = false; +}; + +struct State { + bool indexed = false; + std::map styles; +}; + +State &Get() { + static State state; + return state; +} + +bool ReadFile(const fs::path &file, std::vector &bytes) { + std::ifstream stream(file, std::ios::binary); + if (!stream) { + return false; + } + bytes.assign(std::istreambuf_iterator(stream), std::istreambuf_iterator()); + return true; +} + +// glyphs/ beside the executable, then in the save folder (so the art can be replaced without +// touching the install). +std::vector Directories() { + std::vector directories; + fs::path exe = PathsExecutable(); + if (!exe.empty()) { + directories.push_back(exe.parent_path() / "glyphs"); + } + directories.push_back(PathsSaveRoot() / "glyphs"); + return directories; +} + +void Index() { + State &state = Get(); + if (state.indexed) { + return; + } + state.indexed = true; + for (const fs::path &directory : Directories()) { + std::vector bytes; + if (!ReadFile(directory / "glyphs.json", bytes)) { + continue; + } + nlohmann::json root = nlohmann::json::parse(bytes.begin(), bytes.end(), nullptr, false); + if (!root.is_object() || !root.contains("styles") || !root["styles"].is_object()) { + std::fprintf(stderr, "glyphs: %s is not a glyph index\n", (directory / "glyphs.json").string().c_str()); + continue; + } + for (auto &[name, value] : root["styles"].items()) { + if (!value.is_object() || !value.contains("atlas") || !value.contains("glyphs")) { + continue; + } + Style style; + style.keyboard = name == "keyboard"; + style.file = directory / value["atlas"].get(); + for (auto &[glyph, rect] : value["glyphs"].items()) { + if (rect.is_array() && rect.size() == 4) { + style.glyphs[glyph] = {rect[0].get(), rect[1].get(), rect[2].get(), + rect[3].get()}; + } + } + state.styles.emplace(name, std::move(style)); + } + return; + } +} + +// Decodes the style's atlas into a texture the first time a symbol of it is wanted. +bool Load(Style &style) { + if (style.texture != gfx::kNullTexture) { + return true; + } + if (style.failed) { + return false; + } + style.failed = true; + std::vector bytes; + if (!ReadFile(style.file, bytes)) { + std::fprintf(stderr, "glyphs: cannot read %s\n", style.file.string().c_str()); + return false; + } + int width = 0; + int height = 0; + int channels = 0; + unsigned char *pixels = stbi_load_from_memory(bytes.data(), static_cast(bytes.size()), &width, &height, + &channels, 4); + if (pixels == nullptr) { + std::fprintf(stderr, "glyphs: cannot decode %s\n", style.file.string().c_str()); + return false; + } + gfx::TextureDesc desc; + desc.width = static_cast(width); + desc.height = static_cast(height); + desc.format = gfx::TextureFormat::Rgba8; + desc.has_alpha = true; + gfx::TextureHandle texture = gfx::CreateTexture(desc); + if (texture != gfx::kNullTexture) { + gfx::UpdateTexture(texture, 0, 0, 0, static_cast(width), static_cast(height), pixels); + style.texture = texture; + style.width = width; + style.height = height; + style.failed = false; + } + stbi_image_free(pixels); + return style.texture != gfx::kNullTexture; +} + +bool Fill(Style &style, const Rect &rect, Image &out) { + if (!Load(style)) { + return false; + } + out.binding.texture = style.texture; + out.binding.filter = gfx::Filter::Linear; + // gfx takes Draw2D texture coordinates in texels of the texture + out.u0 = static_cast(rect.x); + out.v0 = static_cast(rect.y); + out.u1 = static_cast(rect.x + rect.w); + out.v1 = static_cast(rect.y + rect.h); + out.width = static_cast(rect.w); + out.height = static_cast(rect.h); + out.key = style.keyboard; + return true; +} + +const char *ActionName(Button button) { + switch (button) { + case Button::Cross: return "cross"; + case Button::Circle: return "circle"; + case Button::Square: return "square"; + case Button::Triangle: return "triangle"; + case Button::L1: return "l1"; + case Button::R1: return "r1"; + case Button::L2: return "l2"; + case Button::R2: return "r2"; + case Button::Start: return "start"; + case Button::Select: return "select"; + case Button::Up: return "up"; + case Button::Down: return "down"; + case Button::Left: return "left"; + case Button::Right: return "right"; + case Button::L3: return "l3"; + case Button::R3: return "r3"; + default: return nullptr; + } +} + +const char *GlyphName(Button button) { + switch (button) { + case Button::Dpad: return "dpad"; + case Button::DpadUpDown: return "dpad_ud"; + case Button::DpadSides: return "dpad_lr"; + case Button::LStick: return "lstick"; + case Button::RStick: return "rstick"; + default: return ActionName(button); + } +} + +const char *StyleName(const Config &config) { + InputGlyphFamily family = InputGlyphFamily::Ps4; + switch (config.glyph_device) { + case ConfigGlyphDevice::Auto: family = InputActiveGlyphFamily(); break; + case ConfigGlyphDevice::Ps4: family = InputGlyphFamily::Ps4; break; + case ConfigGlyphDevice::Ps5: family = InputGlyphFamily::Ps5; break; + case ConfigGlyphDevice::Xbox: family = InputGlyphFamily::Xbox; break; + case ConfigGlyphDevice::Switch: family = InputGlyphFamily::Switch; break; + case ConfigGlyphDevice::Keyboard: family = InputGlyphFamily::Keyboard; break; + } + switch (family) { + case InputGlyphFamily::Ps5: return "ps5"; + case InputGlyphFamily::Xbox: return "xbox"; + case InputGlyphFamily::Switch: return "switch"; + case InputGlyphFamily::Keyboard: return "keyboard"; + default: return "ps4"; + } +} + +} // namespace + +bool FindNamed(const char *style_name, const char *name, Image &out) { + Index(); + auto style = Get().styles.find(style_name); + if (style == Get().styles.end()) { + return false; + } + auto rect = style->second.glyphs.find(name); + return rect != style->second.glyphs.end() && Fill(style->second, rect->second, out); +} + +bool Find(Button button, Image &out) { + const Config &config = ConfigGet(); + if (!config.glyphs_new) { + return false; + } + const char *style = StyleName(config); + const char *glyph = GlyphName(button); + if (glyph == nullptr) { + return false; + } + if (std::string_view(style) == "keyboard") { + // The key (or mouse button) the player bound to the action; the pad's symbol where the art + // has none for it, so a button never shows nothing. + const char *action = ActionName(button); + if (action != nullptr) { + std::string key = InputPrimaryBindingName(action); + if (!key.empty() && FindNamed("keyboard", key.c_str(), out)) { + return true; + } + return FindNamed("ps4", glyph, out); + } + } + return FindNamed(style, glyph, out); +} + +void Reset() { + State &state = Get(); + for (auto &[name, style] : state.styles) { + if (style.texture != gfx::kNullTexture) { + gfx::DestroyTexture(style.texture); + } + } + state.styles.clear(); + state.indexed = false; +} + +} // namespace glyphs diff --git a/port/src/platform/glyphs.hpp b/port/src/platform/glyphs.hpp new file mode 100644 index 000000000..fed47e837 --- /dev/null +++ b/port/src/platform/glyphs.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include "gfx/gfx.hpp" + +// The button symbols the game draws in its text and menus, redrawn from glyphs/ (beside the executable, +// or in the save folder): one atlas per style (PS4, PS5, Xbox, Switch, keyboard and mouse) and +// glyphs.json with each symbol's rectangle. Host side: plain types only. See docs/GLYPHS.md. +namespace glyphs { + +// What the PS2 pad's buttons are called in the game. +enum class Button { + Cross, + Circle, + Square, + Triangle, + L1, + R1, + L2, + R2, + Start, + Select, + Dpad, + DpadUpDown, + DpadSides, + Up, + Down, + Left, + Right, + L3, + R3, + LStick, + RStick, +}; + +// One symbol: where it is in its atlas (texels of the atlas, normalised by the renderer as for any +// Draw2D texture) and how big it is. width and height are in reference pixels, where +// kReference is the width of a round face button; a caller scales by its own slot. +struct Image { + gfx::TextureBinding binding; + float u0 = 0.0f; + float v0 = 0.0f; + float u1 = 0.0f; + float v1 = 0.0f; + float width = 0.0f; + float height = 0.0f; + // A keycap or mouse of the keyboard style: wider or narrower than a round button, and drawn bigger. + bool key = false; +}; + +constexpr float kReference = 64.0f; + +// The symbol for a pad button under input.glyphs and input.glyph_device: the style of the forced +// device or, on Auto, of the device last used (the key a keyboard binds, for the keyboard style). +// False when the game's own art should be drawn: the setting is "original", or the files are not +// there, or the style has no symbol for it. +bool Find(Button button, Image &out); + +// The same for an explicit style name ("ps4", "ps5", "xbox", "switch", "keyboard") and glyph name as +// glyphs.json spells them ("cross", "l1", "dpad", "space", "mouse1"...); for the tests and tools. +bool FindNamed(const char *style, const char *name, Image &out); + +// Forgets what was loaded (and frees the atlases), so the next Find reads glyphs/ again. +void Reset(); + +} // namespace glyphs diff --git a/port/src/platform/input.cpp b/port/src/platform/input.cpp index a64bb3faa..172187afc 100644 --- a/port/src/platform/input.cpp +++ b/port/src/platform/input.cpp @@ -158,6 +158,11 @@ struct PadSlot { Uint64 rumble_time = 0; }; +// Which device the player touched last (InputActiveGlyphFamily): false until a key or the mouse is +// used, and the make of the first gamepad once one is open. +bool g_last_keyboard = false; +InputGlyphFamily g_pad_family = InputGlyphFamily::Ps4; + std::array, kActionCount> g_bindings; bool g_bindings_ready = false; InputMouseSettings g_mouse; @@ -644,11 +649,31 @@ void InputShutdown() { } } +static InputGlyphFamily GamepadFamily(SDL_Gamepad *gamepad) { + switch (SDL_GetGamepadType(gamepad)) { + case SDL_GAMEPAD_TYPE_PS5: + return InputGlyphFamily::Ps5; + case SDL_GAMEPAD_TYPE_XBOX360: + case SDL_GAMEPAD_TYPE_XBOXONE: + return InputGlyphFamily::Xbox; + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO: + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT: + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT: + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR: + return InputGlyphFamily::Switch; + default: + return InputGlyphFamily::Ps4; + } +} + void InputPoll() { EnsureBindings(); if (g_gamepad_subsystem) { SyncGamepads(); } + if (g_slots[0].gamepad != nullptr) { + g_pad_family = GamepadFamily(g_slots[0].gamepad); + } for (int pad = 0; pad < kInputPadCount; ++pad) { InputPadState state; state.connected = pad == 0 || g_slots[pad].gamepad != nullptr; @@ -656,12 +681,20 @@ void InputPoll() { ReadGamepad(g_slots[pad].gamepad, state); } g_device[pad] = state; + if (pad == 0 && g_slots[0].gamepad != nullptr && (state.buttons != 0 || Deflected(state.left_x) || Deflected(state.left_y) || + Deflected(state.right_x) || Deflected(state.right_y))) { + g_last_keyboard = false; + } Compose(pad); } PollHostActions(); } void InputHandleEvent(const SDL_Event &event) { + if (event.type == SDL_EVENT_KEY_DOWN || event.type == SDL_EVENT_MOUSE_BUTTON_DOWN || + (event.type == SDL_EVENT_MOUSE_MOTION && (event.motion.xrel != 0.0f || event.motion.yrel != 0.0f))) { + g_last_keyboard = true; + } if (MouseHandleEvent(event)) { return; } @@ -963,6 +996,35 @@ bool InputBindKeys(std::string_view action, std::span ke return false; } +InputGlyphFamily InputActiveGlyphFamily() { + return g_last_keyboard ? InputGlyphFamily::Keyboard : g_pad_family; +} + +std::string InputPrimaryBindingName(std::string_view action) { + EnsureBindings(); + for (std::size_t i = 0; i < kActionCount; ++i) { + if (kActions[i].name != action) { + continue; + } + for (const Source &source : g_bindings[i]) { + if (source.kind == Source::Key) { + std::string name; + for (const char *c = SDL_GetScancodeName(static_cast(source.code)); *c; ++c) { + if (*c != ' ') { + name += static_cast(std::tolower(static_cast(*c))); + } + } + return name; + } + if (source.kind == Source::MouseButton) { + return "mouse" + std::to_string(source.code); + } + } + return {}; + } + return {}; +} + void InputResetBindings() { g_bindings_ready = false; EnsureBindings(); diff --git a/port/src/platform/input.hpp b/port/src/platform/input.hpp index 7a668a7ab..12053466a 100644 --- a/port/src/platform/input.hpp +++ b/port/src/platform/input.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -170,6 +171,24 @@ bool InputDeveloperMenu(); // Takes the pointer's motion and wheel since the last call, live and scripted. InputMenuMouse InputTakeMenuMouse(); +// The family of button symbols that matches the device last used: keyboard and mouse after a key, +// a mouse button or the mouse moved, otherwise the first gamepad's make (PS4 before one has been +// touched, and for a pad of unknown make). +enum class InputGlyphFamily { + Ps4, + Ps5, + Xbox, + Switch, + Keyboard, +}; + +InputGlyphFamily InputActiveGlyphFamily(); + +// The first key or mouse button bound to a pad action ("cross", "start", "up"...), named as the +// glyph table spells them: SDL's scancode name lower-cased with spaces dropped ("space", "return", +// "leftshift"), or "mouse1" to "mouse5" (left, right, middle, X1, X2). Empty if none is bound. +std::string InputPrimaryBindingName(std::string_view action); + // SDL scancode for a key name, any case; `_` stands for a space ("left_shift"), and Grave, Backquote // and Backtick name the key left of 1. -1 if unknown. int InputScancodeFromName(std::string_view name); diff --git a/port/src/snd.cpp b/port/src/snd.cpp index db4e97260..d9e8bc368 100644 --- a/port/src/snd.cpp +++ b/port/src/snd.cpp @@ -4,8 +4,14 @@ #include #include #include +#include +#include +#include +#include +#include #include "draw2d_port.hpp" +#include "glyph_draw.hpp" #include "mglib_port.hpp" #include "rect.hpp" #include "texture.hpp" @@ -54,9 +60,22 @@ void RectSprite(CTexture *texture, const CRect_i_ &screen, const CRect_i_ &texel return; } + static const bool trace = std::getenv("DC_SPRITE_TRACE") != nullptr; + if (trace) { + static std::set> seen; + if (seen.emplace(texture->name, texel.x, texel.y, texel.width, texel.height).second) { + std::fprintf(stderr, "sprite %s texel %d %d %d %d screen %d %d %d %d\n", texture->name, texel.x, texel.y, + texel.width, texel.height, screen.x, screen.y, screen.width, screen.height); + } + } + + if (GlyphReplaceSprite(texture->name, screen, texel, colour.a)) { + return; + } std::array quad = RectQuad(screen, texel, colour); draw2d::DrawTextured(gfx::Primitive::Quads, quad, texture->tex0, SpriteTex1(), draw2d::SpriteState()); draw2d::RestoreTestZbuf(); + GlyphOverlaySprite(texture->name, screen, texel, colour.a); } // XYZF2 keeps 16 bits of x and y, 24 of z and 8 of the fog coefficient. diff --git a/port/src/tests/glyphs_test.cpp b/port/src/tests/glyphs_test.cpp new file mode 100644 index 000000000..41dacbe6a --- /dev/null +++ b/port/src/tests/glyphs_test.cpp @@ -0,0 +1,123 @@ +#include + +#include +#include +#include +#include +#include + +#include + +#include "../platform/config.hpp" +#include "../platform/input.hpp" + +// The button symbols (platform/glyphs.hpp): their two settings, the key names the keyboard style looks +// glyphs up by, and the shipped glyphs.json the names must be found in. + +namespace { + +nlohmann::json ReadShippedIndex() { + std::filesystem::path file = std::filesystem::path(__FILE__).parent_path() / ".." / ".." / "glyphs" / "glyphs.json"; + std::ifstream stream(file); + return nlohmann::json::parse(stream, nullptr, false); +} + +} // namespace + +TEST(Glyphs, SettingsDefaultToNewSymbolsOnTheDeviceInUse) { + Config config = ConfigParse(""); + ASSERT_TRUE(config.glyphs_new); + ASSERT_EQ(config.glyph_device, ConfigGlyphDevice::Auto); +} + +TEST(Glyphs, SettingsParseAndRoundTrip) { + Config config = ConfigParse(R"({"input":{"glyphs":"original","glyph_device":"switch"}})"); + ASSERT_FALSE(config.glyphs_new); + ASSERT_EQ(config.glyph_device, ConfigGlyphDevice::Switch); + ASSERT_EQ(ConfigParse(ConfigSerialize(config)), config); + for (std::string_view name : {"auto", "ps4", "ps5", "xbox", "switch", "keyboard"}) { + Config c = ConfigParse(std::string(R"({"input":{"glyph_device":")") + std::string(name) + R"("}})"); + ASSERT_EQ(ConfigParse(ConfigSerialize(c)).glyph_device, c.glyph_device) << name; + } +} + +TEST(Glyphs, BadSettingsAreIgnored) { + Config config = ConfigParse(R"({"input":{"glyphs":"fancy","glyph_device":"atari"}})"); + ASSERT_TRUE(config.glyphs_new); + ASSERT_EQ(config.glyph_device, ConfigGlyphDevice::Auto); +} + +TEST(Glyphs, BindingNamesAreTheGlyphTableSpelling) { + InputResetBindings(); + ASSERT_EQ(InputPrimaryBindingName("cross"), "mouse1"); + ASSERT_EQ(InputPrimaryBindingName("circle"), "mouse2"); + ASSERT_EQ(InputPrimaryBindingName("square"), "e"); + ASSERT_EQ(InputPrimaryBindingName("triangle"), "tab"); + ASSERT_EQ(InputPrimaryBindingName("start"), "return"); + ASSERT_EQ(InputPrimaryBindingName("select"), "backspace"); + ASSERT_EQ(InputPrimaryBindingName("up"), "up"); + ASSERT_EQ(InputPrimaryBindingName("nonsense"), ""); + const std::string_view keys[] = {"Left Ctrl"}; + ASSERT_TRUE(InputBindKeys("cross", keys)); + ASSERT_EQ(InputPrimaryBindingName("cross"), "leftctrl"); + InputResetBindings(); +} + +TEST(Glyphs, ShippedIndexHasEveryPadButtonInEveryPadStyle) { + nlohmann::json index = ReadShippedIndex(); + ASSERT_TRUE(index.is_object()); + const std::set buttons = {"cross", "circle", "square", "triangle", "l1", "r1", "l2", "r2", "start", + "select", "dpad", "dpad_ud", "dpad_lr", "up", "down", "left", "right", + "l3", "r3", "lstick", "rstick"}; + for (const char *style : {"ps4", "ps5", "xbox", "switch", "keyboard"}) { + ASSERT_TRUE(index["styles"].contains(style)) << style; + const auto &glyphs = index["styles"][style]["glyphs"]; + for (const std::string &button : buttons) { + // The keyboard style shows the key bound to a button (the next test); only the directional + // symbols, which are no one key, are drawn for it. + bool pad_only = std::string_view(style) == "keyboard" && button != "dpad" && button != "dpad_ud" && + button != "dpad_lr" && button != "lstick" && button != "rstick"; + if (!pad_only) { + ASSERT_TRUE(glyphs.contains(button)) << style << " lacks " << button; + } + } + } +} + +TEST(Glyphs, ShippedKeyboardIndexHasTheDefaultBindings) { + nlohmann::json index = ReadShippedIndex(); + ASSERT_TRUE(index.is_object()); + const auto &glyphs = index["styles"]["keyboard"]["glyphs"]; + InputResetBindings(); + for (const char *action : {"cross", "circle", "square", "triangle", "l1", "r1", "l2", "r2", "l3", "r3", "start", + "select", "up", "down", "left", "right"}) { + std::string name = InputPrimaryBindingName(action); + ASSERT_FALSE(name.empty()) << action; + ASSERT_TRUE(glyphs.contains(name)) << action << " is bound to " << name << ", which has no symbol"; + } +} + +TEST(Glyphs, EveryAtlasRectIsInsideItsAtlasFile) { + nlohmann::json index = ReadShippedIndex(); + ASSERT_TRUE(index.is_object()); + for (const auto &[style, value] : index["styles"].items()) { + std::filesystem::path png = std::filesystem::path(__FILE__).parent_path() / ".." / ".." / "glyphs" / + value["atlas"].get(); + ASSERT_TRUE(std::filesystem::exists(png)) << png; + // PNG: the width and height are the two big-endian words after the signature and "IHDR". + std::ifstream stream(png, std::ios::binary); + unsigned char header[24] = {}; + stream.read(reinterpret_cast(header), sizeof(header)); + auto word = [&](int at) { + return (header[at] << 24) | (header[at + 1] << 16) | (header[at + 2] << 8) | header[at + 3]; + }; + const int width = word(16); + const int height = word(20); + for (const auto &[name, rect] : value["glyphs"].items()) { + ASSERT_GE(rect[0].get(), 0) << style << " " << name; + ASSERT_GE(rect[1].get(), 0) << style << " " << name; + ASSERT_LE(rect[0].get() + rect[2].get(), width) << style << " " << name; + ASSERT_LE(rect[1].get() + rect[3].get(), height) << style << " " << name; + } + } +} diff --git a/port/src/texture.cpp b/port/src/texture.cpp index a972f4194..79d2aeb0e 100644 --- a/port/src/texture.cpp +++ b/port/src/texture.cpp @@ -1,8 +1,11 @@ #include "texture.hpp" +#include #include #include #include +#include +#include #include "dataalloc.hpp" #include "mglib.hpp" @@ -98,6 +101,86 @@ uint32_t BlendColors(uint32_t first, uint32_t second, unsigned first_weight, uns return blended; } +// DC_TEXTURE_DUMP=