#=============================================================================== # Reads files of certain format from a directory #=============================================================================== class Dir # Reads all files in a directory def self.get(dir, filters = "*", full = true) files = [] filters = [filters] if !filters.is_a?(Array) self.chdir(dir) do filters.each do |filter| self.glob(filter) { |f| files.push(full ? (dir + "/" + f) : f) } end end return files.sort end # Generates entire file/folder tree from a certain directory def self.all(dir, filters = "*", full = true) # sets variables for starting files = [] subfolders = [] self.get(dir, filters, full).each do |file| # engages in recursion to read the entire file tree if self.safe?(file) # Is a directory subfolders += self.all(file, filters, full) else # Is a file files += [file] end end # returns all found files return files + subfolders end # Checks for existing directory def self.safe?(dir) return FileTest.directory?(dir) end # Creates all the required directories for filename path def self.create(path) path.gsub!("\\", "/") # Windows compatibility # get path tree dirs = path.split("/") full = "" dirs.each do |dir| full += dir + "/" # creates directories self.mkdir(full) if !self.safe?(full) end end # Generates entire folder tree from a certain directory def self.all_dirs(dir) # sets variables for starting dirs = [] self.get(dir, "*", true).each do |file| # engages in recursion to read the entire folder tree dirs += self.all_dirs(file) if self.safe?(file) end # returns all found directories return dirs.length > 0 ? (dirs + [dir]) : [dir] end # Deletes all the files in a directory and all the sub directories (allows for non-empty dirs) def self.delete_all(dir) # delete all files in dir self.all(dir).each { |f| File.delete(f) } # delete all dirs in dir self.all_dirs(dir).each { |f| Dir.delete(f) } end end #=============================================================================== # Checking for files and directories #=============================================================================== # Similar to "Dir.glob", but designed to work around a problem with accessing # files if a path contains accent marks. # "dir" is the directory path, "wildcard" is the filename pattern to match. def safeGlob(dir, wildcard) ret = [] afterChdir = false begin Dir.chdir(dir) do afterChdir = true Dir.glob(wildcard) { |f| ret.push(dir + "/" + f) } end rescue Errno::ENOENT raise if afterChdir end if block_given? ret.each { |f| yield(f) } end return (block_given?) ? nil : ret end def pbResolveAudioSE(file) return nil if !file if RTP.exists?("Audio/SE/" + file, ["", ".wav", ".ogg", ".mp3", ".wma"]) return RTP.getPath("Audio/SE/" + file, ["", ".wav", ".ogg", ".mp3", ".wma"]) end return nil end # Finds the real path for an image file. This includes paths in encrypted # archives. Returns nil if the path can't be found. def pbResolveBitmap(x) return nil if !x noext = x.gsub(/\.(bmp|png|gif|jpg|jpeg)$/, "") filename = nil # RTP.eachPathFor(x) { |path| # filename = pbTryString(path) if !filename # filename = pbTryString(path + ".gif") if !filename # } RTP.eachPathFor(noext) do |path| filename = pbTryString(path + ".png") if !filename filename = pbTryString(path + ".gif") if !filename # filename = pbTryString(path + ".jpg") if !filename # filename = pbTryString(path + ".jpeg") if !filename # filename = pbTryString(path + ".bmp") if !filename end return filename end # Finds the real path for an image file. This includes paths in encrypted # archives. Returns _x_ if the path can't be found. def pbBitmapName(x) ret = pbResolveBitmap(x) return (ret) ? ret : x end def strsplit(str, re) ret = [] tstr = str while re =~ tstr ret[ret.length] = $~.pre_match tstr = $~.post_match end ret[ret.length] = tstr if ret.length return ret end def canonicalize(c) csplit = strsplit(c, /[\/\\]/) pos = -1 ret = [] retstr = "" csplit.each do |x| if x == ".." if pos >= 0 ret.delete_at(pos) pos -= 1 end elsif x != "." ret.push(x) pos += 1 end end ret.length.times do |i| retstr += "/" if i > 0 retstr += ret[i] end return retstr end #=============================================================================== # #=============================================================================== module RTP @rtpPaths = nil def self.exists?(filename, extensions = []) return false if nil_or_empty?(filename) eachPathFor(filename) do |path| return true if FileTest.exist?(path) extensions.each do |ext| return true if FileTest.exist?(path + ext) end end return false end def self.getImagePath(filename) return self.getPath(filename, ["", ".png", ".gif"]) # ".jpg", ".jpeg", ".bmp" end def self.getAudioPath(filename) return self.getPath(filename, ["", ".wav", ".ogg", ".mp3", ".midi", ".mid", ".wma"]) end def self.getPath(filename, extensions = []) return filename if nil_or_empty?(filename) eachPathFor(filename) do |path| return path if FileTest.exist?(path) extensions.each do |ext| file = path + ext return file if FileTest.exist?(file) end end return filename end # Gets the absolute RGSS paths for the given file name def self.eachPathFor(filename) return if !filename if filename[/^[A-Za-z]\:[\/\\]/] || filename[/^[\/\\]/] # filename is already absolute yield filename else # relative path RTP.eachPath do |path| if path == "./" yield filename else yield path + filename end end end end # Gets all RGSS search paths. # This function basically does nothing now, because # the passage of time and introduction of MKXP make # it useless, but leaving it for compatibility # reasons def self.eachPath # XXX: Use "." instead of Dir.pwd because of problems retrieving files if # the current directory contains an accent mark yield ".".gsub(/[\/\\]/, "/").gsub(/[\/\\]$/, "") + "/" end def self.getSaveFileName(fileName) File.join(getSaveFolder, fileName) end def self.getSaveFolder # MKXP makes sure that this folder has been created # once it starts. The location differs depending on # the operating system: # Windows: %APPDATA% # Linux: $HOME/.local/share # macOS (unsandboxed): $HOME/Library/Application Support System.data_directory end end #=============================================================================== # #=============================================================================== module FileTest IMAGE_EXTENSIONS = [".png", ".gif"] # ".jpg", ".jpeg", ".bmp", AUDIO_EXTENSIONS = [".wav", ".ogg", ".mp3", ".midi", ".mid", ".wma"] def self.audio_exist?(filename) return RTP.exists?(filename, AUDIO_EXTENSIONS) end def self.image_exist?(filename) return RTP.exists?(filename, IMAGE_EXTENSIONS) end end #=============================================================================== # #=============================================================================== # Used to determine whether a data file exists (rather than a graphics or # audio file). Doesn't check RTP, but does check encrypted archives. # NOTE: pbGetFileChar checks anything added in MKXP's RTP setting, and matching # mount points added through System.mount. def pbRgssExists?(filename) return !pbGetFileChar(filename).nil? if FileTest.exist?("./Game.rgssad") filename = canonicalize(filename) return FileTest.exist?(filename) end # Opens an IO, even if the file is in an encrypted archive. # Doesn't check RTP for the file. # NOTE: load_data checks anything added in MKXP's RTP setting, and matching # mount points added through System.mount. def pbRgssOpen(file, mode = nil) # File.open("debug.txt", "ab") { |fw| fw.write([file, mode, Time.now.to_f].inspect + "\r\n") } if !FileTest.exist?("./Game.rgssad") if block_given? File.open(file, mode) { |f| yield f } return nil else return File.open(file, mode) end end file = canonicalize(file) Marshal.neverload = true str = load_data(file, true) if block_given? StringInput.open(str) { |f| yield f } return nil else return StringInput.open(str) end end # Gets at least the first byte of a file. Doesn't check RTP, but does check # encrypted archives. def pbGetFileChar(file) canon_file = canonicalize(file) if !FileTest.exist?("./Game.rgssad") return nil if !FileTest.exist?(canon_file) return nil if file.last == "/" # Is a directory begin File.open(canon_file, "rb") { |f| return f.read(1) } # read one byte rescue Errno::ENOENT, Errno::EINVAL, Errno::EACCES, Errno::EISDIR return nil end end str = nil begin str = load_data(canon_file, true) rescue Errno::ENOENT, Errno::EINVAL, Errno::EACCES, Errno::EISDIR, RGSSError, MKXPError str = nil end return str end def pbTryString(x) ret = pbGetFileChar(x) return nil_or_empty?(ret) ? nil : x end # Gets the contents of a file. Doesn't check RTP, but does check # encrypted archives. # NOTE: load_data will check anything added in MKXP's RTP setting, and matching # mount points added through System.mount. def pbGetFileString(file) file = canonicalize(file) if !FileTest.exist?("./Game.rgssad") return nil if !FileTest.exist?(file) begin File.open(file, "rb") { |f| return f.read } # read all data rescue Errno::ENOENT, Errno::EINVAL, Errno::EACCES return nil end end str = nil begin str = load_data(file, true) rescue Errno::ENOENT, Errno::EINVAL, Errno::EACCES, RGSSError, MKXPError str = nil end return str end #=============================================================================== # #=============================================================================== class StringInput include Enumerable attr_reader :lineno, :string class << self def new(str) if block_given? begin f = super yield f ensure f&.close end else super end end alias open new end def initialize(str) @string = str @pos = 0 @closed = false @lineno = 0 end def inspect return "#<#{self.class}:#{@closed ? 'closed' : 'open'},src=#{@string[0, 30].inspect}>" end def close raise IOError, "closed stream" if @closed @pos = nil @closed = true end def closed?; @closed; end def pos raise IOError, "closed stream" if @closed [@pos, @string.size].min end alias tell pos def rewind; seek(0); end def pos=(value); seek(value); end def seek(offset, whence = IO::SEEK_SET) raise IOError, "closed stream" if @closed case whence when IO::SEEK_SET then @pos = offset when IO::SEEK_CUR then @pos += offset when IO::SEEK_END then @pos = @string.size - offset else raise ArgumentError, "unknown seek flag: #{whence}" end @pos = 0 if @pos < 0 @pos = [@pos, @string.size + 1].min offset end def eof? raise IOError, "closed stream" if @closed @pos > @string.size end def each(&block) raise IOError, "closed stream" if @closed begin @string.each(&block) ensure @pos = 0 end end def gets raise IOError, "closed stream" if @closed idx = @string.index("\n", @pos) if idx idx += 1 # "\n".size line = @string[@pos...idx] @pos = idx @pos += 1 if @pos == @string.size else line = @string[@pos..-1] @pos = @string.size + 1 end @lineno += 1 line end def getc raise IOError, "closed stream" if @closed ch = @string[@pos] @pos += 1 @pos += 1 if @pos == @string.size ch end def read(len = nil) raise IOError, "closed stream" if @closed if !len return nil if eof? rest = @string[@pos...@string.size] @pos = @string.size + 1 return rest end str = @string[@pos, len] @pos += len @pos += 1 if @pos == @string.size str end alias read_all read alias sysread read end