From c311874da007dc60d9aa8c0dd563a1ab99883720 Mon Sep 17 00:00:00 2001
From: Christopher Bonhage <me@christopherbonhage.com>
Date: Sun, 9 Feb 2025 12:08:46 -0500
Subject: [PATCH] Hide Windows/macOS cruft in file browser (#217)

<!--- Provide a general summary of your changes in the Title above -->

## Description
Hides:
* `desktop.ini` - Windows Explorer directory settings
* `Thumbs.db` - Windows Explorer image thumbnails
* `.DS_Store` - macOS Finder directory settings
* `._`-prefixed files - macOS "AppleDouble" metadata

## Motivation and Context
[N64brew sc64-forum post: File list shows 4096 B "Dummy
Files"?](https://discord.com/channels/205520502922543113/1337028964606283806)

## How Has This Been Tested?
On SC64 with an SD card containing all of the offending files in the
root and in subdirectories.

## Screenshots
Tough to prove a negative here with a screenshot; here's a video:


https://github.com/user-attachments/assets/f1843b29-dee0-442f-bf9a-897a69c39169

## Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->
- [X] Improvement (non-breaking change that adds a new feature)
- [X] Bug fix (fixes an issue)
- [ ] Breaking change (breaking change)
- [ ] Documentation Improvement
- [ ] Config and build (change in the configuration and build system,
has no impact on code or features)

## Checklist:
<!--- Go over all the following points, and put an `x` in all the boxes
that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're
here to help! -->
- [X] My code follows the code style of this project.
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [ ] I have added tests to cover my changes.
- [ ] All new and existing tests passed.

<!--- It would be nice if you could sign off your contribution by
replacing the name with your GitHub user name and GitHub email contact.
-->
Signed-off-by: meeq <me@christopherbonhage.com>

Co-authored-by: Robin Jones <networkfusion@users.noreply.github.com>
---
 .gitignore               |  5 +++-
 src/menu/views/browser.c | 55 ++++++++++++++++++++++++++++++++++------
 src/utils/fs.c           |  4 +++
 src/utils/fs.h           |  1 +
 4 files changed, 56 insertions(+), 9 deletions(-)

diff --git a/.gitignore b/.gitignore
index dcc08de7..3ccc4cee 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,4 +16,7 @@
 # Ignore any N64 ROM
 **/*.n64
 **/*.v64
-**/*.z64
\ No newline at end of file
+**/*.z64
+
+# Ignore macOS filesystem cruft
+.DS_Store
diff --git a/src/menu/views/browser.c b/src/menu/views/browser.c
index 21c4f76a..37c61dc7 100644
--- a/src/menu/views/browser.c
+++ b/src/menu/views/browser.c
@@ -36,6 +36,52 @@ static const char *hidden_paths[] = {
     NULL,
 };
 
+struct substr { const char *str; size_t len; };
+#define substr(str) ((struct substr){ str, sizeof(str) - 1 })
+
+static const struct substr hidden_basenames[] = {
+    substr("desktop.ini"), // Windows Explorer settings
+    substr("Thumbs.db"),   // Windows Explorer thumbnails
+    substr(".DS_Store"),   // macOS Finder settings
+};
+#define HIDDEN_BASENAMES_COUNT (sizeof(hidden_basenames) / sizeof(hidden_basenames[0]))
+
+static const struct substr hidden_prefixes[] = {
+    substr("._"), // macOS "AppleDouble" metadata files
+};
+#define HIDDEN_PREFIXES_COUNT (sizeof(hidden_prefixes) / sizeof(hidden_prefixes[0]))
+
+
+static bool path_is_hidden (path_t *path) {
+    char *stripped_path = strip_fs_prefix(path_get(path));
+
+    // Check for hidden files based on full path
+    for (int i = 0; hidden_paths[i] != NULL; i++) {
+        if (strcmp(stripped_path, hidden_paths[i]) == 0) {
+            return true;
+        }
+    }
+
+    char *basename = file_basename(stripped_path);
+    int basename_len = strlen(basename);
+
+    // Check for hidden files based on filename
+    for (int i = 0; i < HIDDEN_BASENAMES_COUNT; i++) {
+        if (basename_len == hidden_basenames[i].len &&
+            strncmp(basename, hidden_basenames[i].str, hidden_basenames[i].len) == 0) {
+            return true;
+        }
+    }
+    // Check for hidden files based on filename prefix
+    for (int i = 0; i < HIDDEN_PREFIXES_COUNT; i++) {
+        if (basename_len > hidden_prefixes[i].len &&
+            strncmp(basename, hidden_prefixes[i].str, hidden_prefixes[i].len) == 0) {
+            return true;
+        }
+    }
+
+    return false;
+}
 
 static int compare_entry (const void *pa, const void *pb) {
     entry_t *a = (entry_t *) (pa);
@@ -108,14 +154,7 @@ static bool load_directory (menu_t *menu) {
 
         if (!menu->settings.show_protected_entries) {
             path_push(path, info.d_name);
-
-            for (int i = 0; hidden_paths[i] != NULL; i++) {
-                if (strcmp(strip_fs_prefix(path_get(path)), hidden_paths[i]) == 0) {
-                    hide = true;
-                    break;
-                }
-            }
-
+            hide = path_is_hidden(path);
             path_pop(path);
         }
 
diff --git a/src/utils/fs.c b/src/utils/fs.c
index b02b2e2c..607e2025 100644
--- a/src/utils/fs.c
+++ b/src/utils/fs.c
@@ -17,6 +17,10 @@ char *strip_fs_prefix (char *path) {
     return path;
 }
 
+char *file_basename (char *path) {
+    char *base = strrchr(path, '/');
+    return base ? base + 1 : path;
+}
 
 bool file_exists (char *path) {
     struct stat st;
diff --git a/src/utils/fs.h b/src/utils/fs.h
index 88f81a7a..ce342a33 100644
--- a/src/utils/fs.h
+++ b/src/utils/fs.h
@@ -11,6 +11,7 @@
 
 
 char *strip_fs_prefix (char *path);
+char *file_basename (char *path);
 
 bool file_exists (char *path);
 int64_t file_get_size (char *path);