Resource icon

Automatic Backup System

Pokémon Essentials Version
v17.2 ✅
My computer crashed two hours ago. Luckily, nothing got corrupt (which can happen when you have RPG Maker open when shutting off your PC!), but it got me thinking. I don't have any backups, and I'm too lazy/forgetful to make them myself.

So that's why I made this script. Once a day, it will make a backup with everything you tell it to. I will likely add more options to this later.

You can tell it to back up these things:
  • MAPS
  • SCRIPTS
  • PBS
  • GRAPHICS_PICTURES
  • GRAPHICS_AUTOTILES
  • GRAPHICS_TILESETS
  • AUDIO_BGM
  • AUDIO_BGS
  • AUDIO_ME
  • AUDIO_SE
  • ICONS (Please read the top of the script for a disclaimer for this option!)

It backs up whenever it detects the last backup was made on a different (earlier!) day.
All files will be stored inside the folder you give it; if you set BACKUP_TO to "Backups", it'll create a new folder in the game folder and call it "Backups". If you give it a full path (C:\Users\....\......, etc.), it'll make a folder there if it doesn't exist and put all files in there.

It will ONLY make backups when you're in Debug mode, as the file system changes whenever a game is encrypted.

Put this in a new section above main, and you should see it work.

Code:
#==============================================================================#
#                           Automatic Backup System                            #
#                                  by Marin                                    #
#                                 Version 1.1                                  #
#==============================================================================#
# You can manually make a backup by calling this method.
# In an event this would be :
# Script: backup
def backup(forced = true)
  BackupHandler.new(forced) { |txt| Win32API.SetWindowText(_INTL(txt)) }
end

