Wolfenstein Modloader Buy me a coffeeKaffee spendieren

Wiki / The New OrderThe New Order

Virtual Textures — the .pages Format of Wolfenstein: The New Order

Partly solvedTeilweise gelöst Original page in the GitHub wikiOriginalseite im GitHub-Wiki

In plain wordsIn einfachen Worten

Wolfenstein: The New Order keeps its large world and material textures in 39 '.pages' files (about 35.6 GB). This page maps the file structure completely: a header, then a tree of texture tiles at different detail levels, and every byte of seven fully checked files is accounted for. The actual pixels inside each tile are stored in a shortened JPEG XR variant that no standard decoder can read yet. So modders can now locate and extract tiles, but cannot yet view or replace the pixels.

  • All 39 .pages files pass the header check; the tile tree is a complete quadtree with one tile at the top and more detail levels below.
  • Seven files were walked completely and every byte was explained, with about 120,000 parent/child links checked without error.
  • Every retail tile uses JPEG XR compression, split into exactly four blocks. That these are diffuse, normal, specular and power maps is a well-supported guess, not proven.
  • The pixel data is not decoded: the block headers lack the normal JPEG XR signature and image size, so a standard decoder cannot be used.
  • About 100 vt_ console settings were found, including ones to change the texture file path or create .pages files; whether they work in the retail game is not verified.

Wolfenstein: The New Order speichert seine großen Welt- und Materialtexturen in 39 „.pages“-Dateien (etwa 35,6 GB). Diese Seite beschreibt den Dateiaufbau vollständig: einen Kopf, dann einen Baum aus Texturkacheln in verschiedenen Detailstufen, und in sieben komplett geprüften Dateien ist jedes Byte erklärt. Die eigentlichen Pixel jeder Kachel liegen in einer gekürzten JPEG-XR-Variante, die noch kein Standard-Decoder lesen kann. Modder können Kacheln also finden und herausziehen, aber die Pixel noch nicht ansehen oder ersetzen.

  • Alle 39 .pages-Dateien bestehen die Kopfprüfung; der Kachelbaum ist ein vollständiger Quadtree mit einer Kachel oben und immer feineren Stufen darunter.
  • Sieben Dateien wurden komplett durchlaufen und jedes Byte erklärt; etwa 120.000 Eltern-Kind-Verbindungen wurden ohne Fehler geprüft.
  • Jede Kachel im Verkaufsspiel nutzt JPEG-XR-Kompression, aufgeteilt in genau vier Blöcke. Dass diese Diffuse-, Normal-, Specular- und Power-Maps sind, ist eine gut gestützte Vermutung, aber nicht bewiesen.
  • Die Pixeldaten sind nicht dekodiert: Den Blockköpfen fehlen die übliche JPEG-XR-Signatur und die Bildgröße, deshalb funktioniert kein Standard-Decoder.
  • Rund 100 vt_-Konsoleneinstellungen wurden gefunden, darunter solche zum Ändern des Texturpfads oder zum Erzeugen von .pages-Dateien; ob sie im Verkaufsspiel funktionieren, ist nicht geprüft.

Technical detailsTechnische Details

Der technische Teil ist auf Englisch – so, wie er im Wiki steht. Unterstrichene Begriffe haben eine Erklärung auf Deutsch.

Status of this analysis: Container fully decrypted and verified; pixel data not yet. What is listed under "Verified" below has been cross-checked against multiple files; what is listed under "Hypothesis" or "Open" has not. The section Where the knowledge ends explicitly states where the boundary lies.

Tools: tools/probe_vtpages.py (read-only, standard library only).

python tools/probe_vtpages.py list
python tools/probe_vtpages.py header c12p1.pages
python tools/probe_vtpages.py walk   c12p1.pages
python tools/probe_vtpages.py page   c12p1.pages 100 40 0
python tools/probe_vtpages.py blocks c12p1.pages 100 40 0 --out DIR
python tools/probe_vtpages.py map    c12p1.pages 0 --out belegung.png
python tools/probe_vtpages.py selftest [file]

1. The Inventory

<Steam library>\...\Wolfenstein.The.New.Order\virtualtextures\ contains 39 files, totaling 35.6 GB (33.2 GiB; remeasured on 21.09.2026 — the earlier "41 GB" figures here and the "~34 GB" figures in File-Formats.md/Modding-Limits.md were both incorrect and have been corrected). Two families:

