new: added some server
Some checks failed
Build (3DS) / build (push) Failing after 2m14s

This commit is contained in:
2025-12-07 01:21:43 +00:00
committed by GitHub
parent 53322f8767
commit e833b79458
5 changed files with 1889 additions and 1 deletions

2
.gitignore vendored
View File

@@ -1,6 +1,6 @@
build
output
server
node_modules
# Object files
*.o

57
server/README.md Normal file
View File

@@ -0,0 +1,57 @@
# mice Download Server
Simple Node.js server to host mice 3DS homebrew files for easy download via QR code.
## Quick Start
1. **Install dependencies:**
```bash
cd server
npm install
```
2. **Build mice first:****
```bash
cd ..
make
```
3. **Start the server:**
```bash
cd server
npm start
```
4. **Access the download page:**
- Open browser to `http://localhost:3000`
- Use QR codes to download on 3DS browser
- Or find your local IP and access from any device on the network
## Features
- 📱 QR codes for easy 3DS browser access
- 📦 Automatic detection of build files (.3dsx, .cia, .3ds, .elf)
- 🎮 Installation instructions included
- 📊 File size information
- 🌐 Network accessible from any device
## Usage on 3DS
1. Connect your 3DS to the same WiFi network as your computer
2. Open the Internet Browser on your 3DS
3. Scan the QR code or manually enter the URL
4. Download the .3dsx or .cia file
5. Install according to the instructions on the page
## Development
```bash
npm run dev # Uses nodemon for auto-restart
```
## File Types
- **.3dsx** - Homebrew Launcher format (copy to `/3ds/` folder)
- **.cia** - Installable format (use FBI or similar installer)
- **.3ds** - 3DS ROM format
- **.elf** - Debug/development format

1465
server/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

20
server/package.json Normal file
View File

@@ -0,0 +1,20 @@
{
"name": "mice-server",
"version": "1.0.0",
"description": "Simple file server for mice 3DS homebrew downloads",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
},
"dependencies": {
"express": "^4.18.2",
"qrcode": "^1.5.3"
},
"devDependencies": {
"nodemon": "^3.0.2"
},
"keywords": ["3ds", "homebrew", "file-server"],
"author": "3DS Dev",
"license": "MIT"
}

346
server/server.js Normal file
View File