class BackupHandler
  # This is the folder the backup will be made in. This can be relative
  # (in the folder itself), or a full path (C:\Users\....\Desktop)
  # If the LAST FOLDER does not exist, it will be created. The rest has to be
  # existent and valid.
  # Examples:
  # BACKUP_TO = "C:/Users/MyUser/Desktop/MyBackupFolder"
  # BACKUP_TO = "Game Backups"
  BACKUP_TO = "Backups"
  
  # Whatever is in this array will determine what will be backed up whenever a
  # backup is being made.
  # Possible values:
  # :MAPS
  # :SCRIPTS
  # :PBS
  # :GRAPHICS_PICTURES
  # :GRAPHICS_AUTOTILES
  # :GRAPHICS_TILESETS
  
  # The following can be used too, but they may cause a bit of delay.
  # :AUDIO_BGM
  # :AUDIO_BGS
  # :AUDIO_ME
  
  # The following can be used too, but they will likely cause a lot of lag and
  # have the potential to crash the game (this does not/should not have any
  # consequences; it will just mean the backup failed (Script is hanging))
  # If it does hang, it has likely still properly copied all Icon/AudioSE files.
  # These two are done last, meaning that all other backups will have been
  # successfully made.
  # :AUDIO_SE
  # :ICONS
  
  SHOULD_BACKUP = [
    :MAPS,
    :SCRIPTS,
    :PBS,
    :AUDIO_BGM,
    :AUDIO_BGS,
    :AUDIO_ME,
    :AUDIO_SE,
    :GRAPHICS_PICTURES,
    :GRAPHICS_AUTOTILES,
    :GRAPHICS_TILESETS,
    :ICONS
  ]
  
  def initialize(forced)
    return if !$DEBUG
    @path = BACKUP_TO
    @make_backup = shouldBackup
    @make_backup = true if forced
    if @make_backup
      yield _INTL("Clearing old backup folders...")
      empty("Data")
      empty("PBS")
      empty("Audio")
      empty("Graphics")
      if SHOULD_BACKUP.include?(:SCRIPTS)
        yield _INTL("Creating backups of Data/Scripts.rxdata...")
        backupScripts
      end
      if SHOULD_BACKUP.include?(:MAPS)
        yield _INTL("Creating backups of Data/MapXXX.rxdata...")
        backupMaps
      end
      if SHOULD_BACKUP.include?(:PBS)
        yield _INTL("Creating backups of PBS...")
        backupPBS
      end
      if SHOULD_BACKUP.include?(:AUDIO_BGM)
        yield _INTL("Creating backups of Audio/BGM...")
        backupRec("Audio", "BGM")
      end
      if SHOULD_BACKUP.include?(:AUDIO_BGS)
        yield _INTL("Creating backups of Audio/BGS...")
        backupRec("Audio", "BGS")
      end
      if SHOULD_BACKUP.include?(:AUDIO_ME)
        yield _INTL("Creating backups of Audio/ME...")
        backupRec("Audio", "ME")
      end
      if SHOULD_BACKUP.include?(:AUDIO_SE)
        yield _INTL("Creating backups of Audio/SE...")
        backupRec("Audio", "SE")
      end
      if SHOULD_BACKUP.include?(:GRAPHICS_PICTURES)
        yield _INTL("Creating backups of Graphics/Pictures...")
        backupRec("Graphics", "Pictures")
      end
      if SHOULD_BACKUP.include?(:GRAPHICS_AUTOTILES)
        yield _INTL("Creating backups of Graphics/Autotiles...")
        backupRec("Graphics", "Autotiles")
      end
      if SHOULD_BACKUP.include?(:GRAPHICS_TILESETS)
        yield _INTL("Creating backups of Graphics/Tilesets...")
        backupRec("Graphics", "Tilesets")
      end
      if SHOULD_BACKUP.include?(:ICONS)
        yield _INTL("Creating backups of Graphics/Icons...")
        backupRec("Graphics", "Icons")
      end
    end
  end  
  
  def backupScripts
    copy("Data/Scripts.rxdata", @path+"/Data/Scripts.rxdata")
  end
  
  def backupMaps
    Dir.foreach("Data") do |f|
      next if f == '.' || f == '..'
      if f[0..2] == 'Map' # Including MapInfos.rxdata as well
        copy("Data/#{f}", @path+"/Data/#{f}")
      end
    end
  end
  
  def backupPBS
    Dir.foreach("PBS") do |f|
      next if f == '.' || f == '..'
      copy("PBS/#{f}", @path+"/PBS/#{f}")
    end
  end
  
  # Recursive and dynamic
  def backupRec(one, two)
    return false if !File.directory?(one)
    if !File.directory?(@path+"/"+one)
      Dir.mkdir(@path+"/"+one)
    end
    empty(one+"/"+two)
    Dir.glob("#{one}/#{two}/**/*") do |f|
      next if !File.directory?(f)
      if !File.directory?("#{@path}/#{f}")
        Dir.mkdir("#{@path}/#{f}")
      end
    end
    Dir.glob("#{one}/#{two}/**/*") do |f|
      next if File.directory?(f)
      copy(f, "#{@path}/#{f}")
    end
  end
  
  # Determines if it should update again
  def shouldBackup
    ret = false
    ensureExists
    if !File.file?(@path+"/last.txt")
      f = File.open(@path+"/last.txt", "w")
      f.write(getTime)
      f.close
      ret = true
    else
      f = File.open(@path+"/last.txt")
      d = f.read
      f.close
      old = d.split('-').map { |e| e.to_i }
      old.map
      if old.size != 3
        ret = false
      else
        new = getTime.split('-').map { |e| e.to_i }
        ret = (new[2] > old[2] || new[1] > old[1] || new[0] > old[0])
      end
      if ret
        File.truncate(@path+"/last.txt", 0)
        file = File.open(@path+"/last.txt", "w")
        file.write(getTime)
        file.close
      end
    end
    return ret
  end
  
  def getTime
    t = Time.now
    return "#{t.day}-#{t.month}-#{t.year.to_s[2..3]}"
  end
  
  def ensureExists
    if !File.directory?(@path)
      Dir.mkdir(@path)
    end
  end
  
  def copy(src, dest)
    File.open(src, 'rb') do |r|
      File.open(dest, 'wb') do |w|
        while s = r.read(4096)
          w.write(s)
        end
      end
    end
  end
  
  def empty(path)
    return if !File.directory?(path)
    ensureExists
    if !File.directory?("#{@path}/#{path}")
      Dir.mkdir("#{@path}/#{path}")
    else
      recursiveDelete(@path+"/"+path) # Clear all files first
      recursiveDelete(@path+"/"+path, true) # Then all empty directories
    end
  end
  
  def recursiveDelete(file, delete_dirs = false)
    Dir.glob("#{file}/**/*") do |f|
      if File.directory?(f)
        if delete_dirs
          if Dir.entries(f).size > 2
            recursiveDelete(f, true)
            Dir.delete(f) rescue nil
          else
            Dir.delete(f)
          end
        else
          recursiveDelete(f, delete_dirs)
        end
      else
        File.delete(f)
      end
    end
  end
end

backup(false) # Call the method; determines if it actually SHOULD backup inside the call
Author
Marin
First release
Last update
Rating
0.00 star(s) 0 ratings

More resources from Marin

Latest updates

  1. Update v1.2

    It can no longer crash while copying over files. You can get this update exclusively through...
  2. Update v1.1

    Updated to v1.1: More backup options: Graphics/Pictures, Graphics/Autotiles, Graphics/Tilesets...
Top