Added files
This commit is contained in:
52
css/_style.css
Normal file
52
css/_style.css
Normal file
@@ -0,0 +1,52 @@
|
||||
:root {
|
||||
--background-color: #1c1c1c;
|
||||
--primary: #e3e3e3;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--background-color);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
main {
|
||||
display: grid;
|
||||
min-height: 100vh;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
header {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
/*#file {
|
||||
display: block;
|
||||
border: 1px solid white;
|
||||
border-radius: 100vw;
|
||||
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
|
||||
background-color: white;
|
||||
}*/
|
||||
|
||||
#input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#upload {
|
||||
display: block;
|
||||
border: 1px solid var(--primary);
|
||||
border-radius: 100vw;
|
||||
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
|
||||
background-color: var(--primary);
|
||||
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#upload > span {
|
||||
font-size: 2em;
|
||||
color: var(--background-color);
|
||||
}
|
||||
190
css/style.css
Normal file
190
css/style.css
Normal file
@@ -0,0 +1,190 @@
|
||||
:root {
|
||||
--col-dark: #1C1C1C;
|
||||
--col-light: #E3E3E3;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Lato';
|
||||
src: url('../fonts/Lato-Regular.ttf');
|
||||
src: url('../fonts/Lato-Regular.ttf') format('truetype');
|
||||
}
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
background: var(--col-dark);
|
||||
}
|
||||
|
||||
section {
|
||||
color: var(--col-light);
|
||||
font-family: Lato;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--col-light);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a[rel="author"] {
|
||||
justify-self: end;
|
||||
}
|
||||
a[rel="author"]::before {
|
||||
content: " - ";
|
||||
}
|
||||
|
||||
main {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
section {
|
||||
display: grid;
|
||||
place-content: center;
|
||||
place-items: center;
|
||||
margin: 0 4rem;
|
||||
}
|
||||
|
||||
section * {
|
||||
margin: .2rem 0;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.btn.cbtn {
|
||||
display: grid;
|
||||
place-content: center;
|
||||
place-items: center;
|
||||
position: relative;
|
||||
|
||||
padding: 2.2rem;
|
||||
|
||||
width: 0;
|
||||
height: 0;
|
||||
|
||||
border: none;
|
||||
background: none;
|
||||
font: unset;
|
||||
|
||||
border-radius: 100vw;
|
||||
}
|
||||
.btn.cbtn > i {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
.btn.cbtn > i::after {
|
||||
content: "";
|
||||
display: grid;
|
||||
place-content: center;
|
||||
position: absolute;
|
||||
|
||||
z-index: 1000;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
padding: 0rem;
|
||||
|
||||
background-color: var(--col-light);
|
||||
color: var(--col-dark);
|
||||
|
||||
width: 0;
|
||||
height: 0;
|
||||
|
||||
border-radius: 100vw;
|
||||
transition: padding .2s, background-color .2s, color .2s;
|
||||
}
|
||||
.btn.cbtn:hover > i::after,
|
||||
.btn.cbtn.dragover > i::after {
|
||||
padding: var(--padding);
|
||||
}
|
||||
|
||||
@keyframes cbtn-click {
|
||||
from {
|
||||
padding: 0rem;
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
padding: calc(var(--padding) * 1.3);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
.btn.cbtn > .click {
|
||||
position: absolute;
|
||||
padding: 0rem;
|
||||
opacity: 1;
|
||||
z-index: 10000;
|
||||
|
||||
border-radius: 100vw;
|
||||
width: 0;
|
||||
height: 0;
|
||||
|
||||
/*box-shadow: inset 0 0 2px #FFF;*/
|
||||
background: #DDD8;
|
||||
border: 4px solid #fff;
|
||||
|
||||
animation: cbtn-click .2s;
|
||||
}
|
||||
|
||||
.btn.cbtn {
|
||||
--padding: 5vmin;
|
||||
padding: var(--padding);
|
||||
font-size: 3vmin;
|
||||
}
|
||||
|
||||
.btn.cbtn.medium {
|
||||
--padding: 5vmin;
|
||||
padding: var(--padding);
|
||||
font-size: 3vmin;
|
||||
}
|
||||
|
||||
.btn.cbtn.big {
|
||||
--padding: 30vmin;
|
||||
padding: var(--padding);
|
||||
font-size: 8vmin;
|
||||
}
|
||||
|
||||
.btn.cbtn.overlap {
|
||||
margin: -1vmin;
|
||||
}
|
||||
|
||||
footer {
|
||||
display: grid;
|
||||
grid-auto-columns: min-content;
|
||||
grid-auto-flow: column;
|
||||
padding: 3vmin;
|
||||
}
|
||||
|
||||
#input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#button_text {
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
#button_text::before, #button_text::after {
|
||||
content: "Drag & Drop Yuzu Executable here";
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.dragover {
|
||||
--col-dark: #1C1C1C;
|
||||
--col-light: #82DD90;
|
||||
}
|
||||
BIN
fonts/Lato-Regular.ttf
Normal file
BIN
fonts/Lato-Regular.ttf
Normal file
Binary file not shown.
32
index.html
Normal file
32
index.html
Normal file
@@ -0,0 +1,32 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>YUZU Patcher</title>
|
||||
<link rel="stylesheet" href="./css/style.css">
|
||||
<script src="./js/FileSaver.js"></script>
|
||||
<script src="./js/main.js"></script>
|
||||
<script src="./js/button.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<section>
|
||||
<h1>A yuzu Emulator fork patcher</h1>
|
||||
<p>
|
||||
yuzu forks often change the application and engine name given to Vulkan.
|
||||
<br>
|
||||
This has the side effect of not applying a driver level patch by AMD for RDNA3 graphic cards, causing visual issues.
|
||||
<br>
|
||||
This tool replaces the application and engine name given to Vulkan with yuzu's.
|
||||
</p>
|
||||
<a rel="author">Sunpy</a>
|
||||
<a id="upload" class="big btn cbtn" style="margin-top:6vmin">
|
||||
<i id="button_text"></i>
|
||||
<input type="file" id="input" />
|
||||
</a>
|
||||
</section>
|
||||
</main>
|
||||
<footer></footer>
|
||||
</body>
|
||||
</html>
|
||||
172
js/FileSaver.js
Normal file
172
js/FileSaver.js
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* FileSaver.js
|
||||
* A saveAs() FileSaver implementation.
|
||||
*
|
||||
* By Eli Grey, http://eligrey.com
|
||||
*
|
||||
* License : https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md (MIT)
|
||||
* source : http://purl.eligrey.com/github/FileSaver.js
|
||||
*/
|
||||
|
||||
// The one and only way of getting global scope in all environments
|
||||
// https://stackoverflow.com/q/3277182/1008999
|
||||
var _global = typeof window === 'object' && window.window === window
|
||||
? window : typeof self === 'object' && self.self === self
|
||||
? self : typeof global === 'object' && global.global === global
|
||||
? global
|
||||
: this
|
||||
|
||||
function bom (blob, opts) {
|
||||
if (typeof opts === 'undefined') opts = { autoBom: false }
|
||||
else if (typeof opts !== 'object') {
|
||||
console.warn('Deprecated: Expected third argument to be a object')
|
||||
opts = { autoBom: !opts }
|
||||
}
|
||||
|
||||
// prepend BOM for UTF-8 XML and text/* types (including HTML)
|
||||
// note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF
|
||||
if (opts.autoBom && /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) {
|
||||
return new Blob([String.fromCharCode(0xFEFF), blob], { type: blob.type })
|
||||
}
|
||||
return blob
|
||||
}
|
||||
|
||||
function download (url, name, opts) {
|
||||
var xhr = new XMLHttpRequest()
|
||||
xhr.open('GET', url)
|
||||
xhr.responseType = 'blob'
|
||||
xhr.onload = function () {
|
||||
saveAs(xhr.response, name, opts)
|
||||
}
|
||||
xhr.onerror = function () {
|
||||
console.error('could not download file')
|
||||
}
|
||||
xhr.send()
|
||||
}
|
||||
|
||||
function corsEnabled (url) {
|
||||
var xhr = new XMLHttpRequest()
|
||||
// use sync to avoid popup blocker
|
||||
xhr.open('HEAD', url, false)
|
||||
try {
|
||||
xhr.send()
|
||||
} catch (e) {}
|
||||
return xhr.status >= 200 && xhr.status <= 299
|
||||
}
|
||||
|
||||
// `a.click()` doesn't work for all browsers (#465)
|
||||
function click (node) {
|
||||
try {
|
||||
node.dispatchEvent(new MouseEvent('click'))
|
||||
} catch (e) {
|
||||
var evt = document.createEvent('MouseEvents')
|
||||
evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80,
|
||||
20, false, false, false, false, 0, null)
|
||||
node.dispatchEvent(evt)
|
||||
}
|
||||
}
|
||||
|
||||
// Detect WebView inside a native macOS app by ruling out all browsers
|
||||
// We just need to check for 'Safari' because all other browsers (besides Firefox) include that too
|
||||
// https://www.whatismybrowser.com/guides/the-latest-user-agent/macos
|
||||
var isMacOSWebView = _global.navigator && /Macintosh/.test(navigator.userAgent) && /AppleWebKit/.test(navigator.userAgent) && !/Safari/.test(navigator.userAgent)
|
||||
|
||||
var saveAs = _global.saveAs || (
|
||||
// probably in some web worker
|
||||
(typeof window !== 'object' || window !== _global)
|
||||
? function saveAs () { /* noop */ }
|
||||
|
||||
// Use download attribute first if possible (#193 Lumia mobile) unless this is a macOS WebView
|
||||
: ('download' in HTMLAnchorElement.prototype && !isMacOSWebView)
|
||||
? function saveAs (blob, name, opts) {
|
||||
var URL = _global.URL || _global.webkitURL
|
||||
// Namespace is used to prevent conflict w/ Chrome Poper Blocker extension (Issue #561)
|
||||
var a = document.createElementNS('http://www.w3.org/1999/xhtml', 'a')
|
||||
name = name || blob.name || 'download'
|
||||
|
||||
a.download = name
|
||||
a.rel = 'noopener' // tabnabbing
|
||||
|
||||
// TODO: detect chrome extensions & packaged apps
|
||||
// a.target = '_blank'
|
||||
|
||||
if (typeof blob === 'string') {
|
||||
// Support regular links
|
||||
a.href = blob
|
||||
if (a.origin !== location.origin) {
|
||||
corsEnabled(a.href)
|
||||
? download(blob, name, opts)
|
||||
: click(a, a.target = '_blank')
|
||||
} else {
|
||||
click(a)
|
||||
}
|
||||
} else {
|
||||
// Support blobs
|
||||
a.href = URL.createObjectURL(blob)
|
||||
setTimeout(function () { URL.revokeObjectURL(a.href) }, 4E4) // 40s
|
||||
setTimeout(function () { click(a) }, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// Use msSaveOrOpenBlob as a second approach
|
||||
: 'msSaveOrOpenBlob' in navigator
|
||||
? function saveAs (blob, name, opts) {
|
||||
name = name || blob.name || 'download'
|
||||
|
||||
if (typeof blob === 'string') {
|
||||
if (corsEnabled(blob)) {
|
||||
download(blob, name, opts)
|
||||
} else {
|
||||
var a = document.createElement('a')
|
||||
a.href = blob
|
||||
a.target = '_blank'
|
||||
setTimeout(function () { click(a) })
|
||||
}
|
||||
} else {
|
||||
navigator.msSaveOrOpenBlob(bom(blob, opts), name)
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to using FileReader and a popup
|
||||
: function saveAs (blob, name, opts, popup) {
|
||||
// Open a popup immediately do go around popup blocker
|
||||
// Mostly only available on user interaction and the fileReader is async so...
|
||||
popup = popup || open('', '_blank')
|
||||
if (popup) {
|
||||
popup.document.title =
|
||||
popup.document.body.innerText = 'downloading...'
|
||||
}
|
||||
|
||||
if (typeof blob === 'string') return download(blob, name, opts)
|
||||
|
||||
var force = blob.type === 'application/octet-stream'
|
||||
var isSafari = /constructor/i.test(_global.HTMLElement) || _global.safari
|
||||
var isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent)
|
||||
|
||||
if ((isChromeIOS || (force && isSafari) || isMacOSWebView) && typeof FileReader !== 'undefined') {
|
||||
// Safari doesn't allow downloading of blob URLs
|
||||
var reader = new FileReader()
|
||||
reader.onloadend = function () {
|
||||
var url = reader.result
|
||||
url = isChromeIOS ? url : url.replace(/^data:[^;]*;/, 'data:attachment/file;')
|
||||
if (popup) popup.location.href = url
|
||||
else location = url
|
||||
popup = null // reverse-tabnabbing #460
|
||||
}
|
||||
reader.readAsDataURL(blob)
|
||||
} else {
|
||||
var URL = _global.URL || _global.webkitURL
|
||||
var url = URL.createObjectURL(blob)
|
||||
if (popup) popup.location = url
|
||||
else location.href = url
|
||||
popup = null // reverse-tabnabbing #460
|
||||
setTimeout(function () { URL.revokeObjectURL(url) }, 4E4) // 40s
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
_global.saveAs = saveAs.saveAs = saveAs
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
module.exports = saveAs;
|
||||
}
|
||||
14
js/button.js
Normal file
14
js/button.js
Normal file
@@ -0,0 +1,14 @@
|
||||
addEventListener("load", () => {
|
||||
const onAnimationFinished = (e) => {
|
||||
e.target.remove();
|
||||
};
|
||||
const onClick = (e, btn) => {
|
||||
const clickElement = document.createElement("div");
|
||||
clickElement.className = "click";
|
||||
clickElement.addEventListener("animationend", onAnimationFinished);
|
||||
btn.appendChild(clickElement);
|
||||
};
|
||||
for (const btn of document.getElementsByClassName("btn")) {
|
||||
btn.addEventListener("click", (e) => onClick(e, btn) );
|
||||
};
|
||||
});
|
||||
109
js/main.js
Normal file
109
js/main.js
Normal file
@@ -0,0 +1,109 @@
|
||||
const VK_PATTERN = (new TextEncoder()).encode("vkDestroyInstance");
|
||||
const YUZU_PATTERN = (new TextEncoder()).encode("yuzu Emulator");
|
||||
|
||||
addEventListener("load", () => {
|
||||
const input = document.getElementById("input");
|
||||
const button = document.getElementById("upload");
|
||||
|
||||
input.addEventListener("change", (e) => {
|
||||
if (e.target.files.length) {
|
||||
handleFile(e.target.files[0]);
|
||||
}
|
||||
});
|
||||
|
||||
button.addEventListener("click", () => input.click());
|
||||
button.addEventListener("drop", (e) => {
|
||||
e.preventDefault();
|
||||
button.classList.remove("dragover");
|
||||
if (e.dataTransfer.files.length) {
|
||||
handleFile(e.dataTransfer.files[0]);
|
||||
}
|
||||
});
|
||||
button.addEventListener("dragover", (e) => {
|
||||
e.preventDefault();
|
||||
button.classList.add("dragover");
|
||||
});
|
||||
button.addEventListener("dragleave", (e) => {
|
||||
button.classList.remove("dragover");
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {File} file
|
||||
*/
|
||||
function handleFile(file) {
|
||||
const filename = file.name ?? "yuzu.exe";
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
const buff = e.target.result;
|
||||
const view = new DataView(buff);
|
||||
|
||||
const patternLen = VK_PATTERN.byteLength;
|
||||
const buffLen = buff.byteLength;
|
||||
|
||||
// Find the pattern
|
||||
let foundIndex = -1;
|
||||
for (let i = 0; i <= buffLen - patternLen; i++) {
|
||||
let found = true;
|
||||
for (let j = 0; j < patternLen; j++) {
|
||||
if (view.getUint8(i + j) !== VK_PATTERN[j]) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
foundIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundIndex == -1) {
|
||||
console.error("Could not find pattern in file");
|
||||
alert("Could not find pattern in file");
|
||||
return;
|
||||
}
|
||||
|
||||
// Walk backwards until we hit the start of the Emulator string
|
||||
let startIndex = -1;
|
||||
let emuLen = 0;
|
||||
for (let i = foundIndex - 2;; i--) { // skip over first 0 byte right before current index
|
||||
if (view.getUint8(i) == 0) {
|
||||
startIndex = i + 1;
|
||||
break;
|
||||
}
|
||||
emuLen++;
|
||||
}
|
||||
|
||||
const yuzuLen = YUZU_PATTERN.byteLength;
|
||||
if (emuLen < yuzuLen) {
|
||||
console.error("Cannot patch emulator with smaller default name then yuzu");
|
||||
alert("Cannot patch emulator with smaller default name then yuzu");
|
||||
return;
|
||||
}
|
||||
|
||||
if (emuLen == yuzuLen) {
|
||||
let isYuzu = true;
|
||||
for (let i = 0; i < emuLen; i++) {
|
||||
if (view.getUint8(startIndex + i) != YUZU_PATTERN[i]) {
|
||||
isYuzu = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isYuzu) {
|
||||
console.error("Either this is an already patched fork or its real yuzu");
|
||||
alert("Either this is an already patched fork or its real yuzu");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < emuLen; i++) {
|
||||
view.setUint8(startIndex + i, YUZU_PATTERN[i] || 0);
|
||||
}
|
||||
|
||||
const blobPatched = new Blob([buff]);
|
||||
saveAs(blobPatched, filename.replace(".exe", "_patched.exe"));
|
||||
}
|
||||
reader.readAsArrayBuffer(file);
|
||||
}
|
||||
Reference in New Issue
Block a user