HOWTO: Including data in the application bundle

BlitzMax Forums/BlitzMax Tutorials/HOWTO: Including data in the application bundle

JazzieB(Posted 2007) [#1]
For those of us working on Mac titles, and in particular those wanting to produce universal binaries at the end of it, the use of IncBin is not exactly ideal. The reason for this is that when you then come to create your universal binary you end up with your media being included twice! Not good, as this turns a 16MB app into a 32MB one, which should just be around the 18MB mark. So, you can either have all this data stored externally, which is messy, or you can put the data into the application bundle itself, where both the PPC and Intel binaries can access the same data.

This is actually quite easy to do, so a lot of you may already have figured it out. But for those who haven't, this is how you go about doing it.

NOTE: I only do this for static game data, i.e. media and other files that I only need to read. Anything that needs to be written to I store in the "/Users/Shared/" folder. You should be able to write to the application bundle as well, although I haven't tested it, and it's generally not recommended.

1. If you haven't already built your app, go ahead and build one. There's no need to worry about making any code changes at this stage.

2. Now go to your application, right-click on it, and select "Show package contents".

3. A window will pop-up that will just show a folder called "Contents". Open this folder.

4. Now you will see two folders ("MacOS" and "Resources"). Open the "Resources" folder. By default, the only thing in here is your app's icon file.

5. Create a new folder and call it "Data" (or anything of your choosing).

6. Copy all of your game's media and other files into this new "Data" folder.

7. Highlight the "Data" folder and press Command+I (or right-click and select "Show info").

8. At the bottom of this dialogue box you will notice that the file permissions say "read and write" for you. Click on the drop-down arrow to reveal the permissions for the other types of user. You'll notice that these say "No access", which is obviously no good if you want your app to run from other accounts. Simply select each type of user and change the permissions to "read only"

9. With the above dialogue still open, click on the "Apply to sub-folders" button. You will be asked for confirmation and to then enter your admin password.

10. If you want to attempt writing to some files/folders, highlight each of the relevant files and/or folders and change the permissions to "read and write".

You may now close the application bundle's window, as you've now got all your data in place. The best bit is you don't need to do all of this again the next time you build your app as it isn't overwritten.

NOTE: If you later need to change any of the files that you have in the bundle, first change the original files and then copy them back into the bundle. You WILL need to change the permissions for any new files for the other users once you've copied the updated files into the bundle.

Now all you need to do is make some small changes to your code in order to load the data from the bundle instead of an external folder. This basically just means finding the correct path for the data.

1. First you need to find the filename of your app, as you can't assume the end-user won't have changed it. The following will do just that.

Global appName:String=StripDir(AppFile)+".app"

2. Next all you need to do is add the path to the data.

Global appData:String=appName+"/Contents/Resources/Data/"

3. Now just add the data path to the beginning of all your loading functions, for example...

Global gameLogo:TImage=LoadImage(appData+"Gfx/GameLogo.png")

If you're making your app for more than one OS then you may need to have different loading sections between the compiler directives ?macos, ?win32, ?linux, etc.

And that's it. Just remember to change the permissions again for any files you later update and there's no need to re-copy data for every rebuild of your app. The only things that get overwritten when an app is built are the binary in the "MacOS" folder and the icon in the "Resources" folder.

For information on creating universal binaries and changing the icon, check here.


ImaginaryHuman(Posted 2007) [#2]
Cool man, thanks for the nice clear and well explained steps. I will definitely refer to your tutorial when I get to that point in development.

Thanks!


xlsior(Posted 2007) [#3]
I suppose you could also use Koriolis' zipstream module to use streamed password protected zips containing the resources if you want to keep people from ripping your resources too easily.


JazzieB(Posted 2007) [#4]
I suppose you could, although I haven't tried. I see no reason why not.


Grey Alien(Posted 2007) [#5]
Excellent thanks. Using this today!

Works like a charm, now I've added a flag to my framework called DataInAppBundle which handles the path change automatically.


mulawa1(Posted 2009) [#6]
Pure Gold JazzieB!

My eMac arrived yesterday and by the end of the day I had my first app fully operational - even managed all the dmg stuff!

Have you found a way to stop Blitz overwriting your icon every time you recompile? I tried making it "read only" but that stopped Blitz from completing the compile.

Peter


mulawa1(Posted 2009) [#7]
PS This item should be Sticky!


mulawa1(Posted 2009) [#8]
I think I've answered my own question about the icon. Obviously the executable and the data have to be copied to a separate folder which will be made into the dmg. After recompiling just copy the executable into the package which is in Contents/MacOS.

Just in case someone is struggling with dmg creation, I did it by using Terminal, moving to my app's directory and then issuing a command like this

hdiutil create XXX.dmg -volname "YYY" -fs HFS+ -srcfolder "ZZZ"

where

XXX = disk image file name
YYY = window title displayed when DMG is opened

Here's mine:

hdiutil create pj.dmg -volname "PJ Special" -fs HFS+ -srcfolder "pj"

Peter


JazzieB(Posted 2009) [#9]
Have you found a way to stop Blitz overwriting your icon every time you recompile? I tried making it "read only" but that stopped Blitz from completing the compile.

I don't think there is a way of stopping this from happening. There are, however, ways of having BM use a different icon. This has been discussed before, although I can't remember if this was Windows only or whether it worked on the Mac as well. I generally don't worry about the icon until I'm ready for the final build.

PS This item should be Sticky!

I agree :)

Glad you found this useful.


Brucey(Posted 2009) [#10]
I'm from the camp that says you shouldn't be doing everything by hand... but anyway.

I don't think there is a way of stopping this from happening.

Indeed not, but my BMK can run post build scripts, which allows you to do extra stuff after a build.

For example, with Grisu's PRP Mac build, it's set up to copy both a icns file, and the FMOD dll into the application bundle. Given the number of releases he comes out with, I cannot comprehend doing this by hand every time.

The same goes for making it a Universal app - doing this by hand for each release? No thanks!

So, for me, I just have a file, "post.bmk" in the application folder, which is processed as part of the build.

The contents look like this :
# MacOS post build script
#
#
@define doPostInstall

	# only interested for Mac platform
	
	if bmk.Platform() == "macos" then

		local path = utils.ModulePath("bah.fmod") .. "/lib/" .. bmk.Platform() .. "/"
		local file = "libfmodex.dylib"

		# copy FMOD
		sys.CopyFile(path .. file, %exepath% .. "/" .. file)
		
		#copy icons
		sys.CopyFile(%buildpath% .. "/data/prp.icns", %exepath% .. "/../Resources/" .. %outfile% .. ".icns")

	end

@end

# run the post install
doPostInstall

The code inside the @define block is basic Lua, with some enhancements. For example variables wrapped in %% are automatically translated by BMK at runtime.

Obviously, you could have it set up to do anything, like copying any files, ftp'ing, etc.

But of course, for everyone that prefers to do it manually, they can follow the 10 steps above. :-)