300 lines
9.2 KiB
Vue
300 lines
9.2 KiB
Vue
<script>
|
|
import { defineComponent } from "vue";
|
|
|
|
import SpritePicker from "@/components/SpritePicker.vue";
|
|
import HeartBeepPicker from "@/components/HeartBeepPicker.vue";
|
|
import HeartColorPicker from "@/components/HeartColorPicker.vue";
|
|
import QuickswapPicker from "@/components/QuickswapPicker.vue";
|
|
import ReduceFlashingPicker from "@/components/ReduceFlashingPicker.vue";
|
|
import BackgroundMusicPicker from "@/components/BackgroundMusicPicker.vue";
|
|
import MsuResumePicker from "@/components/MsuResumePicker.vue";
|
|
import CollectionRatePicker from "@/components/CollectionRatePicker.vue";
|
|
import UltraFastJunkPicker from "@/components/UltraFastJunkPicker.vue";
|
|
import Spoiler from "@/components/Spoiler.vue";
|
|
|
|
import SeedSettings from "@/components/SeedSettings.vue";
|
|
|
|
import { Base64 } from "js-base64";
|
|
import * as bps from "bps";
|
|
import CRC32 from "crc-32";
|
|
import localforage from "localforage";
|
|
import axios from "axios";
|
|
|
|
export default defineComponent({
|
|
components: {
|
|
SeedSettings,
|
|
SpritePicker,
|
|
HeartBeepPicker,
|
|
HeartColorPicker,
|
|
QuickswapPicker,
|
|
ReduceFlashingPicker,
|
|
BackgroundMusicPicker,
|
|
MsuResumePicker,
|
|
CollectionRatePicker,
|
|
UltraFastJunkPicker,
|
|
Spoiler,
|
|
},
|
|
data() {
|
|
return {
|
|
rom_checksum: "3322EFFC",
|
|
baserom: null,
|
|
baserom_error: null,
|
|
created: null,
|
|
patch: null,
|
|
error: null,
|
|
retry: false,
|
|
settings: {},
|
|
spoiler: {},
|
|
multi: null,
|
|
show_spoiler: false,
|
|
};
|
|
},
|
|
props: {
|
|
id: "",
|
|
},
|
|
async mounted() {
|
|
document.title = `ALttPRandomizer: ${this.id}`;
|
|
const file = await localforage.getItem("baserom");
|
|
if (file) {
|
|
// const dataTransfer = new DataTransfer();
|
|
// dataTransfer.items.add(file);
|
|
// this.baserom_file = dataTransfer.files;
|
|
// document.getElementById("rom-input").files = dataTransfer.files;
|
|
this.uploadBaseRom(file);
|
|
}
|
|
|
|
await this.fetchSeed();
|
|
},
|
|
computed: {
|
|
permalink() {
|
|
return `/seed/${this.id}`;
|
|
},
|
|
allow_cr() {
|
|
if (["triforce_hunt", "trinity", "ganon_hunt"].includes(this.settings.goal)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
},
|
|
},
|
|
methods: {
|
|
async retryGeneration() {
|
|
await axios.post(`/seed/${this.id}`, {}, {
|
|
headers: {
|
|
"Content-Type": "text/pain",
|
|
}
|
|
})
|
|
.then(response => {
|
|
this.$router.go();
|
|
})
|
|
.catch(error => {
|
|
console.log(error);
|
|
this.$router.go();
|
|
});
|
|
},
|
|
dataLoaded(patch, seedData) {
|
|
this.patch = patch;
|
|
this.settings = seedData.settings;
|
|
this.multi = seedData.parent;
|
|
this.spoiler = seedData.spoiler;
|
|
this.created = seedData.created;
|
|
},
|
|
async fetchSeed() {
|
|
await axios.get(`/seed/${this.id}`)
|
|
.then(response => {
|
|
if (response && response.data && response.data.patch) {
|
|
const seedData = response.data;
|
|
const patchArray = Base64.toUint8Array(seedData.patch);
|
|
try {
|
|
const { instructions, _ } = bps.parse(patchArray);
|
|
const sourceChecksum = instructions.sourceChecksum.toString(16).toUpperCase();
|
|
if (sourceChecksum == this.rom_checksum) {
|
|
this.dataLoaded(instructions, seedData);
|
|
} else {
|
|
this.error = "Patch does not specify correct source checksum.";
|
|
}
|
|
} catch (error) {
|
|
console.log(error);
|
|
this.error = "Error parsing patch.";
|
|
}
|
|
} else {
|
|
this.error = "Error loading seed.";
|
|
}
|
|
})
|
|
.catch(error => {
|
|
if (error.response?.status == 409) {
|
|
// still generating, try again
|
|
setTimeout(this.fetchSeed.bind(this), 2000);
|
|
} else {
|
|
if (error.response?.data?.retry) {
|
|
this.error = "Seed generation failed. :(";
|
|
this.retry = true;
|
|
} else {
|
|
this.error = "Seed not found. :(";
|
|
}
|
|
}
|
|
});
|
|
},
|
|
uploadBaseRom(file) {
|
|
if (!file) {
|
|
this.baserom_error = null;
|
|
this.baserom = null;
|
|
return;
|
|
}
|
|
|
|
const reader = new FileReader();
|
|
reader.onload = function() {
|
|
const buffer = new Uint8Array(reader.result);
|
|
const crc = (CRC32.buf(buffer, 0) >>> 0).toString(16).toUpperCase();
|
|
|
|
if (crc != this.rom_checksum) {
|
|
this.baserom_error = `Expected CRC ${this.rom_checksum}, but got ${crc}`;
|
|
this.baserom = null;
|
|
return;
|
|
}
|
|
|
|
this.baserom_error = null;
|
|
this.baserom = buffer;
|
|
localforage.setItem("baserom", file);
|
|
}.bind(this);
|
|
reader.readAsArrayBuffer(file);
|
|
},
|
|
async patchRom() {
|
|
var rom = bps.apply(this.patch, this.baserom);
|
|
|
|
await this.$refs.spritepicker.patch(rom);
|
|
|
|
this.$refs.heartbeep.patch(rom);
|
|
this.$refs.heartcolor.patch(rom);
|
|
this.$refs.msu_resume.patch(rom);
|
|
this.$refs.bgm.patch(rom);
|
|
this.$refs.reduce_flashing.patch(rom);
|
|
this.$refs.quickswap.patch(rom);
|
|
this.$refs.ufj.patch(rom);
|
|
|
|
if (this.allow_cr) {
|
|
this.$refs.cr.patch(rom);
|
|
}
|
|
|
|
// Fix Checksum
|
|
const sum = rom.reduce(function(sum, mbyte, i) {
|
|
if (i >= 0x7fdc && i < 0x7fe0) {
|
|
return sum;
|
|
}
|
|
return sum + mbyte;
|
|
});
|
|
const checksum = (sum + 0x01FE) & 0xFFFF;
|
|
const inverse = checksum ^ 0xFFFF;
|
|
rom[0x7FDC] = inverse & 0xFF;
|
|
rom[0x7FDD] = inverse >> 8;
|
|
rom[0x7FDE] = checksum & 0xFF;
|
|
rom[0x7FDF] = checksum >> 8;
|
|
|
|
const blob = new Blob([rom], { type: 'octet/stream' });
|
|
const link = document.getElementById('downloader');
|
|
link.href = URL.createObjectURL(blob);
|
|
link.download = `GK_${this.id}.sfc`;
|
|
link.click();
|
|
},
|
|
async downloadSpoiler() {
|
|
const spoiler = JSON.stringify(this.spoiler, null, 2);
|
|
const blob = new Blob([spoiler], { type: 'application/json' });
|
|
const link = document.getElementById('downloader');
|
|
link.href = URL.createObjectURL(blob);
|
|
link.download = `GK_${this.id}_spoiler.json`;
|
|
link.click();
|
|
},
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div class="mw-30">
|
|
<div v-if="patch" class="card content-div m-3">
|
|
<div class="card-header">
|
|
Permalink: <a :href="permalink">{{ permalink }}</a>
|
|
</div>
|
|
<ul class="list-group list-group-flush">
|
|
<li v-if="!baserom" class="list-group-item">
|
|
<div class="mb-2">
|
|
<label for="rom-input" class="form-label">
|
|
The Legend of Zelda: A Link to the Past (JP 1.0) Rom:
|
|
</label>
|
|
<input id="rom-input" class="form-control" type="file" accept=".sfc,.smc" @change="uploadBaseRom($event.target.files[0])" />
|
|
<div v-if="baserom_error" class="invalid">
|
|
{{ baserom_error }}
|
|
</div>
|
|
</div>
|
|
</li>
|
|
<li v-if="settings" class="list-group-item">
|
|
<div class="mb-1">
|
|
<SeedSettings :settings="settings" :multi="multi" />
|
|
</div>
|
|
</li>
|
|
<li class="list-group-item">
|
|
<div class="mb-2">
|
|
<SpritePicker ref="spritepicker" />
|
|
</div>
|
|
</li>
|
|
<li class="list-group-item">
|
|
<div class="mb-2">
|
|
<HeartBeepPicker ref="heartbeep" />
|
|
</div>
|
|
</li>
|
|
<li class="list-group-item">
|
|
<div class="mb-2">
|
|
<HeartColorPicker ref="heartcolor" />
|
|
</div>
|
|
</li>
|
|
<li class="list-group-item">
|
|
<div>
|
|
<BackgroundMusicPicker ref="bgm" />
|
|
<MsuResumePicker ref="msu_resume" />
|
|
<ReduceFlashingPicker ref="reduce_flashing" />
|
|
<QuickswapPicker ref="quickswap" />
|
|
<CollectionRatePicker v-if="allow_cr" ref="cr" />
|
|
<UltraFastJunkPicker :created="created" ref="ufj" />
|
|
</div>
|
|
</li>
|
|
<li class="list-group-item">
|
|
<button type="submit" class="btn btn-primary submit-btn" :disabled="!baserom || !patch" @click="patchRom">
|
|
Download
|
|
</button>
|
|
<template v-if="spoiler">
|
|
<button type="button" class="btn btn-secondary ms-3" @click="downloadSpoiler">
|
|
Spoiler
|
|
</button>
|
|
<button type="button" class="btn btn-secondary" @click="show_spoiler = !show_spoiler" style="float: right;">
|
|
<i v-if="show_spoiler" class="bi bi-chevron-up"></i>
|
|
<i v-else class="bi bi-chevron-down"></i>
|
|
</button>
|
|
</template>
|
|
<a id="downloader" style="display: none;" />
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<div v-else-if="error" class="error-message">
|
|
{{ error }}
|
|
<div v-if="retry">
|
|
<button type="submit" class="btn btn-primary submit-btn" @click="retryGeneration">
|
|
Retry
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div v-else>
|
|
<img class="center" src="/bludormspinbig.gif" />
|
|
</div>
|
|
</div>
|
|
<div v-if="patch && spoiler && show_spoiler" class="mw-60">
|
|
<Spoiler :spoiler="spoiler" />
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.error-message {
|
|
text-align: center;
|
|
font-size: xx-large;
|
|
font-weight: bold;
|
|
margin-top: 3rem;
|
|
}
|
|
</style>
|