Basically,
there is no way to read asset files using fopen() in C++ on Android.
Similarly, Irrlicht cannot find asset images on Android without first knowing which directories exist in the assets (Irrlicht doesn't do this automatically).
Here is some code I put together to make Irrlicht automatically extract files and find directories in the asset folder.
This allows you to not worry about doing it yourself, and in the case that you need to read/write local files often (like me).
First, you'll need to add this to your MainActivity:
Code: Select all
//...
public class MainActivity extends NativeActivity {
//...
// Method: listAssets
// Params:
// path: The path to recursively search for files and/or folders
// files: If true, also returns file names (such as .txt, .png, etc)
// folders: If true, also returns folder names (directories)
// (for the sake of this snippet, we only want folders to be true)
// Returns:
// ArrayList<String> of all files and/or folders recursively searched starting at the directory (path).
public ArrayList<String> listAssets(String path, boolean files, boolean folders) {
String [] list;
ArrayList<String> found = new ArrayList<String>();
try {
list = getAssets().list(path);
if(list.length > 0){
for(String file : list){
System.out.println("File path = "+file);
if(file.indexOf(".") < 0) { // Folder
if (path.length() > 0)
file = path + "/" + file;
if (folders)
found.add(file);
ArrayList<String> newFound = listAssets(file, files, folders); // File
for (String newFile : newFound)
{
found.add(newFile);
}
}else{
if (path.length() > 0)
file = path + "/" + file;
if (files)
found.add(file);
}
}
}
}catch(IOException e){
e.printStackTrace();
}
return found;
}
// ...
}
We also extract them by reading and then writing the files (this may not be necessary if you use Irrlicht's IReadFile, but in my case I am wanting to use fopen and fwrite).
Code: Select all
void androidCreateFilePath()
{
JNIEnv* jniEnv = 0;
// The app variable is from Irrlicht's Android main method (void android_main(android_app* app))
app->activity->vm->AttachCurrentThread(&jniEnv, NULL);
//////////
// Here is where we call the MainActivity "listAssets" method
jobject objectActivity = app->activity->clazz;
jclass classActivity = jniEnv->GetObjectClass(objectActivity);
// ZZ means boolean, boolean
jmethodID methodGetOpenFileName = jniEnv->GetMethodID(classActivity, "listAssets", "(Ljava/lang/String;ZZ)Ljava/util/ArrayList;");
jobject arrayList = jniEnv->CallObjectMethod(objectActivity, methodGetOpenFileName, jniEnv->NewStringUTF(""), false, true);
/////
// Taken from online, this converts the above arrayList jobject to an std::vector<std::string>
static jclass java_util_ArrayList = static_cast<jclass>(jniEnv->NewGlobalRef(jniEnv->FindClass("java/util/ArrayList")));
static jmethodID java_util_ArrayList_ = jniEnv->GetMethodID(java_util_ArrayList, "<init>", "(I)V");
jmethodID java_util_ArrayList_size = jniEnv->GetMethodID (java_util_ArrayList, "size", "()I");
jmethodID java_util_ArrayList_get = jniEnv->GetMethodID(java_util_ArrayList, "get", "(I)Ljava/lang/Object;");
jmethodID java_util_ArrayList_add = jniEnv->GetMethodID(java_util_ArrayList, "add", "(Ljava/lang/Object;)Z");
jint len = jniEnv->CallIntMethod(arrayList, java_util_ArrayList_size);
std::vector<std::string> result;
result.reserve(len);
for (jint i=0; i<len; i++) {
jstring element = static_cast<jstring>(jniEnv->CallObjectMethod(arrayList, java_util_ArrayList_get, i));
const char* pchars = jniEnv->GetStringUTFChars(element, nullptr);
result.push_back(pchars);
jniEnv->ReleaseStringUTFChars(element, pchars);
jniEnv->DeleteLocalRef(element);
}
//////////
// Game::device is our IrrlichtDevice
for ( u32 i=0; i < Game::device->getFileSystem()->getFileArchiveCount(); ++i )
{
IFileArchive* archive = Game::device->getFileSystem()->getFileArchive(i);
if ( archive->getType() == EFAT_ANDROID_ASSET )
{
// For all of our asset's folders found from the MainActivity "listAssets" method, add it to our file list
for (std::string folder : result)
{
archive->addDirectoryToFileList((char*)(folder + "/").c_str());
}
// Get every file from the archive, and extract it if it isn't already.
// Again, probably not necessary unless you are planning to update files often.
IFileList* files = (IFileList*)archive->getFileList();
for (int x = 0; x < files->getFileCount(); x++)
{
std::string fName = app->activity->internalDataPath + files->getFullFileName(x).c_str();
// If the file is not a directory and it doesn't already exist (you can use stat() or directly try fread() to check if the file exists)
if (!files->isDirectory(x) && !Operations::fileExists(fName))
{
IReadFile* f = archive->createAndOpenFile(files->getFullFileName(x));
int len = f->getSize();
char* buf = new char[len+1];
f->read(buf, len);
f->drop();
buf[len] = '\0';
// This method is pretty simple. Simply tokenize the fName string by '/' and '\\' and use mkdir(split, 0770) for Android.
// The idea there is to create any necessary folders where we should store the extracted file.
Operations::createDirectories(fName);
FILE* fi = fopen(fName.c_str(), "wb");
fwrite(buf, 1, len, fi);
fclose(fi);
delete [] buf;
}
}
break;
}
}
}
I was previously hard-coding directories to the archive, which was a huge hassle when I keep moving stuff around.