596 lines
18 KiB
Markdown
596 lines
18 KiB
Markdown
# 🎮 ESP32-S3 GameBoy Emulator
|
||
|
||
**Hochoptimierter GameBoy/GameBoy Color Emulator für Waveshare ESP32-S3-Touch-LCD-2**
|
||
|
||
---
|
||
|
||
## 📋 Projekt-Übersicht
|
||
|
||
Dieses Projekt ist ein vollständig funktionsfähiger GameBoy Emulator, der auf dem **Waveshare ESP32-S3-Touch-LCD-2** Board läuft. Es nutzt den **Peanut-GB** Emulator-Core und erreicht **90-111 FPS** bei vielen Spielen - schneller als der Original GameBoy (59.73 FPS)!
|
||
|
||
### ✨ Fertige Features
|
||
|
||
- ✅ **GameBoy Emulation** (Peanut-GB Core)
|
||
- ✅ **ST7789 Display** (2.0", 320x240, 80 MHz SPI)
|
||
- ✅ **Perfekter 4-Kanal Audio** (I2S MAX98357A, 32768 Hz)
|
||
- ✅ **SD Card ROM Loading** (FAT32, .gb Dateien)
|
||
- ✅ **PSRAM Optimization** (8MB Octal Mode, Double-Buffering)
|
||
- ✅ **Dynamisches Display-Scaling** (1.0x bis 1.67x konfigurierbar)
|
||
- ✅ **90-111 FPS Performance** (besser als Original!)
|
||
|
||
### 🚧 Geplante Features
|
||
|
||
- ⏳ **8 GameBoy Buttons** (GPIO 8-14, 21 - Hardware fertig, Software TODO)
|
||
- ⏳ **NFC ROM-Auswahl** (PN532 I2C - Hardware fertig, Software TODO)
|
||
- ⏳ **Potentiometer Controls** (Volume & Brightness - Hardware fertig, Software TODO)
|
||
- ⏳ **Link Cable 2-Player** (GPIO 2, 15, 17 - Hardware fertig, Software TODO)
|
||
|
||
---
|
||
|
||
## 🏗️ Programmablauf & Architektur
|
||
|
||
### Systemarchitektur
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ ESP32-S3 Dual Core │
|
||
├──────────────────────────────┬──────────────────────────────┤
|
||
│ CORE 1 │ CORE 0 │
|
||
│ (Emulation Task) │ (Display Task) │
|
||
│ │ │
|
||
│ ┌────────────────────┐ │ ┌────────────────────┐ │
|
||
│ │ GameBoy Emulator │ │ │ Display Rendering │ │
|
||
│ │ (Peanut-GB) │ │ │ (ST7789 SPI) │ │
|
||
│ │ │ │ │ │ │
|
||
│ │ - CPU Emulation │ │ │ - Byte Swapping │ │
|
||
│ │ - PPU Rendering │ │ │ - SPI Transfer │ │
|
||
│ │ - APU Audio │◄─────┼──┤ - Compact Buffer │ │
|
||
│ │ - Memory Map │ │ │ │ │
|
||
│ └────────────────────┘ │ └────────────────────┘ │
|
||
│ │ │ ▲ │
|
||
│ ▼ │ │ │
|
||
│ ┌────────────────────┐ │ ┌────────────────────┐ │
|
||
│ │ Render Buffer │──────┼─►│ Display Buffer │ │
|
||
│ │ (PSRAM 150KB) │ Swap │ │ (PSRAM 150KB) │ │
|
||
│ └────────────────────┘ │ └────────────────────┘ │
|
||
│ │ │
|
||
│ ┌────────────────────┐ │ │
|
||
│ │ Audio Task │ │ │
|
||
│ │ (I2S Output) │ │ │
|
||
│ └────────────────────┘ │ │
|
||
└──────────────────────────────┴──────────────────────────────┘
|
||
│ │
|
||
▼ ▼
|
||
┌─────────────────┐ ┌─────────────────┐
|
||
│ I2S Audio │ │ SPI Display │
|
||
│ (MAX98357A) │ │ (ST7789) │
|
||
└─────────────────┘ └─────────────────┘
|
||
```
|
||
|
||
### Programmablauf beim Start
|
||
|
||
```
|
||
1. app_main() startet
|
||
│
|
||
├─► ST7789 Display initialisieren (80 MHz SPI)
|
||
│ └─► Backlight auf 80% setzen
|
||
│
|
||
├─► SD-Karte mounten (FAT32)
|
||
│ └─► ROM laden: /tetris.gb
|
||
│
|
||
├─► PSRAM prüfen (8MB Octal Mode)
|
||
│ ├─► Render Buffer allokieren (150 KB)
|
||
│ └─► Display Buffer allokieren (150 KB)
|
||
│
|
||
├─► I2S Audio initialisieren (32768 Hz, 16-bit)
|
||
│ └─► Audio Task starten (Core 1, Priority 5)
|
||
│
|
||
├─► Peanut-GB Emulator initialisieren
|
||
│ ├─► ROM laden
|
||
│ ├─► Callbacks registrieren:
|
||
│ │ ├─► gb_lcd_draw_line() - Zeile rendern
|
||
│ │ └─► audio_callback() - Audio-Sample
|
||
│ └─► GameBoy-Palette setzen (DMG Grün)
|
||
│
|
||
├─► Display Task erstellen (Core 0, Priority 10)
|
||
│ └─► Wartet auf frame_ready_sem
|
||
│
|
||
└─► Emulation Loop starten (Main Loop)
|
||
│
|
||
└─► Für immer:
|
||
├─► gb_run_frame() - Emuliert 1 Frame
|
||
│ ├─► CPU läuft (70224 Takte)
|
||
│ ├─► PPU rendert Zeilen → gb_lcd_draw_line()
|
||
│ └─► APU erzeugt Audio → audio_callback()
|
||
│
|
||
├─► Buffer Swap (Render ↔ Display)
|
||
│ └─► frame_ready_sem freigeben
|
||
│
|
||
├─► Auf frame_done_sem warten
|
||
│ └─► Display Task hat gerendert
|
||
│
|
||
└─► FPS berechnen & ausgeben (alle 60 Frames)
|
||
```
|
||
|
||
### Double-Buffering Synchronisation
|
||
|
||
```
|
||
EMULATION TASK (Core 1) DISPLAY TASK (Core 0)
|
||
───────────────────── ────────────────────
|
||
|
||
┌─ Frame N rendern ┌─ Wartet auf Semaphore
|
||
│ in Render Buffer │ (frame_ready_sem)
|
||
│ │
|
||
│ gb_run_frame() │
|
||
│ └─► Zeilen in │
|
||
│ render_buffer │
|
||
│ schreiben │
|
||
│ │
|
||
└─ Frame fertig │
|
||
│
|
||
Buffer Swap: │
|
||
render_buffer ↔ │
|
||
display_buffer │
|
||
│
|
||
frame_ready_sem │
|
||
freigeben ──────────────────► └─ Semaphore empfangen!
|
||
|
||
Warte auf ┌─ Display buffer kopieren
|
||
frame_done_sem │ zu compact_buffer
|
||
◄──────────────────────────── │ (nur GameBoy-Region)
|
||
│
|
||
├─ SPI Transfer
|
||
│ st7789_draw_buffer_
|
||
│ preswapped()
|
||
│
|
||
└─ Fertig!
|
||
|
||
frame_done_sem
|
||
freigeben
|
||
|
||
frame_done_sem empfangen ◄────
|
||
|
||
┌─ Nächstes Frame ┌─ Wartet wieder...
|
||
│ rendern │
|
||
```
|
||
|
||
---
|
||
|
||
## 🧩 Component-Beschreibung
|
||
|
||
### 1. Peanut-GB Component
|
||
|
||
**Pfad:** `components/peanut-gb/`
|
||
|
||
**Funktion:** Kern des GameBoy-Emulators. Header-only Bibliothek für GameBoy CPU/PPU/APU Emulation.
|
||
|
||
**Wichtige Funktionen:**
|
||
- `gb_init()` - Initialisiert den Emulator
|
||
- `gb_run_frame()` - Emuliert 1 GameBoy Frame (70224 CPU-Takte = 16.7ms)
|
||
- `gb_lcd_draw_line()` - Callback für jede gerenderte Bildschirmzeile
|
||
|
||
**Optimierung:** Kompiliert mit `-O3` Flag für maximale Performance
|
||
|
||
### 2. ST7789 Display Driver Component
|
||
|
||
**Pfad:** `components/st7789/`
|
||
|
||
**Funktion:** Treiber für ST7789 TFT-Display (320×240 Pixel, RGB565 Farbformat)
|
||
|
||
**Wichtige Funktionen:**
|
||
- `st7789_init()` - Display initialisieren (80 MHz SPI)
|
||
- `st7789_draw_buffer_preswapped()` - Optimierter Transfer (vorher RGB→BGR gewandelt)
|
||
- `st7789_set_backlight()` - Hintergrundbeleuchtung steuern (0-100%)
|
||
|
||
**Besonderheit:**
|
||
- Chunked SPI Transfer (max 32KB pro Transfer wegen DMA-Limit)
|
||
- Byte-Swapping wird im Emulator gemacht (RGB→BGR), nicht im Treiber
|
||
|
||
### 3. Link Cable Component
|
||
|
||
**Pfad:** `components/link_cable/`
|
||
|
||
**Status:** Hardware fertig, Software TODO
|
||
|
||
**Funktion:** 2-Player Multiplayer über GPIO (serieller Transfer)
|
||
|
||
**Geplante Nutzung:**
|
||
- Pokemon-Tausch
|
||
- Tetris 2-Player
|
||
- Andere Multiplayer-Games
|
||
|
||
### 4. NFC Manager Component
|
||
|
||
**Pfad:** `components/nfc_manager/`
|
||
|
||
**Status:** Hardware fertig (PN532 auf I2C), Software TODO
|
||
|
||
**Funktion:** ROM-Auswahl per NFC-Tag scannen
|
||
|
||
**Geplante Funktionsweise:**
|
||
1. NFC-Tag scannen
|
||
2. UID auslesen
|
||
3. In `nfc_roms.json` nachschlagen
|
||
4. Entsprechende ROM laden
|
||
|
||
### 5. Potentiometer Manager Component
|
||
|
||
**Pfad:** `components/potentiometer_manager/`
|
||
|
||
**Status:** Hardware fertig (ADC GPIO 3, 4), Software TODO
|
||
|
||
**Funktion:** Analoge Steuerung für Volume und Brightness
|
||
|
||
**Geplante Nutzung:**
|
||
- Poti 1 (GPIO 3): Lautstärke (0-100%)
|
||
- Poti 2 (GPIO 4): Helligkeit (10-100%)
|
||
|
||
### 6. Minizip Component
|
||
|
||
**Pfad:** `components/minizip/`
|
||
|
||
**Funktion:** ZIP-Dekompression für .zip ROM-Dateien
|
||
|
||
**Status:** Eingebunden, aber aktuell nicht genutzt (ROMs sind .gb, nicht gezippt)
|
||
|
||
### 7. Zlib Component
|
||
|
||
**Pfad:** `components/zlib/`
|
||
|
||
**Funktion:** Compression-Bibliothek (für Minizip und Save-States)
|
||
|
||
**Status:** Eingebunden als Dependency für Minizip
|
||
|
||
---
|
||
|
||
## 🔧 Wichtige Code-Bereiche in main.c
|
||
|
||
### 1. GameBoy Palette
|
||
|
||
```c
|
||
// Zeilen 417-422: DMG Grün-Palette
|
||
static const uint16_t gb_palette[4] = {
|
||
0xFFFF, // Weiß (Hintergrund)
|
||
0xAD55, // Hellgrün
|
||
0x52AA, // Mittelgrün
|
||
0x0000 // Schwarz (Vordergrund)
|
||
};
|
||
```
|
||
|
||
**Erklärung:** GameBoy hat 4 Graustufen (2-bit), hier als RGB565-Werte definiert.
|
||
|
||
### 2. Audio Callback
|
||
|
||
```c
|
||
// Zeilen 424-461: Audio-Sample Callback
|
||
void audio_callback(struct gb_s *gb, uint16_t left, uint16_t right)
|
||
```
|
||
|
||
**Funktion:** Wird von Peanut-GB für jedes Audio-Sample aufgerufen (32768 Hz).
|
||
|
||
**Ablauf:**
|
||
1. Samples in Ring-Buffer schreiben
|
||
2. Bei Buffer voll: I2S schreiben
|
||
3. APU-Register auslesen für Kanal-Status
|
||
|
||
### 3. Display Zeilen-Rendering
|
||
|
||
```c
|
||
// Zeilen 463-530: LCD Zeilen-Callback
|
||
static void gb_lcd_draw_line(...)
|
||
```
|
||
|
||
**Funktion:** Wird 144× pro Frame aufgerufen (eine Zeile pro Aufruf)
|
||
|
||
**Scaling-Algorithmus:**
|
||
1. Vertikales Scaling: `y_base = (line * GB_RENDER_HEIGHT) / 144`
|
||
2. Horizontales Scaling: Jedes Pixel wird dynamisch auf 1-2 Output-Pixel gemapped
|
||
3. Byte-Swapping: RGB565 → BGR565 (für ST7789)
|
||
4. Pixel-Width-Berechnung verhindert Lücken im Bild
|
||
|
||
**Beispiel bei Scale 1.6:**
|
||
- GameBoy Zeile 0 → Display Y = 0
|
||
- GameBoy Zeile 10 → Display Y = 16 (10 * 1.6 = 16)
|
||
- Einige Zeilen werden dupliziert für gleichmäßige Skalierung
|
||
|
||
### 4. Display Task
|
||
|
||
```c
|
||
// Zeilen 610-653: Display Rendering Task
|
||
static void display_task(void *arg)
|
||
```
|
||
|
||
**Funktion:** Läuft auf Core 0, rendert fertigen Frame zum Display
|
||
|
||
**Optimierung - Compact Buffer:**
|
||
1. Nur GameBoy-Region kopieren (nicht schwarze Ränder)
|
||
2. Von 320×240 Buffer → 256×230 kompakter Buffer
|
||
3. 33% weniger SPI-Transfer!
|
||
4. Geschwindigkeit steigt von 20ms auf 16ms pro Frame
|
||
|
||
### 5. Main Loop
|
||
|
||
```c
|
||
// Zeilen 702-786: Hauptschleife
|
||
void app_main(void)
|
||
```
|
||
|
||
**Ablauf:**
|
||
1. Display init
|
||
2. SD-Card mount
|
||
3. PSRAM check & Buffer alloc
|
||
4. Audio init & Task start
|
||
5. Emulator init
|
||
6. Display Task start
|
||
7. Unendliche Emulations-Loop
|
||
|
||
---
|
||
|
||
## 🎯 Performance-Optimierungen
|
||
|
||
### 1. PSRAM Double-Buffering
|
||
|
||
**Problem:** SPI-Transfer (10ms) blockiert Emulation
|
||
**Lösung:** 2 Buffer in PSRAM, parallel arbeiten
|
||
|
||
**Ergebnis:**
|
||
- Core 0: Display rendering (10ms)
|
||
- Core 1: Emulation (10ms)
|
||
- Parallel = 50% schneller!
|
||
|
||
### 2. Display Scaling
|
||
|
||
**Problem:** Fullscreen (320×240) zu langsam (20ms)
|
||
**Lösung:** Kleinere Auflösung mit schwarzen Rändern
|
||
|
||
**Ergebnis:**
|
||
- Scale 1.0: 160×144 = 83 FPS (sehr klein)
|
||
- Scale 1.5: 240×216 = 90-100 FPS (gut)
|
||
- Scale 1.6: 256×230 = 60-90 FPS (beste Balance)
|
||
- Scale 1.67: 267×240 = 55-70 FPS (volle Höhe)
|
||
|
||
### 3. Compact Buffer
|
||
|
||
**Problem:** 320×240 Buffer mit vielen schwarzen Pixeln
|
||
**Lösung:** Nur GameBoy-Region transferieren
|
||
|
||
**Ergebnis:**
|
||
- Vorher: 153.600 Bytes SPI-Transfer
|
||
- Nachher: 117.760 Bytes (23% weniger!)
|
||
- Speedup: 3-4ms pro Frame
|
||
|
||
### 4. Compiler Optimierung
|
||
|
||
**Problem:** Emulator zu langsam (45ms pro Frame)
|
||
**Lösung:** `-O3` Compiler-Flag für Peanut-GB
|
||
|
||
**Ergebnis:**
|
||
- -O2: 22-25ms
|
||
- -O3: 16-19ms
|
||
- **40% schneller!**
|
||
|
||
### 5. SPI Clock Speed
|
||
|
||
**Problem:** Display-Transfer Bottleneck
|
||
**Lösung:** 80 MHz SPI (Maximum für ST7789)
|
||
|
||
**Ergebnis:**
|
||
- 40 MHz: 28ms pro Frame
|
||
- 80 MHz: 16ms pro Frame
|
||
- **Fast doppelt so schnell!**
|
||
|
||
---
|
||
|
||
## 📊 Performance-Tabelle
|
||
|
||
| Game | Scale | FPS | Frame Time | Audio |
|
||
|---------------|-------|----------|------------|-------|
|
||
| Tetris | 1.5 | 60-70 | 14-16ms | ✅ |
|
||
| DuckTales | 1.5 | 90-111 | 9-11ms | ✅ |
|
||
| Pokemon | 1.6 | 55-65 | 15-18ms | ✅ |
|
||
| Super Mario | 1.6 | 60-75 | 13-16ms | ✅ |
|
||
| Original GB | - | **59.73**| **16.7ms** | ✅ |
|
||
|
||
**Fazit:** Bei vielen Spielen schneller als Original GameBoy!
|
||
|
||
---
|
||
|
||
## 🚀 Schnellstart - Build & Flash
|
||
|
||
### Voraussetzungen
|
||
|
||
- **ESP-IDF v4.4** installiert
|
||
- **Python 3.10** (pyenv empfohlen)
|
||
- **Git**
|
||
|
||
### Build
|
||
|
||
```bash
|
||
# ESP-IDF Environment laden
|
||
source ~/esp-idf/export.sh
|
||
|
||
# Projekt bauen
|
||
cd /home/duffy/Arduino/gameboy/gnuboy
|
||
idf.py build
|
||
```
|
||
|
||
### Flash
|
||
|
||
```bash
|
||
# Flashen
|
||
idf.py -p /dev/ttyUSB0 flash
|
||
|
||
# Mit Monitor
|
||
idf.py -p /dev/ttyUSB0 flash monitor
|
||
```
|
||
|
||
---
|
||
|
||
## 🛠️ Hardware
|
||
|
||
### Hauptkomponenten
|
||
|
||
| Component | Model | Notes |
|
||
|----------------|---------------------------------|------------------------|
|
||
| **MCU Board** | Waveshare ESP32-S3-Touch-LCD-2 | 16MB Flash, 8MB PSRAM |
|
||
| **Display** | ST7789 2.0" | 320×240, integriert |
|
||
| **Audio** | MAX98357A | I2S Amplifier |
|
||
| **Storage** | MicroSD Card | FAT32, via SPI |
|
||
|
||
### Pin-Belegung
|
||
|
||
> **Siehe `main/include/hardware_config.h` für alle Pin-Definitionen!**
|
||
|
||
**Display (ST7789 SPI):**
|
||
- MOSI: GPIO 38, SCLK: GPIO 39, CS: GPIO 45
|
||
- DC: GPIO 42, BCKL: GPIO 1
|
||
|
||
**Audio (I2S):**
|
||
- BCLK: GPIO 48, LRC: GPIO 47, DIN: GPIO 16
|
||
|
||
**Buttons (TODO):**
|
||
- UP: 8, DOWN: 9, LEFT: 10, RIGHT: 11
|
||
- A: 12, B: 13, START: 14, SELECT: 21
|
||
|
||
**NFC (I2C, TODO):**
|
||
- SCL: GPIO 6, SDA: GPIO 5 (shared mit Touch)
|
||
|
||
**Link Cable (TODO):**
|
||
- SCLK: GPIO 15, SOUT: GPIO 2, SIN: GPIO 17
|
||
|
||
**Potentiometer (ADC, TODO):**
|
||
- Volume: GPIO 3, Brightness: GPIO 4
|
||
|
||
---
|
||
|
||
## 📦 Projekt-Struktur
|
||
|
||
```
|
||
gnuboy/
|
||
├── CMakeLists.txt # Root CMake
|
||
├── sdkconfig # ESP-IDF Konfiguration
|
||
├── README.md # Diese Datei
|
||
│
|
||
├── main/
|
||
│ ├── CMakeLists.txt
|
||
│ ├── main.c # Hauptprogramm (ausführlich kommentiert)
|
||
│ └── include/
|
||
│ └── hardware_config.h # Pin-Definitionen & Scaling
|
||
│
|
||
└── components/
|
||
├── peanut-gb/ # GameBoy Emulator Core (-O3)
|
||
├── st7789/ # Display Driver (80 MHz SPI)
|
||
├── minizip/ # ZIP Support
|
||
├── zlib/ # Compression
|
||
├── link_cable/ # 2-Player (TODO)
|
||
├── nfc_manager/ # NFC ROM Selection (TODO)
|
||
└── potentiometer_manager/ # Volume/Brightness (TODO)
|
||
```
|
||
|
||
---
|
||
|
||
## 🔧 Konfiguration
|
||
|
||
### Display Scaling ändern
|
||
|
||
In `main/include/hardware_config.h`:
|
||
|
||
```c
|
||
// Zeile 54: Scaling-Faktor anpassen
|
||
#define GB_SCALE_FACTOR 1.6 // Ändern auf 1.4, 1.5, 1.67, etc.
|
||
```
|
||
|
||
**Empfohlene Werte:**
|
||
- `1.4` - Klein, sehr schnell (>100 FPS)
|
||
- `1.5` - Gut, schnell (90-100 FPS)
|
||
- `1.6` - Beste Balance (60-90 FPS) ⭐
|
||
- `1.67` - Volle Höhe (55-70 FPS)
|
||
|
||
### Fullscreen aktivieren
|
||
|
||
```c
|
||
// Zeile 41: Scaling deaktivieren
|
||
#define GB_PIXEL_PERFECT_SCALING 0 // Fullscreen 320×240
|
||
```
|
||
|
||
**Warnung:** Fullscreen ist langsamer (~50 FPS bei vielen Spielen)
|
||
|
||
---
|
||
|
||
## 🐛 Troubleshooting
|
||
|
||
### Build Fehler?
|
||
|
||
```bash
|
||
# Clean & Rebuild
|
||
idf.py fullclean
|
||
idf.py build
|
||
```
|
||
|
||
### PSRAM nicht erkannt?
|
||
|
||
```bash
|
||
# Prüfen im Monitor
|
||
idf.py monitor
|
||
# Sollte zeigen: "PSRAM: 8191 KB total"
|
||
```
|
||
|
||
Falls nicht:
|
||
```bash
|
||
idf.py menuconfig
|
||
# → Component config → ESP32S3-Specific
|
||
# → [*] Support for external, SPI-connected RAM
|
||
# → Mode: Octal Mode PSRAM
|
||
# → Speed: 80MHz
|
||
```
|
||
|
||
### Zu langsam?
|
||
|
||
1. Display Scaling reduzieren (`GB_SCALE_FACTOR 1.4`)
|
||
2. Compiler-Optimierung prüfen (sollte `-O3` sein)
|
||
3. SPI-Speed prüfen (sollte 80 MHz sein)
|
||
|
||
### Audio knackt?
|
||
|
||
- Normal bei sehr langsamen Spielen (<40 FPS)
|
||
- Bei >50 FPS sollte Audio perfekt sein
|
||
|
||
---
|
||
|
||
## 📝 Lizenz
|
||
|
||
- **Peanut-GB:** MIT License
|
||
- **Projekt-spezifischer Code:** MIT
|
||
- **Components:** Siehe jeweilige LICENSE-Dateien
|
||
|
||
---
|
||
|
||
## 🙏 Credits
|
||
|
||
- **Peanut-GB:** Delta (Header-only GameBoy Emulator)
|
||
- **Waveshare:** Hardware Board ESP32-S3-Touch-LCD-2
|
||
- **Espressif:** ESP-IDF Framework
|
||
- **Duffy:** Dieses LEGO GameBoy Projekt! 🎮
|
||
|
||
---
|
||
|
||
## 🎯 Roadmap
|
||
|
||
**Nächste Schritte:**
|
||
|
||
1. ✅ Emulator läuft perfekt (90-111 FPS!)
|
||
2. ✅ Audio funktioniert (4 Kanäle)
|
||
3. ✅ Display-Scaling optimiert
|
||
4. ⏳ Button-Input implementieren
|
||
5. ⏳ ROM-Menü auf SD-Card
|
||
6. ⏳ NFC ROM-Auswahl
|
||
7. ⏳ Potentiometer Volume/Brightness
|
||
8. ⏳ Link Cable 2-Player
|
||
9. ⏳ Save-States
|
||
|
||
---
|
||
|
||
**Viel Spaß beim Zocken! 🎮🔊**
|
||
|
||
*Erstellt mit Liebe zum Detail ❤️*
|