Class: Feh::Bin::LZ11

Inherits:
Object
  • Object
show all
Defined in:
lib/feh/bin/lz11.rb

Overview

Converter for the LZ11 archive format used in Fire Emblem Heroes. Ported from DSDecmp.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(buffer) ⇒ LZ11

Initializes the LZ11 converter.

Parameters:

  • buffer (Array<Integer>)

    byte array containing the data to convert



16
17
18
# File 'lib/feh/bin/lz11.rb', line 16

def initialize(buffer)
  @buf = ArrayIStream.new(buffer)
end

Instance Attribute Details

#bufArrayIStream (readonly)

Returns the input buffer of the converter.

Returns:



12
13
14
# File 'lib/feh/bin/lz11.rb', line 12

def buf
  @buf
end

Instance Method Details

#compressArray<Integer>, Symbol

Compresses a byte buffer. This function is not required to produce exactly the same results as existing archives in Fire Emblem Heroes when given the same inputs.

Returns:

  • (Array<Integer>)

    byte array representing the compressed LZ11 archive

  • (Symbol)

    error code if the input is too large or empty



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/feh/bin/lz11.rb', line 102

def compress
  return :input_too_short if buf.size < 2
  return :input_too_large if buf.size > 0xFFFFFF

  outstream = ArrayOStream.new
    .u8(0x11).u16(buf.size).u8(buf.size >> 16)

  outbuffer = [8 * 4 + 1] * 33
  outbuffer[0] = 0
  bufferlength = 1
  bufferedBlocks = 0
  readBytes = 0
  while readBytes < buf.size
    if bufferedBlocks == 8
      outstream.write(outbuffer[0, bufferlength])
      outbuffer[0] = 0
      bufferlength = 1
      bufferedBlocks = 0
    end

    oldLength = [readBytes, 0x1000].min
    disp, length = occurrence_length(readBytes,
      [buf.size - readBytes, 0x10110].min, readBytes - oldLength, oldLength)
    if length < 3
      outbuffer[bufferlength] = buf[readBytes]
      readBytes += 1
      bufferlength += 1
    else
      readBytes += length
      outbuffer[0] |= (1 << (7 - bufferedBlocks)) & 0xFF
      case
      when length > 0x110
        outbuffer[bufferlength] = 0x10
        outbuffer[bufferlength] |= ((length - 0x111) >> 12) & 0x0F
        bufferlength += 1
        outbuffer[bufferlength] = ((length - 0x111) >> 4) & 0xFF
        bufferlength += 1
        outbuffer[bufferlength] = ((length - 0x111) << 4) & 0xF0
      when length > 0x10
        outbuffer[bufferlength] = 0x00
        outbuffer[bufferlength] |= ((length - 0x111) >> 4) & 0x0F
        bufferlength += 1
        outbuffer[bufferlength] = ((length - 0x111) << 4) & 0xF0
      else
        outbuffer[bufferlength] = ((length - 1) << 4) & 0xF0
      end
      outbuffer[bufferlength] |= ((disp - 1) >> 8) & 0x0F
      bufferlength += 1
      outbuffer[bufferlength] = (disp - 1) & 0xFF
      bufferlength += 1
    end

    bufferedBlocks += 1
  end

  if bufferedBlocks > 0
    outstream.write(outbuffer[0, bufferlength])
  end

  outstream.buf
end

#decompressArray<Integer>, Symbol

Decompresses an LZ11 archive.

Returns:

  • (Array<Integer>)

    byte array representing the decompressed content of the input archive

  • (Symbol)

    error code if the input is not a valid LZ11 archive



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/feh/bin/lz11.rb', line 24

def decompress
  header = buf.u32
  return :invalid_data if (header & 0xFF) != 0x11
  decompressedSize = header >> 8
  decompressedSize = buf.u32 if decompressedSize == 0

  bufferLength = 0x1000
  buffer = Array.new(bufferLength)
  bufferOffset = 0

  flags = 0
  mask = 1

  outbuf = []
  until outbuf.size >= decompressedSize
    if mask == 1
      flags = buf.u8
      return :stream_too_short if flags.nil?
      mask = 0x80
    else
      mask >>= 1
    end

    if (flags & mask) > 0
      byte1 = buf.u8
      return :stream_too_short if byte1.nil?

      length = byte1 >> 4
      disp = -1
      case length
      when 0
        byte2 = buf.u8
        byte3 = buf.u8
        return :stream_too_short if byte3.nil?
        length = (((byte1 & 0x0F) << 4) | (byte2 >> 4)) + 0x11
        disp = (((byte2 & 0x0F) << 8) | byte3) + 0x1
      when 1
        byte2 = buf.u8
        byte3 = buf.u8
        byte4 = buf.u8
        return :stream_too_short if byte4.nil?
        length = (((byte1 & 0x0F) << 12) | (byte2 << 4) | (byte3 >> 4)) + 0x111
        disp = (((byte3 & 0x0F) << 8) | byte4) + 0x1
      else
        byte2 = buf.u8
        return :stream_too_short if byte2.nil?
        length = ((byte1 & 0xF0) >> 4) + 0x1
        disp = (((byte1 & 0x0F) << 8) | byte2) + 0x1
      end

      return :invalid_data if disp > outbuf.size

      bufIdx = bufferOffset + bufferLength - disp
      length.times do
        next_byte = buffer[bufIdx % bufferLength]
        bufIdx += 1
        outbuf << next_byte
        buffer[bufferOffset] = next_byte
        bufferOffset = (bufferOffset + 1) % bufferLength
      end
    else
      next_byte = buf.u8
      return :stream_too_short if next_byte.nil?
      outbuf << next_byte
      buffer[bufferOffset] = next_byte
      bufferOffset = (bufferOffset + 1) % bufferLength
    end
  end

  outbuf
end