emulatorjs-GameLibrary/play.php

826 lines
32 KiB
PHP
Raw Permalink Normal View History

<?php
session_start();
include 'functions.php';
include 'includes/retroachievements.php';
// Ensure a game has been specified
if (!isset($_GET['game'])) {
header('Location: index.php');
exit;
}
// Get current profile or set default
if (!isset($_SESSION['current_profile'])) {
$profiles = getProfiles();
if (count($profiles) > 0) {
$_SESSION['current_profile'] = $profiles[0]['id'];
} else {
// Create default profile if none exists
$defaultProfileId = createProfile("Player 1", "avatar1.png");
$_SESSION['current_profile'] = $defaultProfileId;
}
}
// Get RetroAchievements settings
$raSettings = getRetroAchievementsSettings();
// Get general settings
$generalSettingsPath = 'config/general.json';
$generalSettings = [];
2023-10-17 16:18:05 +00:00
if (file_exists($generalSettingsPath)) {
$generalSettings = json_decode(file_get_contents($generalSettingsPath), true);
}
2023-10-17 16:18:24 +00:00
$siteName = $generalSettings['site_name'] ?? 'RetroHub';
// Get profile and game information
$currentProfile = getProfileById($_SESSION['current_profile']);
$allProfiles = getProfiles();
$gameFile = $_GET['game'];
// Find console based on file extension
$name = basename($gameFile);
$ext = explode(".", $name);
$ext = strtolower(end($ext));
// For zipfile
if ($ext == 'zip') {
$zip = new ZipArchive;
if ($zip->open("roms/".$name)) {
$names = $zip->getNameIndex(0);
$ext0 = explode(".", $names);
$ext = strtolower(end($ext0));
}
}
// Determine console type
$console = getConsoleByExtension($ext);
$consoleName = getConsoleFriendlyName($console);
// Get save states for this game and profile
$saveStates = getSaveStates($gameFile, $_SESSION['current_profile']);
// Get game metadata from RetroAchievements if enabled
$gameMetadata = null;
$gameScreenshots = [];
if ($raSettings['enabled']) {
$gameName = pathinfo($gameFile, PATHINFO_FILENAME);
$gameMetadata = getGameMetadata($gameName, $console);
if ($gameMetadata) {
if ($gameMetadata['screenshot_title']) {
$gameScreenshots[] = $gameMetadata['screenshot_title'];
}
if ($gameMetadata['screenshot_ingame']) {
$gameScreenshots[] = $gameMetadata['screenshot_ingame'];
}
}
}
// Parse settings
$settings = parse_ini_file("./settings.ini");
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo htmlspecialchars($name); ?> - <?php echo htmlspecialchars($siteName); ?></title>
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
/* Additional styles specific to the game player */
.game-container {
position: relative;
overflow: hidden;
background-color: #000;
border-radius: var(--border-radius);
}
#game {
width: 100%;
height: 100%;
aspect-ratio: 16/9;
}
.game-details-section {
margin-top: 2rem;
display: grid;
grid-template-columns: 1fr 300px;
gap: 2rem;
}
@media (max-width: 991px) {
.game-details-section {
grid-template-columns: 1fr;
}
}
.game-info-panel {
background-color: var(--secondary-bg);
border-radius: var(--border-radius);
padding: 1.5rem;
}
.game-info-header {
display: flex;
align-items: center;
margin-bottom: 1rem;
}
.game-info-icon {
width: 48px;
height: 48px;
margin-right: 1rem;
border-radius: 8px;
}
.game-info-title {
flex: 1;
}
.game-info-title h2 {
margin: 0 0 0.3rem 0;
}
.game-info-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 1.5rem;
}
.game-info-table td {
padding: 0.5rem 0;
border-bottom: 1px solid var(--nav-bg);
}
.game-info-table td:first-child {
font-weight: 500;
width: 120px;
color: var(--accent-color);
}
.game-screenshots {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-top: 1.5rem;
}
.game-screenshot {
border-radius: var(--border-radius);
overflow: hidden;
cursor: pointer;
}
.game-screenshot img {
width: 100%;
height: auto;
transition: transform 0.3s ease;
}
.game-screenshot:hover img {
transform: scale(1.05);
}
.save-states {
background-color: var(--secondary-bg);
border-radius: var(--border-radius);
padding: 1.5rem;
height: fit-content;
}
.save-states-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.save-states-title {
font-size: 1.2rem;
color: var(--text-primary);
}
.save-slots {
display: grid;
gap: 1rem;
}
.save-slot {
background-color: var(--card-bg);
border-radius: var(--border-radius);
overflow: hidden;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.save-slot:hover {
transform: translateY(-3px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.save-slot-preview {
position: relative;
width: 100%;
aspect-ratio: 4/3;
background-color: #000;
overflow: hidden;
}
.save-slot-preview img {
width: 100%;
height: 100%;
object-fit: cover;
}
.save-slot-empty {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
background-color: var(--primary-bg);
}
.save-slot-empty i {
font-size: 2rem;
color: var(--nav-bg);
margin-bottom: 0.5rem;
}
.save-slot-info {
padding: 0.8rem;
}
.save-slot-title {
font-size: 0.9rem;
margin-bottom: 0.3rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.save-slot-time {
font-size: 0.8rem;
color: var(--accent-color);
}
.save-slot-actions {
display: flex;
justify-content: space-between;
padding: 0.5rem 0.8rem;
border-top: 1px solid var(--primary-bg);
}
.save-slot-btn {
background: none;
border: none;
color: var(--text-secondary);
cursor: pointer;
padding: 0.3rem;
border-radius: 4px;
transition: background-color 0.2s ease, color 0.2s ease;
}
.save-slot-btn:hover {
background-color: var(--nav-bg);
color: var(--accent-color);
}
.empty-states {
padding: 2rem;
text-align: center;
background-color: var(--card-bg);
border-radius: var(--border-radius);
}
.empty-states i {
font-size: 3rem;
color: var(--nav-bg);
margin-bottom: 1rem;
}
.empty-states h3 {
margin-bottom: 0.5rem;
}
.empty-states p {
color: var(--text-secondary);
margin-bottom: 1.5rem;
}
/* Screenshot modal */
.screenshot-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.9);
display: flex;
justify-content: center;
align-items: center;
z-index: 2000;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease, visibility 0.3s ease;
}
.screenshot-modal.active {
opacity: 1;
visibility: visible;
}
.screenshot-modal-close {
position: absolute;
top: 20px;
right: 20px;
background: none;
border: none;
color: white;
font-size: 2rem;
cursor: pointer;
}
.screenshot-modal-image {
max-width: 90%;
max-height: 90%;
border-radius: 5px;
}
</style>
</head>
<body>
<header>
<div class="container">
<nav>
<a href="index.php" class="logo">
<i class="fas fa-gamepad"></i>
<span><?php echo htmlspecialchars($siteName); ?></span>
</a>
<ul class="nav-menu">
<li><a href="index.php">Games</a></li>
<li><a href="upload.php">Upload ROMs</a></li>
<li><a href="bios.php">BIOS Files</a></li>
<li><a href="settings.php">Settings</a></li>
</ul>
<div class="profile-menu">
<div class="current-profile" id="profile-toggle">
<img src="img/avatars/<?php echo $currentProfile['avatar']; ?>" alt="Profile">
<span><?php echo $currentProfile['name']; ?></span>
<i class="fas fa-chevron-down"></i>
</div>
<div class="profile-dropdown" id="profile-dropdown">
<ul class="profile-list">
<?php foreach($allProfiles as $profile): ?>
<li class="profile-item <?php echo ($profile['id'] == $_SESSION['current_profile']) ? 'active' : ''; ?>"
data-profile-id="<?php echo $profile['id']; ?>">
<img src="img/avatars/<?php echo $profile['avatar']; ?>" alt="<?php echo $profile['name']; ?>">
<span class="profile-name"><?php echo $profile['name']; ?></span>
</li>
<?php endforeach; ?>
</ul>
<div class="profile-actions">
<button class="add-profile-btn" id="add-profile-btn">
<i class="fas fa-plus-circle"></i>
<span>Add New Profile</span>
</button>
</div>
</div>
</div>
</nav>
</div>
</header>
<main>
<div class="container">
<div class="page-header">
<div>
<h1 class="page-title">
<?php if ($gameMetadata && isset($gameMetadata['title'])): ?>
<?php echo htmlspecialchars($gameMetadata['title']); ?>
<?php else: ?>
<?php echo htmlspecialchars($name); ?>
<?php endif; ?>
</h1>
<span class="game-console-badge"><?php echo $consoleName; ?></span>
</div>
<a href="index.php" class="btn btn-secondary">
<i class="fas fa-arrow-left"></i>
Back to Library
</a>
</div>
<div class="game-container">
<div id="game"></div>
</div>
<div class="game-details-section">
<div class="game-info-panel">
<?php if ($gameMetadata && $gameMetadata['icon']): ?>
<div class="game-info-header">
<img src="<?php echo $gameMetadata['icon']; ?>" alt="Game Icon" class="game-info-icon">
<div class="game-info-title">
<h2><?php echo htmlspecialchars($gameMetadata['title'] ?? $name); ?></h2>
<span class="game-console"><?php echo $consoleName; ?></span>
</div>
</div>
<?php else: ?>
<div class="game-info-header">
<div class="game-info-title">
<h2><?php echo htmlspecialchars($name); ?></h2>
<span class="game-console"><?php echo $consoleName; ?></span>
</div>
</div>
<?php endif; ?>
<?php if ($gameMetadata): ?>
<table class="game-info-table">
<?php if (isset($gameMetadata['developer'])): ?>
<tr>
<td>Developer</td>
<td><?php echo htmlspecialchars($gameMetadata['developer']); ?></td>
</tr>
<?php endif; ?>
<?php if (isset($gameMetadata['publisher'])): ?>
<tr>
<td>Publisher</td>
<td><?php echo htmlspecialchars($gameMetadata['publisher']); ?></td>
</tr>
<?php endif; ?>
<?php if (isset($gameMetadata['genre'])): ?>
<tr>
<td>Genre</td>
<td><?php echo htmlspecialchars($gameMetadata['genre']); ?></td>
</tr>
<?php endif; ?>
<?php if (isset($gameMetadata['released'])): ?>
<tr>
<td>Released</td>
<td><?php echo htmlspecialchars($gameMetadata['released']); ?></td>
</tr>
<?php endif; ?>
</table>
<?php endif; ?>
<div class="game-player-controls">
<button class="btn" id="save-state-btn">
<i class="fas fa-save"></i>
Save State
</button>
<button class="btn btn-secondary" id="fullscreen-btn">
<i class="fas fa-expand"></i>
Fullscreen
</button>
</div>
<?php if (!empty($gameScreenshots)): ?>
<div class="game-screenshots-section">
<h3>Screenshots</h3>
<div class="game-screenshots">
<?php foreach($gameScreenshots as $index => $screenshot): ?>
<div class="game-screenshot" data-index="<?php echo $index; ?>">
<img src="<?php echo $screenshot; ?>" alt="Game Screenshot">
</div>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
</div>
<div class="save-states">
<div class="save-states-header">
<h2 class="save-states-title">Save States</h2>
</div>
<?php if(count($saveStates) > 0): ?>
<div class="save-slots">
<?php foreach($saveStates as $saveState): ?>
<div class="save-slot" data-slot="<?php echo $saveState['slot']; ?>">
<div class="save-slot-preview">
<?php if($saveState['screenshot']): ?>
<img src="<?php echo $saveState['screenshot']; ?>" alt="Save State <?php echo $saveState['slot']; ?>">
<?php else: ?>
<div class="save-slot-empty">
<i class="fas fa-save"></i>
<span>No Preview</span>
</div>
<?php endif; ?>
</div>
<div class="save-slot-info">
<h3 class="save-slot-title">Save Slot <?php echo $saveState['slot']; ?></h3>
<span class="save-slot-time"><?php echo date('M j, Y g:i A', $saveState['timestamp']); ?></span>
</div>
<div class="save-slot-actions">
<button class="save-slot-btn load-state" data-slot="<?php echo $saveState['slot']; ?>">
<i class="fas fa-play"></i>
Load
</button>
<button class="save-slot-btn delete-state" data-slot="<?php echo $saveState['slot']; ?>">
<i class="fas fa-trash"></i>
Delete
</button>
</div>
</div>
<?php endforeach; ?>
</div>
<?php else: ?>
<div class="empty-states">
<i class="fas fa-save"></i>
<h3>No Save States Yet</h3>
<p>Use the Save State button to save your progress.</p>
</div>
<?php endif; ?>
</div>
</div>
</div>
</main>
<!-- Save State Modal -->
<div class="modal-overlay" id="save-state-modal">
<div class="modal">
<div class="modal-header">
<h2 class="modal-title">Save Game State</h2>
<button class="close-modal" id="close-save-modal">&times;</button>
</div>
<div class="modal-body">
<form id="save-state-form">
<div class="form-group">
<label for="save-slot" class="form-label">Select Save Slot</label>
<select id="save-slot" name="slot" class="form-input">
<?php for($i = 1; $i <= 10; $i++): ?>
<option value="<?php echo $i; ?>">Slot <?php echo $i; ?></option>
<?php endfor; ?>
</select>
</div>
<div class="form-group">
<div class="save-slot-warning">
<i class="fas fa-exclamation-triangle"></i>
<p>If a save already exists in this slot, it will be overwritten.</p>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" id="cancel-save">Cancel</button>
<button class="btn" id="save-game-state">Save Game</button>
</div>
</div>
</div>
<!-- Add Profile Modal (same as in index.php) -->
<div class="modal-overlay" id="profile-modal">
<div class="modal">
<div class="modal-header">
<h2 class="modal-title">Create New Profile</h2>
<button class="close-modal" id="close-profile-modal">&times;</button>
</div>
<div class="modal-body">
<form id="profile-form" action="profile_action.php" method="post">
<div class="form-group">
<label for="profile-name" class="form-label">Profile Name</label>
<input type="text" id="profile-name" name="name" class="form-input" required>
</div>
<div class="form-group">
<label class="form-label">Select Avatar</label>
<div class="avatar-selector">
<?php for($i = 1; $i <= 8; $i++): ?>
<img src="img/avatars/avatar<?php echo $i; ?>.png"
class="avatar-option <?php echo ($i == 1) ? 'selected' : ''; ?>"
data-avatar="avatar<?php echo $i; ?>.png">
<?php endfor; ?>
</div>
<input type="hidden" id="selected-avatar" name="avatar" value="avatar1.png">
</div>
</form>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" id="cancel-profile">Cancel</button>
<button class="btn" id="save-profile">Create Profile</button>
</div>
</div>
</div>
<!-- Screenshot Modal -->
<div class="screenshot-modal" id="screenshot-modal">
<button class="screenshot-modal-close" id="close-screenshot-modal">×</button>
<img src="" alt="Game Screenshot" class="screenshot-modal-image" id="screenshot-modal-image">
</div>
<script type="text/javascript">
// EmulatorJS setup
EJS_player = '#game';
EJS_core = '<?php echo $console; ?>';
<?php
if (file_exists("./bios/$console.zip")) {
echo "EJS_biosUrl = './bios/$console.zip';";
}
?>
EJS_gameUrl = './roms/<?php echo $gameFile; ?>';
EJS_pathtodata = 'https://cdn.emulatorjs.org/stable/data/';
// Profile ID for save states
const profileId = '<?php echo $_SESSION['current_profile']; ?>';
const gameName = '<?php echo $gameFile; ?>';
// Save state handling
EJS_onSaveState = function(data) {
const stateBlob = new Blob([data.state], { type: "application/octet-stream" });
const screenshotBlob = new Blob([data.screenshot], { type: "image/png" });
const slotNumber = document.getElementById('save-slot').value;
const formData = new FormData();
formData.append("profile_id", profileId);
formData.append("game_name", gameName);
formData.append("slot", slotNumber);
formData.append("state", stateBlob, `${gameName}_${slotNumber}.state`);
formData.append("screenshot", screenshotBlob, `${gameName}_${slotNumber}.png`);
const xhr = new XMLHttpRequest();
xhr.open("POST", "save_state.php", true);
xhr.onload = function() {
if (xhr.status === 200) {
// Close modal and refresh page to show new save
document.getElementById('save-state-modal').classList.remove('active');
window.location.reload();
} else {
alert("Error saving game state: " + xhr.responseText);
}
};
xhr.send(formData);
};
// Load state handling (will be wired up in the UI)
function loadState(slot) {
const xhr = new XMLHttpRequest();
xhr.open("GET", `get_state.php?profile_id=${profileId}&game=${gameName}&slot=${slot}`, true);
xhr.responseType = "arraybuffer";
xhr.onload = function() {
if (xhr.status === 200) {
const loadedState = new Uint8Array(xhr.response);
EJS_emulator.gameManager.loadState(loadedState);
} else {
alert("Error loading save state");
}
};
xhr.onerror = function() {
alert("Request failed");
};
xhr.send();
}
</script>
<script src="https://cdn.emulatorjs.org/stable/data/loader.js"></script>
<script src="js/profiles.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Save state modal
const saveStateBtn = document.getElementById('save-state-btn');
const saveStateModal = document.getElementById('save-state-modal');
const closeSaveModal = document.getElementById('close-save-modal');
const cancelSave = document.getElementById('cancel-save');
const saveGameState = document.getElementById('save-game-state');
if (saveStateBtn && saveStateModal) {
// Open modal
saveStateBtn.addEventListener('click', function() {
saveStateModal.classList.add('active');
});
// Close modal
function closeSaveStateModal() {
saveStateModal.classList.remove('active');
}
if (closeSaveModal) closeSaveModal.addEventListener('click', closeSaveStateModal);
if (cancelSave) cancelSave.addEventListener('click', closeSaveStateModal);
// Save game state
if (saveGameState) {
saveGameState.addEventListener('click', function() {
// The actual saving is handled by EJS_onSaveState
// This just triggers the emulator's save state function
if (typeof EJS_emulator !== 'undefined') {
try {
EJS_emulator.gameManager.saveState();
} catch (e) {
alert('Error triggering save state: ' + e.message);
}
} else {
alert('Emulator not ready. Please wait and try again.');
}
});
}
}
// Load save state
const loadStateButtons = document.querySelectorAll('.load-state');
if (loadStateButtons.length > 0) {
loadStateButtons.forEach(button => {
button.addEventListener('click', function() {
const slot = this.dataset.slot;
if (typeof loadState === 'function') {
loadState(slot);
} else {
alert('Load state function not available');
}
});
});
}
// Delete save state
const deleteStateButtons = document.querySelectorAll('.delete-state');
if (deleteStateButtons.length > 0) {
deleteStateButtons.forEach(button => {
button.addEventListener('click', function() {
const slot = this.dataset.slot;
if (confirm('Are you sure you want to delete this save state?')) {
const xhr = new XMLHttpRequest();
xhr.open('POST', 'delete_state.php', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onload = function() {
if (xhr.status === 200) {
// Reload the page to update the save states list
window.location.reload();
} else {
alert('Error deleting save state: ' + xhr.responseText);
}
};
xhr.send(`profile_id=${profileId}&game=${gameName}&slot=${slot}`);
}
});
});
}
// Fullscreen button
const fullscreenBtn = document.getElementById('fullscreen-btn');
if (fullscreenBtn) {
fullscreenBtn.addEventListener('click', function() {
if (typeof EJS_emulator !== 'undefined') {
try {
EJS_emulator.setFullscreen(true);
} catch (e) {
alert('Error activating fullscreen: ' + e.message);
}
} else {
alert('Emulator not ready. Please wait and try again.');
}
});
}
// Screenshots modal
const screenshotItems = document.querySelectorAll('.game-screenshot');
const screenshotModal = document.getElementById('screenshot-modal');
const closeScreenshotModal = document.getElementById('close-screenshot-modal');
const screenshotModalImage = document.getElementById('screenshot-modal-image');
if (screenshotItems.length > 0 && screenshotModal) {
screenshotItems.forEach(item => {
item.addEventListener('click', function() {
const imgSrc = this.querySelector('img').src;
screenshotModalImage.src = imgSrc;
screenshotModal.classList.add('active');
});
});
// Close screenshot modal
function closeScreenshotModalFn() {
screenshotModal.classList.remove('active');
}
if (closeScreenshotModal) {
closeScreenshotModal.addEventListener('click', closeScreenshotModalFn);
}
// Close modal when clicking outside the image
screenshotModal.addEventListener('click', function(e) {
if (e.target === screenshotModal) {
closeScreenshotModalFn();
}
});
}
});
</script>
</body>
</html>