first commit

This commit is contained in:
LemonBoy 2013-10-13 18:31:57 +00:00
commit 33af1795f4
20 changed files with 3177 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
*.txt
*.py
*.pl
*.nds
*.elf
fs/
build/

134
Makefile Normal file
View File

@ -0,0 +1,134 @@
#---------------------------------------------------------------------------------
.SUFFIXES:
#---------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITARM)),)
$(error "Please set DEVKITARM in your environment. export DEVKITARM=<path to>devkitARM")
endif
include $(DEVKITARM)/ds_rules
#---------------------------------------------------------------------------------
# TARGET is the name of the output
# BUILD is the directory where object files & intermediate files will be placed
# SOURCES is a list of directories containing source code
# INCLUDES is a list of directories containing extra header files
# MAXMOD_SOUNDBANK contains a directory of music and sound effect files
#---------------------------------------------------------------------------------
TARGET := $(shell basename $(CURDIR))
BUILD := build
SOURCES := source
DATA :=
INCLUDES := include
NITRODATA := fs
#---------------------------------------------------------------------------------
# options for code generation
#---------------------------------------------------------------------------------
ARCH := -marm -mthumb-interwork -march=armv5te -mtune=arm946e-s
CFLAGS := -save-temps \
-funroll-loops -ftree-vectorize \
-Wall -Ofast\
-fomit-frame-pointer\
-ffast-math \
$(ARCH)
#-finstrument-functions -mpoke-function-name \
CFLAGS += $(INCLUDE) -DARM9 -DEMU_BUILD
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
ASFLAGS := $(ARCH)
LDFLAGS = -specs=ds_arm9.specs $(ARCH) -Wl,-Map,$(notdir $*.map)
#---------------------------------------------------------------------------------
# any extra libraries we wish to link with the project (order is important)
#---------------------------------------------------------------------------------
LIBS := -lfilesystem -lfat -lnds9
#---------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level containing
# include and lib
#---------------------------------------------------------------------------------
LIBDIRS := $(LIBNDS)
#---------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional
# rules for different file extensions
#---------------------------------------------------------------------------------
ifneq ($(BUILD),$(notdir $(CURDIR)))
#---------------------------------------------------------------------------------
export OUTPUT := $(CURDIR)/$(TARGET)
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
$(foreach dir,$(DATA),$(CURDIR)/$(dir))
export DEPSDIR := $(CURDIR)/$(BUILD)
ifneq ($(strip $(NITRODATA)),)
export NITRO_FILES := $(CURDIR)/$(NITRODATA)
endif
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
#---------------------------------------------------------------------------------
# use CXX for linking C++ projects, CC for standard C
#---------------------------------------------------------------------------------
ifeq ($(strip $(CPPFILES)),)
#---------------------------------------------------------------------------------
export LD := $(CC)
#---------------------------------------------------------------------------------
else
#---------------------------------------------------------------------------------
export LD := $(CXX)
#---------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------
export OFILES := $(addsuffix .o,$(BINFILES)) \
$(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
-I$(CURDIR)/$(BUILD)
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
.PHONY: $(BUILD) clean
#---------------------------------------------------------------------------------
$(BUILD):
@[ -d $@ ] || mkdir -p $@
@make --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
#---------------------------------------------------------------------------------
clean:
@echo clean ...
@rm -fr $(BUILD) $(TARGET).elf $(TARGET).nds
#---------------------------------------------------------------------------------
else
#---------------------------------------------------------------------------------
# main targets
#---------------------------------------------------------------------------------
$(OUTPUT).nds : $(OUTPUT).elf
$(OUTPUT).elf : $(OFILES)
#---------------------------------------------------------------------------------
%.bin.o : %.bin
#---------------------------------------------------------------------------------
@echo $(notdir $<)
$(bin2o)
-include $(DEPSDIR)/*.d
#---------------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------------

24
source/README.md Normal file
View File

@ -0,0 +1,24 @@
grape
=====
An Apple II emulator for Nintendo DS, powered by devkitPRO, libnds and lots of
sweat. This is how I spent most of my summer nights when I couldn't sleep
because of the heat, debugging the asm core and reading the Apple II manuals.
What is emulated
================
* Apple II/II+
* Language card
* 5.25 floppy disk
* Speaker
Notes
=====
Consider it as a WIP, it lacks some stuff like configurable keymaps and
extensive testing.
The video generation takes ~50% of the CPU time and would definitely benefit
from optimization, beside the fact that it doesn't emulate color bleeding.
When playing games that make extensive use of disk IO disable vblank to make it
load faster (eg. Karateka and Pacman)

10
source/cpu.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef CPU_H
#define CPU_H
int cpu_run (int run_cycles);
void cpu_reset ();
void cpu_nmi ();
extern u32 cpu_regs[6];
#endif

1551
source/cpu.s Normal file

File diff suppressed because it is too large Load Diff

278
source/disk.c Normal file
View File

@ -0,0 +1,278 @@
#include <nds.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "emu.h"
// Working buffer
static u8 diskbuf[35*6656];
static int Q6, Q7;
static int motor_on;
static int phase;
static int last_stepper;
static int track_start, track_pos;
static int sel_disk;
void disk_save (FILE *f)
{
fwrite(&Q6, 1, 4, f);
fwrite(&Q7, 1, 4, f);
fwrite(&motor_on, 1, 4, f);
fwrite(&phase, 1, 4, f);
fwrite(&last_stepper, 1, 4, f);
fwrite(&track_start, 1, 4, f);
fwrite(&track_pos, 1, 4, f);
fwrite(&sel_disk, 1, 4, f);
}
void disk_load (FILE *f)
{
fread(&Q6, 1, 4, f);
fread(&Q7, 1, 4, f);
fread(&motor_on, 1, 4, f);
fread(&phase, 1, 4, f);
fread(&last_stepper, 1, 4, f);
fread(&track_start, 1, 4, f);
fread(&track_pos, 1, 4, f);
fread(&sel_disk, 1, 4, f);
}
u8 disk_io_read (u16 addr) ITCM_CODE;
u8 disk_io_read (u16 addr)
{
int set;
int stepper;
int delta;
set = addr&1;
stepper = (addr>>1)&3;
switch (addr&0xf) {
case 0x1:
case 0x3:
case 0x5:
case 0x7:
if (motor_on) {
delta = 0;
if (stepper == ((last_stepper + 1)&3))
delta += 1;
if (stepper == ((last_stepper + 3)&3))
delta -= 1;
last_stepper = stepper;
if (delta) {
phase += delta;
if (phase < 0)
phase = 0;
if (phase > 70)
phase = 70;
/*track_pos = 0;*/
track_start = (phase >> 1) * 6656;
}
}
return 0xff;
case 0x0:
case 0x2:
case 0x4:
case 0x6:
return 0xff;
// ENABLE
case 0x8:
case 0x9:
motor_on = set;
return 0xff;
// SELECT
case 0xa:
case 0xb:
sel_disk = set;
return 0xff;
// Q6L
case 0xc:
Q6 = 0;
if (!Q7) {
// The tracks are circular, so wrap around
if (track_pos >= 6656) track_pos = 0;
return diskbuf[track_start + (track_pos++)];
}
// Handshake register
return 0x80;
// Q6H
case 0xd:
Q6 = 1;
return 0;
// Q7
case 0xe:
case 0xf:
Q7 = set;
// The status register is read from Q7L
return 0x20|0x80; // Write protect and ready
}
return 0xff;
}
int load_nib_disk (const char *file)
{
FILE *f;
size_t size;
f = fopen(file, "rb");
if (!f)
return 0;
fseek(f, 0, SEEK_END);
size = ftell(f);
fseek(f, 0, SEEK_SET);
if (size != 232960) {
iprintf("Size mismatch\n");
fclose(f);
return 0;
}
fread(diskbuf, 1, 232960, f);
fclose(f);
return 1;
}
// DOS 3.3 format 35 T 16 S (343 nybbles)
// http://www.umich.edu/~archive/apple2/misc/hardware/disk.encoding.txt
// http://mirrors.apple2.org.za/ground.icaen.uiowa.edu/MiscInfo/Programming/c600.disasm
// http://www.mac.linux-m68k.org/devel/iwm.php
// http://www.textfiles.com/apple/ANATOMY/t.dos.b800.bcff.txt
const u8 dos33table[] = {
0x96, 0x97, 0x9A, 0x9B, 0x9D, 0x9E, 0x9F, 0xA6,
0xA7, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB2, 0xB3,
0xB4, 0xB5, 0xB6, 0xB7, 0xB9, 0xBA, 0xBB, 0xBC,
0xBD, 0xBE, 0xBF, 0xCB, 0xCD, 0xCE, 0xCF, 0xD3,
0xD6, 0xD7, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE,
0xDF, 0xE5, 0xE6, 0xE7, 0xE9, 0xEA, 0xEB, 0xEC,
0xED, 0xEE, 0xEF, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6,
0xF7, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
};
const int dos_order[] = {
0x0, 0x7, 0xE, 0x6, 0xD, 0x5, 0xC, 0x4, 0xB, 0x3, 0xA, 0x2, 0x9, 0x1, 0x8, 0xF
};
const int prodos_order[] = {
0x0, 0x8, 0x1, 0x9, 0x2, 0xA, 0x3, 0xB, 0x4, 0xC, 0x5, 0xD, 0x6, 0xE, 0x7, 0xF
};
void write_address (u8 *b, u8 track, u8 sector, u8 volume)
{
// Handy macros for 4 and 4 encoding
#define X1(x) (((x)>>1)|0xAA)
#define X2(x) ((x)|0xAA)
b[0] = 0xD5;
b[1] = 0xAA;
b[2] = 0x96;
b[3] = X1(volume);
b[4] = X2(volume);
b[5] = X1(track);
b[6] = X2(track);
b[7] = X1(sector);
b[8] = X2(sector);
b[9] = X1(volume^track^sector);
b[10] = X2(volume^track^sector);
b[11] = 0xDE;
b[12] = 0xAA;
b[13] = 0xEB;
#undef X1
#undef X2
}
// GAP + (ADDRESS + GAP + DATA + GAP)
#define GAP1_LEN 48
#define GAP2_LEN 6
#define GAP3_LEN 27
#define ADDRESS_LEN 14 // 3 + 2 + 2 + 2 + 2 + 3
#define DATA_LEN 349 // 3 + 342 + 1 + 3
int load_dsk_disk (const char *file, const int *sec_order)
{
FILE *f;
size_t size;
u8 sec_buf[256];
u8 nib_buf[343];
u8 *p, chksum;
int i, j, k;
f = fopen(file, "rb");
if (!f)
return 0;
fseek(f, 0, SEEK_END);
size = ftell(f);
fseek(f, 0, SEEK_SET);
if (size > 143360) {
iprintf("Size mismatch\n");
fclose(f);
return 0;
}
p = diskbuf;
memset(p, 0xff, sizeof(diskbuf));
// Crunch all the tracks
for (i = 0; i < 35; i++) {
p += 64;
// Crunch all the sectors
for (j = 0; j < 16; j++) {
fseek(f, 256 * sec_order[j] + (i * 16 * 256), SEEK_SET);
fread(sec_buf, 1, 256, f);
// 0xFE is the default volume
write_address(p, i, j, 0xFE);
p += 14;
// Second sync gap
p += 4;
// Data marker
p[0] = 0xD5; p[1] = 0xAA; p[2] = 0xAD;
p += 3;
// Convert to 6bit, the stashed bits start at 0
memset(nib_buf, 0, sizeof(nib_buf));
for (k = 0; k < 256; k++) {
u8 v = sec_buf[k];
nib_buf[k+86] = v >> 2;
nib_buf[k%86] |= ((v&1) << 1 | (v&2) >> 1) << (2 * (k/86));
}
chksum = 0;
for (k = 0; k < 342; k++) {
*p++ = dos33table[chksum^nib_buf[k]];
chksum = nib_buf[k];
}
// Write the checksum
*p++ = dos33table[chksum];
// Write the last marker
p[0] = 0xDE; p[1] = 0xAA; p[2] = 0xEB;
p += 3;
p += 45;
}
}
fclose(f);
return 1;
}
int load_disk (const char *path)
{
char *ext;
ext = strrchr(path, '.');
if (!ext)
return 0;
if (!stricmp(ext + 1, "dsk") || !stricmp(ext + 1, "do"))
return load_dsk_disk(path, dos_order);
if (!stricmp(ext + 1, "po"))
return load_dsk_disk(path, prodos_order);
if (!stricmp(ext + 1, "nib"))
return load_nib_disk(path);
return 0;
}

