Compare commits

..

2 commits

Author SHA1 Message Date
Mauricio Garrido
3b7dafb117 3ds: 3ds port sources. Second set of two.
This commit adds new files written exclusively for the 3ds port.

Additional comments:

1. Plugins works, but will be enabled in future commits.
2. The port has only been tested on the New 3DS.
3. Not all features of rockbox have been tested so there may be bugs or non-functional features.
4. There is a known issue where a random crash can occur when exiting the app.

Change-Id: I122d0bea9aa604e04fca45ba8287cf79e6110769
2025-10-23 20:09:12 -04:00
Mauricio Garrido
a4de1195cd 3ds: 3ds port sources. First set of two
This commit adds changes to the original rockbox sources.

Note: the port files, functions, folders, etc., will be referred
to as 'ctru' to avoid using the Nintendo name elsewhere.

Change-Id: I0e2d3d4d2a75bd45ea67dc3452eb8d5487cf1f5a
2025-10-23 20:09:09 -04:00
76 changed files with 7549 additions and 23 deletions

View file

@ -305,4 +305,6 @@ keymaps/keymap-echor1.c
keymaps/keymap-surfansf28.c
#elif CONFIG_KEYPAD == RG_NANO_PAD
keymaps/keymap-rgnano.c
#elif CONFIG_KEYPAD == CTRU_PAD
keymaps/keymap-ctru.c
#endif

302
apps/keymaps/keymap-ctru.c Normal file
View file

@ -0,0 +1,302 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id: keymap-ctru.c 28704 2025-07-09 11:28:53Z gama $
*
* Copyright (C) 2025 Mauricio Garrido
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
/* Button Code Definitions for Civic Type R (ctru) target */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "config.h"
#include "action.h"
#include "button.h"
#include "settings.h"
/*
* The format of the list is as follows
* { Action Code, Button code, Prereq button code }
* if there's no need to check the previous button's value, use BUTTON_NONE
* Insert LAST_ITEM_IN_LIST at the end of each mapping
*/
static const struct button_mapping button_context_standard[] = {
{ ACTION_STD_PREV, BUTTON_UP, BUTTON_NONE },
{ ACTION_STD_PREVREPEAT, BUTTON_UP|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_STD_NEXT, BUTTON_DOWN, BUTTON_NONE },
{ ACTION_STD_NEXTREPEAT, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_STD_CANCEL, BUTTON_LEFT, BUTTON_NONE },
{ ACTION_STD_CANCEL, BUTTON_BACK|BUTTON_REL, BUTTON_BACK },
{ ACTION_STD_OK, BUTTON_SELECT|BUTTON_REL, BUTTON_SELECT },
{ ACTION_STD_OK, BUTTON_RIGHT, BUTTON_NONE },
{ ACTION_STD_QUICKSCREEN, BUTTON_MENU|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_STD_CONTEXT, BUTTON_SELECT|BUTTON_REPEAT, BUTTON_SELECT },
{ ACTION_STD_MENU, BUTTON_MENU|BUTTON_REL, BUTTON_MENU },
{ ACTION_STD_CONTEXT, BUTTON_MENU|BUTTON_REL, BUTTON_NONE },
{ ACTION_STD_KEYLOCK, BUTTON_USER|BUTTON_POWER, BUTTON_NONE },
LAST_ITEM_IN_LIST
}; /* button_context_standard */
static const struct button_mapping button_context_mainmenu[] = {
{ ACTION_TREE_WPS, BUTTON_MENU|BUTTON_REL, BUTTON_MENU },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_TREE),
}; /* button_context_mainmenu */
static const struct button_mapping button_context_wps[] = {
{ ACTION_WPS_PLAY, BUTTON_SELECT|BUTTON_REL, BUTTON_SELECT },
{ ACTION_WPS_STOP, BUTTON_POWER|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_WPS_BROWSE, BUTTON_BACK|BUTTON_REL, BUTTON_BACK },
{ ACTION_WPS_MENU, BUTTON_MENU|BUTTON_REL, BUTTON_MENU },
{ ACTION_WPS_HOTKEY, BUTTON_USER|BUTTON_REL, BUTTON_USER },
{ ACTION_WPS_PITCHSCREEN, BUTTON_USER|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_STD_KEYLOCK, BUTTON_USER|BUTTON_POWER, BUTTON_NONE },
{ ACTION_WPS_CONTEXT, BUTTON_SELECT|BUTTON_REPEAT, BUTTON_SELECT },
{ ACTION_WPS_QUICKSCREEN, BUTTON_MENU|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_WPS_SKIPPREV, BUTTON_LEFT|BUTTON_REL, BUTTON_LEFT },
{ ACTION_WPS_SEEKBACK, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_WPS_STOPSEEK, BUTTON_LEFT|BUTTON_REL, BUTTON_LEFT|BUTTON_REPEAT },
{ ACTION_WPS_SKIPNEXT, BUTTON_RIGHT|BUTTON_REL, BUTTON_RIGHT },
{ ACTION_WPS_SEEKFWD, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_WPS_STOPSEEK, BUTTON_RIGHT|BUTTON_REL, BUTTON_RIGHT|BUTTON_REPEAT },
{ ACTION_WPS_VOLUP, BUTTON_UP|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_WPS_VOLUP, BUTTON_UP, BUTTON_NONE },
{ ACTION_WPS_VOLDOWN, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_WPS_VOLDOWN, BUTTON_DOWN, BUTTON_NONE },
LAST_ITEM_IN_LIST
}; /* button_context_wps */
static const struct button_mapping button_context_list[] = {
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
}; /* button_context_list */
static const struct button_mapping button_context_tree[] = {
{ ACTION_TREE_WPS, BUTTON_USER|BUTTON_REPEAT, BUTTON_USER },
{ ACTION_TREE_STOP, BUTTON_POWER|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_TREE_HOTKEY, BUTTON_USER|BUTTON_REL, BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_LIST)
}; /* button_context_tree */
static const struct button_mapping button_context_settings[] = {
{ ACTION_SETTINGS_INC, BUTTON_UP, BUTTON_NONE },
{ ACTION_SETTINGS_INCREPEAT, BUTTON_UP|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_SETTINGS_DEC, BUTTON_DOWN, BUTTON_NONE },
{ ACTION_SETTINGS_DECREPEAT, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_STD_PREV, BUTTON_LEFT, BUTTON_NONE },
{ ACTION_STD_PREVREPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_STD_NEXT, BUTTON_RIGHT, BUTTON_NONE },
{ ACTION_STD_NEXTREPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_TREE)
}; /* button_context_settings */
static const struct button_mapping button_context_settings_right_is_inc[] = {
{ ACTION_SETTINGS_INC, BUTTON_RIGHT, BUTTON_NONE },
{ ACTION_SETTINGS_INCREPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_SETTINGS_DEC, BUTTON_LEFT, BUTTON_NONE },
{ ACTION_SETTINGS_DECREPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
}; /* button_context_settings_right_is_inc */
static const struct button_mapping button_context_yesno[] = {
{ ACTION_YESNO_ACCEPT, BUTTON_SELECT, BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
}; /* button_context_settings_yesno */
static const struct button_mapping button_context_colorchooser[] = { //check
{ ACTION_STD_OK, BUTTON_SELECT|BUTTON_REL, BUTTON_SELECT },
{ ACTION_STD_CANCEL, BUTTON_BACK, BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_CUSTOM|CONTEXT_SETTINGS),
}; /* button_context_colorchooser */
static const struct button_mapping button_context_eq[] = {
{ ACTION_STD_CANCEL, BUTTON_MENU|BUTTON_REL, BUTTON_MENU },
{ ACTION_SETTINGS_INC, BUTTON_RIGHT , BUTTON_NONE },
{ ACTION_SETTINGS_INCREPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_SETTINGS_DEC, BUTTON_LEFT , BUTTON_NONE },
{ ACTION_SETTINGS_DECREPEAT, BUTTON_LEFT|BUTTON_REPEAT , BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_CUSTOM|CONTEXT_SETTINGS),
}; /* button_context_eq */
/** Bookmark Screen **/
static const struct button_mapping button_context_bmark[] = {
{ ACTION_BMS_DELETE, BUTTON_POWER, BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_LIST),
}; /* button_context_bmark */
static const struct button_mapping button_context_time[] = {
{ ACTION_SETTINGS_INC, BUTTON_UP, BUTTON_NONE },
{ ACTION_SETTINGS_INCREPEAT, BUTTON_UP|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_SETTINGS_DEC, BUTTON_DOWN, BUTTON_NONE },
{ ACTION_SETTINGS_DECREPEAT, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_STD_PREV, BUTTON_LEFT, BUTTON_NONE },
{ ACTION_STD_PREVREPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_STD_NEXT, BUTTON_RIGHT, BUTTON_NONE },
{ ACTION_STD_NEXTREPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD),
}; /* button_context_time */
static const struct button_mapping button_context_quickscreen[] = {
{ ACTION_QS_TOP, BUTTON_UP, BUTTON_NONE },
{ ACTION_QS_TOP, BUTTON_UP|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_QS_DOWN, BUTTON_DOWN, BUTTON_NONE },
{ ACTION_QS_DOWN, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_QS_LEFT, BUTTON_LEFT, BUTTON_NONE },
{ ACTION_QS_LEFT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_QS_RIGHT, BUTTON_RIGHT, BUTTON_NONE },
{ ACTION_QS_RIGHT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_STD_CANCEL, BUTTON_MENU, BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
}; /* button_context_quickscreen */
static const struct button_mapping button_context_pitchscreen[] = {
{ ACTION_PS_INC_SMALL, BUTTON_UP, BUTTON_NONE },
{ ACTION_PS_INC_BIG, BUTTON_UP|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_DEC_SMALL, BUTTON_DOWN, BUTTON_NONE },
{ ACTION_PS_DEC_BIG, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_SLOWER, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_FASTER, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_NUDGE_LEFT, BUTTON_LEFT, BUTTON_NONE },
{ ACTION_PS_NUDGE_LEFTOFF, BUTTON_LEFT|BUTTON_REL, BUTTON_NONE },
{ ACTION_PS_NUDGE_RIGHT, BUTTON_RIGHT, BUTTON_NONE },
{ ACTION_PS_NUDGE_RIGHTOFF, BUTTON_RIGHT|BUTTON_REL, BUTTON_NONE },
{ ACTION_PS_RESET, BUTTON_SELECT, BUTTON_NONE },
{ ACTION_PS_TOGGLE_MODE, BUTTON_USER, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_MENU|BUTTON_REL, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_BACK|BUTTON_REL, BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
}; /* button_context_pitchcreen */
static const struct button_mapping button_context_keyboard[] = {
{ ACTION_KBD_UP, BUTTON_UP, BUTTON_NONE },
{ ACTION_KBD_UP, BUTTON_UP|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_KBD_DOWN, BUTTON_DOWN, BUTTON_NONE },
{ ACTION_KBD_DOWN, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_KBD_LEFT, BUTTON_LEFT, BUTTON_NONE },
{ ACTION_KBD_LEFT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_KBD_RIGHT, BUTTON_RIGHT, BUTTON_NONE },
{ ACTION_KBD_RIGHT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_KBD_SELECT, BUTTON_SELECT, BUTTON_NONE },
{ ACTION_KBD_ABORT, BUTTON_BACK|BUTTON_REL, BUTTON_BACK },
{ ACTION_KBD_DONE, BUTTON_MENU|BUTTON_REL, BUTTON_MENU },
{ ACTION_KBD_BACKSPACE, BUTTON_USER, BUTTON_NONE },
{ ACTION_KBD_PAGE_FLIP, BUTTON_POWER, BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
}; /* button_context_keyboard */
static const struct button_mapping button_context_radio[] = {
{ ACTION_FM_MENU, BUTTON_SELECT | BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_FM_PRESET, BUTTON_MENU | BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_FM_STOP, BUTTON_POWER | BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_FM_MODE, BUTTON_MENU | BUTTON_REL, BUTTON_MENU },
{ ACTION_FM_EXIT, BUTTON_BACK | BUTTON_REL, BUTTON_BACK },
{ ACTION_FM_PLAY, BUTTON_SELECT | BUTTON_REL, BUTTON_SELECT },
{ ACTION_FM_NEXT_PRESET, BUTTON_USER | BUTTON_RIGHT, BUTTON_NONE },
{ ACTION_FM_PREV_PRESET, BUTTON_USER | BUTTON_LEFT, BUTTON_NONE },
/* Volume */
{ ACTION_SETTINGS_INC, BUTTON_UP | BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_SETTINGS_INCREPEAT, BUTTON_UP, BUTTON_NONE },
{ ACTION_SETTINGS_DEC, BUTTON_DOWN | BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_SETTINGS_DECREPEAT, BUTTON_DOWN, BUTTON_NONE },
/* Tuning */
{ ACTION_STD_PREV, BUTTON_LEFT, BUTTON_NONE },
{ ACTION_STD_PREVREPEAT, BUTTON_LEFT | BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_STD_NEXT, BUTTON_RIGHT, BUTTON_NONE },
{ ACTION_STD_NEXTREPEAT, BUTTON_RIGHT | BUTTON_REPEAT, BUTTON_NONE },
}; /* button_context_radio */
const struct button_mapping* target_get_context_mapping(int context)
{
switch (context & ~CONTEXT_LOCKED)
{
case CONTEXT_STD:
return button_context_standard;
case CONTEXT_WPS:
return button_context_wps;
case CONTEXT_LIST:
return button_context_list;
case CONTEXT_MAINMENU:
return button_context_mainmenu;
case CONTEXT_TREE:
return button_context_tree;
case CONTEXT_SETTINGS:
return button_context_settings;
case CONTEXT_CUSTOM|CONTEXT_SETTINGS:
case CONTEXT_SETTINGS_RECTRIGGER:
return button_context_settings_right_is_inc;
case CONTEXT_SETTINGS_COLOURCHOOSER:
return button_context_colorchooser;
case CONTEXT_SETTINGS_EQ:
return button_context_eq;
case CONTEXT_SETTINGS_TIME:
return button_context_time;
case CONTEXT_YESNOSCREEN:
return button_context_yesno;
case CONTEXT_FM:
return button_context_radio;
case CONTEXT_BOOKMARKSCREEN:
return button_context_bmark;
case CONTEXT_QUICKSCREEN:
return button_context_quickscreen;
case CONTEXT_PITCHSCREEN:
return button_context_pitchscreen;
case CONTEXT_KEYBOARD:
return button_context_keyboard;
}
return button_context_standard;
}

View file

@ -565,7 +565,11 @@ void resume_directory(const char *dir)
/* Returns the current working directory and also writes cwd to buf if
non-NULL. In case of error, returns NULL. */
#ifdef CTRU
char *__wrap_getcwd(char *buf, getcwd_size_t size)
#else
char *getcwd(char *buf, getcwd_size_t size)
#endif
{
if (!buf)
return tc.currdir;

View file

@ -128,7 +128,12 @@ void tree_unlock_cache(struct tree_context *t);
#else
#define getcwd_size_t size_t
#endif
#ifdef CTRU
/* devkitarm already defines getcwd */
char *__wrap_getcwd(char *buf, getcwd_size_t size);
#else
char *getcwd(char *buf, getcwd_size_t size);
#endif
void reload_directory(void);
bool check_rockboxdir(void);
struct tree_context* tree_get_context(void);

View file

@ -25,7 +25,7 @@ target/hosted/rtc.c
#if (CONFIG_PLATFORM & PLATFORM_ANDROID) == 0 && \
!defined(DX50) && !defined(DX90) && \
(defined(DEBUG) || defined(SIMULATOR)) /* sim should define DEBUG instead */
(defined(DEBUG) || defined(SIMULATOR) || defined(CTRU)) /* sim should define DEBUG instead */
target/hosted/debug-hosted.c
#endif
@ -94,12 +94,14 @@ target/hosted/sdl/app/button-application.c
#ifdef WIN32
target/hosted/filesystem-win32.c
#else /* !WIN32 */
#ifndef CTRU
target/hosted/filesystem-unix.c
#endif /* CTRU */
#endif /* WIN32 */
#endif /* APPLICATION */
#endif /* HAVE_SDL */
#ifdef APPLICATION
#if defined(APPLICATION) && !defined(CTRU)
target/hosted/filesystem-app.c
#endif /* APPLICATION */
@ -581,6 +583,8 @@ target/hosted/maemo/pcm-gstreamer.c
target/hosted/sdl/pcm-sdl.c
#endif /* (CONFIG_PLATFORM & PLATFORM_MAEMO) */
#elif defined(CTRU)
drivers/audio/ctru.c
#endif
#endif /* (CONFIG_PLATFORM & PLATFORM_NATIVE) */
@ -2071,6 +2075,28 @@ target/hosted/ibasso/dx90/button-dx90.c
#endif
#endif
#if (CONFIG_PLATFORM & PLATFORM_CTRU)
asm/arm/lcd-as-memframe.S
target/hosted/ctru/backlight-ctru.c
target/hosted/ctru/button-ctru.c
target/hosted/ctru/kernel-ctru.c
target/hosted/ctru/thread-ctru.c
target/hosted/ctru/lcd-bitmap.c
target/hosted/ctru/luminance-ctru.c
target/hosted/ctru/system-ctru.c
target/hosted/ctru/filesystem-ctru.c
target/hosted/ctru/lc-ctru.c
target/hosted/ctru/lc-program-resolver.c
target/hosted/ctru/powermgmt-ctru.c
target/hosted/ctru/timer-ctru.c
target/hosted/ctru/pcm-ctru.c
target/hosted/ctru/lib/sys_file.c
target/hosted/ctru/lib/sys_dir.c
target/hosted/ctru/lib/sys_thread.c
target/hosted/ctru/lib/sys_timer.c
target/hosted/ctru/lib/bfile/bfile.c
#endif
#else /* defined(SIMULATOR) */
#ifdef WIN32
@ -2119,9 +2145,9 @@ kernel/queue.c
#ifdef HAVE_SEMAPHORE_OBJECTS
kernel/semaphore.c
#endif
#if defined(HAVE_SDL_THREADS)
#ifdef HAVE_SDL_THREADS
target/hosted/sdl/thread-sdl.c
#else
#elif !defined(CTRU)
kernel/thread.c
#endif
kernel/thread-common.c

View file

@ -0,0 +1,92 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright © 2010 Thomas Martitz
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include "config.h"
#include "sound.h"
#include "pcm_sampr.h"
#ifdef HAVE_SW_VOLUME_CONTROL
#include "pcm_sw_volume.h"
#include "fixedpoint.h"
#endif
/**
* Audio Hardware api. Make some of them do nothing as we cannot properly
* simulate with SDL. if we used DSP we would run code that doesn't actually
* run on the target
**/
void audiohw_set_volume(int vol_l, int vol_r)
{
(void)vol_l; (void)vol_r;
}
#if defined(AUDIOHW_HAVE_BALANCE)
void audiohw_set_balance(int value) { (void)value; }
#endif
#ifndef HAVE_SW_TONE_CONTROLS
#if defined(AUDIOHW_HAVE_BASS)
void audiohw_set_bass(int value) { (void)value; }
#endif
#if defined(AUDIOHW_HAVE_TREBLE)
void audiohw_set_treble(int value) { (void)value; }
#endif
#endif /* HAVE_SW_TONE_CONTROLS */
#if defined(AUDIOHW_HAVE_BASS_CUTOFF)
void audiohw_set_bass_cutoff(int value) { (void)value; }
#endif
#if defined(AUDIOHW_HAVE_TREBLE_CUTOFF)
void audiohw_set_treble_cutoff(int value){ (void)value; }
#endif
/* EQ-based tone controls */
#if defined(AUDIOHW_HAVE_EQ)
void audiohw_set_eq_band_gain(unsigned int band, int value)
{ (void)band; (void)value; }
#endif
#if defined(AUDIOHW_HAVE_EQ_FREQUENCY)
void audiohw_set_eq_band_frequency(unsigned int band, int value)
{ (void)band; (void)value; }
#endif
#if defined(AUDIOHW_HAVE_EQ_WIDTH)
void audiohw_set_eq_band_width(unsigned int band, int value)
{ (void)band; (void)value; }
#endif
#if defined(AUDIOHW_HAVE_DEPTH_3D)
void audiohw_set_depth_3d(int value)
{ (void)value; }
#endif
#if defined(AUDIOHW_HAVE_LINEOUT)
void audiohw_set_lineout_volume(int vol_l, int vol_r)
{ (void)vol_l; (void)vol_r; }
#endif
#if defined(AUDIOHW_HAVE_FILTER_ROLL_OFF)
void audiohw_set_filter_roll_off(int value)
{ (void)value; }
#endif
#if defined(AUDIOHW_HAVE_POWER_MODE)
void audiohw_set_power_mode(int value)
{ (void)value; }
#endif
#ifdef CONFIG_SAMPR_TYPES
unsigned int pcm_sampr_to_hw_sampr(unsigned int samplerate,
unsigned int type)
{ return samplerate; (void)type; }
#endif /* CONFIG_SAMPR_TYPES */

View file

@ -224,7 +224,7 @@ struct sound_settings_info
#elif defined(HAVE_ES9218)
#include "es9218.h"
#elif ((CONFIG_PLATFORM & (PLATFORM_ANDROID | PLATFORM_MAEMO \
| PLATFORM_PANDORA | PLATFORM_SDL )) | defined(RG_NANO))
| PLATFORM_PANDORA | PLATFORM_SDL | PLATFORM_CTRU)) | defined(RG_NANO))
#include "hosted_codec.h"
#elif defined(DX50)
#include "codec-dx50.h"

View file

@ -85,6 +85,7 @@
#define RK27XX 2700
#define X1000 1000
#define STM32H743 32743
#define N10480H 10480
/* platforms
* bit fields to allow PLATFORM_HOSTED to be OR'ed e.g. with a
@ -98,6 +99,7 @@
#define PLATFORM_MAEMO5 (1<<5)
#define PLATFORM_MAEMO (PLATFORM_MAEMO4|PLATFORM_MAEMO5)
#define PLATFORM_PANDORA (1<<6)
#define PLATFORM_CTRU (1<<7)
/* CONFIG_KEYPAD */
#define IRIVER_H100_PAD 4
@ -167,6 +169,7 @@
#define ECHO_R1_PAD 75
#define SURFANS_F28_PAD 76
#define RG_NANO_PAD 77
#define CTRU_PAD 78
/* CONFIG_REMOTE_KEYPAD */
#define H100_REMOTE 1
@ -623,6 +626,8 @@ Lyre prototype 1 */
#include "config/surfansf28.h"
#elif defined(RG_NANO)
#include "config/rgnano.h"
#elif defined(CTRU)
#include "config/ctru.h"
#else
#error "unknown hardware platform!"
#endif
@ -650,7 +655,7 @@ Lyre prototype 1 */
# define CONFIG_BUFLIB_BACKEND BUFLIB_BACKEND_MEMPOOL
#endif
#ifdef APPLICATION
#if defined(APPLICATION)
#ifndef CONFIG_CPU
#define CONFIG_CPU 0
#endif
@ -1033,7 +1038,8 @@ Lyre prototype 1 */
#if defined(ASSEMBLER_THREADS) \
|| defined(HAVE_WIN32_FIBER_THREADS) \
|| defined(HAVE_SIGALTSTACK_THREADS)
|| defined(HAVE_SIGALTSTACK_THREADS) \
|| defined(CTRU)
#define HAVE_PRIORITY_SCHEDULING
#endif
@ -1104,7 +1110,7 @@ Lyre prototype 1 */
* Older versions of GCC emit assembly in divided syntax with no option
* to enable unified syntax.
*/
#if (__GNUC__ < 8) && defined(CPU_ARM_CLASSIC)
#if (__GNUC__ < 8) && defined(CPU_ARM_CLASSIC) || defined(CTRU)
#define BEGIN_ARM_ASM_SYNTAX_UNIFIED ".syntax unified\n"
#define END_ARM_ASM_SYNTAX_UNIFIED ".syntax divided\n"
#else

View file

@ -0,0 +1,102 @@
/*
* This config file is for the N3DS hosted application
*/
/* We don't run on hardware directly */
#define CONFIG_PLATFORM (PLATFORM_HOSTED|PLATFORM_CTRU)
#define HAVE_FPU
/* For Rolo and boot loader */
#define MODEL_NUMBER 100s
#define MODEL_NAME "CTRU"
#define USB_NONE
#define CONFIG_CPU N10480H
#define CPU_FREQ 268000000
/* Define this if you have adjustable CPU frequency */
/* #define HAVE_ADJUSTABLE_CPU_FREQ */
/* define this if you have a colour LCD */
#define HAVE_LCD_COLOR
/* define this if you want album art for this target */
#define HAVE_ALBUMART
/* define this to enable bitmap scaling */
#define HAVE_BMP_SCALING
/* define this to enable JPEG decoding */
#define HAVE_JPEG
/* define this if you have access to the quickscreen */
#define HAVE_QUICKSCREEN
/* define this if you would like tagcache to build on this target */
#define HAVE_TAGCACHE
/* LCD dimensions */
#define LCD_WIDTH 320
#define LCD_HEIGHT 240
#define LCD_DEPTH 16
#define LCD_PIXELFORMAT RGB565
#define LCD_OPTIMIZED_UPDATE
#define LCD_OPTIMIZED_UPDATE_RECT
#define LCD_OPTIMIZED_BLIT_YUV
/* define this to indicate your device's keypad */
#define HAVE_TOUCHSCREEN
#define HAVE_BUTTON_DATA
/* define this if you have a real-time clock */
#define CONFIG_RTC APPLICATION
/* Power management */
#define CONFIG_BATTERY_MEASURE PERCENTAGE_MEASURE
#define CONFIG_CHARGING CHARGING_MONITOR
#define HAVE_SW_POWEROFF
/* The number of bytes reserved for loadable codecs */
#define CODEC_SIZE 0x100000
/* The number of bytes reserved for loadable plugins */
#define PLUGIN_BUFFER_SIZE 0x80000
#define AB_REPEAT_ENABLE
/* #define HAVE_SCROLLWHEEL */
#define CONFIG_KEYPAD CTRU_PAD
#define HAVE_CTRU_AUDIO
#define HAVE_HEADPHONE_DETECTION
/* #define HAVE_SW_VOLUME_CONTROL */
/* #define PCM_SW_VOLUME_UNBUFFERED */
/* #define PCM_SW_VOLUME_FRACBITS (16) */
/* Define this for LCD backlight available */
#define HAVE_BACKLIGHT
#define HAVE_BACKLIGHT_BRIGHTNESS
/* Main LCD backlight brightness range and defaults */
#define MIN_BRIGHTNESS_SETTING 16
#define MAX_BRIGHTNESS_SETTING 142
#define BRIGHTNESS_STEP 5
#define DEFAULT_BRIGHTNESS_SETTING 28
#define CONFIG_BACKLIGHT_FADING BACKLIGHT_FADING_SW_SETTING
#define CONFIG_LCD LCD_COWOND2
/* Define this if a programmable hotkey is mapped */
#define HAVE_HOTKEY
#define BOOTDIR "/"
/* No special storage */
#define CONFIG_STORAGE STORAGE_HOSTFS
#define HAVE_STORAGE_FLUSH

View file

