Reading files in RPG Maker

File I/O is an important topic when it comes to RPG Maker. Everything that you work with is stored in external files and are loaded by the engine at run-time, whether they are database files, graphics, audio, or your own custom text files. Regardless of your experience with Ruby, there are a couple things in RM that you should know about when you’re working with files.

Loading a File

If you perform a search for tutorials on how to open a file in Ruby, you will come across code snippets like this (taken from RubyMonk):

mode = "r+"
file = File.open("friend-list.txt", mode)
puts file.inspect
puts file.read
file.close

Which is a pretty normal way to open a file and read the contents of the file.  So let’s say you had a CSV file that you use for storing tabular data called “custom_data.csv” stored in the project’s Data folder.

readWriteFiles1

Following the example, you would write some code for opening the file and reading it:

file = File.open("Data/custom_data.csv", "r")
puts file.read
file.close

And then you test it in your project, and it works fine: you’ll see the contents of your file in your console as a string.

readWriteFiles3

Success feels good.

Subtle Troubles

Now you finish your script, did whatever testing you thought was necessary, and now you’re ready to go. So you release it, and someone adds it to their own project. But then they come back to you and tell you the game crashed after they added your script. You ask for a screenshot of the error, and they tell you this

readWriteFiles2

Couldn’t find the file? As an experienced debugger, you ask some preliminary questions in case this was simply a case of user error.

  • Do you have the file in your Data folder? Maybe they put it somewhere else
  • Did you name it correctly? Maybe they made a typo
  • Did you change the script to read a different file? It’s possible.
  • Does it work in a new project? Maybe it’s a script compatibility issue!

They will tell you they did everything correctly. You check your dev project again to make sure everything works. And it does. File’s in the right place, works in a new project.

And then they say something particularly curious:

I encrypted my project

You may have forgot about this case. So you go ahead and produce an encrypted project and you run the game, and as if RM was playing tricks on you, it tells you

readWriteFiles2

Loading Encrypted Files

What happened here? You followed the tutorials that you found online on Stackoverflow or RubyMonk or anywhere else that may be credible. You clearly had a working script, but after encrypting the files RM can’t find it anymore.

There’s one difference between the way files are stored in a encrypted project, and an unencrypted project, and that is the RGSS3A archive.

Basically, File doesn’t know how to find files in your encrypted archive. And neither does Dir for that matter so if you’re using that you might need to re-think your strategy.

Introducing load_data

So how do we load files that are encrypted? Fortunately RM provides a built-in way to load data. You can find this in the help file:

readWriteFiles4

Pretty straightforward. It looks instead of calling `File.open` all we have to do is call `load_data` and pass in our path.

Loading non-Marshal files

But now there’s another subtle issue: what the `load_data` function does is to call `Marshal.load`. If you’re working with one of the RM data files (the ones ending in rxdata, rvdata, or rvdata2), that would work. But if you have for example a plain-text CSV file, that’s not a Marshal serialized file, and so Marshal will fail while trying to load it. If you’re not convinced, you can try loading your CSV file. You won’t get a “file not found” error, but you’ll get a different error.

In this case, we introduce a little script that changes the way Marshal loads files. It first tries to load the file as usual, but if it fails, it then just reads the file as a raw byte string and returns that to you.

class << Marshal
  alias_method(:th_core_load, :load)
  def load(port, proc = nil)
    th_core_load(port, proc)  # usual loading
  rescue TypeError
    if port.kind_of?(File)    # didn't work, so we read it as a raw file
      port.rewind 
      port.read
    else
      port
    end
  end
end unless Marshal.respond_to?(:th_core_load)

I didn’t come up with the technique. I don’t remember who posted it though.

Now you can load your custom csv file and process it like usual, and you will be able to read it even if the project is encrypted! Just do something like this

data = load_data("Data/custom_actors.csv")
p data

Here’s how it looks

readWriteFiles5

Processing the Data

But wait…you may have noticed a few things that are different between the output from load_data and the output from File#read

  • There are some weird characters at the front
  • Instead of 3 lines, you have one long line

So when I said you can just “process it like usual” it’s not completely true.

The strange characters you see in the beginning is the byte-order mark for your document. If you saved it as UTF8, this is what you will likely see. One big difference you will run into is that all of the data is read in binary mode, and not text mode. This means that you will see all of the raw bytes as they are stored in your file (new-lines, carriage returns, byte-order marks, etc). You will need to process these yourself somehow, and that’s a lot of extra work.

If you need UTF8 documents, consider saving them in “UTF8 without BOM”. Some text editors support this (notepad++ for example). This will get rid of those three bytes in the beginning.

Next, your data is read as a single string. You can split on end-of-line characters such as \r\n, and then iterate over each line.

data = load_data("Data/custom_actors.csv")
data.split(/\r?\n/).each do |line|
  p line
end

Now you should be able to process it like usual.

Closing

Reading a file in RM is pretty much the same as reading a file in any other Ruby-based application. However, the one thing that you need to keep in mind is that the extra layer of encryption requires you to use functions provided by RM to properly access those files, and that you will need to test that your scripts work in encrypted projects. However, with the technique that I’ve outlined in this tutorial, you should be able to work with custom files without having to worry about whether the user’s project will be encrypted or not.

Spread the Word

If you liked the post and feel that others could benefit from it, consider tweeting it or sharing it on whichever social media channels that you use. You can also follow @HimeWorks on Twitter or like my Facebook page to get the latest updates or suggest topics that you would like to read about.

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *