1//************************************ bs::framework - Copyright 2018 Marko Pintera **************************************//
2//*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********//
3#include "FileSystem/BsDataStream.h"
4#include "Debug/BsDebug.h"
5#include "String/BsUnicode.h"
6
7namespace bs
8{
9 const UINT32 DataStream::StreamTempSize = 128;
10
11 /** Checks does the provided buffer has an UTF32 byte order mark in little endian order. */
12 bool isUTF32LE(const UINT8* buffer)
13 {
14 return buffer[0] == 0xFF && buffer[1] == 0xFE && buffer[2] == 0x00 && buffer[3] == 0x00;
15 }
16
17 /** Checks does the provided buffer has an UTF32 byte order mark in big endian order. */
18 bool isUTF32BE(const UINT8* buffer)
19 {
20 return buffer[0] == 0x00 && buffer[1] == 0x00 && buffer[2] == 0xFE && buffer[3] == 0xFF;
21 }
22
23 /** Checks does the provided buffer has an UTF16 byte order mark in little endian order. */
24 bool isUTF16LE(const UINT8* buffer)
25 {
26 return buffer[0] == 0xFF && buffer[1] == 0xFE;
27 }
28
29 /** Checks does the provided buffer has an UTF16 byte order mark in big endian order. */
30 bool isUTF16BE(const UINT8* buffer)
31 {
32 return buffer[0] == 0xFE && buffer[1] == 0xFF;
33 }
34
35 /** Checks does the provided buffer has an UTF8 byte order mark. */
36 bool isUTF8(const UINT8* buffer)
37 {
38 return (buffer[0] == 0xEF && buffer[1] == 0xBB && buffer[2] == 0xBF);
39 }
40
41 template <typename T> DataStream& DataStream::operator>> (T& val)
42 {
43 read(static_cast<void*>(&val), sizeof(T));
44
45 return *this;
46 }
47
48 void DataStream::writeString(const String& string, StringEncoding encoding)
49 {
50 if (encoding == StringEncoding::UTF16)
51 {
52 // Write BOM
53 UINT8 bom[2] = { 0xFF, 0xFE };
54 write(bom, sizeof(bom));
55
56 U16String u16string = UTF8::toUTF16(string);
57 write(u16string.data(), u16string.length() * sizeof(char16_t));
58 }
59 else
60 {
61 // Write BOM
62 UINT8 bom[3] = { 0xEF, 0xBB, 0xBF };
63 write(bom, sizeof(bom));
64
65 write(string.data(), string.length());
66 }
67 }
68
69 void DataStream::writeString(const WString& string, StringEncoding encoding)
70 {
71 if (encoding == StringEncoding::UTF16)
72 {
73 // Write BOM
74 UINT8 bom[2] = { 0xFF, 0xFE };
75 write(bom, sizeof(bom));
76
77 String u8string = UTF8::fromWide(string);
78 U16String u16string = UTF8::toUTF16(u8string);
79 write(u16string.data(), u16string.length() * sizeof(char16_t));
80 }
81 else
82 {
83 // Write BOM
84 UINT8 bom[3] = { 0xEF, 0xBB, 0xBF };
85 write(bom, sizeof(bom));
86
87 String u8string = UTF8::fromWide(string);
88 write(u8string.data(), u8string.length());
89 }
90 }
91
92 String DataStream::getAsString()
93 {
94 // Ensure read from begin of stream
95 seek(0);
96
97 // Try reading header
98 UINT8 headerBytes[4];
99 size_t numHeaderBytes = read(headerBytes, 4);
100
101 size_t dataOffset = 0;
102 if(numHeaderBytes >= 4)
103 {
104 if (isUTF32LE(headerBytes))
105 dataOffset = 4;
106 else if (isUTF32BE(headerBytes))
107 {
108 LOGWRN("UTF-32 big endian decoding not supported");
109 return u8"";
110 }
111 }
112
113 if(dataOffset == 0 && numHeaderBytes >= 3)
114 {
115 if (isUTF8(headerBytes))
116 dataOffset = 3;
117 }
118
119 if(dataOffset == 0 && numHeaderBytes >= 2)
120 {
121 if (isUTF16LE(headerBytes))
122 dataOffset = 2;
123 else if (isUTF16BE(headerBytes))
124 {
125 LOGWRN("UTF-16 big endian decoding not supported");
126 return u8"";
127 }
128 }
129
130 seek(dataOffset);
131
132 // Read the entire buffer - ideally in one read, but if the size of the buffer is unknown, do multiple fixed size
133 // reads.
134 size_t bufSize = (mSize > 0 ? mSize : 4096);
135 auto tempBuffer = bs_stack_alloc<std::stringstream::char_type>((UINT32)bufSize);
136
137 std::stringstream result;
138 while (!eof())
139 {
140 size_t numReadBytes = read(tempBuffer, bufSize);
141 result.write(tempBuffer, numReadBytes);
142 }
143
144 bs_stack_free(tempBuffer);
145
146 std::string string = result.str();
147
148 switch(dataOffset)
149 {
150 default:
151 case 0: // No BOM = assumed UTF-8
152 case 3: // UTF-8
153 return String(string.data(), string.length());
154 case 2: // UTF-16
155 {
156 UINT32 numElems = (UINT32)string.length() / 2;
157
158 return UTF8::fromUTF16(U16String((char16_t*)string.data(), numElems));
159 }
160 case 4: // UTF-32
161 {
162 UINT32 numElems = (UINT32)string.length() / 4;
163
164 return UTF8::fromUTF32(U32String((char32_t*)string.data(), numElems));
165 }
166 }
167
168 // Note: Never assuming ANSI as there is no ideal way to check for it. If required I need to
169 // try reading the data and if all UTF encodings fail, assume it's ANSI. For now it should be
170 // fine as most files are UTF-8 encoded.
171 }
172
173 WString DataStream::getAsWString()
174 {
175 String u8string = getAsString();
176
177 return UTF8::toWide(u8string);
178 }
179
180 MemoryDataStream::MemoryDataStream(size_t size)
181 : DataStream(READ | WRITE), mData(nullptr), mFreeOnClose(true)
182 {
183 mData = mPos = (UINT8*)bs_alloc((UINT32)size);
184 mSize = size;
185 mEnd = mData + mSize;
186
187 assert(mEnd >= mPos);
188 }
189
190 MemoryDataStream::MemoryDataStream(void* memory, size_t inSize, bool freeOnClose)
191 : DataStream(READ | WRITE), mData(nullptr), mFreeOnClose(freeOnClose)
192 {
193 mData = mPos = static_cast<UINT8*>(memory);
194 mSize = inSize;
195 mEnd = mData + mSize;
196
197 assert(mEnd >= mPos);
198 }
199
200 MemoryDataStream::MemoryDataStream(DataStream& sourceStream)
201 : DataStream(READ | WRITE), mData(nullptr)
202 {
203 // Copy data from incoming stream
204 mSize = sourceStream.size();
205
206 mData = (UINT8*)bs_alloc((UINT32)mSize);
207 mPos = mData;
208 mEnd = mData + sourceStream.read(mData, mSize);
209 mFreeOnClose = true;
210
211 assert(mEnd >= mPos);
212 }
213
214 MemoryDataStream::MemoryDataStream(const SPtr<DataStream>& sourceStream)
215 :DataStream(READ | WRITE), mData(nullptr)
216 {
217 // Copy data from incoming stream
218 mSize = sourceStream->size();
219
220 mData = (UINT8*)bs_alloc((UINT32)mSize);
221 mPos = mData;
222 mEnd = mData + sourceStream->read(mData, mSize);
223 mFreeOnClose = true;
224
225 assert(mEnd >= mPos);
226 }
227
228 MemoryDataStream::~MemoryDataStream()
229 {
230 close();
231 }
232
233 size_t MemoryDataStream::read(void* buf, size_t count)
234 {
235 size_t cnt = count;
236
237 if (mPos + cnt > mEnd)
238 cnt = mEnd - mPos;
239 if (cnt == 0)
240 return 0;
241
242 assert (cnt <= count);
243
244 memcpy(buf, mPos, cnt);
245 mPos += cnt;
246
247 return cnt;
248 }
249
250 size_t MemoryDataStream::write(const void* buf, size_t count)
251 {
252 size_t written = 0;
253 if (isWriteable())
254 {
255 written = count;
256
257 if (mPos + written > mEnd)
258 written = mEnd - mPos;
259 if (written == 0)
260 return 0;
261
262 memcpy(mPos, buf, written);
263 mPos += written;
264 }
265
266 return written;
267 }
268
269 void MemoryDataStream::skip(size_t count)
270 {
271 size_t newpos = (size_t)( (mPos - mData) + count );
272 assert(mData + newpos <= mEnd);
273
274 mPos = mData + newpos;
275 }
276
277 void MemoryDataStream::seek(size_t pos)
278 {
279 assert(mData + pos <= mEnd);
280 mPos = mData + pos;
281 }
282
283 size_t MemoryDataStream::tell() const
284 {
285 return mPos - mData;
286 }
287
288 bool MemoryDataStream::eof() const
289 {
290 return mPos >= mEnd;
291 }
292
293 SPtr<DataStream> MemoryDataStream::clone(bool copyData) const
294 {
295 if (!copyData)
296 return bs_shared_ptr_new<MemoryDataStream>(mData, mSize, false);
297
298 return bs_shared_ptr_new<MemoryDataStream>(*this);
299 }
300
301 void MemoryDataStream::close()
302 {
303 if (mData != nullptr)
304 {
305 if(mFreeOnClose)
306 bs_free(mData);
307
308 mData = nullptr;
309 }
310 }
311
312 FileDataStream::FileDataStream(const Path& path, AccessMode accessMode, bool freeOnClose)
313 : DataStream(accessMode), mPath(path), mFreeOnClose(freeOnClose)
314 {
315 // Always open in binary mode
316 // Also, always include reading
317 std::ios::openmode mode = std::ios::binary;
318
319 if ((accessMode & READ) != 0)
320 mode |= std::ios::in;
321
322 if (((accessMode & WRITE) != 0))
323 {
324 mode |= std::ios::out;
325 mFStream = bs_shared_ptr_new<std::fstream>();
326 mFStream->open(path.toPlatformString().c_str(), mode);
327 mInStream = mFStream;
328 }
329 else
330 {
331 mFStreamRO = bs_shared_ptr_new<std::ifstream>();
332 mFStreamRO->open(path.toPlatformString().c_str(), mode);
333 mInStream = mFStreamRO;
334 }
335
336 // Should check ensure open succeeded, in case fail for some reason.
337 if (mInStream->fail())
338 {
339 LOGWRN("Cannot open file: " + path.toString());
340 return;
341 }
342
343 mInStream->seekg(0, std::ios_base::end);
344 mSize = (size_t)mInStream->tellg();
345 mInStream->seekg(0, std::ios_base::beg);
346 }
347
348 FileDataStream::~FileDataStream()
349 {
350 close();
351 }
352
353 size_t FileDataStream::read(void* buf, size_t count)
354 {
355 mInStream->read(static_cast<char*>(buf), static_cast<std::streamsize>(count));
356
357 return (size_t)mInStream->gcount();
358 }
359
360 size_t FileDataStream::write(const void* buf, size_t count)
361 {
362 size_t written = 0;
363 if (isWriteable() && mFStream)
364 {
365 mFStream->write(static_cast<const char*>(buf), static_cast<std::streamsize>(count));
366 written = count;
367 }
368
369 return written;
370 }
371 void FileDataStream::skip(size_t count)
372 {
373 mInStream->clear(); // Clear fail status in case eof was set
374 mInStream->seekg(static_cast<std::ifstream::pos_type>(count), std::ios::cur);
375 }
376
377 void FileDataStream::seek(size_t pos)
378 {
379 mInStream->clear(); // Clear fail status in case eof was set
380 mInStream->seekg(static_cast<std::streamoff>(pos), std::ios::beg);
381 }
382
383 size_t FileDataStream::tell() const
384 {
385 mInStream->clear(); // Clear fail status in case eof was set
386
387 return (size_t)mInStream->tellg();
388 }
389
390 bool FileDataStream::eof() const
391 {
392 return mInStream->eof();
393 }
394
395 SPtr<DataStream> FileDataStream::clone(bool copyData) const
396 {
397 return bs_shared_ptr_new<FileDataStream>(mPath, (AccessMode)getAccessMode(), true);
398 }
399
400 void FileDataStream::close()
401 {
402 if (mInStream)
403 {
404 if (mFStreamRO)
405 mFStreamRO->close();
406
407 if (mFStream)
408 {
409 mFStream->flush();
410 mFStream->close();
411 }
412
413 if (mFreeOnClose)
414 {
415 mInStream = nullptr;
416 mFStreamRO = nullptr;
417 mFStream = nullptr;
418 }
419 }
420 }
421}
422