diff --git a/project.hxp b/project.hxp index ee8a9c48..4a979af3 100644 --- a/project.hxp +++ b/project.hxp @@ -32,6 +32,7 @@ class Project extends HXProject static final VIDEOS_ALLOWED:CompileFlag = CompileFlag.get("VIDEOS_ALLOWED"); static final DISCORD_ALLOWED:CompileFlag = CompileFlag.get("DISCORD_ALLOWED"); static final DCEBUILD:CompileFlag = CompileFlag.get("DCEBUILD"); + static final USE_OPENFL_FILESYSTEM:CompileFlag = CompileFlag.get("USE_OPENFL_FILESYSTEM"); static final ENABLE_ASCII_ART:Bool = true; @@ -215,6 +216,7 @@ class Project extends HXProject VIDEOS_ALLOWED.integrate((isDesktop() && !isHashLink()) || (isMobile() && !isIOSSimulator())); DISCORD_ALLOWED.integrate((isDesktop() && !isHashLink())); DCEBUILD.integrate(false); + USE_OPENFL_FILESYSTEM.integrate(MODS_ALLOWED.isDisabled() && !isMobile()); setHaxedef("FLX_NO_FOCUS_LOST_SCREEN"); if (!isDebug()) diff --git a/source/engine/backend/Main.hx b/source/engine/backend/Main.hx index 3feee8f4..9aecf820 100644 --- a/source/engine/backend/Main.hx +++ b/source/engine/backend/Main.hx @@ -70,6 +70,7 @@ class Main extends Sprite #if android StorageUtil.requestPermissions(); #end + mobile.backend.io.Assets.init(); #end super(); diff --git a/source/engine/backend/Paths.hx b/source/engine/backend/Paths.hx index 146e5d75..4b819b1e 100644 --- a/source/engine/backend/Paths.hx +++ b/source/engine/backend/Paths.hx @@ -1,5 +1,7 @@ package backend; +import haxe.io.Path; +import lime.media.AudioBuffer; import flixel.graphics.frames.FlxFrame.FlxFrameAngle; import flixel.graphics.frames.FlxAtlasFrames; import flixel.graphics.FlxGraphic; @@ -252,10 +254,10 @@ class Paths localTrackedAssets.push(file); return currentTrackedAssets.get(file); } - else if (Assets.exists(file, getImageAssetType(GPU_IMAGE_EXT))) - bitmap = Assets.getBitmapData(file); + else if (FileSystem.exists(file)) + bitmap = getBitmapData(file); - if (Assets.exists(getPath('images/$key.$IMAGE_EXT', getImageAssetType(IMAGE_EXT), library), getImageAssetType(IMAGE_EXT))) + if (FileSystem.exists(getPath('images/$key.$IMAGE_EXT', getImageAssetType(IMAGE_EXT), library))) { file = getPath('images/$key.$IMAGE_EXT', getImageAssetType(IMAGE_EXT), library); if (currentTrackedAssets.exists(file)) @@ -263,7 +265,7 @@ class Paths localTrackedAssets.push(file); return currentTrackedAssets.get(file); } - bitmap = Assets.getBitmapData(file); + bitmap = getBitmapData(file); } } @@ -278,12 +280,22 @@ class Paths return null; } + static public function getBitmapData(file:String):BitmapData + { + var bytes = File.getBytes(file); + return switch (Path.extension(file)) { + case 'astc': BitmapData.fromTexture(FlxG.stage.context3D.createASTCTexture(bytes)); + case 'dds': BitmapData.fromTexture(FlxG.stage.context3D.createS3TCTexture(bytes)); + default: BitmapData.fromBytes(bytes); + } + } + static public function cacheBitmap(file:String, ?bitmap:BitmapData = null) { if (bitmap == null) { if (FileSystem.exists(file)) - bitmap = BitmapData.fromFile(file); + bitmap = getBitmapData(file); else { if (Assets.exists(file, getImageAssetType(GPU_IMAGE_EXT))) @@ -478,7 +490,7 @@ class Paths { if (!currentTrackedSounds.exists(file)) { - currentTrackedSounds.set(file, Sound.fromFile(file)); + currentTrackedSounds.set(file, Sound.fromAudioBuffer(AudioBuffer.fromBytes(File.getBytes(file)))); // trace('precached mod sound: $file'); } localTrackedAssets.push(file); @@ -496,8 +508,8 @@ class Paths { var retKey:String = (path != null) ? '$path/$key' : key; retKey = getPath('$retKey.ogg', SOUND, library); - if (Assets.exists(retKey, SOUND)) - currentTrackedSounds.set(gottenPath, Assets.getSound(retKey)); + if (FileSystem.exists(retKey)) + currentTrackedSounds.set(gottenPath, Sound.fromAudioBuffer(AudioBuffer.fromBytes(File.getBytes(retKey)))); } localTrackedAssets.push(gottenPath); return currentTrackedSounds.get(gottenPath); diff --git a/source/engine/backend/io/File.hx b/source/engine/backend/io/File.hx index c82fdd17..532d627d 100644 --- a/source/engine/backend/io/File.hx +++ b/source/engine/backend/io/File.hx @@ -1,7 +1,13 @@ package backend.io; -import openfl.Assets; -#if sys +#if USE_OPENFL_FILESYSTEM +import lime.utils.Assets as LimeAssets; +import openfl.Assets as OpenFLAssets; +#end +#if mobile +import mobile.backend.io.Assets as MobileAssets; +#end +#if (sys && MODS_ALLOWED) import sys.FileSystem as SysFileSystem; import sys.FileStat; import sys.io.File as SysFile; @@ -24,15 +30,17 @@ class File return path; } + #if USE_OPENFL_FILESYSTEM static function openflcwd(path:String):String { @:privateAccess - for (library in lime.utils.Assets.libraries.keys()) - if (Assets.exists('$library:$path') && !path.startsWith('$library:')) + for (library in LimeAssets.libraries.keys()) + if (OpenFLAssets.exists('$library:$path') && !path.startsWith('$library:')) return '$library:$path'; return path; } + #end public static function getContent(path:String):Null { @@ -50,8 +58,15 @@ class File #end #end - if (Assets.exists(openflcwd(path))) - return Assets.getText(openflcwd(path)); + #if USE_OPENFL_FILESYSTEM + if (OpenFLAssets.exists(openflcwd(path))) + return OpenFLAssets.getText(openflcwd(path)); + #end + + #if mobile + if (MobileAssets.exists(path)) + return MobileAssets.getContent(path); + #end return null; } @@ -72,14 +87,21 @@ class File #end #end - if (Assets.exists(openflcwd(path))) + #if USE_OPENFL_FILESYSTEM + if (OpenFLAssets.exists(openflcwd(path))) switch (haxe.io.Path.extension(path).toLowerCase()) { case 'otf' | 'ttf': return openfl.utils.ByteArray.fromFile(openflcwd(path)); default: - return Assets.getBytes(openflcwd(path)); + return OpenFLAssets.getBytes(openflcwd(path)); } + #end + + #if mobile + if (MobileAssets.exists(path)) + return MobileAssets.getBytes(path); + #end return null; } @@ -110,9 +132,11 @@ class File #else return SysFile.read(cwd(path), binary); #end - #else - return null; #end + #if mobile + // SHADOW TODO + #end + return null; } public static function write(path:String, binary:Bool = true):Null diff --git a/source/engine/backend/io/FileSystem.hx b/source/engine/backend/io/FileSystem.hx index d2180da6..3d34f1dc 100644 --- a/source/engine/backend/io/FileSystem.hx +++ b/source/engine/backend/io/FileSystem.hx @@ -1,7 +1,13 @@ package backend.io; -import openfl.Assets; -#if sys +#if USE_OPENFL_FILESYSTEM +import lime.utils.Assets as LimeAssets; +import openfl.Assets as OpenFLAssets; +#end +#if mobile +import mobile.backend.io.Assets as MobileAssets; +#end +#if (sys && MODS_ALLOWED) import sys.FileSystem as SysFileSystem; import sys.FileStat; #end @@ -23,15 +29,17 @@ class FileSystem return path; } + #if USE_OPENFL_FILESYSTEM static function openflcwd(path:String):String { @:privateAccess - for (library in lime.utils.Assets.libraries.keys()) - if (Assets.exists('$library:$path') && !path.startsWith('$library:')) + for (library in LimeAssets.libraries.keys()) + if (OpenFLAssets.exists('$library:$path') && !path.startsWith('$library:')) return '$library:$path'; return path; } + #end public static function exists(path:String):Bool { @@ -49,10 +57,17 @@ class FileSystem #end #end - if (Assets.exists(openflcwd(path))) + #if USE_OPENFL_FILESYSTEM + if (OpenFLAssets.exists(openflcwd(path)) || OpenFLAssets.list().filter(asset -> asset.startsWith(path) && asset != path).length > 0) return true; + #end + + #if mobile + if (MobileAssets.exists(path)) + return true; + #end - return Assets.list().filter(asset -> asset.startsWith(path) && asset != path).length > 0; + return false; } public static function rename(path:String, newPath:String):Void @@ -80,13 +95,18 @@ class FileSystem actualPath = getCaseInsensitivePath(path); if (actualPath == null) actualPath = path; - return SysFileSystem.stat(actualPath); + if (SysFileSystem.exists(actualPath)) + return SysFileSystem.stat(actualPath); #else - return SysFileSystem.stat(cwd(path)); + if (SysFileSystem.exists(cwd(path))) + return SysFileSystem.stat(cwd(path)); #end - #else - return null; #end + #if mobile + if (MobileAssets.exists(path)) + return MobileAssets.stat(path); + #end + return null; } public static function fullPath(path:String):String @@ -139,7 +159,17 @@ class FileSystem #end #end - return Assets.list().filter(asset -> asset.startsWith(path) && asset != path).length > 0; + #if USE_OPENFL_FILESYSTEM + if (OpenFLAssets.list().filter(asset -> asset.startsWith(path) && asset != path).length > 0) + return true; + #end + + #if mobile + if (MobileAssets.isDirectory(path)) + return true; + #end + + return false; } public static function createDirectory(path:String):Void @@ -200,7 +230,23 @@ class FileSystem #end #end - var filteredList:Array = Assets.list().filter(f -> f.startsWith(path)); + #if USE_OPENFL_FILESYSTEM + if (OpenFLAssets.list().filter(asset -> asset.startsWith(path) && asset != path).length > 0) + return openflReadDirectory(path); + #end + + #if mobile + if (MobileAssets.exists(path) && MobileAssets.isDirectory(path)) + return MobileAssets.readDirectory(path); + #end + + return null; + } + + #if USE_OPENFL_FILESYSTEM + static function openflReadDirectory(path:String):Array + { + var filteredList:Array = OpenFLAssets.list().filter(f -> f.startsWith(path)); var results:Array = []; for (i in filteredList.copy()) { @@ -214,17 +260,18 @@ class FileSystem for (item in filteredList) { @:privateAccess - for (library in lime.utils.Assets.libraries.keys()) + for (library in LimeAssets.libraries.keys()) { var libPath:String = '$library:$item'; if (library != 'default' && Assets.exists(libPath) && !results.contains(libPath)) results.push(libPath); - else if (Assets.exists(item) && !results.contains(item)) + else if (OpenFLAssets.exists(item) && !results.contains(item)) results.push(item); } } return results.map(f -> f.substr(f.lastIndexOf("/") + 1)); } + #end #if (linux && MODS_ALLOWED) static function getCaseInsensitivePath(path:String):String diff --git a/source/engine/mobile/backend/MobileData.hx b/source/engine/mobile/backend/MobileData.hx index 608519a1..e50e76cd 100644 --- a/source/engine/mobile/backend/MobileData.hx +++ b/source/engine/mobile/backend/MobileData.hx @@ -24,13 +24,13 @@ class MobileData save = new FlxSave(); save.bind('MobileControls', CoolUtil.getSavePath()); - readDirectory(Paths.getSharedPath('mobile/DPadModes'), dpadModes); - readDirectory(Paths.getSharedPath('mobile/ActionModes'), actionModes); + readFilesFromDirectory(Paths.getSharedPath('mobile/DPadModes'), dpadModes); + readFilesFromDirectory(Paths.getSharedPath('mobile/ActionModes'), actionModes); #if MODS_ALLOWED for (folder in Mods.directoriesWithFile(Paths.getSharedPath(), 'mobile/')) { - readDirectory(Path.join([folder, 'DPadModes']), dpadModes); - readDirectory(Path.join([folder, 'ActionModes']), actionModes); + readFilesFromDirectory(Path.join([folder, 'DPadModes']), dpadModes); + readFilesFromDirectory(Path.join([folder, 'ActionModes']), actionModes); } #end @@ -96,7 +96,7 @@ class MobileData return buttonsInstance; } - public static function readDirectory(folder:String, map:Dynamic) + public static function readFilesFromDirectory(folder:String, map:Dynamic) { folder = folder.contains(':') ? folder.split(':')[1] : folder; diff --git a/source/engine/mobile/backend/io/Assets.hx b/source/engine/mobile/backend/io/Assets.hx new file mode 100644 index 00000000..89cd20f6 --- /dev/null +++ b/source/engine/mobile/backend/io/Assets.hx @@ -0,0 +1,7 @@ +package mobile.backend.io; + +#if android +typedef Assets = mobile.backend.io.android.Assets; +// #elseif ios +// typedef Assets = mobile.backend.io.ios.Assets; +#end \ No newline at end of file diff --git a/source/engine/mobile/backend/io/android/Assets.hx b/source/engine/mobile/backend/io/android/Assets.hx new file mode 100644 index 00000000..c8383f71 --- /dev/null +++ b/source/engine/mobile/backend/io/android/Assets.hx @@ -0,0 +1,407 @@ +package mobile.backend.io.android; + +/** + * The code for this class is mostly taken from SDL2. + * This class implements IO methods from the Android NDK's AAssetManager to read bundled app assets. + */ +#if android +import haxe.io.Bytes; +import lime.system.JNI; +import sys.FileStat; + +@:cppFileCode(' +#ifndef INCLUDED_Date +#include +#endif +') +@:cppNamespaceCode(' +#include +#include +#include +#include + +static jmethodID midGetContext; +static jclass mActivityClass; +static AAssetManager *asset_manager = NULL; +static jobject javaAssetManagerRef = 0; + +struct LocalReferenceHolder +{ + JNIEnv *m_env; + const char *m_func; +}; + +static struct LocalReferenceHolder LocalReferenceHolder_Setup(const char *func) +{ + struct LocalReferenceHolder refholder; + refholder.m_env = NULL; + refholder.m_func = func; + __android_log_print (ANDROID_LOG_DEBUG, "Shadow Engine", "Entering function %s", func); + return refholder; +} + +static bool LocalReferenceHolder_Init(struct LocalReferenceHolder *refholder, JNIEnv *env) +{ + const int capacity = 16; + if ((*env).PushLocalFrame(capacity) < 0) + { + __android_log_print (ANDROID_LOG_ERROR, "Shadow Engine", "Failed to allocate enough JVM local references"); + return false; + } + refholder->m_env = env; + return true; +} + +static void LocalReferenceHolder_Cleanup(struct LocalReferenceHolder *refholder) +{ + __android_log_print (ANDROID_LOG_DEBUG, "Shadow Engine", "Leaving function %s", refholder->m_func); + if (refholder->m_env) + { + JNIEnv *env = refholder->m_env; + (*env).PopLocalFrame(NULL); + } +} + +void Assets_obj::native_init(::Dynamic jni_env) +{ + JNIEnv* env = (JNIEnv*)(uintptr_t)jni_env; + jclass cls = env->FindClass("org/libsdl/app/SDLActivity"); + mActivityClass = (jclass)((*env).NewGlobalRef(cls)); + + struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); + jmethodID mid; + jobject context; + jobject javaAssetManager; + + if (!LocalReferenceHolder_Init(&refs, env)) + { + LocalReferenceHolder_Cleanup(&refs); + return; + } + + // context = SDLActivity.getContext(); + midGetContext = (*env).GetStaticMethodID(mActivityClass, "getContext","()Landroid/content/Context;"); + context = (*env).CallStaticObjectMethod(mActivityClass, midGetContext); + + // javaAssetManager = context.getAssets(); + mid = (*env).GetMethodID((*env).GetObjectClass(context), "getAssets", "()Landroid/content/res/AssetManager;"); + javaAssetManager = (*env).CallObjectMethod(context, mid); + + /** + * Given a Dalvik AssetManager object, obtain the corresponding native AAssetManager + * object. Note that the caller is responsible for obtaining and holding a VM reference + * to the jobject to prevent its being garbage collected while the native object is + * in use. + */ + javaAssetManagerRef = (*env).NewGlobalRef(javaAssetManager); + asset_manager = AAssetManager_fromJava(env, javaAssetManagerRef); + + if (asset_manager == NULL) + { + (*env).DeleteGlobalRef(javaAssetManagerRef); + __android_log_print (ANDROID_LOG_DEBUG, "Shadow Engine", "Failed to create Android Assets Manager"); + } + + LocalReferenceHolder_Cleanup(&refs); +} + +void Assets_obj::native_destroy(::Dynamic jni_env) +{ + JNIEnv* env = (JNIEnv*)(uintptr_t)jni_env; + + if (asset_manager) + { + (*env).DeleteGlobalRef(javaAssetManagerRef); + asset_manager = NULL; + } +} + +bool Assets_obj::native_exists(::String path) +{ + hx::EnterGCFreeZone(); + AAsset* file = AAssetManager_open(asset_manager, path.__s, AASSET_MODE_UNKNOWN); + if (file != NULL) + { + AAsset_close(file); + hx::ExitGCFreeZone(); + return true; + } + + if (file) + AAsset_close(file); + + AAssetDir* dir = AAssetManager_openDir(asset_manager, path.__s); + if (dir && AAssetDir_getNextFileName(dir) != NULL) + { + AAssetDir_close(dir); + hx::ExitGCFreeZone(); + return true; + } + + if (dir) + AAssetDir_close(dir); + + hx::ExitGCFreeZone(); + return false; +} + +::String Assets_obj::native_getContent(::String file) { + std::vector buffer; + + hx::EnterGCFreeZone(); + AAsset* asset = AAssetManager_open(asset_manager, file.__s, AASSET_MODE_BUFFER); + + if (!asset) + { + hx::ExitGCFreeZone(); + return ::String(null()); + } + + int len = AAsset_getLength(asset); + if (len <= 0) + { + AAsset_close(asset); + hx::ExitGCFreeZone(); + return ::String::emptyString; + } + + const char* src = (const char*)AAsset_getBuffer(asset); + + buffer.resize(len); + memcpy(&buffer[0], src, len); + + AAsset_close(asset); + hx::ExitGCFreeZone(); + + return ::String::create(&buffer[0], buffer.size()); +} + +Array Assets_obj::native_getBytes(::String file) { + hx::EnterGCFreeZone(); + AAsset* asset = AAssetManager_open(asset_manager, file.__s, AASSET_MODE_STREAMING); + + if (!asset) + { + hx::ExitGCFreeZone(); + return null(); + } + + int fd; + off_t outStart; + off_t outLength; + fd = AAsset_openFileDescriptor (asset, &outStart, &outLength); + + if (fd < 0) { + AAsset_close(asset); + hx::ExitGCFreeZone(); + return null(); + } + + Array buffer = Array_obj::__new(outLength, outLength); + + if (lseek(fd, outStart, SEEK_SET) == -1) { + close(fd); + AAsset_close(asset); + hx::ExitGCFreeZone(); + return null(); + } + + int totalRead = 0; + while (totalRead < outLength) { + int bytesRead = read(fd, buffer->getBase() + totalRead, outLength - totalRead); + + if (bytesRead <= 0) { + close(fd); + AAsset_close(asset); + hx::ExitGCFreeZone(); + return null(); + } + + totalRead += bytesRead; + } + + close(fd); + AAsset_close(asset); + hx::ExitGCFreeZone(); + return buffer; +} + +bool Assets_obj::native_isDirectory(::String path) +{ + hx::EnterGCFreeZone(); + AAssetDir* dir = AAssetManager_openDir(asset_manager, path.__s); + + if (dir && AAssetDir_getNextFileName(dir) != NULL) + { + AAssetDir_close(dir); + hx::ExitGCFreeZone(); + return true; + } + + if (dir) + AAssetDir_close(dir); + + hx::ExitGCFreeZone(); + return false; +} + +Array<::String> Assets_obj::native_readDirectory(::String path) +{ + Array<::String> result = Array_obj<::String>::__new(0, 0); + hx::EnterGCFreeZone(); + AAssetDir* dir = AAssetManager_openDir(asset_manager, path.__s); + const char* filename; + + if (!dir) + { + hx::ExitGCFreeZone(); + return result; + } + + while ((filename = AAssetDir_getNextFileName(dir)) != NULL) + { + result->push(::String(filename)); + } + + AAssetDir_close(dir); + hx::ExitGCFreeZone(); + return result; +} + +::Dynamic Assets_obj::native_stat(::String path) +{ + hx::Anon anon = hx::Anon_obj::Create(); + bool isDir = native_isDirectory(path); + int fileSize = 0; + int mode = isDir ? 0x4000 : 0x8000; + + if (!isDir) + { + hx::EnterGCFreeZone(); + AAsset* asset = AAssetManager_open(asset_manager, path.__s, AASSET_MODE_UNKNOWN); + if (asset) + { + fileSize = AAsset_getLength(asset); + AAsset_close(asset); + } + hx::ExitGCFreeZone(); + } + + anon->Add(HX_CSTRING("gid"), 0); + anon->Add(HX_CSTRING("uid"), 0); + anon->Add(HX_CSTRING("atime"), ::Date_obj::fromTime(0.0)); + anon->Add(HX_CSTRING("mtime"), ::Date_obj::fromTime(0.0)); + anon->Add(HX_CSTRING("ctime"), ::Date_obj::fromTime(0.0)); + anon->Add(HX_CSTRING("size"), fileSize); + anon->Add(HX_CSTRING("dev"), 0); + anon->Add(HX_CSTRING("ino"), 0); + anon->Add(HX_CSTRING("nlink"), 0); + anon->Add(HX_CSTRING("rdev"), 0); + anon->Add(HX_CSTRING("mode"), mode); + + return anon; +} +') +@:headerClassCode(' + static void native_init(::Dynamic jni_env); + static void native_destroy(::Dynamic jni_env); + static bool native_exists(::String path); + static ::String native_getContent(::String file); + static Array native_getBytes(::String file); + static bool native_isDirectory(::String path); + static Array<::String> native_readDirectory(::String path); + static ::Dynamic native_stat(::String path); +') +class Assets +{ + public static function init():Void + { + __init(JNI.getEnv()); + } + + public static function destroy():Void + { + __destroy(JNI.getEnv()); + } + + public static function getContent(file:String):String + { + final content:String = __getContent(file); + + if (content == null) + throw 'file_contents, $file'; + + return content; + } + + public static function getBytes(file:String):Bytes + { + final data:Array = __getBytes(file); + + if (data == null || data.length <= 0) + throw 'file_contents, $file'; + + return Bytes.ofData(data); + } + + public static function isDirectory(path:String):Bool + { + return __isDirectory(path); + } + + public static function readDirectory(path:String):Array + { + return __readDirectory(path); + } + + public static function stat(path:String):FileStat + { + return __stat(path); + } + + public static function exists(path:String):Bool + { + return __exists(path); + } + + @:noCompletion + @:native('mobile::backend::io::android::Assets_obj::native_exists') + public static function __exists(path:String):Bool + return false; + + @:noCompletion + @:native('mobile::backend::io::android::Assets_obj::native_init') + private static function __init(jni_env:Dynamic):Void + return; + + @:noCompletion + @:native('mobile::backend::io::android::Assets_obj::native_destroy') + private static function __destroy(jni_env:Dynamic):Void + return; + + @:noCompletion + @:native('mobile::backend::io::android::Assets_obj::native_getContent') + public static function __getContent(file:String):String + return null; + + @:noCompletion + @:native('mobile::backend::io::android::Assets_obj::native_getBytes') + private static function __getBytes(file:String):Array + return null; + + @:noCompletion + @:native('mobile::backend::io::android::Assets_obj::native_isDirectory') + private static function __isDirectory(path:String):Bool + return false; + + @:noCompletion + @:native('mobile::backend::io::android::Assets_obj::native_readDirectory') + private static function __readDirectory(path:String):Array + return null; + + @:noCompletion + @:native('mobile::backend::io::android::Assets_obj::native_stat') + private static function __stat(path:String):Dynamic + return null; +} +#end