DISCORD_BOT_ENABLE_COMMANDS=
AOS_HOSTNAME=aos.localhorst.tv
+AOS_URL=https://aos.localhorst.tv
/node_modules
+/public/aos-seeds
/public/css/app.css
/public/css/app.css.map
/public/doortracker
--- /dev/null
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Models\AosSeed;
+use Illuminate\Http\Request;
+
+class AosSeedController extends Controller
+{
+
+ public function byHash($hash) {
+ $seed = AosSeed::where('hash', '=', $hash)->firstOrFail();
+
+ if ($seed->race) {
+ $seed->hide('seed');
+ }
+ if ($seed->mystery) {
+ $seed->hide('settings');
+ }
+
+ return $seed->toJson();
+ }
+
+}
--- /dev/null
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+
+class AosSeed extends Model
+{
+ use HasFactory;
+
+ protected $casts = [
+ 'mystery' => 'boolean',
+ 'race' => 'boolean',
+ 'settings' => 'array',
+ ];
+
+}
return [
- /*
- |--------------------------------------------------------------------------
- | Default Filesystem Disk
- |--------------------------------------------------------------------------
- |
- | Here you may specify the default filesystem disk that should be used
- | by the framework. The "local" disk, as well as a variety of cloud
- | based disks are available to your application. Just store away!
- |
- */
+ /*
+ |--------------------------------------------------------------------------
+ | Default Filesystem Disk
+ |--------------------------------------------------------------------------
+ |
+ | Here you may specify the default filesystem disk that should be used
+ | by the framework. The "local" disk, as well as a variety of cloud
+ | based disks are available to your application. Just store away!
+ |
+ */
- 'default' => env('FILESYSTEM_DISK', 'local'),
+ 'default' => env('FILESYSTEM_DISK', 'local'),
- /*
- |--------------------------------------------------------------------------
- | Filesystem Disks
- |--------------------------------------------------------------------------
- |
- | Here you may configure as many filesystem "disks" as you wish, and you
- | may even configure multiple disks of the same driver. Defaults have
- | been set up for each driver as an example of the required values.
- |
- | Supported Drivers: "local", "ftp", "sftp", "s3"
- |
- */
+ /*
+ |--------------------------------------------------------------------------
+ | Filesystem Disks
+ |--------------------------------------------------------------------------
+ |
+ | Here you may configure as many filesystem "disks" as you wish, and you
+ | may even configure multiple disks of the same driver. Defaults have
+ | been set up for each driver as an example of the required values.
+ |
+ | Supported Drivers: "local", "ftp", "sftp", "s3"
+ |
+ */
- 'disks' => [
+ 'disks' => [
- 'local' => [
- 'driver' => 'local',
- 'root' => storage_path('app'),
- 'throw' => false,
- ],
+ 'local' => [
+ 'driver' => 'local',
+ 'root' => storage_path('app'),
+ 'throw' => false,
+ ],
- 'public' => [
- 'driver' => 'local',
- 'root' => storage_path('app/public'),
- 'url' => env('APP_URL').'/storage',
- 'visibility' => 'public',
- 'throw' => false,
- ],
+ 'public' => [
+ 'driver' => 'local',
+ 'root' => storage_path('app/public'),
+ 'url' => env('APP_URL').'/storage',
+ 'visibility' => 'public',
+ 'throw' => false,
+ ],
- 's3' => [
- 'driver' => 's3',
- 'key' => env('AWS_ACCESS_KEY_ID'),
- 'secret' => env('AWS_SECRET_ACCESS_KEY'),
- 'region' => env('AWS_DEFAULT_REGION'),
- 'bucket' => env('AWS_BUCKET'),
- 'url' => env('AWS_URL'),
- 'endpoint' => env('AWS_ENDPOINT'),
- 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
- 'throw' => false,
- ],
+ 'aos-seeds' => [
+ 'driver' => 'local',
+ 'root' => storage_path('app/aos-seeds'),
+ 'url' => env('AOS_URL').'/aos-seeds',
+ 'visibility' => 'public',
+ ],
- ],
+ 's3' => [
+ 'driver' => 's3',
+ 'key' => env('AWS_ACCESS_KEY_ID'),
+ 'secret' => env('AWS_SECRET_ACCESS_KEY'),
+ 'region' => env('AWS_DEFAULT_REGION'),
+ 'bucket' => env('AWS_BUCKET'),
+ 'url' => env('AWS_URL'),
+ 'endpoint' => env('AWS_ENDPOINT'),
+ 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
+ 'throw' => false,
+ ],
- /*
- |--------------------------------------------------------------------------
- | Symbolic Links
- |--------------------------------------------------------------------------
- |
- | Here you may configure the symbolic links that will be created when the
- | `storage:link` Artisan command is executed. The array keys should be
- | the locations of the links and the values should be their targets.
- |
- */
+ ],
- 'links' => [
- public_path('storage') => storage_path('app/public'),
- ],
+ /*
+ |--------------------------------------------------------------------------
+ | Symbolic Links
+ |--------------------------------------------------------------------------
+ |
+ | Here you may configure the symbolic links that will be created when the
+ | `storage:link` Artisan command is executed. The array keys should be
+ | the locations of the links and the values should be their targets.
+ |
+ */
+
+ 'links' => [
+ public_path('storage') => storage_path('app/public'),
+ public_path('aos-seeds') => storage_path('app/aos-seeds'),
+ ],
];
--- /dev/null
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+ /**
+ * Run the migrations.
+ *
+ * @return void
+ */
+ public function up()
+ {
+ Schema::create('aos_seeds', function (Blueprint $table) {
+ $table->id();
+ $table->binary('hash');
+ $table->string('generator');
+ $table->string('preset');
+ $table->boolean('race')->default(false);
+ $table->boolean('mystery')->default(false);
+ $table->string('seed');
+ $table->text('settings');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('aos_seeds');
+ }
+};
"@fortawesome/free-solid-svg-icons": "^6.0.0",
"@fortawesome/react-fontawesome": "^0.1.17",
"crc-32": "^1.2.2",
+ "file-saver": "^2.0.5",
"formik": "^2.2.9",
"i18next": "^21.6.13",
"i18next-browser-languagedetector": "^6.1.3",
"url": "https://opencollective.com/webpack"
}
},
+ "node_modules/file-saver": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
+ "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
+ },
"node_modules/file-type": {
"version": "12.4.2",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-12.4.2.tgz",
}
}
},
+ "file-saver": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
+ "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
+ },
"file-type": {
"version": "12.4.2",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-12.4.2.tgz",
"@fortawesome/free-solid-svg-icons": "^6.0.0",
"@fortawesome/react-fontawesome": "^0.1.17",
"crc-32": "^1.2.2",
+ "file-saver": "^2.0.5",
"formik": "^2.2.9",
"i18next": "^21.6.13",
"i18next-browser-languagedetector": "^6.1.3",
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import Header from './Header';
+import AosSeed from '../pages/AosSeed';
import Front from '../pages/Front';
import User from '../pages/User';
import AosBaseRomProvider from '../../helpers/AosBaseRomContext';
<UserContext.Provider value={user}>
<Header doLogout={doLogout} />
<Routes>
+ <Route path="h/:hash" element={<AosSeed />} />
<Route path="users/:id" element={<User />} />
<Route path="*" element={<Front />} />
</Routes>
--- /dev/null
+import FileSaver from 'file-saver';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button, Col, Container, Row } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+import toastr from 'toastr';
+
+import BaseRomButton from './BaseRomButton';
+import { useAosBaseRom } from '../../helpers/AosBaseRomContext';
+import BPS from '../../helpers/bps';
+import i18n from '../../i18n';
+
+const applyPatch = (rom, patch, filename) => {
+ try {
+ const bps = new BPS();
+ bps.setPatch(patch);
+ bps.setSource(rom);
+ const result = bps.applyPatch();
+ FileSaver.saveAs(new Blob([result], { type: 'application/octet-stream' }), filename);
+ } catch (e) {
+ toastr.error(i18n.t('aosSeeds.patchError', { msg: e.message }));
+ }
+};
+
+const Seed = ({ patch, seed }) => {
+ const { rom } = useAosBaseRom();
+
+ return <Container>
+ <h1>{i18n.t('aosSeeds.heading')}</h1>
+ <Row>
+ <Col md={{ order: 2 }}>
+ {rom ?
+ <Button
+ disabled={!patch}
+ onClick={() => applyPatch(
+ rom,
+ patch,
+ `${i18n.t('aosSeeds.filename', {
+ hash: seed.hash,
+ preset: seed.preset,
+ })}.gba`,
+ )}
+ variant="primary"
+ >
+ {i18n.t(patch ? 'aosSeeds.patch' : 'aosSeeds.fetchingPatch')}
+ </Button>
+ :
+ <BaseRomButton />
+ }
+ </Col>
+ <Col md={{ order: 1 }}>
+ <p>
+ {i18n.t('aosSeeds.preset')}:
+ {' '}
+ <strong>{i18n.t(`aosSeeds.presets.${seed.preset}`)}</strong>
+ </p>
+ <p>{i18n.t(seed.race ? 'aosSeeds.race' : 'aosSeeds.noRace')}</p>
+ <p>{i18n.t(seed.mystery ? 'aosSeeds.mystery' : 'aosSeeds.noMystery')}</p>
+ </Col>
+ </Row>
+ <h2 className="mt-5">{i18n.t('aosSeeds.generator')}</h2>
+ <p>{i18n.t(`aosSeeds.generators.${seed.generator}`)}</p>
+ </Container>;
+};
+
+Seed.propTypes = {
+ patch: PropTypes.instanceOf(ArrayBuffer),
+ seed: PropTypes.shape({
+ generator: PropTypes.string,
+ hash: PropTypes.string,
+ mystery: PropTypes.bool,
+ preset: PropTypes.string,
+ race: PropTypes.bool,
+ }),
+};
+
+export default withTranslation()(Seed);
--- /dev/null
+import axios from 'axios';
+import React, { useEffect, useState } from 'react';
+import { useParams } from 'react-router-dom';
+
+import NotFound from './NotFound';
+import Seed from '../aos/Seed';
+import ErrorBoundary from '../common/ErrorBoundary';
+import ErrorMessage from '../common/ErrorMessage';
+import Loading from '../common/Loading';
+
+const AosSeed = () => {
+ const params = useParams();
+ const { hash } = params;
+
+ const [error, setError] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [patch, setPatch] = useState(null);
+ const [seed, setSeed] = useState(null);
+
+ useEffect(() => {
+ setLoading(true);
+ const ctrl = new AbortController();
+ axios
+ .get(`/api/aos-seed/${hash}`, { signal: ctrl.signal })
+ .then(response => {
+ setError(null);
+ setLoading(false);
+ setSeed(response.data);
+ window.document.title = response.data.hash;
+ })
+ .catch(error => {
+ setError(error);
+ setLoading(false);
+ setSeed(null);
+ });
+ return () => {
+ ctrl.abort();
+ };
+ }, [hash]);
+
+ useEffect(() => {
+ setPatch(null);
+ const ctrl = new AbortController();
+ axios
+ .get(`/aos-seeds/${hash}.bps`, {
+ responseType: 'arraybuffer',
+ signal: ctrl.signal,
+ })
+ .then(response => {
+ setPatch(response.data);
+ })
+ .catch(error => {
+ setError(error);
+ });
+ return () => {
+ ctrl.abort();
+ };
+ }, [hash]);
+
+ if (loading) {
+ return <Loading />;
+ }
+
+ if (error) {
+ return <ErrorMessage error={error} />;
+ }
+
+ if (!seed) {
+ return <NotFound />;
+ }
+
+ return <ErrorBoundary>
+ <Seed patch={patch} seed={seed} />
+ </ErrorBoundary>;
+};
+
+export default AosSeed;
setSource(file) {
this.sourceFile = new Uint8Array(file);
- this.sourceChecksum = CRC32.buf(file);
+ this.sourceChecksum = CRC32.buf(this.sourceFile);
return this;
}
setTarget(file) {
this.targetFile = new Uint8Array(file);
- this.targetChecksum = CRC32.buf(file);
+ this.targetChecksum = CRC32.buf(this.targetFile);
return this;
}
baseRomSet: 'Base ROM gespeichert.',
setBaseRom: 'Base ROM auswählen',
},
+ aosSeeds: {
+ fetchingPatch: 'Lade Patch',
+ filename: 'aosr - {{preset}} - {{hash}}',
+ heading: 'Aria of Sorrow Randomizer Seed',
+ generator: 'Generator',
+ generators: {
+ surge: 'Dieser Seed wurde mit dem Randomizer von fusecv auf aosrando.surge.sh generiert',
+ },
+ mystery: 'Mystery',
+ noMystery: 'Kein Mystery',
+ noRace: 'Kein Race',
+ patch: 'ROM patchen',
+ patchError: 'Fehler beim Patchen: {{msg}}',
+ preset: 'Preset',
+ presets: {
+ Area: 'Area',
+ AreaAllBosses: 'Area alle Bosse',
+ AreaBeginner: 'Area Beginner',
+ AreaExpert: 'Area Experte',
+ AreaExtraFast: 'Area extra schnell',
+ AreaSpicy: 'Area spicy',
+ Beginner: 'Beginner',
+ DoorAllBosses: 'Door alle Bosse',
+ DoorAllBossesEasy: 'Door alle Bosses einfach',
+ ExtraFastNormal: 'Extra schnell normal',
+ Normal: 'Normal',
+ SGLive2020: 'SGLive 2020',
+ SGLive2021: 'SGLive 2021',
+ SpicyNormal: 'Spicy normal',
+ Tournament2021: 'Turnier 2021',
+ Tournament2022: 'Turnier 2022',
+ },
+ race: 'Race',
+ },
applications: {
accept: 'Annehmen',
acceptError: 'Fehler beim Annehmen',
baseRomSet: 'Base ROM set.',
setBaseRom: 'Set base ROM',
},
+ aosSeeds: {
+ fetchingPatch: 'Fetching patch',
+ filename: 'aosr - {{preset}} - {{hash}}',
+ heading: 'Aria of Sorrow Randomizer Seed',
+ generator: 'Generator',
+ generators: {
+ surge: 'This seed has been generated with fusecv\'s randomizer on aosrando.surge.sh.',
+ },
+ mystery: 'Mystery',
+ noMystery: 'No mystery',
+ noRace: 'No race',
+ patch: 'Patch ROM',
+ patchError: 'Error applying patch: {{msg}}',
+ preset: 'Preset',
+ presets: {
+ Area: 'Area',
+ AreaAllBosses: 'Area all bosses',
+ AreaBeginner: 'Area beginner',
+ AreaExpert: 'Area expert',
+ AreaExtraFast: 'Area extra fast',
+ AreaSpicy: 'Area spicy',
+ Beginner: 'Beginner',
+ DoorAllBosses: 'Door all bosses',
+ DoorAllBossesEasy: 'Door all bosses easy',
+ ExtraFastNormal: 'Extra fast normal',
+ Normal: 'Normal',
+ SGLive2020: 'SGLive 2020',
+ SGLive2021: 'SGLive 2021',
+ SpicyNormal: 'Spicy normal',
+ Tournament2021: 'Tournament 2021',
+ Tournament2022: 'Tournament 2022',
+ },
+ race: 'Race',
+ },
applications: {
accept: 'Accept',
acceptError: 'Error accepting',
return $request->user();
});
+Route::get('aos-seed/{hash}', 'App\Http\Controllers\AosSeedController@byHash');
+
Route::post('application/{application}/accept', 'App\Http\Controllers\ApplicationController@accept');
Route::post('application/{application}/reject', 'App\Http\Controllers\ApplicationController@reject');
'@restart/hooks',
'@restart/ui',
'axios',
+ 'file-saver',
'formik',
'i18next',
'i18next-browser-languagedetector',
'laravel-echo',
+ 'localforage',
'lodash',
'moment',
'numeral',