Envision, Create, Share

Welcome to HBGames, a leading amateur game development forum and Discord server. All are welcome, and amongst our ranks you will find experts in their field from all aspects of video game design and development.

save_data Fix

save_data Fix Version: 1.0.0
By: Yeyinde

Introduction

This script fixes the major issue of the save_data function, namely the inability to save to encrypted archives. This enables you to edit the encrypted archive on-the-fly for game updates. You could even save your save files to it!

Script

Code:
#==============================================================================

# ** Fix for save_data(obj, filename)

#------------------------------------------------------------------------------

#  Author:  Yeyinde

#  Date:    May 6, 2009

#  Version: 1.0.0

#------------------------------------------------------------------------------

#  This script provides a fix to the save_data function, allowing you to save

#  objects to the encrypted archive.  Due to the way the archive's encryption

#  works, the script has to read and rewrite the entire archive from the start

#  for every file added with save_data.  However, you can add multiple files

#  to the encrypted archive before rewriting it, saving considerable time.

#  

#  Please note that reading and writing the encrypted archive is a slow process

#  so please have some patience.  Becase RMXP will lock up when Graphics.update

#  has not been called in a while (RMVX does not do this), the script will

#  automatically do so after every update of the encryption key.  This will

#  increase your game time by one frame each update, so save the old frame_count

#  before using save_data and restore it afterwards.

#==============================================================================

 

#==============================================================================

# ** RGSSAD

#------------------------------------------------------------------------------

#  This is a module for parsing the RGSSAD encryption format

#==============================================================================

 

module RGSSAD

  # Internal RGSSAD file array

  @@files = []

  # XOR magic key

  @@xor = 0xDEADCAFE

  # Determine if RMXP or RMVX is being used (May be flawed)

  ENC_FILE = Object.const_defined?(:Scene_Base) ? 'Game.rgss2a' : 'Game.rgssad'

  # Create RGSSAD file data container

  RGSSAD_File = Struct.new('RGSSAD_File', :filename, :filename_size, :file, 

                           :file_size)

  # Public methods

  public

  #----------------------------------------------------------------------------

  # * RGSSAD.decrypt

  #     Decrypts the encrypted archive and stores the file structure in memory

  #----------------------------------------------------------------------------

  def self.decrypt

    # Do no decrypt if there is no encrypted archive

    return unless File.exists?(ENC_FILE)

    # Clear the internal file array

    @@files.clear

    # Initialize the rgssad string

    rgssad = ''

    # Read the encrypted archive

    File.open(ENC_FILE, 'rb') {|file| file.read(8); rgssad = file.read}

    # Parse the encrypted archive

    rgssad = self.parse_rgssad(rgssad, true)

    # Initialize file offset

    offset = 0

    # Read the file until finished

    while rgssad[offset] != nil

      # Create a new RGSSAD file

      file = RGSSAD_File.new

      # Retrieve and store the filename's size

      file.filename_size = rgssad[offset, 4].unpack('L')[0]

      offset += 4

      # Retrieve and store the filename

      file.filename = rgssad[offset, file.filename_size]

      offset += file.filename_size

      # Retrieve and store the file's size

      file.file_size = rgssad[offset, 4].unpack('L')[0]

      offset += 4

      # Retrieve and store the file's contents

      file.file = rgssad[offset, file.file_size]

      # Add the file to the array

      @@files << file

      offset += file.file_size

    end

  end

  #----------------------------------------------------------------------------

  # * RGSSAD.add_file(file_contents, filename)

  #     file_contents : The contents of the file to be save, usually in Marshal

  #                     format

  #     filename      : The filename to be used in the encrypted archive

  #----------------------------------------------------------------------------

  def self.add_file(file_contents, filename)

    # Initialize a new RGSSAD file

    file = RGSSAD_File.new

    # Set the appropriate file variables

    file.filename = filename

    file.filename_size = filename.size

    file.file = file_contents

    file.file_size = file_contents.size

    # Delete any old versions of the file in the file array

    @@files.delete_if{|f| f.filename == file.filename}

    # Add the new file

    @@files << file

    # Sort the files by name

    @@files.sort!{|a,b| a.filename <=> b.filename}

  end

  #----------------------------------------------------------------------------

  # * RGSSAD.encrypt

  #     Encrypts the contents of the internal file array into a new archive

  #----------------------------------------------------------------------------

  def self.encrypt

    # Do not encrypt if there is nothing to encrypt

    return if @@files.empty? && !File.exists?(ENC_FILE)

    # Initialize a new string

    rgssad = ''

    # Iterate through each RGSSAD file in the internal file array

    @@files.each do |file|

      # Pack the filename size

      rgssad += [file.filename_size].pack('L')

      # Add the filename

      rgssad += file.filename

      # Pack the file size

      rgssad += [file.file_size].pack('L')

      # Add the file's contents

      rgssad += file.file

    end

    # Open up the encrypted archive for binary writing

    File.open(ENC_FILE, 'wb') do |file|

      # Write the RGSSAD header

      file.print("RGSSAD\0\1")

      # Parse and write the string

      file.print(self.parse_rgssad(rgssad, false))

    end

  end

  # Private methods

  private

  #----------------------------------------------------------------------------

  # * RGSSAD.next_key (Private)

  #     Iterates the XOR magic key and updates the screen if RMXP is used

  #----------------------------------------------------------------------------

  def self.next_key

    # Iterate the XOR magic key

    @@xor = (@@xor * 7 + 3) & 0xFFFFFFFF

    # Update graphics (RMXP only)

    Graphics.update if ENC_File == 'Game.rgssad'

  end

  #----------------------------------------------------------------------------

  # * RGSSAD.parse_rgssad(string, decypt)  => aString

  #     string  : A string that the RGSSAD encryption format is applied to

  #     decrypt : (true/false) A flag telling the function if string is to be

  #               decrypted instead of encrypted

  #----------------------------------------------------------------------------

  def self.parse_rgssad(string, decrypt)

    # Reset the XOR magic key

    @@xor = 0xDEADCAFE

    # Intialize a new string

    new_string = ''

    # Initialize the offset

    offset = 0

    # Initialize key value arrays

    remember_offsets = []

    remember_keys = []

    remember_size = []

    # Continue reading until finished

    while string[offset] != nil

      # Retrieve the filename size

      namesize = string[offset, 4].unpack('L')[0]

      # Write the XOR'ed filename size to the new string

      new_string << [namesize ^ @@xor].pack('L')

      # XOR the namesize value if decrypting

      namesize ^= @@xor if decrypt

      # Update the offset

      offset += 4

      # Next key iteration

      self.next_key

      # Retrieve the filename

      filename = string[offset, namesize]

      # XOR each character in the filename

      namesize.times do |i|

        filename[i] ^= @@xor & 0xFF

        # Next key iteration

        self.next_key

      end

      # Add the filename to the new string

      new_string << filename

      # Update the offset

      offset += namesize

      # Retrieve the data size

      datasize = string[offset, 4].unpack('L')[0]

      # Write the XOR'ed data size to the new string

      new_string << [datasize ^ @@xor].pack('L')

      # XOR the data size if decrypting

      datasize ^= @@xor if decrypt

      # Remember the size of the data

      remember_size << datasize

      # Update the offset

      offset += 4

      # Next key iteration

      self.next_key

      # Retrieve the file data

      data = string[offset, datasize]

      # Write the data to the new string (unchanged)

      new_string << data

      # Remember the offset and last XOR magic key

      remember_offsets << offset

      remember_keys << @@xor

      # Update the offset

      offset += datasize

    end

    # For the number of saved values

    remember_offsets.size.times do |i|

      # Recall the remembered values

      offset = remember_offsets[i]

      @@xor = remember_keys[i]

      size = remember_size[i]

      # Read the unchanged data

      data = new_string[offset, size]

      # Append NUL spaces to the end of the data (Ruby's limitation with unpack)

      data = data.ljust(size + (4 - size % 4)) if size % 4 != 0

      # Initialize new string

      s = ''

      # For every 4 characters in the file

      data.unpack('L' * (data.size / 4)).each do |j|

        # Write the 4 XOR'ed characters to the new string

        s += ([j ^ @@xor].pack('L'))

        # Next key iteration

        self.next_key

      end

      # Replace the unchanged data with the new one

      new_string[offset, size] = s.slice(0, size)

    end

    # Return the parsed string

    return new_string

  end

