Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Android: LoadTexture works, fopen doesn't? What magic is this? #636

Closed
SethArchambault opened this issue Aug 29, 2018 · 9 comments
Closed

Comments

@SethArchambault
Copy link
Contributor

SethArchambault commented Aug 29, 2018

I'm building a project on an Amazon Fire HD 8, and I've encountered this issue where fopen doesn't seem to find the file correctly. I was using fopen to load a data file, but I decided to test it out using an image file that I had already successfully loaded as a texture:

 Texture2D plant_tex = LoadTexture("plant.png");
    {
        FILE *f = fopen("plant.png", "rb");
        printf("reading plant.png\n");
        if (f != NULL) {
            printf("success\n");
            fclose(f);
        }
        else {
            printf("Failed: %s\n" , strerror(errno));
        }
    }

The result I got:

I/raylib  (18938): INFO: [plant.png] Image loaded successfully (16x16)
I/raylib  (18938): INFO: [TEX ID 4] Texture created successfully (16x16 - 1 mipmaps)
D/myapp (18938): reading plant.png
D/myapp (18938): Failed: No such file or directory

So I figured, hmm - LoadTexture must be doing something special - perhaps altering the path when it sees it's installed on Android somehow. So I set off to track down what that special thing could be:

Texture2D LoadTexture(const char *fileName)
{
    Texture2D texture = { 0 };
    Image image = LoadImage(fileName);
    ...
}

Okay the princess must be in LoadImage:

Image LoadImage(const char *fileName)
{
    Image image = { 0 };

    if ((IsFileExtension(fileName, ".png")))
    {
        int imgWidth = 0;
        int imgHeight = 0;
        int imgBpp = 0;

        FILE *imFile = fopen(fileName, "rb");

        if (imFile != NULL)
        {
            ...
        }
}

Wait, this is verbatim what I'm doing in my own function!

I was stumped here.. then I decided to see what IsFileExtension did (nothing too interesting) - but I stumbled across this:

void StorageSaveValue(int position, int value)
{
    FILE *storageFile = NULL;

    char path[128];
#if defined(PLATFORM_ANDROID)
    strcpy(path, internalDataPath);
    strcat(path, "/");
    strcat(path, STORAGE_FILENAME);
...
}

That looked like something...

#if defined(PLATFORM_ANDROID)
...
static const char *internalDataPath;            // Android internal data path to write data (/data/data/<package>/files)
...

Then I found this:

 internalDataPath = androidApp->activity->internalDataPath;
 InitAssetManager(androidApp->activity->assetManager);

Which led me to the utils.c file, and this!

// Replacement for fopen
FILE *android_fopen(const char *fileName, const char *mode)
{
    if (mode[0] == 'w') return NULL;

    AAsset *asset = AAssetManager_open(assetManager, fileName, 0);

    if (!asset) return NULL;

    return funopen(asset, android_read, android_write, android_seek, android_close);
}
#endif

Which looks like the thing I want. But it doesn't appear to be used anywhere!

I'm very confused right now.. It looks like I should just be using this ASsetManager to open files, but how should I do this with raylib?

Thanks!

@raysan5
Copy link
Owner

raysan5 commented Aug 29, 2018

Hey @SethArchambault!

Great tracking exercise! You got almost there. ;)

Due to Anroid APK internal filesystem, data should be loaded through the assets manager, that's what I'm doing with raylib, just replaced default fopen by a custom one that uses assets manager...

That functionality is not directly exposed by raylib... mmmh... probably it should be...

@SethArchambault
Copy link
Contributor Author

SethArchambault commented Aug 29, 2018

@raysan5 Haha, so close! Thanks!

This is what I love about raylib, the ability to really track the code.. Not something I've ever been able to do in a massive object oriented framework..

Yeah sounds like a LoadFile() function could be useful? Anyways, this resolves it for me..

@SethArchambault
Copy link
Contributor Author

K I was stuck on this for a bit getting a SIGSEGV on AAssetManager_open, but I think I know why now!

I need access to this (in core.c):
androidApp->activity->assetManager

Is this exposed by raylib at all? I need an assetManager to use in AAssetManager_open, and I see that I can't create my own - my theory is I need to use the same one that is used in InitAssetManager..

Thanks!

@raysan5
Copy link
Owner

raysan5 commented Sep 13, 2018

Your assumption is right, you need to use the same.

And no, by default is not exposed by raylib... but you can include utils.h, it should do the trick.

@SethArchambault
Copy link
Contributor Author

@raysan5 Yes! Thank you, that was so simple! 👍

@SethArchambault
Copy link
Contributor Author

@raysan5 Hey even though my problem is solved, I realize I don't fully grasp how this is working. Like I get that I can access this function now, but it's confusing to me how I didn't have access it to before.. Trying to wrap my mind around it.

Like, raylib.h gives me access to the LoadImage function, and that must use fopen eventually which has access to the android_fopen function, yet that's not exposed to me when I include raylib.h. I feel like I'm fundamentally missing a solid conceptualization of how files can use or not use header files to hide functionality.. Any good resource on this?

@a3f
Copy link
Contributor

a3f commented Sep 14, 2018

android_fopen is declared in a raylib-internal header. You could try to copy the function prototype into your own code to make use of it.
But because it's not public API (not in a header raylib installs), you are not guaranteed, that this will work or do so consistently.

Generally, a function provided by raylib for public use must only be exported in the library. On Unix that means you don't add a static in front, because by default everything is exported.
On Windows, it's the other way round. Everything is private and you need to explicitly export those symbols you need. You can use tools like nm(1) to see which symbols are in a library's symbol table.

Additionally, it's customary to supply a header file which contains the declarations necessary to use the symbols. This provides type checking and a contract between library implementor and user on which symbols are usable how.

So, to sum up because android is Linux, you could just copy the prototype out of the header ( or just include it) and call the function yourself.

HTH.

@raysan5
Copy link
Owner

raysan5 commented Sep 14, 2018

@a3f Thanks for the detailed explanation!

@SethArchambault actually, the only thing you're doing is replacing fopen() calls by an Android analog android_fopen() that manages assets data inside the .apk file. If you try to use default fopen() calls, despite possible, those functions can not access .apk data properly.

@SethArchambault
Copy link
Contributor Author

Thanks! I think I've got it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants