module RMXPExtractor

Constants

VERSION

Public Class Methods

export() click to toggle source
# File lib/rmxp_extractor/data_export.rb, line 2
def self.export
  STDERR.puts "No Data Directory!" unless Dir.exists? "./Data"
  exit 1 unless Dir.exists? "./Data"

  require "json"
  require "ruby-progressbar"
  require "fileutils"
  require "pathname"
  require "readline"
  require_relative "classnames"
  require_relative "script_handler"

  window_size = (Readline.get_screen_size[1] - 1).clamp(0, 500)
  progress = ProgressBar.create(
    format: "%a /%e |%B| %p%% %c/%C %r files/sec %t",
    starting_at: 0,
    total: nil,
    output: $stderr,
    length: window_size,
    title: "Exported",
    remainder_mark: "\e[0;30mâ–ˆ\e[0m",
    progress_mark: "â–ˆ",
    unknown_progress_animation_steps: ["==>", ">==", "=>="],
  )
  Dir.mkdir "./Data_JSON" unless Dir.exists? "./Data_JSON"
  paths = Pathname.glob(("./Data/" + ("*" + ".rxdata")))
  count = paths.size
  progress.total = count
  paths.each_with_index do |path, i|
    content = Hash.new

    name = path.basename(".rxdata")
    rxdata = Marshal.load(path.read(mode: "rb"))
    #puts name.to_s
    case name.to_s
    when "xScripts"
      RMXPExtractor.rpgscript("./", "./Scripts", true)
      content[:version] = VERSION
    when "Scripts"
      content[:version] = VERSION
    when "System"
      content = rxdata.hash
      content[:version] = VERSION
    when "MapInfos"
      content = { mapinfos: {} }
      mapinfos = rxdata.sort_by { |key, value| value.order }.to_h
      mapinfos.each do |key, value|
        content[:mapinfos][key] = value.hash
      end
      content[:version] = VERSION
    when /^Map\d+$/
      content = rxdata.hash
      content[:version] = VERSION
    else
      content[name.to_s.downcase] = []
      rxdata.each_with_index do |value|
        content[name.to_s.downcase] << value.hash unless value == nil
      end
      content[:version] = VERSION
    end

    json = File.open("./Data_JSON/" + name.sub_ext(".json").to_s, "wb")
    #puts content
    json.puts JSON.pretty_generate(content)

    progress.increment
  end
end
import() click to toggle source
# File lib/rmxp_extractor/data_import.rb, line 2
def self.import
  STDERR.puts "No Data_JSON Directory!" unless Dir.exists? "./Data_JSON"
  exit 1 unless Dir.exists? "./Data_JSON"

  require "oj"
  require "ruby-progressbar"
  require "fileutils"
  require "pathname"
  require "readline"
  require_relative "classnames"
  require_relative "script_handler"

  window_size = (Readline.get_screen_size[1] - 1).clamp(0, 500)
  progress = ProgressBar.create(
    format: "%a /%e |%B| %p%% %c/%C %r files/sec %t",
    starting_at: 0,
    total: nil,
    output: $stderr,
    length: window_size,
    title: "Imported",
    remainder_mark: "\e[0;30mâ–ˆ\e[0m",
    progress_mark: "â–ˆ",
    unknown_progress_animation_steps: ["==>", ">==", "=>="],
  )
  Dir.mkdir "./Data" unless Dir.exists? "./Data"
  paths = Pathname.glob(("./Data_JSON/" + ("*" + ".json")))
  count = paths.size
  progress.total = count
  paths.each_with_index do |path, i|
    content = Hash.new

    name = path.basename(".json")
    json = Oj.load path.read(mode: "rb")

    puts "\n\e[33;1mWARNING: Incompatible version format in #{name.to_s}!\e[0m\n" if json["version"] != VERSION

    case name.to_s
    when "xScripts"
      RMXPExtractor.rpgscript("./", "./Scripts")
      progress.increment
      return
    when "Scripts"
    when "System"
      content = RPG::System.new(json)
    when "MapInfos"
      content = {}
      json["mapinfos"].each do |key, value|
        content[key.to_i] = RPG::MapInfo.new(value) unless key == "version"
      end
      #when "CommonEvents"
    when /^Map\d+$/
      content = RPG::Map.new json
      #! All files that contain an array of subclasses start with nil since they start from 1, not 0
    else
      content = [nil]
      json[name.to_s.downcase].each_with_index do |value, index|
        eval("content << RPG::#{RMXPExtractor.singularize(name.to_s)}.new(value)")
      end
    end

    rxdata = File.open("./Data/" + name.sub_ext(".rxdata").to_s, "wb")
    rxdata.puts Marshal.dump(content)

    progress.increment
  end
end
process(type) click to toggle source
# File lib/rmxp_extractor.rb, line 12
def self.process(type)
  RMXPExtractor.usage if type.length < 1

  if type[0] == "import"
    RMXPExtractor.import
  elsif type[0] == "export"
    RMXPExtractor.export
  elsif type[0] == "scripts"
    if type[3] == "x"
      RMXPExtractor.rpgscript(type[2], type[1], true)
    elsif type.length < 3 || type.length > 4
      STDERR.puts "usage: rmxp_extractor scripts scripts_dir game_dir [x]"
      exit 1
    elsif type[3] == nil
      RMXPExtractor.rpgscript(type[2], type[1], false)
    end
  else
    RMXPExtractor.usage
  end
end
rpgscript(game_dir, scripts_dir, extract = false) click to toggle source
# File lib/rmxp_extractor/script_handler.rb, line 5
def self.rpgscript(game_dir, scripts_dir, extract = false)
  # Determine version of game engine
  game_data_dir = File.join(game_dir, "Data")
  unless Dir.exist? game_data_dir
    STDERR.puts "error: #{game_dir} does not have a Data subdirectory"
    exit 1
  end

  target_path = nil
  Dir.entries(game_data_dir).each do |e|
    ext = File.extname(e)
    if ext =~ /\.r[xv]data2?/
      target_path = File.join(game_data_dir, "xScripts" + ext)
      break
    end
  end

  unless target_path
    STDERR.puts "warning: could not determine game engine version, assuming XP"
    target_path = File.join(game_data_dir, "xScripts.rxdata")
  end

  # Generate path of script list
  list_path = File.join(scripts_dir, "_scripts.txt")

  if extract
    # Make sure the script directory exists
    Dir.mkdir(scripts_dir) unless Dir.exists? scripts_dir

    # Keep track of names of scripts extracted so we can warn about duplicates
    names = Hash.new(0)

    # Read scripts
    File.open(target_path, "rb") do |fin|
      File.open(list_path, "w") do |flist|
        Marshal.load(fin).each_with_index do |script, index|
          name = script[1].strip
          data = Zlib::Inflate.inflate(script[2]).rstrip
            .gsub(/[ \t]*(?:$|\r\n?)/, "\n")

          # Make sure this file doesn't already exist
          if name.empty?
            if data.empty? || data == "\n"
              flist.puts
              next
            else
              name = "UNTITLED"
            end
          end

          names[name] += 1
          if names[name] > 1
            name << " (#{names[name]})"
          end

          if data.empty? || data == "\n"
            # Treat this like a comment
            flist.puts("# " + name)
          else
            # Write to file order list
            flist.puts(name)

            # Write script file
            File.open(File.join(scripts_dir, name + ".rb"), "wb") do |fout|
              fout.write(data)
            end
          end
        end
      end
    end
    #puts "#{target_path} extracted."
  else
    # Write scripts
    scripts = []

    IO.foreach(list_path) do |name|
      name.strip!
      next if name.empty? || name.start_with?("#")

      data = File.read(File.join(scripts_dir, name + ".rb")).rstrip.gsub("\n", "\r\n")

      script = Array.new(3)
      script[0] = 0
      script[1] = name
      script[2] = Zlib.deflate(data)
      scripts << script
    end

    File.open(target_path, "wb") { |f| f.write(Marshal.dump(scripts)) }
    #puts "#{target_path} written."
  end
end
singularize(string) click to toggle source
# File lib/rmxp_extractor/data_import.rb, line 69
def self.singularize(string)
  if string.end_with? "ies"
    string.delete_suffix("ies") + "y"
  elsif string.end_with? "tes"
    string.delete_suffix("s")
  elsif string.end_with? "es"
    string.delete_suffix("es")
  elsif string.end_with? "s"
    string.delete_suffix("s")
  else
    string
  end
end
usage() click to toggle source
# File lib/rmxp_extractor.rb, line 7
def self.usage
  STDERR.puts "usage: rmxp_extractor import|export|scripts"
  exit 1
end