Family Example Meaning
cNNpM.pages c13p1.pages, c04p2_3.pages Unique-/Mega-texture per chapter section (World Geometry)
chunkN_vmtr.pages, _vmtr.pages chunk7_vmtr.pages Virtual-Material pages — exactly what materials with usevirtualmapping 1 retrieve (Weapons, Characters, shared assets)

_vmtr.pages is the global vmtr set (1.2 GB); the chunkN_vmtr.pages files correspond 1:1 to the base/chunkN.resources archives.

All 39 files pass the header consistency test (probe_vtpages.py list) without exception:

file                          bytes  across  lvl     nodes  scale  hdr
_vmtr.pages              1201132468    1024   11   1398101      2  ok
c01p2.pages              2962672722    2048   12   5592405      2  ok
c12p1.pages                13563938     128    8     21845      2  ok
...  (all 39: ok)

across ∈ {128, 256, 512, 1024, 2048} — the side length of the virtual texture in pages. It always holds that across == 2^(levels-1) and nodes == (4^levels - 1) / 3, which is the exact node count of a complete Quadtree at this depth.


2. File Header — 32 Bytes (Verified)

Offset Type Value Meaning
+0 u32 0x77339904 Magic (04 99 33 77 on disk)
+4 u32 128…2048 pagesAcross — pages per side at Mip 0
+8 u32 0 zero in all 39 files
+12 u32 8…12 Number of Mip levels (log2(across)+1)
+16 u32 Node count of the full Quadtree (4^levels-1)/3
+20 u32 2 Offset Scale: all offsets/sizes in the tree are in units of 2 bytes
+24 u64 File size in bytes — matches exactly for all 39 files

The scale at +20 is the *_DISK_OFFSET_SCALE value of the engine (the EXE knows PRODUCTION_DISK_OFFSET_SCALE, DEFAULT_DISK_OFFSET_SCALE, UNCOMPRESSED_DISK_OFFSET_SCALE). Retail uses 2 consistently. This was the key to the tree: raw reads yield nonsense offsets, but multiplying by ×2 points exactly to the child sets.

There is no other table, no directory, no trailer. The rest of the file is exclusively the tree.


3. Set Header — 40 Bytes per page (Verified)

Starting directly at offset 32 begins the first set entry. Each entry:

Offset Type Meaning
+0 u32 Magic 0xCABFED04
+4 u32 Compression (idTech5-compression_t), Retail always 5 = COMP_JXR
+8 u32[4] Disk offsets of the four children, in 2-byte units (0 = no child)
+24 u16[4] Reserved size of the four children, in 2-byte units
+32 u32 Page coordinate, packed: x = value & 0xFFFF, y = value >> 16
+36 u32 Mip level, 0 = finest, levels-1 = the single 1×1 root page

Sibling order is row-major: index = (y & 1) * 2 + (x & 1).
For each child, child.x >> 1 == parent.x, child.y >> 1 == parent.y, and child.mip == parent.mip - 1 — verified against ~120,000 parent/child edges with no deviations.

The very first sentence at offset 32 is a special case: compression = 0 (COMP_NONE), containing no payload; both coordinate and Mip are set to 0 and are meaningless. It serves only as the root pointer — its child 0 points to the actual 1×1 page at offset 72. Therefore, it must be filtered out by compression == 0 during evaluation; otherwise, it would be counted as a Mip-0 page.

The four u16 sizes represent what is reserved on the slab, not necessarily what the sentence occupies: the surplus is Slack (see §6).


4. Page Payload — 16 Bytes + Four Channel Blocks (Verified)

Immediately following the 40-byte header:

Offset Type Meaning
+0 u32 LE Magic 0x28190D1E (1e 0d 19 28)
+4 u16 LE Layout code (observed: 0,1,2,4,5,6,8,12,13,14)
+6 u16 BE Size of Block 0
+8 u16 BE Size of Block 1
+10 u16 BE Size of Block 2
+12 u16 BE Size of Block 3
+14 u16 0 in every sentence of all tested files

The four block sizes are big endian — the only field in the entire format. Immediately following them lie the four blocks consecutively, and the payload is padded to an even byte count:

payload_size = (16 + sum(blocksizes) + 1) & ~1

