io.bmx 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. ' Copyright (c) 2020-2023 Bruce A Henderson
  2. '
  3. ' This software is provided 'as-is', without any express or implied
  4. ' warranty. In no event will the authors be held liable for any damages
  5. ' arising from the use of this software.
  6. '
  7. ' Permission is granted to anyone to use this software for any purpose,
  8. ' including commercial applications, and to alter it and redistribute it
  9. ' freely, subject to the following restrictions:
  10. '
  11. ' 1. The origin of this software must not be misrepresented; you must not
  12. ' claim that you wrote the original software. If you use this software
  13. ' in a product, an acknowledgment in the product documentation would be
  14. ' appreciated but is not required.
  15. ' 2. Altered source versions must be plainly marked as such, and must not be
  16. ' misrepresented as being the original software.
  17. ' 3. This notice may not be removed or altered from any source distribution.
  18. '
  19. SuperStrict
  20. Rem
  21. bbdoc: IO Abstraction
  22. End Rem
  23. Module BRL.IO
  24. ModuleInfo "Version: 1.02"
  25. ModuleInfo "License: zlib/libpng"
  26. ModuleInfo "Copyright: Bruce A Henderson"
  27. ModuleInfo "History: 1.02"
  28. ModuleInfo "History: Added PermitSymbolicLinks()"
  29. ModuleInfo "History: Documented SMaxIO_Stat and added SDateTime getters."
  30. ModuleInfo "History: 1.01"
  31. ModuleInfo "History: Added GetWriteDir(), GetRealDir(), IsInit(), GetMountPoint() & SetRoot()"
  32. ModuleInfo "History: Added Unmount() and GetSearchPath()"
  33. ModuleInfo "History: 1.00"
  34. ModuleInfo "History: Initial Release."
  35. Import "common.bmx"
  36. Rem
  37. bbdoc: IO abstraction implementation.
  38. about:
  39. End Rem
  40. Type MaxIO
  41. Global ioInitialized:Int
  42. Rem
  43. bbdoc: Initialises the abstraction layer.
  44. about: This must be called before any other #MaxIO functions.
  45. End Rem
  46. Function Init()
  47. If Not ioInitialized Then
  48. If Not bmx_PHYSFS_init() Then
  49. Throw bmx_PHYSFS_getLastError()
  50. End If
  51. End If
  52. ioInitialized = True
  53. End Function
  54. Rem
  55. bbdoc: Determines if the #MaxIO is initialized.
  56. returns: non-zero if #MaxIO is initialized, zero if it is not.
  57. about: Once #Init() returns successfully, this will return non-zero. Before a successful #Init() and after #DeInit() returns
  58. successfully, this will return zero. This function is safe to call at any time.
  59. End Rem
  60. Function IsInit:Int()
  61. Return ioInitialized And PHYSFS_isInit()
  62. End Function
  63. Rem
  64. bbdoc: Deinitializes the abstraction layer.
  65. about: This closes any files opened via the abstraction layer, blanks the search/write paths, frees memory, and invalidates all of your file handles.
  66. 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
  67. buffering writes to network filesystem, and the fileserver has crashed, or a hard drive has failed, etc). It is usually best to close
  68. all write handles yourself before calling this function, so that you can gracefully handle a specific failure.
  69. Once successfully deinitialized, #Init() can be called again to restart the subsystem. All default API states are restored at this point.
  70. End Rem
  71. Function DeInit:Int()
  72. If ioInitialized Then
  73. ioInitialized = False
  74. Return PHYSFS_deinit()
  75. End If
  76. End Function
  77. Rem
  78. bbdoc: Adds an archive or directory to the search path.
  79. returns: Nonzero if added to path, zero on failure (bogus archive, dir missing, etc).
  80. about: If this is a duplicate, the entry is not added again, even though the function succeeds.
  81. You may not add the same archive to two different mountpoints: duplicate checking is done against the archive and not the mountpoint.
  82. 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
  83. hierachical file tree. Two archives mounted at the same place (or an archive with files overlapping another mountpoint) may have
  84. overlapping files: in such a case, the file earliest in the search path is selected, and the other files are inaccessible to the
  85. application. This allows archives to be used to override previous revisions; you can use the mounting mechanism to place archives
  86. at a specific point in the file tree and prevent overlap; this is useful for downloadable mods that might trample over application data
  87. or each other, for example.
  88. The mountpoint does not need to exist prior to mounting, which is different than those familiar with the Unix concept of "mounting"
  89. may expect. As well, more than one archive can be mounted to the same mountpoint, or mountpoints and archive contents can overlap...the
  90. interpolation mechanism still functions as usual.
  91. End Rem
  92. Function Mount:Int(newDir:String, mountPoint:String = Null, appendToPath:Int = True)
  93. Assert ioInitialized Else "MaxIO not initialized"
  94. If newDir.StartsWith("incbin::") Then
  95. Return MountIncBin(newDir[8..], mountPoint, appendToPath)
  96. End If
  97. Return bmx_PHYSFS_mount(newDir, mountPoint, appendToPath)
  98. End Function
  99. Rem
  100. bbdoc: Adds an incbinned archive to the search path.
  101. about: See #Mount for more information.
  102. End Rem
  103. Function MountIncbin:Int(newDir:String, mountPoint:String = Null, appendToPath:Int = True)
  104. Assert ioInitialized Else "MaxIO not initialized"
  105. Assert IncbinPtr(newDir) Else "No Incbin for " + newDir
  106. Return bmx_PHYSFS_mountMemory(IncbinPtr(newDir), IncbinLen(newDir), newDir, mountPoint, appendToPath)
  107. End Function
  108. Rem
  109. bbdoc: Removes a directory or archive from the search path.
  110. returns: Nonzero on success, zero on failure. Use #GetLastErrorCode() to obtain the specific error.
  111. about: This must be a (case-sensitive) match to a dir or archive already in the
  112. search path, specified in platform-dependent notation.
  113. This call will fail (and fail to remove from the path) if the element still has files open in it.
  114. End Rem
  115. Function Unmount:Int(oldDir:String)
  116. Return bmx_PHYSFS_unmount(oldDir)
  117. End Function
  118. Rem
  119. bbdoc: Determines a mounted archive's mountpoint.
  120. returns: The mount point if added to path, or #Null on failure (bogus archive, etc). Use #GetLastErrorCode() to obtain the specific error.
  121. about: You give this function the name of an archive or dir you successfully
  122. added to the search path, and it reports the location in the interpolated
  123. tree where it is mounted. Files mounted with a #Null mountpoint will report "/".
  124. @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.
  125. End Rem
  126. Function GetMountPoint:String(dir:String)
  127. Return bmx_PHYSFS_getMountPoint(dir)
  128. End Function
  129. Rem
  130. bbdoc: Makes a subdirectory of an archive its root directory.
  131. returns: Nonzero on success, zero on failure. Use #GetLastErrorCode() to obtain the specific error.
  132. about: This lets you narrow down the accessible files in a specific archive.
  133. For example, if you have x.zip with a file in y/z.txt, mounted to /a, if you
  134. call #SetRoot("x.zip", "/y"), then the call #OpenRead("/a/z.txt") will succeed.
  135. You can change an archive's root at any time, altering the interpolated
  136. file tree (depending on where paths shift, a different archive may be
  137. providing various files). If you set the root to #Null or "/", the
  138. archive will be treated as if no special root was set (as if the archive
  139. was just mounted normally).
  140. Changing the root only affects future operations on pathnames; a file
  141. that was opened from a path that changed due to a #SetRoot will not be affected.
  142. Setting a new root is not limited to archives in the search path; you may
  143. set one on the write dir, too, which might be useful if you have files
  144. open for write and thus can't change the write dir at the moment.
  145. It is not an error to set a subdirectory that does not exist to be the
  146. root of an archive; however, no files will be visible in this case. If
  147. the missing directories end up getting created (a mkdir to the physical
  148. filesystem, etc) then this will be reflected in the interpolated tree.
  149. End Rem
  150. Function SetRoot:Int(archive:String, subdir:String)
  151. Return bmx_PHYSFS_setRoot(archive, subdir)
  152. End Function
  153. Rem
  154. bbdoc: Gets the path where the application resides.
  155. End Rem
  156. Function GetBaseDir:String()
  157. Assert ioInitialized Else "MaxIO not initialized"
  158. Return bmx_PHYSFS_getBaseDir()
  159. End Function
  160. Rem
  161. bbdoc: Gets the user-and-app-specific path where files can be written.
  162. returns: The pref dir in platform-dependent notation, or #Null if there's a problem (creating directory failed, etc).
  163. about: Get the "pref dir". This is meant to be where users can write personal files (preferences and save games, etc) that are specific
  164. to your application. This directory is unique per user, per application.
  165. This function will decide the appropriate location in the native filesystem, create the directory if necessary, and return a string in platform-dependent
  166. notation, suitable for passing to #SetWriteDir().
  167. On Windows, this might look like: "C:\\Users\\bob\\AppData\\Roaming\\My Company\\My Program Name"
  168. On Linux, this might look like: "/home/bob/.local/share/My Program Name"
  169. On Mac OS X, this might look like: "/Users/bob/Library/Application Support/My Program Name"
  170. (etc.)
  171. You should probably use the pref dir for your write dir, and also put it near the beginning of your search path.
  172. This finds the correct location for whatever platform, which not only changes between operating systems, but also versions of the same operating system.
  173. 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
  174. your application. These should be proper names.
  175. Both the (org) and (app) strings may become part of a directory name, so please follow these rules:
  176. Try to use the same org string (including case-sensitivity) for all your applications that use this function.
  177. Always use a unique app string for each one, and make sure it never changes for an app once you've decided on it.
  178. Unicode characters are legal, as long as it's UTF-8 encoded, but...
  179. ...only use letters, numbers, and spaces. Avoid punctuation like "Game Name 2: Bad Guy's Revenge!" ... "Game Name 2" is sufficient.
  180. > You should assume the path returned by this function is the only safe place to write files (and that #GetBaseDir(),
  181. while they might be writable, or even parents of the returned path, aren't where you should be writing things)..
  182. End Rem
  183. Function GetPrefDir:String(org:String, app:String)
  184. Return bmx_PHYSFS_getPrefDir(org, app)
  185. End Function
  186. Rem
  187. bbdoc: Gets the current search path.
  188. returns: An array of Strings.
  189. about: The default search path is an empty list.
  190. End Rem
  191. Function GetSearchPath:String[]()
  192. Return bmx_PHYSFS_getSearchPath()
  193. End Function
  194. Rem
  195. bbdoc: Indicates where files may be written.
  196. about: Sets a new write dir. This will override the previous setting.
  197. This call will fail (and fail to change the write dir) if the current write dir still has files open in it.
  198. The directory will be the root of the write dir, specified in platform-dependent notation.
  199. Setting to #Null disables the write dir, so no files can be opened for writing.
  200. End Rem
  201. Function SetWriteDir:Int(newDir:String)
  202. Return bmx_PHYSFS_setWriteDir(newDir)
  203. End Function
  204. Rem
  205. bbdoc: Gets the path where files may be written.
  206. about: Gets the current write dir. The default write dir is #Null.
  207. End Rem
  208. Function GetWriteDir:String()
  209. Return bmx_PHYSFS_getWriteDir()
  210. End Function
  211. Rem
  212. bbdoc: Figures out where in the search path a file resides.
  213. returns: The file location, or #Null if not found.
  214. about: The file is specified in platform-independent notation. The returned
  215. filename will be the element of the search path where the file was found,
  216. which may be a directory, or an archive. Even if there are multiple
  217. matches in different parts of the search path, only the first one found
  218. is used, just like when opening a file.
  219. End Rem
  220. Function GetRealDir:String(filename:String)
  221. Return bmx_PHYSFS_getRealDir(filename)
  222. End Function
  223. Rem
  224. bbdoc: Gets various information about a directory or a file, populating the passed in #SMaxIO_Stat instance.
  225. about: This function will never follow symbolic links. If you haven't enabled
  226. symlinks with #PermitSymbolicLinks(), stat'ing a symlink will be treated like stat'ing a non-existant file. If symlinks are enabled,
  227. stat'ing a symlink will give you information on the link itself and not what it points to.
  228. End Rem
  229. Function Stat:Int(filename:String, _stat:SMaxIO_Stat Var)
  230. Return bmx_PHYSFS_stat(filename, _stat)
  231. End Function
  232. Rem
  233. bbdoc: Deletes a file or directory.
  234. about: @path is specified in platform-independent notation in relation to the write dir.
  235. A directory must be empty before this call can delete it.
  236. Deleting a symlink will remove the link, not what it points to, regardless of whether you "permitSymLinks" or not.
  237. So if you've got the write dir set to "C:\mygame\writedir" and call DeletePath("downloads/maps/level1.map") then the file
  238. "C:\mygame\writedir\downloads\maps\level1.map" is removed from the physical filesystem, if it exists and the operating system permits the deletion.
  239. Note that on Unix systems, deleting a file may be successful, but the actual file won't be removed until all processes that have
  240. an open filehandle to it (including your program) close their handles.
  241. 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
  242. consider this a security method or anything.
  243. End Rem
  244. Function DeletePath:Int(path:String)
  245. Return bmx_PHYSFS_delete(path)
  246. End Function
  247. Rem
  248. bbdoc: Opens a file for writing.
  249. about: Opens a file for writing, in platform-independent notation and in relation to the write dir as the root of the writable filesystem.
  250. 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.
  251. Note that entries that are symlinks are ignored if PermitSymbolicLinks(True) hasn't been called, and opening a symlink with this function will
  252. fail in such a case.
  253. End Rem
  254. Function OpenWrite:Byte Ptr(path:String)
  255. Return bmx_PHYSFS_openWrite(path)
  256. End Function
  257. Rem
  258. bbdoc: Opens a file for reading.
  259. about: Opens a file for reading, in platform-independent notation.
  260. 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.
  261. The reading offset is set to the first byte of the file.
  262. 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.
  263. End Rem
  264. Function OpenRead:Byte Ptr(path:String)
  265. Return bmx_PHYSFS_openRead(path)
  266. End Function
  267. Rem
  268. bbdoc: Closes a file handle.
  269. about: This call is capable of failing if the operating system was buffering writes to the physical media, and, now forced to write those
  270. changes to physical media, can not store the data for some reason. In such a case, the filehandle stays open. A well-written program
  271. should ALWAYS check the return value from the close call in addition to every writing call!
  272. End Rem
  273. Function Close:Int(filePtr:Byte Ptr)
  274. Return PHYSFS_close(filePtr)
  275. End Function
  276. Rem
  277. bbdoc: Creates a directory.
  278. 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.
  279. End Rem
  280. Function MkDir:Int(dirName:String)
  281. Return bmx_PHYSFS_mkdir(dirName)
  282. End Function
  283. Rem
  284. bbdoc: Returns the last error code.
  285. about: Calling this function resets the last error code.
  286. End Rem
  287. Function GetLastErrorCode:EMaxIOErrorCode()
  288. Return bmx_PHYSFS_getLastErrorCode()
  289. End Function
  290. Rem
  291. bbdoc: Returns the message for the specified @errorCode.
  292. End Rem
  293. Function GetErrorForCode:String(errorCode:EMaxIOErrorCode)
  294. Return bmx_PHYSFS_getErrorForCode(errorCode)
  295. End Function
  296. Rem
  297. bbdoc: Returns the last error message, or #Null if there is none.
  298. about: Calling this function resets the last error.
  299. End Rem
  300. Function GetLastError:String()
  301. Return bmx_PHYSFS_getLastError()
  302. End Function
  303. Rem
  304. bbdoc: Enables or disables following of symbolic links.
  305. about: Some physical filesystems and archives contain files that are just pointers
  306. to other files. On the physical filesystem, opening such a link will
  307. (transparently) open the file that is pointed to.
  308. By default, MaxIO will check if a file is really a symlink during open
  309. calls and fail if it is. Otherwise, the link could take you outside the
  310. write and search paths, and compromise security.
  311. If you want to take that risk, call this function with a non-zero parameter.
  312. Note that this is more for sandboxing a program's scripting language, in
  313. case untrusted scripts try to compromise the system. Generally speaking,
  314. a user could very well have a legitimate reason to set up a symlink, so
  315. unless you feel there's a specific danger in allowing them, you should
  316. permit them.
  317. Symlinks are only explicitly checked when dealing with filenames
  318. in platform-independent notation. That is, when setting up your
  319. search and write paths, etc, symlinks are never checked for.
  320. Please note that #Stat() will always check the path specified; if
  321. that path is a symlink, it will not be followed in any case. If symlinks
  322. aren't permitted through this function, #Stat() ignores them, and
  323. would treat the query as if the path didn't exist at all.
  324. Symbolic link permission can be enabled or disabled at any time after
  325. you've called #Init(), and is disabled by default.
  326. End Rem
  327. Function PermitSymbolicLinks:Int(allow:Int)
  328. Return PHYSFS_permitSymbolicLinks(allow)
  329. End Function
  330. Rem
  331. bbdoc: Determine if symbolic links are permitted.
  332. returns: #True if symlinks are permitted, #False if not.
  333. about: This reports the setting from the last call to #PermitSymbolicLinks().
  334. If #PermitSymbolicLinks() hasn't been called since the library was last initialized, symbolic links are implicitly disabled.
  335. End Rem
  336. Function SymbolicLinksPermitted:Int()
  337. Return PHYSFS_symbolicLinksPermitted()
  338. End Function
  339. End Type