Download Obb File from Own Server

I’m using split packages for my Android app, so I have an APK and a seperate OBB file.

If the obb file is present it works fine, but if it isn’t present I want to be able to download it from my own server. I can download it using www but when I’ve downloaded it I can’t figure out how to read the contents and / or install it in the correct folder.

Unity documentation mentions how you can host OBB files yourself, but it doesn’t say how you install them. Has anyone done this?

My code looks like this:

protected IEnumerator LoadObb(string mainPath)
    {
        Debug.Log("Load obb from " + mainPath);

        Debug.Log("Get file from: " + mainPath);

        WWW www = null;
        try
        {
            www = WWW.LoadFromCacheOrDownload(mainPath, 0);
        }
        catch (Exception e)
        {
            Debug.Log("Exception downloading obb: " + e);
        }
        

        // Wait for download to complete
        while (www == null || !www.isDone)
        {
            yield return new WaitForSeconds(0.2f);

            Debug.Log("Downloading obb ");

            if (www != null)
            {
                Debug.Log("Progress: " + www.progress);
            }
        }

        if (www.error != null)
        {
            Debug.Log("wwww error " + www.error);
        }
        else
        {
            Debug.Log("obb download complete");

            //TODO: Get this working
            Debug.Log("About to create directory");
            System.IO.Directory.CreateDirectory(GooglePlayDownloader.GetExpansionFilePath());

            Debug.Log("About to get bytes");
            var bytes = www.bytes;

            if (bytes != null)
            {
                Debug.Log("Bytes = " + bytes.Length);
            }

            Debug.Log("About to write file");
            if (bytes != null)
            {
                File.WriteAllBytes(GooglePlayDownloader.GetPathForSavingOBB(), bytes);
            }
        }


        yield return new WaitForSeconds(0.5f);
        Application.LoadLevel("Start");
    }

Everything seems to work up until I try to access www.bytes

At that point I see this message, and bytes are null:

“WWWCached data can only be accessed using the assetBundle property!”

How else can I save the obb file?

1 Like
AssetBundle bundle = www.assetBundle;

Check: http://docs.unity3d.com/Documentation/Manual/DownloadingAssetBundles.html

LoadFromCacheOrDownload is for downloading AssetBundles, if your OBB is a zipped file you can download it via normal WWW class and then unzip the file on your program.

Thanks - I almost have it working.

It’s loading the file and saving it to the correct directory now, but unity doesn’t pick up the obb file until the next restart. Is there a way to force a refresh?

I have the same problem, after downloading the obb file form a server, and putting in the correct folder, the game can´t load scenes, but if I restart the game, it loads scenes.

It seems we have to change the path from main apk to obb, but I don´t konw how to do it manually.

Do you have the solution?

Regards

1 Like

Was anyone able to solve this problem? I’m in the same place where I download the obb file but am unable to move to the next scene until I restart my game.

1 Like

I realised this is an old thread, but did anyone fix this?
I download the obb from my own server. Then load it using WWW.LoadFromCacheOrDownload, then try and load the next scene. I also tried loading a file from Resources to check if the obb had downloaded. It never works until I reboot the app, then it works fine.

This is the original thread: More Google OBB drama - Unity Engine - Unity Discussions

We have a solution, we call a “dummyActivity” after loadFromCacheOrDownload:

public static void resetPath()
    {
        AndroidJavaClass resetClass = new AndroidJavaClass("com.pixelratio.resetapplicationpath.DummyActivity");
        resetClass.CallStatic("static__reload");

    }

The jar file with the dummy activity: https://dl.dropboxusercontent.com/u/51228949/resetappliationpath.jar

This is the content:

package com.pixelratio.resetapplicationpath;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import com.unity3d.player.UnityPlayer;

