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
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:
-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.
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.