end

 

#==============================================================================

# ** Kernel

#------------------------------------------------------------------------------

#  This is a core module, providing functions to every instance of Object

#==============================================================================

 

module Kernel

  alias :original_save_data :save_data

  #----------------------------------------------------------------------------

  # * save_data(obj, filename)

  #     obj      : An instance of Object to be saved to the encrypted archive

  #     filename : The destination name of the file to be saved

  #----------------------------------------------------------------------------

  def save_data(obj, filename)

    # If there is an encrypted archive, preform new methods

    if File.exists?(RGSSAD::ENC_FILE)

      # Decrypt the encrypted archive into memory (SLOW)

      RGSSAD.decrypt

      # Add the Marshalled object into the RGSSAD file array

      RGSSAD.add_file(Marshal.dump(obj), filename)

      # Encrypt the new archive (SLOW)

      RGSSAD.encrypt

    else

      # Use the old save_data if no encrypted archive is present

      original_save_data(obj, filename)

    end

  end

end

Instructions

Place the script above Main. The script will automatically determine if RMXP or RMVX is being used.

FAQ

Q: The game freezes for a few seconds when I used this! What's going on here?
A: Because of the way the encrypted format works, I have to decrypt and encrypt the entire archive whenever you save to it. A way to reduce this lag is to not use save_data, but do this:
Code:
RGSSAD.decrypt

RGSSAD.add_file(someObject1, 'Data/File1.extension')

RGSSAD.add_file(someObject2, 'Data/File2.extension')

# etc.

RGSSAD.encrypt

-No further questions-

Compatibility

Should be compatible with everything.

Credits and Thanks

Thanks to that guy who figured out the RGSSAD encryption format, and vgvgf for linking to his work.

Author's Notes

This script is slow. Very slow. The reason to why is because the entire encrypted archive must be parsed once to get all of its contents, and parsed again to save it. Each parse can take a while depending on number of files and the size of the files. Because of this, and the way RMXP will lock up, I have the script call Graphics.update every time the encryption key is updated.

Terms and Conditions

Because this script has methods to read encrypted archives, please don't modify or use this script to steal, look at, or otherwise decrypt other people's games and resources without proper consent.
 

Thank you for viewing

HBGames is a leading amateur video game development forum and Discord server open to all ability levels. Feel free to have a nosey around!

Discord

Join our growing and active Discord server to discuss all aspects of game making in a relaxed environment. Join Us

Content

  • Our Games
  • Games in Development
  • Emoji by Twemoji.
    Top