commits
tags
from pathlib import Path
from flask import Blueprint, jsonify, request, abort, render_template, send_file
import db
import extensions
from auth import login_required
from config import ALLOWED_ROOTS
bp = Blueprint('emulator', __name__)
# Filändelse → EJS_core-namn
_EXT_TO_CORE = {
'.sfc': 'snes', '.smc': 'snes',
'.nes': 'nes',
'.gba': 'gba',
'.gbc': 'gbc', '.gb': 'gb',
'.n64': 'n64', '.z64': 'n64', '.v64': 'n64',
'.nds': 'nds',
'.md': 'segaMD', '.gen': 'segaMD', '.smd': 'segaMD',
'.bin': 'psx', '.iso': 'psx',
'.psp': 'psp',
'.a26': 'atari2600',
'.gba': 'gba',
}
_CORE_LABELS = {
'snes': 'SNES',
'nes': 'NES / Famicom',
'gba': 'Game Boy Advance',
'gbc': 'Game Boy Color',
'gb': 'Game Boy',
'n64': 'Nintendo 64',
'nds': 'Nintendo DS',
'segaMD': 'Sega Mega Drive',
'psx': 'PlayStation 1',
'psp': 'PSP',
'atari2600': 'Atari 2600',
}
def _safe_path(path_str):
"""Returnerar ett validerat Path-objekt eller None om utanför ALLOWED_ROOTS."""
try:
p = Path(path_str).resolve()
if any(str(p).startswith(str(r)) for r in ALLOWED_ROOTS):
return p
except Exception:
pass
return None
# --- Spelsida (laddas i display-iframe) ---
@bp.route('/emulator')
def emulator_page():
rom_path = request.args.get('rom', '')
core = request.args.get('core', '')
# Validera path
if not rom_path or not _safe_path(rom_path):
abort(403)
if core not in _CORE_LABELS:
core = _EXT_TO_CORE.get(Path(rom_path).suffix.lower(), 'snes')
ejs_path = db.get_state().get('emulator_ejs_path',
'https://cdn.emulatorjs.org/stable/data/')
return render_template('emulator.html',
rom_path=rom_path,
rom_name=Path(rom_path).name,
core=core,
core_label=_CORE_LABELS.get(core, core),
ejs_path=ejs_path)
# --- ROM-fil (serveras till webbläsaren) ---
@bp.route('/api/emulator/rom')
def serve_rom():
path_str = request.args.get('path', '')
p = _safe_path(path_str)
if not p or not p.is_file():
abort(404)
if p.suffix.lower() not in _EXT_TO_CORE:
abort(403)
return send_file(p)
# --- ROM-lista ---
@bp.route('/api/emulator/roms')
@login_required
def list_roms():
folder_str = request.args.get('path', db.get_state().get('emulator_rom_dir', ''))
if not folder_str:
return jsonify([])
folder = _safe_path(folder_str)
if not folder or not folder.is_dir():
return jsonify([])
roms = []
for f in sorted(folder.rglob('*')):
if f.is_file() and f.suffix.lower() in _EXT_TO_CORE:
core = _EXT_TO_CORE[f.suffix.lower()]
roms.append({
'name': f.name,
'path': str(f),
'core': core,
'core_label': _CORE_LABELS.get(core, core),
'size_mb': round(f.stat().st_size / 1_048_576, 1),
})
return jsonify(roms)
# --- Starta emulator på display ---
@bp.route('/api/emulator/launch', methods=['POST'])
@login_required
def launch():
data = request.get_json() or {}
rom_path = str(data.get('rom', '')).strip()
core = str(data.get('core', '')).strip()
if not rom_path or not _safe_path(rom_path):
return jsonify({'error': 'Ogiltig ROM-sökväg'}), 400
if core not in _CORE_LABELS:
core = _EXT_TO_CORE.get(Path(rom_path).suffix.lower(), 'snes')
db.set_state('emulator_active', '1')
db.set_state('emulator_rom', Path(rom_path).name)
db.set_state('emulator_rom_path', rom_path)
db.set_state('emulator_core', core)
url = f'/emulator?rom={rom_path}&core={core}'
extensions.socketio.emit('switch', {
'type': 'url',
'source': url,
'scheduled': False,
})
return '', 204
# --- Stoppa emulator ---
@bp.route('/api/emulator/stop', methods=['POST'])
@login_required
def stop():
db.set_state('emulator_active', '0')
extensions.socketio.emit('switch', {
'type': 'local',
'source': '',
'html': '',
'scheduled': False,
})
return '', 204
# --- Status ---
@bp.route('/api/emulator/status')
@login_required
def status():
state = db.get_state()
return jsonify({
'active': state.get('emulator_active', '0') == '1',
'rom': state.get('emulator_rom', ''),
'core': state.get('emulator_core', ''),
'core_label': _CORE_LABELS.get(state.get('emulator_core', ''), ''),
'rom_dir': state.get('emulator_rom_dir', ''),
'ejs_path': state.get('emulator_ejs_path',
'https://cdn.emulatorjs.org/stable/data/'),
})
# --- Spara inställningar ---
@bp.route('/api/emulator/settings', methods=['POST'])
@login_required
def save_settings():
data = request.get_json() or {}
rom_dir = str(data.get('rom_dir', '')).strip()
ejs_path = str(data.get('ejs_path', '')).strip()
if rom_dir:
db.set_state('emulator_rom_dir', rom_dir)
if ejs_path:
db.set_state('emulator_ejs_path', ejs_path)
return '', 204