27
source/disk.h Normal file
View File

@ -0,0 +1,27 @@
#ifndef DISK_H
#define DISK_H
static const u8 disk_rom[] = {
0xA2, 0x20, 0xA0, 0x00, 0xA2, 0x03, 0x86, 0x3C, 0x8A, 0x0A, 0x24, 0x3C, 0xF0, 0x10, 0x05, 0x3C,
0x49, 0xFF, 0x29, 0x7E, 0xB0, 0x08, 0x4A, 0xD0, 0xFB, 0x98, 0x9D, 0x56, 0x03, 0xC8, 0xE8, 0x10,
0xE5, 0x20, 0x58, 0xFF, 0xBA, 0xBD, 0x00, 0x01, 0x0A, 0x0A, 0x0A, 0x0A, 0x85, 0x2B, 0xAA, 0xBD,
0x8E, 0xC0, 0xBD, 0x8C, 0xC0, 0xBD, 0x8A, 0xC0, 0xBD, 0x89, 0xC0, 0xA0, 0x50, 0xBD, 0x80, 0xC0,
0x98, 0x29, 0x03, 0x0A, 0x05, 0x2B, 0xAA, 0xBD, 0x81, 0xC0, 0xA9, 0x56, 0x20, 0xA8, 0xFC, 0x88,
0x10, 0xEB, 0x85, 0x26, 0x85, 0x3D, 0x85, 0x41, 0xA9, 0x08, 0x85, 0x27, 0x18, 0x08, 0xBD, 0x8C,
0xC0, 0x10, 0xFB, 0x49, 0xD5, 0xD0, 0xF7, 0xBD, 0x8C, 0xC0, 0x10, 0xFB, 0xC9, 0xAA, 0xD0, 0xF3,
0xEA, 0xBD, 0x8C, 0xC0, 0x10, 0xFB, 0xC9, 0x96, 0xF0, 0x09, 0x28, 0x90, 0xDF, 0x49, 0xAD, 0xF0,
0x25, 0xD0, 0xD9, 0xA0, 0x03, 0x85, 0x40, 0xBD, 0x8C, 0xC0, 0x10, 0xFB, 0x2A, 0x85, 0x3C, 0xBD,
0x8C, 0xC0, 0x10, 0xFB, 0x25, 0x3C, 0x88, 0xD0, 0xEC, 0x28, 0xC5, 0x3D, 0xD0, 0xBE, 0xA5, 0x40,
0xC5, 0x41, 0xD0, 0xB8, 0xB0, 0xB7, 0xA0, 0x56, 0x84, 0x3C, 0xBC, 0x8C, 0xC0, 0x10, 0xFB, 0x59,
0xD6, 0x02, 0xA4, 0x3C, 0x88, 0x99, 0x00, 0x03, 0xD0, 0xEE, 0x84, 0x3C, 0xBC, 0x8C, 0xC0, 0x10,
0xFB, 0x59, 0xD6, 0x02, 0xA4, 0x3C, 0x91, 0x26, 0xC8, 0xD0, 0xEF, 0xBC, 0x8C, 0xC0, 0x10, 0xFB,
0x59, 0xD6, 0x02, 0xD0, 0x87, 0xA0, 0x00, 0xA2, 0x56, 0xCA, 0x30, 0xFB, 0xB1, 0x26, 0x5E, 0x00,
0x03, 0x2A, 0x5E, 0x00, 0x03, 0x2A, 0x91, 0x26, 0xC8, 0xD0, 0xEE, 0xE6, 0x27, 0xE6, 0x3D, 0xA5,
0x3D, 0xCD, 0x00, 0x08, 0xA6, 0x2B, 0x90, 0xDB, 0x4C, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
};
int load_disk (const char *path);
void disk_load (FILE *f);
void disk_save (FILE *f);
#endif

