class Gnosis::Translators::RGSSAD

Provides a translator for RGSSAD encrypted archives as used by RPG Maker XP and VX. This class provides the implementation-specific {#scan_files} and {#decrypt} methods as used by an {Archive Archive} instance.

Public Class Methods

new(parent) click to toggle source

@raise [InvalidArchiveError] if the parent object’s archive is not a

valid RGSSAD encrypted archive

@param [Archive] parent the archive which contains this translator

# File lib/gnosis/translators/rgssad.rb, line 13
def initialize(parent)
  unless File.read(parent.archive, 8).unpack('a6U*').join == 'RGSSAD01'
    raise InvalidArchiveError, parent.archive
  end
  @parent = parent
end

Public Instance Methods

decrypt(file) click to toggle source

Decrypts the given file in an encrypted archive, returning the original binary data as a string.

@raise [Errno::ENOENT] if the given file does not exist in the parent

{Archive} representation

@param [String] file internal path of encrypted file @return [String] the decrypted binary contents of the given file

# File lib/gnosis/translators/rgssad.rb, line 52
def decrypt(file)
  raise Errno::ENOENT, file unless @parent.files[file]
  data = ''
  info = @parent.files[file]
  key  = Key.new(info[:subkey])
  size, remainder = (info[:size] / 4.0).ceil, info[:size] % 4
  
  File.open(@parent.archive) do |archive|
    archive.seek(info[:position])
    (1..size).each do |position|
      data << \
      if remainder > 0 && position == size
        bytes = archive.read(remainder)
        translate_data(bytes.ljust(4, "\x00"), key)[0...bytes.size]
      else
        translate_data(archive.read(4), key)
      end
      key.advance
    end
  end
  
  data
end
scan_files() click to toggle source

Builds a hash of file information stored in an encrypted archive. Keys are filenames, values contain a hash of information regarding file encryption (namely its position in the archive, size in bytes, and the subkey needed to decrypt the file).

@return [Hash{String => Hash{Symbol => Number}}] a hash of files with

associated encryption information
# File lib/gnosis/translators/rgssad.rb, line 27
def scan_files
  hash = {}
  @key  = Key.new
  File.open(@parent.archive) do |archive|
    archive.seek(8)
    loop do
      length = translate_int(archive.read(4)) rescue break
      file   = translate_file(archive.read(length)).gsub!(/\\/, '/')
      hash[file]          = { :position => archive.tell + 4 }
      hash[file][:size]   = translate_int(archive.read(4))
      hash[file][:subkey] = @key.current
      archive.seek(hash[file][:size], IO::SEEK_CUR)
    end
  end
  @key = nil
  hash
end

Private Instance Methods

translate_data(bytes, key) click to toggle source

Translates encrypted binary data to decrypted binary data.

@note This method should only be given a string of four encrypted bytes

in order to function properly. Alternative lengths produce undefined
strings.

@param [String] bytes string of encrypted bytes @param [Number] key subkey used to decrypt the given bytes @return [String] string of decrypted bytes

# File lib/gnosis/translators/rgssad.rb, line 86
def translate_data(bytes, key)
  [bytes.unpack('V').first ^ key.current].pack('V')
end
translate_file(bytes) click to toggle source

Translates an encrypted binary filename within the archive to a UTF-8 string.

@param [String] bytes string of encrypted bytes @return [String] a decrypted UTF-8 string

# File lib/gnosis/translators/rgssad.rb, line 95
def translate_file(bytes)
  bytes.each_byte.map do |byte|
    result = (byte ^ @key.current & 0xFF).chr
    @key.advance
    result
  end.join.force_encoding('utf-8')
end
translate_int(bytes) click to toggle source

Translates an encrypted binary integer to a native Ruby integer.

@param [String] bytes string of encrypted bytes @return [Number] a decrypted native integer

# File lib/gnosis/translators/rgssad.rb, line 107
def translate_int(bytes)
  result = bytes.unpack('V').first ^ @key.current
  @key.advance
  result
end