Skip to content
This repository has been archived by the owner on Jul 27, 2022. It is now read-only.

Commit

Permalink
Merge pull request #62 from stromcon/feature/shaders-healthcheck
Browse files Browse the repository at this point in the history
Read Ryujinx logs on shaders compile to make a submission healthcheck
  • Loading branch information
CapitaineJSparrow authored May 24, 2021
2 parents 2f5a964 + faed95b commit dc36335
Show file tree
Hide file tree
Showing 6 changed files with 214 additions and 85 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "emusak",
"productName": "emusak",
"version": "1.0.53",
"version": "1.0.54",
"description": "Saves, shaders, firmwares and keys manager for switch emulators",
"main": ".webpack/main",
"repository": "https://github.com/stromcon/emusak-ui.git",
Expand Down
128 changes: 102 additions & 26 deletions src/service/ryujinx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import zip from "adm-zip";
import {getEmusakProdKeys, PATHS, postEmusakShaderShare} from "../api/emusak";
import Swal from "sweetalert2";
import rimraf from "rimraf";
import {downloadFileWithProgress} from "./http";
import { spawn } from "child_process";
import { downloadFileWithProgress } from "./http";

export interface IryujinxLocalShaderConfig {
titleID: string;
Expand Down Expand Up @@ -134,38 +135,113 @@ export const packShaders = async (config: IRyujinxConfig, titleID: string): Prom
return zipPath;
}

