Writing a 3DS Homebrew app – How hard is it?

Writing a 3DS Homebrew app – How hard is it?

At first, trying to write a 3DS Homebrew app in C/C++ may seem hard, but it really isn’t! If you know a bit about programming, the basics of C++ are quite easy.

Getting Started

Tools you need

To write a C++ app for the 3DS, you need an SDK called devkitPro. Once you have installed devkitPro, you will need an IDE. I personally use CLion, but other IDEs like Visual Studio work well too.

You will also need a C/C++ compiler. CMake is what I use, but GCC is good as well. This “tutorial” will use CMake.

Once these are installed, you can move onto the next step.

Setting up CMake

CMake uses a CMakeLists.txt for toolchains1. I have made a CMakeLists.txt file for this project, but it is targeted at Linux.

cmake_minimum_required(VERSION 3.10)
project(Learning3DS CXX)

# Use devkitARM compiler
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR arm)

set(DEVKITPRO "/opt/devkitpro" CACHE PATH "")
set(DEVKITARM "${DEVKITPRO}/devkitARM" CACHE PATH "")

set(CMAKE_C_COMPILER   "${DEVKITARM}/bin/arm-none-eabi-gcc")
set(CMAKE_CXX_COMPILER "${DEVKITARM}/bin/arm-none-eabi-g++")
set(CMAKE_ASM_COMPILER "${DEVKITARM}/bin/arm-none-eabi-gcc")

# 3DS specific flags
set(COMMON_FLAGS "-march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COMMON_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COMMON_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -specs=3dsx.specs ${COMMON_FLAGS}")

message(STATUS "DEVKITPRO = ${DEVKITPRO}")

# Include directories for headers
include_directories(
        ${DEVKITPRO}/libctru/include
)

# Add executable (ELF)
add_executable(Learning3DS.elf src/main.cpp)


# Link libraries
link_directories(
        ${DEVKITPRO}/libctru/lib
        ${DEVKITPRO}/portlibs/3ds/lib
)

target_link_libraries(Learning3DS.elf
        ${DEVKITPRO}/libctru/lib/libcitro2d.a
        ${DEVKITPRO}/libctru/lib/libcitro3d.a
        ${DEVKITPRO}/libctru/lib/libctru.a
        m
)

# Post-build command to convert ELF to 3DSX
set(3DSXTOOL "${DEVKITPRO}/tools/bin/3dsxtool")
set(SMDHTOOL "${DEVKITPRO}/tools/bin/smdhtool")

# Convert ELF to 3DSX and embed SMDH
add_custom_command(TARGET Learning3DS.elf POST_BUILD
        COMMAND ${SMDHTOOL} --create "Learning3DS" "This is the description" "Your name here" "${DEVKITPRO}/libctru/default_icon.png" ${CMAKE_CURRENT_BINARY_DIR}/Learning3DS.smdh
        COMMAND ${3DSXTOOL} $<TARGET_FILE:Learning3DS.elf> ${CMAKE_CURRENT_BINARY_DIR}/Learning3DS.3dsx --smdh=${CMAKE_CURRENT_BINARY_DIR}/Learning3DS.smdh --romfs=${CMAKE_CURRENT_SOURCE_DIR}/romfs
        COMMENT "Generating SMDH and converting ELF to 3DSX"
)

You should also have a 3ds-toolchain.cmake file. This is your toolchain.

# 3ds-toolchain.cmake
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR arm)

set(DEVKITPRO /opt/devkitpro)
set(DEVKITARM ${DEVKITPRO}/devkitARM)

set(CMAKE_C_COMPILER ${DEVKITARM}/bin/arm-none-eabi-gcc)
set(CMAKE_CXX_COMPILER ${DEVKITARM}/bin/arm-none-eabi-g++)
set(CMAKE_ASM_COMPILER ${DEVKITARM}/bin/arm-none-eabi-as)

set(CMAKE_FIND_ROOT_PATH ${DEVKITPRO})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

Printing

Your file structure should look like the following:

All your C++ source code should live in src, while other files for your project (images, audio, etc) live in romfs.

Create a main.cpp file in /src.

Imports in C++

To import a C/C++ header, we use #include. We need to include the devkitPro 3DS header, so we use #include<3ds.h>. We also need cstudio and cstdlib.
Your includes should look like the following:

#include <3ds.h>
#include <cstdio>   // for printf
#include <cstdlib>  // for exit, etc.

Next, we have to open our main method. To do this, we do the following:

#include <3ds.h>
#include <cstdio>   // for printf
#include <cstdlib>  // for exit, etc.

int main() {

}

The main method is where all our code runs. We need to init the screen/graphics, so we use gfxInitDefault();

Your file should look like this now:

#include <3ds.h>
#include <cstudio>
#include <cstdlib>

int main() {
    gfxInitDefault();
}

We also need to init the console, using consoleInit(GFX_Top, nullptr);. This starts the console on the top screen.

Now we need to actually print using printf("Hello, World!");
Your file should look like the following:

#include <3ds.h>
#include <cstdio>   // for printf
#include <cstdlib>  // for exit, etc.

int main() {
    // Initialize graphics on top/bottom screens
    gfxInitDefault();
    consoleInit(GFX_TOP, nullptr); // console output on top screen

    printf("why is kiki so fat\n");
}

Next, we need our main loop. This waits for us to press START to return us to the Homebrew Launcher.
This is quite easy.

while (aptMainLoop()) {
        gspWaitForVBlank();
        gfxSwapBuffers();
        hidScanInput();

        // Exit if START is pressed
        u32 kDown = hidKeysDown();
        if (kDown & KEY_START) break;
    }

while runs the code in the loop while the statement in the brackets is true.

gspWaitForVBlank(); waits for the next VBlank.

gfxSwapBuffers(); swaps the buffers. It’s a good idea to use gfxFlushBuffers(); with this as well.
hidScanInput(); scans input from the buttons on the console. We use this in conjunction with u32 kDown = hidKeysDown();.
if (kDown & KEY_START) break; checks if the START button is pressed. If it is, end the process and return to the Homebrew launcher.

    gfxExit();
    return 0;

This is needed at the end of the while loop to close the graphics and return with exit code 0 (no errors).

Conclusion

I hope this helped you write your first 3DS Homebrew app! I will post more guides on how to do this shortly, including drawing a square, drawing a cube, roatating said cube and making a basic game.

  1. https://cmake.org/cmake/help/book/mastering-cmake/chapter/Writing%20CMakeLists%20Files.html ↩︎

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.