Module: Esp::Mw::ScriptBlob
- Defined in:
- lib/esp/mw/script_blob.rb
Overview
Synthesizes the ZSTD-framed blobs that tes3conv expects for a Script record’s SCVR (variable list) and SCDT (bytecode) subrecords.
Vanilla blobs use ZSTD frames whose blocks may be raw (small/empty scripts) or compressed-with-LZ77 matches (large scripts). tes3conv accepts either, so we always emit raw blocks — simpler, smaller code, and only marginally larger output. Decoding always goes through libzstd via zstd-ruby so we can extract names from any vanilla blob.
Raw frame layout:
4 bytes ZSTD magic (28 b5 2f fd)
1 byte Frame Header Descriptor (0x00)
1 byte Window Descriptor (0x58)
3 bytes Block header ((size << 3) | type<<1 | last_block)
N bytes Raw payload
Payload layout (both SCVR and SCDT placeholder):
4 bytes LE uint32 — byte length of names section (0 if none)
N bytes Null-terminated variable names, concatenated
Empty vars and the bytecode placeholder both use a 4-byte zero payload (length prefix = 0). A truly zero-byte payload is rejected by tes3conv for SCDT and isn’t what vanilla emits anywhere.
Constant Summary collapse
- ZSTD_MAGIC =
"\x28\xb5\x2f\xfd".b.freeze
- FHD =
0x00- WINDOW_DESCRIPTOR =
0x58
Class Method Summary collapse
-
.decode_var_names(b64) ⇒ Object
Decompress any vanilla SCVR blob and split out the variable names.
-
.decompress(b64) ⇒ Object
Decompress a blob and return its raw payload (length prefix + names).
-
.empty_bytecode ⇒ Object
SCDT placeholder.
-
.variables(names) ⇒ Object
Returns [base64_blob, variables_length] where variables_length is the byte count of the names section (matching the SCHD header field).
Class Method Details
.decode_var_names(b64) ⇒ Object
Decompress any vanilla SCVR blob and split out the variable names.
54 55 56 57 58 59 60 61 62 |
# File 'lib/esp/mw/script_blob.rb', line 54 def decode_var_names(b64) payload = decompress(b64) return [] if payload.bytesize <= 4 names_section = payload.byteslice(4..) names = names_section.split("\x00", -1) names.pop while names.last == '' || names.last.nil? names.map { |n| n.dup.force_encoding(Encoding::ASCII) } end |
.decompress(b64) ⇒ Object
Decompress a blob and return its raw payload (length prefix + names). Useful for tests that compare semantic content across raw and compressed framings.
67 68 69 |
# File 'lib/esp/mw/script_blob.rb', line 67 def decompress(b64) Zstd.decompress(Base64.decode64(b64)) end |
.empty_bytecode ⇒ Object
SCDT placeholder. OpenMW recompiles bytecode from ‘text` on load, so we never emit real bytecode.
49 50 51 |
# File 'lib/esp/mw/script_blob.rb', line 49 def empty_bytecode wrap(("\x00" * 4).b) end |
.variables(names) ⇒ Object
Returns [base64_blob, variables_length] where variables_length is the byte count of the names section (matching the SCHD header field).
37 38 39 40 41 42 43 44 45 |
# File 'lib/esp/mw/script_blob.rb', line 37 def variables(names) if names.empty? [wrap(("\x00" * 4).b), 0] else names_bytes = names.map { |n| "#{n.b}\x00".b }.join payload = [names_bytes.bytesize].pack('V') + names_bytes [wrap(payload), names_bytes.bytesize] end end |