@@ -0,0 +1,346 @@
const express = require('express');
const path = require('path');
const fs = require('fs');
const QRCode = require('qrcode');
const app = express();
const PORT = process.env.PORT || 3000;
// Function to read current version from main.h
function getCurrentVersion() {
try {
const mainHPath = path.join(__dirname, '../include/main.h');
const content = fs.readFileSync(mainHPath, 'utf8');
const versionMatch = content.match(/#define\s+MICE_VERSION\s+"([^"]+)"/);
return versionMatch ? versionMatch[1] : 'unknown';
} catch (error) {
console.log('Could not read version:', error.message);
return 'unknown';
}
}
// Function to get build timestamp
function getBuildTimestamp() {
try {
const outputDir = path.join(__dirname, '../output');
if (fs.existsSync(outputDir)) {
const stats = fs.statSync(outputDir);
return stats.mtime.toLocaleString();
}
} catch (error) {
console.log('Could not read build timestamp:', error.message);
}
return 'unknown';
}
// Serve static files from the output directory
app.use('/downloads', express.static(path.join(__dirname, '../output')));
// Basic styling for the web interface with dark mode support
const CSS = `
<style>
:root {
--bg-color: #f5f5f5;
--container-bg: white;
--text-color: #333;
--border-color: #ddd;
--section-bg: #fafafa;
--accent-color: #0066cc;
--button-hover: #0052a3;
}
@media (prefers-color-scheme: dark) {
:root {
--bg-color: #1a1a1a;
--container-bg: #2d2d2d;
--text-color: #e0e0e0;
--border-color: #555;
--section-bg: #3a3a3a;
--accent-color: #4da6ff;
--button-hover: #3d8bff;
}
}
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: var(--bg-color);
color: var(--text-color);
transition: background-color 0.3s, color 0.3s;
}
.container {
background: var(--container-bg);
padding: 30px;
border-radius: 10px;
box-shadow: 0 2px 20px rgba(0,0,0,0.3);
transition: background-color 0.3s;
}
h1 {
color: var(--text-color);
text-align: center;
border-bottom: 3px solid var(--accent-color);
padding-bottom: 10px;
}
.version-info {
text-align: center;
margin: 10px 0 20px 0;
padding: 10px;
background: var(--section-bg);
border-radius: 5px;
font-size: 14px;
color: var(--text-color);
}
.download-section {
margin: 30px 0;
padding: 20px;
border: 2px solid var(--border-color);
border-radius: 8px;
background-color: var(--section-bg);
transition: background-color 0.3s;
}
.download-link {
display: inline-block;
padding: 12px 24px;
background-color: var(--accent-color);
color: white;
text-decoration: none;
border-radius: 5px;
margin: 10px 10px 10px 0;
font-weight: bold;
transition: background-color 0.3s;
}
.download-link:hover {
background-color: var(--button-hover);
}
.download-link:hover {
background-color: #0052a3;
}
.qr-code {
text-align: center;
margin: 20px 0;
}
.file-info {
background-color: #e8f4fd;
padding: 15px;
border-left: 4px solid #0066cc;
margin: 15px 0;
}
.installation-guide {
background-color: #fff8dc;
padding: 20px;
border-radius: 8px;
margin: 20px 0;
}
@media (prefers-color-scheme: dark) {
.file-info {
background-color: #1a2940;
border-left-color: #2d5aa0;
}
.installation-guide {
background-color: #2a3b5c;
}
}
</style>`;
// Main page
app.get('/', async (req, res) => {
const outputDir = path.join(__dirname, '../output');
// Use the client's actual request URL
const protocol = req.get('x-forwarded-proto') || req.protocol || 'http';
const host = req.get('x-forwarded-host') || req.get('host');
const baseUrl = `${protocol}://${host}`;
// Check if build files exist
const files = [];
const fileExtensions = ['.3dsx', '.cia', '.3ds', '.elf'];
try {
const items = fs.readdirSync(outputDir, { recursive: true });
for (const item of items) {
const itemPath = path.join(outputDir, item);
const stats = fs.statSync(itemPath);
if (stats.isFile()) {
const ext = path.extname(item).toLowerCase();
if (fileExtensions.includes(ext)) {
const filePath = `/downloads/${item}`;
const fileUrl = `${baseUrl}${filePath}`;
files.push({
name: item,
path: filePath,
url: fileUrl,
size: (stats.size / 1024).toFixed(1), // KB
type: ext.substring(1).toUpperCase()
});
}
}
}
} catch (err) {
console.error('Error reading output directory:', err);
}
let html = `
<!DOCTYPE html>
<html>
<head>
<title>mice - 3DS Music Player Downloads</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="refresh" content="30">
${CSS}
<script>
// Auto-refresh every 30 seconds to check for new builds
setInterval(() => {
fetch('/api/version')
.then(response => response.json())
.then(data => {
const versionElement = document.getElementById('current-version');
const buildElement = document.getElementById('build-time');
if (versionElement) versionElement.textContent = data.version;
if (buildElement) buildElement.textContent = data.buildTime;
})
.catch(console.error);
}, 30000);
</script>
</head>
<body>
<div class="container">
<h1>🎵 mice - 3DS Music Player 🎵</h1>
<div class="version-info">
<strong>Current Version:</strong> <span id="current-version">${getCurrentVersion()}</span><br>
<strong>Last Build:</strong> <span id="build-time">${getBuildTimestamp()}</span>
</div>
<div class="installation-guide">
<h3>📱 Installation Instructions:</h3>
<p><strong>For .3dsx files (Homebrew Launcher):</strong></p>
<ul>
<li>Copy the .3dsx file to <code>/3ds/</code> folder on your SD card</li>
<li>Launch via Homebrew Launcher</li>
</ul>
<p><strong>For .cia files (installed to HOME menu):</strong></p>
<ul>
<li>Install using FBI, DevMenu, or similar CIA installer</li>
<li>Appears on HOME menu after installation</li>
</ul>
</div>
`;
if (files.length === 0) {
html += `
<div class="file-info">
<h3>⚠️ No build files found</h3>
<p>Please run <code>make</code> in the mice directory to build the project first.</p>
</div>
`;
} else {
html += `<h2>📦 Available Downloads:</h2>`;
for (const file of files) {
// Generate QR code for each file
let qrCodeDataUrl = '';
try {
qrCodeDataUrl = await QRCode.toDataURL(file.url);
} catch (err) {
console.error('Error generating QR code:', err);
}
html += `
<div class="download-section">
<h3>🎮 ${file.name}</h3>
<div class="file-info">
<strong>Type:</strong> ${file.type} &nbsp;&nbsp;
<strong>Size:</strong> ${file.size} KB
</div>
<a href="${file.path}" class="download-link" download>
📥 Download ${file.type}
</a>
${qrCodeDataUrl ? `
<div class="qr-code">
<p><strong>📱 Scan QR Code for 3DS Browser:</strong></p>
<img src="${qrCodeDataUrl}" alt="QR Code for ${file.name}" style="border: 2px solid #ddd; padding: 10px; background: white;">
<br>
<small style="color: #666; word-break: break-all;">${file.url}</small>
</div>
` : ''}
</div>
`;
}
}
html += `
<div class="file-info">
<h3>🎵 Features:</h3>
<ul>
<li>Support for MP3, FLAC, OGG Vorbis, Opus, WAV, and SID formats</li>
<li>Metadata display (artist, album, title) on top screen</li>
<li>File browser with folder navigation</li>
<li>Playback controls and sleep mode support</li>
</ul>
</div>
<div class="installation-guide">
<h3>🔗 Access Information:</h3>
<p>Server running at: <strong>${baseUrl}</strong></p>
${baseUrl.includes('github.dev') ?
'<p>✅ GitHub Codespaces detected - URL is ready for 3DS browser!</p>' :
'<p>💡 For 3DS access, use your local network IP address</p>'
}
</div>
</div>
</body>
</html>
`;
res.send(html);
});
// API endpoint for version info
app.get('/api/version', (req, res) => {
res.json({
version: getCurrentVersion(),
buildTime: getBuildTimestamp(),
timestamp: new Date().toISOString()
});
});
// Health check endpoint
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
// Start server
app.listen(PORT, '0.0.0.0', () => {
console.log(`🎵 mice Download Server`);
// Detect if we're in GitHub Codespaces
const codespace = process.env.CODESPACE_NAME;
if (codespace) {
const codespacesUrl = `https://${codespace}-${PORT}.app.github.dev/`;
console.log(`🌐 GitHub Codespaces URL: ${codespacesUrl}`);
console.log(`📱 Use this URL for 3DS browser access!`);
} else {
console.log(`🌐 Server running at: http://localhost:${PORT}`);
console.log(`📱 Access from 3DS browser using your local IP`);
}
console.log(`📦 Serving files from: ${path.join(__dirname, '../output')}`);
console.log(`\n🔗 To find your local IP address:`);
console.log(` - Windows: ipconfig`);
console.log(` - Mac/Linux: ifconfig or ip addr show`);
});
// Graceful shutdown
process.on('SIGTERM', () => {
console.log('🛑 Server shutting down...');
process.exit(0);
});
module.exports = app;