123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407 |
- ' Copyright (c) 2020-2023 Bruce A Henderson
- '
- ' This software is provided 'as-is', without any express or implied
- ' warranty. In no event will the authors be held liable for any damages
- ' arising from the use of this software.
- '
- ' Permission is granted to anyone to use this software for any purpose,
- ' including commercial applications, and to alter it and redistribute it
- ' freely, subject to the following restrictions:
- '
- ' 1. The origin of this software must not be misrepresented; you must not
- ' claim that you wrote the original software. If you use this software
- ' in a product, an acknowledgment in the product documentation would be
- ' appreciated but is not required.
- ' 2. Altered source versions must be plainly marked as such, and must not be
- ' misrepresented as being the original software.
- ' 3. This notice may not be removed or altered from any source distribution.
- '
- SuperStrict
- Rem
- bbdoc: IO Abstraction
- End Rem
- Module BRL.IO
- ModuleInfo "Version: 1.02"
- ModuleInfo "License: zlib/libpng"
- ModuleInfo "Copyright: Bruce A Henderson"
- ModuleInfo "History: 1.02"
- ModuleInfo "History: Added PermitSymbolicLinks()"
- ModuleInfo "History: Documented SMaxIO_Stat and added SDateTime getters."
- ModuleInfo "History: 1.01"
- ModuleInfo "History: Added GetWriteDir(), GetRealDir(), IsInit(), GetMountPoint() & SetRoot()"
- ModuleInfo "History: Added Unmount() and GetSearchPath()"
- ModuleInfo "History: 1.00"
- ModuleInfo "History: Initial Release."
- Import "common.bmx"
- Rem
- bbdoc: IO abstraction implementation.
- about:
- End Rem
- Type MaxIO
- Global ioInitialized:Int
- Rem
- bbdoc: Initialises the abstraction layer.
- about: This must be called before any other #MaxIO functions.
- End Rem
- Function Init()
- If Not ioInitialized Then
- If Not bmx_PHYSFS_init() Then
- Throw bmx_PHYSFS_getLastError()
- End If
- End If
- ioInitialized = True
- End Function
- Rem
- bbdoc: Determines if the #MaxIO is initialized.
- returns: non-zero if #MaxIO is initialized, zero if it is not.
- about: Once #Init() returns successfully, this will return non-zero. Before a successful #Init() and after #DeInit() returns
- successfully, this will return zero. This function is safe to call at any time.
- End Rem
- Function IsInit:Int()
- Return ioInitialized And PHYSFS_isInit()
- End Function
-
- Rem
- bbdoc: Deinitializes the abstraction layer.
- about: This closes any files opened via the abstraction layer, blanks the search/write paths, frees memory, and invalidates all of your file handles.
- Note that this call can FAIL if there's a file open for writing that refuses to close (for example, the underlying operating system was
- buffering writes to network filesystem, and the fileserver has crashed, or a hard drive has failed, etc). It is usually best to close
- all write handles yourself before calling this function, so that you can gracefully handle a specific failure.
- Once successfully deinitialized, #Init() can be called again to restart the subsystem. All default API states are restored at this point.
- End Rem
- Function DeInit:Int()
- If ioInitialized Then
- ioInitialized = False
- Return PHYSFS_deinit()
- End If
- End Function
- Rem
- bbdoc: Adds an archive or directory to the search path.
- returns: Nonzero if added to path, zero on failure (bogus archive, dir missing, etc).
- about: If this is a duplicate, the entry is not added again, even though the function succeeds.
- You may not add the same archive to two different mountpoints: duplicate checking is done against the archive and not the mountpoint.
-
- When you mount an archive, it is added to a virtual file system...all files in all of the archives are interpolated into a single
- hierachical file tree. Two archives mounted at the same place (or an archive with files overlapping another mountpoint) may have
- overlapping files: in such a case, the file earliest in the search path is selected, and the other files are inaccessible to the
- application. This allows archives to be used to override previous revisions; you can use the mounting mechanism to place archives
- at a specific point in the file tree and prevent overlap; this is useful for downloadable mods that might trample over application data
- or each other, for example.
- The mountpoint does not need to exist prior to mounting, which is different than those familiar with the Unix concept of "mounting"
- may expect. As well, more than one archive can be mounted to the same mountpoint, or mountpoints and archive contents can overlap...the
- interpolation mechanism still functions as usual.
- End Rem
- Function Mount:Int(newDir:String, mountPoint:String = Null, appendToPath:Int = True)
- Assert ioInitialized Else "MaxIO not initialized"
- If newDir.StartsWith("incbin::") Then
- Return MountIncBin(newDir[8..], mountPoint, appendToPath)
- End If
- Return bmx_PHYSFS_mount(newDir, mountPoint, appendToPath)
- End Function
- Rem
- bbdoc: Adds an incbinned archive to the search path.
- about: See #Mount for more information.
- End Rem
- Function MountIncbin:Int(newDir:String, mountPoint:String = Null, appendToPath:Int = True)
- Assert ioInitialized Else "MaxIO not initialized"
- Assert IncbinPtr(newDir) Else "No Incbin for " + newDir
- Return bmx_PHYSFS_mountMemory(IncbinPtr(newDir), IncbinLen(newDir), newDir, mountPoint, appendToPath)
- End Function
- Rem
- bbdoc: Removes a directory or archive from the search path.
- returns: Nonzero on success, zero on failure. Use #GetLastErrorCode() to obtain the specific error.
- about: This must be a (case-sensitive) match to a dir or archive already in the
- search path, specified in platform-dependent notation.
- This call will fail (and fail to remove from the path) if the element still has files open in it.
- End Rem
- Function Unmount:Int(oldDir:String)
- Return bmx_PHYSFS_unmount(oldDir)
- End Function
- Rem
- bbdoc: Determines a mounted archive's mountpoint.
- returns: The mount point if added to path, or #Null on failure (bogus archive, etc). Use #GetLastErrorCode() to obtain the specific error.
- about: You give this function the name of an archive or dir you successfully
- added to the search path, and it reports the location in the interpolated
- tree where it is mounted. Files mounted with a #Null mountpoint will report "/".
- @dir must exactly match the string used when adding, even if your string would also reference the same file with a different string of characters.
- End Rem
- Function GetMountPoint:String(dir:String)
- Return bmx_PHYSFS_getMountPoint(dir)
- End Function
- Rem
- bbdoc: Makes a subdirectory of an archive its root directory.
- returns: Nonzero on success, zero on failure. Use #GetLastErrorCode() to obtain the specific error.
- about: This lets you narrow down the accessible files in a specific archive.
-
- For example, if you have x.zip with a file in y/z.txt, mounted to /a, if you
- call #SetRoot("x.zip", "/y"), then the call #OpenRead("/a/z.txt") will succeed.
- You can change an archive's root at any time, altering the interpolated
- file tree (depending on where paths shift, a different archive may be
- providing various files). If you set the root to #Null or "/", the
- archive will be treated as if no special root was set (as if the archive
- was just mounted normally).
- Changing the root only affects future operations on pathnames; a file
- that was opened from a path that changed due to a #SetRoot will not be affected.
- Setting a new root is not limited to archives in the search path; you may
- set one on the write dir, too, which might be useful if you have files
- open for write and thus can't change the write dir at the moment.
- It is not an error to set a subdirectory that does not exist to be the
- root of an archive; however, no files will be visible in this case. If
- the missing directories end up getting created (a mkdir to the physical
- filesystem, etc) then this will be reflected in the interpolated tree.
- End Rem
- Function SetRoot:Int(archive:String, subdir:String)
- Return bmx_PHYSFS_setRoot(archive, subdir)
- End Function
- Rem
- bbdoc: Gets the path where the application resides.
- End Rem
- Function GetBaseDir:String()
- Assert ioInitialized Else "MaxIO not initialized"
- Return bmx_PHYSFS_getBaseDir()
- End Function
-
- Rem
- bbdoc: Gets the user-and-app-specific path where files can be written.
- returns: The pref dir in platform-dependent notation, or #Null if there's a problem (creating directory failed, etc).
- about: Get the "pref dir". This is meant to be where users can write personal files (preferences and save games, etc) that are specific
- to your application. This directory is unique per user, per application.
- This function will decide the appropriate location in the native filesystem, create the directory if necessary, and return a string in platform-dependent
- notation, suitable for passing to #SetWriteDir().
- On Windows, this might look like: "C:\\Users\\bob\\AppData\\Roaming\\My Company\\My Program Name"
- On Linux, this might look like: "/home/bob/.local/share/My Program Name"
- On Mac OS X, this might look like: "/Users/bob/Library/Application Support/My Program Name"
- (etc.)
- You should probably use the pref dir for your write dir, and also put it near the beginning of your search path.
- This finds the correct location for whatever platform, which not only changes between operating systems, but also versions of the same operating system.
- You specify the name of your organization (if it's not a real organization, your name or an Internet domain you own might do) and the name of
- your application. These should be proper names.
- Both the (org) and (app) strings may become part of a directory name, so please follow these rules:
- Try to use the same org string (including case-sensitivity) for all your applications that use this function.
- Always use a unique app string for each one, and make sure it never changes for an app once you've decided on it.
- Unicode characters are legal, as long as it's UTF-8 encoded, but...
- ...only use letters, numbers, and spaces. Avoid punctuation like "Game Name 2: Bad Guy's Revenge!" ... "Game Name 2" is sufficient.
-
- > You should assume the path returned by this function is the only safe place to write files (and that #GetBaseDir(),
- while they might be writable, or even parents of the returned path, aren't where you should be writing things)..
- End Rem
- Function GetPrefDir:String(org:String, app:String)
- Return bmx_PHYSFS_getPrefDir(org, app)
- End Function
-
- Rem
- bbdoc: Gets the current search path.
- returns: An array of Strings.
- about: The default search path is an empty list.
- End Rem
- Function GetSearchPath:String[]()
- Return bmx_PHYSFS_getSearchPath()
- End Function
- Rem
- bbdoc: Indicates where files may be written.
- about: Sets a new write dir. This will override the previous setting.
- This call will fail (and fail to change the write dir) if the current write dir still has files open in it.
- The directory will be the root of the write dir, specified in platform-dependent notation.
- Setting to #Null disables the write dir, so no files can be opened for writing.
- End Rem
- Function SetWriteDir:Int(newDir:String)
- Return bmx_PHYSFS_setWriteDir(newDir)
- End Function
- Rem
- bbdoc: Gets the path where files may be written.
- about: Gets the current write dir. The default write dir is #Null.
- End Rem
- Function GetWriteDir:String()
- Return bmx_PHYSFS_getWriteDir()
- End Function
- Rem
- bbdoc: Figures out where in the search path a file resides.
- returns: The file location, or #Null if not found.
- about: The file is specified in platform-independent notation. The returned
- filename will be the element of the search path where the file was found,
- which may be a directory, or an archive. Even if there are multiple
- matches in different parts of the search path, only the first one found
- is used, just like when opening a file.
- End Rem
- Function GetRealDir:String(filename:String)
- Return bmx_PHYSFS_getRealDir(filename)
- End Function
- Rem
- bbdoc: Gets various information about a directory or a file, populating the passed in #SMaxIO_Stat instance.
- about: This function will never follow symbolic links. If you haven't enabled
- symlinks with #PermitSymbolicLinks(), stat'ing a symlink will be treated like stat'ing a non-existant file. If symlinks are enabled,
- stat'ing a symlink will give you information on the link itself and not what it points to.
- End Rem
- Function Stat:Int(filename:String, _stat:SMaxIO_Stat Var)
- Return bmx_PHYSFS_stat(filename, _stat)
- End Function
-
- Rem
- bbdoc: Deletes a file or directory.
- about: @path is specified in platform-independent notation in relation to the write dir.
-
- A directory must be empty before this call can delete it.
- Deleting a symlink will remove the link, not what it points to, regardless of whether you "permitSymLinks" or not.
- So if you've got the write dir set to "C:\mygame\writedir" and call DeletePath("downloads/maps/level1.map") then the file
- "C:\mygame\writedir\downloads\maps\level1.map" is removed from the physical filesystem, if it exists and the operating system permits the deletion.
- Note that on Unix systems, deleting a file may be successful, but the actual file won't be removed until all processes that have
- an open filehandle to it (including your program) close their handles.
- Chances are, the bits that make up the file still exist, they are just made available to be written over at a later point. Don't
- consider this a security method or anything.
- End Rem
- Function DeletePath:Int(path:String)
- Return bmx_PHYSFS_delete(path)
- End Function
-
- Rem
- bbdoc: Opens a file for writing.
- about: Opens a file for writing, in platform-independent notation and in relation to the write dir as the root of the writable filesystem.
- The specified file is created if it doesn't exist. If it does exist, it is truncated to zero bytes, and the writing offset is set to the start.
- Note that entries that are symlinks are ignored if PermitSymbolicLinks(True) hasn't been called, and opening a symlink with this function will
- fail in such a case.
- End Rem
- Function OpenWrite:Byte Ptr(path:String)
- Return bmx_PHYSFS_openWrite(path)
- End Function
-
- Rem
- bbdoc: Opens a file for reading.
- about: Opens a file for reading, in platform-independent notation.
- The search path is checked one at a time until a matching file is found, in which case an abstract filehandle is associated with it, and reading may be done.
- The reading offset is set to the first byte of the file.
- Note that entries that are symlinks are ignored if PHYSFS_permitSymbolicLinks(1) hasn't been called, and opening a symlink with this function will fail in such a case.
- End Rem
- Function OpenRead:Byte Ptr(path:String)
- Return bmx_PHYSFS_openRead(path)
- End Function
-
- Rem
- bbdoc: Closes a file handle.
- about: This call is capable of failing if the operating system was buffering writes to the physical media, and, now forced to write those
- changes to physical media, can not store the data for some reason. In such a case, the filehandle stays open. A well-written program
- should ALWAYS check the return value from the close call in addition to every writing call!
- End Rem
- Function Close:Int(filePtr:Byte Ptr)
- Return PHYSFS_close(filePtr)
- End Function
-
- Rem
- bbdoc: Creates a directory.
- about: This is specified in platform-independent notation in relation to the write dir. All missing parent directories are also created if they don't exist.
- End Rem
- Function MkDir:Int(dirName:String)
- Return bmx_PHYSFS_mkdir(dirName)
- End Function
- Rem
- bbdoc: Returns the last error code.
- about: Calling this function resets the last error code.
- End Rem
- Function GetLastErrorCode:EMaxIOErrorCode()
- Return bmx_PHYSFS_getLastErrorCode()
- End Function
- Rem
- bbdoc: Returns the message for the specified @errorCode.
- End Rem
- Function GetErrorForCode:String(errorCode:EMaxIOErrorCode)
- Return bmx_PHYSFS_getErrorForCode(errorCode)
- End Function
- Rem
- bbdoc: Returns the last error message, or #Null if there is none.
- about: Calling this function resets the last error.
- End Rem
- Function GetLastError:String()
- Return bmx_PHYSFS_getLastError()
- End Function
- Rem
- bbdoc: Enables or disables following of symbolic links.
- about: Some physical filesystems and archives contain files that are just pointers
- to other files. On the physical filesystem, opening such a link will
- (transparently) open the file that is pointed to.
- By default, MaxIO will check if a file is really a symlink during open
- calls and fail if it is. Otherwise, the link could take you outside the
- write and search paths, and compromise security.
- If you want to take that risk, call this function with a non-zero parameter.
- Note that this is more for sandboxing a program's scripting language, in
- case untrusted scripts try to compromise the system. Generally speaking,
- a user could very well have a legitimate reason to set up a symlink, so
- unless you feel there's a specific danger in allowing them, you should
- permit them.
- Symlinks are only explicitly checked when dealing with filenames
- in platform-independent notation. That is, when setting up your
- search and write paths, etc, symlinks are never checked for.
- Please note that #Stat() will always check the path specified; if
- that path is a symlink, it will not be followed in any case. If symlinks
- aren't permitted through this function, #Stat() ignores them, and
- would treat the query as if the path didn't exist at all.
- Symbolic link permission can be enabled or disabled at any time after
- you've called #Init(), and is disabled by default.
- End Rem
- Function PermitSymbolicLinks:Int(allow:Int)
- Return PHYSFS_permitSymbolicLinks(allow)
- End Function
- Rem
- bbdoc: Determine if symbolic links are permitted.
- returns: #True if symlinks are permitted, #False if not.
- about: This reports the setting from the last call to #PermitSymbolicLinks().
- If #PermitSymbolicLinks() hasn't been called since the library was last initialized, symbolic links are implicitly disabled.
- End Rem
- Function SymbolicLinksPermitted:Int()
- Return PHYSFS_symbolicLinksPermitted()
- End Function
- End Type
|