#=============================================================================== # 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, gets around accents #----------------------------------------------------------------------------- def self.safe?(dir) return false if !FileTest.directory?(dir) ret = false self.chdir(dir) { ret = true } rescue nil return ret end #----------------------------------------------------------------------------- end #=============================================================================== # extensions for file class #=============================================================================== class File #----------------------------------------------------------------------------- # Checks for existing file, gets around accents #----------------------------------------------------------------------------- def self.safe?(file) ret = false self.open(file, "rb") { ret = true } rescue nil return ret end #----------------------------------------------------------------------------- end #=============================================================================== # Checking for files and directories #=============================================================================== # Works around a problem with FileTest.directory if directory contains accent marks def safeIsDirectory?(f) ret = false Dir.chdir(f) { ret = true } rescue nil return ret end # Works around a problem with FileTest.exist if path contains accent marks def safeExists?(f) return FileTest.exist?(f) if f[/\A[\x20-\x7E]*\z/] ret = false begin File.open(f, "rb") { ret = true } rescue Errno::ENOENT, Errno::EINVAL, Errno::EACCES ret = false end return ret end # 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) { afterChdir = true Dir.glob(wildcard) { |f| ret.push(dir + "/" + f) } } 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" return RTP.getPath("Audio/SE/" + file, ["", ".wav", ".ogg"]) # ".mp3" 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) { |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 } 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) { |path| return true if safeExists?(path) extensions.each do |ext| return true if safeExists?(path + ext) 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", ".wma", ".mid", ".ogg", ".midi"]) # ".mp3" end def self.getPath(filename, extensions = []) return filename if nil_or_empty?(filename) eachPathFor(filename) { |path| return path if safeExists?(path) extensions.each do |ext| file = path + ext return file if safeExists?(file) 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 { |path| if path == "./" yield filename else yield path + filename 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 = [".mid", ".midi", ".ogg", ".wav", ".wma"] # ".mp3" 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) if safeExists?("./Game.rgssad") return pbGetFileChar(filename) != nil else filename = canonicalize(filename) return safeExists?(filename) end 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 !safeExists?("./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 !safeExists?("./Game.rgssad") return nil if !safeExists?(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 !safeExists?("./Game.rgssad") return nil if !safeExists?(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 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 attr_reader :lineno, :string 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