82
source/emu.c Normal file
View File

@ -0,0 +1,82 @@
#include <nds.h>
#include "emu.h"
static u32 frames_done;
static u32 vblanks;
void irq_vblank ()
{
if (vblanks++ == 60) {
consoleClear();
iprintf("FPS %i\n", frames_done);
// iprintf("keybd_latch : %02x\n", keybd_latch);
frames_done = 0;
vblanks = 0;
}
}
void emu_reset ()
{
sound_reset();
mem_reset();
cpu_reset();
}
void emu_init ()
{
keyboardShow();
// Set some sane defaults
emu_vsync = 1;
// Setup the video hardware
video_init();
// Load the appropriate bios
u16 crc;
/*load_bin("APPLE2.ROM", 0xB000, 0x5000, &crc);*/
if (load_bin("BASIC.ROM", 0xD000, 0x3000, &crc)) {
iprintf("BIOS CRC16 %04x (%s)\n", crc, (crc == 0xAC8F) ? "Valid" : "Invalid");
// Refuse to load a wrong bios
if (crc != 0xAC8F)
while (1);
} else {
iprintf("No BASIC.ROM found. Halting.");
while (1);
}
// Load the disk rom in place
load_buf(disk_rom, 0xc600, 0x100);
basename = NULL;
// Used by the fps counter
frames_done = 0;
vblanks = 0;
// Fire the counter
irqSet(IRQ_VBLANK, irq_vblank);
irqEnable(IRQ_VBLANK);
emu_reset();
}
void emu_run ()
{
while (1) {
cpu_run(17030);
video_draw();
update_input();
frames_done++;
if (keysDown()&KEY_START)
pause_menu();
sound_play();
if (emu_vsync)
swiWaitForVBlank();
bgUpdate();
}
}

23
source/emu.h Normal file
View File

@ -0,0 +1,23 @@
#ifndef EMU_H
#define EMU_H
#include <stdio.h>
#include "disk.h"
#include "cpu.h"
#include "video.h"
#include "mem.h"
#include "state.h"
#define INPUT_JSTK 0
#define INPUT_KBD 1
int emu_vsync;
int emu_input;
static char *basename;
void emu_init ();
void emu_run ();
void emu_reset ();
#endif

52
source/input.c Normal file
View File

