Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
|---|---|
| User setup | `README.md` |
| Configuration contract | `docs/configuration.md` |
| Runtime design, build, testing, release | `docs/development.md` |
| Runtime design, build, testing | `docs/development.md` |
| Release process and packaging | `docs/release.md` |
| Change history | `CHANGELOG.md` |
| CI | `.github/workflows/ci.yml` |
| Published releases | `.github/workflows/release.yml` |
52 changes: 30 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

A configurable **BepInEx 5 plugin for Unity Mono games using TextMeshPro**. Add missing Vietnamese glyphs or use a consistent replacement font for selected font groups.

This plugin does not translate text. It can work alongside XUnity.AutoTranslator or an existing Vietnamese translation.
This plugin does not translate text. It can work alongside [XUnity.AutoTranslator](https://github.com/bbepis/XUnity.AutoTranslator) or an existing Vietnamese translation.

## Compatibility

Expand All @@ -24,44 +24,44 @@ This plugin does not translate text. It can work alongside XUnity.AutoTranslator
## Installation

1. Install [BepInEx 5 for Unity Mono](https://docs.bepinex.dev/articles/user_guide/installation/index.html).
2. Extract the release archive into `<Game>/BepInEx/`. The plugin DLL should be at:
2. Download the ZIP from the [latest release](https://github.com/zeldrisho/UnityVietnameseFontSync/releases/latest) and extract it into `<Game>/BepInEx/`. The plugin DLL should be at:
```text
<Game>/BepInEx/plugins/zeldrisho.UnityVietnameseFontSync/UnityVietnameseFontSync.dll
```
3. Run the game once with the plugin installed to generate:
```text
<Game>/BepInEx/config/zeldrisho.UnityVietnameseFontSync.cfg
```
4. Close the game, configure a font file and matching rules, then start it again.

There is no need for a separate BepInEx-only run before installing this plugin. If you previously used a UnityExplorer font script, restart with that script disabled and do not run it alongside the plugin.
4. Close the game and follow the setup steps below to discover font names and configure a matching rule.

The initial configuration makes **no font changes**. No game assemblies or proprietary font files are bundled.

## Worked configuration example
## Configure and verify

### 1. Discover the font asset name

With the game closed, set `Diagnostics = true` under `[General]` in the generated config. Leave `Rules.Order` empty while discovering names. Start the game, visit the menus or screens you want to change, then inspect `Discovered TMP font:` entries in `BepInEx/LogOutput.log`. Match the TMP font asset name, not the text component name.

### 2. Add an exact Fallback rule

Close the game before editing. Create this directory and put a licensed Vietnamese-capable font file in it; fonts are not included in the release:

The generated config starts with no matching rules, so it makes no font changes. Close the game before editing the file, then restart it. Enable diagnostics to find the TMP font asset name in `BepInEx/LogOutput.log`; match the asset name, not the text component name.
```text
<Game>/BepInEx/plugins/zeldrisho.UnityVietnameseFontSync/Fonts/
```

This complete example synchronizes one fictional asset named `MainMenu SDF`, using a primary font plus a secondary font for missing characters. Replace the filenames and exact asset name with values available in your installation. Put both licensed font files in the plugin's `Fonts/` directory. **Synchronize replaces the matched font's characters in memory**; do not broaden the match to icon or unrelated fonts.
Use the following example, replacing `MyVietnameseFont.ttf` and the fictional asset name `MainMenu SDF` with your filename and discovered name. Other settings can retain their generated defaults.

```ini
[General]
Enabled = true
Diagnostics = true
ScanInterval = 2
RetryInterval = 30

[Fonts]
Profiles = menu
CustomCharacters =

[Font.menu]
File = MyVietnameseFont.ttf
FallbackFiles = CoverageFont.ttf
SamplingSize = 36
Padding = 4
AtlasSize = 2048
MultiAtlas = true

[Rules]
Order = menu-text
Expand All @@ -71,16 +71,20 @@ Exclude = Icons, Symbols
Profile = menu
Match = Exact
Pattern = MainMenu SDF
Mode = Synchronize
Mode = Fallback
```

`FallbackFiles` supplies characters missing from the primary file, while keeping its style for available characters. Coverage must be complete across the ordered files; otherwise the profile is disabled. Leave this setting empty when the primary font covers the whole preset.
Fallback retains original glyphs and adds missing-character coverage. To replace incorrect existing glyphs or make accented and unaccented letters use the same replacement style, change only `Mode = Fallback` to `Mode = Synchronize`.

**Synchronize clears the matched font's whole character table in memory.** Use narrow rules and check symbols, untranslated text, and layout. Both modes preserve existing fallback chains and leave unmatched fonts unchanged. Matching outlines, shadows, spacing, and line breaks is not guaranteed. Restart without the plugin to restore the game's original assets.

### 3. Verify and turn off diagnostics

Keep `Synchronize` when replacing incorrect existing glyphs or making accented and unaccented letters use the same replacement style. Use `Mode = Fallback` instead if you only need missing-character coverage: it retains the original glyphs and cannot replace incorrect ones. Both modes preserve existing fallback chains and leave unmatched fonts unchanged. Restart without the plugin to restore the game's original assets.
Restart the game. Check the log for a loaded profile and an `Applied` rule, then visually check the relevant screens. Successful log messages do not guarantee correct rendering or layout.

See [configuration](docs/configuration.md) for multiple profiles, matching modes, exclusions, and atlas settings.
Once verified, close the game, set `Diagnostics = false`, and restart. This stops discovered-font-name logging. With at least one ready profile, scanning and font changes continue; otherwise, game-font enumeration is skipped while diagnostics are off. Normal status messages, warnings, and errors remain enabled; there is no separate plugin debug setting, and you do not need to disable BepInEx logging globally. Re-enable diagnostics when troubleshooting or discovering more fonts.

**Synchronization clears the matched font's character table in memory.** Use narrow rules and check symbols, untranslated text, and layout. Restart to recover. Matching outlines, shadows, spacing, and line breaks is not guaranteed.
See [configuration](docs/configuration.md) for secondary font files, multiple profiles, matching modes, exclusions, and atlas settings.

## With XUnity.AutoTranslator

Expand Down Expand Up @@ -112,6 +116,10 @@ Keep translation enabled. This plugin neither edits XUnity configuration nor rew

To disable, set `Enabled = false` and restart. To uninstall, close the game and remove the plugin DLL.

## Development

See the [development guide](docs/development.md) for setup, build/test commands, runtime design, and validation limits, and the [release guide](docs/release.md) for packaging and publication.

## License

[MIT](LICENSE). The license covers this plugin and its documentation, not third-party fonts or game assemblies. Do not redistribute proprietary system fonts or game assets.
[MIT](LICENSE).
17 changes: 15 additions & 2 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ File: `BepInEx/config/zeldrisho.UnityVietnameseFontSync.cfg`. Edit while the gam
| Rules | Order | empty | Comma-separated rule IDs in first-match order |
| Rules | Exclude | empty | Case-insensitive asset-name fragments excluded before all rules |

Enable `Diagnostics` during setup or troubleshooting to discover asset names, then set it to `false` and restart after verifying the relevant screens. This stops discovered-font-name logging. With at least one ready profile, scanning and font changes continue; otherwise, game-font enumeration is skipped while diagnostics are off. Normal status messages, warnings, and errors remain enabled. There is no separate plugin debug setting; leave BepInEx logging available for troubleshooting.

IDs contain letters, digits, underscores, or hyphens. Lists trim whitespace and remove exact duplicates. IDs/profile references are case-sensitive; asset matching is case-insensitive. Unlisted sections are inactive.

The preset covers printable ASCII and uppercase/lowercase precomposed Vietnamese. Add curly quotes, ellipsis, combining marks, or other required characters explicitly. Game text is not normalized or rewritten. Invalid surrogate sequences are rejected.
Expand All @@ -34,9 +36,20 @@ Relative paths search `<plugin DLL directory>/Fonts/` first, then the OS Fonts d

Profiles must successfully add the entire preset plus custom characters across `File` and `FallbackFiles` combined. Each fallback file is preloaded with only the characters still missing from earlier files. Later files are not loaded once coverage is complete. All generated files use the profile's atlas settings; the primary asset links to the generated fallback assets in order. No links back to game fonts are added.

Example: `File = PrimaryFont.ttf` with `FallbackFiles = CoverageFont.ttf` keeps the primary style wherever available and uses the second font for missing characters. A character supplied by another font may look different. This does not guarantee combining-mark shaping.
For a profile needing secondary-file coverage:

```ini
[Fonts]
Profiles = menu

[Font.menu]
File = PrimaryFont.ttf
FallbackFiles = CoverageFont.ttf
```

Put both licensed files in the plugin's `Fonts/` directory, or use valid absolute paths. This keeps the primary style wherever available and uses the second font for missing characters. A character supplied by another font may look different. Leave `FallbackFiles` empty when the primary file covers the whole preset. This does not guarantee combining-mark shaping; matching rules are still required to apply the profile.

If coverage remains incomplete, a needed file is missing, or TMP cannot report which characters failed, the entire profile is disabled and mapped game fonts are left alone. Missing Unicode values are logged where TMP supplies them. Files are loaded once at startup; restart to retry after fixing a file.
If coverage remains incomplete, a needed file is missing, or TMP cannot report which characters failed, the entire profile is disabled and mapped game fonts are left alone. Missing Unicode values are logged where TMP supplies them. Files are loaded once when TMP becomes available; restart to retry after fixing a file.

## Rules: `[Rule.<ID>]`

Expand Down
42 changes: 8 additions & 34 deletions docs/development.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Development

For CI, packaging, and publication instructions, see the [release guide](release.md).

## Toolchain and layout

- Stable .NET 10 SDK, Git, and Python 3 for release packaging/tests.
Expand Down Expand Up @@ -31,9 +33,9 @@ python3 -m unittest discover -s scripts -p 'test_*.py'

To apply formatting, omit `--verify-no-changes`. Output: `src/bin/Release/netstandard2.1/UnityVietnameseFontSync.dll`. Deploy only that DLL, manually, to the game's plugin directory. Build/test commands never deploy.

The project originated from official [BepInEx.Templates](https://github.com/BepInEx/BepInEx.Templates). Stable 1.4.0 generation failed under SDK 10.0.112; documented template 2.0.0-be.4 worked. That scaffolding exception does not require a bleeding-edge runtime. Restore/build does not require templates to remain installed.
Dependencies use exact versions plus lock files. UnityEngine.Modules 2022.3.0 is an intentional compile-only compatibility baseline, not a required game version; blindly selecting the newest Unity package could introduce newer runtime API dependencies. No Unity/TMP/game DLL ships with the plugin.

Dependencies use exact versions plus lock files. BepInEx packages use the latest stable versions available in the configured feed when checked. UnityEngine.Modules 2022.3.0 is an intentional compile-only compatibility baseline, not a required game version; blindly selecting the newest Unity package could introduce newer runtime API dependencies. No Unity/TMP/game DLL ships with the plugin. `Version`, display name, and GUID live in `src/UnityVietnameseFontSync.csproj`; template tooling generates constants for the plugin attribute. The initial stable version is `1.0.0`, matching the .NET SDK default and using three-part SemVer. The SDK derives the assembly/file versions (`1.0.0.0` initially); no separate version constants are maintained.
Edit `Version`, display name, and GUID in `src/UnityVietnameseFontSync.csproj`, not the generated plugin constants. Use three-part SemVer; the SDK derives assembly/file versions. Restore/build does not require BepInEx templates to be installed.

## Runtime design

Expand Down Expand Up @@ -67,6 +69,10 @@ The working starting point was C# run inside UnityExplorer, not an installed ver

A texture PNG alone is not a portable TMP font asset: glyph mappings, metrics, and other font data are also required. Runtime-generated fonts are not exported by this plugin. Restarting clears manual in-memory changes; disable startup scripts before testing the plugin independently.

### Plugin log evidence

A local Unity Mono plugin run logged successful preflight for two profiles, including secondary-file coverage for a character missing from the primary file, followed by successful Synchronize application to four matching game font assets. Discovery continued beyond startup. This confirms profile creation and rule application for that run, not visual correctness, broad runtime compatibility, or performance. Keep local game mappings, paths, and font files out of committed examples and runtime configuration.

### Automated and in-game checks

Unit tests exercise configuration, matching, paths, Unicode scalars, adapter capability checks, optional parameters, and failed glyph-preflight reporting. Packaging tests check version/tag validation, changelog requirements, the exact four-file layout, documentation links, deterministic archive contents, and rejection of XML DTDs/entities before output is created. These are not native rendering tests.
Expand All @@ -88,35 +94,3 @@ Before claiming runtime support, manually test:
- Startup, scenes, async loads, inactive text activation, retries, and long-session atlas growth.
- XUnity coexistence with its competing TMP font settings empty.
- Discovery/refresh timings and memory in multiple games. No rendering/performance claims are verified yet.

## CI and release

Workflows use minimal permissions, timeouts, concurrency controls, and action SHA pins with stable version comments. Review dependency updates manually and retain lock files. Review the Unity compatibility baseline rather than blindly accepting engine upgrades.

CI restores in locked mode, checks formatting, builds, runs tests, checks packaging logic, and uploads only the plugin DLL. Both CI and release packaging install the pinned, hash-verified dependency from `scripts/requirements.txt`. The packager uses `defusedxml` to reject DTDs, entities, and external references when reading project metadata; this build-time dependency is not included in the plugin archive. It does not need Steam or private reference assemblies.

To release:

1. Finish manual validation and review the MIT license and any third-party redistribution rights.
2. Set the project `Version`; move relevant user-facing `Unreleased` changes to `## [X.Y.Z] - YYYY-MM-DD` in `CHANGELOG.md` and retain `Unreleased` above it. Keep developer instructions, CI changes, and test implementation details out of the changelog.
3. Validate packaging locally, commit, and push a matching stable `vX.Y.Z` tag only when ready to publish.
4. The release workflow reruns CI, validates tag/version/changelog consistency, creates the allowlisted ZIP, and publishes a normal GitHub release automatically. **Tagging is the publication trigger; there is no draft/manual-publish step.**

Local packaging after adding the dated release entry:

```bash
python3 scripts/package.py --tag vX.Y.Z --dll src/bin/Release/netstandard2.1/UnityVietnameseFontSync.dll
```

The initial archive is `UnityVietnameseFontSync-1.0.0.zip`; future archives retain the `UnityVietnameseFontSync-X.Y.Z.zip` pattern. It contains exactly:

```text
plugins/zeldrisho.UnityVietnameseFontSync/UnityVietnameseFontSync.dll
plugins/zeldrisho.UnityVietnameseFontSync/README.md
plugins/zeldrisho.UnityVietnameseFontSync/CHANGELOG.md
plugins/zeldrisho.UnityVietnameseFontSync/LICENSE
```

Extract into `<Game>/BepInEx/`, not the game root. The install directory is derived from the project GUID; the DLL name remains the assembly name. Guides stay in the repository. During GitHub packaging, README guide links point to that repository's release tag; local packages without GitHub metadata use plain-text documentation references instead of broken local links.

The ZIP is the only attached release asset. The generated `notes.md` becomes the GitHub release body, not an archive entry. Separate checksum files are optional and omitted for this small distribution. No fonts, game/BepInEx/Unity DLLs, generated config, debug symbols, or internal planning notes are included. Users supply fonts and generate per-game configuration on first launch. Comparison links can be added once the repository remote and first release exist.
Loading
Loading