5 Commits

Author SHA1 Message Date
54a6dfddad Merge branch 'master' of git.sillyangel.dev:angel/mice-3ds
All checks were successful
Build (3DS) / build (push) Successful in 2m16s
2026-02-01 16:41:12 -06:00
e3683a12b7 feat: update build tasks, enhance metadata handling, and improve GUI text display 2026-02-01 16:39:05 -06:00
877e40b073 Update Makefile.linux
All checks were successful
Build (3DS) / build (push) Successful in 2m6s
Signed-off-by: angel <angel@sillyangel.dev>
2025-12-09 09:11:28 -06:00
88026628ea fix: url
Some checks failed
Build (3DS) / build (push) Failing after 57s
2025-12-07 16:49:58 -06:00
87669c74a5 feat: add unistore configuration and spritesheet assets for mice music player
Some checks failed
Build (3DS) / build (push) Has been cancelled
2025-12-07 16:49:20 -06:00
12 changed files with 268 additions and 9 deletions

67
.vscode/tasks.json vendored
View File

@@ -1,6 +1,24 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Clean build artifacts",
"type": "shell",
"command": "rm",
"args": [
"-rf",
"${workspaceFolder}/build/*",
"${workspaceFolder}/output/3ds-arm/*"
],
"options": {
"cwd": "${workspaceFolder}"
},
"problemMatcher": [],
"presentation": {
"reveal": "always",
"panel": "shared"
}
},
{
"label": "Build 3DS ROM",
"type": "shell",
@@ -12,10 +30,59 @@
"problemMatcher": [
"$gcc"
],
"presentation": {
"reveal": "always",
"panel": "shared"
},
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "Build (full)",
"type": "shell",
"command": "make",
"args": [],
"options": {
"cwd": "${workspaceFolder}"
},
"problemMatcher": [
"$gcc"
],
"presentation": {
"reveal": "always",
"panel": "shared"
},
"group": "build"
},
{
"label": "Serve (dev)",
"type": "shell",
"command": "npm",
"args": [ "run", "dev" ],
"options": {
"cwd": "${workspaceFolder}/server"
},
"isBackground": true,
"presentation": {
"echo": true,
"reveal": "always",
"panel": "dedicated"
}
},
{
"label": "Serve (prod)",
"type": "shell",
"command": "npm",
"args": [ "start" ],
"options": {
"cwd": "${workspaceFolder}/server"
},
"presentation": {
"reveal": "always",
"panel": "shared"
}
}
]
}

View File

@@ -15,6 +15,8 @@ else
HOST_ARCH := x86_64
else ifeq ($(UNAME_M),$(filter $(UNAME_M),i386 i686))
HOST_ARCH := i686
else ifeq ($(UNAME_M),$(filter $(UNAME_M),aarch64 arm64))
HOST_ARCH := arm64
else
$(error "Unsupported host architecture.")
endif

View File

@@ -13,7 +13,7 @@
#define mice_main_h
/* Application version */
#define MICE_VERSION "dev63"
#define MICE_VERSION "dev68"
/* Default folder */
#define DEFAULT_DIR "sdmc:/"

View File

