A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd

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

authored by

Mauricio Garrido and committed by
Solomon Peachy
3b7dafb1 a4de1195

+7322
+302
apps/keymaps/keymap-ctru.c
··· 1 + /*************************************************************************** 2 + * __________ __ ___. 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * $Id: keymap-ctru.c 28704 2025-07-09 11:28:53Z gama $ 9 + * 10 + * Copyright (C) 2025 Mauricio Garrido 11 + * 12 + * This program is free software; you can redistribute it and/or 13 + * modify it under the terms of the GNU General Public License 14 + * as published by the Free Software Foundation; either version 2 15 + * of the License, or (at your option) any later version. 16 + * 17 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 + * KIND, either express or implied. 19 + * 20 + ****************************************************************************/ 21 + 22 + /* Button Code Definitions for Civic Type R (ctru) target */ 23 + 24 + #include <stdio.h> 25 + #include <string.h> 26 + #include <stdlib.h> 27 + 28 + #include "config.h" 29 + #include "action.h" 30 + #include "button.h" 31 + #include "settings.h" 32 + 33 + /* 34 + * The format of the list is as follows 35 + * { Action Code, Button code, Prereq button code } 36 + * if there's no need to check the previous button's value, use BUTTON_NONE 37 + * Insert LAST_ITEM_IN_LIST at the end of each mapping 38 + */ 39 + 40 + static const struct button_mapping button_context_standard[] = { 41 + { ACTION_STD_PREV, BUTTON_UP, BUTTON_NONE }, 42 + { ACTION_STD_PREVREPEAT, BUTTON_UP|BUTTON_REPEAT, BUTTON_NONE }, 43 + { ACTION_STD_NEXT, BUTTON_DOWN, BUTTON_NONE }, 44 + { ACTION_STD_NEXTREPEAT, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE }, 45 + 46 + { ACTION_STD_CANCEL, BUTTON_LEFT, BUTTON_NONE }, 47 + { ACTION_STD_CANCEL, BUTTON_BACK|BUTTON_REL, BUTTON_BACK }, 48 + 49 + { ACTION_STD_OK, BUTTON_SELECT|BUTTON_REL, BUTTON_SELECT }, 50 + { ACTION_STD_OK, BUTTON_RIGHT, BUTTON_NONE }, 51 + 52 + { ACTION_STD_QUICKSCREEN, BUTTON_MENU|BUTTON_REPEAT, BUTTON_NONE }, 53 + { ACTION_STD_CONTEXT, BUTTON_SELECT|BUTTON_REPEAT, BUTTON_SELECT }, 54 + { ACTION_STD_MENU, BUTTON_MENU|BUTTON_REL, BUTTON_MENU }, 55 + { ACTION_STD_CONTEXT, BUTTON_MENU|BUTTON_REL, BUTTON_NONE }, 56 + 57 + { ACTION_STD_KEYLOCK, BUTTON_USER|BUTTON_POWER, BUTTON_NONE }, 58 + 59 + LAST_ITEM_IN_LIST 60 + }; /* button_context_standard */ 61 + 62 + 63 + static const struct button_mapping button_context_mainmenu[] = { 64 + { ACTION_TREE_WPS, BUTTON_MENU|BUTTON_REL, BUTTON_MENU }, 65 + 66 + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_TREE), 67 + }; /* button_context_mainmenu */ 68 + 69 + static const struct button_mapping button_context_wps[] = { 70 + { ACTION_WPS_PLAY, BUTTON_SELECT|BUTTON_REL, BUTTON_SELECT }, 71 + { ACTION_WPS_STOP, BUTTON_POWER|BUTTON_REPEAT, BUTTON_NONE }, 72 + 73 + { ACTION_WPS_BROWSE, BUTTON_BACK|BUTTON_REL, BUTTON_BACK }, 74 + { ACTION_WPS_MENU, BUTTON_MENU|BUTTON_REL, BUTTON_MENU }, 75 + 76 + 77 + { ACTION_WPS_HOTKEY, BUTTON_USER|BUTTON_REL, BUTTON_USER }, 78 + { ACTION_WPS_PITCHSCREEN, BUTTON_USER|BUTTON_REPEAT, BUTTON_NONE }, 79 + 80 + { ACTION_STD_KEYLOCK, BUTTON_USER|BUTTON_POWER, BUTTON_NONE }, 81 + 82 + 83 + { ACTION_WPS_CONTEXT, BUTTON_SELECT|BUTTON_REPEAT, BUTTON_SELECT }, 84 + { ACTION_WPS_QUICKSCREEN, BUTTON_MENU|BUTTON_REPEAT, BUTTON_NONE }, 85 + 86 + { ACTION_WPS_SKIPPREV, BUTTON_LEFT|BUTTON_REL, BUTTON_LEFT }, 87 + { ACTION_WPS_SEEKBACK, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE }, 88 + { ACTION_WPS_STOPSEEK, BUTTON_LEFT|BUTTON_REL, BUTTON_LEFT|BUTTON_REPEAT }, 89 + 90 + { ACTION_WPS_SKIPNEXT, BUTTON_RIGHT|BUTTON_REL, BUTTON_RIGHT }, 91 + { ACTION_WPS_SEEKFWD, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE }, 92 + { ACTION_WPS_STOPSEEK, BUTTON_RIGHT|BUTTON_REL, BUTTON_RIGHT|BUTTON_REPEAT }, 93 + 94 + { ACTION_WPS_VOLUP, BUTTON_UP|BUTTON_REPEAT, BUTTON_NONE }, 95 + { ACTION_WPS_VOLUP, BUTTON_UP, BUTTON_NONE }, 96 + { ACTION_WPS_VOLDOWN, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE }, 97 + { ACTION_WPS_VOLDOWN, BUTTON_DOWN, BUTTON_NONE }, 98 + 99 + LAST_ITEM_IN_LIST 100 + }; /* button_context_wps */ 101 + 102 + static const struct button_mapping button_context_list[] = { 103 + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD) 104 + }; /* button_context_list */ 105 + 106 + static const struct button_mapping button_context_tree[] = { 107 + { ACTION_TREE_WPS, BUTTON_USER|BUTTON_REPEAT, BUTTON_USER }, 108 + { ACTION_TREE_STOP, BUTTON_POWER|BUTTON_REPEAT, BUTTON_NONE }, 109 + { ACTION_TREE_HOTKEY, BUTTON_USER|BUTTON_REL, BUTTON_NONE }, 110 + 111 + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_LIST) 112 + }; /* button_context_tree */ 113 + 114 + static const struct button_mapping button_context_settings[] = { 115 + { ACTION_SETTINGS_INC, BUTTON_UP, BUTTON_NONE }, 116 + { ACTION_SETTINGS_INCREPEAT, BUTTON_UP|BUTTON_REPEAT, BUTTON_NONE }, 117 + { ACTION_SETTINGS_DEC, BUTTON_DOWN, BUTTON_NONE }, 118 + { ACTION_SETTINGS_DECREPEAT, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE }, 119 + { ACTION_STD_PREV, BUTTON_LEFT, BUTTON_NONE }, 120 + { ACTION_STD_PREVREPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE }, 121 + { ACTION_STD_NEXT, BUTTON_RIGHT, BUTTON_NONE }, 122 + { ACTION_STD_NEXTREPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE }, 123 + 124 + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_TREE) 125 + }; /* button_context_settings */ 126 + 127 + static const struct button_mapping button_context_settings_right_is_inc[] = { 128 + { ACTION_SETTINGS_INC, BUTTON_RIGHT, BUTTON_NONE }, 129 + { ACTION_SETTINGS_INCREPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE }, 130 + { ACTION_SETTINGS_DEC, BUTTON_LEFT, BUTTON_NONE }, 131 + { ACTION_SETTINGS_DECREPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE }, 132 + 133 + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD) 134 + }; /* button_context_settings_right_is_inc */ 135 + 136 + static const struct button_mapping button_context_yesno[] = { 137 + { ACTION_YESNO_ACCEPT, BUTTON_SELECT, BUTTON_NONE }, 138 + 139 + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD) 140 + }; /* button_context_settings_yesno */ 141 + 142 + static const struct button_mapping button_context_colorchooser[] = { //check 143 + { ACTION_STD_OK, BUTTON_SELECT|BUTTON_REL, BUTTON_SELECT }, 144 + { ACTION_STD_CANCEL, BUTTON_BACK, BUTTON_NONE }, 145 + 146 + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_CUSTOM|CONTEXT_SETTINGS), 147 + }; /* button_context_colorchooser */ 148 + 149 + static const struct button_mapping button_context_eq[] = { 150 + { ACTION_STD_CANCEL, BUTTON_MENU|BUTTON_REL, BUTTON_MENU }, 151 + { ACTION_SETTINGS_INC, BUTTON_RIGHT , BUTTON_NONE }, 152 + { ACTION_SETTINGS_INCREPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE }, 153 + { ACTION_SETTINGS_DEC, BUTTON_LEFT , BUTTON_NONE }, 154 + { ACTION_SETTINGS_DECREPEAT, BUTTON_LEFT|BUTTON_REPEAT , BUTTON_NONE }, 155 + 156 + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_CUSTOM|CONTEXT_SETTINGS), 157 + }; /* button_context_eq */ 158 + 159 + /** Bookmark Screen **/ 160 + static const struct button_mapping button_context_bmark[] = { 161 + { ACTION_BMS_DELETE, BUTTON_POWER, BUTTON_NONE }, 162 + 163 + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_LIST), 164 + }; /* button_context_bmark */ 165 + 166 + static const struct button_mapping button_context_time[] = { 167 + { ACTION_SETTINGS_INC, BUTTON_UP, BUTTON_NONE }, 168 + { ACTION_SETTINGS_INCREPEAT, BUTTON_UP|BUTTON_REPEAT, BUTTON_NONE }, 169 + { ACTION_SETTINGS_DEC, BUTTON_DOWN, BUTTON_NONE }, 170 + { ACTION_SETTINGS_DECREPEAT, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE }, 171 + { ACTION_STD_PREV, BUTTON_LEFT, BUTTON_NONE }, 172 + { ACTION_STD_PREVREPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE }, 173 + { ACTION_STD_NEXT, BUTTON_RIGHT, BUTTON_NONE }, 174 + { ACTION_STD_NEXTREPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE }, 175 + 176 + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD), 177 + }; /* button_context_time */ 178 + 179 + static const struct button_mapping button_context_quickscreen[] = { 180 + { ACTION_QS_TOP, BUTTON_UP, BUTTON_NONE }, 181 + { ACTION_QS_TOP, BUTTON_UP|BUTTON_REPEAT, BUTTON_NONE }, 182 + { ACTION_QS_DOWN, BUTTON_DOWN, BUTTON_NONE }, 183 + { ACTION_QS_DOWN, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE }, 184 + { ACTION_QS_LEFT, BUTTON_LEFT, BUTTON_NONE }, 185 + { ACTION_QS_LEFT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE }, 186 + { ACTION_QS_RIGHT, BUTTON_RIGHT, BUTTON_NONE }, 187 + { ACTION_QS_RIGHT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE }, 188 + { ACTION_STD_CANCEL, BUTTON_MENU, BUTTON_NONE }, 189 + 190 + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD) 191 + }; /* button_context_quickscreen */ 192 + 193 + static const struct button_mapping button_context_pitchscreen[] = { 194 + { ACTION_PS_INC_SMALL, BUTTON_UP, BUTTON_NONE }, 195 + { ACTION_PS_INC_BIG, BUTTON_UP|BUTTON_REPEAT, BUTTON_NONE }, 196 + { ACTION_PS_DEC_SMALL, BUTTON_DOWN, BUTTON_NONE }, 197 + { ACTION_PS_DEC_BIG, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE }, 198 + 199 + { ACTION_PS_SLOWER, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE }, 200 + { ACTION_PS_FASTER, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE }, 201 + 202 + { ACTION_PS_NUDGE_LEFT, BUTTON_LEFT, BUTTON_NONE }, 203 + { ACTION_PS_NUDGE_LEFTOFF, BUTTON_LEFT|BUTTON_REL, BUTTON_NONE }, 204 + { ACTION_PS_NUDGE_RIGHT, BUTTON_RIGHT, BUTTON_NONE }, 205 + { ACTION_PS_NUDGE_RIGHTOFF, BUTTON_RIGHT|BUTTON_REL, BUTTON_NONE }, 206 + 207 + { ACTION_PS_RESET, BUTTON_SELECT, BUTTON_NONE }, 208 + { ACTION_PS_TOGGLE_MODE, BUTTON_USER, BUTTON_NONE }, 209 + { ACTION_PS_EXIT, BUTTON_MENU|BUTTON_REL, BUTTON_NONE }, 210 + { ACTION_PS_EXIT, BUTTON_BACK|BUTTON_REL, BUTTON_NONE }, 211 + 212 + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD) 213 + }; /* button_context_pitchcreen */ 214 + 215 + static const struct button_mapping button_context_keyboard[] = { 216 + { ACTION_KBD_UP, BUTTON_UP, BUTTON_NONE }, 217 + { ACTION_KBD_UP, BUTTON_UP|BUTTON_REPEAT, BUTTON_NONE }, 218 + { ACTION_KBD_DOWN, BUTTON_DOWN, BUTTON_NONE }, 219 + { ACTION_KBD_DOWN, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE }, 220 + { ACTION_KBD_LEFT, BUTTON_LEFT, BUTTON_NONE }, 221 + { ACTION_KBD_LEFT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE }, 222 + { ACTION_KBD_RIGHT, BUTTON_RIGHT, BUTTON_NONE }, 223 + { ACTION_KBD_RIGHT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE }, 224 + 225 + { ACTION_KBD_SELECT, BUTTON_SELECT, BUTTON_NONE }, 226 + { ACTION_KBD_ABORT, BUTTON_BACK|BUTTON_REL, BUTTON_BACK }, 227 + { ACTION_KBD_DONE, BUTTON_MENU|BUTTON_REL, BUTTON_MENU }, 228 + { ACTION_KBD_BACKSPACE, BUTTON_USER, BUTTON_NONE }, 229 + { ACTION_KBD_PAGE_FLIP, BUTTON_POWER, BUTTON_NONE }, 230 + 231 + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD) 232 + }; /* button_context_keyboard */ 233 + 234 + static const struct button_mapping button_context_radio[] = { 235 + { ACTION_FM_MENU, BUTTON_SELECT | BUTTON_REPEAT, BUTTON_NONE }, 236 + { ACTION_FM_PRESET, BUTTON_MENU | BUTTON_REPEAT, BUTTON_NONE }, 237 + { ACTION_FM_STOP, BUTTON_POWER | BUTTON_REPEAT, BUTTON_NONE }, 238 + { ACTION_FM_MODE, BUTTON_MENU | BUTTON_REL, BUTTON_MENU }, 239 + { ACTION_FM_EXIT, BUTTON_BACK | BUTTON_REL, BUTTON_BACK }, 240 + { ACTION_FM_PLAY, BUTTON_SELECT | BUTTON_REL, BUTTON_SELECT }, 241 + { ACTION_FM_NEXT_PRESET, BUTTON_USER | BUTTON_RIGHT, BUTTON_NONE }, 242 + { ACTION_FM_PREV_PRESET, BUTTON_USER | BUTTON_LEFT, BUTTON_NONE }, 243 + 244 + /* Volume */ 245 + { ACTION_SETTINGS_INC, BUTTON_UP | BUTTON_REPEAT, BUTTON_NONE }, 246 + { ACTION_SETTINGS_INCREPEAT, BUTTON_UP, BUTTON_NONE }, 247 + { ACTION_SETTINGS_DEC, BUTTON_DOWN | BUTTON_REPEAT, BUTTON_NONE }, 248 + { ACTION_SETTINGS_DECREPEAT, BUTTON_DOWN, BUTTON_NONE }, 249 + /* Tuning */ 250 + { ACTION_STD_PREV, BUTTON_LEFT, BUTTON_NONE }, 251 + { ACTION_STD_PREVREPEAT, BUTTON_LEFT | BUTTON_REPEAT, BUTTON_NONE }, 252 + { ACTION_STD_NEXT, BUTTON_RIGHT, BUTTON_NONE }, 253 + { ACTION_STD_NEXTREPEAT, BUTTON_RIGHT | BUTTON_REPEAT, BUTTON_NONE }, 254 + }; /* button_context_radio */ 255 + 256 + const struct button_mapping* target_get_context_mapping(int context) 257 + { 258 + switch (context & ~CONTEXT_LOCKED) 259 + { 260 + case CONTEXT_STD: 261 + return button_context_standard; 262 + case CONTEXT_WPS: 263 + return button_context_wps; 264 + 265 + case CONTEXT_LIST: 266 + return button_context_list; 267 + case CONTEXT_MAINMENU: 268 + return button_context_mainmenu; 269 + 270 + case CONTEXT_TREE: 271 + return button_context_tree; 272 + 273 + case CONTEXT_SETTINGS: 274 + return button_context_settings; 275 + 276 + case CONTEXT_CUSTOM|CONTEXT_SETTINGS: 277 + case CONTEXT_SETTINGS_RECTRIGGER: 278 + return button_context_settings_right_is_inc; 279 + 280 + case CONTEXT_SETTINGS_COLOURCHOOSER: 281 + return button_context_colorchooser; 282 + case CONTEXT_SETTINGS_EQ: 283 + return button_context_eq; 284 + 285 + case CONTEXT_SETTINGS_TIME: 286 + return button_context_time; 287 + 288 + case CONTEXT_YESNOSCREEN: 289 + return button_context_yesno; 290 + case CONTEXT_FM: 291 + return button_context_radio; 292 + case CONTEXT_BOOKMARKSCREEN: 293 + return button_context_bmark; 294 + case CONTEXT_QUICKSCREEN: 295 + return button_context_quickscreen; 296 + case CONTEXT_PITCHSCREEN: 297 + return button_context_pitchscreen; 298 + case CONTEXT_KEYBOARD: 299 + return button_context_keyboard; 300 + } 301 + return button_context_standard; 302 + }
+92
firmware/drivers/audio/ctru.c
··· 1 + /*************************************************************************** 2 + * __________ __ ___. 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * $Id$ 9 + * 10 + * Copyright © 2010 Thomas Martitz 11 + * 12 + * This program is free software; you can redistribute it and/or 13 + * modify it under the terms of the GNU General Public License 14 + * as published by the Free Software Foundation; either version 2 15 + * of the License, or (at your option) any later version. 16 + * 17 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 + * KIND, either express or implied. 19 + * 20 + ****************************************************************************/ 21 + 22 + #include "config.h" 23 + #include "sound.h" 24 + #include "pcm_sampr.h" 25 + #ifdef HAVE_SW_VOLUME_CONTROL 26 + #include "pcm_sw_volume.h" 27 + #include "fixedpoint.h" 28 + #endif 29 + 30 + /** 31 + * Audio Hardware api. Make some of them do nothing as we cannot properly 32 + * simulate with SDL. if we used DSP we would run code that doesn't actually 33 + * run on the target 34 + **/ 35 + 36 + void audiohw_set_volume(int vol_l, int vol_r) 37 + { 38 + (void)vol_l; (void)vol_r; 39 + } 40 + 41 + #if defined(AUDIOHW_HAVE_BALANCE) 42 + void audiohw_set_balance(int value) { (void)value; } 43 + #endif 44 + #ifndef HAVE_SW_TONE_CONTROLS 45 + #if defined(AUDIOHW_HAVE_BASS) 46 + void audiohw_set_bass(int value) { (void)value; } 47 + #endif 48 + #if defined(AUDIOHW_HAVE_TREBLE) 49 + void audiohw_set_treble(int value) { (void)value; } 50 + #endif 51 + #endif /* HAVE_SW_TONE_CONTROLS */ 52 + #if defined(AUDIOHW_HAVE_BASS_CUTOFF) 53 + void audiohw_set_bass_cutoff(int value) { (void)value; } 54 + #endif 55 + #if defined(AUDIOHW_HAVE_TREBLE_CUTOFF) 56 + void audiohw_set_treble_cutoff(int value){ (void)value; } 57 + #endif 58 + /* EQ-based tone controls */ 59 + #if defined(AUDIOHW_HAVE_EQ) 60 + void audiohw_set_eq_band_gain(unsigned int band, int value) 61 + { (void)band; (void)value; } 62 + #endif 63 + #if defined(AUDIOHW_HAVE_EQ_FREQUENCY) 64 + void audiohw_set_eq_band_frequency(unsigned int band, int value) 65 + { (void)band; (void)value; } 66 + #endif 67 + #if defined(AUDIOHW_HAVE_EQ_WIDTH) 68 + void audiohw_set_eq_band_width(unsigned int band, int value) 69 + { (void)band; (void)value; } 70 + #endif 71 + #if defined(AUDIOHW_HAVE_DEPTH_3D) 72 + void audiohw_set_depth_3d(int value) 73 + { (void)value; } 74 + #endif 75 + #if defined(AUDIOHW_HAVE_LINEOUT) 76 + void audiohw_set_lineout_volume(int vol_l, int vol_r) 77 + { (void)vol_l; (void)vol_r; } 78 + #endif 79 + #if defined(AUDIOHW_HAVE_FILTER_ROLL_OFF) 80 + void audiohw_set_filter_roll_off(int value) 81 + { (void)value; } 82 + #endif 83 + #if defined(AUDIOHW_HAVE_POWER_MODE) 84 + void audiohw_set_power_mode(int value) 85 + { (void)value; } 86 + #endif 87 + 88 + #ifdef CONFIG_SAMPR_TYPES 89 + unsigned int pcm_sampr_to_hw_sampr(unsigned int samplerate, 90 + unsigned int type) 91 + { return samplerate; (void)type; } 92 + #endif /* CONFIG_SAMPR_TYPES */
+102
firmware/export/config/ctru.h
··· 1 + /* 2 + * This config file is for the N3DS hosted application 3 + */ 4 + 5 + /* We don't run on hardware directly */ 6 + #define CONFIG_PLATFORM (PLATFORM_HOSTED|PLATFORM_CTRU) 7 + #define HAVE_FPU 8 + 9 + /* For Rolo and boot loader */ 10 + #define MODEL_NUMBER 100s 11 + #define MODEL_NAME "CTRU" 12 + 13 + #define USB_NONE 14 + 15 + #define CONFIG_CPU N10480H 16 + 17 + #define CPU_FREQ 268000000 18 + 19 + /* Define this if you have adjustable CPU frequency */ 20 + /* #define HAVE_ADJUSTABLE_CPU_FREQ */ 21 + 22 + /* define this if you have a colour LCD */ 23 + #define HAVE_LCD_COLOR 24 + 25 + /* define this if you want album art for this target */ 26 + #define HAVE_ALBUMART 27 + 28 + /* define this to enable bitmap scaling */ 29 + #define HAVE_BMP_SCALING 30 + 31 + /* define this to enable JPEG decoding */ 32 + #define HAVE_JPEG 33 + 34 + /* define this if you have access to the quickscreen */ 35 + #define HAVE_QUICKSCREEN 36 + 37 + /* define this if you would like tagcache to build on this target */ 38 + #define HAVE_TAGCACHE 39 + 40 + /* LCD dimensions */ 41 + #define LCD_WIDTH 320 42 + #define LCD_HEIGHT 240 43 + 44 + #define LCD_DEPTH 16 45 + #define LCD_PIXELFORMAT RGB565 46 + 47 + #define LCD_OPTIMIZED_UPDATE 48 + #define LCD_OPTIMIZED_UPDATE_RECT 49 + #define LCD_OPTIMIZED_BLIT_YUV 50 + 51 + /* define this to indicate your device's keypad */ 52 + #define HAVE_TOUCHSCREEN 53 + #define HAVE_BUTTON_DATA 54 + 55 + /* define this if you have a real-time clock */ 56 + #define CONFIG_RTC APPLICATION 57 + 58 + /* Power management */ 59 + #define CONFIG_BATTERY_MEASURE PERCENTAGE_MEASURE 60 + #define CONFIG_CHARGING CHARGING_MONITOR 61 + #define HAVE_SW_POWEROFF 62 + 63 + /* The number of bytes reserved for loadable codecs */ 64 + #define CODEC_SIZE 0x100000 65 + 66 + /* The number of bytes reserved for loadable plugins */ 67 + #define PLUGIN_BUFFER_SIZE 0x80000 68 + 69 + #define AB_REPEAT_ENABLE 70 + 71 + 72 + /* #define HAVE_SCROLLWHEEL */ 73 + #define CONFIG_KEYPAD CTRU_PAD 74 + 75 + #define HAVE_CTRU_AUDIO 76 + #define HAVE_HEADPHONE_DETECTION 77 + /* #define HAVE_SW_VOLUME_CONTROL */ 78 + /* #define PCM_SW_VOLUME_UNBUFFERED */ 79 + /* #define PCM_SW_VOLUME_FRACBITS (16) */ 80 + 81 + /* Define this for LCD backlight available */ 82 + #define HAVE_BACKLIGHT 83 + #define HAVE_BACKLIGHT_BRIGHTNESS 84 + 85 + /* Main LCD backlight brightness range and defaults */ 86 + #define MIN_BRIGHTNESS_SETTING 16 87 + #define MAX_BRIGHTNESS_SETTING 142 88 + #define BRIGHTNESS_STEP 5 89 + #define DEFAULT_BRIGHTNESS_SETTING 28 90 + #define CONFIG_BACKLIGHT_FADING BACKLIGHT_FADING_SW_SETTING 91 + 92 + #define CONFIG_LCD LCD_COWOND2 93 + 94 + /* Define this if a programmable hotkey is mapped */ 95 + #define HAVE_HOTKEY 96 + 97 + #define BOOTDIR "/" 98 + 99 + /* No special storage */ 100 + #define CONFIG_STORAGE STORAGE_HOSTFS 101 + #define HAVE_STORAGE_FLUSH 102 +
firmware/target/hosted/ctru/app/adc-target.h

This is a binary file and will not be displayed.

