android.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810
  1. /**
  2. * Copyright (c) 2006-2023 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 "filesystem/physfs/PhysfsIo.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. struct AssetInfo: public love::filesystem::physfs::PhysfsIo<AssetInfo>
  309. {
  310. static const uint32_t version = 0;
  311. AAssetManager *assetManager;
  312. AAsset *asset;
  313. char *filename;
  314. size_t size;
  315. static AssetInfo *fromAAsset(AAssetManager *assetManager, const char *filename, AAsset *asset)
  316. {
  317. return new AssetInfo(assetManager, filename, asset);
  318. }
  319. int64_t read(void* buf, uint64_t len) const
  320. {
  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. int64_t write(const void* buf, uint64_t len) const
  326. {
  327. LOVE_UNUSED(buf);
  328. LOVE_UNUSED(len);
  329. PHYSFS_setErrorCode(PHYSFS_ERR_READ_ONLY);
  330. return -1;
  331. }
  332. int64_t seek(uint64_t offset) const
  333. {
  334. int64_t success = AAsset_seek64(asset, (off64_t) offset, SEEK_SET) != -1;
  335. PHYSFS_setErrorCode(success ? PHYSFS_ERR_OK : PHYSFS_ERR_OS_ERROR);
  336. return success;
  337. }
  338. int64_t tell() const
  339. {
  340. off64_t len = AAsset_getLength64(asset);
  341. off64_t remain = AAsset_getRemainingLength64(asset);
  342. return len - remain;
  343. }
  344. int64_t length() const
  345. {
  346. return AAsset_getLength64(asset);
  347. }
  348. int64_t flush() const
  349. {
  350. // Do nothing
  351. PHYSFS_setErrorCode(PHYSFS_ERR_OK);
  352. return 1;
  353. }
  354. AssetInfo *duplicate() const
  355. {
  356. AAsset *newAsset = AAssetManager_open(assetManager, filename, AASSET_MODE_RANDOM);
  357. if (newAsset == nullptr)
  358. {
  359. PHYSFS_setErrorCode(PHYSFS_ERR_OS_ERROR);
  360. return nullptr;
  361. }
  362. AAsset_seek64(asset, tell(), SEEK_SET);
  363. return fromAAsset(assetManager, filename, asset);
  364. }
  365. ~AssetInfo() override
  366. {
  367. AAsset_close(asset);
  368. delete[] filename;
  369. }
  370. private:
  371. AssetInfo(AAssetManager *assetManager, const char *filename, AAsset *asset)
  372. : assetManager(assetManager)
  373. , asset(asset)
  374. , size(strlen(filename) + 1)
  375. {
  376. this->filename = new (std::nothrow) char[size];
  377. memcpy(this->filename, filename, size);
  378. }
  379. };
  380. static std::unordered_map<std::string, PHYSFS_FileType> fileTree;
  381. void *openArchive(PHYSFS_Io *io, const char *name, int forWrite, int *claimed)
  382. {
  383. if (forWrite || io->opaque == nullptr || memcmp(io->opaque, "ASET", 4) != 0)
  384. return nullptr;
  385. // It's our archive
  386. *claimed = 1;
  387. AAssetManager *assetManager = getAssetManager();
  388. if (fileTree.empty())
  389. {
  390. // AAssetDir_getNextFileName intentionally excludes directories, so
  391. // we have to use JNI that calls AssetManager.list() recursively.
  392. JNIEnv *env = (JNIEnv *) SDL_AndroidGetJNIEnv();
  393. jobject activity = (jobject) SDL_AndroidGetActivity();
  394. jclass clazz = env->GetObjectClass(activity);
  395. jmethodID method = env->GetMethodID(clazz, "buildFileTree", "()[Ljava/lang/String;");
  396. jobjectArray list = (jobjectArray) env->CallObjectMethod(activity, method);
  397. for (jsize i = 0; i < env->GetArrayLength(list); i++)
  398. {
  399. jstring jstr = (jstring) env->GetObjectArrayElement(list, i);
  400. const char *str = env->GetStringUTFChars(jstr, nullptr);
  401. fileTree[str + 1] = str[0] == 'd' ? PHYSFS_FILETYPE_DIRECTORY : PHYSFS_FILETYPE_REGULAR;
  402. env->ReleaseStringUTFChars(jstr, str);
  403. env->DeleteLocalRef(jstr);
  404. }
  405. env->DeleteLocalRef(list);
  406. env->DeleteLocalRef(clazz);
  407. env->DeleteLocalRef(activity);
  408. }
  409. return assetManager;
  410. }
  411. PHYSFS_EnumerateCallbackResult enumerate(
  412. void *opaque,
  413. const char *dirname,
  414. PHYSFS_EnumerateCallback cb,
  415. const char *origdir,
  416. void *callbackdata
  417. )
  418. {
  419. using FileTreeIterator = std::unordered_map<std::string, PHYSFS_FileType>::iterator;
  420. LOVE_UNUSED(opaque);
  421. const char *path = dirname;
  422. if (path == nullptr || (path[0] == '/' && path[1] == 0))
  423. path = "";
  424. if (path[0] != 0)
  425. {
  426. FileTreeIterator result = fileTree.find(path);
  427. if (result == fileTree.end() || result->second != PHYSFS_FILETYPE_DIRECTORY)
  428. {
  429. PHYSFS_setErrorCode(PHYSFS_ERR_NOT_FOUND);
  430. return PHYSFS_ENUM_ERROR;
  431. }
  432. }
  433. JNIEnv *env = (JNIEnv *) SDL_AndroidGetJNIEnv();
  434. jobject assetManager = getJavaAssetManager();
  435. jclass clazz = env->GetObjectClass(assetManager);
  436. jmethodID method = env->GetMethodID(clazz, "list", "(Ljava/lang/String;)[Ljava/lang/String;");
  437. jstring jstringDir = env->NewStringUTF(path);
  438. jobjectArray dir = (jobjectArray) env->CallObjectMethod(assetManager, method, jstringDir);
  439. PHYSFS_EnumerateCallbackResult ret = PHYSFS_ENUM_OK;
  440. if (env->ExceptionCheck())
  441. {
  442. // IOException occured
  443. ret = PHYSFS_ENUM_ERROR;
  444. env->ExceptionClear();
  445. }
  446. else
  447. {
  448. jsize i = 0;
  449. jsize len = env->GetArrayLength(dir);
  450. while (ret == PHYSFS_ENUM_OK && i < len) {
  451. jstring jstr = (jstring) env->GetObjectArrayElement(dir, i++);
  452. const char *name = env->GetStringUTFChars(jstr, nullptr);
  453. ret = cb(callbackdata, origdir, name);
  454. env->ReleaseStringUTFChars(jstr, name);
  455. env->DeleteLocalRef(jstr);
  456. }
  457. env->DeleteLocalRef(dir);
  458. }
  459. env->DeleteLocalRef(jstringDir);
  460. env->DeleteLocalRef(clazz);
  461. return ret;
  462. }
  463. PHYSFS_Io *openRead(void *opaque, const char *name)
  464. {
  465. AAssetManager *assetManager = (AAssetManager *) opaque;
  466. AAsset *file = AAssetManager_open(assetManager, name, AASSET_MODE_UNKNOWN);
  467. if (file == nullptr)
  468. {
  469. PHYSFS_setErrorCode(PHYSFS_ERR_NOT_FOUND);
  470. return nullptr;
  471. }
  472. PHYSFS_setErrorCode(PHYSFS_ERR_OK);
  473. return AssetInfo::fromAAsset(assetManager, name, file);
  474. }
  475. PHYSFS_Io *openWriteAppend(void *opaque, const char *name)
  476. {
  477. LOVE_UNUSED(opaque);
  478. LOVE_UNUSED(name);
  479. // AAsset doesn't support modification
  480. PHYSFS_setErrorCode(PHYSFS_ERR_READ_ONLY);
  481. return nullptr;
  482. }
  483. int removeMkdir(void *opaque, const char *name)
  484. {
  485. LOVE_UNUSED(opaque);
  486. LOVE_UNUSED(name);
  487. // AAsset doesn't support modification
  488. PHYSFS_setErrorCode(PHYSFS_ERR_READ_ONLY);
  489. return 0;
  490. }
  491. int stat(void *opaque, const char *name, PHYSFS_Stat *out)
  492. {
  493. using FileTreeIterator = std::unordered_map<std::string, PHYSFS_FileType>::iterator;
  494. LOVE_UNUSED(opaque);
  495. FileTreeIterator result = fileTree.find(name);
  496. if (result != fileTree.end())
  497. {
  498. out->filetype = result->second;
  499. out->modtime = -1;
  500. out->createtime = -1;
  501. out->accesstime = -1;
  502. out->readonly = 1;
  503. PHYSFS_setErrorCode(PHYSFS_ERR_OK);
  504. return 1;
  505. }
  506. PHYSFS_setErrorCode(PHYSFS_ERR_NOT_FOUND);
  507. return 0;
  508. }
  509. void closeArchive(void *opaque)
  510. {
  511. // Do nothing
  512. LOVE_UNUSED(opaque);
  513. PHYSFS_setErrorCode(PHYSFS_ERR_OK);
  514. }
  515. static PHYSFS_Archiver g_AAssetArchiver = {
  516. 0,
  517. {
  518. "AASSET",
  519. "Android AAsset Wrapper",
  520. "LOVE Development Team",
  521. "https://developer.android.com/ndk/reference/group/asset",
  522. 0
  523. },
  524. openArchive,
  525. enumerate,
  526. openRead,
  527. openWriteAppend,
  528. openWriteAppend,
  529. removeMkdir,
  530. removeMkdir,
  531. stat,
  532. closeArchive
  533. };
  534. static PHYSFS_sint64 dummyReturn0(PHYSFS_Io *io)
  535. {
  536. LOVE_UNUSED(io);
  537. PHYSFS_setErrorCode(PHYSFS_ERR_OK);
  538. return 0;
  539. }
  540. static PHYSFS_Io *getDummyIO(PHYSFS_Io *io);
  541. static char dummyOpaque[] = "ASET";
  542. static PHYSFS_Io dummyIo = {
  543. 0,
  544. dummyOpaque,
  545. nullptr,
  546. nullptr,
  547. [](PHYSFS_Io *io, PHYSFS_uint64 offset) -> int
  548. {
  549. PHYSFS_setErrorCode(offset == 0 ? PHYSFS_ERR_OK : PHYSFS_ERR_PAST_EOF);
  550. return offset == 0;
  551. },
  552. dummyReturn0,
  553. dummyReturn0,
  554. getDummyIO,
  555. nullptr,
  556. [](PHYSFS_Io *io) { LOVE_UNUSED(io); }
  557. };
  558. static PHYSFS_Io *getDummyIO(PHYSFS_Io *io)
  559. {
  560. return &dummyIo;
  561. }
  562. }
  563. static bool isVirtualArchiveInitialized = false;
  564. bool initializeVirtualArchive()
  565. {
  566. if (isVirtualArchiveInitialized)
  567. return true;
  568. if (!PHYSFS_registerArchiver(&aasset::g_AAssetArchiver))
  569. return false;
  570. if (!PHYSFS_mountIo(&aasset::dummyIo, "ASET.AASSET", nullptr, 0))
  571. {
  572. PHYSFS_deregisterArchiver(aasset::g_AAssetArchiver.info.extension);
  573. return false;
  574. }
  575. isVirtualArchiveInitialized = true;
  576. return true;
  577. }
  578. void deinitializeVirtualArchive()
  579. {
  580. if (isVirtualArchiveInitialized)
  581. {
  582. PHYSFS_deregisterArchiver(aasset::g_AAssetArchiver.info.extension);
  583. isVirtualArchiveInitialized = false;
  584. }
  585. }
  586. bool checkFusedGame(void **physfsIO_Out)
  587. {
  588. // TODO: Reorder the loading in 12.0
  589. PHYSFS_Io *&io = *(PHYSFS_Io **) physfsIO_Out;
  590. AAssetManager *assetManager = getAssetManager();
  591. // Prefer game.love inside assets/ folder
  592. AAsset *asset = AAssetManager_open(assetManager, "game.love", AASSET_MODE_RANDOM);
  593. if (asset)
  594. {
  595. io = aasset::AssetInfo::fromAAsset(assetManager, "game.love", asset);
  596. return true;
  597. }
  598. // If there's no game.love inside assets/ try main.lua
  599. asset = AAssetManager_open(assetManager, "main.lua", AASSET_MODE_STREAMING);
  600. if (asset)
  601. {
  602. AAsset_close(asset);
  603. io = nullptr;
  604. return true;
  605. }
  606. // Not found
  607. return false;
  608. }
  609. const char *getCRequirePath()
  610. {
  611. static bool initialized = false;
  612. static const char *path = nullptr;
  613. if (!initialized)
  614. {
  615. JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
  616. jobject activity = (jobject) SDL_AndroidGetActivity();
  617. jclass clazz(env->GetObjectClass(activity));
  618. jmethodID method_id = env->GetMethodID(clazz, "getCRequirePath", "()Ljava/lang/String;");
  619. path = "";
  620. initialized = true;
  621. if (method_id)
  622. {
  623. jstring cpath = (jstring) env->CallObjectMethod(activity, method_id);
  624. const char *utf = env->GetStringUTFChars(cpath, nullptr);
  625. if (utf)
  626. {
  627. path = SDL_strdup(utf);
  628. env->ReleaseStringUTFChars(cpath, utf);
  629. }
  630. env->DeleteLocalRef(cpath);
  631. }
  632. else
  633. {
  634. // NoSuchMethodException is thrown in case methodID is null
  635. env->ExceptionClear();
  636. return "";
  637. }
  638. env->DeleteLocalRef(activity);
  639. env->DeleteLocalRef(clazz);
  640. }
  641. return path;
  642. }
  643. } // android
  644. } // love
  645. #endif // LOVE_ANDROID