mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2024-12-23 03:29:39 +00:00
NES: fixed clipping, update presets, apu.c/h, vrambuf.c
This commit is contained in:
parent
8daa260527
commit
db60c8e380
@ -95,6 +95,7 @@ TODO:
|
||||
- show .map file in listings? memory map view?
|
||||
- open ROM from URL?
|
||||
- NES: disassembly not aligned on PC
|
||||
- NES: vrambuf.c for Solarian
|
||||
|
||||
|
||||
WEB WORKER FORMAT
|
||||
|
20
presets/nes/apu.c
Normal file
20
presets/nes/apu.c
Normal file
@ -0,0 +1,20 @@
|
||||
|
||||
|
||||
#include "apu.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
const unsigned char APUINIT[0x13] = {
|
||||
0x30,0x08,0x00,0x00,
|
||||
0x30,0x08,0x00,0x00,
|
||||
0x80,0x00,0x00,0x00,
|
||||
0x30,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00
|
||||
};
|
||||
|
||||
void apu_init() {
|
||||
// from https://wiki.nesdev.com/w/index.php/APU_basics
|
||||
memcpy((void*)0x4000, APUINIT, sizeof(APUINIT));
|
||||
APU.fcontrol = 0x40; // frame counter 5-step
|
||||
APU.status = 0x0f; // turn on all channels except DMC
|
||||
}
|
77
presets/nes/apu.h
Normal file
77
presets/nes/apu.h
Normal file
@ -0,0 +1,77 @@
|
||||
|
||||
#ifndef _APU_H
|
||||
#define _APU_H
|
||||
|
||||
#include <nes.h>
|
||||
|
||||
// Functions/macros for direct control
|
||||
// of APU sound generation
|
||||
|
||||
// enable
|
||||
#define ENABLE_PULSE0 0x1
|
||||
#define ENABLE_PULSE1 0x2
|
||||
#define ENABLE_TRIANGLE 0x4
|
||||
#define ENABLE_NOISE 0x8
|
||||
#define ENABLE_DMC 0x10
|
||||
|
||||
#define APU_ENABLE(enable)\
|
||||
APU.status = (enable);
|
||||
|
||||
// pulse channels
|
||||
#define DUTY_75 0xc0
|
||||
#define DUTY_50 0x80
|
||||
#define DUTY_25 0x40
|
||||
#define DUTY_12 0x00
|
||||
|
||||
#define PULSE_ENVLOOP 0x20
|
||||
#define PULSE_CONSTVOL 0x10
|
||||
#define PULSE_VOLENVMASK 0xf
|
||||
|
||||
#define PULSE_CH0 0
|
||||
#define PULSE_CH1 1
|
||||
|
||||
#define APU_PULSE_DECAY(channel,period,duty,decay,len)\
|
||||
APU.pulse[channel].period_low = (period)&0xff;\
|
||||
APU.pulse[channel].len_period_high = (((period)>>8)&7) | ((len)<<3);\
|
||||
APU.pulse[channel].control = (duty) | (decay);
|
||||
|
||||
#define APU_PULSE_SUSTAIN(channel,period,duty,vol)\
|
||||
APU.pulse[channel].period_low = (period)&0xff;\
|
||||
APU.pulse[channel].len_period_high = (((period)>>8)&7);\
|
||||
APU.pulse[channel].control = (duty) | (vol) | (PULSE_CONSTVOL|PULSE_ENVLOOP);
|
||||
|
||||
#define APU_PULSE_CONTROL(channel,duty,decay)\
|
||||
APU.pulse[channel].control = (duty) | (decay);
|
||||
|
||||
|
||||
// triangle channel
|
||||
#define TRIANGLE_LC_HALT 0x80
|
||||
#define TRIANGLE_LC_MASK 0x7f
|
||||
|
||||
#define APU_TRIANGLE_SUSTAIN(period)\
|
||||
APU.triangle.counter = 0xc0;\
|
||||
APU.triangle.period_low = (period) & 0xff;\
|
||||
APU.triangle.len_period_high = (period) >> 8;
|
||||
|
||||
// noise channel
|
||||
#define NOISE_ENVLOOP 0x20
|
||||
#define NOISE_CONSTVOL 0x10
|
||||
#define NOISE_VOLENVMASK 0xf
|
||||
|
||||
#define NOISE_PERIOD_BUZZ 0x80
|
||||
|
||||
#define APU_NOISE_SUSTAIN(_period,vol)\
|
||||
APU.noise.control = (vol) | (NOISE_ENVLOOP|NOISE_CONSTVOL);\
|
||||
APU.noise.period = (_period);
|
||||
|
||||
#define APU_NOISE_DECAY(_period,_decay,_len)\
|
||||
APU.noise.control = (_decay);\
|
||||
APU.noise.period = (_period);\
|
||||
APU.noise.len = (_len);
|
||||
|
||||
|
||||
// initialize APU with default state
|
||||
void apu_init(void);
|
||||
|
||||
|
||||
#endif
|
22
presets/nes/bcd.c
Normal file
22
presets/nes/bcd.c
Normal file
@ -0,0 +1,22 @@
|
||||
|
||||
#include "neslib.h"
|
||||
|
||||
word bcd_add(word a, word b) {
|
||||
word result = 0;
|
||||
byte c = 0;
|
||||
byte shift = 0;
|
||||
while (shift < 16) {
|
||||
byte d = (a & 0xf) + (b & 0xf) + c;
|
||||
c = 0;
|
||||
while (d >= 10) {
|
||||
c++;
|
||||
d -= 10;
|
||||
}
|
||||
result |= d << shift;
|
||||
shift += 4;
|
||||
a >>= 4;
|
||||
b >>= 4;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
2
presets/nes/bcd.h
Normal file
2
presets/nes/bcd.h
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
unsigned int bcd_add(unsigned int a, unsigned int b);
|
@ -8,6 +8,14 @@
|
||||
// include CC65 NES Header (PPU)
|
||||
#include <nes.h>
|
||||
|
||||
// BCD arithmetic support
|
||||
#include "bcd.h"
|
||||
//#link "bcd.c"
|
||||
|
||||
// VRAM update buffer
|
||||
#include "vrambuf.h"
|
||||
//#link "vrambuf.c"
|
||||
|
||||
// link the pattern table into CHR ROM
|
||||
//#link "chr_generic.s"
|
||||
|
||||
@ -22,12 +30,6 @@ extern char demo_sounds[];
|
||||
|
||||
typedef enum { SND_START, SND_HIT, SND_COIN, SND_JUMP } SFXIndex;
|
||||
|
||||
// define basic types
|
||||
typedef unsigned char byte;
|
||||
typedef signed char sbyte;
|
||||
typedef unsigned short word;
|
||||
typedef enum { false, true } bool;
|
||||
|
||||
///// DEFINES
|
||||
|
||||
#define COLS 30 // floor width in tiles
|
||||
@ -66,7 +68,7 @@ static byte player_screen_y = 0;
|
||||
// score (BCD)
|
||||
static byte score = 0;
|
||||
|
||||
// flash animation (virtual bright)
|
||||
// screen flash animation (virtual bright)
|
||||
static byte vbright = 4;
|
||||
|
||||
// random byte between (a ... b-1)
|
||||
@ -75,15 +77,6 @@ byte rndint(byte a, byte b) {
|
||||
return (rand() % (b-a)) + a;
|
||||
}
|
||||
|
||||
byte bcdadd(byte a, byte b) {
|
||||
byte c = (a & 0xf) + (b & 0xf);
|
||||
if (c < 10) {
|
||||
return a + b;
|
||||
} else {
|
||||
return (c-10) + 0x10 + (a & 0xf0) + (b & 0xf0);
|
||||
}
|
||||
}
|
||||
|
||||
///// OAM buffer (for sprites)
|
||||
|
||||
#define OAMBUF ((unsigned char*) 0x200)
|
||||
@ -102,46 +95,10 @@ word getntaddr(byte x, byte y) {
|
||||
|
||||
// convert nametable address to attribute address
|
||||
word nt2attraddr(word a) {
|
||||
return (a & 0x2c00) | 0x3c0 | (a & 0x0C00) |
|
||||
return (a & 0x2c00) | 0x3c0 |
|
||||
((a >> 4) & 0x38) | ((a >> 2) & 0x07);
|
||||
}
|
||||
|
||||
///// VRAM UPDATE BUFFER
|
||||
|
||||
#define VBUFSIZE 64
|
||||
byte updbuf[VBUFSIZE]; // update buffer
|
||||
byte updptr = 0; // index to end of buffer
|
||||
|
||||
// add EOF marker to buffer
|
||||
void cendbuf() {
|
||||
updbuf[updptr] = NT_UPD_EOF;
|
||||
}
|
||||
|
||||
// flush buffer now, waiting for next frame
|
||||
void cflushnow() {
|
||||
// make sure buffer has EOF marker
|
||||
cendbuf();
|
||||
// wait for next frame to flush update buffer
|
||||
// this will also set the scroll registers properly
|
||||
ppu_wait_frame();
|
||||
// clear the buffer
|
||||
updptr = 0;
|
||||
cendbuf();
|
||||
}
|
||||
|
||||
// add multiple characters to update buffer
|
||||
// using horizontal increment
|
||||
void putbytes(word addr, char* str, byte len) {
|
||||
if (updptr >= VBUFSIZE-4-len) cflushnow();
|
||||
updbuf[updptr++] = (addr >> 8) | NT_UPD_HORZ;
|
||||
updbuf[updptr++] = addr & 0xff;
|
||||
updbuf[updptr++] = len;
|
||||
while (len--) {
|
||||
updbuf[updptr++] = *str++;
|
||||
}
|
||||
cendbuf();
|
||||
}
|
||||
|
||||
/// METASPRITES
|
||||
|
||||
// define a 2x2 metasprite
|
||||
@ -178,7 +135,13 @@ DEF_METASPRITE_2x2_FLIP(playerLJump, 0xe8, 0);
|
||||
DEF_METASPRITE_2x2_FLIP(playerLClimb, 0xec, 0);
|
||||
DEF_METASPRITE_2x2_FLIP(playerLSad, 0xf0, 0);
|
||||
|
||||
DEF_METASPRITE_2x2(personToSave, 0xba, 1);
|
||||
//DEF_METASPRITE_2x2(personToSave, 0xba, 1);
|
||||
const unsigned char personToSave[]={
|
||||
0, 0, (0xba)+0, 3,
|
||||
0, 8, (0xba)+2, 0,
|
||||
8, 0, (0xba)+1, 3,
|
||||
8, 8, (0xba)+3, 0,
|
||||
128};
|
||||
|
||||
const unsigned char* const playerRunSeq[16] = {
|
||||
playerLRun1, playerLRun2, playerLRun3,
|
||||
@ -353,6 +316,8 @@ void draw_entire_stage() {
|
||||
byte y;
|
||||
for (y=0; y<ROWS; y++) {
|
||||
draw_floor_line(y);
|
||||
// allow buffer to flush, delaying a frame
|
||||
cflushnow();
|
||||
}
|
||||
}
|
||||
|
||||
@ -649,7 +614,7 @@ void pickup_object(Actor* actor) {
|
||||
sfx_play(SND_HIT,0);
|
||||
vbright = 8; // flash
|
||||
} else {
|
||||
score = bcdadd(score, 1);
|
||||
score = bcd_add(score, 1);
|
||||
sfx_play(SND_COIN,0);
|
||||
}
|
||||
}
|
||||
|
@ -6,8 +6,11 @@
|
||||
seg.u RAM
|
||||
org $0
|
||||
|
||||
ScrollX byte ; used during NMI
|
||||
ScrollY byte ; used during NMI
|
||||
ScrollPos byte ; used during NMI
|
||||
Rand byte
|
||||
Temp1 byte
|
||||
|
||||
SpriteBuf equ $200
|
||||
|
||||
;;;;; NES CARTRIDGE HEADER
|
||||
|
||||
@ -24,6 +27,7 @@ Start:
|
||||
jsr SetPalette ;set colors
|
||||
jsr FillVRAM ;set PPU RAM
|
||||
jsr WaitSync ;wait for VSYNC (and PPU warmup)
|
||||
jsr InitSprites
|
||||
lda #0
|
||||
sta PPU_ADDR
|
||||
sta PPU_ADDR ;PPU addr = 0
|
||||
@ -51,6 +55,33 @@ FillVRAM: subroutine
|
||||
bne .loop
|
||||
rts
|
||||
|
||||
;
|
||||
InitSprites: subroutine
|
||||
lda #1
|
||||
ldx #0
|
||||
.loop
|
||||
sta SpriteBuf,x
|
||||
jsr NextRandom
|
||||
inx
|
||||
bne .loop
|
||||
rts
|
||||
|
||||
;
|
||||
MoveSprites: subroutine
|
||||
lda #1
|
||||
ldx #0
|
||||
.loop
|
||||
sta Temp1
|
||||
and #3
|
||||
clc
|
||||
adc SpriteBuf,x
|
||||
sta SpriteBuf,x
|
||||
lda Temp1
|
||||
jsr NextRandom
|
||||
inx
|
||||
bne .loop
|
||||
rts
|
||||
|
||||
; set palette colors
|
||||
|
||||
SetPalette: subroutine
|
||||
@ -76,34 +107,21 @@ SetPalette: subroutine
|
||||
|
||||
NMIHandler:
|
||||
SAVE_REGS
|
||||
; load sprites
|
||||
lda #$02
|
||||
sta PPU_OAM_DMA
|
||||
; update scroll position (must be done after VRAM updates)
|
||||
jsr ReadJoypad
|
||||
pha
|
||||
and #$03
|
||||
tay
|
||||
lda ScrollDirTab,y
|
||||
clc
|
||||
adc ScrollX
|
||||
sta ScrollX
|
||||
inc ScrollPos
|
||||
lda ScrollPos
|
||||
sta PPU_SCROLL
|
||||
pla
|
||||
lsr
|
||||
lsr
|
||||
and #$03
|
||||
tay
|
||||
lda ScrollDirTab,y
|
||||
clc
|
||||
adc ScrollY
|
||||
sta ScrollY
|
||||
lda #0
|
||||
sta PPU_SCROLL
|
||||
; move sprites
|
||||
jsr MoveSprites
|
||||
; reload registers
|
||||
RESTORE_REGS
|
||||
rti
|
||||
|
||||
; Scroll direction lookup table
|
||||
ScrollDirTab:
|
||||
hex 00 01 ff 00
|
||||
|
||||
;;;;; CONSTANT DATA
|
||||
|
||||
align $100
|
||||
|
147
presets/nes/horizmask.c
Normal file
147
presets/nes/horizmask.c
Normal file
@ -0,0 +1,147 @@
|
||||
|
||||
#include "neslib.h"
|
||||
#include <string.h>
|
||||
|
||||
// 0 = horizontal mirroring
|
||||
// 1 = vertical mirroring
|
||||
#define NES_MIRRORING 1
|
||||
|
||||
// VRAM update buffer
|
||||
#include "vrambuf.h"
|
||||
//#link "vrambuf.c"
|
||||
|
||||
// link the pattern table into CHR ROM
|
||||
//#link "chr_generic.s"
|
||||
|
||||
// function to write a string into the name table
|
||||
// adr = start address in name table
|
||||
// str = pointer to string
|
||||
void put_str(unsigned int adr, const char *str) {
|
||||
vram_adr(adr); // set PPU read/write address
|
||||
vram_write(str, strlen(str)); // write bytes to PPU
|
||||
}
|
||||
|
||||
static word x_scroll = 0;
|
||||
static byte bldg_height = 8;
|
||||
static byte bldg_width = 8;
|
||||
static byte bldg_char = 1;
|
||||
static byte bldg_attr = 0x55;
|
||||
|
||||
#define PLAYROWS 24
|
||||
|
||||
word nt2attraddr(word a) {
|
||||
return (a & 0x2c00) | 0x3c0 |
|
||||
((a >> 4) & 0x38) | ((a >> 2) & 0x07);
|
||||
}
|
||||
|
||||
void update_nametable() {
|
||||
word addr;
|
||||
char buf[PLAYROWS];
|
||||
// divide x_scroll by 8
|
||||
// to get nametable X position
|
||||
byte x = ((x_scroll >> 3)+32) & 63;
|
||||
if (x < 32)
|
||||
addr = NTADR_A(x, 4);
|
||||
else
|
||||
addr = NTADR_B(x&31, 4);
|
||||
// create vertical slice
|
||||
// clear empty space
|
||||
memset(buf, 0, PLAYROWS-bldg_height);
|
||||
// draw roof
|
||||
buf[PLAYROWS-bldg_height-1] = bldg_char & 3;
|
||||
// draw rest of building
|
||||
memset(buf+PLAYROWS-bldg_height, bldg_char, bldg_height);
|
||||
// draw vertical slice in name table
|
||||
putbytes(addr ^ 0xc000, buf, sizeof(buf));
|
||||
// every 4 columns, update attribute table
|
||||
if ((x & 3) == 1) {
|
||||
// compute attribute table address
|
||||
// of upper attribute block
|
||||
addr = nt2attraddr(addr) + 8*4;
|
||||
VRAMBUF_PUT(addr, bldg_attr, 0);
|
||||
// put lower attribute block
|
||||
addr += 8;
|
||||
VRAMBUF_PUT(addr, bldg_attr, 0);
|
||||
cendbuf();
|
||||
}
|
||||
// generate new building?
|
||||
if (--bldg_width == 0) {
|
||||
bldg_height = (rand8() & 7) + 2;
|
||||
bldg_width = (rand8() & 3) * 4 + 4;
|
||||
bldg_char = (rand8() & 15);
|
||||
bldg_attr = rand8();
|
||||
}
|
||||
}
|
||||
|
||||
// function to scroll window up and down until end
|
||||
void scroll_demo() {
|
||||
// infinite loop
|
||||
while (1) {
|
||||
// update nametable every 8 pixels
|
||||
if ((x_scroll & 7) == 0) {
|
||||
update_nametable();
|
||||
}
|
||||
// manually force vram update
|
||||
ppu_wait_nmi();
|
||||
flush_vram_update(updbuf);
|
||||
cclearbuf();
|
||||
// reset ppu address
|
||||
vram_adr(0x0);
|
||||
// set scroll register
|
||||
// and increment x_scroll
|
||||
split(x_scroll++, 0);
|
||||
}
|
||||
}
|
||||
|
||||
const char PALETTE[32] = {
|
||||
0x03, // background color
|
||||
|
||||
0x11,0x30,0x27, 0, // background 0
|
||||
0x1c,0x20,0x2c, 0, // background 1
|
||||
0x00,0x10,0x20, 0, // background 2
|
||||
0x06,0x16,0x26, 0, // background 3
|
||||
|
||||
0x16,0x35,0x24, 0, // sprite 0
|
||||
0x00,0x37,0x25, 0, // sprite 1
|
||||
0x0d,0x2d,0x3a, 0, // sprite 2
|
||||
0x0d,0x27,0x2a // sprite 3
|
||||
};
|
||||
|
||||
// main function, run after console reset
|
||||
void main(void) {
|
||||
// set palette colors
|
||||
pal_all(PALETTE);
|
||||
|
||||
// write text to name table
|
||||
put_str(NTADR_A(7,0), "Nametable A, Line 0");
|
||||
put_str(NTADR_A(7,1), "Nametable A, Line 1");
|
||||
put_str(NTADR_A(7,2), "Nametable A, Line 2");
|
||||
vram_adr(NTADR_A(0,3));
|
||||
vram_fill(5, 32);
|
||||
put_str(NTADR_A(2,4), "Nametable A, Line 4");
|
||||
put_str(NTADR_A(2,15),"Nametable A, Line 15");
|
||||
put_str(NTADR_A(2,27),"Nametable A, Line 27");
|
||||
put_str(NTADR_B(2,4), "Nametable B, Line 4");
|
||||
put_str(NTADR_B(2,15),"Nametable B, Line 15");
|
||||
put_str(NTADR_B(2,27),"Nametable B, Line 27");
|
||||
|
||||
// set attributes
|
||||
vram_adr(0x23c0);
|
||||
vram_fill(0x55, 8);
|
||||
|
||||
// set sprite 0
|
||||
oam_clear();
|
||||
oam_spr(0, 30, 1, 1, 0);
|
||||
|
||||
// clip left 8 pixels of screen
|
||||
ppu_mask(MASK_SPR|MASK_BG);
|
||||
|
||||
// clear vram buffer
|
||||
cclearbuf();
|
||||
|
||||
// enable PPU rendering (turn on screen)
|
||||
ppu_on_all();
|
||||
|
||||
// scroll window back and forth
|
||||
scroll_demo();
|
||||
}
|
@ -4,7 +4,8 @@
|
||||
|
||||
#include "neslib.h"
|
||||
|
||||
typedef unsigned char byte;
|
||||
#include "apu.h"
|
||||
//#link "apu.c"
|
||||
|
||||
//
|
||||
// MUSIC ROUTINES
|
||||
@ -41,13 +42,6 @@ byte next_music_byte() {
|
||||
return *music_ptr++;
|
||||
}
|
||||
|
||||
#define DUTY_75 0xc0
|
||||
#define DUTY_50 0x80
|
||||
#define DUTY_25 0x40
|
||||
#define DUTY_12 0x00
|
||||
|
||||
#define DUTY (DUTY_25 | 0x1) // decay rate == 1
|
||||
|
||||
void play_music() {
|
||||
static byte ch = 0;
|
||||
if (music_ptr) {
|
||||
@ -56,25 +50,9 @@ void play_music() {
|
||||
if ((note & 0x80) == 0) {
|
||||
int period = note_table[note & 63];
|
||||
if (ch == 0) {
|
||||
APU.pulse[0].period_low = period & 0xff;
|
||||
APU.pulse[0].len_period_high = period >> 8;
|
||||
APU.pulse[0].control = DUTY;
|
||||
APU_PULSE_DECAY(0, period, DUTY_25, 2, 10);
|
||||
} else {
|
||||
APU.pulse[1].period_low = period & 0xff;
|
||||
APU.pulse[1].len_period_high = period >> 8;
|
||||
APU.pulse[1].control = DUTY;
|
||||
/*
|
||||
//period = note_table_tri[note & 63];
|
||||
period >>= 1;
|
||||
APU.triangle.counter = 0xc0;
|
||||
APU.triangle.period_low = period & 0xff;
|
||||
APU.triangle.len_period_high = period >> 8;
|
||||
*/
|
||||
/*
|
||||
APU.noise.control = 0x4;
|
||||
APU.noise.period = 0xe;
|
||||
APU.noise.len = 0xf;
|
||||
*/
|
||||
APU_PULSE_DECAY(1, period, DUTY_25, 2, 10);
|
||||
}
|
||||
ch = ch^1;
|
||||
} else {
|
||||
@ -92,24 +70,9 @@ void start_music(const byte* music) {
|
||||
cur_duration = 0;
|
||||
}
|
||||
|
||||
const byte APUINIT[0x13] = {
|
||||
0x30,0x08,0x00,0x00,
|
||||
0x30,0x08,0x00,0x00,
|
||||
0x80,0x00,0x00,0x00,
|
||||
0x30,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00
|
||||
};
|
||||
|
||||
void init_apu() {
|
||||
// from https://wiki.nesdev.com/w/index.php/APU_basics
|
||||
memcpy((void*)0x4000, APUINIT, sizeof(APUINIT));
|
||||
APU.fcontrol = 0x40; // frame counter 5-step
|
||||
APU.status = 0x0f; // turn on all channels except DMC
|
||||
}
|
||||
|
||||
void main(void)
|
||||
{
|
||||
init_apu();
|
||||
apu_init();
|
||||
music_ptr = 0;
|
||||
while (1) {
|
||||
if (!music_ptr) start_music(music1);
|
||||
|
@ -1,3 +1,5 @@
|
||||
#ifndef _NESLIB_H
|
||||
#define _NESLIB_H
|
||||
/*
|
||||
(C) 2015 Alex Semenov (Shiru)
|
||||
(C) 2016 Lauri Kasanen
|
||||
@ -30,8 +32,14 @@
|
||||
// unrle_vram renamed to vram_unrle, with adr argument removed
|
||||
// 060414 - many fixes and improvements, including sequental VRAM updates
|
||||
// previous versions were created since mid-2011, there were many updates
|
||||
// xxxx19 - updated by sehugg@8bitworkshop
|
||||
|
||||
|
||||
// define basic types for convenience
|
||||
typedef unsigned char byte; // 8-bit unsigned
|
||||
typedef signed char sbyte; // 8-bit signed
|
||||
typedef unsigned short word; // 16-bit signed
|
||||
typedef enum { false, true } bool; // boolean
|
||||
|
||||
|
||||
|
||||
@ -300,3 +308,6 @@ void __fastcall__ nmi_set_callback(void (*callback)(void));
|
||||
|
||||
#define MSB(x) (((x)>>8))
|
||||
#define LSB(x) (((x)&0xff))
|
||||
|
||||
#endif /* neslib.h */
|
||||
|
||||
|
@ -5,17 +5,23 @@
|
||||
|
||||
#include "neslib.h"
|
||||
|
||||
// APU (sound) support
|
||||
#include "apu.h"
|
||||
//#link "apu.c"
|
||||
|
||||
// BCD arithmetic support
|
||||
#include "bcd.h"
|
||||
//#link "bcd.c"
|
||||
|
||||
// VRAM update buffer
|
||||
#include "vrambuf.h"
|
||||
//#link "vrambuf.c"
|
||||
|
||||
#define COLS 32
|
||||
#define ROWS 28
|
||||
|
||||
//#define DEBUG_FRAMERATE
|
||||
|
||||
typedef unsigned char byte;
|
||||
typedef signed char sbyte;
|
||||
typedef unsigned short word;
|
||||
|
||||
///
|
||||
|
||||
const char PALETTE[32] = {
|
||||
0x0f,
|
||||
|
||||
@ -131,59 +137,11 @@ const char TILESET[128*8*2] = {/*{w:8,h:8,bpp:1,count:128,brev:1,np:2,pofs:8,rem
|
||||
#define CHAR(x) ((x)-' ')
|
||||
#define BLANK 0
|
||||
|
||||
// VRAM UPDATE BUFFER
|
||||
|
||||
#define VBUFSIZE 96
|
||||
byte updbuf[VBUFSIZE];
|
||||
byte updptr = 0;
|
||||
|
||||
void cendbuf() {
|
||||
updbuf[updptr] = NT_UPD_EOF;
|
||||
}
|
||||
|
||||
void cflushnow() {
|
||||
cendbuf();
|
||||
ppu_wait_nmi();
|
||||
flush_vram_update(updbuf);
|
||||
updptr = 0;
|
||||
cendbuf();
|
||||
vram_adr(0x0);
|
||||
}
|
||||
|
||||
void vdelay(byte count) {
|
||||
while (count--) cflushnow();
|
||||
}
|
||||
|
||||
void putchar(byte x, byte y, char ch) {
|
||||
word addr = NTADR_A(x,y);
|
||||
if (updptr >= VBUFSIZE-4) cflushnow();
|
||||
updbuf[updptr++] = addr >> 8;
|
||||
updbuf[updptr++] = addr & 0xff;
|
||||
updbuf[updptr++] = ch;
|
||||
cendbuf();
|
||||
}
|
||||
|
||||
void putbytes(byte x, byte y, char* str, byte len) {
|
||||
word addr = NTADR_A(x,y);
|
||||
if (updptr >= VBUFSIZE-4-len) cflushnow();
|
||||
updbuf[updptr++] = (addr >> 8) | NT_UPD_HORZ;
|
||||
updbuf[updptr++] = addr & 0xff;
|
||||
updbuf[updptr++] = len;
|
||||
while (len--) {
|
||||
updbuf[updptr++] = *str++;
|
||||
}
|
||||
cendbuf();
|
||||
}
|
||||
|
||||
void putstring(byte x, byte y, char* str) {
|
||||
putbytes(x, y, str, strlen(str));
|
||||
}
|
||||
|
||||
void clrscr() {
|
||||
updptr = 0;
|
||||
cendbuf();
|
||||
ppu_off();
|
||||
vram_adr(0x2000);
|
||||
vram_adr(NAMETABLE_A);
|
||||
vram_fill(BLANK, 32*28);
|
||||
vram_adr(0x0);
|
||||
ppu_on_all();
|
||||
@ -203,26 +161,7 @@ void draw_bcd_word(byte col, byte row, word bcd) {
|
||||
buf[j] = CHAR('0'+(bcd&0xf));
|
||||
bcd >>= 4;
|
||||
}
|
||||
putbytes(col, row, buf, 5);
|
||||
}
|
||||
|
||||
word bcd_add(word a, word b) {
|
||||
word result = 0;
|
||||
byte c = 0;
|
||||
byte shift = 0;
|
||||
while (shift < 16) {
|
||||
byte d = (a & 0xf) + (b & 0xf) + c;
|
||||
c = 0;
|
||||
while (d >= 10) {
|
||||
c++;
|
||||
d -= 10;
|
||||
}
|
||||
result |= d << shift;
|
||||
shift += 4;
|
||||
a >>= 4;
|
||||
b >>= 4;
|
||||
}
|
||||
return result;
|
||||
putbytes(NTADR_A(col, row), buf, 5);
|
||||
}
|
||||
|
||||
// GAME CODE
|
||||
@ -261,17 +200,17 @@ typedef struct {
|
||||
} AttackingEnemy;
|
||||
|
||||
typedef struct {
|
||||
signed char dx;
|
||||
byte xpos;
|
||||
signed char dy;
|
||||
byte ypos;
|
||||
signed char dx;
|
||||
signed char dy;
|
||||
} Missile;
|
||||
|
||||
typedef struct {
|
||||
byte name;
|
||||
byte tag;
|
||||
byte x;
|
||||
byte y;
|
||||
byte name;
|
||||
byte tag;
|
||||
} Sprite;
|
||||
|
||||
#define ENEMIES_PER_ROW 8
|
||||
@ -366,7 +305,7 @@ void draw_row(byte row) {
|
||||
}
|
||||
x += 3;
|
||||
}
|
||||
putbytes(0, y, buf, sizeof(buf));
|
||||
putbytes(NTADR_A(0, y), buf, sizeof(buf));
|
||||
}
|
||||
|
||||
void draw_next_row() {
|
||||
@ -724,37 +663,30 @@ void new_player_ship() {
|
||||
|
||||
void set_sounds() {
|
||||
byte i;
|
||||
byte enable = 0x1 | 0x2 | 0x8;
|
||||
// these channels decay, so ok to always enable
|
||||
byte enable = ENABLE_PULSE0|ENABLE_PULSE1|ENABLE_NOISE;
|
||||
// missile fire sound
|
||||
if (missiles[PLYRMISSILE].ypos != YOFFSCREEN) {
|
||||
APU.pulse[0].period_low = missiles[PLYRMISSILE].ypos ^ 0xff;
|
||||
APU.pulse[0].len_period_high = 0;
|
||||
APU.pulse[0].control = 0x80 | 0x30 | 6;
|
||||
APU_PULSE_SUSTAIN(0, missiles[PLYRMISSILE].ypos ^ 0xff, DUTY_50, 6);
|
||||
} else {
|
||||
APU.pulse[0].control = 0x30;
|
||||
APU_PULSE_CONTROL(0, DUTY_50, 1);
|
||||
}
|
||||
// enemy explosion sound
|
||||
if (player_exploding && player_exploding < 8) {
|
||||
APU.noise.control = 4;
|
||||
APU.noise.period = 8 + player_exploding;
|
||||
APU.noise.len = 15;
|
||||
APU_NOISE_DECAY(8 + player_exploding, 5, 15);
|
||||
} else if (enemy_exploding) {
|
||||
APU.noise.control = 2;
|
||||
APU.noise.period = 8 + enemy_exploding;
|
||||
APU.noise.len = 8;
|
||||
APU_NOISE_DECAY(8 + enemy_exploding, 2, 8);
|
||||
}
|
||||
// set diving sounds for spaceships
|
||||
for (i=0; i<2; i++) {
|
||||
register AttackingEnemy* a = i ? &attackers[4] : &attackers[0];
|
||||
if (a->findex && !a->returning) {
|
||||
byte y = a->y >> 8;
|
||||
APU.triangle.counter = 0xc0;
|
||||
APU.triangle.period_low = y;
|
||||
APU.triangle.len_period_high = 1;
|
||||
enable |= 0x4;
|
||||
APU_TRIANGLE_SUSTAIN(0x100 | y);
|
||||
enable |= ENABLE_TRIANGLE;
|
||||
}
|
||||
}
|
||||
APU.status = enable;
|
||||
APU_ENABLE(enable);
|
||||
}
|
||||
|
||||
static char starx[32];
|
||||
@ -783,7 +715,7 @@ void play_round() {
|
||||
register byte t0;
|
||||
byte end_timer = 255;
|
||||
add_score(0);
|
||||
putstring(0, 0, "PLAYER 1");
|
||||
//putbytes(NTADR_A(0, 1), "PLAYER 1", 8);
|
||||
setup_formation();
|
||||
clrobjs();
|
||||
formation_direction = 1;
|
||||
@ -851,7 +783,7 @@ void set_shifted_pattern(const byte* src, word dest, byte shift) {
|
||||
vram_write(buf, sizeof(buf));
|
||||
}
|
||||
|
||||
void setup_tileset() {
|
||||
void setup_graphics() {
|
||||
byte i;
|
||||
word src;
|
||||
word dest;
|
||||
@ -869,26 +801,14 @@ void setup_tileset() {
|
||||
set_shifted_pattern(&TILESET[src], dest, i);
|
||||
dest += 3*16;
|
||||
}
|
||||
}
|
||||
|
||||
const byte APUINIT[0x13] = {
|
||||
0x30,0x08,0x00,0x00,
|
||||
0x30,0x08,0x00,0x00,
|
||||
0x80,0x00,0x00,0x00,
|
||||
0x30,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00
|
||||
};
|
||||
|
||||
void init_apu() {
|
||||
// from https://wiki.nesdev.com/w/index.php/APU_basics
|
||||
memcpy((void*)0x4000, APUINIT, sizeof(APUINIT));
|
||||
APU.fcontrol = 0x40; // frame counter 5-step
|
||||
APU.status = 0x0f; // turn on all channels except DMC
|
||||
// activate vram buffer
|
||||
cendbuf();
|
||||
set_vram_update(updbuf);
|
||||
}
|
||||
|
||||
void main() {
|
||||
setup_tileset();
|
||||
init_apu();
|
||||
setup_graphics();
|
||||
apu_init();
|
||||
init_stars();
|
||||
player_score = 0;
|
||||
while (1) {
|
||||
|
@ -12,13 +12,7 @@ extern unsigned char palSprites[16];
|
||||
extern unsigned char TILESET[8*256];
|
||||
|
||||
#define COLS 32
|
||||
#define ROWS 28
|
||||
|
||||
#define NTADR(x,y) ((0x2000|((y)<<5)|(x)))
|
||||
|
||||
typedef unsigned char byte;
|
||||
typedef signed char sbyte;
|
||||
typedef unsigned short word;
|
||||
#define ROWS 27
|
||||
|
||||
// read a character from VRAM.
|
||||
// this is tricky because we have to wait
|
||||
@ -27,10 +21,10 @@ typedef unsigned short word;
|
||||
// back to the start of the frame.
|
||||
byte getchar(byte x, byte y) {
|
||||
// compute VRAM read address
|
||||
word addr = NTADR(x,y);
|
||||
word addr = NTADR_A(x,y);
|
||||
byte rd;
|
||||
// wait for VBLANK to start
|
||||
waitvsync();
|
||||
ppu_wait_nmi();
|
||||
vram_adr(addr);
|
||||
vram_read(&rd, 1);
|
||||
vram_adr(0x0);
|
||||
@ -60,7 +54,7 @@ void vdelay(byte count) {
|
||||
}
|
||||
|
||||
void cputcxy(byte x, byte y, char ch) {
|
||||
word addr = NTADR(x,y);
|
||||
word addr = NTADR_A(x,y);
|
||||
if (updptr >= 60) cflushnow();
|
||||
updbuf[updptr++] = addr >> 8;
|
||||
updbuf[updptr++] = addr & 0xff;
|
||||
@ -69,7 +63,7 @@ void cputcxy(byte x, byte y, char ch) {
|
||||
}
|
||||
|
||||
void cputsxy(byte x, byte y, char* str) {
|
||||
word addr = NTADR(x,y);
|
||||
word addr = NTADR_A(x,y);
|
||||
byte len = strlen(str);
|
||||
if (updptr >= 60 - len) cflushnow();
|
||||
updbuf[updptr++] = (addr >> 8) | NT_UPD_HORZ;
|
||||
|
40
presets/nes/vrambuf.c
Normal file
40
presets/nes/vrambuf.c
Normal file
@ -0,0 +1,40 @@
|
||||
|
||||
#include "neslib.h"
|
||||
#include "vrambuf.h"
|
||||
|
||||
// index to end of buffer
|
||||
byte updptr = 0;
|
||||
|
||||
// add EOF marker to buffer
|
||||
void cendbuf(void) {
|
||||
updbuf[updptr] = NT_UPD_EOF;
|
||||
}
|
||||
|
||||
void cclearbuf(void) {
|
||||
updptr = 0;
|
||||
cendbuf();
|
||||
}
|
||||
|
||||
// flush buffer now, waiting for next frame
|
||||
void cflushnow(void) {
|
||||
// make sure buffer has EOF marker
|
||||
cendbuf();
|
||||
// wait for next frame to flush update buffer
|
||||
// this will also set the scroll registers properly
|
||||
ppu_wait_frame();
|
||||
// clear the buffer
|
||||
cclearbuf();
|
||||
}
|
||||
|
||||
// add multiple characters to update buffer
|
||||
// using horizontal increment
|
||||
void putbytes(word addr, char* str, byte len) {
|
||||
if (updptr >= VBUFSIZE-4-len) cflushnow();
|
||||
updbuf[updptr++] = (addr >> 8) ^ NT_UPD_HORZ;
|
||||
updbuf[updptr++] = addr & 0xff;
|
||||
updbuf[updptr++] = len;
|
||||
while (len--) {
|
||||
updbuf[updptr++] = *str++;
|
||||
}
|
||||
cendbuf();
|
||||
}
|
38
presets/nes/vrambuf.h
Normal file
38
presets/nes/vrambuf.h
Normal file
@ -0,0 +1,38 @@
|
||||
|
||||
#ifndef _VRAMBUF_H
|
||||
#define _VRAMBUF_H
|
||||
|
||||
#include "neslib.h"
|
||||
|
||||
// VBUFSIZE = maximum update buffer bytes
|
||||
#define VBUFSIZE 128
|
||||
|
||||
// update buffer starts at $100 (stack page)
|
||||
#define updbuf ((byte*)0x100)
|
||||
|
||||
// index to end of buffer
|
||||
extern byte updptr;
|
||||
|
||||
// macros
|
||||
#define VRAMBUF_PUT(addr,len,flags)\
|
||||
updbuf[updptr++] = ((addr) >> 8) | (flags);\
|
||||
updbuf[updptr++] = (addr) & 0xff;\
|
||||
updbuf[updptr++] = (len);
|
||||
|
||||
#define VRAMBUF_ADD(b)\
|
||||
updbuf[updptr++] = (b);
|
||||
|
||||
// add EOF marker to buffer
|
||||
void cendbuf(void);
|
||||
|
||||
// clear update buffer
|
||||
void cclearbuf(void);
|
||||
|
||||
// flush buffer now, waiting for next frame
|
||||
void cflushnow(void);
|
||||
|
||||
// add multiple characters to update buffer
|
||||
// using horizontal increment
|
||||
void putbytes(word addr, char* str, byte len);
|
||||
|
||||
#endif // vrambuf.h
|
@ -17,6 +17,7 @@ const JSNES_PRESETS = [
|
||||
{id:'neslib1.c', name:'Text'},
|
||||
{id:'scroll.c', name:'Scrolling'},
|
||||
{id:'statusbar.c', name:'Status Bar'},
|
||||
{id:'horizmask.c', name:'Horizontal Scrolling'},
|
||||
{id:'sprites.c', name:'Sprites'},
|
||||
{id:'metasprites.c', name:'Metasprites'},
|
||||
{id:'flicker.c', name:'Flickering Sprites'},
|
||||
@ -118,6 +119,7 @@ const _JSNESPlatform = function(mainElement) {
|
||||
},
|
||||
//TODO: onBatteryRamWrite
|
||||
});
|
||||
//nes.ppu.clipToTvSize = false;
|
||||
nes.stop = function() {
|
||||
// TODO: trigger breakpoint
|
||||
self.pause();
|
||||
@ -300,10 +302,10 @@ const _JSNESPlatform = function(mainElement) {
|
||||
var PPUFLAGS = [
|
||||
["f_nmiOnVblank","NMI_ON_VBLANK"],
|
||||
["f_spVisibility","SPRITES"],
|
||||
["f_spClipping","CLIP_SPRITES"],
|
||||
["f_spClipping","NO_CLIP_SPRITES"],
|
||||
["f_dispType","MONOCHROME"],
|
||||
["f_bgVisibility","BACKGROUND"],
|
||||
["f_bgClipping","CLIP_BACKGROUND"],
|
||||
["f_bgClipping","NO_CLIP_BACKGROUND"],
|
||||
];
|
||||
for (var i=0; i<PPUFLAGS.length; i++) {
|
||||
var flag = PPUFLAGS[i];
|
||||
|
@ -892,7 +892,7 @@ function _recordVideo() {
|
||||
};
|
||||
f();
|
||||
});
|
||||
return true;
|
||||
//TODO? return true;
|
||||
}
|
||||
|
||||
function setFrameRateUI(fps:number) {
|
||||
|
@ -881,13 +881,12 @@ function linkLD65(step:BuildStep) {
|
||||
var binpath = "main";
|
||||
if (staleFiles(step, [binpath])) {
|
||||
var errors = [];
|
||||
var errmsg = '';
|
||||
var LD65 = emglobal.ld65({
|
||||
instantiateWasm: moduleInstFn('ld65'),
|
||||
noInitialRun:true,
|
||||
//logReadFiles:true,
|
||||
print:print_fn,
|
||||
printErr:function(s) { errmsg += s + '\n'; }
|
||||
printErr:function(s) { errors.push({msg:s,line:0}); }
|
||||
});
|
||||
var FS = LD65['FS'];
|
||||
var cfgfile = '/' + platform + '.cfg';
|
||||
@ -905,8 +904,6 @@ function linkLD65(step:BuildStep) {
|
||||
'-o', 'main', '-m', 'main.map'].concat(step.args, libargs);
|
||||
//console.log(args);
|
||||
execMain(step, LD65, args);
|
||||
if (errmsg.length)
|
||||
errors.push({line:0, msg:errmsg});
|
||||
if (errors.length)
|
||||
return {errors:errors};
|
||||
var aout = FS.readFile("main", {encoding:'binary'});
|
||||
|
Loading…
Reference in New Issue
Block a user