This is not an assumption: sentence size = 40 + payload_size reproduces the file size for c12p1.pages, c16p0.pages, and c03p3.pages byte-for-byte, with no remainder.


5. Verification

probe_vtpages.py selftest <file> runs the entire tree and verifies: Magic, sibling order, coordinate derivation, Mip descent, coordinates within the grid across >> mip, parent size consistency, and that no unreferenced sentence exists in gaps.

selftest ok: c12p1.pages,       4468 sentences,   4467 edges,  13563938 B +       0 B Slack =  13563938 B
selftest ok: c03p1.pages,       9031 sentences,   9030 edges,  27216028 B +   28672 B Slack =  27244700 B
selftest ok: c16p0.pages,      24154 sentences,  24153 edges,  80537674 B +       0 B Slack =  80537674 B
selftest ok: c02p0.pages,      29526 sentences,  29525 edges,  85948078 B + 1513472 B Slack =  87461550 B
selftest ok: chunk3_vmtr.pages, 21578 sentences,  21577 edges, 155853376 B + 2071462 B Slack = 157924838 B
selftest ok: c01p4.pages,      27766 sentences,  27765 edges,  97858590 B +  544768 B Slack =  98403358 B
selftest ok: c03p3.pages,      29063 sentences,  29062 edges,  76360770 B +       0 B Slack =  76360770 B

Every byte of every tested file is accounted for. The tree is thus confirmed as a model, not merely plausible.

Additional visual inspection: probe_vtpages.py map c03p1.pages 0 --out x.png renders the occupancy of Mip 0. The result consists of clean axis-parallel rectangles of varying sizes — an atlas-packer layout, with no noise. If x/y were swapped or sibling order incorrect, confetti would appear. Typical occupancy: 18% (c12p1) to 34% (c03p1) of pages on Mip 0 are stored at all; the tree is therefore sparsely populated.

Distribution of sentences across Mips, example c02p0.pages (256 pages wide, 9 levels):

mip 0: 18398   mip 3:  727   mip 6:  15
mip 1:  7683   mip 4:  225   mip 7:   4
mip 2:  2413   mip 5:   60   mip 8:   1   <- the 1x1 root page

6. Slack Between Sentences (Partially Open)

Not every file is gapless. c02p0.pages has 739 gaps, chunk3_vmtr 471. Verified:

  • Gaps are reserved by the parent sentence — the parent's u16 size claim exactly covers sentence + gap. The sum of reservation surpluses is byte-identical to the sum of gap lengths.
  • No sentence magic exists within any gap. Thus, the tree reaches everything.
  • The vast majority of gaps are exactly 2048 bytes long.

The content is open. Most entries are 0xFF (free space), but individual bytes within them are deliberately set to 0xE7/0xEF — single deleted bits, which resembles a occupancy bitmap more than filler garbage. In chunk3_vmtr, there are additionally 31 larger gaps (up to ~39 KB) filled with zeros.

Interpretation (Hypothesis, unproven): The Pagefiles are writable. The Engine provides Cvars for this: vt_logPageWrites ("write out log file entries when virtual texture pages are created and written"), vt_stopOnWriteErrors, vt_readOnlyNetPages, vt_createPagesFile. The 2048-byte slack would then be a growth reserve, allowing a newly transcoded page to be rewritten in place at its location, even if it becomes slightly larger.


7. Engine Constants from WolfNewOrder_x64.exe

The EXE carries a Reflection/RTTI table with constant names and their source text expressions. This is hard documentation, not interpretation:

PAGE_WIDTH_LOG2            7
PAGE_WIDTH                  (1<<7)          -> 128
PAGE_BORDER_WIDTH           4
PAGE_PAYLOAD_WIDTH          128-2*4         -> 120
LOAD_WIDTH                  128-2*4         -> 120
PAGE_TEXELS                 128*128         -> 16384
DXT1_BLOCK_SIZE / DXT5_BLOCK_SIZE
PAGEDATA_NUM_CHANNEL_IMAGES
PAGEDATA_IMAGE0_SIZE_DXT    16384*1/2       -> 8192   (= DXT1, 128x128)
MAX_PAGE_DATA_SIZE_DXT      16384*5/2       -> 40960
MAX_PAGE_DATA_SIZE          16384*4*3
MAX_PAGEFILE_HEADER_SIZE    4096
MAX_VIRTUAL_PAGES_WIDE_LOG2 11
MAX_VIRTUAL_PAGES_WIDE      1<<11           -> 2048
MAX_TOTAL_MIP_LEVELS / MIN_INSTALLED_MIP_LEVEL
MAX_CONTAINER_PAGE_NAME
PRODUCTION_DISK_OFFSET_SCALE / DEFAULT_DISK_OFFSET_SCALE / UNCOMPRESSED_DISK_OFFSET_SCALE

