wasmboy
Version:
Gameboy / Gameboy Color Emulator written for Web Assembly using AssemblyScript. Shell/Debugger in Preact
240 lines (208 loc) • 8.15 kB
text/typescript
import { GBC_PALETTE_LOCATION } from '../constants';
import { Cpu } from '../cpu/index';
import { eightBitLoadFromGBMemory, eightBitStoreIntoGBMemory } from '../memory/index';
import { checkBitOnByte, resetBitOnByte, setBitOnByte, concatenateBytes } from '../helpers/index';
import { Colors } from './colors';
// Class for GBC Color palletes
// http://gbdev.gg8.se/wiki/articles/Video_Display#FF68_-_BCPS.2FBGPI_-_CGB_Mode_Only_-_Background_Palette_Index
export class Palette {
static memoryLocationBackgroundPaletteIndex: i32 = 0xff68;
static memoryLocationBackgroundPaletteData: i32 = 0xff69;
static memoryLocationSpritePaletteIndex: i32 = 0xff6a;
static memoryLocationSpritePaletteData: i32 = 0xff6b;
// Palettes
static readonly memoryLocationBackgroundPalette: i32 = 0xff47;
static readonly memoryLocationSpritePaletteOne: i32 = 0xff48;
static readonly memoryLocationSpritePaletteTwo: i32 = 0xff49;
}
// Inlined because closure compiler inlines
export function initializePalette(): void {
if (Cpu.GBCEnabled) {
// GBC Palettes
eightBitStoreIntoGBMemory(0xff68, 0xc0);
eightBitStoreIntoGBMemory(0xff69, 0xff);
eightBitStoreIntoGBMemory(0xff6a, 0xc1);
eightBitStoreIntoGBMemory(0xff6b, 0x0d);
} else {
// GBC Palettes
eightBitStoreIntoGBMemory(0xff68, 0xff);
eightBitStoreIntoGBMemory(0xff69, 0xff);
eightBitStoreIntoGBMemory(0xff6a, 0xff);
eightBitStoreIntoGBMemory(0xff6b, 0xff);
}
// Override some values if using the bootrom
if (Cpu.BootROMEnabled && Cpu.GBCEnabled) {
// GBC Palettes
eightBitStoreIntoGBMemory(0xff69, 0x20);
eightBitStoreIntoGBMemory(0xff6b, 0x8a);
}
}
// Simple get pallete color or monochrome GB
// shouldRepresentColorByColorId is good for debugging tile data for GBC games that don't have
// monochromePalettes
// Inlined because closure compiler inlines
export function getMonochromeColorFromPalette(
colorId: i32,
paletteMemoryLocation: i32,
shouldRepresentColorByColorId: boolean = false
): i32 {
// Shift our paletteByte, 2 times for each color ID
// And off any extra bytes
// Return our Color (00 - white, 01 - light grey, 10 Dark grey, or 11 - Black)
let color = colorId;
if (!shouldRepresentColorByColorId) {
color = ((<i32>eightBitLoadFromGBMemory(paletteMemoryLocation)) >> (colorId << 1)) & 0x03;
}
// Since our max is 254, and max is 3.
// monochrome color palette is modified from bgb
// TODO: Make these colors into a constant
let rgbColor = 242;
switch (color) {
case 0:
break;
case 1:
rgbColor = 160;
break;
case 2:
rgbColor = 88;
break;
case 3:
rgbColor = 8;
break;
}
return rgbColor;
}
// Function to returns the Colorized color for a GB games
export function getColorizedGbHexColorFromPalette(colorId: i32, paletteMemoryLocation: i32): i32 {
// Shift our paletteByte, 2 times for each color ID
// And off any extra bytes
// Return our Color (00 - white, 01 - light grey, 10 Dark grey, or 11 - Black)
let color = ((<i32>eightBitLoadFromGBMemory(paletteMemoryLocation)) >> (colorId * 2)) & 0x03;
// Check which palette we got, to apply the right color layer
let hexColor = 0;
if (paletteMemoryLocation === Palette.memoryLocationSpritePaletteOne) {
hexColor = Colors.obj0White;
switch (color) {
case 0:
break;
case 1:
hexColor = Colors.obj0LightGrey;
break;
case 2:
hexColor = Colors.obj0DarkGrey;
break;
case 3:
hexColor = Colors.obj0Black;
break;
}
} else if (paletteMemoryLocation === Palette.memoryLocationSpritePaletteTwo) {
hexColor = Colors.obj1White;
switch (color) {
case 0:
break;
case 1:
hexColor = Colors.obj1LightGrey;
break;
case 2:
hexColor = Colors.obj1DarkGrey;
break;
case 3:
hexColor = Colors.obj1Black;
break;
}
} else {
hexColor = Colors.bgWhite;
switch (color) {
case 0:
break;
case 1:
hexColor = Colors.bgLightGrey;
break;
case 2:
hexColor = Colors.bgDarkGrey;
break;
case 3:
hexColor = Colors.bgBlack;
break;
}
}
return hexColor;
}
// Inlined because closure compiler inlines
export function writeColorPaletteToMemory(offset: i32, value: i32): void {
// FF68
// Bit 0-5 Index (00-3F)
let memoryLocationSpritePaletteData = Palette.memoryLocationSpritePaletteData;
if (offset === Palette.memoryLocationBackgroundPaletteData || offset === memoryLocationSpritePaletteData) {
// Get the palette index
let paletteIndex: i32 = eightBitLoadFromGBMemory(offset - 1);
// Clear the 6th bit, as it does nothing
paletteIndex = resetBitOnByte(6, paletteIndex);
// Check if we are changing the sprite pallete data
let isSprite = offset === memoryLocationSpritePaletteData;
storePaletteByteInWasmMemory(paletteIndex, value, isSprite);
incrementPaletteIndexIfSet(paletteIndex, offset - 1);
}
}
// Functions to Handle Write to pallete data registers
// http://gbdev.gg8.se/wiki/articles/Video_Display#FF68_-_BCPS.2FBGPI_-_CGB_Mode_Only_-_Background_Palette_Index
// Function to handle incrementing the pallete index if required
// Inlined because closure compiler inlines
function incrementPaletteIndexIfSet(paletteIndex: i32, offset: i32): void {
// Check ther auto increment box
if (checkBitOnByte(7, paletteIndex)) {
// Increment the index, and return the value before the increment
// Ensure we don't ouverflow our auto increment bit
paletteIndex += 1;
paletteIndex = setBitOnByte(7, paletteIndex);
eightBitStoreIntoGBMemory(offset, paletteIndex);
}
}
// FF68
// Bit 0-5 Index (00-3F)
// Bit 7 Auto Increment (0=Disabled, 1=Increment after Writing)
// Index is 00-0x3F because the means 0 - 63 (64),
// and apparently there are 8 bytes per pallete to describe Color 0-3 (4 colors),
// and 0-7 (8 palltetes). Therefore, 64!
export function getRgbColorFromPalette(paletteId: i32, colorId: i32, isSprite: boolean): i32 {
// Each Pallete takes 8 bytes, so multiply by 8 to get the pallete
// And Each color takes 2 bytes, therefore, multiple by 2 for the correct color bytes in the palette
let paletteIndex = paletteId * 8 + colorId * 2;
// Load the Color that is seperated into two bytes
let paletteHighByte: i32 = loadPaletteByteFromWasmMemory(paletteIndex + 1, isSprite);
let paletteLowByte: i32 = loadPaletteByteFromWasmMemory(paletteIndex, isSprite);
// Return the concatenated color byte
return <i32>concatenateBytes(paletteHighByte, paletteLowByte);
}
// Function to return the color from a passed 16 bit color pallette
export function getColorComponentFromRgb(colorId: i32, colorRgb: i32): i32 {
// Get our bitmask for the color ID
// bit mask tested good :)
colorId *= 5;
let bitMask = 0x1f << colorId;
let colorValue = (colorRgb & bitMask) >> colorId;
// Goal is to reach 254 for each color, so 255 / 31 (0x1F) ~8 TODO: Make exact
// Want 5 bits for each
return colorValue * 8;
}
// Function to load a byte from our Gbc Palette memory
export function loadPaletteByteFromWasmMemory(paletteIndexByte: i32, isSprite: boolean): u8 {
// Clear the top two bits to just get the bottom palette Index
let paletteIndex = paletteIndexByte & 0x3f;
// Move over the palette index to not overlap the background has 0x3F, so Zero for Sprites is 0x40)
if (isSprite) {
paletteIndex += 0x40;
}
return load<u8>(GBC_PALETTE_LOCATION + paletteIndex);
}
// Function to store a byte to our Gbc Palette memory
// Inlined because closure compiler inlines
export function storePaletteByteInWasmMemory(paletteIndexByte: i32, value: i32, isSprite: boolean): void {
// Clear the top two bits to just get the bottom palette Index
let paletteIndex = paletteIndexByte & 0x3f;
// Move over the palette index to not overlap the background (has 0x3F, so Zero for Sprites is 0x40)
if (isSprite) {
paletteIndex += 0x40;
}
store<u8>(GBC_PALETTE_LOCATION + paletteIndex, <u8>value);
}