Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- "use strict";
- /*
- * constants
- */
- // total size of FRAM
- const SIZE=512;
- const S3_SAVE_NAME ="sonic3.srm";
- const S3K_SAVE_NAME="s3&k.srm";
- const MIME_TYPE="application/x-sonic3-save-file";
- const STORAGE_NAME="sonic3";
- // single-player for Sonic 3
- const S3_SECTION_LENGTH=52;
- const S3_SLOT_LENGTH =8;
- const S3_SLOTS =6;
- const S3_START1=0x0b4;
- const S3_START2=0x0fa;
- // single-player for S3&K
- const S3K_SECTION_LENGTH=84; // also competition section length
- const S3K_SLOT_LENGTH =10;
- const S3K_SLOTS =8;
- const S3K_START1=0x140;
- const S3K_START2=0x196;
- // competition mode
- const RANKINGS=3; // number of competition rankings
- const CP_SLOT_LENGTH=4;
- const CP_START1=0x008;
- const CP_START2=0x05e;
- // characters
- const SONIC_TAILS=0;
- const SONIC=1;
- const TAILS=2;
- const KNUCKLES=3;
- const NOBODY=-1;
- const S3=false, S3K=true;
- const CLEAR=0x01, CHAOS_CLEAR=0x02, SUPER_CLEAR=0x03, NEW=0x80;
- const S3_LAST_ZONE=0x06;
- const SONIC_LAST_ZONE=0x0d, TAILS_LAST_ZONE=0x0c, KNUCKLES_LAST_ZONE=0x0b;
- const defaults=[0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 128, 0, 0, 0, 128, 0, 0, 0, 0, 1, 2, 0, 128, 0, 0, 0, 128, 0, 0, 0, 128, 0, 0, 0, 0, 1, 2, 0, 128, 0, 0, 0, 128, 0, 0, 0, 128, 0, 0, 0, 0, 1, 2, 0, 128, 0, 0, 0, 128, 0, 0, 0, 128, 0, 0, 0, 0, 1, 2, 0, 128, 0, 0, 0, 128, 0, 0, 0, 128, 0, 0, 0, 0, 1, 2, 0, 76, 68, 46, 90, 0, 0, 128, 0, 0, 0, 128, 0, 0, 0, 128, 0, 0, 0, 0, 1, 2, 0, 128, 0, 0, 0, 128, 0, 0, 0, 128, 0, 0, 0, 0, 1, 2, 0, 128, 0, 0, 0, 128, 0, 0, 0, 128, 0, 0, 0, 0, 1, 2, 0, 128, 0, 0, 0, 128, 0, 0, 0, 128, 0, 0, 0, 0, 1, 2, 0, 128, 0, 0, 0, 128, 0, 0, 0, 128, 0, 0, 0, 0, 1, 2, 0, 76, 68, 46, 90, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 66, 68, 229, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 66, 68, 229, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 3, 0, 128, 0, 0, 0, 0, 0, 0, 0, 3, 0, 128, 0, 0, 0, 0, 0, 0, 0, 3, 0, 128, 0, 0, 0, 0, 0, 0, 0, 3, 0, 128, 0, 0, 0, 0, 0, 0, 0, 3, 0, 128, 0, 0, 0, 0, 0, 0, 0, 3, 0, 128, 0, 0, 0, 0, 0, 0, 0, 3, 0, 128, 0, 0, 0, 0, 0, 0, 0, 3, 0, 66, 68, 112, 244, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 3, 0, 128, 0, 0, 0, 0, 0, 0, 0, 3, 0, 128, 0, 0, 0, 0, 0, 0, 0, 3, 0, 128, 0, 0, 0, 0, 0, 0, 0, 3, 0, 128, 0, 0, 0, 0, 0, 0, 0, 3, 0, 128, 0, 0, 0, 0, 0, 0, 0, 3, 0, 128, 0, 0, 0, 0, 0, 0, 0, 3, 0, 128, 0, 0, 0, 0, 0, 0, 0, 3, 0, 66, 68, 112, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
- /*
- * initialization
- */
- window.addEventListener("load", function() {
- const editor=new Editor(defaults);
- const store=new Storage(STORAGE_NAME);
- let mem=store.load();
- let save=new Save();
- if (mem) { // restores from local storage if set, otherwise uses defaults
- save.loadFromArray(mem);
- } else {
- save.loadFromArray(defaults);
- }
- editor.open(save);
- editor.toggleAdvanced();
- window.addEventListener("beforeunload", function() {
- store.save(editor.saveToStorage());
- });
- // load
- $("#file").addEventListener("change", function(event) {
- let file=event.target.files[0];
- if (file) {
- let reader=new FileReader();
- reader.addEventListener("load", function(event) {
- let save=new Save();
- save.loadFromBuffer(event.target.result);
- editor.open(save);
- });
- reader.readAsArrayBuffer(file);
- }
- });
- $("#download").addEventListener("click", function() {
- let filename="", blob=null;
- try {
- [filename, blob]=editor.saveToFile();
- let a=$("#link");
- a.download=filename;
- a.href=window.URL.createObjectURL(blob);
- a.click();
- window.URL.revokeObjectURL(blob);
- } catch (err) {
- displayError(err);
- }
- });
- $("#advanced").addEventListener("click", function() {
- editor.toggleAdvanced();
- });
- // save
- $("#reset").addEventListener("click", function() {
- let save=new Save();
- save.loadFromArray(defaults);
- editor.open(save);
- });
- for (let element of $$(".game")) {
- element.addEventListener("click", function() {
- editor.setGame(Number.parseInt(element.value));
- });
- }
- for (let element of $$(".write")) {
- element.addEventListener("click", function() {
- editor.setWrite(Number.parseInt(this.value), this.checked);
- editor.saveSinglePlayer();
- });
- }
- for (let element of $$(".slot")) {
- element.addEventListener("click", function() {
- editor.setSlot(Number.parseInt(element.value));
- });
- }
- for (let element of $$(".stage")) {
- element.addEventListener("click", function() {
- editor.setStage(Number.parseInt(element.value));
- });
- }
- for (let element of $$(".ring")) {
- element.addEventListener("click", function() {
- this.classList.toggle("active");
- editor.saveSinglePlayer();
- });
- }
- for (let element of $$(".arrow")) {
- element.addEventListener("click", function() {
- let id=this.parentNode.parentNode.parentNode.parentNode.id;
- let select=$(`#${id} .zone`);
- let modifier=Number.parseInt(this.value);
- let value=Number.parseInt(select.value)+modifier;
- if (value>=0&&value<select.length) {
- select.value=value;
- }
- editor.saveSinglePlayer();
- });
- }
- for (let element of $$("#singlePlayer .character")) {
- element.addEventListener("click", function() {
- let id=this.parentNode.parentNode.parentNode.parentNode.id;
- editor.selectActive(`#${id} .character`, this.value);
- editor.saveSinglePlayer();
- });
- }
- for (let element of $$("#singlePlayer .new, #singlePlayer .clear")) {
- element.addEventListener("click", function() { // click event
- editor.saveSinglePlayer();
- });
- }
- for (let element of $$("#singlePlayer input[type=\"number\"], .zone")) {
- element.addEventListener("input", function() { // input event
- editor.saveSinglePlayer();
- });
- }
- for (let element of $$("#s3 .emerald")) {
- element.addEventListener("click", function() {
- let chaos=this.classList.contains("chaos");
- this.classList.toggle("chaos", !chaos);
- this.classList.toggle("empty", chaos);
- editor.saveSinglePlayer();
- });
- }
- for (let element of $$("#s3k .emerald")) {
- element.addEventListener("click", function() {
- // rotates through emerald states
- if (this.classList.contains("empty")) {
- this.classList.add("chaos");
- this.classList.remove("empty");
- } else if (this.classList.contains("chaos")) {
- this.classList.add("palace");
- this.classList.remove("chaos");
- } else if (this.classList.contains("palace")) {
- this.classList.add("super");
- this.classList.remove("palace");
- } else if (this.classList.contains("super")) {
- this.classList.add("empty");
- this.classList.remove("super");
- }
- editor.saveSinglePlayer();
- });
- }
- for (let element of $$("#competition .character")) {
- element.addEventListener("click", function() {
- let n=this.parentNode.parentNode.id.replace(/[^0-9]+/, "");
- editor.selectActive(`#row${n} .character`, this.value);
- editor.saveCompetition();
- });
- }
- for (let element of $$("#competition input")) { // checkboxes and numbers
- element.addEventListener("input", function() {
- editor.saveCompetition();
- });
- }
- for (let element of $$(".close")) {
- element.addEventListener("click", function() {
- for (let overlay of $$(".overlay")) {
- overlay.classList.remove("open");
- }
- });
- }
- for (let element of $$(".size")) {
- element.addEventListener("click", function() {
- editor.toggleAdvanced();
- });
- }
- function displayError(message) {
- $("#error").classList.add("open");
- $("#error p").textContent=message;
- }
- });
- function $(selector) {
- return document.querySelector(selector);
- }
- function $$(selector) {
- return Array.from(document.querySelectorAll(selector));
- }
- /*
- * Editor prototype
- */
- function Editor(defaults) {
- this.save=null;
- this.defaults=defaults;
- this.currentGame =S3K;
- this.currentSlot =0;
- this.currentStage=0;
- this.writeS3 =false;
- this.writeS3K=true;
- }
- Editor.prototype.open=function(save) {
- this.save=save;
- if (this.save.singlePlayerS3==null) {
- this.save.singlePlayerS3=this.defaults.slice(
- S3_START1, S3_START1+S3_SECTION_LENGTH
- );
- }
- if (this.save.singlePlayerS3K==null) {
- this.save.singlePlayerS3K=this.defaults.slice(
- S3K_START1, S3K_START1+S3K_SECTION_LENGTH
- );
- }
- if (this.save.competition==null) {
- this.save.competition=this.defaults.slice(
- CP_START1, CP_START1+S3K_SECTION_LENGTH
- );
- }
- this.writeS3 =this.getChecksum(this.save.singlePlayerS3) !=0;
- this.writeS3K=this.getChecksum(this.save.singlePlayerS3K)!=0;
- this.setWrite(S3, this.writeS3);
- this.setWrite(S3K, this.writeS3K);
- // sets game to Sonic 3 if no S3&K data available, otherwise sets to S3&K
- this.setGame(this.writeS3K);
- this.setSlot();
- this.setStage();
- };
- Editor.prototype.getChecksum=function(section) {
- return (section[section.length-2]<<8)|section[section.length-1];
- };
- Editor.prototype.openDefaults=function(save) {
- this.open(save);
- // Sonic 3 is disabled by default but will not be disabled automatically
- // because default save file contains save data for both S3 and S3&K
- this.setWrite(S3, false);
- this.saveSinglePlayer();
- };
- Editor.prototype.saveToFile=function() {
- if (!this.writeS3&&!this.writeS3K) {
- throw "Must enable Sonic 3 or Sonic 3 & Knuckles.";
- }
- let file=null;
- // merges changes to file buffer
- this.save.update(this.writeS3, this.writeS3K);
- if ($("#byte").checked) { // byte
- file=this.save.file;
- } else { // word
- let fillerByte=$("#bff").checked?0xff:0;
- let byteOrder=$("#little").checked;
- // using Uint8Array because the byte order of numbers saved in
- // Uint16Array is architecture-dependent
- file=new Uint8Array(SIZE*2);
- for (let i=0, n=0; i<file.length; i+=2, n++) {
- if (byteOrder) { // little endian
- file[i] =this.save.file[n];
- file[i+1]=fillerByte;
- } else { // big endian
- file[i] =fillerByte;
- file[i+1]=this.save.file[n];
- }
- }
- }
- // uses upload file name if available
- let filename=$("#file").value.replace(/.*(\\|\/)/, "")||"";
- if (!filename) {
- filename=this.writeS3K?S3K_SAVE_NAME:S3_SAVE_NAME;
- }
- return [filename, new Blob([file], {type: MIME_TYPE})];
- };
- Editor.prototype.saveToStorage=function() {
- this.save.update(this.writeS3, this.writeS3K);
- return this.save.file;
- };
- Editor.prototype.selectActive=function(selector, value) {
- for (let element of $$(selector)) {
- let condition=Number.parseInt(element.value)==value;
- element.classList.toggle("active", condition);
- }
- };
- Editor.prototype.setGame=function(value) {
- this.currentGame=value;
- $("#s3").classList.toggle("hidden", this.currentGame);
- $("#s3k").classList.toggle("hidden", !this.currentGame);
- this.selectActive(".game", value);
- let max=(this.currentGame?S3K_SLOTS:S3_SLOTS)-1;
- this.setSlot(Math.min(this.currentSlot, max));
- for (let element of $$(".slot")) {
- element.disabled=Number.parseInt(element.value)>max;
- }
- };
- Editor.prototype.setWrite=function(game, checked) {
- if (game==S3) {
- this.writeS3=checked;
- $("#s3 .write").checked=checked;
- this.toggleElements("#s3 button, #s3 tbody input", !checked);
- // loads default values if data is empty
- if (this.getChecksum(this.save.singlePlayerS3)==0) {
- this.save.singlePlayerS3=this.defaults.slice(
- S3_START1, S3_START1+S3_SECTION_LENGTH
- );
- }
- } else {
- this.writeS3K=checked;
- $("#s3k .write").checked=checked;
- this.toggleElements("#s3k button, #s3k tbody input", !checked);
- if (this.getChecksum(this.save.singlePlayerS3K)==0) {
- this.save.singlePlayerS3K=this.defaults.slice(
- S3K_START1, S3K_START1+S3K_SECTION_LENGTH
- );
- }
- }
- };
- Editor.prototype.setSlot=function(value=0) {
- this.currentSlot=value;
- this.loadSinglePlayer();
- this.selectActive(".slot", value);
- };
- Editor.prototype.setStage=function(value=0) {
- this.currentStage=value;
- this.loadCompetition();
- this.selectActive(".stage", value);
- };
- Editor.prototype.loadSinglePlayer=function() {
- let self=this;
- if (this.currentGame==S3) {
- loadSinglePlayerS3();
- loadTabs(this.save.getSlotCharactersS3(), S3_SLOTS, this.writeS3);
- } else {
- loadSinglePlayerS3K();
- loadTabs(this.save.getSlotCharactersS3K(), S3K_SLOTS, this.writeS3K);
- }
- function loadSinglePlayerS3() {
- let slot=self.save.getSaveSlotS3(self.currentSlot);
- if (self.writeS3) {
- self.toggleElements(
- "#s3 button, #s3 input[type=\"number\"]",
- slot.isNew
- );
- }
- $("#s3 .new").checked =slot.isNew;
- $("#s3 .clear").checked=slot.isClear;
- $("#last").value =slot.last;
- self.selectActive("#s3 .character", slot.character);
- selectRings("#s3 .ring", slot.rings);
- selectZone("s3", slot.zone);
- for (let element of $$("#s3 .emerald")) {
- let chaos=Number.parseInt(element.value)&slot.emeralds;
- element.classList.toggle("empty", !chaos);
- element.classList.toggle("chaos", chaos);
- }
- let image="";
- if (self.writeS3) {
- if (slot.isNew) {
- image="new";
- } else {
- if (slot.isClear) {
- // Sonic 3 shows Sonic picture regardless of character
- if (slot.numEmeralds>=7) {
- image="clear-sonic-chaos";
- } else {
- image="clear-sonic";
- }
- } else {
- image="zone-"+slot.zone.toString().padStart(2, "0");
- }
- }
- } else {
- image="static";
- }
- $("#s3 .preview").src="images/"+image+".png";
- }
- function loadSinglePlayerS3K() {
- let slot=self.save.getSaveSlotS3K(self.currentSlot);
- if (self.writeS3K) {
- self.toggleElements(
- "#s3k button, #s3k input[type=\"number\"]",
- slot.isNew
- );
- }
- $("#s3k .new").checked =slot.isNew;
- $("#s3k .clear").checked=slot.isClear;
- $("#lives").value =slot.lives;
- $("#continues").value =slot.continues;
- self.selectActive("#s3k .character", slot.character);
- selectRings("#s3k .ring", slot.rings);
- selectZone("s3k", slot.zone);
- let numEmeralds=0;
- for (let element of $$("#s3k .emerald")) {
- let offset=Number.parseInt(element.value);
- let emeralds=slot.emeralds1;
- if (element.classList.contains("grey")||
- element.classList.contains("red")||
- element.classList.contains("blue")
- ) {
- emeralds=slot.emeralds2;
- }
- let chaos =emeralds&(1<<offset);
- let palace=emeralds&(1<<offset+1);
- numEmeralds+=Number(chaos||palace);
- element.classList.toggle("empty", !chaos&&!palace);
- element.classList.toggle("chaos", chaos&&!palace);
- element.classList.toggle("palace", !chaos&& palace);
- element.classList.toggle("super", chaos&& palace);
- }
- let select=$("#s3k .zone");
- for (let element of $$("#s3k .zone option")) {
- let value=Number.parseInt(element.value);
- if (slot.character==TAILS) {
- element.disabled=value>TAILS_LAST_ZONE;
- clampZone(slot, select, TAILS_LAST_ZONE);
- } else if (slot.character==KNUCKLES) {
- element.disabled=value>KNUCKLES_LAST_ZONE;
- clampZone(slot, select, KNUCKLES_LAST_ZONE);
- } else {
- // at least 7 chaos or super emeralds collected as Sonic
- if (numEmeralds>=7) {
- element.disabled=false;
- // value can be higher for cleared games
- clampZone(slot, select, SONIC_LAST_ZONE);
- } else {
- let lastZone=SONIC_LAST_ZONE-1;
- element.disabled=value>lastZone;
- clampZone(slot, select, lastZone);
- }
- }
- }
- let image="";
- if (self.writeS3K) {
- if (slot.isNew) {
- image="new";
- } else {
- if (slot.isClear) { // clear
- if (slot.isClear==SUPER_CLEAR) { // all super emeralds
- image="clear-super";
- } else {
- if (slot.isClear==CHAOS_CLEAR) { // all chaos emeralds
- switch (slot.character) {
- case TAILS:
- image="clear-tails-chaos";
- break;
- case KNUCKLES:
- image="clear-knuckles-chaos";
- break;
- default:
- image="clear-sonic-chaos";
- }
- } else {
- switch (slot.character) {
- case TAILS:
- image="clear-tails";
- break;
- case KNUCKLES:
- image="clear-knuckles";
- break;
- default:
- image="clear-sonic";
- }
- }
- }
- } else {
- image="zone-"+slot.zone.toString().padStart(2, "0");
- }
- }
- } else {
- image="static";
- }
- $("#s3k .preview").src="images/"+image+".png";
- }
- function loadTabs(characters, max, enabled) {
- for (let [i, element] of $$(".slot").entries()) {
- if (i>=max) {
- break;
- }
- if (enabled) {
- let character=characters[i];
- element.classList.toggle("sonic", character==SONIC);
- element.classList.toggle("tails", character==TAILS);
- element.classList.toggle("sonictails", character==SONIC_TAILS);
- element.classList.toggle("knuckles", character==KNUCKLES);
- } else {
- element.classList.remove(
- "sonic", "tails", "sonictails", "knuckles"
- );
- }
- }
- }
- function selectRings(selector, rings) {
- for (let element of $$(selector)) {
- let condition=rings&(1<<Number.parseInt(element.value));
- element.classList.toggle("active", condition);
- }
- }
- function clampZone(slot, select, lastZone) {
- if (slot.zone>lastZone) {
- select.value=lastZone;
- slot.zone=lastZone;
- }
- }
- function selectZone(id, zone) {
- let select=$(`#${id} .zone`);
- select.value=zone;
- let clear=$(`#${id} .new`).checked||$(`#${id} .clear`).checked;
- $(`#${id} .zone`).disabled=clear;
- $(`#${id} .prev`).disabled=clear||select.value==0;
- $(`#${id} .next`).disabled=clear||select.value==select.length-1;
- }
- };
- Editor.prototype.saveSinglePlayer=function() {
- let self=this;
- if (this.currentGame==S3) {
- saveSinglePlayerS3();
- } else {
- saveSinglePlayerS3K();
- }
- function saveSinglePlayerS3() {
- let emeralds=0, numEmeralds=0;
- for (let element of $$("#s3 .emerald")) {
- if (element.classList.contains("chaos")) {
- emeralds+=Number.parseInt(element.value);
- numEmeralds++;
- }
- }
- // must check if null before attempting to use value
- let character=$("#s3 .character.active");
- character=character?Number.parseInt(character.value):0;
- self.save.setSaveSlotS3(self.currentSlot, {
- isNew: $("#s3 .new").checked,
- isClear: $("#s3 .clear").checked,
- character: character,
- zone: Number.parseInt($("#s3 .zone").value)||0,
- last: fillNumber("#last"),
- numEmeralds: numEmeralds,
- emeralds: emeralds,
- rings: fillRings("#s3 .ring")
- });
- self.loadSinglePlayer();
- }
- function saveSinglePlayerS3K() {
- let emeralds1=0, emeralds2=0, chaosEmeralds=0, superEmeralds=0;
- for (let element of $$("#s3k .emerald")) {
- let offset=Number.parseInt(element.value);
- let chaos =element.classList.contains("chaos");
- let palace=element.classList.contains("palace");
- if (element.classList.contains("super")) {
- chaos =true;
- palace=true;
- }
- if (element.classList.contains("grey")||
- element.classList.contains("red")||
- element.classList.contains("blue")
- ) {
- if (chaos||palace) {
- if (chaos) {
- emeralds2|=1<<offset;
- }
- if (palace) {
- emeralds2|=1<<offset+1;
- }
- chaosEmeralds++;
- superEmeralds+=Number(chaos&&palace);
- }
- } else {
- if (chaos||palace) {
- if (chaos) {
- emeralds1|=1<<offset;
- }
- if (palace) {
- emeralds1|=1<<offset+1;
- }
- chaosEmeralds++;
- superEmeralds+=Number(chaos&&palace);
- }
- }
- }
- let clear=0;
- if ($("#s3k .clear").checked) {
- if (superEmeralds>=7) {
- clear=SUPER_CLEAR;
- } else if (chaosEmeralds>=7) {
- clear=CHAOS_CLEAR;
- } else {
- clear=CLEAR;
- }
- }
- let character=$("#s3k .character.active");
- character=character?Number.parseInt(character.value):0;
- self.save.setSaveSlotS3K(self.currentSlot, {
- isNew: $("#s3k .new").checked,
- isClear: clear,
- character: character,
- numEmeralds: chaosEmeralds,
- zone: Number.parseInt($("#s3k .zone").value)||0,
- rings: fillRings("#s3k .ring"),
- emeralds1: emeralds1,
- emeralds2: emeralds2,
- lives: fillNumber("#lives"),
- continues: fillNumber("#continues")
- });
- self.loadSinglePlayer();
- }
- function fillNumber(selector) {
- let element=$(selector);
- let value=Number.parseInt(element.value);
- value=Math.min(element.max, value);
- value=Math.max(element.min, value);
- return value;
- }
- function fillRings(selector) {
- let rings=0;
- for (let element of $$(selector)) {
- if (element.classList.contains("active")) {
- rings|=1<<Number.parseInt(element.value);
- }
- }
- return rings;
- }
- };
- Editor.prototype.loadCompetition=function() {
- let rows=this.save.getStage(this.currentStage);
- for (let [i, row] of rows.entries()) {
- $(`#row${i} .new`).checked=row.isNew;
- $(`#row${i} .min`).value =row.min;
- $(`#row${i} .sec`).value =row.sec;
- $(`#row${i} .msec`).value =row.msec;
- this.toggleElements(
- `#row${i} button, #row${i} input[type="number"]`,
- row.isNew
- );
- this.selectActive(`#row${i} .character`, row.character);
- }
- };
- Editor.prototype.saveCompetition=function() {
- let rows=Array(RANKINGS).fill().map(function() {
- return {};
- });
- rows=fillBoolean("#competition .new", "isNew");
- rows=fillNumber("#competition .min", "min");
- rows=fillNumber("#competition .sec", "sec");
- rows=fillNumber("#competition .msec", "msec");
- rows=fillButtons("#competition .character", "character");
- this.save.setStage(this.currentStage, rows);
- this.loadCompetition();
- function fillBoolean(selector, key) {
- for (let [i, element] of $$(selector).entries()) {
- rows[i][key]=element.checked;
- }
- return rows;
- }
- function fillNumber(selector, key) {
- for (let [i, element] of $$(selector).entries()) {
- let value=Number.parseInt(element.value);
- value=Math.min(element.max, value);
- value=Math.max(element.min, value);
- rows[i][key]=value;
- }
- return rows;
- }
- function fillButtons(selector, key) {
- let n=0;
- for (let element of $$(selector)) {
- if (element.classList.contains("active")) {
- rows[n][key]=Number.parseInt(element.value);
- n++; // only increments once per table row
- }
- }
- return rows;
- }
- };
- Editor.prototype.toggleAdvanced=function() {
- let advanced=$("#advanced").checked;
- for (let element of $$(".advanced")) {
- element.classList.toggle("hidden", !advanced);
- }
- let order=$("#byte").checked;
- for (let element of $$(".filler, .order")) {
- element.disabled=order;
- }
- };
- Editor.prototype.toggleElements=function(selector, condition) {
- for (let element of $$(selector)) {
- element.disabled=condition;
- }
- };
- /*
- * Save prototype
- */
- function Save() {
- this.file=null;
- this.singlePlayerS3 =[];
- this.singlePlayerS3K=[];
- this.competition =[];
- }
- Save.prototype.loadFromArray=function(arr) {
- this.file=Uint8Array.from(arr);
- this.parse();
- };
- Save.prototype.loadFromBuffer=function(buffer) {
- this.file=new Uint8Array(buffer);
- if (buffer.byteLength==SIZE*2) {
- this.file=this.file.filter(function(undefined, i) {
- return i%2!=0; // skips odd bytes
- });
- }
- this.parse();
- };
- Save.prototype.parse=function() {
- let self=this;
- this.singlePlayerS3=checkChecksums(
- this.file.slice(S3_START1, S3_START1+S3_SECTION_LENGTH),
- this.file.slice(S3_START2, S3_START2+S3_SECTION_LENGTH)
- );
- this.singlePlayerS3K=checkChecksums(
- this.file.slice(S3K_START1, S3K_START1+S3K_SECTION_LENGTH),
- this.file.slice(S3K_START2, S3K_START2+S3K_SECTION_LENGTH)
- );
- this.competition=checkChecksums(
- this.file.slice(CP_START1, CP_START1+S3K_SECTION_LENGTH),
- this.file.slice(CP_START2, CP_START2+S3K_SECTION_LENGTH)
- );
- // all data is duplicated in the save file for integrity;
- // uses first set if checksum passes,
- // otherwise uses second set if checksum passes,
- // otherwise returns null
- function checkChecksums(arr1, arr2) {
- let result=[];
- if (self.verifyChecksum(arr1)) {
- result=arr1;
- } else {
- if (self.verifyChecksum(arr2)) {
- result=arr2;
- } else {
- result=null;
- }
- }
- return result;
- }
- };
- Save.prototype.update=function(writeS3=true, writeS3K=true) {
- this.singlePlayerS3 =this.updateChecksum(this.singlePlayerS3);
- this.singlePlayerS3K=this.updateChecksum(this.singlePlayerS3K);
- this.competition =this.updateChecksum(this.competition);
- let self=this;
- this.file=mergeSection(S3_START1, this.singlePlayerS3, writeS3);
- this.file=mergeSection(S3_START2, this.singlePlayerS3, writeS3);
- this.file=mergeSection(S3K_START1, this.singlePlayerS3K, writeS3K);
- this.file=mergeSection(S3K_START2, this.singlePlayerS3K, writeS3K);
- this.file=mergeSection(CP_START1, this.competition);
- this.file=mergeSection(CP_START2, this.competition);
- function mergeSection(start, source, write=true) {
- let stop=start+source.length;
- for (let i=start, n=0; i<stop; i++, n++) {
- self.file[i]=write?source[n]:0;
- }
- return self.file;
- }
- };
- Save.prototype.calcChecksum=function(bytes) {
- const BIT_MASK=0x8810;
- let checksum=0, carry=0;
- for (let i=0; i<bytes.length-2; i+=2) {
- checksum^=(bytes[i]<<8)|bytes[i+1];
- carry=checksum&1; // gets least significant bit before shift
- checksum>>>=1;
- if (carry) {
- checksum^=BIT_MASK;
- }
- }
- return checksum;
- };
- Save.prototype.verifyChecksum=function(bytes) {
- // saves original checksum
- let original=(bytes[bytes.length-2]<<8)|bytes[bytes.length-1];
- let checksum=this.calcChecksum(bytes);
- return original==checksum;
- };
- Save.prototype.updateChecksum=function(bytes) {
- let checksum=this.calcChecksum(bytes);
- // writes new checksum to last two bytes of data
- bytes[bytes.length-1]= checksum&0x00ff;
- bytes[bytes.length-2]=(checksum&0xff00)>>8;
- return bytes;
- };
- Save.prototype.getSaveSlotS3=function(current) {
- let pos=current*S3_SLOT_LENGTH;
- let zone=this.singlePlayerS3[pos+3];
- // adjusts zones after Flying Battery to match S3&K
- if (zone>4&&zone<=S3_LAST_ZONE) {
- zone--;
- }
- return {
- isNew: this.singlePlayerS3[pos]==NEW,
- isClear: zone>S3_LAST_ZONE,
- character: this.singlePlayerS3[pos+2],
- zone: zone,
- last: this.singlePlayerS3[pos+4],
- numEmeralds: this.singlePlayerS3[pos+5],
- emeralds: this.singlePlayerS3[pos+6],
- rings: this.singlePlayerS3[pos+7]
- };
- };
- Save.prototype.getSaveSlotS3K=function(current) {
- let pos=current*S3K_SLOT_LENGTH;
- let clear=this.singlePlayerS3K[pos];
- if (clear>SUPER_CLEAR) {
- clear=0;
- }
- return {
- isNew: this.singlePlayerS3K[pos]==NEW,
- isClear: clear,
- character: (this.singlePlayerS3K[pos+2]&0xf0)>>4,
- numEmeralds: this.singlePlayerS3K[pos+2]&0x0f,
- zone: this.singlePlayerS3K[pos+3],
- rings: this.singlePlayerS3K[pos+4],
- emeralds1: this.singlePlayerS3K[pos+6],
- emeralds2: this.singlePlayerS3K[pos+7],
- lives: this.singlePlayerS3K[pos+8],
- continues: this.singlePlayerS3K[pos+9]
- };
- };
- Save.prototype.getSlotCharactersS3=function() {
- let characters=[];
- for (let i=0; i<S3_SLOTS; i++) {
- let pos=i*S3_SLOT_LENGTH;
- let isNew =this.singlePlayerS3[pos]==NEW;
- let character=this.singlePlayerS3[pos+2];
- characters.push(isNew?NOBODY:character);
- }
- return characters;
- };
- Save.prototype.getSlotCharactersS3K=function() {
- let characters=[];
- for (let i=0; i<S3K_SLOTS; i++) {
- let pos=i*S3K_SLOT_LENGTH;
- let isNew =this.singlePlayerS3K[pos]==NEW;
- let character=(this.singlePlayerS3K[pos+2]&0xf0)>>4;
- characters.push(isNew?NOBODY:character);
- }
- return characters;
- };
- Save.prototype.setSaveSlotS3=function(current, slot) {
- let pos=current*S3_SLOT_LENGTH;
- if (slot.isNew) {
- this.singlePlayerS3[pos] =NEW;
- this.singlePlayerS3[pos+1]=0;
- this.singlePlayerS3[pos+2]=0;
- this.singlePlayerS3[pos+3]=0;
- this.singlePlayerS3[pos+4]=0;
- this.singlePlayerS3[pos+5]=0;
- this.singlePlayerS3[pos+6]=0;
- this.singlePlayerS3[pos+7]=0;
- } else {
- let zone=slot.zone;
- if (slot.isClear) {
- zone=S3_LAST_ZONE+1;
- } else {
- // adjust from S3&K numbering
- if (zone>=4) {
- zone++;
- }
- }
- this.singlePlayerS3[pos] =0;
- this.singlePlayerS3[pos+1]=0; // always zero
- this.singlePlayerS3[pos+2]=slot.character;
- this.singlePlayerS3[pos+3]=zone;
- this.singlePlayerS3[pos+4]=slot.last;
- this.singlePlayerS3[pos+5]=slot.numEmeralds;
- this.singlePlayerS3[pos+6]=slot.emeralds;
- this.singlePlayerS3[pos+7]=slot.rings;
- }
- };
- Save.prototype.setSaveSlotS3K=function(current, slot) {
- let pos=current*S3K_SLOT_LENGTH;
- if (slot.isNew) {
- this.singlePlayerS3K[pos] =NEW;
- this.singlePlayerS3K[pos+1]=0;
- this.singlePlayerS3K[pos+2]=0;
- this.singlePlayerS3K[pos+3]=0;
- this.singlePlayerS3K[pos+4]=0;
- this.singlePlayerS3K[pos+5]=0;
- this.singlePlayerS3K[pos+6]=0;
- this.singlePlayerS3K[pos+7]=0;
- this.singlePlayerS3K[pos+8]=0;
- this.singlePlayerS3K[pos+9]=0;
- } else {
- let numEmeralds=slot.numEmeralds;
- if (numEmeralds>=7) { // goes back to 0 when all emeralds collected
- numEmeralds=0;
- }
- let zone=slot.zone;
- if (slot.isClear) {
- switch (slot.character) {
- case TAILS:
- zone=TAILS_LAST_ZONE;
- break;
- case KNUCKLES:
- zone=KNUCKLES_LAST_ZONE;
- break;
- default:
- if (slot.isClear==CHAOS_CLEAR||slot.isClear==SUPER_CLEAR) {
- zone=SONIC_LAST_ZONE; // Doomsday
- } else {
- zone=SONIC_LAST_ZONE-1; // Death Egg
- }
- }
- zone++;
- }
- this.singlePlayerS3K[pos] =slot.isClear;
- this.singlePlayerS3K[pos+1]=0; // always zero
- this.singlePlayerS3K[pos+2]=slot.character<<4|numEmeralds;
- this.singlePlayerS3K[pos+3]=zone;
- this.singlePlayerS3K[pos+4]=slot.rings;
- this.singlePlayerS3K[pos+5]=0; // always zero
- this.singlePlayerS3K[pos+6]=slot.emeralds1;
- this.singlePlayerS3K[pos+7]=slot.emeralds2;
- this.singlePlayerS3K[pos+8]=slot.lives;
- this.singlePlayerS3K[pos+9]=slot.continues;
- }
- };
- Save.prototype.getStage=function(current) {
- let start=current*CP_SLOT_LENGTH*(RANKINGS+1);
- let rows=[];
- for (let i=0; i<RANKINGS; i++) {
- let pos=start+i*CP_SLOT_LENGTH;
- rows.push({
- isNew: this.competition[pos]==NEW,
- min: this.competition[pos+1],
- sec: this.competition[pos+2],
- msec: this.competition[pos+3],
- character: this.competition[start+CP_SLOT_LENGTH*RANKINGS+i]
- });
- }
- return rows;
- };
- Save.prototype.setStage=function(current, rows) {
- let pos=current*(CP_SLOT_LENGTH*(RANKINGS+1));
- let characters=pos+CP_SLOT_LENGTH*RANKINGS; // start of characters slot
- for (let [i, row] of rows.entries()) {
- if (row.isNew) {
- this.competition[pos] =NEW;
- this.competition[pos+1]=0;
- this.competition[pos+2]=0;
- this.competition[pos+3]=0;
- this.competition[pos+CP_SLOT_LENGTH*RANKINGS+i]=0;
- } else {
- this.competition[pos] =0;
- this.competition[pos+1]=row.min;
- this.competition[pos+2]=row.sec;
- this.competition[pos+3]=row.msec;
- this.competition[characters+i]=row.character;
- }
- pos+=4;
- }
- };
- /*
- * Storage prototype
- */
- function Storage(name) {
- this.name=name;
- }
- Storage.prototype.load=function() {
- try {
- let contents=localStorage.getItem(this.name);
- if (contents!=null) {
- return JSON.parse(contents);
- }
- } catch (err) {
- console.error(err);
- }
- };
- Storage.prototype.save=function(file) {
- try {
- if (file.length>0) {
- localStorage.setItem(this.name, JSON.stringify(Array.from(file)));
- } else {
- this.reset();
- }
- } catch (err) {
- console.error(err);
- }
- };
- Storage.prototype.reset=function() {
- try {
- localStorage.removeItem(this.name);
- } catch (err) {
- console.error(err);
- }
- };
Advertisement
Add Comment
Please, Sign In to add comment