Skip to content

Tutorial) 1. Using the Debugger

Bryce "BtheDestroyer" Dixon edited this page Mar 21, 2017 · 8 revisions

Note: This tutorial is specifically geared towards beginners who have little to no programming experience. Because of this, a lot of simple stuff will be explained. These explanations will become more scarce as the tutorial goes on as every section expects the reader to have read everything prior to it, so repetition is not needed.

1.0. Before You Start

Make sure you have devkitPro and devkitARM installed. If you do not, download and install it here

Next, download the source.zip or source.tar.gz of the latest release of SpriteTools and extract it to an empty folder. Open a command window in that folder and run the command make install (you may need sudo permissions on Linux and macOS). This will install SpriteTools and all of its dependencies.

1.1. Starting a New Project

Download the included example.X.zip (X is the version so it will be something like example.2.2.zip) and extract it to a folder with no spaces in its path. Personally, I use C:/3dshacking/PROGRAMNAME/ on Windows and ~/Documents/3DSHacking/PROGRAMNAME/ on Linux. It should look something like this:

Extracted example

Note: If you don't have a good text editor (Notepad++, Geany, Sublime, etc.) it is recommended that you download one.

Now that we've got the folder set up, let's open the Makefile. Right now it has default information regarding who made the program and what it is, so let's change that:

Default Makefile

Modified Makefile

If you have an icon for your application, either change the ICON field as described in the comment or replace the icon.48.png with your own. If you don't have an icon, you can just stick with the default one provided.

Now open the source folder and delete the main.c file from the template. We will be starting our own project so we won't need this example file.

1.2. Starting Your main.c

First, make sure you can see file extensions. In Windows' default browser you can do this by going into View and checking "File name extensions" like this:

View>File name extensions

Now create a new file and name it main.c like this:

New>Text Document

Rename it

Next, open it in your text editor of choice and type in this code, then save:

#include <3ds.h>
#include <spritetools.h>
#include <stdio.h>

int main(void)
{
  return 0;
}

This is the base of our program. Including 3ds.h will give us access to some 3DS functions, datatypes, etc. through ctrulib. Including spritetools.h will give us access to all spritetools functions, datatypes, etc. Finally, including stdio.h will give us access to some basic input and output functions.

Now to ensure everything is set up correctly, let's build what we have by opening a command window in our main folder (which contains the Makefile) and run the command make. The first time it may take a minute, but future builds will be much faster. If everything was successful, you should get an .elf file, an .smdh file, and a .3dsx file. If there's a problem, make sure you followed every step listed above.

1.3. Printing to the Screen(s)

Let's add some code to our program so it actually does something now. First, we should initialize everything when the program starts and finish/clean it when the program ends with the functions ST_Init() and ST_Fini(). Your main should now look like this:

int main(void)
{
  ST_Init();
  
  ST_Fini();
  return 0;
}

Now we can set the screen to print to and print text using the following in between ST_Init() and ST_Fini():

consoleInit(GFX_TOP, NULL);
printf("Hello world!");

Next, we'll need to throw in a while loop so the program doesn't immediately close. Put the following code block after our printf before ST_Fini() to tell the program to run for 10 seconds (10000 ms) before closing:

while (aptMainLoop())
{
  if (ST_TimeRunning() > 10000)
    break;
}

ST_TimeRunning() returns the time your program has been running in ms.

Now our full main.c file should look like this:

#include <3ds.h>
#include <spritetools.h>
#include <stdio.h>

int main(void)
{
  ST_Init();
  consoleInit(GFX_TOP, NULL);
  printf("Hello world!");
  while (aptMainLoop())
  {
    if (ST_TimeRunning() > 10000)
      break;
  }
  ST_Fini();
  return 0;
}

