Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 76 additions & 5 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
name: Build Windows
name: Build

on:
pull_request:
branches: [ main ]
branches: [ 'main' ]
workflow_dispatch:

jobs:
build:
runs-on: windows-latest
name: Build on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [windows-latest, ubuntu-latest]

steps:
- uses: actions/checkout@v4
Expand All @@ -18,23 +23,89 @@ jobs:
python-version: '3.12'
cache: 'pip'

- name: Install dependencies
- name: Install System Dependencies (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxcb-sync1 libxcb-xfixes0 libxcb-xinerama0 libxcb-xkb1 libxkbcommon-x11-0 libegl1

# Ensure executable permission for bundled binary
chmod +x resources/bin/aria2c || true
ls -l resources/bin/aria2c || echo "Aria2c not found in resources/bin"

- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pyinstaller

- name: Setup UPX
if: runner.os == 'Windows'
uses: crazy-max/ghaction-upx@v3
with:
version: latest
install-only: true

- name: Build with PyInstaller
run: |
pyinstaller EmuMan.spec

- name: Upload Artifact
- name: Upload Artifact (Windows)
if: runner.os == 'Windows'
uses: actions/upload-artifact@v4
with:
name: EmuMan-Windows
path: dist/EmuMan.exe

- name: Upload Artifact (Linux)
if: runner.os == 'Linux'
uses: actions/upload-artifact@v4
with:
name: EmuMan-Linux
path: dist/EmuMan

release:
name: Release
needs: build
runs-on: ubuntu-latest
if: github.ref_name == 'main'

steps:
- name: Download Windows Artifact
uses: actions/download-artifact@v4
with:
name: EmuMan-Windows
path: release_dist

- name: Download Linux Artifact
uses: actions/download-artifact@v4
with:
name: EmuMan-Linux
path: release_dist

- name: Rename Artifacts
run: |
mv release_dist/EmuMan.exe release_dist/EmuMan-Windows.exe
mv release_dist/EmuMan release_dist/EmuMan-Linux
chmod +x release_dist/EmuMan-Linux

- name: Get Version
run: |
VERSION=$(grep 'CURRENT_VERSION =' app/config.py | cut -d '"' -f 2)
echo "Detected version: $VERSION"
echo "VERSION=$VERSION" >> $GITHUB_ENV

- name: Create Release
uses: softprops/action-gh-release@v2.5.0
with:
tag_name: ${{ env.VERSION }}
prerelease: false
draft: false
generate_release_notes: false
fail_on_unmatched_files: false
make_latest: true
files: |
release_dist/EmuMan-Windows.exe
release_dist/EmuMan-Linux
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
5 changes: 1 addition & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,11 @@ api_cache.json
cache/
backups/
downloads/
upx/

# IDEs
.idea/
.vscode/
.cursor/
*.swp
*.swo


# Binaries
upx.exe
18 changes: 15 additions & 3 deletions EmuMan.spec
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# -*- mode: python ; coding: utf-8 -*-
import shutil
import sys
import os

block_cipher = None

Expand All @@ -19,19 +22,28 @@ a = Analysis(
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)

# Define Aria2 Path based on OS
aria2_filename = 'aria2c.exe' if sys.platform == 'win32' else 'aria2c'
aria2_path = os.path.join('resources', 'bin', aria2_filename)
aria2_binary = []

if os.path.exists(aria2_path):
# Bundle it to the root of the frozen app (sys._MEIPASS/aria2c)
aria2_binary = [(aria2_filename, aria2_path, 'BINARY')]

exe = EXE(
pyz,
a.scripts,
a.binaries,
a.binaries + aria2_binary,
a.zipfiles,
a.datas,
[],
name='EmuMan',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
upx=(sys.platform == 'win32'),
upx_exclude=['_uuid.pyd', 'vcruntime140.dll', 'python3.dll', 'python312.dll'],
runtime_tmpdir=None,
console=False,
disable_windowed_traceback=False,
Expand Down
41 changes: 20 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,62 +16,61 @@
**EmuMan** (Emulator Manager) is a helper utility specifically designed for the Eden Emulator. It addresses common pain points such as frequent emulator updates, complex firmware management, and difficult save data backups by providing a one-stop maintenance solution for players.

> [!TIP]
> Whether you are a player seeking stability or a developer who enjoys testing the latest features, EmuMan helps you effortlessly switch between different emulator branches while keeping your saves and settings safe.
> **Stay Informed & Secure**: EmuMan puts the latest Eden changelogs front and center, so you always know what's new. Whether you're chasing the latest features or sticking to stability, switch branches effortlessly while keeping your saves safe.

---

## ✨ Core Features

- 🚀 **Multi-Version Management**: Automatic downloading, installation, and quick switching between Master (Stable) and Nightly (Dev) branches.
- 📦 **Firmware & Keys Management**: Automatically sync the latest firmware or install local files. One-click scanning and importing for `prod.keys` and `title.keys`.
- 🛡️ **Safe Backup**: Integrated "Save Manager" with multi-point backup and restore functionality—never worry about losing saves during updates again.
- 📦 **Firmware & Keys Management**: Automatically sync the latest firmware or install local files. One-click scanning and importing for `*.keys`.
- 🛡️ **Safe Backup**: Integrated "Save Manager" with multi-point backup and restore functionality—never worry about losing saves during updates.
- 🛠️ **Toolbox**: Useful utilities including log exporting and Mod (LayeredFS) toggle management.
- 🌍 **Multi-Language Support**: Native support for English, Chinese (Simplified/Traditional), Japanese, Korean, and more.
- 🎨 **Modern UI**: A high-quality native interface built with PySide6, featuring smooth Light/Dark mode transitions.
- 🎨 **Modern UI**: A high-quality native interface built with [PySide6-Fluent-Widgets](https://github.com/zhiyiYo/PyQt-Fluent-Widgets/tree/PySide6), featuring smooth Light/Dark mode transitions.

---

## 🚀 Quick Start

### Option 1: Run from Source (Recommended for Developers)

1. **Clone the Repo**:
1. **Prerequisites (Linux Only)**:
```bash
git clone https://github.com/your-username/EmuMan.git
# Debian/Ubuntu
sudo apt install aria2 python3-venv python3-pip
# Arch Linux
sudo pacman -S aria2 python-pip
```

2. **Clone the Repo**:
```bash
git clone https://github.com/pflyly/EmuMan.git
cd EmuMan
```

2. **Create and Activate Virtual Environment**:
3. **Create and Activate Virtual Environment**:
```bash
python -m venv .venv
# Windows
.venv\Scripts\activate
# Linux/macOS
source .venv/bin/activate
```

3. **Install Dependencies**:
4. **Install Dependencies**:
```bash
pip install -r requirements.txt
```

4. **Launch Application**:
5. **Launch Application**:
```bash
python main.py
```

### Option 2: Run Pre-compiled Version (Coming Soon)

Please visit the [Releases](https://github.com/your-username/EmuMan/releases) page to download the latest `.exe` installer.

---

## 📅 Roadmap

- [x] Multi-language UI support
- [x] Automatic sync for multiple emulator versions
- [x] One-click firmware/keys installation
- [ ] More intelligent Mod management (In progress...)
- [ ] Cloud save synchronization (Planned)
- [ ] Game library management (In design)
Please visit the [Releases](https://github.com/pflyly/EmuMan/releases) page to download the latest `.exe` installer.

---

Expand Down
4 changes: 4 additions & 0 deletions app/core/file_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ def fix_executable_permission(path):
"""Apply chmod +x to a specific file on Linux/Unix systems."""
if sys.platform == "win32": return
try:
# Idempotency check: if already executable, skip
if os.access(path, os.X_OK):
return True

logger.info(f"Linux: Applying executable permission to {path}")
st = os.stat(path)
os.chmod(path, st.st_mode | stat.S_IEXEC)
Expand Down
53 changes: 18 additions & 35 deletions app/core/firmware_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ def list_local_firmware():

return results



@staticmethod
def get_firmware_version(eden_exe_path=None):
"""
Expand All @@ -141,20 +143,13 @@ def get_firmware_version(eden_exe_path=None):
log_time = 0

log_paths = []
if eden_exe_path and os.path.exists(eden_exe_path):
exe_dir = Path(eden_exe_path).parent
portable_log = exe_dir / "user" / "log" / "eden_log.txt"
log_paths.append(portable_log)
user_dir = FirmwareManager.get_user_data_path(eden_exe_path)

if sys.platform == "win32":
system_log = Path.home() / "AppData" / "Roaming" / "eden" / "log" / "eden_log.txt"
else:
xdg_data_home = os.getenv("XDG_DATA_HOME")
if xdg_data_home:
system_log = Path(xdg_data_home) / "eden" / "log" / "eden_log.txt"
else:
system_log = Path.home() / ".local" / "share" / "eden" / "log" / "eden_log.txt"
# Determine portable log path if applicable (it resides in user/log)
# However _get_base_user_dir returns the 'user' or equivalent root.
# Log is at <root>/log/eden_log.txt

system_log = user_dir / "log" / "eden_log.txt"
log_paths.append(system_log)

for log_path in log_paths:
Expand Down Expand Up @@ -449,24 +444,8 @@ def get_nand_path(eden_exe_path=None):
2. System (Win): %APPDATA%/eden/nand/system/Contents/registered/
3. System (Linux): ~/.local/share/eden/nand/system/Contents/registered/
"""
if eden_exe_path and os.path.exists(eden_exe_path):
path_obj = Path(eden_exe_path)
# If input is directory (from settings), use it as exe_dir; if file (exe), use parent
exe_dir = path_obj if path_obj.is_dir() else path_obj.parent

# Check for Portable Mode (check 'user' folder)
if (exe_dir / "user").exists():
return exe_dir / "user" / "nand" / "system" / "Contents" / "registered"

# System Mode Fallback
if sys.platform == "win32":
return Path.home() / "AppData" / "Roaming" / "eden" / "nand" / "system" / "Contents" / "registered"
else:
xdg_data_home = os.getenv("XDG_DATA_HOME")
if xdg_data_home:
return Path(xdg_data_home) / "eden" / "nand" / "system" / "Contents" / "registered"
else:
return Path.home() / ".local" / "share" / "eden" / "nand" / "system" / "Contents" / "registered"
user_dir = FirmwareManager.get_user_data_path(eden_exe_path)
return user_dir / "nand" / "system" / "Contents" / "registered"

@staticmethod
def get_user_data_path(eden_exe_path=None):
Expand All @@ -486,11 +465,15 @@ def get_user_data_path(eden_exe_path=None):
if sys.platform == "win32":
return Path.home() / "AppData" / "Roaming" / "eden"
else:
xdg_data_home = os.getenv("XDG_DATA_HOME")
if xdg_data_home:
return Path(xdg_data_home) / "eden"
else:
return Path.home() / ".local" / "share" / "eden"
default_linux = Path.home() / ".local" / "share" / "eden"
if default_linux.exists():
return default_linux

xdg = os.getenv("XDG_DATA_HOME")
if xdg:
return Path(xdg) / "eden"

return default_linux

@staticmethod
def install_firmware(zip_path, eden_exe_path=None, progress_callback=None, cancel_check=None, version_tag=None):
Expand Down
7 changes: 3 additions & 4 deletions app/core/keys_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,10 @@ def auto_detect_keys():
candidates = []
roaming = Path(os.getenv('APPDATA')) if os.name == 'nt' else Path.home() / ".config"

# Common paths for Yuzu/Ryujinx
# Eden paths
search_paths = [
roaming / "yuzu" / "keys",
roaming / "Ryujinx" / "system",
roaming / "suyu" / "keys",
roaming / "eden" / "keys",
Path.home() / ".local" / "share" / "eden" / "keys",
]

found_files = []
Expand Down
2 changes: 1 addition & 1 deletion app/ui/about_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ def initCredits(self):
components = [
("Eden Emulator", "https://git.eden-emu.dev/eden-emu/eden", "Core Switch Emulator"),
("Aria2", "https://github.com/aria2/aria2", "High speed download utility"),
("QFluentWidgets", "https://qfluentwidgets.com/", "Modern UI Components"),
("PyQt-Fluent-Widgets", "https://github.com/zhiyiYo/PyQt-Fluent-Widgets/tree/PySide6", "Modern UI Components"),
("NX Firmware", "https://github.com/THZoria/NX_Firmware", "Firmware Source"),
]

Expand Down
Loading