Enums:

compression_t:          COMP_NONE, COMP_DXT, COMP_LZW, COMP_DCT, COMP_HDP,
                        COMP_JXR, COMP_MAX_COMPRESSIONS      (0..5, 6 = count)
virtualTextureFlavor_t: ID_VTF_UNKNOWN, ID_VTF_BAKE, ID_VTF_VMTR
physical pools:         PHYSICAL_PAGES_POOL_VMTR, _UNIQUE,
                        _UNIQUE_DIFFUSE_ONLY, _UNIQUE_DIFFUSE_ONLY2,
                        _SHARED_COUNT, _PRT, _MAX

Source files that the EXE refers to in Assert strings: w:\tech5\engine\renderer\VirtualTextureSystem.cpp, w:\tech5\engine\renderer\jobs\transcode\PageCompression.cpp.

Calculation for the virtualmapping field. A material with virtualmapping = { 0.015625, 0.015625, 0.0, 0.0625 } occupies 1/64 of the virtual texture per axis. In a vmtr file with across = 1024, this equals 16 pages, so 16 × 128 = 2048 texels side length — a completely normal texture size. The first two numbers are thus scaling, and the last two are offset (here: 0 in x, 1/16 = 64 pages in y). The 1024 pages × 128 texels result in a virtual texture of 131072 = 2^17 texels side length, matching the Cvar vt_virtualSizeLog2.

Minor uncertainty: PAGE_PAYLOAD_WIDTH = 120 suggests that a page carries only 128 unique texels and the 4 border texels per page are copies of neighbors. Then the stride would be 120 instead of 128, and the above calculation would yield 1920 instead of 2048. The power-of-two calculation fits better with vt_virtualSizeLog2, but which one is correct remains unproven.


8. The Codec — what is known

Secured:

  • Every Retail set carries compression = 5 = COMP_JXR. No single deviating value in ~145,000 checked sets.
  • Each page consists of exactly four channel blocks, never more, never less.
  • Each block begins with a constant prefix that is bit-identical across all pages and all files:

Block 0: 71 00 a3 c6 0a 3c 60 a3 c6 00 ff 00 00 01 00 Block 1: d1 10 a1 a1 aa 1a 1a a1 a1 a0 ff 00 00 01 00 Block 2: d1 00 8c a3 28 c8 ff 00 00 01 00 Block 3: d1 00 94 25 09 40 ff 00 00 01 00

Blocks 0/1 have a 10-byte header; blocks 2/3 have only 6 bytes. Afterward, all four share the same 5-byte sequence ff 00 00 01 00, followed by variable data. The fact that the header is constant means it encodes only things that are identical across all sides—dimensions (always 128×128) and quantization (set globally via the vt_quality* Cvars).

  • The data section is not a byte-stopped JPEG stream: among 22,238 0xFF bytes in a sample, only 4,893 are followed by a 0x00. Therefore, the standard JPEG FF 00 stop rule does not apply here.
  • The data section is a bitstream. Typical patterns include long runs of 99 99 99…, 66 66 66…, or 49 24 92 49 24 92…—these are periodic bit patterns (10011001, 01100110, 100100100…) produced by an entropy coder when encoding a highly probable symbol repeatedly.
  • The EXE contains the complete symbol table of an HD-Photo/JPEG-XR decoder, verbatim as in Microsoft's reference implementation: WMPHOTO, IndLPLum, AbsLevelIndLP0/1, AbsLevelIndHP0/1, DecIndLPLum0/1, DecFirstIndLPChr, DecIndLPChr0/1, DecNumCBP, DecNumBlkCBP, DecIndHPLum0/1, DecFirstIndHPLum/Chr, DecIndHPChr0/1. (LP = Lowpass, HP = Highpass, CBP = Coded Block Pattern, Lum/Chr = Luma/Chroma—these are the adaptive VLC context tables of JPEG XR.)

