diff --git a/+layout.svelte b/+layout.svelte new file mode 100644 index 0000000..5e0c69d --- /dev/null +++ b/+layout.svelte @@ -0,0 +1,445 @@ + + + +{#if navigator.userAgent.indexOf("Firefox") === -1} +
+ +
+{/if} + +
+
+ +
+
+ +
+
+ +
+
+
+ + {#each navItems as item, i} + toggleDropdown()} + > +

{item.name}

+
+ {/each} +
+
+ + {#key data.url} +
+ +
+ {/key} +
diff --git a/+page.svelte b/+page.svelte new file mode 100644 index 0000000..81ea910 --- /dev/null +++ b/+page.svelte @@ -0,0 +1,279 @@ + + + + {metadata.title} + + + + + + + + + + + + + + +
+ + + + +

+ suyu is a fully open-source + Switch emulator +

+

+ suyu is a familiar C++ based Switch emulator with a focus on compatibility. Completely free + and open-source, forever. +

+ +
+ +
+
+

By the numbers

+ + + + +
+
+
+
+ +
+
+
+
+ +
+

+ Built by and for the community +

+

+ The future of suyu is shaped by you. New features are always being added, and our community + continually shapes the direction of the project. +

+
+ + + +
+

+ we’re passionate about preserving games. +

+

+ We’re developing suyu as a tool to prevent Switch games from becoming lost media, so we’re + taking care to prevent dissolution from outside influences. +

+
+
+ + + +

suyu is community-run.

+
+
+ + + +

suyu is completely free. There’s no option to monetarily support the project.

+
+
+ + + +

suyu exists solely as an effort of hardware preservation and research.

+
+
+ + + +

suyu does not condone nor facilitate piracy or intellectual property theft.

+
+
+
+ +
diff --git a/src/lib/server/util/index.ts b/src/lib/server/util/index.ts index fb4030d..104afea 100644 --- a/src/lib/server/util/index.ts +++ b/src/lib/server/util/index.ts @@ -1,6 +1,6 @@ import type { IJwtData } from "$types/auth"; import type { Role } from "$types/db"; -import { PUBLIC_KEY } from "../secrets/secrets.json"; +import { PUB_KEY } from "$env/static/private"; import jwt from "jsonwebtoken"; export function json(body: T, status?: number): Response { @@ -14,7 +14,7 @@ export function json(body: T, status?: number): Response { export async function getJwtData(token: string): Promise { return new Promise((resolve, reject) => { - jwt.verify(token, PUBLIC_KEY, { algorithms: ["RS256"] }, (err, data) => { + jwt.verify(token, PUB_KEY, { algorithms: ["RS256"] }, (err, data) => { if (err) reject(err); else resolve(data as IJwtData); }); diff --git a/src/lib/util/api/index.ts b/src/lib/util/api/index.ts index b5c24e4..dbedfb6 100644 --- a/src/lib/util/api/index.ts +++ b/src/lib/util/api/index.ts @@ -1,6 +1,6 @@ import { userRepo } from "$lib/server/repo"; import type { SuyuUser } from "$lib/server/schema"; -import { PUBLIC_KEY } from "$lib/server/secrets/secrets.json"; +import { PUB_KEY } from "$env/static/private"; import type { IJwtData } from "$types/auth"; import cookie from "cookie"; import jwt from "jsonwebtoken"; @@ -18,7 +18,7 @@ export async function useAuth( } if (apiKey.startsWith("Bearer ")) { const token = apiKey.replace("Bearer ", ""); - const decoded: IJwtData = jwt.verify(token, Buffer.from(PUBLIC_KEY), { + const decoded: IJwtData = jwt.verify(token, Buffer.from(PUB_KEY), { algorithms: ["RS256"], }) as IJwtData; let user = await userRepo.findOne({ diff --git a/src/routes/+page.server.ts b/src/routes/+page.server.ts index 2ce74a2..4927338 100644 --- a/src/routes/+page.server.ts +++ b/src/routes/+page.server.ts @@ -1,11 +1,11 @@ import { building } from "$app/environment"; -import { DISCORD_USER_TOKEN, GITLAB_API_TOKEN } from "$env/static/private"; +import { DISCORD_USER_TOKEN } from "$env/static/private"; let memberCount = 0; -let starCount = 0; let roleMembers = { "1214817156420862012": 50, }; +let gitCommits = 0; async function fetchServerSideData() { console.log("Fetching member count"); @@ -19,25 +19,20 @@ async function fetchServerSideData() { }, }) : Promise.resolve({ json: () => roleMembers }), - GITLAB_API_TOKEN - ? fetch("https://gitlab.com/api/v4/projects/suyu-emu%2Fsuyu/", { - headers: { - Authorization: `Bearer ${GITLAB_API_TOKEN}`, - }, - }) - : Promise.resolve({ json: () => ({ star_count: 0 }) }), // Default to 0 if no token is provided + fetch('https://git.suyu.dev/api/v1/repos/suyu/suyu/commits?stat=false&verification=false&files=false&limit=1') ]; - const [res, roles, gitlabRes] = await Promise.all(promises); - const jsonPromises = [res.json(), roles.json(), gitlabRes.json()]; - const [resJson, rolesJson, gitlabResJson] = await Promise.all(jsonPromises); + const [res, roles, git] = await Promise.all(promises); + const jsonPromises = [res.json(), roles.json()]; + const [resJson, rolesJson] = await Promise.all(jsonPromises); + memberCount = resJson.approximate_member_count; - starCount = gitlabResJson.star_count; + gitCommits = parseInt(git?.headers?.get('x-total'), 10) || 0; if (DISCORD_USER_TOKEN) roleMembers = rolesJson; console.log("Member count:", memberCount); - console.log("Stars count:", starCount); + console.log('Git commit count:', gitCommits); } if (!building) { @@ -50,7 +45,7 @@ export async function load({ cookies }) { return { tokenCookie: token, memberCount, - starCount, roleMembers, + gitCommits }; } diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 7447cf2..1b13d73 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -8,8 +8,8 @@ export let data: PageData; $: memberCount = parseFloat(data.memberCount?.toPrecision(2)); - $: contributors = parseFloat(data.roleMembers["1214817156420862012"]?.toPrecision(2)); - $: starCount = parseFloat(data.starCount?.toPrecision(2)); + $: contributors = parseFloat(data.roleMembers?.["1214817156420862012"]?.toPrecision(2)); + $: gitCommits = parseFloat(data.gitCommits?.toPrecision(2)); let metadata = { url: "https://suyu.dev", title: "suyu - Open-source, non-profit Switch emulator", @@ -70,12 +70,8 @@ and open-source, forever.

@@ -186,8 +182,8 @@ >

Git

- Our Git instance is where all the magic of suyu happens. We're always looking for new contributors - to help us out, so feel free to check out our code. + Our Git instance is where all the magic of suyu happens. We're always looking for new + contributors to help us out, so feel free to check out our code.

(".navbar"); if (navBars.length !== 1) navBars.forEach((bar) => { @@ -52,6 +52,7 @@ const bounds = target.getBoundingClientRect(); const navBounds = navBar.getBoundingClientRect(); const pillBounds = indicator.getBoundingClientRect(); + if (ignoreAnimation) return; indicator.style.transform = `translateX(${bounds.left - navBounds.left}px)`; indicator.style.width = `${bounds.width}px`; if ( @@ -190,7 +191,7 @@ navClick(e, true)} class={`navitem flex flex-grow basis-[0] items-center justify-center whitespace-nowrap rounded-full px-4 py-2 text-sm font-bold ${ selected === i ? " text-[#a9a9a9] opacity-100" : "opacity-50" }`} diff --git a/src/routes/download/+page.svelte b/src/routes/download/+page.svelte index 0acb36c..1173fcb 100644 --- a/src/routes/download/+page.svelte +++ b/src/routes/download/+page.svelte @@ -1,74 +1,80 @@ - Downloading Suyu @@ -94,36 +100,14 @@ stroke="white" /> - - -

- Downloading Suyu... -

- - +

+ {@html htmlContent} +

Your download should start shortly. If it doesn't, click herehere.

diff --git a/src/routes/faq/+page.svelte b/src/routes/faq/+page.svelte index 741b64b..350fc9a 100644 --- a/src/routes/faq/+page.svelte +++ b/src/routes/faq/+page.svelte @@ -39,11 +39,11 @@

Q: What is the purpose of Suyu?

A: The purpose of this project is to provide a free, open-source alternative to the now-dead Yuzu emulator. We believe that the community should be able to emulate their Switch device (legally) and be able to enjoy their favorite game titles.

Q: How can I contribute to Suyu?

-

A: You can contribute to this project by submitting a pull request on our Git page. We are always looking for new contributors to help us improve the project!

+

A: You can contribute to this project by submitting a pull request on our Git page. We are always looking for new contributors to help us improve the project!

Q: Where can I download Suyu?

-

A: You can download the latest build of Suyu from our Git. Please make sure you are using the right URL!

+

A: You can download the latest build of Suyu from our Git. Please make sure you are using the right URL!

Q: What is the current progress for Suyu?

-

A: As of 3/20/2024, we have released our first Windows binary 🎉! You can find it here. We are always trying to make more and more progress, so please feel free to join the Discord!

+

A: As of 3/20/2024, we have released our first Windows binary 🎉! You can find it here. We are always trying to make more and more progress, so please feel free to join the Discord!


Email hosting lovingly provided by diff --git a/src/routes/jwt/external/[audience]/+server.ts b/src/routes/jwt/external/[audience]/+server.ts new file mode 100644 index 0000000..9551a48 --- /dev/null +++ b/src/routes/jwt/external/[audience]/+server.ts @@ -0,0 +1,24 @@ +import { PRIVATE_KEY } from "$env/static/private"; +import { userRepo } from "$lib/server/repo/index.js"; +import { getJwtData } from "$lib/server/util/index.js"; +import { useAuth } from "$lib/util/api/index.js"; +import type { IJwtData } from "$types/auth.js"; +import jwt from "jsonwebtoken"; + +export async function POST({ request }) { + const jwtData = await getJwtData(request.headers.get("authorization")?.split(" ")[1] || ""); + const user = await userRepo.findOne({ + where: { + apiKey: jwtData.apiKey, + }, + }); + const token = jwt.sign({ ...user }, Buffer.from(PRIVATE_KEY), { + algorithm: "RS256", + }); + return new Response(token, { + status: 200, + headers: { + "content-type": "text/html", + }, + }); +} diff --git a/src/routes/jwt/external/key.pem/+server.ts b/src/routes/jwt/external/key.pem/+server.ts index ef8c292..a5815f2 100644 --- a/src/routes/jwt/external/key.pem/+server.ts +++ b/src/routes/jwt/external/key.pem/+server.ts @@ -1,45 +1,11 @@ -import { json } from "$lib/server/util/index.js"; +import { PUB_KEY } from "$env/static/private"; export function GET({ request }) { - return new Response( - `-----BEGIN CERTIFICATE----- -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== ------END CERTIFICATE-----`, - { - headers: { - "content-type": "text/plain", - }, + return new Response(PUB_KEY, { + headers: { + "content-type": "text/plain", }, - ); + }); } export function POST({ request }) { diff --git a/src/routes/jwt/internal/+server.ts b/src/routes/jwt/internal/+server.ts index ba144e7..a880dce 100644 --- a/src/routes/jwt/internal/+server.ts +++ b/src/routes/jwt/internal/+server.ts @@ -1,4 +1,4 @@ -import { PRIVATE_KEY } from "$lib/server/secrets/secrets.json"; +import { PRIVATE_KEY } from "$env/static/private"; import { useAuth } from "$lib/util/api/index.js"; import jwt from "jsonwebtoken"; diff --git a/src/routes/lobby/+server.ts b/src/routes/lobby/+server.ts index bb80a7d..650989a 100644 --- a/src/routes/lobby/+server.ts +++ b/src/routes/lobby/+server.ts @@ -1,7 +1,6 @@ import { Room, RoomManager } from "$lib/server/class/Room"; import { userRepo } from "$lib/server/repo/index.js"; import { SuyuUser } from "$lib/server/schema"; -import { PUBLIC_KEY } from "$lib/server/secrets/secrets.json"; import { json } from "$lib/server/util"; import { useAuth } from "$lib/util/api/index.js"; import type { IJwtData } from "$types/auth.js"; @@ -65,12 +64,5 @@ export async function POST({ request, getClientAddress }) { host: user, hasPassword: body.hasPassword || false, }); - console.log("Room added:", JSON.stringify(room, null, 2)); - // push every room to the top which starts with `[SUYU OFFICIAL]` and was created with username "suyu" - const suyuRoom = RoomManager.rooms.find((r) => r.roomInfo.name.startsWith("[SUYU OFFICIAL]")); - if (suyuRoom && suyuRoom.host.username === "suyu") { - RoomManager.rooms.splice(RoomManager.rooms.indexOf(suyuRoom), 1); - RoomManager.rooms.unshift(suyuRoom); - } return json(room.toJSON()); }