File.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  1. // Copyright (c) 2008-2023 the Urho3D project
  2. // License: MIT
  3. #include "../Precompiled.h"
  4. #include "../Core/Profiler.h"
  5. #include "../IO/File.h"
  6. #include "../IO/FileSystem.h"
  7. #include "../IO/Log.h"
  8. #include "../IO/MemoryBuffer.h"
  9. #include "../IO/PackageFile.h"
  10. #ifdef __ANDROID__
  11. #include <SDL/SDL_rwops.h>
  12. #endif
  13. #include <cstdio>
  14. #include <LZ4/lz4.h>
  15. #include "../DebugNew.h"
  16. namespace Urho3D
  17. {
  18. #ifdef _WIN32
  19. static const wchar_t* openMode[] =
  20. {
  21. L"rb",
  22. L"wb",
  23. L"r+b",
  24. L"w+b"
  25. };
  26. #else
  27. static const char* openMode[] =
  28. {
  29. "rb",
  30. "wb",
  31. "r+b",
  32. "w+b"
  33. };
  34. #endif
  35. #ifdef __ANDROID__
  36. const char* APK = "/apk/";
  37. static constexpr i32 READ_BUFFER_SIZE = 32768;
  38. #endif
  39. static constexpr i32 SKIP_BUFFER_SIZE = 1024;
  40. static i32 FSeek64(FILE* stream, i64 offset, i32 origin)
  41. {
  42. #ifdef _MSC_VER
  43. return _fseeki64(stream, offset, origin);
  44. #elif defined(__APPLE__)
  45. return fseeko(stream, offset, origin);
  46. #else
  47. return fseeko64(stream, offset, origin);
  48. #endif
  49. }
  50. static i64 FTell64(FILE* stream)
  51. {
  52. #ifdef _MSC_VER
  53. return _ftelli64(stream);
  54. #elif defined(__APPLE__)
  55. return ftello(stream);
  56. #else
  57. return ftello64(stream);
  58. #endif
  59. }
  60. File::File(Context* context) :
  61. Object(context),
  62. mode_(FILE_READ),
  63. handle_(nullptr),
  64. #ifdef __ANDROID__
  65. assetHandle_(0),
  66. #endif
  67. readBufferOffset_(0),
  68. readBufferSize_(0),
  69. offset_(0),
  70. checksum_(0),
  71. compressed_(false),
  72. readSyncNeeded_(false),
  73. writeSyncNeeded_(false)
  74. {
  75. }
  76. File::File(Context* context, const String& fileName, FileMode mode) :
  77. Object(context),
  78. mode_(FILE_READ),
  79. handle_(nullptr),
  80. #ifdef __ANDROID__
  81. assetHandle_(0),
  82. #endif
  83. readBufferOffset_(0),
  84. readBufferSize_(0),
  85. offset_(0),
  86. checksum_(0),
  87. compressed_(false),
  88. readSyncNeeded_(false),
  89. writeSyncNeeded_(false)
  90. {
  91. Open(fileName, mode);
  92. }
  93. File::File(Context* context, PackageFile* package, const String& fileName) :
  94. Object(context),
  95. mode_(FILE_READ),
  96. handle_(nullptr),
  97. #ifdef __ANDROID__
  98. assetHandle_(0),
  99. #endif
  100. readBufferOffset_(0),
  101. readBufferSize_(0),
  102. offset_(0),
  103. checksum_(0),
  104. compressed_(false),
  105. readSyncNeeded_(false),
  106. writeSyncNeeded_(false)
  107. {
  108. Open(package, fileName);
  109. }
  110. File::~File()
  111. {
  112. Close();
  113. }
  114. bool File::Open(const String& fileName, FileMode mode)
  115. {
  116. return OpenInternal(fileName, mode);
  117. }
  118. bool File::Open(PackageFile* package, const String& fileName)
  119. {
  120. if (!package)
  121. return false;
  122. const PackageEntry* entry = package->GetEntry(fileName);
  123. if (!entry)
  124. return false;
  125. bool success = OpenInternal(package->GetName(), FILE_READ, true);
  126. if (!success)
  127. {
  128. URHO3D_LOGERROR("Could not open package file " + fileName);
  129. return false;
  130. }
  131. name_ = fileName;
  132. offset_ = entry->offset_;
  133. checksum_ = entry->checksum_;
  134. size_ = entry->size_;
  135. compressed_ = package->IsCompressed();
  136. // Seek to beginning of package entry's file data
  137. SeekInternal(offset_);
  138. return true;
  139. }
  140. i32 File::Read(void* dest, i32 size)
  141. {
  142. assert(size >= 0);
  143. if (!IsOpen())
  144. {
  145. // If file not open, do not log the error further here to prevent spamming the stderr stream
  146. return 0;
  147. }
  148. if (mode_ == FILE_WRITE)
  149. {
  150. URHO3D_LOGERROR("File not opened for reading");
  151. return 0;
  152. }
  153. if (size + position_ > size_)
  154. size = size_ - position_;
  155. if (!size)
  156. return 0;
  157. #ifdef __ANDROID__
  158. if (assetHandle_ && !compressed_)
  159. {
  160. // If not using a compressed package file, buffer file reads on Android for better performance
  161. if (!readBuffer_)
  162. {
  163. readBuffer_ = new u8[READ_BUFFER_SIZE];
  164. readBufferOffset_ = 0;
  165. readBufferSize_ = 0;
  166. }
  167. i32 sizeLeft = size;
  168. u8* destPtr = (u8*)dest;
  169. while (sizeLeft)
  170. {
  171. if (readBufferOffset_ >= readBufferSize_)
  172. {
  173. readBufferSize_ = Min(size_ - position_, READ_BUFFER_SIZE);
  174. readBufferOffset_ = 0;
  175. ReadInternal(readBuffer_.Get(), readBufferSize_);
  176. }
  177. i32 copySize = Min((readBufferSize_ - readBufferOffset_), sizeLeft);
  178. memcpy(destPtr, readBuffer_.Get() + readBufferOffset_, copySize);
  179. destPtr += copySize;
  180. sizeLeft -= copySize;
  181. readBufferOffset_ += copySize;
  182. position_ += copySize;
  183. }
  184. return size;
  185. }
  186. #endif
  187. if (compressed_)
  188. {
  189. i32 sizeLeft = size;
  190. u8* destPtr = (u8*)dest;
  191. while (sizeLeft)
  192. {
  193. if (!readBuffer_ || readBufferOffset_ >= readBufferSize_)
  194. {
  195. u8 blockHeaderBytes[4];
  196. ReadInternal(blockHeaderBytes, sizeof blockHeaderBytes);
  197. MemoryBuffer blockHeader(&blockHeaderBytes[0], sizeof blockHeaderBytes);
  198. i32 unpackedSize = blockHeader.ReadU16();
  199. i32 packedSize = blockHeader.ReadU16();
  200. if (!readBuffer_)
  201. {
  202. readBuffer_ = new u8[unpackedSize];
  203. inputBuffer_ = new u8[LZ4_compressBound(unpackedSize)];
  204. }
  205. /// \todo Handle errors
  206. ReadInternal(inputBuffer_.Get(), packedSize);
  207. LZ4_decompress_fast((const char*)inputBuffer_.Get(), (char*)readBuffer_.Get(), unpackedSize);
  208. readBufferSize_ = unpackedSize;
  209. readBufferOffset_ = 0;
  210. }
  211. i32 copySize = Min((readBufferSize_ - readBufferOffset_), sizeLeft);
  212. memcpy(destPtr, readBuffer_.Get() + readBufferOffset_, copySize);
  213. destPtr += copySize;
  214. sizeLeft -= copySize;
  215. readBufferOffset_ += copySize;
  216. position_ += copySize;
  217. }
  218. return size;
  219. }
  220. // Need to reassign the position due to internal buffering when transitioning from writing to reading
  221. if (readSyncNeeded_)
  222. {
  223. SeekInternal(position_ + offset_);
  224. readSyncNeeded_ = false;
  225. }
  226. if (!ReadInternal(dest, size))
  227. {
  228. // Return to the position where the read began
  229. SeekInternal(position_ + offset_);
  230. URHO3D_LOGERROR("Error while reading from file " + GetName());
  231. return 0;
  232. }
  233. writeSyncNeeded_ = true;
  234. position_ += size;
  235. return size;
  236. }
  237. i64 File::Seek(i64 position)
  238. {
  239. assert(position >= 0);
  240. if (!IsOpen())
  241. {
  242. // If file not open, do not log the error further here to prevent spamming the stderr stream
  243. return 0;
  244. }
  245. // Allow sparse seeks if writing
  246. if (mode_ == FILE_READ && position > size_)
  247. position = size_;
  248. if (compressed_)
  249. {
  250. // Start over from the beginning
  251. if (position == 0)
  252. {
  253. position_ = 0;
  254. readBufferOffset_ = 0;
  255. readBufferSize_ = 0;
  256. SeekInternal(offset_);
  257. }
  258. // Skip bytes
  259. else if (position >= position_)
  260. {
  261. u8 skipBuffer[SKIP_BUFFER_SIZE];
  262. while (position > position_)
  263. Read(skipBuffer, Min(position - position_, SKIP_BUFFER_SIZE));
  264. }
  265. else
  266. URHO3D_LOGERROR("Seeking backward in a compressed file is not supported");
  267. return position_;
  268. }
  269. SeekInternal(position + offset_);
  270. position_ = position;
  271. readSyncNeeded_ = false;
  272. writeSyncNeeded_ = false;
  273. return position_;
  274. }
  275. i32 File::Write(const void* data, i32 size)
  276. {
  277. assert(size >= 0);
  278. if (!IsOpen())
  279. {
  280. // If file not open, do not log the error further here to prevent spamming the stderr stream
  281. return 0;
  282. }
  283. if (mode_ == FILE_READ)
  284. {
  285. URHO3D_LOGERROR("File not opened for writing");
  286. return 0;
  287. }
  288. if (!size)
  289. return 0;
  290. // Need to reassign the position due to internal buffering when transitioning from reading to writing
  291. if (writeSyncNeeded_)
  292. {
  293. FSeek64((FILE*)handle_, position_ + offset_, SEEK_SET);
  294. writeSyncNeeded_ = false;
  295. }
  296. if (fwrite(data, size, 1, (FILE*)handle_) != 1)
  297. {
  298. // Return to the position where the write began
  299. FSeek64((FILE*)handle_, position_ + offset_, SEEK_SET);
  300. URHO3D_LOGERROR("Error while writing to file " + GetName());
  301. return 0;
  302. }
  303. readSyncNeeded_ = true;
  304. position_ += size;
  305. if (position_ > size_)
  306. size_ = position_;
  307. return size;
  308. }
  309. hash32 File::GetChecksum()
  310. {
  311. if (offset_ || checksum_)
  312. return checksum_;
  313. #ifdef __ANDROID__
  314. if ((!handle_ && !assetHandle_) || mode_ == FILE_WRITE)
  315. #else
  316. if (!handle_ || mode_ == FILE_WRITE)
  317. #endif
  318. return 0;
  319. URHO3D_PROFILE(CalculateFileChecksum);
  320. i64 oldPos = position_;
  321. checksum_ = 0;
  322. Seek(0);
  323. while (!IsEof())
  324. {
  325. u8 block[1024];
  326. i32 readBytes = Read(block, 1024);
  327. for (i32 i = 0; i < readBytes; ++i)
  328. checksum_ = SDBMHash(checksum_, block[i]);
  329. }
  330. Seek(oldPos);
  331. return checksum_;
  332. }
  333. void File::Close()
  334. {
  335. #ifdef __ANDROID__
  336. if (assetHandle_)
  337. {
  338. SDL_RWclose(assetHandle_);
  339. assetHandle_ = 0;
  340. }
  341. #endif
  342. readBuffer_.Reset();
  343. inputBuffer_.Reset();
  344. if (handle_)
  345. {
  346. fclose((FILE*)handle_);
  347. handle_ = nullptr;
  348. position_ = 0;
  349. size_ = 0;
  350. offset_ = 0;
  351. checksum_ = 0;
  352. }
  353. }
  354. void File::Flush()
  355. {
  356. if (handle_)
  357. fflush((FILE*)handle_);
  358. }
  359. bool File::IsOpen() const
  360. {
  361. #ifdef __ANDROID__
  362. return handle_ != 0 || assetHandle_ != 0;
  363. #else
  364. return handle_ != nullptr;
  365. #endif
  366. }
  367. bool File::OpenInternal(const String& fileName, FileMode mode, bool fromPackage)
  368. {
  369. Close();
  370. compressed_ = false;
  371. readSyncNeeded_ = false;
  372. writeSyncNeeded_ = false;
  373. auto* fileSystem = GetSubsystem<FileSystem>();
  374. if (fileSystem && !fileSystem->CheckAccess(GetPath(fileName)))
  375. {
  376. URHO3D_LOGERRORF("Access denied to %s", fileName.CString());
  377. return false;
  378. }
  379. if (fileName.Empty())
  380. {
  381. URHO3D_LOGERROR("Could not open file with empty name");
  382. return false;
  383. }
  384. #ifdef __ANDROID__
  385. if (URHO3D_IS_ASSET(fileName))
  386. {
  387. if (mode != FILE_READ)
  388. {
  389. URHO3D_LOGERROR("Only read mode is supported for Android asset files");
  390. return false;
  391. }
  392. assetHandle_ = SDL_RWFromFile(URHO3D_ASSET(fileName), "rb");
  393. if (!assetHandle_)
  394. {
  395. URHO3D_LOGERRORF("Could not open Android asset file %s", fileName.CString());
  396. return false;
  397. }
  398. else
  399. {
  400. name_ = fileName;
  401. mode_ = mode;
  402. position_ = 0;
  403. if (!fromPackage)
  404. {
  405. size_ = SDL_RWsize(assetHandle_);
  406. offset_ = 0;
  407. }
  408. checksum_ = 0;
  409. return true;
  410. }
  411. }
  412. #endif
  413. #ifdef _WIN32
  414. handle_ = _wfopen(GetWideNativePath(fileName).CString(), openMode[mode]);
  415. #elif defined(__APPLE__)
  416. handle_ = fopen(GetNativePath(fileName).CString(), openMode[mode]);
  417. #else
  418. handle_ = fopen64(GetNativePath(fileName).CString(), openMode[mode]);
  419. #endif
  420. // If file did not exist in readwrite mode, retry with write-update mode
  421. if (mode == FILE_READWRITE && !handle_)
  422. {
  423. #ifdef _WIN32
  424. handle_ = _wfopen(GetWideNativePath(fileName).CString(), openMode[mode + 1]);
  425. #elif defined(__APPLE__)
  426. handle_ = fopen(GetNativePath(fileName).CString(), openMode[mode + 1]);
  427. #else
  428. handle_ = fopen64(GetNativePath(fileName).CString(), openMode[mode + 1]);
  429. #endif
  430. }
  431. if (!handle_)
  432. {
  433. URHO3D_LOGERRORF("Could not open file %s", fileName.CString());
  434. return false;
  435. }
  436. if (!fromPackage)
  437. {
  438. FSeek64((FILE*)handle_, 0, SEEK_END);
  439. i64 size = FTell64((FILE*)handle_);
  440. FSeek64((FILE*)handle_, 0, SEEK_SET);
  441. if (size > M_MAX_UNSIGNED)
  442. {
  443. URHO3D_LOGERRORF("Could not open file %s which is larger than 4GB", fileName.CString());
  444. Close();
  445. size_ = 0;
  446. return false;
  447. }
  448. size_ = (unsigned)size;
  449. offset_ = 0;
  450. }
  451. name_ = fileName;
  452. mode_ = mode;
  453. position_ = 0;
  454. checksum_ = 0;
  455. return true;
  456. }
  457. bool File::ReadInternal(void* dest, i32 size)
  458. {
  459. assert(size >= 0);
  460. #ifdef __ANDROID__
  461. if (assetHandle_)
  462. {
  463. return SDL_RWread(assetHandle_, dest, size, 1) == 1;
  464. }
  465. else
  466. #endif
  467. return fread(dest, size, 1, (FILE*)handle_) == 1;
  468. }
  469. void File::SeekInternal(i64 newPosition)
  470. {
  471. assert(newPosition >= 0);
  472. #ifdef __ANDROID__
  473. if (assetHandle_)
  474. {
  475. SDL_RWseek(assetHandle_, newPosition, SEEK_SET);
  476. // Reset buffering after seek
  477. readBufferOffset_ = 0;
  478. readBufferSize_ = 0;
  479. }
  480. else
  481. #endif
  482. {
  483. FSeek64((FILE*)handle_, newPosition, SEEK_SET);
  484. }
  485. }
  486. }