Hypothesis (well-supported but unproven): block assignment. Quality Cvars exist for JXR in exactly four variants—vt_qualityJXRDiffuse, …Normal, …Specular, …Power—and there are exactly four blocks. MAX_PAGE_DATA_SIZE_DXT = 16384*5/2 = 40960 decomposes cleanly into DXT1 (8192) + DXT5 (16384) + DXT1 (8192) + DXT1 (8192), and PAGEDATA_IMAGE0_SIZE_DXT = 8192 is exactly DXT1 for 128×128. This matches the measured byte proportions (proportion of total volume per file):

File Block 0 Block 1 Block 2 Block 3
c12p1 40 % 34 % 24 % 2 %
c03p1 26 % 48 % 23 % 3 %
c02p0 36 % 43 % 19 % 3 %
c16p0 29 % 48 % 19 % 4 %
chunk3_vmtr 22 % 63 % 14 % 1 %

Block 1 is almost always the largest (Normal Maps are high-frequency and compress poorly); Block 3 is tiny at 1–4% (Power/Gloss is mostly constant). Therefore: Block 0 = Diffuse, 1 = Normal, 2 = Specular, 3 = Power. The longer header for blocks 0/1 fits the fact that these two are multi-channel while blocks 2/3 are single-channel. This has not been proven.

Hypothesis: the layout code in payload +4. vt_showPageInfo describes itself as "Shows page info: source #, layout #, compression, mono/color specular", and there is vt_maxMonoSpecularError ("specular is considered monochrome if the error is less than this value"). The observed values 0,1,2,4,5,6 and 8,12,13,14 appear to be bit3 = mono specular plus a layout ID 0…6 in the lower bits. The most frequent value is 6.

Open question: the actual decoding. The block header is not a valid JPEG-XR codestream header. A true codestream begins with the 8-byte signature WMPHOTO\0, followed by RESERVED_B=1, flags, and—when SHORT_HEADER_FLAG is set—16-bit width and height. Here, the signature is missing, and there is no room for 32-bit dimensions before the ff 00 00 01 00 sequence (only 6 or 10 bytes total). I additionally searched every bit position of the headers to see if anywhere two identical 16-bit fields with values 127/119/63/31 appear (i.e., 128−1, 120−1, etc.)—no hits.

This means: MachineGames writes a truncated stream. Dimensions and configuration are implicit because they are always identical. A standard JXR decoder cannot process these bytes without first reconstructing the header—and to do that, one would need to know which bits the engine omitted. This cannot be clarified without disassembling PageCompression.cpp code.


9. The vt_ Cvars

Extracted from .rdata of WolfNewOrder_x64.exe: 119 name matches, approximately 100 unique Cvars. The descriptions are stored in the pool directly next to the names; the following pairings were read from the layout of the .rdata region (see tools/mine_cvars_tnc.py for pitfalls).

Format and Encoding

Cvar Description
vt_qualityDCTLuma quality in the range [0-100] percent where higher is better quality
vt_qualityDCTChroma quality in the range [0-100] percent
vt_qualityDCTNormal quality in the range [0-100] percent
vt_qualityDCTSpecular quality in the range [0-100] percent
vt_qualityDCTPower quality in the range [0-100] percent
vt_qualityHDPDiffuse amount of quantization to apply in the range [0-255] where lower is better quality
vt_qualityHDPNormal / …Specular / …Power same as above
vt_qualityHDPLossless force lossless HD-Photo compression
vt_qualityJXRDiffuse amount of quantization to apply in the range [0-255] where lower is better quality
vt_qualityJXRNormal / …Specular / …Power same as above
vt_qualityJXRLossless force lossless JPEG-XR compression
vt_maxMonoSpecularError specular is considered monochrome if the error is less than this value
vt_useSpecularScale scale the specular to avoid DXT quantization errors
vt_specularThreshold / vt_specularScale (Default 0.2 for one of the two)
vt_uncompressedVmtr Use uncompressed vmtr data. Only read at startup.

This is the most important part of the entire document: there are three codecs (DCT, HDP, JXR) each with their own quality controllers, and Retail files use JXR.

Files, Paths, Generation