@ -0,0 +1,52 @@
#include <nds.h>
#include "emu.h"
static u8 key_map[12] = {
0x0d, // A
0x20, // B
0x80, // SELECT
0x80, // START
0x4C, // RIGHT
0x4A, // LEFT
0x4B, // UP
0x80, // DOWN
0x80, // R
0x80, // L
0x80, // X
0x80, // Y
};
void update_input () ITCM_CODE;
void update_input ()
{
u32 keys;
int kbd_key;
scanKeys();
keys = keysHeld()&0xfff;
// Send keyboard scancodes when a key is pressed
if (emu_input == INPUT_KBD && keys) {
keybd_latch = 0x80 ^ key_map[__builtin_ctz(keys)];
}
if (emu_input == INPUT_JSTK) {
jstk_btn[0] = (keys&KEY_X) ? 0x80 : 0x00;
jstk_btn[1] = (keys&KEY_Y) ? 0x80 : 0x00;
jstk_btn[2] = (keys&KEY_B) ? 0x80 : 0x00;
}
kbd_key = keyboardUpdate();
if (kbd_key > 0) {
switch (keybd_latch) {
case DVK_ENTER:
keybd_latch = 0x8d;
return;
default:
if (kbd_key >= '`') keybd_latch = 0x80 | (kbd_key - 32);
else keybd_latch = 0x80 | kbd_key;
return;
}
}
}

55
source/main.c Normal file
View File

@ -0,0 +1,55 @@
#include <nds.h>
#include <filesystem.h>
#include <fat.h>
#include <stdio.h>
#include "emu.h"
int main(int argc, char **argv)
{
defaultExceptionHandler();
consoleDebugInit(DebugDevice_NOCASH);
consoleDemoInit();
keyboardDemoInit();
fatInitDefault();
/*nitroFSInit(NULL);*/
soundEnable();
iprintf("-- grape\n");
emu_init();
if (argc != 2) {
// Slow loading
/*load_disk("PACMAN.DSK");*/
/*load_disk("Karateka (1984)(Broderbund).dsk");*/
// Awesome!
/*load_disk("lode.dsk");*/
// AppleII+
/*load_disk("Prince of Persia (1989)(Broderbund)(Disk 1 of 3)[cr].dsk");*/
// Gfx heavy
/*load_disk("Starglider (19xx)(Rainbird).dsk");*/
// Undocumented OPs
/*load_disk("Ms. Pacman (19xx)(-)[cr].dsk");*/
/*load_disk("Round About (1983)(Datamost).dsk");*/
/*load_disk("Bug Attack (1981)(Cavalier Computer).dsk");*/
// Scroller
load_disk("TetrisII.DSK");
// Mixed mode
/*load_disk("tetris48k.nib");*/
// Lowres
/*load_disk("Fracas (1980)(Quality Software).dsk");*/
/*load_disk("Colorix.dsk");*/
/*load_disk("LoRes Games.DSK");*/
// SP Crash
/*load_disk("Apple II Business Graphics 1.0 (1981).nib");*/
} else {
load_disk(argv[1]);
}
emu_run();
return 0;
}

23
source/mem.h Normal file
View File

@ -0,0 +1,23 @@
#ifndef MEM_H
#define MEM_H
// mem.s
extern u8 page_dirty[64];
extern u8 keybd_latch;
extern u32 jstk_axis[4];
extern u32 jstk_btn[3];
extern u32 jstk_rst_cycle;
extern u8 mainram[0x10000];
extern u8 lcram[0x4000];
extern u32 memmap_r[0x10];
extern u32 memmap_w[0x10];
u8 readb (u16 addr);
void writeb (u16 addr, u8 val);
void mem_reset (void);
int load_bin (char *file, u16 addr, u16 len, u16 *crc);
int load_buf (const u8 *buf, u16 addr, u16 len);
#endif

255
source/mem.s Normal file
View File

