File.cpp 13 KB

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