diff --git a/project.hxp b/project.hxp index b1858f6b..44460bd0 100644 --- a/project.hxp +++ b/project.hxp @@ -39,6 +39,7 @@ class Project extends HXProject static final FEATURE_TRACY:CompileFlag = CompileFlag.get("FEATURE_TRACY"); static final FEATURE_FUNKIN_CONTENT:CompileFlag = CompileFlag.get("FEATURE_FUNKIN_CONTENT"); static final FEATURE_EMBED_ASSETS:CompileFlag = CompileFlag.get("EMBED_ASSETS"); + static final USE_OPENFL_FILESYSTEM:CompileFlag = CompileFlag.get("USE_OPENFL_FILESYSTEM"); static final ENABLE_ASCII_ART:Bool = true; @@ -239,11 +240,12 @@ class Project extends HXProject FEATURE_DCE.integrate(false); FEATURE_TRACY.integrate(false); FEATURE_FUNKIN_CONTENT.integrate(true); + FEATURE_EMBED_ASSETS.integrate(false); if (FEATURE_FUNKIN_CONTENT.isEnabled()) FEATURE_HSCRIPT.integrate(true); - FEATURE_EMBED_ASSETS.integrate(false); + USE_OPENFL_FILESYSTEM.integrate((FEATURE_MODS.isDisabled() || FEATURE_EMBED_ASSETS.isEnabled()) && !isMobile()); setHaxedef("FLX_NO_FOCUS_LOST_SCREEN"); if (!isDebug()) diff --git a/source/engine/backend/Main.hx b/source/engine/backend/Main.hx index 315826d0..04300a7e 100644 --- a/source/engine/backend/Main.hx +++ b/source/engine/backend/Main.hx @@ -47,6 +47,7 @@ class Main extends Sprite #if android StorageUtil.requestPermissions(); #end + mobile.backend.io.Assets.init(); #end super(); diff --git a/source/engine/backend/io/File.hx b/source/engine/backend/io/File.hx index 46b57927..d9cdf7f1 100644 --- a/source/engine/backend/io/File.hx +++ b/source/engine/backend/io/File.hx @@ -1,7 +1,14 @@ package backend.io; -import openfl.Assets; -#if sys +#if USE_OPENFL_FILESYSTEM +import lime.utils.Assets as LimeAssets; +import openfl.Assets as OpenFLAssets; +import openfl.utils.ByteArray as OpenFLByteArray; +#end +#if mobile +import mobile.backend.io.Assets as MobileAssets; +#end +#if (sys && FEATURE_MODS) import sys.FileSystem as SysFileSystem; import sys.FileStat; import sys.io.File as SysFile; @@ -24,17 +31,19 @@ class File return path; } + #if USE_OPENFL_FILESYSTEM static function openflcwd(path:String):String { #if !FEATURE_EMBED_ASSETS @: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'; #end return path; } + #end public static function getContent(path:String):Null { @@ -52,8 +61,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; } @@ -74,14 +90,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)); + return OpenFLByteArray.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; } @@ -113,6 +136,9 @@ class File return SysFile.read(cwd(path), binary); #end #else + #if mobile + // return MobileAssets.read(path, binary); + #end return null; #end } @@ -183,7 +209,7 @@ class File #end } - #if ((linux || ios) && FEATURE_MODS) + #if (linux && FEATURE_MODS) static function getCaseInsensitivePath(path:String):String { if (SysFileSystem.exists(path)) diff --git a/source/engine/backend/io/FileSystem.hx b/source/engine/backend/io/FileSystem.hx index e2b007af..4b8efe79 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 && FEATURE_MODS) 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,11 +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 + return null; #else + #if mobile + if (MobileAssets.exists(path)) + return MobileAssets.stat(path); + #end return null; #end } @@ -139,7 +161,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 +232,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,19 +262,20 @@ 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 || ios) && FEATURE_MODS) + #if (linux && FEATURE_MODS) static function getCaseInsensitivePath(path:String):String { if (SysFileSystem.exists(path)) diff --git a/source/engine/mobile/backend/io/Assets.hx b/source/engine/mobile/backend/io/Assets.hx new file mode 100644 index 00000000..95e0cf66 --- /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..36311e2e --- /dev/null +++ b/source/engine/mobile/backend/io/android/Assets.hx @@ -0,0 +1,474 @@ +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 cpp.UInt8; +import haxe.io.Bytes; +import haxe.io.BytesInput; +import lime.system.JNI; +import sys.FileStat; + +@:cppFileCode(' +#ifndef INCLUDED_Date +#include +#endif +#include +#include +') +@: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_ERROR, "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) +{ + if (!asset_manager) + return false; + + hx::EnterGCFreeZone(); + + AAsset* file = AAssetManager_open(asset_manager, path.__s, AASSET_MODE_UNKNOWN); + if (file != NULL) + { + AAsset_close(file); + hx::ExitGCFreeZone(); + return true; + } + + 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) +{ + if (!asset_manager) + return ::String(null()); + + hx::EnterGCFreeZone(); + AAsset* asset = AAssetManager_open(asset_manager, file.__s, AASSET_MODE_BUFFER); + + if (!asset) + { + hx::ExitGCFreeZone(); + return ::String(null()); + } + + off64_t len = AAsset_getLength64(asset); + if (len <= 0) + { + AAsset_close(asset); + hx::ExitGCFreeZone(); + return ::String::emptyString; + } + + std::vector buffer((size_t)len); + + const char* src = (const char*)AAsset_getBuffer(asset); + if (src != NULL) + { + memcpy(&buffer[0], src, (size_t)len); + } + else + { + off64_t totalRead = 0; + while (totalRead < len) + { + int bytesRead = AAsset_read(asset, &buffer[totalRead], (size_t)(len - totalRead)); + if (bytesRead <= 0) + { + AAsset_close(asset); + hx::ExitGCFreeZone(); + return ::String(null()); + } + totalRead += bytesRead; + } + } + + AAsset_close(asset); + hx::ExitGCFreeZone(); + + return ::String::create(&buffer[0], (int)buffer.size()); +} + +Array Assets_obj::native_getBytes(::String file) +{ + if (!asset_manager) + return null(); + + hx::EnterGCFreeZone(); + AAsset* asset = AAssetManager_open(asset_manager, file.__s, AASSET_MODE_STREAMING); + + if (!asset) + { + hx::ExitGCFreeZone(); + return null(); + } + + off64_t len = AAsset_getLength64(asset); + if (len == 0) + { + AAsset_close(asset); + hx::ExitGCFreeZone(); + return Array_obj::__new(0, 0); + } + if (len < 0) + { + AAsset_close(asset); + hx::ExitGCFreeZone(); + return null(); + } + + Array buffer = Array_obj::__new((int)len, (int)len); + + int fd; + off_t outStart; + off_t outLength; + fd = AAsset_openFileDescriptor(asset, &outStart, &outLength); + + if (fd >= 0) + { + if (lseek(fd, outStart, SEEK_SET) == -1) + { + close(fd); + AAsset_close(asset); + hx::ExitGCFreeZone(); + return null(); + } + + off64_t totalRead = 0; + while (totalRead < len) + { + int bytesRead = read(fd, buffer->getBase() + totalRead, (size_t)(len - totalRead)); + if (bytesRead <= 0) + { + close(fd); + AAsset_close(asset); + hx::ExitGCFreeZone(); + return null(); + } + totalRead += bytesRead; + } + close(fd); + } + else + { + off64_t totalRead = 0; + while (totalRead < len) + { + int bytesRead = AAsset_read(asset, buffer->getBase() + totalRead, (size_t)(len - totalRead)); + if (bytesRead <= 0) + { + AAsset_close(asset); + hx::ExitGCFreeZone(); + return null(); + } + totalRead += bytesRead; + } + } + + AAsset_close(asset); + hx::ExitGCFreeZone(); + return buffer; +} + +bool Assets_obj::native_isDirectory(::String path) +{ + if (!asset_manager) + return false; + + 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) +{ + if (!asset_manager) + return Array_obj<::String>::__new(0, 0); + + std::vector names; + + hx::EnterGCFreeZone(); + AAssetDir* dir = AAssetManager_openDir(asset_manager, path.__s); + + if (dir) + { + const char* filename; + while ((filename = AAssetDir_getNextFileName(dir)) != NULL) + names.push_back(filename); + AAssetDir_close(dir); + } + hx::ExitGCFreeZone(); + + Array<::String> result = Array_obj<::String>::__new(0, 0); + for (size_t i = 0; i < names.size(); i++) + result->push(::String(names[i].c_str())); + + 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 && asset_manager) + { + hx::EnterGCFreeZone(); + AAsset* asset = AAssetManager_open(asset_manager, path.__s, AASSET_MODE_UNKNOWN); + if (asset) + { + fileSize = (int)AAsset_getLength64(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) + throw 'file_contents, $file'; + + return Bytes.ofData(data); + } + + /*public static function read(file:String, ?binary:Bool):BytesInput + { + return new BytesInput(getBytes(file)); + }*/ + + public static function exists(path:String):Bool + { + return __exists(path); + } + + 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); + } + + @: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_exists') + private static function __exists(path:String):Bool + return false; + + @:noCompletion + @:native('mobile::backend::io::android::Assets_obj::native_getContent') + private 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 diff --git a/source/engine/mobile/backend/io/ios/Assets.hx b/source/engine/mobile/backend/io/ios/Assets.hx new file mode 100644 index 00000000..bf421f85 --- /dev/null +++ b/source/engine/mobile/backend/io/ios/Assets.hx @@ -0,0 +1,330 @@ +package mobile.backend.io.ios; + +/** + * The code for this class is mostly taken from SDL2. + * This class implements IO methods from the CoreFoundation's CFBundle to read bundled app assets. + */ +#if ios +import cpp.UInt8; +import haxe.io.Bytes; +import haxe.io.BytesInput; +import sys.FileStat; + +@:cppFileCode(' +#ifndef INCLUDED_Date +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +') +@:cppNamespaceCode(' +static const std::string& getBundleResourcePath() +{ + static std::string cached; + static bool resolved = false; + if (resolved) + return cached; + + CFBundleRef bundle = CFBundleGetMainBundle(); + if (bundle) + { + CFURLRef url = CFBundleCopyResourcesDirectoryURL(bundle); + if (url) + { + char path[PATH_MAX]; + if (CFURLGetFileSystemRepresentation(url, true, (UInt8*)path, (CFIndex)sizeof(path))) + cached = path; + CFRelease(url); + } + } + resolved = true; + return cached; +} + +static std::string resolveAssetPath(const char* logicalPath) +{ + const std::string& base = getBundleResourcePath(); + if (base.empty()) + return std::string(); + // avoiding double-slash if logicalPath already starts + if (logicalPath[0] == \'/\') + return base + logicalPath; + return base + "/" + logicalPath; +} + +bool Assets_obj::native_exists(::String path) +{ + hx::EnterGCFreeZone(); + std::string fullPath = resolveAssetPath(path.__s); + bool exists = false; + if (!fullPath.empty()) + { + struct stat st; + exists = (::stat(fullPath.c_str(), &st) == 0); + } + hx::ExitGCFreeZone(); + return exists; +} + +::String Assets_obj::native_getContent(::String file) +{ + hx::EnterGCFreeZone(); + std::string fullPath = resolveAssetPath(file.__s); + std::vector buffer; + bool failed = true; + + if (!fullPath.empty()) + { + int fd = open(fullPath.c_str(), O_RDONLY | O_CLOEXEC); + if (fd >= 0) + { + struct stat st; + if (fstat(fd, &st) == 0 && S_ISREG(st.st_mode)) + { + off_t len = st.st_size; + if (len == 0) + { + failed = false; // valid empty file + } + else + { + buffer.resize((size_t)len); + ssize_t totalRead = 0; + while (totalRead < (ssize_t)len) + { + ssize_t n = read(fd, &buffer[totalRead], (size_t)(len - totalRead)); + if (n <= 0) { buffer.clear(); break; } + totalRead += n; + } + if (totalRead == (ssize_t)len) + failed = false; + } + } + close(fd); + } + } + + hx::ExitGCFreeZone(); + + if (failed) + return ::String(null()); + if (buffer.empty()) + return ::String::emptyString; + return ::String::create(&buffer[0], (int)buffer.size()); +} + +Array Assets_obj::native_getBytes(::String file) +{ + hx::EnterGCFreeZone(); + std::string fullPath = resolveAssetPath(file.__s); + off_t len = -1; + bool isFile = false; + + if (!fullPath.empty()) + { + struct stat st; + if (::stat(fullPath.c_str(), &st) == 0 && S_ISREG(st.st_mode)) + { + len = st.st_size; + isFile = true; + } + } + hx::ExitGCFreeZone(); + + if (!isFile) + return null(); + + if (len == 0) + return Array_obj::__new(0, 0); + + Array result = Array_obj::__new((int)len, (int)len); + + hx::EnterGCFreeZone(); + int fd = open(fullPath.c_str(), O_RDONLY | O_CLOEXEC); + bool readOk = false; + + if (fd >= 0) + { + ssize_t totalRead = 0; + while (totalRead < (ssize_t)len) + { + ssize_t n = read(fd, result->getBase() + totalRead, (size_t)(len - totalRead)); + if (n <= 0) break; + totalRead += n; + } + readOk = (totalRead == (ssize_t)len); + close(fd); + } + hx::ExitGCFreeZone(); + + return readOk ? result : null(); +} + +bool Assets_obj::native_isDirectory(::String path) +{ + hx::EnterGCFreeZone(); + std::string fullPath = resolveAssetPath(path.__s); + bool isDir = false; + if (!fullPath.empty()) + { + struct stat st; + if (::stat(fullPath.c_str(), &st) == 0) + isDir = S_ISDIR(st.st_mode); + } + hx::ExitGCFreeZone(); + return isDir; +} + +Array<::String> Assets_obj::native_readDirectory(::String path) +{ + hx::EnterGCFreeZone(); + std::string fullPath = resolveAssetPath(path.__s); + std::vector names; + + if (!fullPath.empty()) + { + DIR* dir = opendir(fullPath.c_str()); + if (dir) + { + struct dirent* entry; + while ((entry = readdir(dir)) != NULL) + { + const char* name = entry->d_name; + if (name[0] == \'.\' && (name[1] == \'\\0\' || (name[1] == \'.\' && name[2] == \'\\0\'))) + continue; + names.push_back(name); + } + closedir(dir); + } + } + hx::ExitGCFreeZone(); + + Array<::String> result = Array_obj<::String>::__new(0, 0); + for (size_t i = 0; i < names.size(); i++) + result->push(::String(names[i].c_str())); + return result; +} + +::Dynamic Assets_obj::native_stat(::String path) +{ + hx::EnterGCFreeZone(); + std::string fullPath = resolveAssetPath(path.__s); + bool found = false; + struct stat st; + memset(&st, 0, sizeof(st)); + if (!fullPath.empty()) + found = (::stat(fullPath.c_str(), &st) == 0); + hx::ExitGCFreeZone(); + + hx::Anon anon = hx::Anon_obj::Create(); + int mode = !found ? 0 : (S_ISDIR(st.st_mode) ? 0x4000 : 0x8000); + + anon->Add(HX_CSTRING("gid"), found ? (int)st.st_gid : 0); + anon->Add(HX_CSTRING("uid"), found ? (int)st.st_uid : 0); + anon->Add(HX_CSTRING("atime"), ::Date_obj::fromTime(found ? (double)st.st_atime * 1000.0 : 0.0)); + anon->Add(HX_CSTRING("mtime"), ::Date_obj::fromTime(found ? (double)st.st_mtime * 1000.0 : 0.0)); + anon->Add(HX_CSTRING("ctime"), ::Date_obj::fromTime(found ? (double)st.st_ctime * 1000.0 : 0.0)); + anon->Add(HX_CSTRING("size"), found ? (int)st.st_size : 0); + anon->Add(HX_CSTRING("dev"), found ? (int)st.st_dev : 0); + anon->Add(HX_CSTRING("ino"), found ? (int)st.st_ino : 0); + anon->Add(HX_CSTRING("nlink"), found ? (int)st.st_nlink : 0); + anon->Add(HX_CSTRING("rdev"), found ? (int)st.st_rdev : 0); + anon->Add(HX_CSTRING("mode"), mode); + return anon; +} +') +@:headerClassCode(' + 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 {} + public static function destroy():Void {} + + 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) + throw 'file_contents, $file'; + + return Bytes.ofData(data); + } + + /*public static function read(file:String, ?binary:Bool):BytesInput + { + return new BytesInput(getBytes(file)); + }*/ + + public static function exists(path:String):Bool + { + return __exists(path); + } + + 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); + } + + @:noCompletion + @:native('mobile::backend::io::ios::Assets_obj::native_exists') + private static function __exists(path:String):Bool + return false; + + @:noCompletion + @:native('mobile::backend::io::ios::Assets_obj::native_getContent') + private static function __getContent(file:String):String + return null; + + @:noCompletion + @:native('mobile::backend::io::ios::Assets_obj::native_getBytes') + private static function __getBytes(file:String):Array + return null; + + @:noCompletion + @:native('mobile::backend::io::ios::Assets_obj::native_isDirectory') + private static function __isDirectory(path:String):Bool + return false; + + @:noCompletion + @:native('mobile::backend::io::ios::Assets_obj::native_readDirectory') + private static function __readDirectory(path:String):Array + return null; + + @:noCompletion + @:native('mobile::backend::io::ios::Assets_obj::native_stat') + private static function __stat(path:String):Dynamic + return null; +} +#end diff --git a/source/engine/states/LoadingState.hx b/source/engine/states/LoadingState.hx index c5c5fe79..c9d3f8f7 100644 --- a/source/engine/states/LoadingState.hx +++ b/source/engine/states/LoadingState.hx @@ -272,7 +272,7 @@ class LoadingState extends MusicBeatState public static function clearInvalids() { - clearInvalidFrom(imagesToPrepare, 'images', ['.png'], IMAGE); // leaving this as is + clearInvalidFrom(imagesToPrepare, 'images', ['png'], IMAGE); // leaving this as is // clearInvalidFrom(imagesToPrepare, 'images', ['.${Paths.IMAGE_EXT}'], Paths.IMAGE_ASSETTYPE); clearInvalidFrom(soundsToPrepare, 'sounds', Paths.SOUND_EXTS, SOUND); clearInvalidFrom(musicToPrepare, 'music', Paths.SOUND_EXTS, SOUND); @@ -310,7 +310,7 @@ class LoadingState extends MusicBeatState var valid:Bool = false; for (ext in exts) { - var myKey = '$prefix/$member$ext'; + var myKey = '$prefix/$member.$ext'; if (Paths.fileExists(myKey, type, false, library)) { valid = true;