@ -0,0 +1,255 @@
.arm
.section .itcm,"ax",%progbits
.global readb
.global writeb
.global mem_reset
.global mainram
.global lcram
.global jstk_btn
.global jstk_axis
.global memmap_w
.global memmap_r
.global keybd_latch
.global jstk_rst_cycle
.global page_dirty
// uint8_t readb (uint16_t addr)
readb:
mov r1, r0, lsr #8
cmp r1, #0xc0
beq 1f
mov r1, r1, lsr #4
ldr r2, =memmap_r
ldr r3, [r2, r1, lsl #2]
ldrb r0, [r3, r0]
bx lr
// IO Read @ 0xc000
1: and r1, r0, #0xf0
ldr pc, [pc, r1, lsr #2]
nop
io_read_tbl:
.word io_keybd
.word io_keybdl
.word io_null
.word io_spkr
.word io_null
.word video_io_read
.word io_jstk
.word io_jstk_rst
.word io_lc
.word io_null
.word io_null
.word io_null
.word io_null
.word io_null
.word disk_io_read
.word io_null
.ltorg
// void writeb (uint16_t addr, uint8_t val)
writeb:
mov r2, r0, lsr #8
cmp r2, #0xc0
// IO Read
beq 1f
mov r2, r2, lsr #2
// Mark the page as dirty
mov r3, #1
ldr r12, =page_dirty
strb r3, [r12, r2]
mov r2, r2, lsr #2
ldr r12, =memmap_w
ldr r3, [r12, r2, lsl #2]
strb r1, [r3, r0]
bx lr
1: and r1, r0, #0xf0
ldr pc, [pc, r1, lsr #2]
nop
io_write_tbl:
.word io_null
.word io_keybdl
.word io_null
.word io_null
.word io_null
.word video_io_read
.word io_jstk_rst
.word io_null
.word io_lc
.word io_null
.word io_null
.word io_null
.word io_null
.word io_null
.word disk_io_read
.word io_null
.ltorg
// IO handlers
io_keybd:
ldr r2, =keybd_latch
ldr r0, [r2]
bx lr
io_keybdl:
ldr r2, =keybd_latch
ldr r0, [r2]
// Clear the latch
and r0, #0x7f
str r0, [r2]
bx lr
io_jstk:
and r0, #0xf
ldr r1, =(jstk_btn-4)
cmp r0, #0x4
ldr r0, [r1, r0, lsl #2]
// Handle the pushbutton status
bxlo lr
ldr r1, [r1, #32] // jstk_rst_cycle
// Check how many cycles have passed
subs r2, r1, r5
ldrmi r3, =17030
addmi r2, r3
// Should the paddle be sent low ?
cmp r2, r0
movls r0, #0x80
movhi r0, #0x00
bx lr
io_jstk_rst:
ldr r0, =jstk_rst_cycle
str r5, [r0]
// Read KEYINPUT
ldr r3, =0x4000130
ldrh r3, [r3]
// Find how many cycles the joystick counter takes to return to 0
mvn r3, r3, lsr #4
adr r2, axis_to_cycles
and r3, #0xf
add r2, r3, lsl #2
ldrh r1, [r2, #0]
str r1, [r0, #-16]
ldrh r1, [r2, #2]
str r1, [r0, #-12]
bx lr
// The pdlread routine takes 11 cycles
axis_to_cycles:
.short 0x7f*11,0x7f*11 //
.short 0xff*11,0x7f*11 // R
.short 0x00*11,0x7f*11 // L
.short 0x00*11,0x7f*11 // R+L
.short 0x7f*11,0x00*11 // U
.short 0xff*11,0x00*11 // R+U
.short 0x00*11,0x00*11 // L+U
.short 0x00*11,0x00*11 // R+L+U
.short 0x7f*11,0xff*11 // D
.short 0xff*11,0xff*11 // R+D
.short 0x00*11,0xff*11 // L+D
.short 0x00*11,0xff*11 // R+L+D
.short 0x7f*11,0xff*11 // U+D
.short 0xff*11,0xff*11 // R+U+D
.short 0x00*11,0xff*11 // L+U+D
.short 0x00*11,0xff*11 // R+L+U+D
io_lc:
ldr r12, =memmap_w
ldr r3, =(mainram+0x4000)
// Do it backwards so we can reuse r3
str r3, [r12, #0xf<<2]
str r3, [r12, #0xe<<2]
// Select between bank 1/2
tst r0, #8
mov r2, r3
subne r2, #0x1000
str r2, [r12, #0xd<<2]
// Check if rom is mapped on read
ldr r1, =map_rom_tbl
and r0, #3
ldr r0, [r1, r0, lsl #2]
ldr r12, =memmap_r
tst r0, #1 // this is lame
bne 1f // map the rom
str r2, [r12, #0xd<<2]
str r3, [r12, #0xe<<2]
str r3, [r12, #0xf<<2]
bx lr
1: sub r3, #0x4000
str r3, [r12, #0xd<<2]
str r3, [r12, #0xe<<2]
str r3, [r12, #0xf<<2]
bx lr
io_spkr:
mov r0, r5
ldr r1, =sound_spkr_flip
bx r1
io_null:
mov r0, #0
bx lr
mem_reset:
ldr r0, =memmap_r
ldr r1, =memmap_w
ldr r2, =mainram
mov r3, #0x10
1: str r2, [r0], #4
str r2, [r1], #4
subs r3, #1
bne 1b
bx lr
.ltorg
.bss
.align 4
mainram:
.space 0x10000
lcram:
.space 0x1000*4
.section .dtcm
.align 4
memmap_r:
.rept 16
.word mainram
.endr
memmap_w:
.rept 16
.word mainram
.endr
page_dirty:
.rept 64
.byte 0
.endr
// A button is active when the 7th bit is set
jstk_btn:
.word 0 // PB1
.word 0 // PB2
.word 0 // PB3 (shift)
// Axis are 0 <= x <= 255, where 0x80 is center
jstk_axis:
.word 0 // P1 X
.word 0 // P1 Y
.word 0 // P2 X
.word 0 // P2 Y
// io_jstk_rst relies on this to be after the joystick
// data for correct indexing. Don't move.
jstk_rst_cycle:
.word 0
keybd_latch:
.word 0
.section .rodata
map_rom_tbl:
.word 0,1,1,0

39
source/memutil.c Normal file
View File

@ -0,0 +1,39 @@
#include <nds.h>
#include <stdio.h>
#include <stdint.h>
#include "cpu.h"
#include "mem.h"
int load_buf (const u8 *buf, u16 addr, u16 len)
{
if (addr + len > 0x10000) {
iprintf("oob\n");
return 0;
}
memcpy(mainram + addr, buf, len);
return 1;
}
int load_bin (char *file, u16 addr, u16 len, u16 *crc)
{
FILE *f;
if (addr + len > 0x10000) {
iprintf("oob\n");
return 0;
}
f = fopen(file, "rb");
if (!f) {
iprintf("Can't open %s\n", file);
return 0;
}
fread(mainram + addr, 1, len, f);
fclose(f);
if (crc)
*crc = swiCRC16(0xffff, mainram + addr, len);
return 1;
}

100
source/menu.c Normal file
View File

@ -0,0 +1,100 @@
#include <nds.h>
#include <stdio.h>
#include "emu.h"
typedef struct entry_t {
const char *label;
const int opts_no;
const char *opts[10];
int (*cb)(int);
} entry_t;
typedef struct page_t {
const char *title;
const int entries_no;
const entry_t *entries;
} page_t;
#define OPT(n) int opt_##n (int sel) { emu_##n = sel; return 1; }
OPT(vsync);
OPT(input);
int opt_screen (int sel)
{
(sel) ? lcdMainOnBottom() : lcdMainOnTop();
return 1;
}
const static struct page_t paused_pg = {
"Paused", 7, (const entry_t []){
{ "Vsync", 2, { "No", "Yes" }, opt_vsync },
{ "Scale", 2, { "No", "Yes" }, video_set_scale },
{ "Screen", 2, { "Top", "Bottom" }, opt_screen },
{ "Map keys to", 2, { "joystick", "keyboard" }, opt_input },
{ "Save state", 9, { "1", "2", "3", "4", "5", "6", "7", "8", "9" }, state_save },
{ "Load state", 9, { "1", "2", "3", "4", "5", "6", "7", "8", "9" }, state_load },
{ "Exit", 0, { }, exit },
}
};
static int paused_pg_opt[6] = { 1, 0, 0, 0, 0, 0 };
void menu_print_page (const page_t *page, int *opts)
{
int i;
int cur;
int keys;
cur = 0;
while (1) {
consoleClear();
iprintf("-- %s\n\n", page->title);
const entry_t *sel_entry = &page->entries[cur];
for (i = 0; i < page->entries_no; i++) {
const entry_t *entry = &page->entries[i];
iprintf("%c %s %s\n", (i == cur) ? '>' : ' ', entry->label,
(entry->opts_no) ? entry->opts[opts[i]] : "");
}
scanKeys();
keys = keysDownRepeat();
if (keys&KEY_UP) {
cur--;
if (cur < 0)
cur = page->entries_no - 1;
}
if (keys&KEY_DOWN) {
cur++;
if (cur == page->entries_no)
cur = 0;
}
if (sel_entry->opts_no) {
if (keys&KEY_LEFT && opts[cur] > 0) {
opts[cur]--;
if (sel_entry->cb) sel_entry->cb(opts[cur]);
}
if (keys&KEY_RIGHT && opts[cur] < sel_entry->opts_no - 1) {
opts[cur]++;
if (sel_entry->cb) sel_entry->cb(opts[cur]);
}
}
if (keys&KEY_A) {
if (sel_entry->cb)
sel_entry->cb(opts[cur]); // TODO : Handle errors
return;
}
// Use keysDown to avoid bouncing
if (keysDown()&KEY_START)
return;
swiWaitForVBlank();
}
}
void pause_menu () { menu_print_page(&paused_pg, (int *)&paused_pg_opt); }

46
source/sound.c Normal file
View File

@ -0,0 +1,46 @@
#include <nds.h>
#include "emu.h"
static u8 sound_buf[44100];
static int buf_pos;
static int last_cycles;
static u8 spkr_phase;
void sound_reset ()
{
memset(&sound_buf, 0, sizeof(sound_buf));
buf_pos = 0;
last_cycles = 0;
spkr_phase = 0x80;
}
#define MIN(a,b) (((a) < (b)) ? (a) : (b))
void sound_spkr_flip (u32 cycles_left)
{
int ts, i;
// Check how many cycles have passed since last flip
ts = (last_cycles - cycles_left);
if (ts < 0)
ts += 17030;
last_cycles = cycles_left;
int len;
len = MIN(ts/23, sizeof(sound_buf) - buf_pos);
for (i = 0; i < len; i++) {
sound_buf[buf_pos++] = spkr_phase;
}
spkr_phase = ~spkr_phase;
}
void sound_play ()
{
if (buf_pos) {
memset(sound_buf + buf_pos, 0, sizeof(sound_buf) - buf_pos);
soundPlaySample(sound_buf, SoundFormat_8Bit, buf_pos, 44100, 127, 0x40, 0, 0);
buf_pos = 0;
}
}

82
source/state.c Normal file
View File

@ -0,0 +1,82 @@
#include <stdio.h>
#include <nds.h>
#include "emu.h"
typedef struct state_hdr_t {
u32 magic;
u8 ver;
u8 flags;
u16 resv;
} __attribute__((packed)) state_hdr_t;
#define STATE_MAGIC (0x47525033)
int state_save (int slot)
{
FILE *f;
state_hdr_t h;
f = fopen("game.sav", "w+");
if (f)
return 0;
h.magic = STATE_MAGIC;
h.ver = 1;
h.flags = 0;
h.resv = 0;
fwrite(&h, 1, sizeof(state_hdr_t), f);
fwrite(&cpu_regs, 6, 4, f);
fwrite(&mainram, 1, sizeof(mainram), f);
fwrite(&lcram, 1, sizeof(lcram), f);
fwrite(&memmap_r, 0x10, 4, f);
fwrite(&memmap_w, 0x10, 4, f);
fwrite(&page_dirty, 1, 64, f);
fwrite(&jstk_axis, 4, 4, f);
fwrite(&jstk_btn, 3, 4, f);
fwrite(&jstk_rst_cycle, 1, 4, f);
fwrite(&keybd_latch, 1, 4, f);
video_save(f);
disk_save(f);
fclose(f);
return 1;
}
int state_load (int slot)
{
FILE *f;
state_hdr_t h;
f = fopen("game.sav", "w+");
if (f)
return 0;
h.magic = STATE_MAGIC;
h.ver = 1;
h.flags = 0;
h.resv = 0;
fread(&h, 1, sizeof(state_hdr_t), f);
fread(&cpu_regs, 6, 4, f);
fread(&mainram, 1, sizeof(mainram), f);
fread(&lcram, 1, sizeof(lcram), f);
fread(&memmap_r, 0x10, 4, f);
fread(&memmap_w, 0x10, 4, f);
fread(&page_dirty, 1, 64, f);
fread(&jstk_axis, 4, 4, f);
fread(&jstk_btn, 3, 4, f);
fread(&jstk_rst_cycle, 1, 4, f);
fread(&keybd_latch, 1, 4, f);
video_load(f);
disk_load(f);
fclose(f);
return 1;
}

7
source/state.h Normal file
View File

@ -0,0 +1,7 @@
#ifndef STATE_H
#define STATE_H
int state_load (int slot);
int state_save (int slot);
#endif

371
source/video.c Normal file
View File

@ -0,0 +1,371 @@
#include <stdio.h>
#include <nds.h>
#include "mem.h"
const u8 apple_font[] = {
0x00, 0x70, 0x88, 0xa8, 0xe8, 0x68, 0x08, 0xf0, 0x00, 0x20, 0x50, 0x88, 0x88, 0xf8, 0x88, 0x88,
0x00, 0x78, 0x88, 0x88, 0x78, 0x88, 0x88, 0x78, 0x00, 0x70, 0x88, 0x08, 0x08, 0x08, 0x88, 0x70,
0x00, 0x78, 0x88, 0x88, 0x88, 0x88, 0x88, 0x78, 0x00, 0xf8, 0x08, 0x08, 0x78, 0x08, 0x08, 0xf8,
0x00, 0xf8, 0x08, 0x08, 0x78, 0x08, 0x08, 0x08, 0x00, 0xf0, 0x08, 0x08, 0x08, 0xc8, 0x88, 0xf0,
0x00, 0x88, 0x88, 0x88, 0xf8, 0x88, 0x88, 0x88, 0x00, 0x70, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70,
0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x88, 0x70, 0x00, 0x88, 0x48, 0x28, 0x18, 0x28, 0x48, 0x88,
0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0xf8, 0x00, 0x88, 0xd8, 0xa8, 0xa8, 0x88, 0x88, 0x88,
0x00, 0x88, 0x88, 0x98, 0xa8, 0xc8, 0x88, 0x88, 0x00, 0x70, 0x88, 0x88, 0x88, 0x88, 0x88, 0x70,
0x00, 0x78, 0x88, 0x88, 0x78, 0x08, 0x08, 0x08, 0x00, 0x70, 0x88, 0x88, 0x88, 0xa8, 0x48, 0xb0,
0x00, 0x78, 0x88, 0x88, 0x78, 0x28, 0x48, 0x88, 0x00, 0x70, 0x88, 0x08, 0x70, 0x80, 0x88, 0x70,
0x00, 0xf8, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x70,
0x00, 0x88, 0x88, 0x88, 0x88, 0x88, 0x50, 0x20, 0x00, 0x88, 0x88, 0x88, 0xa8, 0xa8, 0xd8, 0x88,
0x00, 0x88, 0x88, 0x50, 0x20, 0x50, 0x88, 0x88, 0x00, 0x88, 0x88, 0x50, 0x20, 0x20, 0x20, 0x20,
0x00, 0xf8, 0x80, 0x40, 0x20, 0x10, 0x08, 0xf8, 0x00, 0xf8, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf8,
0x00, 0x00, 0x08, 0x10, 0x20, 0x40, 0x80, 0x00, 0x00, 0xf8, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xf8,
0x00, 0x00, 0x00, 0x20, 0x50, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x20,
0x00, 0x50, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x50, 0xf8, 0x50, 0xf8, 0x50, 0x50,
0x00, 0x20, 0xf0, 0x28, 0x70, 0xa0, 0x78, 0x20, 0x00, 0x18, 0x98, 0x40, 0x20, 0x10, 0xc8, 0xc0,
0x00, 0x10, 0x28, 0x28, 0x10, 0xa8, 0x48, 0xb0, 0x00, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x20, 0x10, 0x08, 0x08, 0x08, 0x10, 0x20, 0x00, 0x20, 0x40, 0x80, 0x80, 0x80, 0x40, 0x20,
0x00, 0x20, 0xa8, 0x70, 0x20, 0x70, 0xa8, 0x20, 0x00, 0x00, 0x20, 0x20, 0xf8, 0x20, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x00,
0x00, 0x70, 0x88, 0xc8, 0xa8, 0x98, 0x88, 0x70, 0x00, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x70,
0x00, 0x70, 0x88, 0x80, 0x60, 0x10, 0x08, 0xf8, 0x00, 0xf8, 0x80, 0x40, 0x60, 0x80, 0x88, 0x70,
0x00, 0x40, 0x60, 0x50, 0x48, 0xf8, 0x40, 0x40, 0x00, 0xf8, 0x08, 0x78, 0x80, 0x80, 0x88, 0x70,
0x00, 0xe0, 0x10, 0x08, 0x78, 0x88, 0x88, 0x70, 0x00, 0xf8, 0x80, 0x40, 0x20, 0x10, 0x10, 0x10,
0x00, 0x70, 0x88, 0x88, 0x70, 0x88, 0x88, 0x70, 0x00, 0x70, 0x88, 0x88, 0xf0, 0x80, 0x40, 0x38,
0x00, 0x00, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x20, 0x20, 0x10,
0x00, 0x40, 0x20, 0x10, 0x08, 0x10, 0x20, 0x40, 0x00, 0x00, 0x00, 0xf8, 0x00, 0xf8, 0x00, 0x00,
0x00, 0x10, 0x20, 0x40, 0x80, 0x40, 0x20, 0x10, 0x00, 0x70, 0x88, 0x40, 0x20, 0x20, 0x00, 0x20
};
static const u16 video_addr[] DTCM_DATA = {
0x000, 0x080, 0x100, 0x180,
0x200, 0x280, 0x300, 0x380,
0x028, 0x0a8, 0x128, 0x1a8,
0x228, 0x2a8, 0x328, 0x3a8,
0x050, 0x0d0, 0x150, 0x1d0,
0x250, 0x2d0, 0x350, 0x3d0
};
static const u16 palette[] = {
RGB15(0, 0, 0),
RGB15(28, 4, 12),
RGB15(12, 9, 23),
RGB15(31, 8, 31),
RGB15(0, 20, 12),
RGB15(19, 19, 19),
RGB15(2, 25, 31),
RGB15(25, 24, 31),
RGB15(12, 14, 0),
RGB15(31, 13, 7),
RGB15(19, 19, 19),
RGB15(31, 19, 25),
RGB15(2, 30, 7),
RGB15(25, 27, 17),
RGB15(14, 31, 25),
RGB15(31, 31, 31),
};
static int text_bg;
static int gfx_bg;
static int text_mode;
static int mixed_mode;
static int sel_page;
static int hires;
void video_set_mode ();
void video_save (FILE *f)
{
fwrite(&text_mode, 1, 4, f);
fwrite(&mixed_mode, 1, 4, f);
fwrite(&sel_page, 1, 4, f);
fwrite(&hires, 1, 4, f);
}
void video_load (FILE *f)
{
fread(&text_mode, 1, 4, f);
fread(&mixed_mode, 1, 4, f);
fread(&sel_page, 1, 4, f);
fread(&hires, 1, 4, f);
video_set_mode();
}
void video_set_mode ()
{
if (mixed_mode) {
bgShow(gfx_bg);
bgShow(text_bg);
REG_DISPCNT |= DISPLAY_WIN0_ON;
} else {
bgShow(text_mode ? text_bg : gfx_bg);
bgHide(text_mode ? gfx_bg : text_bg);
REG_DISPCNT &= ~DISPLAY_WIN0_ON;
}
}
u8 video_io_read (u16 addr) ITCM_CODE;
u8 video_io_read (u16 addr)
{
switch (addr&0xf) {
case 0x0:
text_mode = 0;
break;
case 0x1:
text_mode = 1;
break;
case 0x2:
mixed_mode = 0;
break;
case 0x3:
mixed_mode = 1;
break;
case 0x4:
sel_page = 1;
break;
case 0x5:
sel_page = 2;
break;
case 0x6:
hires = 0;
break;
case 0x7:
hires = 1;
break;
// Annunciators
case 0x8:
case 0xa:
case 0xc:
case 0xe:
break;
}
video_set_mode();
// Duh, some scrollers wont work if we don't return 0x80
// http://rich12345.tripod.com/aiivideo/vbl.html
return 0x80;
}
void draw_lores_scr (u16 *map) ITCM_CODE;
void draw_lores_scr (u16 *map)
{
int last_line;
int i, j;
u8 *ptr;
if (!page_dirty[sel_page])
return;
last_line = mixed_mode ? 168 : 192;
for (i = 0; i < last_line; i++) {
ptr = (u8 *)(mainram + (sel_page << 10) + video_addr[i/8]);
for (j = 0; j < 40/2; j++) {
const u8 col_a = ((i&0x7)>=4) ? (*ptr>>4) : (*ptr&0xf);
ptr++;
const u8 col_b = ((i&0x7)>=4) ? (*ptr>>4) : (*ptr&0xf);
ptr++;
*map++ = col_a << 8 | col_a;
*map++ = col_a << 8 | col_a;
*map++ = col_a << 8 | col_a;
*map++ = col_b << 8 | col_a;
*map++ = col_b << 8 | col_b;
*map++ = col_b << 8 | col_b;
*map++ = col_b << 8 | col_b;
}
map += 232/2;
}
}
/*odd_color = (b1&0x80) ? 6 : 3;*/
/*even_color = (b1&0x80) ? 9 : 12;*/
void draw_hires_scr_1 (u16 *map)
{
int last_line;
int i, j, k;
u8 *ptr, tmp;
static u8 tmp_line[0x200];
static const u8 color_lut[] DTCM_DATA = { 0, 12, 0, 15, 0, 9, 0, 15, 0, 3, 0, 15, 0, 6, 0, 15 };
last_line = mixed_mode ? 168 : 192;
for (i = 0; i < last_line; i++, map += 0x100) {
if (!page_dirty[8 + (i&7)])
continue;
ptr = mainram + (sel_page << 13) + ((i&7) * 0x400) + video_addr[i>>3];
for (j = 0, tmp = 0; j < 280; j += 7) {
const u8 b1 = *ptr++;
tmp = (b1&0x7f) << 1 | tmp;
// embb
// e = even / odd
// m = msb
// b = data
for (k = 0; k <= 7; k++)
tmp_line[j+k] = color_lut[(((j+k)&1) ? 0x8 : 0x0) + ((b1&0x80)>>5) + ((tmp>>k)&3)];
tmp >>= 7;
}
dmaCopyAsynch(tmp_line, map, 0x200);
}
}
void draw_hires_scr (u16 *map) ITCM_CODE;
void draw_hires_scr (u16 *map)
{
int last_line;
int i, j, k, x;
u8 *xptr, *ptr, tmp;
u16 *omap;
static u8 tmp_line[0x200];
static const u8 color_lut[] DTCM_DATA = { 0, 12, 0, 15, 0, 9, 0, 15, 0, 3, 0, 15, 0, 6, 0, 15 };
last_line = mixed_mode ? 168/8 : 192/8;
for(x = 0; x < 8; x++, map += 0x100) {
if (!page_dirty[8+x])
continue;
xptr = mainram + (sel_page << 13) + (x << 10);
for(omap = map, i = 0; i < last_line; i++, omap += 0x800) {
ptr = xptr + video_addr[i];
for (j = 0, tmp = 0; j < 280; j += 7) {
const u8 b1 = *ptr++;
tmp = (b1&0x7f) << 1 | tmp;
const u8 *lut_b = color_lut + ((b1&0x80)>>5);
for (k = 0; k < 7; k+=2)
{
tmp_line[j+k+0] = lut_b[((j&1) << 3) + (tmp&3)];
tmp >>= 1;
if(k == 6)
break;
tmp_line[j+k+1] = lut_b[((~j&1) << 3) + (tmp&3)];
tmp >>= 1;
}
}
dmaCopyAsynch(tmp_line, omap, 0x200);
/*memcpy(omap, tmp_line, 0x200);*/
}
page_dirty[8+x] = 0;
}
}
void draw_text_scr (u16 *map) ITCM_CODE;
void draw_text_scr (u16 *map)
{
int start_line;
int i, j;
u16 *ptr;
if (!page_dirty[sel_page])
return;
if ((start_line = mixed_mode ? 21 : 0))
map += 32 * start_line;
for (i = start_line; i < 24; i++) {
ptr = (u16 *)(mainram + (sel_page << 10) + video_addr[i]);
for (j = 0; j < 5; j++) {
*map++ = (*ptr++)&0x3f3f;
*map++ = (*ptr++)&0x3f3f;
*map++ = (*ptr++)&0x3f3f;
*map++ = (*ptr++)&0x3f3f;
}
map += 12;
}
}
void video_draw () ITCM_CODE;
void video_draw ()
{
u16 *text_ptr = (u16 *)0x6002000;
u16 *gfx_ptr = (u16 *)0x6020000;
if (mixed_mode) {
draw_text_scr(text_ptr);
(hires) ? draw_hires_scr(gfx_ptr) : draw_lores_scr(gfx_ptr);
} else {
if (text_mode)
draw_text_scr(text_ptr);
else
if (hires)
draw_hires_scr(gfx_ptr);
else
draw_lores_scr(gfx_ptr);
}
// Clear this here otherwise we won't be able to render a lores
// screen after the text one
page_dirty[sel_page] = 0;
}
int video_set_scale (int mode)
{
int scale_x;
int scale_y;
switch (mode) {
default:
case 0:
scale_x = floatToFixed(1., 8);
scale_y = floatToFixed(1., 8);
break;
case 1:
// 280 / 256 = 1.09
scale_x = floatToFixed(1.09, 8);
scale_y = floatToFixed(1., 8);
break;
}
bgSetScale(gfx_bg, scale_x, scale_y);
bgSetScale(text_bg, scale_x, scale_y);
return 1;
}
void video_init ()
{
videoSetMode(MODE_4_2D);
vramSetBankA(VRAM_A_MAIN_BG_0x06000000);
vramSetBankB(VRAM_B_MAIN_BG_0x06020000);
// http://mtheall.com/vram.html#T2=3&NT2=128&MB2=4&TB2=0&S2=2&T3=5&NT3=32&MB3=8&TB3=1&S3=3
text_bg = bgInit(2, BgType_Rotation, BgSize_R_512x512, 4, 0);
gfx_bg = bgInit(3, BgType_Bmp8, BgSize_B8_512x256, 8, 0);
REG_DISPCNT &= ~DISPLAY_WIN0_ON;
// Setup the window used for mixed mode
WIN0_X0 = 0;
WIN0_X1 = 255;
WIN0_Y0 = 0;
WIN0_Y1 = 160;
// BG3 inside
WIN_IN = 0x8;
// BG2 outside
WIN_OUT = 0x4;
memcpy(BG_PALETTE, palette, sizeof(palette));
struct UnpackStruct unpack_bit;
unpack_bit.dataOffset = 14;
unpack_bit.destWidth = 8;
unpack_bit.sourceSize = 64*8;
unpack_bit.sourceWidth = 1;
swiUnpackBits((u8 *)&apple_font, (u32 *)bgGetGfxPtr(text_bg), &unpack_bit);
}

11
source/video.h Normal file
View File

@ -0,0 +1,11 @@
#ifndef VIDEO_H
#define VIDEO_H
int video_set_scale (int mode);
void video_draw ();
void video_init ();
void video_save (FILE *f);
void video_load (FILE *f);
#endif