If you want to put your text in the middle of the screen, you can use \x1b[y;xH before text to move the cursor (where y is the y position in characters and x is the x position in characters) like this: printf("\x1b[14;20HHello world!");

Now make your program and open the Homebrew Launcher on your 3DS (through any method) (Make sure that your 3DS and computer are both connected to the same network!). Now press Y on your 3DS to turn on the netloader and then run the following command on your computer (in the same window that just built your program): 3dslink PROGRAM.3dsx -a ADDRESS. Replace PROGRAM with the name of your .3dsx file and ADDRESS with the ip of your 3DS. In my case, I'll be running 3dslink SpriteToolsTesting.3dsx -a 192.168.0.15, but your command will probably differ. This may fail a few times. If it doesn't work for more than 60 seconds, restart your 3DS and ensure that it and your computer connected to the same network.

When your program loads, it should display "Hello world!" for 10 seconds and then close.

1.4 Debugging and Variables

Let's make some variables at the very top before ST_Init() and remove the printf():

int linkX = 5;
int linkY = 5;
int second = 0;

ST_Init();
consoleInit(GFX_TOP, NULL);
while (aptMainLoop())

Now we can put a printf() in our while loop: printf("\x1b[%d;%dHL", linkX, linkY);. The %ds take the ints that we gave it afterwards which allows us to move the cursor using variables when we're printing our character L. Now if we build and run the program it should print an L at the position 5, 5. The second will be used in a bit.

The point of variables is that they can change, so let's use ST_TimeRunning() to move our character down 1 every second.

if (ST_TimeRunning() % 1000 <= 50 && !second)
{
  second = 1;
  linkY++;
  printf("\x1b[2J");
}
if (ST_TimeRunning() % 1000 > 50)
  second = 0;

The % is called "mod" and it will return the remainder when the first number is divided by the second number. In this example, every 1000 ms, the remainder will be 0 and linkY will increase by 1, moving our character's position down by 1. Our second variable ensures that it can only increment once per second in case the loop runs multiple times in one ms. Finally, the \x1b[2J in the printf() clears the screen when our character moves.

Now, if we make and run our program, our character should move down one line every second. However, it's moving right instead of down. It's time to use the debugger to see if our variables are getting set correctly. Move the consoleInit() inside the while loop and throw in a few lines right before and inside the while loop like this:

ST_DebugSetOn();
ST_DebugAddVar("Link X",(void *)&linkX, INT);
ST_DebugAddVar("Link Y",(void *)&linkY, INT);
while (aptMainLoop())
{
  consoleInit(GFX_TOP, NULL);
  /* ...Code... */
  consoleInit(GFX_BOTTOM, NULL);
  ST_DebugDisplay();
}

The function ST_DebugAddVar(char *name, void *varp, ST_PointerType datatype) adds a variable to the debugger. ST_DebugSetOn() turns the debugger on as it's off by default and it needs to be on to be displayed. ST_DebugDisplay() will show the debugger on the currently selected screen which is why we need to consoleInit() the bottom screen first so it doesn't draw over the what we have already on the top screen.

Screens with debugger 1Screens with debugger 2

Well our variables are being set correctly, so our code must be wrong. Looking at our printf() that draws our character, it seems we switched linkX and linkY. Switching those should give us printf("\x1b[%d;%dHL", linkY, linkX) which should display correctly.

Finished screens 1Finished screens 2

Now when we're ready for release, we can just remove the ST_DebugSetOn() when the application starts and all of the debug messages will go away. Your final code should look like this:

#include <3ds.h>
#include <spritetools.h>
#include <stdio.h>

int main(void)
{
  int linkX = 5;
  int linkY = 5;
  int second = 0;

  ST_Init();
  ST_DebugSetOn();
  ST_DebugAddVar("Link X",(void *)&linkX, INT);
  ST_DebugAddVar("Link Y",(void *)&linkY, INT);
  while (aptMainLoop())
  {
    consoleInit(GFX_TOP, NULL);
    if (ST_TimeRunning() % 1000 <= 50 && second == 0)
    {
      second = 1;
      linkY++;
      printf("\x1b[2J");
    }
    if (ST_TimeRunning() % 1000 > 50)
      second = 0;

    printf("\x1b[%d;%dHL", linkX, linkY);
    if (ST_TimeRunning() > 10000)
      break;
    consoleInit(GFX_BOTTOM, NULL);
    ST_DebugDisplay();
  }
  ST_Fini();
  return 0;
}