/// <summary> /// Parse the VDX to locate individual chunks /// </summary> private void parseVDX(GJD.RLData? rlData) { long stop; // Final stream position if (rlData.HasValue) { stop = rlData.Value.offset + rlData.Value.length; } else { stop = file.BaseStream.Length; } VDXHeader header = new VDXHeader(); VDXChunkHeader chunk = new VDXChunkHeader(); // Initialise queue's audioOffsets = new Queue<long>(); videoOffsets = new Queue<long>(); // Read VDX header header.ID = file.ReadUInt16(); if (header.ID != 0x9267) throw new Exception("Invalid VDX header"); header.Unknown1 = file.ReadByte(); header.Unknown2 = file.ReadByte(); header.Unknown3 = file.ReadByte(); header.Unknown4 = file.ReadByte(); header.FPS = file.ReadUInt16(); //fps = (byte)header.FPS; try { while (file.BaseStream.Position < stop) { // Read VDX chunk header chunk = readChunkHeader(); // Add offsets where appropriate switch (chunk.BlockType) { case VDXBlockType.Video: videoFrames++; goto case VDXBlockType.Zero; case VDXBlockType.Zero: videoMaster.Enqueue(file.BaseStream.Position - VDXChunkHeader.size); break; case VDXBlockType.Sound: audioOffsets.Enqueue(file.BaseStream.Position - VDXChunkHeader.size); break; case VDXBlockType.Image: imageOffset = file.BaseStream.Position - VDXChunkHeader.size; break; } // Seek to next chunk file.BaseStream.Seek(chunk.DataSize, System.IO.SeekOrigin.Current); } } catch { audio = null; videoFrames = 1; } }
/* The LZSS algorithm - http://wiki.xentax.com/index.php/LZSS * * Compression * Set up parameters * N Circular buffer size * F Length window size * Threshold Minimum compression size (minimum length for a match) * BufPos Initial buffer position. Generally N - F * BufVoid Initial fill byte. * * To compress * Try to find longest sequence that is also in buffer * If length >= Threshold, save offset and length in output file. record literal to buffer * If no match, copy literal byte to output and repeat * * To record if compression or literal copy made, set special flag byte, * * * Decompression * Done in reverse order. Parameters need to be known. * Pseudo: * * while InputPos < InputSize do * begin * * FlagByte := Input[InputPos++] * for i := 1 to 8 do * begin * * if (FlagByte and 1) = 0 then * begin * * OfsLen := Input[InputPos] + Input[InputPos + 1] shl 8 * InputPos += 2 * if OfsLen = 0 then * Exit; * * Length := OfsLen and LengthMask + Threshold * Offset := (BufPos - (OfsLen shr LengthBits)) and (N - 1) * copy Length bytes from Buffer[Offset] onwards * while increasing BufPos * * end * else * begin * * copy 1 byte from Input[InputPos] * InputPos += 1 * * end * FlagByte := FlagByte shr 1 * * end * end * * Length bits descibes number of bits to find F. Above assumes length of buffer reference * in lower bit of the word. lenght mask = (1 << lenbits) - 1 * * also assumed buffer offset relative to current write position. if absolute, BufPos subtraction unneeded * * Buffer is circular. wrap offset. * * mirror output writes to history buffer * * in most cases, data from history buffer will be byte-wise. */ private void decompress(System.IO.BinaryReader inStream, ref VDXChunkHeader header, out System.IO.MemoryStream stream) { stream = new System.IO.MemoryStream(); uint i, j, N, F, initwrite; byte flagbyte, tempa; uint offsetlen, length, offset, bufpos; byte[] hisbuff; N = (uint)(1 << (16 - header.LengthBits)); // History buffer size hisbuff = new byte[N]; // History buffer F = (uint)(1 << header.LengthBits); // Window size initwrite = N - F; // Initial history write pos bufpos = 0; for (i = 0; i < N; i++) hisbuff[i] = 0; // Init history buffer long final = header.DataSize + inStream.BaseStream.Position; while (inStream.BaseStream.Position < final) { flagbyte = inStream.ReadByte(); for (i = 1; i <= 8; i++) { if (inStream.BaseStream.Position < final) { if ((flagbyte & 1) == 0) { offsetlen = inStream.ReadUInt16(); if (offsetlen == 0) break; length = (offsetlen & header.LengthMask) + 3; offset = (bufpos - (offsetlen >> header.LengthBits)) & (N - 1); for (j = 0; j < length; j++) { tempa = hisbuff[(offset + j) & (N - 1)]; stream.WriteByte(tempa); hisbuff[bufpos] = tempa; bufpos = (bufpos + 1) & (N - 1); } } else { tempa = inStream.ReadByte(); stream.WriteByte(tempa); hisbuff[bufpos] = tempa; bufpos = (bufpos + 1) & (N - 1); } flagbyte = (byte)(flagbyte >> 1); } } } }
/// <summary> /// Read in the header for the current chunk /// </summary> /// <returns>Chunk header structure</returns> private VDXChunkHeader readChunkHeader() { VDXChunkHeader chunk = new VDXChunkHeader(); chunk.BlockType = (VDXBlockType)file.ReadByte(); chunk.PlayCmd = (VDXChunkPlayCmd)file.ReadByte(); chunk.DataSize = file.ReadInt32(); chunk.LengthMask = file.ReadByte(); chunk.LengthBits = file.ReadByte(); return chunk; }