Skip to content
Snippets Groups Projects
Verified Commit 7d8fae33 authored by João Magalhães's avatar João Magalhães :rocket:
Browse files

feat: more changed to support battery

backed RAM, related to
#7
parent d13a8f87
No related branches found
No related tags found
1 merge request!13feat: local storage for battery RAM
Pipeline #1547 failed
......@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Support for true fullscreen at a browser level
* Support for more flexible palette colors
* Support for setting palette colors using WASM
* Local storage usage for saving battery backed RAM
### Changed
......
......@@ -15,13 +15,22 @@ import {
PpuMode
} from "./lib/boytacean.js";
import info from "./package.json";
import { base64ToBuffer, bufferToBase64 } from "./util";
declare const require: any;
const LOGIC_HZ = 4194304;
const VISUAL_HZ = 59.7275;
const IDLE_HZ = 10;
/**
* The rate at which the local storage RAM state flush
* operation is going to be performed, this value is the
* number of seconds in between flush operations (eg: 5 seconds).
*/
const STORE_RATE = 5;
const SAMPLE_RATE = 2;
const KEYS_NAME: Record<string, number> = {
......@@ -65,6 +74,7 @@ export class GameboyEmulator extends EmulatorBase implements Emulator {
private frameStart: number = new Date().getTime();
private frameCount: number = 0;
private paletteIndex: number = 0;
private storeCycles: number = LOGIC_HZ * STORE_RATE;
private romName: string | null = null;
private romData: Uint8Array | null = null;
......@@ -190,7 +200,8 @@ export class GameboyEmulator extends EmulatorBase implements Emulator {
// runs the Game Boy clock, this operations should
// include the advance of both the CPU and the PPU
counterCycles += this.gameBoy?.clock() ?? 0;
const tickCycles = this.gameBoy?.clock() ?? 0;
counterCycles += tickCycles;
// in case the current PPU mode is VBlank and the
// frame is different from the previously rendered
......@@ -206,13 +217,16 @@ export class GameboyEmulator extends EmulatorBase implements Emulator {
// triggers the frame event indicating that
// a new frame is now available for drawing
this.trigger("frame");
}
// @todo this has to be structureed in a better way
if (this.cartridge && this.cartridge.has_battery()) {
const ramData = this.cartridge.ram_data_eager();
const decoder = new TextDecoder("utf8");
const ramDataB64 = btoa(decoder.decode(ramData));
localStorage.setItem(this.cartridge.title(), ramDataB64)
// in case the current cartridge is battery backed
// then we need to check if a RAM flush to local
// storage operation is required
if (this.cartridge && this.cartridge.has_battery()) {
this.storeCycles -= tickCycles;
if (this.storeCycles <= 0) {
this.storeRam();
this.storeCycles = this.logicFrequency * STORE_RATE;
}
}
}
......@@ -299,22 +313,22 @@ export class GameboyEmulator extends EmulatorBase implements Emulator {
this.gameBoy.load_boot_default();
const cartridge = this.gameBoy.load_rom_ws(romData!);
// in case there's a battery involved tries to obtain
if (cartridge.has_battery()) {
}
// updates the name of the currently selected engine
// to the one that has been provided (logic change)
if (engine) this._engine = engine;
// updates the ROM name in case there's extra information
// coming from the cartridge
romName = cartridge.title() ? cartridge.title() : romName;
// updates the name of the currently selected engine
// to the one that has been provided (logic change)
if (engine) this._engine = engine;
// updates the complete set of global information that
// is going to be displayed
this.setRom(romName!, romData!, cartridge);
// in case there's a battery involved tries to load the
// current RAM from the local storage
if (cartridge.has_battery()) this.loadRam();
// in case the restore (state) flag is set
// then resumes the machine execution
if (restore) this.resume();
......@@ -504,6 +518,22 @@ export class GameboyEmulator extends EmulatorBase implements Emulator {
}
}
private loadRam() {
if (!this.gameBoy || !this.cartridge) return;
const ramDataB64 = localStorage.getItem(this.cartridge.title());
if (!ramDataB64) return;
const ramData = base64ToBuffer(ramDataB64);
this.gameBoy.set_ram_data(ramData);
}
private storeRam() {
if (!this.gameBoy || !this.cartridge) return;
const title = this.cartridge.title();
const ramData = this.gameBoy.ram_data_eager();
const ramDataB64 = bufferToBase64(ramData);
localStorage.setItem(title, ramDataB64);
}
private setPalette(index?: number) {
index ??= this.paletteIndex;
const palette = PALETTES[index];
......
export const bufferToBase64 = (buffer: Uint8Array) => {
const array = Array(buffer.length)
.fill("")
.map((_, i) => String.fromCharCode(buffer[i]))
.join("");
const base64 = btoa(array);
return base64;
};
export const base64ToBuffer = (base64: string) => {
const data = window.atob(base64);
const length = data.length;
const buffer = new Uint8Array(length);
for (let i = 0; i < length; i++) {
buffer[i] = data.charCodeAt(i);
}
return buffer;
};
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment