1/**
2 * Copyright (c) 2006-2023 LOVE Development Team
3 *
4 * This software is provided 'as-is', without any express or implied
5 * warranty. In no event will the authors be held liable for any damages
6 * arising from the use of this software.
7 *
8 * Permission is granted to anyone to use this software for any purpose,
9 * including commercial applications, and to alter it and redistribute it
10 * freely, subject to the following restrictions:
11 *
12 * 1. The origin of this software must not be misrepresented; you must not
13 * claim that you wrote the original software. If you use this software
14 * in a product, an acknowledgment in the product documentation would be
15 * appreciated but is not required.
16 * 2. Altered source versions must be plainly marked as such, and must not be
17 * misrepresented as being the original software.
18 * 3. This notice may not be removed or altered from any source distribution.
19 **/
20
21#include "File.h"
22
23// STD
24#include <cstring>
25
26// LOVE
27#include "Filesystem.h"
28#include "filesystem/FileData.h"
29
30namespace love
31{
32namespace filesystem
33{
34
35extern bool hack_setupWriteDirectory();
36
37namespace physfs
38{
39
40File::File(const std::string &filename)
41 : filename(filename)
42 , file(nullptr)
43 , mode(MODE_CLOSED)
44 , bufferMode(BUFFER_NONE)
45 , bufferSize(0)
46{
47}
48
49File::~File()
50{
51 if (mode != MODE_CLOSED)
52 close();
53}
54
55bool File::open(Mode mode)
56{
57 if (mode == MODE_CLOSED)
58 return true;
59
60 if (!PHYSFS_isInit())
61 throw love::Exception("PhysFS is not initialized.");
62
63 // File must exist if read mode.
64 if ((mode == MODE_READ) && !PHYSFS_exists(filename.c_str()))
65 throw love::Exception("Could not open file %s. Does not exist.", filename.c_str());
66
67 // Check whether the write directory is set.
68 if ((mode == MODE_APPEND || mode == MODE_WRITE) && (PHYSFS_getWriteDir() == nullptr) && !hack_setupWriteDirectory())
69 throw love::Exception("Could not set write directory.");
70
71 // File already open?
72 if (file != nullptr)
73 return false;
74
75 PHYSFS_getLastErrorCode();
76 PHYSFS_File *handle = nullptr;
77
78 switch (mode)
79 {
80 case MODE_READ:
81 handle = PHYSFS_openRead(filename.c_str());
82 break;
83 case MODE_APPEND:
84 handle = PHYSFS_openAppend(filename.c_str());
85 break;
86 case MODE_WRITE:
87 handle = PHYSFS_openWrite(filename.c_str());
88 break;
89 default:
90 break;
91 }
92
93 if (handle == nullptr)
94 {
95 const char *err = PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode());
96 if (err == nullptr)
97 err = "unknown error";
98 throw love::Exception("Could not open file %s (%s)", filename.c_str(), err);
99 }
100
101 file = handle;
102
103 this->mode = mode;
104
105 if (file != nullptr && !setBuffer(bufferMode, bufferSize))
106 {
107 // Revert to buffer defaults if we don't successfully set the buffer.
108 bufferMode = BUFFER_NONE;
109 bufferSize = 0;
110 }
111
112 return (file != nullptr);
113}
114
115bool File::close()
116{
117 if (file == nullptr || !PHYSFS_close(file))
118 return false;
119
120 mode = MODE_CLOSED;
121 file = nullptr;
122
123 return true;
124}
125
126bool File::isOpen() const
127{
128 return mode != MODE_CLOSED && file != nullptr;
129}
130
131int64 File::getSize()
132{
133 // If the file is closed, open it to
134 // check the size.
135 if (file == nullptr)
136 {
137 open(MODE_READ);
138 int64 size = (int64) PHYSFS_fileLength(file);
139 close();
140 return size;
141 }
142
143 return (int64) PHYSFS_fileLength(file);
144}
145
146int64 File::read(void *dst, int64 size)
147{
148 if (!file || mode != MODE_READ)
149 throw love::Exception("File is not opened for reading.");
150
151 int64 max = (int64)PHYSFS_fileLength(file);
152 size = (size == ALL) ? max : size;
153 size = (size > max) ? max : size;
154
155 if (size < 0)
156 throw love::Exception("Invalid read size.");
157
158 return PHYSFS_readBytes(file, dst, (PHYSFS_uint64) size);
159}
160
161bool File::write(const void *data, int64 size)
162{
163 if (!file || (mode != MODE_WRITE && mode != MODE_APPEND))
164 throw love::Exception("File is not opened for writing.");
165
166 if (size < 0)
167 throw love::Exception("Invalid write size.");
168
169 // Try to write.
170 int64 written = PHYSFS_writeBytes(file, data, (PHYSFS_uint64) size);
171
172 // Check that correct amount of data was written.
173 if (written != size)
174 return false;
175
176 // Manually flush the buffer in BUFFER_LINE mode if we find a newline.
177 if (bufferMode == BUFFER_LINE && bufferSize > size)
178 {
179 if (memchr(data, '\n', (size_t) size) != nullptr)
180 flush();
181 }
182
183 return true;
184}
185
186bool File::flush()
187{
188 if (!file || (mode != MODE_WRITE && mode != MODE_APPEND))
189 throw love::Exception("File is not opened for writing.");
190
191 return PHYSFS_flush(file) != 0;
192}
193
194#ifdef LOVE_WINDOWS
195// MSVC doesn't like the 'this' keyword
196// well, we'll use 'that'.
197// It zigs, we zag.
198inline bool test_eof(File *that, PHYSFS_File *)
199{
200 int64 pos = that->tell();
201 int64 size = that->getSize();
202 return pos == -1 || size == -1 || pos >= size;
203}
204#else
205inline bool test_eof(File *, PHYSFS_File *file)
206{
207 return PHYSFS_eof(file);
208}
209#endif
210
211bool File::isEOF()
212{
213 return file == nullptr || test_eof(this, file);
214}
215
216int64 File::tell()
217{
218 if (file == nullptr)
219 return -1;
220
221 return (int64) PHYSFS_tell(file);
222}
223
224bool File::seek(uint64 pos)
225{
226 return file != nullptr && PHYSFS_seek(file, (PHYSFS_uint64) pos) != 0;
227}
228
229bool File::setBuffer(BufferMode bufmode, int64 size)
230{
231 if (size < 0)
232 return false;
233
234 // If the file isn't open, we'll make sure the buffer values are set in
235 // File::open.
236 if (!isOpen())
237 {
238 bufferMode = bufmode;
239 bufferSize = size;
240 return true;
241 }
242
243 int ret = 1;
244
245 switch (bufmode)
246 {
247 case BUFFER_NONE:
248 default:
249 ret = PHYSFS_setBuffer(file, 0);
250 size = 0;
251 break;
252 case BUFFER_LINE:
253 case BUFFER_FULL:
254 ret = PHYSFS_setBuffer(file, size);
255 break;
256 }
257
258 if (ret == 0)
259 return false;
260
261 bufferMode = bufmode;
262 bufferSize = size;
263
264 return true;
265}
266
267File::BufferMode File::getBuffer(int64 &size) const
268{
269 size = bufferSize;
270 return bufferMode;
271}
272
273const std::string &File::getFilename() const
274{
275 return filename;
276}
277
278filesystem::File::Mode File::getMode() const
279{
280 return mode;
281}
282
283} // physfs
284} // filesystem
285} // love
286