Module: MPQ::Hashing

Defined in:
lib/mpq.rb

Overview

Various hashes are used throughout MPQ archives.

Class Method Summary collapse

Class Method Details

.decrypt(data, seed1) ⇒ Object

Data in the hash and block tables can be decrypted using this algorithm.



135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/mpq.rb', line 135

def self.decrypt data, seed1
  seed2 = 0xEEEEEEEE
  data.unpack('V*').map do |value|
    
    # Again, the `AND`s here forces 32-bit precision.
    seed2 = (seed2 + @encryption_table[0x400 + (seed1 & 0xFF)]) & 0xFFFFFFFF
    value = (value ^ (seed1 + seed2)) & 0xFFFFFFFF
    seed1 = (((~seed1 << 0x15) + 0x11111111) | (seed1 >> 0x0B)) & 0xFFFFFFFF
    seed2 = (value + seed2 + (seed2 << 5) + 3) & 0xFFFFFFFF
    value
  end.pack('V*')
end

.hash_for(hash_type, s) ⇒ Object

The algorithm is unchanged across hash types, but the first step in the hashing differs depending on what we’re hashing.

Both this hashing and the decryption below make use of a precalculated table of values.



119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/mpq.rb', line 119

def self.hash_for hash_type, s
  hash_type = [:table_offset, :hash_a, :hash_b, :table].index hash_type
  seed1, seed2 = 0x7FED7FED, 0xEEEEEEEE
  s.upcase.each_byte do |c|
    value = @encryption_table[(hash_type << 8) + c]
    
    # The seemingly pointless `AND`ing by 32 ones is because Ruby's numbers 
    # are arbitrary precision. Normally that's great, but right now that's 
    # actually unhelpful.
    seed1 = (value ^ (seed1 + seed2)) & 0xFFFFFFFF
    seed2 = (c + seed1 + seed2 + (seed2 << 5) + 3) & 0xFFFFFFFF
  end
  seed1
end