+32
firmware/target/hosted/ctru/app/backlight-target.h
··· 1 + /*************************************************************************** 2 + * __________ __ ___ 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * 9 + * Copyright (C) 2017 Marcin Bukat 10 + * 11 + * This program is free software; you can redistribute it and/or 12 + * modify it under the terms of the GNU General Public License 13 + * as published by the Free Software Foundation; either version 2 14 + * of the License, or (at your option) any later version. 15 + * 16 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 17 + * KIND, either express or implied. 18 + * 19 + ****************************************************************************/ 20 + 21 + #ifndef _BACKLIGHT_TARGET_H_ 22 + #define _BACKLIGHT_TARGET_H_ 23 + 24 + #include <stdbool.h> 25 + 26 + /* See backlight.c */ 27 + bool backlight_hw_init(void); 28 + void backlight_hw_on(void); 29 + void backlight_hw_off(void); 30 + void backlight_hw_brightness(int brightness); 31 + 32 + #endif
+54
firmware/target/hosted/ctru/app/button-target.h
··· 1 + /*************************************************************************** 2 + * __________ __ ___. 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * $Id$ 9 + * 10 + * Copyright (C) 2007 by Rob Purchase 11 + * 12 + * This program is free software; you can redistribute it and/or 13 + * modify it under the terms of the GNU General Public License 14 + * as published by the Free Software Foundation; either version 2 15 + * of the License, or (at your option) any later version. 16 + * 17 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 + * KIND, either express or implied. 19 + * 20 + ****************************************************************************/ 21 + 22 + #ifndef _BUTTON_TARGET_H_ 23 + #define _BUTTON_TARGET_H_ 24 + 25 + /* Logical buttons key codes */ 26 + #define BUTTON_UP 0x00000001 27 + #define BUTTON_DOWN 0x00000002 28 + #define BUTTON_LEFT 0x00000004 29 + #define BUTTON_RIGHT 0x00000008 30 + #define BUTTON_USER 0x00000010 31 + #define BUTTON_MENU 0x00000020 32 + #define BUTTON_BACK 0x00000040 33 + #define BUTTON_POWER 0x00000080 34 + #define BUTTON_SELECT 0x00000100 35 + 36 + /* Touch Screen Area Buttons */ 37 + #define BUTTON_TOPLEFT 0x00001000 38 + #define BUTTON_TOPMIDDLE 0x00002000 39 + #define BUTTON_TOPRIGHT 0x00004000 40 + #define BUTTON_MIDLEFT 0x00008000 41 + #define BUTTON_CENTER 0x00010000 42 + #define BUTTON_MIDRIGHT 0x00020000 43 + #define BUTTON_BOTTOMLEFT 0x00040000 44 + #define BUTTON_BOTTOMMIDDLE 0x00080000 45 + #define BUTTON_BOTTOMRIGHT 0x00100000 46 + 47 + #define BUTTON_MAIN 0x1FFF 48 + 49 + /* Software power-off */ 50 + #define POWEROFF_BUTTON BUTTON_POWER 51 + /* About 3 seconds */ 52 + #define POWEROFF_COUNT 10 53 + 54 + #endif /* _BUTTON_TARGET_H_ */
+114
firmware/target/hosted/ctru/backlight-ctru.c
··· 1 + /*************************************************************************** 2 + * __________ __ ___ 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * 9 + * Copyright (C) 2025 Mauricio G. 10 + * 11 + * This program is free software; you can redistribute it and/or 12 + * modify it under the terms of the GNU General Public License 13 + * as published by the Free Software Foundation; either version 2 14 + * of the License, or (at your option) any later version. 15 + * 16 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 17 + * KIND, either express or implied. 18 + * 19 + ****************************************************************************/ 20 + #include <sys/types.h> 21 + #include <sys/stat.h> 22 + #include <stdbool.h> 23 + #include <unistd.h> 24 + #include <string.h> 25 + #include <stdio.h> 26 + #include "config.h" 27 + #include "backlight-target.h" 28 + #include "sysfs.h" 29 + #include "panic.h" 30 + #include "lcd.h" 31 + #include "debug.h" 32 + 33 + #include <3ds/services/gsplcd.h> 34 + #include <3ds/result.h> 35 + #include "luminance-ctru.h" 36 + 37 + /* TODO: To use calibrated values in rockbox, 38 + MIN_BRIGHTNESS_SETTING (etc) would need to be 39 + declared as constants in settings-list.c */ 40 + 41 + u32 ctru_min_lum = 16; 42 + u32 ctru_max_lum = 142; 43 + u32 ctru_luminance = 28; 44 + 45 + /* FIXME: After calling gspLcdInit() the home menu will no longer be 46 + accesible, to fix this we have to call gspLcdInit/gspLcdExit as 47 + a mutex lock/unlock when using gsplcd.h functions. */ 48 + void lcd_mutex_lock(void) 49 + { 50 + Result res = gspLcdInit(); 51 + if (R_FAILED(res)) { 52 + DEBUGF("backlight_hw_init: gspLcdInit failed.\n"); 53 + } 54 + } 55 + 56 + void lcd_mutex_unlock(void) 57 + { 58 + gspLcdExit(); 59 + } 60 + 61 + bool backlight_hw_init(void) 62 + { 63 + /* read calibrated values */ 64 + ctru_luminance = getCurrentLuminance(false); 65 + ctru_min_lum = getMinLuminancePreset(); 66 + ctru_max_lum = getMaxLuminancePreset(); 67 + 68 + backlight_hw_on(); 69 + backlight_hw_brightness(DEFAULT_BRIGHTNESS_SETTING); 70 + return true; 71 + } 72 + 73 + static int last_bl = -1; 74 + 75 + void backlight_hw_on(void) 76 + { 77 + lcd_mutex_lock(); 78 + if (last_bl != 1) { 79 + #ifdef HAVE_LCD_ENABLE 80 + lcd_enable(true); 81 + #endif 82 + GSPLCD_PowerOnAllBacklights(); 83 + last_bl = 1; 84 + } 85 + lcd_mutex_unlock(); 86 + } 87 + 88 + void backlight_hw_off(void) 89 + { 90 + lcd_mutex_lock(); 91 + if (last_bl != 0) { 92 + /* only power off rockbox ui screen */ 93 + GSPLCD_PowerOffBacklight(GSPLCD_SCREEN_BOTTOM); 94 + #ifdef HAVE_LCD_ENABLE 95 + lcd_enable(false); 96 + #endif 97 + last_bl = 0; 98 + } 99 + lcd_mutex_unlock(); 100 + } 101 + 102 + void backlight_hw_brightness(int brightness) 103 + { 104 + /* cap range, just in case */ 105 + if (brightness > MAX_BRIGHTNESS_SETTING) 106 + brightness = MAX_BRIGHTNESS_SETTING; 107 + if (brightness < MIN_BRIGHTNESS_SETTING) 108 + brightness = MIN_BRIGHTNESS_SETTING; 109 + 110 + /* normalize level on both screens */ 111 + lcd_mutex_lock(); 112 + GSPLCD_SetBrightnessRaw(GSPLCD_SCREEN_TOP | GSPLCD_SCREEN_BOTTOM, (u32) brightness); 113 + lcd_mutex_unlock(); 114 + }
+184
firmware/target/hosted/ctru/button-ctru.c
··· 1 + /*************************************************************************** 2 + * __________ __ ___. 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * $Id$ 9 + * 10 + * Copyright (C) 2002 by Felix Arends 11 + * 12 + * This program is free software; you can redistribute it and/or 13 + * modify it under the terms of the GNU General Public License 14 + * as published by the Free Software Foundation; either version 2 15 + * of the License, or (at your option) any later version. 16 + * 17 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 + * KIND, either express or implied. 19 + * 20 + ****************************************************************************/ 21 + 22 + #include <math.h> 23 + #include <stdlib.h> /* EXIT_SUCCESS */ 24 + #include <stdio.h> 25 + #include "config.h" 26 + #include "button.h" 27 + #include "kernel.h" 28 + #include "backlight.h" 29 + #include "system.h" 30 + #include "button-ctru.h" 31 + #include "buttonmap.h" 32 + #include "debug.h" 33 + #include "powermgmt.h" 34 + #include "storage.h" 35 + #include "settings.h" 36 + #include "sound.h" 37 + #include "misc.h" 38 + 39 + #include "touchscreen.h" 40 + 41 + #include <3ds/types.h> 42 + #include <3ds/services/apt.h> 43 + #include <3ds/services/hid.h> 44 + #include <3ds/services/mcuhwc.h> 45 + #include <3ds/services/dsp.h> 46 + 47 + static u8 old_slider_level = 0; 48 + static int last_y, last_x; 49 + 50 + static enum { 51 + STATE_UNKNOWN = -1, 52 + STATE_UP = 0, 53 + STATE_DOWN = 1, 54 + } last_touch_state = STATE_UNKNOWN; 55 + 56 + static double map_values(double n, double source_start, double source_end, double dest_start, double dest_end, int decimal_precision ) { 57 + double delta_start = source_end - source_start; 58 + double delta_end = dest_end - dest_start; 59 + if(delta_start == 0.0 || delta_end == 0.0) { 60 + return 1.0; 61 + } 62 + double scale = delta_end / delta_start; 63 + double neg_start = -1.0 * source_start; 64 + double offset = (neg_start * scale) + dest_start; 65 + double final_number = (n * scale) + offset; 66 + int calc_scale = (int) pow(10.0, decimal_precision); 67 + return (double) round(final_number * calc_scale) / calc_scale; 68 + } 69 + 70 + void update_sound_slider_level(void) 71 + { 72 + /* update global volume based on sound slider level */ 73 + u8 level; 74 + MCUHWC_GetSoundSliderLevel(&level); 75 + 76 + if (level != old_slider_level) { 77 + int volume = (int) map_values((double) level, 78 + 0.0, /* min slider voslume */ 79 + (double) 0x3f, /* max slider value */ 80 + (double) sound_min(SOUND_VOLUME), 81 + (double) sound_max(SOUND_VOLUME), 82 + 0); 83 + global_status.volume = volume; 84 + setvol(); 85 + old_slider_level = level; 86 + } 87 + } 88 + 89 + int button_read_device(int* data) 90 + { 91 + int key = BUTTON_NONE; 92 + 93 + /* TODO: implement Home Menu button support */ 94 + /* if (!aptMainLoop()) { 95 + return true; 96 + } */ 97 + 98 + hidScanInput(); 99 + u32 kDown = hidKeysDown(); 100 + 101 + if (kDown & KEY_SELECT) { 102 + touchscreen_set_mode(touchscreen_get_mode() == TOUCHSCREEN_POINT ? TOUCHSCREEN_BUTTON : TOUCHSCREEN_POINT); 103 + printf("Touchscreen mode: %s\n", touchscreen_get_mode() == TOUCHSCREEN_POINT ? "TOUCHSCREEN_POINT" : "TOUCHSCREEN_BUTTON"); 104 + } 105 + 106 + u32 kHeld = hidKeysHeld(); 107 + 108 + /* rockbox will handle button repeats */ 109 + kDown |= kHeld; 110 + 111 + /* Check for all the keys */ 112 + if (kDown & KEY_A) { 113 + key |= BUTTON_SELECT; 114 + } 115 + if (kDown & KEY_B) { 116 + key |= BUTTON_BACK; 117 + } 118 + if (kDown & KEY_X) { 119 + key |= BUTTON_MENU; 120 + } 121 + if (kDown & KEY_Y) { 122 + key |= BUTTON_USER; 123 + } 124 + if (kDown & KEY_START) { 125 + key |= BUTTON_POWER; 126 + } 127 + if (kDown & KEY_DRIGHT) { 128 + key |= BUTTON_RIGHT; 129 + } 130 + if (kDown & KEY_DLEFT) { 131 + key |= BUTTON_LEFT; 132 + } 133 + if (kDown & KEY_DUP) { 134 + key |= BUTTON_UP; 135 + } 136 + if (kDown & KEY_DDOWN) { 137 + key |= BUTTON_DOWN; 138 + } 139 + if (kDown & KEY_START) { 140 + key |= BUTTON_POWER; 141 + } 142 + 143 + touchPosition touch; 144 + hidTouchRead(&touch); 145 + 146 + /* Generate UP and DOWN events */ 147 + if (kDown & KEY_TOUCH) { 148 + last_touch_state = STATE_DOWN; 149 + } 150 + else { 151 + last_touch_state = STATE_UP; 152 + } 153 + 154 + last_x = touch.px; 155 + last_y = touch.py; 156 + 157 + int tkey = touchscreen_to_pixels(last_x, last_y, data); 158 + if (last_touch_state == STATE_DOWN) { 159 + key |= tkey; 160 + } 161 + 162 + update_sound_slider_level(); 163 + 164 + return key; 165 + } 166 + 167 + void button_init_device(void) 168 + { 169 + hidInit(); 170 + } 171 + 172 + #ifndef HAS_BUTTON_HOLD 173 + void touchscreen_enable_device(bool en) 174 + { 175 + (void)en; 176 + } 177 + #endif 178 + 179 + bool headphones_inserted(void) 180 + { 181 + bool is_inserted; 182 + DSP_GetHeadphoneStatus(&is_inserted); 183 + return is_inserted; 184 + }
+34
firmware/target/hosted/ctru/button-ctru.h
··· 1 + /*************************************************************************** 2 + * __________ __ ___. 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * $Id$ 9 + * 10 + * Copyright (C) 2009 by Thomas Martitz 11 + * 12 + * This program is free software; you can redistribute it and/or 13 + * modify it under the terms of the GNU General Public License 14 + * as published by the Free Software Foundation; either version 2 15 + * of the License, or (at your option) any later version. 16 + * 17 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 + * KIND, either express or implied. 19 + * 20 + ****************************************************************************/ 21 + 22 + 23 + #ifndef __BUTTON_CTRU_H__ 24 + #define __BUTTON_CTRU_H__ 25 + 26 + #include <stdbool.h> 27 + #include "config.h" 28 + 29 + bool button_hold(void); 30 + #undef button_init_device 31 + void button_init_device(void); 32 + int button_read_device(int *data); 33 + 34 + #endif /* __BUTTON_CTRU_H__ */
+41
firmware/target/hosted/ctru/buttonmap.h
··· 1 + /*************************************************************************** 2 + * __________ __ ___. 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * $Id$ 9 + * 10 + * Copyright (C) 2010 by Fred Bauer 11 + * 12 + * This program is free software; you can redistribute it and/or 13 + * modify it under the terms of the GNU General Public License 14 + * as published by the Free Software Foundation; either version 2 15 + * of the License, or (at your option) any later version. 16 + * 17 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 + * KIND, either express or implied. 19 + * 20 + ****************************************************************************/ 21 + 22 + #ifndef __BUTTONMAP_H__ 23 + #define __BUTTONMAP_H__ 24 + /* Button maps: simulated key, x, y, radius, name */ 25 + /* Run sim with --mapping to get coordinates */ 26 + /* or --debugbuttons to check */ 27 + /* The First matching button is returned */ 28 + struct button_map { 29 + int button, x, y, radius; 30 + char *description; 31 + }; 32 + 33 + extern struct button_map bm[]; 34 + 35 + int xy2button( int x, int y); 36 + 37 + #ifdef HAVE_TOUCHSCREEN 38 + int key_to_touch(int keyboard_button, unsigned int mouse_coords); 39 + #endif 40 + 41 + #endif /* __BUTTONMAP_H__ */
+73
firmware/target/hosted/ctru/filesystem-ctru.c
··· 1 + /*************************************************************************** 2 + * __________ __ ___. 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * $Id$ 9 + * 10 + * Copyright (C) 2010 by Thomas Martitz 11 + * 12 + * This program is free software; you can redistribute it and/or 13 + * modify it under the terms of the GNU General Public License 14 + * as published by the Free Software Foundation; either version 2 15 + * of the License, or (at your option) any later version. 16 + * 17 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 + * KIND, either express or implied. 19 + * 20 + ****************************************************************************/ 21 + #define RB_FILESYSTEM_OS 22 + #include <sys/stat.h> 23 + #include <string.h> 24 + #include <stdio.h> 25 + #include <errno.h> 26 + #include <utime.h> 27 + #include "config.h" 28 + #include "system.h" 29 + #include "file.h" 30 + #include "dir.h" 31 + #include "mv.h" 32 + #include "debug.h" 33 + #include "pathfuncs.h" 34 + #include "string-extra.h" 35 + 36 + #include <3ds/archive.h> 37 + 38 + void paths_init(void) 39 + { 40 + /* is this needed in 3DS? */ 41 + #if 0 42 + char config_dir[MAX_PATH]; 43 + 44 + const char *home = "/3ds"; 45 + mkdir("/3ds/.rockbox" __MKDIR_MODE_ARG); 46 + 47 + snprintf(config_dir, sizeof(config_dir), "%s/.config", home); 48 + mkdir(config_dir __MKDIR_MODE_ARG); 49 + snprintf(config_dir, sizeof(config_dir), "%s/.config/rockbox.org", home); 50 + mkdir(config_dir __MKDIR_MODE_ARG); 51 + /* Plugin data directory */ 52 + snprintf(config_dir, sizeof(config_dir), "%s/.config/rockbox.org/rocks.data", home); 53 + mkdir(config_dir __MKDIR_MODE_ARG); 54 + #endif 55 + } 56 + 57 + /* only sdcard volume is accesible to the user */ 58 + void volume_size(IF_MV(int volume,) sector_t *sizep, sector_t *freep) 59 + { 60 + sector_t size = 0, free = 0; 61 + 62 + FS_ArchiveResource sdmcRSRC; 63 + FSUSER_GetSdmcArchiveResource(&sdmcRSRC); 64 + 65 + size = (sdmcRSRC.totalClusters * sdmcRSRC.clusterSize) / sdmcRSRC.sectorSize; 66 + free = (sdmcRSRC.freeClusters * sdmcRSRC.clusterSize) / sdmcRSRC.sectorSize; 67 + 68 + if (sizep) 69 + *sizep = size; 70 + 71 + if (freep) 72 + *freep = free; 73 + }
+116
firmware/target/hosted/ctru/filesystem-ctru.h
··· 1 + /*************************************************************************** 2 + * __________ __ ___. 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * $Id$ 9 + * 10 + * Copyright (C) 2025 by Mauricio G. 11 + * 12 + * This program is free software; you can redistribute it and/or 13 + * modify it under the terms of the GNU General Public License 14 + * as published by the Free Software Foundation; either version 2 15 + * of the License, or (at your option) any later version. 16 + * 17 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 + * KIND, either express or implied. 19 + * 20 + ****************************************************************************/ 21 + #ifndef _FILESYSTEM_CTRU_H_ 22 + #define _FILESYSTEM_CTRU_H_ 23 + 24 + #if defined(PLUGIN) || defined(CODEC) 25 + #define FILEFUNCTIONS_DECLARED 26 + #define FILEFUNCTIONS_DEFINED 27 + #define DIRFUNCTIONS_DECLARED 28 + #define DIRFUNCTIONS_DEFINED 29 + #define OSFUNCTIONS_DECLARED 30 + #endif /* PLUGIN || CODEC */ 31 + 32 + #ifndef OSFUNCTIONS_DECLARED 33 + #define FS_PREFIX(_x_) ctru_ ## _x_ 34 + 35 + void paths_init(void); 36 + #endif /* !OSFUNCTIONS_DECLARED */ 37 + #endif /* _FILESYSTEM_CTRU_H_ */ 38 + 39 + #ifdef _FILE_H_ 40 + #include <unistd.h> 41 + 42 + #ifndef _FILESYSTEM_CTRU__FILE_H_ 43 + #define _FILESYSTEM_CTRU__FILE_H_ 44 + 45 + #ifdef RB_FILESYSTEM_OS 46 + #define FILEFUNCTIONS_DEFINED 47 + #endif 48 + 49 + #ifndef FILEFUNCTIONS_DECLARED 50 + #define __OPEN_MODE_ARG 51 + #define __CREAT_MODE_ARG \ 52 + , mode 53 + 54 + #include <time.h> 55 + 56 + int ctru_open(const char *name, int oflag, ...); 57 + int ctru_creat(const char *name, mode_t mode); 58 + int ctru_close(int fildes); 59 + int ctru_ftruncate(int fildes, off_t length); 60 + int ctru_fsync(int fildes); 61 + off_t ctru_lseek(int fildes, off_t offset, int whence); 62 + ssize_t ctru_read(int fildes, void *buf, size_t nbyte); 63 + ssize_t ctru_write(int fildes, const void *buf, size_t nbyte); 64 + int ctru_remove(const char *path); 65 + int ctru_rename(const char *old, const char *new); 66 + int ctru_modtime(const char *path, time_t modtime); 67 + off_t ctru_filesize(int fildes); 68 + int ctru_fsamefile(int fildes1, int fildes2); 69 + int ctru_relate(const char *path1, const char *path2); 70 + bool ctru_file_exists(const char *path); 71 + ssize_t ctru_readlink(const char *path, char *buf, size_t bufsiz); 72 + 73 + #endif /* !FILEFUNCTIONS_DECLARED */ 74 + 75 + #endif /* _FILESYSTEM_CTRU__FILE_H_ */ 76 + #endif /* _FILE_H_ */ 77 + 78 + #ifdef _DIR_H_ 79 + #ifndef _FILESYSTEM_CTRU__DIR_H_ 80 + #define _FILESYSTEM_CTRU__DIR_H_ 81 + 82 + #define DIRENT dirent 83 + struct dirent; 84 + 85 + struct dirinfo_native 86 + { 87 + unsigned int attr; 88 + off_t size; 89 + uint16_t wrtdate; 90 + uint16_t wrttime; 91 + }; 92 + 93 + typedef struct {} DIR; 94 + 95 + #ifndef DIRFUNCTIONS_DECLARED 96 + #define __MKDIR_MODE_ARG \ 97 + , 0777 98 + 99 + #ifdef RB_FILESYSTEM_OS 100 + #define DIRFUNCTIONS_DEFINED 101 + #endif 102 + 103 + DIR * ctru_opendir(const char *dirname); 104 + struct dirent * ctru_readdir(DIR *dirp); 105 + int ctru_readdir_r(DIR *dirp, struct dirent *entry, 106 + struct dirent **result); 107 + void ctru_rewinddir(DIR *dirp); 108 + int ctru_closedir(DIR *dirp); 109 + int ctru_mkdir(const char *path); 110 + int ctru_rmdir(const char *path); 111 + int ctru_samedir(DIR *dirp1, DIR *dirp2); 112 + bool ctru_dir_exists(const char *dirname); 113 + #endif /* !DIRFUNCTIONS_DECLARED */ 114 + 115 + #endif /* _FILESYSTEM_CTRU__DIR_H_ */ 116 + #endif /* _DIR_H_ */
+166
firmware/target/hosted/ctru/kernel-ctru.c
··· 1 + /*************************************************************************** 2 + * __________ __ ___. 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * $Id$ 9 + * 10 + * Copyright (C) 2025 Mauricio G. 11 + * 12 + * This program is free software; you can redistribute it and/or 13 + * modify it under the terms of the GNU General Public License 14 + * as published by the Free Software Foundation; either version 2 15 + * of the License, or (at your option) any later version. 16 + * 17 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 + * KIND, either express or implied. 19 + * 20 + ****************************************************************************/ 21 + 22 + #include <stdlib.h> 23 + #include <stdio.h> 24 + #include <inttypes.h> 25 + #include "system-ctru.h" 26 + #include "thread-ctru.h" 27 + #include "kernel.h" 28 + #include "thread.h" 29 + #include "panic.h" 30 + #include "debug.h" 31 + 32 + static int tick_timer_id; 33 + long start_tick; 34 + 35 + /* Condition to signal that "interrupts" may proceed */ 36 + static sysCond *sim_thread_cond; 37 + /* Mutex to serialize changing levels and exclude other threads while 38 + * inside a handler */ 39 + static RecursiveLock sim_irq_mtx; 40 + /* Level: 0 = enabled, not 0 = disabled */ 41 + static int volatile interrupt_level = HIGHEST_IRQ_LEVEL; 42 + /* How many handers waiting? Not strictly needed because CondSignal is a 43 + * noop if no threads were waiting but it filters-out calls to functions 44 + * with higher overhead and provides info when debugging. */ 45 + static int handlers_pending = 0; 46 + /* 1 = executing a handler; prevents CondSignal calls in set_irq_level 47 + * while in a handler */ 48 + static int status_reg = 0; 49 + 50 + /* Nescessary logic: 51 + * 1) All threads must pass unblocked 52 + * 2) Current handler must always pass unblocked 53 + * 3) Threads must be excluded when irq routine is running 54 + * 4) No more than one handler routine should execute at a time 55 + */ 56 + int set_irq_level(int level) 57 + { 58 + RecursiveLock_Lock(&sim_irq_mtx); 59 + 60 + int oldlevel = interrupt_level; 61 + 62 + if (status_reg == 0 && level == 0 && oldlevel != 0) 63 + { 64 + /* Not in a handler and "interrupts" are going from disabled to 65 + * enabled; signal any pending handlers still waiting */ 66 + if (handlers_pending > 0) 67 + sys_cond_broadcast(sim_thread_cond); 68 + } 69 + 70 + interrupt_level = level; /* save new level */ 71 + 72 + RecursiveLock_Unlock(&sim_irq_mtx); 73 + return oldlevel; 74 + } 75 + 76 + void sim_enter_irq_handler(void) 77 + { 78 + RecursiveLock_Lock(&sim_irq_mtx); 79 + handlers_pending++; 80 + 81 + /* Check each time before proceeding: disabled->enabled->...->disabled 82 + * is possible on an app thread before a handler thread is ever granted 83 + * the mutex; a handler can also leave "interrupts" disabled during 84 + * its execution */ 85 + while (interrupt_level != 0) 86 + sys_cond_wait(sim_thread_cond, &sim_irq_mtx); 87 + 88 + status_reg = 1; 89 + } 90 + 91 + void sim_exit_irq_handler(void) 92 + { 93 + /* If any others are waiting, give the signal */ 94 + if (--handlers_pending > 0) 95 + sys_cond_signal(sim_thread_cond); 96 + 97 + status_reg = 0; 98 + RecursiveLock_Unlock(&sim_irq_mtx); 99 + } 100 + 101 + static bool sim_kernel_init(void) 102 + { 103 + RecursiveLock_Init(&sim_irq_mtx); 104 + sim_thread_cond = sys_cond_create(); 105 + if (sim_thread_cond == NULL) 106 + { 107 + panicf("Cannot create sim_thread_cond\n"); 108 + return false; 109 + } 110 + return true; 111 + } 112 + 113 + void sim_kernel_shutdown(void) 114 + { 115 + sys_remove_timer(tick_timer_id); 116 + enable_irq(); 117 + while(handlers_pending > 0) 118 + sys_delay(10); 119 + 120 + sys_cond_destroy(sim_thread_cond); 121 + } 122 + 123 + u32 tick_timer(u32 interval, void *param) 124 + { 125 + long new_tick; 126 + 127 + (void) interval; 128 + (void) param; 129 + 130 + new_tick = (sys_get_ticks() - start_tick) / (1000/HZ); 131 + 132 + while(new_tick != current_tick) 133 + { 134 + sim_enter_irq_handler(); 135 + 136 + /* Run through the list of tick tasks - increments tick 137 + * on each iteration. */ 138 + call_tick_tasks(); 139 + 140 + sim_exit_irq_handler(); 141 + } 142 + 143 + return interval; 144 + } 145 + 146 + void tick_start(unsigned int interval_in_ms) 147 + { 148 + if (!sim_kernel_init()) 149 + { 150 + panicf("Could not initialize kernel!"); 151 + exit(-1); 152 + } 153 + 154 + if (tick_timer_id != 0) 155 + { 156 + sys_remove_timer(tick_timer_id); 157 + tick_timer_id = 0; 158 + } 159 + else 160 + { 161 + start_tick = sys_get_ticks(); 162 + } 163 + 164 + tick_timer_id = sys_add_timer(interval_in_ms, tick_timer, NULL); 165 + } 166 +
+68
firmware/target/hosted/ctru/lc-ctru.c
··· 1 + /*************************************************************************** 2 + * __________ __ ___. 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * $Id$ 9 + * 10 + * Copyright (C) 2025 Mauricio G. 11 + * 12 + * This program is free software; you can redistribute it and/or 13 + * modify it under the terms of the GNU General Public License 14 + * as published by the Free Software Foundation; either version 2 15 + * of the License, or (at your option) any later version. 16 + * 17 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 + * KIND, either express or implied. 19 + * 20 + ****************************************************************************/ 21 + #define RB_FILESYSTEM_OS 22 + #include <dlfcn.h> 23 + #include <stdlib.h> 24 + #include <string.h> 25 + #include "system.h" 26 + #include "load_code.h" 27 + #include "filesystem-ctru.h" 28 + #include "debug.h" 29 + 30 + void* programResolver(const char* sym, void *userData); 31 + void * lc_open(const char *filename, unsigned char *buf, size_t buf_size) 32 + { 33 + DEBUGF("dlopen(path=\"%s\")\n", filename); 34 + 35 + /* note: the 3ds dlopen implementation needs a custom resolver 36 + for the unresolved symbols in shared objects */ 37 + /* void *handle = dlopen(filename, RTLD_NOW | RTLD_LOCAL); */ 38 + void *handle = ctrdlOpen(filename, 39 + RTLD_NOW | RTLD_LOCAL, 40 + programResolver, 41 + NULL); 42 + if (handle == NULL) 43 + { 44 + DEBUGF("%s(\"%s\") failed\n", __func__, filename); 45 + DEBUGF(" lc_open error '%s'\n", dlerror()); 46 + } 47 + DEBUGF("handle = %p\n", handle); 48 + 49 + return handle; 50 + (void) buf; (void) buf_size; 51 + } 52 + 53 + void * lc_get_header(void *handle) 54 + { 55 + void *symbol = dlsym(handle, "__header"); 56 + if (!symbol) { 57 + symbol = dlsym(handle, "___header"); 58 + } 59 + 60 + return symbol; 61 + } 62 + 63 + void lc_close(void *handle) 64 + { 65 + if (handle) { 66 + dlclose(handle); 67 + } 68 + }
+53
firmware/target/hosted/ctru/lc-program-resolver.c
··· 1 + /*************************************************************************** 2 + * __________ __ ___. 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * $Id$ 9 + * 10 + * Copyright (C) 2025 Mauricio G. 11 + * 12 + * This program is free software; you can redistribute it and/or 13 + * modify it under the terms of the GNU General Public License 14 + * as published by the Free Software Foundation; either version 2 15 + * of the License, or (at your option) any later version. 16 + * 17 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 + * KIND, either express or implied. 19 + * 20 + ****************************************************************************/ 21 + 22 + #include <string.h> 23 + #include <stdio.h> 24 + #include <stdlib.h> 25 + #include <ctype.h> 26 + #include <errno.h> 27 + #include <setjmp.h> 28 + #include "debug.h" 29 + 30 + #define RESOLVER_ENTRY(name) \ 31 + if (!strcmp(symName, #name)) \ 32 + return (void *)name; 33 + 34 + /* void* ctrdlProgramResolver(const char* symName) */ 35 + void* programResolver(const char* symName, void *userData) 36 + { 37 + DEBUGF("programResolver(name=\"%s\")\n", symName); 38 + 39 + /* codecs, etc */ 40 + RESOLVER_ENTRY(abs); 41 + RESOLVER_ENTRY(labs); 42 + RESOLVER_ENTRY(llabs); 43 + RESOLVER_ENTRY(printf); 44 + 45 + /* plugins */ 46 + RESOLVER_ENTRY(_ctype_); 47 + RESOLVER_ENTRY(__errno); 48 + RESOLVER_ENTRY(longjmp); 49 + RESOLVER_ENTRY(setjmp); 50 + 51 + return NULL; 52 + (void) userData; 53 + }
+245
firmware/target/hosted/ctru/lcd-bitmap.c
··· 1 + /*************************************************************************** 2 + * __________ __ ___. 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * $Id$ 9 + * 10 + * Copyright (C) 2006 Dan Everton 11 + * 12 + * This program is free software; you can redistribute it and/or 13 + * modify it under the terms of the GNU General Public License 14 + * as published by the Free Software Foundation; either version 2 15 + * of the License, or (at your option) any later version. 16 + * 17 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 + * KIND, either express or implied. 19 + * 20 + ****************************************************************************/ 21 + 22 + #include <math.h> 23 + #include <stdlib.h> 24 + #include <string.h> 25 + #include "debug.h" 26 + #include "system.h" 27 + #include "button-ctru.h" 28 + #include "screendump.h" 29 + #include "lcd-target.h" 30 + 31 + #include <3ds/gfx.h> 32 + #include <3ds/allocator/linear.h> 33 + #include <3ds/console.h> 34 + #include <3ds/services/cfgu.h> 35 + 36 + /*#define LOGF_ENABLE*/ 37 + #include "logf.h" 38 + 39 + fb_data *dev_fb = 0; 40 + 41 + static u8 system_model = CFG_MODEL_3DS; 42 + 43 + typedef struct 44 + { 45 + int width, height; 46 + } dimensions_t; 47 + 48 + int get_dest_offset(int x, int y, int dest_width) 49 + { 50 + return dest_width - y - 1 + dest_width * x; 51 + } 52 + 53 + int get_source_offset(int x, int y, int source_width) 54 + { 55 + return x + y * source_width; 56 + } 57 + 58 + void copy_framebuffer_16(u16 *dest, const dimensions_t dest_dim, const u16 *source, const dimensions_t source_dim) 59 + { 60 + int rows = MIN(dest_dim.width, source_dim.height); 61 + int cols = MIN(dest_dim.height, source_dim.width); 62 + for (int y = 0; y < rows; ++y) { 63 + for (int x = 0; x < cols; ++x) { 64 + const u16 *s = source + get_source_offset(x, y, source_dim.width); 65 + u16 *d = dest + get_dest_offset(x, y, dest_dim.width); 66 + *d = *s; 67 + } 68 + } 69 + } 70 + 71 + void copy_framebuffer_24(u8 *dest, const dimensions_t dest_dim, const u8 *source, const dimensions_t source_dim) 72 + { 73 + int rows = MIN(dest_dim.width, source_dim.height); 74 + int cols = MIN(dest_dim.height, source_dim.width); 75 + for (int y = 0; y < rows; ++y) { 76 + for (int x = 0; x < cols; ++x) { 77 + const u8 *s = source + get_source_offset(x, y, source_dim.width) * 3; 78 + u8 *d = dest + get_dest_offset(x, y, dest_dim.width) * 3; 79 + d[0] = s[0]; 80 + d[1] = s[1]; 81 + d[2] = s[2]; 82 + } 83 + } 84 + } 85 + 86 + void copy_framebuffer_32(u32 *dest, const dimensions_t dest_dim, const u32 *source, const dimensions_t source_dim) 87 + { 88 + int rows = MIN(dest_dim.width, source_dim.height); 89 + int cols = MIN(dest_dim.height, source_dim.width); 90 + for (int y = 0; y < rows; ++y) { 91 + for (int x = 0; x < cols; ++x) { 92 + const u32 *s = source + get_source_offset(x, y, source_dim.width); 93 + u32 *d = dest + get_dest_offset(x, y, dest_dim.width); 94 + *d = *s; 95 + } 96 + } 97 + } 98 + 99 + void update_framebuffer(void) 100 + { 101 + u16 fb_width, fb_height; 102 + u32 bufsize; 103 + u8* fb = gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, &fb_width, &fb_height); 104 + bufsize = fb_width * fb_height * 2; 105 + 106 + /* lock_window_mutex() */ 107 + 108 + if (FB_DATA_SZ == 2) { 109 + copy_framebuffer_16((void *)fb, (dimensions_t){ fb_width, fb_height }, 110 + (u16 *)dev_fb, (dimensions_t){ LCD_WIDTH, LCD_HEIGHT }); 111 + } 112 + else if (FB_DATA_SZ == 3) { 113 + copy_framebuffer_24((void *)fb, (dimensions_t){ fb_width, fb_height }, 114 + (u8 *)dev_fb, (dimensions_t){ LCD_WIDTH, LCD_HEIGHT }); 115 + } 116 + else { 117 + copy_framebuffer_32((void *)fb, (dimensions_t){ fb_width, fb_height }, 118 + (u32 *)dev_fb, (dimensions_t){ LCD_WIDTH, LCD_HEIGHT }); 119 + } 120 + 121 + // Flush and swap framebuffers 122 + /* gfxFlushBuffers(); */ 123 + GSPGPU_FlushDataCache(fb, bufsize); 124 + gfxSwapBuffers(); 125 + gspWaitForVBlank(); 126 + 127 + /* unlock_window_mutex() */ 128 + } 129 + 130 + /* lcd-as-memframe.c */ 131 + extern void lcd_copy_buffer_rect(fb_data *dst, const fb_data *src, 132 + int width, int height); 133 + 134 + void lcd_update_rect(int x, int y, int width, int height) 135 + { 136 + fb_data *dst, *src; 137 + 138 + if (x + width > LCD_WIDTH) 139 + width = LCD_WIDTH - x; /* Clip right */ 140 + if (x < 0) 141 + width += x, x = 0; /* Clip left */ 142 + if (width <= 0) 143 + return; /* nothing left to do */ 144 + 145 + if (y + height > LCD_HEIGHT) 146 + height = LCD_HEIGHT - y; /* Clip bottom */ 147 + if (y < 0) 148 + height += y, y = 0; /* Clip top */ 149 + if (height <= 0) 150 + return; /* nothing left to do */ 151 + 152 + dst = LCD_FRAMEBUF_ADDR(x, y); 153 + src = FBADDR(x,y); 154 + 155 + /* Copy part of the Rockbox framebuffer to the second framebuffer */ 156 + if (width < LCD_WIDTH) 157 + { 158 + /* Not full width - do line-by-line */ 159 + lcd_copy_buffer_rect(dst, src, width, height); 160 + } 161 + else 162 + { 163 + /* Full width - copy as one line */ 164 + lcd_copy_buffer_rect(dst, src, LCD_WIDTH*height, 1); 165 + } 166 + 167 + update_framebuffer(); 168 + } 169 + 170 + void lcd_update(void) 171 + { 172 + /* update a full screen rect */ 173 + lcd_update_rect(0, 0, LCD_WIDTH, LCD_HEIGHT); 174 + } 175 + 176 + void sys_console_init(void) 177 + { 178 + gfxInit(GSP_BGR8_OES, GSP_RGB565_OES, false); 179 + consoleInit(GFX_TOP, NULL); 180 + } 181 + 182 + void lcd_init_device(void) 183 + { 184 + gfxSetDoubleBuffering(GFX_BOTTOM, true); 185 + 186 + /* hidInit(); */ 187 + 188 + u16 fb_width, fb_height; 189 + u8* fb = gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, &fb_width, &fb_height); 190 + u32 bufsize = fb_width * fb_height * 2; 191 + 192 + dev_fb = (fb_data *) linearAlloc(bufsize); 193 + if (dev_fb == NULL) { 194 + logf("could not allocate dev_fb size %d bytes\n", 195 + bufsize); 196 + exit(EXIT_FAILURE); 197 + } 198 + 199 + memset(dev_fb, 0x00, bufsize); 200 + 201 + /* Set dpi value from system model */ 202 + CFGU_GetSystemModel(&system_model); 203 + } 204 + 205 + void lcd_shutdown(void) 206 + { 207 + if (dev_fb) { 208 + linearFree(dev_fb); 209 + dev_fb = 0; 210 + } 211 + 212 + /* hidExit(); */ 213 + gfxExit(); 214 + } 215 + 216 + int lcd_get_dpi(void) 217 + { 218 + /* link: https://www.reddit.com/r/nintendo/comments/2uzk5y/informative_post_about_dpi_on_the_new_3ds/ */ 219 + /* all values for bottom lcd */ 220 + float dpi = 96.0f; 221 + switch(system_model) { 222 + case CFG_MODEL_3DS: 223 + dpi = 132.45f; 224 + break; 225 + case CFG_MODEL_3DSXL: 226 + dpi = 95.69f; 227 + break; 228 + case CFG_MODEL_N3DS: 229 + dpi = 120.1f; 230 + break; 231 + case CFG_MODEL_2DS: 232 + dpi = 132.45f; 233 + break; 234 + case CFG_MODEL_N3DSXL: 235 + dpi = 95.69f; 236 + break; 237 + case CFG_MODEL_N2DSXL: 238 + dpi = 95.69f; 239 + break; 240 + default: 241 + break; 242 + }; 243 + return (int) roundf(dpi); 244 + } 245 +
+31
firmware/target/hosted/ctru/lcd-bitmap.h
··· 1 + /*************************************************************************** 2 + * __________ __ ___. 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * $Id$ 9 + * 10 + * Copyright (C) 2025 Mauricio G. 11 + * 12 + * This program is free software; you can redistribute it and/or 13 + * modify it under the terms of the GNU General Public License 14 + * as published by the Free Software Foundation; either version 2 15 + * of the License, or (at your option) any later version. 16 + * 17 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 + * KIND, either express or implied. 19 + * 20 + ****************************************************************************/ 21 + 22 + #ifndef __LCDBITMAP_H__ 23 + #define __LCDBITMAP_H__ 24 + 25 + #include "lcd.h" 26 + 27 + void sim_lcd_init(void); 28 + void sys_console_init(void); 29 + 30 + #endif /* #ifndef __LCDBITMAP_H__ */ 31 +
+34
firmware/target/hosted/ctru/lcd-target.h
··· 1 + /*************************************************************************** 2 + * __________ __ ___. 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * $Id$ 9 + * 10 + * Copyright (C) 2025 Mauricio G. 11 + * 12 + * This program is free software; you can redistribute it and/or 13 + * modify it under the terms of the GNU General Public License 14 + * as published by the Free Software Foundation; either version 2 15 + * of the License, or (at your option) any later version. 16 + * 17 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 + * KIND, either express or implied. 19 + * 20 + ****************************************************************************/ 21 + #ifndef LCD_TARGET_H 22 + #define LCD_TARGET_H 23 + 24 + #include "lcd.h" 25 + 26 + /* 27 + Framebuffer device and framebuffer access. 28 + See lcd-memframe.c 29 + */ 30 + extern fb_data *dev_fb; 31 + #define LCD_FRAMEBUF_ADDR(col, row) (dev_fb + row * LCD_WIDTH + col) 32 + 33 + #endif /* LCD_TARGET_H */ 34 +
+99
firmware/target/hosted/ctru/lib/bfile/bfile-internal.h
··· 1 + #ifndef _BFILE_INTERNAL_H_ 2 + #define _BFILE_INTERNAL_H_ 3 + 4 + #include <stdlib.h> 5 + #include <stdio.h> 6 + #include <stdint.h> 7 + #include <stdbool.h> 8 + #include <sys/stat.h> 9 + #include <math.h> 10 + 11 + #include <3ds/gfx.h> 12 + #include <3ds/svc.h> 13 + #include <3ds/types.h> 14 + #include <3ds/thread.h> 15 + #include <3ds/result.h> 16 + #include <3ds/services/fs.h> 17 + #include <3ds/synchronization.h> 18 + 19 + /* #define MALLOC_DEBUG 20 + #include "rmalloc/rmalloc.h" */ 21 + 22 + #include "cslice.h" 23 + #include "cmap.h" 24 + 25 + #define nil NULL 26 + 27 + /* in go functions can return two values */ 28 + #define two_type_value(type1, type2, name1, name2, type_name) \ 29 + typedef struct { \ 30 + type1 name1; \ 31 + type2 name2; \ 32 + } type_name##_t; 33 + 34 + /* single mutex implementation */ 35 + #define sync_Mutex LightLock 36 + static inline void sync_MutexInit(sync_Mutex* m) { 37 + LightLock_Init(m); 38 + } 39 + 40 + /* mutex unlock */ 41 + static inline void sync_MutexLock(sync_Mutex* m) { 42 + LightLock_Lock(m); 43 + } 44 + 45 + /* mutex lock */ 46 + static inline void sync_MutexUnlock(sync_Mutex* m) { 47 + LightLock_Unlock(m); 48 + } 49 + 50 + /* read_write mutex implementation */ 51 + typedef struct { 52 + sync_Mutex shared; 53 + CondVar reader_q; 54 + CondVar writer_q; 55 + int active_readers; 56 + int active_writers; 57 + int waiting_writers; 58 + } sync_RWMutex; 59 + 60 + void sync_RWMutexInit(sync_RWMutex *m); 61 + void sync_RWMutexRLock(sync_RWMutex *m); 62 + void sync_RWMutexRUnlock(sync_RWMutex *m); 63 + void sync_RWMutexLock(sync_RWMutex *m); 64 + void sync_RWMutexUnlock(sync_RWMutex *m); 65 + 66 + /* declare a two type value with name 'n_err_t' */ 67 + two_type_value(int, const char*, n, err, int_error); 68 + two_type_value(struct stat, const char*, fi, err, stat_error); 69 + typedef const char* file_error_t; 70 + 71 + typedef struct page { 72 + s64 num; 73 + struct page* prev; 74 + struct page* next; 75 + u8* data; 76 + } page; 77 + 78 + /* the two map types used by this library */ 79 + cmap_declare(page, s64, struct page*); 80 + cmap_declare(bool, s64, bool); 81 + 82 + typedef struct shard { 83 + sync_Mutex mu; 84 + cmap(page) pages; 85 + cmap(bool) dirty; 86 + struct page* head; 87 + struct page* tail; 88 + } shard; 89 + 90 + typedef struct Pager { 91 + Handle file; 92 + s64 pgsize; 93 + s64 pgmax; 94 + /* sync_RWMutex mu; */ 95 + s64 size; 96 + cslice(shard) shards; 97 + } Pager; 98 + 99 + #endif /* _BFILE_INTERNAL_H_ */
+471
firmware/target/hosted/ctru/lib/bfile/bfile.c
··· 1 + /* 2 + * This code is based on bfile.go by Josh Baker. 3 + * Converted to C code by Mauricio G. 4 + * Released under the MIT License. 5 + */ 6 + 7 + /* IMPORTANT: this code only works for O_RDONLY and O_RDWR files. */ 8 + 9 + #include "bfile.h" 10 + 11 + /* note: for ease of reading and ease of comparing go code 12 + with c implementation, function names are similar to the 13 + go version */ 14 + 15 + /* note2: sync_RWMutex calls have been moved to rockbox sys_file 16 + implementation. To use as standalone code please uncomment those calls. */ 17 + 18 + void sync_RWMutexInit(sync_RWMutex *m) { 19 + LightLock_Init(&m->shared); 20 + CondVar_Init(&m->reader_q); 21 + CondVar_Init(&m->writer_q); 22 + m->active_readers = 0; 23 + m->active_writers = 0; 24 + m->waiting_writers = 0; 25 + } 26 + 27 + static inline LightLock *unique_lock(LightLock *lk) { 28 + LightLock_Lock(lk); 29 + return lk; 30 + } 31 + 32 + void sync_RWMutexRLock(sync_RWMutex *m) { 33 + LightLock *lk = unique_lock(&m->shared); 34 + while (m->waiting_writers != 0) { 35 + CondVar_Wait(&m->reader_q, lk); 36 + } 37 + ++m->active_readers; 38 + LightLock_Unlock(lk); 39 + } 40 + 41 + void sync_RWMutexRUnlock(sync_RWMutex *m) { 42 + LightLock *lk = unique_lock(&m->shared); 43 + --m->active_readers; 44 + LightLock_Unlock(lk); 45 + CondVar_Signal(&m->writer_q); 46 + } 47 + 48 + void sync_RWMutexLock(sync_RWMutex *m) { 49 + LightLock *lk = unique_lock(&m->shared); 50 + ++m->waiting_writers; 51 + while ((m->active_readers != 0) || (m->active_writers != 0)) { 52 + CondVar_Wait(&m->writer_q, lk); 53 + } 54 + ++m->active_writers; 55 + LightLock_Unlock(lk); 56 + } 57 + 58 + void sync_RWMutexUnlock(sync_RWMutex *m) { 59 + LightLock *lk = unique_lock(&m->shared); 60 + --m->waiting_writers; 61 + --m->active_writers; 62 + if (m->waiting_writers > 0) { 63 + CondVar_Signal(&m->writer_q); 64 + } else { 65 + CondVar_Broadcast(&m->reader_q); 66 + } 67 + LightLock_Unlock(lk); 68 + } 69 + 70 + void s_init(shard* s); 71 + 72 + void s_push(shard* s, page* p) { 73 + s->head->next->prev = p; 74 + p->next = s->head->next; 75 + p->prev = s->head; 76 + s->head->next = p; 77 + } 78 + 79 + void s_pop(shard* s, page* p) { 80 + p->prev->next = p->next; 81 + p->next->prev = p->prev; 82 + } 83 + 84 + void s_bump(shard* s, page* p) { 85 + s_pop(s, p); 86 + s_push(s, p); 87 + } 88 + 89 + /* page_pair_t destructor */ 90 + /* page_pair_t type is defined by cmap_declare(page, s64, struct page*) */ 91 + /* struct { 92 + s64 key; 93 + struct page* value; 94 + } page_pair_t; 95 + */ 96 + void page_pair_free(void* pair_ptr) { 97 + if (pair_ptr) { 98 + page_pair_t *pair = (page_pair_t*) pair_ptr; 99 + struct page *p = pair->value; 100 + if (p != nil) { 101 + if (p->data != nil) { 102 + free(p->data); 103 + } 104 + free(p); 105 + } 106 + } 107 + } 108 + 109 + /* shard destructor */ 110 + void s_free(void* s_ptr) { 111 + if (s_ptr) { 112 + shard *s = (shard*) s_ptr; 113 + if (s->pages != nil) { 114 + cmap_set_elem_destructor(s->pages, page_pair_free); 115 + cmap_clear(page, s->pages); 116 + cmap_clear(bool, s->dirty); 117 + free(s->head); 118 + free(s->tail); 119 + } 120 + } 121 + } 122 + 123 + Pager* NewPager(Handle file) { 124 + return NewPagerSize(file, 0, 0); 125 + } 126 + 127 + Pager* NewPagerSize(Handle file, int pageSize, int bufferSize) { 128 + if (pageSize <= 0) { 129 + pageSize = defaultPageSize; 130 + } else if ((pageSize & 4095) != 0) { 131 + // must be a power of two 132 + int x = 1; 133 + while (x < pageSize) { 134 + x *= 2; 135 + } 136 + pageSize = x; 137 + } 138 + 139 + if (bufferSize <= 0) { 140 + bufferSize = defaultBufferSize; 141 + } else if (bufferSize < pageSize) { 142 + bufferSize = pageSize; 143 + } 144 + 145 + Pager* f = (Pager*) malloc(sizeof(Pager)); 146 + f->file = file; 147 + f->size = -1; 148 + f->pgsize = (s64) pageSize; 149 + 150 + /* sync_RWMutexInit(&f->mu); */ 151 + 152 + // calculate the max number of pages across all shards 153 + s64 pgmax = (s64) bufferSize / f->pgsize; 154 + if (pgmax < minPages) { 155 + pgmax = minPages; 156 + } 157 + 158 + // calculate how many shards are needed, power of 2 159 + s64 nshards = (s64) ceil((double) pgmax / (double) pagesPerShard); 160 + if (nshards > maxShards) { 161 + nshards = maxShards; 162 + } 163 + s64 x = 1; 164 + while (x < nshards) { 165 + x *= 2; 166 + } 167 + nshards = x; 168 + 169 + // calculate the max number of pages per shard 170 + f->pgmax = (s64) floor((double) pgmax / (double) nshards); 171 + cslice_make(f->shards, nshards, (shard) { 0 }); 172 + 173 + // initialize sync mutex 174 + size_t i; 175 + for (i = 0; i < cslice_len(f->shards); i++) { 176 + sync_MutexInit(&f->shards[i].mu); 177 + } 178 + return f; 179 + } 180 + 181 + static int_error_t read_at(Handle file, u8 *data, size_t data_len, off_t off) 182 + { 183 + u32 read_bytes = 0; 184 + if (R_FAILED(FSFILE_Read(file, &read_bytes, (u64) off, data, (u32) data_len))) { 185 + return (int_error_t) { -1, "I/O error" }; 186 + } 187 + 188 + /* io.EOF */ 189 + if (read_bytes == 0) { 190 + return (int_error_t) { 0, "io.EOF" }; 191 + } 192 + 193 + return (int_error_t) { (int) read_bytes, nil }; 194 + } 195 + 196 + static int_error_t write_at(Handle file, u8 *data, size_t data_len, off_t off) 197 + { 198 + u32 written = 0; 199 + if (R_FAILED(FSFILE_Write(file, &written, (u64) off, data, (u32) data_len, FS_WRITE_FLUSH))) { 200 + return (int_error_t) { -1, "I/O error" }; 201 + } 202 + 203 + /* I/O error */ 204 + if ((written == 0) || (written < (u32) data_len)) { 205 + return (int_error_t) { -1, "I/O error" }; 206 + } 207 + 208 + return (int_error_t) { (int) written, nil }; 209 + } 210 + 211 + static stat_error_t file_stat(Handle file) 212 + { 213 + u64 size = 0; 214 + struct stat fi = { 0 }; 215 + if (R_FAILED(FSFILE_GetSize(file, &size))) { 216 + fi.st_size = 0; 217 + return (stat_error_t) { fi, "I/O error" }; 218 + } 219 + 220 + fi.st_size = (off_t) size; 221 + return (stat_error_t) { fi, nil }; 222 + } 223 + 224 + void s_init(shard* s) 225 + { 226 + if (s->pages == nil) { 227 + s->pages = cmap_make(/*s64*/page); 228 + s->dirty = cmap_make(/*s64*/bool); 229 + s->head = (page*) malloc(sizeof(page)); 230 + s->tail = (page*) malloc(sizeof(page)); 231 + s->head->next = s->tail; 232 + s->tail->prev = s->head; 233 + } 234 + } 235 + 236 + file_error_t f_write(Pager* f, page* p) { 237 + s64 off = p->num * f->pgsize; 238 + s64 end = f->pgsize; 239 + if ((off + end) > f->size) { 240 + end = f->size - off; 241 + } 242 + int_error_t __err = write_at(f->file, p->data, end, off); 243 + if (__err.err != nil) { 244 + return __err.err; 245 + } 246 + return nil; 247 + } 248 + 249 + file_error_t f_read(Pager* f, page* p) { 250 + int_error_t __err = read_at(f->file, p->data, f->pgsize, p->num * f->pgsize); 251 + if ((__err.err != nil) && strcmp(__err.err, "io.EOF")) { 252 + return "I/O error"; 253 + } 254 + 255 + return nil; 256 + } 257 + 258 + const char* f_incrSize(Pager* f, s64 end, bool write) 259 + { 260 + #define defer(m) \ 261 + sync_RWMutexUnlock(&f->mu); \ 262 + sync_RWMutexRLock(&f->mu); 263 + 264 + /* sync_RWMutexRUnlock(&f->mu); 265 + sync_RWMutexLock(&f->mu); */ 266 + 267 + if (f->size == -1) { 268 + stat_error_t fi_err = file_stat(f->file); 269 + if (fi_err.err != nil) { 270 + /* defer(&f->mu); */ 271 + return nil; 272 + } 273 + f->size = fi_err.fi.st_size; 274 + } 275 + if (write && (end > f->size)) { 276 + f->size = end; 277 + } 278 + 279 + /* defer(&f->mu); */ 280 + return nil; 281 + } 282 + 283 + int_error_t f_pio(Pager *f, u8 *b, size_t len_b, s64 pnum, s64 pstart, s64 pend, bool write); 284 + int_error_t f_io(Pager *f, u8 *b, size_t len_b, s64 off, bool write) { 285 + if (f == nil) { 286 + return (int_error_t) { 0, "invalid argument" }; 287 + } 288 + bool eof = false; 289 + s64 start = off, end = off + len_b; 290 + if (start < 0) { 291 + return (int_error_t) { 0, "negative offset" }; 292 + } 293 + 294 + // Check the upper bounds of the input to the known file size. 295 + // Increase the file size if needed. 296 + /* sync_RWMutexRLock(&f->mu); */ 297 + if (end > f->size) { 298 + file_error_t err = f_incrSize(f, end, write); 299 + if (err != nil) { 300 + /* sync_RWMutexRUnlock(&f->mu); */ 301 + return (int_error_t) { 0, err }; 302 + } 303 + if (!write && (end > f->size)) { 304 + end = f->size; 305 + if ((end - start) < 0) { 306 + end = start; 307 + } 308 + eof = true; 309 + len_b = end-start; /* b = b[:end-start] */ 310 + } 311 + } 312 + /* sync_RWMutexRUnlock(&f->mu); */ 313 + 314 + // Perform the page I/O. 315 + int total = 0; 316 + while (len_b > 0) { 317 + s64 pnum = off / f->pgsize; 318 + s64 pstart = off & (f->pgsize - 1); 319 + s64 pend = pstart + (s64) len_b; 320 + if (pend > f->pgsize) { 321 + pend = f->pgsize; 322 + } 323 + 324 + int_error_t result = f_pio(f, b, pend - pstart, pnum, pstart, pend, write); 325 + if (result.err != nil) { 326 + return (int_error_t) { total, result.err }; 327 + } 328 + 329 + off += (s64) result.n; 330 + total += result.n; 331 + b = &b[result.n]; len_b -= result.n; /* b = b[n:] */ 332 + } 333 + if (eof) { 334 + return (int_error_t) { total, "io.EOF" }; 335 + } 336 + 337 + return (int_error_t) { total, nil }; 338 + } 339 + 340 + int_error_t f_pio(Pager *f, u8 *b, size_t len_b, s64 pnum, s64 pstart, s64 pend, bool write) { 341 + /* printf("pio(%p, %d, %lld, %lld, %lld, %s)\n", b, len_b, pnum, pstart, pend, write == true ? "true" : "false"); */ 342 + shard *s = &f->shards[pnum & (s64) (cslice_len(f->shards) - 1)]; 343 + sync_MutexLock(&s->mu); 344 + s_init(s); 345 + page *p = cmap_get_ptr(page, s->pages, pnum); 346 + if (p == nil) { 347 + // Page does not exist in memory. 348 + // Acquire a new one. 349 + if (cmap_len(s->pages) == f->pgmax) { 350 + // The buffer is at capacity. 351 + // Evict lru page and hang on to it. 352 + p = s->tail->prev; 353 + s_pop(s, p); 354 + cmap_delete(page, s->pages, p->num); 355 + if (cmap_get(bool, s->dirty, p->num)) { 356 + // dirty page. flush it now 357 + file_error_t err = f_write(f, p); 358 + if (err != nil) { 359 + sync_MutexUnlock(&s->mu); 360 + return (int_error_t) { 0, err }; 361 + } 362 + cmap_delete(bool, s->dirty, p->num); 363 + } 364 + // Clear the previous page memory for partial page writes for 365 + // pages that are being partially written to. 366 + if (write && ((pend - pstart) < f->pgsize)) { 367 + memset(p->data, 0, f->pgsize); 368 + } 369 + } else { 370 + // Allocate an entirely new page. 371 + p = (page *) malloc(sizeof(page)); 372 + p->data = (u8 *) malloc(f->pgsize); 373 + } 374 + p->num = pnum; 375 + // Read contents of page from file for all read operations, and 376 + // partial write operations. Ignore for full page writes. 377 + if (!write || ((pend-pstart) < f->pgsize)) { 378 + file_error_t err = f_read(f, p); 379 + if (err != nil) { 380 + sync_MutexUnlock(&s->mu); 381 + return (int_error_t) { 0, err }; 382 + } 383 + } 384 + // Add the newly acquired page to the list. 385 + cmap_set(page, s->pages, p->num, p); 386 + s_push(s, p); 387 + } else { 388 + // Bump the page to the front of the list. 389 + s_bump(s, p); 390 + } 391 + if (write) { 392 + memcpy(p->data + pstart, b, pend - pstart); 393 + cmap_set(bool, s->dirty, pnum, true); 394 + } else { 395 + memcpy(b, p->data + pstart, pend - pstart); 396 + } 397 + sync_MutexUnlock(&s->mu); 398 + return (int_error_t) { len_b, nil }; 399 + } 400 + 401 + // Flush writes any unwritten buffered data to the underlying file. 402 + file_error_t PagerFlush(Pager *f) { 403 + if (f == nil) { 404 + return "invalid argument"; 405 + } 406 + 407 + /* sync_RWMutexLock(&f->mu); */ 408 + for (size_t i = 0; i < cslice_len(f->shards); i++) { 409 + cmap_iterator(bool) pnum; 410 + if (f->shards[i].dirty != nil) { 411 + for (pnum = cmap_begin(f->shards[i].dirty); 412 + pnum != cmap_end(f->shards[i].dirty); pnum++) { 413 + if (pnum->value == true) { 414 + page *p = cmap_get_ptr(page, f->shards[i].pages, pnum->key); 415 + if (p != nil) { 416 + file_error_t err = f_write(f, p); 417 + if (err != nil) { 418 + /* sync_RWMutexUnlock(&f->mu); */ 419 + return err; 420 + } 421 + } 422 + cmap_set(bool, f->shards[i].dirty, pnum->key, false); 423 + } 424 + } 425 + } 426 + } 427 + /* sync_RWMutexUnlock(&f->mu); */ 428 + return nil; 429 + } 430 + 431 + // ReadAt reads len(b) bytes from the File starting at byte offset off. 432 + int_error_t PagerReadAt(Pager *f, u8 *b, size_t len_b, off_t off) { 433 + return f_io(f, b, len_b, off, false); 434 + } 435 + 436 + // WriteAt writes len(b) bytes to the File starting at byte offset off. 437 + int_error_t PagerWriteAt(Pager *f, u8 *b, size_t len_b, off_t off) { 438 + return f_io(f, b, len_b, off, true); 439 + } 440 + 441 + file_error_t PagerTruncate(Pager *f, off_t length) { 442 + if (f == nil) { 443 + return "invalid argument"; 444 + } 445 + 446 + /* flush unwritten changes to disk */ 447 + PagerFlush(f); 448 + 449 + /* sync_RWMutexRLock(&f->mu); */ 450 + /* set new file size */ 451 + Handle handle = f->file; 452 + Result res = FSFILE_SetSize(handle, (u64) length); 453 + if (R_FAILED(res)) { 454 + return "I/O error"; 455 + } 456 + /* sync_RWMutexRUnlock(&f->mu); */ 457 + 458 + /* FIXME: truncate only required pages. Remove all for now */ 459 + PagerClear(f); 460 + f = NewPager(handle); 461 + return nil; 462 + } 463 + 464 + void PagerClear(Pager *f) { 465 + if (f) { 466 + cslice_set_elem_destructor(f->shards, s_free); 467 + cslice_clear(f->shards); 468 + free(f); 469 + } 470 + } 471 +
+37
firmware/target/hosted/ctru/lib/bfile/bfile.h
··· 1 + #ifndef _BFILE_H_ 2 + #define _BFILE_H_ 3 + 4 + #include "bfile-internal.h" 5 + 6 + static const int defaultPageSize = 4096; // all pages are this size 7 + static const int defaultBufferSize = 0x800000; // default buffer size, 8 MB 8 + static const int minPages = 4; // minimum total pages per file 9 + static const int pagesPerShard = 32; // ideal number of pages per shard 10 + static const int maxShards = 128; // maximum number of shards per file 11 + 12 + // NewPager returns a new Pager that is backed by the provided file. 13 + Pager* NewPager(Handle file); 14 + 15 + // NewPagerSize returns a new Pager with a custom page size and buffer size. 16 + // The bufferSize is the maximum amount of memory dedicated to individual 17 + // pages. Setting pageSize and bufferSize to zero will use their defaults, 18 + // which are 4096 and 8 MB respectively. Custom values are rounded up to the 19 + // nearest power of 2. 20 + Pager* NewPagerSize(Handle file, int pageSize, int bufferSize); 21 + 22 + // ReadAt reads len(b) bytes from the File starting at byte offset off. 23 + int_error_t PagerReadAt(Pager *f, u8 *b, size_t len_b, off_t off); 24 + 25 + // WriteAt writes len(b) bytes to the File starting at byte offset off. 26 + int_error_t PagerWriteAt(Pager *f, u8 *b, size_t len_b, off_t off); 27 + 28 + // Flush writes any unwritten buffered data to the underlying file. 29 + file_error_t PagerFlush(Pager *f); 30 + 31 + // Truncates pager to specified length 32 + file_error_t PagerTruncate(Pager *f, off_t length); 33 + 34 + // Free all memory associated to a Pager file 35 + void PagerClear(Pager *f); 36 + 37 + #endif /* _B_FILE_H_ */
+209
firmware/target/hosted/ctru/lib/cmap.h
··· 1 + #ifndef CMAP_H_ 2 + #define CMAP_H_ 3 + 4 + #define CVECTOR_LINEAR_GROWTH 5 + #include "cvector.h" 6 + 7 + /* note: for ease of porting go code to c, many functions (macros) names 8 + remain similar to the ones used by go */ 9 + 10 + /* 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 */ 11 + 12 + #define nil NULL 13 + 14 + #define cmap_elem_destructor_t cvector_elem_destructor_t 15 + 16 + /** 17 + * @brief cmap_declare - The map type used in this library 18 + * @param name - The name associated to a map type. 19 + * @param key_type - The map pair key type. 20 + * @param val_type - The map pair value type. 21 + * @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. 22 + */ 23 + #define cmap_declare(name, key_type, val_type) \ 24 + typedef struct { \ 25 + key_type key; \ 26 + val_type value; \ 27 + } name##_pair_t; \ 28 + \ 29 + typedef struct { \ 30 + cvector(name##_pair_t) tree; \ 31 + cmap_elem_destructor_t elem_destructor; \ 32 + } name##_map_t; \ 33 + \ 34 + static inline val_type name##_get_( \ 35 + name##_map_t *this, const key_type key) \ 36 + { \ 37 + if (this) { \ 38 + size_t i; \ 39 + for (i = 0; i < cvector_size(this->tree); i++) { \ 40 + if (key == this->tree[i].key) { \ 41 + return this->tree[i].value; \ 42 + } \ 43 + } \ 44 + } \ 45 + return 0; \ 46 + } \ 47 + \ 48 + static inline val_type name##_get_ptr_( \ 49 + name##_map_t *this, const key_type key) \ 50 + { \ 51 + if (this) { \ 52 + size_t i; \ 53 + for (i = 0; i < cvector_size(this->tree); i++) { \ 54 + if (key == this->tree[i].key) { \ 55 + return this->tree[i].value; \ 56 + } \ 57 + } \ 58 + } \ 59 + return nil; \ 60 + } \ 61 + \ 62 + static inline void name##_set_( \ 63 + name##_map_t *this, const key_type key, val_type value) \ 64 + { \ 65 + if (this) { \ 66 + size_t i; \ 67 + for (i = 0; i < cvector_size(this->tree); i++) { \ 68 + if (key == this->tree[i].key) { \ 69 + this->tree[i].value = value; \ 70 + return; \ 71 + } \ 72 + } \ 73 + name##_pair_t new_pair = (name##_pair_t) { key, value }; \ 74 + cvector_push_back(this->tree, new_pair); \ 75 + } \ 76 + } \ 77 + \ 78 + static inline void name##_delete_( \ 79 + name##_map_t *this, const key_type key) \ 80 + { \ 81 + if (this) { \ 82 + size_t i; \ 83 + for (i = 0; i < cvector_size(this->tree); i++) { \ 84 + if (key == this->tree[i].key) { \ 85 + cvector_erase(this->tree, i); \ 86 + return; \ 87 + } \ 88 + } \ 89 + } \ 90 + } \ 91 + \ 92 + static inline name##_map_t* name##_map_make_(void) \ 93 + { \ 94 + name##_map_t *map = (name##_map_t*) malloc(sizeof(name##_map_t)); \ 95 + if (map) { \ 96 + map->tree = nil; \ 97 + cvector_init(map->tree, 0, nil); \ 98 + return map; \ 99 + } \ 100 + return nil; \ 101 + } \ 102 + \ 103 + static inline void name##_clear_(name##_map_t *this) \ 104 + { \ 105 + if (this) { \ 106 + cvector_free(this->tree); \ 107 + free(this); \ 108 + } \ 109 + } \ 110 + \ 111 + 112 + 113 + /** 114 + * @brief cmap - The map type used in this library 115 + * @param name - The name associated to a map type. 116 + */ 117 + #define cmap(name) name##_map_t * 118 + 119 + /** 120 + * @brief cmap_make - creates a new map. Automatically initializes the map. 121 + * @param name - the name asociated to the map type 122 + * @return a pointer to a new map. 123 + */ 124 + #define cmap_make(name) name##_map_make_() 125 + 126 + /** 127 + * @brief cmap_size - gets the current size of the map 128 + * @param map_ptr - the map pointer 129 + * @return the size as a size_t 130 + */ 131 + #define cmap_len(map_ptr) cvector_size(map_ptr->tree) 132 + 133 + /** 134 + * @brief cmap_get - gets value associated to a key. 135 + * @param name - the name asociated to the map type 136 + * @param map_ptr - the map pointer 137 + * @param key - the key to search for 138 + * @return the value associated to a key 139 + */ 140 + #define cmap_get(name, map_ptr, key) name##_get_(map_ptr, key) 141 + 142 + /** 143 + * @brief cmap_get-ptr - gets ptr_value associated to a key. Use it to avoid assigning a ptr to 0. 144 + * @param name - the name asociated to the map type 145 + * @param map_ptr - the map pointer 146 + * @param key - the key to search for 147 + * @return the value associated to a key 148 + */ 149 + #define cmap_get_ptr(name, map_ptr, key) name##_get_ptr_(map_ptr, key) 150 + 151 + 152 + /** 153 + * @brief cmap_set - sets value associated to a key. 154 + * @param name - the name asociated to the map type 155 + * @param map_ptr - the map pointer 156 + * @param key - the key to search for 157 + * @param value - the new value 158 + * @return void 159 + */ 160 + #define cmap_set(name, map_ptr, key, val) name##_set_(map_ptr, key, val) 161 + 162 + /** 163 + * @brief cmap_delete - deletes map entry associated to a key. 164 + * @param name - the name asociated to the map type 165 + * @param map_ptr - the map pointer 166 + * @param key - the key to search for 167 + * @return void 168 + */ 169 + #define cmap_delete(name, map_ptr, key) name##_delete_(map_ptr, key) 170 + 171 + /** 172 + * @brief cmap_set_elem_destructor - set the element destructor function 173 + * used to clean up removed elements. The map must NOT be NULL for this to do anything. 174 + * @param map_ptr - the map pointer 175 + * @param elem_destructor_fn - function pointer of type cvector_elem_destructor_t used to destroy elements 176 + * @return void 177 + */ 178 + #define cmap_set_elem_destructor(map_ptr, elem_destructor_fn) \ 179 + cvector_set_elem_destructor(map_ptr->tree, elem_destructor_fn) 180 + 181 + /** 182 + * @brief cmap_clear - deletes all map entries. And frees memory is an element destructor was set previously. 183 + * @param name - the name asociated to the map type 184 + * @param map_ptr - the map pointer 185 + * @return void 186 + */ 187 + #define cmap_clear(name, map_ptr) name##_clear_(map_ptr) 188 + 189 + /** 190 + * @brief cmap_iterator - The iterator type used for cmap 191 + * @param type The type of iterator to act on. 192 + */ 193 + #define cmap_iterator(name) cvector_iterator(name##_pair_t) 194 + 195 + /** 196 + * @brief cmap_begin - returns an iterator to first element of the vector 197 + * @param map_ptr - the map pointer 198 + * @return a pointer to the first element (or NULL) 199 + */ 200 + #define cmap_begin(map_ptr) ((map_ptr) ? cvector_begin(map_ptr->tree) : nil) 201 + 202 + /** 203 + * @brief cmap_end - returns an iterator to one past the last element of the vector 204 + * @param map_ptrs - the map pointer 205 + * @return a pointer to one past the last element (or NULL) 206 + */ 207 + #define cmap_end(map_ptr) ((map_ptr) ? cvector_end(map_ptr->tree) : nil) 208 + 209 + #endif /* CMAP_H_ */
+63
firmware/target/hosted/ctru/lib/cslice.h
··· 1 + #ifndef CSLICE_H_ 2 + #define CSLICE_H_ 3 + 4 + #define CVECTOR_LINEAR_GROWTH 5 + #include "cvector.h" 6 + 7 + /* note: for ease of porting go code to c, many functions (macros) names 8 + remain similar to the ones used by go */ 9 + 10 + #define nil NULL 11 + 12 + /** 13 + * @brief cslice - The slice type used in this library 14 + * @param type The type of slice to act on. 15 + */ 16 + #define cslice(type) cvector(type) 17 + 18 + /** 19 + * @brief cslice_make - creates a new slice. Automatically initializes the slice. 20 + * @param slice - the slice 21 + * @param count - new size of the slice 22 + * @param value - the value to initialize new elements with 23 + * @return void 24 + */ 25 + #define cslice_make(slice, capacity, value) \ 26 + do { \ 27 + slice = nil; \ 28 + cvector_init(slice, capacity, nil); \ 29 + cvector_resize(slice, capacity, value); \ 30 + } while(0) 31 + 32 + /** 33 + * @brief cslice_size - gets the current size of the slice 34 + * @param slice - the slice 35 + * @return the size as a size_t 36 + */ 37 + #define cslice_len(slice) cvector_size(slice) 38 + 39 + /** 40 + * @brief cslice_capacity - gets the current capacity of the slice 41 + * @param slice - the slice 42 + * @return the capacity as a size_t 43 + */ 44 + #define cslice_cap(slice) cvector_capacity(slice) 45 + 46 + /** 47 + * @brief cslice_set_elem_destructor - set the element destructor function 48 + * used to clean up removed elements. The vector must NOT be NULL for this to do anything. 49 + * @param slice - the slice 50 + * @param elem_destructor_fn - function pointer of type cslice_elem_destructor_t used to destroy elements 51 + * @return void 52 + */ 53 + #define cslice_set_elem_destructor(slice, elem_destructor_fn) \ 54 + cvector_set_elem_destructor(slice, elem_destructor_fn) 55 + 56 + /** 57 + * @brief cslice_free - frees all memory associated with the slice 58 + * @param slice - the slice 59 + * @return void 60 + */ 61 + #define cslice_clear(slice) cvector_free(slice) 62 + 63 + #endif /* CSLICE_H_ */
+549
firmware/target/hosted/ctru/lib/cvector.h
··· 1 + #ifndef CVECTOR_H_ 2 + #define CVECTOR_H_ 3 + /** 4 + * @copyright Copyright (c) 2015 Evan Teran, 5 + * License: The MIT License (MIT) 6 + * @brief cvector heap implemented using C library malloc() 7 + * @file cvector.h 8 + */ 9 + 10 + /* in case C library malloc() needs extra protection, 11 + * allow these defines to be overridden. 12 + */ 13 + /* functions for allocation and deallocation need to correspond to each other, fall back to C library functions if not all are overridden */ 14 + #if !defined(cvector_clib_free) || !defined(cvector_clib_malloc) || !defined(cvector_clib_calloc) || !defined(cvector_clib_realloc) 15 + #ifdef cvector_clib_free 16 + #undef cvector_clib_free 17 + #endif 18 + #ifdef cvector_clib_malloc 19 + #undef cvector_clib_malloc 20 + #endif 21 + #ifdef cvector_clib_calloc 22 + #undef cvector_clib_calloc 23 + #endif 24 + #ifdef cvector_clib_realloc 25 + #undef cvector_clib_realloc 26 + #endif 27 + #include <stdlib.h> 28 + #define cvector_clib_free free 29 + #define cvector_clib_malloc malloc 30 + #define cvector_clib_calloc calloc 31 + #define cvector_clib_realloc realloc 32 + #endif 33 + /* functions independent of memory allocation */ 34 + #ifndef cvector_clib_assert 35 + #include <assert.h> /* for assert */ 36 + #define cvector_clib_assert assert 37 + #endif 38 + #ifndef cvector_clib_memcpy 39 + #include <string.h> /* for memcpy */ 40 + #define cvector_clib_memcpy memcpy 41 + #endif 42 + #ifndef cvector_clib_memmove 43 + #include <string.h> /* for memmove */ 44 + #define cvector_clib_memmove memmove 45 + #endif 46 + 47 + /* NOTE: Similar to C's qsort and bsearch, you will receive a T* 48 + * for a vector of Ts. This means that you cannot use `free` directly 49 + * as a destructor. Instead if you have for example a cvector_vector_type(int *) 50 + * you will need to supply a function which casts `elem_ptr` to an `int**` 51 + * and then does a free on what that pointer points to: 52 + * 53 + * ex: 54 + * 55 + * void free_int(void *p) { free(*(int **)p); } 56 + */ 57 + typedef void (*cvector_elem_destructor_t)(void *elem_ptr); 58 + 59 + typedef struct cvector_metadata_t { 60 + size_t size; 61 + size_t capacity; 62 + cvector_elem_destructor_t elem_destructor; 63 + } cvector_metadata_t; 64 + 65 + /** 66 + * @brief cvector_vector_type - The vector type used in this library 67 + * @param type The type of vector to act on. 68 + */ 69 + #define cvector_vector_type(type) type * 70 + 71 + /** 72 + * @brief cvector - Syntactic sugar to retrieve a vector type 73 + * @param type The type of vector to act on. 74 + */ 75 + #define cvector(type) cvector_vector_type(type) 76 + 77 + /** 78 + * @brief cvector_iterator - The iterator type used for cvector 79 + * @param type The type of iterator to act on. 80 + */ 81 + #define cvector_iterator(type) cvector_vector_type(type) 82 + 83 + /** 84 + * @note you can also safely pass a pointer to a cvector iterator to a function 85 + * but you have to update the pointer at the end to update the original 86 + * iterator. 87 + * example: 88 + * void function( cvector_vector_type( type ) * p_it ) 89 + * { 90 + * cvector_vector_type( type ) it = *p_it; 91 + * it ++; 92 + * 93 + * ... 94 + * 95 + * *p_it = it; 96 + * } 97 + */ 98 + 99 + /** 100 + * @brief cvector_vector_type_ptr - helper to make code more "readable" 101 + * @param type - the vector type pointer 102 + */ 103 + #define cvector_ptr_type(type) \ 104 + cvector_vector_type(type) * 105 + 106 + /** 107 + * @brief cvector_vector_ptr_get_iterator/set - helpers to make code more "readable" 108 + * @param it - the vector iterator 109 + * @param ptr - the vector pointer 110 + */ 111 + #define cvector_ptr_get_iterator(ptr) \ 112 + *(ptr) 113 + 114 + #define cvector_ptr_set(ptr, it) \ 115 + *(ptr) = it 116 + 117 + /** 118 + * @brief cvector_vector_container_declare - defined a vector container type 119 + */ 120 + #define cvector_vector_container_declare(name, type) \ 121 + struct cvector_vector_container_##name { \ 122 + cvector_vector_type(type) vector; \ 123 + } 124 + 125 + /** 126 + * @brief cvector_vector_container - used to pass a cvector wrapped inside a container as a function parameter 127 + */ 128 + #define cvector_vector_container(name) \ 129 + struct cvector_vector_container_##name 130 + 131 + 132 + /** 133 + * @brief cvector_vec_to_base - For internal use, converts a vector pointer to a metadata pointer 134 + * @param vec - the vector 135 + * @return the metadata pointer of the vector 136 + * @internal 137 + */ 138 + #define cvector_vec_to_base(vec) \ 139 + (&((cvector_metadata_t *)(void *)(vec))[-1]) 140 + 141 + /** 142 + * @brief cvector_base_to_vec - For internal use, converts a metadata pointer to a vector pointer 143 + * @param ptr - pointer to the metadata 144 + * @return the vector 145 + * @internal 146 + */ 147 + #define cvector_base_to_vec(ptr) \ 148 + ((void *)&((cvector_metadata_t *)(ptr))[1]) 149 + 150 + /** 151 + * @brief cvector_capacity - gets the current capacity of the vector 152 + * @param vec - the vector 153 + * @return the capacity as a size_t 154 + */ 155 + #define cvector_capacity(vec) \ 156 + ((vec) ? cvector_vec_to_base(vec)->capacity : (size_t)0) 157 + 158 + /** 159 + * @brief cvector_size - gets the current size of the vector 160 + * @param vec - the vector 161 + * @return the size as a size_t 162 + */ 163 + #define cvector_size(vec) \ 164 + ((vec) ? cvector_vec_to_base(vec)->size : (size_t)0) 165 + 166 + /** 167 + * @brief cvector_elem_destructor - get the element destructor function used 168 + * to clean up elements 169 + * @param vec - the vector 170 + * @return the function pointer as cvector_elem_destructor_t 171 + */ 172 + #define cvector_elem_destructor(vec) \ 173 + ((vec) ? cvector_vec_to_base(vec)->elem_destructor : NULL) 174 + 175 + /** 176 + * @brief cvector_empty - returns non-zero if the vector is empty 177 + * @param vec - the vector 178 + * @return non-zero if empty, zero if non-empty 179 + */ 180 + #define cvector_empty(vec) \ 181 + (cvector_size(vec) == 0) 182 + 183 + /** 184 + * @brief cvector_reserve - Requests that the vector capacity be at least enough 185 + * to contain n elements. If n is greater than the current vector capacity, the 186 + * function causes the container to reallocate its storage increasing its 187 + * capacity to n (or greater). 188 + * @param vec - the vector 189 + * @param n - Minimum capacity for the vector. 190 + * @return void 191 + */ 192 + #define cvector_reserve(vec, n) \ 193 + do { \ 194 + size_t cv_reserve_cap__ = cvector_capacity(vec); \ 195 + if (cv_reserve_cap__ < (n)) { \ 196 + cvector_grow((vec), (n)); \ 197 + } \ 198 + } while (0) 199 + 200 + /** 201 + * @brief cvector_init - Initialize a vector. The vector must be NULL for this to do anything. 202 + * @param vec - the vector 203 + * @param capacity - vector capacity to reserve 204 + * @param elem_destructor_fn - element destructor function 205 + * @return void 206 + */ 207 + #define cvector_init(vec, capacity, elem_destructor_fn) \ 208 + do { \ 209 + if (!(vec)) { \ 210 + cvector_reserve((vec), capacity); \ 211 + cvector_set_elem_destructor((vec), (elem_destructor_fn)); \ 212 + } \ 213 + } while (0) 214 + 215 + /** 216 + * @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. 217 + * @param vec - the vector 218 + * @param capacity - vector capacity to reserve 219 + * @param elem_destructor_fn - element destructor function 220 + * @return void 221 + */ 222 + #define cvector_init_default(vec, capacity, default) \ 223 + do { \ 224 + if (!(vec)) { \ 225 + cvector_reserve((vec), capacity); \ 226 + cvector_set_elem_destructor((vec), (elem_destructor_fn)); \ 227 + } \ 228 + } while (0) 229 + 230 + /** 231 + * @brief cvector_erase - removes the element at index i from the vector 232 + * @param vec - the vector 233 + * @param i - index of element to remove 234 + * @return void 235 + */ 236 + #define cvector_erase(vec, i) \ 237 + do { \ 238 + if (vec) { \ 239 + const size_t cv_erase_sz__ = cvector_size(vec); \ 240 + if ((i) < cv_erase_sz__) { \ 241 + cvector_elem_destructor_t cv_erase_elem_dtor__ = cvector_elem_destructor(vec); \ 242 + if (cv_erase_elem_dtor__) { \ 243 + cv_erase_elem_dtor__(&(vec)[i]); \ 244 + } \ 245 + cvector_set_size((vec), cv_erase_sz__ - 1); \ 246 + cvector_clib_memmove( \ 247 + (vec) + (i), \ 248 + (vec) + (i) + 1, \ 249 + sizeof(*(vec)) * (cv_erase_sz__ - 1 - (i))); \ 250 + } \ 251 + } \ 252 + } while (0) 253 + 254 + /** 255 + * @brief cvector_clear - erase all of the elements in the vector 256 + * @param vec - the vector 257 + * @return void 258 + */ 259 + #define cvector_clear(vec) \ 260 + do { \ 261 + if (vec) { \ 262 + cvector_elem_destructor_t cv_clear_elem_dtor__ = cvector_elem_destructor(vec); \ 263 + if (cv_clear_elem_dtor__) { \ 264 + size_t cv_clear_i__; \ 265 + for (cv_clear_i__ = 0; cv_clear_i__ < cvector_size(vec); ++cv_clear_i__) { \ 266 + cv_clear_elem_dtor__(&(vec)[cv_clear_i__]); \ 267 + } \ 268 + } \ 269 + cvector_set_size(vec, 0); \ 270 + } \ 271 + } while (0) 272 + 273 + /** 274 + * @brief cvector_free - frees all memory associated with the vector 275 + * @param vec - the vector 276 + * @return void 277 + */ 278 + #define cvector_free(vec) \ 279 + do { \ 280 + if (vec) { \ 281 + void *cv_free_p__ = cvector_vec_to_base(vec); \ 282 + cvector_elem_destructor_t cv_free_elem_dtor__ = cvector_elem_destructor(vec); \ 283 + if (cv_free_elem_dtor__) { \ 284 + size_t cv_free_i__; \ 285 + for (cv_free_i__ = 0; cv_free_i__ < cvector_size(vec); ++cv_free_i__) { \ 286 + cv_free_elem_dtor__(&(vec)[cv_free_i__]); \ 287 + } \ 288 + } \ 289 + cvector_clib_free(cv_free_p__); \ 290 + } \ 291 + } while (0) 292 + 293 + /** 294 + * @brief cvector_begin - returns an iterator to first element of the vector 295 + * @param vec - the vector 296 + * @return a pointer to the first element (or NULL) 297 + */ 298 + #define cvector_begin(vec) \ 299 + (vec) 300 + 301 + /** 302 + * @brief cvector_end - returns an iterator to one past the last element of the vector 303 + * @param vec - the vector 304 + * @return a pointer to one past the last element (or NULL) 305 + */ 306 + #define cvector_end(vec) \ 307 + ((vec) ? &((vec)[cvector_size(vec)]) : NULL) 308 + 309 + /* user request to use linear growth algorithm */ 310 + #ifdef CVECTOR_LINEAR_GROWTH 311 + 312 + /** 313 + * @brief cvector_compute_next_grow - returns an the computed size in next vector grow 314 + * size is increased by 1 315 + * @param size - current size 316 + * @return size after next vector grow 317 + */ 318 + #define cvector_compute_next_grow(size) \ 319 + ((size) + 1) 320 + 321 + #else 322 + 323 + /** 324 + * @brief cvector_compute_next_grow - returns an the computed size in next vector grow 325 + * size is increased by multiplication of 2 326 + * @param size - current size 327 + * @return size after next vector grow 328 + */ 329 + #define cvector_compute_next_grow(size) \ 330 + ((size) ? ((size) << 1) : 1) 331 + 332 + #endif /* CVECTOR_LINEAR_GROWTH */ 333 + 334 + /** 335 + * @brief cvector_push_back - adds an element to the end of the vector 336 + * @param vec - the vector 337 + * @param value - the value to add 338 + * @return void 339 + */ 340 + #define cvector_push_back(vec, value) \ 341 + do { \ 342 + size_t cv_push_back_cap__ = cvector_capacity(vec); \ 343 + if (cv_push_back_cap__ <= cvector_size(vec)) { \ 344 + cvector_grow((vec), cvector_compute_next_grow(cv_push_back_cap__)); \ 345 + } \ 346 + (vec)[cvector_size(vec)] = (value); \ 347 + cvector_set_size((vec), cvector_size(vec) + 1); \ 348 + } while (0) 349 + 350 + /** 351 + * @brief cvector_insert - insert element at position pos to the vector 352 + * @param vec - the vector 353 + * @param pos - position in the vector where the new elements are inserted. 354 + * @param val - value to be copied (or moved) to the inserted elements. 355 + * @return void 356 + */ 357 + #define cvector_insert(vec, pos, val) \ 358 + do { \ 359 + size_t cv_insert_cap__ = cvector_capacity(vec); \ 360 + if (cv_insert_cap__ <= cvector_size(vec)) { \ 361 + cvector_grow((vec), cvector_compute_next_grow(cv_insert_cap__)); \ 362 + } \ 363 + if ((pos) < cvector_size(vec)) { \ 364 + cvector_clib_memmove( \ 365 + (vec) + (pos) + 1, \ 366 + (vec) + (pos), \ 367 + sizeof(*(vec)) * ((cvector_size(vec)) - (pos))); \ 368 + } \ 369 + (vec)[(pos)] = (val); \ 370 + cvector_set_size((vec), cvector_size(vec) + 1); \ 371 + } while (0) 372 + 373 + /** 374 + * @brief cvector_pop_back - removes the last element from the vector 375 + * @param vec - the vector 376 + * @return void 377 + */ 378 + #define cvector_pop_back(vec) \ 379 + do { \ 380 + cvector_elem_destructor_t cv_pop_back_elem_dtor__ = cvector_elem_destructor(vec); \ 381 + if (cv_pop_back_elem_dtor__) { \ 382 + cv_pop_back_elem_dtor__(&(vec)[cvector_size(vec) - 1]); \ 383 + } \ 384 + cvector_set_size((vec), cvector_size(vec) - 1); \ 385 + } while (0) 386 + 387 + /** 388 + * @brief cvector_copy - copy a vector 389 + * @param from - the original vector 390 + * @param to - destination to which the function copy to 391 + * @return void 392 + */ 393 + #define cvector_copy(from, to) \ 394 + do { \ 395 + if ((from)) { \ 396 + cvector_grow(to, cvector_size(from)); \ 397 + cvector_set_size(to, cvector_size(from)); \ 398 + cvector_clib_memcpy((to), (from), cvector_size(from) * sizeof(*(from))); \ 399 + } \ 400 + } while (0) 401 + 402 + /** 403 + * @brief cvector_swap - exchanges the content of the vector by the content of another vector of the same type 404 + * @param vec - the original vector 405 + * @param other - the other vector to swap content with 406 + * @param type - the type of both vectors 407 + * @return void 408 + */ 409 + #define cvector_swap(vec, other, type) \ 410 + do { \ 411 + if (vec && other) { \ 412 + cvector_vector_type(type) cv_swap__ = vec; \ 413 + vec = other; \ 414 + other = cv_swap__; \ 415 + } \ 416 + } while (0) 417 + 418 + /** 419 + * @brief cvector_set_capacity - For internal use, sets the capacity variable of the vector 420 + * @param vec - the vector 421 + * @param size - the new capacity to set 422 + * @return void 423 + * @internal 424 + */ 425 + #define cvector_set_capacity(vec, size) \ 426 + do { \ 427 + if (vec) { \ 428 + cvector_vec_to_base(vec)->capacity = (size); \ 429 + } \ 430 + } while (0) 431 + 432 + /** 433 + * @brief cvector_set_size - For internal use, sets the size variable of the vector 434 + * @param vec - the vector 435 + * @param _size - the new capacity to set 436 + * @return void 437 + * @internal 438 + */ 439 + #define cvector_set_size(vec, _size) \ 440 + do { \ 441 + if (vec) { \ 442 + cvector_vec_to_base(vec)->size = (_size); \ 443 + } \ 444 + } while (0) 445 + 446 + /** 447 + * @brief cvector_set_elem_destructor - set the element destructor function 448 + * used to clean up removed elements. The vector must NOT be NULL for this to do anything. 449 + * @param vec - the vector 450 + * @param elem_destructor_fn - function pointer of type cvector_elem_destructor_t used to destroy elements 451 + * @return void 452 + */ 453 + #define cvector_set_elem_destructor(vec, elem_destructor_fn) \ 454 + do { \ 455 + if (vec) { \ 456 + cvector_vec_to_base(vec)->elem_destructor = (elem_destructor_fn); \ 457 + } \ 458 + } while (0) 459 + 460 + /** 461 + * @brief cvector_grow - For internal use, ensures that the vector is at least `count` elements big 462 + * @param vec - the vector 463 + * @param count - the new capacity to set 464 + * @return void 465 + * @internal 466 + */ 467 + #define cvector_grow(vec, count) \ 468 + do { \ 469 + const size_t cv_grow_sz__ = (count) * sizeof(*(vec)) + sizeof(cvector_metadata_t); \ 470 + if (vec) { \ 471 + void *cv_grow_p1__ = cvector_vec_to_base(vec); \ 472 + void *cv_grow_p2__ = cvector_clib_realloc(cv_grow_p1__, cv_grow_sz__); \ 473 + cvector_clib_assert(cv_grow_p2__); \ 474 + (vec) = cvector_base_to_vec(cv_grow_p2__); \ 475 + } else { \ 476 + void *cv_grow_p__ = cvector_clib_malloc(cv_grow_sz__); \ 477 + cvector_clib_assert(cv_grow_p__); \ 478 + (vec) = cvector_base_to_vec(cv_grow_p__); \ 479 + cvector_set_size((vec), 0); \ 480 + cvector_set_elem_destructor((vec), NULL); \ 481 + } \ 482 + cvector_set_capacity((vec), (count)); \ 483 + } while (0) 484 + 485 + /** 486 + * @brief cvector_shrink_to_fit - requests the container to reduce its capacity to fit its size 487 + * @param vec - the vector 488 + * @return void 489 + */ 490 + #define cvector_shrink_to_fit(vec) \ 491 + do { \ 492 + if (vec) { \ 493 + const size_t cv_shrink_to_fit_sz__ = cvector_size(vec); \ 494 + cvector_grow(vec, cv_shrink_to_fit_sz__); \ 495 + } \ 496 + } while (0) 497 + 498 + /** 499 + * @brief cvector_at - returns a reference to the element at position n in the vector. 500 + * @param vec - the vector 501 + * @param n - position of an element in the vector. 502 + * @return the element at the specified position in the vector. 503 + */ 504 + #define cvector_at(vec, n) \ 505 + ((vec) ? (((int)(n) < 0 || (size_t)(n) >= cvector_size(vec)) ? NULL : &(vec)[n]) : NULL) 506 + 507 + /** 508 + * @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. 509 + * @param vec - the vector 510 + * @return a reference to the first element in the vector container. 511 + */ 512 + #define cvector_front(vec) \ 513 + ((vec) ? ((cvector_size(vec) > 0) ? cvector_at(vec, 0) : NULL) : NULL) 514 + 515 + /** 516 + * @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. 517 + * @param vec - the vector 518 + * @return a reference to the last element in the vector. 519 + */ 520 + #define cvector_back(vec) \ 521 + ((vec) ? ((cvector_size(vec) > 0) ? cvector_at(vec, cvector_size(vec) - 1) : NULL) : NULL) 522 + 523 + /** 524 + * @brief cvector_resize - resizes the container to contain count elements. 525 + * @param vec - the vector 526 + * @param count - new size of the vector 527 + * @param value - the value to initialize new elements with 528 + * @return void 529 + */ 530 + #define cvector_resize(vec, count, value) \ 531 + do { \ 532 + if (vec) { \ 533 + size_t cv_resize_count__ = (size_t)(count); \ 534 + size_t cv_resize_sz__ = cvector_vec_to_base(vec)->size; \ 535 + if (cv_resize_count__ > cv_resize_sz__) { \ 536 + cvector_reserve((vec), cv_resize_count__); \ 537 + cvector_set_size((vec), cv_resize_count__); \ 538 + do { \ 539 + (vec)[cv_resize_sz__++] = (value); \ 540 + } while (cv_resize_sz__ < cv_resize_count__); \ 541 + } else { \ 542 + while (cv_resize_count__ < cv_resize_sz__--) { \ 543 + cvector_pop_back(vec); \ 544 + } \ 545 + } \ 546 + } \ 547 + } while (0) 548 + 549 + #endif /* CVECTOR_H_ */
+403
firmware/target/hosted/ctru/lib/sys_dir.c
··· 1 + /*************************************************************************** 2 + * __________ __ ___. 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * $Id$ 9 + * 10 + * Copyright (C) 2025 Mauricio G. 11 + * 12 + * This program is free software; you can redistribute it and/or 13 + * modify it under the terms of the GNU General Public License 14 + * as published by the Free Software Foundation; either version 2 15 + * of the License, or (at your option) any later version. 16 + * 17 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 + * KIND, either express or implied. 19 + * 20 + ****************************************************************************/ 21 + #define DIRFUNCTIONS_DEFINED 22 + #include "config.h" 23 + #include <errno.h> 24 + #include <string.h> 25 + #include "debug.h" 26 + #include "dir.h" 27 + #include "pathfuncs.h" 28 + #include "timefuncs.h" 29 + #include "system.h" 30 + #include "fs_defines.h" 31 + #include "sys_file.h" 32 + 33 + #include <3ds/archive.h> 34 + #include <3ds/util/utf.h> 35 + 36 + /* This file is based on firmware/common/dir.c */ 37 + 38 + /* Define LOGF_ENABLE to enable logf output in this file */ 39 + // #define LOGF_ENABLE 40 + #include "logf.h" 41 + 42 + /* structure used for open directory streams */ 43 + static struct dirstr_desc 44 + { 45 + struct filestr_base stream; /* basic stream info (first!) */ 46 + struct dirent entry; /* current parsed entry information */ 47 + } open_streams[MAX_OPEN_DIRS] = 48 + { 49 + [0 ... MAX_OPEN_FILES-1] = { .stream = { .handle = 0 } } 50 + }; 51 + 52 + extern FS_Archive sdmcArchive; 53 + 54 + /* check and return a struct dirstr_desc* from a DIR* */ 55 + static struct dirstr_desc * get_dirstr(DIR *dirp) 56 + { 57 + struct dirstr_desc *dir = (struct dirstr_desc *)dirp; 58 + 59 + if (!PTR_IN_ARRAY(open_streams, dir, MAX_OPEN_DIRS)) 60 + dir = NULL; 61 + else if (dir->stream.handle != 0) 62 + return dir; 63 + 64 + int errnum; 65 + 66 + if (!dir) 67 + { 68 + errnum = EFAULT; 69 + } 70 + else 71 + { 72 + logf("dir #%d: dir not open\n", (int)(dir - open_streams)); 73 + errnum = EBADF; 74 + } 75 + 76 + errno = errnum; 77 + return NULL; 78 + } 79 + 80 + #define GET_DIRSTR(type, dirp) \ 81 + ({ \ 82 + file_internal_lock_##type(); \ 83 + struct dirstr_desc *_dir = get_dirstr(dirp); \ 84 + if (_dir) \ 85 + FILESTR_LOCK(type, &_dir->stream); \ 86 + else { \ 87 + file_internal_unlock_##type(); \ 88 + } \ 89 + _dir; \ 90 + }) 91 + 92 + /* release the lock on the dirstr_desc* */ 93 + #define RELEASE_DIRSTR(type, dir) \ 94 + ({ \ 95 + FILESTR_UNLOCK(type, &(dir)->stream); \ 96 + file_internal_unlock_##type(); \ 97 + }) 98 + 99 + 100 + /* find a free dir stream descriptor */ 101 + static struct dirstr_desc * alloc_dirstr(void) 102 + { 103 + for (unsigned int dd = 0; dd < MAX_OPEN_DIRS; dd++) 104 + { 105 + struct dirstr_desc *dir = &open_streams[dd]; 106 + if (dir->stream.handle == 0) 107 + return dir; 108 + } 109 + 110 + logf("Too many dirs open\n"); 111 + return NULL; 112 + } 113 + 114 + u32 fs_error(void) { 115 + u32 err; 116 + FSUSER_GetSdmcFatfsError(&err); 117 + return err; 118 + } 119 + 120 + /* Initialize the base descriptor */ 121 + static void filestr_base_init(struct filestr_base *stream) 122 + { 123 + stream->cache = nil; 124 + stream->handle = 0; 125 + stream->size = 0; 126 + LightLock_Init(&stream->mtx); 127 + } 128 + 129 + /** POSIX interface **/ 130 + 131 + /* open a directory */ 132 + DIR * ctru_opendir(const char *dirname) 133 + { 134 + logf("opendir(dirname=\"%s\")\n", dirname); 135 + 136 + DIR *dirp = NULL; 137 + file_internal_lock_WRITER(); 138 + 139 + int rc; 140 + 141 + struct dirstr_desc * const dir = alloc_dirstr(); 142 + if (!dir) 143 + FILE_ERROR(EMFILE, _RC); 144 + 145 + filestr_base_init(&dir->stream); 146 + Result res = FSUSER_OpenDirectory(&dir->stream.handle, 147 + sdmcArchive, 148 + fsMakePath(PATH_ASCII, dirname)); 149 + if (R_FAILED(res)) { 150 + logf("Open failed: %lld\n", fs_error()); 151 + FILE_ERROR(EMFILE, -1); 152 + } 153 + 154 + dir->stream.size = 0; 155 + dir->stream.flags = 0; 156 + 157 + /* we will use file path to implement ctru_samedir function */ 158 + strcpy(dir->stream.path, dirname); 159 + 160 + dirp = (DIR *)dir; 161 + file_error: 162 + file_internal_unlock_WRITER(); 163 + return dirp; 164 + } 165 + 166 + /* close a directory stream */ 167 + int ctru_closedir(DIR *dirp) 168 + { 169 + int rc; 170 + 171 + file_internal_lock_WRITER(); 172 + 173 + /* needs to work even if marked "nonexistant" */ 174 + struct dirstr_desc * const dir = (struct dirstr_desc *)dirp; 175 + if (!PTR_IN_ARRAY(open_streams, dir, MAX_OPEN_DIRS)) 176 + FILE_ERROR(EFAULT, -1); 177 + 178 + logf("closedir(dirname=\"%s\")\n", dir->stream.path); 179 + 180 + if (dir->stream.handle == 0) 181 + { 182 + logf("dir #%d: dir not open\n", (int)(dir - open_streams)); 183 + FILE_ERROR(EBADF, -2); 184 + } 185 + 186 + Result res = FSDIR_Close(dir->stream.handle); 187 + if (R_FAILED(res)) 188 + FILE_ERROR(ERRNO, -3); 189 + 190 + dir->stream.handle = 0; 191 + dir->stream.path[0] = '\0'; 192 + 193 + rc = 0; 194 + file_error: 195 + file_internal_unlock_WRITER(); 196 + return rc; 197 + } 198 + 199 + void dirstr_entry_init(FS_DirectoryEntry *dirEntry, struct dirent *entry) 200 + { 201 + /* clear */ 202 + memset(entry, 0, sizeof(struct dirent)); 203 + 204 + /* attributes */ 205 + if (dirEntry->attributes & FS_ATTRIBUTE_DIRECTORY) 206 + entry->info.attr |= ATTR_DIRECTORY; 207 + if (dirEntry->attributes & FS_ATTRIBUTE_HIDDEN) 208 + entry->info.attr |= ATTR_HIDDEN; 209 + if (dirEntry->attributes & FS_ATTRIBUTE_ARCHIVE) 210 + entry->info.attr |= ATTR_ARCHIVE; 211 + if (dirEntry->attributes & FS_ATTRIBUTE_READ_ONLY) 212 + entry->info.attr |= ATTR_READ_ONLY; 213 + 214 + /* size */ 215 + entry->info.size = dirEntry->fileSize; 216 + 217 + /* name */ 218 + uint8_t d_name[0xA0 + 1]; 219 + memset(d_name, '\0', 0xA0); 220 + utf16_to_utf8(d_name, (uint16_t *) &dirEntry->name, 0xA0); 221 + memcpy(entry->d_name, d_name, 0xA0); 222 + } 223 + 224 + /* read a directory */ 225 + struct dirent * ctru_readdir(DIR *dirp) 226 + { 227 + struct dirstr_desc * const dir = GET_DIRSTR(READER, dirp); 228 + if (!dir) 229 + FILE_ERROR_RETURN(ERRNO, NULL); 230 + 231 + int rc; 232 + struct dirent *res = NULL; 233 + 234 + logf("readdir(dirname=\"%s\")\n", dir->stream.path); 235 + 236 + u32 dataRead = 0; 237 + FS_DirectoryEntry dirEntry; 238 + Result result = FSDIR_Read(dir->stream.handle, 239 + &dataRead, 240 + 1, 241 + &dirEntry); 242 + if (R_FAILED(result)) 243 + FILE_ERROR(EIO, _RC); 244 + 245 + if (dataRead == 0) { 246 + /* directory end. return NULL value, no errno */ 247 + res = NULL; 248 + goto file_error; 249 + } 250 + 251 + res = &dir->entry; 252 + dirstr_entry_init(&dirEntry, res); 253 + 254 + /* time */ 255 + char full_path[MAX_PATH+1]; 256 + 257 + if (!strcmp(PATH_ROOTSTR, dir->stream.path)) 258 + snprintf(full_path, MAX_PATH, "%s%s", dir->stream.path, res->d_name); 259 + else 260 + snprintf(full_path, MAX_PATH, "%s/%s", dir->stream.path, res->d_name); 261 + 262 + u64 mtime; 263 + archive_getmtime(full_path, &mtime); 264 + 265 + /* DEBUGF("archive_getmtime(%s): %lld\n", full_path, mtime); */ 266 + 267 + uint16_t dosdate, dostime; 268 + dostime_localtime(mtime, &dosdate, &dostime); 269 + res->info.wrtdate = dosdate; 270 + res->info.wrttime = dostime; 271 + 272 + file_error: 273 + RELEASE_DIRSTR(READER, dir); 274 + return res; 275 + } 276 + 277 + /* make a directory */ 278 + int ctru_mkdir(const char *path) 279 + { 280 + logf("mkdir(path=\"%s\")\n", path); 281 + 282 + int rc; 283 + 284 + file_internal_lock_WRITER(); 285 + 286 + Result res = FSUSER_CreateDirectory(sdmcArchive, 287 + fsMakePath(PATH_ASCII, path), 288 + 0); 289 + if (R_FAILED(res)) 290 + FILE_ERROR(ERRNO, -1); 291 + 292 + rc = 0; 293 + file_error: 294 + file_internal_unlock_WRITER(); 295 + return rc; 296 + } 297 + 298 + /* remove a directory */ 299 + int ctru_rmdir(const char *name) 300 + { 301 + logf("rmdir(name=\"%s\")\n", name); 302 + 303 + int rc; 304 + 305 + if (name) 306 + { 307 + /* path may not end with "." */ 308 + const char *basename; 309 + size_t len = path_basename(name, &basename); 310 + if (basename[0] == '.' && len == 1) 311 + { 312 + logf("Invalid path; last component is \".\"\n"); 313 + FILE_ERROR_RETURN(EINVAL, -9); 314 + } 315 + } 316 + 317 + file_internal_lock_WRITER(); 318 + Result res = FSUSER_DeleteDirectory(sdmcArchive, 319 + fsMakePath(PATH_ASCII, name)); 320 + if (R_FAILED(res)) 321 + FILE_ERROR(ERRNO, -1); 322 + 323 + rc = 0; 324 + file_error: 325 + file_internal_unlock_WRITER(); 326 + return rc; 327 + } 328 + 329 + 330 + /** Extended interface **/ 331 + 332 + /* return if two directory streams refer to the same directory */ 333 + int ctru_samedir(DIR *dirp1, DIR *dirp2) 334 + { 335 + struct dirstr_desc * const dir1 = GET_DIRSTR(WRITER, dirp1); 336 + if (!dir1) 337 + FILE_ERROR_RETURN(ERRNO, -1); 338 + 339 + int rc = -2; 340 + 341 + struct dirstr_desc * const dir2 = get_dirstr(dirp2); 342 + if (dir2) { 343 + rc = strcmp(dir1->stream.path, dir2->stream.path) == 0 ? 1 : 0; 344 + } 345 + 346 + RELEASE_DIRSTR(WRITER, dir1); 347 + return rc; 348 + } 349 + 350 + /* test directory existence (returns 'false' if a file) */ 351 + bool ctru_dir_exists(const char *dirname) 352 + { 353 + file_internal_lock_WRITER(); 354 + 355 + int rc; 356 + 357 + Handle handle; 358 + Result res = FSUSER_OpenDirectory(&handle, 359 + sdmcArchive, 360 + fsMakePath(PATH_ASCII, dirname)); 361 + if (R_FAILED(res)) { 362 + logf("Directory not found: %ld\n", fs_error()); 363 + FILE_ERROR(EMFILE, -1); 364 + } 365 + 366 + rc = 0; 367 + file_error: 368 + if (rc == 0) { 369 + FSDIR_Close(handle); 370 + } 371 + file_internal_unlock_WRITER(); 372 + return rc == 0 ? true : false; 373 + } 374 + 375 + /* get the portable info from the native entry */ 376 + struct dirinfo dir_get_info(DIR *dirp, struct dirent *entry) 377 + { 378 + int rc; 379 + if (!dirp || !entry) 380 + FILE_ERROR(EFAULT, _RC); 381 + 382 + if (entry->d_name[0] == '\0') 383 + FILE_ERROR(ENOENT, _RC); 384 + 385 + if ((file_size_t)entry->info.size > FILE_SIZE_MAX) 386 + FILE_ERROR(EOVERFLOW, _RC); 387 + 388 + return (struct dirinfo) 389 + { 390 + .attribute = entry->info.attr, 391 + .size = entry->info.size, 392 + .mtime = dostime_mktime(entry->info.wrtdate, entry->info.wrttime), 393 + }; 394 + 395 + file_error: 396 + return (struct dirinfo){ .attribute = 0 }; 397 + } 398 + 399 + const char* ctru_root_realpath(void) 400 + { 401 + /* Native only, for APP and SIM see respective filesystem-.c files */ 402 + return PATH_ROOTSTR; /* rb_namespace.c */ 403 + }
+777
firmware/target/hosted/ctru/lib/sys_file.c
··· 1 + /*************************************************************************** 2 + * __________ __ ___. 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * $Id$ 9 + * 10 + * Copyright (C) 2025 Mauricio G. 11 + * 12 + * This program is free software; you can redistribute it and/or 13 + * modify it under the terms of the GNU General Public License 14 + * as published by the Free Software Foundation; either version 2 15 + * of the License, or (at your option) any later version. 16 + * 17 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 + * KIND, either express or implied. 19 + * 20 + ****************************************************************************/ 21 + #define RB_FILESYSTEM_OS 22 + #include "config.h" 23 + #include "system.h" 24 + #include <string.h> 25 + #include <errno.h> 26 + #include <fcntl.h> 27 + #include <unistd.h> 28 + #include <utime.h> 29 + #include "file.h" 30 + #include "debug.h" 31 + #include "string-extra.h" 32 + #include "fs_defines.h" 33 + #include "sys_file.h" 34 + 35 + /* This file is based on firmware/common/file.c */ 36 + 37 + /* Define LOGF_ENABLE to enable logf output in this file */ 38 + // #define LOGF_ENABLE 39 + #include "logf.h" 40 + 41 + /** 42 + * These functions provide a roughly POSIX-compatible file I/O API. 43 + * Important: the bufferio I/O library (bfile) used in the 3ds does not work 44 + * with write-only files due to an internal limitation. 45 + * So all files will be opened with the read flag by default. 46 + */ 47 + 48 + /* structure used for open file descriptors */ 49 + static struct filestr_desc 50 + { 51 + struct filestr_base stream; /* basic stream info (first!) */ 52 + file_size_t offset; /* current offset for stream */ 53 + u64 *sizep; /* shortcut to file size in fileobj */ 54 + } open_streams[MAX_OPEN_FILES] = 55 + { 56 + [0 ... MAX_OPEN_FILES-1] = { .stream = { .cache = nil, .flags = 0 } } 57 + }; 58 + 59 + extern FS_Archive sdmcArchive; 60 + 61 + /* check and return a struct filestr_desc* from a file descriptor number */ 62 + static struct filestr_desc * get_filestr(int fildes) 63 + { 64 + struct filestr_desc *file = &open_streams[fildes]; 65 + 66 + if ((unsigned int)fildes >= MAX_OPEN_FILES) 67 + file = NULL; 68 + else if (file->stream.cache != nil) 69 + return file; 70 + 71 + logf("fildes %d: bad file number\n", fildes); 72 + errno = (file && (file->stream.cache == nil)) ? ENXIO : EBADF; 73 + return NULL; 74 + } 75 + 76 + #define GET_FILESTR(type, fildes) \ 77 + ({ \ 78 + file_internal_lock_##type(); \ 79 + struct filestr_desc * _file = get_filestr(fildes); \ 80 + if (_file) \ 81 + FILESTR_LOCK(type, &_file->stream); \ 82 + else { \ 83 + file_internal_unlock_##type(); \ 84 + }\ 85 + _file; \ 86 + }) 87 + 88 + /* release the lock on the filestr_desc* */ 89 + #define RELEASE_FILESTR(type, file) \ 90 + ({ \ 91 + FILESTR_UNLOCK(type, &(file)->stream); \ 92 + file_internal_unlock_##type(); \ 93 + }) 94 + 95 + /* find a free file descriptor */ 96 + static int alloc_filestr(struct filestr_desc **filep) 97 + { 98 + for (int fildes = 0; fildes < MAX_OPEN_FILES; fildes++) 99 + { 100 + struct filestr_desc *file = &open_streams[fildes]; 101 + if (file->stream.cache == nil) 102 + { 103 + *filep = file; 104 + return fildes; 105 + } 106 + } 107 + 108 + logf("Too many files open\n"); 109 + return -1; 110 + } 111 + 112 + /* check for file existence */ 113 + int test_stream_exists_internal(const char *path) 114 + { 115 + int rc; 116 + bool is_dir = false; 117 + 118 + Handle handle; 119 + Result res = FSUSER_OpenFile(&handle, 120 + sdmcArchive, 121 + fsMakePath(PATH_ASCII, path), 122 + FS_OPEN_READ, 123 + 0); 124 + if (R_FAILED(res)) { 125 + /* not a file, try to open a directory */ 126 + res = FSUSER_OpenDirectory(&handle, 127 + sdmcArchive, 128 + fsMakePath(PATH_ASCII, path)); 129 + if (R_FAILED(res)) { 130 + logf("File does not exist\n"); 131 + FILE_ERROR(ERRNO, -1); 132 + } 133 + 134 + is_dir = true; 135 + } 136 + 137 + rc = 1; 138 + file_error: 139 + if (handle > 0) { 140 + if (is_dir) 141 + FSDIR_Close(handle); 142 + else 143 + FSFILE_Close(handle); 144 + } 145 + 146 + return rc; 147 + } 148 + 149 + /* set the file pointer */ 150 + static off_t lseek_internal(struct filestr_desc *file, off_t offset, 151 + int whence) 152 + { 153 + off_t rc; 154 + off_t pos; 155 + 156 + off_t size = MIN(*file->sizep, FILE_SIZE_MAX); 157 + off_t file_offset = AtomicLoad(&file->offset); 158 + 159 + switch (whence) 160 + { 161 + case SEEK_SET: 162 + if (offset < 0 || (off_t)offset > size) 163 + FILE_ERROR(EINVAL, -1); 164 + 165 + pos = offset; 166 + break; 167 + 168 + case SEEK_CUR: 169 + if ((offset < 0 && (off_t)-offset > file_offset) || 170 + (offset > 0 && (off_t)offset > size - file_offset)) 171 + FILE_ERROR(EINVAL, -1); 172 + 173 + pos = file_offset + offset; 174 + break; 175 + 176 + case SEEK_END: 177 + if (offset > 0 || (off_t)-offset > size) 178 + FILE_ERROR(EINVAL, -1); 179 + 180 + pos = size + offset; 181 + break; 182 + 183 + default: 184 + FILE_ERROR(EINVAL, -1); 185 + } 186 + 187 + AtomicSwap(&file->offset, pos); 188 + 189 + return pos; 190 + file_error: 191 + return rc; 192 + } 193 + 194 + /* read from or write to the file; back end to read() and write() */ 195 + static ssize_t readwrite(struct filestr_desc *file, void *buf, size_t nbyte, 196 + bool write) 197 + { 198 + #ifndef LOGF_ENABLE /* wipes out log before you can save it */ 199 + /* DEBUGF("readwrite(%p,%lx,%lu,%s)\n", 200 + file, (long)buf, (unsigned long)nbyte, write ? "write" : "read"); */ 201 + #endif 202 + 203 + const file_size_t size = *file->sizep; 204 + size_t filerem; 205 + 206 + if (write) 207 + { 208 + /* if opened in append mode, move pointer to end */ 209 + if (file->stream.flags & O_APPEND) 210 + AtomicSwap(&file->offset, MIN(size, FILE_SIZE_MAX)); 211 + 212 + filerem = FILE_SIZE_MAX - AtomicLoad(&file->offset); 213 + } 214 + else 215 + { 216 + /* limit to maximum possible offset (EOF or FILE_SIZE_MAX) */ 217 + filerem = MIN(size, FILE_SIZE_MAX) - AtomicLoad(&file->offset); 218 + } 219 + 220 + if (nbyte > filerem) 221 + { 222 + nbyte = filerem; 223 + if (nbyte > 0) 224 + {} 225 + else if (write) 226 + FILE_ERROR_RETURN(EFBIG, -1); /* would get too large */ 227 + else if (AtomicLoad(&file->offset) >= FILE_SIZE_MAX) 228 + FILE_ERROR_RETURN(EOVERFLOW, -2); /* can't read here */ 229 + } 230 + 231 + if (nbyte == 0) 232 + return 0; 233 + 234 + int rc = 0; 235 + int_error_t n_err; 236 + 237 + if (write) 238 + n_err = PagerWriteAt(file->stream.cache, (u8 *) buf, nbyte, AtomicLoad(&file->offset)); 239 + else 240 + n_err = PagerReadAt(file->stream.cache, (u8 *) buf, nbyte, AtomicLoad(&file->offset)); 241 + 242 + if ((n_err.err != nil) && strcmp(n_err.err, "io.EOF")) { 243 + FILE_ERROR(ERRNO, -3); 244 + } 245 + 246 + file_error:; 247 + #ifdef DEBUG 248 + if (errno == ENOSPC) 249 + logf("No space left on device\n"); 250 + #endif 251 + 252 + size_t done = n_err.n; 253 + if (done) 254 + { 255 + /* error or not, update the file offset and size if anything was 256 + transferred */ 257 + AtomicAdd(&file->offset, done); 258 + #ifndef LOGF_ENABLE /* wipes out log before you can save it */ 259 + /* DEBUGF("file offset: %lld\n", file->offset); */ 260 + #endif 261 + /* adjust file size to length written */ 262 + if (write && AtomicLoad(&file->offset) > size) 263 + *file->sizep = AtomicLoad(&file->offset); 264 + 265 + return done; 266 + } 267 + 268 + return rc; 269 + } 270 + 271 + /* initialize the base descriptor */ 272 + static void filestr_base_init(struct filestr_base *stream) 273 + { 274 + stream->cache = nil; 275 + stream->handle = 0; 276 + stream->size = 0; 277 + LightLock_Init(&stream->mtx); 278 + } 279 + 280 + int open_internal_inner2(Handle *handle, const char *path, u32 openFlags, u32 attributes) 281 + { 282 + int rc; 283 + Result res = FSUSER_OpenFile(handle, 284 + sdmcArchive, 285 + fsMakePath(PATH_ASCII, path), 286 + openFlags, 287 + attributes); 288 + if (R_FAILED(res)) { 289 + FILE_ERROR(ERRNO, -1); 290 + } 291 + 292 + rc = 1; 293 + file_error: 294 + return rc; 295 + } 296 + 297 + static int open_internal_inner1(const char *path, int oflag) 298 + { 299 + int rc; 300 + struct filestr_desc *file; 301 + int fildes = alloc_filestr(&file); 302 + if (fildes < 0) 303 + FILE_ERROR_RETURN(EMFILE, -1); 304 + 305 + u32 openFlags = 0, attributes = 0; 306 + 307 + /* open for reading by default */ 308 + openFlags = FS_OPEN_READ; 309 + 310 + if (oflag & O_ACCMODE) 311 + { 312 + if ((oflag & O_ACCMODE) == O_RDONLY) { 313 + attributes |= FS_ATTRIBUTE_READ_ONLY; 314 + } 315 + if ((oflag & O_ACCMODE) == O_WRONLY) { 316 + openFlags |= FS_OPEN_WRITE; 317 + } 318 + if ((oflag & O_ACCMODE) == O_RDWR) { 319 + openFlags |= FS_OPEN_WRITE; 320 + } 321 + } 322 + else if (oflag & O_TRUNC) 323 + { 324 + /* O_TRUNC requires write mode */ 325 + logf("No write mode but have O_TRUNC\n"); 326 + FILE_ERROR(EINVAL, -2); 327 + } 328 + 329 + /* O_CREAT and O_APPEND are fine without write mode 330 + * for the former, an empty file is created but no data may be written 331 + * for the latter, no append will be allowed anyway */ 332 + if (!(oflag & O_CREAT)) 333 + oflag &= ~O_EXCL; /* result is undefined: we choose "ignore" */ 334 + 335 + filestr_base_init(&file->stream); 336 + rc = open_internal_inner2(&file->stream.handle, path, openFlags, attributes); 337 + 338 + if (rc > 0) { 339 + if (oflag & O_EXCL) 340 + { 341 + logf("File exists\n"); 342 + FILE_ERROR(EEXIST, -4); 343 + } 344 + } 345 + else if (oflag & O_CREAT) 346 + { 347 + /* not found; try to create it */ 348 + openFlags |= FS_OPEN_CREATE; 349 + rc = open_internal_inner2(&file->stream.handle, path, openFlags, attributes); 350 + if (rc < 0) 351 + FILE_ERROR(ERRNO, rc * 10 - 6); 352 + } 353 + else 354 + { 355 + logf("File not found\n"); 356 + FILE_ERROR(ENOENT, -5); 357 + } 358 + 359 + /* truncate file if requested */ 360 + if (oflag & O_TRUNC) { 361 + Result res = FSFILE_SetSize(file->stream.handle, 0); 362 + if (R_FAILED(res)) { 363 + FILE_ERROR(ERRNO, -6); 364 + } 365 + } 366 + 367 + /* we need to set file size here, or else lseek 368 + will fail if no read or write has been done */ 369 + u64 size = 0; 370 + Result res = FSFILE_GetSize(file->stream.handle, &size); 371 + if (R_FAILED(res)) { 372 + FILE_ERROR(ERRNO, -8); 373 + } 374 + 375 + int pageSize = 4096; /* 4096 bytes */ 376 + int bufferSize = 512 * 1024; /* 512 kB */ 377 + 378 + /* streamed file formats like flac and mp3 need very large page 379 + sizes to avoid stuttering */ 380 + if (((oflag & O_ACCMODE) == O_RDONLY) && (size > 0x200000)) { 381 + /* printf("open(%s)_BIG_pageSize\n", path); */ 382 + pageSize = 32 * 1024; 383 + bufferSize = MIN(size, defaultBufferSize); 384 + } 385 + 386 + file->stream.cache = NewPagerSize(file->stream.handle, 387 + pageSize, 388 + bufferSize); 389 + if (file->stream.cache == nil) { 390 + FILE_ERROR(ERRNO, -7); 391 + } 392 + 393 + file->stream.flags = oflag; 394 + file->stream.size = size; 395 + file->sizep = &file->stream.size; 396 + AtomicSwap(&file->offset, 0); 397 + 398 + /* we will use file path to implement ctru_fsamefile function */ 399 + strcpy(file->stream.path, path); 400 + 401 + return fildes; 402 + 403 + file_error: 404 + if (fildes >= 0) { 405 + if (file->stream.cache != nil) { 406 + PagerFlush(file->stream.cache); 407 + PagerClear(file->stream.cache); 408 + file->stream.cache = nil; 409 + } 410 + 411 + FSFILE_Close(file->stream.handle); 412 + file->stream.handle = 0; 413 + } 414 + 415 + return rc; 416 + } 417 + 418 + static int open_internal_locked(const char *path, int oflag) 419 + { 420 + file_internal_lock_WRITER(); 421 + int rc = open_internal_inner1(path, oflag); 422 + file_internal_unlock_WRITER(); 423 + return rc; 424 + } 425 + 426 + int ctru_open(const char *path, int oflag, ...) 427 + { 428 + logf("open(path=\"%s\",oflag=%X)\n", path, (unsigned)oflag); 429 + return open_internal_locked(path, oflag); 430 + } 431 + 432 + int ctru_creat(const char *path, mode_t mode) 433 + { 434 + logf("creat(path=\"%s\")\n", path); 435 + return ctru_open(path, O_WRONLY|O_CREAT|O_TRUNC, mode); 436 + } 437 + 438 + int ctru_close(int fildes) 439 + { 440 + logf("close(fd=%d)\n", fildes); 441 + 442 + int rc; 443 + 444 + file_internal_lock_WRITER(); 445 + 446 + /* needs to work even if marked "nonexistant" */ 447 + struct filestr_desc *file = &open_streams[fildes]; 448 + if ((unsigned int)fildes >= MAX_OPEN_FILES || (file->stream.cache == nil)) 449 + { 450 + logf("filedes %d not open\n", fildes); 451 + FILE_ERROR(EBADF, -2); 452 + } 453 + 454 + if (file->stream.cache != nil) { 455 + PagerFlush(file->stream.cache); 456 + PagerClear(file->stream.cache); 457 + file->stream.cache = nil; 458 + } 459 + 460 + FSFILE_Close(file->stream.handle); 461 + file->stream.handle = 0; 462 + file->stream.path[0] = '\0'; 463 + 464 + rc = 0; 465 + file_error: 466 + file_internal_unlock_WRITER(); 467 + return rc; 468 + } 469 + 470 + /* truncate a file to a specified length */ 471 + int ctru_ftruncate(int fildes, off_t length) 472 + { 473 + logf("ftruncate(fd=%d,len=%ld)\n", fildes, (long)length); 474 + 475 + struct filestr_desc * const file = GET_FILESTR(READER, fildes); 476 + if (!file) 477 + FILE_ERROR_RETURN(ERRNO, -1); 478 + 479 + int rc; 480 + 481 + if (file->stream.flags & O_RDONLY) 482 + { 483 + logf("Descriptor is read-only mode\n"); 484 + FILE_ERROR(EBADF, -2); 485 + } 486 + 487 + if (length < 0) 488 + { 489 + logf("Length %ld is invalid\n", (long)length); 490 + FILE_ERROR(EINVAL, -3); 491 + } 492 + 493 + file_error_t err = PagerTruncate(file->stream.cache, length); 494 + if (err) { 495 + FILE_ERROR(ERRNO, -11); 496 + } 497 + 498 + *file->sizep = length; 499 + 500 + rc = 0; 501 + file_error: 502 + RELEASE_FILESTR(READER, file); 503 + return rc; 504 + } 505 + 506 + /* synchronize changes to a file */ 507 + int ctru_fsync(int fildes) 508 + { 509 + logf("fsync(fd=%d)\n", fildes); 510 + 511 + struct filestr_desc * const file = GET_FILESTR(WRITER, fildes); 512 + if (!file) 513 + FILE_ERROR_RETURN(ERRNO, -1); 514 + 515 + int rc; 516 + 517 + if (file->stream.flags & O_RDONLY) 518 + { 519 + logf("Descriptor is read-only mode\n"); 520 + FILE_ERROR(EINVAL, -2); 521 + } 522 + 523 + /* flush all pending changes to disk */ 524 + file_error_t err = PagerFlush(file->stream.cache); 525 + if (err != nil) { 526 + FILE_ERROR(ERRNO, -3); 527 + } 528 + 529 + rc = 0; 530 + file_error: 531 + RELEASE_FILESTR(WRITER, file); 532 + return rc; 533 + } 534 + 535 + /* move the read/write file offset */ 536 + off_t ctru_lseek(int fildes, off_t offset, int whence) 537 + { 538 + #ifndef LOGF_ENABLE /* wipes out log before you can save it */ 539 + /* DEBUGF("lseek(fd=%d,ofs=%ld,wh=%d)\n", fildes, (long)offset, whence); */ 540 + #endif 541 + struct filestr_desc * const file = GET_FILESTR(READER, fildes); 542 + if (!file) 543 + FILE_ERROR_RETURN(ERRNO, -1); 544 + 545 + off_t rc = lseek_internal(file, offset, whence); 546 + if (rc < 0) 547 + FILE_ERROR(ERRNO, rc * 10 - 2); 548 + 549 + file_error: 550 + RELEASE_FILESTR(READER, file); 551 + return rc; 552 + } 553 + 554 + /* read from a file */ 555 + ssize_t ctru_read(int fildes, void *buf, size_t nbyte) 556 + { 557 + struct filestr_desc * const file = GET_FILESTR(READER, fildes); 558 + if (!file) 559 + FILE_ERROR_RETURN(ERRNO, -1); 560 + 561 + ssize_t rc; 562 + 563 + if (file->stream.flags & O_WRONLY) 564 + { 565 + logf("read(fd=%d,buf=%p,nb=%lu) - " 566 + "descriptor is write-only mode\n", 567 + fildes, buf, (unsigned long)nbyte); 568 + FILE_ERROR(EBADF, -2); 569 + } 570 + 571 + rc = readwrite(file, buf, nbyte, false); 572 + if (rc < 0) 573 + FILE_ERROR(ERRNO, rc * 10 - 3); 574 + 575 + file_error: 576 + RELEASE_FILESTR(READER, file); 577 + return rc; 578 + } 579 + 580 + /* write on a file */ 581 + ssize_t ctru_write(int fildes, const void *buf, size_t nbyte) 582 + { 583 + struct filestr_desc * const file = GET_FILESTR(READER, fildes); 584 + if (!file) 585 + FILE_ERROR_RETURN(ERRNO, -1); 586 + 587 + ssize_t rc; 588 + 589 + if (file->stream.flags & O_RDONLY) 590 + { 591 + logf("write(fd=%d,buf=%p,nb=%lu) - " 592 + "descriptor is read-only mode\n", 593 + fildes, buf, (unsigned long)nbyte); 594 + FILE_ERROR(EBADF, -2); 595 + } 596 + 597 + rc = readwrite(file, (void *)buf, nbyte, true); 598 + if (rc < 0) 599 + FILE_ERROR(ERRNO, rc * 10 - 3); 600 + 601 + file_error: 602 + RELEASE_FILESTR(READER, file); 603 + return rc; 604 + } 605 + 606 + /* remove a file */ 607 + int ctru_remove(const char *path) 608 + { 609 + logf("remove(path=\"%s\")\n", path); 610 + 611 + int rc; 612 + 613 + file_internal_lock_WRITER(); 614 + Result res = FSUSER_DeleteFile(sdmcArchive, 615 + fsMakePath(PATH_ASCII, path)); 616 + if (R_FAILED(res)) 617 + FILE_ERROR(ERRNO, -1); 618 + 619 + rc = 0; 620 + file_error: 621 + file_internal_unlock_WRITER(); 622 + return rc; 623 + } 624 + 625 + /* rename a file */ 626 + int ctru_rename(const char *old, const char *new) 627 + { 628 + /* note: move by rename does not work in devkitARM toolchain */ 629 + logf("rename(old=\"%s\",new=\"%s\")\n", old, new); 630 + 631 + int rc; 632 + 633 + /* if 'old' is a directory then 'new' is also required to be one if 'new' 634 + is to be overwritten */ 635 + bool are_dirs = false; 636 + 637 + file_internal_lock_WRITER(); 638 + 639 + if (!strcmp(new, old)) /* case-only is ok */ 640 + { 641 + logf("No name change (success)\n"); 642 + rc = 0; 643 + FILE_ERROR(ERRNO, _RC); 644 + } 645 + 646 + /* open 'old'; it must exist */ 647 + Handle open1rc; 648 + Result res = FSUSER_OpenFile(&open1rc, 649 + sdmcArchive, 650 + fsMakePath(PATH_ASCII, old), 651 + FS_OPEN_READ, 652 + 0); 653 + if (R_FAILED(res)) { 654 + /* not a file, try to open a directory */ 655 + res = FSUSER_OpenDirectory(&open1rc, 656 + sdmcArchive, 657 + fsMakePath(PATH_ASCII, old)); 658 + if (R_FAILED(res)) { 659 + logf("Failed opening old\n"); 660 + FILE_ERROR(ERRNO, -1); 661 + } 662 + 663 + are_dirs = true; 664 + } 665 + 666 + if (are_dirs) { 667 + /* rename directory */ 668 + FSUSER_RenameDirectory(sdmcArchive, 669 + fsMakePath(PATH_ASCII, old), 670 + sdmcArchive, 671 + fsMakePath(PATH_ASCII, new)); 672 + } 673 + else { 674 + /* rename file */ 675 + FSUSER_RenameFile(sdmcArchive, 676 + fsMakePath(PATH_ASCII, old), 677 + sdmcArchive, 678 + fsMakePath(PATH_ASCII, new)); 679 + } 680 + 681 + if (R_FAILED(res)) { 682 + logf("Rename failed\n"); 683 + FILE_ERROR(ERRNO, -2); 684 + } 685 + 686 + rc = 0; 687 + file_error: 688 + /* for now, there is nothing to fail upon closing the old stream */ 689 + if (open1rc > 0) { 690 + if (are_dirs) 691 + FSDIR_Close(open1rc); 692 + else 693 + FSFILE_Close(open1rc); 694 + } 695 + 696 + file_internal_unlock_WRITER(); 697 + return rc; 698 + } 699 + 700 + /** Extensions **/ 701 + 702 + /* todo: utime does not work in devkitARM toolchain */ 703 + int ctru_modtime(const char *path, time_t modtime) 704 + { 705 + struct utimbuf times = 706 + { 707 + .actime = modtime, 708 + .modtime = modtime, 709 + }; 710 + 711 + return utime(path, &times); 712 + } 713 + 714 + /* get the binary size of a file (in bytes) */ 715 + off_t ctru_filesize(int fildes) 716 + { 717 + struct filestr_desc * const file = GET_FILESTR(READER, fildes); 718 + if (!file) 719 + FILE_ERROR_RETURN(ERRNO, -1); 720 + 721 + off_t rc; 722 + file_size_t size = *file->sizep; 723 + 724 + if (size > FILE_SIZE_MAX) 725 + FILE_ERROR(EOVERFLOW, -2); 726 + 727 + rc = (off_t)size; 728 + file_error: 729 + RELEASE_FILESTR(READER, file); 730 + return rc; 731 + } 732 + 733 + /* test if two file descriptors refer to the same file */ 734 + int ctru_fsamefile(int fildes1, int fildes2) 735 + { 736 + struct filestr_desc * const file1 = GET_FILESTR(WRITER, fildes1); 737 + if (!file1) 738 + FILE_ERROR_RETURN(ERRNO, -1); 739 + 740 + int rc = -2; 741 + 742 + struct filestr_desc * const file2 = get_filestr(fildes2); 743 + if (file2) 744 + rc = strcmp(file1->stream.path, file2->stream.path) == 0 ? 1 : 0; 745 + 746 + RELEASE_FILESTR(WRITER, file1); 747 + return rc; 748 + } 749 + 750 + /* tell the relationship of path1 to path2 */ 751 + int ctru_relate(const char *path1, const char *path2) 752 + { 753 + /* FAT32 file system does not support symbolic links, 754 + therefore, comparing the two full paths should be enough 755 + to tell relationship */ 756 + logf("relate(path1=\"%s\",path2=\"%s\")\n", path1, path2); 757 + int rc = RELATE_DIFFERENT; 758 + if (strcmp(path1, path2) == 0) 759 + rc = RELATE_SAME; 760 + return rc; 761 + } 762 + 763 + /* test file or directory existence */ 764 + bool ctru_file_exists(const char *path) 765 + { 766 + file_internal_lock_WRITER(); 767 + bool rc = test_stream_exists_internal(path) > 0; 768 + file_internal_unlock_WRITER(); 769 + return rc; 770 + } 771 + 772 + /* note: no symbolic links support in devkitARM */ 773 + ssize_t ctru_readlink(const char *path, char *buf, size_t bufsiz) 774 + { 775 + return readlink(path, buf, bufsiz); 776 + } 777 +
+129
firmware/target/hosted/ctru/lib/sys_file.h
··· 1 + /*************************************************************************** 2 + * __________ __ ___. 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * $Id$ 9 + * 10 + * Copyright (C) 2025 by Mauricio G. 11 + * 12 + * This program is free software; you can redistribute it and/or 13 + * modify it under the terms of the GNU General Public License 14 + * as published by the Free Software Foundation; either version 2 15 + * of the License, or (at your option) any later version. 16 + * 17 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 + * KIND, either express or implied. 19 + * 20 + ****************************************************************************/ 21 + #ifndef _SYS_FILE_H 22 + #define _SYS_FILE_H 23 + 24 + #include "bfile.h" 25 + 26 + /* Include for file.h and dir.h because mkdir and friends may be here */ 27 + #include <sys/stat.h> 28 + 29 + #define strlcpy_from_os strlcpy 30 + 31 + #define AtomicLoad(ptr) __atomic_load_n((u32*)(ptr), __ATOMIC_SEQ_CST) 32 + #define AtomicAdd(ptr, value) __atomic_add_fetch((u32*)(ptr), value, __ATOMIC_SEQ_CST) 33 + 34 + struct filestr_base { 35 + Pager* cache; /* buffer IO implementation (cache) */ 36 + Handle handle; /* file handle */ 37 + u64 size; /* file size */ 38 + int flags; /* stream flags */ 39 + char path[MAX_PATH+1]; 40 + 41 + LightLock mtx; /* serialization for this stream */ 42 + }; 43 + 44 + static inline void filestr_lock(struct filestr_base *stream) 45 + { 46 + LightLock_Lock(&stream->mtx); 47 + } 48 + 49 + static inline void filestr_unlock(struct filestr_base *stream) 50 + { 51 + LightLock_Unlock(&stream->mtx); 52 + } 53 + 54 + /* stream lock doesn't have to be used if getting RW lock writer access */ 55 + #define FILESTR_WRITER 0 56 + #define FILESTR_READER 1 57 + 58 + #define FILESTR_LOCK(type, stream) \ 59 + ({ if (FILESTR_##type) filestr_lock(stream); }) 60 + 61 + #define FILESTR_UNLOCK(type, stream) \ 62 + ({ if (FILESTR_##type) filestr_unlock(stream); }) 63 + 64 + /** Synchronization used throughout **/ 65 + 66 + /* acquire the filesystem lock as READER */ 67 + static inline void file_internal_lock_READER(void) 68 + { 69 + extern sync_RWMutex file_internal_mrsw; 70 + sync_RWMutexRLock(&file_internal_mrsw); 71 + } 72 + 73 + /* release the filesystem lock as READER */ 74 + static inline void file_internal_unlock_READER(void) 75 + { 76 + extern sync_RWMutex file_internal_mrsw; 77 + sync_RWMutexRUnlock(&file_internal_mrsw); 78 + } 79 + 80 + /* acquire the filesystem lock as WRITER */ 81 + static inline void file_internal_lock_WRITER(void) 82 + { 83 + extern sync_RWMutex file_internal_mrsw; 84 + sync_RWMutexLock(&file_internal_mrsw); 85 + } 86 + 87 + /* release the filesystem lock as WRITER */ 88 + static inline void file_internal_unlock_WRITER(void) 89 + { 90 + extern sync_RWMutex file_internal_mrsw; 91 + sync_RWMutexUnlock(&file_internal_mrsw); 92 + } 93 + 94 + #define ERRNO 0 /* maintain errno value */ 95 + #define _RC 0 /* maintain rc value */ 96 + 97 + /* NOTES: if _errno is a non-constant expression, it must set an error 98 + * number and not return the ERRNO constant which will merely set 99 + * errno to zero, not preserve the current value; if you must set 100 + * errno to zero, set it explicitly, not in the macro 101 + * 102 + * if _rc is constant-expression evaluation to 'RC', then rc will 103 + * NOT be altered; i.e. if you must set rc to zero, set it explicitly, 104 + * not in the macro 105 + */ 106 + 107 + #define FILE_SET_CODE(_name, _keepcode, _value) \ 108 + ({ __builtin_constant_p(_value) ? \ 109 + ({ if ((_value) != (_keepcode)) _name = (_value); }) : \ 110 + ({ _name = (_value); }); }) 111 + 112 + /* set errno and rc and proceed to the "file_error:" label */ 113 + #define FILE_ERROR(_errno, _rc) \ 114 + ({ FILE_SET_CODE(errno, ERRNO, (_errno)); \ 115 + FILE_SET_CODE(rc, _RC, (_rc)); \ 116 + goto file_error; }) 117 + 118 + /* set errno and return a value at the point of invocation */ 119 + #define FILE_ERROR_RETURN(_errno, _rc...) \ 120 + ({ FILE_SET_CODE(errno, ERRNO, _errno); \ 121 + return _rc; }) 122 + 123 + /* set errno and return code, no branching */ 124 + #define FILE_ERROR_SET(_errno, _rc) \ 125 + ({ FILE_SET_CODE(errno, ERRNO, (_errno)); \ 126 + FILE_SET_CODE(rc, _RC, (_rc)); }) 127 + 128 + #endif /* _SYS_FILE_H */ 129 +
+403
firmware/target/hosted/ctru/lib/sys_thread.c
··· 1 + /*************************************************************************** 2 + * __________ __ ___. 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * $Id$ 9 + * 10 + * Copyright (C) 2025 Mauricio Ga. 11 + * 12 + * This program is free software; you can redistribute it and/or 13 + * modify it under the terms of the GNU General Public License 14 + * as published by the Free Software Foundation; either version 2 15 + * of the License, or (at your option) any later version. 16 + * 17 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 + * KIND, either express or implied. 19 + * 20 + ****************************************************************************/ 21 + 22 + #include <stdlib.h> 23 + #include <stdbool.h> 24 + #include <stdio.h> 25 + #include <string.h> 26 + #include <assert.h> 27 + #include "sys_thread.h" 28 + #include "sys_timer.h" 29 + #include "debug.h" 30 + #include "logf.h" 31 + 32 + bool _AtomicCAS(u32 *ptr, int oldval, int newval) 33 + { 34 + int expected = oldval; 35 + int desired = newval; 36 + return __atomic_compare_exchange(ptr, &expected, &desired, false, 37 + __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); 38 + } 39 + 40 + bool _AtomicTryLock(int *lock) 41 + { 42 + int result; 43 + asm volatile( 44 + "ldrex %0, [%2] \n" 45 + "teq %0, #0 \n" 46 + "strexeq %0, %1, [%2] \n" 47 + : "=&r"(result) 48 + : "r"(1), "r"(lock) 49 + : "cc", "memory" 50 + ); 51 + return result == 0; 52 + } 53 + 54 + #define CPUPauseInstruction() asm volatile("yield" ::: "memory") 55 + void AtomicLock(int *lock) 56 + { 57 + int iterations = 0; 58 + while (!_AtomicTryLock(lock)) { 59 + if (iterations < 32) { 60 + iterations++; 61 + CPUPauseInstruction(); 62 + } else { 63 + sys_delay(0); 64 + } 65 + } 66 + } 67 + 68 + void AtomicUnlock(int *lock) 69 + { 70 + *lock = 0; 71 + } 72 + 73 + /* Convert rockbox priority value to libctru value */ 74 + int get_ctru_thread_priority(int priority) 75 + { 76 + if ((priority == PRIORITY_REALTIME_1) || (priority == PRIORITY_REALTIME_2) || 77 + (priority == PRIORITY_REALTIME_3) || (priority == PRIORITY_REALTIME_4) || 78 + (priority == PRIORITY_REALTIME)) 79 + return 0x18; 80 + else if (priority == PRIORITY_BUFFERING) 81 + return 0x18; /* Highest */ 82 + else if ((priority == PRIORITY_USER_INTERFACE) || (priority == PRIORITY_RECORDING) || 83 + (priority == PRIORITY_PLAYBACK)) 84 + return 0x30; 85 + else if (priority == PRIORITY_PLAYBACK_MAX) 86 + return 0x2F; 87 + else if (priority == PRIORITY_SYSTEM) 88 + return 0x30; 89 + else if (priority == PRIORITY_BACKGROUND) 90 + return 0x3F; /* Lowest */ 91 + else 92 + return 0x30; 93 + } 94 + 95 + static size_t get_thread_stack_size(size_t requested_size) 96 + { 97 + if (requested_size == 0) { 98 + return (80 * 1024); /* 80 kB */ 99 + } 100 + 101 + return requested_size; 102 + } 103 + 104 + static void thread_entry(void *arg) 105 + { 106 + sys_run_thread((sysThread *)arg); 107 + threadExit(0); 108 + } 109 + 110 + int wait_on_semaphore_for(LightSemaphore *sem, u32 timeout) 111 + { 112 + u64 stop_time = sys_get_ticks64() + timeout; 113 + u64 current_time = sys_get_ticks64(); 114 + while (current_time < stop_time) { 115 + if (LightSemaphore_TryAcquire(sem, 1) == 0) { 116 + return 0; 117 + } 118 + /* 100 microseconds seems to be the sweet spot */ 119 + svcSleepThread(100000LL); 120 + current_time = sys_get_ticks64(); 121 + } 122 + 123 + /* If we failed, yield to avoid starvation on busy waits */ 124 + svcSleepThread(1); 125 + return 1; 126 + } 127 + 128 + int sys_sem_try_wait(LightSemaphore *sem) 129 + { 130 + if (LightSemaphore_TryAcquire(sem, 1) != 0) { 131 + /* If we failed, yield to avoid starvation on busy waits */ 132 + svcSleepThread(1); 133 + return 1; 134 + } 135 + 136 + return 0; 137 + } 138 + 139 + int sys_sem_wait_timeout(LightSemaphore *sem, u32 timeout) 140 + { 141 + if (timeout == (~(u32)0)) { 142 + LightSemaphore_Acquire(sem, 1); 143 + return 0; 144 + } 145 + 146 + if (LightSemaphore_TryAcquire(sem, 1) != 0) { 147 + return wait_on_semaphore_for(sem, timeout); 148 + } 149 + 150 + return 0; 151 + } 152 + 153 + int sys_sem_wait(LightSemaphore *sem) 154 + { 155 + return sys_sem_wait_timeout(sem, (~(u32)0)); 156 + } 157 + 158 + u32 sys_sem_value(LightSemaphore *sem) 159 + { 160 + return sem->current_count; 161 + } 162 + 163 + int sys_thread_id(void) 164 + { 165 + u32 thread_ID = 0; 166 + svcGetThreadId(&thread_ID, CUR_THREAD_HANDLE); 167 + return (int)thread_ID; 168 + } 169 + 170 + void sys_run_thread(sysThread *thread) 171 + { 172 + void *userdata = thread->userdata; 173 + int(* userfunc)(void *) = thread->userfunc; 174 + 175 + int *statusloc = &thread->status; 176 + 177 + /* Get the thread id */ 178 + thread->threadid = sys_thread_id(); 179 + 180 + /* Run the function */ 181 + *statusloc = userfunc(userdata); 182 + 183 + /* Mark us as ready to be joined (or detached) */ 184 + if (!AtomicCAS(&thread->state, THREAD_STATE_ALIVE, THREAD_STATE_ZOMBIE)) { 185 + /* Clean up if something already detached us. */ 186 + if (AtomicCAS(&thread->state, THREAD_STATE_DETACHED, THREAD_STATE_CLEANED)) { 187 + free(thread); 188 + } 189 + } 190 + } 191 + 192 + sysThread *sys_create_thread(int(*fn)(void *), const char *name, const size_t stacksize, 193 + void *data IF_PRIO(, int priority) IF_COP(, unsigned int core)) 194 + { 195 + sys_ticks_init(); 196 + 197 + /* Allocate memory for the thread info structure */ 198 + sysThread *thread = (sysThread *) calloc(1, sizeof(sysThread)); 199 + if (thread == NULL) { 200 + DEBUGF("sys_create_thread: could not allocate memory\n"); 201 + return NULL; 202 + } 203 + thread->status = -1; 204 + AtomicSet(&thread->state, THREAD_STATE_ALIVE); 205 + 206 + /* Set up the arguments for the thread */ 207 + thread->userfunc = fn; 208 + thread->userdata = data; 209 + thread->stacksize = stacksize; 210 + 211 + int cpu = -1; 212 + if (name && (strncmp(name, "tagcache", 8) == 0) && R_SUCCEEDED(APT_SetAppCpuTimeLimit(30))) { 213 + cpu = 1; 214 + printf("thread: %s, running in cpu 1\n", name); 215 + } 216 + 217 + thread->handle = threadCreate(thread_entry, 218 + thread, 219 + get_thread_stack_size(stacksize), 220 + get_ctru_thread_priority(priority), 221 + cpu, 222 + false); 223 + 224 + if (!thread->handle) { 225 + DEBUGF("sys_create_thread: threadCreate failed\n"); 226 + free(thread); 227 + thread = NULL; 228 + } 229 + 230 + /* Everything is running now */ 231 + return thread; 232 + } 233 + 234 + void sys_wait_thread(sysThread *thread, int *status) 235 + { 236 + if (thread) { 237 + Result res = threadJoin(thread->handle, U64_MAX); 238 + 239 + /* 240 + Detached threads can be waited on, but should NOT be cleaned manually 241 + as it would result in a fatal error. 242 + */ 243 + if (R_SUCCEEDED(res) && AtomicGet(&thread->state) != THREAD_STATE_DETACHED) { 244 + threadFree(thread->handle); 245 + } 246 + if (status) { 247 + *status = thread->status; 248 + } 249 + free(thread); 250 + } 251 + } 252 + 253 + void sys_detach_thread(sysThread *thread) 254 + { 255 + if (!thread) { 256 + return; 257 + } 258 + 259 + /* Grab dibs if the state is alive+joinable. */ 260 + if (AtomicCAS(&thread->state, THREAD_STATE_ALIVE, THREAD_STATE_DETACHED)) { 261 + threadDetach(thread->handle); 262 + } else { 263 + /* all other states are pretty final, see where we landed. */ 264 + const int thread_state = AtomicGet(&thread->state); 265 + if ((thread_state == THREAD_STATE_DETACHED) || (thread_state == THREAD_STATE_CLEANED)) { 266 + return; /* already detached (you shouldn't call this twice!) */ 267 + } else if (thread_state == THREAD_STATE_ZOMBIE) { 268 + sys_wait_thread(thread, NULL); /* already done, clean it up. */ 269 + } else { 270 + assert(0 && "Unexpected thread state"); 271 + } 272 + } 273 + } 274 + 275 + int sys_set_thread_priority(sysThread *thread, int priority) 276 + { 277 + Handle h = threadGetHandle(thread->handle); 278 + int old_priority = priority; 279 + Result res = svcSetThreadPriority(h, get_ctru_thread_priority(priority)); 280 + if (R_SUCCEEDED(res)) { 281 + return priority; 282 + } 283 + 284 + return old_priority; 285 + } 286 + 287 + /* sysCond */ 288 + sysCond *sys_cond_create(void) 289 + { 290 + sysCond *cond; 291 + 292 + cond = (sysCond *)malloc(sizeof(sysCond)); 293 + if (cond) { 294 + RecursiveLock_Init(&cond->lock); 295 + LightSemaphore_Init(&cond->wait_sem, 0, ((s16)0x7FFF)); 296 + LightSemaphore_Init(&cond->wait_done, 0, ((s16)0x7FFF)); 297 + cond->waiting = cond->signals = 0; 298 + } else { 299 + DEBUGF("sys_cond_create: out of memory.\n");; 300 + } 301 + return cond; 302 + } 303 + 304 + /* Destroy a condition variable */ 305 + void sys_cond_destroy(sysCond *cond) 306 + { 307 + if (cond) { 308 + free(cond); 309 + } 310 + } 311 + 312 + /* Restart one of the threads that are waiting on the condition variable */ 313 + int sys_cond_signal(sysCond *cond) 314 + { 315 + if (!cond) { 316 + DEBUGF("sys_cond_signal: Invalid param 'cond'\n"); 317 + return -1; 318 + } 319 + 320 + /* If there are waiting threads not already signalled, then 321 + signal the condition and wait for the thread to respond. 322 + */ 323 + RecursiveLock_Lock(&cond->lock); 324 + if (cond->waiting > cond->signals) { 325 + ++cond->signals; 326 + LightSemaphore_Release(&cond->wait_sem, 1); 327 + RecursiveLock_Unlock(&cond->lock); 328 + LightSemaphore_Acquire(&cond->wait_done, 1); 329 + } else { 330 + RecursiveLock_Unlock(&cond->lock); 331 + } 332 + 333 + return 0; 334 + } 335 + 336 + /* Restart all threads that are waiting on the condition variable */ 337 + int sys_cond_broadcast(sysCond *cond) 338 + { 339 + if (!cond) { 340 + DEBUGF("sys_cond_signal: Invalid param 'cond'\n"); 341 + return -1; 342 + } 343 + 344 + /* If there are waiting threads not already signalled, then 345 + signal the condition and wait for the thread to respond. 346 + */ 347 + RecursiveLock_Lock(&cond->lock); 348 + if (cond->waiting > cond->signals) { 349 + int i, num_waiting; 350 + 351 + num_waiting = (cond->waiting - cond->signals); 352 + cond->signals = cond->waiting; 353 + for (i = 0; i < num_waiting; ++i) { 354 + LightSemaphore_Release(&cond->wait_sem, 1); 355 + } 356 + /* Now all released threads are blocked here, waiting for us. 357 + Collect them all (and win fabulous prizes!) :-) 358 + */ 359 + RecursiveLock_Unlock(&cond->lock); 360 + for (i = 0; i < num_waiting; ++i) { 361 + LightSemaphore_Acquire(&cond->wait_done, 1); 362 + } 363 + } else { 364 + RecursiveLock_Unlock(&cond->lock); 365 + } 366 + 367 + return 0; 368 + } 369 + 370 + int sys_cond_wait(sysCond *cond, RecursiveLock *mutex) 371 + { 372 + if (!cond) { 373 + DEBUGF("sys_cond_signal: Invalid param 'cond'\n"); 374 + return -1; 375 + } 376 + 377 + RecursiveLock_Lock(&cond->lock); 378 + ++cond->waiting; 379 + RecursiveLock_Unlock(&cond->lock); 380 + 381 + /* Unlock the mutex, as is required by condition variable semantics */ 382 + RecursiveLock_Unlock(mutex); 383 + 384 + /* Wait for a signal */ 385 + LightSemaphore_Acquire(&cond->wait_sem, 1); 386 + 387 + RecursiveLock_Lock(&cond->lock); 388 + if (cond->signals > 0) { 389 + /* We always notify the signal thread that we are done */ 390 + LightSemaphore_Release(&cond->wait_done, 1); 391 + 392 + /* Signal handshake complete */ 393 + --cond->signals; 394 + } 395 + --cond->waiting; 396 + RecursiveLock_Unlock(&cond->lock); 397 + 398 + /* Lock the mutex, as is required by condition variable semantics */ 399 + RecursiveLock_Lock(mutex); 400 + 401 + return 0; 402 + } 403 +
+88
firmware/target/hosted/ctru/lib/sys_thread.h
··· 1 + /*************************************************************************** 2 + * __________ __ ___. 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * $Id$ 9 + * 10 + * Copyright (C) 2025 Mauricio G. 11 + * 12 + * This program is free software; you can redistribute it and/or 13 + * modify it under the terms of the GNU General Public License 14 + * as published by the Free Software Foundation; either version 2 15 + * of the License, or (at your option) any later version. 16 + * 17 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 + * KIND, either express or implied. 19 + * 20 + ****************************************************************************/ 21 + 22 + #ifndef __SYSTHREAD_H__ 23 + #define __SYSTHREAD_H__ 24 + 25 + #include "thread.h" 26 + 27 + #include <3ds/synchronization.h> 28 + #include <3ds/thread.h> 29 + #include <3ds/services/apt.h> 30 + 31 + /* Complementary atomic operations */ 32 + bool _AtomicCAS(u32 *ptr, int oldval, int newval); 33 + #define AtomicGet(ptr) __atomic_load_n((u32*)(ptr), __ATOMIC_SEQ_CST) 34 + #define AtomicSet(ptr, value) AtomicSwap(ptr, value) 35 + #define AtomicCAS(ptr, oldvalue, newvalue) _AtomicCAS((u32 *)(ptr), oldvalue, newvalue) 36 + void AtomicLock(int *lock); 37 + void AtomicUnlock(int *lock); 38 + 39 + /* This code was taken from SDL2 thread implementation */ 40 + 41 + enum thread_state_t 42 + { 43 + THREAD_STATE_ALIVE, 44 + THREAD_STATE_DETACHED, 45 + THREAD_STATE_ZOMBIE, 46 + THREAD_STATE_CLEANED, 47 + }; 48 + 49 + typedef struct _thread 50 + { 51 + int threadid; 52 + Thread handle; 53 + int status; 54 + int state; 55 + size_t stacksize; 56 + int(* userfunc)(void *); 57 + void *userdata; 58 + void *data; 59 + } sysThread; 60 + 61 + typedef struct _cond 62 + { 63 + RecursiveLock lock; 64 + int waiting; 65 + int signals; 66 + LightSemaphore wait_sem; 67 + LightSemaphore wait_done; 68 + } sysCond; 69 + 70 + int sys_sem_wait(LightSemaphore *sem); 71 + int sys_sem_wait_timeout(LightSemaphore *sem, u32 timeout); 72 + int sys_sem_try_wait(LightSemaphore *sem); 73 + u32 sys_sem_value(LightSemaphore *sem); 74 + 75 + sysThread *sys_create_thread(int(*fn)(void *), const char *name, const size_t stacksize, 76 + void *data IF_PRIO(, int priority) IF_COP(, unsigned int core)); 77 + void sys_run_thread(sysThread *thread); 78 + void sys_wait_thread(sysThread *thread, int *status); 79 + int sys_thread_id(void); 80 + int sys_set_thread_priority(sysThread *thread, int priority); 81 + 82 + sysCond *sys_cond_create(void); 83 + void sys_cond_destroy(sysCond *cond); 84 + int sys_cond_signal(sysCond *cond); 85 + int sys_cond_broadcast(sysCond *cond); 86 + int sys_cond_wait(sysCond *cond, RecursiveLock *mutex); 87 + #endif /* #ifndef __SYSTHREAD_H__ */ 88 +
+402
firmware/target/hosted/ctru/lib/sys_timer.c
··· 1 + /*************************************************************************** 2 + * __________ __ ___. 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * $Id$ 9 + * 10 + * Copyright (C) 2006 Dan Everton 11 + * 12 + * This program is free software; you can redistribute it and/or 13 + * modify it under the terms of the GNU General Public License 14 + * as published by the Free Software Foundation; either version 2 15 + * of the License, or (at your option) any later version. 16 + * 17 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 + * KIND, either express or implied. 19 + * 20 + ****************************************************************************/ 21 + 22 + #include <stdlib.h> 23 + #include <stdbool.h> 24 + #include <stdio.h> 25 + #include <string.h> 26 + #include <assert.h> 27 + #include "debug.h" 28 + #include "logf.h" 29 + 30 + #include <3ds/os.h> 31 + #include "sys_thread.h" 32 + #include "sys_timer.h" 33 + 34 + #define CACHELINE_SIZE 128 35 + 36 + static bool ticks_started = false; 37 + static u64 start_tick; 38 + 39 + #define NSEC_PER_MSEC 1000000ULL 40 + 41 + void sys_ticks_init(void) 42 + { 43 + if (ticks_started) { 44 + return; 45 + } 46 + ticks_started = true; 47 + 48 + start_tick = svcGetSystemTick(); 49 + } 50 + 51 + void sys_ticks_quit(void) 52 + { 53 + ticks_started = false; 54 + } 55 + 56 + u64 sys_get_ticks64(void) 57 + { 58 + u64 elapsed; 59 + if (!ticks_started) { 60 + sys_ticks_init(); 61 + } 62 + 63 + elapsed = svcGetSystemTick() - start_tick; 64 + return elapsed / CPU_TICKS_PER_MSEC; 65 + } 66 + 67 + u32 sys_get_ticks(void) 68 + { 69 + return (u32)(sys_get_ticks64() & 0xFFFFFFFF); 70 + } 71 + 72 + void sys_delay(u32 ms) 73 + { 74 + svcSleepThread(ms * NSEC_PER_MSEC); 75 + } 76 + 77 + typedef struct _timer 78 + { 79 + int timerID; 80 + timer_callback_ptr callback; 81 + void *param; 82 + u32 interval; 83 + u32 scheduled; 84 + int canceled; 85 + struct _timer *next; 86 + } sysTimer; 87 + 88 + typedef struct _timer_map 89 + { 90 + int timerID; 91 + sysTimer *timer; 92 + struct _timer_map *next; 93 + } timerMap; 94 + 95 + /* The timers are kept in a sorted list */ 96 + typedef struct 97 + { 98 + /* Data used by the main thread */ 99 + Thread thread; 100 + int nextID; 101 + timerMap *timermap; 102 + RecursiveLock timermap_lock; 103 + 104 + /* Padding to separate cache lines between threads */ 105 + char cache_pad[CACHELINE_SIZE]; 106 + 107 + /* Data used to communicate with the timer thread */ 108 + int lock; 109 + LightSemaphore sem; 110 + sysTimer *pending; 111 + sysTimer *freelist; 112 + int active; 113 + 114 + /* List of timers - this is only touched by the timer thread */ 115 + sysTimer *timers; 116 + } timerData; 117 + 118 + static timerData timer_data = { .active = 0 }; 119 + 120 + /* The idea here is that any thread might add a timer, but a single 121 + * thread manages the active timer queue, sorted by scheduling time. 122 + * 123 + * Timers are removed by simply setting a canceled flag 124 + */ 125 + 126 + static void add_timer_interval(timerData *data, sysTimer *timer) 127 + { 128 + sysTimer *prev, *curr; 129 + 130 + prev = NULL; 131 + for (curr = data->timers; curr; prev = curr, curr = curr->next) { 132 + if ((s32)(timer->scheduled - curr->scheduled) < 0) { 133 + break; 134 + } 135 + } 136 + 137 + /* Insert the timer here! */ 138 + if (prev) { 139 + prev->next = timer; 140 + } else { 141 + data->timers = timer; 142 + } 143 + timer->next = curr; 144 + } 145 + 146 + static void timer_thread(void *_data) 147 + { 148 + timerData *data = (timerData *)_data; 149 + sysTimer *pending; 150 + sysTimer *current; 151 + sysTimer *freelist_head = NULL; 152 + sysTimer *freelist_tail = NULL; 153 + u32 tick, now, interval, delay; 154 + 155 + /* Threaded timer loop: 156 + * 1. Queue timers added by other threads 157 + * 2. Handle any timers that should dispatch this cycle 158 + * 3. Wait until next dispatch time or new timer arrives 159 + */ 160 + for (;;) { 161 + /* Pending and freelist maintenance */ 162 + AtomicLock(&data->lock); 163 + { 164 + /* Get any timers ready to be queued */ 165 + pending = data->pending; 166 + data->pending = NULL; 167 + 168 + /* Make any unused timer structures available */ 169 + if (freelist_head) { 170 + freelist_tail->next = data->freelist; 171 + data->freelist = freelist_head; 172 + } 173 + } 174 + AtomicUnlock(&data->lock); 175 + 176 + /* Sort the pending timers into our list */ 177 + while (pending) { 178 + current = pending; 179 + pending = pending->next; 180 + add_timer_interval(data, current); 181 + } 182 + freelist_head = NULL; 183 + freelist_tail = NULL; 184 + 185 + /* Check to see if we're still running, after maintenance */ 186 + if (!AtomicGet(&data->active)) { 187 + break; 188 + } 189 + 190 + /* Initial delay if there are no timers */ 191 + delay = (~(u32)0); 192 + 193 + tick = sys_get_ticks(); 194 + 195 + /* Process all the pending timers for this tick */ 196 + while (data->timers) { 197 + current = data->timers; 198 + 199 + if ((s32)(tick - current->scheduled) < 0) { 200 + /* Scheduled for the future, wait a bit */ 201 + delay = (current->scheduled - tick); 202 + break; 203 + } 204 + 205 + /* We're going to do something with this timer */ 206 + data->timers = current->next; 207 + 208 + if (AtomicGet(&current->canceled)) { 209 + interval = 0; 210 + } else { 211 + interval = current->callback(current->interval, current->param); 212 + } 213 + 214 + if (interval > 0) { 215 + /* Reschedule this timer */ 216 + current->interval = interval; 217 + current->scheduled = tick + interval; 218 + add_timer_interval(data, current); 219 + } else { 220 + if (!freelist_head) { 221 + freelist_head = current; 222 + } 223 + if (freelist_tail) { 224 + freelist_tail->next = current; 225 + } 226 + freelist_tail = current; 227 + 228 + AtomicSet(&current->canceled, 1); 229 + } 230 + } 231 + 232 + /* Adjust the delay based on processing time */ 233 + now = sys_get_ticks(); 234 + interval = (now - tick); 235 + if (interval > delay) { 236 + delay = 0; 237 + } else { 238 + delay -= interval; 239 + } 240 + 241 + /* Note that each time a timer is added, this will return 242 + immediately, but we process the timers added all at once. 243 + That's okay, it just means we run through the loop a few 244 + extra times. 245 + */ 246 + sys_sem_wait_timeout(&data->sem, delay); 247 + } 248 + } 249 + 250 + int sys_timer_init(void) 251 + { 252 + timerData *data = &timer_data; 253 + 254 + if (!AtomicGet(&data->active)) { 255 + RecursiveLock_Init(&data->timermap_lock); 256 + LightSemaphore_Init(&data->sem, 0, ((s16)0x7FFF)); 257 + AtomicSet(&data->active, 1); 258 + 259 + /* Timer threads use a callback into the app, so we can't set a limited stack size here. */ 260 + data->thread = threadCreate(timer_thread, 261 + data, 262 + 32 * 1024, 263 + 0x28, 264 + -1, 265 + false); 266 + if (!data->thread) { 267 + sys_timer_quit(); 268 + return -1; 269 + } 270 + 271 + AtomicSet(&data->nextID, 1); 272 + } 273 + return 0; 274 + } 275 + 276 + void sys_timer_quit(void) 277 + { 278 + timerData *data = &timer_data; 279 + sysTimer *timer; 280 + timerMap *entry; 281 + 282 + if (AtomicCAS(&data->active, 1, 0)) { /* active? Move to inactive. */ 283 + /* Shutdown the timer thread */ 284 + if (data->thread) { 285 + LightSemaphore_Release(&data->sem, 1); 286 + Result res = threadJoin(data->thread, U64_MAX); 287 + threadFree(data->thread); 288 + data->thread = NULL; 289 + } 290 + 291 + /* Clean up the timer entries */ 292 + while (data->timers) { 293 + timer = data->timers; 294 + data->timers = timer->next; 295 + free(timer); 296 + } 297 + while (data->freelist) { 298 + timer = data->freelist; 299 + data->freelist = timer->next; 300 + free(timer); 301 + } 302 + while (data->timermap) { 303 + entry = data->timermap; 304 + data->timermap = entry->next; 305 + free(entry); 306 + } 307 + } 308 + } 309 + 310 + int sys_add_timer(u32 interval, timer_callback_ptr callback, void *param) 311 + { 312 + timerData *data = &timer_data; 313 + sysTimer *timer; 314 + timerMap *entry; 315 + 316 + AtomicLock(&data->lock); 317 + if (!AtomicGet(&data->active)) { 318 + if (sys_timer_init() < 0) { 319 + AtomicUnlock(&data->lock); 320 + return 0; 321 + } 322 + } 323 + 324 + timer = data->freelist; 325 + if (timer) { 326 + data->freelist = timer->next; 327 + } 328 + AtomicUnlock(&data->lock); 329 + 330 + if (timer) { 331 + sys_remove_timer(timer->timerID); 332 + } else { 333 + timer = (sysTimer *) malloc(sizeof(*timer)); 334 + if (!timer) { 335 + DEBUGF("sys_add_timer: out of memory\n"); 336 + return 0; 337 + } 338 + } 339 + timer->timerID = AtomicIncrement(&data->nextID); 340 + timer->callback = callback; 341 + timer->param = param; 342 + timer->interval = interval; 343 + timer->scheduled = sys_get_ticks() + interval; 344 + AtomicSet(&timer->canceled, 0); 345 + 346 + entry = (timerMap *) malloc(sizeof(*entry)); 347 + if (!entry) { 348 + free(timer); 349 + DEBUGF("sys_add_timer: out of memory\n"); 350 + return 0; 351 + } 352 + entry->timer = timer; 353 + entry->timerID = timer->timerID; 354 + 355 + RecursiveLock_Lock(&data->timermap_lock); 356 + entry->next = data->timermap; 357 + data->timermap = entry; 358 + RecursiveLock_Unlock(&data->timermap_lock); 359 + 360 + /* Add the timer to the pending list for the timer thread */ 361 + AtomicLock(&data->lock); 362 + timer->next = data->pending; 363 + data->pending = timer; 364 + AtomicUnlock(&data->lock); 365 + 366 + /* Wake up the timer thread if necessary */ 367 + LightSemaphore_Release(&data->sem, 1); 368 + 369 + return entry->timerID; 370 + } 371 + 372 + bool sys_remove_timer(int id) 373 + { 374 + timerData *data = &timer_data; 375 + timerMap *prev, *entry; 376 + bool canceled = false; 377 + 378 + /* Find the timer */ 379 + RecursiveLock_Lock(&data->timermap_lock); 380 + prev = NULL; 381 + for (entry = data->timermap; entry; prev = entry, entry = entry->next) { 382 + if (entry->timerID == id) { 383 + if (prev) { 384 + prev->next = entry->next; 385 + } else { 386 + data->timermap = entry->next; 387 + } 388 + break; 389 + } 390 + } 391 + RecursiveLock_Unlock(&data->timermap_lock); 392 + 393 + if (entry) { 394 + if (!AtomicGet(&entry->timer->canceled)) { 395 + AtomicSet(&entry->timer->canceled, 1); 396 + canceled = true; 397 + } 398 + free(entry); 399 + } 400 + return canceled; 401 + } 402 +
+42
firmware/target/hosted/ctru/lib/sys_timer.h
··· 1 + /*************************************************************************** 2 + * __________ __ ___. 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * $Id$ 9 + * 10 + * Copyright (C) 2025 Mauricio G. 11 + * 12 + * This program is free software; you can redistribute it and/or 13 + * modify it under the terms of the GNU General Public License 14 + * as published by the Free Software Foundation; either version 2 15 + * of the License, or (at your option) any later version. 16 + * 17 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 + * KIND, either express or implied. 19 + * 20 + ****************************************************************************/ 21 + 22 + #ifndef __SYSTIMER_H__ 23 + #define __SYSTIMER_H__ 24 + 25 + #include <3ds/svc.h> 26 + #include "sys_thread.h" 27 + 28 + typedef u32 (* timer_callback_ptr) (u32 interval, void *param); 29 + 30 + void sys_ticks_init(void); 31 + void sys_ticks_quit(void); 32 + u32 sys_get_ticks(void); 33 + u64 sys_get_ticks64(void); 34 + void sys_delay(u32 ms); 35 + 36 + int sys_timer_init(void); 37 + void sys_timer_quit(void); 38 + int sys_add_timer(u32 interval, timer_callback_ptr callback, void *param); 39 + bool sys_remove_timer(int id); 40 + 41 + #endif /* #ifndef __SYSTIMER_H__ */ 42 +
+133
firmware/target/hosted/ctru/luminance-ctru.c
··· 1 + /* 2 + * This file is part of Luma3DS 3 + * Copyright (C) 2016-2020 Aurora Wright, TuxSH 4 + * 5 + * This program is free software: you can redistribute it and/or modify 6 + * it under the terms of the GNU General Public License as published by 7 + * the Free Software Foundation, either version 3 of the License, or 8 + * (at your option) any later version. 9 + * 10 + * This program is distributed in the hope that it will be useful, 11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 + * GNU General Public License for more details. 14 + * 15 + * You should have received a copy of the GNU General Public License 16 + * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 + * 18 + * Additional Terms 7.b and 7.c of GPLv3 apply to this file: 19 + * * Requiring preservation of specified reasonable legal notices or 20 + * author attributions in that material or in the Appropriate Legal 21 + * Notices displayed by works containing it. 22 + * * Prohibiting misrepresentation of the origin of that material, 23 + * or requiring that modified versions of such material be marked in 24 + * reasonable ways as different from the original version. 25 + */ 26 + 27 + /* This file is taken from Luma3DS project */ 28 + 29 + #include <3ds.h> 30 + #include <math.h> 31 + #include "luminance-ctru.h" 32 + 33 + // For accessing physmem uncached (and directly) 34 + #define PA_PTR(addr) (void *)((u32)(addr) | 1 << 31) 35 + 36 + #ifndef PA_FROM_VA_PTR 37 + #define PA_FROM_VA_PTR(addr) PA_PTR(svcConvertVAToPA((const void *)(addr), false)) 38 + #endif 39 + 40 + #define REG32(addr) (*(vu32 *)(PA_PTR(addr))) 41 + 42 + 43 + extern bool is_n3ds; 44 + 45 + typedef struct BlPwmData 46 + { 47 + float coeffs[3][3]; 48 + u8 numLevels; 49 + u8 unk; 50 + u16 luminanceLevels[7]; 51 + u16 brightnessMax; 52 + u16 brightnessMin; 53 + } BlPwmData; 54 + 55 + // Calibration, with (dubious) default values as fallback 56 + static BlPwmData s_blPwmData = { 57 + .coeffs = { 58 + { 0.00111639f, 1.41412f, 0.07178809f }, 59 + { 0.000418169f, 0.66567f, 0.06098654f }, 60 + { 0.00208543f, 1.55639f, 0.0385939f } 61 + }, 62 + .numLevels = 5, 63 + .unk = 0, 64 + .luminanceLevels = { 20, 43, 73, 95, 117, 172, 172 }, 65 + .brightnessMax = 512, 66 + .brightnessMin = 13, 67 + }; 68 + 69 + static inline float getPwmRatio(u32 brightnessMax, u32 pwmCnt) 70 + { 71 + u32 val = (pwmCnt & 0x10000) ? pwmCnt & 0x3FF : 511; // check pwm enabled flag 72 + return (float)brightnessMax / (val + 1); 73 + } 74 + 75 + // nn's asm has rounding errors (originally at 10^-3) 76 + static inline u32 luminanceToBrightness(u32 luminance, const float coeffs[3], u32 minLuminance, float pwmRatio) 77 + { 78 + float x = (float)luminance; 79 + float y = coeffs[0]*x*x + coeffs[1]*x + coeffs[2]; 80 + y = (y <= minLuminance ? (float)minLuminance : y) / pwmRatio; 81 + 82 + return (u32)(y + 0.5f); 83 + } 84 + 85 + static inline u32 brightnessToLuminance(u32 brightness, const float coeffs[3], float pwmRatio) 86 + { 87 + // Find polynomial root of ax^2 + bx + c = y 88 + 89 + float y = (float)brightness * pwmRatio; 90 + float a = coeffs[0]; 91 + float b = coeffs[1]; 92 + float c = coeffs[2] - y; 93 + 94 + float x0 = (-b + sqrtf(b*b - 4.0f*a*c)) / (a + a); 95 + 96 + return (u32)(x0 + 0.5f); 97 + } 98 + 99 + static void readCalibration(void) 100 + { 101 + static bool calibRead = false; 102 + 103 + if (!calibRead) { 104 + cfguInit(); 105 + calibRead = R_SUCCEEDED(CFG_GetConfigInfoBlk8(sizeof(BlPwmData), 0x50002, &s_blPwmData)); 106 + cfguExit(); 107 + } 108 + } 109 + 110 + u32 getMinLuminancePreset(void) 111 + { 112 + readCalibration(); 113 + return s_blPwmData.luminanceLevels[0]; 114 + } 115 + 116 + u32 getMaxLuminancePreset(void) 117 + { 118 + readCalibration(); 119 + return s_blPwmData.luminanceLevels[s_blPwmData.numLevels - 1]; 120 + } 121 + 122 + u32 getCurrentLuminance(bool top) 123 + { 124 + u32 regbase = top ? 0x10202200 : 0x10202A00; 125 + 126 + readCalibration(); 127 + 128 + const float *coeffs = s_blPwmData.coeffs[top ? (is_n3ds ? 2 : 1) : 0]; 129 + u32 brightness = REG32(regbase + 0x40); 130 + float ratio = getPwmRatio(s_blPwmData.brightnessMax, REG32(regbase + 0x44)); 131 + 132 + return brightnessToLuminance(brightness, coeffs, ratio); 133 + }
+40
firmware/target/hosted/ctru/luminance-ctru.h
··· 1 + /* 2 + * This file is part of Luma3DS 3 + * Copyright (C) 2016-2020 Aurora Wright, TuxSH 4 + * 5 + * This program is free software: you can redistribute it and/or modify 6 + * it under the terms of the GNU General Public License as published by 7 + * the Free Software Foundation, either version 3 of the License, or 8 + * (at your option) any later version. 9 + * 10 + * This program is distributed in the hope that it will be useful, 11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 + * GNU General Public License for more details. 14 + * 15 + * You should have received a copy of the GNU General Public License 16 + * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 + * 18 + * Additional Terms 7.b and 7.c of GPLv3 apply to this file: 19 + * * Requiring preservation of specified reasonable legal notices or 20 + * author attributions in that material or in the Appropriate Legal 21 + * Notices displayed by works containing it. 22 + * * Prohibiting misrepresentation of the origin of that material, 23 + * or requiring that modified versions of such material be marked in 24 + * reasonable ways as different from the original version. 25 + */ 26 + 27 + #ifndef _LUMINANCE_CTRU_H_ 28 + #define _LUMINANCE_CTRU_H_ 29 + 30 + #include <3ds/types.h> 31 + 32 + extern u32 ctru_min_lum; 33 + extern u32 ctru_max_lum; 34 + extern u32 ctru_luminance; 35 + 36 + u32 getMinLuminancePreset(void); 37 + u32 getMaxLuminancePreset(void); 38 + u32 getCurrentLuminance(bool top); 39 + 40 + #endif /* _LUMINANCE_CTRU_H_ */
+359
firmware/target/hosted/ctru/pcm-ctru.c
··· 1 + /*************************************************************************** 2 + * __________ __ ___. 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * $Id$ 9 + * 10 + * Copyright (C) 2005 by Nick Lanham 11 + * Copyright (C) 2010 by Thomas Martitz 12 + * 13 + * This program is free software; you can redistribute it and/or 14 + * modify it under the terms of the GNU General Public License 15 + * as published by the Free Software Foundation; either version 2 16 + * of the License, or (at your option) any later version. 17 + * 18 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 19 + * KIND, either express or implied. 20 + * 21 + ****************************************************************************/ 22 + 23 + #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) 24 + 25 + #include "autoconf.h" 26 + 27 + #include <stdlib.h> 28 + #include <stdbool.h> 29 + #include <stdio.h> 30 + #include "config.h" 31 + #include "debug.h" 32 + #include "sound.h" 33 + #include "audiohw.h" 34 + #include "system.h" 35 + #include "panic.h" 36 + 37 + #ifdef HAVE_RECORDING 38 + #include "audiohw.h" 39 + #ifdef HAVE_SPDIF_IN 40 + #include "spdif.h" 41 + #endif 42 + #endif 43 + 44 + #include "pcm.h" 45 + #include "pcm-internal.h" 46 + #include "pcm_sampr.h" 47 + #include "pcm_mixer.h" 48 + 49 + #include <3ds/ndsp/ndsp.h> 50 + #include <3ds/ndsp/channel.h> 51 + #include <3ds/services/dsp.h> 52 + #include <3ds/synchronization.h> 53 + #include <3ds/allocator/linear.h> 54 + 55 + /*#define LOGF_ENABLE*/ 56 + #include "logf.h" 57 + 58 + #ifdef DEBUG 59 + extern bool debug_audio; 60 + #endif 61 + 62 + extern const char *audiodev; 63 + 64 + /* Bytes left in the Rockbox PCM frame buffer. */ 65 + static size_t _pcm_buffer_size = 0; 66 + 67 + 68 + /* Rockbox PCM frame buffer. */ 69 + static const void *_pcm_buffer = NULL; 70 + 71 + /* 72 + 1: PCM thread suspended. 73 + 0: PCM thread running. 74 + These are used by pcm_play_[lock|unlock] or pcm_play_dma_[start|stop|pause]. These need to be 75 + separated because of nested calls for suspending and stopping. 76 + */ 77 + static volatile int _dsp_enabled = 1; 78 + static volatile int _pcm_shutdown = 0; 79 + 80 + 81 + /* Mutex for PCM thread suspend/unsuspend. */ 82 + static RecursiveLock _pcm_lock_mtx; /* audio device mutex */ 83 + static LightEvent _dsp_callback_event; /* dsp callback synchronization flag */ 84 + 85 + static Thread _pcm_thread; 86 + static int _pcm_thread_id = -1; 87 + 88 + /* DSP wave buffers */ 89 + static ndspWaveBuf _dsp_wave_bufs[3]; 90 + static s16 *_dsp_audio_buffer = NULL; 91 + 92 + static inline bool is_in_audio_thread(int audio_thread_id) 93 + { 94 + /* The device thread locks the same mutex, but not through the public API. 95 + This check is in case the application, in the audio callback, 96 + tries to lock the thread that we've already locked from the 97 + device thread...just in case we only have non-recursive mutexes. */ 98 + if ( (sys_thread_id() == audio_thread_id)) { 99 + return true; 100 + } 101 + 102 + return false; 103 + } 104 + 105 + void pcm_play_lock(void) 106 + { 107 + if (!is_in_audio_thread(_pcm_thread_id)) { 108 + RecursiveLock_Lock(&_pcm_lock_mtx); 109 + } 110 + } 111 + 112 + void pcm_play_unlock(void) 113 + { 114 + if (!is_in_audio_thread(_pcm_thread_id)) { 115 + RecursiveLock_Unlock(&_pcm_lock_mtx); 116 + } 117 + } 118 + 119 + static void pcm_write_to_soundcard(const void *pcm_buffer, size_t pcm_buffer_size, ndspWaveBuf *dsp_buffer) 120 + { 121 + s16 *buffer = dsp_buffer->data_pcm16; 122 + memcpy(buffer, pcm_buffer, pcm_buffer_size); 123 + 124 + dsp_buffer->nsamples = pcm_buffer_size / 2 / sizeof(s16); 125 + ndspChnWaveBufAdd(0, dsp_buffer); 126 + DSP_FlushDataCache(buffer, pcm_buffer_size); 127 + } 128 + 129 + bool fill_buffer(ndspWaveBuf *dsp_buffer) 130 + { 131 + if(_pcm_buffer_size == 0) 132 + { 133 + /* Retrive a new PCM buffer from Rockbox. */ 134 + if(!pcm_play_dma_complete_callback(PCM_DMAST_OK, &_pcm_buffer, &_pcm_buffer_size)) 135 + { 136 + /* DEBUGF("DEBUG %s: No new buffer.\n", __func__); */ 137 + svcSleepThread(10000); 138 + return false; 139 + } 140 + } 141 + pcm_play_dma_status_callback(PCM_DMAST_STARTED); 142 + 143 + /* This relies on Rockbox PCM frame buffer size == ALSA PCM frame buffer size. */ 144 + pcm_write_to_soundcard(_pcm_buffer, _pcm_buffer_size, dsp_buffer); 145 + _pcm_buffer_size = 0; 146 + 147 + return true; 148 + } 149 + 150 + void pcm_thread_run(void* nothing) 151 + { 152 + (void) nothing; 153 + 154 + DEBUGF("DEBUG %s: Thread start.\n", __func__); 155 + 156 + _pcm_thread_id = sys_thread_id(); 157 + 158 + while(!AtomicGet(&_pcm_shutdown)) 159 + { 160 + RecursiveLock_Lock(&_pcm_lock_mtx); 161 + for(size_t i = 0; i < ARRAY_SIZE(_dsp_wave_bufs); ++i) { 162 + if(_dsp_wave_bufs[i].status != NDSP_WBUF_DONE) { 163 + continue; 164 + } 165 + 166 + if(!fill_buffer(&_dsp_wave_bufs[i])) { 167 + continue; 168 + } 169 + } 170 + RecursiveLock_Unlock(&_pcm_lock_mtx); 171 + 172 + // Wait for a signal that we're needed again before continuing, 173 + // so that we can yield to other things that want to run 174 + // (Note that the 3DS uses cooperative threading) 175 + LightEvent_Wait(&_dsp_callback_event); 176 + } 177 + 178 + DEBUGF("DEBUG %s: Thread end.\n", __func__); 179 + } 180 + 181 + void dsp_callback(void *const nul_) { 182 + (void)nul_; 183 + 184 + if(AtomicGet(&_pcm_shutdown)) { 185 + return; 186 + } 187 + 188 + LightEvent_Signal(&_dsp_callback_event); 189 + } 190 + 191 + static void pcm_dma_apply_settings_nolock(void) 192 + { 193 + ndspChnReset(0); 194 + 195 + ndspSetOutputMode(NDSP_OUTPUT_STEREO); 196 + 197 + ndspChnSetRate(0, pcm_sampr); 198 + ndspChnSetFormat(0, NDSP_FORMAT_STEREO_PCM16); 199 + /* ndspChnSetInterp(0, NDSP_INTERP_POLYPHASE); */ 200 + /* ndspChnSetInterp(0, NDSP_INTERP_NONE); */ 201 + ndspChnSetInterp(0, NDSP_INTERP_LINEAR); 202 + 203 + float mix[12]; 204 + memset(mix, 0, sizeof(mix)); 205 + mix[0] = 1.0; 206 + mix[1] = 1.0; 207 + ndspChnSetMix(0, mix); 208 + 209 + memset(_dsp_wave_bufs, 0, sizeof(_dsp_wave_bufs) * ARRAY_SIZE(_dsp_wave_bufs)); 210 + 211 + const size_t wave_buffer_size = MIX_FRAME_SAMPLES * 2 * sizeof(s16); 212 + size_t buffer_size = wave_buffer_size * ARRAY_SIZE(_dsp_wave_bufs); 213 + 214 + _dsp_audio_buffer = (s16 *)linearAlloc(buffer_size); 215 + 216 + s16 *buffer = _dsp_audio_buffer; 217 + for (unsigned i = 0; i < ARRAY_SIZE(_dsp_wave_bufs); i++) { 218 + _dsp_wave_bufs[i].data_vaddr = buffer; 219 + _dsp_wave_bufs[i].status = NDSP_WBUF_DONE; 220 + 221 + buffer += wave_buffer_size / sizeof(buffer[0]); 222 + } 223 + 224 + ndspChnSetPaused(0, true); 225 + ndspSetCallback(dsp_callback, NULL); 226 + 227 + AtomicSet(&_pcm_shutdown, 0); 228 + AtomicSet(&_dsp_enabled, 1); 229 + 230 + // Start the thread, passing our opusFile as an argument. 231 + _pcm_thread = threadCreate(pcm_thread_run, 232 + NULL, 233 + 32 * 1024, /* 32kB stack size */ 234 + 0x18, /* high priority */ 235 + -1, /* run on any core */ false); 236 + } 237 + 238 + void pcm_play_dma_start(const void *addr, size_t size) 239 + { 240 + _pcm_buffer = addr; 241 + _pcm_buffer_size = size; 242 + 243 + RecursiveLock_Lock(&_pcm_lock_mtx); 244 + ndspChnSetPaused(0, false); 245 + RecursiveLock_Unlock(&_pcm_lock_mtx); 246 + } 247 + 248 + void pcm_play_dma_stop(void) 249 + { 250 + RecursiveLock_Lock(&_pcm_lock_mtx); 251 + ndspChnSetPaused(0, true); 252 + RecursiveLock_Unlock(&_pcm_lock_mtx); 253 + } 254 + 255 + /* TODO: implement recording */ 256 + #ifdef HAVE_RECORDING 257 + void pcm_rec_lock(void) 258 + { 259 + } 260 + 261 + void pcm_rec_unlock(void) 262 + { 263 + } 264 + 265 + void pcm_rec_dma_init(void) 266 + { 267 + } 268 + 269 + void pcm_rec_dma_close(void) 270 + { 271 + } 272 + 273 + void pcm_rec_dma_start(void *start, size_t size) 274 + { 275 + (void)start; 276 + (void)size; 277 + 278 + 279 + } 280 + 281 + void pcm_rec_dma_stop(void) 282 + { 283 + } 284 + 285 + const void * pcm_rec_dma_get_peak_buffer(void) 286 + { 287 + return NULL; 288 + } 289 + 290 + void audiohw_set_recvol(int left, int right, int type) 291 + { 292 + (void)left; 293 + (void)right; 294 + (void)type; 295 + } 296 + 297 + #ifdef HAVE_SPDIF_IN 298 + unsigned long spdif_measure_frequency(void) 299 + { 300 + return 0; 301 + } 302 + #endif 303 + 304 + #endif /* HAVE_RECORDING */ 305 + 306 + void pcm_play_dma_init(void) 307 + { 308 + Result ndsp_init_res = ndspInit(); 309 + if (R_FAILED(ndsp_init_res)) { 310 + if ((R_SUMMARY(ndsp_init_res) == RS_NOTFOUND) && (R_MODULE(ndsp_init_res) == RM_DSP)) { 311 + logf("DSP init failed: dspfirm.cdc missing!"); 312 + } else { 313 + logf("DSP init failed. Error code: 0x%lX", ndsp_init_res); 314 + } 315 + return; 316 + } 317 + 318 + RecursiveLock_Init(&_pcm_lock_mtx); 319 + LightEvent_Init(&_dsp_callback_event, RESET_ONESHOT); 320 + } 321 + 322 + void pcm_play_dma_postinit(void) 323 + { 324 + } 325 + 326 + void pcm_dma_apply_settings(void) 327 + { 328 + pcm_play_lock(); 329 + pcm_dma_apply_settings_nolock(); 330 + pcm_play_unlock(); 331 + } 332 + 333 + void pcm_close_device(void) 334 + { 335 + RecursiveLock_Lock(&_pcm_lock_mtx); 336 + AtomicSet(&_pcm_shutdown, 1); 337 + LightEvent_Signal(&_dsp_callback_event); 338 + 339 + threadJoin(_pcm_thread, UINT64_MAX); 340 + threadFree(_pcm_thread); 341 + _pcm_thread_id = -1; 342 + RecursiveLock_Unlock(&_pcm_lock_mtx); 343 + 344 + ndspSetCallback(NULL, NULL); 345 + 346 + ndspChnReset(0); 347 + if (_dsp_audio_buffer != NULL) { 348 + linearFree(_dsp_audio_buffer); 349 + _dsp_audio_buffer = NULL; 350 + } 351 + 352 + ndspExit(); 353 + } 354 + 355 + /* moved from drivers/audio/ctru.c */ 356 + void audiohw_close(void) 357 + { 358 + pcm_close_device(); 359 + }
+107
firmware/target/hosted/ctru/powermgmt-ctru.c
··· 1 + /*************************************************************************** 2 + * __________ __ ___. 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * 9 + * Copyright (C) 2025 Mauricio G. 10 + * 11 + * This program is free software; you can redistribute it and/or 12 + * modify it under the terms of the GNU General Public License 13 + * as published by the Free Software Foundation; either version 2 14 + * of the License, or (at your option) any later version. 15 + * 16 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 17 + * KIND, either express or implied. 18 + * 19 + ****************************************************************************/ 20 + 21 + #include <stdio.h> 22 + #include <unistd.h> 23 + #include <stdbool.h> 24 + 25 + #include "config.h" 26 + #include "kernel.h" 27 + #include "powermgmt.h" 28 + #include "power.h" 29 + #include "adc.h" 30 + #include "system.h" 31 + #include "debug.h" 32 + 33 + #include <3ds/types.h> 34 + #include <3ds/result.h> 35 + #include <3ds/services/mcuhwc.h> 36 + #include <3ds/services/ptmu.h> 37 + 38 + void mcuhwc_init(void) 39 + { 40 + Result result = mcuHwcInit(); 41 + if (R_FAILED(result)) { 42 + DEBUGF("mcuhwc_init: warning, mcuHwcInit failed\n"); 43 + } 44 + 45 + result = ptmuInit(); 46 + if (R_FAILED(result)) { 47 + DEBUGF("mcuhwc_init: warning, ptmuInit failed\n"); 48 + } 49 + } 50 + 51 + void mcuhwc_close(void) 52 + { 53 + mcuHwcExit(); 54 + ptmuExit(); 55 + } 56 + 57 + /* FIXME: what level should disksafe be?*/ 58 + unsigned short battery_level_disksafe = 0; 59 + 60 + unsigned short battery_level_shutoff = 0; 61 + 62 + /* voltages (millivolt) of 0%, 10%, ... 100% when charging disabled */ 63 + unsigned short percent_to_volt_discharge[11] = 64 + { 65 + }; 66 + 67 + /* voltages (millivolt) of 0%, 10%, ... 100% when charging enabled */ 68 + unsigned short percent_to_volt_charge[11] = 69 + { 70 + 3450, 3670, 3721, 3751, 3782, 3821, 3876, 3941, 4034, 4125, 4200 71 + }; 72 + 73 + enum 74 + { 75 + BATT_NOT_CHARGING = 0, 76 + BATT_CHARGING, 77 + }; 78 + 79 + static u8 charging_status = BATT_NOT_CHARGING; 80 + 81 + unsigned int power_input_status(void) 82 + { 83 + unsigned status = POWER_INPUT_NONE; 84 + PTMU_GetBatteryChargeState(&charging_status); 85 + if (charging_status == BATT_CHARGING) 86 + status = POWER_INPUT_MAIN_CHARGER; 87 + return status; 88 + } 89 + 90 + /* Returns battery voltage from MAX17040 VCELL ADC [millivolts steps], 91 + * adc returns voltage in 1.25mV steps */ 92 + /* 93 + * TODO this would be interesting to be mixed with battery percentage, for information 94 + * and completition purpouses 95 + */ 96 + int _battery_level(void) 97 + { 98 + u8 level = 100; 99 + MCUHWC_GetBatteryLevel(&level); 100 + return level; 101 + } 102 + 103 + bool charging_state(void) 104 + { 105 + return (charging_status == BATT_CHARGING); 106 + } 107 +
+145
firmware/target/hosted/ctru/system-ctru.c
··· 1 + /*************************************************************************** 2 + * __________ __ ___. 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * $Id$ 9 + * 10 + * Copyright (C) 2006 by Daniel Everton <dan@iocaine.org> 11 + * 12 + * This program is free software; you can redistribute it and/or 13 + * modify it under the terms of the GNU General Public License 14 + * as published by the Free Software Foundation; either version 2 15 + * of the License, or (at your option) any later version. 16 + * 17 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 + * KIND, either express or implied. 19 + * 20 + ****************************************************************************/ 21 + 22 + #include <stdlib.h> 23 + #include <stdio.h> 24 + #include <string.h> 25 + #include <inttypes.h> 26 + #include "system.h" 27 + #include "kernel.h" 28 + #include "thread-ctru.h" 29 + #include "system-ctru.h" 30 + #include "button-ctru.h" 31 + #include "lcd-bitmap.h" 32 + #include "panic.h" 33 + #include "debug.h" 34 + 35 + #include <3ds/types.h> 36 + #include <3ds/allocator/linear.h> 37 + #include <3ds/services/cfgu.h> 38 + #include "bfile.h" 39 + 40 + const char *audiodev = NULL; 41 + 42 + #ifdef DEBUG 43 + bool debug_audio = false; 44 + #endif 45 + 46 + /* default main thread priority, low */ 47 + s32 main_thread_priority = 0x30; 48 + bool is_n3ds = false; 49 + 50 + /* filesystem */ 51 + sync_RWMutex file_internal_mrsw; 52 + FS_Archive sdmcArchive; 53 + 54 + void ctru_sys_quit(void) 55 + { 56 + sys_poweroff(); 57 + } 58 + 59 + void power_off(void) 60 + { 61 + /* since sim_thread_shutdown() grabs the mutex we need to let it free, 62 + * otherwise sys_wait_thread will deadlock */ 63 + struct thread_entry* t = sim_thread_unlock(); 64 + sim_thread_shutdown(); 65 + 66 + /* lock again before entering the scheduler */ 67 + sim_thread_lock(t); 68 + /* sim_thread_shutdown() will cause sim_do_exit() to be called via longjmp, 69 + * but only if we let the sdl thread scheduler exit the other threads */ 70 + while(1) yield(); 71 + } 72 + 73 + void sim_do_exit() 74 + { 75 + sim_kernel_shutdown(); 76 + sys_timer_quit(); 77 + /* TODO: quit_everything() */ 78 + exit(EXIT_SUCCESS); 79 + } 80 + 81 + uintptr_t *stackbegin; 82 + uintptr_t *stackend; 83 + void system_init(void) 84 + { 85 + /* fake stack, OS manages size (and growth) */ 86 + volatile uintptr_t stack = 0; 87 + stackbegin = stackend = (uintptr_t*) &stack; 88 + 89 + /* disable sleep mode when lid is closed */ 90 + aptSetSleepAllowed(false); 91 + 92 + sys_console_init(); 93 + sys_timer_init(); 94 + 95 + svcGetThreadPriority(&main_thread_priority, CUR_THREAD_HANDLE); 96 + if (main_thread_priority != 0x30) { 97 + DEBUGF("warning, main_thread_priority = 0x%x\n", main_thread_priority); 98 + } 99 + 100 + /* check for New 3DS model */ 101 + s64 dummyInfo; 102 + is_n3ds = svcGetSystemInfo(&dummyInfo, 0x10001, 0) == 0; 103 + 104 + /* filesystem */ 105 + sync_RWMutexInit(&file_internal_mrsw); 106 + Result res = FSUSER_OpenArchive(&sdmcArchive, 107 + ARCHIVE_SDMC, 108 + fsMakePath(PATH_ASCII, "")); 109 + if (R_FAILED(res)) { 110 + DEBUGF("FSUSER_OpenArchive failed\n"); 111 + exit(-1); 112 + } 113 + 114 + mcuhwc_init(); 115 + cfguInit(); 116 + } 117 + 118 + 119 + void system_reboot(void) 120 + { 121 + sim_thread_exception_wait(); 122 + } 123 + 124 + void system_exception_wait(void) 125 + { 126 + system_reboot(); 127 + } 128 + 129 + int hostfs_init(void) 130 + { 131 + /* stub */ 132 + /* romfsInit(); */ 133 + return 0; 134 + } 135 + 136 + #ifdef HAVE_STORAGE_FLUSH 137 + int hostfs_flush(void) 138 + { 139 + #ifdef __unix__ 140 + sync(); 141 + #endif 142 + return 0; 143 + } 144 + #endif /* HAVE_STORAGE_FLUSH */ 145 +
+70
firmware/target/hosted/ctru/system-ctru.h
··· 1 + /*************************************************************************** 2 + * __________ __ ___. 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * $Id$ 9 + * 10 + * Copyright (C) 2025 by Mauricio G. 11 + * 12 + * This program is free software; you can redistribute it and/or 13 + * modify it under the terms of the GNU General Public License 14 + * as published by the Free Software Foundation; either version 2 15 + * of the License, or (at your option) any later version. 16 + * 17 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 + * KIND, either express or implied. 19 + * 20 + ****************************************************************************/ 21 + #ifndef _SYSTEM_SDL_H_ 22 + #define _SYSTEM_SDL_H_ 23 + 24 + #include <stdbool.h> 25 + #include <stdbool.h> 26 + #include "config.h" 27 + #include "gcc_extensions.h" 28 + 29 + #include <3ds/types.h> 30 + #include <3ds/svc.h> 31 + #include "sys_timer.h" 32 + 33 + #define HIGHEST_IRQ_LEVEL 1 34 + 35 + int set_irq_level(int level); 36 + 37 + #define disable_irq() \ 38 + ((void)set_irq_level(HIGHEST_IRQ_LEVEL)) 39 + 40 + #define enable_irq() \ 41 + ((void)set_irq_level(0)) 42 + 43 + #define disable_irq_save() \ 44 + set_irq_level(HIGHEST_IRQ_LEVEL) 45 + 46 + #define restore_irq(level) \ 47 + ((void)set_irq_level(level)) 48 + 49 + #define wait_for_interrupt() 50 + 51 + #include "system-hosted.h" 52 + 53 + void sim_enter_irq_handler(void); 54 + void sim_exit_irq_handler(void); 55 + void sim_kernel_shutdown(void); 56 + void sys_poweroff(void); 57 + void sys_handle_argv(int argc, char *argv[]); 58 + void gui_message_loop(void); 59 + void sim_do_exit(void) NORETURN_ATTR; 60 + void sdl_sys_quit(void); 61 + 62 + void mcuhwc_init(void); 63 + void mcuhwc_close(void); 64 + 65 + extern bool background; /* True if the background image is enabled */ 66 + extern bool showremote; 67 + extern double display_zoom; 68 + extern long start_tick; 69 + 70 + #endif /* _SYSTEM_SDL_H_ */
+4
firmware/target/hosted/ctru/system-target.h
··· 1 + #include "system-ctru.h" 2 + 3 + #define NEED_GENERIC_BYTESWAPS 4 +
+503
firmware/target/hosted/ctru/thread-ctru.c
··· 1 + /*************************************************************************** 2 + * __________ __ ___. 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * $Id$ 9 + * 10 + * Copyright (C) 2006 Dan Everton 11 + * 12 + * This program is free software; you can redistribute it and/or 13 + * modify it under the terms of the GNU General Public License 14 + * as published by the Free Software Foundation; either version 2 15 + * of the License, or (at your option) any later version. 16 + * 17 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 + * KIND, either express or implied. 19 + * 20 + ****************************************************************************/ 21 + 22 + #include "autoconf.h" 23 + 24 + #include <stdbool.h> 25 + #include <time.h> 26 + #include <stdlib.h> 27 + #include <string.h> /* memset() */ 28 + #include <setjmp.h> 29 + #include "system-ctru.h" 30 + #include "thread-ctru.h" 31 + #include "sys_thread.h" 32 + #include "../kernel-internal.h" 33 + #include "core_alloc.h" 34 + 35 + /* Define this as 1 to show informational messages that are not errors. */ 36 + #define THREAD_DEBUGF_ENABLED 0 37 + 38 + #if THREAD_DEBUGF_ENABLED 39 + #define THREAD_DEBUGF(...) DEBUGF(__VA_ARGS__) 40 + static char __name[sizeof (((struct thread_debug_info *)0)->name)]; 41 + #define THREAD_GET_NAME(thread) \ 42 + ({ format_thread_name(__name, sizeof (__name), thread); __name; }) 43 + #else 44 + #define THREAD_DEBUGF(...) 45 + #define THREAD_GET_NAME(thread) 46 + #endif 47 + 48 + #define THREAD_PANICF(str...) \ 49 + ({ fprintf(stderr, str); exit(-1); }) 50 + 51 + /* Jump buffers for graceful exit - kernel threads don't stay neatly 52 + * in their start routines responding to messages so this is the only 53 + * way to get them back in there so they may exit */ 54 + static jmp_buf thread_jmpbufs[MAXTHREADS]; 55 + /* this mutex locks out other Rockbox threads while one runs, 56 + * that enables us to simulate a cooperative environment even if 57 + * the host is preemptive */ 58 + static RecursiveLock m; 59 + #define THREADS_RUN 0 60 + #define THREADS_EXIT 1 61 + #define THREADS_EXIT_COMMAND_DONE 2 62 + static volatile int threads_status = THREADS_RUN; 63 + 64 + extern long start_tick; 65 + 66 + void sim_thread_shutdown(void) 67 + { 68 + int i; 69 + 70 + /* This *has* to be a push operation from a thread not in the pool 71 + so that they may be dislodged from their blocking calls. */ 72 + 73 + /* Tell all threads jump back to their start routines, unlock and exit 74 + gracefully - we'll check each one in turn for it's status. Threads 75 + _could_ terminate via thread_exit or multiple threads could exit 76 + on each unlock but that is safe. */ 77 + 78 + /* Do this before trying to acquire lock */ 79 + threads_status = THREADS_EXIT; 80 + 81 + /* Take control */ 82 + RecursiveLock_Lock(&m); 83 + 84 + /* Signal all threads on delay or block */ 85 + for (i = 0; i < MAXTHREADS; i++) 86 + { 87 + struct thread_entry *thread = __thread_slot_entry(i); 88 + if (thread->context.s == NULL) 89 + continue; 90 + LightSemaphore_Release(thread->context.s, 1); 91 + } 92 + 93 + /* Wait for all threads to finish and cleanup old ones. */ 94 + for (i = 0; i < MAXTHREADS; i++) 95 + { 96 + struct thread_entry *thread = __thread_slot_entry(i); 97 + sysThread *t = thread->context.t; 98 + 99 + if (t != NULL) 100 + { 101 + RecursiveLock_Unlock(&m); 102 + /* Wait for it to finish */ 103 + sys_wait_thread(t, NULL); 104 + /* Relock for next thread signal */ 105 + RecursiveLock_Lock(&m); 106 + /* Already waited and exiting thread would have waited .told, 107 + * replacing it with t. */ 108 + thread->context.told = NULL; 109 + } 110 + else 111 + { 112 + /* Wait on any previous thread in this location-- could be one not 113 + * quite finished exiting but has just unlocked the mutex. If it's 114 + * NULL, the call returns immediately. 115 + * 116 + * See thread_exit below for more information. */ 117 + sys_wait_thread(thread->context.told, NULL); 118 + } 119 + } 120 + 121 + RecursiveLock_Unlock(&m); 122 + 123 + /* Signal completion of operation */ 124 + threads_status = THREADS_EXIT_COMMAND_DONE; 125 + } 126 + 127 + void sim_thread_exception_wait(void) 128 + { 129 + while (1) 130 + { 131 + sys_delay(HZ/10); 132 + if (threads_status != THREADS_RUN) 133 + thread_exit(); 134 + } 135 + } 136 + 137 + /* A way to yield and leave the threading system for extended periods */ 138 + void sim_thread_lock(void *me) 139 + { 140 + RecursiveLock_Lock(&m); 141 + __running_self_entry() = (struct thread_entry *)me; 142 + 143 + if (threads_status != THREADS_RUN) 144 + thread_exit(); 145 + } 146 + 147 + void * sim_thread_unlock(void) 148 + { 149 + struct thread_entry *current = __running_self_entry(); 150 + RecursiveLock_Unlock(&m); 151 + return current; 152 + } 153 + 154 + void switch_thread(void) 155 + { 156 + struct thread_entry *current = __running_self_entry(); 157 + 158 + enable_irq(); 159 + 160 + switch (current->state) 161 + { 162 + case STATE_RUNNING: 163 + { 164 + RecursiveLock_Unlock(&m); 165 + /* Any other thread waiting already will get it first */ 166 + RecursiveLock_Lock(&m); 167 + break; 168 + } /* STATE_RUNNING: */ 169 + 170 + case STATE_BLOCKED: 171 + { 172 + int oldlevel; 173 + 174 + RecursiveLock_Unlock(&m); 175 + sys_sem_wait(current->context.s); 176 + RecursiveLock_Lock(&m); 177 + 178 + oldlevel = disable_irq_save(); 179 + current->state = STATE_RUNNING; 180 + restore_irq(oldlevel); 181 + break; 182 + } /* STATE_BLOCKED: */ 183 + 184 + case STATE_BLOCKED_W_TMO: 185 + { 186 + int result, oldlevel; 187 + 188 + RecursiveLock_Unlock(&m); 189 + result = sys_sem_wait_timeout(current->context.s, current->tmo_tick); 190 + RecursiveLock_Lock(&m); 191 + 192 + oldlevel = disable_irq_save(); 193 + 194 + current->state = STATE_RUNNING; 195 + 196 + if (result == 1) 197 + { 198 + /* Other signals from an explicit wake could have been made before 199 + * arriving here if we timed out waiting for the semaphore. Make 200 + * sure the count is reset. */ 201 + while (sys_sem_value(current->context.s) > 0) 202 + sys_sem_try_wait(current->context.s); 203 + } 204 + 205 + restore_irq(oldlevel); 206 + break; 207 + } /* STATE_BLOCKED_W_TMO: */ 208 + 209 + case STATE_SLEEPING: 210 + { 211 + RecursiveLock_Unlock(&m); 212 + sys_sem_wait_timeout(current->context.s, current->tmo_tick); 213 + RecursiveLock_Lock(&m); 214 + current->state = STATE_RUNNING; 215 + break; 216 + } /* STATE_SLEEPING: */ 217 + } 218 + 219 + #ifdef BUFLIB_DEBUG_CHECK_VALID 220 + core_check_valid(); 221 + #endif 222 + __running_self_entry() = current; 223 + 224 + if (threads_status != THREADS_RUN) 225 + thread_exit(); 226 + } 227 + 228 + void sleep_thread(int ticks) 229 + { 230 + struct thread_entry *current = __running_self_entry(); 231 + int rem; 232 + 233 + current->state = STATE_SLEEPING; 234 + 235 + rem = (sys_get_ticks() - start_tick) % (1000/HZ); 236 + if (rem < 0) 237 + rem = 0; 238 + 239 + current->tmo_tick = (1000/HZ) * ticks + ((1000/HZ)-1) - rem; 240 + } 241 + 242 + void block_thread_(struct thread_entry *current, int ticks) 243 + { 244 + if (ticks < 0) 245 + current->state = STATE_BLOCKED; 246 + else 247 + { 248 + current->state = STATE_BLOCKED_W_TMO; 249 + current->tmo_tick = (1000/HZ)*ticks; 250 + } 251 + 252 + wait_queue_register(current); 253 + } 254 + 255 + unsigned int wakeup_thread_(struct thread_entry *thread 256 + IF_PRIO(, enum wakeup_thread_protocol proto)) 257 + { 258 + switch (thread->state) 259 + { 260 + case STATE_BLOCKED: 261 + case STATE_BLOCKED_W_TMO: 262 + wait_queue_remove(thread); 263 + thread->state = STATE_RUNNING; 264 + LightSemaphore_Release(thread->context.s, 1); 265 + return THREAD_OK; 266 + } 267 + 268 + return THREAD_NONE; 269 + (void) proto; 270 + } 271 + 272 + void thread_thaw(unsigned int thread_id) 273 + { 274 + struct thread_entry *thread = __thread_id_entry(thread_id); 275 + 276 + if (thread->id == thread_id && thread->state == STATE_FROZEN) 277 + { 278 + thread->state = STATE_RUNNING; 279 + LightSemaphore_Release(thread->context.s, 1); 280 + } 281 + } 282 + 283 + int runthread(void *data) 284 + { 285 + /* Cannot access thread variables before locking the mutex as the 286 + data structures may not be filled-in yet. */ 287 + RecursiveLock_Lock(&m); 288 + 289 + struct thread_entry *current = (struct thread_entry *)data; 290 + __running_self_entry() = current; 291 + 292 + jmp_buf *current_jmpbuf = &thread_jmpbufs[THREAD_ID_SLOT(current->id)]; 293 + 294 + /* Setup jump for exit */ 295 + if (setjmp(*current_jmpbuf) == 0) 296 + { 297 + /* Run the thread routine */ 298 + if (current->state == STATE_FROZEN) 299 + { 300 + RecursiveLock_Unlock(&m); 301 + sys_sem_wait(current->context.s); 302 + RecursiveLock_Lock(&m); 303 + __running_self_entry() = current; 304 + } 305 + 306 + if (threads_status == THREADS_RUN) 307 + { 308 + current->context.start(); 309 + THREAD_DEBUGF("Thread Done: %d (%s)\n", 310 + THREAD_ID_SLOT(current->id), 311 + THREAD_GET_NAME(current)); 312 + /* Thread routine returned - suicide */ 313 + } 314 + 315 + thread_exit(); 316 + } 317 + else 318 + { 319 + /* Unlock and exit */ 320 + RecursiveLock_Unlock(&m); 321 + } 322 + 323 + return 0; 324 + } 325 + 326 + unsigned int create_thread(void (*function)(void), 327 + void* stack, size_t stack_size, 328 + unsigned flags, const char *name 329 + IF_PRIO(, int priority) 330 + IF_COP(, unsigned int core)) 331 + { 332 + THREAD_DEBUGF("Creating thread: (%s)\n", name ? name : ""); 333 + 334 + struct thread_entry *thread = thread_alloc(); 335 + if (thread == NULL) 336 + { 337 + DEBUGF("Failed to find thread slot\n"); 338 + return 0; 339 + } 340 + 341 + LightSemaphore *s = (LightSemaphore *) malloc(sizeof(LightSemaphore)); 342 + if (s == NULL) 343 + { 344 + DEBUGF("Failed to create semaphore\n"); 345 + return 0; 346 + } 347 + 348 + LightSemaphore_Init(s, 0, 255); 349 + 350 + sysThread *t = sys_create_thread(runthread, 351 + name, 352 + stack_size, 353 + thread 354 + IF_PRIO(, priority) 355 + IF_COP(, core)); 356 + if (t == NULL) 357 + { 358 + DEBUGF("Failed to create thread\n"); 359 + free(s); 360 + return 0; 361 + } 362 + 363 + thread->name = name; 364 + thread->state = (flags & CREATE_THREAD_FROZEN) ? 365 + STATE_FROZEN : STATE_RUNNING; 366 + thread->context.start = function; 367 + thread->context.t = t; 368 + thread->context.s = s; 369 + thread->priority = priority; 370 + 371 + THREAD_DEBUGF("New Thread: %lu (%s)\n", 372 + (unsigned long)thread->id, 373 + THREAD_GET_NAME(thread)); 374 + 375 + return thread->id; 376 + (void)stack; 377 + } 378 + 379 + void thread_exit(void) 380 + { 381 + struct thread_entry *current = __running_self_entry(); 382 + 383 + int oldlevel = disable_irq_save(); 384 + 385 + sysThread *t = current->context.t; 386 + LightSemaphore *s = current->context.s; 387 + 388 + /* Wait the last thread here and keep this one or ctru will leak it since 389 + * it doesn't free its own library allocations unless a wait is performed. 390 + * Such behavior guards against the memory being invalid by the time 391 + * sys_wait_thread is reached and also against two different threads having 392 + * the same pointer. It also makes sys_wait_thread a non-concurrent function. 393 + * 394 + * However, see more below about sys_kill_thread. 395 + */ 396 + sys_wait_thread(current->context.told, NULL); 397 + 398 + current->context.t = NULL; 399 + current->context.s = NULL; 400 + current->context.told = t; 401 + 402 + unsigned int id = current->id; 403 + new_thread_id(current); 404 + current->state = STATE_KILLED; 405 + wait_queue_wake(&current->queue); 406 + 407 + free(s); 408 + 409 + /* Do a graceful exit - perform the longjmp back into the thread 410 + function to return */ 411 + restore_irq(oldlevel); 412 + 413 + thread_free(current); 414 + 415 + longjmp(thread_jmpbufs[THREAD_ID_SLOT(id)], 1); 416 + 417 + /* This should never and must never be reached - if it is, the 418 + * state is corrupted */ 419 + THREAD_PANICF("thread_exit->K:*R (ID: %d)", id); 420 + while (1); 421 + } 422 + 423 + void thread_wait(unsigned int thread_id) 424 + { 425 + struct thread_entry *current = __running_self_entry(); 426 + struct thread_entry *thread = __thread_id_entry(thread_id); 427 + 428 + if (thread->id == thread_id && thread->state != STATE_KILLED) 429 + { 430 + block_thread(current, TIMEOUT_BLOCK, &thread->queue, NULL); 431 + switch_thread(); 432 + } 433 + } 434 + 435 + int thread_set_priority(unsigned int thread_id, int priority) 436 + { 437 + struct thread_entry *thread = __thread_id_entry(thread_id); 438 + sysThread *t = thread->context.t; 439 + thread->priority = sys_set_thread_priority(t, priority); 440 + return thread->priority; 441 + } 442 + 443 + int thread_get_priority(unsigned int thread_id) 444 + { 445 + struct thread_entry *thread = __thread_id_entry(thread_id); 446 + return thread->priority; 447 + } 448 + 449 + /* Initialize threading */ 450 + void init_threads(void) 451 + { 452 + RecursiveLock_Init(&m); 453 + RecursiveLock_Lock(&m); 454 + 455 + thread_alloc_init(); 456 + 457 + struct thread_entry *thread = thread_alloc(); 458 + if (thread == NULL) 459 + { 460 + fprintf(stderr, "Main thread alloc failed\n"); 461 + return; 462 + } 463 + 464 + /* Slot 0 is reserved for the main thread - initialize it here and 465 + then create the thread - it is possible to have a quick, early 466 + shutdown try to access the structure. */ 467 + thread->name = __main_thread_name; 468 + thread->state = STATE_RUNNING; 469 + thread->context.s = (LightSemaphore *) malloc(sizeof(LightSemaphore)); 470 + LightSemaphore_Init(thread->context.s, 0, 255); 471 + thread->context.t = NULL; /* NULL for the implicit main thread */ 472 + __running_self_entry() = thread; 473 + 474 + if (thread->context.s == NULL) 475 + { 476 + fprintf(stderr, "Failed to create main semaphore\n"); 477 + return; 478 + } 479 + 480 + /* Tell all threads jump back to their start routines, unlock and exit 481 + gracefully - we'll check each one in turn for it's status. Threads 482 + _could_ terminate via thread_exit or multiple threads could exit 483 + on each unlock but that is safe. */ 484 + 485 + /* Setup jump for exit */ 486 + if (setjmp(thread_jmpbufs[THREAD_ID_SLOT(thread->id)]) == 0) 487 + { 488 + THREAD_DEBUGF("Main Thread: %lu (%s)\n", 489 + (unsigned long)thread->id, 490 + THREAD_GET_NAME(thread)); 491 + return; 492 + } 493 + 494 + RecursiveLock_Unlock(&m); 495 + 496 + /* Set to 'COMMAND_DONE' when other rockbox threads have exited. */ 497 + while (threads_status < THREADS_EXIT_COMMAND_DONE) 498 + sys_delay(10); 499 + 500 + /* We're the main thead - perform exit - doesn't return. */ 501 + sim_do_exit(); 502 + } 503 +
+32
firmware/target/hosted/ctru/thread-ctru.h
··· 1 + /*************************************************************************** 2 + * __________ __ ___. 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * $Id$ 9 + * 10 + * Copyright (C) 2025 Mauricio G. 11 + * 12 + * This program is free software; you can redistribute it and/or 13 + * modify it under the terms of the GNU General Public License 14 + * as published by the Free Software Foundation; either version 2 15 + * of the License, or (at your option) any later version. 16 + * 17 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 + * KIND, either express or implied. 19 + * 20 + ****************************************************************************/ 21 + 22 + #ifndef __THREAD_CTRU_H__ 23 + #define __THREAD_CTRU_H__ 24 + 25 + /* extra thread functions that only apply when running on hosting platforms */ 26 + void sim_thread_lock(void *me); 27 + void * sim_thread_unlock(void); 28 + void sim_thread_exception_wait(void); 29 + void sim_thread_shutdown(void); /* Shut down all kernel threads gracefully */ 30 + 31 + #endif /* #ifndef __THREAD_CTRU_H__ */ 32 +
+61
firmware/target/hosted/ctru/timer-ctru.c
··· 1 + /*************************************************************************** 2 + * __________ __ ___. 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * $Id$ 9 + * 10 + * Copyright (C) 2025 Mauricio G. 11 + * 12 + * This program is free software; you can redistribute it and/or 13 + * modify it under the terms of the GNU General Public License 14 + * as published by the Free Software Foundation; either version 2 15 + * of the License, or (at your option) any later version. 16 + * 17 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 + * KIND, either express or implied. 19 + * 20 + ****************************************************************************/ 21 + 22 + #include "sys_timer.h" 23 + #include "timer.h" 24 + 25 + static int timer_prio = -1; 26 + void (*global_timer_callback)(void); 27 + int timerId; 28 + 29 + u32 _timer_callback(u32 interval, void *param){ 30 + (void)param; 31 + global_timer_callback(); 32 + return(interval); 33 + } 34 + 35 + #define cycles_to_miliseconds(cycles) \ 36 + ((int)((1000*cycles)/TIMER_FREQ)) 37 + 38 + bool timer_register(int reg_prio, void (*unregister_callback)(void), 39 + long cycles, void (*timer_callback)(void)) 40 + { 41 + (void)unregister_callback; 42 + if (reg_prio <= timer_prio || cycles == 0) 43 + return false; 44 + timer_prio=reg_prio; 45 + global_timer_callback=timer_callback; 46 + timerId=sys_add_timer(cycles_to_miliseconds(cycles), _timer_callback, 0); 47 + return true; 48 + } 49 + 50 + bool timer_set_period(long cycles) 51 + { 52 + sys_remove_timer(timerId); 53 + timerId=sys_add_timer(cycles_to_miliseconds(cycles), _timer_callback, 0); 54 + return true; 55 + } 56 + 57 + void timer_unregister(void) 58 + { 59 + sys_remove_timer(timerId); 60 + timer_prio = -1; 61 + }
+61
firmware/target/hosted/ctru/timer-ctru_sys.c
··· 1 + /*************************************************************************** 2 + * __________ __ ___. 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * $Id$ 9 + * 10 + * Copyright (C) 2025 Mauricio G. 11 + * 12 + * This program is free software; you can redistribute it and/or 13 + * modify it under the terms of the GNU General Public License 14 + * as published by the Free Software Foundation; either version 2 15 + * of the License, or (at your option) any later version. 16 + * 17 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 + * KIND, either express or implied. 19 + * 20 + ****************************************************************************/ 21 + 22 + #include "sys_timer.h" 23 + #include "timer.h" 24 + 25 + static int timer_prio = -1; 26 + void (*global_timer_callback)(void); 27 + int timerId; 28 + 29 + u32 _timer_callback(u32 interval, void *param){ 30 + (void)param; 31 + global_timer_callback(); 32 + return(interval); 33 + } 34 + 35 + #define cycles_to_miliseconds(cycles) \ 36 + ((int)((1000*cycles)/TIMER_FREQ)) 37 + 38 + bool timer_register(int reg_prio, void (*unregister_callback)(void), 39 + long cycles, void (*timer_callback)(void)) 40 + { 41 + (void)unregister_callback; 42 + if (reg_prio <= timer_prio || cycles == 0) 43 + return false; 44 + timer_prio=reg_prio; 45 + global_timer_callback=timer_callback; 46 + timerId=sys_add_timer(cycles_to_miliseconds(cycles), _timer_callback, 0); 47 + return true; 48 + } 49 + 50 + bool timer_set_period(long cycles) 51 + { 52 + sys_remove_timer(timerId); 53 + timerId=sys_add_timer(cycles_to_miliseconds(cycles), _timer_callback, 0); 54 + return true; 55 + } 56 + 57 + void timer_unregister(void) 58 + { 59 + sys_remove_timer(timerId); 60 + timer_prio = -1; 61 + }
+101
packaging/ctru/ctru.make
··· 1 + # __________ __ ___. 2 + # Open \______ \ ____ ____ | | _\_ |__ _______ ___ 3 + # Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 4 + # Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 5 + # Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 6 + # \/ \/ \/ \/ \/ 7 + # $Id$ 8 + # 9 + 10 + # 3ds_rules 11 + export DEVKITARM ?= /opt/devkitpro/devkitARM 12 + export DEVKITPRO ?= /opt/devkitpro 13 + 14 + PORTLIBS := $(DEVKITPRO)/portlibs/3ds 15 + 16 + CTRULIB ?= $(DEVKITPRO)/libctru 17 + 18 + export PATH := $(DEVKITPRO)/portlibs/3ds/bin:$(PATH) 19 + 20 + # base_rules 21 + export SHELL := /usr/bin/env bash 22 + 23 + DEVKITPATH=$(shell echo "$(DEVKITPRO)" | sed -e 's/^\([a-zA-Z]\):/\/\1/') 24 + 25 + export PATH := $(DEVKITPATH)/tools/bin:$(DEVKITPATH)/devkitARM/bin:$(PATH) 26 + 27 + # 3DSX 28 + VERSION_MAJOR := 1 29 + VERSION_MINOR := 0 30 + VERSION_MICRO := 0 31 + 32 + APP_TITLE := rockbox 33 + APP_DESCRIPTION := Open Source Jukebox Firmware 34 + APP_AUTHOR := rockbox.org 35 + APP_ICON := $(ROOTDIR)/packaging/ctru/res/icon.png 36 + 37 + # CIA 38 + BANNER_AUDIO := $(ROOTDIR)/packaging/ctru/res/banner.wav 39 + BANNER_IMAGE := $(ROOTDIR)/packaging/ctru/res/banner.cgfx 40 + RSF_PATH := $(ROOTDIR)/packaging/ctru/res/app.rsf 41 + #LOGO := $(ROOTDIR)/packaging/ctru/logo.lz11 42 + UNIQUE_ID := 0xCB001 43 + PRODUCT_CODE := CTR-ROCKBOX 44 + ICON_FLAGS := nosavebackups,visible 45 + 46 + # CIA Configuration 47 + USE_ON_SD := true 48 + APP_ENCRYPTED := false 49 + CATEGORY := Application 50 + USE_ON_SD := true 51 + MEMORY_TYPE := Application 52 + SYSTEM_MODE := 64MB 53 + SYSTEM_MODE_EXT := Legacy 54 + CPU_SPEED := 268MHz 55 + ENABLE_L2_CACHE := false 56 + 57 + .SECONDEXPANSION: # $$(OBJ) is not populated until after this 58 + 59 + MAKEROM ?= makerom 60 + 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)" 61 + MAKEROM_ARGS += -major $(VERSION_MAJOR) -minor $(VERSION_MINOR) -micro $(VERSION_MICRO) 62 + 63 + ifneq ($(strip $(LOGO)),) 64 + MAKEROM_ARGS += -logo "$(LOGO)" 65 + endif 66 + ifneq ($(strip $(ROMFS)),) 67 + MAKEROM_ARGS += -DAPP_ROMFS="$(ROMFS)" 68 + endif 69 + 70 + BANNERTOOL ?= bannertool 71 + 72 + ifeq ($(suffix $(BANNER_IMAGE)),.cgfx) 73 + BANNER_IMAGE_ARG := -ci 74 + else 75 + BANNER_IMAGE_ARG := -i 76 + endif 77 + 78 + ifeq ($(suffix $(BANNER_AUDIO)),.cwav) 79 + BANNER_AUDIO_ARG := -ca 80 + else 81 + BANNER_AUDIO_ARG := -a 82 + endif 83 + 84 + # main binary 85 + $(BUILDDIR)/$(BINARY): $$(OBJ) $(FIRMLIB) $(VOICESPEEXLIB) $(CORE_LIBS) 86 + ifeq ($(UNAME), Darwin) 87 + $(call PRINTS,LD $(BINARY))$(CC) -o $@ $^ $(LDOPTS) $(GLOBAL_LDOPTS) -Wl,$(LDMAP_OPT),$(BUILDDIR)/rockbox.map 88 + else 89 + $(call PRINTS,LD $(BINARY))$(CC) -o $@ -Wl,--start-group $^ -Wl,--end-group $(LDOPTS) $(GLOBAL_LDOPTS) \ 90 + -Wl,$(LDMAP_OPT),$(BUILDDIR)/rockbox-.map 91 + @mv $(BINARY) $(BINARY).elf 92 + smdhtool --create "$(APP_TITLE)" "$(APP_DESCRIPTION)" "$(APP_AUTHOR)" $(APP_ICON) "rockbox.smdh" 93 + 3dsxtool $(BINARY).elf $(BINARY).3dsx --smdh="rockbox.smdh" 94 + $(BANNERTOOL) makebanner $(BANNER_IMAGE_ARG) "$(BANNER_IMAGE)" $(BANNER_AUDIO_ARG) "$(BANNER_AUDIO)" -o "$(BUILDDIR)/banner.bnr" 95 + $(BANNERTOOL) makesmdh -s "$(APP_TITLE)" -l "$(APP_DESCRIPTION)" -p "$(APP_AUTHOR)" -i "$(APP_ICON)" -f "$(ICON_FLAGS)" -o "$(BUILDDIR)/icon.icn" 96 + $(MAKEROM) -f cia -o "$(BINARY).cia" -target t -exefslogo $(MAKEROM_ARGS) 97 + ifndef DEBUG 98 + $(SILENT)rm $(BINARY).elf 99 + endif 100 + endif 101 +
+286
packaging/ctru/res/app.rsf
··· 1 + BasicInfo: 2 + Title : $(APP_TITLE) 3 + ProductCode : $(APP_PRODUCT_CODE) 4 + Logo : Homebrew 5 + 6 + RomFs: 7 + RootPath: $(APP_ROMFS) 8 + 9 + TitleInfo: 10 + Category : $(APP_CATEGORY) 11 + UniqueId : $(APP_UNIQUE_ID) 12 + 13 + Option: 14 + UseOnSD : $(APP_USE_ON_SD) # true if App is to be installed to SD 15 + FreeProductCode : true # Removes limitations on ProductCode 16 + MediaFootPadding : false # If true CCI files are created with padding 17 + EnableCrypt : $(APP_ENCRYPTED) # Enables encryption for NCCH and CIA 18 + EnableCompress : true # Compresses where applicable (currently only exefs:/.code) 19 + 20 + AccessControlInfo: 21 + CoreVersion : 2 22 + 23 + # Exheader Format Version 24 + DescVersion : 2 25 + 26 + # Minimum Required Kernel Version (below is for 4.5.0) 27 + ReleaseKernelMajor : "02" 28 + ReleaseKernelMinor : "33" 29 + 30 + # ExtData 31 + UseExtSaveData : false # enables ExtData 32 + #ExtSaveDataId : 0x300 # only set this when the ID is different to the UniqueId 33 + 34 + # FS:USER Archive Access Permissions 35 + # Uncomment as required 36 + FileSystemAccess: 37 + - CategorySystemApplication 38 + - CategoryHardwareCheck 39 + - CategoryFileSystemTool 40 + - Debug 41 + - TwlCardBackup 42 + - TwlNandData 43 + - Boss 44 + - DirectSdmc 45 + - Core 46 + - CtrNandRo 47 + - CtrNandRw 48 + - CtrNandRoWrite 49 + - CategorySystemSettings 50 + - CardBoard 51 + - ExportImportIvs 52 + - DirectSdmcWrite 53 + - SwitchCleanup 54 + - SaveDataMove 55 + - Shop 56 + - Shell 57 + - CategoryHomeMenu 58 + - SeedDB 59 + IoAccessControl: 60 + - FsMountNand 61 + - FsMountNandRoWrite 62 + - FsMountTwln 63 + - FsMountWnand 64 + - FsMountCardSpi 65 + - UseSdif3 66 + - CreateSeed 67 + - UseCardSpi 68 + 69 + # Process Settings 70 + MemoryType : $(APP_MEMORY_TYPE) # Application/System/Base 71 + SystemMode : $(APP_SYSTEM_MODE) # 64MB(Default)/96MB/80MB/72MB/32MB 72 + IdealProcessor : 0 73 + AffinityMask : 1 74 + Priority : 16 75 + MaxCpu : 0x9E # Default 76 + HandleTableSize : 0x200 77 + DisableDebug : false 78 + EnableForceDebug : false 79 + CanWriteSharedPage : true 80 + CanUsePrivilegedPriority : false 81 + CanUseNonAlphabetAndNumber : true 82 + PermitMainFunctionArgument : true 83 + CanShareDeviceMemory : true 84 + RunnableOnSleep : false 85 + SpecialMemoryArrange : true 86 + 87 + # New3DS Exclusive Process Settings 88 + SystemModeExt : $(APP_SYSTEM_MODE_EXT) # Legacy(Default)/124MB/178MB Legacy:Use Old3DS SystemMode 89 + CpuSpeed : $(APP_CPU_SPEED) # 268MHz(Default)/804MHz 90 + EnableL2Cache : $(APP_ENABLE_L2_CACHE) # false(default)/true 91 + CanAccessCore2 : true 92 + 93 + # Virtual Address Mappings 94 + IORegisterMapping: 95 + - 1ff00000-1ff7ffff # DSP memory 96 + MemoryMapping: 97 + - 1f000000-1f5fffff:r # VRAM 98 + 99 + # Accessible SVCs, <Name>:<ID> 100 + SystemCallAccess: 101 + ControlMemory: 1 102 + QueryMemory: 2 103 + ExitProcess: 3 104 + GetProcessAffinityMask: 4 105 + SetProcessAffinityMask: 5 106 + GetProcessIdealProcessor: 6 107 + SetProcessIdealProcessor: 7 108 + CreateThread: 8 109 + ExitThread: 9 110 + SleepThread: 10 111 + GetThreadPriority: 11 112 + SetThreadPriority: 12 113 + GetThreadAffinityMask: 13 114 + SetThreadAffinityMask: 14 115 + GetThreadIdealProcessor: 15 116 + SetThreadIdealProcessor: 16 117 + GetCurrentProcessorNumber: 17 118 + Run: 18 119 + CreateMutex: 19 120 + ReleaseMutex: 20 121 + CreateSemaphore: 21 122 + ReleaseSemaphore: 22 123 + CreateEvent: 23 124 + SignalEvent: 24 125 + ClearEvent: 25 126 + CreateTimer: 26 127 + SetTimer: 27 128 + CancelTimer: 28 129 + ClearTimer: 29 130 + CreateMemoryBlock: 30 131 + MapMemoryBlock: 31 132 + UnmapMemoryBlock: 32 133 + CreateAddressArbiter: 33 134 + ArbitrateAddress: 34 135 + CloseHandle: 35 136 + WaitSynchronization1: 36 137 + WaitSynchronizationN: 37 138 + SignalAndWait: 38 139 + DuplicateHandle: 39 140 + GetSystemTick: 40 141 + GetHandleInfo: 41 142 + GetSystemInfo: 42 143 + GetProcessInfo: 43 144 + GetThreadInfo: 44 145 + ConnectToPort: 45 146 + SendSyncRequest1: 46 147 + SendSyncRequest2: 47 148 + SendSyncRequest3: 48 149 + SendSyncRequest4: 49 150 + SendSyncRequest: 50 151 + OpenProcess: 51 152 + OpenThread: 52 153 + GetProcessId: 53 154 + GetProcessIdOfThread: 54 155 + GetThreadId: 55 156 + GetResourceLimit: 56 157 + GetResourceLimitLimitValues: 57 158 + GetResourceLimitCurrentValues: 58 159 + GetThreadContext: 59 160 + Break: 60 161 + OutputDebugString: 61 162 + ControlPerformanceCounter: 62 163 + CreatePort: 71 164 + CreateSessionToPort: 72 165 + CreateSession: 73 166 + AcceptSession: 74 167 + ReplyAndReceive1: 75 168 + ReplyAndReceive2: 76 169 + ReplyAndReceive3: 77 170 + ReplyAndReceive4: 78 171 + ReplyAndReceive: 79 172 + BindInterrupt: 80 173 + UnbindInterrupt: 81 174 + InvalidateProcessDataCache: 82 175 + StoreProcessDataCache: 83 176 + FlushProcessDataCache: 84 177 + StartInterProcessDma: 85 178 + StopDma: 86 179 + GetDmaState: 87 180 + RestartDma: 88 181 + DebugActiveProcess: 96 182 + BreakDebugProcess: 97 183 + TerminateDebugProcess: 98 184 + GetProcessDebugEvent: 99 185 + ContinueDebugEvent: 100 186 + GetProcessList: 101 187 + GetThreadList: 102 188 + GetDebugThreadContext: 103 189 + SetDebugThreadContext: 104 190 + QueryDebugProcessMemory: 105 191 + ReadProcessMemory: 106 192 + WriteProcessMemory: 107 193 + SetHardwareBreakPoint: 108 194 + GetDebugThreadParam: 109 195 + ControlProcessMemory: 112 196 + MapProcessMemory: 113 197 + UnmapProcessMemory: 114 198 + CreateCodeSet: 115 199 + CreateProcess: 117 200 + TerminateProcess: 118 201 + SetProcessResourceLimits: 119 202 + CreateResourceLimit: 120 203 + SetResourceLimitValues: 121 204 + AddCodeSegment: 122 205 + Backdoor: 123 206 + KernelSetState: 124 207 + QueryProcessMemory: 125 208 + 209 + # Service List 210 + # Maximum 34 services (32 if firmware is prior to 9.6.0) 211 + ServiceAccessControl: 212 + - APT:U 213 + - ac:u 214 + - am:net 215 + - boss:U 216 + - cam:u 217 + - cecd:u 218 + - cfg:nor 219 + - cfg:u 220 + - csnd:SND 221 + - dsp::DSP 222 + - frd:u 223 + - fs:USER 224 + - gsp::Gpu 225 + - gsp::Lcd 226 + - hid:USER 227 + - http:C 228 + - ir:rst 229 + - ir:u 230 + - ir:USER 231 + - mic:u 232 + - mcu::HWC 233 + - ndm:u 234 + - news:s 235 + - nwm::EXT 236 + - nwm::UDS 237 + - ptm:sysm 238 + - ptm:u 239 + - pxi:dev 240 + - soc:U 241 + - ssl:C 242 + - y2r:u 243 + 244 + 245 + SystemControlInfo: 246 + SaveDataSize: 0KB # Change if the app uses savedata 247 + RemasterVersion: $(APP_VERSION_MAJOR) 248 + StackSize: 0x40000 249 + 250 + # Modules that run services listed above should be included below 251 + # Maximum 48 dependencies 252 + # <module name>:<module titleid> 253 + Dependency: 254 + ac: 0x0004013000002402 255 + #act: 0x0004013000003802 256 + am: 0x0004013000001502 257 + boss: 0x0004013000003402 258 + camera: 0x0004013000001602 259 + cecd: 0x0004013000002602 260 + cfg: 0x0004013000001702 261 + codec: 0x0004013000001802 262 + csnd: 0x0004013000002702 263 + dlp: 0x0004013000002802 264 + dsp: 0x0004013000001a02 265 + friends: 0x0004013000003202 266 + gpio: 0x0004013000001b02 267 + gsp: 0x0004013000001c02 268 + hid: 0x0004013000001d02 269 + http: 0x0004013000002902 270 + i2c: 0x0004013000001e02 271 + ir: 0x0004013000003302 272 + mcu: 0x0004013000001f02 273 + mic: 0x0004013000002002 274 + ndm: 0x0004013000002b02 275 + news: 0x0004013000003502 276 + #nfc: 0x0004013000004002 277 + nim: 0x0004013000002c02 278 + nwm: 0x0004013000002d02 279 + pdn: 0x0004013000002102 280 + ps: 0x0004013000003102 281 + ptm: 0x0004013000002202 282 + #qtm: 0x0004013020004202 283 + ro: 0x0004013000003702 284 + socket: 0x0004013000002e02 285 + spi: 0x0004013000002302 286 + ssl: 0x0004013000002f02
packaging/ctru/res/banner.cgfx

This is a binary file and will not be displayed.

packaging/ctru/res/banner.png

This is a binary file and will not be displayed.

packaging/ctru/res/banner.wav

This is a binary file and will not be displayed.

packaging/ctru/res/banner_.cgfx

This is a binary file and will not be displayed.

packaging/ctru/res/icon.png

This is a binary file and will not be displayed.

packaging/ctru/res/icon_yellow.png

This is a binary file and will not be displayed.

+7
packaging/ctru/rockbox.xml
··· 1 + <shortcut> 2 + <executable>/3ds/.rockbox/rockbox.3dsx</executable> 3 + <icon>.rockbox/icon.icn</icon> 4 + <name>rockbox</name> 5 + <description>Open Source Jukebox Firmware</description> 6 + <author>rockbox.org</author> 7 + </shortcut>