Cvar Description
vt_filePath Path for the virtual texture
vt_filePathVmtrOverride Optionally get vmtr files from a different source than vt_filePath
vt_createPagesFile (creates a .pages file)
vt_packPageFiles Use the virtual texture file name without any suffixes, 0 = version vmtr and megas, 1 = version megas, 2 = version none
vt_containers Set to enable loading from containers
vt_logPageWrites write out log file entries when virtual texture pages are created and written
vt_stopOnWriteErrors error out when encountering write errors
vt_readOnlyNetPages Set to always open network pagefiles in readonly mode
vt_useNetworkPagesWithPreload Use the network page files and vmtr even when using linear preload files
vt_usePageFileCache Use the pageFileCache for all reads
vt_useNonBufferedIO use non-buffered IO
vt_noSeekCacheLines number of cache lines we can skip without seeking
vt_minMipLevelInstalled minimum mip level installed on the hard disk (2 = all mips except finest two installed, 16 = no mips installed)
vt_supportRoundedDownVmtrs quick temporary fix to load in the 4x1 pages properly
vt_genMissingPages Generate a page in memory for pages that don't exist on disk.

Runtime / Physical Cache

Cvar Beschreibung
vt_pageImageSizeVmtr Dimensions of the physical page images. vt_restart after changing this. (Default 4096)
vt_pageImageSizeUnique dito (Default 4096)
vt_pageImageSizeUniqueDiffuseOnly / …2 dito
vt_uncompressedPhysicalImages Use uncompressed physical page images. vt_restart after changing this.
vt_linearPhysicalImages
vt_maxPhysicalPRTPages Maximum number of resident PRT pages. (Default 4096)
vt_enablePRT Set to enable PRT mode
vt_maxLockedPages Don't lock more than pages than this.
vt_minLod Set the finest mip that can be accessed.
vt_lodBias Additional floating point bias for virtual texture page tables
vt_maxAniso Floating point max aniso for physical textures.
vt_filter filter physical images, 0 = off, 1 = bilinear, 2 = trilinear
vt_maxPPF maximum number of transcoded pages per frame
vt_pageFadeInSteps
vt_pageBorderAdjust Debug adjustment of a page's mapping about it's center
vt_stripPageBorders Strip the page border by upscaling the center

Feedback / Streaming

