Repository navigation
Usage
SLTextureDecoder 0.x.y
Decodes Second Life / Firestorm JPEG 2000 texture caches into PNG.
Usage:
SLTextureDecoder --cache-dir <dir> [options]
Options:
--cache-dir <dir> texture cache directory to read (required)
--out-dir <dir> where the PNG files go (default: <cache-dir>/png)
--id <uuid> decode one texture instead of the whole cache
--index <n> decode only the record at this index
--limit <n> stop after n textures
--complete-only skip records whose cached codestream is truncated
--overwrite write over existing PNG files
--dry-run report what a run would write and change nothing
--keep-j2k also write the assembled codestream next to the PNG
--no-alpha write an RGB PNG instead of RGBA
-v, --verbose log every record
-h, --help show this help
-V, --version show version and linked libraries
Output:
<uuid>.png decoded from a complete codestream
<uuid>.partial.png best effort result from a truncated codestream
<uuid>.j2c assembled codestream, only with --keep-j2k
Interruption:
Every file is written to <name>.part first and renamed when it is
complete. Ctrl+C therefore stops after the record in progress, leaves
no half written PNG behind and exits with code 10.
Run the tool without any argument to print the same text (and exit with code 1,
because the invocation is incomplete). --help exits with 0.
| Invocation | Selection |
|---|---|
--cache-dir <dir> |
every record that has a body file (mBodySize > 0) |
--cache-dir <dir> --complete-only |
only the records whose codestream is complete on disk |
--limit <n> |
the first n of that selection, in cache index order |
--index <n> |
exactly the record n, whatever --limit/--complete-only say |
--id <uuid> |
the record with that texture id (looked up in texture.entries) |
-
--indexwins over--idwhen both are given. - The next run skips a texture whose PNG is already in the output directory
(that is what makes resuming an interrupted run cheap);
--overwriterewrites it. The name that is checked is the one the record would produce, i.e.<uuid>.partial.pngfor a truncated record,<uuid>.pngotherwise. - A record without a body file (
mBodySize <= 0, a free slot) is counted as failed and reported - it cannot be decoded by definition. - The output directory is created on demand, including
<cacheDir>/pngwhen--out-diris omitted. -
--dry-rundecodes nothing: it readstexture.entriesand checks the body files (name and size, no OpenJPEG) and prints the counts a real run would produce. Nothing is created, not even the output directory, and the counters in the final line are prefixed withdry run:. A predicted failure still makes the process exit with the matching code, so--dry-runcan gate a long run in a script.
-
Atomic output. Each file is written to
<name>.part(<uuid>.png.part,<uuid>.j2c.part, ...) and renamed over the target once it is complete, so a killed or interrupted process leaves a stale.partfile - which no later run mistakes for a finished PNG - instead of a truncated PNG. -
Ctrl+C. The CLI installs a console handler that only sets a flag; the run
finishes the record it is working on and stops, logging
interrupted: stopping before record Nand ending the summary line with[interrupted]. The exit code is10. The same applies toSLTextureDecoderGUI.exe --batch. -
One bad record never ends the run. Errors, and unexpected
std::exceptions such asstd::bad_alloc, are logged and counted; only problems that stop the whole run (missing cache directory, unusable output directory) abort it. -
Sanity limits. A record that announces a body larger than
kMaxBodySize(512 MiB) is refused before the file is read, the body file size is compared withmBodySizebefore it is loaded, and a codestream that announces an image larger than 8192x8192, a reversed image area or an implausible component count is rejected before OpenJPEG allocates the samples (seesrc/utils/Constants.h). The cache itself is only ever read. - Sharing violations of the viewer that has the cache open are not retried yet - a record fails and is reported (issue #6 tracks the retry and the "the cache changed while it was read" check).
<uuid>.png the whole codestream was on disk and decoded losslessly
<uuid>.partial.png only the cached prefix existed; OpenJPEG recovered a blurry
but usable image (best effort, not the original texture)
Two files for the same texture can never appear at once, so the suffix is a reliable "this output is not the real thing" marker for scripts.
The numeric exit code is the error category, so a script can react without
parsing the log (src/utils/Errors.h):
| Code | Name | Typical cause |
|---|---|---|
| 0 | Ok | every selected texture was written (best effort results count as written) |
| 1 | Usage | bad command line, missing --cache-dir, unknown option |
| 2 | CacheNotFound |
--cache-dir is not a directory, or texture.entries / texture.cache is missing |
| 3 | EntryNotFound |
--id is not in the cache, --index out of range, body file missing |
| 4 | CacheTooSmall | header block or texture.cache shorter than the record announces |
| 5 | SizeMismatch | body file size differs from mBodySize
|
| 6 | DecodeError | at least one selected texture could not be decoded even leniently, or a codestream is inconsistent |
| 7 | WriteError | output directory/file could not be created or written |
| 8 | IoError | read failed, short read, file vanished |
| 9 | InvalidFormat |
texture.entries is not a plausible cache (too small, truncated, absurd count, body size beyond the format) |
| 10 | Interrupted | the run was stopped with Ctrl+C before the end of the selection |
Exit code 0 with a non-zero written count is the normal case for a cache full
of truncated records: the tool reports them as best effort instead of failing.
10 is only used when a record boundary was reached after the interruption, so
a run that ignores Ctrl+C until the very end reports the ordinary code.
Plain lines on stdout/stderr, no colour codes, so redirecting works:
[INFO] cache D:\FS Cache\texturecache: 196700 records, encoder KDU v8.4.1
[INFO] 120 texture(s) to decode into D:\textures
[INFO] 250/54825 written
[WARN] record 123386: texture has 5 components, dropping the surplus plane
[INFO] done: 120 PNG written (83 best effort), 0 skipped, 0 failed
[WARN] interrupted: stopping before record 87
[INFO] done: 86 PNG written (30 best effort), 0 skipped, 0 failed [interrupted]
[INFO] dry run: 120 texture(s) would be written (83 best effort), 0 skipped, 0 failed
| Level | When |
|---|---|
INFO |
cache opened, selection, every 250 written PNGs, the final summary |
WARN |
a texture decoded with a caveat (5 components, lenient recovery) |
ERROR |
a record failed; the run continues |
verbose (-v) |
one line per record: index, uuid, file name, size, components, OpenJPEG diagnostics |
# the whole cache, default output <cache-dir>\png, skip what exists
SLTextureDecoder --cache-dir "$env:APPDATA\Firestorm_x64\texturecache"
# a quick smoke test: five records, each one logged
SLTextureDecoder --cache-dir "D:\FS Cache\texturecache" --out-dir D:\textures --limit 5 -v
# only losslessly recoverable textures, into a separate tree
SLTextureDecoder --cache-dir "D:\FS Cache\texturecache" --out-dir D:\textures\complete --complete-only
# one texture you know the id of
SLTextureDecoder --cache-dir "D:\FS Cache\texturecache" --id 8f7d1b1e-0a1b-4c5d-8e9f-0123456789ab
# record 123386 by index
SLTextureDecoder --cache-dir "D:\FS Cache\texturecache" --out-dir D:\one --index 123386 -v
# scripted, quiet: only failures matter
SLTextureDecoder --cache-dir "D:\FS Cache\texturecache" 2> errors.log
if ($LASTEXITCODE -ne 0) { Write-Host "see errors.log" }
# preflight: what would a run do, without writing anything?
SLTextureDecoder --cache-dir "D:\FS Cache\texturecache" --out-dir D:\textures --dry-run -vSLTextureDecoderGUI.exe accepts the same options plus:
--jobs <n> worker threads (0 = one per logical CPU)
--batch convert without opening a window
# the whole cache on four threads, unattended, log to a file
SLTextureDecoderGUI.exe --batch --jobs 4 --cache-dir "D:\FS Cache\texturecache" `
--out-dir D:\textures > run.logThe headless mode shares the record loop of the CLI, so it accepts the same
options (including --dry-run) and reacts to Ctrl+C the same way: the workers
stop between records and the process exits with code 10.
See GUI for the window and Performance for what
--jobs buys you.
MIT licensed - the licence covers the source code only, never the decoded textures. Educational and research use only; not affiliated with Linden Research, Inc. or the Phoenix Firestorm Project - see the Legal notice.
SLTextureDecoder