Skip to content

Instantly share code, notes, and snippets.

@NAEL2XD
Last active May 20, 2025 18:30
Show Gist options
  • Select an option

  • Save NAEL2XD/be1593607fe8de6f59d7e9a138aedaac to your computer and use it in GitHub Desktop.

Select an option

Save NAEL2XD/be1593607fe8de6f59d7e9a138aedaac to your computer and use it in GitHub Desktop.
3DS: Cool Utils!
#include <3ds.h>
#include <citro2d.h>
// Prework those things.
// Text Functions
C2D_Text renderText;
C2D_Font defaultFont;
C2D_TextBuf g_staticBuf;
u32 borderColor;
u32 textColor;
C3D_RenderTarget* pos[2];
// Custom Vars
u64 oldTime = 0;
touchPosition touch;
bool initialized = false;
// If the x variable is -1, then it will be set to middle.
float centerX(float xCheck, C2D_Text text, float size) {
return (int)xCheck == -1 ? (-(text.width * size) / 2) + 200 : xCheck;
}
void UTILS_Init() {
if (initialized) return;
// Initializes Citro2D
C2D_Init(C2D_DEFAULT_MAX_OBJECTS);
C3D_Init(C3D_DEFAULT_CMDBUF_SIZE);
C2D_Prepare();
pos[0] = C2D_CreateScreenTarget(GFX_TOP, GFX_LEFT);
pos[1] = C2D_CreateScreenTarget(GFX_BOTTOM, GFX_LEFT);
// TEXT
defaultFont = C2D_FontLoadSystem(CFG_REGION_USA);
g_staticBuf = C2D_TextBufNew(4096);
borderColor = C2D_Color32(0, 0, 0, 170);
textColor = C2D_Color32(255, 255, 255, 255);
// Initializes stuff
gfxInitDefault();
romfsInit();
cfguInit();
newsInit();
// TIME
oldTime = osGetTime();
initialized = true;
}
void UTILS_Exit() {
if (!initialized) return;
C2D_Fini();
C3D_Fini();
newsExit();
cfguExit();
romfsExit();
initialized = false;
}
bool UTILS_SCREEN_renderBothScreenUsingFuncs(bool (*func1)(), bool (*func2)()) {
bool exit;
C3D_FrameBegin(C3D_FRAME_SYNCDRAW);
for (int i = 0; i < 2; i++) {
C2D_TargetClear(pos[i], C2D_Color32(60, 60, 60, 0xFF));
C2D_SceneBegin(pos[i]);
exit = i == 0 ? func1() : func2();
if (exit) break;
}
C3D_FrameEnd(0);
return exit;
}
void UTILS_TEXT_renderBorderText(const char *text, float x, float y, float borderSize, float size, C2D_Font font) {
// Clear and Parse to make text work.
C2D_TextBufClear(g_staticBuf);
C2D_TextFontParse(&renderText, font ? font : defaultFont, g_staticBuf, text);
C2D_TextOptimize(&renderText);
// Center X
x = centerX(x, renderText, size);
// Do the trick.
int pos[8][2] = {{-1, -1}, {1, -1}, {-1, 1}, {1, 1}, {1, 0}, {-1, 0}, {0, 1}, {0, -1}};
for (int i = 0; i < 8; i++) {
float finalMult = borderSize * size;
C2D_DrawText(&renderText, C2D_WithColor, x + (pos[i][0] * finalMult), y + (pos[i][1] * finalMult), 0.5f, size, size, borderColor);
}
C2D_DrawText(&renderText, C2D_WithColor, x, y, 0.5f, size, size, textColor);
}
void UTILS_TEXT_quickRenderText(const char *text, float x, float y, u32 col, float size, C2D_Font font) {
// Clear and Parse to make text work.
C2D_TextBufClear(g_staticBuf);
C2D_TextFontParse(&renderText, font ? font : defaultFont, g_staticBuf, text);
C2D_TextOptimize(&renderText);
// Center X
x = centerX(x, renderText, size);
// Check col
if (col == 0) col = C2D_Color32(255, 255, 255, 255);
// Draw!
C2D_DrawText(&renderText, C2D_WithColor, x, y, 0.5f, size, size, col);
}
u64 UTILS_OS_getRunningTime() {
return osGetTime() - oldTime;
}
C2D_Image UTILS_IMAGE_loadImage(char *file) {
C2D_SpriteSheet spriteSheet = C2D_SpriteSheetLoad(file);
if (!spriteSheet) {
printf("Failed to load sprite sheet: %s\n", file);
return (C2D_Image){0}; // Return an empty image on failure
}
return C2D_SpriteSheetGetImage(spriteSheet, 0);
}
double UTILS_MATH_angleToRadians(double angle) {
return angle * (M_PI / 180.0);
}
bool UTILS_BOOL_isTouchingImage(C2D_Image img, float x, float y, float size) {
hidTouchRead(&touch);
return (x < touch.px && x + img.tex->width > touch.px && y < touch.py && y + img.tex->height > touch.py);
}
bool UTILS_BOOL_isTouchingHitbox(float x, float y, float width, float height) {
hidTouchRead(&touch);
return (x < touch.px && x + width > touch.px && y < touch.py && y + height > touch.py);
}
char* UTILS_STRING_swkbdGetInputText() { // Return char pointer
static char text[512];
SwkbdState swkbd;
swkbdInit(&swkbd, SWKBD_TYPE_NORMAL, 1, -1);
swkbdSetFeatures(&swkbd, SWKBD_MULTILINE | SWKBD_DARKEN_TOP_SCREEN | SWKBD_FIXED_WIDTH);
swkbdSetHintText(&swkbd, "Enter Text Here.");
swkbdSetInitialText(&swkbd, text);
swkbdInputText(&swkbd, text, sizeof(text));
return text;
}
void UTILS_OS_popupError(const char* text) {
errorConf errorCtx;
errorInit(&errorCtx, ERROR_TEXT, CFG_LANGUAGE_EN);
errorText(&errorCtx, text);
errorCtx.homeButton = false;
errorDisp(&errorCtx);
}
void UTILS_OS_sendNotification(const char* titleText, const char* descText) {
// Convert strings to UTF-16
u16 title[128] = {0};
u16 message[1024] = {0};
// Convert ASCII to UTF-16
for (size_t i = 0; i < strlen(titleText); i++) title[i] = titleText[i];
for (size_t i = 0; i < strlen(descText); i++) message[i] = descText[i];
// Create notification
NEWS_AddNotification(title, strlen(titleText) + 2, message, strlen(descText) + 2, NULL, 0, false);
}
const char* UTILS_IO_getContentFromFile(const char* filePath) {
static char content[8192] = {0}; // Static buffer persists after return
FILE* f = fopen(filePath, "r");
if (!f) {
printf("File read failed: %s\n", filePath);
content[0] = '\0'; // Ensure empty string
return content;
}
size_t bytes_read = fread(content, 1, sizeof(content)-1, f);
content[bytes_read] = '\0'; // Null-terminate
fclose(f);
return content;
}
#ifndef _utils_h_
#define _utils_h_
#include <3ds.h>
#include <citro2d.h>
/**
Initializes `UTILS`, giving you useful functions.
**/
void UTILS_Init();
/**
Exits services being `Citro2D`, `Citro3D`, `news`, `cfgu` and `romfs`.
*/
void UTILS_Exit();
/**
@brief Render both screens with both function.
@param func1 1st function to render top screen.
@param func2 2nd function to render bottom screen.
@return A boolean variable that would be true if you want to exit or false to keep continuing.
*/
bool UTILS_SCREEN_renderBothScreenUsingFuncs(bool (*func1)(), bool (*func2)());
/**
Renders a bordered text quickly, no `C2D_Text` required
@param text String text to write as.
@param x The x position to draw in. If `x` is set to `-1` then it will make the text go in middle.
@param y The y position to draw in.
@param borderSize Size for the border in pixels to draw.
@param size Size for the text to draw.
`NOTE` that `borderSize` will be multiplied by `size`!
@param font *(Optional)* Font to use, can be NULL for default nintendo font.
**/
void UTILS_TEXT_renderBorderText(const char *text, float x, float y, float borderSize, float size, C2D_Font font);
/**
Quickly renders a bordered text without having to use any special variables.
@param text String text to write as.
@param x The x position to draw in. If `x` is set to `-1` then it will make the text go in middle.
@param y The y position to draw in.
@param col u32 color to set as. Optional, can be `NULL` (will be white).
@param size Size for the text to draw. NOTE that `borderSize` will be multiplied by `size`!
@param font *(Optional)* Font to use, can be NULL for default nintendo font.
**/
void UTILS_TEXT_quickRenderText(const char *text, float x, float y, u32 col, float size, C2D_Font font);
/**
@brief Gets the current time the application has ran.
@return A u64 (float) variable by how many milliseconds it has been ran.
**/
u64 UTILS_OS_getRunningTime();
/**
Loads an image without having to use special properties (like `C2D_SpriteSheet`)
### ~~You will need to initialize ROMFS using `romfsInit()` next to `gfxInitDefault()`!!~~
Edit (20/25/2025): You don't need this anymore because the UTILS does it for you.
@param file The file name to load in romfs, make sure the image is a .t3x! Example: `romfs:/image.t3x`
@return The image variable stored in there.
**/
C2D_Image UTILS_IMAGE_loadImage(char *file);
/**
@brief Returns converted angle to radians. Also the same as [this](https://www.rapidtables.com/convert/number/degrees-to-radians.html).
@param angle The angle to convert to radians.
@return The converted radian.
**/
double UTILS_MATH_angleToRadians(double angle);
/**
@brief Returns whatever if it's touching the image or not in the bottom screen.
@param img The image to use and check if it's touching.
@param x The X Position of the image.
@param y The Y Position of the image.
@param size The Size of the image.
@return `true` if it collides and is touching the image, `false` otherwise.
**/
bool UTILS_BOOL_isTouchingImage(C2D_Image img, float x, float y, float size);
/**
@brief Same as `UTILS_BOOL_isTouchingImage` but it's a hitbox instead.
Returns whatever if it's touching the hitbox or not in the bottom screen.
@param x The X Position of the hitbox.
@param y The Y Position of the hitbox.
@param width The Width of the hitbox.
@param height The Height of the hitbox.
@return `true` if it collides and is touching the hitbox, `false` otherwise.
**/
bool UTILS_BOOL_isTouchingHitbox(float x, float y, float width, float height);
/**
@brief Reads the input text from user and returns the char result.
@return Char String with text inputted to user.
**/
char* UTILS_STRING_swkbdGetInputText();
/**
@brief Displays a blocking error popup with custom text
#### Features:
- Disables home button while active
- Uses system error dialog style
- Blocks execution until dismissed
- Auto-wraps long text
@param text Error message to display (ASCII). Supports newlines with '\n'.
Max ~2000 chars (libctru limit). UTF-8 not supported.
@note Requires error service (automatically initialized)
@warning Avoid special characters - use basic ASCII only
*/
void UTILS_OS_popupError(const char* text);
/**
@brief Sends a notification to HOME Menu
#### Features:
- Shows in Notifications (blue LED blinks)
- Appears under "Nintendo 3DS" sender
- No icon/image attached
- Automatically encodes to UTF-16
@param titleText Notification title (ASCII, max 128 chars)
@param descText Notification body (ASCII, max 1024 chars)
@note Requires news service (auto-init/shutdown)
@warning Title truncates at 128 chars, description at 1024
*/
void UTILS_OS_sendNotification(const char* titleText, const char* descText);
/**
@brief Reads file contents into a static buffer
@param filePath Path to file (e.g. "romfs:/text.txt")
@return const char* - Pointer to null-terminated file contents.
WARNING: Buffer is reused between calls! Copy immediately.
Returns empty string "" on failure.
@note Security Considerations:
- Buffer size fixed at 8191 bytes + null terminator
- Subsequent calls overwrite previous content
- Not thread-safe
- Limited to ASCII/text files
@warning MAX FILE SIZE: 8191 bytes. Larger files will be truncated.
Returned pointer becomes invalid on next function call!
*/
const char* UTILS_IO_getContentFromFile(const char* filePath);
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment