Per-player resource pack filtering and merged translations #94

Merged
bot merged 1 commit from feat/split-res-packs into main 2026-10-01 20:12:03 +00:00

Summary

Per-player resource pack list, plus merged translations across packs. bds-manager writes Resources.Packs from packs.json (index 0 = highest priority). This PR does not change session or protocol handling beyond ListenConfig.

Config

[Resources]
PacksRequired = true
URLResources = []      # legacy, unfiltered, appended after Packs
PathResources = []     # legacy, unfiltered, appended after Packs

[[Resources.Packs]]
URL = "https://git.pokebedrock.com/.../pokebedrock-res-texts-en_US.mcpack"
Path = ""              # exactly one of URL / Path
MinMemoryTier = 0      # 0 = no requirement
Language = ""          # "" = everyone

URL loads through readURLPack (empty DownloadURL, RakNet chunks). Path uses resource.ReadPath. A failed pack is logged and skipped. Legacy URLResources / PathResources stay unfiltered and are loaded after Packs.

Filter

A pack is sent when both are true:

  • MinMemoryTier == 0 or clientData.MemoryTier >= MinMemoryTier
  • Language == "" or normalized clientData.LanguageCode equals normalized Language

Normalize = lowercase, - replaced with _. FetchResourcePacks is set on the shared listen config, so both the RakNet and NetherNet listeners filter. Relative order of packs that match is preserved through the filter.

Ordering

gophertunnel v1.62.0 copies conn.resourcePacks in slice order into ResourcePackStack.TexturePacks and does not reverse it:

  • minecraft/conn.go:1117 (for _, pack := range conn.resourcePacks)
  • minecraft/protocol/packet/resource_pack_stack.go:16 ("The first in the list will be applied first.")

First-applied is lowest priority. Resources.Packs is the opposite (index 0 highest, same as packs.json), so that prefix is reversed before it is returned from FetchResourcePacks. 3d (before core in Packs) therefore lands after core on the wire and overrides it. Legacy URL/path entries keep today's load order and are placed before Packs, so a config with only URLResources/PathResources is unchanged and a monolith stays below the split packs.

Translations are set up in that same lowest-first order. SetTranslationFor merges keys; a later pack wins per key, so Packs[0] overwrites. A pack with no texts/ (languages.json missing) makes Setup return nil instead of an error.

Tests

  • TestFilterPackOffers / TestPackMatchesEdges: memory-tier edges (0, below, equal, above), language case and -/_, AND of both filters, unfiltered packs (including legacy) always sent, filter order preserved.
  • TestStackOrder: Packs[0] last on the wire, 3d after core, legacy order unchanged.
  • TestPackConfigTOML: contract tags.
  • TestSetupMergesOverlappingKeys: two packs, overlapping keys, later wins.
  • TestSetupSkipsPackWithoutTexts.
  • Existing TestReadURLPackHasNoDownloadURL still passes.

go test ./... and go vet ./... clean.

## Summary Per-player resource pack list, plus merged translations across packs. bds-manager writes `Resources.Packs` from `packs.json` (index 0 = highest priority). This PR does not change session or protocol handling beyond `ListenConfig`. ## Config ```toml [Resources] PacksRequired = true URLResources = [] # legacy, unfiltered, appended after Packs PathResources = [] # legacy, unfiltered, appended after Packs [[Resources.Packs]] URL = "https://git.pokebedrock.com/.../pokebedrock-res-texts-en_US.mcpack" Path = "" # exactly one of URL / Path MinMemoryTier = 0 # 0 = no requirement Language = "" # "" = everyone ``` `URL` loads through `readURLPack` (empty DownloadURL, RakNet chunks). `Path` uses `resource.ReadPath`. A failed pack is logged and skipped. Legacy `URLResources` / `PathResources` stay unfiltered and are loaded after `Packs`. ## Filter A pack is sent when both are true: - `MinMemoryTier == 0` or `clientData.MemoryTier >= MinMemoryTier` - `Language == ""` or normalized `clientData.LanguageCode` equals normalized `Language` Normalize = lowercase, `-` replaced with `_`. `FetchResourcePacks` is set on the shared listen config, so both the RakNet and NetherNet listeners filter. Relative order of packs that match is preserved through the filter. ## Ordering gophertunnel v1.62.0 copies `conn.resourcePacks` in slice order into `ResourcePackStack.TexturePacks` and does not reverse it: - `minecraft/conn.go:1117` (`for _, pack := range conn.resourcePacks`) - `minecraft/protocol/packet/resource_pack_stack.go:16` ("The first in the list will be applied first.") First-applied is lowest priority. `Resources.Packs` is the opposite (index 0 highest, same as `packs.json`), so that prefix is reversed before it is returned from `FetchResourcePacks`. `3d` (before `core` in `Packs`) therefore lands after `core` on the wire and overrides it. Legacy URL/path entries keep today's load order and are placed before `Packs`, so a config with only `URLResources`/`PathResources` is unchanged and a monolith stays below the split packs. Translations are set up in that same lowest-first order. `SetTranslationFor` merges keys; a later pack wins per key, so `Packs[0]` overwrites. A pack with no `texts/` (`languages.json` missing) makes `Setup` return nil instead of an error. ## Tests - `TestFilterPackOffers` / `TestPackMatchesEdges`: memory-tier edges (0, below, equal, above), language case and `-`/`_`, AND of both filters, unfiltered packs (including legacy) always sent, filter order preserved. - `TestStackOrder`: `Packs[0]` last on the wire, `3d` after `core`, legacy order unchanged. - `TestPackConfigTOML`: contract tags. - `TestSetupMergesOverlappingKeys`: two packs, overlapping keys, later wins. - `TestSetupSkipsPackWithoutTexts`. - Existing `TestReadURLPackHasNoDownloadURL` still passes. `go test ./...` and `go vet ./...` clean.
Filter resource packs per player and merge translation keys.
Some checks failed
golangci-lint / Lint Dockerfile. (pull_request) Successful in 11s
golangci-lint / lint (pull_request) Failing after 58s
12ed85b6df
gophertunnel applies the pack slice first-to-last, so Packs[0] is reversed onto the top of the stack. Legacy URL and path packs stay unfiltered.

Co-authored-by: Cursor <cursoragent@cursor.com>
bot merged commit 23d6fc4b8b into main 2026-10-01 20:12:03 +00:00
Sign in to join this conversation.
No description provided.