@ -35,6 +35,10 @@ extern void ldebugf(const char* file, int line, const char *fmt, ...)
|| (defined(APPLICATION) && defined(DEBUG))
#define DEBUGF debugf
#define LDEBUGF(...) ldebugf(__FILE__, __LINE__, __VA_ARGS__)
#elif (CONFIG_PLATFORM & PLATFORM_CTRU)
/* let's use second display for debug output */
#define DEBUGF debugf
#define LDEBUGF(...) ldebugf(__FILE__, __LINE__, __VA_ARGS__)
#elif defined(DEBUG) /* DEBUG on native targets */
#ifdef HAVE_GDB_API

View file

@ -21,8 +21,9 @@
#ifndef HOSTED_CODEC_H
#define HOSTED_CODEC_H
#if defined(HAVE_SDL_AUDIO) \
&& !(CONFIG_PLATFORM & PLATFORM_MAEMO5)
#if (defined(HAVE_SDL_AUDIO) \
&& !(CONFIG_PLATFORM & PLATFORM_MAEMO5)) \
|| (CONFIG_PLATFORM & PLATFORM_CTRU)
AUDIOHW_SETTING(VOLUME, "dB", 0, 1, -80, 0, 0)
#else
#define AUDIOHW_CAPS (MONO_VOL_CAP)

View file

@ -41,7 +41,7 @@
/* NOTE: target-specific hosted HOME_DIR resides in filesystem-app.c */
#if !defined(APPLICATION) || defined(SAMSUNG_YPR0) || defined(SAMSUNG_YPR1) || \
defined(DX50) || defined(DX90) || defined(SONY_NWZ_LINUX) || \
defined(HIBY_LINUX) || defined(FIIO_M3K_LINUX)
defined(HIBY_LINUX) || defined(FIIO_M3K_LINUX) || defined(CTRU)
#define HOME_DIR "/"
@ -87,7 +87,7 @@
#if defined(APPLICATION) && \
!(defined(SAMSUNG_YPR0) || defined(SAMSUNG_YPR1) || \
defined(DX50) || defined(DX90) || defined(SONY_NWZ_LINUX) || defined(HIBY_LINUX) || defined(FIIO_M3K_LINUX))
defined(DX50) || defined(DX90) || defined(SONY_NWZ_LINUX) || defined(HIBY_LINUX) || defined(FIIO_M3K_LINUX) || defined(CTRU))
#define PLUGIN_DATA_DIR ROCKBOX_DIR "/rocks.data"
#define PLUGIN_GAMES_DATA_DIR PLUGIN_DATA_DIR

View file

@ -9,8 +9,8 @@
"comment out" the non-ANSI parts of the ANSI header files (non-ANSI header
files aren't affected). */
#ifndef _ANSIDECL_H_
#define _ANSIDECL_H_
#ifndef __ANSIDECL_H__
#define __ANSIDECL_H__
/* First try to figure out whether we really are in an ANSI C environment. */
/* FIXME: This probably needs some work. Perhaps sys/config.h can be
@ -64,4 +64,4 @@
#endif
#endif
#endif /* _ANSIDECL_H_ */
#endif /* __ANSIDECL_H__ */

View file

@ -28,7 +28,9 @@
#include "fs_attr.h"
#include "fs_defines.h"
#if defined (APPLICATION) || defined(CHECKWPS)
#if defined(CTRU) && !defined(SIMULATOR)
#include "filesystem-ctru.h"
#elif defined (APPLICATION) || defined(CHECKWPS)
#include "filesystem-app.h"
#elif defined(SIMULATOR) || defined(DBTOOL)
#include "../../uisimulator/common/filesystem-sim.h"

View file

@ -40,7 +40,9 @@ enum relate_result
RELATE_PREFIX, /* the path2 contains path1 as a prefix */
};
#if defined(APPLICATION) || defined(CHECKWPS)
#if defined(CTRU) && !defined(SIMULATOR)
#include "filesystem-ctru.h"
#elif defined(APPLICATION) || defined(CHECKWPS)
#include "filesystem-app.h"
#elif defined(SIMULATOR) || defined(DBTOOL)
#include "../../uisimulator/common/filesystem-sim.h"

View file

@ -28,7 +28,7 @@
#endif
#ifndef __MINGW32__
#ifdef __APPLE__
#if defined(__APPLE__) || defined(CTRU)
#include <sys/types.h>
#else
#include <endian.h>

View file

@ -89,7 +89,7 @@ struct thread_entry;
*
* simulator (possibly) doesn't simulate stack usage anyway but well ... */
#if defined(HAVE_SDL_THREADS) || defined(__PCTOOL__)
#if defined(HAVE_SDL_THREADS) || defined(__PCTOOL__) || defined(CTRU)
#define DEFAULT_STACK_SIZE 0x100 /* tiny, ignored anyway */
#else
#include "asm/thread.h"

View file

