mirror of
https://github.com/rommapp/romm.git
synced 2026-07-01 08:16:21 +00:00
[ROMM-911] Fix playing multi disc games in emujs
This commit is contained in:
@@ -194,7 +194,6 @@ async def head_rom_content(
|
||||
request: Request,
|
||||
id: int,
|
||||
file_name: str,
|
||||
file_ids: list[int] | None = None,
|
||||
):
|
||||
"""Head rom content endpoint
|
||||
|
||||
@@ -213,13 +212,14 @@ async def head_rom_content(
|
||||
if not rom:
|
||||
raise RomNotFoundInDatabaseException(id)
|
||||
|
||||
file_ids = file_ids or []
|
||||
file_ids = request.query_params.get("file_ids") or ""
|
||||
file_ids = [int(f) for f in file_ids.split(",") if f]
|
||||
files = db_rom_handler.get_rom_files(rom.id)
|
||||
files = [f for f in files if f.id in file_ids or not file_ids]
|
||||
|
||||
if not rom.multi:
|
||||
# Serve the file directly in development mode for emulatorjs
|
||||
if DEV_MODE:
|
||||
# Serve the file directly in development mode for emulatorjs
|
||||
if DEV_MODE:
|
||||
if not rom.multi:
|
||||
rom_path = f"{LIBRARY_BASE_PATH}/{rom.full_path}"
|
||||
return FileResponse(
|
||||
path=rom_path,
|
||||
@@ -231,6 +231,28 @@ async def head_rom_content(
|
||||
},
|
||||
)
|
||||
|
||||
if len(files) == 1:
|
||||
file = files[0]
|
||||
rom_path = f"{LIBRARY_BASE_PATH}/{file.full_path}"
|
||||
return FileResponse(
|
||||
path=rom_path,
|
||||
filename=file.file_name,
|
||||
headers={
|
||||
"Content-Disposition": f'attachment; filename="{quote(file.file_name)}"',
|
||||
"Content-Type": "application/octet-stream",
|
||||
"Content-Length": str(file.file_size_bytes),
|
||||
},
|
||||
)
|
||||
|
||||
return Response(
|
||||
headers={
|
||||
"Content-Type": "application/zip",
|
||||
"Content-Disposition": f'attachment; filename="{quote(file_name)}.zip"',
|
||||
},
|
||||
)
|
||||
|
||||
# Otherwise proxy through nginx
|
||||
if not rom.multi:
|
||||
return FileRedirectResponse(
|
||||
download_path=Path(f"/library/{rom.full_path}"),
|
||||
filename=rom.fs_name,
|
||||
@@ -258,7 +280,6 @@ async def get_rom_content(
|
||||
request: Request,
|
||||
id: int,
|
||||
file_name: str,
|
||||
file_ids: list[int] | None = None,
|
||||
):
|
||||
"""Download rom endpoint (one single file or multiple zipped files for multi-part roms)
|
||||
|
||||
@@ -266,7 +287,6 @@ async def get_rom_content(
|
||||
request (Request): Fastapi Request object
|
||||
id (int): Rom internal id
|
||||
file_name: Zip file output name
|
||||
file_ids (list[int]): List of file ids to download for multi-part roms
|
||||
|
||||
Returns:
|
||||
FileResponse: Returns one file for single file roms
|
||||
@@ -281,15 +301,16 @@ async def get_rom_content(
|
||||
if not rom:
|
||||
raise RomNotFoundInDatabaseException(id)
|
||||
|
||||
file_ids = file_ids or []
|
||||
file_ids = request.query_params.get("file_ids") or ""
|
||||
file_ids = [int(f) for f in file_ids.split(",") if f]
|
||||
files = db_rom_handler.get_rom_files(rom.id)
|
||||
files = [f for f in files if f.id in file_ids or not file_ids]
|
||||
|
||||
log.info(f"User {current_username} is downloading {rom.fs_name}")
|
||||
|
||||
if not rom.multi:
|
||||
# Serve the file directly in development mode for emulatorjs
|
||||
if DEV_MODE:
|
||||
# Serve the file directly in development mode for emulatorjs
|
||||
if DEV_MODE:
|
||||
if not rom.multi:
|
||||
rom_path = f"{LIBRARY_BASE_PATH}/{rom.full_path}"
|
||||
return FileResponse(
|
||||
path=rom_path,
|
||||
@@ -301,6 +322,36 @@ async def get_rom_content(
|
||||
},
|
||||
)
|
||||
|
||||
if len(files) == 1:
|
||||
file = files[0]
|
||||
rom_path = f"{LIBRARY_BASE_PATH}/{file.full_path}"
|
||||
return FileResponse(
|
||||
path=rom_path,
|
||||
filename=file.file_name,
|
||||
headers={
|
||||
"Content-Disposition": f'attachment; filename="{quote(file.file_name)}"',
|
||||
"Content-Type": "application/octet-stream",
|
||||
"Content-Length": str(file.file_size_bytes),
|
||||
},
|
||||
)
|
||||
|
||||
content_lines = [
|
||||
ZipContentLine(
|
||||
crc32=f.crc_hash,
|
||||
size_bytes=(await Path(LIBRARY_BASE_PATH, f.full_path).stat()).st_size,
|
||||
encoded_location=quote(f"{LIBRARY_BASE_PATH}/{f.full_path}"),
|
||||
filename=f.full_path.replace(rom.full_path, ""),
|
||||
)
|
||||
for f in files
|
||||
]
|
||||
|
||||
return ZipResponse(
|
||||
content_lines=content_lines,
|
||||
filename=f"{quote(file_name)}.zip",
|
||||
)
|
||||
|
||||
# Otherwise proxy through nginx
|
||||
if not rom.multi:
|
||||
return FileRedirectResponse(
|
||||
download_path=Path(f"/library/{rom.full_path}"),
|
||||
filename=rom.fs_name,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"platforms": "Plattformen",
|
||||
"platforms-n": "{n} Plattform | {n} Plattformen",
|
||||
"firmware": "Firmware",
|
||||
"core": "Core",
|
||||
"games-n": "{n} Spiel | {n} Spiele",
|
||||
"collection": "Sammlung",
|
||||
"collections": "Sammlungen",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"platforms": "Platforms",
|
||||
"platforms-n": "{n} Platform | {n} Platforms",
|
||||
"firmware": "Firmware",
|
||||
"core": "Core",
|
||||
"games-n": "{n} Game | {n} Games",
|
||||
"collection": "Collection",
|
||||
"collections": "Collections",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"platforms": "Platforms",
|
||||
"platforms-n": "{n} Platform | {n} Platforms",
|
||||
"firmware": "Firmware",
|
||||
"core": "Core",
|
||||
"games-n": "{n} Game | {n} Games",
|
||||
"collection": "Collection",
|
||||
"collections": "Collections",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"platforms": "Plataformas",
|
||||
"platforms-n": "{n} Plataforma | {n} Plataformas",
|
||||
"firmware": "Firmware",
|
||||
"core": "Núcleo",
|
||||
"games-n": "{n} Juego | {n} Juegos",
|
||||
"collection": "Colección",
|
||||
"collections": "Colecciones",
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
"platform": "Plateforme",
|
||||
"platforms": "Plateformes",
|
||||
"platforms-n": "{n} Plateforme | {n} Plateformes",
|
||||
"firmware": "Firmware",
|
||||
"firmware": "Micrologiciel",
|
||||
"core": "Noyau",
|
||||
"games-n": "{n} Jeu | {n} Jeux",
|
||||
"collection": "Collection",
|
||||
"collections": "Collections",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"platforms": "플랫폼",
|
||||
"platforms-n": "{n}가지 플랫폼 | {n}가지 플랫폼",
|
||||
"firmware": "펌웨어",
|
||||
"core": "코어",
|
||||
"games-n": "게임 {n}개 | 게임 {n}개",
|
||||
"collection": "모음집",
|
||||
"collections": "모음집",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"platforms": "Plataformas",
|
||||
"platforms-n": "{n} Plataforma | {n} Plataformas",
|
||||
"firmware": "Firmware",
|
||||
"core": "Núcleo",
|
||||
"games-n": "{n} Jogo | {n} Jogos",
|
||||
"collection": "Coleção",
|
||||
"collections": "Coleções",
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
"platform": "Платформа",
|
||||
"platforms": "Платформы",
|
||||
"platforms-n": "{n} Платформа | {n} Платформы",
|
||||
"firmware": "Firmware",
|
||||
"firmware": "Прошивка",
|
||||
"core": "Ядро",
|
||||
"games-n": "{n} Игра | {n} Игры",
|
||||
"collection": "Коллекция",
|
||||
"collections": "Коллекции",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"platforms": "平台",
|
||||
"platforms-n": "{n} 平台 | {n} 平台",
|
||||
"firmware": "固件",
|
||||
"core": "核心",
|
||||
"games-n": "{n} 游戏 | {n} 游戏",
|
||||
"collection": "收藏",
|
||||
"collections": "收藏",
|
||||
|
||||
@@ -95,7 +95,9 @@ export function getDownloadPath({
|
||||
}) {
|
||||
const queryParams = new URLSearchParams();
|
||||
if (fileIDs.length > 0) {
|
||||
fileIDs.forEach((fileId) => queryParams.append("files", fileId.toString()));
|
||||
fileIDs.forEach((fileId) =>
|
||||
queryParams.append("file_ids", fileId.toString()),
|
||||
);
|
||||
}
|
||||
return `/api/roms/${rom.id}/content/${rom.fs_name}?${queryParams.toString()}`;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import RomListItem from "@/components/common/Game/ListItem.vue";
|
||||
import firmwareApi from "@/services/api/firmware";
|
||||
import romApi from "@/services/api/rom";
|
||||
import storeGalleryView from "@/stores/galleryView";
|
||||
import storeHeartbeat from "@/stores/heartbeat";
|
||||
import type { DetailedRom } from "@/stores/roms";
|
||||
import { formatBytes, formatTimestamp, getSupportedEJSCores } from "@/utils";
|
||||
import Player from "@/views/Player/EmulatorJS/Player.vue";
|
||||
@@ -21,13 +20,13 @@ const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
const galleryViewStore = storeGalleryView();
|
||||
const { defaultAspectRatioScreenshot } = storeToRefs(galleryViewStore);
|
||||
const heartbeat = storeHeartbeat();
|
||||
const rom = ref<DetailedRom | null>(null);
|
||||
const firmwareOptions = ref<FirmwareSchema[]>([]);
|
||||
const biosRef = ref<FirmwareSchema | null>(null);
|
||||
const saveRef = ref<SaveSchema | null>(null);
|
||||
const stateRef = ref<StateSchema | null>(null);
|
||||
const coreRef = ref<string | null>(null);
|
||||
const discRef = ref<number | null>(null);
|
||||
const supportedCores = ref<string[]>([]);
|
||||
const gameRunning = ref(false);
|
||||
const storedFSOP = localStorage.getItem("fullScreenOnPlay");
|
||||
@@ -117,6 +116,11 @@ onMounted(async () => {
|
||||
// Otherwise auto select first supported core
|
||||
coreRef.value = supportedCores.value[0];
|
||||
}
|
||||
|
||||
const storedDisc = localStorage.getItem(`player:${rom.value.id}:disc`);
|
||||
if (storedDisc) {
|
||||
discRef.value = parseInt(storedDisc);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -138,6 +142,7 @@ onMounted(async () => {
|
||||
:save="saveRef"
|
||||
:bios="biosRef"
|
||||
:core="coreRef"
|
||||
:disc="discRef"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
@@ -157,6 +162,22 @@ onMounted(async () => {
|
||||
<v-divider class="my-4" />
|
||||
<rom-list-item :rom="rom" with-filename with-size />
|
||||
<v-divider class="my-4" />
|
||||
<v-select
|
||||
v-if="rom.multi"
|
||||
v-model="discRef"
|
||||
class="my-1"
|
||||
hide-details
|
||||
rounded="0"
|
||||
variant="outlined"
|
||||
clearable
|
||||
:label="t('rom.file')"
|
||||
:items="
|
||||
rom.files.map((f) => ({
|
||||
title: f.file_name,
|
||||
value: f.id,
|
||||
}))
|
||||
"
|
||||
/>
|
||||
<v-select
|
||||
v-if="supportedCores.length > 1"
|
||||
:disabled="gameRunning"
|
||||
@@ -166,7 +187,7 @@ onMounted(async () => {
|
||||
hide-details
|
||||
variant="outlined"
|
||||
clearable
|
||||
label="Core"
|
||||
:label="t('common.core')"
|
||||
:items="
|
||||
supportedCores.map((c) => ({
|
||||
title: c,
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
areThreadsRequiredForEJSCore,
|
||||
getSupportedEJSCores,
|
||||
getControlSchemeForPlatform,
|
||||
getDownloadPath,
|
||||
} from "@/utils";
|
||||
import { onBeforeUnmount, onMounted, ref } from "vue";
|
||||
|
||||
@@ -17,6 +18,7 @@ const props = defineProps<{
|
||||
state: StateSchema | null;
|
||||
bios: FirmwareSchema | null;
|
||||
core: string | null;
|
||||
disc: number | null;
|
||||
}>();
|
||||
const romRef = ref<DetailedRom>(props.rom);
|
||||
const saveRef = ref<SaveSchema | null>(props.save);
|
||||
@@ -62,7 +64,10 @@ window.EJS_controlScheme = getControlSchemeForPlatform(
|
||||
);
|
||||
window.EJS_threads = areThreadsRequiredForEJSCore(window.EJS_core);
|
||||
window.EJS_gameID = romRef.value.id;
|
||||
window.EJS_gameUrl = `/api/roms/${romRef.value.id}/content/${romRef.value.fs_name}`;
|
||||
window.EJS_gameUrl = getDownloadPath({
|
||||
rom: romRef.value,
|
||||
fileIDs: props.disc ? [props.disc] : [],
|
||||
});
|
||||
window.EJS_biosUrl = props.bios
|
||||
? `/api/firmware/${props.bios.id}/content/${props.bios.file_name}`
|
||||
: "";
|
||||
@@ -114,6 +119,12 @@ onMounted(() => {
|
||||
} else {
|
||||
localStorage.removeItem(`player:${props.rom.platform_slug}:core`);
|
||||
}
|
||||
|
||||
if (props.disc) {
|
||||
localStorage.setItem(`player:${props.rom.id}:disc`, props.disc.toString());
|
||||
} else {
|
||||
localStorage.removeItem(`player:${props.rom.id}:disc`);
|
||||
}
|
||||
});
|
||||
|
||||
function buildStateName(): string {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import RomListItem from "@/components/common/Game/ListItem.vue";
|
||||
import romApi from "@/services/api/rom";
|
||||
import storeHeartbeat from "@/stores/heartbeat";
|
||||
import storeGalleryView from "@/stores/galleryView";
|
||||
import type { DetailedRom } from "@/stores/roms";
|
||||
import { isNull } from "lodash";
|
||||
@@ -9,6 +8,7 @@ import { storeToRefs } from "pinia";
|
||||
import { nextTick, onMounted, ref } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { getDownloadPath } from "@/utils";
|
||||
|
||||
const RUFFLE_VERSION = "0.1.0-nightly.2024.12.28";
|
||||
|
||||
@@ -16,7 +16,6 @@ const RUFFLE_VERSION = "0.1.0-nightly.2024.12.28";
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
const galleryViewStore = storeGalleryView();
|
||||
const heartbeat = storeHeartbeat();
|
||||
const { defaultAspectRatioScreenshot } = storeToRefs(galleryViewStore);
|
||||
const rom = ref<DetailedRom | null>(null);
|
||||
const gameRunning = ref(false);
|
||||
@@ -36,6 +35,8 @@ function onPlay() {
|
||||
gameRunning.value = true;
|
||||
|
||||
nextTick(() => {
|
||||
if (!rom.value) return;
|
||||
|
||||
const ruffle = window.RufflePlayer.newest();
|
||||
const player = ruffle.createPlayer();
|
||||
const container = document.getElementById("game");
|
||||
@@ -46,7 +47,7 @@ function onPlay() {
|
||||
backgroundColor: "#0D1117",
|
||||
openUrlMode: "confirm",
|
||||
publicPath: "/assets/ruffle/",
|
||||
url: `/api/roms/${rom.value?.id}/content/${rom.value?.fs_name}`,
|
||||
url: getDownloadPath({ rom: rom.value }),
|
||||
});
|
||||
player.style.width = "100%";
|
||||
player.style.height = "100%";
|
||||
@@ -73,8 +74,7 @@ onMounted(async () => {
|
||||
|
||||
script.onerror = () => {
|
||||
const fallbackScript = document.createElement("script");
|
||||
fallbackScript.src =
|
||||
"https://unpkg.com/@ruffle-rs/ruffle@${RUFFLE_VERSION}/ruffle.js";
|
||||
fallbackScript.src = `https://unpkg.com/@ruffle-rs/ruffle@${RUFFLE_VERSION}/ruffle.js`;
|
||||
document.body.appendChild(fallbackScript);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user