1/*
2 * LegacyClonk
3 *
4 * Copyright (c) 1998-2000, Matthes Bender (RedWolf Design)
5 * Copyright (c) 2017-2020, The LegacyClonk Team and contributors
6 *
7 * Distributed under the terms of the ISC license; see accompanying file
8 * "COPYING" for details.
9 *
10 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11 * See accompanying file "TRADEMARK" for details.
12 *
13 * To redistribute this file separately, substitute the full license texts
14 * for the above references.
15 */
16
17/* A handy wrapper class to gzio files */
18
19#include <Standard.h>
20#include <StdFile.h>
21#include <CStdFile.h>
22
23#include <stdio.h>
24#include <stdlib.h>
25
26#include <sys/stat.h>
27#include <fcntl.h>
28#include <assert.h>
29
30#include <algorithm>
31#include <cstring>
32
33CStdFile::CStdFile()
34{
35 Status = false;
36 hFile = nullptr;
37 ClearBuffer();
38 ModeWrite = false;
39 Name[0] = 0;
40}
41
42CStdFile::~CStdFile()
43{
44 Close();
45}
46
47bool CStdFile::Create(const char *szFilename, bool fCompressed, bool fExecutable, bool exclusive)
48{
49 SCopy(szSource: szFilename, sTarget: Name, _MAX_PATH);
50 // Set modes
51 ModeWrite = true;
52 // Open standard file
53 if (fCompressed)
54 {
55 try
56 {
57 writeCompressedFile.reset(p: new StdGzCompressedFile::Write{szFilename});
58 }
59 catch (const StdGzCompressedFile::Exception &)
60 {
61 return false;
62 }
63 }
64 else
65 {
66 if (fExecutable)
67 {
68 // Create an executable file
69#ifdef _WIN32
70 int mode = _S_IREAD | _S_IWRITE;
71 int flags = _O_BINARY | _O_CREAT | _O_WRONLY | _O_TRUNC | (exclusive ? _O_EXCL : 0);
72#else
73 mode_t mode = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IWOTH | S_IXOTH;
74 int flags = O_CREAT | O_WRONLY | O_TRUNC | (exclusive ? O_EXCL : 0);
75#endif
76 int fd = open(file: Name, oflag: flags, mode);
77 if (fd == -1) return false;
78 if (!(hFile = fdopen(fd: fd, modes: "wb"))) return false;
79 }
80 else
81 {
82 if (!(hFile = fopen(filename: Name, modes: "wb"))) return false;
83 }
84 }
85 // Reset buffer
86 ClearBuffer();
87 // Set status
88 Status = true;
89 return true;
90}
91
92bool CStdFile::Open(const char *szFilename, bool fCompressed)
93{
94 SCopy(szSource: szFilename, sTarget: Name, _MAX_PATH);
95 // Set modes
96 ModeWrite = false;
97 // Open standard file
98 if (fCompressed)
99 {
100 try
101 {
102 readCompressedFile.reset(p: new StdGzCompressedFile::Read{szFilename});
103 }
104 catch (const StdGzCompressedFile::Exception &)
105 {
106 return false;
107 }
108 }
109 else
110 {
111 if (!(hFile = fopen(filename: Name, modes: "rb"))) return false;
112 }
113 // Reset buffer
114 ClearBuffer();
115 // Set status
116 Status = true;
117 return true;
118}
119
120bool CStdFile::Append(const char *szFilename)
121{
122 SCopy(szSource: szFilename, sTarget: Name, _MAX_PATH);
123 // Set modes
124 ModeWrite = true;
125 // Open standard file
126 if (!(hFile = fopen(filename: Name, modes: "ab"))) return false;
127 // Reset buffer
128 ClearBuffer();
129 // Set status
130 Status = true;
131 return true;
132}
133
134bool CStdFile::Close()
135
136{
137 bool rval = true;
138 Status = false;
139 Name[0] = 0;
140 // Save buffer if in write mode
141 if (ModeWrite && BufferLoad) if (!SaveBuffer()) rval = false;
142 // Close file(s)
143 readCompressedFile.reset();
144 writeCompressedFile.reset();
145 if (hFile) if (fclose(stream: hFile) != 0) rval = false;
146 hFile = nullptr;
147 return !!rval;
148}
149
150bool CStdFile::Default()
151{
152 Status = false;
153 Name[0] = 0;
154 readCompressedFile.reset();
155 writeCompressedFile.reset();
156 hFile = nullptr;
157 BufferLoad = BufferPtr = 0;
158 return true;
159}
160
161bool CStdFile::Read(void *pBuffer, size_t iSize, size_t *ipFSize)
162{
163 size_t transfer;
164 if (!pBuffer) return false;
165 if (ModeWrite) return false;
166 uint8_t *bypBuffer = static_cast<uint8_t *>(pBuffer);
167 if (ipFSize) *ipFSize = 0;
168 while (iSize > 0)
169 {
170 // Valid data in the buffer: Transfer as much as possible
171 if (BufferLoad > BufferPtr)
172 {
173 transfer = std::min<size_t>(a: BufferLoad - BufferPtr, b: iSize);
174 memmove(dest: bypBuffer, src: Buffer + BufferPtr, n: transfer);
175 BufferPtr += transfer;
176 bypBuffer += transfer;
177 iSize -= transfer;
178 if (ipFSize) *ipFSize += transfer;
179 }
180 // Buffer empty: Load
181 else if (LoadBuffer() <= 0) return false;
182 }
183 return true;
184}
185
186size_t CStdFile::LoadBuffer()
187{
188 if (hFile) BufferLoad = fread(ptr: Buffer, size: 1, n: CStdFileBufSize, stream: hFile);
189 if (readCompressedFile)
190 {
191 try
192 {
193 BufferLoad = readCompressedFile->ReadData(toBuffer: Buffer, size: CStdFileBufSize);
194 }
195 catch (const StdGzCompressedFile::Exception &)
196 {
197 BufferLoad = 0;
198 }
199 }
200 BufferPtr = 0;
201 return BufferLoad;
202}
203
204bool CStdFile::SaveBuffer()
205{
206 size_t saved = 0;
207 if (hFile) saved = fwrite(ptr: Buffer, size: 1, n: BufferLoad, s: hFile);
208 if (writeCompressedFile)
209 {
210 try
211 {
212 writeCompressedFile->WriteData(fromBuffer: Buffer, size: BufferLoad);
213 saved = BufferLoad;
214 }
215 catch (const StdGzCompressedFile::Exception &)
216 {
217 return false;
218 }
219 }
220 if (saved != BufferLoad) return false;
221 BufferLoad = 0;
222 return true;
223}
224
225void CStdFile::ClearBuffer()
226{
227 BufferLoad = BufferPtr = 0;
228}
229
230bool CStdFile::Write(const void *pBuffer, size_t iSize)
231{
232 size_t transfer;
233 if (!pBuffer) return false;
234 if (!ModeWrite) return false;
235 const uint8_t *bypBuffer = static_cast<const uint8_t *>(pBuffer);
236 while (iSize > 0)
237 {
238 // Space in buffer: Transfer as much as possible
239 if (BufferLoad < CStdFileBufSize)
240 {
241 transfer = std::min(a: CStdFileBufSize - BufferLoad, b: iSize);
242 memcpy(dest: Buffer + BufferLoad, src: bypBuffer, n: transfer);
243 BufferLoad += transfer;
244 bypBuffer += transfer;
245 iSize -= transfer;
246 }
247 // Buffer full: Save
248 else if (!SaveBuffer()) return false;
249 }
250 return true;
251}
252
253bool CStdFile::WriteString(const char *szStr)
254{
255 uint8_t nl[2] = { 0x0D, 0x0A };
256 if (!szStr) return false;
257 const auto size = SLen(sptr: szStr);
258 if (!Write(pBuffer: static_cast<const void *>(szStr), iSize: size)) return false;
259 if (!Write(pBuffer: nl, iSize: 2)) return false;
260 return true;
261}
262
263bool CStdFile::Rewind()
264{
265 if (ModeWrite) return false;
266 ClearBuffer();
267 if (hFile) rewind(stream: hFile);
268 if (readCompressedFile)
269 {
270 readCompressedFile->Rewind();
271 }
272 return true;
273}
274
275bool CStdFile::Advance(size_t iOffset)
276{
277 if (ModeWrite) return false;
278 while (iOffset > 0)
279 {
280 // Valid data in the buffer: Transfer as much as possible
281 if (BufferLoad > BufferPtr)
282 {
283 const auto transfer = std::min(a: BufferLoad - BufferPtr, b: iOffset);
284 BufferPtr += transfer;
285 iOffset -= transfer;
286 }
287 // Buffer empty: Load or skip
288 else
289 {
290 if (hFile) return !fseek(stream: hFile, off: iOffset, SEEK_CUR); // uncompressed: Just skip
291 if (LoadBuffer() <= 0) return false; // compressed: Read...
292 }
293 }
294 return true;
295}
296
297bool CStdFile::Save(const char *szFilename, const uint8_t *bpBuf,
298 size_t iSize, bool fCompressed, bool executable, bool exclusive)
299{
300 if (!bpBuf || (iSize < 1)) return false;
301 if (!Create(szFilename, fCompressed, fExecutable: executable, exclusive)) return false;
302 if (!Write(pBuffer: bpBuf, iSize)) return false;
303 if (!Close()) return false;
304 return true;
305}
306
307bool CStdFile::Load(const char *szFilename, uint8_t **lpbpBuf,
308 size_t *ipSize, int iAppendZeros,
309 bool fCompressed)
310{
311 size_t iSize = fCompressed ? UncompressedFileSize(szFileName: szFilename) : FileSize(fname: szFilename);
312 if (!lpbpBuf || (iSize < 1)) return false;
313 if (!Open(szFilename, fCompressed)) return false;
314 *lpbpBuf = new uint8_t[iSize + iAppendZeros];
315 if (!Read(pBuffer: *lpbpBuf, iSize)) { delete[] *lpbpBuf; return false; }
316 if (iAppendZeros) std::fill_n(first: *lpbpBuf + iSize, n: iAppendZeros, value: 0);
317 if (ipSize) *ipSize = iSize;
318 Close();
319 return true;
320}
321
322size_t UncompressedFileSize(const char *szFilename)
323{
324 try
325 {
326 StdGzCompressedFile::Read file{szFilename};
327 return file.UncompressedSize();
328 }
329 catch (const StdGzCompressedFile::Exception &)
330 {
331 return 0;
332 }
333}
334
335size_t CStdFile::AccessedEntrySize()
336{
337 if (hFile)
338 {
339 long pos = ftell(stream: hFile);
340 fseek(stream: hFile, off: 0, SEEK_END);
341 long r = ftell(stream: hFile);
342 fseek(stream: hFile, off: pos, SEEK_SET);
343 return static_cast<size_t>(r);
344 }
345 assert(!readCompressedFile);
346 return 0;
347}
348