@ -75,11 +75,15 @@ void mutex_lock(struct mutex *m)
/* Release ownership of a mutex object - only owning thread must call this */
void mutex_unlock(struct mutex *m)
{
#ifndef CTRU
/* FIXME: synchronization primitives does not behave
correctly between different cores */
/* unlocker not being the owner is an unlocking violation */
KERNEL_ASSERT(m->blocker.thread == __running_self_entry(),
"mutex_unlock->wrong thread (%s != %s)\n",
m->blocker.thread->name,
__running_self_entry()->name);
#endif
if(m->recursion > 0)
{

View file

@ -304,7 +304,7 @@ int thread_get_debug_info(unsigned int thread_id,
#ifdef HAVE_SCHEDULER_BOOSTCTRL
cpu_boost = thread->cpu_boost;
#endif
#ifndef HAVE_SDL_THREADS
#if !defined(HAVE_SDL_THREADS) && !defined(CTRU)
infop->stack_usage = stack_usage(thread->stack, thread->stack_size);
size_t stack_used_current =

View file

@ -32,7 +32,7 @@
*
* simulator (possibly) doesn't simulate stack usage anyway but well ... */
#if defined(HAVE_SDL_THREADS) || defined(__PCTOOL__)
#if defined(HAVE_SDL_THREADS) || defined(__PCTOOL__) || defined(CTRU)
struct regs
{
void *t; /* OS thread */

View file

@ -0,0 +1,32 @@
/***************************************************************************
* __________ __ ___
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
*
* Copyright (C) 2017 Marcin Bukat
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#ifndef _BACKLIGHT_TARGET_H_
#define _BACKLIGHT_TARGET_H_
#include <stdbool.h>
/* See backlight.c */
bool backlight_hw_init(void);
void backlight_hw_on(void);
void backlight_hw_off(void);
void backlight_hw_brightness(int brightness);
#endif

View file

@ -0,0 +1,54 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2007 by Rob Purchase
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#ifndef _BUTTON_TARGET_H_
#define _BUTTON_TARGET_H_
/* Logical buttons key codes */
#define BUTTON_UP 0x00000001
#define BUTTON_DOWN 0x00000002
#define BUTTON_LEFT 0x00000004
#define BUTTON_RIGHT 0x00000008
#define BUTTON_USER 0x00000010
#define BUTTON_MENU 0x00000020
#define BUTTON_BACK 0x00000040
#define BUTTON_POWER 0x00000080
#define BUTTON_SELECT 0x00000100
/* Touch Screen Area Buttons */
#define BUTTON_TOPLEFT 0x00001000
#define BUTTON_TOPMIDDLE 0x00002000
#define BUTTON_TOPRIGHT 0x00004000
#define BUTTON_MIDLEFT 0x00008000
#define BUTTON_CENTER 0x00010000
#define BUTTON_MIDRIGHT 0x00020000
#define BUTTON_BOTTOMLEFT 0x00040000
#define BUTTON_BOTTOMMIDDLE 0x00080000
#define BUTTON_BOTTOMRIGHT 0x00100000
#define BUTTON_MAIN 0x1FFF
/* Software power-off */
#define POWEROFF_BUTTON BUTTON_POWER
/* About 3 seconds */
#define POWEROFF_COUNT 10
#endif /* _BUTTON_TARGET_H_ */

View file

@ -0,0 +1,114 @@
/***************************************************************************
* __________ __ ___
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
*
* Copyright (C) 2025 Mauricio G.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include <sys/types.h>
#include <sys/stat.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include "config.h"
#include "backlight-target.h"
#include "sysfs.h"
#include "panic.h"
#include "lcd.h"
#include "debug.h"
#include <3ds/services/gsplcd.h>
#include <3ds/result.h>
#include "luminance-ctru.h"
/* TODO: To use calibrated values in rockbox,
MIN_BRIGHTNESS_SETTING (etc) would need to be
declared as constants in settings-list.c */
u32 ctru_min_lum = 16;
u32 ctru_max_lum = 142;
u32 ctru_luminance = 28;
/* FIXME: After calling gspLcdInit() the home menu will no longer be
accesible, to fix this we have to call gspLcdInit/gspLcdExit as
a mutex lock/unlock when using gsplcd.h functions. */
void lcd_mutex_lock(void)
{
Result res = gspLcdInit();
if (R_FAILED(res)) {
DEBUGF("backlight_hw_init: gspLcdInit failed.\n");
}
}
void lcd_mutex_unlock(void)
{
gspLcdExit();
}
bool backlight_hw_init(void)
{
/* read calibrated values */
ctru_luminance = getCurrentLuminance(false);
ctru_min_lum = getMinLuminancePreset();
ctru_max_lum = getMaxLuminancePreset();
backlight_hw_on();
backlight_hw_brightness(DEFAULT_BRIGHTNESS_SETTING);
return true;
}
static int last_bl = -1;
void backlight_hw_on(void)
{
lcd_mutex_lock();
if (last_bl != 1) {
#ifdef HAVE_LCD_ENABLE
lcd_enable(true);
#endif
GSPLCD_PowerOnAllBacklights();
last_bl = 1;
}
lcd_mutex_unlock();
}
void backlight_hw_off(void)
{
lcd_mutex_lock();
if (last_bl != 0) {
/* only power off rockbox ui screen */
GSPLCD_PowerOffBacklight(GSPLCD_SCREEN_BOTTOM);
#ifdef HAVE_LCD_ENABLE
lcd_enable(false);
#endif
last_bl = 0;
}
lcd_mutex_unlock();
}
void backlight_hw_brightness(int brightness)
{
/* cap range, just in case */
if (brightness > MAX_BRIGHTNESS_SETTING)
brightness = MAX_BRIGHTNESS_SETTING;
if (brightness < MIN_BRIGHTNESS_SETTING)
brightness = MIN_BRIGHTNESS_SETTING;
/* normalize level on both screens */
lcd_mutex_lock();
GSPLCD_SetBrightnessRaw(GSPLCD_SCREEN_TOP | GSPLCD_SCREEN_BOTTOM, (u32) brightness);
lcd_mutex_unlock();
}

View file

@ -0,0 +1,184 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2002 by Felix Arends
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include <math.h>
#include <stdlib.h> /* EXIT_SUCCESS */
#include <stdio.h>
#include "config.h"
#include "button.h"
#include "kernel.h"
#include "backlight.h"
#include "system.h"
#include "button-ctru.h"
#include "buttonmap.h"
#include "debug.h"
#include "powermgmt.h"
#include "storage.h"
#include "settings.h"
#include "sound.h"
#include "misc.h"
#include "touchscreen.h"
#include <3ds/types.h>
#include <3ds/services/apt.h>
#include <3ds/services/hid.h>
#include <3ds/services/mcuhwc.h>
#include <3ds/services/dsp.h>
static u8 old_slider_level = 0;
static int last_y, last_x;
static enum {
STATE_UNKNOWN = -1,
STATE_UP = 0,
STATE_DOWN = 1,
} last_touch_state = STATE_UNKNOWN;
static double map_values(double n, double source_start, double source_end, double dest_start, double dest_end, int decimal_precision ) {
double delta_start = source_end - source_start;
double delta_end = dest_end - dest_start;
if(delta_start == 0.0 || delta_end == 0.0) {
return 1.0;
}
double scale = delta_end / delta_start;
double neg_start = -1.0 * source_start;
double offset = (neg_start * scale) + dest_start;
double final_number = (n * scale) + offset;
int calc_scale = (int) pow(10.0, decimal_precision);
return (double) round(final_number * calc_scale) / calc_scale;
}
void update_sound_slider_level(void)
{
/* update global volume based on sound slider level */
u8 level;
MCUHWC_GetSoundSliderLevel(&level);
if (level != old_slider_level) {
int volume = (int) map_values((double) level,
0.0, /* min slider voslume */
(double) 0x3f, /* max slider value */
(double) sound_min(SOUND_VOLUME),
(double) sound_max(SOUND_VOLUME),
0);
global_status.volume = volume;
setvol();
old_slider_level = level;
}
}
int button_read_device(int* data)
{
int key = BUTTON_NONE;
/* TODO: implement Home Menu button support */
/* if (!aptMainLoop()) {
return true;
} */
hidScanInput();
u32 kDown = hidKeysDown();
if (kDown & KEY_SELECT) {
touchscreen_set_mode(touchscreen_get_mode() == TOUCHSCREEN_POINT ? TOUCHSCREEN_BUTTON : TOUCHSCREEN_POINT);
printf("Touchscreen mode: %s\n", touchscreen_get_mode() == TOUCHSCREEN_POINT ? "TOUCHSCREEN_POINT" : "TOUCHSCREEN_BUTTON");
}
u32 kHeld = hidKeysHeld();
/* rockbox will handle button repeats */
kDown |= kHeld;
/* Check for all the keys */
if (kDown & KEY_A) {
key |= BUTTON_SELECT;
}
if (kDown & KEY_B) {
key |= BUTTON_BACK;
}
if (kDown & KEY_X) {
key |= BUTTON_MENU;
}
if (kDown & KEY_Y) {
key |= BUTTON_USER;
}
if (kDown & KEY_START) {
key |= BUTTON_POWER;
}
if (kDown & KEY_DRIGHT) {
key |= BUTTON_RIGHT;
}
if (kDown & KEY_DLEFT) {
key |= BUTTON_LEFT;
}
if (kDown & KEY_DUP) {
key |= BUTTON_UP;
}
if (kDown & KEY_DDOWN) {
key |= BUTTON_DOWN;
}
if (kDown & KEY_START) {
key |= BUTTON_POWER;
}
touchPosition touch;
hidTouchRead(&touch);
/* Generate UP and DOWN events */
if (kDown & KEY_TOUCH) {
last_touch_state = STATE_DOWN;
}
else {
last_touch_state = STATE_UP;
}
last_x = touch.px;
last_y = touch.py;
int tkey = touchscreen_to_pixels(last_x, last_y, data);
if (last_touch_state == STATE_DOWN) {
key |= tkey;
}
update_sound_slider_level();
return key;
}
void button_init_device(void)
{
hidInit();
}
#ifndef HAS_BUTTON_HOLD
void touchscreen_enable_device(bool en)
{
(void)en;
}
#endif
bool headphones_inserted(void)
{
bool is_inserted;
DSP_GetHeadphoneStatus(&is_inserted);
return is_inserted;
}

View file

@ -0,0 +1,34 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2009 by Thomas Martitz
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#ifndef __BUTTON_CTRU_H__
#define __BUTTON_CTRU_H__
#include <stdbool.h>
#include "config.h"
bool button_hold(void);
#undef button_init_device
void button_init_device(void);
int button_read_device(int *data);
#endif /* __BUTTON_CTRU_H__ */

View file

@ -0,0 +1,41 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2010 by Fred Bauer
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#ifndef __BUTTONMAP_H__
#define __BUTTONMAP_H__
/* Button maps: simulated key, x, y, radius, name */
/* Run sim with --mapping to get coordinates */
/* or --debugbuttons to check */
/* The First matching button is returned */
struct button_map {
int button, x, y, radius;
char *description;
};
extern struct button_map bm[];
int xy2button( int x, int y);
#ifdef HAVE_TOUCHSCREEN
int key_to_touch(int keyboard_button, unsigned int mouse_coords);
#endif
#endif /* __BUTTONMAP_H__ */

View file

@ -0,0 +1,73 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2010 by Thomas Martitz
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#define RB_FILESYSTEM_OS
#include <sys/stat.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <utime.h>
#include "config.h"
#include "system.h"
#include "file.h"
#include "dir.h"
#include "mv.h"
#include "debug.h"
#include "pathfuncs.h"
#include "string-extra.h"
#include <3ds/archive.h>
void paths_init(void)
{
/* is this needed in 3DS? */
#if 0
char config_dir[MAX_PATH];
const char *home = "/3ds";
mkdir("/3ds/.rockbox" __MKDIR_MODE_ARG);
snprintf(config_dir, sizeof(config_dir), "%s/.config", home);
mkdir(config_dir __MKDIR_MODE_ARG);
snprintf(config_dir, sizeof(config_dir), "%s/.config/rockbox.org", home);
mkdir(config_dir __MKDIR_MODE_ARG);
/* Plugin data directory */
snprintf(config_dir, sizeof(config_dir), "%s/.config/rockbox.org/rocks.data", home);
mkdir(config_dir __MKDIR_MODE_ARG);
#endif
}
/* only sdcard volume is accesible to the user */
void volume_size(IF_MV(int volume,) sector_t *sizep, sector_t *freep)
{
sector_t size = 0, free = 0;
FS_ArchiveResource sdmcRSRC;
FSUSER_GetSdmcArchiveResource(&sdmcRSRC);
size = (sdmcRSRC.totalClusters * sdmcRSRC.clusterSize) / sdmcRSRC.sectorSize;
free = (sdmcRSRC.freeClusters * sdmcRSRC.clusterSize) / sdmcRSRC.sectorSize;
if (sizep)
*sizep = size;
if (freep)
*freep = free;
}

View file

@ -0,0 +1,116 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2025 by Mauricio G.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#ifndef _FILESYSTEM_CTRU_H_
#define _FILESYSTEM_CTRU_H_
#if defined(PLUGIN) || defined(CODEC)
#define FILEFUNCTIONS_DECLARED
#define FILEFUNCTIONS_DEFINED
#define DIRFUNCTIONS_DECLARED
#define DIRFUNCTIONS_DEFINED
#define OSFUNCTIONS_DECLARED
#endif /* PLUGIN || CODEC */
#ifndef OSFUNCTIONS_DECLARED
#define FS_PREFIX(_x_) ctru_ ## _x_
void paths_init(void);
#endif /* !OSFUNCTIONS_DECLARED */
#endif /* _FILESYSTEM_CTRU_H_ */
#ifdef _FILE_H_
#include <unistd.h>
#ifndef _FILESYSTEM_CTRU__FILE_H_
#define _FILESYSTEM_CTRU__FILE_H_
#ifdef RB_FILESYSTEM_OS
#define FILEFUNCTIONS_DEFINED
#endif
#ifndef FILEFUNCTIONS_DECLARED
#define __OPEN_MODE_ARG
#define __CREAT_MODE_ARG \
, mode
#include <time.h>
int ctru_open(const char *name, int oflag, ...);
int ctru_creat(const char *name, mode_t mode);
int ctru_close(int fildes);
int ctru_ftruncate(int fildes, off_t length);
int ctru_fsync(int fildes);
off_t ctru_lseek(int fildes, off_t offset, int whence);
ssize_t ctru_read(int fildes, void *buf, size_t nbyte);
ssize_t ctru_write(int fildes, const void *buf, size_t nbyte);
int ctru_remove(const char *path);
int ctru_rename(const char *old, const char *new);
int ctru_modtime(const char *path, time_t modtime);
off_t ctru_filesize(int fildes);
int ctru_fsamefile(int fildes1, int fildes2);
int ctru_relate(const char *path1, const char *path2);
bool ctru_file_exists(const char *path);
ssize_t ctru_readlink(const char *path, char *buf, size_t bufsiz);
#endif /* !FILEFUNCTIONS_DECLARED */
#endif /* _FILESYSTEM_CTRU__FILE_H_ */
#endif /* _FILE_H_ */
#ifdef _DIR_H_
#ifndef _FILESYSTEM_CTRU__DIR_H_
#define _FILESYSTEM_CTRU__DIR_H_
#define DIRENT dirent
struct dirent;
struct dirinfo_native
{
unsigned int attr;
off_t size;
uint16_t wrtdate;
uint16_t wrttime;
};
typedef struct {} DIR;
#ifndef DIRFUNCTIONS_DECLARED
#define __MKDIR_MODE_ARG \
, 0777
#ifdef RB_FILESYSTEM_OS
#define DIRFUNCTIONS_DEFINED
#endif
DIR * ctru_opendir(const char *dirname);
struct dirent * ctru_readdir(DIR *dirp);
int ctru_readdir_r(DIR *dirp, struct dirent *entry,
struct dirent **result);
void ctru_rewinddir(DIR *dirp);
int ctru_closedir(DIR *dirp);
int ctru_mkdir(const char *path);
int ctru_rmdir(const char *path);
int ctru_samedir(DIR *dirp1, DIR *dirp2);
bool ctru_dir_exists(const char *dirname);
#endif /* !DIRFUNCTIONS_DECLARED */
#endif /* _FILESYSTEM_CTRU__DIR_H_ */
#endif /* _DIR_H_ */

View file

@ -0,0 +1,166 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2025 Mauricio G.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>
#include "system-ctru.h"
#include "thread-ctru.h"
#include "kernel.h"
#include "thread.h"
#include "panic.h"
#include "debug.h"
static int tick_timer_id;
long start_tick;
/* Condition to signal that "interrupts" may proceed */
static sysCond *sim_thread_cond;
/* Mutex to serialize changing levels and exclude other threads while
* inside a handler */
static RecursiveLock sim_irq_mtx;
/* Level: 0 = enabled, not 0 = disabled */
static int volatile interrupt_level = HIGHEST_IRQ_LEVEL;
/* How many handers waiting? Not strictly needed because CondSignal is a
* noop if no threads were waiting but it filters-out calls to functions
* with higher overhead and provides info when debugging. */
static int handlers_pending = 0;
/* 1 = executing a handler; prevents CondSignal calls in set_irq_level
* while in a handler */
static int status_reg = 0;
/* Nescessary logic:
* 1) All threads must pass unblocked
* 2) Current handler must always pass unblocked
* 3) Threads must be excluded when irq routine is running
* 4) No more than one handler routine should execute at a time
*/
int set_irq_level(int level)
{
RecursiveLock_Lock(&sim_irq_mtx);
int oldlevel = interrupt_level;
if (status_reg == 0 && level == 0 && oldlevel != 0)
{
/* Not in a handler and "interrupts" are going from disabled to
* enabled; signal any pending handlers still waiting */
if (handlers_pending > 0)
sys_cond_broadcast(sim_thread_cond);
}
interrupt_level = level; /* save new level */
RecursiveLock_Unlock(&sim_irq_mtx);
return oldlevel;
}
void sim_enter_irq_handler(void)
{
RecursiveLock_Lock(&sim_irq_mtx);
handlers_pending++;
/* Check each time before proceeding: disabled->enabled->...->disabled
* is possible on an app thread before a handler thread is ever granted
* the mutex; a handler can also leave "interrupts" disabled during
* its execution */
while (interrupt_level != 0)
sys_cond_wait(sim_thread_cond, &sim_irq_mtx);
status_reg = 1;
}
void sim_exit_irq_handler(void)
{
/* If any others are waiting, give the signal */
if (--handlers_pending > 0)
sys_cond_signal(sim_thread_cond);
status_reg = 0;
RecursiveLock_Unlock(&sim_irq_mtx);
}
static bool sim_kernel_init(void)
{
RecursiveLock_Init(&sim_irq_mtx);
sim_thread_cond = sys_cond_create();
if (sim_thread_cond == NULL)
{
panicf("Cannot create sim_thread_cond\n");
return false;
}
return true;
}
void sim_kernel_shutdown(void)
{
sys_remove_timer(tick_timer_id);
enable_irq();
while(handlers_pending > 0)
sys_delay(10);
sys_cond_destroy(sim_thread_cond);
}
u32 tick_timer(u32 interval, void *param)
{
long new_tick;
(void) interval;
(void) param;
new_tick = (sys_get_ticks() - start_tick) / (1000/HZ);
while(new_tick != current_tick)
{
sim_enter_irq_handler();
/* Run through the list of tick tasks - increments tick
* on each iteration. */
call_tick_tasks();
sim_exit_irq_handler();
}
return interval;
}
void tick_start(unsigned int interval_in_ms)
{
if (!sim_kernel_init())
{
panicf("Could not initialize kernel!");
exit(-1);
}
if (tick_timer_id != 0)
{
sys_remove_timer(tick_timer_id);
tick_timer_id = 0;
}
else
{
start_tick = sys_get_ticks();
}
tick_timer_id = sys_add_timer(interval_in_ms, tick_timer, NULL);
}

View file

@ -0,0 +1,68 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2025 Mauricio G.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#define RB_FILESYSTEM_OS
#include <dlfcn.h>
#include <stdlib.h>
#include <string.h>
#include "system.h"
#include "load_code.h"
#include "filesystem-ctru.h"
#include "debug.h"
void* programResolver(const char* sym, void *userData);
void * lc_open(const char *filename, unsigned char *buf, size_t buf_size)
{
DEBUGF("dlopen(path=\"%s\")\n", filename);
/* note: the 3ds dlopen implementation needs a custom resolver
for the unresolved symbols in shared objects */
/* void *handle = dlopen(filename, RTLD_NOW | RTLD_LOCAL); */
void *handle = ctrdlOpen(filename,
RTLD_NOW | RTLD_LOCAL,
programResolver,
NULL);
if (handle == NULL)
{
DEBUGF("%s(\"%s\") failed\n", __func__, filename);
DEBUGF(" lc_open error '%s'\n", dlerror());
}
DEBUGF("handle = %p\n", handle);
return handle;
(void) buf; (void) buf_size;
}
void * lc_get_header(void *handle)
{
void *symbol = dlsym(handle, "__header");
if (!symbol) {
symbol = dlsym(handle, "___header");
}
return symbol;
}
void lc_close(void *handle)
{
if (handle) {
dlclose(handle);
}
}

View file

@ -0,0 +1,53 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2025 Mauricio G.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
#include <setjmp.h>
#include "debug.h"
#define RESOLVER_ENTRY(name) \
if (!strcmp(symName, #name)) \
return (void *)name;
/* void* ctrdlProgramResolver(const char* symName) */
void* programResolver(const char* symName, void *userData)
{
DEBUGF("programResolver(name=\"%s\")\n", symName);
/* codecs, etc */
RESOLVER_ENTRY(abs);
RESOLVER_ENTRY(labs);
RESOLVER_ENTRY(llabs);
RESOLVER_ENTRY(printf);
/* plugins */
RESOLVER_ENTRY(_ctype_);
RESOLVER_ENTRY(__errno);
RESOLVER_ENTRY(longjmp);
RESOLVER_ENTRY(setjmp);
return NULL;
(void) userData;
}

View file

@ -0,0 +1,245 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2006 Dan Everton
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include "debug.h"
#include "system.h"
#include "button-ctru.h"
#include "screendump.h"
#include "lcd-target.h"
#include <3ds/gfx.h>
#include <3ds/allocator/linear.h>
#include <3ds/console.h>
#include <3ds/services/cfgu.h>
/*#define LOGF_ENABLE*/
#include "logf.h"
fb_data *dev_fb = 0;
static u8 system_model = CFG_MODEL_3DS;
typedef struct
{
int width, height;
} dimensions_t;
int get_dest_offset(int x, int y, int dest_width)
{
return dest_width - y - 1 + dest_width * x;
}
int get_source_offset(int x, int y, int source_width)
{
return x + y * source_width;
}
void copy_framebuffer_16(u16 *dest, const dimensions_t dest_dim, const u16 *source, const dimensions_t source_dim)
{
int rows = MIN(dest_dim.width, source_dim.height);
int cols = MIN(dest_dim.height, source_dim.width);
for (int y = 0; y < rows; ++y) {
for (int x = 0; x < cols; ++x) {
const u16 *s = source + get_source_offset(x, y, source_dim.width);
u16 *d = dest + get_dest_offset(x, y, dest_dim.width);
*d = *s;
}
}
}
void copy_framebuffer_24(u8 *dest, const dimensions_t dest_dim, const u8 *source, const dimensions_t source_dim)
{
int rows = MIN(dest_dim.width, source_dim.height);
int cols = MIN(dest_dim.height, source_dim.width);
for (int y = 0; y < rows; ++y) {
for (int x = 0; x < cols; ++x) {
const u8 *s = source + get_source_offset(x, y, source_dim.width) * 3;
u8 *d = dest + get_dest_offset(x, y, dest_dim.width) * 3;
d[0] = s[0];
d[1] = s[1];
d[2] = s[2];
}
}
}
void copy_framebuffer_32(u32 *dest, const dimensions_t dest_dim, const u32 *source, const dimensions_t source_dim)
{
int rows = MIN(dest_dim.width, source_dim.height);
int cols = MIN(dest_dim.height, source_dim.width);
for (int y = 0; y < rows; ++y) {
for (int x = 0; x < cols; ++x) {
const u32 *s = source + get_source_offset(x, y, source_dim.width);
u32 *d = dest + get_dest_offset(x, y, dest_dim.width);
*d = *s;
}
}
}
void update_framebuffer(void)
{
u16 fb_width, fb_height;
u32 bufsize;
u8* fb = gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, &fb_width, &fb_height);
bufsize = fb_width * fb_height * 2;
/* lock_window_mutex() */
if (FB_DATA_SZ == 2) {
copy_framebuffer_16((void *)fb, (dimensions_t){ fb_width, fb_height },
(u16 *)dev_fb, (dimensions_t){ LCD_WIDTH, LCD_HEIGHT });
}
else if (FB_DATA_SZ == 3) {
copy_framebuffer_24((void *)fb, (dimensions_t){ fb_width, fb_height },
(u8 *)dev_fb, (dimensions_t){ LCD_WIDTH, LCD_HEIGHT });
}
else {
copy_framebuffer_32((void *)fb, (dimensions_t){ fb_width, fb_height },
(u32 *)dev_fb, (dimensions_t){ LCD_WIDTH, LCD_HEIGHT });
}
// Flush and swap framebuffers
/* gfxFlushBuffers(); */
GSPGPU_FlushDataCache(fb, bufsize);
gfxSwapBuffers();
gspWaitForVBlank();
/* unlock_window_mutex() */
}
/* lcd-as-memframe.c */
extern void lcd_copy_buffer_rect(fb_data *dst, const fb_data *src,
int width, int height);
void lcd_update_rect(int x, int y, int width, int height)
{
fb_data *dst, *src;
if (x + width > LCD_WIDTH)
width = LCD_WIDTH - x; /* Clip right */
if (x < 0)
width += x, x = 0; /* Clip left */
if (width <= 0)
return; /* nothing left to do */
if (y + height > LCD_HEIGHT)
height = LCD_HEIGHT - y; /* Clip bottom */
if (y < 0)
height += y, y = 0; /* Clip top */
if (height <= 0)
return; /* nothing left to do */
dst = LCD_FRAMEBUF_ADDR(x, y);
src = FBADDR(x,y);
/* Copy part of the Rockbox framebuffer to the second framebuffer */
if (width < LCD_WIDTH)
{
/* Not full width - do line-by-line */
lcd_copy_buffer_rect(dst, src, width, height);
}
else
{
/* Full width - copy as one line */
lcd_copy_buffer_rect(dst, src, LCD_WIDTH*height, 1);
}
update_framebuffer();
}
void lcd_update(void)
{
/* update a full screen rect */
lcd_update_rect(0, 0, LCD_WIDTH, LCD_HEIGHT);
}
void sys_console_init(void)
{
gfxInit(GSP_BGR8_OES, GSP_RGB565_OES, false);
consoleInit(GFX_TOP, NULL);
}
void lcd_init_device(void)
{
gfxSetDoubleBuffering(GFX_BOTTOM, true);
/* hidInit(); */
u16 fb_width, fb_height;
u8* fb = gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, &fb_width, &fb_height);
u32 bufsize = fb_width * fb_height * 2;
dev_fb = (fb_data *) linearAlloc(bufsize);
if (dev_fb == NULL) {
logf("could not allocate dev_fb size %d bytes\n",
bufsize);
exit(EXIT_FAILURE);
}
memset(dev_fb, 0x00, bufsize);
/* Set dpi value from system model */
CFGU_GetSystemModel(&system_model);
}
void lcd_shutdown(void)
{
if (dev_fb) {
linearFree(dev_fb);
dev_fb = 0;
}
/* hidExit(); */
gfxExit();
}
int lcd_get_dpi(void)
{
/* link: https://www.reddit.com/r/nintendo/comments/2uzk5y/informative_post_about_dpi_on_the_new_3ds/ */
/* all values for bottom lcd */
float dpi = 96.0f;
switch(system_model) {
case CFG_MODEL_3DS:
dpi = 132.45f;
break;
case CFG_MODEL_3DSXL:
dpi = 95.69f;
break;
case CFG_MODEL_N3DS:
dpi = 120.1f;
break;
case CFG_MODEL_2DS:
dpi = 132.45f;
break;
case CFG_MODEL_N3DSXL:
dpi = 95.69f;
break;
case CFG_MODEL_N2DSXL:
dpi = 95.69f;
break;
default:
break;
};
return (int) roundf(dpi);
}

View file

@ -0,0 +1,31 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2025 Mauricio G.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#ifndef __LCDBITMAP_H__
#define __LCDBITMAP_H__
#include "lcd.h"
void sim_lcd_init(void);
void sys_console_init(void);
#endif /* #ifndef __LCDBITMAP_H__ */

View file

@ -0,0 +1,34 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2025 Mauricio G.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#ifndef LCD_TARGET_H
#define LCD_TARGET_H
#include "lcd.h"
/*
Framebuffer device and framebuffer access.
See lcd-memframe.c
*/
extern fb_data *dev_fb;
#define LCD_FRAMEBUF_ADDR(col, row) (dev_fb + row * LCD_WIDTH + col)
#endif /* LCD_TARGET_H */

View file

@ -0,0 +1,99 @@
#ifndef _BFILE_INTERNAL_H_
#define _BFILE_INTERNAL_H_
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <sys/stat.h>
#include <math.h>
#include <3ds/gfx.h>
#include <3ds/svc.h>
#include <3ds/types.h>
#include <3ds/thread.h>
#include <3ds/result.h>
#include <3ds/services/fs.h>
#include <3ds/synchronization.h>
/* #define MALLOC_DEBUG
#include "rmalloc/rmalloc.h" */
#include "cslice.h"
#include "cmap.h"
#define nil NULL
/* in go functions can return two values */
#define two_type_value(type1, type2, name1, name2, type_name) \
typedef struct { \
type1 name1; \
type2 name2; \
} type_name##_t;
/* single mutex implementation */
#define sync_Mutex LightLock
static inline void sync_MutexInit(sync_Mutex* m) {
LightLock_Init(m);
}
/* mutex unlock */
static inline void sync_MutexLock(sync_Mutex* m) {
LightLock_Lock(m);
}
/* mutex lock */
static inline void sync_MutexUnlock(sync_Mutex* m) {
LightLock_Unlock(m);
}
/* read_write mutex implementation */
typedef struct {
sync_Mutex shared;
CondVar reader_q;
CondVar writer_q;
int active_readers;
int active_writers;
int waiting_writers;
} sync_RWMutex;
void sync_RWMutexInit(sync_RWMutex *m);
void sync_RWMutexRLock(sync_RWMutex *m);
void sync_RWMutexRUnlock(sync_RWMutex *m);
void sync_RWMutexLock(sync_RWMutex *m);
void sync_RWMutexUnlock(sync_RWMutex *m);
/* declare a two type value with name 'n_err_t' */
two_type_value(int, const char*, n, err, int_error);
two_type_value(struct stat, const char*, fi, err, stat_error);
typedef const char* file_error_t;
typedef struct page {
s64 num;
struct page* prev;
struct page* next;
u8* data;
} page;
/* the two map types used by this library */
cmap_declare(page, s64, struct page*);
cmap_declare(bool, s64, bool);
typedef struct shard {
sync_Mutex mu;
cmap(page) pages;
cmap(bool) dirty;
struct page* head;
struct page* tail;
} shard;
typedef struct Pager {
Handle file;
s64 pgsize;
s64 pgmax;
/* sync_RWMutex mu; */
s64 size;
cslice(shard) shards;
} Pager;
#endif /* _BFILE_INTERNAL_H_ */

View file

@ -0,0 +1,471 @@
/*
* This code is based on bfile.go by Josh Baker.
* Converted to C code by Mauricio G.
* Released under the MIT License.
*/
/* IMPORTANT: this code only works for O_RDONLY and O_RDWR files. */
#include "bfile.h"
/* note: for ease of reading and ease of comparing go code
with c implementation, function names are similar to the
go version */
/* note2: sync_RWMutex calls have been moved to rockbox sys_file
implementation. To use as standalone code please uncomment those calls. */
void sync_RWMutexInit(sync_RWMutex *m) {
LightLock_Init(&m->shared);
CondVar_Init(&m->reader_q);
CondVar_Init(&m->writer_q);
m->active_readers = 0;
m->active_writers = 0;
m->waiting_writers = 0;
}
static inline LightLock *unique_lock(LightLock *lk) {
LightLock_Lock(lk);
return lk;
}
void sync_RWMutexRLock(sync_RWMutex *m) {
LightLock *lk = unique_lock(&m->shared);
while (m->waiting_writers != 0) {
CondVar_Wait(&m->reader_q, lk);
}
++m->active_readers;
LightLock_Unlock(lk);
}
void sync_RWMutexRUnlock(sync_RWMutex *m) {
LightLock *lk = unique_lock(&m->shared);
--m->active_readers;
LightLock_Unlock(lk);
CondVar_Signal(&m->writer_q);
}
void sync_RWMutexLock(sync_RWMutex *m) {
LightLock *lk = unique_lock(&m->shared);
++m->waiting_writers;
while ((m->active_readers != 0) || (m->active_writers != 0)) {
CondVar_Wait(&m->writer_q, lk);
}
++m->active_writers;
LightLock_Unlock(lk);
}
void sync_RWMutexUnlock(sync_RWMutex *m) {
LightLock *lk = unique_lock(&m->shared);
--m->waiting_writers;
--m->active_writers;
if (m->waiting_writers > 0) {
CondVar_Signal(&m->writer_q);
} else {
CondVar_Broadcast(&m->reader_q);
}
LightLock_Unlock(lk);
}
void s_init(shard* s);
void s_push(shard* s, page* p) {
s->head->next->prev = p;
p->next = s->head->next;
p->prev = s->head;
s->head->next = p;
}
void s_pop(shard* s, page* p) {
p->prev->next = p->next;
p->next->prev = p->prev;
}
void s_bump(shard* s, page* p) {
s_pop(s, p);
s_push(s, p);
}
/* page_pair_t destructor */
/* page_pair_t type is defined by cmap_declare(page, s64, struct page*) */
/* struct {
s64 key;
struct page* value;
} page_pair_t;
*/
void page_pair_free(void* pair_ptr) {
if (pair_ptr) {
page_pair_t *pair = (page_pair_t*) pair_ptr;
struct page *p = pair->value;
if (p != nil) {
if (p->data != nil) {
free(p->data);
}
free(p);
}
}
}
/* shard destructor */
void s_free(void* s_ptr) {
if (s_ptr) {
shard *s = (shard*) s_ptr;
if (s->pages != nil) {
cmap_set_elem_destructor(s->pages, page_pair_free);
cmap_clear(page, s->pages);
cmap_clear(bool, s->dirty);
free(s->head);
free(s->tail);
}
}
}
Pager* NewPager(Handle file) {
return NewPagerSize(file, 0, 0);
}
Pager* NewPagerSize(Handle file, int pageSize, int bufferSize) {
if (pageSize <= 0) {
pageSize = defaultPageSize;
} else if ((pageSize & 4095) != 0) {
// must be a power of two
int x = 1;
while (x < pageSize) {
x *= 2;
}
pageSize = x;
}
if (bufferSize <= 0) {
bufferSize = defaultBufferSize;
} else if (bufferSize < pageSize) {
bufferSize = pageSize;
}
Pager* f = (Pager*) malloc(sizeof(Pager));
f->file = file;
f->size = -1;
f->pgsize = (s64) pageSize;
/* sync_RWMutexInit(&f->mu); */
// calculate the max number of pages across all shards
s64 pgmax = (s64) bufferSize / f->pgsize;
if (pgmax < minPages) {
pgmax = minPages;
}
// calculate how many shards are needed, power of 2
s64 nshards = (s64) ceil((double) pgmax / (double) pagesPerShard);
if (nshards > maxShards) {
nshards = maxShards;
}
s64 x = 1;
while (x < nshards) {
x *= 2;
}
nshards = x;
// calculate the max number of pages per shard
f->pgmax = (s64) floor((double) pgmax / (double) nshards);
cslice_make(f->shards, nshards, (shard) { 0 });
// initialize sync mutex
size_t i;
for (i = 0; i < cslice_len(f->shards); i++) {
sync_MutexInit(&f->shards[i].mu);
}
return f;
}
static int_error_t read_at(Handle file, u8 *data, size_t data_len, off_t off)
{
u32 read_bytes = 0;
if (R_FAILED(FSFILE_Read(file, &read_bytes, (u64) off, data, (u32) data_len))) {
return (int_error_t) { -1, "I/O error" };
}
/* io.EOF */
if (read_bytes == 0) {
return (int_error_t) { 0, "io.EOF" };
}
return (int_error_t) { (int) read_bytes, nil };
}
static int_error_t write_at(Handle file, u8 *data, size_t data_len, off_t off)
{
u32 written = 0;
if (R_FAILED(FSFILE_Write(file, &written, (u64) off, data, (u32) data_len, FS_WRITE_FLUSH))) {
return (int_error_t) { -1, "I/O error" };
}
/* I/O error */
if ((written == 0) || (written < (u32) data_len)) {
return (int_error_t) { -1, "I/O error" };
}
return (int_error_t) { (int) written, nil };
}
static stat_error_t file_stat(Handle file)
{
u64 size = 0;
struct stat fi = { 0 };
if (R_FAILED(FSFILE_GetSize(file, &size))) {
fi.st_size = 0;
return (stat_error_t) { fi, "I/O error" };
}
fi.st_size = (off_t) size;
return (stat_error_t) { fi, nil };
}
void s_init(shard* s)
{
if (s->pages == nil) {
s->pages = cmap_make(/*s64*/page);
s->dirty = cmap_make(/*s64*/bool);
s->head = (page*) malloc(sizeof(page));
s->tail = (page*) malloc(sizeof(page));
s->head->next = s->tail;
s->tail->prev = s->head;
}
}
file_error_t f_write(Pager* f, page* p) {
s64 off = p->num * f->pgsize;
s64 end = f->pgsize;
if ((off + end) > f->size) {
end = f->size - off;
}
int_error_t __err = write_at(f->file, p->data, end, off);
if (__err.err != nil) {
return __err.err;
}
return nil;
}
file_error_t f_read(Pager* f, page* p) {
int_error_t __err = read_at(f->file, p->data, f->pgsize, p->num * f->pgsize);
if ((__err.err != nil) && strcmp(__err.err, "io.EOF")) {
return "I/O error";
}
return nil;
}
const char* f_incrSize(Pager* f, s64 end, bool write)
{
#define defer(m) \
sync_RWMutexUnlock(&f->mu); \
sync_RWMutexRLock(&f->mu);
/* sync_RWMutexRUnlock(&f->mu);
sync_RWMutexLock(&f->mu); */
if (f->size == -1) {
stat_error_t fi_err = file_stat(f->file);
if (fi_err.err != nil) {
/* defer(&f->mu); */
return nil;
}
f->size = fi_err.fi.st_size;
}
if (write && (end > f->size)) {
f->size = end;
}
/* defer(&f->mu); */
return nil;
}
int_error_t f_pio(Pager *f, u8 *b, size_t len_b, s64 pnum, s64 pstart, s64 pend, bool write);
int_error_t f_io(Pager *f, u8 *b, size_t len_b, s64 off, bool write) {
if (f == nil) {
return (int_error_t) { 0, "invalid argument" };
}
bool eof = false;
s64 start = off, end = off + len_b;
if (start < 0) {
return (int_error_t) { 0, "negative offset" };
}
// Check the upper bounds of the input to the known file size.
// Increase the file size if needed.
/* sync_RWMutexRLock(&f->mu); */
if (end > f->size) {
file_error_t err = f_incrSize(f, end, write);
if (err != nil) {
/* sync_RWMutexRUnlock(&f->mu); */
return (int_error_t) { 0, err };
}
if (!write && (end > f->size)) {
end = f->size;
if ((end - start) < 0) {
end = start;
}
eof = true;
len_b = end-start; /* b = b[:end-start] */
}
}
/* sync_RWMutexRUnlock(&f->mu); */
// Perform the page I/O.
int total = 0;
while (len_b > 0) {
s64 pnum = off / f->pgsize;
s64 pstart = off & (f->pgsize - 1);
s64 pend = pstart + (s64) len_b;
if (pend > f->pgsize) {
pend = f->pgsize;
}
int_error_t result = f_pio(f, b, pend - pstart, pnum, pstart, pend, write);
if (result.err != nil) {
return (int_error_t) { total, result.err };
}
off += (s64) result.n;
total += result.n;
b = &b[result.n]; len_b -= result.n; /* b = b[n:] */
}
if (eof) {
return (int_error_t) { total, "io.EOF" };
}
return (int_error_t) { total, nil };
}
int_error_t f_pio(Pager *f, u8 *b, size_t len_b, s64 pnum, s64 pstart, s64 pend, bool write) {
/* printf("pio(%p, %d, %lld, %lld, %lld, %s)\n", b, len_b, pnum, pstart, pend, write == true ? "true" : "false"); */
shard *s = &f->shards[pnum & (s64) (cslice_len(f->shards) - 1)];
sync_MutexLock(&s->mu);
s_init(s);
page *p = cmap_get_ptr(page, s->pages, pnum);
if (p == nil) {
// Page does not exist in memory.
// Acquire a new one.
if (cmap_len(s->pages) == f->pgmax) {
// The buffer is at capacity.
// Evict lru page and hang on to it.
p = s->tail->prev;
s_pop(s, p);
cmap_delete(page, s->pages, p->num);
if (cmap_get(bool, s->dirty, p->num)) {
// dirty page. flush it now
file_error_t err = f_write(f, p);
if (err != nil) {
sync_MutexUnlock(&s->mu);
return (int_error_t) { 0, err };
}
cmap_delete(bool, s->dirty, p->num);
}
// Clear the previous page memory for partial page writes for
// pages that are being partially written to.
if (write && ((pend - pstart) < f->pgsize)) {
memset(p->data, 0, f->pgsize);
}
} else {
// Allocate an entirely new page.
p = (page *) malloc(sizeof(page));
p->data = (u8 *) malloc(f->pgsize);
}
p->num = pnum;
// Read contents of page from file for all read operations, and
// partial write operations. Ignore for full page writes.
if (!write || ((pend-pstart) < f->pgsize)) {
file_error_t err = f_read(f, p);
if (err != nil) {
sync_MutexUnlock(&s->mu);
return (int_error_t) { 0, err };
}
}
// Add the newly acquired page to the list.
cmap_set(page, s->pages, p->num, p);
s_push(s, p);
} else {
// Bump the page to the front of the list.
s_bump(s, p);
}
if (write) {
memcpy(p->data + pstart, b, pend - pstart);
cmap_set(bool, s->dirty, pnum, true);
} else {
memcpy(b, p->data + pstart, pend - pstart);
}
sync_MutexUnlock(&s->mu);
return (int_error_t) { len_b, nil };
}
// Flush writes any unwritten buffered data to the underlying file.
file_error_t PagerFlush(Pager *f) {
if (f == nil) {
return "invalid argument";
}
/* sync_RWMutexLock(&f->mu); */
for (size_t i = 0; i < cslice_len(f->shards); i++) {
cmap_iterator(bool) pnum;
if (f->shards[i].dirty != nil) {
for (pnum = cmap_begin(f->shards[i].dirty);
pnum != cmap_end(f->shards[i].dirty); pnum++) {
if (pnum->value == true) {
page *p = cmap_get_ptr(page, f->shards[i].pages, pnum->key);
if (p != nil) {
file_error_t err = f_write(f, p);
if (err != nil) {
/* sync_RWMutexUnlock(&f->mu); */
return err;
}
}
cmap_set(bool, f->shards[i].dirty, pnum->key, false);
}
}
}
}
/* sync_RWMutexUnlock(&f->mu); */
return nil;
}
// ReadAt reads len(b) bytes from the File starting at byte offset off.
int_error_t PagerReadAt(Pager *f, u8 *b, size_t len_b, off_t off) {
return f_io(f, b, len_b, off, false);
}
// WriteAt writes len(b) bytes to the File starting at byte offset off.
int_error_t PagerWriteAt(Pager *f, u8 *b, size_t len_b, off_t off) {
return f_io(f, b, len_b, off, true);
}
file_error_t PagerTruncate(Pager *f, off_t length) {
if (f == nil) {
return "invalid argument";
}
/* flush unwritten changes to disk */
PagerFlush(f);
/* sync_RWMutexRLock(&f->mu); */
/* set new file size */
Handle handle = f->file;
Result res = FSFILE_SetSize(handle, (u64) length);
if (R_FAILED(res)) {
return "I/O error";
}
/* sync_RWMutexRUnlock(&f->mu); */
/* FIXME: truncate only required pages. Remove all for now */
PagerClear(f);
f = NewPager(handle);
return nil;
}
void PagerClear(Pager *f) {
if (f) {
cslice_set_elem_destructor(f->shards, s_free);
cslice_clear(f->shards);
free(f);
}
}

View file

@ -0,0 +1,37 @@
#ifndef _BFILE_H_
#define _BFILE_H_
#include "bfile-internal.h"
static const int defaultPageSize = 4096; // all pages are this size
static const int defaultBufferSize = 0x800000; // default buffer size, 8 MB
static const int minPages = 4; // minimum total pages per file
static const int pagesPerShard = 32; // ideal number of pages per shard
static const int maxShards = 128; // maximum number of shards per file
// NewPager returns a new Pager that is backed by the provided file.
Pager* NewPager(Handle file);
// NewPagerSize returns a new Pager with a custom page size and buffer size.
// The bufferSize is the maximum amount of memory dedicated to individual
// pages. Setting pageSize and bufferSize to zero will use their defaults,
// which are 4096 and 8 MB respectively. Custom values are rounded up to the
// nearest power of 2.
Pager* NewPagerSize(Handle file, int pageSize, int bufferSize);
// ReadAt reads len(b) bytes from the File starting at byte offset off.
int_error_t PagerReadAt(Pager *f, u8 *b, size_t len_b, off_t off);
// WriteAt writes len(b) bytes to the File starting at byte offset off.
int_error_t PagerWriteAt(Pager *f, u8 *b, size_t len_b, off_t off);
// Flush writes any unwritten buffered data to the underlying file.
file_error_t PagerFlush(Pager *f);
// Truncates pager to specified length
file_error_t PagerTruncate(Pager *f, off_t length);
// Free all memory associated to a Pager file
void PagerClear(Pager *f);
#endif /* _B_FILE_H_ */

View file

@ -0,0 +1,209 @@
#ifndef CMAP_H_
#define CMAP_H_
#define CVECTOR_LINEAR_GROWTH
#include "cvector.h"
/* note: for ease of porting go code to c, many functions (macros) names
remain similar to the ones used by go */
/* note2: this is a very basic map implementation. It does not do any sorting, and only works for basic types (and pointers) that can be compared with the equality operator */
#define nil NULL
#define cmap_elem_destructor_t cvector_elem_destructor_t
/**
* @brief cmap_declare - The map type used in this library
* @param name - The name associated to a map type.
* @param key_type - The map pair key type.
* @param val_type - The map pair value type.
* @param compare_func - The function used to compare for key_type. Should return value < 0 when a < b, 0 when a == b and value > 0 when a > b.
*/
#define cmap_declare(name, key_type, val_type) \
typedef struct { \
key_type key; \
val_type value; \
} name##_pair_t; \
\
typedef struct { \
cvector(name##_pair_t) tree; \
cmap_elem_destructor_t elem_destructor; \
} name##_map_t; \
\
static inline val_type name##_get_( \
name##_map_t *this, const key_type key) \
{ \
if (this) { \
size_t i; \
for (i = 0; i < cvector_size(this->tree); i++) { \
if (key == this->tree[i].key) { \
return this->tree[i].value; \
} \
} \
} \
return 0; \
} \
\
static inline val_type name##_get_ptr_( \
name##_map_t *this, const key_type key) \
{ \
if (this) { \
size_t i; \
for (i = 0; i < cvector_size(this->tree); i++) { \
if (key == this->tree[i].key) { \
return this->tree[i].value; \
} \
} \
} \
return nil; \
} \
\
static inline void name##_set_( \
name##_map_t *this, const key_type key, val_type value) \
{ \
if (this) { \
size_t i; \
for (i = 0; i < cvector_size(this->tree); i++) { \
if (key == this->tree[i].key) { \
this->tree[i].value = value; \
return; \
} \
} \
name##_pair_t new_pair = (name##_pair_t) { key, value }; \
cvector_push_back(this->tree, new_pair); \
} \
} \
\
static inline void name##_delete_( \
name##_map_t *this, const key_type key) \
{ \
if (this) { \
size_t i; \
for (i = 0; i < cvector_size(this->tree); i++) { \
if (key == this->tree[i].key) { \
cvector_erase(this->tree, i); \
return; \
} \
} \
} \
} \
\
static inline name##_map_t* name##_map_make_(void) \
{ \
name##_map_t *map = (name##_map_t*) malloc(sizeof(name##_map_t)); \
if (map) { \
map->tree = nil; \
cvector_init(map->tree, 0, nil); \
return map; \
} \
return nil; \
} \
\
static inline void name##_clear_(name##_map_t *this) \
{ \
if (this) { \
cvector_free(this->tree); \
free(this); \
} \
} \
\
/**
* @brief cmap - The map type used in this library
* @param name - The name associated to a map type.
*/
#define cmap(name) name##_map_t *
/**
* @brief cmap_make - creates a new map. Automatically initializes the map.
* @param name - the name asociated to the map type
* @return a pointer to a new map.
*/
#define cmap_make(name) name##_map_make_()
/**
* @brief cmap_size - gets the current size of the map
* @param map_ptr - the map pointer
* @return the size as a size_t
*/
#define cmap_len(map_ptr) cvector_size(map_ptr->tree)
/**
* @brief cmap_get - gets value associated to a key.
* @param name - the name asociated to the map type
* @param map_ptr - the map pointer
* @param key - the key to search for
* @return the value associated to a key
*/
#define cmap_get(name, map_ptr, key) name##_get_(map_ptr, key)
/**
* @brief cmap_get-ptr - gets ptr_value associated to a key. Use it to avoid assigning a ptr to 0.
* @param name - the name asociated to the map type
* @param map_ptr - the map pointer
* @param key - the key to search for
* @return the value associated to a key
*/
#define cmap_get_ptr(name, map_ptr, key) name##_get_ptr_(map_ptr, key)
/**
* @brief cmap_set - sets value associated to a key.
* @param name - the name asociated to the map type
* @param map_ptr - the map pointer
* @param key - the key to search for
* @param value - the new value
* @return void
*/
#define cmap_set(name, map_ptr, key, val) name##_set_(map_ptr, key, val)
/**
* @brief cmap_delete - deletes map entry associated to a key.
* @param name - the name asociated to the map type
* @param map_ptr - the map pointer
* @param key - the key to search for
* @return void
*/
#define cmap_delete(name, map_ptr, key) name##_delete_(map_ptr, key)
/**
* @brief cmap_set_elem_destructor - set the element destructor function
* used to clean up removed elements. The map must NOT be NULL for this to do anything.
* @param map_ptr - the map pointer
* @param elem_destructor_fn - function pointer of type cvector_elem_destructor_t used to destroy elements
* @return void
*/
#define cmap_set_elem_destructor(map_ptr, elem_destructor_fn) \
cvector_set_elem_destructor(map_ptr->tree, elem_destructor_fn)
/**
* @brief cmap_clear - deletes all map entries. And frees memory is an element destructor was set previously.
* @param name - the name asociated to the map type
* @param map_ptr - the map pointer
* @return void
*/
#define cmap_clear(name, map_ptr) name##_clear_(map_ptr)
/**
* @brief cmap_iterator - The iterator type used for cmap
* @param type The type of iterator to act on.
*/
#define cmap_iterator(name) cvector_iterator(name##_pair_t)
/**
* @brief cmap_begin - returns an iterator to first element of the vector
* @param map_ptr - the map pointer
* @return a pointer to the first element (or NULL)
*/
#define cmap_begin(map_ptr) ((map_ptr) ? cvector_begin(map_ptr->tree) : nil)
/**
* @brief cmap_end - returns an iterator to one past the last element of the vector
* @param map_ptrs - the map pointer
* @return a pointer to one past the last element (or NULL)
*/
#define cmap_end(map_ptr) ((map_ptr) ? cvector_end(map_ptr->tree) : nil)
#endif /* CMAP_H_ */

View file

@ -0,0 +1,63 @@
#ifndef CSLICE_H_
#define CSLICE_H_
#define CVECTOR_LINEAR_GROWTH
#include "cvector.h"
/* note: for ease of porting go code to c, many functions (macros) names
remain similar to the ones used by go */
#define nil NULL
/**
* @brief cslice - The slice type used in this library
* @param type The type of slice to act on.
*/
#define cslice(type) cvector(type)
/**
* @brief cslice_make - creates a new slice. Automatically initializes the slice.
* @param slice - the slice
* @param count - new size of the slice
* @param value - the value to initialize new elements with
* @return void
*/
#define cslice_make(slice, capacity, value) \
do { \
slice = nil; \
cvector_init(slice, capacity, nil); \
cvector_resize(slice, capacity, value); \
} while(0)
/**
* @brief cslice_size - gets the current size of the slice
* @param slice - the slice
* @return the size as a size_t
*/
#define cslice_len(slice) cvector_size(slice)
/**
* @brief cslice_capacity - gets the current capacity of the slice
* @param slice - the slice
* @return the capacity as a size_t
*/
#define cslice_cap(slice) cvector_capacity(slice)
/**
* @brief cslice_set_elem_destructor - set the element destructor function
* used to clean up removed elements. The vector must NOT be NULL for this to do anything.
* @param slice - the slice
* @param elem_destructor_fn - function pointer of type cslice_elem_destructor_t used to destroy elements
* @return void
*/
#define cslice_set_elem_destructor(slice, elem_destructor_fn) \
cvector_set_elem_destructor(slice, elem_destructor_fn)
/**
* @brief cslice_free - frees all memory associated with the slice
* @param slice - the slice
* @return void
*/
#define cslice_clear(slice) cvector_free(slice)
#endif /* CSLICE_H_ */

View file

@ -0,0 +1,549 @@
#ifndef CVECTOR_H_
#define CVECTOR_H_
/**
* @copyright Copyright (c) 2015 Evan Teran,
* License: The MIT License (MIT)
* @brief cvector heap implemented using C library malloc()
* @file cvector.h
*/
/* in case C library malloc() needs extra protection,
* allow these defines to be overridden.
*/
/* functions for allocation and deallocation need to correspond to each other, fall back to C library functions if not all are overridden */
#if !defined(cvector_clib_free) || !defined(cvector_clib_malloc) || !defined(cvector_clib_calloc) || !defined(cvector_clib_realloc)
#ifdef cvector_clib_free
#undef cvector_clib_free
#endif
#ifdef cvector_clib_malloc
#undef cvector_clib_malloc
#endif
#ifdef cvector_clib_calloc
#undef cvector_clib_calloc
#endif
#ifdef cvector_clib_realloc
#undef cvector_clib_realloc
#endif
#include <stdlib.h>
#define cvector_clib_free free
#define cvector_clib_malloc malloc
#define cvector_clib_calloc calloc
#define cvector_clib_realloc realloc
#endif
/* functions independent of memory allocation */
#ifndef cvector_clib_assert
#include <assert.h> /* for assert */
#define cvector_clib_assert assert
#endif
#ifndef cvector_clib_memcpy
#include <string.h> /* for memcpy */
#define cvector_clib_memcpy memcpy
#endif
#ifndef cvector_clib_memmove
#include <string.h> /* for memmove */
#define cvector_clib_memmove memmove
#endif
/* NOTE: Similar to C's qsort and bsearch, you will receive a T*
* for a vector of Ts. This means that you cannot use `free` directly
* as a destructor. Instead if you have for example a cvector_vector_type(int *)
* you will need to supply a function which casts `elem_ptr` to an `int**`
* and then does a free on what that pointer points to:
*
* ex:
*
* void free_int(void *p) { free(*(int **)p); }
*/
typedef void (*cvector_elem_destructor_t)(void *elem_ptr);
typedef struct cvector_metadata_t {
size_t size;
size_t capacity;
cvector_elem_destructor_t elem_destructor;
} cvector_metadata_t;
/**
* @brief cvector_vector_type - The vector type used in this library
* @param type The type of vector to act on.
*/
#define cvector_vector_type(type) type *
/**
* @brief cvector - Syntactic sugar to retrieve a vector type
* @param type The type of vector to act on.
*/
#define cvector(type) cvector_vector_type(type)
/**
* @brief cvector_iterator - The iterator type used for cvector
* @param type The type of iterator to act on.
*/
#define cvector_iterator(type) cvector_vector_type(type)
/**
* @note you can also safely pass a pointer to a cvector iterator to a function
* but you have to update the pointer at the end to update the original
* iterator.
* example:
* void function( cvector_vector_type( type ) * p_it )
* {
* cvector_vector_type( type ) it = *p_it;
* it ++;
*
* ...
*
* *p_it = it;
* }
*/
/**
* @brief cvector_vector_type_ptr - helper to make code more "readable"
* @param type - the vector type pointer
*/
#define cvector_ptr_type(type) \
cvector_vector_type(type) *
/**
* @brief cvector_vector_ptr_get_iterator/set - helpers to make code more "readable"
* @param it - the vector iterator
* @param ptr - the vector pointer
*/
#define cvector_ptr_get_iterator(ptr) \
*(ptr)
#define cvector_ptr_set(ptr, it) \
*(ptr) = it
/**
* @brief cvector_vector_container_declare - defined a vector container type
*/
#define cvector_vector_container_declare(name, type) \
struct cvector_vector_container_##name { \
cvector_vector_type(type) vector; \
}
/**
* @brief cvector_vector_container - used to pass a cvector wrapped inside a container as a function parameter
*/
#define cvector_vector_container(name) \
struct cvector_vector_container_##name
/**
* @brief cvector_vec_to_base - For internal use, converts a vector pointer to a metadata pointer
* @param vec - the vector
* @return the metadata pointer of the vector
* @internal
*/
#define cvector_vec_to_base(vec) \
(&((cvector_metadata_t *)(void *)(vec))[-1])
/**
* @brief cvector_base_to_vec - For internal use, converts a metadata pointer to a vector pointer
* @param ptr - pointer to the metadata
* @return the vector
* @internal
*/
#define cvector_base_to_vec(ptr) \
((void *)&((cvector_metadata_t *)(ptr))[1])
/**
* @brief cvector_capacity - gets the current capacity of the vector
* @param vec - the vector
* @return the capacity as a size_t
*/
#define cvector_capacity(vec) \
((vec) ? cvector_vec_to_base(vec)->capacity : (size_t)0)
/**
* @brief cvector_size - gets the current size of the vector
* @param vec - the vector
* @return the size as a size_t
*/
#define cvector_size(vec) \
((vec) ? cvector_vec_to_base(vec)->size : (size_t)0)
/**
* @brief cvector_elem_destructor - get the element destructor function used
* to clean up elements
* @param vec - the vector
* @return the function pointer as cvector_elem_destructor_t
*/
#define cvector_elem_destructor(vec) \
((vec) ? cvector_vec_to_base(vec)->elem_destructor : NULL)
/**
* @brief cvector_empty - returns non-zero if the vector is empty
* @param vec - the vector
* @return non-zero if empty, zero if non-empty
*/
#define cvector_empty(vec) \
(cvector_size(vec) == 0)
/**
* @brief cvector_reserve - Requests that the vector capacity be at least enough
* to contain n elements. If n is greater than the current vector capacity, the
* function causes the container to reallocate its storage increasing its
* capacity to n (or greater).
* @param vec - the vector
* @param n - Minimum capacity for the vector.
* @return void
*/
#define cvector_reserve(vec, n) \
do { \
size_t cv_reserve_cap__ = cvector_capacity(vec); \
if (cv_reserve_cap__ < (n)) { \
cvector_grow((vec), (n)); \
} \
} while (0)
/**
* @brief cvector_init - Initialize a vector. The vector must be NULL for this to do anything.
* @param vec - the vector
* @param capacity - vector capacity to reserve
* @param elem_destructor_fn - element destructor function
* @return void
*/
#define cvector_init(vec, capacity, elem_destructor_fn) \
do { \
if (!(vec)) { \
cvector_reserve((vec), capacity); \
cvector_set_elem_destructor((vec), (elem_destructor_fn)); \
} \
} while (0)
/**
* @brief cvector_init_default - Initialize a vector with default value. The vector must be NULL for this to do anything. Does NOT work for struct types.
* @param vec - the vector
* @param capacity - vector capacity to reserve
* @param elem_destructor_fn - element destructor function
* @return void
*/
#define cvector_init_default(vec, capacity, default) \
do { \
if (!(vec)) { \
cvector_reserve((vec), capacity); \
cvector_set_elem_destructor((vec), (elem_destructor_fn)); \
} \
} while (0)
/**
* @brief cvector_erase - removes the element at index i from the vector
* @param vec - the vector
* @param i - index of element to remove
* @return void
*/
#define cvector_erase(vec, i) \
do { \
if (vec) { \
const size_t cv_erase_sz__ = cvector_size(vec); \
if ((i) < cv_erase_sz__) { \
cvector_elem_destructor_t cv_erase_elem_dtor__ = cvector_elem_destructor(vec); \
if (cv_erase_elem_dtor__) { \
cv_erase_elem_dtor__(&(vec)[i]); \
} \
cvector_set_size((vec), cv_erase_sz__ - 1); \
cvector_clib_memmove( \
(vec) + (i), \
(vec) + (i) + 1, \
sizeof(*(vec)) * (cv_erase_sz__ - 1 - (i))); \
} \
} \
} while (0)
/**
* @brief cvector_clear - erase all of the elements in the vector
* @param vec - the vector
* @return void
*/
#define cvector_clear(vec) \
do { \
if (vec) { \
cvector_elem_destructor_t cv_clear_elem_dtor__ = cvector_elem_destructor(vec); \
if (cv_clear_elem_dtor__) { \
size_t cv_clear_i__; \
for (cv_clear_i__ = 0; cv_clear_i__ < cvector_size(vec); ++cv_clear_i__) { \
cv_clear_elem_dtor__(&(vec)[cv_clear_i__]); \
} \
} \
cvector_set_size(vec, 0); \
} \
} while (0)
/**
* @brief cvector_free - frees all memory associated with the vector
* @param vec - the vector
* @return void
*/
#define cvector_free(vec) \
do { \
if (vec) { \
void *cv_free_p__ = cvector_vec_to_base(vec); \
cvector_elem_destructor_t cv_free_elem_dtor__ = cvector_elem_destructor(vec); \
if (cv_free_elem_dtor__) { \
size_t cv_free_i__; \
for (cv_free_i__ = 0; cv_free_i__ < cvector_size(vec); ++cv_free_i__) { \
cv_free_elem_dtor__(&(vec)[cv_free_i__]); \
} \
} \
cvector_clib_free(cv_free_p__); \
} \
} while (0)
/**
* @brief cvector_begin - returns an iterator to first element of the vector
* @param vec - the vector
* @return a pointer to the first element (or NULL)
*/
#define cvector_begin(vec) \
(vec)
/**
* @brief cvector_end - returns an iterator to one past the last element of the vector
* @param vec - the vector
* @return a pointer to one past the last element (or NULL)
*/
#define cvector_end(vec) \
((vec) ? &((vec)[cvector_size(vec)]) : NULL)
/* user request to use linear growth algorithm */
#ifdef CVECTOR_LINEAR_GROWTH
/**
* @brief cvector_compute_next_grow - returns an the computed size in next vector grow
* size is increased by 1
* @param size - current size
* @return size after next vector grow
*/
#define cvector_compute_next_grow(size) \
((size) + 1)
#else
/**
* @brief cvector_compute_next_grow - returns an the computed size in next vector grow
* size is increased by multiplication of 2
* @param size - current size
* @return size after next vector grow
*/
#define cvector_compute_next_grow(size) \
((size) ? ((size) << 1) : 1)
#endif /* CVECTOR_LINEAR_GROWTH */
/**
* @brief cvector_push_back - adds an element to the end of the vector
* @param vec - the vector
* @param value - the value to add
* @return void
*/
#define cvector_push_back(vec, value) \
do { \
size_t cv_push_back_cap__ = cvector_capacity(vec); \
if (cv_push_back_cap__ <= cvector_size(vec)) { \
cvector_grow((vec), cvector_compute_next_grow(cv_push_back_cap__)); \
} \
(vec)[cvector_size(vec)] = (value); \
cvector_set_size((vec), cvector_size(vec) + 1); \
} while (0)
/**
* @brief cvector_insert - insert element at position pos to the vector
* @param vec - the vector
* @param pos - position in the vector where the new elements are inserted.
* @param val - value to be copied (or moved) to the inserted elements.
* @return void
*/
#define cvector_insert(vec, pos, val) \
do { \
size_t cv_insert_cap__ = cvector_capacity(vec); \
if (cv_insert_cap__ <= cvector_size(vec)) { \
cvector_grow((vec), cvector_compute_next_grow(cv_insert_cap__)); \
} \
if ((pos) < cvector_size(vec)) { \
cvector_clib_memmove( \
(vec) + (pos) + 1, \
(vec) + (pos), \
sizeof(*(vec)) * ((cvector_size(vec)) - (pos))); \
} \
(vec)[(pos)] = (val); \
cvector_set_size((vec), cvector_size(vec) + 1); \
} while (0)
/**
* @brief cvector_pop_back - removes the last element from the vector
* @param vec - the vector
* @return void
*/
#define cvector_pop_back(vec) \
do { \
cvector_elem_destructor_t cv_pop_back_elem_dtor__ = cvector_elem_destructor(vec); \
if (cv_pop_back_elem_dtor__) { \
cv_pop_back_elem_dtor__(&(vec)[cvector_size(vec) - 1]); \
} \
cvector_set_size((vec), cvector_size(vec) - 1); \
} while (0)
/**
* @brief cvector_copy - copy a vector
* @param from - the original vector
* @param to - destination to which the function copy to
* @return void
*/
#define cvector_copy(from, to) \
do { \
if ((from)) { \
cvector_grow(to, cvector_size(from)); \
cvector_set_size(to, cvector_size(from)); \
cvector_clib_memcpy((to), (from), cvector_size(from) * sizeof(*(from))); \
} \
} while (0)
/**
* @brief cvector_swap - exchanges the content of the vector by the content of another vector of the same type
* @param vec - the original vector
* @param other - the other vector to swap content with
* @param type - the type of both vectors
* @return void
*/
#define cvector_swap(vec, other, type) \
do { \
if (vec && other) { \
cvector_vector_type(type) cv_swap__ = vec; \
vec = other; \
other = cv_swap__; \
} \
} while (0)
/**
* @brief cvector_set_capacity - For internal use, sets the capacity variable of the vector
* @param vec - the vector
* @param size - the new capacity to set
* @return void
* @internal
*/
#define cvector_set_capacity(vec, size) \
do { \
if (vec) { \
cvector_vec_to_base(vec)->capacity = (size); \
} \
} while (0)
/**
* @brief cvector_set_size - For internal use, sets the size variable of the vector
* @param vec - the vector
* @param _size - the new capacity to set
* @return void
* @internal
*/
#define cvector_set_size(vec, _size) \
do { \
if (vec) { \
cvector_vec_to_base(vec)->size = (_size); \
} \
} while (0)
/**
* @brief cvector_set_elem_destructor - set the element destructor function
* used to clean up removed elements. The vector must NOT be NULL for this to do anything.
* @param vec - the vector
* @param elem_destructor_fn - function pointer of type cvector_elem_destructor_t used to destroy elements
* @return void
*/
#define cvector_set_elem_destructor(vec, elem_destructor_fn) \
do { \
if (vec) { \
cvector_vec_to_base(vec)->elem_destructor = (elem_destructor_fn); \
} \
} while (0)
/**
* @brief cvector_grow - For internal use, ensures that the vector is at least `count` elements big
* @param vec - the vector
* @param count - the new capacity to set
* @return void
* @internal
*/
#define cvector_grow(vec, count) \
do { \
const size_t cv_grow_sz__ = (count) * sizeof(*(vec)) + sizeof(cvector_metadata_t); \
if (vec) { \
void *cv_grow_p1__ = cvector_vec_to_base(vec); \
void *cv_grow_p2__ = cvector_clib_realloc(cv_grow_p1__, cv_grow_sz__); \
cvector_clib_assert(cv_grow_p2__); \
(vec) = cvector_base_to_vec(cv_grow_p2__); \
} else { \
void *cv_grow_p__ = cvector_clib_malloc(cv_grow_sz__); \
cvector_clib_assert(cv_grow_p__); \
(vec) = cvector_base_to_vec(cv_grow_p__); \
cvector_set_size((vec), 0); \
cvector_set_elem_destructor((vec), NULL); \
} \
cvector_set_capacity((vec), (count)); \
} while (0)
/**
* @brief cvector_shrink_to_fit - requests the container to reduce its capacity to fit its size
* @param vec - the vector
* @return void
*/
#define cvector_shrink_to_fit(vec) \
do { \
if (vec) { \
const size_t cv_shrink_to_fit_sz__ = cvector_size(vec); \
cvector_grow(vec, cv_shrink_to_fit_sz__); \
} \
} while (0)
/**
* @brief cvector_at - returns a reference to the element at position n in the vector.
* @param vec - the vector
* @param n - position of an element in the vector.
* @return the element at the specified position in the vector.
*/
#define cvector_at(vec, n) \
((vec) ? (((int)(n) < 0 || (size_t)(n) >= cvector_size(vec)) ? NULL : &(vec)[n]) : NULL)
/**
* @brief cvector_front - returns a reference to the first element in the vector. Unlike member cvector_begin, which returns an iterator to this same element, this function returns a direct reference.
* @param vec - the vector
* @return a reference to the first element in the vector container.
*/
#define cvector_front(vec) \
((vec) ? ((cvector_size(vec) > 0) ? cvector_at(vec, 0) : NULL) : NULL)
/**
* @brief cvector_back - returns a reference to the last element in the vector.Unlike member cvector_end, which returns an iterator just past this element, this function returns a direct reference.
* @param vec - the vector
* @return a reference to the last element in the vector.
*/
#define cvector_back(vec) \
((vec) ? ((cvector_size(vec) > 0) ? cvector_at(vec, cvector_size(vec) - 1) : NULL) : NULL)
/**
* @brief cvector_resize - resizes the container to contain count elements.
* @param vec - the vector
* @param count - new size of the vector
* @param value - the value to initialize new elements with
* @return void
*/
#define cvector_resize(vec, count, value) \
do { \
if (vec) { \
size_t cv_resize_count__ = (size_t)(count); \
size_t cv_resize_sz__ = cvector_vec_to_base(vec)->size; \
if (cv_resize_count__ > cv_resize_sz__) { \
cvector_reserve((vec), cv_resize_count__); \
cvector_set_size((vec), cv_resize_count__); \
do { \
(vec)[cv_resize_sz__++] = (value); \
} while (cv_resize_sz__ < cv_resize_count__); \
} else { \
while (cv_resize_count__ < cv_resize_sz__--) { \
cvector_pop_back(vec); \
} \
} \
} \
} while (0)
#endif /* CVECTOR_H_ */

View file

@ -0,0 +1,403 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2025 Mauricio G.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#define DIRFUNCTIONS_DEFINED
#include "config.h"
#include <errno.h>
#include <string.h>
#include "debug.h"
#include "dir.h"
#include "pathfuncs.h"
#include "timefuncs.h"
#include "system.h"
#include "fs_defines.h"
#include "sys_file.h"
#include <3ds/archive.h>
#include <3ds/util/utf.h>
/* This file is based on firmware/common/dir.c */
/* Define LOGF_ENABLE to enable logf output in this file */
// #define LOGF_ENABLE
#include "logf.h"
/* structure used for open directory streams */
static struct dirstr_desc
{
struct filestr_base stream; /* basic stream info (first!) */
struct dirent entry; /* current parsed entry information */
} open_streams[MAX_OPEN_DIRS] =
{
[0 ... MAX_OPEN_FILES-1] = { .stream = { .handle = 0 } }
};
extern FS_Archive sdmcArchive;
/* check and return a struct dirstr_desc* from a DIR* */
static struct dirstr_desc * get_dirstr(DIR *dirp)
{
struct dirstr_desc *dir = (struct dirstr_desc *)dirp;
if (!PTR_IN_ARRAY(open_streams, dir, MAX_OPEN_DIRS))
dir = NULL;
else if (dir->stream.handle != 0)
return dir;
int errnum;
if (!dir)
{
errnum = EFAULT;
}
else
{
logf("dir #%d: dir not open\n", (int)(dir - open_streams));
errnum = EBADF;
}
errno = errnum;
return NULL;
}
#define GET_DIRSTR(type, dirp) \
({ \
file_internal_lock_##type(); \
struct dirstr_desc *_dir = get_dirstr(dirp); \
if (_dir) \
FILESTR_LOCK(type, &_dir->stream); \
else { \
file_internal_unlock_##type(); \
} \
_dir; \
})
/* release the lock on the dirstr_desc* */
#define RELEASE_DIRSTR(type, dir) \
({ \
FILESTR_UNLOCK(type, &(dir)->stream); \
file_internal_unlock_##type(); \
})
/* find a free dir stream descriptor */
static struct dirstr_desc * alloc_dirstr(void)
{
for (unsigned int dd = 0; dd < MAX_OPEN_DIRS; dd++)
{
struct dirstr_desc *dir = &open_streams[dd];
if (dir->stream.handle == 0)
return dir;
}
logf("Too many dirs open\n");
return NULL;
}
u32 fs_error(void) {
u32 err;
FSUSER_GetSdmcFatfsError(&err);
return err;
}
/* Initialize the base descriptor */
static void filestr_base_init(struct filestr_base *stream)
{
stream->cache = nil;
stream->handle = 0;
stream->size = 0;
LightLock_Init(&stream->mtx);
}
/** POSIX interface **/
/* open a directory */
DIR * ctru_opendir(const char *dirname)
{
logf("opendir(dirname=\"%s\")\n", dirname);
DIR *dirp = NULL;
file_internal_lock_WRITER();
int rc;
struct dirstr_desc * const dir = alloc_dirstr();
if (!dir)
FILE_ERROR(EMFILE, _RC);
filestr_base_init(&dir->stream);
Result res = FSUSER_OpenDirectory(&dir->stream.handle,
sdmcArchive,
fsMakePath(PATH_ASCII, dirname));
if (R_FAILED(res)) {
logf("Open failed: %lld\n", fs_error());
FILE_ERROR(EMFILE, -1);
}
dir->stream.size = 0;
dir->stream.flags = 0;
/* we will use file path to implement ctru_samedir function */
strcpy(dir->stream.path, dirname);
dirp = (DIR *)dir;
file_error:
file_internal_unlock_WRITER();
return dirp;
}
/* close a directory stream */
int ctru_closedir(DIR *dirp)
{
int rc;
file_internal_lock_WRITER();
/* needs to work even if marked "nonexistant" */
struct dirstr_desc * const dir = (struct dirstr_desc *)dirp;
if (!PTR_IN_ARRAY(open_streams, dir, MAX_OPEN_DIRS))
FILE_ERROR(EFAULT, -1);
logf("closedir(dirname=\"%s\")\n", dir->stream.path);
if (dir->stream.handle == 0)
{
logf("dir #%d: dir not open\n", (int)(dir - open_streams));
FILE_ERROR(EBADF, -2);
}
Result res = FSDIR_Close(dir->stream.handle);
if (R_FAILED(res))
FILE_ERROR(ERRNO, -3);
dir->stream.handle = 0;
dir->stream.path[0] = '\0';
rc = 0;
file_error:
file_internal_unlock_WRITER();
return rc;
}
void dirstr_entry_init(FS_DirectoryEntry *dirEntry, struct dirent *entry)
{
/* clear */
memset(entry, 0, sizeof(struct dirent));
/* attributes */
if (dirEntry->attributes & FS_ATTRIBUTE_DIRECTORY)
entry->info.attr |= ATTR_DIRECTORY;
if (dirEntry->attributes & FS_ATTRIBUTE_HIDDEN)
entry->info.attr |= ATTR_HIDDEN;
if (dirEntry->attributes & FS_ATTRIBUTE_ARCHIVE)
entry->info.attr |= ATTR_ARCHIVE;
if (dirEntry->attributes & FS_ATTRIBUTE_READ_ONLY)
entry->info.attr |= ATTR_READ_ONLY;
/* size */
entry->info.size = dirEntry->fileSize;
/* name */
uint8_t d_name[0xA0 + 1];
memset(d_name, '\0', 0xA0);
utf16_to_utf8(d_name, (uint16_t *) &dirEntry->name, 0xA0);
memcpy(entry->d_name, d_name, 0xA0);
}
/* read a directory */
struct dirent * ctru_readdir(DIR *dirp)
{
struct dirstr_desc * const dir = GET_DIRSTR(READER, dirp);
if (!dir)
FILE_ERROR_RETURN(ERRNO, NULL);
int rc;
struct dirent *res = NULL;
logf("readdir(dirname=\"%s\")\n", dir->stream.path);
u32 dataRead = 0;
FS_DirectoryEntry dirEntry;
Result result = FSDIR_Read(dir->stream.handle,
&dataRead,
1,
&dirEntry);
if (R_FAILED(result))
FILE_ERROR(EIO, _RC);
if (dataRead == 0) {
/* directory end. return NULL value, no errno */
res = NULL;
goto file_error;
}
res = &dir->entry;
dirstr_entry_init(&dirEntry, res);
/* time */
char full_path[MAX_PATH+1];
if (!strcmp(PATH_ROOTSTR, dir->stream.path))
snprintf(full_path, MAX_PATH, "%s%s", dir->stream.path, res->d_name);
else
snprintf(full_path, MAX_PATH, "%s/%s", dir->stream.path, res->d_name);
u64 mtime;
archive_getmtime(full_path, &mtime);
/* DEBUGF("archive_getmtime(%s): %lld\n", full_path, mtime); */
uint16_t dosdate, dostime;
dostime_localtime(mtime, &dosdate, &dostime);
res->info.wrtdate = dosdate;
res->info.wrttime = dostime;
file_error:
RELEASE_DIRSTR(READER, dir);
return res;
}
/* make a directory */
int ctru_mkdir(const char *path)
{
logf("mkdir(path=\"%s\")\n", path);
int rc;
file_internal_lock_WRITER();
Result res = FSUSER_CreateDirectory(sdmcArchive,
fsMakePath(PATH_ASCII, path),
0);
if (R_FAILED(res))
FILE_ERROR(ERRNO, -1);
rc = 0;
file_error:
file_internal_unlock_WRITER();
return rc;
}
/* remove a directory */
int ctru_rmdir(const char *name)
{
logf("rmdir(name=\"%s\")\n", name);
int rc;
if (name)
{
/* path may not end with "." */
const char *basename;
size_t len = path_basename(name, &basename);
if (basename[0] == '.' && len == 1)
{
logf("Invalid path; last component is \".\"\n");
FILE_ERROR_RETURN(EINVAL, -9);
}
}
file_internal_lock_WRITER();
Result res = FSUSER_DeleteDirectory(sdmcArchive,
fsMakePath(PATH_ASCII, name));
if (R_FAILED(res))
FILE_ERROR(ERRNO, -1);
rc = 0;
file_error:
file_internal_unlock_WRITER();
return rc;
}
/** Extended interface **/
/* return if two directory streams refer to the same directory */
int ctru_samedir(DIR *dirp1, DIR *dirp2)
{
struct dirstr_desc * const dir1 = GET_DIRSTR(WRITER, dirp1);
if (!dir1)
FILE_ERROR_RETURN(ERRNO, -1);
int rc = -2;
struct dirstr_desc * const dir2 = get_dirstr(dirp2);
if (dir2) {
rc = strcmp(dir1->stream.path, dir2->stream.path) == 0 ? 1 : 0;
}
RELEASE_DIRSTR(WRITER, dir1);
return rc;
}
/* test directory existence (returns 'false' if a file) */
bool ctru_dir_exists(const char *dirname)
{
file_internal_lock_WRITER();
int rc;
Handle handle;
Result res = FSUSER_OpenDirectory(&handle,
sdmcArchive,
fsMakePath(PATH_ASCII, dirname));
if (R_FAILED(res)) {
logf("Directory not found: %ld\n", fs_error());
FILE_ERROR(EMFILE, -1);
}
rc = 0;
file_error:
if (rc == 0) {
FSDIR_Close(handle);
}
file_internal_unlock_WRITER();
return rc == 0 ? true : false;
}
/* get the portable info from the native entry */
struct dirinfo dir_get_info(DIR *dirp, struct dirent *entry)
{
int rc;
if (!dirp || !entry)
FILE_ERROR(EFAULT, _RC);
if (entry->d_name[0] == '\0')
FILE_ERROR(ENOENT, _RC);
if ((file_size_t)entry->info.size > FILE_SIZE_MAX)
FILE_ERROR(EOVERFLOW, _RC);
return (struct dirinfo)
{
.attribute = entry->info.attr,
.size = entry->info.size,
.mtime = dostime_mktime(entry->info.wrtdate, entry->info.wrttime),
};
file_error:
return (struct dirinfo){ .attribute = 0 };
}
const char* ctru_root_realpath(void)
{
/* Native only, for APP and SIM see respective filesystem-.c files */
return PATH_ROOTSTR; /* rb_namespace.c */
}

View file

@ -0,0 +1,777 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2025 Mauricio G.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#define RB_FILESYSTEM_OS
#include "config.h"
#include "system.h"
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <utime.h>
#include "file.h"
#include "debug.h"
#include "string-extra.h"
#include "fs_defines.h"
#include "sys_file.h"
/* This file is based on firmware/common/file.c */
/* Define LOGF_ENABLE to enable logf output in this file */
// #define LOGF_ENABLE
#include "logf.h"
/**
* These functions provide a roughly POSIX-compatible file I/O API.
* Important: the bufferio I/O library (bfile) used in the 3ds does not work
* with write-only files due to an internal limitation.
* So all files will be opened with the read flag by default.
*/
/* structure used for open file descriptors */
static struct filestr_desc
{
struct filestr_base stream; /* basic stream info (first!) */
file_size_t offset; /* current offset for stream */
u64 *sizep; /* shortcut to file size in fileobj */
} open_streams[MAX_OPEN_FILES] =
{
[0 ... MAX_OPEN_FILES-1] = { .stream = { .cache = nil, .flags = 0 } }
};
extern FS_Archive sdmcArchive;
/* check and return a struct filestr_desc* from a file descriptor number */
static struct filestr_desc * get_filestr(int fildes)
{
struct filestr_desc *file = &open_streams[fildes];
if ((unsigned int)fildes >= MAX_OPEN_FILES)
file = NULL;
else if (file->stream.cache != nil)
return file;
logf("fildes %d: bad file number\n", fildes);
errno = (file && (file->stream.cache == nil)) ? ENXIO : EBADF;
return NULL;
}
#define GET_FILESTR(type, fildes) \
({ \
file_internal_lock_##type(); \
struct filestr_desc * _file = get_filestr(fildes); \
if (_file) \
FILESTR_LOCK(type, &_file->stream); \
else { \
file_internal_unlock_##type(); \
}\
_file; \
})
/* release the lock on the filestr_desc* */
#define RELEASE_FILESTR(type, file) \
({ \
FILESTR_UNLOCK(type, &(file)->stream); \
file_internal_unlock_##type(); \
})
/* find a free file descriptor */
static int alloc_filestr(struct filestr_desc **filep)
{
for (int fildes = 0; fildes < MAX_OPEN_FILES; fildes++)
{
struct filestr_desc *file = &open_streams[fildes];
if (file->stream.cache == nil)
{
*filep = file;
return fildes;
}
}
logf("Too many files open\n");
return -1;
}
/* check for file existence */
int test_stream_exists_internal(const char *path)
{
int rc;
bool is_dir = false;
Handle handle;
Result res = FSUSER_OpenFile(&handle,
sdmcArchive,
fsMakePath(PATH_ASCII, path),
FS_OPEN_READ,
0);
if (R_FAILED(res)) {
/* not a file, try to open a directory */
res = FSUSER_OpenDirectory(&handle,
sdmcArchive,
fsMakePath(PATH_ASCII, path));
if (R_FAILED(res)) {
logf("File does not exist\n");
FILE_ERROR(ERRNO, -1);
}
is_dir = true;
}
rc = 1;
file_error:
if (handle > 0) {
if (is_dir)
FSDIR_Close(handle);
else
FSFILE_Close(handle);
}
return rc;
}
/* set the file pointer */
static off_t lseek_internal(struct filestr_desc *file, off_t offset,
int whence)
{
off_t rc;
off_t pos;
off_t size = MIN(*file->sizep, FILE_SIZE_MAX);
off_t file_offset = AtomicLoad(&file->offset);
switch (whence)
{
case SEEK_SET:
if (offset < 0 || (off_t)offset > size)
FILE_ERROR(EINVAL, -1);
pos = offset;
break;
case SEEK_CUR:
if ((offset < 0 && (off_t)-offset > file_offset) ||
(offset > 0 && (off_t)offset > size - file_offset))
FILE_ERROR(EINVAL, -1);
pos = file_offset + offset;
break;
case SEEK_END:
if (offset > 0 || (off_t)-offset > size)
FILE_ERROR(EINVAL, -1);
pos = size + offset;
break;
default:
FILE_ERROR(EINVAL, -1);
}
AtomicSwap(&file->offset, pos);
return pos;
file_error:
return rc;
}
/* read from or write to the file; back end to read() and write() */
static ssize_t readwrite(struct filestr_desc *file, void *buf, size_t nbyte,
bool write)
{
#ifndef LOGF_ENABLE /* wipes out log before you can save it */
/* DEBUGF("readwrite(%p,%lx,%lu,%s)\n",
file, (long)buf, (unsigned long)nbyte, write ? "write" : "read"); */
#endif
const file_size_t size = *file->sizep;
size_t filerem;
if (write)
{
/* if opened in append mode, move pointer to end */
if (file->stream.flags & O_APPEND)
AtomicSwap(&file->offset, MIN(size, FILE_SIZE_MAX));
filerem = FILE_SIZE_MAX - AtomicLoad(&file->offset);
}
else
{
/* limit to maximum possible offset (EOF or FILE_SIZE_MAX) */
filerem = MIN(size, FILE_SIZE_MAX) - AtomicLoad(&file->offset);
}
if (nbyte > filerem)
{
nbyte = filerem;
if (nbyte > 0)
{}
else if (write)
FILE_ERROR_RETURN(EFBIG, -1); /* would get too large */
else if (AtomicLoad(&file->offset) >= FILE_SIZE_MAX)
FILE_ERROR_RETURN(EOVERFLOW, -2); /* can't read here */
}
if (nbyte == 0)
return 0;
int rc = 0;
int_error_t n_err;
if (write)
n_err = PagerWriteAt(file->stream.cache, (u8 *) buf, nbyte, AtomicLoad(&file->offset));
else
n_err = PagerReadAt(file->stream.cache, (u8 *) buf, nbyte, AtomicLoad(&file->offset));
if ((n_err.err != nil) && strcmp(n_err.err, "io.EOF")) {
FILE_ERROR(ERRNO, -3);
}
file_error:;
#ifdef DEBUG
if (errno == ENOSPC)
logf("No space left on device\n");
#endif
size_t done = n_err.n;
if (done)
{
/* error or not, update the file offset and size if anything was
transferred */
AtomicAdd(&file->offset, done);
#ifndef LOGF_ENABLE /* wipes out log before you can save it */
/* DEBUGF("file offset: %lld\n", file->offset); */
#endif
/* adjust file size to length written */
if (write && AtomicLoad(&file->offset) > size)
*file->sizep = AtomicLoad(&file->offset);
return done;
}
return rc;
}
/* initialize the base descriptor */
static void filestr_base_init(struct filestr_base *stream)
{
stream->cache = nil;
stream->handle = 0;
stream->size = 0;
LightLock_Init(&stream->mtx);
}
int open_internal_inner2(Handle *handle, const char *path, u32 openFlags, u32 attributes)
{
int rc;
Result res = FSUSER_OpenFile(handle,
sdmcArchive,
fsMakePath(PATH_ASCII, path),
openFlags,
attributes);
if (R_FAILED(res)) {
FILE_ERROR(ERRNO, -1);
}
rc = 1;
file_error:
return rc;
}
static int open_internal_inner1(const char *path, int oflag)
{
int rc;
struct filestr_desc *file;
int fildes = alloc_filestr(&file);
if (fildes < 0)
FILE_ERROR_RETURN(EMFILE, -1);
u32 openFlags = 0, attributes = 0;
/* open for reading by default */
openFlags = FS_OPEN_READ;
if (oflag & O_ACCMODE)
{
if ((oflag & O_ACCMODE) == O_RDONLY) {
attributes |= FS_ATTRIBUTE_READ_ONLY;
}
if ((oflag & O_ACCMODE) == O_WRONLY) {
openFlags |= FS_OPEN_WRITE;
}
if ((oflag & O_ACCMODE) == O_RDWR) {
openFlags |= FS_OPEN_WRITE;
}
}
else if (oflag & O_TRUNC)
{
/* O_TRUNC requires write mode */
logf("No write mode but have O_TRUNC\n");
FILE_ERROR(EINVAL, -2);
}
/* O_CREAT and O_APPEND are fine without write mode
* for the former, an empty file is created but no data may be written
* for the latter, no append will be allowed anyway */
if (!(oflag & O_CREAT))
oflag &= ~O_EXCL; /* result is undefined: we choose "ignore" */
filestr_base_init(&file->stream);
rc = open_internal_inner2(&file->stream.handle, path, openFlags, attributes);
if (rc > 0) {
if (oflag & O_EXCL)
{
logf("File exists\n");
FILE_ERROR(EEXIST, -4);
}
}
else if (oflag & O_CREAT)
{
/* not found; try to create it */
openFlags |= FS_OPEN_CREATE;
rc = open_internal_inner2(&file->stream.handle, path, openFlags, attributes);
if (rc < 0)
FILE_ERROR(ERRNO, rc * 10 - 6);
}
else
{
logf("File not found\n");
FILE_ERROR(ENOENT, -5);
}
/* truncate file if requested */
if (oflag & O_TRUNC) {
Result res = FSFILE_SetSize(file->stream.handle, 0);
if (R_FAILED(res)) {
FILE_ERROR(ERRNO, -6);
}
}
/* we need to set file size here, or else lseek
will fail if no read or write has been done */
u64 size = 0;
Result res = FSFILE_GetSize(file->stream.handle, &size);
if (R_FAILED(res)) {
FILE_ERROR(ERRNO, -8);
}
int pageSize = 4096; /* 4096 bytes */
int bufferSize = 512 * 1024; /* 512 kB */
/* streamed file formats like flac and mp3 need very large page
sizes to avoid stuttering */
if (((oflag & O_ACCMODE) == O_RDONLY) && (size > 0x200000)) {
/* printf("open(%s)_BIG_pageSize\n", path); */
pageSize = 32 * 1024;
bufferSize = MIN(size, defaultBufferSize);
}
file->stream.cache = NewPagerSize(file->stream.handle,
pageSize,
bufferSize);
if (file->stream.cache == nil) {
FILE_ERROR(ERRNO, -7);
}
file->stream.flags = oflag;
file->stream.size = size;
file->sizep = &file->stream.size;
AtomicSwap(&file->offset, 0);
/* we will use file path to implement ctru_fsamefile function */
strcpy(file->stream.path, path);
return fildes;
file_error:
if (fildes >= 0) {
if (file->stream.cache != nil) {
PagerFlush(file->stream.cache);
PagerClear(file->stream.cache);
file->stream.cache = nil;
}
FSFILE_Close(file->stream.handle);
file->stream.handle = 0;
}
return rc;
}
static int open_internal_locked(const char *path, int oflag)
{
file_internal_lock_WRITER();
int rc = open_internal_inner1(path, oflag);
file_internal_unlock_WRITER();
return rc;
}
int ctru_open(const char *path, int oflag, ...)
{
logf("open(path=\"%s\",oflag=%X)\n", path, (unsigned)oflag);
return open_internal_locked(path, oflag);
}
int ctru_creat(const char *path, mode_t mode)
{
logf("creat(path=\"%s\")\n", path);
return ctru_open(path, O_WRONLY|O_CREAT|O_TRUNC, mode);
}
int ctru_close(int fildes)
{
logf("close(fd=%d)\n", fildes);
int rc;
file_internal_lock_WRITER();
/* needs to work even if marked "nonexistant" */
struct filestr_desc *file = &open_streams[fildes];
if ((unsigned int)fildes >= MAX_OPEN_FILES || (file->stream.cache == nil))
{
logf("filedes %d not open\n", fildes);
FILE_ERROR(EBADF, -2);
}
if (file->stream.cache != nil) {
PagerFlush(file->stream.cache);
PagerClear(file->stream.cache);
file->stream.cache = nil;
}
FSFILE_Close(file->stream.handle);
file->stream.handle = 0;
file->stream.path[0] = '\0';
rc = 0;
file_error:
file_internal_unlock_WRITER();
return rc;
}
/* truncate a file to a specified length */
int ctru_ftruncate(int fildes, off_t length)
{
logf("ftruncate(fd=%d,len=%ld)\n", fildes, (long)length);
struct filestr_desc * const file = GET_FILESTR(READER, fildes);
if (!file)
FILE_ERROR_RETURN(ERRNO, -1);
int rc;
if (file->stream.flags & O_RDONLY)
{
logf("Descriptor is read-only mode\n");
FILE_ERROR(EBADF, -2);
}
if (length < 0)
{
logf("Length %ld is invalid\n", (long)length);
FILE_ERROR(EINVAL, -3);
}
file_error_t err = PagerTruncate(file->stream.cache, length);
if (err) {
FILE_ERROR(ERRNO, -11);
}
*file->sizep = length;
rc = 0;
file_error:
RELEASE_FILESTR(READER, file);
return rc;
}
/* synchronize changes to a file */
int ctru_fsync(int fildes)
{
logf("fsync(fd=%d)\n", fildes);
struct filestr_desc * const file = GET_FILESTR(WRITER, fildes);
if (!file)
FILE_ERROR_RETURN(ERRNO, -1);
int rc;
if (file->stream.flags & O_RDONLY)
{
logf("Descriptor is read-only mode\n");
FILE_ERROR(EINVAL, -2);
}
/* flush all pending changes to disk */
file_error_t err = PagerFlush(file->stream.cache);
if (err != nil) {
FILE_ERROR(ERRNO, -3);
}
rc = 0;
file_error:
RELEASE_FILESTR(WRITER, file);
return rc;
}
/* move the read/write file offset */
off_t ctru_lseek(int fildes, off_t offset, int whence)
{
#ifndef LOGF_ENABLE /* wipes out log before you can save it */
/* DEBUGF("lseek(fd=%d,ofs=%ld,wh=%d)\n", fildes, (long)offset, whence); */
#endif
struct filestr_desc * const file = GET_FILESTR(READER, fildes);
if (!file)
FILE_ERROR_RETURN(ERRNO, -1);
off_t rc = lseek_internal(file, offset, whence);
if (rc < 0)
FILE_ERROR(ERRNO, rc * 10 - 2);
file_error:
RELEASE_FILESTR(READER, file);
return rc;
}
/* read from a file */
ssize_t ctru_read(int fildes, void *buf, size_t nbyte)
{
struct filestr_desc * const file = GET_FILESTR(READER, fildes);
if (!file)
FILE_ERROR_RETURN(ERRNO, -1);
ssize_t rc;
if (file->stream.flags & O_WRONLY)
{
logf("read(fd=%d,buf=%p,nb=%lu) - "
"descriptor is write-only mode\n",
fildes, buf, (unsigned long)nbyte);
FILE_ERROR(EBADF, -2);
}
rc = readwrite(file, buf, nbyte, false);
if (rc < 0)
FILE_ERROR(ERRNO, rc * 10 - 3);
file_error:
RELEASE_FILESTR(READER, file);
return rc;
}
/* write on a file */
ssize_t ctru_write(int fildes, const void *buf, size_t nbyte)
{
struct filestr_desc * const file = GET_FILESTR(READER, fildes);
if (!file)
FILE_ERROR_RETURN(ERRNO, -1);
ssize_t rc;
if (file->stream.flags & O_RDONLY)
{
logf("write(fd=%d,buf=%p,nb=%lu) - "
"descriptor is read-only mode\n",
fildes, buf, (unsigned long)nbyte);
FILE_ERROR(EBADF, -2);
}
rc = readwrite(file, (void *)buf, nbyte, true);
if (rc < 0)
FILE_ERROR(ERRNO, rc * 10 - 3);
file_error:
RELEASE_FILESTR(READER, file);
return rc;
}
/* remove a file */
int ctru_remove(const char *path)
{
logf("remove(path=\"%s\")\n", path);
int rc;
file_internal_lock_WRITER();
Result res = FSUSER_DeleteFile(sdmcArchive,
fsMakePath(PATH_ASCII, path));
if (R_FAILED(res))
FILE_ERROR(ERRNO, -1);
rc = 0;
file_error:
file_internal_unlock_WRITER();
return rc;
}
/* rename a file */
int ctru_rename(const char *old, const char *new)
{
/* note: move by rename does not work in devkitARM toolchain */
logf("rename(old=\"%s\",new=\"%s\")\n", old, new);
int rc;
/* if 'old' is a directory then 'new' is also required to be one if 'new'
is to be overwritten */
bool are_dirs = false;
file_internal_lock_WRITER();
if (!strcmp(new, old)) /* case-only is ok */
{
logf("No name change (success)\n");
rc = 0;
FILE_ERROR(ERRNO, _RC);
}
/* open 'old'; it must exist */
Handle open1rc;
Result res = FSUSER_OpenFile(&open1rc,
sdmcArchive,
fsMakePath(PATH_ASCII, old),
FS_OPEN_READ,
0);
if (R_FAILED(res)) {
/* not a file, try to open a directory */
res = FSUSER_OpenDirectory(&open1rc,
sdmcArchive,
fsMakePath(PATH_ASCII, old));
if (R_FAILED(res)) {
logf("Failed opening old\n");
FILE_ERROR(ERRNO, -1);
}
are_dirs = true;
}
if (are_dirs) {
/* rename directory */
FSUSER_RenameDirectory(sdmcArchive,
fsMakePath(PATH_ASCII, old),
sdmcArchive,
fsMakePath(PATH_ASCII, new));
}
else {
/* rename file */
FSUSER_RenameFile(sdmcArchive,
fsMakePath(PATH_ASCII, old),
sdmcArchive,
fsMakePath(PATH_ASCII, new));
}
if (R_FAILED(res)) {
logf("Rename failed\n");
FILE_ERROR(ERRNO, -2);
}
rc = 0;
file_error:
/* for now, there is nothing to fail upon closing the old stream */
if (open1rc > 0) {
if (are_dirs)
FSDIR_Close(open1rc);
else
FSFILE_Close(open1rc);
}
file_internal_unlock_WRITER();
return rc;
}
/** Extensions **/
/* todo: utime does not work in devkitARM toolchain */
int ctru_modtime(const char *path, time_t modtime)
{
struct utimbuf times =
{
.actime = modtime,
.modtime = modtime,
};
return utime(path, &times);
}
/* get the binary size of a file (in bytes) */
off_t ctru_filesize(int fildes)
{
struct filestr_desc * const file = GET_FILESTR(READER, fildes);
if (!file)
FILE_ERROR_RETURN(ERRNO, -1);
off_t rc;
file_size_t size = *file->sizep;
if (size > FILE_SIZE_MAX)
FILE_ERROR(EOVERFLOW, -2);
rc = (off_t)size;
file_error:
RELEASE_FILESTR(READER, file);
return rc;
}
/* test if two file descriptors refer to the same file */
int ctru_fsamefile(int fildes1, int fildes2)
{
struct filestr_desc * const file1 = GET_FILESTR(WRITER, fildes1);
if (!file1)
FILE_ERROR_RETURN(ERRNO, -1);
int rc = -2;
struct filestr_desc * const file2 = get_filestr(fildes2);
if (file2)
rc = strcmp(file1->stream.path, file2->stream.path) == 0 ? 1 : 0;
RELEASE_FILESTR(WRITER, file1);
return rc;
}
/* tell the relationship of path1 to path2 */
int ctru_relate(const char *path1, const char *path2)
{
/* FAT32 file system does not support symbolic links,
therefore, comparing the two full paths should be enough
to tell relationship */
logf("relate(path1=\"%s\",path2=\"%s\")\n", path1, path2);
int rc = RELATE_DIFFERENT;
if (strcmp(path1, path2) == 0)
rc = RELATE_SAME;
return rc;
}
/* test file or directory existence */
bool ctru_file_exists(const char *path)
{
file_internal_lock_WRITER();
bool rc = test_stream_exists_internal(path) > 0;
file_internal_unlock_WRITER();
return rc;
}
/* note: no symbolic links support in devkitARM */
ssize_t ctru_readlink(const char *path, char *buf, size_t bufsiz)
{
return readlink(path, buf, bufsiz);
}

View file

@ -0,0 +1,129 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2025 by Mauricio G.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#ifndef _SYS_FILE_H
#define _SYS_FILE_H
#include "bfile.h"
/* Include for file.h and dir.h because mkdir and friends may be here */
#include <sys/stat.h>
#define strlcpy_from_os strlcpy
#define AtomicLoad(ptr) __atomic_load_n((u32*)(ptr), __ATOMIC_SEQ_CST)
#define AtomicAdd(ptr, value) __atomic_add_fetch((u32*)(ptr), value, __ATOMIC_SEQ_CST)
struct filestr_base {
Pager* cache; /* buffer IO implementation (cache) */
Handle handle; /* file handle */
u64 size; /* file size */
int flags; /* stream flags */
char path[MAX_PATH+1];
LightLock mtx; /* serialization for this stream */
};
static inline void filestr_lock(struct filestr_base *stream)
{
LightLock_Lock(&stream->mtx);
}
static inline void filestr_unlock(struct filestr_base *stream)
{
LightLock_Unlock(&stream->mtx);
}
/* stream lock doesn't have to be used if getting RW lock writer access */
#define FILESTR_WRITER 0
#define FILESTR_READER 1
#define FILESTR_LOCK(type, stream) \
({ if (FILESTR_##type) filestr_lock(stream); })
#define FILESTR_UNLOCK(type, stream) \
({ if (FILESTR_##type) filestr_unlock(stream); })
/** Synchronization used throughout **/
/* acquire the filesystem lock as READER */
static inline void file_internal_lock_READER(void)
{
extern sync_RWMutex file_internal_mrsw;
sync_RWMutexRLock(&file_internal_mrsw);
}
/* release the filesystem lock as READER */
static inline void file_internal_unlock_READER(void)
{
extern sync_RWMutex file_internal_mrsw;
sync_RWMutexRUnlock(&file_internal_mrsw);
}
/* acquire the filesystem lock as WRITER */
static inline void file_internal_lock_WRITER(void)
{
extern sync_RWMutex file_internal_mrsw;
sync_RWMutexLock(&file_internal_mrsw);
}
/* release the filesystem lock as WRITER */
static inline void file_internal_unlock_WRITER(void)
{
extern sync_RWMutex file_internal_mrsw;
sync_RWMutexUnlock(&file_internal_mrsw);
}
#define ERRNO 0 /* maintain errno value */
#define _RC 0 /* maintain rc value */
/* NOTES: if _errno is a non-constant expression, it must set an error
* number and not return the ERRNO constant which will merely set
* errno to zero, not preserve the current value; if you must set
* errno to zero, set it explicitly, not in the macro
*
* if _rc is constant-expression evaluation to 'RC', then rc will
* NOT be altered; i.e. if you must set rc to zero, set it explicitly,
* not in the macro
*/
#define FILE_SET_CODE(_name, _keepcode, _value) \
({ __builtin_constant_p(_value) ? \
({ if ((_value) != (_keepcode)) _name = (_value); }) : \
({ _name = (_value); }); })
/* set errno and rc and proceed to the "file_error:" label */
#define FILE_ERROR(_errno, _rc) \
({ FILE_SET_CODE(errno, ERRNO, (_errno)); \
FILE_SET_CODE(rc, _RC, (_rc)); \
goto file_error; })
/* set errno and return a value at the point of invocation */
#define FILE_ERROR_RETURN(_errno, _rc...) \
({ FILE_SET_CODE(errno, ERRNO, _errno); \
return _rc; })
/* set errno and return code, no branching */
#define FILE_ERROR_SET(_errno, _rc) \
({ FILE_SET_CODE(errno, ERRNO, (_errno)); \
FILE_SET_CODE(rc, _RC, (_rc)); })
#endif /* _SYS_FILE_H */

View file

@ -0,0 +1,403 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2025 Mauricio Ga.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include <stdlib.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include "sys_thread.h"
#include "sys_timer.h"
#include "debug.h"
#include "logf.h"
bool _AtomicCAS(u32 *ptr, int oldval, int newval)
{
int expected = oldval;
int desired = newval;
return __atomic_compare_exchange(ptr, &expected, &desired, false,
__ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
}
bool _AtomicTryLock(int *lock)
{
int result;
asm volatile(
"ldrex %0, [%2] \n"
"teq %0, #0 \n"
"strexeq %0, %1, [%2] \n"
: "=&r"(result)
: "r"(1), "r"(lock)
: "cc", "memory"
);
return result == 0;
}
#define CPUPauseInstruction() asm volatile("yield" ::: "memory")
void AtomicLock(int *lock)
{
int iterations = 0;
while (!_AtomicTryLock(lock)) {
if (iterations < 32) {
iterations++;
CPUPauseInstruction();
} else {
sys_delay(0);
}
}
}
void AtomicUnlock(int *lock)
{
*lock = 0;
}
/* Convert rockbox priority value to libctru value */
int get_ctru_thread_priority(int priority)
{
if ((priority == PRIORITY_REALTIME_1) || (priority == PRIORITY_REALTIME_2) ||
(priority == PRIORITY_REALTIME_3) || (priority == PRIORITY_REALTIME_4) ||
(priority == PRIORITY_REALTIME))
return 0x18;
else if (priority == PRIORITY_BUFFERING)
return 0x18; /* Highest */
else if ((priority == PRIORITY_USER_INTERFACE) || (priority == PRIORITY_RECORDING) ||
(priority == PRIORITY_PLAYBACK))
return 0x30;
else if (priority == PRIORITY_PLAYBACK_MAX)
return 0x2F;
else if (priority == PRIORITY_SYSTEM)
return 0x30;
else if (priority == PRIORITY_BACKGROUND)
return 0x3F; /* Lowest */
else
return 0x30;
}
static size_t get_thread_stack_size(size_t requested_size)
{
if (requested_size == 0) {
return (80 * 1024); /* 80 kB */
}
return requested_size;
}
static void thread_entry(void *arg)
{
sys_run_thread((sysThread *)arg);
threadExit(0);
}
int wait_on_semaphore_for(LightSemaphore *sem, u32 timeout)
{
u64 stop_time = sys_get_ticks64() + timeout;
u64 current_time = sys_get_ticks64();
while (current_time < stop_time) {
if (LightSemaphore_TryAcquire(sem, 1) == 0) {
return 0;
}
/* 100 microseconds seems to be the sweet spot */
svcSleepThread(100000LL);
current_time = sys_get_ticks64();
}
/* If we failed, yield to avoid starvation on busy waits */
svcSleepThread(1);
return 1;
}
int sys_sem_try_wait(LightSemaphore *sem)
{
if (LightSemaphore_TryAcquire(sem, 1) != 0) {
/* If we failed, yield to avoid starvation on busy waits */
svcSleepThread(1);
return 1;
}
return 0;
}
int sys_sem_wait_timeout(LightSemaphore *sem, u32 timeout)
{
if (timeout == (~(u32)0)) {
LightSemaphore_Acquire(sem, 1);
return 0;
}
if (LightSemaphore_TryAcquire(sem, 1) != 0) {
return wait_on_semaphore_for(sem, timeout);
}
return 0;
}
int sys_sem_wait(LightSemaphore *sem)
{
return sys_sem_wait_timeout(sem, (~(u32)0));
}
u32 sys_sem_value(LightSemaphore *sem)
{
return sem->current_count;
}
int sys_thread_id(void)
{
u32 thread_ID = 0;
svcGetThreadId(&thread_ID, CUR_THREAD_HANDLE);
return (int)thread_ID;
}
void sys_run_thread(sysThread *thread)
{
void *userdata = thread->userdata;
int(* userfunc)(void *) = thread->userfunc;
int *statusloc = &thread->status;
/* Get the thread id */
thread->threadid = sys_thread_id();
/* Run the function */
*statusloc = userfunc(userdata);
/* Mark us as ready to be joined (or detached) */
if (!AtomicCAS(&thread->state, THREAD_STATE_ALIVE, THREAD_STATE_ZOMBIE)) {
/* Clean up if something already detached us. */
if (AtomicCAS(&thread->state, THREAD_STATE_DETACHED, THREAD_STATE_CLEANED)) {
free(thread);
}
}
}
sysThread *sys_create_thread(int(*fn)(void *), const char *name, const size_t stacksize,
void *data IF_PRIO(, int priority) IF_COP(, unsigned int core))
{
sys_ticks_init();
/* Allocate memory for the thread info structure */
sysThread *thread = (sysThread *) calloc(1, sizeof(sysThread));
if (thread == NULL) {
DEBUGF("sys_create_thread: could not allocate memory\n");
return NULL;
}
thread->status = -1;
AtomicSet(&thread->state, THREAD_STATE_ALIVE);
/* Set up the arguments for the thread */
thread->userfunc = fn;
thread->userdata = data;
thread->stacksize = stacksize;
int cpu = -1;
if (name && (strncmp(name, "tagcache", 8) == 0) && R_SUCCEEDED(APT_SetAppCpuTimeLimit(30))) {
cpu = 1;
printf("thread: %s, running in cpu 1\n", name);
}
thread->handle = threadCreate(thread_entry,
thread,
get_thread_stack_size(stacksize),
get_ctru_thread_priority(priority),
cpu,
false);
if (!thread->handle) {
DEBUGF("sys_create_thread: threadCreate failed\n");
free(thread);
thread = NULL;
}
/* Everything is running now */
return thread;
}
void sys_wait_thread(sysThread *thread, int *status)
{
if (thread) {
Result res = threadJoin(thread->handle, U64_MAX);
/*
Detached threads can be waited on, but should NOT be cleaned manually
as it would result in a fatal error.
*/
if (R_SUCCEEDED(res) && AtomicGet(&thread->state) != THREAD_STATE_DETACHED) {
threadFree(thread->handle);
}
if (status) {
*status = thread->status;
}
free(thread);
}
}
void sys_detach_thread(sysThread *thread)
{
if (!thread) {
return;
}
/* Grab dibs if the state is alive+joinable. */
if (AtomicCAS(&thread->state, THREAD_STATE_ALIVE, THREAD_STATE_DETACHED)) {
threadDetach(thread->handle);
} else {
/* all other states are pretty final, see where we landed. */
const int thread_state = AtomicGet(&thread->state);
if ((thread_state == THREAD_STATE_DETACHED) || (thread_state == THREAD_STATE_CLEANED)) {
return; /* already detached (you shouldn't call this twice!) */
} else if (thread_state == THREAD_STATE_ZOMBIE) {
sys_wait_thread(thread, NULL); /* already done, clean it up. */
} else {
assert(0 && "Unexpected thread state");
}
}
}
int sys_set_thread_priority(sysThread *thread, int priority)
{
Handle h = threadGetHandle(thread->handle);
int old_priority = priority;
Result res = svcSetThreadPriority(h, get_ctru_thread_priority(priority));
if (R_SUCCEEDED(res)) {
return priority;
}
return old_priority;
}
/* sysCond */
sysCond *sys_cond_create(void)
{
sysCond *cond;
cond = (sysCond *)malloc(sizeof(sysCond));
if (cond) {
RecursiveLock_Init(&cond->lock);
LightSemaphore_Init(&cond->wait_sem, 0, ((s16)0x7FFF));
LightSemaphore_Init(&cond->wait_done, 0, ((s16)0x7FFF));
cond->waiting = cond->signals = 0;
} else {
DEBUGF("sys_cond_create: out of memory.\n");;
}
return cond;
}
/* Destroy a condition variable */
void sys_cond_destroy(sysCond *cond)
{
if (cond) {
free(cond);
}
}
/* Restart one of the threads that are waiting on the condition variable */
int sys_cond_signal(sysCond *cond)
{
if (!cond) {
DEBUGF("sys_cond_signal: Invalid param 'cond'\n");
return -1;
}
/* If there are waiting threads not already signalled, then
signal the condition and wait for the thread to respond.
*/
RecursiveLock_Lock(&cond->lock);
if (cond->waiting > cond->signals) {
++cond->signals;
LightSemaphore_Release(&cond->wait_sem, 1);
RecursiveLock_Unlock(&cond->lock);
LightSemaphore_Acquire(&cond->wait_done, 1);
} else {
RecursiveLock_Unlock(&cond->lock);
}
return 0;
}
/* Restart all threads that are waiting on the condition variable */
int sys_cond_broadcast(sysCond *cond)
{
if (!cond) {
DEBUGF("sys_cond_signal: Invalid param 'cond'\n");
return -1;
}
/* If there are waiting threads not already signalled, then
signal the condition and wait for the thread to respond.
*/
RecursiveLock_Lock(&cond->lock);
if (cond->waiting > cond->signals) {
int i, num_waiting;
num_waiting = (cond->waiting - cond->signals);
cond->signals = cond->waiting;
for (i = 0; i < num_waiting; ++i) {
LightSemaphore_Release(&cond->wait_sem, 1);
}
/* Now all released threads are blocked here, waiting for us.
Collect them all (and win fabulous prizes!) :-)
*/
RecursiveLock_Unlock(&cond->lock);
for (i = 0; i < num_waiting; ++i) {
LightSemaphore_Acquire(&cond->wait_done, 1);
}
} else {
RecursiveLock_Unlock(&cond->lock);
}
return 0;
}
int sys_cond_wait(sysCond *cond, RecursiveLock *mutex)
{
if (!cond) {
DEBUGF("sys_cond_signal: Invalid param 'cond'\n");
return -1;
}
RecursiveLock_Lock(&cond->lock);
++cond->waiting;
RecursiveLock_Unlock(&cond->lock);
/* Unlock the mutex, as is required by condition variable semantics */
RecursiveLock_Unlock(mutex);
/* Wait for a signal */
LightSemaphore_Acquire(&cond->wait_sem, 1);
RecursiveLock_Lock(&cond->lock);
if (cond->signals > 0) {
/* We always notify the signal thread that we are done */
LightSemaphore_Release(&cond->wait_done, 1);
/* Signal handshake complete */
--cond->signals;
}
--cond->waiting;
RecursiveLock_Unlock(&cond->lock);
/* Lock the mutex, as is required by condition variable semantics */
RecursiveLock_Lock(mutex);
return 0;
}

View file

@ -0,0 +1,88 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2025 Mauricio G.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#ifndef __SYSTHREAD_H__
#define __SYSTHREAD_H__
#include "thread.h"
#include <3ds/synchronization.h>
#include <3ds/thread.h>
#include <3ds/services/apt.h>
/* Complementary atomic operations */
bool _AtomicCAS(u32 *ptr, int oldval, int newval);
#define AtomicGet(ptr) __atomic_load_n((u32*)(ptr), __ATOMIC_SEQ_CST)
#define AtomicSet(ptr, value) AtomicSwap(ptr, value)
#define AtomicCAS(ptr, oldvalue, newvalue) _AtomicCAS((u32 *)(ptr), oldvalue, newvalue)
void AtomicLock(int *lock);
void AtomicUnlock(int *lock);
/* This code was taken from SDL2 thread implementation */
enum thread_state_t
{
THREAD_STATE_ALIVE,
THREAD_STATE_DETACHED,
THREAD_STATE_ZOMBIE,
THREAD_STATE_CLEANED,
};
typedef struct _thread
{
int threadid;
Thread handle;
int status;
int state;
size_t stacksize;
int(* userfunc)(void *);
void *userdata;
void *data;
} sysThread;
typedef struct _cond
{
RecursiveLock lock;
int waiting;
int signals;
LightSemaphore wait_sem;
LightSemaphore wait_done;
} sysCond;
int sys_sem_wait(LightSemaphore *sem);
int sys_sem_wait_timeout(LightSemaphore *sem, u32 timeout);
int sys_sem_try_wait(LightSemaphore *sem);
u32 sys_sem_value(LightSemaphore *sem);
sysThread *sys_create_thread(int(*fn)(void *), const char *name, const size_t stacksize,
void *data IF_PRIO(, int priority) IF_COP(, unsigned int core));
void sys_run_thread(sysThread *thread);
void sys_wait_thread(sysThread *thread, int *status);
int sys_thread_id(void);
int sys_set_thread_priority(sysThread *thread, int priority);
sysCond *sys_cond_create(void);
void sys_cond_destroy(sysCond *cond);
int sys_cond_signal(sysCond *cond);
int sys_cond_broadcast(sysCond *cond);
int sys_cond_wait(sysCond *cond, RecursiveLock *mutex);
#endif /* #ifndef __SYSTHREAD_H__ */

View file

@ -0,0 +1,402 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2006 Dan Everton
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include <stdlib.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include "debug.h"
#include "logf.h"
#include <3ds/os.h>
#include "sys_thread.h"
#include "sys_timer.h"
#define CACHELINE_SIZE 128
static bool ticks_started = false;
static u64 start_tick;
#define NSEC_PER_MSEC 1000000ULL
void sys_ticks_init(void)
{
if (ticks_started) {
return;
}
ticks_started = true;
start_tick = svcGetSystemTick();
}
void sys_ticks_quit(void)
{
ticks_started = false;
}
u64 sys_get_ticks64(void)
{
u64 elapsed;
if (!ticks_started) {
sys_ticks_init();
}
elapsed = svcGetSystemTick() - start_tick;
return elapsed / CPU_TICKS_PER_MSEC;
}
u32 sys_get_ticks(void)
{
return (u32)(sys_get_ticks64() & 0xFFFFFFFF);
}
void sys_delay(u32 ms)
{
svcSleepThread(ms * NSEC_PER_MSEC);
}
typedef struct _timer
{
int timerID;
timer_callback_ptr callback;
void *param;
u32 interval;
u32 scheduled;
int canceled;
struct _timer *next;
} sysTimer;
typedef struct _timer_map
{
int timerID;
sysTimer *timer;
struct _timer_map *next;
} timerMap;
/* The timers are kept in a sorted list */
typedef struct
{
/* Data used by the main thread */
Thread thread;
int nextID;
timerMap *timermap;
RecursiveLock timermap_lock;
/* Padding to separate cache lines between threads */
char cache_pad[CACHELINE_SIZE];
/* Data used to communicate with the timer thread */
int lock;
LightSemaphore sem;
sysTimer *pending;
sysTimer *freelist;
int active;
/* List of timers - this is only touched by the timer thread */
sysTimer *timers;
} timerData;
static timerData timer_data = { .active = 0 };
/* The idea here is that any thread might add a timer, but a single
* thread manages the active timer queue, sorted by scheduling time.
*
* Timers are removed by simply setting a canceled flag
*/
static void add_timer_interval(timerData *data, sysTimer *timer)
{
sysTimer *prev, *curr;
prev = NULL;
for (curr = data->timers; curr; prev = curr, curr = curr->next) {
if ((s32)(timer->scheduled - curr->scheduled) < 0) {
break;
}
}
/* Insert the timer here! */
if (prev) {
prev->next = timer;
} else {
data->timers = timer;
}
timer->next = curr;
}
static void timer_thread(void *_data)
{
timerData *data = (timerData *)_data;
sysTimer *pending;
sysTimer *current;
sysTimer *freelist_head = NULL;
sysTimer *freelist_tail = NULL;
u32 tick, now, interval, delay;
/* Threaded timer loop:
* 1. Queue timers added by other threads
* 2. Handle any timers that should dispatch this cycle
* 3. Wait until next dispatch time or new timer arrives
*/
for (;;) {
/* Pending and freelist maintenance */
AtomicLock(&data->lock);
{
/* Get any timers ready to be queued */
pending = data->pending;
data->pending = NULL;
/* Make any unused timer structures available */
if (freelist_head) {
freelist_tail->next = data->freelist;
data->freelist = freelist_head;
}
}
AtomicUnlock(&data->lock);
/* Sort the pending timers into our list */
while (pending) {
current = pending;
pending = pending->next;
add_timer_interval(data, current);
}
freelist_head = NULL;
freelist_tail = NULL;
/* Check to see if we're still running, after maintenance */
if (!AtomicGet(&data->active)) {
break;
}
/* Initial delay if there are no timers */
delay = (~(u32)0);
tick = sys_get_ticks();
/* Process all the pending timers for this tick */
while (data->timers) {
current = data->timers;
if ((s32)(tick - current->scheduled) < 0) {
/* Scheduled for the future, wait a bit */
delay = (current->scheduled - tick);
break;
}
/* We're going to do something with this timer */
data->timers = current->next;
if (AtomicGet(&current->canceled)) {
interval = 0;
} else {
interval = current->callback(current->interval, current->param);
}
if (interval > 0) {
/* Reschedule this timer */
current->interval = interval;
current->scheduled = tick + interval;
add_timer_interval(data, current);
} else {
if (!freelist_head) {
freelist_head = current;
}
if (freelist_tail) {
freelist_tail->next = current;
}
freelist_tail = current;
AtomicSet(&current->canceled, 1);
}
}
/* Adjust the delay based on processing time */
now = sys_get_ticks();
interval = (now - tick);
if (interval > delay) {
delay = 0;
} else {
delay -= interval;
}
/* Note that each time a timer is added, this will return
immediately, but we process the timers added all at once.
That's okay, it just means we run through the loop a few
extra times.
*/
sys_sem_wait_timeout(&data->sem, delay);
}
}
int sys_timer_init(void)
{
timerData *data = &timer_data;
if (!AtomicGet(&data->active)) {
RecursiveLock_Init(&data->timermap_lock);
LightSemaphore_Init(&data->sem, 0, ((s16)0x7FFF));
AtomicSet(&data->active, 1);
/* Timer threads use a callback into the app, so we can't set a limited stack size here. */
data->thread = threadCreate(timer_thread,
data,
32 * 1024,
0x28,
-1,
false);
if (!data->thread) {
sys_timer_quit();
return -1;
}
AtomicSet(&data->nextID, 1);
}
return 0;
}
void sys_timer_quit(void)
{
timerData *data = &timer_data;
sysTimer *timer;
timerMap *entry;
if (AtomicCAS(&data->active, 1, 0)) { /* active? Move to inactive. */
/* Shutdown the timer thread */
if (data->thread) {
LightSemaphore_Release(&data->sem, 1);
Result res = threadJoin(data->thread, U64_MAX);
threadFree(data->thread);
data->thread = NULL;
}
/* Clean up the timer entries */
while (data->timers) {
timer = data->timers;
data->timers = timer->next;
free(timer);
}
while (data->freelist) {
timer = data->freelist;
data->freelist = timer->next;
free(timer);
}
while (data->timermap) {
entry = data->timermap;
data->timermap = entry->next;
free(entry);
}
}
}
int sys_add_timer(u32 interval, timer_callback_ptr callback, void *param)
{
timerData *data = &timer_data;
sysTimer *timer;
timerMap *entry;
AtomicLock(&data->lock);
if (!AtomicGet(&data->active)) {
if (sys_timer_init() < 0) {
AtomicUnlock(&data->lock);
return 0;
}
}
timer = data->freelist;
if (timer) {
data->freelist = timer->next;
}
AtomicUnlock(&data->lock);
if (timer) {
sys_remove_timer(timer->timerID);
} else {
timer = (sysTimer *) malloc(sizeof(*timer));
if (!timer) {
DEBUGF("sys_add_timer: out of memory\n");
return 0;
}
}
timer->timerID = AtomicIncrement(&data->nextID);
timer->callback = callback;
timer->param = param;
timer->interval = interval;
timer->scheduled = sys_get_ticks() + interval;
AtomicSet(&timer->canceled, 0);
entry = (timerMap *) malloc(sizeof(*entry));
if (!entry) {
free(timer);
DEBUGF("sys_add_timer: out of memory\n");
return 0;
}
entry->timer = timer;
entry->timerID = timer->timerID;
RecursiveLock_Lock(&data->timermap_lock);
entry->next = data->timermap;
data->timermap = entry;
RecursiveLock_Unlock(&data->timermap_lock);
/* Add the timer to the pending list for the timer thread */
AtomicLock(&data->lock);
timer->next = data->pending;
data->pending = timer;
AtomicUnlock(&data->lock);
/* Wake up the timer thread if necessary */
LightSemaphore_Release(&data->sem, 1);
return entry->timerID;
}
bool sys_remove_timer(int id)
{
timerData *data = &timer_data;
timerMap *prev, *entry;
bool canceled = false;
/* Find the timer */
RecursiveLock_Lock(&data->timermap_lock);
prev = NULL;
for (entry = data->timermap; entry; prev = entry, entry = entry->next) {
if (entry->timerID == id) {
if (prev) {
prev->next = entry->next;
} else {
data->timermap = entry->next;
}
break;
}
}
RecursiveLock_Unlock(&data->timermap_lock);
if (entry) {
if (!AtomicGet(&entry->timer->canceled)) {
AtomicSet(&entry->timer->canceled, 1);
canceled = true;
}
free(entry);
}
return canceled;
}

View file

@ -0,0 +1,42 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2025 Mauricio G.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#ifndef __SYSTIMER_H__
#define __SYSTIMER_H__
#include <3ds/svc.h>
#include "sys_thread.h"
typedef u32 (* timer_callback_ptr) (u32 interval, void *param);
void sys_ticks_init(void);
void sys_ticks_quit(void);
u32 sys_get_ticks(void);
u64 sys_get_ticks64(void);
void sys_delay(u32 ms);
int sys_timer_init(void);
void sys_timer_quit(void);
int sys_add_timer(u32 interval, timer_callback_ptr callback, void *param);
bool sys_remove_timer(int id);
#endif /* #ifndef __SYSTIMER_H__ */

View file

@ -0,0 +1,133 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2020 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
/* This file is taken from Luma3DS project */
#include <3ds.h>
#include <math.h>
#include "luminance-ctru.h"
// For accessing physmem uncached (and directly)
#define PA_PTR(addr) (void *)((u32)(addr) | 1 << 31)
#ifndef PA_FROM_VA_PTR
#define PA_FROM_VA_PTR(addr) PA_PTR(svcConvertVAToPA((const void *)(addr), false))
#endif
#define REG32(addr) (*(vu32 *)(PA_PTR(addr)))
extern bool is_n3ds;
typedef struct BlPwmData
{
float coeffs[3][3];
u8 numLevels;
u8 unk;
u16 luminanceLevels[7];
u16 brightnessMax;
u16 brightnessMin;
} BlPwmData;
// Calibration, with (dubious) default values as fallback
static BlPwmData s_blPwmData = {
.coeffs = {
{ 0.00111639f, 1.41412f, 0.07178809f },
{ 0.000418169f, 0.66567f, 0.06098654f },
{ 0.00208543f, 1.55639f, 0.0385939f }
},
.numLevels = 5,
.unk = 0,
.luminanceLevels = { 20, 43, 73, 95, 117, 172, 172 },
.brightnessMax = 512,
.brightnessMin = 13,
};
static inline float getPwmRatio(u32 brightnessMax, u32 pwmCnt)
{
u32 val = (pwmCnt & 0x10000) ? pwmCnt & 0x3FF : 511; // check pwm enabled flag
return (float)brightnessMax / (val + 1);
}
// nn's asm has rounding errors (originally at 10^-3)
static inline u32 luminanceToBrightness(u32 luminance, const float coeffs[3], u32 minLuminance, float pwmRatio)
{
float x = (float)luminance;
float y = coeffs[0]*x*x + coeffs[1]*x + coeffs[2];
y = (y <= minLuminance ? (float)minLuminance : y) / pwmRatio;
return (u32)(y + 0.5f);
}
static inline u32 brightnessToLuminance(u32 brightness, const float coeffs[3], float pwmRatio)
{
// Find polynomial root of ax^2 + bx + c = y
float y = (float)brightness * pwmRatio;
float a = coeffs[0];
float b = coeffs[1];
float c = coeffs[2] - y;
float x0 = (-b + sqrtf(b*b - 4.0f*a*c)) / (a + a);
return (u32)(x0 + 0.5f);
}
static void readCalibration(void)
{
static bool calibRead = false;
if (!calibRead) {
cfguInit();
calibRead = R_SUCCEEDED(CFG_GetConfigInfoBlk8(sizeof(BlPwmData), 0x50002, &s_blPwmData));
cfguExit();
}
}
u32 getMinLuminancePreset(void)
{
readCalibration();
return s_blPwmData.luminanceLevels[0];
}
u32 getMaxLuminancePreset(void)
{
readCalibration();
return s_blPwmData.luminanceLevels[s_blPwmData.numLevels - 1];
}
u32 getCurrentLuminance(bool top)
{
u32 regbase = top ? 0x10202200 : 0x10202A00;
readCalibration();
const float *coeffs = s_blPwmData.coeffs[top ? (is_n3ds ? 2 : 1) : 0];
u32 brightness = REG32(regbase + 0x40);
float ratio = getPwmRatio(s_blPwmData.brightnessMax, REG32(regbase + 0x44));
return brightnessToLuminance(brightness, coeffs, ratio);
}

View file

@ -0,0 +1,40 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2020 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#ifndef _LUMINANCE_CTRU_H_
#define _LUMINANCE_CTRU_H_
#include <3ds/types.h>
extern u32 ctru_min_lum;
extern u32 ctru_max_lum;
extern u32 ctru_luminance;
u32 getMinLuminancePreset(void);
u32 getMaxLuminancePreset(void);
u32 getCurrentLuminance(bool top);
#endif /* _LUMINANCE_CTRU_H_ */

View file

@ -0,0 +1,359 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2005 by Nick Lanham
* Copyright (C) 2010 by Thomas Martitz
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
#include "autoconf.h"
#include <stdlib.h>
#include <stdbool.h>
#include <stdio.h>
#include "config.h"
#include "debug.h"
#include "sound.h"
#include "audiohw.h"
#include "system.h"
#include "panic.h"
#ifdef HAVE_RECORDING
#include "audiohw.h"
#ifdef HAVE_SPDIF_IN
#include "spdif.h"
#endif
#endif
#include "pcm.h"
#include "pcm-internal.h"
#include "pcm_sampr.h"
#include "pcm_mixer.h"
#include <3ds/ndsp/ndsp.h>
#include <3ds/ndsp/channel.h>
#include <3ds/services/dsp.h>
#include <3ds/synchronization.h>
#include <3ds/allocator/linear.h>
/*#define LOGF_ENABLE*/
#include "logf.h"
#ifdef DEBUG
extern bool debug_audio;
#endif
extern const char *audiodev;
/* Bytes left in the Rockbox PCM frame buffer. */
static size_t _pcm_buffer_size = 0;
/* Rockbox PCM frame buffer. */
static const void *_pcm_buffer = NULL;
/*
1: PCM thread suspended.
0: PCM thread running.
These are used by pcm_play_[lock|unlock] or pcm_play_dma_[start|stop|pause]. These need to be
separated because of nested calls for suspending and stopping.
*/
static volatile int _dsp_enabled = 1;
static volatile int _pcm_shutdown = 0;
/* Mutex for PCM thread suspend/unsuspend. */
static RecursiveLock _pcm_lock_mtx; /* audio device mutex */
static LightEvent _dsp_callback_event; /* dsp callback synchronization flag */
static Thread _pcm_thread;
static int _pcm_thread_id = -1;
/* DSP wave buffers */
static ndspWaveBuf _dsp_wave_bufs[3];
static s16 *_dsp_audio_buffer = NULL;
static inline bool is_in_audio_thread(int audio_thread_id)
{
/* The device thread locks the same mutex, but not through the public API.
This check is in case the application, in the audio callback,
tries to lock the thread that we've already locked from the
device thread...just in case we only have non-recursive mutexes. */
if ( (sys_thread_id() == audio_thread_id)) {
return true;
}
return false;
}
void pcm_play_lock(void)
{
if (!is_in_audio_thread(_pcm_thread_id)) {
RecursiveLock_Lock(&_pcm_lock_mtx);
}
}
void pcm_play_unlock(void)
{
if (!is_in_audio_thread(_pcm_thread_id)) {
RecursiveLock_Unlock(&_pcm_lock_mtx);
}
}
static void pcm_write_to_soundcard(const void *pcm_buffer, size_t pcm_buffer_size, ndspWaveBuf *dsp_buffer)
{
s16 *buffer = dsp_buffer->data_pcm16;
memcpy(buffer, pcm_buffer, pcm_buffer_size);
dsp_buffer->nsamples = pcm_buffer_size / 2 / sizeof(s16);
ndspChnWaveBufAdd(0, dsp_buffer);
DSP_FlushDataCache(buffer, pcm_buffer_size);
}
bool fill_buffer(ndspWaveBuf *dsp_buffer)
{
if(_pcm_buffer_size == 0)
{
/* Retrive a new PCM buffer from Rockbox. */
if(!pcm_play_dma_complete_callback(PCM_DMAST_OK, &_pcm_buffer, &_pcm_buffer_size))
{
/* DEBUGF("DEBUG %s: No new buffer.\n", __func__); */
svcSleepThread(10000);
return false;
}
}
pcm_play_dma_status_callback(PCM_DMAST_STARTED);
/* This relies on Rockbox PCM frame buffer size == ALSA PCM frame buffer size. */
pcm_write_to_soundcard(_pcm_buffer, _pcm_buffer_size, dsp_buffer);
_pcm_buffer_size = 0;
return true;
}
void pcm_thread_run(void* nothing)
{
(void) nothing;
DEBUGF("DEBUG %s: Thread start.\n", __func__);
_pcm_thread_id = sys_thread_id();
while(!AtomicGet(&_pcm_shutdown))
{
RecursiveLock_Lock(&_pcm_lock_mtx);
for(size_t i = 0; i < ARRAY_SIZE(_dsp_wave_bufs); ++i) {
if(_dsp_wave_bufs[i].status != NDSP_WBUF_DONE) {
continue;
}
if(!fill_buffer(&_dsp_wave_bufs[i])) {
continue;
}
}
RecursiveLock_Unlock(&_pcm_lock_mtx);
// Wait for a signal that we're needed again before continuing,
// so that we can yield to other things that want to run
// (Note that the 3DS uses cooperative threading)
LightEvent_Wait(&_dsp_callback_event);
}
DEBUGF("DEBUG %s: Thread end.\n", __func__);
}
void dsp_callback(void *const nul_) {
(void)nul_;
if(AtomicGet(&_pcm_shutdown)) {
return;
}
LightEvent_Signal(&_dsp_callback_event);
}
static void pcm_dma_apply_settings_nolock(void)
{
ndspChnReset(0);
ndspSetOutputMode(NDSP_OUTPUT_STEREO);
ndspChnSetRate(0, pcm_sampr);
ndspChnSetFormat(0, NDSP_FORMAT_STEREO_PCM16);
/* ndspChnSetInterp(0, NDSP_INTERP_POLYPHASE); */
/* ndspChnSetInterp(0, NDSP_INTERP_NONE); */
ndspChnSetInterp(0, NDSP_INTERP_LINEAR);
float mix[12];
memset(mix, 0, sizeof(mix));
mix[0] = 1.0;
mix[1] = 1.0;
ndspChnSetMix(0, mix);
memset(_dsp_wave_bufs, 0, sizeof(_dsp_wave_bufs) * ARRAY_SIZE(_dsp_wave_bufs));
const size_t wave_buffer_size = MIX_FRAME_SAMPLES * 2 * sizeof(s16);
size_t buffer_size = wave_buffer_size * ARRAY_SIZE(_dsp_wave_bufs);
_dsp_audio_buffer = (s16 *)linearAlloc(buffer_size);
s16 *buffer = _dsp_audio_buffer;
for (unsigned i = 0; i < ARRAY_SIZE(_dsp_wave_bufs); i++) {
_dsp_wave_bufs[i].data_vaddr = buffer;
_dsp_wave_bufs[i].status = NDSP_WBUF_DONE;
buffer += wave_buffer_size / sizeof(buffer[0]);
}
ndspChnSetPaused(0, true);
ndspSetCallback(dsp_callback, NULL);
AtomicSet(&_pcm_shutdown, 0);
AtomicSet(&_dsp_enabled, 1);
// Start the thread, passing our opusFile as an argument.
_pcm_thread = threadCreate(pcm_thread_run,
NULL,
32 * 1024, /* 32kB stack size */
0x18, /* high priority */
-1, /* run on any core */ false);
}
void pcm_play_dma_start(const void *addr, size_t size)
{
_pcm_buffer = addr;
_pcm_buffer_size = size;
RecursiveLock_Lock(&_pcm_lock_mtx);
ndspChnSetPaused(0, false);
RecursiveLock_Unlock(&_pcm_lock_mtx);
}
void pcm_play_dma_stop(void)
{
RecursiveLock_Lock(&_pcm_lock_mtx);
ndspChnSetPaused(0, true);
RecursiveLock_Unlock(&_pcm_lock_mtx);
}
/* TODO: implement recording */
#ifdef HAVE_RECORDING
void pcm_rec_lock(void)
{
}
void pcm_rec_unlock(void)
{
}
void pcm_rec_dma_init(void)
{
}
void pcm_rec_dma_close(void)
{
}
void pcm_rec_dma_start(void *start, size_t size)
{
(void)start;
(void)size;
}
void pcm_rec_dma_stop(void)
{
}
const void * pcm_rec_dma_get_peak_buffer(void)
{
return NULL;
}
void audiohw_set_recvol(int left, int right, int type)
{
(void)left;
(void)right;
(void)type;
}
#ifdef HAVE_SPDIF_IN
unsigned long spdif_measure_frequency(void)
{
return 0;
}
#endif
#endif /* HAVE_RECORDING */
void pcm_play_dma_init(void)
{
Result ndsp_init_res = ndspInit();
if (R_FAILED(ndsp_init_res)) {
if ((R_SUMMARY(ndsp_init_res) == RS_NOTFOUND) && (R_MODULE(ndsp_init_res) == RM_DSP)) {
logf("DSP init failed: dspfirm.cdc missing!");
} else {
logf("DSP init failed. Error code: 0x%lX", ndsp_init_res);
}
return;
}
RecursiveLock_Init(&_pcm_lock_mtx);
LightEvent_Init(&_dsp_callback_event, RESET_ONESHOT);
}
void pcm_play_dma_postinit(void)
{
}
void pcm_dma_apply_settings(void)
{
pcm_play_lock();
pcm_dma_apply_settings_nolock();
pcm_play_unlock();
}
void pcm_close_device(void)
{
RecursiveLock_Lock(&_pcm_lock_mtx);
AtomicSet(&_pcm_shutdown, 1);
LightEvent_Signal(&_dsp_callback_event);
threadJoin(_pcm_thread, UINT64_MAX);
threadFree(_pcm_thread);
_pcm_thread_id = -1;
RecursiveLock_Unlock(&_pcm_lock_mtx);
ndspSetCallback(NULL, NULL);
ndspChnReset(0);
if (_dsp_audio_buffer != NULL) {
linearFree(_dsp_audio_buffer);
_dsp_audio_buffer = NULL;
}
ndspExit();
}
/* moved from drivers/audio/ctru.c */
void audiohw_close(void)
{
pcm_close_device();
}

View file

@ -0,0 +1,107 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
*
* Copyright (C) 2025 Mauricio G.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <stdbool.h>
#include "config.h"
#include "kernel.h"
#include "powermgmt.h"
#include "power.h"
#include "adc.h"
#include "system.h"
#include "debug.h"
#include <3ds/types.h>
#include <3ds/result.h>
#include <3ds/services/mcuhwc.h>
#include <3ds/services/ptmu.h>
void mcuhwc_init(void)
{
Result result = mcuHwcInit();
if (R_FAILED(result)) {
DEBUGF("mcuhwc_init: warning, mcuHwcInit failed\n");
}
result = ptmuInit();
if (R_FAILED(result)) {
DEBUGF("mcuhwc_init: warning, ptmuInit failed\n");
}
}
void mcuhwc_close(void)
{
mcuHwcExit();
ptmuExit();
}
/* FIXME: what level should disksafe be?*/
unsigned short battery_level_disksafe = 0;
unsigned short battery_level_shutoff = 0;
/* voltages (millivolt) of 0%, 10%, ... 100% when charging disabled */
unsigned short percent_to_volt_discharge[11] =
{
};
/* voltages (millivolt) of 0%, 10%, ... 100% when charging enabled */
unsigned short percent_to_volt_charge[11] =
{
3450, 3670, 3721, 3751, 3782, 3821, 3876, 3941, 4034, 4125, 4200
};
enum
{
BATT_NOT_CHARGING = 0,
BATT_CHARGING,
};
static u8 charging_status = BATT_NOT_CHARGING;
unsigned int power_input_status(void)
{
unsigned status = POWER_INPUT_NONE;
PTMU_GetBatteryChargeState(&charging_status);
if (charging_status == BATT_CHARGING)
status = POWER_INPUT_MAIN_CHARGER;
return status;
}
/* Returns battery voltage from MAX17040 VCELL ADC [millivolts steps],
* adc returns voltage in 1.25mV steps */
/*
* TODO this would be interesting to be mixed with battery percentage, for information
* and completition purpouses
*/
int _battery_level(void)
{
u8 level = 100;
MCUHWC_GetBatteryLevel(&level);
return level;
}
bool charging_state(void)
{
return (charging_status == BATT_CHARGING);
}

View file

@ -0,0 +1,145 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2006 by Daniel Everton <dan@iocaine.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include "system.h"
#include "kernel.h"
#include "thread-ctru.h"
#include "system-ctru.h"
#include "button-ctru.h"
#include "lcd-bitmap.h"
#include "panic.h"
#include "debug.h"
#include <3ds/types.h>
#include <3ds/allocator/linear.h>
#include <3ds/services/cfgu.h>
#include "bfile.h"
const char *audiodev = NULL;
#ifdef DEBUG
bool debug_audio = false;
#endif
/* default main thread priority, low */
s32 main_thread_priority = 0x30;
bool is_n3ds = false;
/* filesystem */
sync_RWMutex file_internal_mrsw;
FS_Archive sdmcArchive;
void ctru_sys_quit(void)
{
sys_poweroff();
}
void power_off(void)
{
/* since sim_thread_shutdown() grabs the mutex we need to let it free,
* otherwise sys_wait_thread will deadlock */
struct thread_entry* t = sim_thread_unlock();
sim_thread_shutdown();
/* lock again before entering the scheduler */
sim_thread_lock(t);
/* sim_thread_shutdown() will cause sim_do_exit() to be called via longjmp,
* but only if we let the sdl thread scheduler exit the other threads */
while(1) yield();
}
void sim_do_exit()
{
sim_kernel_shutdown();
sys_timer_quit();
/* TODO: quit_everything() */
exit(EXIT_SUCCESS);
}
uintptr_t *stackbegin;
uintptr_t *stackend;
void system_init(void)
{
/* fake stack, OS manages size (and growth) */
volatile uintptr_t stack = 0;
stackbegin = stackend = (uintptr_t*) &stack;
/* disable sleep mode when lid is closed */
aptSetSleepAllowed(false);
sys_console_init();
sys_timer_init();
svcGetThreadPriority(&main_thread_priority, CUR_THREAD_HANDLE);
if (main_thread_priority != 0x30) {
DEBUGF("warning, main_thread_priority = 0x%x\n", main_thread_priority);
}
/* check for New 3DS model */
s64 dummyInfo;
is_n3ds = svcGetSystemInfo(&dummyInfo, 0x10001, 0) == 0;
/* filesystem */
sync_RWMutexInit(&file_internal_mrsw);
Result res = FSUSER_OpenArchive(&sdmcArchive,
ARCHIVE_SDMC,
fsMakePath(PATH_ASCII, ""));
if (R_FAILED(res)) {
DEBUGF("FSUSER_OpenArchive failed\n");
exit(-1);
}
mcuhwc_init();
cfguInit();
}
void system_reboot(void)
{
sim_thread_exception_wait();
}
void system_exception_wait(void)
{
system_reboot();
}
int hostfs_init(void)
{
/* stub */
/* romfsInit(); */
return 0;
}
#ifdef HAVE_STORAGE_FLUSH
int hostfs_flush(void)
{
#ifdef __unix__
sync();
#endif
return 0;
}
#endif /* HAVE_STORAGE_FLUSH */

View file

@ -0,0 +1,70 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2025 by Mauricio G.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#ifndef _SYSTEM_SDL_H_
#define _SYSTEM_SDL_H_
#include <stdbool.h>
#include <stdbool.h>
#include "config.h"
#include "gcc_extensions.h"
#include <3ds/types.h>
#include <3ds/svc.h>
#include "sys_timer.h"
#define HIGHEST_IRQ_LEVEL 1
int set_irq_level(int level);
#define disable_irq() \
((void)set_irq_level(HIGHEST_IRQ_LEVEL))
#define enable_irq() \
((void)set_irq_level(0))
#define disable_irq_save() \
set_irq_level(HIGHEST_IRQ_LEVEL)
#define restore_irq(level) \
((void)set_irq_level(level))
#define wait_for_interrupt()
#include "system-hosted.h"
void sim_enter_irq_handler(void);
void sim_exit_irq_handler(void);
void sim_kernel_shutdown(void);
void sys_poweroff(void);
void sys_handle_argv(int argc, char *argv[]);
void gui_message_loop(void);
void sim_do_exit(void) NORETURN_ATTR;
void sdl_sys_quit(void);
void mcuhwc_init(void);
void mcuhwc_close(void);
extern bool background; /* True if the background image is enabled */
extern bool showremote;
extern double display_zoom;
extern long start_tick;
#endif /* _SYSTEM_SDL_H_ */

View file

@ -0,0 +1,4 @@
#include "system-ctru.h"
#define NEED_GENERIC_BYTESWAPS

View file

@ -0,0 +1,503 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2006 Dan Everton
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include "autoconf.h"
#include <stdbool.h>
#include <time.h>
#include <stdlib.h>
#include <string.h> /* memset() */
#include <setjmp.h>
#include "system-ctru.h"
#include "thread-ctru.h"
#include "sys_thread.h"
#include "../kernel-internal.h"
#include "core_alloc.h"
/* Define this as 1 to show informational messages that are not errors. */
#define THREAD_DEBUGF_ENABLED 0
#if THREAD_DEBUGF_ENABLED
#define THREAD_DEBUGF(...) DEBUGF(__VA_ARGS__)
static char __name[sizeof (((struct thread_debug_info *)0)->name)];
#define THREAD_GET_NAME(thread) \
({ format_thread_name(__name, sizeof (__name), thread); __name; })
#else
#define THREAD_DEBUGF(...)
#define THREAD_GET_NAME(thread)
#endif
#define THREAD_PANICF(str...) \
({ fprintf(stderr, str); exit(-1); })
/* Jump buffers for graceful exit - kernel threads don't stay neatly
* in their start routines responding to messages so this is the only
* way to get them back in there so they may exit */
static jmp_buf thread_jmpbufs[MAXTHREADS];
/* this mutex locks out other Rockbox threads while one runs,
* that enables us to simulate a cooperative environment even if
* the host is preemptive */
static RecursiveLock m;
#define THREADS_RUN 0
#define THREADS_EXIT 1
#define THREADS_EXIT_COMMAND_DONE 2
static volatile int threads_status = THREADS_RUN;
extern long start_tick;
void sim_thread_shutdown(void)
{
int i;
/* This *has* to be a push operation from a thread not in the pool
so that they may be dislodged from their blocking calls. */
/* Tell all threads jump back to their start routines, unlock and exit
gracefully - we'll check each one in turn for it's status. Threads
_could_ terminate via thread_exit or multiple threads could exit
on each unlock but that is safe. */
/* Do this before trying to acquire lock */
threads_status = THREADS_EXIT;
/* Take control */
RecursiveLock_Lock(&m);
/* Signal all threads on delay or block */
for (i = 0; i < MAXTHREADS; i++)
{
struct thread_entry *thread = __thread_slot_entry(i);
if (thread->context.s == NULL)
continue;
LightSemaphore_Release(thread->context.s, 1);
}
/* Wait for all threads to finish and cleanup old ones. */
for (i = 0; i < MAXTHREADS; i++)
{
struct thread_entry *thread = __thread_slot_entry(i);
sysThread *t = thread->context.t;
if (t != NULL)
{
RecursiveLock_Unlock(&m);
/* Wait for it to finish */
sys_wait_thread(t, NULL);
/* Relock for next thread signal */
RecursiveLock_Lock(&m);
/* Already waited and exiting thread would have waited .told,
* replacing it with t. */
thread->context.told = NULL;
}
else
{
/* Wait on any previous thread in this location-- could be one not
* quite finished exiting but has just unlocked the mutex. If it's
* NULL, the call returns immediately.
*
* See thread_exit below for more information. */
sys_wait_thread(thread->context.told, NULL);
}
}
RecursiveLock_Unlock(&m);
/* Signal completion of operation */
threads_status = THREADS_EXIT_COMMAND_DONE;
}
void sim_thread_exception_wait(void)
{
while (1)
{
sys_delay(HZ/10);
if (threads_status != THREADS_RUN)
thread_exit();
}
}
/* A way to yield and leave the threading system for extended periods */
void sim_thread_lock(void *me)
{
RecursiveLock_Lock(&m);
__running_self_entry() = (struct thread_entry *)me;
if (threads_status != THREADS_RUN)
thread_exit();
}
void * sim_thread_unlock(void)
{
struct thread_entry *current = __running_self_entry();
RecursiveLock_Unlock(&m);
return current;
}
void switch_thread(void)
{
struct thread_entry *current = __running_self_entry();
enable_irq();
switch (current->state)
{
case STATE_RUNNING:
{
RecursiveLock_Unlock(&m);
/* Any other thread waiting already will get it first */
RecursiveLock_Lock(&m);
break;
} /* STATE_RUNNING: */
case STATE_BLOCKED:
{
int oldlevel;
RecursiveLock_Unlock(&m);
sys_sem_wait(current->context.s);
RecursiveLock_Lock(&m);
oldlevel = disable_irq_save();
current->state = STATE_RUNNING;
restore_irq(oldlevel);
break;
} /* STATE_BLOCKED: */
case STATE_BLOCKED_W_TMO:
{
int result, oldlevel;
RecursiveLock_Unlock(&m);
result = sys_sem_wait_timeout(current->context.s, current->tmo_tick);
RecursiveLock_Lock(&m);
oldlevel = disable_irq_save();
current->state = STATE_RUNNING;
if (result == 1)
{
/* Other signals from an explicit wake could have been made before
* arriving here if we timed out waiting for the semaphore. Make
* sure the count is reset. */
while (sys_sem_value(current->context.s) > 0)
sys_sem_try_wait(current->context.s);
}
restore_irq(oldlevel);
break;
} /* STATE_BLOCKED_W_TMO: */
case STATE_SLEEPING:
{
RecursiveLock_Unlock(&m);
sys_sem_wait_timeout(current->context.s, current->tmo_tick);
RecursiveLock_Lock(&m);
current->state = STATE_RUNNING;
break;
} /* STATE_SLEEPING: */
}
#ifdef BUFLIB_DEBUG_CHECK_VALID
core_check_valid();
#endif
__running_self_entry() = current;
if (threads_status != THREADS_RUN)
thread_exit();
}
void sleep_thread(int ticks)
{
struct thread_entry *current = __running_self_entry();
int rem;
current->state = STATE_SLEEPING;
rem = (sys_get_ticks() - start_tick) % (1000/HZ);
if (rem < 0)
rem = 0;
current->tmo_tick = (1000/HZ) * ticks + ((1000/HZ)-1) - rem;
}
void block_thread_(struct thread_entry *current, int ticks)
{
if (ticks < 0)
current->state = STATE_BLOCKED;
else
{
current->state = STATE_BLOCKED_W_TMO;
current->tmo_tick = (1000/HZ)*ticks;
}
wait_queue_register(current);
}
unsigned int wakeup_thread_(struct thread_entry *thread
IF_PRIO(, enum wakeup_thread_protocol proto))
{
switch (thread->state)
{
case STATE_BLOCKED:
case STATE_BLOCKED_W_TMO:
wait_queue_remove(thread);
thread->state = STATE_RUNNING;
LightSemaphore_Release(thread->context.s, 1);
return THREAD_OK;
}
return THREAD_NONE;
(void) proto;
}
void thread_thaw(unsigned int thread_id)
{
struct thread_entry *thread = __thread_id_entry(thread_id);
if (thread->id == thread_id && thread->state == STATE_FROZEN)
{
thread->state = STATE_RUNNING;
LightSemaphore_Release(thread->context.s, 1);
}
}
int runthread(void *data)
{
/* Cannot access thread variables before locking the mutex as the
data structures may not be filled-in yet. */
RecursiveLock_Lock(&m);
struct thread_entry *current = (struct thread_entry *)data;
__running_self_entry() = current;
jmp_buf *current_jmpbuf = &thread_jmpbufs[THREAD_ID_SLOT(current->id)];
/* Setup jump for exit */
if (setjmp(*current_jmpbuf) == 0)
{
/* Run the thread routine */
if (current->state == STATE_FROZEN)
{
RecursiveLock_Unlock(&m);
sys_sem_wait(current->context.s);
RecursiveLock_Lock(&m);
__running_self_entry() = current;
}
if (threads_status == THREADS_RUN)
{
current->context.start();
THREAD_DEBUGF("Thread Done: %d (%s)\n",
THREAD_ID_SLOT(current->id),
THREAD_GET_NAME(current));
/* Thread routine returned - suicide */
}
thread_exit();
}
else
{
/* Unlock and exit */
RecursiveLock_Unlock(&m);
}
return 0;
}
unsigned int create_thread(void (*function)(void),
void* stack, size_t stack_size,
unsigned flags, const char *name
IF_PRIO(, int priority)
IF_COP(, unsigned int core))
{
THREAD_DEBUGF("Creating thread: (%s)\n", name ? name : "");
struct thread_entry *thread = thread_alloc();
if (thread == NULL)
{
DEBUGF("Failed to find thread slot\n");
return 0;
}
LightSemaphore *s = (LightSemaphore *) malloc(sizeof(LightSemaphore));
if (s == NULL)
{
DEBUGF("Failed to create semaphore\n");
return 0;
}
LightSemaphore_Init(s, 0, 255);
sysThread *t = sys_create_thread(runthread,
name,
stack_size,
thread
IF_PRIO(, priority)
IF_COP(, core));
if (t == NULL)
{
DEBUGF("Failed to create thread\n");
free(s);
return 0;
}
thread->name = name;
thread->state = (flags & CREATE_THREAD_FROZEN) ?
STATE_FROZEN : STATE_RUNNING;
thread->context.start = function;
thread->context.t = t;
thread->context.s = s;
thread->priority = priority;
THREAD_DEBUGF("New Thread: %lu (%s)\n",
(unsigned long)thread->id,
THREAD_GET_NAME(thread));
return thread->id;
(void)stack;
}
void thread_exit(void)
{
struct thread_entry *current = __running_self_entry();
int oldlevel = disable_irq_save();
sysThread *t = current->context.t;
LightSemaphore *s = current->context.s;
/* Wait the last thread here and keep this one or ctru will leak it since
* it doesn't free its own library allocations unless a wait is performed.
* Such behavior guards against the memory being invalid by the time
* sys_wait_thread is reached and also against two different threads having
* the same pointer. It also makes sys_wait_thread a non-concurrent function.
*
* However, see more below about sys_kill_thread.
*/
sys_wait_thread(current->context.told, NULL);
current->context.t = NULL;
current->context.s = NULL;
current->context.told = t;
unsigned int id = current->id;
new_thread_id(current);
current->state = STATE_KILLED;
wait_queue_wake(&current->queue);
free(s);
/* Do a graceful exit - perform the longjmp back into the thread
function to return */
restore_irq(oldlevel);
thread_free(current);
longjmp(thread_jmpbufs[THREAD_ID_SLOT(id)], 1);
/* This should never and must never be reached - if it is, the
* state is corrupted */
THREAD_PANICF("thread_exit->K:*R (ID: %d)", id);
while (1);
}
void thread_wait(unsigned int thread_id)
{
struct thread_entry *current = __running_self_entry();
struct thread_entry *thread = __thread_id_entry(thread_id);
if (thread->id == thread_id && thread->state != STATE_KILLED)
{
block_thread(current, TIMEOUT_BLOCK, &thread->queue, NULL);
switch_thread();
}
}
int thread_set_priority(unsigned int thread_id, int priority)
{
struct thread_entry *thread = __thread_id_entry(thread_id);
sysThread *t = thread->context.t;
thread->priority = sys_set_thread_priority(t, priority);
return thread->priority;
}
int thread_get_priority(unsigned int thread_id)
{
struct thread_entry *thread = __thread_id_entry(thread_id);
return thread->priority;
}
/* Initialize threading */
void init_threads(void)
{
RecursiveLock_Init(&m);
RecursiveLock_Lock(&m);
thread_alloc_init();
struct thread_entry *thread = thread_alloc();
if (thread == NULL)
{
fprintf(stderr, "Main thread alloc failed\n");
return;
}
/* Slot 0 is reserved for the main thread - initialize it here and
then create the thread - it is possible to have a quick, early
shutdown try to access the structure. */
thread->name = __main_thread_name;
thread->state = STATE_RUNNING;
thread->context.s = (LightSemaphore *) malloc(sizeof(LightSemaphore));
LightSemaphore_Init(thread->context.s, 0, 255);
thread->context.t = NULL; /* NULL for the implicit main thread */
__running_self_entry() = thread;
if (thread->context.s == NULL)
{
fprintf(stderr, "Failed to create main semaphore\n");
return;
}
/* Tell all threads jump back to their start routines, unlock and exit
gracefully - we'll check each one in turn for it's status. Threads
_could_ terminate via thread_exit or multiple threads could exit
on each unlock but that is safe. */
/* Setup jump for exit */
if (setjmp(thread_jmpbufs[THREAD_ID_SLOT(thread->id)]) == 0)
{
THREAD_DEBUGF("Main Thread: %lu (%s)\n",
(unsigned long)thread->id,
THREAD_GET_NAME(thread));
return;
}
RecursiveLock_Unlock(&m);
/* Set to 'COMMAND_DONE' when other rockbox threads have exited. */
while (threads_status < THREADS_EXIT_COMMAND_DONE)
sys_delay(10);
/* We're the main thead - perform exit - doesn't return. */
sim_do_exit();
}

View file

@ -0,0 +1,32 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2025 Mauricio G.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#ifndef __THREAD_CTRU_H__
#define __THREAD_CTRU_H__
/* extra thread functions that only apply when running on hosting platforms */
void sim_thread_lock(void *me);
void * sim_thread_unlock(void);
void sim_thread_exception_wait(void);
void sim_thread_shutdown(void); /* Shut down all kernel threads gracefully */
#endif /* #ifndef __THREAD_CTRU_H__ */

View file

@ -0,0 +1,61 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2025 Mauricio G.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include "sys_timer.h"
#include "timer.h"
static int timer_prio = -1;
void (*global_timer_callback)(void);
int timerId;
u32 _timer_callback(u32 interval, void *param){
(void)param;
global_timer_callback();
return(interval);
}
#define cycles_to_miliseconds(cycles) \
((int)((1000*cycles)/TIMER_FREQ))
bool timer_register(int reg_prio, void (*unregister_callback)(void),
long cycles, void (*timer_callback)(void))
{
(void)unregister_callback;
if (reg_prio <= timer_prio || cycles == 0)
return false;
timer_prio=reg_prio;
global_timer_callback=timer_callback;
timerId=sys_add_timer(cycles_to_miliseconds(cycles), _timer_callback, 0);
return true;
}
bool timer_set_period(long cycles)
{
sys_remove_timer(timerId);
timerId=sys_add_timer(cycles_to_miliseconds(cycles), _timer_callback, 0);
return true;
}
void timer_unregister(void)
{
sys_remove_timer(timerId);
timer_prio = -1;
}

View file

@ -0,0 +1,61 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2025 Mauricio G.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include "sys_timer.h"
#include "timer.h"
static int timer_prio = -1;
void (*global_timer_callback)(void);
int timerId;
u32 _timer_callback(u32 interval, void *param){
(void)param;
global_timer_callback();
return(interval);
}
#define cycles_to_miliseconds(cycles) \
((int)((1000*cycles)/TIMER_FREQ))
bool timer_register(int reg_prio, void (*unregister_callback)(void),
long cycles, void (*timer_callback)(void))
{
(void)unregister_callback;
if (reg_prio <= timer_prio || cycles == 0)
return false;
timer_prio=reg_prio;
global_timer_callback=timer_callback;
timerId=sys_add_timer(cycles_to_miliseconds(cycles), _timer_callback, 0);
return true;
}
bool timer_set_period(long cycles)
{
sys_remove_timer(timerId);
timerId=sys_add_timer(cycles_to_miliseconds(cycles), _timer_callback, 0);
return true;
}
void timer_unregister(void)
{
sys_remove_timer(timerId);
timer_prio = -1;
}

View file

@ -58,7 +58,11 @@ const char * handle_special_dirs(const char *dir, unsigned flags,
#ifdef WIN32
#include "filesystem-win32.h"
#else /* !WIN32 */
#if defined(CTRU) && !defined(SIMULATOR)
#include "filesystem-ctru.h"
#else
#include "filesystem-unix.h"
#endif
#endif /* WIN32 */
#include "filesystem-hosted.h"

View file

@ -24,7 +24,7 @@
#include <sys/time.h>
#if !defined(WIN32)
#include <sys/ioctl.h>
#if !defined(__APPLE__)
#if !defined(__APPLE__) && !defined(CTRU)
#include <linux/rtc.h>
#endif
#include <fcntl.h>
@ -49,7 +49,7 @@ int rtc_read_datetime(struct tm *tm)
int rtc_write_datetime(const struct tm *tm)
{
#if !defined(WIN32) && !defined(__APPLE__)
#if !defined(WIN32) && !defined(__APPLE__) && !defined(CTRU)
struct timeval tv;
struct tm *tm_time;

View file

@ -31,6 +31,12 @@ extern unsigned char plugin_end_addr[];
* avoid warning with certain compilers */
int _start(void) {return 0;}
#ifdef CTRU
/* dummy undefined symbols */
void __aeabi_unwind_cpp_pr0(void) {}
struct _reent * _EXFUN(__getreent, (void)) {}
#endif
enum codec_status codec_start(enum codec_entry_call_reason reason)
{
#if (CONFIG_PLATFORM & PLATFORM_NATIVE)

View file

@ -137,6 +137,10 @@ ifeq ($(ARCH),arch_arm)
$(SGCLIB) : CODECFLAGS += -O1
$(VGMLIB) : CODECFLAGS += -O1
$(WAVPACKLIB) : CODECFLAGS += -O3
ifneq (,$(findstring ctru, $(MODELNAME)))
# segfault with -O1
$(SPCLIB) : CODECFLAGS += -O2
endif
else ifeq ($(ARCH),arch_m68k)
$(CODECLIB) : CODECFLAGS += -O2
$(A52LIB) : CODECFLAGS += -O2

101
packaging/ctru/ctru.make Normal file
View file

@ -0,0 +1,101 @@
# __________ __ ___.
# Open \______ \ ____ ____ | | _\_ |__ _______ ___
# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
# \/ \/ \/ \/ \/
# $Id$
#
# 3ds_rules
export DEVKITARM ?= /opt/devkitpro/devkitARM
export DEVKITPRO ?= /opt/devkitpro
PORTLIBS := $(DEVKITPRO)/portlibs/3ds
CTRULIB ?= $(DEVKITPRO)/libctru
export PATH := $(DEVKITPRO)/portlibs/3ds/bin:$(PATH)
# base_rules
export SHELL := /usr/bin/env bash
DEVKITPATH=$(shell echo "$(DEVKITPRO)" | sed -e 's/^\([a-zA-Z]\):/\/\1/')
export PATH := $(DEVKITPATH)/tools/bin:$(DEVKITPATH)/devkitARM/bin:$(PATH)
# 3DSX
VERSION_MAJOR := 1
VERSION_MINOR := 0
VERSION_MICRO := 0
APP_TITLE := rockbox
APP_DESCRIPTION := Open Source Jukebox Firmware
APP_AUTHOR := rockbox.org
APP_ICON := $(ROOTDIR)/packaging/ctru/res/icon.png
# CIA
BANNER_AUDIO := $(ROOTDIR)/packaging/ctru/res/banner.wav
BANNER_IMAGE := $(ROOTDIR)/packaging/ctru/res/banner.cgfx
RSF_PATH := $(ROOTDIR)/packaging/ctru/res/app.rsf
#LOGO := $(ROOTDIR)/packaging/ctru/logo.lz11
UNIQUE_ID := 0xCB001
PRODUCT_CODE := CTR-ROCKBOX
ICON_FLAGS := nosavebackups,visible
# CIA Configuration
USE_ON_SD := true
APP_ENCRYPTED := false
CATEGORY := Application
USE_ON_SD := true
MEMORY_TYPE := Application
SYSTEM_MODE := 64MB
SYSTEM_MODE_EXT := Legacy
CPU_SPEED := 268MHz
ENABLE_L2_CACHE := false
.SECONDEXPANSION: # $$(OBJ) is not populated until after this
MAKEROM ?= makerom
MAKEROM_ARGS := -elf "$(BINARY).elf" -rsf "$(RSF_PATH)" -banner "$(BUILDDIR)/banner.bnr" -icon "$(BUILDDIR)/icon.icn" -DAPP_TITLE="$(APP_TITLE)" -DAPP_PRODUCT_CODE="$(PRODUCT_CODE)" -DAPP_UNIQUE_ID="$(UNIQUE_ID)" -DAPP_ENCRYPTED="$(APP_ENCRYPTED)" -DAPP_SYSTEM_MODE="$(SYSTEM_MODE)" -DAPP_SYSTEM_MODE_EXT="$(SYSTEM_MODE_EXT)" -DAPP_CATEGORY="$(CATEGORY)" -DAPP_USE_ON_SD="$(USE_ON_SD)" -DAPP_MEMORY_TYPE="$(MEMORY_TYPE)" -DAPP_CPU_SPEED="$(CPU_SPEED)" -DAPP_ENABLE_L2_CACHE="$(ENABLE_L2_CACHE)"
MAKEROM_ARGS += -major $(VERSION_MAJOR) -minor $(VERSION_MINOR) -micro $(VERSION_MICRO)
ifneq ($(strip $(LOGO)),)
MAKEROM_ARGS += -logo "$(LOGO)"
endif
ifneq ($(strip $(ROMFS)),)
MAKEROM_ARGS += -DAPP_ROMFS="$(ROMFS)"
endif
BANNERTOOL ?= bannertool
ifeq ($(suffix $(BANNER_IMAGE)),.cgfx)
BANNER_IMAGE_ARG := -ci
else
BANNER_IMAGE_ARG := -i
endif
ifeq ($(suffix $(BANNER_AUDIO)),.cwav)
BANNER_AUDIO_ARG := -ca
else
BANNER_AUDIO_ARG := -a
endif
# main binary
$(BUILDDIR)/$(BINARY): $$(OBJ) $(FIRMLIB) $(VOICESPEEXLIB) $(CORE_LIBS)
ifeq ($(UNAME), Darwin)
$(call PRINTS,LD $(BINARY))$(CC) -o $@ $^ $(LDOPTS) $(GLOBAL_LDOPTS) -Wl,$(LDMAP_OPT),$(BUILDDIR)/rockbox.map
else
$(call PRINTS,LD $(BINARY))$(CC) -o $@ -Wl,--start-group $^ -Wl,--end-group $(LDOPTS) $(GLOBAL_LDOPTS) \
-Wl,$(LDMAP_OPT),$(BUILDDIR)/rockbox-.map
@mv $(BINARY) $(BINARY).elf
smdhtool --create "$(APP_TITLE)" "$(APP_DESCRIPTION)" "$(APP_AUTHOR)" $(APP_ICON) "rockbox.smdh"
3dsxtool $(BINARY).elf $(BINARY).3dsx --smdh="rockbox.smdh"
$(BANNERTOOL) makebanner $(BANNER_IMAGE_ARG) "$(BANNER_IMAGE)" $(BANNER_AUDIO_ARG) "$(BANNER_AUDIO)" -o "$(BUILDDIR)/banner.bnr"
$(BANNERTOOL) makesmdh -s "$(APP_TITLE)" -l "$(APP_DESCRIPTION)" -p "$(APP_AUTHOR)" -i "$(APP_ICON)" -f "$(ICON_FLAGS)" -o "$(BUILDDIR)/icon.icn"
$(MAKEROM) -f cia -o "$(BINARY).cia" -target t -exefslogo $(MAKEROM_ARGS)
ifndef DEBUG
$(SILENT)rm $(BINARY).elf
endif
endif

286
packaging/ctru/res/app.rsf Normal file
View file

@ -0,0 +1,286 @@
BasicInfo:
Title : $(APP_TITLE)
ProductCode : $(APP_PRODUCT_CODE)
Logo : Homebrew
RomFs:
RootPath: $(APP_ROMFS)
TitleInfo:
Category : $(APP_CATEGORY)
UniqueId : $(APP_UNIQUE_ID)
Option:
UseOnSD : $(APP_USE_ON_SD) # true if App is to be installed to SD
FreeProductCode : true # Removes limitations on ProductCode
MediaFootPadding : false # If true CCI files are created with padding
EnableCrypt : $(APP_ENCRYPTED) # Enables encryption for NCCH and CIA
EnableCompress : true # Compresses where applicable (currently only exefs:/.code)
AccessControlInfo:
CoreVersion : 2
# Exheader Format Version
DescVersion : 2
# Minimum Required Kernel Version (below is for 4.5.0)
ReleaseKernelMajor : "02"
ReleaseKernelMinor : "33"
# ExtData
UseExtSaveData : false # enables ExtData
#ExtSaveDataId : 0x300 # only set this when the ID is different to the UniqueId
# FS:USER Archive Access Permissions
# Uncomment as required
FileSystemAccess:
- CategorySystemApplication
- CategoryHardwareCheck
- CategoryFileSystemTool
- Debug
- TwlCardBackup
- TwlNandData
- Boss
- DirectSdmc
- Core
- CtrNandRo
- CtrNandRw
- CtrNandRoWrite
- CategorySystemSettings
- CardBoard
- ExportImportIvs
- DirectSdmcWrite
- SwitchCleanup
- SaveDataMove
- Shop
- Shell
- CategoryHomeMenu
- SeedDB
IoAccessControl:
- FsMountNand
- FsMountNandRoWrite
- FsMountTwln
- FsMountWnand
- FsMountCardSpi
- UseSdif3
- CreateSeed
- UseCardSpi
# Process Settings
MemoryType : $(APP_MEMORY_TYPE) # Application/System/Base
SystemMode : $(APP_SYSTEM_MODE) # 64MB(Default)/96MB/80MB/72MB/32MB
IdealProcessor : 0
AffinityMask : 1
Priority : 16
MaxCpu : 0x9E # Default
HandleTableSize : 0x200
DisableDebug : false
EnableForceDebug : false
CanWriteSharedPage : true
CanUsePrivilegedPriority : false
CanUseNonAlphabetAndNumber : true
PermitMainFunctionArgument : true
CanShareDeviceMemory : true
RunnableOnSleep : false
SpecialMemoryArrange : true
# New3DS Exclusive Process Settings
SystemModeExt : $(APP_SYSTEM_MODE_EXT) # Legacy(Default)/124MB/178MB Legacy:Use Old3DS SystemMode
CpuSpeed : $(APP_CPU_SPEED) # 268MHz(Default)/804MHz
EnableL2Cache : $(APP_ENABLE_L2_CACHE) # false(default)/true
CanAccessCore2 : true
# Virtual Address Mappings
IORegisterMapping:
- 1ff00000-1ff7ffff # DSP memory
MemoryMapping:
- 1f000000-1f5fffff:r # VRAM
# Accessible SVCs, <Name>:<ID>
SystemCallAccess:
ControlMemory: 1
QueryMemory: 2
ExitProcess: 3
GetProcessAffinityMask: 4
SetProcessAffinityMask: 5
GetProcessIdealProcessor: 6
SetProcessIdealProcessor: 7
CreateThread: 8
ExitThread: 9
SleepThread: 10
GetThreadPriority: 11
SetThreadPriority: 12
GetThreadAffinityMask: 13
SetThreadAffinityMask: 14
GetThreadIdealProcessor: 15
SetThreadIdealProcessor: 16
GetCurrentProcessorNumber: 17
Run: 18
CreateMutex: 19
ReleaseMutex: 20
CreateSemaphore: 21
ReleaseSemaphore: 22
CreateEvent: 23
SignalEvent: 24
ClearEvent: 25
CreateTimer: 26
SetTimer: 27
CancelTimer: 28
ClearTimer: 29
CreateMemoryBlock: 30
MapMemoryBlock: 31
UnmapMemoryBlock: 32
CreateAddressArbiter: 33
ArbitrateAddress: 34
CloseHandle: 35
WaitSynchronization1: 36
WaitSynchronizationN: 37
SignalAndWait: 38
DuplicateHandle: 39
GetSystemTick: 40
GetHandleInfo: 41
GetSystemInfo: 42
GetProcessInfo: 43
GetThreadInfo: 44
ConnectToPort: 45
SendSyncRequest1: 46
SendSyncRequest2: 47
SendSyncRequest3: 48
SendSyncRequest4: 49
SendSyncRequest: 50
OpenProcess: 51
OpenThread: 52
GetProcessId: 53
GetProcessIdOfThread: 54
GetThreadId: 55
GetResourceLimit: 56
GetResourceLimitLimitValues: 57
GetResourceLimitCurrentValues: 58
GetThreadContext: 59
Break: 60
OutputDebugString: 61
ControlPerformanceCounter: 62
CreatePort: 71
CreateSessionToPort: 72
CreateSession: 73
AcceptSession: 74
ReplyAndReceive1: 75
ReplyAndReceive2: 76
ReplyAndReceive3: 77
ReplyAndReceive4: 78
ReplyAndReceive: 79
BindInterrupt: 80
UnbindInterrupt: 81
InvalidateProcessDataCache: 82
StoreProcessDataCache: 83
FlushProcessDataCache: 84
StartInterProcessDma: 85
StopDma: 86
GetDmaState: 87
RestartDma: 88
DebugActiveProcess: 96
BreakDebugProcess: 97
TerminateDebugProcess: 98
GetProcessDebugEvent: 99
ContinueDebugEvent: 100
GetProcessList: 101
GetThreadList: 102
GetDebugThreadContext: 103
SetDebugThreadContext: 104
QueryDebugProcessMemory: 105
ReadProcessMemory: 106
WriteProcessMemory: 107
SetHardwareBreakPoint: 108
GetDebugThreadParam: 109
ControlProcessMemory: 112
MapProcessMemory: 113
UnmapProcessMemory: 114
CreateCodeSet: 115
CreateProcess: 117
TerminateProcess: 118
SetProcessResourceLimits: 119
CreateResourceLimit: 120
SetResourceLimitValues: 121
AddCodeSegment: 122
Backdoor: 123
KernelSetState: 124
QueryProcessMemory: 125
# Service List
# Maximum 34 services (32 if firmware is prior to 9.6.0)
ServiceAccessControl:
- APT:U
- ac:u
- am:net
- boss:U
- cam:u
- cecd:u
- cfg:nor
- cfg:u
- csnd:SND
- dsp::DSP
- frd:u
- fs:USER
- gsp::Gpu
- gsp::Lcd
- hid:USER
- http:C
- ir:rst
- ir:u
- ir:USER
- mic:u
- mcu::HWC
- ndm:u
- news:s
- nwm::EXT
- nwm::UDS
- ptm:sysm
- ptm:u
- pxi:dev
- soc:U
- ssl:C
- y2r:u
SystemControlInfo:
SaveDataSize: 0KB # Change if the app uses savedata
RemasterVersion: $(APP_VERSION_MAJOR)
StackSize: 0x40000
# Modules that run services listed above should be included below
# Maximum 48 dependencies
# <module name>:<module titleid>
Dependency:
ac: 0x0004013000002402
#act: 0x0004013000003802
am: 0x0004013000001502
boss: 0x0004013000003402
camera: 0x0004013000001602
cecd: 0x0004013000002602
cfg: 0x0004013000001702
codec: 0x0004013000001802
csnd: 0x0004013000002702
dlp: 0x0004013000002802
dsp: 0x0004013000001a02
friends: 0x0004013000003202
gpio: 0x0004013000001b02
gsp: 0x0004013000001c02
hid: 0x0004013000001d02
http: 0x0004013000002902
i2c: 0x0004013000001e02
ir: 0x0004013000003302
mcu: 0x0004013000001f02
mic: 0x0004013000002002
ndm: 0x0004013000002b02
news: 0x0004013000003502
#nfc: 0x0004013000004002
nim: 0x0004013000002c02
nwm: 0x0004013000002d02
pdn: 0x0004013000002102
ps: 0x0004013000003102
ptm: 0x0004013000002202
#qtm: 0x0004013020004202
ro: 0x0004013000003702
socket: 0x0004013000002e02
spi: 0x0004013000002302
ssl: 0x0004013000002f02

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Binary file not shown.

BIN
packaging/ctru/res/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

View file

@ -0,0 +1,7 @@
<shortcut>
<executable>/3ds/.rockbox/rockbox.3dsx</executable>
<icon>.rockbox/icon.icn</icon>
<name>rockbox</name>
<description>Open Source Jukebox Firmware</description>
<author>rockbox.org</author>
</shortcut>

View file

@ -424,6 +424,11 @@ sub buildzip {
glob_mkdir("$temp_dir/libertas");
glob_copy("$ROOT/firmware/drivers/libertas/firmware/*", "$temp_dir/libertas/");
}
# add hbmenu shortcut's icon and 3dsx executable
if ($modelname =~ /ctru/) {
glob_copy("icon.icn", "$temp_dir/");
glob_copy("rockbox.3dsx", "$temp_dir/");
}
glob_mkdir("$temp_dir/langs");
glob_mkdir("$temp_dir/rocks");
@ -740,6 +745,18 @@ sub runone {
move(".rockbox", $rbdir);
print "mv .rockbox $rbdir\n" if $verbose;
}
# add hbmenu shortcut and cia file to zip file
if ($modelname =~ /ctru/) {
move("rockbox.cia", "3ds");
copy("$ROOT/packaging/ctru/rockbox.xml", "3ds");
system("$ziptool -u $output 3ds/rockbox.xml $target >/dev/null");
print "$ziptool $output $ROOT/packaging/ctru/rockbox.xml $target >/dev/null\n" if $verbose;
system("$ziptool -u $output 3ds/rockbox.cia $target >/dev/null");
print "$ziptool $output rockbox.cia $target >/dev/null\n" if $verbose;
}
system("$ziptool $output $rbdir $target >/dev/null");
print "$ziptool $output $rbdir $target >/dev/null\n" if $verbose;
rmtree("$rbdir");

112
tools/configure vendored
View file

@ -1009,6 +1009,90 @@ rgnanocc () {
rbdir="/FunKey/.rockbox"
}
devkitarmcc () {
if [ -z "$DEVKITPRO" ]; then
echo "ERROR: You need a devkitPro toolchain and libraries installed"
echo "and have the DEVKITPRO environment variable point to the root"
echo "of the devkitPro installation."
echo "More info at https://devkitpro.org/wiki/Getting_Started"
exit
fi
if [ -z "$DEVKITARM" ]; then
echo "ERROR: You need devkitARM toolchain installed and have the DEVKITARM"
echo "environment variable point to the root directory of the sdk."
exit
fi
# check for additional dependencies
if [ ! -e "$DEVKITPRO/portlibs/3ds/lib/libCTRL.a" ]; then
echo "ERROR: You need to install libCTRL utility library."
echo "https://github.com/kynex7510/CTRL"
exit
fi
if [ ! -e "$DEVKITPRO/portlibs/3ds/lib/libdl.a" ]; then
echo "ERROR: You need to install libdl implementation for 3ds (CTRDL)"
echo "https://github.com/kynex7510/CTRDL"
exit
fi
if [ ! -n "`findtool makerom`" ]; then
echo "ERROR: makerom not found, please install and run configure again."
echo "https://github.com/3DSGuy/Project_CTR"
exit
fi
if [ ! -n "`findtool bannertool`" ]; then
echo "ERROR: bannertool not found, please install and run configure again."
echo "https://github.com/carstene1ns/3ds-bannertool"
exit
fi
arch="arm"
arch_version="6"
arch_profile="classic"
CC=$DEVKITARM/bin/arm-none-eabi-gcc
CPP=$DEVKITARM/bin/arm-none-eabi-cpp
LD=$DEVKITARM/bin/arm-none-eabi-ld
AR=$DEVKITARM/bin/arm-none-eabi-gcc-ar
AS=$DEVKITARM/bin/arm-none-eabi-as
OC=$DEVKITARM/bin/arm-none-eabi-objcopy
WINDRES=windres
DLLTOOL=dlltool
DLLWRAP=dllwrap
RANLIB=$DEVKITARM/bin/arm-none-eabi-gcc-ranlib
if [ "yes" = "$use_debug" ]; then
GCCOPTS=`echo $GCCOPTS | sed -e s/\ -Os/\ -Og/`
fi
GCCOPTS="$GCCOPTS -fno-builtin -g -Wno-unused-result"
GCCOPTS="$GCCOPTS -I$DEVKITPRO/libctru/include -I$DEVKITPRO/portlibs/3ds/include"
GCCOPTS="$GCCOPTS -mword-relocations -ffunction-sections -march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft"
GCCOPTS="$GCCOPTS -D_GNU_SOURCE=1 -D_REENTRANT -masm-syntax-unified"
SHARED_LDFLAGS="-shared"
SHARED_CFLAGS="-fPIC -fvisibility=hidden"
LDOPTS="-specs=3dsx.specs -L$DEVKITPRO/libctru/lib -L$DEVKITPRO/portlibs/3ds/lib -ldl -lCTRL -lctru -lm"
GLOBAL_LDOPTS="$GLOBAL_LDOPTS -march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft"
# let's allow building shared libraries even if unresolved symbols are found,
# CTRDL (libdl) implementation will use a custom resolver for resolve symbols at runtime.
# TODO: use ResGen command to automatically detect unresolved symbols.
GLOBAL_LDOPTS=`echo $GLOBAL_LDOPTS | sed -e s/\-Wl,-z,defs//`
# devkitarm already defines getcwd
GLOBAL_LDOPTS="$GLOBAL_LDOPTS -Wl,-wrap,getcwd"
ARG_PREFIX="romfs"
extradefines="-D__3DS__"
rbdir="/3ds/.rockbox"
}
do_bootloader() {
appsdir='$(ROOTDIR)/bootloader'
@ -1779,6 +1863,9 @@ cat <<EOF
(hw4 bl only)
==Echo project== ==Surfans==
270) Echo R1 (WIP) 280) F28 (WIP)
==Nintendo==
290) Nintendo 3DS (WIP)
EOF
buildfor=`input`;
@ -4471,6 +4558,28 @@ fi
sysfontbl="16-Terminus"
;;
290|ctru)
target_id=122
application="yes"
modelname="ctru"
app_type="ctru-app"
target="CTRU"
memory=16
uname=`uname`
devkitarmcc
tool="cp "
boottool="cp "
bmp2rb_mono="$rootdir/tools/bmp2rb -f 0"
bmp2rb_native="$rootdir/tools/bmp2rb -f 4"
output="rockbox"
bootoutput="rockbox"
appextra="recorder:gui"
plugins="no"
t_cpu="hosted"
t_manufacturer="ctru"
t_model="app"
;;
*)
echo "Please select a supported target platform!"
exit 7
@ -5024,6 +5133,9 @@ if test -n "$t_cpu"; then
TARGET_INC="$TARGET_INC -I\$(FIRMDIR)/target/hosted"
elif [ "$t_manufacturer" = "ibasso" ]; then
TARGET_INC="$TARGET_INC -I\$(FIRMDIR)/target/hosted/ibasso/tinyalsa/include"
elif [ "$application" = "yes" ] && [ "$t_manufacturer" = "ctru" ]; then
TARGET_INC="$TARGET_INC -I\$(FIRMDIR)/target/hosted/ctru/lib"
TARGET_INC="$TARGET_INC -I\$(FIRMDIR)/target/hosted/ctru/lib/bfile"
fi
TARGET_INC="$TARGET_INC -I\$(FIRMDIR)/target/$t_cpu/$t_manufacturer"

View file

@ -40,6 +40,8 @@ ifndef APP_TYPE
objcopy = $(OC) $(if $(filter yes, $(USE_ELF)), -S -x, -O binary) $(1) $(2) # objcopy native
else ifneq (,$(findstring sdl-sim,$(APP_TYPE)))
objcopy = cp $(1) $(1).tmp;mv -f $(1).tmp $(2) # objcopy simulator
else ifneq (,$(findstring ctru,$(MODELNAME))) # 3dsxtool requires symbols
objcopy = cp $(1) $(1).tmp;mv -f $(1).tmp $(2)
else
ifdef DEBUG
objcopy = cp $(1) $(1).tmp;mv -f $(1).tmp $(2) # objcopy hosted (DEBUG)

View file

@ -171,6 +171,9 @@ else # core
ifneq (,$(findstring rgnano, $(MODELNAME)))
include $(ROOTDIR)/packaging/rgnano/rgnano.make
endif
ifneq (,$(findstring ctru, $(APP_TYPE)))
include $(ROOTDIR)/packaging/ctru/ctru.make
endif
endif # bootloader