Skip to content

Compiling libnds code as a static library

N•I•L edited this page Apr 17, 2022 · 4 revisions

Introduction

You're doing the final polishes of your Wonderful game you've worked on for the last months. The result is spectacular. You launch it. Your fans are astounded and you want to surprise them even more with a brand new Wonderful game 2, pretty much based on the same idea as your previous project, but with revolutionary graphics, more game modes, improved gameplay and other crazy stuff you can think of.

Now, there's a bit of a problem. How are you gonna do it? You can simply copy-paste your old code and change it according to your increbidle plan. Well, that's lame, isn't it? Even a caveman can do that. Plus, imagine what happens if you discover a 5☆ critical bug in your Wonderful game which is implicitly present in Wonderful game 2 as well. You work so hard to fix the bug by editing more than 70 files in Wonderful game's code, and now let's see how are you gonna do the same for the Wonderful game 2? Can you easily remember in which files modified what? What if in your second game you added so many things that your code becomes unrecognizable from its first state? What if you would also have to fix the same thing in Wonderful game 3, and 4, and 5 ... ?

Alright, you'll say: just put the common code into a separate place so you can access it easily and any change will become visible in all of your projects as soon as commiting it. Yep, that's an excellent starting point. You can isolate your game engine from the games that make use of it. And you can stop right here if this fits all your needs. This wiki is useless, I know, how dare I teach you programming tips that even myself I doubt their practicality?

Supponsing you reach this paragraph, I'll think you might be at least curious or willing to listen to what I have to say. We can do even better than that. Let's say some sparkling mind named Jack, homebrewer and DS programmer out there on the internet, caught interest in your fascinating projects and wants to create a Wonderful Clone in their own style, bringing their own innovative ideas into a game everyone loves. You would love to see that. Jack reaches out to you and asks for your Wonderful game library that he'll use for his own game. Wait... a Wonderful game LIBRARY? What's he talking about? Does he mean the engine? Ok... You send him an archive of your common code folder, all good, everyone's happy, right? Yes... or not. You have a secret. Everybody has secrets. Your engine code contains secrets. You probably don't want Jack to see that behind your Brave Character of your _Wonderful game_s actually lies a laughably timid implementation of attack mechanics masked out by some gripping special effects. You have tocover your Brave Character's weakness in order to defend its honor.

That's why you'd wanna provide Jack with something that gives him the ability to use your Wonderful engine, but makes him harder to see the dark side of the Brave Character. The solution is to build Wonderful engine into a static library. Therfore Jack will receive a libwonderfulengine.a library file along with some innofensive header files that will let him manipulate the library's content in any way he wants.

Now I'll speak for rookies (like me) who had never had the chance to hear about statically-linked *.a libraries before. Where does that .a came from? The word Alien?? (ba dum tss) To be frank, I don't know :)). It's a library, it should end with .lib or something. Well, it actually does, but on Windows systems. In the great kingdom of Linux, this type of files are known as .a. And I'll stick to that convention also here in NDS Practical Wiki just for us to know that this is not an ordinary LIB file (even though more or less it is), but A specific NDS one. Aaaand because libnds is part of devkitARM which runs under a Unix-subsystem and it feels more natural to keep the files naming style the way others expect to see them. Ok, this settled, let's shortly about about what it is a static .a library. It's a binary file which can be compiled from your actual C(++) code, but cannot be executed. It contains the object machine code along with symbols and definitions (meaningly variable names, class and methods names etc). It's basically an intermediate state between programmer's code and the final executable. The library can be attached to an external piece of code and together form an NDS ROM you can finally run on your console/emulator.

Some time ago when I was researching the matter I was too idiot to realise that libnds actually has a library template so I modified the Makefile myself to compile into a lib. You don't have to be that hardcore so just clone the example template and you're fine.

Setups

Imagine this simplified scenario:

wonderfulengine                   WonderfulGame
╠═ include                        ╠═ source
║  ╚═ brave_character.h           ║  ╚═ main.cpp
╠═ lib                            ╚═ Makefile
║  ╚═ libwonderfulengine.a       
╠═ source                         
║  ╚═ brave_character.cpp
╚═ Makefile

where WonderfulEngine uses the arm9lib as a template (from my tests the library's name must be all lowercase, otherwise can't be linked (?)).

wonderfulengine/include/brave_character.h

#pragma once
int brave_character_max_hp();

wonderfulengine/source/brave_character.cpp

#include "brave_character.h"
int brave_character_max_hp() { return 100; }

WonderfulGame/source/main.cpp

#include <brave_character.h>

...

int main() {
    ...
    printf("%i", brave_character_max_hp()); // prints 100 !
    ...
}

In order for this to work, you have to edit the Wonderful game's Makefile:

# Define a variable containing the file path to our library.
# This path should contain both the include/ and lib/ subdirectories
LIBWONDERFULENGINE := /path/to/.../wonderfulengine 

... # default Makefile content


#---------------------------------------------------------------------------------
# any extra libraries we wish to link with the project
#---------------------------------------------------------------------------------

# Here we add our library by its name starting with "-l"
# The libraries should be declared before any of their dependencies
# WonderfulEngine only depends on libnds, so it goes before -lnds9
# if WonderfulEngine had any FAT file management features, the order
# of libs would have been "-lwonderfulengine -lfat -lnds9"
LIBS	:= -lwonderfulengine -lnds9
#---------------------------------------------------------------------------------

... # other Makefile content

# Previously we have stated that our game uses WonderfulEngine, but the Makefile has no
# idea where to take it from. Remember the library path variable from above? It's time to use it!

# list of directories containing libraries, this must be the top level containing
# include and lib
#---------------------------------------------------------------------------------
LIBDIRS	:=	$(LIBNDS) $(LIBWONDERFULENGINE)

These being said, your game should now run code from the WonderfulEngine library. Problem solved. Congratulations!

Extra

Here I'll describe what changes to bring to the normal Makefile which generates NDS files in order to compile into a *.a library. This may be useful in one of the following cases:

  • you want to use both ARM7 and ARM9 of the DS in your library (who even does that though?)
  • you want to compile for GBA system (the process is similar to the DS, except that GBA examples don't have a library template as far as I can tell)
  • you want to use GRIT generated graphics in your library (The arm9lib does not have Makefile rules for graphics, so you have two equivalent choices: either change the arm9lib to add rules which transform PNGs into binary buffers ready to load into VRAM [ If you're experienced with Makefiles enough to get this done on the first time, then who am I to stop you? ], or take the Makefile from any graphics example and do the following 2-lines change, assuring you can use graphics in your project and also compiling results in a library)

wonderfulengine/Makefile

# we need to set the output to be library
export OUTPUT	:=	$(CURDIR)/lib/libwonderfulengine.a
# the arm9lib example uses a more generic case approach, := $(CURDIR)/lib/lib$(TARGET).a 
# but I rewrote it in a that way for the sake of simplicity

... # Makefile code

#---------------------------------------------------------------------------------
# main targets
#---------------------------------------------------------------------------------
$(OUTPUT)		:	$(OFILES)   # That's all!! Seriously.

Note that the library Makefile may raise an error if the lib/ folder does not exist in your path. If that's the case, just manually create it manually, mkdir it somewhere in the Makefile idk.