android.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829
  1. /**
  2. * Copyright (c) 2006-2021 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. #include "android.h"
  21. #ifdef LOVE_ANDROID
  22. #include <cerrno>
  23. #include <unordered_map>
  24. #include <SDL.h>
  25. #include <jni.h>
  26. #include <android/asset_manager.h>
  27. #include <android/asset_manager_jni.h>
  28. #include <sys/stat.h>
  29. #include <sys/types.h>
  30. #include <unistd.h>
  31. #include "physfs.h"
  32. namespace love
  33. {
  34. namespace android
  35. {
  36. void setImmersive(bool immersive_active)
  37. {
  38. JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
  39. jobject activity = (jobject) SDL_AndroidGetActivity();
  40. jclass clazz(env->GetObjectClass(activity));
  41. jmethodID method_id = env->GetMethodID(clazz, "setImmersiveMode", "(Z)V");
  42. env->CallVoidMethod(activity, method_id, immersive_active);
  43. env->DeleteLocalRef(activity);
  44. env->DeleteLocalRef(clazz);
  45. }
  46. bool getImmersive()
  47. {
  48. JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
  49. jobject activity = (jobject) SDL_AndroidGetActivity();
  50. jclass clazz(env->GetObjectClass(activity));
  51. jmethodID method_id = env->GetMethodID(clazz, "getImmersiveMode", "()Z");
  52. jboolean immersive_active = env->CallBooleanMethod(activity, method_id);
  53. env->DeleteLocalRef(activity);
  54. env->DeleteLocalRef(clazz);
  55. return immersive_active;
  56. }
  57. double getScreenScale()
  58. {
  59. static double result = -1.;
  60. if (result == -1.)
  61. {
  62. JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
  63. jclass activity = env->FindClass("org/love2d/android/GameActivity");
  64. jmethodID getMetrics = env->GetStaticMethodID(activity, "getMetrics", "()Landroid/util/DisplayMetrics;");
  65. jobject metrics = env->CallStaticObjectMethod(activity, getMetrics);
  66. jclass metricsClass = env->GetObjectClass(metrics);
  67. result = env->GetFloatField(metrics, env->GetFieldID(metricsClass, "density", "F"));
  68. env->DeleteLocalRef(metricsClass);
  69. env->DeleteLocalRef(metrics);
  70. env->DeleteLocalRef(activity);
  71. }
  72. return result;
  73. }
  74. bool getSafeArea(int &top, int &left, int &bottom, int &right)
  75. {
  76. JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
  77. jobject activity = (jobject) SDL_AndroidGetActivity();
  78. jclass clazz(env->GetObjectClass(activity));
  79. jmethodID methodID = env->GetMethodID(clazz, "initializeSafeArea", "()Z");
  80. bool hasSafeArea = false;
  81. if (methodID == nullptr)
  82. // NoSuchMethodException is thrown in case methodID is null
  83. env->ExceptionClear();
  84. else if ((hasSafeArea = env->CallBooleanMethod(activity, methodID)))
  85. {
  86. top = env->GetIntField(activity, env->GetFieldID(clazz, "safeAreaTop", "I"));
  87. left = env->GetIntField(activity, env->GetFieldID(clazz, "safeAreaLeft", "I"));
  88. bottom = env->GetIntField(activity, env->GetFieldID(clazz, "safeAreaBottom", "I"));
  89. right = env->GetIntField(activity, env->GetFieldID(clazz, "safeAreaRight", "I"));
  90. }
  91. env->DeleteLocalRef(clazz);
  92. env->DeleteLocalRef(activity);
  93. return hasSafeArea;
  94. }
  95. const char *getSelectedGameFile()
  96. {
  97. static const char *path = NULL;
  98. if (path)
  99. {
  100. delete path;
  101. path = NULL;
  102. }
  103. JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
  104. jclass activity = env->FindClass("org/love2d/android/GameActivity");
  105. jmethodID getGamePath = env->GetStaticMethodID(activity, "getGamePath", "()Ljava/lang/String;");
  106. jstring gamePath = (jstring) env->CallStaticObjectMethod(activity, getGamePath);
  107. const char *utf = env->GetStringUTFChars(gamePath, 0);
  108. if (utf)
  109. {
  110. path = SDL_strdup(utf);
  111. env->ReleaseStringUTFChars(gamePath, utf);
  112. }
  113. env->DeleteLocalRef(gamePath);
  114. env->DeleteLocalRef(activity);
  115. return path;
  116. }
  117. bool openURL(const std::string &url)
  118. {
  119. JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
  120. jclass activity = env->FindClass("org/love2d/android/GameActivity");
  121. jmethodID openURL = env->GetStaticMethodID(activity, "openURLFromLOVE", "(Ljava/lang/String;)Z");
  122. if (openURL == nullptr)
  123. {
  124. env->ExceptionClear();
  125. openURL = env->GetStaticMethodID(activity, "openURL", "(Ljava/lang/String;)Z");
  126. }
  127. jstring url_jstring = (jstring) env->NewStringUTF(url.c_str());
  128. jboolean result = env->CallStaticBooleanMethod(activity, openURL, url_jstring);
  129. env->DeleteLocalRef(url_jstring);
  130. env->DeleteLocalRef(activity);
  131. return result;
  132. }
  133. void vibrate(double seconds)
  134. {
  135. JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
  136. jclass activity = env->FindClass("org/love2d/android/GameActivity");
  137. jmethodID vibrate_method = env->GetStaticMethodID(activity, "vibrate", "(D)V");
  138. env->CallStaticVoidMethod(activity, vibrate_method, seconds);
  139. env->DeleteLocalRef(activity);
  140. }
  141. /*
  142. * Helper functions for the filesystem module
  143. */
  144. void freeGameArchiveMemory(void *ptr)
  145. {
  146. char *game_love_data = static_cast<char*>(ptr);
  147. delete[] game_love_data;
  148. }
  149. bool loadGameArchiveToMemory(const char* filename, char **ptr, size_t *size)
  150. {
  151. SDL_RWops *asset_game_file = SDL_RWFromFile(filename, "rb");
  152. if (!asset_game_file) {
  153. SDL_Log("Could not find %s", filename);
  154. return false;
  155. }
  156. Sint64 file_size = asset_game_file->size(asset_game_file);
  157. if (file_size <= 0) {
  158. SDL_Log("Could not load game from %s. File has invalid file size: %d.", filename, (int) file_size);
  159. return false;
  160. }
  161. *ptr = new char[file_size];
  162. if (!*ptr) {
  163. SDL_Log("Could not allocate memory for in-memory game archive");
  164. return false;
  165. }
  166. size_t bytes_copied = asset_game_file->read(asset_game_file, (void*) *ptr, sizeof(char), (size_t) file_size);
  167. if (bytes_copied != file_size) {
  168. SDL_Log("Incomplete copy of in-memory game archive!");
  169. delete[] *ptr;
  170. return false;
  171. }
  172. *size = (size_t) file_size;
  173. return true;
  174. }
  175. bool directoryExists(const char *path)
  176. {
  177. struct stat s;
  178. int err = stat(path, &s);
  179. if (err == -1)
  180. {
  181. if (errno != ENOENT)
  182. SDL_Log("Error checking for directory %s errno = %d: %s", path, errno, strerror(errno));
  183. return false;
  184. }
  185. return S_ISDIR(s.st_mode);
  186. }
  187. bool mkdir(const char *path)
  188. {
  189. int err = ::mkdir(path, 0770);
  190. if (err == -1)
  191. {
  192. SDL_Log("Error: Could not create directory %s", path);
  193. return false;
  194. }
  195. return true;
  196. }
  197. bool createStorageDirectories()
  198. {
  199. std::string internal_storage_path = SDL_AndroidGetInternalStoragePath();
  200. std::string save_directory = internal_storage_path + "/save";
  201. if (!directoryExists(save_directory.c_str()) && !mkdir(save_directory.c_str()))
  202. return false;
  203. std::string game_directory = internal_storage_path + "/game";
  204. if (!directoryExists (game_directory.c_str()) && !mkdir(game_directory.c_str()))
  205. return false;
  206. return true;
  207. }
  208. bool hasBackgroundMusic()
  209. {
  210. JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
  211. jobject activity = (jobject) SDL_AndroidGetActivity();
  212. jclass clazz(env->GetObjectClass(activity));
  213. jmethodID method_id = env->GetMethodID(clazz, "hasBackgroundMusic", "()Z");
  214. jboolean result = env->CallBooleanMethod(activity, method_id);
  215. env->DeleteLocalRef(activity);
  216. env->DeleteLocalRef(clazz);
  217. return result;
  218. }
  219. bool hasRecordingPermission()
  220. {
  221. JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
  222. jobject activity = (jobject) SDL_AndroidGetActivity();
  223. jclass clazz(env->GetObjectClass(activity));
  224. jmethodID methodID = env->GetMethodID(clazz, "hasRecordAudioPermission", "()Z");
  225. jboolean result = false;
  226. if (methodID == nullptr)
  227. env->ExceptionClear();
  228. else
  229. result = env->CallBooleanMethod(activity, methodID);
  230. env->DeleteLocalRef(activity);
  231. env->DeleteLocalRef(clazz);
  232. return result;
  233. }
  234. void requestRecordingPermission()
  235. {
  236. JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
  237. jobject activity = (jobject) SDL_AndroidGetActivity();
  238. jclass clazz(env->GetObjectClass(activity));
  239. jmethodID methodID = env->GetMethodID(clazz, "requestRecordAudioPermission", "()V");
  240. if (methodID == nullptr)
  241. env->ExceptionClear();
  242. else
  243. env->CallVoidMethod(activity, methodID);
  244. env->DeleteLocalRef(clazz);
  245. env->DeleteLocalRef(activity);
  246. }
  247. void showRecordingPermissionMissingDialog()
  248. {
  249. JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
  250. jobject activity = (jobject) SDL_AndroidGetActivity();
  251. jclass clazz(env->GetObjectClass(activity));
  252. jmethodID methodID = env->GetMethodID(clazz, "showRecordingAudioPermissionMissingDialog", "()V");
  253. if (methodID == nullptr)
  254. env->ExceptionClear();
  255. else
  256. env->CallVoidMethod(activity, methodID);
  257. env->DeleteLocalRef(clazz);
  258. env->DeleteLocalRef(activity);
  259. }
  260. /* A container for AssetManager Java object */
  261. class AssetManagerObject
  262. {
  263. public:
  264. AssetManagerObject()
  265. {
  266. JNIEnv *env = (JNIEnv *) SDL_AndroidGetJNIEnv();
  267. jobject am = getLocalAssetManager(env);
  268. assetManager = env->NewGlobalRef(am);
  269. env->DeleteLocalRef(am);
  270. }
  271. ~AssetManagerObject()
  272. {
  273. JNIEnv *env = (JNIEnv *) SDL_AndroidGetJNIEnv();
  274. env->DeleteGlobalRef(assetManager);
  275. }
  276. static jobject getLocalAssetManager(JNIEnv *env) {
  277. jobject self = (jobject) SDL_AndroidGetActivity();
  278. jclass activity = env->GetObjectClass(self);
  279. jmethodID method = env->GetMethodID(activity, "getAssets", "()Landroid/content/res/AssetManager;");
  280. jobject am = env->CallObjectMethod(self, method);
  281. env->DeleteLocalRef(self);
  282. env->DeleteLocalRef(activity);
  283. return am;
  284. }
  285. explicit operator jobject()
  286. {
  287. return assetManager;
  288. };
  289. private:
  290. jobject assetManager;
  291. };
  292. /*
  293. * Helper functions to aid new fusing method
  294. */
  295. // This returns *global* reference, no need to free it.
  296. static jobject getJavaAssetManager()
  297. {
  298. static AssetManagerObject assetManager;
  299. return (jobject) assetManager;
  300. }
  301. static AAssetManager *getAssetManager()
  302. {
  303. JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
  304. return AAssetManager_fromJava(env, (jobject) getJavaAssetManager());
  305. }
  306. namespace aasset
  307. {
  308. namespace io
  309. {
  310. struct AssetInfo
  311. {
  312. AAssetManager *assetManager;
  313. AAsset *asset;
  314. char *filename;
  315. size_t size;
  316. };
  317. static std::unordered_map<std::string, PHYSFS_FileType> fileTree;
  318. PHYSFS_sint64 read(PHYSFS_Io *io, void *buf, PHYSFS_uint64 len)
  319. {
  320. AAsset *asset = ((AssetInfo *) io->opaque)->asset;
  321. int readed = AAsset_read(asset, buf, (size_t) len);
  322. PHYSFS_setErrorCode(readed < 0 ? PHYSFS_ERR_OS_ERROR : PHYSFS_ERR_OK);
  323. return (PHYSFS_sint64) readed;
  324. }
  325. PHYSFS_sint64 write(PHYSFS_Io *io, const void *buf, PHYSFS_uint64 len)
  326. {
  327. LOVE_UNUSED(io);
  328. LOVE_UNUSED(buf);
  329. LOVE_UNUSED(len);
  330. PHYSFS_setErrorCode(PHYSFS_ERR_READ_ONLY);
  331. return -1;
  332. }
  333. int seek(PHYSFS_Io *io, PHYSFS_uint64 offset)
  334. {
  335. AAsset *asset = ((AssetInfo *) io->opaque)->asset;
  336. int success = AAsset_seek64(asset, (off64_t) offset, SEEK_SET) != -1;
  337. PHYSFS_setErrorCode(success ? PHYSFS_ERR_OK : PHYSFS_ERR_OS_ERROR);
  338. return success;
  339. }
  340. PHYSFS_sint64 tell(PHYSFS_Io *io)
  341. {
  342. AAsset *asset = ((AssetInfo *) io->opaque)->asset;
  343. off64_t len = AAsset_getLength64(asset);
  344. off64_t remain = AAsset_getRemainingLength64(asset);
  345. return len - remain;
  346. }
  347. PHYSFS_sint64 length(PHYSFS_Io *io)
  348. {
  349. AAsset *asset = ((AssetInfo *) io->opaque)->asset;
  350. return AAsset_getLength64(asset);
  351. }
  352. // Forward declaration
  353. PHYSFS_Io *fromAAsset(AAssetManager *assetManager, const char *filename, AAsset *asset);
  354. PHYSFS_Io *duplicate(PHYSFS_Io *io)
  355. {
  356. AssetInfo *assetInfo = (AssetInfo *) io->opaque;
  357. AAsset *asset = AAssetManager_open(assetInfo->assetManager, assetInfo->filename, AASSET_MODE_RANDOM);
  358. if (asset == nullptr)
  359. {
  360. PHYSFS_setErrorCode(PHYSFS_ERR_OS_ERROR);
  361. return nullptr;
  362. }
  363. AAsset_seek64(asset, tell(io), SEEK_SET);
  364. return fromAAsset(assetInfo->assetManager, assetInfo->filename, asset);
  365. }
  366. void destroy(PHYSFS_Io *io)
  367. {
  368. AssetInfo *assetInfo = (AssetInfo *) io->opaque;
  369. AAsset_close(assetInfo->asset);
  370. delete[] assetInfo->filename;
  371. delete assetInfo;
  372. delete io;
  373. }
  374. PHYSFS_Io *fromAAsset(AAssetManager *assetManager, const char *filename, AAsset *asset)
  375. {
  376. // Create AssetInfo
  377. AssetInfo *assetInfo = new (std::nothrow) AssetInfo();
  378. assetInfo->assetManager = assetManager;
  379. assetInfo->asset = asset;
  380. assetInfo->size = strlen(filename) + 1;
  381. assetInfo->filename = new (std::nothrow) char[assetInfo->size];
  382. memcpy(assetInfo->filename, filename, assetInfo->size);
  383. // Create PHYSFS_Io
  384. PHYSFS_Io *io = new (std::nothrow) PHYSFS_Io();
  385. io->version = 0;
  386. io->opaque = assetInfo;
  387. io->read = read;
  388. io->write = write;
  389. io->seek = seek;
  390. io->tell = tell;
  391. io->length = length;
  392. io->duplicate = duplicate;
  393. io->flush = nullptr;
  394. io->destroy = destroy;
  395. return io;
  396. }
  397. }
  398. void *openArchive(PHYSFS_Io *io, const char *name, int forWrite, int *claimed)
  399. {
  400. if (io->opaque == nullptr || memcmp(io->opaque, "ASET", 4) != 0)
  401. return nullptr;
  402. // It's our archive
  403. *claimed = 1;
  404. AAssetManager *assetManager = getAssetManager();
  405. if (io::fileTree.empty())
  406. {
  407. // AAssetDir_getNextFileName intentionally excludes directories, so
  408. // we have to use JNI that calls AssetManager.list() recursively.
  409. JNIEnv *env = (JNIEnv *) SDL_AndroidGetJNIEnv();
  410. jobject activity = (jobject) SDL_AndroidGetActivity();
  411. jclass clazz = env->GetObjectClass(activity);
  412. jmethodID method = env->GetMethodID(clazz, "buildFileTree", "()[Ljava/lang/String;");
  413. jobjectArray list = (jobjectArray) env->CallObjectMethod(activity, method);
  414. for (jsize i = 0; i < env->GetArrayLength(list); i++)
  415. {
  416. jstring jstr = (jstring) env->GetObjectArrayElement(list, i);
  417. const char *str = env->GetStringUTFChars(jstr, nullptr);
  418. io::fileTree[str + 1] = str[0] == 'd' ? PHYSFS_FILETYPE_DIRECTORY : PHYSFS_FILETYPE_REGULAR;
  419. env->ReleaseStringUTFChars(jstr, str);
  420. env->DeleteLocalRef(jstr);
  421. }
  422. env->DeleteLocalRef(list);
  423. env->DeleteLocalRef(clazz);
  424. env->DeleteLocalRef(activity);
  425. }
  426. return assetManager;
  427. }
  428. PHYSFS_EnumerateCallbackResult enumerate(
  429. void *opaque,
  430. const char *dirname,
  431. PHYSFS_EnumerateCallback cb,
  432. const char *origdir,
  433. void *callbackdata
  434. )
  435. {
  436. using FileTreeIterator = std::unordered_map<std::string, PHYSFS_FileType>::iterator;
  437. LOVE_UNUSED(opaque);
  438. const char *path = dirname;
  439. if (path == nullptr || (path[0] == '/' && path[1] == 0))
  440. path = "";
  441. if (path[0] != 0)
  442. {
  443. FileTreeIterator result = io::fileTree.find(path);
  444. if (result == io::fileTree.end() || result->second != PHYSFS_FILETYPE_DIRECTORY)
  445. {
  446. PHYSFS_setErrorCode(PHYSFS_ERR_NOT_FOUND);
  447. return PHYSFS_ENUM_ERROR;
  448. }
  449. }
  450. JNIEnv *env = (JNIEnv *) SDL_AndroidGetJNIEnv();
  451. jobject assetManager = getJavaAssetManager();
  452. jclass clazz = env->GetObjectClass(assetManager);
  453. jmethodID method = env->GetMethodID(clazz, "list", "(Ljava/lang/String;)[Ljava/lang/String;");
  454. jstring jstringDir = env->NewStringUTF(path);
  455. jobjectArray dir = (jobjectArray) env->CallObjectMethod(assetManager, method, jstringDir);
  456. PHYSFS_EnumerateCallbackResult ret = PHYSFS_ENUM_OK;
  457. if (env->ExceptionCheck())
  458. {
  459. // IOException occured
  460. ret = PHYSFS_ENUM_ERROR;
  461. env->ExceptionClear();
  462. }
  463. else
  464. {
  465. jsize i = 0;
  466. jsize len = env->GetArrayLength(dir);
  467. while (ret == PHYSFS_ENUM_OK && i < len) {
  468. jstring jstr = (jstring) env->GetObjectArrayElement(dir, i++);
  469. const char *name = env->GetStringUTFChars(jstr, nullptr);
  470. ret = cb(callbackdata, origdir, name);
  471. env->ReleaseStringUTFChars(jstr, name);
  472. env->DeleteLocalRef(jstr);
  473. }
  474. env->DeleteLocalRef(dir);
  475. }
  476. env->DeleteLocalRef(jstringDir);
  477. env->DeleteLocalRef(clazz);
  478. return ret;
  479. }
  480. PHYSFS_Io *openRead(void *opaque, const char *name)
  481. {
  482. AAssetManager *assetManager = (AAssetManager *) opaque;
  483. AAsset *file = AAssetManager_open(assetManager, name, AASSET_MODE_UNKNOWN);
  484. if (file == nullptr)
  485. {
  486. PHYSFS_setErrorCode(PHYSFS_ERR_NOT_FOUND);
  487. return nullptr;
  488. }
  489. PHYSFS_setErrorCode(PHYSFS_ERR_OK);
  490. return io::fromAAsset(assetManager, name, file);
  491. }
  492. PHYSFS_Io *openWriteAppend(void *opaque, const char *name)
  493. {
  494. LOVE_UNUSED(opaque);
  495. LOVE_UNUSED(name);
  496. // AAsset doesn't support modification
  497. PHYSFS_setErrorCode(PHYSFS_ERR_READ_ONLY);
  498. return nullptr;
  499. }
  500. int removeMkdir(void *opaque, const char *name)
  501. {
  502. LOVE_UNUSED(opaque);
  503. LOVE_UNUSED(name);
  504. // AAsset doesn't support modification
  505. PHYSFS_setErrorCode(PHYSFS_ERR_READ_ONLY);
  506. return 0;
  507. }
  508. int stat(void *opaque, const char *name, PHYSFS_Stat *out)
  509. {
  510. LOVE_UNUSED(opaque);
  511. auto result = io::fileTree.find(name);
  512. if (result != io::fileTree.end())
  513. {
  514. out->filetype = result->second;
  515. out->modtime = -1;
  516. out->createtime = -1;
  517. out->accesstime = -1;
  518. out->readonly = 1;
  519. PHYSFS_setErrorCode(PHYSFS_ERR_OK);
  520. return 1;
  521. }
  522. else
  523. {
  524. PHYSFS_setErrorCode(PHYSFS_ERR_NOT_FOUND);
  525. return 0;
  526. }
  527. }
  528. void closeArchive(void *opaque)
  529. {
  530. // Do nothing
  531. LOVE_UNUSED(opaque);
  532. PHYSFS_setErrorCode(PHYSFS_ERR_OK);
  533. }
  534. static PHYSFS_Archiver g_AAssetArchiver = {
  535. 0,
  536. {
  537. "AASSET",
  538. "Android AAsset Wrapper",
  539. "LOVE Development Team",
  540. "https://developer.android.com/ndk/reference/group/asset",
  541. 0
  542. },
  543. openArchive,
  544. enumerate,
  545. openRead,
  546. openWriteAppend,
  547. openWriteAppend,
  548. removeMkdir,
  549. removeMkdir,
  550. stat,
  551. closeArchive
  552. };
  553. static PHYSFS_sint64 dummyReturn0(PHYSFS_Io *io)
  554. {
  555. LOVE_UNUSED(io);
  556. PHYSFS_setErrorCode(PHYSFS_ERR_OK);
  557. return 0;
  558. }
  559. static PHYSFS_Io *getDummyIO(PHYSFS_Io *io);
  560. static char dummyOpaque[] = "ASET";
  561. static PHYSFS_Io dummyIo = {
  562. 0,
  563. dummyOpaque,
  564. nullptr,
  565. nullptr,
  566. [](PHYSFS_Io *io, PHYSFS_uint64 offset) -> int
  567. {
  568. PHYSFS_setErrorCode(offset == 0 ? PHYSFS_ERR_OK : PHYSFS_ERR_PAST_EOF);
  569. return offset == 0;
  570. },
  571. dummyReturn0,
  572. dummyReturn0,
  573. getDummyIO,
  574. nullptr,
  575. [](PHYSFS_Io *io) { LOVE_UNUSED(io); }
  576. };
  577. static PHYSFS_Io *getDummyIO(PHYSFS_Io *io)
  578. {
  579. return &dummyIo;
  580. }
  581. }
  582. static bool isVirtualArchiveInitialized = false;
  583. bool initializeVirtualArchive()
  584. {
  585. if (isVirtualArchiveInitialized)
  586. return true;
  587. if (!PHYSFS_registerArchiver(&aasset::g_AAssetArchiver))
  588. return false;
  589. if (!PHYSFS_mountIo(&aasset::dummyIo, "ASET.AASSET", nullptr, 0))
  590. {
  591. PHYSFS_deregisterArchiver(aasset::g_AAssetArchiver.info.extension);
  592. return false;
  593. }
  594. isVirtualArchiveInitialized = true;
  595. return true;
  596. }
  597. void deinitializeVirtualArchive()
  598. {
  599. if (isVirtualArchiveInitialized)
  600. {
  601. PHYSFS_deregisterArchiver(aasset::g_AAssetArchiver.info.extension);
  602. isVirtualArchiveInitialized = false;
  603. }
  604. }
  605. bool checkFusedGame(void **physfsIO_Out)
  606. {
  607. // TODO: Reorder the loading in 12.0
  608. PHYSFS_Io *&io = *(PHYSFS_Io **) physfsIO_Out;
  609. AAssetManager *assetManager = getAssetManager();
  610. // Prefer game.love inside assets/ folder
  611. AAsset *asset = AAssetManager_open(assetManager, "game.love", AASSET_MODE_RANDOM);
  612. if (asset)
  613. {
  614. io = aasset::io::fromAAsset(assetManager, "game.love", asset);
  615. return true;
  616. }
  617. // If there's no game.love inside assets/ try main.lua
  618. asset = AAssetManager_open(assetManager, "main.lua", AASSET_MODE_STREAMING);
  619. if (asset)
  620. {
  621. AAsset_close(asset);
  622. io = nullptr;
  623. return true;
  624. }
  625. // Not found
  626. return false;
  627. }
  628. const char *getCRequirePath()
  629. {
  630. static bool initialized = false;
  631. static const char *path = nullptr;
  632. if (!initialized)
  633. {
  634. JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
  635. jobject activity = (jobject) SDL_AndroidGetActivity();
  636. jclass clazz(env->GetObjectClass(activity));
  637. jmethodID method_id = env->GetMethodID(clazz, "getCRequirePath", "()Ljava/lang/String;");
  638. path = "";
  639. initialized = true;
  640. if (method_id)
  641. {
  642. jstring cpath = (jstring) env->CallObjectMethod(activity, method_id);
  643. const char *utf = env->GetStringUTFChars(cpath, nullptr);
  644. if (utf)
  645. {
  646. path = SDL_strdup(utf);
  647. env->ReleaseStringUTFChars(cpath, utf);
  648. }
  649. env->DeleteLocalRef(cpath);
  650. }
  651. else
  652. {
  653. // NoSuchMethodException is thrown in case methodID is null
  654. env->ExceptionClear();
  655. return "";
  656. }
  657. env->DeleteLocalRef(activity);
  658. env->DeleteLocalRef(clazz);
  659. }
  660. return path;
  661. }
  662. } // android
  663. } // love
  664. #endif // LOVE_ANDROID