public class DummyActivity
  extends Activity
{
  protected void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    UnityPlayer.UnitySendMessage("obbManager", "__loadMainLevel", "");
    finish();
  }
  public static void static__reload()
  {
    UnityPlayer.currentActivity.runOnUiThread(new Runnable()
    {
      public void run()
      {
        Intent intent = new Intent(UnityPlayer.currentActivity.getApplicationContext(), DummyActivity.class);
    
        UnityPlayer.currentActivity.startActivity(intent);
      }
    });
  }
}
[/co

Thank you very much!
Although I’m curious what you mean by Application.dataPath will indicate if it’s mounted?
Will Application.dataPath be null or empty if the OBB is not loaded?

I don’t know, but I have this function in my old code:

(please note that is an old code writed in javascript… XD )

function checkPath(){


    while(true){
        
          Debug.Log(Application.dataPath);
          yield WaitForSeconds(0.5);
       }

}

It’s called before resetPath() function (it’s only for debug purpose). If you view the jar file, it sends the message “__loadMainLevel”, it waits 2 seconds and load the first scene on the obb file.

Here is the complete script:

#pragma strict

private var expPath : String;
private var serverUrl : String = "https://dl.dropboxusercontent.com/u.......squest.obb";


public var loadingBar : Transform;

public var screen01 : GameObject;
public var screen02 : GameObject;
public var loadingObject : GameObject;
public var loadingObject2 : GameObject;
public var textObject : GameObject;

public var loadFromOwnServer : boolean = false;


public var  background :Texture2D;

function OnGUI(){
        if( loadFromOwnServer == false)
            GUI.DrawTexture(new Rect(0,0,Screen.width,Screen.height),background, ScaleMode.ScaleToFit,true,0);

}

function Awake(){

    if( loadFromOwnServer == false) this.GetComponent(Camera).enabled = true;
    else{
        screen01.active = false;
        screen02.active = false;
        loadingObject.active = false;
        loadingObject2.active = false;
        textObject.active = false;
    }
}

function Start () {

  

    if(Application.platform != RuntimePlatform.Android){ //Editor loading
  
        yield WaitForSeconds(5);
      
      
        Application.LoadLevel("Main_GUI");
        return;
    }
  
  
  
    var obbState : int = checkOBBFile();
    if(obbState== 1)
            Application.LoadLevel("Main_GUI");
          
    else if(obbState == -1){
            if( loadFromOwnServer == false) DownloadFromGooglePlay();
            if( loadFromOwnServer == true)  DownloadFromOwnServer();
    }
          
    else if(obbState == -2)
            Debug.Log("External Storage is not available");
  
  
  
  
  
  
  

}











function DownloadFromGooglePlay() {

    /*screen01.active = true;
    screen02.active = true;
    loadingObject.active = true;
    loadingObject2.active = true;
    textObject.active = true;*/
      
    GooglePlayDownloader.FetchOBB();

    var mainPath : String;
    do{
    
        yield WaitForSeconds(0.5);
        mainPath = GooglePlayDownloader.GetMainOBBPath(expPath);
        Debug.Log("Waiting mainPath "+mainPath);
          
    }
    while(mainPath == null);
    

    
  
    var uri : String  = "file://" + mainPath;
    Debug.Log("downloading " + uri);
    var www: WWW = WWW.LoadFromCacheOrDownload(uri , 0);      
  
    //Wait for download to complete
    while(www.isDone == false){

        //textObject.GetComponent(TextMesh).text = "Downloading... " + (www.progress*52000) + "/52000Kb";

        loadingBar.localScale.z = www.progress;
        yield;
                          
              
    }
  
    if (www.error != null) {
        Debug.Log (www.error);
        return;
    }
    else{

        Application.LoadLevel("Main_GUI");
    }
      
      


}













function DownloadFromOwnServer () {

  
    screen01.active = true;
    screen02.active = true;
    loadingObject.active = true;
    loadingObject2.active = true;
    textObject.active = true;
  
    
    
    

    var uri : String  = serverUrl;
    Debug.Log("downloading " + uri);
    //var www: WWW = WWW.LoadFromCacheOrDownload(uri , 0);      
    var www: WWW = new WWW(uri);  
    //Wait for download to complete
    while(www.isDone == false){

        textObject.GetComponent(TextMesh).text = "Downloading... " + (www.progress*52032) + "/55032Kb";
        loadingBar.localScale.z = www.progress;
        yield;              
    }
  
    if (www.error != null) {
        Debug.Log (www.error);
        return;
    }
    else{
  
        textObject.GetComponent(TextMesh).text = "Loading... ";
  
        //Put file in the correct directory
        Debug.Log("About to create directory "+expPath);
        System.IO.Directory.CreateDirectory(expPath);
      
        var futureFile : String = GooglePlayDownloader.GetMainOBBPath_ifNoExist(expPath);
        Debug.Log("About to create file " + futureFile);
      
        var bytes = www.bytes;
        if (bytes != null){
            System.IO.File.WriteAllBytes(futureFile, bytes);
            Debug.Log("file created" + futureFile);
         }
        
         /////////////////////
         ////////////////////
      
        
      
       GooglePlayDownloader.resetPath();
       checkPath();
      
    
      

      
    }
      
      
    

}

function __loadMainLevel(param : String){

    yield WaitForSeconds(2);
    Application.LoadLevel("Main_GUI");
}
















function checkOBBFile() : int{

  
    expPath = GooglePlayDownloader.GetExpansionFilePath();
    if (expPath == null){
  
        //Debug.Log("External Storage is not available");
        return -2;
          
    }
  
    else {
  
        var mainPath : String = GooglePlayDownloader.GetMainOBBPath(expPath);
        var patchPath : String = GooglePlayDownloader.GetPatchOBBPath(expPath);
      
        Debug.Log("expPath " + expPath);
        Debug.Log("Main " + mainPath);
        //Debug.Log("Main_ " + mainPath.Substring(expPath.Length));
  
      
        if(mainPath != null) {  
          
            Debug.Log("Main " + mainPath);
            return 1;
        }
      
        else{
          
            Debug.Log("mainPath not available");
            return -1;
        }
    }
      
      
          
}



function checkPath(){


    while(true){
        
          Debug.Log(Application.dataPath);
          yield WaitForSeconds(0.5);
       }

}

And this is the GooglePlayDownloader.cs with some changes:

using UnityEngine;
using System.Collections;
using System.IO;
using System;

public class GooglePlayDownloader
{
    private static AndroidJavaClass detectAndroidJNI;
    public static bool RunningOnAndroid()
    {
        if (detectAndroidJNI == null)
            detectAndroidJNI = new AndroidJavaClass("android.os.Build");
        return detectAndroidJNI.GetRawClass() != IntPtr.Zero;
    }
  
    private static AndroidJavaClass Environment;
    private const string Environment_MEDIA_MOUNTED = "mounted";

    static GooglePlayDownloader()
    {
        if (!RunningOnAndroid())
            return;

        Environment = new AndroidJavaClass("android.os.Environment");
      
        using (AndroidJavaClass dl_service = new AndroidJavaClass("com.unity3d.plugin.downloader.UnityDownloaderService"))
        {
       // stuff for LVL -- MODIFY FOR YOUR APPLICATION!
            dl_service.SetStatic("BASE64_PUBLIC_KEY", "MIIBIjANBgkqhkiG9w0BAQ.....EleWfyliTpVzUUwvPY00/Mc6WaYa34Inju0TB6VeUA6b64pa2uHv08KEsAGoDwIDAQAB");
       // used by the preference obfuscater
            dl_service.SetStatic("SALT", new byte[]{1, 43, 256-12, 256-1, 54, 98, 256-100, 256-12, 43, 2, 256-8, 256-4, 9, 5, 256-106, 256-108, 256-33, 45, 256-1, 84});
        }
    }
  
    public static string GetExpansionFilePath()
    {
        populateOBBData();

        if (Environment.CallStatic<string>("getExternalStorageState") != Environment_MEDIA_MOUNTED)
            return null;
          
        const string obbPath = "Android/obb";
          
        using (AndroidJavaObject externalStorageDirectory = Environment.CallStatic<AndroidJavaObject>("getExternalStorageDirectory"))
        {
            string root = externalStorageDirectory.Call<string>("getPath");
            return String.Format("{0}/{1}/{2}", root, obbPath, obb_package);
        }
    }
    public static string GetMainOBBPath(string expansionFilePath)
    {
        populateOBBData();

        if (expansionFilePath == null)
            return null;
        string main = String.Format("{0}/main.{1}.{2}.obb", expansionFilePath, obb_version, obb_package);
        if (!File.Exists(main))
            return null;
        return main;
    }
  
        public static string GetMainOBBPath_ifNoExist(string expansionFilePath)
    {
        populateOBBData();

        if (expansionFilePath == null)
            return null;
        string main = String.Format("{0}/main.{1}.{2}.obb", expansionFilePath, obb_version, obb_package);
        //if (!File.Exists(main))
        //    return null;
        return main;
    }
  
    public static string GetPatchOBBPath(string expansionFilePath)
    {
        populateOBBData();

        if (expansionFilePath == null)
            return null;
        string main = String.Format("{0}/patch.{1}.{2}.obb", expansionFilePath, obb_version, obb_package);
        if (!File.Exists(main))
            return null;
        return main;
    }
  

    public static void FetchOBB()
    {
        using (AndroidJavaClass unity_player = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
        {
            AndroidJavaObject current_activity = unity_player.GetStatic<AndroidJavaObject>("currentActivity");
  
            AndroidJavaObject intent = new AndroidJavaObject("android.content.Intent",
                                                            current_activity,
                                                            new AndroidJavaClass("com.unity3d.plugin.downloader.UnityDownloaderActivity"));
  
            int Intent_FLAG_ACTIVITY_NO_ANIMATION = 0x10000;
            intent.Call<AndroidJavaObject>("addFlags", Intent_FLAG_ACTIVITY_NO_ANIMATION);
            intent.Call<AndroidJavaObject>("putExtra", "unityplayer.Activity",
                                                        current_activity.Call<AndroidJavaObject>("getClass").Call<string>("getName"));
            current_activity.Call("startActivity", intent);
  
            if (AndroidJNI.ExceptionOccurred() != System.IntPtr.Zero)
            {
                Debug.LogError("Exception occurred while attempting to start DownloaderActivity - is the AndroidManifest.xml incorrect?");
                AndroidJNI.ExceptionDescribe();
                AndroidJNI.ExceptionClear();
            }
        }
    }

    public static void resetPath()
    {
        AndroidJavaClass resetClass = new AndroidJavaClass("com.pixelratio.resetapplicationpath.DummyActivity");
        resetClass.CallStatic("static__reload");

    }
  
    // This code will reuse the package version from the .apk when looking for the .obb
    // Modify as appropriate
    private static string obb_package;
    private static int obb_version = 0;
    private static void populateOBBData()
    {
        if (obb_version != 0)
            return;
        using (AndroidJavaClass unity_player = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
        {
            AndroidJavaObject current_activity = unity_player.GetStatic<AndroidJavaObject>("currentActivity");
            obb_package = current_activity.Call<string>("getPackageName");
            AndroidJavaObject package_info = current_activity.Call<AndroidJavaObject>("getPackageManager").Call<AndroidJavaObject>("getPackageInfo", obb_package, 0);
            obb_version = package_info.Get<int>("versionCode");
        }
    }
}

Well I got it working, although I used OnApplicationPause to check when the Android activity had switched back and that seemed to work fine. Thanks for the help :slight_smile:

2 Likes

@Gizmoi : I reached exactly where you were but i am unable to swtich activity…can you please help.

Many thanks in advance. Once Done i will share the entire code for people who gets stuck like me.

This is the code we use to switch to a DummyActivity I made in Android Studio.
RunDummyActivity

private static void RunDummyActivity()
{
    using (var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
    {
        var activity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");

        var dummyActivity = new AndroidJavaClass("com.threesixty.dummy.DummyActivity");
        dummyActivity.CallStatic("launch", activity);
    }
}

The DummyActivity looks like this:
DummyActivity.java

public class DummyActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        finish();
    }

    public static void launch(Activity activity) {
        Intent intent = new Intent(activity, DummyActivity.class);
        activity.startActivity(intent);
    }
}

This is the code we use to check that the app has returned back to Unity.
OnApplicationPause

private void OnApplicationPause(bool paused)
{
    Debug.Log("Application Paused: " + paused);
    if (paused == false && m_WaitingForResume == true)
    {
        OnComplete();
    }
}

Hope that helps.

@Gizmoi did you have to add this activity to the manifest file for this to work? I am getting the following error:

AndroidJavaException: android.content.ActivityNotFoundException: Unable to find explicit activity class {com.mycompany.mygame/com.mycompany.lib.DummyActivity}; have you declared this activity in your AndroidManifest.

I managed to get this working (as in launching the other activity) by adding the activity to the overridden manifest file. However, this does not fix the issue. The screen simply goes black and stays black and does not load the scene from the OBB :frowning:

hiiii

I am also trying to download and install obb from self server but all the plugins are absolute and depricated. Could you please send the code of how you are downloading anf how do you exactly use it. please.
it would be very helpful.
Thank You