diff --git a/.github/workflows/jest-tests.yml b/.github/workflows/jest-tests.yml
index 8fd070dd..4f68124e 100644
--- a/.github/workflows/jest-tests.yml
+++ b/.github/workflows/jest-tests.yml
@@ -2,9 +2,6 @@ name: Run Jest Tests
on:
push:
- branches:
- - main
- - canary
jobs:
test:
diff --git a/.gitignore b/.gitignore
index ac66d430..c023481e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,4 +16,5 @@ node_modules/
.turbo
packages/stats/bin/
-packages/stats/results/*.html
\ No newline at end of file
+packages/stats/results/*.html
+.env*.local
diff --git a/LICENSE.md b/LICENSE.md
index 1455c0ae..e7f812a9 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2023 Dash Randomizer
+Copyright (c) 2024 Dash Randomizer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 7144d9c5..9f3089cd 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,6 @@ DASH is a Super Metroid randomizer aimed at competitive play.
This is a monorepo which consists of all the DASH projects, which are located in the [`apps`](apps) and [`packages`](packages) folders.
* [`web`](apps/web): the website for [dashrando.net](https://www.dashrando.net)
-* [`headless`](apps/headless/): a standalone Node.js version which can be used to generate seeds outside of the website (such as bots).
* [`core`](packages/core): the logic for seeds for each mode.
## Local Development
diff --git a/apps/headless/.gitignore b/apps/headless/.gitignore
deleted file mode 100644
index c9c2782a..00000000
--- a/apps/headless/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-public/*
-!public/.keep
\ No newline at end of file
diff --git a/apps/headless/README.md b/apps/headless/README.md
deleted file mode 100644
index c4badf1a..00000000
--- a/apps/headless/README.md
+++ /dev/null
@@ -1,18 +0,0 @@
-# DASH Headless
-
-This is a standalone file for rolling a DASH Randomizer seed. It is meant to be used by bots in a Node.js environment.
-
-## Usage
-```sh
-node ./dash.headless.js [options]
-
-Usage: dash.headless.js -r -p [options]
-
-Generate a randomized DASH seed from the command line
-
-Options:
- -r --vanillaPath path to vanilla rom
- -p --preset preset to use
- -b --base-url base url for rolling the seed (default: "https://dashrando.net/")
- -h, --help display help for command
-```
diff --git a/apps/headless/build.js b/apps/headless/build.js
deleted file mode 100644
index 0137680f..00000000
--- a/apps/headless/build.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import ncc from "@vercel/ncc"
-import path from "node:path"
-import fs from "node:fs/promises"
-
-async function build() {
- const entry = path.resolve(process.cwd(), 'index.ts')
- console.log(entry)
- const output = await ncc(entry, {
- minify: true,
- sourceMapRegister: false,
- cache: false,
- })
- await fs.writeFile(path.resolve(process.cwd(), 'public/dash.headless.js'), output.code)
-}
-
-build()
- .then(() => {
- console.log("build successful");
- process.exit(0);
- })
- .catch((err) => {
- console.error("build failed");
- console.error(err);
- process.exit(1);
- });
\ No newline at end of file
diff --git a/apps/headless/index.ts b/apps/headless/index.ts
deleted file mode 100644
index 3ba71c0a..00000000
--- a/apps/headless/index.ts
+++ /dev/null
@@ -1,173 +0,0 @@
-#!/usr/bin/env node
-
-import fs from "fs";
-import crypto from "crypto";
-import { program } from "commander";
-import { BpsPatch, patchRom } from "core";
-import got, { HTTPError, RequestError } from "got";
-import chalk from "chalk";
-import path from "path";
-
-export type SeedAPIResponse = {
- basePatchUrl: string;
- seedPatch: SeedPatchPart[];
- fileName: string;
- preset: string;
-};
-
-export type SeedPatchPart = [
- number,
- number,
- {
- [key: number]: number;
- }
-];
-
-export type SeedPatchRequestBody = {
- preset: string;
- seedNumber: number;
-};
-
-export type HeadlessOptions = {
- baseUrl: string;
-};
-
-const vanillaHash =
- "12b77c4bc9c1832cee8881244659065ee1d84c70c3d29e6eaf92e6798cc2ca72";
-const headeredHash =
- "9a4441809ac9331cdbc6a50fba1a8fbfd08bc490bc8644587ee84a4d6f924fea";
-
-const verify = (rom) => {
- let hash = crypto.createHash("sha256");
- hash.update(rom);
- const signature = hash.digest("hex");
- if (signature === vanillaHash) {
- return rom;
- } else if (signature === headeredHash) {
- console.warn(
- `${chalk.yellow(
- "You have entered a headered ROM"
- )}. The header will now be removed.`
- );
- const unheaderedContent = rom.slice(512);
- return verify(unheaderedContent);
- }
- throw Error("Invalid vanilla ROM");
-};
-
-function getVanilla(vanillaPath: string) {
- const fileExists = fs.existsSync(vanillaPath);
- if (!fileExists) {
- throw new Error(`Could not find ROM: ${vanillaPath}`);
- }
- const contents = fs.readFileSync(vanillaPath);
- return verify(contents);
-}
-
-const fetchSeedData = async (input: SeedPatchRequestBody, options) => {
- const url = new URL("/api/seed", options.baseUrl);
- try {
- const res: SeedAPIResponse = await got
- .post(url.href, {
- json: input,
- })
- .json();
- return res;
- } catch (err: HTTPError | unknown) {
- if (err instanceof HTTPError) {
- const error = err as HTTPError;
- const body = JSON.parse(error.response.body as any);
- if (typeof body.error == "object") {
- throw new Error(`${body.error.code}: ${url.href}`);
- } else {
- const msg = body.error || "Error fetching seed data";
- throw new Error(msg);
- }
- } else if (err instanceof RequestError) {
- const error = err as RequestError;
- let msg;
- switch (true) {
- case error.code === "ECONNREFUSED":
- msg = `Could not connect to server: ${options.baseUrl}`;
- break;
- default:
- msg = error.message || "Error fetching seed data";
- break;
- }
- throw new Error(msg);
- } else {
- const error = err as Error;
- const msg = error.message || "Error fetching seed data";
- throw new Error(msg);
- }
- }
-};
-
-const fetchBasePatch = async (basePatchUrl: string, baseUrl: string) => {
- const url = new URL(basePatchUrl, baseUrl).href;
- const { body } = await got.get(url, { responseType: "buffer" });
- return body;
-};
-
-const createFile = async (vanilla, seedPatch, basePatch, fileName) => {
- const seedData = seedPatch.map((h: any[]) => [
- h[0],
- h[1],
- new Uint8Array(Object.values(h[2])),
- ]);
-
- const rom = patchRom(
- vanilla,
- new BpsPatch(new Uint8Array(basePatch)),
- seedData
- );
- fs.writeFileSync(fileName, rom);
- const filePath = path.join(process.cwd(), fileName);
- console.log(
- `Generated ${chalk.cyan(fileName)} at ${chalk.magenta(filePath)}`
- );
-};
-
-async function main() {
- // Setup CLI with arguments and options
- program
- .name("dash.headless.js")
- .usage(`-r -p [options]`)
- .description("Generate a randomized DASH seed from the command line")
- .requiredOption("-r --vanillaPath ", "path to vanilla rom")
- .requiredOption("-p --preset ", "preset to use")
- .option("-s --seed ", "number between 1 - 999999", "")
- .option(
- "-b --base-url ",
- "base url for rolling the seed",
- "https://dashrando.net/"
- );
- program.parse();
-
- const options = program.opts();
- const { vanillaPath, preset, baseUrl, seed } = options;
-
- const seedNumber: number = parseInt(seed, 10);
- if (seed && (isNaN(seedNumber) || seedNumber < 1 || seedNumber > 999999)) {
- throw new Error("seed must be an integer between 1 - 999999");
- }
-
- // Check and verify `vanillaRom`
- const vanilla = await getVanilla(vanillaPath);
-
- // Setup options for fetching patch
- const seedData = await fetchSeedData({ preset, seedNumber }, options);
- const basePatch = await fetchBasePatch(seedData.basePatchUrl, baseUrl);
- await createFile(vanilla, seedData.seedPatch, basePatch, seedData.fileName);
-
- return 0;
-}
-
-main()
- .then((code: number) => {
- process.exit(code);
- })
- .catch((e) => {
- console.error(chalk.red(e.message));
- process.exit(1);
- });
diff --git a/apps/headless/package.json b/apps/headless/package.json
deleted file mode 100644
index 683ae167..00000000
--- a/apps/headless/package.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "name": "headless",
- "version": "1.0.0",
- "description": "",
- "main": "index.ts",
- "type": "module",
- "scripts": {
- "build": "node build.js"
- },
- "author": "",
- "license": "MIT",
- "devDependencies": {
- "@types/node": "^18.15.11",
- "@vercel/ncc": "^0.36.1",
- "typescript": "^5.0.3"
- },
- "dependencies": {
- "chalk": "^4.1.2",
- "commander": "^10.0.0",
- "core": "*",
- "got": "^12.6.0"
- }
-}
diff --git a/apps/headless/public/.keep b/apps/headless/public/.keep
deleted file mode 100644
index e69de29b..00000000
diff --git a/apps/headless/tsconfig.json b/apps/headless/tsconfig.json
deleted file mode 100644
index e817ff15..00000000
--- a/apps/headless/tsconfig.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "compilerOptions": {
- "target": "es2021",
- "moduleResolution": "node",
- "strict": false,
- "resolveJsonModule": true,
- "esModuleInterop": true,
- "skipLibCheck": false
- },
- "exclude": ["dist"]
-}
diff --git a/apps/headless/vercel.json b/apps/headless/vercel.json
deleted file mode 100644
index 5a9a48ae..00000000
--- a/apps/headless/vercel.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "rewrites": [
- {
- "source": "/",
- "destination": "/dash.headless.js"
- }
- ]
-}
diff --git a/apps/web/app/api/seed/route.ts b/apps/web/app/api/seed/route.ts
deleted file mode 100644
index 2f0bfba5..00000000
--- a/apps/web/app/api/seed/route.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-import type { NextRequest } from "next/server";
-import { generateFromPreset, getAllPresets } from "core";
-
-export const runtime = "nodejs";
-
-export type HTTPError = Error & { status?: number };
-
-const resJSON = (data: any, status = 200, headers = {}) =>
- new Response(JSON.stringify(data), {
- status,
- headers,
- });
-
-export async function POST(req: NextRequest) {
- try {
- const body = await req.json();
- if (!body.preset) {
- throw new Error("Missing preset");
- }
- const { preset, seedNumber } = body;
- const validPresets = getAllPresets().map(p => p.tags).reduce((acc, cur) => {
- return acc.concat(cur)
- }, []);
- const isValidPreset = validPresets.includes(preset);
- if (!isValidPreset) {
- const msg = `Invalid preset. Valid presets are: ${validPresets
- .slice(0, -1)
- .join(", ")} or ${validPresets.slice(-1)}`;
- const err = new Error(msg) as HTTPError;
- err.status = 422;
- throw err;
- }
- const [basePatchUrl, seedPatch, fileName] = await generateFromPreset(
- preset,
- seedNumber
- );
- return resJSON(
- {
- basePatchUrl,
- seedPatch,
- fileName,
- preset,
- },
- 200,
- {
- "Cache-Control": "s-maxage=86400",
- "Content-Type": "application/json",
- }
- );
- } catch (err: unknown) {
- console.error(err);
- const error = err as HTTPError;
- const status = error.status || 500;
- return resJSON({ error: error.message }, status);
- }
-}
diff --git a/apps/web/app/components/file-drop.tsx b/apps/web/app/components/file-drop.tsx
index acd812d1..5b9c36ac 100644
--- a/apps/web/app/components/file-drop.tsx
+++ b/apps/web/app/components/file-drop.tsx
@@ -3,33 +3,17 @@
import { useDropzone } from 'react-dropzone'
import { useCallback, useEffect, useState } from 'react'
import {
+ encodeSeed,
isDASHSeed,
- paramsToString,
- readParams,
+ readRom,
+ readSeedKey,
vanilla as vanillaData,
} from "core";
import styles from './file-drop.module.css'
import { useRouter } from 'next/navigation'
import { useVanilla } from '../generate/vanilla'
import { toast } from 'sonner'
-
-const getParamsFromFile = (bytes: Uint8Array) => {
- try {
- const byteParams = readParams(bytes)
- const seedKey = paramsToString(
- byteParams.seed,
- byteParams.settings,
- byteParams.options
- )
- return seedKey
- } catch (e) {
- const err = e as Error;
- console.error(err.message)
- // TODO: Present a friendly error message to the user instead of an alert.
- //alert(err.message)
- return null
- }
-}
+import { getNewSeedKey, getSeedData, saveSeedData } from '@/lib/seed-data';
async function getVanilla(value: Uint8Array): Promise {
const { getSignature, isVerified, isHeadered } = vanillaData
@@ -111,10 +95,37 @@ const FileDrop = (props: React.PropsWithChildren) => {
// return null
}
- const isDASH = isDASHSeed(data)
- if (isDASH) {
- const seedKey = getParamsFromFile(data)
- if (seedKey) {
+ if (!isDASHSeed(data)) {
+ // Not a vanilla or DASH file
+ toast.error(`Not vanilla ROM or DASH seed`)
+ return
+ }
+
+ // Try to read a seed key from the ROM and load it
+ const { key, race } = readSeedKey(data);
+ if (key.length > 0) {
+ const data = await getSeedData(key)
+ if (data != null) {
+ toast('Loading DASH seed...')
+ router.push(`/seed/${key}`)
+ return
+ }
+ }
+
+ // No seed key so try to read the parameters from the
+ // ROM and regenerate it; does not work for race seeds
+ if (!race) {
+ const { params, graph } = readRom(data);
+ if (params !== undefined && graph !== undefined) {
+ const hash = encodeSeed(params, graph)
+ const seedKey = key.length > 0 ? key : await getNewSeedKey()
+ await saveSeedData(
+ seedKey,
+ hash,
+ params.options.Mystery,
+ false,
+ params.options.Spoiler
+ );
toast('Loading DASH seed...')
router.push(`/seed/${seedKey}`)
return
diff --git a/apps/web/app/demo/globals.css b/apps/web/app/demo/globals.css
deleted file mode 100644
index 36683004..00000000
--- a/apps/web/app/demo/globals.css
+++ /dev/null
@@ -1,88 +0,0 @@
-:root {
- --color-background: #000;
- --color-foreground: #cbcec8;
- --color-highlight: #fff;
- --color-green: #5aad36;
- --color-magenta: #d12ebb;
-
- --selection-background: var(--color-magenta);
- --selection-foreground: var(--color-background);
-
- --type-mono: "SFMono Regular",Consolas,"Liberation Mono",Menlo,Courier,monospace;
-
- --spacer: 4px;
- --spacer-2x: 8px;
- --spacer-3x: 12px;
- --spacer-4x: 16px;
- --spacer-5x: 20px;
- --spacer-6x: 24px;
- --spacer-7x: 28px;
- --spacer-8x: 32px;
- --spacer-9x: 36px;
- --spacer-10x: 40px;
- --spacer-11x: 44px;
- --spacer-12x: 48px;
- --spacer-13x: 52px;
- --spacer-14x: 56px;
- --spacer-15x: 60px;
- --spacer-16x: 64px;
- --spacer-17x: 68px;
- --spacer-18x: 72px;
- --spacer-19x: 76px;
- --spacer-20x: 80px;
-
- --spacer-negative: -4px;
- --spacer-2x-negative: -8px;
- --spacer-3x-negative: -12px;
- --spacer-4x-negative: -16px;
- --spacer-5x-negative: -20px;
- --spacer-6x-negative: -24px;
- --spacer-7x-negative: -28px;
- --spacer-8x-negative: -32px;
- --spacer-9x-negative: -36px;
- --spacer-10x-negative: -40px;
- --spacer-11x-negative: -44px;
- --spacer-12x-negative: -48px;
- --spacer-13x-negative: -52px;
- --spacer-14x-negative: -56px;
- --spacer-15x-negative: -60px;
- --spacer-16x-negative: -64px;
- --spacer-17x-negative: -68px;
- --spacer-18x-negative: -72px;
- --spacer-19x-negative: -76px;
- --spacer-20x-negative: -80px;
-
- --mq-mobile: 600px;
-}
-
-html { box-sizing: border-box; }
-*, *:before, *:after { box-sizing: inherit; }
-
-html, body {
- height: 100%;
-}
-
-html,
-body {
- max-width: 100vw;
- overflow-x: hidden;
-}
-
-body {
- text-rendering: optimizelegibility;
- -webkit-font-smoothing: antialiased;
- color: var(--color-foreground);
- background-color: var(--color-background);
- margin: 0;
- padding: 0;
-}
-
-::-moz-selection {
- background-color: var(--selection-background);
- color: var(--selection-foreground);
-}
-
-::selection {
- background-color: var(--selection-background);
- color: var(--selection-foreground);
-}
diff --git a/apps/web/app/demo/layout.tsx b/apps/web/app/demo/layout.tsx
deleted file mode 100644
index 2540a7a0..00000000
--- a/apps/web/app/demo/layout.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import './globals.css'
-import { Inter } from 'next/font/google'
-
-const inter = Inter({ subsets: ['latin'] })
-
-export const metadata = {
- title: 'DASH Randomizer',
- description: 'Super Metroid Randomizer for competitive play',
-}
-
-export default function RootLayout({
- children,
-}: {
- children: React.ReactNode
-}) {
- return (
-
- {children}
-
- )
-}
diff --git a/apps/web/app/demo/page.tsx b/apps/web/app/demo/page.tsx
deleted file mode 100644
index 6c5ae676..00000000
--- a/apps/web/app/demo/page.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import Typography from '@/app/components/typography'
-import Spacer from '@/app/components/spacer'
-import { Headline, Heading, Body } from '@/app/components/text'
-
-export default function Home() {
- return (
-
- This is a X-Large
-
- This is a Large
-
- This is Medium
-
- This is Small
-
- This is Compact
-
-
-
- DASH: Recall
-
-
- The DASH Dev Team is excited to announce our newest project - DASH: Recall! This mode is a reimagining and rebalancing of vanilla map with the goal of offering up even more routing possibilities in a variety of seeds and further diversity in how seeds can be completed within a competitive racing situation. The following changes are introduced along with the existing DASH modifications:
-
-
- DASH Cash Tournament on SpeedGaming
-
-
- There will be a special DASH Randomizer one-day tournament on January 18, 2020 that will be featured on the SpeedGaming Twitch channel. This tournament will feature 16 players who qualify for the tournament with the top 8 finishers winning cash prizes. The tournament will begin at 12:00 p.m. ET and will continue throughout the full day until it is completed. For more information and a complete set of rules, click here.
-
-