Cvar Beschreibung
vt_feedbackLodBias Additional floating point bias for feedback LOD
vt_feedbackHighWater More than this percentage of pages needed this frame and we consider ourselves oversubscribed.
vt_feedbackLowWater Less than this percentage of pages needed this frame and we consider ourselves undersubscribed.
vt_feedbackLODIncrement Base increment value for adjusting dynamic LOD bias. (Default 0.125)
vt_skipStreaming Skip streaming but still transcode and update the page tables
vt_skipTranscode Skip transcoding (but don't skip reading)
vt_skipUpdatePageImages Skip updating page table and physical images on the PC (but still read/transcode)
vt_skipExplicitPageFeedback don't append explicit pages from gui surfaces.
vt_nopreload

Upsampling

Cvar Beschreibung
vt_useUpsample Number of extra levels in vt to use for upsample
vt_upsampleFilter 0 = nearest, 1 = bilinear, 2 = biquadratic, 3 = bicubic, 4 = sinc+hamming, 5 = sinc+blackman, 6 = sinc+lanczos, 7 = sinc+kaiser
vt_upsampleSharpen amount of sharpening to apply to upsampled texture pages
vt_upsampleNoise amount of noise to add to upsampled texture pages

Jobs

vt_useAnalyzeJobs, vt_usePageSortJobs, vt_useTranscodeJobs, vt_usePageMinLodJobs — alle "0 = off, 1 = threaded". vt_splitTranscodeJobs ("split transcode jobs into two jobs per page"), vt_useCudaTranscode ("controls Cuda texture transcoding: 0 - off, 1 - smart, 2 - on"), vt_singleStep ("single step through the transcode process"), vt_usePageTablePBOs, vt_useUnsynchronizedPageTablePBOs, vt_useInvalidateRangeBitForMappingAndPageTablePBOMap, vt_useDirectTextureAccess, vt_useMinLodTexture, vt_useMinLodTexture, vt_touchPhysCacheOnAlloc, vt_skipPhysicalTextureUploads, vt_virtualSizeLog2 ("Log2 of the virtualTexture dimensions for general texture allocation. Must be set on the command line.").

Debug-Visualisierung

vt_showPageLoads, vt_showFeedbackAnalysis, vt_showFeedbackBuffer, vt_showFeedbackStreaming, vt_showStats, vt_recordStats, vt_showLockedPages, vt_showPageSources ("Shades vmtr and unique pages differently"), vt_showPageMips, vt_showPageColors, vt_showPageNumbers, vt_showPageInfo ("Shows page info: source #, layout #, compression, mono/color specular"), vt_showPageBorders, vt_showPageCheckered, vt_showPageSizes, vt_showPageUsage, vt_showExplicitPages.

Kommandos (keine Variablen)

vt_info ("prints data on a virtual material"), vt_emptyCache, vt_lockPages, vt_restart ("reallocates the physical images for changes in size or compression"), vt_reload ("reload all the currently loaded virtual textures"), vt_validateCache ("consistency checks the page buffers"), vt_setSource ("Explicitly change the page file for a virtual material to compare"), vt_transcodeBenchmark, vt_benchmark.


10. What This Means for Modding

What is now possible:

  • .pages files can be fully read, indexed, and verified for integrity. One can precisely state which tile resides on which Mipmap level at which coordinate, its dimensions, and its offset within the file.
  • Individual page blocks can be raw extracted (probe_vtpages.py blocks) and compared against others.
  • It is possible to specifically determine which region of a Mega-texture belongs to a given material — via virtualmapping (Scale/Offset) in the Material Declaration and the Occupancy map.

What is not possible: Swapping pixels. This would require an JXR encoder that generates the same truncated stream as MachineGames' encoder. Even with a functioning decoder, the return path would be another project entirely.

Therefore, the more promising paths are not the format itself, but the Cvars:

  1. vt_filePath and vt_filePathVmtrOverride bend the search path. If one reaches a location where complete, custom .pages files can be placed, there is no need to patch the format — only to generate it.
  2. vt_createPagesFile is exactly this generator, built into the game. Together with vt_uncompressedVmtr ("Use uncompressed vmtr data") and the vt_quality* controls, this suggests that the engine still has the encoding side onboard, not just the decoding side.
  3. vt_setSource swaps the Pagefile of a single virtual material at runtime — intended for comparison, but precisely the lever a texture mod would need.

Whether these Cvars are accessible in the Retail build (Console, Cheat flags) has not been verified here. That would be the next sensible step and can be approached with the existing tools (tools/probe_cvars.py, tools/probe_retail_lock.py).


Where This Knowledge Ends

Secured, double-checked:

  • File header (32 bytes), all seven fields, across 39 files.
  • The Offset scale 2 — without it, nothing makes sense.
  • Sentence header (40 bytes): Magic, Compression enum, four child offsets, four child sizes, packed x/y, Mipmap level. Child index (y&1)*2 + (x&1).
  • Page subheader (16 bytes) with four big-endian block sizes.
  • That exactly four blocks exist per page and their lengths.
  • That across seven fully traversed files, every single byte is accounted for.
  • That Retail consistently uses COMP_JXR.
  • The engine constants (128-byte tiles, 4 texel border, 120 usable texels) — these are literally in the EXE.

Speculated, reasoned, but not proven:

  • Block assignment Diffuse/Normal/Specular/Power.
  • Layout code = Layout ID plus Mono-Specular bit.
  • The 2048-byte slack as growth reserve for overwritten pages.
  • Whether the virtual texture edge is across × 128 or across × 120.

Not solved:

  • Pixel data. The bitstream behind ff 00 00 01 00 is not decoded. It is related to HD-Photo/JPEG-XR (the EXE contains the complete HD-Photo decoder), but the block headers are not a valid JXR codestream header — they lack signature and dimensions. Therefore, a standard decoder cannot be attached, and how to correctly reconstruct the header cannot be determined without disassembly.
  • Consequently, no PNG export of a real page. probe_vtpages.py map exports a map (which pages exist, colored by size) — this is proof of the tree structure, explicitly not for the pixel data. A PNG with invented pixels would be worse than none at all.
  • Meaning of individual bytes in the constant block headers.
  • Content and purpose of the gaps partially filled with zeros and bit patterns.