export const shareShader = async (config: IRyujinxConfig, titleID: string, GameName: string, localCount: number = 0, emusakCount: number = 0) => {
const asyncReadRyujinxProcess = async (ryuBinPath: string): Promise<any> => new Promise((resolve, reject) => {
const child = spawn(ryuBinPath);
let fullData = '';
let ranTitleId: string;
let compiledShadersCount: number;

child.on('exit', () => resolve(false));
child.stdout.on('data', (data) => {
fullData += data;
const titleIdMatch = /for Title (.+)/gi.exec(fullData);
const shaderCountMatch = /Shader cache loaded (\d+) entries/gi.exec(fullData);

if (titleIdMatch && titleIdMatch.length >= 2) {
ranTitleId = titleIdMatch[1].trim();
}

if (shaderCountMatch && shaderCountMatch.length >= 2) {
compiledShadersCount = parseInt(shaderCountMatch[1].trim());
}

if (ranTitleId && (compiledShadersCount || compiledShadersCount === 0)) {
resolve({ ranTitleId, compiledShadersCount });
child.kill();
}
});
child.stdout.on('error', () => reject(false));
})

export const shareShader = async (
config: IRyujinxConfig,
titleID: string,
gameName: string,
localCount: number = 0,
emusakCount: number = 0,
onRyujinxOpen: Function,
onRyujinxClose: Function,
) => {

if (process.platform !== "win32") {
Swal.fire({
icon: 'error',
text: "This feature is not available for linux, i'll provide an update for you soon !"
})
return;
}

const key = `ryu-share-${titleID}-${localCount}`;

if (localStorage.getItem(key)) {
Swal.fire('error', 'You already shared those shaders, thanks !');
return false;
}

if (!localStorage.getItem('shaders-share-warning')) {
const { value } = await Swal.fire({
title: 'Notice',
showCancelButton: true,
html: `
Please make sure to only share shaders that are working for you and do no "just click the button" if you are not 100% sure.
<br />
<br />
Remember it takes a lot of time to validate shaders since we are downloading them and testing ingame before upload to emusak, this goes to everyone
<br />
<br />
<b>Please do NOT merge two separate Shader caches (Files), this causes Shader cache corruption ~ Mid game crash. Using one as a base and adding more through playing is fine</b>
`,
});

if (!value) {
return false;
}
const { value } = await Swal.fire({
icon: "info",
html: `To be sure your submission is valid, emusak will do a sanity check. Ryujinx will be open and you'll have to run <b>${gameName}</b> to make sure no errors are encountered while compiling shaders before share them to everyone. <br /> <br /> <b style="color: #e74c3c">Please close any opened Ryujinx instance before continue</b>. Emusak will automatically close Ryujinx when shaders compilation is finished`,
confirmButtonText: "I'm ready, let's go !",
cancelButtonText: "Later",
showCancelButton: true
});

if (!value) {
return;
}

const ryuConfPath = getRyujinxPath(config, 'Config.json');
let ryujinxConfig = JSON.parse((await fs.promises.readFile(ryuConfPath)).toString());
ryujinxConfig['logging_enable_error'] = true;
ryujinxConfig['logging_enable_guest'] = true;
ryujinxConfig['logging_enable_info'] = true;
ryujinxConfig['logging_enable_stub'] = true;
ryujinxConfig['logging_enable_warn'] = true;
ryujinxConfig['logging_enable_fs_access_log'] = true;
await fs.promises.writeFile(ryuConfPath, JSON.stringify(ryujinxConfig, null, 2), 'utf-8');

const ryuBinary = path.resolve(config.path, 'Ryujinx.exe');
onRyujinxOpen();
const result = await asyncReadRyujinxProcess(ryuBinary).catch(() => false);
onRyujinxClose();

if (!result) {
await Swal.fire({
icon: 'error',
text: 'You either closed Ryujinx before running the game or Ryujinx crashed'
})
return;
}

console.log({ result, titleID })
if (result.ranTitleId !== titleID.toUpperCase()) {
await Swal.fire({
icon: 'error',
text: `You ran the wrong game ! You had to launch ${gameName}`
})
return;
}

localStorage.setItem('shaders-share-warning', 'true');
if (result.compiledShadersCount !== localCount) {
await Swal.fire({
icon: 'error',
text: `You have ${localCount} on your cache but Ryujinx compiled ${result.compiledShadersCount}. That means some shaders are either corrupted or rejected. This probably not your fault, that maybe means you build shaders since a long time ago and Ryujinx choose to reject them because they changed something in the code and the game probably run fine. But because we share shaders to everyone, we choose to reject your submission to avoid any conflict because we are not sure at 100% this will not cause issue to anyone`
})
return;
}

const path = await packShaders(config, titleID);
electron.ipcRenderer.send('shadersBuffer', path);
const shadersPath = await packShaders(config, titleID);
electron.ipcRenderer.send('shadersBuffer', shadersPath);
electron.ipcRenderer.on('uploaded', async (_, body) => {

// IPC can trigger multiple time for same event, we just want to be sure it triggers only one time
Expand All @@ -176,15 +252,15 @@ export const shareShader = async (config: IRyujinxConfig, titleID: string, GameN
paths.push(key);

const json = JSON.parse(body);
const message = `Hey there, I'm sharing my shaders using emusak for **${GameName}** (${titleID.toUpperCase()}). I have ${localCount} shaders while emusak has ${emusakCount} shaders. Download them from here : \`${btoa(json.data.file.url.short)}\``;
const message = `Hey there, I'm sharing my shaders using emusak for **${gameName}** (${titleID.toUpperCase()}). I have ${localCount} shaders while emusak has ${emusakCount} shaders. Download them from here : \`${btoa(json.data.file.url.short)}\``;
const response = await postEmusakShaderShare(message);
try {
rimraf(path, () => {});
rimraf(shadersPath, () => {});
} catch(e) {}

if (response.status === 200) {
localStorage.setItem(key, 'true')
await fs.promises.unlink(path);
await fs.promises.unlink(shadersPath);
Swal.fire('success', 'You shaders has been submitted ! You can find them in #ryu-shaders channel. Once approved it will be shared to everyone !');
} else {
Swal.fire('error', 'You shared too many shaders !');
Expand Down
4 changes: 1 addition & 3 deletions src/ui/changelog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,7 @@ const Changelog = () => {
<h1 style={{ textAlign: 'center' }}>What's new ? v{version}</h1>
<br />
<ul style={{ marginLeft: 20 }}>
<li>Update firmware version</li>
<li>CDN updates to download firmware</li>
<li>Added an exponential backoff strategy when you fetch emusak saves state or download keys</li>
<li>When you click on the "Share Shaders" button, Emusak will now ask to open Ryujinx and open the game to read Ryujinx logs and be sure no errors occurred. This is designed to reduce workload on our side, because validation submissions is very time consuming</li>
</ul>
<br/>
<p>
Expand Down
6 changes: 1 addition & 5 deletions src/ui/page/ryujinx/RyujinxHome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,7 @@ const RyujinxHome = () => {
setEmusakShadersCount(loweredKeysObject);
});

getEmusakSaves().then(s => {
setEmusakSaves(s)
console.log(s);
});

getEmusakSaves().then(s => setEmusakSaves(s));
getEmusakFirmwareVersion().then(v => setEmusakFirmwareVersion(v));
}, []);

Expand Down
157 changes: 108 additions & 49 deletions src/ui/page/ryujinx/gamelist/ShadersList.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from "react";
import React, {useState} from "react";
import TableRow from "@material-ui/core/TableRow";
import TableCell from "@material-ui/core/TableCell";
import {Button} from "@material-ui/core";
import {Button, CircularProgress, LinearProgress, makeStyles, Modal} from "@material-ui/core";
import {shareShader} from "../../../../service/ryujinx";
import TableBody from "@material-ui/core/TableBody";
import {IRyujinxConfig} from "../../../../model/RyujinxModel";
Expand All @@ -18,6 +18,18 @@ interface TableProps {
emusakShadersCount: IEmusakShadersCount;
}

const useStyles = makeStyles((theme) => ({
modal: {
backgroundColor: theme.palette.background.paper,
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
padding: 20,
width: '50%'
},
}));

export default ({
games,
extractNameFromID,
Expand All @@ -27,53 +39,100 @@ export default ({
config,
threshold,
triggerShadersDownload
}: TableProps) => (
<TableBody>
{
games
.filter(titleId => titleId != '0000000000000000')
.map(titleId => ({ titleId, name: extractNameFromID(titleId) }))
.sort((a, b) => a.name.localeCompare(b.name))
.map(({ titleId, name }) => {
const localShadersCount = extractLocalShaderCount(titleId);
const emusakCount: number = emusakShadersCount[titleId] || 0;
}: TableProps) => {
const classes = useStyles();
const [modalOpen, setModalOpen] = useState(false);
const [currentGame, setCurrentGame] = useState('');

if (filter && name.toLowerCase().search(filter.toLowerCase()) === -1) {
return null;
}
const onShareShaderButtonClick = (config: IRyujinxConfig, titleId: string, name: string, localShadersCount: number, emusakCount: number) => {
setCurrentGame(name);
shareShader(
config,
titleId,
name,
localShadersCount,
emusakCount,
() => setModalOpen(true),
() => setModalOpen(false),
);
}

return (
<TableRow key={`${titleId}-${config.path}`}>
<TableCell>
<span>{name}</span>
<br />
<span><small>{titleId.toUpperCase()}</small></span>
</TableCell>
<TableCell>{emusakCount > 0 ? emusakCount : 'No remote shaders'}</TableCell>
<TableCell>{localShadersCount === 0 ? 'No local shaders': localShadersCount}</TableCell>
<TableCell>
<Button
disabled={!emusakShadersCount[titleId] || (localShadersCount >= emusakCount)}
onClick={() => triggerShadersDownload(titleId, localShadersCount)}
variant="contained"
color="primary"
>
Download shaders
</Button>
&nbsp;
&nbsp;
<Button
disabled={!localShadersCount || (localShadersCount <= (emusakCount + threshold))}
onClick={() => shareShader(config, titleId, name, localShadersCount, emusakCount)}
variant="contained"
color="primary"
return (
<TableBody>
{
games
.filter(titleId => titleId != '0000000000000000')
.map(titleId => ({titleId, name: extractNameFromID(titleId)}))
.sort((a, b) => a.name.localeCompare(b.name))
.map(({titleId, name}) => {
const localShadersCount = extractLocalShaderCount(titleId);
const emusakCount: number = emusakShadersCount[titleId] || 0;

if (filter && name.toLowerCase().search(filter.toLowerCase()) === -1) {
return null;
}

return (
<>
<Modal
open={modalOpen}
onClose={() => {}}
aria-labelledby="simple-modal-title"
aria-describedby="simple-modal-description"
>
Share shaders
</Button>
</TableCell>
</TableRow>
)
})
}
</TableBody>
)
<div className={classes.modal}>
<h2 style={{textAlign: 'center', fontWeight: 'normal'}}>Please run <b>{currentGame}</b> in Ryujinx !</h2>

<ul style={{listStyle: 'none'}}>
<li style={{ display: 'flex', alignItems: 'center' }}>
<span> <CircularProgress color="secondary" size={30} /></span>
&nbsp;
&nbsp;
<span> Waiting for <b>{currentGame}</b> to be run in ryujinx</span>
</li>
<li style={{ display: 'flex', alignItems: 'center' }}>
<span> <CircularProgress color="secondary" size={30} /></span>
&nbsp;
&nbsp;
<span> Waiting for shaders to be compiled</span>
</li>
</ul>
</div>
</Modal>

<TableRow key={`${titleId}-${config.path}`}>
<TableCell>
<span>{name}</span>
<br/>
<span><small>{titleId.toUpperCase()}</small></span>
</TableCell>
<TableCell>{emusakCount > 0 ? emusakCount : 'No remote shaders'}</TableCell>
<TableCell>{localShadersCount === 0 ? 'No local shaders' : localShadersCount}</TableCell>
<TableCell>
<Button
disabled={!emusakShadersCount[titleId] || (localShadersCount >= emusakCount)}
onClick={() => triggerShadersDownload(titleId, localShadersCount)}
variant="contained"
color="primary"
>
Download shaders
</Button>
&nbsp;
&nbsp;
<Button
disabled={!localShadersCount || (localShadersCount <= (emusakCount + threshold))}
onClick={() => onShareShaderButtonClick(config, titleId, name, localShadersCount, emusakCount)}
variant="contained"
color="primary"
>
Share shaders
</Button>
</TableCell>
</TableRow>
</>
)
})
}
</TableBody>
);
}

0 comments on commit dc36335

Please sign in to comment.