@@ -13,8 +13,6 @@ struct metadata_t
char title[METADATA_TITLE_MAX];
char artist[METADATA_ARTIST_MAX];
char album[METADATA_ALBUM_MAX];
};
/**

View File

@@ -1,4 +1,4 @@
## audio.wav
Ctrmus uses a modified version of [Rad Adventure by Scott Holmes](http://freemusicarchive.org/music/Scott_Holmes/~/Rad_Adventure) of which is licensed under a [Attribution-NonCommercial License](https://creativecommons.org/licenses/by-nc/4.0/).
Permission for use in ctrmus is granted.
Mice uses [Internal Monologues by Jon Shuemaker](https://freemusicarchive.org/music/jon-shuemaker/anoka-vol2/internal-monologues/) of which is licensed under a [Attribution-NonCommercial 4.0 International License.](https://creativecommons.org/licenses/by-nc/4.0/).
Permission for use in mice is granted.

Binary file not shown.

View File

@@ -83,6 +83,12 @@ void guiClearBottomScreen(void)
/**
* Draw a simple text string at specified position
* @param screen Screen to draw on (GFX_TOP or GFX_BOTTOM)
* @param x X position
* @param y Y position
* @param text Text string to draw
* @param color Text color
* @param scale Text scale
*/
void guiDrawText(gfxScreen_t screen, float x, float y, const char* text, u32 color, float scale)
{
@@ -101,6 +107,8 @@ void guiDrawText(gfxScreen_t screen, float x, float y, const char* text, u32 col
/**
* Display metadata on the top screen
* @param metadata Pointer to metadata structure to display
* @param filename Filename to display if no title is available
*/
void guiDisplayMetadata(struct metadata_t* metadata, const char* filename)
{
@@ -185,6 +193,9 @@ void guiDisplayMetadata(struct metadata_t* metadata, const char* filename)
/**
* Display log messages on the top screen
* @param messages Array of log message strings
* @param count Number of messages in the array
* @param scroll Index of first visible message for scrolling
*/
void guiDisplayLog(const char** messages, int count, int scroll)
{
@@ -215,6 +226,10 @@ void guiDisplayLog(const char** messages, int count, int scroll)
/**
* Display file list on the bottom screen
* @param files Array of file/folder names
* @param count Number of entries in the array
* @param selected Index of currently selected entry
* @param scroll Index of first visible entry for scrolling
*/
void guiDisplayFileList(const char** files, int count, int selected, int scroll)
{
@@ -269,6 +284,10 @@ void guiDisplayFileList(const char** files, int count, int selected, int scroll)
/**
* Display playback controls and status on the top screen
* @param isPlaying Whether playback is active
* @param isPaused Whether playback is paused
* @param position Current playback position in seconds
* @param duration Total duration in seconds
*/
void guiDisplayPlaybackStatus(bool isPlaying, bool isPaused, float position, float duration)
{
@@ -319,6 +338,7 @@ void guiDisplayPlaybackStatus(bool isPlaying, bool isPaused, float position, flo
/**
* Display version text and credits at bottom of bottom screen
* @param version Version string to display
*/
void guiDisplayVersion(const char* version)
{
@@ -331,16 +351,20 @@ void guiDisplayVersion(const char* version)
C2D_TextBufClear(textBuf);
/* Display "mice - by sillyangel" at bottom center */
const char* credits = "mice - by sillyangel";
char credits[64];
if(version && version[0])
snprintf(credits, sizeof(credits), "mice %s - by sillyangel", version);
else
snprintf(credits, sizeof(credits), "mice - by sillyangel");
C2D_TextParse(&text, textBuf, credits);
C2D_TextOptimize(&text);
C2D_DrawText(&text, C2D_WithColor, 80.0f, 220.0f, 0.5f, 0.45f, 0.45f, GUI_COLOR_TEXT_DIM);
}
/**
/**
* Display progress bar on top screen
* Display progress bar on the top screen
* @param position Current playback position in seconds
* @param duration Total duration in seconds
*/
void guiDisplayProgressBar(float position, float duration)
{
@@ -372,6 +396,7 @@ void guiDisplayProgressBar(float position, float duration)
/**
* Display current directory path on bottom screen
* @param path Current directory path
*/
void guiDisplayCurrentPath(const char* path)
{

View File

@@ -11,6 +11,7 @@
#include <dirent.h>
#include <stdbool.h>
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
@@ -146,6 +147,125 @@ static int cmpstringp(const void *p1, const void *p2)
return strcasecmp(* (char * const *) p1, * (char * const *) p2);
}
/* Check filename extension against supported audio types. Case-insensitive. */
static bool isMusicFilename(const char *name)
{
if(name == NULL)
return false;
const char *ext = strrchr(name, '.');
if(ext == NULL || *(ext + 1) == '\0')
return false;
ext++; /* skip dot */
char lext[16];
size_t i = 0;
for(; i < sizeof(lext)-1 && ext[i]; i++)
lext[i] = tolower((unsigned char)ext[i]);
lext[i] = '\0';
const char *allowed[] = {"mp3","wav","flac","ogg","opus","sid","m4a","aac", NULL};
for(int j = 0; allowed[j] != NULL; j++)
if(strcmp(lext, allowed[j]) == 0)
return true;
return false;
}
/* Build a list of music filenames (basenames) in the given directory. Caller must free array and each string. */
static int buildMusicFileListInDir(const char *dirpath, char ***outFiles)
{
DIR *dp;
struct dirent *ep;
char **files = NULL;
int fileNum = 0;
if((dp = opendir(dirpath)) == NULL)
return -1;
while((ep = readdir(dp)) != NULL)
{
if(ep->d_type == DT_DIR)
continue;
if(!isMusicFilename(ep->d_name))
continue;
files = realloc(files, (fileNum + 1) * sizeof(char*));
files[fileNum] = strdup(ep->d_name);
fileNum++;
}
if(fileNum > 0)
qsort(files, fileNum, sizeof(char*), cmpstringp);
closedir(dp);
*outFiles = files;
return fileNum;
}
/* Play next file in the same folder as the currently-playing file. Returns 0 on success, -1 on failure or no-next. */
static int playNextFromPath(struct playbackInfo_t *playbackInfo)
{
if(playbackInfo == NULL || playbackInfo->file[0] == '\0')
return -1;
char fullcpy[PATH_MAX];
strncpy(fullcpy, playbackInfo->file, sizeof(fullcpy));
fullcpy[sizeof(fullcpy)-1] = '\0';
/* find last slash */
char *slash = strrchr(fullcpy, '/');
char dirpath[PATH_MAX];
char *basename = NULL;
if(slash == NULL)
{
/* No directory component; assume current dir */
if(getcwd(dirpath, sizeof(dirpath)) == NULL)
return -1;
basename = fullcpy;
}
else
{
size_t dirlen = slash - fullcpy;
if(dirlen >= sizeof(dirpath))
return -1;
memcpy(dirpath, fullcpy, dirlen);
dirpath[dirlen] = '\0';
basename = slash + 1;
}
char **files = NULL;
int count = buildMusicFileListInDir(dirpath, &files);
if(count <= 0)
return -1;
int index = -1;
for(int i = 0; i < count; i++)
{
if(strcmp(files[i], basename) == 0)
{
index = i;
break;
}
}
int result = -1;
if(index >= 0 && index + 1 < count)
{
char nextpath[PATH_MAX];
snprintf(nextpath, sizeof(nextpath), "%s/%s", dirpath, files[index+1]);
result = changeFile(nextpath, playbackInfo);
}
for(int i = 0; i < count; i++)
free(files[i]);
free(files);
return result;
}
/**
* Store the list of files and folders in current directory to an array.
*/

44
unistore/mice.unistore Normal file
View File

@@ -0,0 +1,44 @@
{
"storeInfo": {
"title": "mice unistore",
"author": "sillyangel",
"description": "A music player for the Nintendo 3DS",
"url": "https://git.sillyangel.dev/angel/mice-3ds/raw/branch/master/unistore/mice.unistore",
"file": "mice.unistore",
"sheetURL": "https://git.sillyangel.dev/angel/mice-3ds/raw/branch/master/unistore/spritesheet/sheet.t3x",
"sheet": "sheet.t3x",
"version": 4,
"revision": 0
},
"storeContent": [
{
"info": {
"title": "mice",
"author": "sillyangel",
"description": "A music player for the Nintendo 3DS. Plays PCM WAV, AIFF, FLAC, Opus, Vorbis and MP3 files.",
"category": ["utility", "music"],
"console": ["3DS"],
"icon_index": 0,
"sheet_index": 0,
"last_updated": "2025-12-07 at 00:00 (UTC)",
"license": "gpl-3.0",
"version": "dev63"
}
"mice.cia": [
{
"type": "downloadFile",
"file": "hhttps://git.sillyangel.dev/angel/mice-3ds/releases/download/latest/mice.cia",
"output": "sdmc:/mice.cia"
},
{
"type": "installCia",
"file": "/mice.cia"
},
{
"type": "deleteFile",
"file": "sdmc:/mice.cia"
}
],
}
]
}

BIN
unistore/spritesheet/0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -0,0 +1,3 @@
--atlas -f rgba -z auto
0.png

Binary file not shown.