diff --git a/Scripts/extractMuiStrings.sh b/Scripts/extractMuiStrings.sh
new file mode 100755
index 0000000..f0cce59
--- /dev/null
+++ b/Scripts/extractMuiStrings.sh
@@ -0,0 +1,83 @@
+# Usage: extractMuiStrings.sh [muiFile] [outputCs] [namespace] [className]
+# Reads the RT_STRING (STRINGTABLE) resources out of a Win32 MUI satellite
+# resource file and generates a C# class mapping resource ID -> string.
+
+MUI_FILE=${1:-../../windows/shared/zune-x64/Zune/en-US/ZuneResources.dll.mui}
+OUT_CS=${2:-../ZuneDBApi/MicrosoftZuneLibrary/ZuneLibraryResources.g.cs}
+NAMESPACE=${3:-MicrosoftZuneLibrary}
+CLASS_NAME=${4:-ZuneLibraryResources}
+
+for cmd in wrestool od dd iconv awk; do
+ command -v "$cmd" >/dev/null 2>&1 || { echo "$cmd is required but was not found on PATH." >&2; exit 1; }
+done
+
+if [ ! -f "$MUI_FILE" ]; then
+ echo "MUI file not found: $MUI_FILE" >&2
+ exit 1
+fi
+
+echo "Extracting RT_STRING resources from $MUI_FILE..."
+
+TMP_DIR=$(mktemp -d)
+trap 'rm -rf "$TMP_DIR"' EXIT
+
+mkdir -p "$(dirname "$OUT_CS")"
+
+{
+ echo "// "
+ echo "// Generated by Scripts/extractMuiStrings.sh from $(basename "$MUI_FILE"). Do not edit by hand."
+ echo "// "
+ echo
+ echo "using System.Collections.Generic;"
+ echo
+ echo "namespace $NAMESPACE;"
+ echo
+ echo "public static class $CLASS_NAME"
+ echo "{"
+ echo " public static readonly IReadOnlyDictionary Strings = new Dictionary"
+ echo " {"
+} > "$OUT_CS"
+
+# Each RT_STRING block (resource name N) holds ids (N-1)*16 .. (N-1)*16+15,
+# each stored as a WORD char-count followed by that many UTF-16LE chars (no
+# null terminator); a zero count marks an unused slot.
+while read -r name lang; do
+ raw="$TMP_DIR/$name.bin"
+ wrestool --raw -x --type=6 --name="$name" --language="$lang" -o "$raw" "$MUI_FILE"
+ size=$(stat -c%s "$raw")
+ base_id=$(( (name - 1) * 16 ))
+ offset=0
+
+ for ((i = 0; i < 16; i++)); do
+ (( offset + 2 > size )) && break
+
+ length=$(od -An -tu2 --endian=little -j "$offset" -N 2 -- "$raw" | tr -d ' ')
+ offset=$(( offset + 2 ))
+ bytelen=$(( length * 2 ))
+
+ if (( length > 0 )); then
+ id=$(( base_id + i ))
+ escaped=$(
+ dd if="$raw" iflag=skip_bytes,count_bytes skip="$offset" count="$bytelen" 2>/dev/null \
+ | iconv -f UTF-16LE -t UTF-8 \
+ | awk 'BEGIN { RS = "\0" }
+ { gsub(/\\/, "\\\\"); gsub(/"/, "\\\""); gsub(/\r/, "\\r"); gsub(/\n/, "\\n"); gsub(/\t/, "\\t"); printf "%s", $0 }'
+ )
+ printf ' [%d] = "%s",\n' "$id" "$escaped" >> "$OUT_CS"
+ fi
+
+ offset=$(( offset + bytelen ))
+ done
+done < <(
+ wrestool -l --type=6 "$MUI_FILE" \
+ | grep -oE -- '--name=[0-9]+ --language=[0-9]+' \
+ | sed -E 's/--name=([0-9]+) --language=([0-9]+)/\1 \2/' \
+ | sort -n -k1,1
+)
+
+{
+ echo " };"
+ echo "}"
+} >> "$OUT_CS"
+
+echo "Wrote $(grep -c '] = "' "$OUT_CS") strings to $OUT_CS"
diff --git a/ZuneDBApi/CLAUDE.md b/ZuneDBApi/CLAUDE.md
index 74c3e42..5e23973 100644
--- a/ZuneDBApi/CLAUDE.md
+++ b/ZuneDBApi/CLAUDE.md
@@ -82,7 +82,7 @@ document.
**Do not make assumptions**. If you have a question about something,
follow this procedure:
-1. **Log your question.** Use `LOG.md` in the repo root to log any questions
+1. **Log your question.** Use log files to document any questions
or confusions. Follow the instructions in *Logging*.
2. **Ask the human.** They've been working on Zune longer than you have. They may
have an immediate answer for you, or at least can narrow down your search.
@@ -92,8 +92,8 @@ http://github.com/ZuneDev/Wiki
4. **Move on.** If you still haven't gotten a concrete, verifiable answer,
you may make whatever assumption is both likely and has the fewest predicates,
**if and only if you document this assumption in the code**, using `// TODO`
-comments. You are also required to log this assumption and its corresponding
-rationale in `LOG.md`.
+comments. You are also required to log this assumption and the rationale behind
+it.
### STAGE 1: Decompilation
diff --git a/ZuneShell/ZuneUI/FontLoader.cs b/ZuneShell/ZuneUI/FontLoader.cs
index 7bdb123..d25044e 100644
--- a/ZuneShell/ZuneUI/FontLoader.cs
+++ b/ZuneShell/ZuneUI/FontLoader.cs
@@ -20,11 +20,11 @@ namespace ZuneUI
public List Fonts
{
- get => this._fonts;
+ get => _fonts;
set
{
- this._fonts = value;
- this.QueueFontLoading();
+ _fonts = value;
+ QueueFontLoading();
}
}
@@ -32,31 +32,37 @@ namespace ZuneUI
{
set
{
- this._resourceDll = value;
- this.QueueFontLoading();
+ _resourceDll = value;
+ QueueFontLoading();
}
}
private void QueueFontLoading()
{
- if (this._loaded)
+ if (_loaded)
throw new InvalidOperationException("This is a one trick pony, sorry.");
- if (this._loadQueued)
+ if (_loadQueued)
return;
- Application.DeferredInvoke(new DeferredInvokeHandler(this.LoadFonts), null);
- this._loadQueued = true;
+
+ Application.DeferredInvoke(LoadFonts, null);
+
+ _loadQueued = true;
}
private void LoadFonts(object args)
{
- this._loadQueued = false;
- if (this._fonts == null || this._fonts.Count == 0)
+ _loadQueued = false;
+ if (_fonts == null || _fonts.Count == 0)
return;
- if (this._resourceDll == null)
+ if (_resourceDll == null)
throw new InvalidOperationException("Must specify a Resource to retrieve the fonts from.");
- foreach (string font in this._fonts)
- MemoryFonts.TryLoadFromResource(this._resourceDll, font);
- this._loaded = true;
+
+#if WINDOWS
+ foreach (var font in _fonts)
+ MemoryFonts.TryLoadFromResource(_resourceDll, font);
+#endif
+
+ _loaded = true;
}
}
}
diff --git a/logs/MicrosoftZuneLibrary/ZuneLibrary.md b/logs/MicrosoftZuneLibrary/ZuneLibrary.md
index 3f410e4..754627a 100644
--- a/logs/MicrosoftZuneLibrary/ZuneLibrary.md
+++ b/logs/MicrosoftZuneLibrary/ZuneLibrary.md
@@ -4,6 +4,188 @@ Append-only. Do not edit previous entries.
---
+## 2026-07-27 — Relocated: the question-log entries below
+
+**Housekeeping note:** the next two entries were originally logged in a standalone
+`LOG.md` (briefly `logs/LOG.md`), per *Dealing with unknowns and uncertainty*'s old
+wording, which named one central file for all questions. Per feedback, questions should
+be categorized the same as every other log entry — filed under the type/namespace they
+concern, same as research notes — so they're relocated here verbatim, with only their
+now-redundant `logs/MicrosoftZuneLibrary/ZuneLibrary.md` cross-references (which were
+pointing at this same file from outside it) reworded to point within this file instead.
+`CLAUDE.md`'s *Dealing with unknowns* section has been generalized accordingly (no longer
+names one specific log file).
+
+---
+
+## 2026-07-27 — RESOLVED: the question below
+
+Answered by inspecting the on-disk resource DLLs directly with `wrestool` (icoutils)
+instead of chasing the native function further in Ghidra: the strings live in
+`windows/shared/zune-x64/Zune/en-US/ZuneResources.dll.mui`, a real Win32 MUI satellite
+resource module with 302 `RT_STRING` blocks in the standard `STRINGTABLE` binary format.
+Full writeup: the "Resolved" entry below. `ZuneDBApi.dll`'s own `GetLocResourceInstance()`
+native internals (exactly which `LoadLibraryEx` call loads it) remain unconfirmed but are
+no longer the open question — that was module identity and string format, both now known.
+
+---
+
+## 2026-07-27 — `ZuneLibrary.LoadStringFromResource`: what does `GetLocResourceInstance()` return?
+
+**Context:** researching `MicrosoftZuneLibrary.ZuneLibrary.LoadStringFromResource` (full
+research notes: the "Researched" entry below). The managed method is fully understood —
+it's a thin wrapper around Win32 `LoadStringW` — but it depends on a native-only helper,
+`ZuneLibraryExports.GetLocResourceInstance()`, which returns the `HINSTANCE` of whatever
+module holds the localized string table.
+
+**Question:** which physical module does `GetLocResourceInstance()` actually resolve to at
+runtime — `ZuneDBApi.dll` itself, or a separate satellite resource DLL (e.g.
+`ZuneResources.dll` or `ZuneMarketplaceResources.dll`, both present in
+`windows/shared/zune-x64/Zune/` alongside `ZuneDBApi.dll`)? `ZuneDBApi.dll`'s own `.rsrc`
+section is only ~2 KB (too small for a real string table), which points toward a separate
+module, but this is circumstantial — the native function itself (VA `0x1800148c2` in
+`ZuneDBApi.dll`, per Ghidra) isn't disassembling to recognizable code yet (no function
+defined there, raw bytes look like data), so it hasn't been confirmed by disassembly.
+
+**Status:** unresolved. Not blocking — `ZuneLibrary.LoadStringFromResource` stays a stub
+(`ZuneUI` code doesn't yet depend on real localized strings), so no `// TODO` was needed in
+code for this pass. Logged here per *Dealing with unknowns and uncertainty* in case the human
+already knows which module holds Zune's loc resource strings, which would save a Ghidra
+deep-dive.
+
+---
+
+## 2026-07-27 — Resolved: where `LoadStringFromResource`'s strings actually live
+
+**Follow-up to the entry below**, prompted by a direct question ("where do the strings come
+from, what format are they stored in?"). The `GetLocResourceInstance()` gap noted below is
+now settled — **not** by further Ghidra work on the native function, but by directly
+inspecting the candidate satellite resource DLLs on disk with `wrestool` (icoutils).
+
+**Source module confirmed:** `windows/shared/zune-x64/Zune/en-US/ZuneResources.dll.mui`.
+`wrestool -l` on it shows **302 `RT_STRING` (`--type=6`) resource blocks**, plus a
+single `RT_MUI` (`--type='MUI'`) resource — this is a genuine Win32 MUI (Multilingual User
+Interface) satellite resource-only module, the standard mechanism where a language-neutral
+binary (here `ZuneResources.dll` itself, sitting directly in `Zune/`) has its actual
+resources split out into a same-named `.mui` file under a BCP-47 locale subfolder
+(`en-US\`). This lines up exactly with the earlier finding that `ZuneDBApi.dll`'s own
+`.rsrc` is too small and that it imports `LoadLibraryW`/`GetProcAddress` — it's loading
+*this* file (almost certainly via `LoadLibraryExW` with `LOAD_LIBRARY_AS_DATAFILE`, the
+standard way to open a MUI purely for resource access, though the exact call wasn't
+re-confirmed in Ghidra this pass). `ZuneResources.dll` (the LN/language-neutral binary
+next to it) itself carries no `RT_STRING` resources — its own `wrestool -l` shows only
+`BINARY`/icon/version/`MUI` entries, no `--type=6` — confirming the neutral binary is a
+stub and the `.mui` is where the real string table lives, exactly as MUI architecture
+predicts. `ZuneMarketplaceResources.dll` was a red herring — it holds only `RCDATA`
+(compiled `.uix` markup), no strings.
+
+**Format confirmed byte-for-byte** by extracting block 1 (`wrestool --raw -x --type=6
+--name=1 --language=1033`) and reading the raw bytes: this is the plain, well-documented
+Win32 `STRINGTABLE` binary layout —
+- Each numbered resource block (`--name=N`) holds up to 16 consecutive string IDs:
+ `blockNumber = (stringId >> 4) + 1`, `indexInBlock = stringId & 0xF`.
+- Within a block: back-to-back records, each `WORD charCount` immediately followed by
+ `charCount` **UTF-16LE code units with no null terminator** (an unused slot is just a
+ `WORD` value of `0`).
+- Verified two real entries against the extracted bytes: string id 1 (block 1, index 1) is
+ `0018` (=24) followed by 24 UTF-16LE chars spelling `"Starts the Zune software"`; string
+ id 4 is `002C` (=44) followed by 44 chars spelling
+ `"http://go.microsoft.com/fwlink/?LinkID=97902"`. Both char counts match their strings
+ exactly — format is not in doubt.
+
+This is exactly what `LoadStringW(hInstance, dwResourceNumber, (LPWSTR)&buf, 0)` (see entry
+below) is built to read: the OS/CRT loader locates the `(dwResourceNumber >> 4) + 1`-th
+`RT_STRING` block inside whatever module `hInstance` refers to, walks the length-prefixed
+records to the `dwResourceNumber & 0xF`-th slot, and — because `cchBufferMax` is `0` — hands
+back a pointer straight at that slot's inline character data plus its `WORD` length, which
+is exactly what `ZuneLibrary.LoadStringFromResource` then wraps in
+`Marshal.PtrToStringUni`.
+
+**Still open, lower priority:** the exact `LoadLibraryEx`/flags call inside
+`GetLocResourceInstance()` and how it locates the `en-US` subfolder at runtime (fixed
+relative path vs. `GetThreadPreferredUILanguages`-style lookup) — not re-investigated this
+pass since the module/format question (the thing actually asked) is now fully answered.
+Marking the `LOG.md` question resolved.
+
+---
+
+## 2026-07-27 — Researched `ZuneLibrary.LoadStringFromResource`
+
+**Trigger:** explicit request to research how `MicrosoftZuneLibrary.ZuneLibrary.LoadStringFromResource`
+works. Current stub (`ZuneLibrary.cs:33`): `public static string LoadStringFromResource(uint dwResourceNumber) => "todo";`.
+
+**Source:** `mcp__ilspy__decompile_method` on `MicrosoftZuneLibrary.ZuneLibrary.LoadStringFromResource`
+in the real `windows/shared/zune-x64/Zune/ZuneDBApi.dll` (this method was apparently never
+actually decompiled/inspected during the original bulk pass below — it was stubbed sight-unseen
+along with everything else in the file).
+
+**Finding: `ZuneDBApi.dll` is mixed-mode (C++/CLI), and this specific method is not pure IL.**
+Its decompiled body:
+
+```csharp
+public unsafe static string LoadStringFromResource(uint dwResourceNumber)
+{
+ HINSTANCE__* ptr = global::.ZuneLibraryExports.GetLocResourceInstance();
+ if (ptr == null) return null;
+ object result = null;
+ IntPtr intPtr = default(IntPtr);
+ int len = global::.LoadStringW(ptr, dwResourceNumber, (ushort*)(&intPtr), 0);
+ if (intPtr != IntPtr.Zero)
+ result = Marshal.PtrToStringUni(intPtr, len);
+ return (string)result;
+}
+```
+
+Algorithm:
+1. Calls the module-internal native export `ZuneLibraryExports.GetLocResourceInstance()` to get
+ an `HINSTANCE` for a localized-resource module. If that's null (resource module not loaded),
+ returns `null`.
+2. Calls the real Win32 `User32`/`Kernel32` `LoadStringW(hInstance, dwResourceNumber, (LPWSTR)&buf, 0)`
+ — the classic **zero-`cchBufferMax` idiom**: when the buffer-size argument is `0`,
+ `LoadStringW` treats the out-param as `LPWSTR*` and writes back a pointer *directly into the
+ module's own resource section* (no allocation, no copy) instead of copying into a
+ caller-supplied buffer. The return value is the string's character length (no null terminator).
+3. If that pointer is non-null, `Marshal.PtrToStringUni(ptr, len)` copies exactly `len` UTF-16
+ characters starting at that native pointer into a new managed `string`.
+4. Returns the result, or `null` on any failure path (module not loaded, or resource ID not found —
+ `LoadStringW` returns `len == 0` and a null/garbage buffer pointer in that case too).
+
+So this is a genuine (small) resource-string lookup, not something with real "business logic" of
+its own beyond marshaling.
+
+**Unresolved — where `GetLocResourceInstance()`'s `HINSTANCE` actually points:** followed the
+export into Ghidra (`ZuneDBApi.dll` already open in the shared instance). The exported thunk
+(`ZuneLibraryExports.GetLocResourceInstance @ 180111b98`) is a single tail-jump,
+`jmp qword ptr [0x180001518]`, through a local function-pointer slot — i.e. the real
+implementation is a separate internal function, not an imported one (confirmed absent from
+`mcp__ghidra__list_imports`). The slot's stored value resolves to VA `0x1800148c2`, which falls
+inside `.text` (`180001000`–`1801155ff` per `list_segments`), but Ghidra has no function defined
+there, and neither `decompile_function` nor `disassemble_bytes` recognize valid instructions
+starting exactly at that address — the raw bytes read there (`inspect_memory_content`) look like a
+data table, not code. Did not push further (would need manual function creation/re-analysis in
+Ghidra, out of scope for a research-and-log pass) — logging as a genuine gap per
+*Dealing with unknowns* rather than guessing at the internals.
+
+**Weak supporting evidence for "separate satellite resource DLL" (not confirmed):**
+`ZuneDBApi.dll`'s own `.rsrc` section (`180136000`–`1801367ff`, ~2 KB) is far too small to hold a
+real `RT_STRING` table with many localized entries, and the module imports `LoadLibraryW`/
+`GetProcAddress` (per `list_imports`). This is consistent with `GetLocResourceInstance()` lazily
+loading and caching an `HINSTANCE` for a companion resource-only module — e.g. one of
+`ZuneResources.dll` / `ZuneMarketplaceResources.dll`, both present alongside `ZuneDBApi.dll` in
+`windows/shared/zune-x64/Zune/` — rather than `ZuneDBApi.dll`'s own module handle. This is
+circumstantial, not verified by disassembly.
+
+**Searched `github.com/ZuneDev/Wiki`** (via `gh search code` scoped to that repo) for
+`LoadStringFromResource` and `GetLocResourceInstance` — no hits, nothing documented there yet.
+
+**Stub status flagged, not changed this pass:** the current `=> "todo"` body doesn't match this
+file's own header convention (every other no-op stub returns the same default the original
+would produce when the native dependency is unavailable — here that's `null`, matching this
+method's own `ptr == null` early-return path). Left as-is since this pass was research/logging
+only per the request; worth fixing to `=> null` in a follow-up implementation pass.
+
+---
+
## 2026-07-21 — Bulk reconstruction of the MicrosoftZuneLibrary namespace
**Trigger:** continuation of "decompile the missing types in ZuneShell.csproj" after