Sync with upstream BlastEm (hg tip 732f5689d438)

This repo has been sitting on the July 2022 state of upstream BlastEm. The
sync brings in four years of upstream work, most importantly for this repo
the DSL generated CPU cores: upstream's Makefile now switches to NEW_CORE
automatically on any architecture that has no native code generator, and
genesis.h picks up the generated m68k context in that configuration, which
is what makes non-x86 builds possible at all.

Only .gitlab-ci.yml and Makefile.libretro are kept from this repo, every
other file is upstream's.
This commit is contained in:
WizzardSK committed 2026-07-27 19:05:30 +02:00
1 parent 277e4a6266
commit e1a4cb6c5b
298 files changed
+76400 -15370

No files matched your search

+10 -5
View File
@@ -21,12 +21,17 @@ generated_tests/*
ztests
glew/*
lib/*
android/obj
android/bin
android/gen
android/libs
obj/*
z80.c
z80.h
m68k.c
m68k.h
sh2.c
sh2.h
android/.gradle
android/app/build
android/app/jni/SDL
android/local.properties
android/jni/SDL
sdl
*.o
*.list
+1508
View File
File diff suppressed because it is too large. Load diff
+139
View File
@@ -0,0 +1,139 @@
#ifndef S32X_H_
#define S32X_H_
#include <stdint.h>
#include "sh2.h"
#include "32x_video.h"
#include "oscilloscope.h"
#include "render_audio.h"
enum {
S32X_ADAPT_CTRL,
S32X_INT_CTRL,
S32X_CART_BANK,
S32X_DREQ_CTRL,
S32X_DREQ_SRC_HI,
S32X_DREQ_SRC_LO,
S32X_DREQ_DST_HI,
S32X_DREQ_DST_LO,
S32X_DREQ_LEN,
S32X_DREQ_FIFO,
S32X_VRES_INT_CLR,
S32X_VINT_CLR,
S32X_HINT_CLR,
S32X_SEGA_TV,
S32X_PWM_INT_CLR,
S32X_REG_1E,
S32X_COMM_0,
S32X_COMM_1,
S32X_COMM_2,
S32X_COMM_3,
S32X_COMM_4,
S32X_COMM_5,
S32X_COMM_6,
S32X_COMM_7,
S32X_PWM_CTRL,
S32X_PWM_CYCLE,
S32X_PWM_WIDTH_L,
S32X_PWM_WIDTH_R,
S32X_PWM_WIDTH_M,
S32X_NUM_REGS,
S32X_CMD_INT_CLR = S32X_SEGA_TV
};
enum {
S32X_SH2_INT_CTRL,
S32X_SH2_STANDBY,
S32X_SH2_HINT_COUNT,
S32X_SH2_SUB_INT,
S32X_NUM_SH2_REGS = S32X_SH2_SUB_INT
};
#define BIT_ADEN_M68K 0x0001
#define BIT_ADCT_FM 0x8000
#define BIT_CART_SH2 0x0100
#define BIT_ADEN_SH2 0x0200
#define BIT_MAIN_INT 0x0001
#define BIT_SUB_INT 0x0002
#define BIT_INTMASK_HEN 0x0080
#define BIT_VERT_INT_EN 0x0008
#define BIT_HORZ_INT_EN 0x0004
#define BIT_CMD_INT_EN 0x0002
#define BIT_PWM_INT_EN 0x0001
#define BIT_SH2_RESET 0x0002
#define BIT_DREQ_FULL 0x0080
#define BIT_DREQ_68S 0x0004
#define BIT_DREQ_RV 0x0001
#define BIT_PWM_RTP 0x0080
#define BIT_PWM_FULL 0x8000
#define BIT_PWM_EMPTY 0x4000
#define S32X_BANK_MASK 0x0003
#define S32X_INTEN_MASK 0x000F
#define S32X_PWM_LRMD 0x000F
typedef struct {
uint16_t fifo[3];
uint8_t write;
uint8_t read;
} pwm_fifo;
void pwm_fifo_write(pwm_fifo *fifo, uint16_t *status, uint16_t value);
void pwm_fifo_read(pwm_fifo *fifo, uint16_t *status, uint16_t cycle, int16_t *out);
typedef struct {
void *gen;
sh2_context *main;
sh2_context *sub;
sh2_context *main_tmp;
sh2_context *sub_tmp;
uint16_t *sdram;
uint16_t *rom;
uint16_t *vector_rom;
audio_source *pwm;
oscilloscope *scope;
s32x_video video;
uint32_t pwm_cycle;
uint32_t cur_sh2_target;
uint16_t regs[S32X_NUM_REGS];
uint16_t sh2_regs[S32X_SH2_SUB_INT+1];
uint16_t dreq_fifo[8];
pwm_fifo fifo_left;
pwm_fifo fifo_right;
int16_t pwm_left;
int16_t pwm_right;
int16_t pwm_left_accum;
int16_t pwm_right_accum;
uint16_t pwm_counter;
uint8_t pwm_decimate;
uint8_t pwm_timer;
uint8_t pwm_main_int_pending;
uint8_t pwm_sub_int_pending;
uint8_t scope_left;
uint8_t scope_right;
uint8_t dreq_fifo_write;
uint8_t dreq_fifo_read;
uint8_t main_enter_debugger;
uint8_t sub_enter_debugger;
uint8_t saved_sh2_state;
} s32x;
s32x *alloc_32x(system_media *media, uint8_t pal, uint8_t cd_boot);
void free_32x(s32x *mars);
void s32x_run(s32x *mars, uint32_t target);
void s32x_adjust_cycles(s32x *mars, uint32_t deduction);
void s32x_enable_scope(s32x *mars, oscilloscope *scope, uint32_t main_clock);
void s32x_set_speed(s32x *mars, uint32_t main_clock);
uint16_t s32x_68k_read(uint32_t address, void *vcontext);
void *s32x_68k_write(uint32_t address, void *vcontext, uint16_t value);
uint8_t s32x_68k_read_b(uint32_t address, void *vcontext);
void *s32x_68k_write_b(uint32_t address, void *vcontext, uint8_t value);
void *s32x_write_hint(uint32_t address, void *vcontext, uint16_t value);
void *s32x_write_hint_b(uint32_t address, void *vcontext, uint8_t value);
void *s32x_fb_write_w(uint32_t address, void *vcontext, uint16_t value);
void *s32x_fb_write_b(uint32_t address, void *vcontext, uint8_t value);
uint16_t s32x_fb_read_w(uint32_t address, void *vcontext);
uint8_t s32x_fb_read_b(uint32_t address, void *vcontext);
void *s32x_overwrite_write_w(uint32_t address, void *vcontext, uint16_t value);
void *s32x_overwrite_write_b(uint32_t address, void *vcontext, uint8_t value);
#endif //S32X_H_
+849
View File
@@ -0,0 +1,849 @@
#include <stdlib.h>
#include <stdio.h>
#include "32x_video.h"
#include "vdp.h"
#include "render.h"
#ifdef DO_DEBUG_PRINT
#define dprintf printf
#else
#define dprintf
#endif
void s32x_video_init(s32x_video *vid, uint8_t pal)
{
vid->front = calloc(256*1024, sizeof(uint8_t));
vid->back = vid->front + 128*1024;
memset(vid->regs, 0, sizeof(vid->regs));
if (!pal) {
vid->regs[S32X_VID_MODE] |= S32X_VID_BIT_PAL;
}
}
void s32x_video_free(s32x_video *vid)
{
if (vid->front > vid->back) {
free(vid->back);
} else {
free(vid->front);
}
}
#define MCLKS_PIXEL 8
#define HSYNC_START 368
#define HSYNC_END (HSYNC_START+17*2)
#define HBLANK_START 356
#define LINE_END 420
static uint32_t mclks_pixel[] = {
10, 9, 10, 10, 10, 10, 10, 10,
9, 9, 10, 10, 10, 10, 10, 10,
9, 9, 10, 10, 10, 10, 10, 10,
9, 9, 10, 10, 10, 10, 10, 10,
10, 9
};
//manual says 3, but they must mean SH2 clocks and not MCLKS
//3 SH2 clocks = 1 68K clock = 7 MCLKs
#define MCLKS_FILL_WORD 7
#define MCLKS_FILL_LAST (MCLKS_FILL_WORD+16)
void s32x_video_run(s32x_video *vid, uint32_t target)
{
if (vid->cycle < target) {
uint32_t delta = target - vid->cycle;
uint32_t lines = delta / MCLKS_LINE;
uint32_t rest = delta % MCLKS_LINE;
uint16_t vblank_start = 224, frame_end;
if (!(vid->regs[S32X_VID_MODE] & S32X_VID_BIT_PAL)) {
frame_end = 313;
if (vid->regs[S32X_VID_MODE] & S32X_VID_BIT_V240) {
vblank_start = 240;
}
} else {
frame_end = 262;
}
for (uint32_t hint_lines = lines; hint_lines > 0;)
{
if (hint_lines > vid->hint_counter) {
if (vid->hen || (vid->vcounter + vid->hint_counter + 1 < vblank_start) || (!vid->hint_counter == vid->vcounter == (frame_end - 1))) {
vid->main_hint_pending = vid->sub_hint_pending = 1;
}
hint_lines -= vid->hint_counter + 1;
vid->hint_counter = vid->hint_count;
} else {
if (vid->vcounter < frame_end - 1 && hint_lines + vid->vcounter >= frame_end - 1) {
vid->hint_counter = vid->hint_count;
hint_lines -= frame_end - 1 - vid->vcounter;
} else {
vid->hint_counter -= hint_lines;
hint_lines = 0;
}
}
}
uint16_t line_start = vid->vcounter;
while (rest >= MCLKS_PIXEL) {
if (vid->hcounter < HSYNC_START) {
uint16_t new = vid->hcounter + rest / MCLKS_PIXEL;
if (vid->hcounter < HBLANK_START && new >= HBLANK_START && (vid->hen || vid->vcounter < (vblank_start - 1) || vid->vcounter == (frame_end - 1))) {
if (vid->hint_counter) {
vid->hint_counter--;
} else {
vid->main_hint_pending = vid->sub_hint_pending = 1;
vid->hint_counter = vid->hint_count;
}
}
if (new > HSYNC_START) {
rest -= (HSYNC_START - vid->hcounter) * MCLKS_PIXEL;
vid->hcounter = HSYNC_START;
} else {
rest -= (new - vid->hcounter) * MCLKS_PIXEL;
vid->hcounter = new;
}
} else if (vid->hcounter >= HSYNC_END) {
uint16_t new = vid->hcounter + rest / MCLKS_PIXEL;
if (new >= LINE_END) {
rest -= (LINE_END - vid->hcounter) * MCLKS_PIXEL;
vid->hcounter = 0;
lines++;
} else {
rest -= (new - vid->hcounter) * MCLKS_PIXEL;
vid->hcounter = new;
}
} else {
uint16_t old = vid->hcounter;
while (vid->hcounter < HSYNC_END && rest >= mclks_pixel[vid->hcounter-HSYNC_START])
{
rest -= mclks_pixel[vid->hcounter-HSYNC_START];
vid->hcounter++;
}
if (old == vid->hcounter) {
//no progress made, rest > MCLKS_PIXEL, but not one of the adjusted pixels
break;
}
}
}
if (lines) {
vid->vcounter += lines;
if (vid->vcounter >= frame_end) {
vid->vcounter -= frame_end;
}
if (vid->vcounter >= vblank_start) {
if (!(vid->regs[S32X_VID_FB_CTRL] & S32X_VID_BIT_VBLK)) {
vid->regs[S32X_VID_FB_CTRL] |= S32X_VID_BIT_VBLK;
vid->main_vint_pending = 1;
vid->sub_vint_pending = 1;
if (vid->flip_pending) {
vid->flip_pending = 0;
uint8_t *tmp = vid->front;
vid->front = vid->back;
vid->back = tmp;
vid->regs[S32X_VID_FB_CTRL] ^= S32X_VID_BIT_FS;
}
}
} else {
vid->regs[S32X_VID_FB_CTRL] &= ~S32X_VID_BIT_VBLK;
if (line_start < vblank_start && lines >= frame_end - line_start) {
//passed through vblank and wrapped around
vid->main_vint_pending = 1;
vid->sub_vint_pending = 1;
if (vid->flip_pending) {
vid->flip_pending = 0;
uint8_t *tmp = vid->front;
vid->front = vid->back;
vid->back = tmp;
vid->regs[S32X_VID_FB_CTRL] ^= S32X_VID_BIT_FS;
}
}
}
}
if (vid->hcounter >= HBLANK_START) {
vid->regs[S32X_VID_FB_CTRL] |= S32X_VID_BIT_HBLK;
} else {
vid->regs[S32X_VID_FB_CTRL] &= ~S32X_VID_BIT_HBLK;
}
//run fill
delta = target - rest - vid->cycle + vid->fill_remainder;
if (vid->regs[S32X_VID_FB_CTRL] & S32X_VID_BIT_FEN) {
uint8_t count = vid->fill_count;
uint32_t address = vid->regs[S32X_VID_FILL_START] << 1;
uint8_t data_hi = vid->regs[S32X_VID_FILL_DATA] >> 8;
uint8_t data_lo = vid->regs[S32X_VID_FILL_DATA];
uint32_t min_delta = count ? MCLKS_FILL_WORD : MCLKS_FILL_LAST;
while (delta >= min_delta)
{
vid->back[address] = data_hi;
vid->back[address|1] = data_lo;
address = (address & 0x1FE00) | ((address + 2) & 0x1FE);
if (count) {
--count;
delta -= MCLKS_FILL_WORD;
if (!count) {
min_delta = MCLKS_FILL_LAST;
}
} else {
vid->regs[S32X_VID_FB_CTRL] &= ~S32X_VID_BIT_FEN;
delta -= MCLKS_FILL_LAST;
break;
}
}
vid->fill_count = count;
vid->regs[S32X_VID_FILL_START] = address >> 1;
vid->fill_remainder = delta;
}
if (
(vid->regs[S32X_VID_MODE] & S32X_VID_MODE_MASK) == 0
|| (vid->regs[S32X_VID_FB_CTRL] & (S32X_VID_BIT_VBLK|S32X_VID_BIT_HBLK))
) {
// palette access allowed during H or V blanking or when display is forcibly blanked (mode = 0)
vid->regs[S32X_VID_FB_CTRL] |= S32X_VID_BIT_PEN;
} else {
vid->regs[S32X_VID_FB_CTRL] &= ~S32X_VID_BIT_PEN;
}
vid->cycle = target - rest;
}
}
static pixel_t map_32x_color(uint16_t color)
{
return render_map_color(color << 3 & 0xF8, color >> 2 & 0xF8, color >> 7 & 0xF8);
}
static void s32x_composite_indexed_h32(s32x_video *vid, pixel_t *output, uint8_t *compositebuf, uint32_t line_start)
{
uint8_t *cur = vid->front + line_start + 319;
uint16_t pri = (vid->regs[S32X_VID_MODE] & S32X_VID_BIT_PRI) << 8;
for (pixel_t *dst = output + 319; dst >= output; dst--)
{
uint16_t color = vid->palette[*(cur--)];
uint16_t on_top = (color & 0x8000) ^ pri;
if (on_top) {
*dst = map_32x_color(color);
} else {
uint32_t h32_pos = (dst - output) * 0x40000 / 5;
uint32_t base_index = h32_pos >> 16;
h32_pos &= 0xFFFF;
if (h32_pos > 0xE000) {
base_index++;
} else if (h32_pos >= 0x1000) {
//mix
uint8_t first_opaque = compositebuf[base_index] & 0xF;
uint8_t second_opaque = compositebuf[base_index+1] & 0xF;
if (first_opaque && second_opaque) {
*dst = mix_colors(output[base_index], output[base_index + 1], h32_pos);
} else if (first_opaque) {
*dst = mix_colors(output[base_index], map_32x_color(color), h32_pos);
} else if (second_opaque) {
*dst = mix_colors(map_32x_color(color), output[base_index + 1], h32_pos);
} else {
*dst = map_32x_color(color);
}
continue;
}
if (compositebuf[base_index] & 0xF) {
*dst = output[base_index];
} else {
*dst = map_32x_color(color);
}
}
}
}
static void s32x_composite_indexed(s32x_video *vid, pixel_t *output, uint8_t *compositebuf, uint32_t line_start, uint8_t is_h40)
{
if (vid->regs[S32X_VID_SHIFT] & 1) {
line_start += 1;
}
if (!is_h40) {
s32x_composite_indexed_h32(vid, output, compositebuf, line_start);
return;
}
uint8_t *cur = vid->front + line_start;
uint16_t pri = (vid->regs[S32X_VID_MODE] & S32X_VID_BIT_PRI) << 8;
for (pixel_t *end = output + 320; output < end; output++)
{
uint16_t color = vid->palette[*(cur++)];
uint16_t on_top = (color & 0x8000) ^ pri;
if (on_top || !(*compositebuf & 0xF)) {
*output = map_32x_color(color);
}
compositebuf++;
}
}
static void s32x_composite_direct_h32(s32x_video *vid, pixel_t *output, uint8_t *compositebuf, uint32_t line_start)
{
if (vid->regs[S32X_VID_SHIFT] & 1) {
line_start += 1;
}
uint8_t *cur = vid->front + line_start + 319 * 2;
uint16_t pri = (vid->regs[S32X_VID_MODE] & S32X_VID_BIT_PRI) << 8;
for (pixel_t *dst = output + 319; dst >= output; dst--)
{
uint16_t color = *(cur--);
color |= *(cur--) << 8;
uint16_t on_top = (color & 0x8000) ^ pri;
if (on_top) {
*dst = map_32x_color(color);
} else {
uint32_t h32_pos = (dst - output) * 0x40000 / 5;
uint32_t base_index = h32_pos >> 16;
h32_pos &= 0xFFFF;
if (h32_pos > 0xE000) {
base_index++;
} else if (h32_pos >= 0x1000) {
uint8_t first_opaque = compositebuf[base_index] & 0xF;
uint8_t second_opaque = compositebuf[base_index+1] & 0xF;
if (first_opaque && second_opaque) {
*dst = mix_colors(output[base_index], output[base_index + 1], h32_pos);
} else if (first_opaque) {
*dst = mix_colors(output[base_index], map_32x_color(color), h32_pos);
} else if (second_opaque) {
*dst = mix_colors(map_32x_color(color), output[base_index + 1], h32_pos);
} else {
*dst = map_32x_color(color);
}
continue;
}
if (compositebuf[base_index] & 0xF) {
*dst = output[base_index];
} else {
*dst = map_32x_color(color);
}
}
}
}
static void s32x_composite_direct(s32x_video *vid, pixel_t *output, uint8_t *compositebuf, uint32_t line_start, uint8_t is_h40)
{
if (!is_h40) {
s32x_composite_direct_h32(vid, output, compositebuf, line_start);
return;
}
//does shift do anything in this mode?
uint8_t *cur = vid->front + line_start;
uint16_t pri = (vid->regs[S32X_VID_MODE] & S32X_VID_BIT_PRI) << 8;
for (pixel_t *end = output + 320; output < end; output++)
{
uint16_t color = *cur << 8 | cur[1];
cur += 2;
uint16_t on_top = (color & 0x8000) ^ pri;
if (on_top || !(*compositebuf & 0xF)) {
*output = map_32x_color(color);
}
compositebuf++;
}
}
static void s32x_composite_rle_h32(s32x_video *vid, pixel_t *output, uint8_t *compositebuf, uint32_t line_start)
{
pixel_t md_layer[257];
memcpy(md_layer, output, sizeof(md_layer));
uint8_t *cur = vid->front + line_start;
uint16_t pri = (vid->regs[S32X_VID_MODE] & S32X_VID_BIT_PRI) << 8;
uint32_t count = 0;
uint16_t color;
pixel_t *start = output;
for (pixel_t *end = output + 320; output < end; output++)
{
if (!count) {
count = *(cur++) + 1;
color = vid->palette[*(cur++)];
}
count--;
uint16_t on_top = (color & 0x8000) ^ pri;
if (on_top) {
*output = map_32x_color(color);
} else {
uint32_t h32_pos = (output - start) * 0x40000 / 5;
uint32_t base_index = h32_pos >> 16;
h32_pos &= 0xFFFF;
if (h32_pos > 0xE000) {
base_index++;
} else if (h32_pos >= 0x1000) {
//mix
uint8_t first_opaque = compositebuf[base_index] & 0xF;
uint8_t second_opaque = compositebuf[base_index+1] & 0xF;
if (first_opaque && second_opaque) {
*output = mix_colors(md_layer[base_index], md_layer[base_index + 1], h32_pos);
} else if (first_opaque) {
*output = mix_colors(md_layer[base_index], map_32x_color(color), h32_pos);
} else if (second_opaque) {
*output = mix_colors(map_32x_color(color), md_layer[base_index + 1], h32_pos);
} else {
*output = map_32x_color(color);
}
continue;
}
if (compositebuf[base_index] & 0xF) {
*output = output[base_index];
} else {
*output = map_32x_color(color);
}
}
}
}
static void s32x_composite_rle(s32x_video *vid, pixel_t *output, uint8_t *compositebuf, uint32_t line_start, uint8_t is_h40)
{
if (!is_h40) {
s32x_composite_rle_h32(vid, output, compositebuf, line_start);
return;
}
//does shift do anything in this mode?
uint8_t *cur = vid->front + line_start;
uint16_t pri = (vid->regs[S32X_VID_MODE] & S32X_VID_BIT_PRI) << 8;
uint32_t count = 0;
uint16_t color;
for (pixel_t *end = output + 320; output < end; output++)
{
if (!count) {
count = *(cur++) + 1;
color = vid->palette[*(cur++)];
}
uint16_t on_top = (color & 0x8000) ^ pri;
if (on_top || !(*compositebuf & 0xF)) {
*output = map_32x_color(color);
}
compositebuf++;
count--;
}
}
uint8_t s32x_video_composite(s32x_video *vid, pixel_t *output, uint8_t *compositebuf, uint32_t line, uint8_t is_h40)
{
uint32_t line_start = vid->front[line * 2] << 9 | vid->front[line * 2 + 1] << 1;
switch (vid->regs[S32X_VID_MODE] & S32X_VID_MODE_MASK)
{
case S32X_VID_MODE_BLANK:
return 0;
case S32X_VID_MODE_INDEXED:
s32x_composite_indexed(vid, output, compositebuf, line_start, is_h40);
break;
case S32X_VID_MODE_DIRECT:
s32x_composite_direct(vid, output, compositebuf, line_start, is_h40);
break;
case S32X_VID_MODE_RLE:
s32x_composite_rle(vid, output, compositebuf, line_start, is_h40);
break;
}
return 1;
}
uint16_t s32x_video_68k_read(uint32_t address, s32x_video *video)
{
//TODO: check FM
if (address < 0xA15180 + S32X_NUM_VID_REGS * 2) {
uint32_t reg = (address & 0xF) >> 1;
if (reg == S32X_VID_FB_CTRL && video->hcounter >= HBLANK_START && video->hcounter < HBLANK_START + 6) {
//TODO: determine all times VRAM refresh happens
//refresh
return video->regs[reg] | S32X_VID_BIT_FEN;
}
return video->regs[reg];
} else if (address >= 0xA15200 && address < 0xA15400) {
return video->palette[(address & 0x1FF) >> 1];
}
return 0xFFFF;
}
uint16_t s32x_video_sh2_read(uint32_t address, s32x_video *video)
{
//TODO: check FM
if (address < 0x0004100 + S32X_NUM_VID_REGS * 2) {
uint32_t reg = (address & 0xF) >> 1;
if (reg == S32X_VID_FB_CTRL && video->hcounter >= HBLANK_START && video->hcounter < HBLANK_START + 6) {
//TODO: determine all times VRAM refresh happens
//refresh
return video->regs[reg] | S32X_VID_BIT_FEN;
}
return video->regs[reg];
} else if (address >= 0x0004200 && address < 0x0004400) {
return video->palette[(address & 0x1FF) >> 1];
}
return 0xFFFF;
}
uint32_t s32x_cycles_to_vblank(s32x_video *video)
{
uint32_t cycles;
uint16_t vblank_start = 224, frame_end;
if (!(video->regs[S32X_VID_MODE] & S32X_VID_BIT_PAL)) {
frame_end = 313;
if (video->regs[S32X_VID_MODE] & S32X_VID_BIT_V240) {
vblank_start = 240;
}
} else {
frame_end = 262;
}
if (video->vcounter < vblank_start) {
cycles = (vblank_start - 1 - video->vcounter) * MCLKS_LINE;
} else {
cycles = (vblank_start - 1 + frame_end - video->vcounter) * MCLKS_LINE;
}
if (video->hcounter <= HSYNC_START) {
cycles += 3420 - video->hcounter * MCLKS_PIXEL;
} else if (video->hcounter >= HSYNC_END) {
cycles += (LINE_END - video->hcounter) * MCLKS_PIXEL;
} else {
cycles += (LINE_END - HSYNC_END) * MCLKS_PIXEL;
for (uint16_t i = video->hcounter; i < HSYNC_END; i++)
{
cycles += mclks_pixel[i - HSYNC_START];
}
}
return cycles;
}
static uint32_t cycles_to_pen(s32x_video *video)
{
return (HBLANK_START - video->hcounter) * MCLKS_PIXEL;
}
uint32_t s32x_cycles_to_hint(s32x_video *video)
{
uint16_t vblank_start = 224, frame_end;
if (!(video->regs[S32X_VID_MODE] & S32X_VID_BIT_PAL)) {
frame_end = 313;
if (video->regs[S32X_VID_MODE] & S32X_VID_BIT_V240) {
vblank_start = 240;
}
} else {
frame_end = 262;
}
uint16_t next_hint_line = video->vcounter + video->hint_counter;
if (video->hcounter > HBLANK_START) {
next_hint_line++;
}
uint32_t cycles;
if ((next_hint_line < vblank_start - 1) || video->hen) {
cycles = video->hint_counter * MCLKS_LINE;
} else {
cycles = (video->hint_count + (frame_end - 2 - video->vcounter)) * MCLKS_LINE;
if (video->hcounter < HBLANK_START) {
cycles += MCLKS_LINE;
}
}
if (video->hcounter <= HBLANK_START) {
//just add cycles left to HBLANK_START
return cycles + (HBLANK_START - video->hcounter) * MCLKS_PIXEL;
} else if (video->hcounter <= HSYNC_START) {
//add cycles until end of line + cycles to HBLANK_START on the next line
return cycles + HBLANK_START * MCLKS_PIXEL + MCLKS_LINE - video->hcounter * MCLKS_PIXEL;
} else if (video->hcounter >= HSYNC_END) {
//same as above, just calculated differently
return cycles + (HBLANK_START + LINE_END - video->hcounter) * MCLKS_PIXEL;
} else {
//same as above, but annoying
for (uint16_t hcnt = video->hcounter; hcnt < HSYNC_END; hcnt++)
{
cycles += mclks_pixel[hcnt - HSYNC_START];
}
return cycles + (HBLANK_START + LINE_END - HSYNC_END) * MCLKS_PIXEL;
}
}
static uint16_t video_write_mask[] = {
0x00C3,
0x0001,
0x00FF,
0xFFFF,
0xFFFF,
0x0001,
};
uint32_t s32x_video_68k_write(uint32_t address, s32x_video *video, uint16_t value)
{
if (address < 0xA15180 + S32X_NUM_VID_REGS * 2) {
uint32_t reg = (address & 0xF) >> 1;
uint16_t mask = video_write_mask[reg];
uint16_t old = video->regs[reg];
uint16_t new = (old & ~mask) | (value & mask);
uint16_t changed = old ^ new;
if (reg == S32X_VID_FB_CTRL && (changed & S32X_VID_BIT_FS)) {
if (old & S32X_VID_BIT_VBLK || !(video->regs[S32X_VID_MODE] & S32X_VID_MODE_MASK)) {
uint8_t *tmp = video->front;
video->front = video->back;
video->back = tmp;
video->flip_pending = 0;
} else {
video->flip_pending = 1;
new &= ~S32X_VID_BIT_FS;
new |= old & S32X_VID_BIT_FS;
}
}
dprintf("32X VDP Write: %06X: %04X\n", address, value);
video->regs[reg] = new;
if (reg == S32X_VID_FILL_DATA) {
video->fill_count = video->regs[S32X_VID_FILL_LEN];
video->regs[S32X_VID_FB_CTRL] |= S32X_VID_BIT_FEN;
video->fill_remainder = 0;
}
} else if (address >= 0xA15200 && address < 0xA15400) {
if (!(video->regs[S32X_VID_FB_CTRL] & S32X_VID_BIT_PEN)) {
return cycles_to_pen(video);
}
dprintf("32X Palette Write: %06X: %04X\n", address, value);
video->palette[(address & 0x1FF) >> 1] = value;
}
return 0;
}
uint32_t s32x_video_68k_write_b(uint32_t address, s32x_video *video, uint16_t value)
{
if (address < 0xA15180 + S32X_NUM_VID_REGS * 2) {
uint32_t reg = (address & 0xF) >> 1;
uint16_t mask = video_write_mask[reg];
uint16_t extended;
if (address & 1) {
extended = value;
mask &= 0x00FF;;
} else {
extended = value << 8;
mask &= 0xFF00;
}
uint16_t old = video->regs[reg];
uint16_t new = (old & ~mask) | (extended & mask);
uint16_t changed = old ^ new;
if (reg == S32X_VID_FB_CTRL && (changed & S32X_VID_BIT_FS)) {
if (old & S32X_VID_BIT_VBLK || !(video->regs[S32X_VID_MODE] & S32X_VID_MODE_MASK)) {
uint8_t *tmp = video->front;
video->front = video->back;
video->back = tmp;
video->flip_pending = 0;
} else {
video->flip_pending = 1;
new &= ~S32X_VID_BIT_FS;
new |= old & S32X_VID_BIT_FS;
}
}
dprintf("32X VDP Write (byte): %06X: %04X\n", address, value);
video->regs[reg] = new;
if (reg == S32X_VID_FILL_DATA) {
video->fill_count = video->regs[S32X_VID_FILL_LEN];
video->regs[S32X_VID_FB_CTRL] |= S32X_VID_BIT_FEN;
}
} else if (address >= 0xA15200 && address < 0xA15400) {
if (!(video->regs[S32X_VID_FB_CTRL] & S32X_VID_BIT_PEN)) {
return cycles_to_pen(video);
}
dprintf("32X Palette Write (byte): %06X: %04X\n", address, value);
//manual says this isn't allowed, what actually happens here?
}
return 0;
}
uint32_t s32x_video_sh2_write(uint32_t address, s32x_video *video, uint16_t value)
{
if (address < 0x0004100 + S32X_NUM_VID_REGS * 2) {
uint32_t reg = (address & 0xF) >> 1;
uint16_t mask = video_write_mask[reg];
uint16_t old = video->regs[reg];
uint16_t new = (old & ~mask) | (value & mask);
uint16_t changed = old ^ new;
if (reg == S32X_VID_FB_CTRL && (changed & S32X_VID_BIT_FS)) {
if (old & S32X_VID_BIT_VBLK || !(video->regs[S32X_VID_MODE] & S32X_VID_MODE_MASK)) {
uint8_t *tmp = video->front;
video->front = video->back;
video->back = tmp;
video->flip_pending = 0;
} else {
video->flip_pending = 1;
new &= ~S32X_VID_BIT_FS;
new |= old & S32X_VID_BIT_FS;
}
}
dprintf("32X VDP Write: %06X: %04X\n", address, value);
video->regs[reg] = new;
if (reg == S32X_VID_FILL_DATA) {
video->fill_count = video->regs[S32X_VID_FILL_LEN];
video->regs[S32X_VID_FB_CTRL] |= S32X_VID_BIT_FEN;
}
} else if (address >= 0x0004200 && address < 0x0004400) {
if (!(video->regs[S32X_VID_FB_CTRL] & S32X_VID_BIT_PEN)) {
return cycles_to_pen(video) * 3;
}
dprintf("32X Palette Write: %06X: %04X\n", address, value);
video->palette[(address & 0x1FF) >> 1] = value;
}
return 0;
}
uint32_t s32x_video_sh2_write_b(uint32_t address, s32x_video *video, uint8_t value)
{
if (address < 0x0004100 + S32X_NUM_VID_REGS * 2) {
uint32_t reg = (address & 0xF) >> 1;
uint16_t mask = video_write_mask[reg];
uint16_t extended;
if (address & 1) {
extended = value;
mask &= 0x00FF;;
} else {
extended = value << 8;
mask &= 0xFF00;
}
uint16_t old = video->regs[reg];
uint16_t new = (old & ~mask) | (extended & mask);
uint16_t changed = old ^ new;
if (reg == S32X_VID_FB_CTRL && (changed & S32X_VID_BIT_FS)) {
if (old & S32X_VID_BIT_VBLK || !(video->regs[S32X_VID_MODE] & S32X_VID_MODE_MASK)) {
uint8_t *tmp = video->front;
video->front = video->back;
video->back = tmp;
video->flip_pending = 0;
} else {
video->flip_pending = 1;
new &= ~S32X_VID_BIT_FS;
new |= old & S32X_VID_BIT_FS;
}
}
dprintf("32X VDP Write (byte): %06X: %04X\n", address, value);
video->regs[reg] = new;
if (reg == S32X_VID_FILL_DATA) {
video->fill_count = video->regs[S32X_VID_FILL_LEN];
video->regs[S32X_VID_FB_CTRL] |= S32X_VID_BIT_FEN;
}
} else if (address >= 0x0004200 && address < 0x0004400) {
if (!(video->regs[S32X_VID_FB_CTRL] & S32X_VID_BIT_PEN)) {
return cycles_to_pen(video) * 3;
}
dprintf("32X Palette Write (byte): %06X: %04X\n", address, value);
//manual says this isn't allowed, what actually happens here?
}
return 0;
}
void s32x_video_fb_write_w(uint32_t address, s32x_video *video, uint16_t value)
{
address &= 0x1FFFE;
video->back[address] = value >> 8;
video->back[address | 1] = value;
}
void s32x_video_fb_write_b(uint32_t address, s32x_video *video, uint8_t value)
{
address &= 0x1FFFF;
video->back[address] = value;
}
uint16_t s32x_video_fb_read_w(uint32_t address, s32x_video *video)
{
address &= 0x1FFFE;
return video->back[address] << 8 | video->back[address | 1];
}
uint16_t s32x_video_fb_read_b(uint32_t address, s32x_video *video)
{
address &= 0x1FFFF;
return video->back[address];
}
void s32x_video_overwrite_write_w(uint32_t address, s32x_video *video, uint16_t value)
{
address &= 0x1FFFE;
uint8_t first = value >> 8;
uint8_t second = value;
if (first) {
video->back[address] = first;
}
if (second) {
video->back[address | 1] = second;
}
}
void s32x_video_overwrite_write_b(uint32_t address, s32x_video *video, uint8_t value)
{
address &= 0x1FFFF;
if (value) {
video->back[address] = value;
}
}
static void s32x_fb_debug_indexed(pixel_t *fb, uint32_t pitch, s32x_video *video, uint8_t *buffer, uint16_t vblank_start, pixel_t *colors, uint32_t *line_offsets)
{
//TODO: display offscreen portions with a scroll rect like for MD VDP planes
for (int y = 0; y < vblank_start; y++)
{
pixel_t *cur = fb;
fb += pitch / sizeof(pixel_t);
uint32_t cur_off = line_offsets[y];
if (video->regs[S32X_VID_SHIFT] & 1) {
cur_off += 1;
cur_off &= 0x1FFFF;
}
for (int x = 0; x < 320; x++)
{
*(cur++) = colors[buffer[cur_off++]];
cur_off &= 0x1FFFF;
}
}
}
static void s32x_fb_debug_rle(pixel_t *fb, uint32_t pitch, s32x_video *video, uint8_t *buffer, uint16_t vblank_start, pixel_t *colors, uint32_t *line_offsets)
{
//TODO: implement me
}
static void s32x_fb_debug_direct(pixel_t *fb, uint32_t pitch, s32x_video *video, uint8_t *buffer, uint16_t vblank_start, uint32_t *line_offsets)
{
//TODO: display offscreen portions with a scroll rect like for MD VDP planes
//TODO: display offscreen portions with a scroll rect like for MD VDP planes
for (int y = 0; y < vblank_start; y++)
{
pixel_t *cur = fb;
fb += pitch / sizeof(pixel_t);
uint32_t cur_off = line_offsets[y];
for (int x = 0; x < 320; x++)
{
uint16_t color = buffer[cur_off++] << 8;
cur_off &= 0x1FFFF;
color |= buffer[cur_off++];
*(cur++) = render_map_color(color << 3 & 0xF8, color >> 2 & 0xF8, color >> 7 & 0xF8);
cur_off &= 0x1FFFF;
}
}
}
static void s32x_fb_debug_one(pixel_t *fb, uint32_t pitch, s32x_video *video, uint8_t *buffer)
{
pixel_t colors[256];
pixel_t black = render_map_color(0, 0, 0);
uint32_t line_offsets[240];
int vblank_start;
if (!(video->regs[S32X_VID_MODE] & S32X_VID_BIT_PAL) && (video->regs[S32X_VID_MODE] & S32X_VID_BIT_V240)) {
vblank_start = 240;
} else {
vblank_start = 224;
}
for (int i = 0; i < vblank_start; i++)
{
line_offsets[i] = buffer[i << 1] << 9 | buffer[i << 1 | 1] << 1;
}
switch (video->regs[S32X_VID_MODE] & S32X_VID_MODE_MASK)
{
case S32X_VID_MODE_BLANK:
break;
case S32X_VID_MODE_INDEXED:
case S32X_VID_MODE_RLE:
for (int i = 0; i < 256; i++)
{
uint16_t color = video->palette[i];
colors[i] = render_map_color(color << 3 & 0xF8, color >> 2 & 0xF8, color >> 7 & 0xF8);
}
if ((video->regs[S32X_VID_MODE] & S32X_VID_MODE_MASK) == S32X_VID_MODE_INDEXED) {
s32x_fb_debug_indexed(fb, pitch, video, buffer, vblank_start, colors, line_offsets);
} else {
s32x_fb_debug_rle(fb, pitch, video, buffer, vblank_start, colors, line_offsets);
}
break;
case S32X_VID_MODE_DIRECT:
s32x_fb_debug_direct(fb, pitch, video, buffer, vblank_start, line_offsets);
break;
}
}
void s32x_fb_debug(pixel_t *fb, uint32_t pitch, s32x_video *video)
{
s32x_fb_debug_one(fb, pitch, video, video->front);
s32x_fb_debug_one(fb + pitch*512/sizeof(pixel_t), pitch, video, video->back);
}
+75
View File
@@ -0,0 +1,75 @@
#ifndef S32X_VIDEO_H_
#define S32X_VIDEO_H_
#include <stdint.h>
#include "pixel.h"
enum {
S32X_VID_MODE,
S32X_VID_SHIFT,
S32X_VID_FILL_LEN,
S32X_VID_FILL_START,
S32X_VID_FILL_DATA,
S32X_VID_FB_CTRL,
S32X_NUM_VID_REGS
};
#define S32X_VID_BIT_PAL 0x8000
#define S32X_VID_BIT_V240 0x0040
#define S32X_VID_BIT_PRI 0x0080
#define S32X_VID_MODE_MASK 0x0003
#define S32X_VID_BIT_VBLK 0x8000
#define S32X_VID_BIT_HBLK 0x4000
#define S32X_VID_BIT_PEN 0x2000
#define S32X_VID_BIT_FEN 0x0002
#define S32X_VID_BIT_FS 0x0001
enum {
S32X_VID_MODE_BLANK,
S32X_VID_MODE_INDEXED,
S32X_VID_MODE_DIRECT,
S32X_VID_MODE_RLE
};
typedef struct {
uint8_t *front;
uint8_t *back;
uint32_t cycle;
uint32_t fill_remainder;
uint16_t vcounter;
uint16_t hcounter;
uint16_t regs[S32X_NUM_VID_REGS];
uint16_t palette[256];
uint16_t fill_count;
uint16_t hint_count;
uint16_t hint_counter;
//TODO: FIFO
uint8_t main_vint_pending;
uint8_t sub_vint_pending;
uint8_t main_hint_pending;
uint8_t sub_hint_pending;
uint8_t hen;
uint8_t flip_pending;
} s32x_video;
void s32x_video_init(s32x_video *vid, uint8_t pal);
void s32x_video_free(s32x_video *vid);
void s32x_video_run(s32x_video *vid, uint32_t target);
uint8_t s32x_video_composite(s32x_video *vid, pixel_t *output, uint8_t *compositebuf, uint32_t line, uint8_t is_h40);
uint32_t s32x_cycles_to_vblank(s32x_video *video);
uint32_t s32x_cycles_to_hint(s32x_video *video);
uint16_t s32x_video_68k_read(uint32_t address, s32x_video *video);
uint16_t s32x_video_sh2_read(uint32_t address, s32x_video *video);
uint32_t s32x_video_68k_write(uint32_t address, s32x_video *video, uint16_t value);
uint32_t s32x_video_68k_write_b(uint32_t address, s32x_video *video, uint16_t value);
uint32_t s32x_video_sh2_write(uint32_t address, s32x_video *video, uint16_t value);
uint32_t s32x_video_sh2_write_b(uint32_t address, s32x_video *video, uint8_t value);
void s32x_video_fb_write_w(uint32_t address, s32x_video *video, uint16_t value);
void s32x_video_fb_write_b(uint32_t address, s32x_video *video, uint8_t value);
uint16_t s32x_video_fb_read_w(uint32_t address, s32x_video *video);
uint16_t s32x_video_fb_read_b(uint32_t address, s32x_video *video);
void s32x_video_overwrite_write_w(uint32_t address, s32x_video *video, uint16_t value);
void s32x_video_overwrite_write_b(uint32_t address, s32x_video *video, uint8_t value);
void s32x_fb_debug(pixel_t *fb, uint32_t pitch, s32x_video *video);
#endif //S32X_VIDEO_H_
+413 -349
View File
File diff suppressed because it is too large. Load diff
+4 -4
View File
@@ -7,6 +7,7 @@
#define M68KINST_H_
#include <stdint.h>
#include "disasm.h"
#ifdef M68030
#define M68020
@@ -333,15 +334,14 @@ typedef enum {
VECTOR_USER0 = 64
} m68k_vector;
typedef int (*format_label_fun)(char * dst, uint32_t address, void * data);
typedef uint16_t (*m68k_fetch_fun)(uint32_t address, void *data);
uint16_t * m68k_decode(uint16_t * istream, m68kinst * dst, uint32_t address);
uint32_t m68k_decode(m68k_fetch_fun fetch, void *data, m68kinst * dst, uint32_t address);
uint32_t m68k_branch_target(m68kinst * inst, uint32_t *dregs, uint32_t *aregs);
uint8_t m68k_is_branch(m68kinst * inst);
uint8_t m68k_is_noncall_branch(m68kinst * inst);
int m68k_disasm(m68kinst * decoded, char * dst);
int m68k_disasm_labels(m68kinst * decoded, char * dst, format_label_fun label_fun, void * data);
int m68k_default_label_fun(char * dst, uint32_t address, void * data);
int m68k_disasm_labels(m68kinst * decoded, char * dst, disasm_context *disasm);
#endif
+17 -14
View File
@@ -4,25 +4,28 @@ include $(CLEAR_VARS)
LOCAL_MODULE := main
SDL_PATH := android/jni/SDL
SDL_PATH := android/app/jni/SDL
LOCAL_C_INCLUDES := $(LOCAL_PATH)/$(SDL_PATH)/include
LOCAL_CFLAGS += -std=gnu99 -DX86_32 -DUSE_GLES
LOCAL_CFLAGS += -std=gnu99 -DNEW_CORE -DUSE_GLES
# Add your application source files here...
LOCAL_SRC_FILES := $(SDL_PATH)/src/main/android/SDL_android_main.c \
68kinst.c debug.c gst.c psg.c z80_to_x86.c backend.c io.c render_sdl.c \
tern.c backend_x86.c gdb_remote.c m68k_core.c romdb.c m68k_core_x86.c \
util.c wave.c blastem.c gen.c mem.c vdp.c ym2612.c config.c gen_x86.c \
terminal.c z80inst.c menu.c arena.c zlib/adler32.c zlib/compress.c \
zlib/crc32.c zlib/deflate.c zlib/gzclose.c zlib/gzlib.c zlib/gzread.c \
zlib/gzwrite.c zlib/infback.c zlib/inffast.c zlib/inflate.c \
zlib/inftrees.c zlib/trees.c zlib/uncompr.c zlib/zutil.c \
nuklear_ui/font_android.c nuklear_ui/blastem_nuklear.c nuklear_ui/sfnt.c \
ppm.c controller_info.c png.c system.c genesis.c sms.c serialize.c \
saves.c hash.c xband.c zip.c bindings.c jcart.c paths.c megawifi.c \
nor.c i2c.c sega_mapper.c realtec.c multi_game.c net.c
LOCAL_SRC_FILES := $(SDL_PATH)/src/main/android/SDL_android_main.c 68kinst.c \
debug.c gst.c psg.c z80.c backend.c io.c render_sdl.c tern.c gdb_remote.c \
m68k.c romdb.c util.c wave.c flac.c blastem.c gen.c mem.c vdp.c ym2612.c \
ymf262.c ym_common.c vgm.c event_log.c render_audio.c config.c terminal.c \
z80inst.c menu.c arena.c zlib/adler32.c zlib/compress.c zlib/crc32.c \
zlib/deflate.c zlib/gzclose.c zlib/gzlib.c zlib/gzread.c zlib/gzwrite.c \
zlib/infback.c zlib/inffast.c zlib/inflate.c zlib/inftrees.c zlib/trees.c \
zlib/uncompr.c zlib/zutil.c nuklear_ui/font_android.c \
nuklear_ui/filechooser_null.c nuklear_ui/blastem_nuklear.c \
nuklear_ui/sfnt.c ppm.c controller_info.c png.c system.c genesis.c sms.c \
serialize.c saves.c hash.c xband.c zip.c bindings.c jcart.c paths.c \
megawifi.c nor.c i2c.c sega_mapper.c realtec.c multi_game.c net.c coleco.c \
pico_pcm.c ymz263b.c segacd.c lc8951.c cdimage.c cdd_mcu.c cd_graphics.c \
cdd_fader.c rf5c164.c sft_mapper.c mediaplayer.c oscilloscope.c disasm.c \
i8255.c gen_player.c
LOCAL_SHARED_LIBRARIES := SDL2
+212 -126
View File
@@ -1,20 +1,28 @@
#disable built-in rules
.SUFFIXES :
.PHONY: all clean
ifndef OS
OS:=$(shell uname -s)
endif
ifndef SDL
SDL:=sdl2
endif
SDL_UPPER:=$(shell echo $(SDL) | tr '[a-z]' '[A-Z]')
FIXUP:=true
Z80_DISPATCH:=goto
BUNDLED_LIBZ:=zlib/adler32.o zlib/compress.o zlib/crc32.o zlib/deflate.o zlib/gzclose.o zlib/gzlib.o zlib/gzread.o\
zlib/gzwrite.o zlib/infback.o zlib/inffast.o zlib/inflate.o zlib/inftrees.o zlib/trees.o zlib/uncompr.o zlib/zutil.o
UTIL_LDFLAGS:=
ifeq ($(OS),Windows)
GLEW_PREFIX:=glew
MEM:=mem_win.o
TERMINAL:=terminal_win.o
FONT:=nuklear_ui/font_win.o
CHOOSER:=nuklear_ui/filechooser_win.o
NET:=net_win.o
EXE:=.exe
SO:=dll
@@ -32,10 +40,10 @@ GLUDIR:=x64
endif
GLEW32S_LIB:=$(GLEW_PREFIX)/lib/Release/$(GLUDIR)/glew32s.lib
CFLAGS:=-std=gnu99 -Wreturn-type -Werror=return-type -Werror=implicit-function-declaration -Wpointer-arith -Werror=pointer-arith
LDFLAGS:=-lm -lmingw32 -lws2_32 -mwindows
LDFLAGS:=-lm -lmingw32 -lws2_32 -lcomdlg32 -lole32 -mwindows
ifneq ($(MAKECMDGOALS),libblastem.dll)
CFLAGS+= -I"$(SDL2_PREFIX)/include/SDL2" -I"$(GLEW_PREFIX)/include" -DGLEW_STATIC
LDFLAGS+= $(GLEW32S_LIB) -L"$(SDL2_PREFIX)/lib" -lSDL2main -lSDL2 -lopengl32 -lglu32
CFLAGS+= -I"$(SDL2_PREFIX)/include/$(SDL_UPPER)" -I"$(GLEW_PREFIX)/include" -DGLEW_STATIC
LDFLAGS+= $(GLEW32S_LIB) -L"$(SDL2_PREFIX)/lib" -l$(SDL_UPPER)main -l$(SDL_UPPER) -lopengl32 -lglu32
endif
LIBZOBJS=$(BUNDLED_LIBZ)
@@ -50,12 +58,21 @@ HAS_PROC:=$(shell if [ -d /proc ]; then /bin/echo -e -DHAS_PROC; fi)
CFLAGS:=-std=gnu99 -Wreturn-type -Werror=return-type -Werror=implicit-function-declaration -Wno-unused-value -Wpointer-arith -Werror=pointer-arith $(HAS_PROC) -DHAVE_UNISTD_H
ifeq ($(OS),Darwin)
LIBS=sdl2 glew
LIBS=$(SDL) glew
FONT:=nuklear_ui/font_mac.o
CHOOSER:=nuklear_ui/filechooser_null.o
SO:=dylib
else
SO:=so
ifeq ($(CPU),wasm)
USE_GLES:=1
endif
ifdef USE_RGB565
CFLAGS+= -DUSE_RGB565
endif
ifdef USE_FBDEV
LIBS=alsa
ifndef NOGL
@@ -64,13 +81,34 @@ endif
CFLAGS+= -DUSE_GLES -DUSE_FBDEV -pthread
else
ifdef USE_GLES
LIBS=sdl2 glesv2
LIBS=$(SDL) glesv2
CFLAGS+= -DUSE_GLES
else
LIBS=sdl2 glew gl
LIBS=$(SDL) glew gl
endif #USE_GLES
endif #USE_FBDEV
ifeq ($(CPU),wasm)
CHOOSER:=nuklear_ui/filechooser_null.o
FONT:=nuklear_ui/font_web.o
Z80_DISPATCH:=call
else #CPU=wasm
FONT:=nuklear_ui/font.o
ifneq ($(MAKECMDGOALS),libblastem.$(SO))
CHOOSER:=nuklear_ui/filechooser_gtk.o
GTKFLAGS:=$(shell pkg-config --cflags gtk+-3.0 2>/dev/null)
ifeq ($(GTKFLAGS),)
GTKFLAGS:=$(shell pkg-config --cflags gtk+-2.0 2>/dev/null)
ifeq ($(GTKFLAGS),)
CHOOSER:=nuklear_ui/filechooser_null.o
endif
endif
endif #neq ($(MAKECMDGOALS),libblastem.$(SO))
endif #CPU=wasm
ifeq ($(GTKFLAGS),)
else
EXTRA_NUKLEAR_LDFLAGS:=-ldl
endif
CFLAGS+= $(GTKFLAGS)
endif #Darwin
ifdef HOST_ZLIB
@@ -84,32 +122,46 @@ ifeq ($(OS),Darwin)
#This should really be based on whether or not the C compiler is clang rather than based on the OS
CFLAGS+= -Wno-logical-op-parentheses
endif
ifeq ($(CPU),wasm)
CFLAGS+= --use-port=sdl2
LDFLAGS+= --use-port=sdl2 --embed-file rom.db --embed-file default.cfg --embed-file systems.cfg \
--embed-file shaders/ --embed-file images/ --embed-file DroidSans.ttf -sEXPORTED_FUNCTIONS=_main,_handle_chooser_result \
-sEXPORTED_RUNTIME_METHODS=ccall,cwrap,callMain -sINITIAL_HEAP=50331648 --pre-js emscripten_pre.js -lidbfs.js
EXE:=.html
else #CPU=wasm
ifdef PORTABLE
ifdef USE_GLES
ifndef GLES_LIB
GLES_LIB:=$(shell pkg-config --libs glesv2)
endif
LDFLAGS:=-lm $(GLES_LIB)
else
else #USE_GLES
CFLAGS+= -DGLEW_STATIC -Iglew/include
LDFLAGS:=-lm glew/lib/libGLEW.a
endif
LDFLAGS:=-lm glew/lib/libGLEW.a -lEGL
endif #USE_GLES
ifeq ($(OS),Darwin)
SDL_INCLUDE_PATH:=Frameworks/SDL2.framework/Headers
SDL_INCLUDE_PATH:=Frameworks/$(SDL_UPPER).framework/Headers
CFLAGS+= -mmacosx-version-min=10.10
LDFLAGS+= -mmacosx-version-min=10.10
FIXUP:=install_name_tool -change @rpath/SDL2.framework/Versions/A/SDL2 @executable_path/Frameworks/SDL2.framework/Versions/A/SDL2
LDFLAGS+= -FFrameworks -framework $(SDL_UPPER) -framework OpenGL -framework AppKit -mmacosx-version-min=10.10
FIXUP:=install_name_tool -change @rpath/$(SDL_UPPER).framework/Versions/A/$(SDL_UPPER) @executable_path/Frameworks/$(SDL_UPPER).framework/Versions/A/$(SDL_UPPER)
else #Darwin
ifeq ($(SDL),sdl3)
SDL_INCLUDE_PATH:=sdl/include/SDL3
CFLAGS+= -Isdl/include -DSDL_ENABLE_OLD_NAMES
else
SDL_INCLUDE_PATH:=sdl/include
LDFLAGS+= -Wl,-rpath='$$ORIGIN/lib' -Llib -lSDL2
endif
LDFLAGS+= -Wl,-rpath='$$ORIGIN/lib' -Llib -l$(SDL_UPPER)
ifndef USE_GLES
LDFLAGS+= $(shell pkg-config --libs gl)
endif
endif #Darwin
CFLAGS+= -I$(SDL_INCLUDE_PATH)
else
else #PORTABLE
ifeq ($(MAKECMDGOALS),libblastem.$(SO))
LDFLAGS:=-lm
else
@@ -121,25 +173,25 @@ endif
endif #libblastem.so
ifeq ($(OS),Darwin)
ifneq ($(LIBRETRO),1)
LDFLAGS+= -framework OpenGL -framework AppKit
endif
endif
endif #PORTABLE
endif #CPU=wasm
endif #Windows
ifndef OPT
ifdef DEBUG
OBJDIR:=obj/debug
OPT:=-g3 -O0
else
OBJDIR:=obj/release
ifdef NOLTO
OPT:=-O2
else
OPT:=-O2 -flto
endif #NOLTO
endif #DEBUG
endif #OPT
LIBOBJDIR:=$(OBJDIR)/lib
CFLAGS:=$(OPT) $(CFLAGS)
LDFLAGS:=$(OPT) $(LDFLAGS)
@@ -178,17 +230,27 @@ ifeq ($(CPU),i386)
CPU:=i686
endif
endif
#Haiku uses a different name for 32bit
ifeq ($(CPU),BePC)
CPU:=i686
ifeq ($(CPU),x86_64)
CFLAGS+=-DX86_64 -m64
LDFLAGS+=-m64
else
ifeq ($(CPU),i686)
CFLAGS+=-DX86_32 -m32
LDFLAGS+=-m32
else
NEW_CORE:=1
endif
endif
TRANSOBJS=gen.o backend.o $(MEM) arena.o tern.o
M68KOBJS=68kinst.o
M68KOBJS=68kinst.o disasm.o
ifdef NO_FILE_CHOOSER
CHOOSER:=nuklear_ui/filechooser_nulll.o
endif
ifdef NEW_CORE
Z80OBJS=z80.o z80inst.o
Z80OBJS=z80.o z80inst.o
M68KOBJS+= m68k.o
CFLAGS+= -DNEW_CORE
else
@@ -203,53 +265,44 @@ TRANSOBJS+= gen_x86.o backend_x86.o
endif
endif
endif
AUDIOOBJS=ym2612.o psg.o wave.o vgm.o event_log.o render_audio.o
CONFIGOBJS=config.o tern.o util.o paths.o
NUKLEAROBJS=$(FONT) nuklear_ui/blastem_nuklear.o nuklear_ui/sfnt.o
AUDIOOBJS=ym2612.o ymf262.o ym_common.o psg.o wave.o flac.o vgm.o event_log.o render_audio.o rf5c164.o
CONFIGOBJS=config.o tern.o util.o paths.o
NUKLEAROBJS=$(FONT) $(CHOOSER) nuklear_ui/blastem_nuklear.o nuklear_ui/sfnt.o nuklear_ui/debug_ui.o
RENDEROBJS=ppm.o controller_info.o
ifdef USE_FBDEV
RENDEROBJS+= render_fbdev.o
else
RENDEROBJS+= render_sdl.o
endif
ifdef NOZLIB
CFLAGS+= -DDISABLE_ZLIB
else
RENDEROBJS+= $(LIBZOBJS) png.o
endif
MAINOBJS=blastem.o system.o genesis.o debug.o gdb_remote.o vdp.o $(RENDEROBJS) io.o romdb.o hash.o menu.o xband.o \
realtec.o i2c.o nor.o sega_mapper.o multi_game.o megawifi.o $(NET) serialize.o $(TERMINAL) $(CONFIGOBJS) gst.o \
$(M68KOBJS) $(TRANSOBJS) $(AUDIOOBJS) saves.o zip.o bindings.o jcart.o gen_player.o
LIBOBJS=libblastem.o system.o genesis.o debug.o gdb_remote.o vdp.o io.o romdb.o hash.o xband.o realtec.o \
i2c.o nor.o sega_mapper.o multi_game.o megawifi.o $(NET) serialize.o $(TERMINAL) $(CONFIGOBJS) gst.o \
$(M68KOBJS) $(TRANSOBJS) $(AUDIOOBJS) saves.o jcart.o rom.db.o gen_player.o $(LIBZOBJS)
ifdef NONUKLEAR
CFLAGS+= -DDISABLE_NUKLEAR
else
MAINOBJS+= $(NUKLEAROBJS)
endif
ifeq ($(CPU),x86_64)
CFLAGS+=-DX86_64 -m64
LDFLAGS+=-m64
else
ifeq ($(CPU),i686)
CFLAGS+=-DX86_32 -m32
LDFLAGS+=-m32
else
$(error $(CPU) is not a supported architecture)
endif
endif
COREOBJS:=system.o genesis.o vdp.o io.o romdb.o hash.o xband.o realtec.o i2c.o nor.o $(M68KOBJS) \
sega_mapper.o multi_game.o megawifi.o $(NET) serialize.o $(TERMINAL) $(CONFIGOBJS) gst.o \
$(TRANSOBJS) $(AUDIOOBJS) saves.o jcart.o gen_player.o coleco.o pico_pcm.o ymz263b.o \
segacd.o lc8951.o cdimage.o cdd_mcu.o cd_graphics.o cdd_fader.o sft_mapper.o mediaplayer.o \
laseractive.o upd78k2_dis.o upd78k2.o osd_font.o pd0178.o radica.o 32x.o 32x_video.o sh2.o \
sh2_decode.o sh7095.o
ifdef NOZ80
CFLAGS+=-DNO_Z80
else
MAINOBJS+= sms.o $(Z80OBJS)
LIBOBJS+= sms.o $(Z80OBJS)
COREOBJS+= sms.o i8255.o korean_sms_multi.o $(Z80OBJS)
endif
MAINOBJS:=$(COREOBJS) blastem.o $(RENDEROBJS) zip.o menu.o debug.o gdb_remote.o bindings.o oscilloscope.o
LIBOBJS:=$(COREOBJS) libblastem.o rom.db.o $(LIBZOBJS)
ifdef NONUKLEAR
CFLAGS+= -DDISABLE_NUKLEAR
else
MAINOBJS+= $(NUKLEAROBJS)
LDFLAGS+=$(EXTRA_NUKLEAR_LDFLAGS)
endif
ifeq ($(OS),Windows)
@@ -268,105 +321,138 @@ ifdef FONT_PATH
CFLAGS+= -DFONT_PATH='"'$(FONT_PATH)'"'
endif
ALL=dis$(EXE) zdis$(EXE) vgmplay$(EXE) blastem$(EXE)
ALL=dis$(EXE) zdis$(EXE) upddis$(EXE) sh2dis$(EXE) blastem$(EXE)
ifneq ($(OS),Windows)
ALL+= termhelper
endif
DISOBJS:=dis.o disasm.o backend.o 68kinst.o tern.o vos_program_module.o util.o
ZDISOBJS:=zdis.o z80inst.o tern.o util.o
MTESTOBJS:=trans.o serialize.o $(M68KOBJS) $(TRANSOBJS) util.o
ZTESTOBJS:=ztestrun.o serialize.o $(Z80OBJS) $(TRANSOBJS) util.o
CPMOBJS:=blastcpm.o util.o serialize.o $(Z80OBJS) $(TRANSOBJS)
UPD78K2RUNOBJS:=upd78k2.o upd78k2run.o util.o backend.o tern.o
UPDDISOBJS:=upddis.o upd78k2_dis.o disasm.o tern.o util.o backend.o
SH2DISOBJS:=sh2dis.o sh2_decode.o disasm.o tern.o util.o backend.o
ifeq ($(MAKECMDGOALS),libblastem.$(SO))
CFLAGS+= -fpic -DIS_LIB
endif
LIBCFLAGS=$(CFLAGS) -fpic -DIS_LIB -DDISABLE_ZLIB
all : $(ALL)
libblastem.$(SO) : $(LIBOBJS)
ORDERONLY:=$(OBJDIR)
LIBORDERONLY:=$(LIBOBJDIR)
ifdef NEW_CORE
ifeq ($(wildcard $(OBJDIR)/*.d),)
ORDERONLY+= m68k.c z80.c
endif
ifeq ($(wildcard $(LIBOBJDIR)/*.d),)
LIBORDERONLY+= m68k.c z80.c
endif
endif
#if we've never built the uPD78K2 core before we don't know which other files
#depend on its header, so force DSL compilation to occur before everything else
ifeq ($(wildcard $(OBJDIR)/upd78k2.d),)
ORDERONLY+= upd78k2.c
endif
ifeq ($(wildcard $(LIBOBJDIR)/upd78k2.d),)
LIBORDERONLY+= upd78k2.c
endif
#same for the SH2 core
ifeq ($(wildcard $(OBJDIR)/sh2.d),)
ORDERONLY+= sh2.c
endif
ifeq ($(wildcard $(LIBOBJDIR)/sh2.d),)
LIBORDERONLY+= sh2.c
endif
-include $(MAINOBJS:%.o=$(OBJDIR)/%.d)
-include $(LIBOBJS:%.o=$(LIBOBJDIR)/%.d)
-include $(DISOBJS:%.o=$(OBJDIR)/%.d)
-include $(ZDISOBJS:%.o=$(OBJDIR)/%.d)
-include $(UPD78K2RUNOBJS:%.o=$(OBJDIR)/%.d)
-include $(UPDDISOBJS:%.o=$(OBJDIR)/%.d)
-include $(SH2DISOBJS:%.o=$(OBJDIR)/%.d)
-include $(OBJDIR)/trans.d
-include $(OBJDIR)/ztestrun.d
-include $(OBJDIR)/blastcpm.d
$(OBJDIR) :
mkdir -p $(OBJDIR)/nuklear_ui
mkdir -p $(OBJDIR)/zlib
$(LIBOBJDIR) :
mkdir -p $(LIBOBJDIR)/zlib
libblastem.$(SO) : $(LIBOBJS:%.o=$(LIBOBJDIR)/%.o)
$(CC) -shared -o $@ $^ $(LDFLAGS)
blastem$(EXE) : $(MAINOBJS)
blastem$(EXE) : $(MAINOBJS:%.o=$(OBJDIR)/%.o)
$(CC) -o $@ $^ $(LDFLAGS) $(PROFFLAGS)
$(FIXUP) ./$@
blastjag$(EXE) : jaguar.o jag_video.o $(RENDEROBJS) serialize.o $(M68KOBJS) $(TRANSOBJS) $(CONFIGOBJS)
termhelper : $(OBJDIR)/termhelper.o
$(CC) -o $@ $^ $(LDFLAGS)
termhelper : termhelper.o
$(CC) -o $@ $^ $(LDFLAGS)
dis$(EXE) : dis.o 68kinst.o tern.o vos_program_module.o
$(CC) -o $@ $^ $(OPT)
jagdis : jagdis.o jagcpu.o tern.o
$(CC) -o $@ $^
zdis$(EXE) : zdis.o z80inst.o
$(CC) -o $@ $^
libemu68k.a : $(M68KOBJS) $(TRANSOBJS)
ar rcs libemu68k.a $(M68KOBJS) $(TRANSOBJS)
trans : trans.o serialize.o $(M68KOBJS) $(TRANSOBJS) util.o
dis$(EXE) : $(DISOBJS:%.o=$(OBJDIR)/%.o)
$(CC) -o $@ $^ $(OPT)
transz80 : transz80.o $(Z80OBJS) $(TRANSOBJS)
$(CC) -o transz80 transz80.o $(Z80OBJS) $(TRANSOBJS)
jagdis : $(OBJDIR)/jagdis.o $(OBJDIR)/jagcpu.o $(OBJDIR)/tern.o
$(CC) -o $@ $^ $(OPT)
ztestrun : ztestrun.o serialize.o $(Z80OBJS) $(TRANSOBJS)
$(CC) -o ztestrun $^ $(OPT)
zdis$(EXE) : $(ZDISOBJS:%.o=$(OBJDIR)/%.o)
$(CC) -o $@ $^ $(OPT)
ztestgen : ztestgen.o z80inst.o
$(CC) -ggdb -o ztestgen ztestgen.o z80inst.o
trans : $(MTESTOBJS:%.o=$(OBJDIR)/%.o)
$(CC) -o $@ $^ $(OPT)
vgmplay$(EXE) : vgmplay.o $(RENDEROBJS) serialize.o $(CONFIGOBJS) $(AUDIOOBJS)
$(CC) -o $@ $^ $(LDFLAGS)
$(FIXUP) ./$@
ztestrun : $(ZTESTOBJS:%.o=$(OBJDIR)/%.o)
$(CC) -o $@ $^ $(OPT)
blastcpm : blastcpm.o util.o serialize.o $(Z80OBJS) $(TRANSOBJS)
ztestgen : $(OBJDIR)/ztestgen.o $(OBJDIR)/z80inst.o
$(CC) -o $@ $^ $(OPT)
blastcpm$(EXE) : $(CPMOBJS:%.o=$(OBJDIR)/%.o)
$(CC) -o $@ $^ $(OPT) $(PROFFLAGS)
test : test.o vdp.o
$(CC) -o test test.o vdp.o
vos_prog_info : $(OBJDIR)/vos_prog_info.o $(OBJDIR)/vos_program_module.o
$(CC) -o $@ $^ $(OPT)
testgst : testgst.o gst.o
$(CC) -o testgst testgst.o gst.o
upd78k2run : $(UPD78K2RUNOBJS:%.o=$(OBJDIR)/%.o)
$(CC) -o $@ $^ $(OPT)
test_x86 : test_x86.o gen_x86.o gen.o
$(CC) -o test_x86 test_x86.o gen_x86.o gen.o
upddis$(EXE) : $(UPDDISOBJS:%.o=$(OBJDIR)/%.o)
$(CC) -o $@ $^ $(OPT)
test_arm : test_arm.o gen_arm.o mem.o gen.o
$(CC) -o test_arm test_arm.o gen_arm.o mem.o gen.o
test_int_timing : test_int_timing.o vdp.o
$(CC) -o $@ $^
sh2dis$(EXE) : $(SH2DISOBJS:%.o=$(OBJDIR)/%.o)
$(CC) -o $@ $^ $(OPT)
gen_fib : gen_fib.o gen_x86.o mem.o
$(CC) -o gen_fib gen_fib.o gen_x86.o mem.o
offsets : offsets.c z80_to_x86.h m68k_core.h
$(CC) -o offsets offsets.c
vos_prog_info : vos_prog_info.o vos_program_module.o
$(CC) -o vos_prog_info vos_prog_info.o vos_program_module.o
m68k.c : m68k.cpu cpu_dsl.py
./cpu_dsl.py -d call $< > $@
%.c : %.cpu cpu_dsl.py
./cpu_dsl.py -d goto $< > $@
.PRECIOUS: %.c
%.c %.h : %.cpu cpu_dsl.py
./cpu_dsl.py -d $(shell echo $@ | sed -E -e "s/^z80.*$$/$(Z80_DISPATCH)/" -e '/^goto/! s/^.*$$/call/') $< > $(shell echo $@ | sed -E 's/\.[ch]$$/./')c
%.db.c : %.db
sed -e 's/"/\\"/g' -e 's/^\(.*\)$$/"\1\\n"/' -e'1s/^\(.*\)$$/const char $(shell echo $< | tr '.' '_')_data[] = \1/' -e '$$s/^\(.*\)$$/\1;/' $< > $@
sed $< -e 's/"/\\"/g' -e 's/^\(.*\)$$/"\1\\n"/' -e'1s/^\(.*\)$$/const char $(shell echo $< | tr '.' '_')_data[] = \1/' -e '$$s/^\(.*\)$$/\1;/' > $@
%.o : %.S
$(CC) -c -o $@ $<
$(OBJDIR)/%.o : %.S | $(ORDERONLY)
$(CC) -c -MMD -o $@ $<
%.o : %.c
$(CC) $(CFLAGS) -c -o $@ $<
%.o : %.m
$(CC) $(CFLAGS) -c -o $@ $<
$(OBJDIR)/%.o : %.c | $(ORDERONLY)
$(CC) $(CFLAGS) -c -MMD -o $@ $<
$(OBJDIR)/%.o : %.m | $(ORDERONLY)
$(CC) $(CFLAGS) -c -MMD -o $@ $<
$(LIBOBJDIR)/%.o : %.S | $(LIBORDERONLY)
$(CC) -c -MMD -o $@ $<
$(LIBOBJDIR)/%.o : %.c | $(LIBORDERONLY)
$(CC) $(LIBCFLAGS) -c -MMD -o $@ $<
$(LIBOBJDIR)/%.o : %.m | $(LIBORDERONLY)
$(CC) $(LIBCFLAGS) -c -MMD -o $@ $<
%.png : %.xcf
xcf2png $< > $@
convert -background none -flatten $< $@
%.tiles : %.spec
./img2tiles.py -s $< $@
@@ -379,8 +465,8 @@ m68k.c : m68k.cpu cpu_dsl.py
%.bin : %.sz8
vasmz80_mot -Fbin -spaces -o $@ $<
res.o : blastem.rc
$(WINDRES) blastem.rc res.o
$(OBJDIR)/res.o : blastem.rc
$(WINDRES) $< $@
arrow.tiles : arrow.png
cursor.tiles : cursor.png
@@ -392,4 +478,4 @@ menu.bin : font_interlace_variable.tiles arrow.tiles cursor.tiles button.tiles f
tmss.md : font.tiles
clean :
rm -rf $(ALL) trans ztestrun ztestgen *.o nuklear_ui/*.o zlib/*.o
rm -rf $(ALL) trans ztestrun ztestgen *.o nuklear_ui/*.o zlib/*.o $(OBJDIR)
+151 -65
View File
@@ -1,4 +1,4 @@
BlastEm 0.6.0
BlastEm 0.6.X
-------------
Installation
@@ -9,7 +9,7 @@ Extract this archive to a directory of your choosing.
NOTE: Prior to version 0.4.1, BlastEm was still using Unixy locations for config
and save files. If you're upgrading from a previous version on Windows, you will
need to move them manually. For config files, the relevant paths are in the
previous paragraph. For save files, move all the directories found in
previous paragraph. For save files, move all the directories found in
%userprofile%\.local\share\blastem to %localappdata%\blastem
Usage
@@ -17,23 +17,22 @@ Usage
This version of BlastEm has a GUI that allows access to most configuration options.
Simply start BlastEm without passing a ROM filename on the command line to access
the main menu. You can also access the menu by hitting the button mapped to the ui.exit
the main menu. You can also access the menu by hitting the button mapped to the ui.menu
action (default Esc).
If Open GL is disabled or unavaible, or you explicitly request it, the old ROM-based UI
will be used instead. This UI does not support configuration so you will need to modify
the configuration file manually if you use it. See the rest of this README for instructions
on modifying the configuration file.
If you explicitly request it, you can still use the old ROM-based UI instead. This UI does
not support configuration so you will need to modify the configuration file manually if you
use it. See the rest of this README for instructions on modifying the configuration file.
Some operations are currently only supported through the command line. To get a
list of supported command line options on Linux or OSX type:
./blastem -h
From within your BlastEm directory. On Windows type:
blastem.exe -h
Lock-On Support
---------------
@@ -46,7 +45,7 @@ the command line, specify the Sonic & Knuckles ROM as the primary ROM and
specify the ROM to be locked on using the -o option. As an example:
./blastem ~/romz/sonic_and_knuckles.bin -o ~/romz/sonic3.bin
Please note that Sonic 2 lock-on does not work at this time.
Configuration
@@ -55,7 +54,7 @@ Configuration
Configuration is read from the file at $HOME/.config/blastem/blastem.cfg on
Unix-like systems and %localappdata%\blastem\blastem.cfg if it exists.
Othwerise it is read from default.cfg from the same directory as the BlastEm
executable. Sections are denoted by a section name followed by an open curly
executable. Sections are denoted by a section name followed by an open curly
bracket, the section's contents and a closing curly bracket. Individual
configuration values are set by entering the value's name followed by a space
or tab and followed by the desired value.
@@ -105,10 +104,26 @@ don't correspond to a normal character, check the list below:
play
search
back
np0 Number Pad 0
np1 Number Pad 1
np2 Number Pad 2
np3 Number Pad 3
np4 Number Pad 4
np5 Number Pad 5
np6 Number Pad 6
np7 Number Pad 7
np8 Number Pad 8
np9 Number Pad 9
np/ Number Pad /
np* Number Pad *
np- Number Pad -
np+ Number Pad +
npenter Number Pad Enter
np. Number Pad .
The pads subsection is used to map gamepads and joysticks. Gamepads that are
recognized, can have their buttons and axes mapped with semantic names.
Xbox 360, PS4 and PS3 style names are supported. Unrecognized gamepads can be
recognized, can have their buttons and axes mapped with semantic names.
Xbox 360, PS4 and PS3 style names are supported. Unrecognized gamepads can be
mapped using numeric button and axis ids. The following button names are
recognized by BlastEm:
a, cross
@@ -129,7 +144,7 @@ The following axis names are recognized by BlastEm:
righty
lefttrigger, l2
righttrigger, r2
The mice subsection is used to map mice to emulated Mega/Sega mice. The default
configuration maps both the first and second host mice to the first emulated
@@ -153,19 +168,27 @@ ui.plane_debug Toggles the VDP plane debug view
ui.vram_debug Toggles the VDP VRAM debug view
ui.cram_debug Toggles the VDP CRAM debug view
ui.compositing_debug Toggles the VDP compositing debug view
ui.oscilloscope Toggles an oscilloscope visualization
ui.cd_graphics_debug Toggles the Sega CD graphics ASIC debug view
ui.vdp_debug_mode Cycles the mode/palette of the VDP debug view
that currently has focus
ui.enter_debugger Enters the debugger for the main CPU of the
currently emulated system
ui.screenshot Takes an internal screenshot
ui.exit Returns to the menu ROM if currently in a game
that was launched from the menu. Exits otherwise
ui.record_video Toggles video recording. Videos are recorded as an
animated PNG paired with a WAV file.
ui.vgm_log Toggles VGM file logging
ui.menu Opens the in-game menu, or exits the menu if it
is currently open.
ui.exit Quits the emulator
ui.save_state Saves a savestate to the quicksave slot
ui.set_speed.N Selects a specific machine speed specified by N
which should be a number between 0-9. Speeds are
specified in the "clocks" section of the config
specified in the "clocks" section of the config
ui.next_speed Selects the next machine speed
ui.prev_speed Selects the previous machine speed
ui.pause Pauses emulation
ui.advance Advances a single frame if paused. Pauses if not.
ui.toggle_fullscreen Toggles between fullscreen and windowed mode
ui.soft_reset Resets a portion of the emulated machine
Equivalent to pushing the reset button on the
@@ -176,7 +199,23 @@ ui.sms_pause Triggers a press of the pause button when in SMS
mode
ui.toggle_keyboard_captured Toggles the capture state of the host keyboard
when an emulated keyboard is present
ui.release_mouse Release the mouse from the captured state
ui.paste Pastes the contents of the clipboard into the
emulated system by simulating keypresses.
Currently, this is only implemented for SC-3000
Cassette Actions
----------------
When emulating the SC-3000, cassette emulation is supported via the lock-on
functionality. The following binding names can be triggered by a key or gamepad
binding.
cassette.play Start playing the virtual tape
cassette.record Starting recording on the virtual tape
cassette.stop Stop the virtual tape at the current position
cassette.rewind Rewind the virtual tape
IO
--
@@ -185,7 +224,22 @@ IO assignments can be overridden by the ROM database when appropriate. For
instance, games with mouse support can automatically use the mouse and games
that only support 3-button pads can automatically force an appropriate pad.
Unforunately, the ROM database is not yet exhaustive so manual configuration
may be needed here in some cases.
may be needed here in some cases. The following IO devices are recognized
gamepad.2 SMS gamepad
gamepad.3 3-button Gen/MD gamepad
gamepad.6 6-button Gen/MD gamepad
heartbeat_trainer Special peripheral used for the Heartbeat Personal Trainer
mouse Sega/Mega mouse
saturn_keyboard Saturn keybord or keyboard adapter
xband_keyboard Xband keyboard
sega_parallel PC Parallel port connection used by some of Sega's devtools
generic Generic pipe-based device emulation for use with external
software.
serial Generic pipe-based serial emulation
sega_multitap Sega's 4-player Multitap
ea_multitap_port_a First port of EA's 4-way Play multitap
ea_multitap_port_a Second port of EA's 4-way Play multitap
Video
-----
@@ -203,7 +257,7 @@ left unspecified, it will be calculated from "width" and "aspect".
"vertex_shader" and "fragment_shader" define the GLSL shader program that
produces the final image for each frame. Shaders can be used to add various
visual effects or enhancements. Currently BlastEm only ships with the default
shader and a "subtle" crt shader. If you write your own shaders, place them in
shader and a "subtle" crt shader. If you write your own shaders, place them in
$HOME/.config/blastem/shaders and then specify the basename of the new shader
files in the "vertex_shader" and "fragment_shader" config options. Note that
shaders are not available in the SDL fallback renderer.
@@ -334,7 +388,7 @@ defined in the OS environment can be used.
"remember_path" specifies whether BlastEm should remember the last path used in
the file browser. When it is set to "on", the last path will be remembered and
used instead of "initial_path" in subsequent runs. If it is set to "off",
used instead of "initial_path" in subsequent runs. If it is set to "off",
"initial_path" will always be used.
"screenshot_path" specifies the directory "internal" screenshots will be saved
@@ -364,7 +418,7 @@ This section explains the meaning of the special path variables referenced
in the previous section.
$HOME The home directory of the current user. On most Unix variants, it
will be a subdirectory of /home. On Windows it will typically be a
will be a subdirectory of /home. On Windows it will typically be a
subdirectory of C:\Users
$EXEDIR The directory the BlastEm executable is located in
$USERDATA This is an OS-specific path used for storing application specific
@@ -401,43 +455,77 @@ Debugger
--------
BlastEm has an integrated command-line debugger loosely based on GDB's
interface. The interface is very rough at the moment. Available commands in the
68K debugger are:
b ADDRESS - Set a breakpoint at ADDRESS
d BREAKPOINT - Delete a 68K breakpoint
co BREAKPOINT - Run a list of debugger commands each time
BREAKPOINT is hit
a ADDRESS - Advance to address
n - Advance to next instruction
o - Advance to next instruction ignoring branches to
lower addresses (good for breaking out of loops)
s - Advance to next instruction (follows bsr/jsr)
c - Continue
bt - Print a backtrace
p[/(x|X|d|c)] VALUE - Print a register or memory location
di[/(x|X|d|c)] VALUE - Print a register or memory location each time
a breakpoint is hit
vs - Print VDP sprite list
vr - Print VDP register info
zb ADDRESS - Set a Z80 breakpoint
zp[/(x|X|d|c)] VALUE - Display a Z80 value
q - Quit BlastEm
Available commands in the Z80 debugger are:
b ADDRESS - Set a breakpoint at ADDRESS
de BREAKPOINT - Delete a Z80 breakpoint
a ADDRESS - Advance to address
n - Advance to next instruction
c - Continue
p[/(x|X|d|c)] VALUE - Print a register or memory location
di[/(x|X|d|c)] VALUE - Print a register or memory location each time
a breakpoint is hit
q - Quit BlastEm
interface. It contains a turing complete language to support complex
breakpoint conditions and to allow some basic scripting.
The -d flag can be used to cause BlastEm to start in the debugger.
Alternatively, you can use the ui.enter_debugger action (mapped to the 'u' key
by default) to enter the debugger while a game is running. To debug the menu
ROM, use the -dm flag.
Top level debugger commands take the following form:
COMMAND[/FORMAT_CHAR] [ARGS]
Where COMMAND is the name of the command, an unambiguous prefix or an explict
alias. FORMAT_CHAR is a single character specifying the desired format for
certain commands. ARGS is a list of arguments separated by spaces. Arguments
must be syntactically valid as an expression and are generally evaluated as
such, but there are some exceptions. For instance, the "function" command
expects its first argument to be the bare name of the function. This is used
directly rather than evaluated.
To get a list of commands valid in the current context use the help command.
The help command will also list explicit aliases for a command and its expected
arguments.
The commands available in all contexts are:
quit
help
breakpoint ADDRESS Add a new breakpoint at ADDRESS for the
current CPU
watchpoint ADDRESS [SIZE] Set a watchpoint at ADDRESS with an optional
size in bytes. If not specified, the default
size is context dependent.
continue Resume execution
advance ADDRESS Resume execution until ADDRESS is reached
step Advance to the next instruction, stepping
into subroutines.
next Advance to the next instruction, stepping
over subroutine calls.
over Similar to next, but ignores branches to
a lower address. Good for skipping over
loops.
backtrace Attempts to print a backtrace. Note that
this uses heuristics and will not provide
correct results in all cases.
frames EXPRESSION Resume execution for EXPRESSION video frames
print[/FORMAT] EXPRESSION Print the value of EXPRESSION using FORMAT
printf FORMAT EXPRESSION... Basically the same as the C function
softreset Equivalent to the ui.soft_reset binding
display[/FORMAT] EXPRESSION... Print one ore more expressions every time
this debug context is entered
deletedisplay Removes an entry added via "display"
commands BREAKPOINT Set a list of commands to execute when a
breakpoint is hit. End the list with "end"
function NAME [ARGS...] Creates a user-defined named NAME with
arguments ARGS. Enter the body of the
function on subsequent lines. End with "end"
return VALUE Returns a value inside a function
set MEM|NAME VALUE Sets a register, symbol or memory location
to VALUE
variable NAME [VALUE] Create a new variable called NAME and set it
to VALUE or 0 if no value provided
array NAME [VALUE...] Create a new array called NAME if it doesn't
already exist. The array is initialized with
the remaining parameters.
append NAME VALUE Appens VALUE to the array NAME
bindup NAME Simulate a keyup for binding NAME
binddown NAME Simulate a keydown for binding NAME
TODO: expressions, builtin functions and operators
GDB Remote Debugging
--------------------
@@ -453,7 +541,7 @@ installation and ROM_FILE.bin is the name of the raw binary for your program.
BlastEm will halt at the beginning of your program's entry point and return
control to GDB. This will allow you to set breakpoints before your code runs.
On Windows, the procedure is slightly different. First run
On Windows, the procedure is slightly different. First run
blastem.exe ROM_FILE.bin -D
This will cause BlastEm to wait for a socket connection on port 1234. It will
appear to be frozen until gdb connects to it. Now open the ELF file in gdb
@@ -468,12 +556,10 @@ Included Tools
BlastEm ships with a few small utilities that leverage portions of the emulator
code.
dis - 68K disassembler
zdis - Z80 disassembler
vgmplay - Very basic VGM player
stateview - GST save state viewer
Sync Source and VSync
-----
@@ -509,9 +595,9 @@ Charles MacDonald - While it hasn't been updated in a while, I still find his
hardware document has also come in handy.
Eke-Eke - Eke-Eke wrote a great document on the use of I2C EEPROM in
Genesis games and also left some useful very helpful
Genesis games and also left some useful very helpful
comments about problematic games in Genesis Plus GX
Sauraen - Sauraen has analyzed the YM2203 and YM2612 dies and written
a VHDL operator implementation. These have been useful in
improving the accuracy of my YM2612 core.
@@ -524,16 +610,16 @@ Alexey Khokholov - Alexey (aka Nuke.YKT) has analyzed the YM3438 die and writt
Bart Trzynadlowski - His documents on the Genecyst save-state format and the
mapper used in Super Street Fighter 2 were definitely
appreciated.
KanedaFR - Kaneda's SpritesMind forum is a great resource for the
Sega development community.
Titan - Titan has created what are without a doubt the most
impressive demos on the Megadrive. Additionally, I am very
grateful for the documentation provided by Kabuto and the
assistance of Kabuto, Sik and Jorge in getting Overdrive 2
to run properly in BlastEm.
flamewing - flamewing created a very handy exhaustive test ROM for 68K
BCD instructions and documented the proper behavior for
certain BCD edge cases
@@ -557,4 +643,4 @@ COPYING for full license details.
Binary releases of BlastEm are packaged with GLEW, SDL2 and zlib which have their
own licenses. See GLEW-LICENSE and SDL-LICENSE for details. For zlib license
information, please see zlib.h in the source code release.
information, please see zlib/zlib.h or zlib/LICENSE in the source code release.
-55
View File
@@ -1,55 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Replace org.libsdl.app with the identifier of your game below, e.g.
com.gamemaker.game
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.retrodev.blastem"
android:versionCode="1"
android:versionName="1.0"
android:installLocation="auto">
<!-- Create a Java class extending SDLActivity and place it in a
directory under src matching the package, e.g.
src/com/gamemaker/game/MyGame.java
then replace "SDLActivity" with the name of your class (e.g. "MyGame")
in the XML below.
An example Java class can be found in README-android.txt
-->
<application android:label="@string/app_name"
android:icon="@drawable/ic_launcher"
android:banner="@drawable/ic_launcher"
android:allowBackup="true"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:hardwareAccelerated="true"
android:isGame="true"
android:debuggable="true">
<activity android:name="BlastEmActivity"
android:label="@string/app_name"
android:configChanges="keyboardHidden|orientation"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
</activity>
</application>
<!-- Android 4.1.1 -->
<uses-sdk android:minSdkVersion="16" android:targetSdkVersion="23" />
<!-- OpenGL ES 2.0 -->
<uses-feature android:glEsVersion="0x00020000" />
<uses-feature android:name="android.hardware.gamepad" android:required="false" />
<uses-feature android:name="android.software.leanback" android:required="false" />
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
<!-- Allow writing to external storage -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
</manifest>
-17
View File
@@ -1,17 +0,0 @@
# This file is used to override default values used by the Ant build system.
#
# This file must be checked into Version Control Systems, as it is
# integral to the build system of your project.
# This file is only used by the Ant script.
# You can use this to override default values such as
# 'source.dir' for the location of your java source folder and
# 'out.dir' for the location of your output folder.
# You can also use it define how the release builds are signed by declaring
# the following properties:
# 'key.store' for the location of your keystore and
# 'key.alias' for the name of the key to use.
# The password will be asked during the build when you use the 'release' target.
+75
View File
@@ -0,0 +1,75 @@
def buildAsLibrary = project.hasProperty('BUILD_AS_LIBRARY');
def buildAsApplication = !buildAsLibrary
if (buildAsApplication) {
apply plugin: 'com.android.application'
}
else {
apply plugin: 'com.android.library'
}
android {
if (buildAsApplication) {
namespace "com.retrodev.blastem"
}
compileSdkVersion 35
defaultConfig {
minSdkVersion 19
targetSdkVersion 35
versionCode 1
versionName "1.0"
externalNativeBuild {
ndkBuild {
arguments "APP_PLATFORM=android-19"
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
// cmake {
// arguments "-DANDROID_APP_PLATFORM=android-19", "-DANDROID_STL=c++_static"
// // abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
// abiFilters 'arm64-v8a'
// }
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
applicationVariants.all { variant ->
tasks["merge${variant.name.capitalize()}Assets"]
.dependsOn("externalNativeBuild${variant.name.capitalize()}")
}
if (!project.hasProperty('EXCLUDE_NATIVE_LIBS')) {
sourceSets.main {
jniLibs.srcDir 'libs'
}
externalNativeBuild {
ndkBuild {
path 'jni/Android.mk'
}
// cmake {
// path 'jni/CMakeLists.txt'
// }
}
}
lint {
abortOnError false
}
if (buildAsLibrary) {
libraryVariants.all { variant ->
variant.outputs.each { output ->
def outputFile = output.outputFile
if (outputFile != null && outputFile.name.endsWith(".aar")) {
def fileName = "org.libsdl.app.aar";
output.outputFile = new File(outputFile.parent, fileName);
}
}
}
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
}
@@ -1,2 +1 @@
APP_ABI=x86
include $(call all-subdir-makefiles)
+10
View File
@@ -0,0 +1,10 @@
# Uncomment this if you're using STL in your project
# You can find more information here:
# https://developer.android.com/ndk/guides/cpp-support
# APP_STL := c++_shared
APP_ABI := armeabi-v7a arm64-v8a x86 x86_64
# Min runtime API level
APP_PLATFORM=android-19
+1
View File
@@ -0,0 +1 @@
../../..
+98
View File
@@ -0,0 +1,98 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in [sdk]/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLInputConnection {
void nativeCommitText(java.lang.String, int);
void nativeGenerateScancodeForUnichar(char);
}
-keep,includedescriptorclasses class org.libsdl.app.SDLActivity {
# for some reason these aren't compatible with allowoptimization modifier
boolean supportsRelativeMouse();
void setWindowStyle(boolean);
}
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLActivity {
java.lang.String nativeGetHint(java.lang.String); # Java-side doesn't use this, so it gets minified, but C-side still tries to register it
boolean onNativeSoftReturnKey();
void onNativeKeyboardFocusLost();
boolean isScreenKeyboardShown();
android.util.DisplayMetrics getDisplayDPI();
java.lang.String clipboardGetText();
boolean clipboardHasText();
void clipboardSetText(java.lang.String);
int createCustomCursor(int[], int, int, int, int);
void destroyCustomCursor(int);
android.content.Context getContext();
boolean getManifestEnvironmentVariables();
android.view.Surface getNativeSurface();
void initTouch();
boolean isAndroidTV();
boolean isChromebook();
boolean isDeXMode();
boolean isTablet();
void manualBackButton();
int messageboxShowMessageBox(int, java.lang.String, java.lang.String, int[], int[], java.lang.String[], int[]);
void minimizeWindow();
int openURL(java.lang.String);
void requestPermission(java.lang.String, int);
int showToast(java.lang.String, int, int, int, int);
boolean sendMessage(int, int);
boolean setActivityTitle(java.lang.String);
boolean setCustomCursor(int);
void setOrientation(int, int, boolean, java.lang.String);
boolean setRelativeMouseEnabled(boolean);
boolean setSystemCursor(int);
boolean shouldMinimizeOnFocusLoss();
boolean showTextInput(int, int, int, int);
}
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.HIDDeviceManager {
boolean initialize(boolean, boolean);
boolean openDevice(int);
int sendOutputReport(int, byte[]);
int sendFeatureReport(int, byte[]);
boolean getFeatureReport(int, byte[]);
void closeDevice(int);
}
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLAudioManager {
int[] getAudioOutputDevices();
int[] getAudioInputDevices();
int[] audioOpen(int, int, int, int, int);
void audioWriteFloatBuffer(float[]);
void audioWriteShortBuffer(short[]);
void audioWriteByteBuffer(byte[]);
void audioClose();
int[] captureOpen(int, int, int, int, int);
int captureReadFloatBuffer(float[], boolean);
int captureReadShortBuffer(short[], boolean);
int captureReadByteBuffer(byte[], boolean);
void captureClose();
void audioSetThreadPriority(boolean, int);
native int nativeSetupJNI();
native void removeAudioDevice(boolean, int);
native void addAudioDevice(boolean, int);
}
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLControllerManager {
void pollInputDevices();
void pollHapticDevices();
void hapticRun(int, float, int);
void hapticStop(int);
}
+101
View File
@@ -0,0 +1,101 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Replace com.test.game with the identifier of your game below, e.g.
com.gamemaker.game
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.retrodev.blastem"
android:versionCode="1"
android:versionName="1.0"
android:installLocation="auto">
<!-- OpenGL ES 2.0 -->
<uses-feature android:glEsVersion="0x00020000" />
<!-- Touchscreen support -->
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<!-- Game controller support -->
<uses-feature
android:name="android.hardware.bluetooth"
android:required="false" />
<uses-feature
android:name="android.hardware.gamepad"
android:required="false" />
<uses-feature
android:name="android.hardware.usb.host"
android:required="false" />
<!-- External mouse input events -->
<uses-feature
android:name="android.hardware.type.pc"
android:required="false" />
<!-- Audio recording support -->
<!-- if you want to capture audio, uncomment this. -->
<!-- <uses-feature
android:name="android.hardware.microphone"
android:required="false" /> -->
<!-- Allow downloading to the external storage on Android 5.1 and older -->
<!-- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="22" /> -->
<!-- Allow access to Bluetooth devices -->
<!-- Currently this is just for Steam Controller support and requires setting SDL_HINT_JOYSTICK_HIDAPI_STEAM -->
<!-- <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" /> -->
<!-- <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> -->
<!-- Allow access to the vibrator -->
<uses-permission android:name="android.permission.VIBRATE" />
<!-- if you want to capture audio, uncomment this. -->
<!-- <uses-permission android:name="android.permission.RECORD_AUDIO" /> -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- Create a Java class extending SDLActivity and place it in a
directory under app/src/main/java matching the package, e.g. app/src/main/java/com/gamemaker/game/MyGame.java
then replace "SDLActivity" with the name of your class (e.g. "MyGame")
in the XML below.
An example Java class can be found in README-android.md
-->
<application android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:allowBackup="true"
android:theme="@style/AppTheme"
android:hardwareAccelerated="true" >
<!-- Example of setting SDL hints from AndroidManifest.xml:
<meta-data android:name="SDL_ENV.SDL_ACCELEROMETER_AS_JOYSTICK" android:value="0"/>
-->
<activity android:name="BlastEmActivity"
android:label="@string/app_name"
android:alwaysRetainTaskState="true"
android:launchMode="singleInstance"
android:configChanges="layoutDirection|locale|orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation"
android:preferMinimalPostProcessing="true"
android:exported="true"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- Let Android know that we can handle some USB devices and should receive this event -->
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
<!-- Drop file event -->
<!--
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
-->
</activity>
</application>
</manifest>
+1
View File
@@ -0,0 +1 @@
../../../../../default.cfg
+1
View File
@@ -0,0 +1 @@
../../../../../images
+1
View File
@@ -0,0 +1 @@
../../../../../rom.db
+1
View File
@@ -0,0 +1 @@
../../../../../shaders
+1
View File
@@ -0,0 +1 @@
../../../../../systems.cfg
@@ -0,0 +1,158 @@
package com.retrodev.blastem;
import org.libsdl.app.SDLActivity;
import android.content.ContentResolver;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.provider.DocumentsContract;
import android.util.Log;
import android.view.View;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
public class BlastEmActivity extends SDLActivity
{
static final int DOC_TREE_CODE = 4242;
boolean chooseDirInProgress = false;
String chooseDirResult = null;
Map<String, Uri> uriMap = new HashMap<String, Uri>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//set immersive mode on devices that support it
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
View blah = mSurface;
blah.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
);
}
}
public String getRomPath() {
if (chooseDirInProgress) {
if (chooseDirResult != null) {
chooseDirInProgress = false;
return chooseDirResult;
}
return null;
}
String extStorage = Environment.getExternalStorageDirectory().getAbsolutePath();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
return extStorage;
}
chooseDirInProgress = true;
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
//intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, Uri.parse(extStorage));
/*intent.putExtra("android.content.extra.SHOW_ADVANCED", true);
intent.putExtra("android.content.extra.FANCY", true);
intent.putExtra("android.content.extra.SHOW_FILESIZE", true);*/
startActivityForResult(intent, DOC_TREE_CODE);
return null;
}
public String[] readUriDir(String uriString) {
Uri uri = uriMap.get(uriString);
if (uri == null) {
return new String[0];
}
//adapted from some androidx.documentfile
final ContentResolver resolver = getContentResolver();
final ArrayList<String> results = new ArrayList<>();
Cursor c = null;
try {
Log.i("BlastEm", "getTreeDocumentId: " + DocumentsContract.getTreeDocumentId(uri));
final Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(uri,
DocumentsContract.getTreeDocumentId(uri)
);
c = resolver.query(
childrenUri, new String[] {
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
DocumentsContract.Document.COLUMN_MIME_TYPE
}, null, null, null
);
while (c.moveToNext()) {
final String documentId = c.getString(0);
String name = c.getString(1);
final String mime = c.getString(2);
final Uri documentUri = DocumentsContract.buildDocumentUriUsingTree(uri,
documentId);
uriMap.put(uriString + "/" + name, documentUri);
if (DocumentsContract.Document.MIME_TYPE_DIR.equals(mime)) {
name += "/";
}
results.add(name);
}
} catch (Exception e) {
Log.w("BlastEm", "Failed query: " + e);
} finally {
if (c != null) {
c.close();
}
}
return results.toArray(new String[0]);
}
public int openUriAsFd(String uriString, String mode) {
Uri uri = uriMap.get(uriString);
if (uri == null) {
Log.w("BlastEm", "Did not find URI in map: " + uriString);
return 0;
}
if (mode.equals("rb")) {
mode = "r";
} else if (mode.equals("wb")) {
mode = "w";
}
try {
ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(uri, mode);
if (pfd != null) {
return pfd.detachFd();
}
Log.w("BlastEm", "openFileDescriptor returned null: " + uriString);
} catch (FileNotFoundException e) {
Log.w("BlastEm", "Failed to open URI: " + e);
} catch (IllegalArgumentException e) {
Log.w("BlastEm", "Failed to open URI: " + e);
}
return 0;
}
public String[] getAssetsList(String path) {
try {
return getAssets().list(path);
} catch (IOException e) {
Log.w("BlastEm", "Failed to get assets at '" + path + "': " + e);
}
return new String[0];
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
if (requestCode == DOC_TREE_CODE) {
if (resultCode == RESULT_OK && resultData != null) {
Uri uri = resultData.getData();
getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
chooseDirResult = uri.toString();
uriMap.put(chooseDirResult, uri);
Log.i("BlastEm", "ACTION_OPEN_DOCUMENT_TREE got URI " + chooseDirResult);
} else {
Log.i("BlastEm", "ACTION_OPEN_DOCUMENT_TREE failed! ");
chooseDirResult = "";
}
} else {
super.onActivityResult(requestCode, resultCode, resultData);
}
}
}
+1
View File
@@ -0,0 +1 @@
../../../jni/SDL/android-project/app/src/main/java/org
Binary file not shown.

After

Width:  |  Height:  |  Size: 834 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 755 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 897 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
</resources>
@@ -1,4 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">BlastEm</string>
</resources>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="android:Theme.NoTitleBar.Fullscreen">
<!-- Customize your theme here. -->
</style>
</resources>
-395
View File
@@ -1,395 +0,0 @@
bindings {
keys {
up gamepads.1.up
down gamepads.1.down
left gamepads.1.left
right gamepads.1.right
a gamepads.1.a
s gamepads.1.b
d gamepads.1.c
q gamepads.1.x
w gamepads.1.y
e gamepads.1.z
f gamepads.1.mode
enter gamepads.1.start
r ui.release_mouse
[ ui.vdp_debug_mode
u ui.enter_debugger
p ui.screenshot
b ui.plane_debug
v ui.vram_debug
c ui.cram_debug
n ui.compositing_debug
esc ui.exit
` ui.save_state
0 ui.set_speed.0
1 ui.set_speed.1
2 ui.set_speed.2
3 ui.set_speed.3
4 ui.set_speed.4
5 ui.set_speed.5
6 ui.set_speed.6
7 ui.set_speed.7
= ui.next_speed
- ui.prev_speed
f11 ui.toggle_fullscreen
tab ui.soft_reset
f5 ui.reload
z ui.sms_pause
rctrl ui.toggle_keyboard_captured
select gamepads.1.c
play gamepads.1.start
back ui.exit
}
pads {
default {
dpads {
0 {
up gamepads.n.up
down gamepads.n.down
left gamepads.n.left
right gamepads.n.right
}
}
buttons {
a gamepads.n.a
b gamepads.n.b
rightshoulder gamepads.n.c
x gamepads.n.x
y gamepads.n.y
leftshoulder gamepads.n.z
back gamepads.n.mode
start gamepads.n.start
guide ui.exit
leftstick ui.save_state
}
axes {
lefty.positive gamepads.n.down
lefty.negative gamepads.n.up
leftx.positive gamepads.n.right
leftx.negative gamepads.n.left
lefttrigger ui.prev_speed
righttrigger ui.next_speed
}
}
ps4_6b_right {
axes {
lefttrigger ui.next_speed
leftx.negative gamepads.n.up
leftx.positive gamepads.n.down
lefty.negative gamepads.n.left
lefty.positive gamepads.n.right
righttrigger gamepads.n.c
}
buttons {
a gamepads.n.a
b gamepads.n.b
back ui.sms_pause
guide ui.exit
leftshoulder gamepads.n.mode
leftstick ui.save_state
rightshoulder gamepads.n.z
rightstick ui.prev_speed
start gamepads.n.start
x gamepads.n.x
y gamepads.n.y
}
dpads {
0 {
down gamepads.n.down
left gamepads.n.left
right gamepads.n.right
up gamepads.n.up
}
}
}
ps3_6b_right {
axes {
lefttrigger ui.next_speed
leftx.negative gamepads.n.up
leftx.positive gamepads.n.down
lefty.negative gamepads.n.left
lefty.positive gamepads.n.right
righttrigger gamepads.n.c
}
buttons {
a gamepads.n.a
b gamepads.n.b
back ui.sms_pause
guide ui.exit
leftshoulder gamepads.n.mode
leftstick ui.save_state
rightshoulder gamepads.n.z
rightstick ui.prev_speed
start gamepads.n.start
x gamepads.n.x
y gamepads.n.y
}
dpads {
0 {
down gamepads.n.down
left gamepads.n.left
right gamepads.n.right
up gamepads.n.up
}
}
}
xbox_360_6b_right {
axes {
lefttrigger ui.next_speed
leftx.negative gamepads.n.up
leftx.positive gamepads.n.down
lefty.negative gamepads.n.left
lefty.positive gamepads.n.right
righttrigger gamepads.n.c
}
buttons {
a gamepads.n.a
b gamepads.n.b
back ui.sms_pause
guide ui.exit
leftshoulder gamepads.n.mode
leftstick ui.save_state
rightshoulder gamepads.n.z
rightstick ui.prev_speed
start gamepads.n.start
x gamepads.n.x
y gamepads.n.y
}
dpads {
0 {
down gamepads.n.down
left gamepads.n.left
right gamepads.n.right
up gamepads.n.up
}
}
}
xbone_6b_right {
axes {
lefttrigger ui.next_speed
leftx.negative gamepads.n.up
leftx.positive gamepads.n.down
lefty.negative gamepads.n.left
lefty.positive gamepads.n.right
righttrigger gamepads.n.c
}
buttons {
a gamepads.n.a
b gamepads.n.b
back ui.sms_pause
guide ui.exit
leftshoulder gamepads.n.mode
leftstick ui.save_state
rightshoulder gamepads.n.z
rightstick ui.prev_speed
start gamepads.n.start
x gamepads.n.x
y gamepads.n.y
}
dpads {
0 {
down gamepads.n.down
left gamepads.n.left
right gamepads.n.right
up gamepads.n.up
}
}
}
genesis_6b_bumpers {
axes {
lefttrigger ui.exit
righttrigger gamepads.n.mode
}
buttons {
a gamepads.n.a
b gamepads.n.b
back ui.sms_pause
guide ui.exit
leftshoulder gamepads.n.z
rightshoulder gamepads.n.c
start gamepads.n.start
x gamepads.n.x
y gamepads.n.y
}
dpads {
0 {
down gamepads.n.down
left gamepads.n.left
right gamepads.n.right
up gamepads.n.up
}
}
}
saturn_6b_bumpers {
axes {
lefttrigger ui.exit
righttrigger gamepads.n.mode
}
buttons {
a gamepads.n.a
b gamepads.n.b
back ui.sms_pause
guide ui.exit
leftshoulder gamepads.n.z
rightshoulder gamepads.n.c
start gamepads.n.start
x gamepads.n.x
y gamepads.n.y
}
dpads {
0 {
down gamepads.n.down
left gamepads.n.left
right gamepads.n.right
up gamepads.n.up
}
}
}
}
mice {
0 {
motion mouse.1.motion
buttons {
1 mouse.1.left
2 mouse.1.middle
3 mouse.1.right
4 mouse.1.start
}
}
#having the second host mouse also mapped to the first emulated
#mouse is useful for laptop users with an external mouse
1 {
motion mouse.1.motion
buttons {
1 mouse.1.left
2 mouse.1.middle
3 mouse.1.right
4 mouse.1.start
}
}
}
}
io {
devices {
1 gamepad6.1
2 gamepad6.2
}
}
video {
#special value "stretch" will cause aspect to match window aspect ratio
aspect 4:3
width 640
#height is normally calculated automatically from width using the aspect setting
#if you would like to set it explicitly, uncomment the line below
#height 480
vertex_shader default.v.glsl
fragment_shader default.f.glsl
scanlines off
vsync off
fullscreen off
#setting gl to off, will force use of the SDL2 fallback renderer
#this is useful for those running on machines with Open GL 2.0 unavailable
#so the warning doesn't display on startup
gl on
#scaling can be linear (for linear interpolation) or nearest (for nearest neighbor)
scaling linear
ntsc {
overscan {
#these values will result in square pixels in H40 mode
top 2
bottom 1
#if you want to completely hide the border instead
#comment out those two lines and uncomment these
#top 11
#bottom 8
#these values will completely hide the horizontal border
left 13
right 14
}
}
pal {
overscan {
#these values will produce the same size border in V30 mode
#as the default NTSC settings will produce in V24 mode
#this results in a slightly vertically squished picture
#which is probably approximately correct on a properly calibrated TV
top 21
bottom 17
#for square pixels and zero border in V30 mode
#coment out those two lines and uncomment these
#top 30
#bottom 24
#these values will completely hide the horizontal border
left 13
right 14
}
}
}
audio {
rate 48000
buffer 512
lowpass_cutoff 3390
}
clocks {
m68k_divider 7
max_cycles 3420
speeds {
0 100
1 150
2 200
3 300
4 400
5 25
6 50
7 75
}
}
ui {
#specifies the ROM that implements the Menu UI
rom menu.bin
#starting path for ROM browsing, accepts special variables $HOME, $EXEDIR
#and variables defined in the OS environment
initial_path $HOME
#if this is set to on, then the menu will remember the last path when visited
#if it's set to off, initial_path will always be used on startup
remember_path on
#path for storing internal screenshots, accepts the same variables as initial_path
screenshot_path $HOME
#see strftime for the format specifiers valid in screenshot_template
screenshot_template blastem_%Y%m%d_%H%M%S.png
#path template for saving SRAM, EEPROM and savestates
#accepts special variables $HOME, $EXEDIR, $USERDATA, $ROMNAME
save_path $USERDATA/blastem/$ROMNAME
#space delimited list of file extensions to filter against in menu
extensions bin gen md smd sms gg zip gz
#specifies the preferred save-state format, set to gst for Genecyst compatible states
state_format native
}
system {
#controls how the emulated system is synced to the host
#video provides the smoothest experience when the host and emulated system have similar refresh rates
#audio provides lower audio latency, especially when there is a refresh rate mismatch
sync_source audio
#set this to random to debug initialization bugs
ram_init zero
default_region U
#controls whether MegaWiFi support is enabled or not
#MegaWiFi allows ROMs to make connections to the internet
#so it should only be enabled for ROMs you trust
megawifi off
}
-1
View File
@@ -1 +0,0 @@
../../images
-1
View File
@@ -1 +0,0 @@
../../menu.bin
-1
View File
@@ -1 +0,0 @@
../../rom.db
-1
View File
@@ -1 +0,0 @@
../../shaders
+25
View File
@@ -0,0 +1,25 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
mavenCentral()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.1.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
mavenCentral()
google()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
-17
View File
@@ -1,17 +0,0 @@
# This file is used to override default values used by the Ant build system.
#
# This file must be checked in Version Control Systems, as it is
# integral to the build system of your project.
# This file is only used by the Ant script.
# You can use this to override default values such as
# 'source.dir' for the location of your java source folder and
# 'out.dir' for the location of your output folder.
# You can also use it define how the release builds are signed by declaring
# the following properties:
# 'key.store' for the location of your keystore and
# 'key.alias' for the name of the key to use.
# The password will be asked during the build when you use the 'release' target.
-93
View File
@@ -1,93 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- This should be changed to the name of your project -->
<project name="BlastEmActivity" default="help">
<!-- The local.properties file is created and updated by the 'android' tool.
It contains the path to the SDK. It should *NOT* be checked into
Version Control Systems. -->
<property file="local.properties" />
<!-- The ant.properties file can be created by you. It is only edited by the
'android' tool to add properties to it.
This is the place to change some Ant specific build properties.
Here are some properties you may want to change/update:
source.dir
The name of the source directory. Default is 'src'.
out.dir
The name of the output directory. Default is 'bin'.
For other overridable properties, look at the beginning of the rules
files in the SDK, at tools/ant/build.xml
Properties related to the SDK location or the project target should
be updated using the 'android' tool with the 'update' action.
This file is an integral part of the build system for your
application and should be checked into Version Control Systems.
-->
<property file="ant.properties" />
<!-- if sdk.dir was not set from one of the property file, then
get it from the ANDROID_HOME env var.
This must be done before we load project.properties since
the proguard config can use sdk.dir -->
<property environment="env" />
<condition property="sdk.dir" value="${env.ANDROID_HOME}">
<isset property="env.ANDROID_HOME" />
</condition>
<!-- The project.properties file is created and updated by the 'android'
tool, as well as ADT.
This contains project specific properties such as project target, and library
dependencies. Lower level build properties are stored in ant.properties
(or in .classpath for Eclipse projects).
This file is an integral part of the build system for your
application and should be checked into Version Control Systems. -->
<loadproperties srcFile="project.properties" />
<!-- quick check on sdk.dir -->
<fail
message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
unless="sdk.dir"
/>
<!--
Import per project custom build rules if present at the root of the project.
This is the place to put custom intermediary targets such as:
-pre-build
-pre-compile
-post-compile (This is typically used for code obfuscation.
Compiled code location: ${out.classes.absolute.dir}
If this is not done in place, override ${out.dex.input.absolute.dir})
-post-package
-post-build
-pre-clean
-->
<import file="custom_rules.xml" optional="true" />
<!-- Import the actual build file.
To customize existing targets, there are two options:
- Customize only one target:
- copy/paste the target into this file, *before* the
<import> task.
- customize it to your needs.
- Customize the whole content of build.xml
- copy/paste the content of the rules files (minus the top node)
into this file, replacing the <import> task.
- customize to your needs.
***********************
****** IMPORTANT ******
***********************
In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
in order to avoid having your file be overridden by tools such as "android update project"
-->
<!-- version-tag: 1 -->
<import file="${sdk.dir}/tools/ant/build.xml" />
</project>
-11
View File
@@ -1,11 +0,0 @@
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system use,
# "build.properties", and override values to adapt the script to your
# project structure.
# Project target.
target=android-12
+17
View File
@@ -0,0 +1,17 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
Binary file not shown.
+6
View File
@@ -0,0 +1,6 @@
#Thu Nov 11 18:20:34 PST 2021
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
Vendored Executable
+160
View File
@@ -0,0 +1,160 @@
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|grep -E -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|grep -E -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
+90
View File
@@ -0,0 +1,90 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
-8
View File
@@ -1,8 +0,0 @@
# Uncomment this if you're using STL in your project
# See CPLUSPLUS-SUPPORT.html in the NDK documentation for more information
# APP_STL := stlport_static
APP_ABI := x86
APP_PLATFORM := android-16
APP_OPTIM := release
-1
View File
@@ -1 +0,0 @@
../..
-20
View File
@@ -1,20 +0,0 @@
# To enable ProGuard in your project, edit project.properties
# to define the proguard.config property as described in that file.
#
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in ${sdk.dir}/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the ProGuard
# include property in project.properties.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
-14
View File
@@ -1,14 +0,0 @@
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system edit
# "ant.properties", and override values to adapt the script to your
# project structure.
#
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
# Project target.
target=android-23
Binary file not shown.

Before

Width:  |  Height:  |  Size: 306 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 240 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 368 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 484 B

-13
View File
@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Hello World, SDLActivity"
/>
</LinearLayout>
+1
View File
@@ -0,0 +1 @@
include ':app'
@@ -1,22 +0,0 @@
package com.retrodev.blastem;
import org.libsdl.app.SDLActivity;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
public class BlastEmActivity extends SDLActivity
{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//set immersive mode on devices that support it
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
View blah = mSurface;
blah.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
);
}
}
}
-37
View File
@@ -1,37 +0,0 @@
package org.libsdl.app;
import android.content.Context;
/**
SDL library initialization
*/
public class SDL {
// This function should be called first and sets up the native code
// so it can call into the Java classes
public static void setupJNI() {
SDLActivity.nativeSetupJNI();
SDLAudioManager.nativeSetupJNI();
SDLControllerManager.nativeSetupJNI();
}
// This function should be called each time the activity is started
public static void initialize() {
setContext(null);
SDLActivity.initialize();
SDLAudioManager.initialize();
SDLControllerManager.initialize();
}
// This function stores the current activity (SDL or not)
public static void setContext(Context context) {
mContext = context;
}
public static Context getContext() {
return mContext;
}
protected static Context mContext;
}
File diff suppressed because it is too large. Load diff
@@ -1,178 +0,0 @@
package org.libsdl.app;
import android.media.*;
import android.util.Log;
public class SDLAudioManager
{
protected static final String TAG = "SDLAudio";
protected static AudioTrack mAudioTrack;
protected static AudioRecord mAudioRecord;
public static void initialize() {
mAudioTrack = null;
mAudioRecord = null;
}
// Audio
/**
* This method is called by SDL using JNI.
*/
public static int audioOpen(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) {
int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO;
int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT;
int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1);
Log.v(TAG, "SDL audio: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + (sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer");
// Let the user pick a larger buffer if they really want -- but ye
// gods they probably shouldn't, the minimums are horrifyingly high
// latency already
desiredFrames = Math.max(desiredFrames, (AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize);
if (mAudioTrack == null) {
mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate,
channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM);
// Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid
// Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java
// Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState()
if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
Log.e(TAG, "Failed during initialization of Audio Track");
mAudioTrack = null;
return -1;
}
mAudioTrack.play();
}
Log.v(TAG, "SDL audio: got " + ((mAudioTrack.getChannelCount() >= 2) ? "stereo" : "mono") + " " + ((mAudioTrack.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " " + (mAudioTrack.getSampleRate() / 1000f) + "kHz, " + desiredFrames + " frames buffer");
return 0;
}
/**
* This method is called by SDL using JNI.
*/
public static void audioWriteShortBuffer(short[] buffer) {
if (mAudioTrack == null) {
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
return;
}
for (int i = 0; i < buffer.length; ) {
int result = mAudioTrack.write(buffer, i, buffer.length - i);
if (result > 0) {
i += result;
} else if (result == 0) {
try {
Thread.sleep(1);
} catch(InterruptedException e) {
// Nom nom
}
} else {
Log.w(TAG, "SDL audio: error return from write(short)");
return;
}
}
}
/**
* This method is called by SDL using JNI.
*/
public static void audioWriteByteBuffer(byte[] buffer) {
if (mAudioTrack == null) {
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
return;
}
for (int i = 0; i < buffer.length; ) {
int result = mAudioTrack.write(buffer, i, buffer.length - i);
if (result > 0) {
i += result;
} else if (result == 0) {
try {
Thread.sleep(1);
} catch(InterruptedException e) {
// Nom nom
}
} else {
Log.w(TAG, "SDL audio: error return from write(byte)");
return;
}
}
}
/**
* This method is called by SDL using JNI.
*/
public static int captureOpen(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) {
int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO;
int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT;
int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1);
Log.v(TAG, "SDL capture: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + (sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer");
// Let the user pick a larger buffer if they really want -- but ye
// gods they probably shouldn't, the minimums are horrifyingly high
// latency already
desiredFrames = Math.max(desiredFrames, (AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize);
if (mAudioRecord == null) {
mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, sampleRate,
channelConfig, audioFormat, desiredFrames * frameSize);
// see notes about AudioTrack state in audioOpen(), above. Probably also applies here.
if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
Log.e(TAG, "Failed during initialization of AudioRecord");
mAudioRecord.release();
mAudioRecord = null;
return -1;
}
mAudioRecord.startRecording();
}
Log.v(TAG, "SDL capture: got " + ((mAudioRecord.getChannelCount() >= 2) ? "stereo" : "mono") + " " + ((mAudioRecord.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " " + (mAudioRecord.getSampleRate() / 1000f) + "kHz, " + desiredFrames + " frames buffer");
return 0;
}
/** This method is called by SDL using JNI. */
public static int captureReadShortBuffer(short[] buffer, boolean blocking) {
// !!! FIXME: this is available in API Level 23. Until then, we always block. :(
//return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
return mAudioRecord.read(buffer, 0, buffer.length);
}
/** This method is called by SDL using JNI. */
public static int captureReadByteBuffer(byte[] buffer, boolean blocking) {
// !!! FIXME: this is available in API Level 23. Until then, we always block. :(
//return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
return mAudioRecord.read(buffer, 0, buffer.length);
}
/** This method is called by SDL using JNI. */
public static void audioClose() {
if (mAudioTrack != null) {
mAudioTrack.stop();
mAudioTrack.release();
mAudioTrack = null;
}
}
/** This method is called by SDL using JNI. */
public static void captureClose() {
if (mAudioRecord != null) {
mAudioRecord.stop();
mAudioRecord.release();
mAudioRecord = null;
}
}
public static native int nativeSetupJNI();
}
@@ -1,433 +0,0 @@
package org.libsdl.app;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import android.content.Context;
import android.os.*;
import android.view.*;
import android.util.Log;
public class SDLControllerManager
{
public static native int nativeSetupJNI();
public static native int nativeAddJoystick(int device_id, String name, String desc,
int is_accelerometer, int nbuttons,
int naxes, int nhats, int nballs);
public static native int nativeRemoveJoystick(int device_id);
public static native int nativeAddHaptic(int device_id, String name);
public static native int nativeRemoveHaptic(int device_id);
public static native int onNativePadDown(int device_id, int keycode);
public static native int onNativePadUp(int device_id, int keycode);
public static native void onNativeJoy(int device_id, int axis,
float value);
public static native void onNativeHat(int device_id, int hat_id,
int x, int y);
protected static SDLJoystickHandler mJoystickHandler;
protected static SDLHapticHandler mHapticHandler;
private static final String TAG = "SDLControllerManager";
public static void initialize() {
mJoystickHandler = null;
mHapticHandler = null;
SDLControllerManager.setup();
}
public static void setup() {
if (Build.VERSION.SDK_INT >= 16) {
mJoystickHandler = new SDLJoystickHandler_API16();
} else if (Build.VERSION.SDK_INT >= 12) {
mJoystickHandler = new SDLJoystickHandler_API12();
} else {
mJoystickHandler = new SDLJoystickHandler();
}
mHapticHandler = new SDLHapticHandler();
}
// Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance
public static boolean handleJoystickMotionEvent(MotionEvent event) {
return mJoystickHandler.handleMotionEvent(event);
}
/**
* This method is called by SDL using JNI.
*/
public static void pollInputDevices() {
mJoystickHandler.pollInputDevices();
}
/**
* This method is called by SDL using JNI.
*/
public static void pollHapticDevices() {
mHapticHandler.pollHapticDevices();
}
/**
* This method is called by SDL using JNI.
*/
public static void hapticRun(int device_id, int length) {
mHapticHandler.run(device_id, length);
}
// Check if a given device is considered a possible SDL joystick
public static boolean isDeviceSDLJoystick(int deviceId) {
InputDevice device = InputDevice.getDevice(deviceId);
// We cannot use InputDevice.isVirtual before API 16, so let's accept
// only nonnegative device ids (VIRTUAL_KEYBOARD equals -1)
if ((device == null) || (deviceId < 0)) {
return false;
}
int sources = device.getSources();
if ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) == InputDevice.SOURCE_CLASS_JOYSTICK) {
Log.v(TAG, "Input device " + device.getName() + " is a joystick.");
}
if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) {
Log.v(TAG, "Input device " + device.getName() + " is a dpad.");
}
if ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) {
Log.v(TAG, "Input device " + device.getName() + " is a gamepad.");
}
return (((sources & InputDevice.SOURCE_CLASS_JOYSTICK) == InputDevice.SOURCE_CLASS_JOYSTICK) ||
((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) ||
((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)
);
}
}
/* A null joystick handler for API level < 12 devices (the accelerometer is handled separately) */
class SDLJoystickHandler {
/**
* Handles given MotionEvent.
* @param event the event to be handled.
* @return if given event was processed.
*/
public boolean handleMotionEvent(MotionEvent event) {
return false;
}
/**
* Handles adding and removing of input devices.
*/
public void pollInputDevices() {
}
}
/* Actual joystick functionality available for API >= 12 devices */
class SDLJoystickHandler_API12 extends SDLJoystickHandler {
static class SDLJoystick {
public int device_id;
public String name;
public String desc;
public ArrayList<InputDevice.MotionRange> axes;
public ArrayList<InputDevice.MotionRange> hats;
}
static class RangeComparator implements Comparator<InputDevice.MotionRange> {
@Override
public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) {
return arg0.getAxis() - arg1.getAxis();
}
}
private ArrayList<SDLJoystick> mJoysticks;
public SDLJoystickHandler_API12() {
mJoysticks = new ArrayList<SDLJoystick>();
}
@Override
public void pollInputDevices() {
int[] deviceIds = InputDevice.getDeviceIds();
// It helps processing the device ids in reverse order
// For example, in the case of the XBox 360 wireless dongle,
// so the first controller seen by SDL matches what the receiver
// considers to be the first controller
for(int i=deviceIds.length-1; i>-1; i--) {
SDLJoystick joystick = getJoystick(deviceIds[i]);
if (joystick == null) {
joystick = new SDLJoystick();
InputDevice joystickDevice = InputDevice.getDevice(deviceIds[i]);
if (SDLControllerManager.isDeviceSDLJoystick(deviceIds[i])) {
joystick.device_id = deviceIds[i];
joystick.name = joystickDevice.getName();
joystick.desc = getJoystickDescriptor(joystickDevice);
joystick.axes = new ArrayList<InputDevice.MotionRange>();
joystick.hats = new ArrayList<InputDevice.MotionRange>();
List<InputDevice.MotionRange> ranges = joystickDevice.getMotionRanges();
Collections.sort(ranges, new RangeComparator());
for (InputDevice.MotionRange range : ranges ) {
if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
if (range.getAxis() == MotionEvent.AXIS_HAT_X ||
range.getAxis() == MotionEvent.AXIS_HAT_Y) {
joystick.hats.add(range);
}
else {
joystick.axes.add(range);
}
}
}
mJoysticks.add(joystick);
SDLControllerManager.nativeAddJoystick(joystick.device_id, joystick.name, joystick.desc, 0, -1,
joystick.axes.size(), joystick.hats.size()/2, 0);
}
}
}
/* Check removed devices */
ArrayList<Integer> removedDevices = new ArrayList<Integer>();
for(int i=0; i < mJoysticks.size(); i++) {
int device_id = mJoysticks.get(i).device_id;
int j;
for (j=0; j < deviceIds.length; j++) {
if (device_id == deviceIds[j]) break;
}
if (j == deviceIds.length) {
removedDevices.add(Integer.valueOf(device_id));
}
}
for(int i=0; i < removedDevices.size(); i++) {
int device_id = removedDevices.get(i).intValue();
SDLControllerManager.nativeRemoveJoystick(device_id);
for (int j=0; j < mJoysticks.size(); j++) {
if (mJoysticks.get(j).device_id == device_id) {
mJoysticks.remove(j);
break;
}
}
}
}
protected SDLJoystick getJoystick(int device_id) {
for(int i=0; i < mJoysticks.size(); i++) {
if (mJoysticks.get(i).device_id == device_id) {
return mJoysticks.get(i);
}
}
return null;
}
@Override
public boolean handleMotionEvent(MotionEvent event) {
if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0) {
int actionPointerIndex = event.getActionIndex();
int action = event.getActionMasked();
switch(action) {
case MotionEvent.ACTION_MOVE:
SDLJoystick joystick = getJoystick(event.getDeviceId());
if ( joystick != null ) {
for (int i = 0; i < joystick.axes.size(); i++) {
InputDevice.MotionRange range = joystick.axes.get(i);
/* Normalize the value to -1...1 */
float value = ( event.getAxisValue( range.getAxis(), actionPointerIndex) - range.getMin() ) / range.getRange() * 2.0f - 1.0f;
SDLControllerManager.onNativeJoy(joystick.device_id, i, value );
}
for (int i = 0; i < joystick.hats.size(); i+=2) {
int hatX = Math.round(event.getAxisValue( joystick.hats.get(i).getAxis(), actionPointerIndex ) );
int hatY = Math.round(event.getAxisValue( joystick.hats.get(i+1).getAxis(), actionPointerIndex ) );
SDLControllerManager.onNativeHat(joystick.device_id, i/2, hatX, hatY );
}
}
break;
default:
break;
}
}
return true;
}
public String getJoystickDescriptor(InputDevice joystickDevice) {
return joystickDevice.getName();
}
}
class SDLJoystickHandler_API16 extends SDLJoystickHandler_API12 {
@Override
public String getJoystickDescriptor(InputDevice joystickDevice) {
String desc = joystickDevice.getDescriptor();
if (desc != null && !Objects.equals(desc, "")) {
return desc;
}
return super.getJoystickDescriptor(joystickDevice);
}
}
class SDLHapticHandler {
class SDLHaptic {
public int device_id;
public String name;
public Vibrator vib;
}
private ArrayList<SDLHaptic> mHaptics;
public SDLHapticHandler() {
mHaptics = new ArrayList<SDLHaptic>();
}
public void run(int device_id, int length) {
SDLHaptic haptic = getHaptic(device_id);
if (haptic != null) {
haptic.vib.vibrate (length);
}
}
public void pollHapticDevices() {
final int deviceId_VIBRATOR_SERVICE = 999999;
boolean hasVibratorService = false;
int[] deviceIds = InputDevice.getDeviceIds();
// It helps processing the device ids in reverse order
// For example, in the case of the XBox 360 wireless dongle,
// so the first controller seen by SDL matches what the receiver
// considers to be the first controller
if (Build.VERSION.SDK_INT >= 16)
{
for (int i = deviceIds.length - 1; i > -1; i--) {
SDLHaptic haptic = getHaptic(deviceIds[i]);
if (haptic == null) {
InputDevice device = InputDevice.getDevice(deviceIds[i]);
Vibrator vib = device.getVibrator();
if (vib.hasVibrator()) {
haptic = new SDLHaptic();
haptic.device_id = deviceIds[i];
haptic.name = device.getName();
haptic.vib = vib;
mHaptics.add(haptic);
SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
}
}
}
}
/* Check VIBRATOR_SERVICE */
Vibrator vib = (Vibrator) SDL.getContext().getSystemService(Context.VIBRATOR_SERVICE);
if (vib != null) {
if (Build.VERSION.SDK_INT >= 11) {
hasVibratorService = vib.hasVibrator();
} else {
hasVibratorService = true;
}
if (hasVibratorService) {
SDLHaptic haptic = getHaptic(deviceId_VIBRATOR_SERVICE);
if (haptic == null) {
haptic = new SDLHaptic();
haptic.device_id = deviceId_VIBRATOR_SERVICE;
haptic.name = "VIBRATOR_SERVICE";
haptic.vib = vib;
mHaptics.add(haptic);
SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
}
}
}
/* Check removed devices */
ArrayList<Integer> removedDevices = new ArrayList<Integer>();
for(int i=0; i < mHaptics.size(); i++) {
int device_id = mHaptics.get(i).device_id;
int j;
for (j=0; j < deviceIds.length; j++) {
if (device_id == deviceIds[j]) break;
}
if (device_id == deviceId_VIBRATOR_SERVICE && hasVibratorService) {
// don't remove the vibrator if it is still present
} else if (j == deviceIds.length) {
removedDevices.add(device_id);
}
}
for(int i=0; i < removedDevices.size(); i++) {
int device_id = removedDevices.get(i);
SDLControllerManager.nativeRemoveHaptic(device_id);
for (int j=0; j < mHaptics.size(); j++) {
if (mHaptics.get(j).device_id == device_id) {
mHaptics.remove(j);
break;
}
}
}
}
protected SDLHaptic getHaptic(int device_id) {
for(int i=0; i < mHaptics.size(); i++) {
if (mHaptics.get(i).device_id == device_id) {
return mHaptics.get(i);
}
}
return null;
}
}
class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener {
// Generic Motion (mouse hover, joystick...) events go here
@Override
public boolean onGenericMotion(View v, MotionEvent event) {
float x, y;
int action;
switch ( event.getSource() ) {
case InputDevice.SOURCE_JOYSTICK:
case InputDevice.SOURCE_GAMEPAD:
case InputDevice.SOURCE_DPAD:
return SDLControllerManager.handleJoystickMotionEvent(event);
case InputDevice.SOURCE_MOUSE:
if (!SDLActivity.mSeparateMouseAndTouch) {
break;
}
action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_SCROLL:
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
SDLActivity.onNativeMouse(0, action, x, y);
return true;
case MotionEvent.ACTION_HOVER_MOVE:
x = event.getX(0);
y = event.getY(0);
SDLActivity.onNativeMouse(0, action, x, y);
return true;
default:
break;
}
break;
default:
break;
}
// Event was not managed
return false;
}
}
+760 -10
View File
@@ -10,7 +10,7 @@ deferred_addr * defer_address(deferred_addr * old_head, uint32_t address, uint8_
{
deferred_addr * new_head = malloc(sizeof(deferred_addr));
new_head->next = old_head;
new_head->address = address & 0xFFFFFF;
new_head->address = address;
new_head->dest = dest;
return new_head;
}
@@ -56,11 +56,11 @@ memmap_chunk const *find_map_chunk(uint32_t address, cpu_options *opts, uint16_t
if (size_sum) {
*size_sum = 0;
}
uint32_t minsize;
uint32_t size_round_mask;
if (flags == MMAP_CODE) {
minsize = 1 << (opts->ram_flags_shift + 3);
size_round_mask = (1 << (opts->ram_flags_shift + 3)) - 1;
} else {
minsize = 0;
size_round_mask = 0;
}
address &= opts->address_mask;
for (memmap_chunk const *cur = opts->memmap, *end = opts->memmap + opts->memmap_chunks; cur != end; cur++)
@@ -69,8 +69,11 @@ memmap_chunk const *find_map_chunk(uint32_t address, cpu_options *opts, uint16_t
return cur;
} else if (size_sum && (cur->flags & flags) == flags) {
uint32_t size = chunk_size(opts, cur);
if (size < minsize) {
size = minsize;
if (size_round_mask) {
if (size & size_round_mask) {
size &= ~size_round_mask;
size += size_round_mask + 1;
}
}
*size_sum += size;
}
@@ -93,11 +96,23 @@ void * get_native_pointer(uint32_t address, void ** mem_pointers, cpu_options *
: memmap[chunk].buffer;
if (!base) {
if (memmap[chunk].flags & MMAP_AUX_BUFF) {
return ((uint8_t *)memmap[chunk].buffer) + (address & memmap[chunk].aux_mask);
address &= memmap[chunk].aux_mask;
if (memmap[chunk].shift > 0) {
address <<= memmap[chunk].shift;
} else if (memmap[chunk].shift < 0) {
address >>= -memmap[chunk].shift;
}
return ((uint8_t *)memmap[chunk].buffer) + address;
}
return NULL;
}
return base + (address & memmap[chunk].mask);
address &= memmap[chunk].mask;
if (memmap[chunk].shift > 0) {
address <<= memmap[chunk].shift;
} else if (memmap[chunk].shift < 0) {
address >>= -memmap[chunk].shift;
}
return base + address;
}
}
return NULL;
@@ -118,11 +133,23 @@ void * get_native_write_pointer(uint32_t address, void ** mem_pointers, cpu_opti
: memmap[chunk].buffer;
if (!base) {
if (memmap[chunk].flags & MMAP_AUX_BUFF) {
return ((uint8_t *)memmap[chunk].buffer) + (address & memmap[chunk].aux_mask);
address &= memmap[chunk].aux_mask;
if (memmap[chunk].shift > 0) {
address <<= memmap[chunk].shift;
} else if (memmap[chunk].shift < 0) {
address >>= -memmap[chunk].shift;
}
return ((uint8_t *)memmap[chunk].buffer) + address;
}
return NULL;
}
return base + (address & memmap[chunk].mask);
address &= memmap[chunk].mask;
if (memmap[chunk].shift > 0) {
address <<= memmap[chunk].shift;
} else if (memmap[chunk].shift < 0) {
address >>= -memmap[chunk].shift;
}
return base + address;
}
}
return NULL;
@@ -135,6 +162,55 @@ uint16_t read_word(uint32_t address, void **mem_pointers, cpu_options *opts, voi
return 0xFFFF;
}
uint32_t offset = address & chunk->mask;
if (chunk->shift > 0) {
offset <<= chunk->shift;
} else if (chunk->shift < 0){
offset >>= -chunk->shift;
}
if (chunk->flags & MMAP_READ) {
uint8_t *base;
if (chunk->flags & MMAP_PTR_IDX) {
base = mem_pointers[chunk->ptr_index];
} else {
base = chunk->buffer;
}
if (base) {
uint16_t val;
if ((chunk->flags & MMAP_ONLY_ODD) || (chunk->flags & MMAP_ONLY_EVEN)) {
offset /= 2;
val = base[offset];
if (chunk->flags & MMAP_ONLY_ODD) {
val |= 0xFF00;
} else {
val = val << 8 | 0xFF;
}
} else {
val = *(uint16_t *)(base + offset);
}
return val;
}
}
if ((!(chunk->flags & MMAP_READ) || (chunk->flags & MMAP_FUNC_NULL)) && chunk->read_16) {
return chunk->read_16(offset, context);
}
return 0xFFFF;
}
uint16_t read_word_cycles(uint32_t address, void **mem_pointers, cpu_options *opts, void *context, uint32_t *cycles)
{
memmap_chunk const *chunk = find_map_chunk(address, opts, 0, NULL);
if (!chunk) {
return 0xFFFF;
}
uint32_t offset = address & chunk->mask;
if (chunk->shift > 0) {
offset <<= chunk->shift;
} else if (chunk->shift < 0){
offset >>= -chunk->shift;
}
if (chunk->read_cycles) {
*cycles += chunk->read_cycles * opts->clock_divider;
}
if (chunk->flags & MMAP_READ) {
uint8_t *base;
if (chunk->flags & MMAP_PTR_IDX) {
@@ -171,6 +247,52 @@ void write_word(uint32_t address, uint16_t value, void **mem_pointers, cpu_optio
return;
}
uint32_t offset = address & chunk->mask;
if (chunk->shift > 0) {
offset <<= chunk->shift;
} else if (chunk->shift < 0){
offset >>= -chunk->shift;
}
if (chunk->flags & MMAP_WRITE) {
uint8_t *base;
if (chunk->flags & MMAP_PTR_IDX) {
base = mem_pointers[chunk->ptr_index];
} else {
base = chunk->buffer;
}
if (base) {
if ((chunk->flags & MMAP_ONLY_ODD) || (chunk->flags & MMAP_ONLY_EVEN)) {
offset /= 2;
if (chunk->flags & MMAP_ONLY_EVEN) {
value >>= 16;
}
base[offset] = value;
} else {
*(uint16_t *)(base + offset) = value;
}
return;
}
}
if ((!(chunk->flags & MMAP_WRITE) || (chunk->flags & MMAP_FUNC_NULL)) && chunk->write_16) {
chunk->write_16(offset, context, value);
}
}
void write_word_cycles(uint32_t address, uint16_t value, void **mem_pointers, cpu_options *opts, void *context, uint32_t *cycles)
{
memmap_chunk const *chunk = find_map_chunk(address, opts, 0, NULL);
if (!chunk) {
return;
}
uint32_t offset = address & chunk->mask;
if (chunk->shift > 0) {
offset <<= chunk->shift;
} else if (chunk->shift < 0){
offset >>= -chunk->shift;
}
if (chunk->write_cycles) {
*cycles += chunk->write_cycles * opts->clock_divider;
}
if (chunk->flags & MMAP_WRITE) {
uint8_t *base;
if (chunk->flags & MMAP_PTR_IDX) {
@@ -203,6 +325,66 @@ uint8_t read_byte(uint32_t address, void **mem_pointers, cpu_options *opts, void
return 0xFF;
}
uint32_t offset = address & chunk->mask;
if (offset) {
uint32_t low_bit = offset & 1;
offset &= ~1;
if (chunk->shift > 0) {
offset <<= chunk->shift;
} else {
offset >>= -chunk->shift;
}
offset |= low_bit;
}
if (chunk->flags & MMAP_READ) {
uint8_t *base;
if (chunk->flags & MMAP_PTR_IDX) {
base = mem_pointers[chunk->ptr_index];
} else {
base = chunk->buffer;
}
if (base) {
if ((chunk->flags & MMAP_ONLY_ODD) || (chunk->flags & MMAP_ONLY_EVEN)) {
if (address & 1) {
if (chunk->flags & MMAP_ONLY_EVEN) {
return 0xFF;
}
} else if (chunk->flags & MMAP_ONLY_ODD) {
return 0xFF;
}
offset /= 2;
} else if(opts->byte_swap) {
offset ^= 1;
}
return base[offset];
}
}
if ((!(chunk->flags & MMAP_READ) || (chunk->flags & MMAP_FUNC_NULL)) && chunk->read_8) {
return chunk->read_8(offset, context);
}
return 0xFF;
}
uint8_t read_byte_cycles(uint32_t address, void **mem_pointers, cpu_options *opts, void *context, uint32_t *cycles)
{
memmap_chunk const *chunk = find_map_chunk(address, opts, 0, NULL);
if (!chunk) {
return 0xFF;
}
uint32_t offset = address & chunk->mask;
if (offset) {
uint32_t low_bit = offset & 1;
offset &= ~1;
if (chunk->shift > 0) {
offset <<= chunk->shift;
} else {
offset >>= -chunk->shift;
}
offset |= low_bit;
}
if (chunk->read_cycles) {
*cycles += chunk->read_cycles * opts->clock_divider;
}
if (chunk->flags & MMAP_READ) {
uint8_t *base;
if (chunk->flags & MMAP_PTR_IDX) {
@@ -239,6 +421,64 @@ void write_byte(uint32_t address, uint8_t value, void **mem_pointers, cpu_option
return;
}
uint32_t offset = address & chunk->mask;
if (chunk->shift) {
uint32_t low_bit = offset & 1;
offset &= ~1;
if (chunk->shift > 0) {
offset <<= chunk->shift;
} else {
offset >>= -chunk->shift;
}
offset |= low_bit;
}
if (chunk->flags & MMAP_WRITE) {
uint8_t *base;
if (chunk->flags & MMAP_PTR_IDX) {
base = mem_pointers[chunk->ptr_index];
} else {
base = chunk->buffer;
}
if (base) {
if ((chunk->flags & MMAP_ONLY_ODD) || (chunk->flags & MMAP_ONLY_EVEN)) {
if (address & 1) {
if (chunk->flags & MMAP_ONLY_EVEN) {
return;
}
} else if (chunk->flags & MMAP_ONLY_ODD) {
return;
}
offset /= 2;
} else if(opts->byte_swap) {
offset ^= 1;
}
base[offset] = value;
}
}
if ((!(chunk->flags & MMAP_WRITE) || (chunk->flags & MMAP_FUNC_NULL)) && chunk->write_8) {
chunk->write_8(offset, context, value);
}
}
void write_byte_cycles(uint32_t address, uint8_t value, void **mem_pointers, cpu_options *opts, void *context, uint32_t *cycles)
{
memmap_chunk const *chunk = find_map_chunk(address, opts, 0, NULL);
if (!chunk) {
return;
}
uint32_t offset = address & chunk->mask;
if (chunk->shift) {
uint32_t low_bit = offset & 1;
offset &= ~1;
if (chunk->shift > 0) {
offset <<= chunk->shift;
} else {
offset >>= -chunk->shift;
}
offset |= low_bit;
}
if (chunk->write_cycles) {
*cycles += chunk->write_cycles * opts->clock_divider;
}
if (chunk->flags & MMAP_WRITE) {
uint8_t *base;
if (chunk->flags & MMAP_PTR_IDX) {
@@ -293,3 +533,513 @@ uint32_t ram_size(cpu_options *opts)
}
return size;
}
uint16_t interp_read_direct_16(uint32_t address, void *context, void *data)
{
return *(uint16_t *)((address & 0xFFFE) + (uint8_t *)data);
}
uint8_t interp_read_direct_8(uint32_t address, void *context, void *data)
{
return ((uint8_t *)data)[(address & 0xFFFF) ^ 1];
}
void interp_write_direct_16(uint32_t address, void *context, uint16_t value, void *data)
{
*(uint16_t *)((address & 0xFFFE) + (uint8_t *)data) = value;
}
void interp_write_direct_8(uint32_t address, void *context, uint8_t value, void *data)
{
((uint8_t *)data)[(address & 0xFFFF) ^ 1] = value;
}
uint16_t interp_read_indexed_16(uint32_t address, void *context, void *data)
{
return *(uint16_t *)((*(uint8_t **)data) + (address & 0xFFFE));
}
uint8_t interp_read_indexed_8(uint32_t address, void *context, void *data)
{
return (*(uint8_t **)data)[(address & 0xFFFF) ^ 1];
}
void interp_write_indexed_16(uint32_t address, void *context, uint16_t value, void *data)
{
*(uint16_t *)((*(uint8_t **)data) + (address & 0xFFFE)) = value;
}
void interp_write_indexed_8(uint32_t address, void *context, uint8_t value, void *data)
{
(*(uint8_t **)data)[(address & 0xFFFF) ^ 1] = value;
}
uint16_t interp_read_fixed_16(uint32_t address, void *context, void *data)
{
return (uintptr_t)data;
}
uint8_t interp_read_fixed_8(uint32_t address, void *context, void *data)
{
uint16_t val = (uintptr_t)data;
if (address & 1) {
return val;
}
return val >> 8;
}
void interp_write_ignored_16(uint32_t address, void *context, uint16_t value, void *data)
{
}
void interp_write_ignored_8(uint32_t address, void *context, uint8_t value, void *data)
{
}
uint16_t interp_read_func_16(uint32_t address, void *context, void *data)
{
read_16_fun f = (read_16_fun)data;
return f(address, context);
}
uint8_t interp_read_func_8(uint32_t address, void *context, void *data)
{
read_8_fun f = (read_8_fun)data;
return f(address, context);
}
void interp_write_func_16(uint32_t address, void *context, uint16_t value, void *data)
{
write_16_fun f = (write_16_fun)data;
f(address, context, value);
}
void interp_write_func_8(uint32_t address, void *context, uint8_t value, void *data)
{
write_8_fun f = (write_8_fun)data;
f(address, context, value);
}
uint16_t interp_read_map_16(uint32_t address, void *context, void *data)
{
const memmap_chunk *chunk = data;
cpu_options * opts = *(cpu_options **)context;
if (address < chunk->start || address >= chunk->end)
{
const memmap_chunk *map_end = opts->memmap + opts->memmap_chunks;
for (chunk++; chunk < map_end; chunk++)
{
if (address >= chunk->start && address < chunk->end) {
break;
}
}
if (chunk == map_end) {
return 0xFFFF;
}
}
uint32_t offset = address & chunk->mask;
if (chunk->shift > 0) {
offset <<= chunk->shift;
} else if (chunk->shift < 0){
offset >>= -chunk->shift;
}
if (chunk->flags & MMAP_READ) {
uint8_t *base;
if (chunk->flags & MMAP_PTR_IDX) {
uint8_t ** mem_pointers = (uint8_t**)(opts->mem_ptr_off + (uint8_t *)context);
base = mem_pointers[chunk->ptr_index];
} else {
base = chunk->buffer;
}
if (base) {
uint16_t val;
if ((chunk->flags & MMAP_ONLY_ODD) || (chunk->flags & MMAP_ONLY_EVEN)) {
offset /= 2;
val = base[offset];
if (chunk->flags & MMAP_ONLY_ODD) {
val |= 0xFF00;
} else {
val = val << 8 | 0xFF;
}
} else {
val = *(uint16_t *)(base + offset);
}
return val;
}
}
if ((!(chunk->flags & MMAP_READ) || (chunk->flags & MMAP_FUNC_NULL)) && chunk->read_16) {
return chunk->read_16(offset, context);
}
return 0xFFFF;
}
uint8_t interp_read_map_8(uint32_t address, void *context, void *data)
{
const memmap_chunk *chunk = data;
cpu_options * opts = *(cpu_options **)context;
if (address < chunk->start || address >= chunk->end)
{
const memmap_chunk *map_end = opts->memmap + opts->memmap_chunks;
for (chunk++; chunk < map_end; chunk++)
{
if (address >= chunk->start && address < chunk->end) {
break;
}
}
if (chunk == map_end) {
return 0xFF;
}
}
uint32_t offset = address & chunk->mask;
if (chunk->shift) {
uint32_t low_bit = offset & 1;
offset &= ~1;
if (chunk->shift > 0) {
offset <<= chunk->shift;
} else {
offset >>= -chunk->shift;
}
offset |= low_bit;
}
if (chunk->flags & MMAP_READ) {
uint8_t *base;
if (chunk->flags & MMAP_PTR_IDX) {
uint8_t ** mem_pointers = (uint8_t**)(opts->mem_ptr_off + (uint8_t *)context);
base = mem_pointers[chunk->ptr_index];
} else {
base = chunk->buffer;
}
if (base) {
if ((chunk->flags & MMAP_ONLY_ODD) || (chunk->flags & MMAP_ONLY_EVEN)) {
if (address & 1) {
if (chunk->flags & MMAP_ONLY_EVEN) {
return 0xFF;
}
} else if (chunk->flags & MMAP_ONLY_ODD) {
return 0xFF;
}
offset /= 2;
} else if(opts->byte_swap) {
offset ^= 1;
}
return base[offset];
}
}
if ((!(chunk->flags & MMAP_READ) || (chunk->flags & MMAP_FUNC_NULL)) && chunk->read_8) {
return chunk->read_8(offset, context);
}
return 0xFF;
}
void interp_write_map_16(uint32_t address, void *context, uint16_t value, void *data)
{
const memmap_chunk *chunk = data;
cpu_options * opts = *(cpu_options **)context;
if (address < chunk->start || address >= chunk->end)
{
const memmap_chunk *map_end = opts->memmap + opts->memmap_chunks;
for (chunk++; chunk < map_end; chunk++)
{
if (address >= chunk->start && address < chunk->end) {
break;
}
}
if (chunk == map_end) {
return;
}
}
uint32_t offset = address & chunk->mask;
if (chunk->shift > 0) {
offset <<= chunk->shift;
} else if (chunk->shift < 0){
offset >>= -chunk->shift;
}
if (chunk->flags & MMAP_WRITE) {
uint8_t *base;
if (chunk->flags & MMAP_PTR_IDX) {
uint8_t ** mem_pointers = (uint8_t**)(opts->mem_ptr_off + (uint8_t *)context);
base = mem_pointers[chunk->ptr_index];
} else {
base = chunk->buffer;
}
if (base) {
if ((chunk->flags & MMAP_ONLY_ODD) || (chunk->flags & MMAP_ONLY_EVEN)) {
offset /= 2;
if (chunk->flags & MMAP_ONLY_EVEN) {
value >>= 16;
}
base[offset] = value;
} else {
*(uint16_t *)(base + offset) = value;
}
return;
}
}
if ((!(chunk->flags & MMAP_WRITE) || (chunk->flags & MMAP_FUNC_NULL)) && chunk->write_16) {
chunk->write_16(offset, context, value);
}
}
void interp_write_map_8(uint32_t address, void *context, uint8_t value, void *data)
{
const memmap_chunk *chunk = data;
cpu_options * opts = *(cpu_options **)context;
if (address < chunk->start || address >= chunk->end)
{
const memmap_chunk *map_end = opts->memmap + opts->memmap_chunks;
for (chunk++; chunk < map_end; chunk++)
{
if (address >= chunk->start && address < chunk->end) {
break;
}
}
if (chunk == map_end) {
return;
}
}
uint32_t offset = address & chunk->mask;
if (chunk->shift) {
uint32_t low_bit = offset & 1;
offset &= ~1;
if (chunk->shift > 0) {
offset <<= chunk->shift;
} else {
offset >>= -chunk->shift;
}
offset |= low_bit;
}
if (chunk->flags & MMAP_WRITE) {
uint8_t *base;
if (chunk->flags & MMAP_PTR_IDX) {
uint8_t ** mem_pointers = (uint8_t**)(opts->mem_ptr_off + (uint8_t *)context);
base = mem_pointers[chunk->ptr_index];
} else {
base = chunk->buffer;
}
if (base) {
if ((chunk->flags & MMAP_ONLY_ODD) || (chunk->flags & MMAP_ONLY_EVEN)) {
if (address & 1) {
if (chunk->flags & MMAP_ONLY_EVEN) {
return;
}
} else if (chunk->flags & MMAP_ONLY_ODD) {
return;
}
offset /= 2;
} else if(opts->byte_swap) {
offset ^= 1;
}
base[offset] = value;
}
}
if ((!(chunk->flags & MMAP_WRITE) || (chunk->flags & MMAP_FUNC_NULL)) && chunk->write_8) {
chunk->write_8(offset, context, value);
}
}
interp_read_16 get_interp_read_16(void *context, cpu_options *opts, uint32_t start, uint32_t end, void **data_out)
{
const memmap_chunk *chunk;
for (chunk = opts->memmap; chunk < opts->memmap + opts->memmap_chunks; chunk++)
{
if (chunk->end > start && chunk->start < end) {
break;
}
}
if (chunk == opts->memmap + opts->memmap_chunks) {
*data_out = (void *)(uintptr_t)0xFFFF;
return interp_read_fixed_16;
}
if (chunk->end < end || chunk->start > start || chunk->shift) {
goto use_map;
}
if (chunk->flags & MMAP_READ) {
if ((chunk->flags & (MMAP_ONLY_ODD|MMAP_ONLY_EVEN|MMAP_FUNC_NULL))) {
goto use_map;
}
if (!chunk->mask && !(chunk->flags & ~MMAP_READ)) {
uintptr_t value = *(uint16_t *)chunk->buffer;
*data_out = (void *)value;
return interp_read_fixed_16;
}
if ((chunk->mask & 0xFFFF) != 0xFFFF) {
goto use_map;
}
if (chunk->flags & MMAP_PTR_IDX) {
if (chunk->mask != 0xFFFF && start > 0) {
goto use_map;
}
*data_out = (void *)(chunk->ptr_index + (void **)(((char *)context) + opts->mem_ptr_off));
return interp_read_indexed_16;
} else {
*data_out = (start & chunk->mask) + (uint8_t *)chunk->buffer;
return interp_read_direct_16;
}
}
if (chunk->read_16 && chunk->mask == opts->address_mask) {
#ifdef __EMSCRIPTEN__
*data_out = chunk->read_16;
return interp_read_func_16;
#else
*data_out = NULL;
//This is not safe for all calling conventions due to the extra param
//but should work for the ones we actually care about except WASM
return (interp_read_16)chunk->read_16;
#endif
}
use_map:
*data_out = (void *)chunk;
return interp_read_map_16;
}
interp_read_8 get_interp_read_8(void *context, cpu_options *opts, uint32_t start, uint32_t end, void **data_out)
{
const memmap_chunk *chunk;
for (chunk = opts->memmap; chunk < opts->memmap + opts->memmap_chunks; chunk++)
{
if (chunk->end > start && chunk->start < end) {
break;
}
}
if (chunk == opts->memmap + opts->memmap_chunks) {
*data_out = (void *)(uintptr_t)0xFFFF;
return interp_read_fixed_8;
}
if (chunk->end != end || chunk->start != start || chunk->shift) {
goto use_map;
}
if (chunk->flags & MMAP_READ) {
if ((chunk->flags & (MMAP_ONLY_ODD|MMAP_ONLY_EVEN|MMAP_FUNC_NULL)) || !opts->byte_swap) {
goto use_map;
}
if (!chunk->mask && !(chunk->flags & ~MMAP_READ)) {
uintptr_t value = *(uint8_t *)chunk->buffer;
*data_out = (void *)value;
return interp_read_fixed_8;
}
if ((chunk->mask & 0xFFFF) != 0xFFFF) {
goto use_map;
}
if (chunk->flags & MMAP_PTR_IDX) {
if (chunk->mask != 0xFFFF && start > 0) {
goto use_map;
}
*data_out = (void *)(chunk->ptr_index + (void **)(((char *)context) + opts->mem_ptr_off));
return interp_read_indexed_8;
} else {
*data_out = (start & chunk->mask) + (uint8_t *)chunk->buffer;
return interp_read_direct_8;
}
}
if (chunk->read_8 && chunk->mask == opts->address_mask) {
#ifdef __EMSCRIPTEN__
*data_out = chunk->read_8;
return interp_read_func_8;
#else
*data_out = NULL;
//This is not safe for all calling conventions due to the extra param
//but should work for the ones we actually care about except WASM
return (interp_read_8)chunk->read_8;
#endif
}
use_map:
*data_out = (void *)chunk;
return interp_read_map_8;
}
interp_write_16 get_interp_write_16(void *context, cpu_options *opts, uint32_t start, uint32_t end, void **data_out)
{
const memmap_chunk *chunk;
for (chunk = opts->memmap; chunk < opts->memmap + opts->memmap_chunks; chunk++)
{
if (chunk->end > start && chunk->start < end) {
break;
}
}
if (chunk == opts->memmap + opts->memmap_chunks) {
*data_out = NULL;
return interp_write_ignored_16;
}
if (chunk->end != end || chunk->start != start || chunk->shift) {
goto use_map;
}
if (chunk->flags & MMAP_WRITE) {
if ((chunk->flags & (MMAP_ONLY_ODD|MMAP_ONLY_EVEN|MMAP_FUNC_NULL)) || (chunk->mask & 0xFFFF) != 0xFFFF) {
goto use_map;
}
if (chunk->flags & MMAP_PTR_IDX) {
if (chunk->mask != 0xFFFF && start > 0) {
goto use_map;
}
*data_out = (void *)(chunk->ptr_index + (void **)(((char *)context) + opts->mem_ptr_off));
return interp_write_indexed_16;
} else {
*data_out = (start & chunk->mask) + (uint8_t *)chunk->buffer;
return interp_write_direct_16;
}
}
if (chunk->write_16 && chunk->mask == opts->address_mask) {
#ifdef __EMSCRIPTEN__
*data_out = chunk->write_16;
return interp_write_func_16;
#else
*data_out = NULL;
//This is not safe for all calling conventions due to the extra param
//but should work for the ones we actually care about except WASM
return (interp_write_16)chunk->write_16;
#endif
}
use_map:
*data_out = (void *)chunk;
return interp_write_map_16;
}
interp_write_8 get_interp_write_8(void *context, cpu_options *opts, uint32_t start, uint32_t end, void **data_out)
{
const memmap_chunk *chunk;
for (chunk = opts->memmap; chunk < opts->memmap + opts->memmap_chunks; chunk++)
{
if (chunk->end > start && chunk->start < end) {
break;
}
}
if (chunk == opts->memmap + opts->memmap_chunks) {
*data_out = NULL;
return interp_write_ignored_8;
}
if (chunk->end != end || chunk->start != start || chunk->shift) {
goto use_map;
}
if (chunk->flags & MMAP_WRITE) {
if ((chunk->flags & (MMAP_ONLY_ODD|MMAP_ONLY_EVEN|MMAP_FUNC_NULL))
|| (chunk->mask & 0xFFFF) != 0xFFFF || !opts->byte_swap
) {
goto use_map;
}
if (chunk->flags & MMAP_PTR_IDX) {
if (chunk->mask != 0xFFFF && start > 0) {
goto use_map;
}
*data_out = (void *)(chunk->ptr_index + (void **)(((char *)context) + opts->mem_ptr_off));
return interp_write_indexed_8;
} else {
*data_out = (start & chunk->mask) + (uint8_t *)chunk->buffer;
return interp_write_direct_8;
}
}
if (chunk->write_8 && chunk->mask == opts->address_mask) {
#ifdef __EMSCRIPTEN__
*data_out = chunk->write_8;
return interp_write_func_8;
#else
*data_out = NULL;
//This is not safe for all calling conventions due to the extra param
//but should work for the ones we actually care about
return (interp_write_8)chunk->write_8;
#endif
}
use_map:
*data_out = (void *)chunk;
return interp_write_map_8;
}
+29 -1
View File
@@ -43,6 +43,9 @@ typedef struct deferred_addr {
#include "memmap.h"
#include "system.h"
typedef void * (*watchpoint16_fun)(uint32_t address, void * context, uint16_t);
typedef void * (*watchpoint8_fun)(uint32_t address, void * context, uint8_t);
typedef struct {
uint32_t flags;
native_map_slot *native_code_map;
@@ -57,6 +60,8 @@ typedef struct {
code_ptr handle_code_write;
code_ptr handle_align_error_write;
code_ptr handle_align_error_read;
watchpoint16_fun check_watchpoints_16;
watchpoint8_fun check_watchpoints_8;
system_str_fun_r8 debug_cmd_handler;
uint32_t memmap_chunks;
uint32_t address_mask;
@@ -65,7 +70,9 @@ typedef struct {
uint32_t clock_divider;
uint32_t move_pc_off;
uint32_t move_pc_size;
int32_t watchpoint_range_off;
int32_t mem_ptr_off;
int32_t cycles_off;
int32_t ram_flags_off;
uint8_t ram_flags_shift;
uint8_t address_size;
@@ -78,8 +85,19 @@ typedef struct {
uint8_t align_error_mask;
} cpu_options;
typedef void (*debug_handler)(void *context, uint32_t pc);
typedef struct {
debug_handler handler;
uint32_t address;
} breakpoint;
typedef uint8_t * (*native_addr_func)(void * context, uint32_t address);
typedef uint16_t (*interp_read_16)(uint32_t address, void *context, void *data);
typedef uint8_t (*interp_read_8)(uint32_t address, void *context, void *data);
typedef void (*interp_write_16)(uint32_t address, void *context, uint16_t value, void *data);
typedef void (*interp_write_8)(uint32_t address, void *context, uint8_t value, void *data);
deferred_addr * defer_address(deferred_addr * old_head, uint32_t address, uint8_t *dest);
void remove_deferred_until(deferred_addr **head_ptr, deferred_addr * remove_to);
void process_deferred(deferred_addr ** head_ptr, void * context, native_addr_func get_native);
@@ -92,17 +110,27 @@ void log_address(cpu_options *opts, uint32_t address, char * format);
void retranslate_calc(cpu_options *opts);
void patch_for_retranslate(cpu_options *opts, code_ptr native_address, code_ptr handler);
void defer_translation(cpu_options *opts, uint32_t address, code_ptr handler);
code_ptr gen_mem_fun(cpu_options * opts, memmap_chunk const * memmap, uint32_t num_chunks, ftype fun_type, code_ptr *after_inc);
code_ptr gen_mem_fun(cpu_options * opts, memmap_chunk const * memmap, uint32_t num_chunks, ftype fun_type, code_ptr *after_inc, uint8_t from_c);
code_ptr gen_burst_read(cpu_options * opts, memmap_chunk const * memmap, uint32_t num_chunks);
void * get_native_pointer(uint32_t address, void ** mem_pointers, cpu_options * opts);
void * get_native_write_pointer(uint32_t address, void ** mem_pointers, cpu_options * opts);
uint16_t read_word(uint32_t address, void **mem_pointers, cpu_options *opts, void *context);
uint16_t read_word_cycles(uint32_t address, void **mem_pointers, cpu_options *opts, void *context, uint32_t *cycles);
void write_word(uint32_t address, uint16_t value, void **mem_pointers, cpu_options *opts, void *context);
void write_word_cycles(uint32_t address, uint16_t value, void **mem_pointers, cpu_options *opts, void *context, uint32_t *cycles);
uint8_t read_byte(uint32_t address, void **mem_pointers, cpu_options *opts, void *context);
uint8_t read_byte_cycles(uint32_t address, void **mem_pointers, cpu_options *opts, void *context, uint32_t *cycles);
void write_byte(uint32_t address, uint8_t value, void **mem_pointers, cpu_options *opts, void *context);
void write_byte_cycles(uint32_t address, uint8_t value, void **mem_pointers, cpu_options *opts, void *context, uint32_t *cycles);
memmap_chunk const *find_map_chunk(uint32_t address, cpu_options *opts, uint16_t flags, uint32_t *size_sum);
uint32_t chunk_size(cpu_options *opts, memmap_chunk const *chunk);
uint32_t ram_size(cpu_options *opts);
interp_read_16 get_interp_read_16(void *context, cpu_options *opts, uint32_t start, uint32_t end, void **data_out);
interp_read_8 get_interp_read_8(void *context, cpu_options *opts, uint32_t start, uint32_t end, void **data_out);
interp_write_16 get_interp_write_16(void *context, cpu_options *opts, uint32_t start, uint32_t end, void **data_out);
interp_write_8 get_interp_write_8(void *context, cpu_options *opts, uint32_t start, uint32_t end, void **data_out);
#endif //BACKEND_H_
+606 -89
View File
@@ -4,7 +4,9 @@
void cycles(cpu_options *opts, uint32_t num)
{
if (opts->limit < 0) {
if (opts->cycles < 0) {
add_irdisp(&opts->code, num*opts->clock_divider, opts->context_reg, opts->cycles_off, SZ_D);
} else if (opts->limit < 0) {
sub_ir(&opts->code, num*opts->clock_divider, opts->cycles, SZ_D);
} else {
add_ir(&opts->code, num*opts->clock_divider, opts->cycles, SZ_D);
@@ -64,6 +66,12 @@ void patch_for_retranslate(cpu_options *opts, code_ptr native_address, code_ptr
jmp(&tmp, handler);
}
void defer_translation(cpu_options *opts, uint32_t address, code_ptr handler)
{
mov_ir(&opts->code, address, opts->scratch1, SZ_D);
jmp(&opts->code, handler);
}
void check_cycles(cpu_options * opts)
{
code_info *code = &opts->code;
@@ -75,11 +83,12 @@ void check_cycles(cpu_options * opts)
cmp_rr(code, opts->cycles, opts->limit, SZ_D);
cc = CC_A;
}
check_alloc_code(code, MAX_INST_LEN*2);
code_ptr jmp_off = code->cur+1;
code_ptr jmp_off;
ALLOC_CODE_RETRY_POINT
jmp_off = code->cur+1;
jcc(code, cc, jmp_off+1);
call(code, opts->handle_cycle_limit);
*jmp_off = code->cur - (jmp_off+1);
CHECK_BRANCH_DEST(jmp_off);
}
void log_address(cpu_options *opts, uint32_t address, char * format)
@@ -88,7 +97,7 @@ void log_address(cpu_options *opts, uint32_t address, char * format)
call(code, opts->save_context);
push_r(code, opts->context_reg);
mov_rr(code, opts->cycles, RDX, SZ_D);
mov_ir(code, (int64_t)format, RDI, SZ_PTR);
mov_ir(code, (intptr_t)format, RDI, SZ_PTR);
mov_ir(code, address, RSI, SZ_D);
call_args_abi(code, (code_ptr)printf, 3, RDI, RSI, RDX);
pop_r(code, opts->context_reg);
@@ -100,46 +109,121 @@ void check_code_prologue(code_info *code)
check_alloc_code(code, MAX_INST_LEN*4);
}
code_ptr gen_mem_fun(cpu_options * opts, memmap_chunk const * memmap, uint32_t num_chunks, ftype fun_type, code_ptr *after_inc)
code_ptr gen_mem_fun(cpu_options * opts, memmap_chunk const * memmap, uint32_t num_chunks, ftype fun_type, code_ptr *after_inc, uint8_t from_c)
{
code_info *code = &opts->code;
code_ptr start = code->cur;
check_cycles(opts);
uint8_t is_write = fun_type == WRITE_16 || fun_type == WRITE_8;
uint8_t adr_reg = is_write ? opts->scratch2 : opts->scratch1;
uint8_t adr_reg, context_reg, value_reg, dst_reg, tmp_context_reg;
uint8_t size = (fun_type == READ_16 || fun_type == WRITE_16) ? SZ_W : SZ_B;
if (from_c) {
#if defined(X86_64)
#if defined(_WIN32)
//RCX, RDX, R8
adr_reg = RCX;
context_reg = RDX;
value_reg = is_write ? R8 : RAX;
#else
//RDI, RSI, RDX
adr_reg = RDI;
context_reg = RSI;
value_reg = is_write ? RDX : RAX;
#endif
#else
//rtl on stack, EAX, ECX, EDX are caller saved
adr_reg = RCX;
context_reg = RDX;
value_reg = RAX;
mov_rr(code, RSP, RAX, SZ_D);
mov_rdispr(code, RAX, 4, adr_reg, opts->address_size);
mov_rdispr(code, RAX, 8, context_reg, SZ_D);
if (is_write) {
mov_rdispr(code, RAX, 12, value_reg, size);
}
#endif
tmp_context_reg = opts->context_reg;
opts->context_reg = context_reg;
} else {
adr_reg = is_write ? opts->scratch2 : opts->scratch1;
value_reg = opts->scratch1;
context_reg = opts->context_reg;
check_cycles(opts);
}
if (size != SZ_B && opts->align_error_mask) {
test_ir(code, opts->align_error_mask, adr_reg, SZ_D);
jcc(code, CC_NZ, is_write ? opts->handle_align_error_write : opts->handle_align_error_read);
}
cycles(opts, opts->bus_cycles);
if (opts->bus_cycles) {
cycles(opts, opts->bus_cycles);
}
if (after_inc) {
*after_inc = code->cur;
}
if (opts->address_size == SZ_D && opts->address_mask != 0xFFFFFFFF) {
and_ir(code, opts->address_mask, adr_reg, SZ_D);
} else if (opts->address_size == SZ_W && opts->address_mask != 0xFFFF) {
and_ir(code, opts->address_mask, adr_reg, SZ_W);
}
code_ptr check_watchpoints = size == SZ_W ? (code_ptr)opts->check_watchpoints_16 : (code_ptr)opts->check_watchpoints_8;
if (is_write && check_watchpoints) {
//watchpoints are enabled, check if the address is within the watchpoint range
cmp_rdispr(code, context_reg, opts->watchpoint_range_off, adr_reg, opts->address_size);
code_ptr watch_lb = code->cur + 1;
jcc(code, CC_C, code->cur + 2);
cmp_rdispr(code, context_reg, opts->watchpoint_range_off + (opts->address_size == SZ_D ? 4 : 2), adr_reg, opts->address_size);
code_ptr watch_ub = code->cur + 1;
jcc(code, CC_A, code->cur + 2);
push_r(code, value_reg);
push_r(code, adr_reg);
if (!from_c) {
call(code, opts->save_context);
}
call_args_abi(code, check_watchpoints, 3, adr_reg, context_reg, value_reg);
mov_rr(code, RAX, context_reg, SZ_PTR);
if (!from_c) {
call(code, opts->load_context);
}
pop_r(code, adr_reg);
pop_r(code, value_reg);
*watch_lb = code->cur - (watch_lb + 1);
*watch_ub = code->cur - (watch_ub + 1);
}
code_ptr lb_jcc = NULL, ub_jcc = NULL;
uint16_t access_flag = is_write ? MMAP_WRITE : MMAP_READ;
uint32_t ram_flags_off = opts->ram_flags_off;
uint32_t min_address = 0;
uint32_t max_address = opts->max_address;
uint8_t need_wide_jcc = 0;
for (uint32_t chunk = 0; chunk < num_chunks; chunk++)
{
code_info chunk_start = *code;
if (memmap[chunk].start > min_address) {
cmp_ir(code, memmap[chunk].start, adr_reg, opts->address_size);
lb_jcc = code->cur + 1;
jcc(code, CC_C, code->cur + 2);
if (need_wide_jcc) {
jcc(code, CC_C, code->cur + 130);
lb_jcc++;
} else {
jcc(code, CC_C, code->cur + 2);
}
} else {
min_address = memmap[chunk].end;
}
if (memmap[chunk].end < max_address) {
cmp_ir(code, memmap[chunk].end, adr_reg, opts->address_size);
ub_jcc = code->cur + 1;
jcc(code, CC_NC, code->cur + 2);
if (need_wide_jcc) {
jcc(code, CC_NC, code->cur + 130);
ub_jcc++;
} else {
jcc(code, CC_NC, code->cur + 2);
}
} else {
max_address = memmap[chunk].start;
}
@@ -147,6 +231,47 @@ code_ptr gen_mem_fun(cpu_options * opts, memmap_chunk const * memmap, uint32_t n
if (memmap[chunk].mask != opts->address_mask) {
and_ir(code, memmap[chunk].mask, adr_reg, opts->address_size);
}
if (!is_write && memmap[chunk].read_cycles) {
cycles(opts, memmap[chunk].read_cycles);
} else if (is_write && memmap[chunk].write_cycles) {
cycles(opts, memmap[chunk].write_cycles);
}
code_ptr after_normal = NULL;
uint8_t need_addr_pop = 0;
if (size == SZ_B && memmap[chunk].shift != 0) {
if (is_write && (memmap[chunk].flags & MMAP_CODE) && !from_c) {
push_r(code, adr_reg);
need_addr_pop = 1;
}
btr_ir(code, 0, adr_reg, opts->address_size);
code_ptr normal = code->cur+1;
jcc(code, CC_NC, normal);
if (memmap[chunk].shift > 0) {
shl_ir(code, memmap[chunk].shift, adr_reg, opts->address_size);
} else {
shr_ir(code, -memmap[chunk].shift, adr_reg, opts->address_size);
}
or_ir(code, 1, adr_reg, opts->address_size);
after_normal = code->cur + 1;
jmp(code, after_normal);
*normal = code->cur - (normal + 1);
}
if (memmap[chunk].shift > 0) {
if (!need_addr_pop && is_write && (memmap[chunk].flags & MMAP_CODE) && !from_c) {
push_r(code, adr_reg);
need_addr_pop = 1;
}
shl_ir(code, memmap[chunk].shift, adr_reg, opts->address_size);
} else if (memmap[chunk].shift < 0) {
if (!need_addr_pop && is_write && (memmap[chunk].flags & MMAP_CODE) && !from_c) {
push_r(code, adr_reg);
need_addr_pop = 1;
}
shr_ir(code, -memmap[chunk].shift, adr_reg, opts->address_size);
}
if (after_normal) {
*after_normal = code->cur - (after_normal + 1);
}
void * cfun;
switch (fun_type)
{
@@ -166,52 +291,62 @@ code_ptr gen_mem_fun(cpu_options * opts, memmap_chunk const * memmap, uint32_t n
cfun = NULL;
}
if(memmap[chunk].flags & access_flag) {
uint8_t tmp_size = size;
if (memmap[chunk].flags & MMAP_PTR_IDX) {
if (memmap[chunk].flags & MMAP_FUNC_NULL) {
cmp_irdisp(code, 0, opts->context_reg, opts->mem_ptr_off + sizeof(void*) * memmap[chunk].ptr_index, SZ_PTR);
if (memmap[chunk].flags & (MMAP_FUNC_NULL | MMAP_AUX_BUFF)) {
cmp_irdisp(code, 0, context_reg, opts->mem_ptr_off + sizeof(void*) * memmap[chunk].ptr_index, SZ_PTR);
code_ptr not_null = code->cur + 1;
jcc(code, CC_NZ, code->cur + 2);
call(code, opts->save_context);
if (is_write) {
call_args_abi(code, cfun, 3, opts->scratch2, opts->context_reg, opts->scratch1);
mov_rr(code, RAX, opts->context_reg, SZ_PTR);
if (memmap[chunk].flags & MMAP_FUNC_NULL) {
uint32_t stack_off;
if (need_addr_pop) {
stack_off = code->stack_off;
pop_r(code, adr_reg);
}
if (!from_c) {
call(code, opts->save_context);
}
if (is_write) {
call_args_abi(code, cfun, 3, adr_reg, context_reg, value_reg);
if (!from_c) {
mov_rr(code, RAX, context_reg, SZ_PTR);
}
} else {
if (!from_c) {
push_r(code, context_reg);
}
call_args_abi(code, cfun, 2, adr_reg, context_reg);
if (!from_c) {
pop_r(code, context_reg);
}
if (value_reg != RAX) {
mov_rr(code, RAX, value_reg, size);
}
}
if (from_c) {
retn(code);
} else {
jmp(code, opts->load_context);
}
if (need_addr_pop) {
code->stack_off = stack_off;
}
} else {
push_r(code, opts->context_reg);
call_args_abi(code, cfun, 2, opts->scratch1, opts->context_reg);
pop_r(code, opts->context_reg);
mov_rr(code, RAX, opts->scratch1, size);
if (!is_write) {
mov_ir(code, size == SZ_B ? 0xFF : 0xFFFF, value_reg, size);
}
retn(code);
}
jmp(code, opts->load_context);
*not_null = code->cur - (not_null + 1);
}
if ((opts->byte_swap || memmap[chunk].flags & MMAP_BYTESWAP) && size == SZ_B) {
xor_ir(code, 1, adr_reg, opts->address_size);
}
if (opts->address_size != SZ_D) {
movzx_rr(code, adr_reg, adr_reg, opts->address_size, SZ_D);
}
if (is_write && (memmap[chunk].flags & MMAP_CODE)) {
push_r(code, adr_reg);
}
add_rdispr(code, opts->context_reg, opts->mem_ptr_off + sizeof(void*) * memmap[chunk].ptr_index, adr_reg, SZ_PTR);
if (is_write) {
mov_rrind(code, opts->scratch1, opts->scratch2, size);
if (memmap[chunk].flags & MMAP_CODE) {
pop_r(code, adr_reg);
}
} else {
mov_rindr(code, opts->scratch1, opts->scratch1, size);
}
} else {
uint8_t tmp_size = size;
if (size == SZ_B) {
if ((memmap[chunk].flags & MMAP_ONLY_ODD) || (memmap[chunk].flags & MMAP_ONLY_EVEN)) {
bt_ir(code, 0, adr_reg, opts->address_size);
code_ptr good_addr = code->cur + 1;
jcc(code, (memmap[chunk].flags & MMAP_ONLY_ODD) ? CC_C : CC_NC, code->cur + 2);
if (!is_write) {
mov_ir(code, 0xFF, opts->scratch1, SZ_B);
mov_ir(code, 0xFF, value_reg, SZ_B);
}
retn(code);
*good_addr = code->cur - (good_addr + 1);
@@ -223,7 +358,50 @@ code_ptr gen_mem_fun(cpu_options * opts, memmap_chunk const * memmap, uint32_t n
tmp_size = SZ_B;
shr_ir(code, 1, adr_reg, opts->address_size);
if ((memmap[chunk].flags & MMAP_ONLY_EVEN) && is_write) {
shr_ir(code, 8, opts->scratch1, SZ_W);
shr_ir(code, 8, value_reg, SZ_W);
}
}
if (opts->address_size != SZ_D) {
movzx_rr(code, adr_reg, adr_reg, opts->address_size, SZ_D);
}
if (!need_addr_pop && is_write && (memmap[chunk].flags & MMAP_CODE) && !from_c) {
push_r(code, adr_reg);
need_addr_pop = 1;
}
add_rdispr(code, context_reg, opts->mem_ptr_off + sizeof(void*) * memmap[chunk].ptr_index, adr_reg, SZ_PTR);
if (is_write) {
mov_rrind(code, value_reg, adr_reg, tmp_size);
} else {
mov_rindr(code, adr_reg, value_reg, tmp_size);
}
if (size != tmp_size && !is_write) {
if (memmap[chunk].flags & MMAP_ONLY_EVEN) {
shl_ir(code, 8, value_reg, SZ_W);
mov_ir(code, 0xFF, value_reg, SZ_B);
} else {
or_ir(code, 0xFF00, value_reg, SZ_W);
}
}
} else {
if (size == SZ_B) {
if ((memmap[chunk].flags & MMAP_ONLY_ODD) || (memmap[chunk].flags & MMAP_ONLY_EVEN)) {
bt_ir(code, 0, adr_reg, opts->address_size);
code_ptr good_addr = code->cur + 1;
jcc(code, (memmap[chunk].flags & MMAP_ONLY_ODD) ? CC_C : CC_NC, code->cur + 2);
if (!is_write) {
mov_ir(code, 0xFF, value_reg, SZ_B);
}
retn(code);
*good_addr = code->cur - (good_addr + 1);
shr_ir(code, 1, adr_reg, opts->address_size);
} else if (opts->byte_swap || memmap[chunk].flags & MMAP_BYTESWAP) {
xor_ir(code, 1, adr_reg, opts->address_size);
}
} else if ((memmap[chunk].flags & MMAP_ONLY_ODD) || (memmap[chunk].flags & MMAP_ONLY_EVEN)) {
tmp_size = SZ_B;
shr_ir(code, 1, adr_reg, opts->address_size);
if ((memmap[chunk].flags & MMAP_ONLY_EVEN) && is_write) {
shr_ir(code, 8, value_reg, SZ_W);
}
}
if (opts->address_size != SZ_D) {
@@ -231,98 +409,437 @@ code_ptr gen_mem_fun(cpu_options * opts, memmap_chunk const * memmap, uint32_t n
}
if ((intptr_t)memmap[chunk].buffer <= 0x7FFFFFFF && (intptr_t)memmap[chunk].buffer >= -2147483648) {
if (is_write) {
mov_rrdisp(code, opts->scratch1, opts->scratch2, (intptr_t)memmap[chunk].buffer, tmp_size);
mov_rrdisp(code, value_reg, adr_reg, (intptr_t)memmap[chunk].buffer, tmp_size);
} else {
mov_rdispr(code, opts->scratch1, (intptr_t)memmap[chunk].buffer, opts->scratch1, tmp_size);
mov_rdispr(code, adr_reg, (intptr_t)memmap[chunk].buffer, value_reg, tmp_size);
}
} else {
if (is_write) {
push_r(code, opts->scratch2);
mov_ir(code, (intptr_t)memmap[chunk].buffer, opts->scratch2, SZ_PTR);
add_rdispr(code, RSP, 0, opts->scratch2, SZ_PTR);
mov_rrind(code, opts->scratch1, opts->scratch2, tmp_size);
if (is_write && (memmap[chunk].flags & MMAP_CODE)) {
pop_r(code, opts->scratch2);
push_r(code, adr_reg);
mov_ir(code, (intptr_t)memmap[chunk].buffer, adr_reg, SZ_PTR);
add_rdispr(code, RSP, 0, adr_reg, SZ_PTR);
mov_rrind(code, value_reg, adr_reg, tmp_size);
if (is_write && (memmap[chunk].flags & MMAP_CODE) && !from_c) {
need_addr_pop = 1;
} else {
add_ir(code, sizeof(void*), RSP, SZ_PTR);
code->stack_off -= sizeof(void *);
}
} else {
push_r(code, opts->scratch2);
mov_ir(code, (intptr_t)memmap[chunk].buffer, opts->scratch2, SZ_PTR);
mov_rindexr(code, opts->scratch2, opts->scratch1, 1, opts->scratch1, tmp_size);
pop_r(code, opts->scratch2);
if (from_c) {
mov_ir(code, (intptr_t)memmap[chunk].buffer, value_reg, SZ_PTR);
mov_rindexr(code, value_reg, adr_reg, 1, value_reg, tmp_size);
} else {
push_r(code, opts->scratch2);
mov_ir(code, (intptr_t)memmap[chunk].buffer, opts->scratch2, SZ_PTR);
mov_rindexr(code, opts->scratch2, opts->scratch1, 1, opts->scratch1, tmp_size);
pop_r(code, opts->scratch2);
}
}
}
if (size != tmp_size && !is_write) {
if (memmap[chunk].flags & MMAP_ONLY_EVEN) {
shl_ir(code, 8, opts->scratch1, SZ_W);
mov_ir(code, 0xFF, opts->scratch1, SZ_B);
shl_ir(code, 8, value_reg, SZ_W);
mov_ir(code, 0xFF, value_reg, SZ_B);
} else {
or_ir(code, 0xFF00, opts->scratch1, SZ_W);
or_ir(code, 0xFF00, value_reg, SZ_W);
}
}
}
if (is_write && (memmap[chunk].flags & MMAP_CODE)) {
if (is_write && (memmap[chunk].flags & MMAP_CODE) && !from_c) {
//FIXME: make this work in the from_c case
if (need_addr_pop) {
pop_r(code, adr_reg);
}
mov_rr(code, opts->scratch2, opts->scratch1, opts->address_size);
shr_ir(code, opts->ram_flags_shift, opts->scratch1, opts->address_size);
bt_rrdisp(code, opts->scratch1, opts->context_reg, ram_flags_off, opts->address_size);
bt_rrdisp(code, opts->scratch1, context_reg, ram_flags_off, opts->address_size);
code_ptr not_code = code->cur + 1;
jcc(code, CC_NC, code->cur + 2);
if (memmap[chunk].mask != opts->address_mask) {
or_ir(code, memmap[chunk].start, opts->scratch2, opts->address_size);
}
call(code, opts->save_context);
call_args(code, opts->handle_code_write, 2, opts->scratch2, opts->context_reg);
mov_rr(code, RAX, opts->context_reg, SZ_PTR);
call_args(code, opts->handle_code_write, 2, opts->scratch2, context_reg);
mov_rr(code, RAX, context_reg, SZ_PTR);
jmp(code, opts->load_context);
*not_code = code->cur - (not_code+1);
}
retn(code);
} else if (cfun) {
call(code, opts->save_context);
if (!from_c) {
call(code, opts->save_context);
}
if (is_write) {
call_args_abi(code, cfun, 3, opts->scratch2, opts->context_reg, opts->scratch1);
mov_rr(code, RAX, opts->context_reg, SZ_PTR);
call_args_abi(code, cfun, 3, adr_reg, context_reg, value_reg);
if (!from_c) {
mov_rr(code, RAX, context_reg, SZ_PTR);
}
} else {
push_r(code, opts->context_reg);
call_args_abi(code, cfun, 2, opts->scratch1, opts->context_reg);
pop_r(code, opts->context_reg);
mov_rr(code, RAX, opts->scratch1, size);
if (!from_c) {
push_r(code, context_reg);
}
call_args_abi(code, cfun, 2, adr_reg, context_reg);
if (!from_c) {
pop_r(code, context_reg);
}
if (value_reg != RAX) {
mov_rr(code, RAX, value_reg, size);
}
}
if (from_c) {
retn(code);
} else {
jmp(code, opts->load_context);
}
jmp(code, opts->load_context);
} else {
//Not sure the best course of action here
if (!is_write) {
mov_ir(code, size == SZ_B ? 0xFF : 0xFFFF, opts->scratch1, size);
mov_ir(code, size == SZ_B ? 0xFF : 0xFFFF, value_reg, size);
}
retn(code);
}
if (memmap[chunk].flags & MMAP_CODE) {
uint32_t added_offset;
if (memmap[chunk].mask == opts->address_mask) {
added_offset = (memmap[chunk].end - memmap[chunk].start) / (1 << opts->ram_flags_shift) / 8;
} else {
added_offset = (memmap[chunk].mask + 1) / (1 << opts->ram_flags_shift) / 8;
}
if (added_offset) {
ram_flags_off += added_offset;
} else {
ram_flags_off += 1;
}
}
if (lb_jcc) {
*lb_jcc = code->cur - (lb_jcc+1);
if (need_wide_jcc) {
*((int32_t*)lb_jcc) = code->cur - (lb_jcc+4);
} else if (code->cur - (lb_jcc+1) > 0x7f) {
need_wide_jcc = 1;
chunk--;
*code = chunk_start;
continue;
} else {
*lb_jcc = code->cur - (lb_jcc+1);
}
lb_jcc = NULL;
}
if (ub_jcc) {
*ub_jcc = code->cur - (ub_jcc+1);
if (need_wide_jcc) {
*((int32_t*)ub_jcc) = code->cur - (ub_jcc+4);
} else if (code->cur - (ub_jcc+1) > 0x7f) {
need_wide_jcc = 1;
chunk--;
*code = chunk_start;
continue;
} else {
*ub_jcc = code->cur - (ub_jcc+1);
}
ub_jcc = NULL;
}
if (memmap[chunk].flags & MMAP_CODE) {
uint32_t size = chunk_size(opts, memmap + chunk);
uint32_t size_round_mask = (1 << (opts->ram_flags_shift + 3)) - 1;
if (size & size_round_mask) {
size &= ~size_round_mask;
size += size_round_mask + 1;
}
ram_flags_off += size >> (opts->ram_flags_shift + 3);
}
if (need_wide_jcc) {
need_wide_jcc = 0;
}
}
if (!is_write) {
mov_ir(code, size == SZ_B ? 0xFF : 0xFFFF, opts->scratch1, size);
mov_ir(code, size == SZ_B ? 0xFF : 0xFFFF, value_reg, size);
}
retn(code);
if (from_c) {
opts->context_reg = tmp_context_reg;
}
return start;
}
code_ptr gen_burst_read(cpu_options * opts, memmap_chunk const * memmap, uint32_t num_chunks)
{
code_info *code = &opts->code;
code_ptr start = code->cur;
uint8_t context_reg, adr_reg, dst_reg, tmp_context_reg;
//assumes caller ensures burst alignment
//currently assumes 16-byte/8-word burst needed for SH2
//from_c is assumed for now
#if defined(X86_64)
#if defined(_WIN32)
//RCX, RDX, R8
adr_reg = RCX;
context_reg = RDX;
dst_reg = R8;
#else
//RDI, RSI, RDX
adr_reg = RDI;
context_reg = RSI;
dst_reg = RDX;
#endif
#else
//rtl on stack, EAX, ECX, EDX are caller saved
adr_reg = RCX;
context_reg = RDX;
dst_reg = RAX;
mov_rr(code, RSP, RAX, SZ_D);
mov_rdispr(code, RAX, 4, adr_reg, opts->address_size);
mov_rdispr(code, RAX, 8, context_reg, SZ_PTR);
mov_rdispr(code, RAX, 12, dst_reg, SZ_PTR);
#endif
tmp_context_reg = opts->context_reg;
opts->context_reg = context_reg;
if (opts->address_size == SZ_D && opts->address_mask != 0xFFFFFFFF) {
and_ir(code, opts->address_mask, adr_reg, SZ_D);
} else if (opts->address_size == SZ_W && opts->address_mask != 0xFFFF) {
and_ir(code, opts->address_mask, adr_reg, SZ_W);
}
code_ptr lb_jcc = NULL, ub_jcc = NULL;
uint32_t min_address = 0;
uint32_t max_address = opts->max_address;
uint8_t use_sse2 = cpu_has_sse2();
uint8_t need_wide_jcc = 0;
for (uint32_t chunk = 0; chunk < num_chunks; chunk++)
{
code_info chunk_start = *code;
if (memmap[chunk].start > min_address) {
cmp_ir(code, memmap[chunk].start, adr_reg, opts->address_size);
lb_jcc = code->cur + 1;
if (need_wide_jcc) {
jcc(code, CC_C, code->cur + 130);
lb_jcc++;
} else {
jcc(code, CC_C, code->cur + 2);
}
} else {
min_address = memmap[chunk].end;
}
if (memmap[chunk].end < max_address) {
cmp_ir(code, memmap[chunk].end, adr_reg, opts->address_size);
ub_jcc = code->cur + 1;
if (need_wide_jcc) {
jcc(code, CC_NC, code->cur + 130);
ub_jcc++;
} else {
jcc(code, CC_NC, code->cur + 2);
}
} else {
max_address = memmap[chunk].start;
}
if (memmap[chunk].mask != opts->address_mask) {
and_ir(code, memmap[chunk].mask, adr_reg, opts->address_size);
}
if (memmap[chunk].burst_cycles) {
cycles(opts, memmap[chunk].burst_cycles);
}
if (memmap[chunk].flags & MMAP_READ) {
uint8_t need_pop_rbp = 0;
if (memmap[chunk].flags & MMAP_PTR_IDX) {
if (memmap[chunk].flags & MMAP_FUNC_NULL) {
cmp_irdisp(code, 0, context_reg, opts->mem_ptr_off + sizeof(void*) * memmap[chunk].ptr_index, SZ_PTR);
code_ptr not_null = code->cur + 1;
jcc(code, CC_NZ, code->cur + 2);
push_r(code, RBX);//counter
#if defined(X86_64)
push_r(code, RBP);
push_r(code, R12);
push_r(code, R13);
mov_rr(code, dst_reg, RBP, SZ_PTR);
uint8_t my_dst_reg = RBP;
mov_rr(code, adr_reg, R12, opts->address_size);
uint8_t my_adr_reg = R12;
mov_rr(code, context_reg, R13, SZ_PTR);
uint8_t my_context_reg = R13;
#else
push_r(code, RBP);
push_r(code, RDI);
push_r(code, RSI);
mov_rr(code, dst_reg, RBP, SZ_PTR);
uint8_t my_dst_reg = RBP;
mov_rr(code, adr_reg, RDI, opts->address_size);
uint8_t my_adr_reg = RDI;
mov_rr(code, context_reg, RSI, SZ_PTR);
uint8_t my_context_reg = RSI;
#endif
mov_ir(code, 8, RBX, SZ_D);
code_ptr loop_top = code->cur;
//caller saved
//32-bit: EAX, ECX, EDX
//SYSV 64: RAX, RCX, RDX, RDI, RSI, R8, R9, R10, R11
//WIN64: RAX, RCX, RDX, R8, R9, R10, R11
//callee saved
//32-bit: EBX, EDI, ESI, EBP
//SYSV 64: RBX, RBP, R12-R15
//WIN64: RBX, RDI, RSI, RBP, R12-R15
xor_ir(code, 2, my_dst_reg, SZ_PTR);
call_args_abi(code, (code_ptr)memmap[chunk].read_16, 2, my_adr_reg, my_context_reg);
mov_rrind(code, RAX, my_dst_reg, SZ_W);
xor_ir(code, 2, my_dst_reg, SZ_PTR);
add_ir(code, 2, my_dst_reg, SZ_PTR);
add_ir(code, 2, my_adr_reg, opts->address_size);
dec_r(code, RBX, SZ_D);
jcc(code, CC_NZ, loop_top);
#if defined(X86_64)
pop_r(code, R13);
pop_r(code, R12);
pop_r(code, RBP);
#else
pop_r(code, RSI);
pop_r(code, RDI);
pop_r(code, RBP);
#endif
pop_r(code, RBX);
retn(code);
*not_null = code->cur - (not_null + 1);
}
if (opts->address_size != SZ_D) {
movzx_rr(code, adr_reg, adr_reg, opts->address_size, SZ_D);
}
add_rdispr(code, context_reg, opts->mem_ptr_off + sizeof(void*) * memmap[chunk].ptr_index, adr_reg, SZ_PTR);
//TODO: Don't shuffle if byteswap is not set
if (use_sse2) {
// 10 11 00 01
pshuflw_rindr(code, adr_reg, XMM0, 0xB1);
} else {
pshufw_rindr(code, adr_reg, MM0, 0xB1);
pshufw_rdispr(code, adr_reg, 8, MM1, 0xB1);
}
} else {
if (opts->address_size != SZ_D) {
movzx_rr(code, adr_reg, adr_reg, opts->address_size, SZ_D);
}
#if defined(X86_64)
if ((intptr_t)memmap[chunk].buffer <= 0x7FFFFFFF && (intptr_t)memmap[chunk].buffer >= -2147483648) {
#endif
//TODO: Don't shuffle if byteswap is not set
if (use_sse2) {
pshuflw_rdispr(code, adr_reg, (intptr_t)memmap[chunk].buffer, XMM0, 0xB1);
} else {
pshufw_rdispr(code, adr_reg, (intptr_t)memmap[chunk].buffer, MM0, 0xB1);
pshufw_rdispr(code, adr_reg, ((intptr_t)memmap[chunk].buffer) + 8, MM1, 0xB1);
}
#if defined(X86_64)
} else {
mov_ir(code, (intptr_t)memmap[chunk].buffer, RAX, SZ_PTR);
//TODO: Don't shuffle if byteswap is not set
//SSE2 always available on 64-bit
pshuflw_rindexr(code, RAX, adr_reg, 1, XMM0, 0xB1);
}
#endif
}
if (use_sse2) {
pshufhw_rr(code, XMM0, XMM0, 0xB1);
movdqa_rrind(code, XMM0, dst_reg);
} else {
movq_rrind(code, MM0, dst_reg);
movq_rrdisp(code, MM1, dst_reg, 8);
emms(code);
}
retn(code);
} else if (memmap[chunk].read_16) {
push_r(code, RBX);//counter
#if defined(X86_64)
push_r(code, RBP);
push_r(code, R12);
push_r(code, R13);
mov_rr(code, dst_reg, RBP, SZ_PTR);
uint8_t my_dst_reg = RBP;
mov_rr(code, adr_reg, R12, opts->address_size);
uint8_t my_adr_reg = R12;
mov_rr(code, context_reg, R13, SZ_PTR);
uint8_t my_context_reg = R13;
#else
push_r(code, RBP);
push_r(code, RDI);
push_r(code, RSI);
mov_rr(code, dst_reg, RBP, SZ_PTR);
uint8_t my_dst_reg = RBP;
mov_rr(code, adr_reg, RDI, opts->address_size);
uint8_t my_adr_reg = RDI;
mov_rr(code, context_reg, RSI, SZ_PTR);
uint8_t my_context_reg = RSI;
#endif
mov_ir(code, 8, RBX, SZ_D);
code_ptr loop_top = code->cur;
//caller saved
//32-bit: EAX, ECX, EDX
//SYSV 64: RAX, RCX, RDX, RDI, RSI, R8, R9, R10, R11
//WIN64: RAX, RCX, RDX, R8, R9, R10, R11
//callee saved
//32-bit: EBX, EDI, ESI, EBP
//SYSV 64: RBX, RBP, R12-R15
//WIN64: RBX, RDI, RSI, RBP, R12-R15
xor_ir(code, 2, my_dst_reg, SZ_PTR);
call_args_abi(code, (code_ptr)memmap[chunk].read_16, 2, my_adr_reg, my_context_reg);
mov_rrind(code, RAX, my_dst_reg, SZ_W);
xor_ir(code, 2, my_dst_reg, SZ_PTR);
add_ir(code, 2, my_dst_reg, SZ_PTR);
add_ir(code, 2, my_adr_reg, opts->address_size);
dec_r(code, RBX, SZ_D);
jcc(code, CC_NZ, loop_top);
#if defined(X86_64)
pop_r(code, R13);
pop_r(code, R12);
pop_r(code, RBP);
#else
pop_r(code, RSI);
pop_r(code, RDI);
pop_r(code, RBP);
#endif
pop_r(code, RBX);
retn(code);
} else {
#if defined(X86_64)
mov_ir(code, 0xFFFFFFFFFFFFFFFFULL, adr_reg, SZ_Q);
mov_rrind(code, adr_reg, dst_reg, SZ_Q);
mov_rrdisp(code, adr_reg, dst_reg, 8, SZ_Q);
#else
mov_ir(code, 0xFFFFFFFF, adr_reg, SZ_D);
mov_rrind(code, adr_reg, dst_reg, SZ_D);
mov_rrdisp(code, adr_reg, dst_reg, 4, SZ_D);
mov_rrdisp(code, adr_reg, dst_reg, 8, SZ_D);
mov_rrdisp(code, adr_reg, dst_reg, 12, SZ_D);
#endif
retn(code);
}
if (lb_jcc) {
if (need_wide_jcc) {
*((int32_t*)lb_jcc) = code->cur - (lb_jcc+4);
} else if (code->cur - (lb_jcc+1) > 0x7f) {
need_wide_jcc = 1;
chunk--;
*code = chunk_start;
continue;
} else {
*lb_jcc = code->cur - (lb_jcc+1);
}
lb_jcc = NULL;
}
if (ub_jcc) {
if (need_wide_jcc) {
*((int32_t*)ub_jcc) = code->cur - (ub_jcc+4);
} else if (code->cur - (ub_jcc+1) > 0x7f) {
need_wide_jcc = 1;
chunk--;
*code = chunk_start;
continue;
} else {
*ub_jcc = code->cur - (ub_jcc+1);
}
ub_jcc = NULL;
}
if (need_wide_jcc) {
need_wide_jcc = 0;
}
}
#if defined(X86_64)
mov_ir(code, 0xFFFFFFFFFFFFFFFFULL, adr_reg, SZ_Q);
mov_rrind(code, adr_reg, dst_reg, SZ_Q);
mov_rrdisp(code, adr_reg, dst_reg, 8, SZ_Q);
#else
mov_ir(code, 0xFFFFFFFF, adr_reg, SZ_D);
mov_rrind(code, adr_reg, dst_reg, SZ_D);
mov_rrdisp(code, adr_reg, dst_reg, 4, SZ_D);
mov_rrdisp(code, adr_reg, dst_reg, 8, SZ_D);
mov_rrdisp(code, adr_reg, dst_reg, 12, SZ_D);
#endif
retn(code);
opts->context_reg = tmp_context_reg;
return start;
}
+182 -59
View File
@@ -5,7 +5,7 @@
#include "io.h"
#include "blastem.h"
#include "saves.h"
#include "util.h"
#include "paths.h"
#include "genesis.h"
#include "sms.h"
#include "menu.h"
@@ -19,13 +19,15 @@ enum {
BIND_NONE,
BIND_UI,
BIND_GAMEPAD,
BIND_MOUSE
BIND_MOUSE,
BIND_CASSETTE
};
typedef enum {
UI_DEBUG_MODE_INC,
UI_ENTER_DEBUGGER,
UI_SAVE_STATE,
UI_LOAD_STATE,
UI_SET_SPEED,
UI_NEXT_SPEED,
UI_PREV_SPEED,
@@ -36,12 +38,19 @@ typedef enum {
UI_RELOAD,
UI_SMS_PAUSE,
UI_SCREENSHOT,
UI_RECORD_VIDEO,
UI_VGM_LOG,
UI_MENU,
UI_EXIT,
UI_PLANE_DEBUG,
UI_VRAM_DEBUG,
UI_CRAM_DEBUG,
UI_COMPOSITE_DEBUG
UI_COMPOSITE_DEBUG,
UI_OSCILLOSCOPE_DEBUG,
UI_CD_GRAPHICS_DEBUG,
UI_PASTE,
UI_PAUSE,
UI_ADVANCE
} ui_action;
typedef struct {
@@ -59,6 +68,7 @@ typedef struct {
keybinding positive;
keybinding negative;
int16_t value;
int16_t deadzone;
} joyaxis;
typedef struct {
@@ -78,6 +88,7 @@ typedef struct {
#define DEFAULT_JOYBUTTON_ALLOC 12
static keybinding *bindings[0x10000];
static joystick joysticks[MAX_JOYSTICKS];
static uint8_t joystick_enabled[MAX_JOYSTICKS] = {1,1,1,1,1,1,1,1};
static mousebinding mice[MAX_MICE];
const uint8_t dpadbits[] = {RENDER_DPAD_UP, RENDER_DPAD_DOWN, RENDER_DPAD_LEFT, RENDER_DPAD_RIGHT};
@@ -139,7 +150,7 @@ void bind_dpad(int joystick, int dpad, int direction, uint8_t bind_type, uint8_t
}
}
void bind_axis(int joystick, int axis, int positive, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b)
void bind_axis(int joystick, int axis, int positive, int deadzone, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b)
{
if (joystick >= MAX_JOYSTICKS) {
return;
@@ -154,6 +165,7 @@ void bind_axis(int joystick, int axis, int positive, uint8_t bind_type, uint8_t
joysticks[joystick].axes = realloc(joysticks[joystick].axes, sizeof(joyaxis) * joysticks[joystick].num_axes);
memset(joysticks[joystick].axes + old_capacity, 0, (joysticks[joystick].num_axes - old_capacity) * sizeof(joyaxis));
}
joysticks[joystick].axes[axis].deadzone = deadzone;
if (positive) {
do_bind(&joysticks[joystick].axes[axis].positive, bind_type, subtype_a, subtype_b);
} else {
@@ -196,9 +208,22 @@ void set_content_binding_state(uint8_t enabled)
content_binds_enabled = enabled;
}
uint8_t get_content_binding_state(void)
{
return content_binds_enabled;
}
void bindings_set_joy_state(int joystick, uint8_t enabled)
{
if (joystick >= MAX_JOYSTICKS || joystick < 0) {
return;
}
joystick_enabled[joystick] = enabled;
}
void handle_binding_down(keybinding * binding)
{
if (!current_system) {
if (!current_system || !content_binds_enabled) {
return;
}
if (binding->bind_type == BIND_GAMEPAD && current_system && current_system->gamepad_down)
@@ -226,7 +251,7 @@ void handle_keydown(int keycode, uint8_t scancode)
void handle_joydown(int joystick, int button)
{
if (joystick >= MAX_JOYSTICKS || button >= joysticks[joystick].num_buttons) {
if (joystick >= MAX_JOYSTICKS || button >= joysticks[joystick].num_buttons || !joystick_enabled[joystick]) {
return;
}
keybinding * binding = joysticks[joystick].buttons + button;
@@ -307,6 +332,11 @@ void handle_binding_up(keybinding * binding)
current_system->mouse_up(current_system, binding->subtype_a, binding->subtype_b);
}
break;
case BIND_CASSETTE:
if (allow_content_binds && current_system->cassette_action) {
current_system->cassette_action(current_system, binding->subtype_a);
}
break;
case BIND_UI:
switch (binding->subtype_a)
{
@@ -325,6 +355,11 @@ void handle_binding_up(keybinding * binding)
current_system->save_state = QUICK_SAVE_SLOT+1;
}
break;
case UI_LOAD_STATE:
if (allow_content_binds && current_system->load_state) {
current_system->load_state(current_system, QUICK_SAVE_SLOT);
}
break;
case UI_NEXT_SPEED:
if (allow_content_binds) {
current_speed++;
@@ -392,6 +427,19 @@ void handle_binding_up(keybinding * binding)
render_save_screenshot(path);
}
break;
case UI_RECORD_VIDEO:
if (allow_content_binds) {
if (render_saving_video()) {
render_end_video();
render_end_audio();
} else {
char *path = get_content_config_path("ui\0video_path\0", "ui\0video_template\0", "blastem_%c.apng");
render_save_video(path);
path = get_content_config_path("ui\0audio_path\0", "ui\0audio_template\0", "blastem_%c.wav");
render_save_audio(path);
}
}
break;
case UI_VGM_LOG:
if (allow_content_binds && current_system->start_vgm_log) {
if (current_system->vgm_logging) {
@@ -403,7 +451,7 @@ void handle_binding_up(keybinding * binding)
}
}
break;
case UI_EXIT:
case UI_MENU:
#ifndef DISABLE_NUKLEAR
if (is_nuklear_active()) {
show_pause_menu();
@@ -422,33 +470,41 @@ void handle_binding_up(keybinding * binding)
}
#endif
break;
case UI_PLANE_DEBUG:
case UI_VRAM_DEBUG:
case UI_EXIT:
exit(0);
case UI_PLANE_DEBUG:
case UI_VRAM_DEBUG:
case UI_CRAM_DEBUG:
case UI_COMPOSITE_DEBUG:
if (allow_content_binds) {
vdp_context *vdp = NULL;
if (current_system->type == SYSTEM_GENESIS) {
genesis_context *gen = (genesis_context *)current_system;
vdp = gen->vdp;
} else if (current_system->type == SYSTEM_SMS) {
sms_context *sms = (sms_context *)current_system;
vdp = sms->vdp;
}
if (vdp) {
uint8_t debug_type;
switch(binding->subtype_a)
{
case UI_PLANE_DEBUG: debug_type = VDP_DEBUG_PLANE; break;
case UI_VRAM_DEBUG: debug_type = VDP_DEBUG_VRAM; break;
case UI_CRAM_DEBUG: debug_type = VDP_DEBUG_CRAM; break;
case UI_COMPOSITE_DEBUG: debug_type = VDP_DEBUG_COMPOSITE; break;
default: return;
}
vdp_toggle_debug_view(vdp, debug_type);
}
case UI_OSCILLOSCOPE_DEBUG:
case UI_CD_GRAPHICS_DEBUG:
if (allow_content_binds && current_system->toggle_debug_view) {
current_system->toggle_debug_view(current_system, binding->subtype_a - UI_PLANE_DEBUG + DEBUG_PLANE);
break;
}
case UI_PASTE:
if (allow_content_binds) {
if (current_system->paste_buffer) {
free(current_system->paste_buffer);
}
current_system->paste_buffer = render_read_clipboard();
current_system->paste_cur_char = 0;
}
break;
case UI_PAUSE:
if (allow_content_binds) {
current_system->paused = !current_system->paused;
if (current_system->paused) {
system_request_exit(current_system, 1);
}
}
break;
case UI_ADVANCE:
if (allow_content_binds) {
current_system->frame_advance = 1;
current_system->paused = 0;
}
break;
}
break;
}
@@ -468,7 +524,7 @@ void handle_keyup(int keycode, uint8_t scancode)
void handle_joyup(int joystick, int button)
{
if (joystick >= MAX_JOYSTICKS || button >= joysticks[joystick].num_buttons) {
if (joystick >= MAX_JOYSTICKS || button >= joysticks[joystick].num_buttons || !joystick_enabled[joystick]) {
return;
}
keybinding * binding = joysticks[joystick].buttons + button;
@@ -477,7 +533,7 @@ void handle_joyup(int joystick, int button)
void handle_joy_dpad(int joystick, int dpadnum, uint8_t value)
{
if (joystick >= MAX_JOYSTICKS || dpadnum >= joysticks[joystick].num_dpads) {
if (joystick >= MAX_JOYSTICKS || dpadnum >= joysticks[joystick].num_dpads || !joystick_enabled[joystick]) {
return;
}
joydpad * dpad = joysticks[joystick].dpads + dpadnum;
@@ -493,16 +549,14 @@ void handle_joy_dpad(int joystick, int dpadnum, uint8_t value)
}
}
#define JOY_AXIS_THRESHOLD 2000
void handle_joy_axis(int joystick, int axis, int16_t value)
{
if (joystick >= MAX_JOYSTICKS || axis >= joysticks[joystick].num_axes) {
if (joystick >= MAX_JOYSTICKS || axis >= joysticks[joystick].num_axes || !joystick_enabled[joystick]) {
return;
}
joyaxis *jaxis = joysticks[joystick].axes + axis;
int old_active = abs(jaxis->value) > JOY_AXIS_THRESHOLD;
int new_active = abs(value) > JOY_AXIS_THRESHOLD;
int old_active = abs(jaxis->value) > jaxis->deadzone;
int new_active = abs(value) > jaxis->deadzone;
int old_pos = jaxis->value > 0;
int new_pos = value > 0;
jaxis->value = value;
@@ -533,7 +587,7 @@ void handle_mouse_moved(int mouse, uint16_t x, uint16_t y, int16_t deltax, int16
float scale_y = (render_emulated_height() * 2.0f) / ((float)render_height());
int32_t adj_x = x * scale_x + 2 * render_overscan_left() - 2 * BORDER_LEFT;
int32_t adj_y = y * scale_y + 2 * render_overscan_top() - 4;
current_system->mouse_motion_absolute(current_system, target_mouse, adj_x, adj_y);
}
break;
@@ -580,7 +634,7 @@ void bindings_reacquire_capture(void)
}
}
int parse_binding_target(int device_num, char * target, tern_node * padbuttons, tern_node *mousebuttons, uint8_t * subtype_a, uint8_t * subtype_b)
int parse_binding_target(int device_num, const char * target, tern_node * padbuttons, tern_node *mousebuttons, uint8_t * subtype_a, uint8_t * subtype_b)
{
const int gpadslen = strlen("gamepads.");
const int mouselen = strlen("mouse.");
@@ -620,6 +674,20 @@ int parse_binding_target(int device_num, char * target, tern_node * padbuttons,
} else {
warning("Gamepad mapping string '%s' refers to an invalid mouse number %c\n", target, target[mouselen]);
}
} else if (startswith(target, "cassette.")) {
if (!strcmp(target + 9, "play")) {
*subtype_a = CASSETTE_PLAY;
} else if (!strcmp(target + 9, "record")) {
*subtype_a = CASSETTE_RECORD;
} else if (!strcmp(target + 9, "stop")) {
*subtype_a = CASSETTE_STOP;
} else if (!strcmp(target + 9, "rewind")) {
*subtype_a = CASSETTE_REWIND;
} else {
warning("Cassette mapping string '%s' refers to an invalid action\n", target);
return BIND_NONE;
}
return BIND_CASSETTE;
} else if(startswith(target, "ui.")) {
if (!strcmp(target + 3, "vdp_debug_mode")) {
*subtype_a = UI_DEBUG_MODE_INC;
@@ -630,6 +698,8 @@ int parse_binding_target(int device_num, char * target, tern_node * padbuttons,
*subtype_a = UI_ENTER_DEBUGGER;
} else if(!strcmp(target + 3, "save_state")) {
*subtype_a = UI_SAVE_STATE;
} else if(!strcmp(target + 3, "load_state")) {
*subtype_a = UI_LOAD_STATE;
} else if(startswith(target + 3, "set_speed.")) {
*subtype_a = UI_SET_SPEED;
*subtype_b = atoi(target + 3 + strlen("set_speed."));
@@ -651,8 +721,12 @@ int parse_binding_target(int device_num, char * target, tern_node * padbuttons,
*subtype_a = UI_SMS_PAUSE;
} else if (!strcmp(target + 3, "screenshot")) {
*subtype_a = UI_SCREENSHOT;
} else if (!strcmp(target + 3, "record_video")) {
*subtype_a = UI_RECORD_VIDEO;
} else if (!strcmp(target + 3, "vgm_log")) {
*subtype_a = UI_VGM_LOG;
} else if(!strcmp(target + 3, "menu")) {
*subtype_a = UI_MENU;
} else if(!strcmp(target + 3, "exit")) {
*subtype_a = UI_EXIT;
} else if (!strcmp(target + 3, "plane_debug")) {
@@ -663,6 +737,16 @@ int parse_binding_target(int device_num, char * target, tern_node * padbuttons,
*subtype_a = UI_CRAM_DEBUG;
} else if (!strcmp(target + 3, "compositing_debug")) {
*subtype_a = UI_COMPOSITE_DEBUG;
} else if (!strcmp(target + 3, "oscilloscope")) {
*subtype_a = UI_OSCILLOSCOPE_DEBUG;
} else if (!strcmp(target + 3, "cd_graphics_debug")) {
*subtype_a = UI_CD_GRAPHICS_DEBUG;
} else if (!strcmp(target + 3, "paste")) {
*subtype_a = UI_PASTE;
} else if (!strcmp(target + 3, "pause")) {
*subtype_a = UI_PAUSE;
} else if (!strcmp(target + 3, "advance")) {
*subtype_a = UI_ADVANCE;
} else {
warning("Unreconized UI binding type %s\n", target);
return 0;
@@ -821,6 +905,8 @@ typedef struct {
int padnum;
tern_node *padbuttons;
tern_node *mousebuttons;
int stick_deadzone;
int trigger_deadzone;
} pad_button_state;
@@ -853,7 +939,7 @@ void process_pad_button(char *key, tern_val val, uint8_t valtype, void *data)
bind_dpad(hostpadnum, render_dpad_part(hostbutton), render_direction_part(hostbutton), bindtype, subtype_a, subtype_b);
return;
} else if (hostbutton & RENDER_AXIS_BIT) {
bind_axis(hostpadnum, render_axis_part(hostbutton), hostbutton & RENDER_AXIS_POS, bindtype, subtype_a, subtype_b);
bind_axis(hostpadnum, render_axis_part(hostbutton), hostbutton & RENDER_AXIS_POS, state->trigger_deadzone, bindtype, subtype_a, subtype_b);
return;
}
}
@@ -884,6 +970,7 @@ void process_pad_axis(char *key, tern_val val, uint8_t valtype, void *data)
}
char *end;
long axis = strtol(key, &end, 10);
int deadzone = state->stick_deadzone;
if (*end) {
//key is not a valid base 10 integer
axis = render_translate_input_name(hostpadnum, key, 1);
@@ -896,6 +983,9 @@ void process_pad_axis(char *key, tern_val val, uint8_t valtype, void *data)
}
goto done;
}
if (!strcmp("lefttrigger", key) || !strcmp("righttrigger", key) || !strcmp("l2", key) || !strcmp("r2", key)) {
deadzone = state->trigger_deadzone;
}
if (axis & RENDER_DPAD_BIT) {
bind_dpad(hostpadnum, render_dpad_part(axis), render_direction_part(axis), bindtype, subtype_a, subtype_b);
goto done;
@@ -906,7 +996,7 @@ void process_pad_axis(char *key, tern_val val, uint8_t valtype, void *data)
goto done;
}
}
bind_axis(hostpadnum, axis, positive, bindtype, subtype_a, subtype_b);
bind_axis(hostpadnum, axis, positive, deadzone, bindtype, subtype_a, subtype_b);
done:
free(key);
return;
@@ -945,7 +1035,30 @@ static tern_node *get_mouse_buttons()
return mousebuttons;
}
tern_node *get_binding_node_for_pad(int padnum)
uint8_t bind_up(const char *target)
{
keybinding bind;
bind.bind_type = parse_binding_target(0, target, get_pad_buttons(), get_mouse_buttons(), &bind.subtype_a, &bind.subtype_b);
if (!bind.bind_type) {
return 0;
}
handle_binding_up(&bind);
return 1;
}
uint8_t bind_down(const char *target)
{
keybinding bind;
bind.bind_type = parse_binding_target(0, target, get_pad_buttons(), get_mouse_buttons(), &bind.subtype_a, &bind.subtype_b);
if (!bind.bind_type) {
return 0;
}
handle_binding_down(&bind);
return 1;
}
tern_node *get_binding_node_for_pad(int padnum, controller_info *info)
{
if (padnum > MAX_JOYSTICKS) {
return NULL;
@@ -954,7 +1067,7 @@ tern_node *get_binding_node_for_pad(int padnum)
if (!pads) {
return NULL;
}
char numstr[11];
char numstr[12];
sprintf(numstr, "%d", padnum);
tern_node * pad = tern_find_node(pads, numstr);
if (!pad) {
@@ -963,8 +1076,7 @@ tern_node *get_binding_node_for_pad(int padnum)
free(type_id);
}
if (!pad) {
controller_info info = get_controller_info(padnum);
char *key = make_controller_type_key(&info);
char *key = make_controller_type_key(info);
pad = tern_find_node(pads, key);
free(key);
}
@@ -976,7 +1088,8 @@ tern_node *get_binding_node_for_pad(int padnum)
void handle_joy_added(int joystick)
{
tern_node *pad = get_binding_node_for_pad(joystick);
controller_info info = get_controller_info(joystick);
tern_node *pad = get_binding_node_for_pad(joystick, &info);
if (!pad) {
return;
}
@@ -1003,7 +1116,7 @@ void handle_joy_added(int joystick)
} else if (hostbutton & RENDER_AXIS_BIT) {
//SDL2 knows internally whether this should be a positive or negative binding, but doesn't expose that externally
//for now I'll just assume that any controller with axes for a d-pad has these mapped the "sane" way
bind_axis(joystick, render_axis_part(hostbutton), dir == 1 || dir == 3 ? 1 : 0, bindtype, subtype_a, subtype_b);
bind_axis(joystick, render_axis_part(hostbutton), dir == 1 || dir == 3 ? 1 : 0, info.stick_deadzone, bindtype, subtype_a, subtype_b);
} else {
bind_button(joystick, hostbutton, bindtype, subtype_a, subtype_b);
}
@@ -1011,22 +1124,19 @@ void handle_joy_added(int joystick)
}
}
}
pad_button_state state = {
.padnum = joystick,
.padbuttons = get_pad_buttons(),
.mousebuttons = get_mouse_buttons(),
.stick_deadzone = info.stick_deadzone,
.trigger_deadzone = info.trigger_deadzone
};
tern_node *button_node = tern_find_node(pad, "buttons");
if (button_node) {
pad_button_state state = {
.padnum = joystick,
.padbuttons = get_pad_buttons(),
.mousebuttons = get_mouse_buttons()
};
tern_foreach(button_node, process_pad_button, &state);
}
tern_node *axes_node = tern_find_node(pad, "axes");
if (axes_node) {
pad_button_state state = {
.padnum = joystick,
.padbuttons = get_pad_buttons(),
.mousebuttons = get_mouse_buttons()
};
tern_foreach(axes_node, process_pad_axis, &state);
}
}
@@ -1090,11 +1200,11 @@ void set_bindings(void)
tern_node *padbuttons = get_pad_buttons();
tern_node *mousebuttons = get_mouse_buttons();
tern_node * keys = tern_find_path(config, "bindings\0keys\0", TVAL_NODE).ptrval;
process_keys(keys, special, padbuttons, mousebuttons, NULL);
tern_free(special);
memset(mice, 0, sizeof(mice));
tern_node * mice = tern_find_path(config, "bindings\0mice\0", TVAL_NODE).ptrval;
if (mice) {
@@ -1102,8 +1212,10 @@ void set_bindings(void)
tern_foreach(mice, process_mouse, buttonmaps);
}
tern_node * speed_nodes = tern_find_path(config, "clocks\0speeds\0", TVAL_NODE).ptrval;
free(speeds);
speeds = malloc(sizeof(uint32_t));
speeds[0] = 100;
num_speeds = 1;
process_speeds(speed_nodes, NULL);
for (int i = 0; i < num_speeds; i++)
{
@@ -1114,6 +1226,17 @@ void set_bindings(void)
}
}
void update_pad_bindings(void)
{
for (int i = 0; i < MAX_JOYSTICKS; i++)
{
if (joysticks[i].num_buttons || joysticks[i].num_axes || joysticks[i].num_dpads) {
reset_joystick_bindings(i);
handle_joy_added(i);
}
}
}
void bindings_set_mouse_mode(uint8_t mode)
{
mouse_mode = mode;
+7 -1
View File
@@ -1,6 +1,7 @@
#ifndef BINDINGS_H_
#define BINDINGS_H_
#include <stdint.h>
#include "controller_info.h"
typedef enum {
MOUSE_NONE, //mouse is ignored
@@ -10,8 +11,9 @@ typedef enum {
} mouse_modes;
void set_bindings(void);
void update_pad_bindings(void);
void bindings_set_mouse_mode(uint8_t mode);
tern_node *get_binding_node_for_pad(int padnum);
tern_node *get_binding_node_for_pad(int padnum, controller_info *info);
void handle_keydown(int keycode, uint8_t scancode);
void handle_keyup(int keycode, uint8_t scancode);
void handle_joydown(int joystick, int button);
@@ -22,9 +24,13 @@ void handle_joy_added(int joystick);
void handle_mouse_moved(int mouse, uint16_t x, uint16_t y, int16_t deltax, int16_t deltay);
void handle_mousedown(int mouse, int button);
void handle_mouseup(int mouse, int button);
uint8_t bind_up(const char *target);
uint8_t bind_down(const char *target);
void bindings_release_capture(void);
void bindings_reacquire_capture(void);
void set_content_binding_state(uint8_t enabled);
uint8_t get_content_binding_state(void);
void bindings_set_joy_state(int joystick, uint8_t enabled);
#endif //BINDINGS_H_
+12 -15
View File
@@ -3,7 +3,9 @@
#include <stddef.h>
#include <string.h>
#include <time.h>
#ifndef _WIN32
#include <sys/select.h>
#endif
#ifdef NEW_CORE
#include "z80.h"
@@ -17,7 +19,6 @@ uint8_t ram[64 * 1024];
#define START_OFF 0x100
#define OS_START 0xE400
#define OS_RESET 0xE403
int headless = 1;
#ifndef NEW_CORE
void z80_next_int_pulse(z80_context * context)
@@ -26,14 +27,6 @@ void z80_next_int_pulse(z80_context * context)
}
#endif
void render_errorbox(char *title, char *message)
{
}
void render_infobox(char *title, char *message)
{
}
void *console_write(uint32_t address, void *context, uint8_t value)
{
putchar(value);
@@ -53,13 +46,17 @@ void *console_flush_write(uint32_t address, void *context, uint8_t value)
uint8_t console_status_read(uint32_t address, void *context)
{
#ifdef _WIN32
return 0;
#else
fd_set read_fds;
FD_ZERO(&read_fds);
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 0;
FD_SET(fileno(stdin), &read_fds);
return select(fileno(stdin)+1, &read_fds, NULL, NULL, &timeout) > 0;
return select(fileno(stdin)+1, &read_fds, NULL, NULL, &timeout) > 0;
#endif
}
time_t start;
@@ -71,7 +68,7 @@ void *exit_write(uint32_t address, void *context, uint8_t value)
#ifdef NEW_CORE
total_cycles += z80->cycles;
#else
total_cycles += context->current_cycle;
total_cycles += z80->current_cycle;
#endif
printf("Effective clock speed: %f MHz\n", ((double)total_cycles) / (1000000.0 * duration));
exit(0);
@@ -79,13 +76,13 @@ void *exit_write(uint32_t address, void *context, uint8_t value)
}
const memmap_chunk z80_map[] = {
{ 0x0000, 0x10000, 0xFFFF, 0, 0, MMAP_READ | MMAP_WRITE | MMAP_CODE, ram, NULL, NULL, NULL, NULL},
{ 0x0000, 0x10000, 0xFFFF, .flags = MMAP_READ | MMAP_WRITE | MMAP_CODE, .buffer = ram},
};
memmap_chunk io_map[] = {
{ 0x0, 0x1, 0xFFFF, 0, 0, 0, NULL, NULL, NULL, console_read, console_write},
{ 0x1, 0x2, 0xFFFF, 0, 0, 0, NULL, NULL, NULL, console_status_read, console_flush_write},
{ 0x2, 0x3, 0xFFFF, 0, 0, 0, NULL, NULL, NULL, NULL, exit_write},
{ 0x0, 0x1, 0xFFFF, .read_8 = console_read, .write_8 = console_write},
{ 0x1, 0x2, 0xFFFF, .read_8 = console_status_read, .write_8 = console_flush_write},
{ 0x2, 0x3, 0xFFFF, .write_8 = exit_write},
};
int main(int argc, char **argv)
+241 -247
View File
@@ -7,15 +7,13 @@
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#ifndef USE_FBDEV
// for SDL_main on platforms that need it
#include <SDL.h>
#endif
#include "system.h"
#include "68kinst.h"
#include "m68k_core.h"
#ifdef NEW_CORE
#include "z80.h"
#else
#include "z80_to_x86.h"
#endif
#include "mem.h"
#include "vdp.h"
#include "render.h"
@@ -23,6 +21,7 @@
#include "gdb_remote.h"
#include "gst.h"
#include "util.h"
#include "paths.h"
#include "romdb.h"
#include "terminal.h"
#include "arena.h"
@@ -30,12 +29,20 @@
#include "bindings.h"
#include "menu.h"
#include "zip.h"
#include "cdimage.h"
#include "event_log.h"
#ifndef DISABLE_NUKLEAR
#include "nuklear_ui/blastem_nuklear.h"
#endif
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#include "render_audio.h"
#endif
#ifndef _WIN32
#include <unistd.h>
#endif
#define BLASTEM_VERSION "0.6.3-pre"
#include "version.inc"
#ifdef __ANDROID__
#define FULLSCREEN_DEFAULT 1
@@ -51,201 +58,15 @@ uint8_t use_native_states = 1;
tern_node * config;
#define SMD_HEADER_SIZE 512
#define SMD_MAGIC1 0x03
#define SMD_MAGIC2 0xAA
#define SMD_MAGIC3 0xBB
#define SMD_BLOCK_SIZE 0x4000
#ifdef DISABLE_ZLIB
#define ROMFILE FILE*
#define romopen fopen
#define romread fread
#define romseek fseek
#define romgetc fgetc
#define romclose fclose
#else
#include "zlib/zlib.h"
#define ROMFILE gzFile
#define romopen gzopen
#define romread gzfread
#define romseek gzseek
#define romgetc gzgetc
#define romclose gzclose
#endif
uint16_t *process_smd_block(uint16_t *dst, uint8_t *src, size_t bytes)
{
for (uint8_t *low = src, *high = (src+bytes/2), *end = src+bytes; high < end; high++, low++) {
*(dst++) = *low << 8 | *high;
}
return dst;
}
int load_smd_rom(ROMFILE f, void **buffer)
{
uint8_t block[SMD_BLOCK_SIZE];
romseek(f, SMD_HEADER_SIZE, SEEK_SET);
size_t filesize = 512 * 1024;
size_t readsize = 0;
uint16_t *dst, *buf;
dst = buf = malloc(filesize);
size_t read;
do {
if ((readsize + SMD_BLOCK_SIZE > filesize)) {
filesize *= 2;
buf = realloc(buf, filesize);
dst = buf + readsize/sizeof(uint16_t);
}
read = romread(block, 1, SMD_BLOCK_SIZE, f);
if (read > 0) {
dst = process_smd_block(dst, block, read);
readsize += read;
}
} while(read > 0);
romclose(f);
*buffer = buf;
return readsize;
}
uint8_t is_smd_format(const char *filename, uint8_t *header)
{
if (header[1] == SMD_MAGIC1 && header[8] == SMD_MAGIC2 && header[9] == SMD_MAGIC3) {
int i;
for (i = 3; i < 8; i++) {
if (header[i] != 0) {
return 0;
}
}
if (i == 8) {
if (header[2]) {
fatal_error("%s is a split SMD ROM which is not currently supported", filename);
}
return 1;
}
}
return 0;
}
uint32_t load_rom_zip(const char *filename, void **dst)
{
static const char *valid_exts[] = {"bin", "md", "gen", "sms", "rom", "smd"};
const uint32_t num_exts = sizeof(valid_exts)/sizeof(*valid_exts);
zip_file *z = zip_open(filename);
if (!z) {
return 0;
}
for (uint32_t i = 0; i < z->num_entries; i++)
{
char *ext = path_extension(z->entries[i].name);
if (!ext) {
continue;
}
for (uint32_t j = 0; j < num_exts; j++)
{
if (!strcasecmp(ext, valid_exts[j])) {
size_t out_size = nearest_pow2(z->entries[i].size);
*dst = zip_read(z, i, &out_size);
if (*dst) {
if (is_smd_format(z->entries[i].name, *dst)) {
size_t offset;
for (offset = 0; offset + SMD_BLOCK_SIZE + SMD_HEADER_SIZE <= out_size; offset += SMD_BLOCK_SIZE)
{
uint8_t tmp[SMD_BLOCK_SIZE];
uint8_t *u8dst = *dst;
memcpy(tmp, u8dst + offset + SMD_HEADER_SIZE, SMD_BLOCK_SIZE);
process_smd_block((void *)(u8dst + offset), tmp, SMD_BLOCK_SIZE);
}
out_size = offset;
}
free(ext);
zip_close(z);
return out_size;
}
}
}
free(ext);
}
zip_close(z);
return 0;
}
uint32_t load_rom(const char * filename, void **dst, system_type *stype)
{
uint8_t header[10];
char *ext = path_extension(filename);
if (ext && !strcasecmp(ext, "zip")) {
free(ext);
return load_rom_zip(filename, dst);
}
free(ext);
ROMFILE f = romopen(filename, "rb");
if (!f) {
return 0;
}
if (sizeof(header) != romread(header, 1, sizeof(header), f)) {
fatal_error("Error reading from %s\n", filename);
}
if (is_smd_format(filename, header)) {
if (stype) {
*stype = SYSTEM_GENESIS;
}
return load_smd_rom(f, dst);
}
size_t filesize = 512 * 1024;
size_t readsize = sizeof(header);
char *buf = malloc(filesize);
memcpy(buf, header, readsize);
size_t read;
do {
read = romread(buf + readsize, 1, filesize - readsize, f);
if (read > 0) {
readsize += read;
if (readsize == filesize) {
int one_more = romgetc(f);
if (one_more >= 0) {
filesize *= 2;
buf = realloc(buf, filesize);
buf[readsize++] = one_more;
} else {
read = 0;
}
}
}
} while (read > 0);
*dst = buf;
romclose(f);
return readsize;
}
int break_on_sync = 0;
char *save_state_path;
char * save_filename;
system_header *current_system;
system_header *menu_system;
system_header *game_system;
void persist_save()
{
if (!game_system) {
if (!game_system || !game_system->persist_save) {
return;
}
game_system->persist_save(game_system);
@@ -294,6 +115,10 @@ const char *get_save_fname(uint8_t save_type)
void setup_saves(system_media *media, system_header *context)
{
if (!context->load_save) {
// system doesn't support saves
return;
}
static uint8_t persist_save_registered;
rom_info *info = &context->info;
char *save_dir = get_save_dir(info->is_save_lock_on ? media->chain : media);
@@ -314,7 +139,9 @@ void setup_saves(system_media *media, system_header *context)
free(save_state_path);
save_state_path = alloc_concat_m(3, parts);
context->save_dir = save_dir;
if (info->save_type != SAVE_NONE) {
if (info->save_type != SAVE_NONE || context->type == SYSTEM_SEGACD || context->type == SYSTEM_32XCD
|| (context->type == SYSTEM_GENESIS && info->wants_cd)
) {
context->load_save(context);
if (!persist_save_registered) {
atexit(persist_save);
@@ -323,15 +150,101 @@ void setup_saves(system_media *media, system_header *context)
}
}
static uint8_t menu;
static uint8_t use_nuklear;
#ifdef __EMSCRIPTEN__
void handle_frame_presented(void)
{
if (current_system) {
current_system->request_exit(current_system);
}
}
void browser_main_loop(void)
{
static uint8_t system_started;
#ifndef DISABLE_NUKLEAR
static uint8_t was_menu;
if (use_nuklear) {
if (menu) {
if (!was_menu) {
ui_enter();
}
was_menu = menu;
render_update_display();
menu = !get_content_binding_state();
if (!menu) {
ui_exit();
return;
}
} else {
was_menu = menu;
}
}
#endif
if (!current_system && game_system) {
current_system = game_system;
}
if (current_system) {
if (system_started && !current_system->force_release && render_is_audio_sync()) {
if (all_sources_ready()) {
return;
}
}
if (current_system->next_rom) {
char *next_rom = current_system->next_rom;
current_system->next_rom = NULL;
init_system_with_media(next_rom, 0);
system_started = 0;
menu = 0;
current_system = game_system;
} else if (!menu) {
if (system_started) {
current_system->resume_context(current_system);
} else {
system_started = 1;
current_system->start_context(current_system, NULL);
}
if (current_system->force_release) {
menu = 1;
}
}
}
}
void setup_main_loop(void)
{
//can't use render_is_audio_sync since we haven't called render_init/render_config_updated yet
char *sync = tern_find_path_default(config, "system\0sync_source\0", (tern_val){.ptrval = "audio"}, TVAL_PTR).ptrval;
emscripten_cancel_main_loop();
if (!strcmp("video", sync)) {
render_set_audio_full_fun(NULL);
render_set_frame_presented_fun(handle_frame_presented);
emscripten_set_main_loop(browser_main_loop, 0, 0);
} else {
render_set_frame_presented_fun(NULL);
render_set_audio_full_fun(handle_frame_presented);
emscripten_set_main_loop(browser_main_loop, 1, 0); //dummy fps value, will be overridden by a call to emscripten_set_main_loop_timing
}
}
#endif
void apply_updated_config(void)
{
#ifdef __EMSCRIPTEN__
setup_main_loop();
#endif
render_config_updated();
set_bindings();
update_pad_bindings();
if (current_system && current_system->config_updated) {
current_system->config_updated(current_system);
}
}
static void on_drag_drop(const char *filename)
static system_media cart, lock_on;
static void on_drag_drop(char *filename)
{
if (current_system) {
if (current_system->next_rom) {
@@ -346,6 +259,7 @@ static void on_drag_drop(const char *filename)
menu->external_game_load = 1;
}
}
cart.chain = NULL;
} else {
init_system_with_media(filename, SYSTEM_UNKNOWN);
}
@@ -356,7 +270,6 @@ static void on_drag_drop(const char *filename)
#endif
}
static system_media cart, lock_on;
const system_media *current_media(void)
{
return &cart;
@@ -364,43 +277,53 @@ const system_media *current_media(void)
void reload_media(void)
{
if (!current_system) {
if (!current_system || !cart.orig_path) {
return;
}
if (current_system->next_rom) {
free(current_system->next_rom);
}
char const *parts[] = {
cart.dir, PATH_SEP, cart.name, ".", cart.extension
};
char const **start = parts[0] ? parts : parts + 2;
int num_parts = parts[0] ? 5 : 3;
if (!parts[4]) {
num_parts--;
current_system->next_rom = cart.orig_path;
cart.orig_path = NULL;
if (cart.chain) {
load_media(cart.chain->orig_path, cart.chain, NULL);
}
current_system->next_rom = alloc_concat_m(num_parts, start);
system_request_exit(current_system, 1);
}
void lockon_media(char *lock_on_path)
{
reload_media();
cart.chain = &lock_on;
free(lock_on.dir);
free(lock_on.name);
free(lock_on.extension);
lock_on.dir = path_dirname(lock_on_path);
lock_on.name = basename_no_extension(lock_on_path);
lock_on.extension = path_extension(lock_on_path);
lock_on.size = load_rom(lock_on_path, &lock_on.buffer, NULL);
free(lock_on.orig_path);
if (lock_on_path) {
if (!current_system || !current_system->lockon_change) {
cart.chain = NULL;
reload_media();
}
cart.chain = &lock_on;
load_media(lock_on_path, &lock_on, NULL);
if (current_system && current_system->lockon_change) {
current_system->lockon_change(current_system, &lock_on);
}
} else {
lock_on.dir = NULL;
lock_on.name = NULL;
lock_on.extension = NULL;
lock_on.orig_path = NULL;
cart.chain = NULL;
}
}
static uint32_t opts = 0;
static uint8_t force_region = 0;
void init_system_with_media(const char *path, system_type force_stype)
void init_system_with_media(char *path, system_type force_stype)
{
if (game_system) {
game_system->persist_save(game_system);
if (game_system->persist_save) {
game_system->persist_save(game_system);
}
//swap to game context arena and mark all allocated pages in it free
if (current_system == menu_system) {
current_system->arena = set_current_arena(game_system->arena);
@@ -411,16 +334,15 @@ void init_system_with_media(const char *path, system_type force_stype)
//start a new arena and save old one in suspended system context
current_system->arena = start_new_arena();
}
system_type stype = SYSTEM_UNKNOWN;
if (!(cart.size = load_rom(path, &cart.buffer, &stype))) {
fatal_error("Failed to open %s for reading\n", path);
}
free(cart.dir);
free(cart.name);
free(cart.extension);
cart.dir = path_dirname(path);
cart.name = basename_no_extension(path);
cart.extension = path_extension(path);
free(cart.orig_path);
system_type stype = SYSTEM_UNKNOWN;
if (!(cart.size = load_media(path, &cart, &stype))) {
fatal_error("Failed to open %s for reading\n", path);
}
if (force_stype != SYSTEM_UNKNOWN) {
stype = force_stype;
}
@@ -443,6 +365,24 @@ void init_system_with_media(const char *path, system_type force_stype)
update_title(game_system->info.name);
}
void render_log_handler(log_level level, char *message)
{
switch(level)
{
case INFO:
render_infobox("BlastEm Info", message);
break;
case WARN:
render_warnbox("BlastEm Warning", message);
break;
case FATAL:
render_errorbox("Fatal Error", message);
break;
default:
break;
}
}
char *parse_addr_port(char *arg)
{
while (*arg && *arg != ':') {
@@ -463,6 +403,14 @@ char *parse_addr_port(char *arg)
int main(int argc, char ** argv)
{
set_exe_str(argv[0]);
#ifndef _WIN32
if (!(isatty(STDERR_FILENO) && isatty(STDIN_FILENO))) {
#endif
register_log_handler(render_log_handler);
#ifndef _WIN32
}
#endif
config = load_config();
int width = -1;
int height = -1;
@@ -486,6 +434,7 @@ int main(int argc, char ** argv)
if (i >= argc) {
fatal_error("-b must be followed by a frame count\n");
}
register_log_handler(NULL);
headless = 1;
exit_after = atoi(argv[i]);
break;
@@ -542,14 +491,32 @@ int main(int argc, char ** argv)
case 'm':
i++;
if (i >= argc) {
fatal_error("-r must be followed by a machine type (sms, gen or jag)\n");
fatal_error("-r must be followed by a machine type (sms, gg, sg, sc, gen, 32x, 32xcd, pico, copera, jag or media)\n");
}
if (!strcmp("sms", argv[i])) {
stype = force_stype = SYSTEM_SMS;
} else if (!strcmp("gg", argv[i])) {
stype = force_stype = SYSTEM_GAME_GEAR;
} else if (!strcmp("sg", argv[i])) {
stype = force_stype = SYSTEM_SG1000;
} else if (!strcmp("sc", argv[i])) {
stype = force_stype = SYSTEM_SC3000;
} else if (!strcmp("gen", argv[i])) {
stype = force_stype = SYSTEM_GENESIS;
} else if (!strcmp("32x", argv[i])) {
stype = force_stype = SYSTEM_32X;
} else if (!strcmp("32xcd", argv[i])) {
stype = force_stype = SYSTEM_32XCD;
} else if (!strcmp("pico", argv[i])) {
stype = force_stype = SYSTEM_PICO;
} else if (!strcmp("copera", argv[i])) {
stype = force_stype = SYSTEM_COPERA;
} else if (!strcmp("jag", argv[i])) {
stype = force_stype = SYSTEM_JAGUAR;
} else if (!strcmp("media", argv[i])) {
stype = force_stype = SYSTEM_MEDIA_PLAYER;
} else if (!strcmp("laser", argv[i])) {
stype = force_stype = SYSTEM_LASERACTIVE;
} else {
fatal_error("Unrecognized machine type %s\n", argv[i]);
}
@@ -572,12 +539,9 @@ int main(int argc, char ** argv)
if (i >= argc) {
fatal_error("-o must be followed by a lock on cartridge filename\n");
}
lock_on.size = load_rom(argv[i], &lock_on.buffer, NULL);
if (!lock_on.size) {
if (!load_media(argv[i], &lock_on, NULL)) {
fatal_error("Failed to load lock on cartridge %s\n", argv[i]);
}
lock_on.name = basename_no_extension(argv[i]);
lock_on.extension = path_extension(argv[i]);
cart.chain = &lock_on;
break;
}
@@ -588,9 +552,16 @@ int main(int argc, char ** argv)
" -h Print this help text\n"
" -r (J|U|E) Force region to Japan, US or Europe respectively\n"
" -m MACHINE Force emulated machine type to MACHINE. Valid values are:\n"
" sms - Sega Master System/Mark III\n"
" gen - Sega Genesis/Megadrive\n"
" jag - Atari Jaguar\n"
" sms - Sega Master System/Mark III\n"
" gg - Sega Game Gear\n"
" sg - Sega SG-1000\n"
" sc - Sega SC-3000\n"
" gen - Sega Genesis/Megadrive\n"
" 32x - Sega 32X\n"
" pico - Sega Pico\n"
" copera - Yamaha Copera\n"
" media - Media Player\n"
" laser - Pioneer LaserActive (no PAC)\n"
" -f Toggles fullscreen mode\n"
" -g Disable OpenGL rendering\n"
" -s FILE Load a GST format savestate from FILE\n"
@@ -611,14 +582,11 @@ int main(int argc, char ** argv)
if (reader_port) {
reader_addr = argv[i];
} else {
if (!(cart.size = load_rom(argv[i], &cart.buffer, stype == SYSTEM_UNKNOWN ? &stype : NULL))) {
romfname = strdup(argv[i]);
if (!load_media(romfname, &cart, stype == SYSTEM_UNKNOWN ? &stype : NULL)) {
fatal_error("Failed to open %s for reading\n", argv[i]);
}
cart.dir = path_dirname(argv[i]);
cart.name = basename_no_extension(argv[i]);
cart.extension = path_extension(argv[i]);
}
romfname = argv[i];
loaded = 1;
} else if (width < 0) {
width = atoi(argv[i]);
@@ -626,7 +594,7 @@ int main(int argc, char ** argv)
height = atoi(argv[i]);
}
}
int def_width = 0, def_height = 0;
char *config_width = tern_find_path(config, "video\0width\0", TVAL_PTR).ptrval;
if (config_width) {
@@ -649,6 +617,10 @@ int main(int argc, char ** argv)
if (config_fullscreen && !strcmp("on", config_fullscreen)) {
fullscreen = !fullscreen;
}
#ifdef __EMSCRIPTEN__
config = tern_insert_path(config, "ui\0initial_path\0", (tern_val){.ptrval = strdup("/roms")}, TVAL_PTR);
setup_main_loop();
#endif
if (!headless) {
if (reader_addr) {
render_set_external_sync(1);
@@ -657,9 +629,8 @@ int main(int argc, char ** argv)
render_set_drag_drop_handler(on_drag_drop);
}
set_bindings();
uint8_t menu = !loaded;
uint8_t use_nuklear = 0;
menu = !loaded;
#ifndef DISABLE_NUKLEAR
use_nuklear = !headless && is_nuklear_available();
#endif
@@ -669,8 +640,9 @@ int main(int argc, char ** argv)
if (!romfname) {
romfname = "menu.bin";
}
romfname = strdup(romfname);
if (is_absolute_path(romfname)) {
if (!(cart.size = load_rom(romfname, &cart.buffer, &stype))) {
if (!(cart.size = load_media(romfname, &cart, &stype))) {
fatal_error("Failed to open UI ROM %s for reading", romfname);
}
} else {
@@ -683,12 +655,13 @@ int main(int argc, char ** argv)
cart.buffer = realloc(cart.buffer, rom_size);
cart.size = rom_size;
}
cart.dir = path_dirname(romfname);
cart.name = basename_no_extension(romfname);
cart.extension = path_extension(romfname);
cart.orig_path = romfname;
}
//force system detection, value on command line is only for games not the menu
stype = detect_system_type(&cart);
cart.dir = path_dirname(romfname);
cart.name = basename_no_extension(romfname);
cart.extension = path_extension(romfname);
loaded = 1;
}
char *state_format = tern_find_path(config, "ui\0state_format\0", TVAL_PTR).ptrval;
@@ -705,12 +678,12 @@ int main(int argc, char ** argv)
if (stype == SYSTEM_UNKNOWN) {
fatal_error("Failed to detect system type for %s\n", romfname);
}
current_system = alloc_config_system(stype, &cart, menu ? 0 : opts, force_region);
if (!current_system) {
fatal_error("Failed to configure emulated machine for %s\n", romfname);
}
setup_saves(&cart, current_system);
update_title(current_system->info.name);
if (menu) {
@@ -719,12 +692,14 @@ int main(int argc, char ** argv)
game_system = current_system;
}
}
#ifndef DISABLE_NUKLEAR
if (use_nuklear) {
blastem_nuklear_init(!menu);
#ifndef __EMSCRIPTEN__
current_system = game_system;
menu = 0;
#endif
}
#endif
@@ -740,9 +715,10 @@ int main(int argc, char ** argv)
setup_saves(&cart, current_system);
update_title(current_system->info.name);
}
current_system->debugger_type = dtype;
current_system->enter_debugger = start_in_debugger && menu == debug_target;
#ifndef __EMSCRIPTEN__
current_system->start_context(current_system, menu ? NULL : statefile);
render_video_loop();
for(;;)
@@ -750,17 +726,34 @@ int main(int argc, char ** argv)
if (current_system->should_exit) {
break;
}
uint8_t was_paused = current_system->paused;
while (current_system->paused) {
uint32_t start = render_elapsed_ms();
process_events();
render_update_display();
if (current_system->get_vdp) {
vdp_context *vdp = current_system->get_vdp(current_system);
if (vdp) {
vdp_update_per_frame_debug(vdp);
}
}
uint32_t duration = render_elapsed_ms() - start;
if (duration < 1000/60) {
render_sleep_ms(1000/60 - duration);
}
}
if (current_system->next_rom) {
char *next_rom = current_system->next_rom;
current_system->next_rom = NULL;
init_system_with_media(next_rom, force_stype);
free(next_rom);
menu = 0;
current_system = game_system;
current_system->debugger_type = dtype;
current_system->enter_debugger = start_in_debugger && menu == debug_target;
current_system->start_context(current_system, statefile);
render_video_loop();
} else if (was_paused) {
current_system->resume_context(current_system);
} else if (menu && game_system) {
current_system->arena = set_current_arena(game_system->arena);
current_system = game_system;
@@ -784,6 +777,7 @@ int main(int argc, char ** argv)
break;
}
}
#endif //__EMSCRIPTEN__
return 0;
}
+15
View File
@@ -0,0 +1,15 @@
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<assemblyIdentity version="0.6.3.0" name="retrodev.BlastEm"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</compatibility>
<asmv3:application>
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">
<activeCodePage>UTF-8</activeCodePage>
</asmv3:windowsSettings>
</asmv3:application>
</assembly>
+1 -1
View File
@@ -17,7 +17,7 @@ extern char *save_filename;
extern uint8_t use_native_states;
void reload_media(void);
void lockon_media(char *lock_on_path);
void init_system_with_media(const char *path, system_type force_stype);
void init_system_with_media(char *path, system_type force_stype);
void apply_updated_config(void);
const system_media *current_media(void);
+5 -4
View File
@@ -1,6 +1,6 @@
1 VERSIONINFO
FILEVERSION 0,4,0,0
PRODUCTVERSION 0,4,0,0
FILEVERSION 0,6,3,0
PRODUCTVERSION 0,6,3,0
BEGIN
BLOCK "StringFileInfo"
BEGIN
@@ -10,10 +10,10 @@ BEGIN
VALUE "FileDescription", "BlastEm - A fast and accurate Genesis/Megadrive emulator"
VALUE "FileVersion", "1.0"
VALUE "InternalName", "blastem"
VALUE "LegalCopyright", "Copyright 2011-2016 Michael Pavone"
VALUE "LegalCopyright", "Copyright 2011-2023 Michael Pavone"
VALUE "OriginalFilename", "blastem.exe"
VALUE "ProductName", "BlastEm"
VALUE "ProductVersion", "0.4.0"
VALUE "ProductVersion", "0.6.3-pre"
END
END
@@ -23,3 +23,4 @@ BEGIN
END
END
2 ICON "icons/windows.ico"
3 RTF_MANIFEST blastem.exe.manifest
+2 -2
View File
@@ -5,7 +5,7 @@ set -e
build_user=$1
needsu=$2
if [ "$needsu" = needsu ]; then
su $build_user -c "$0 $1";
su -l $build_user -c "$0 $1";
exit
fi
@@ -14,5 +14,5 @@ hg revert -a
hg pull
hg up
rev=`hg summary | sed -E -n 's/^parent: [^:]+:([^ ]+) .*$/\1/p'`
sed -i -E "s/(define BLASTEM_VERSION \"[^-]+)-pre\"/\1-pre-$rev\"/" blastem.c
sed -i -E "s/(define BLASTEM_VERSION \"[^-]+)-pre\"/\1-pre-$rev\"/" version.inc
./build_release
+29 -17
View File
@@ -5,20 +5,30 @@ set -e
if [ -z "$OS" ]; then
OS=`uname -s`
fi
SDL="sdl2"
if [ $OS = "Linux" ]; then
cd sdl
./configure
make all
cd ..
rm -rf lib
mkdir lib
cp sdl/build/.libs/libSDL2-*.so.*.*.* lib
sdl=`ls lib`
link=`echo $sdl | sed -E 's/(so\.[0-9]*).*/\1/'`
cd lib
ln -s "$sdl" "$link"
ln -s "$sdl" libSDL2.so
cd sdl
if [ -f ./configure ]; then
./configure
make all
cd ..
cp sdl/build/.libs/libSDL2-*.so.*.*.* lib
sdl=`ls lib`
link=`echo $sdl | sed -E 's/(so\.[0-9]*).*/\1/'`
cd lib
ln -s "$sdl" "$link"
ln -s "$sdl" libSDL2.so
elif [ -f ./CMakeLists.txt ]; then
cmake -S . -B build
cmake --build build
cp build/libSDL3.so* ../lib
SDL="sdl3"
else
echo "Failed to find SDL build file!";
exit 1
fi
cd ..
cd glew
make all
@@ -26,19 +36,19 @@ if [ $OS = "Linux" ]; then
fi
echo "Path is: $PATH"
if [ $OS = "Win64" ]; then
make PORTABLE=1 OS=Windows CPU=x86_64 clean all
make PORTABLE=1 OS=Windows CPU=x86_64 SDL=$SDL clean all
SDLDLLPATH=sdl/x86_64-w64-mingw32/bin
else
make PORTABLE=1 clean all
make PORTABLE=1 SDL=$SDL clean all
SDLDLLPATH=sdl/i686-w64-mingw32/bin
fi
make menu.bin tmss.md
if [ $OS = "Windows" -o $OS = "Win64" ]; then
binaries="dis.exe zdis.exe vgmplay.exe blastem.exe $SDLDLLPATH/SDL2.dll"
verstr=`sed -E -n 's/^[^B]+BLASTEM_VERSION "([^"]+)"/blastem \1/p' blastem.c`
binaries="blastem.exe $SDLDLLPATH/SDL2.dll"
verstr=`sed -E -n 's/^[^B]+BLASTEM_VERSION "([^"]+)"/blastem \1/p' version.inc`
txt=".txt"
else
binaries="dis zdis vgmplay blastem termhelper"
binaries="dis zdis blastem termhelper"
if [ $OS = "Darwin" ]; then
binaries="$binaries Frameworks"
else
@@ -68,8 +78,10 @@ for file in README COPYING CHANGELOG; do
done
if [ $OS = "Darwin" ]; then
cp SDL-LICENSE "$dir"
else
elif [ $OS = "Windows" -o $OS = "Win64" ]; then
cp sdl/COPYING.txt "$dir"/SDL-LICENSE$txt
else
cp sdl/LICENSE.txt "$dir"/SDL-LICENSE$txt
fi
cp glew/LICENSE.txt "$dir"/GLEW-LICENSE$txt
+76
View File
@@ -0,0 +1,76 @@
#!/usr/bin/env bash
name=$1
. $HOME/software/emsdk/emsdk_env.sh
. "$HOME/$name.params"
cd $HOME/$DIR
hg revert -a
hg pull
hg up
rev=`hg summary | sed -E -n 's/^parent: [^:]+:([^ ]+) .*$/\1/p'`
sed -i -E "s/(define BLASTEM_VERSION \"[^-]+)-pre\"/\1-pre-$rev\"/" version.inc
make CC=emcc CPU=wasm m68k.c z80.c -j2
result=$?
echo "DIR $DIR"
if [ $result -ne 0 ]; then
echo Build falied with return code $result
curdate=`date -Iseconds`
curl -d'@-' -H 'Content-Type: application/json' "$WEBHOOKURL" <<WEBHOOKEOF
{
"embeds": [
{
"title": "Build $name failed!",
"type": "rich",
"description": "Build falied with return code $result",
"timestamp": "$curdate",
"color": 16711680
}
]
}
WEBHOOKEOF
exit $result
fi
make CC=emcc CPU=wasm blastem.html -j4
result=$?
if [ $result -ne 0 ]; then
echo Build falied with return code $result
curdate=`date -Iseconds`
curl -d'@-' -H 'Content-Type: application/json' "$WEBHOOKURL" <<WEBHOOKEOF
{
"embeds": [
{
"title": "Build $name failed!",
"type": "rich",
"description": "Build falied with return code $result",
"timestamp": "$curdate",
"color": 16711680
}
]
}
WEBHOOKEOF
exit $result
fi
. $HOME/remote.params
echo "Uploaing blastem.wasm and blastem.js to $REMOTE_HOST"
scp -i "$REMOTE_IDENT" "$HOME/$DIR/blastem.wasm" "$HOME/$DIR/blastem.js" $REMOTE_USER@$REMOTE_HOST:/home/$REMOTE_USER/web
curdate=`date -Iseconds`
version=`sed -E 's/^[^"]+"([^"]+)"$/\1/' version.inc`
curl -d'@-' -H 'Content-Type: application/json' "$WEBHOOKURL" <<WEBHOOKEOF
{
"embeds": [
{
"title": "BlastEm Web (emscripten)",
"type": "rich",
"url": "https://www.retrodev.com/blastem/web/",
"description": "New build of $name succeeded!",
"timestamp": "$curdate",
"color": 65280,
"fields": [
{
"name": "Version",
"value": "$version"
}
]
}
]
}
WEBHOOKEOF
+1 -1
View File
@@ -6,7 +6,7 @@ hg revert -a
hg pull
hg up
rev=`hg summary | sed -E -n 's/^parent: [^:]+:([^ ]+) .*$/\1/p'`
sed -i -E "s/(define BLASTEM_VERSION \"[^-]+)-pre\"/\1-pre-$rev\"/" blastem.c
sed -i -E "s/(define BLASTEM_VERSION \"[^-]+)-pre\"/\1-pre-$rev\"/" version.inc
export OS
./build_release > /tmp/build_${name}_out.log
result=$?
+354
View File
@@ -0,0 +1,354 @@
#include "cd_graphics.h"
#include "backend.h"
#include "render.h"
void cd_graphics_init(segacd_context *cd)
{
cd->graphics_int_cycle = CYCLE_NEVER;
}
#define BIT_HFLIP 0x8000
static uint8_t get_src_pixel(segacd_context *cd)
{
uint16_t x = cd->graphics_x >> 11;
uint16_t y = cd->graphics_y >> 11;
cd->graphics_x += cd->graphics_dx;
cd->graphics_x &= 0xFFFFFF;
cd->graphics_y += cd->graphics_dy;
cd->graphics_y &= 0xFFFFFF;
uint16_t stamp_shift, pixel_mask;
uint16_t stamp_num_mask;
if (cd->gate_array[GA_STAMP_SIZE] & BIT_STS) {
//32x32 stamps
stamp_shift = 5;
pixel_mask = 0x1F;
stamp_num_mask = 0x7FC;
} else {
//16x16 stamps
stamp_shift = 4;
pixel_mask = 0xF;
stamp_num_mask = 0x7FF;
}
uint16_t stamp_x = x >> stamp_shift;
uint16_t stamp_y = y >> stamp_shift;
uint16_t max, base_mask;
uint32_t row_shift;
if (cd->gate_array[GA_STAMP_SIZE] & BIT_SMS) {
max = 4096 >> stamp_shift;
base_mask = 0xE000 << ((5 - stamp_shift) << 1);
//128 stamps in 32x32 mode, 256 stamps in 16x16 mode
row_shift = 12 - stamp_shift;
} else {
max = 256 >> stamp_shift;
base_mask = 0xFFE0 << ((5 - stamp_shift) << 1);
//8 stamps in 32x32 mode, 16 stamps in 16x16 mode
row_shift = 8 - stamp_shift;
}
if (stamp_x >= max || stamp_y >= max) {
if (cd->gate_array[GA_STAMP_SIZE] & BIT_RPT) {
stamp_x &= max - 1;
stamp_y &= max - 1;
} else {
return 0;
}
}
uint32_t address = (cd->gate_array[GA_STAMP_MAP_BASE] & base_mask) << 1;
address += (stamp_y << row_shift) + stamp_x;
uint16_t stamp_def = cd->word_ram[address];
uint16_t stamp_num = stamp_def & stamp_num_mask;
if (!stamp_num) {
//manual says stamp 0 can't be used, I assume that means it's treated as transparent
return 0;
}
uint16_t pixel_x = x & pixel_mask;
uint16_t pixel_y = y & pixel_mask;
if (stamp_def & BIT_HFLIP) {
pixel_x = pixel_mask - pixel_x;
}
uint16_t tmp;
switch (stamp_def >> 13 & 3)
{
case 0:
break;
case 1:
tmp = pixel_y;
pixel_y = pixel_x;
pixel_x = pixel_mask - tmp;
break;
case 2:
pixel_y = pixel_mask - pixel_y;
pixel_x = pixel_mask - pixel_x;
break;
case 3:
tmp = pixel_y;
pixel_y = pixel_mask - pixel_x;
pixel_x = tmp;
break;
}
uint16_t cell_x = pixel_x >> 3;
uint32_t pixel_address = stamp_num << 6;
pixel_address += (pixel_y << 1) + (cell_x << (stamp_shift + 1)) + (pixel_x >> 2 & 1);
uint16_t word = cd->word_ram[pixel_address];
switch (pixel_x & 3)
{
default:
case 0:
return word >> 12;
case 1:
return word >> 8 & 0xF;
case 2:
return word >> 4 & 0xF;
case 3:
return word & 0xF;
}
}
enum {
FETCH_X,
FETCH_Y,
FETCH_DX,
FETCH_DY,
PIXEL0,
PIXEL1,
PIXEL2,
PIXEL3,
DRAW
};
void draw_pixels(segacd_context *cd)
{
uint16_t to_draw = 4 - (cd->graphics_dst_x & 3);
uint16_t x_end = cd->gate_array[GA_IMAGE_BUFFER_HDOTS] + (cd->gate_array[GA_IMAGE_BUFFER_OFFSET] & 7);
if (cd->graphics_dst_x + to_draw > x_end) {
to_draw = cd->gate_array[GA_IMAGE_BUFFER_HDOTS] + (cd->gate_array[GA_IMAGE_BUFFER_OFFSET] & 7) - cd->graphics_dst_x;
}
for(uint16_t i = 0; i < to_draw; i++)
{
uint32_t dst_address = cd->gate_array[GA_IMAGE_BUFFER_START] << 1;
dst_address += cd->graphics_dst_y << 1;
dst_address += cd->graphics_dst_x >> 2 & 1;
dst_address += ((cd->graphics_dst_x >> 3) * (cd->gate_array[GA_IMAGE_BUFFER_VCELLS] + 1)) << 4;
uint16_t pixel_shift = 12 - 4 * (cd->graphics_dst_x & 3);
uint16_t pixel = cd->graphics_pixels[i] << pixel_shift;
uint16_t src_mask_check = 0xF << pixel_shift;
uint16_t src_mask_keep = ~src_mask_check;
pixel &= src_mask_check;
switch (cd->gate_array[1] >> 3 & 3)
{
case 0:
//priority mode off
cd->word_ram[dst_address] &= src_mask_keep;
cd->word_ram[dst_address] |= pixel;
break;
case 1:
//underwrite
if (pixel && ! (cd->word_ram[dst_address] & src_mask_check)) {
cd->word_ram[dst_address] &= src_mask_keep;
cd->word_ram[dst_address] |= pixel;
}
break;
case 2:
//overwrite
if (pixel) {
cd->word_ram[dst_address] &= src_mask_keep;
cd->word_ram[dst_address] |= pixel;
}
break;
}
cd->graphics_dst_x++;
}
if (cd->graphics_dst_x == x_end) {
cd->graphics_dst_y++;
--cd->gate_array[GA_IMAGE_BUFFER_LINES];
cd->gate_array[GA_TRACE_VECTOR_BASE] += 2;
cd->graphics_step = FETCH_X;
} else {
cd->graphics_step = PIXEL0;
}
}
#define CHECK_CYCLES cd->graphics_step++; if(cd->graphics_cycle >= cycle) break
#define CHECK_ONLY if(cd->graphics_cycle >= cycle) break
static void do_graphics(segacd_context *cd, uint32_t cycle)
{
if (!cd->gate_array[GA_IMAGE_BUFFER_LINES]) {
return;
}
while (cd->graphics_cycle < cycle)
{
switch (cd->graphics_step)
{
case FETCH_X:
cd->graphics_x = cd->word_ram[cd->gate_array[GA_TRACE_VECTOR_BASE] << 1] << 8;
cd->graphics_cycle += 3*4;
cd->graphics_dst_x = cd->gate_array[GA_IMAGE_BUFFER_OFFSET] & 7;
CHECK_CYCLES;
case FETCH_Y:
cd->graphics_y = cd->word_ram[(cd->gate_array[GA_TRACE_VECTOR_BASE] << 1) + 1] << 8;
cd->graphics_cycle += 2*4;
CHECK_CYCLES;
case FETCH_DX:
cd->graphics_dx = cd->word_ram[(cd->gate_array[GA_TRACE_VECTOR_BASE] << 1) + 2];
if (cd->graphics_dx & 0x8000) {
cd->graphics_dx |= 0xFF0000;
}
cd->graphics_cycle += 2*4;
CHECK_CYCLES;
case FETCH_DY:
cd->graphics_dy = cd->word_ram[(cd->gate_array[GA_TRACE_VECTOR_BASE] << 1) + 3];
if (cd->graphics_dy & 0x8000) {
cd->graphics_dy |= 0xFF0000;
}
cd->graphics_cycle += 2*4;
CHECK_CYCLES;
case PIXEL0:
cd->graphics_pixels[0] = get_src_pixel(cd);
cd->graphics_cycle += 2*4;
if ((cd->graphics_dst_x & 3) == 3 || (cd->graphics_dst_x + 1 == cd->gate_array[GA_IMAGE_BUFFER_HDOTS] + (cd->gate_array[GA_IMAGE_BUFFER_OFFSET] & 7))) {
cd->graphics_step = DRAW;
CHECK_ONLY;
goto draw;
} else {
CHECK_CYCLES;
}
case PIXEL1:
cd->graphics_pixels[1] = get_src_pixel(cd);
cd->graphics_cycle += 2*4;
if ((cd->graphics_dst_x & 3) == 2 || (cd->graphics_dst_x + 2 == cd->gate_array[GA_IMAGE_BUFFER_HDOTS] + (cd->gate_array[GA_IMAGE_BUFFER_OFFSET] & 7))) {
cd->graphics_step = DRAW;
CHECK_ONLY;
goto draw;
} else {
CHECK_CYCLES;
}
case PIXEL2:
cd->graphics_pixels[2] = get_src_pixel(cd);
cd->graphics_cycle += 2*4;
if ((cd->graphics_dst_x & 3) == 1 || (cd->graphics_dst_x + 3 == cd->gate_array[GA_IMAGE_BUFFER_HDOTS] + (cd->gate_array[GA_IMAGE_BUFFER_OFFSET] & 7))) {
cd->graphics_step = DRAW;
CHECK_ONLY;
goto draw;
} else {
CHECK_CYCLES;
}
case PIXEL3:
cd->graphics_pixels[3] = get_src_pixel(cd);
cd->graphics_cycle += 2*4;
CHECK_CYCLES;
draw:
case DRAW:
draw_pixels(cd);
cd->graphics_cycle += 1*4;
if (!cd->gate_array[GA_IMAGE_BUFFER_LINES]) {
return;
}
CHECK_ONLY;
}
}
}
void scd_toggle_graphics_debug(segacd_context *cd)
{
if (cd->graphics_debug_window) {
render_destroy_window(cd->graphics_debug_window);
cd->graphics_debug_window = 0;
} else {
cd->graphics_debug_window = render_create_window("CD ASIC", 1024, 1024, NULL);
}
}
static void render_graphics_debug(segacd_context *cd)
{
int pitch;
pixel_t *fb = render_get_framebuffer(cd->graphics_debug_window, &pitch);
uint32_t pixels = (cd->gate_array[GA_STAMP_SIZE] & BIT_SMS) ? 4096 : 256;
uint32_t stamp_size = (cd->gate_array[GA_STAMP_SIZE] & BIT_STS) ? 32 : 16;
uint32_t num_stamps = pixels / stamp_size;
//TODO: fix mask based on SMS and STS
uint32_t address = (cd->gate_array[GA_STAMP_MAP_BASE] & 0xFFE0) << 1;
genesis_context *gen = cd->genesis;
for (uint32_t stamp_y = 0; stamp_y < num_stamps; stamp_y++)
{
uint32_t start_y = stamp_y * stamp_size;
for (uint32_t stamp_x = 0; stamp_x < num_stamps; stamp_x++)
{
uint16_t entry = cd->word_ram[address++];
uint32_t tile_address = (entry & 0x7FF) << 6;
//TODO: flip and rotation
uint32_t start_x = stamp_x * stamp_size;
if (start_x < 1024 && start_y < 1024) {
for (uint32_t tile_x = start_x; tile_x < start_x + stamp_size; tile_x+=8)
{
for (uint32_t y = start_y; y < start_y + stamp_size; y++)
{
pixel_t *line = fb + y * pitch / sizeof(uint32_t);
for (uint32_t x = tile_x; x < tile_x + 8; x += 4)
{
uint16_t word = cd->word_ram[tile_address++];
uint16_t pixels[4] = {
word >> 12,
word >> 8 & 0xF,
word >> 4 & 0xF,
word & 0xF
};
for (uint32_t i = 0; i < 4; i++)
{
line[x + i] = gen->vdp->colors[pixels[i]];
}
}
}
}
}
}
}
render_framebuffer_updated(cd->graphics_debug_window, 1024);
}
void cd_graphics_run(segacd_context *cd, uint32_t cycle)
{
while (cd->graphics_cycle < cycle)
{
if (cd->gate_array[GA_STAMP_SIZE] & BIT_GRON) {
do_graphics(cd, cycle);
//end calculation and actual emulated execution time probably don't 100% line up yet
//deal with that here for now
for(; cd->graphics_cycle < cycle; cd->graphics_cycle += 4)
{
}
if (cd->graphics_cycle >= cd->graphics_int_cycle) {
cd->gate_array[GA_STAMP_SIZE] &= ~BIT_GRON;
if (cd->graphics_debug_window) {
render_graphics_debug(cd);
}
break;
}
} else {
cd->graphics_cycle = cycle;
}
}
}
void cd_graphics_start(segacd_context *cd)
{
if (!(cd->gate_array[GA_STAMP_SIZE] & BIT_GRON)) {
cd->gate_array[GA_STAMP_SIZE] |= BIT_GRON;
//Manual scan is bad, but formula appears to be
// vsize * (13 + 2 * hoffset + 9 * (hdots + hoffset - 1))
//with an additional 13? cycle setup cost per line
uint32_t lines = cd->gate_array[GA_IMAGE_BUFFER_LINES];
uint32_t hdots = cd->gate_array[GA_IMAGE_BUFFER_HDOTS];
uint32_t hoffset = cd->gate_array[GA_IMAGE_BUFFER_OFFSET] & 7;
uint16_t pm = cd->gate_array[1] >> 3 & 3;
cd->graphics_int_cycle = cd->graphics_cycle + 4 * lines * (13 + 2 * hoffset + 9 * (hdots + hoffset - 1));
cd->graphics_dst_y = cd->gate_array[GA_IMAGE_BUFFER_OFFSET] >> 3;
cd->graphics_step = FETCH_X;
} else {
printf("graphics start ignored @ %u\n", cd->graphics_cycle);
}
}
+27
View File
@@ -0,0 +1,27 @@
#ifndef CD_GRAPHICS_H_
#define CD_GRAPHICS_H_
#include "segacd.h"
enum {
GA_STAMP_SIZE = 0x58/2,
GA_STAMP_MAP_BASE,
GA_IMAGE_BUFFER_VCELLS,
GA_IMAGE_BUFFER_START,
GA_IMAGE_BUFFER_OFFSET,
GA_IMAGE_BUFFER_HDOTS,
GA_IMAGE_BUFFER_LINES,
GA_TRACE_VECTOR_BASE
};
//GA_STAMP_SIZE
#define BIT_GRON 0x8000
#define BIT_SMS 0x0004
#define BIT_STS 0x0002
#define BIT_RPT 0x0001
void cd_graphics_init(segacd_context *cd);
void cd_graphics_run(segacd_context *cd, uint32_t cycle);
void cd_graphics_start(segacd_context *cd);
#endif //CD_GRAPHICS_H_
+99
View File
@@ -0,0 +1,99 @@
#include "cdd_fader.h"
#include <stdio.h>
#define CDDA_MCLKS 16934400
void cdd_fader_init(cdd_fader *fader)
{
fader->audio = render_audio_source("CDDA", CDDA_MCLKS, 384, 2);
fader->cur_attenuation = 0x4000;
fader->dst_attenuation = 0x4000;
fader->attenuation_step = 0;
}
void cdd_fader_deinit(cdd_fader *fader)
{
render_free_source(fader->audio);
}
void cdd_fader_set_speed_percent(cdd_fader *fader, uint32_t percent)
{
uint32_t new_clock = ((uint64_t)CDDA_MCLKS * (uint64_t)percent) / 100;
render_audio_adjust_clock(fader->audio, new_clock, 384);
}
void cdd_fader_attenuation_write(cdd_fader *fader, uint16_t attenuation)
{
fader->dst_attenuation = attenuation & 0xFFF0;
fader->flags = attenuation & 0xE;
if (fader->dst_attenuation > fader->cur_attenuation) {
fader->attenuation_step = (fader->dst_attenuation - fader->cur_attenuation) >> 4;
} else if (fader->dst_attenuation < fader->cur_attenuation) {
fader->attenuation_step = (fader->cur_attenuation - fader->dst_attenuation) >> 4;
} else {
fader->attenuation_step = 0;
}
}
void cdd_fader_data(cdd_fader *fader, uint8_t byte)
{
fader->bytes[fader->byte_counter++] = byte;
if (fader->byte_counter == sizeof(fader->bytes)) {
fader->byte_counter = 0;
int32_t left = (fader->bytes[1] << 8) | fader->bytes[0];
int32_t right = (fader->bytes[3] << 8) | fader->bytes[2];
if (left & 0x8000) {
left |= 0xFFFF0000;
}
if (right & 0x8000) {
right |= 0xFFFF0000;
}
if (!fader->cur_attenuation) {
left = right = 0;
} else if (fader->cur_attenuation >= 4) {
left *= fader->cur_attenuation & 0x7FF0;
right *= fader->cur_attenuation & 0x7FF0;
left >>= 14;
right >>= 14;
} else {
//TODO: FIXME
left = right = 0;
}
render_put_stereo_sample(fader->audio, left, right);
if (fader->attenuation_step) {
if (fader->dst_attenuation > fader->cur_attenuation) {
fader->cur_attenuation += fader->attenuation_step;
if (fader->cur_attenuation >= fader->dst_attenuation) {
fader->cur_attenuation = fader->dst_attenuation;
fader->attenuation_step = 0;
}
} else {
fader->cur_attenuation -= fader->attenuation_step;
if (fader->cur_attenuation <= fader->dst_attenuation) {
fader->cur_attenuation = fader->dst_attenuation;
fader->attenuation_step = 0;
}
}
}
}
}
void cdd_fader_serialize(cdd_fader *fader, serialize_buffer *buf)
{
save_int16(buf, fader->cur_attenuation);
save_int16(buf, fader->dst_attenuation);
save_int16(buf, fader->attenuation_step);
save_int8(buf, fader->flags);
save_buffer8(buf, fader->bytes, sizeof(fader->bytes));
save_int8(buf, fader->byte_counter);
}
void cdd_fader_deserialize(deserialize_buffer *buf, void *vfader)
{
cdd_fader *fader = vfader;
fader->cur_attenuation = load_int16(buf);
fader->dst_attenuation = load_int16(buf);
fader->attenuation_step = load_int16(buf);
fader->flags = load_int8(buf);
load_buffer8(buf, fader->bytes, sizeof(fader->bytes));
fader->byte_counter = load_int8(buf);
}
+26
View File
@@ -0,0 +1,26 @@
#ifndef CDD_FADER_H_
#define CDD_FADER_H_
#include "render_audio.h"
#include "serialize.h"
typedef struct {
audio_source *audio;
uint16_t cur_attenuation;
uint16_t dst_attenuation;
uint16_t attenuation_step;
uint8_t flags;
uint8_t bytes[4];
uint8_t byte_counter;
} cdd_fader;
void cdd_fader_init(cdd_fader *fader);
void cdd_fader_deinit(cdd_fader *fader);
void cdd_fader_set_speed_percent(cdd_fader *fader, uint32_t percent);
void cdd_fader_attenuation_write(cdd_fader *fader, uint16_t attenuation);
void cdd_fader_data(cdd_fader *fader, uint8_t byte);
void cdd_fader_pause(cdd_fader *fader);
void cdd_fader_serialize(cdd_fader *fader, serialize_buffer *buf);
void cdd_fader_deserialize(deserialize_buffer *buf, void *vfader);
#endif //CDD_FADER_H_
+901
View File
@@ -0,0 +1,901 @@
#include <stdlib.h>
#include <string.h>
#include "cdd_mcu.h"
#include "backend.h"
#ifdef DO_DEBUG_PRINT
#define dprintf printf
#define dputs puts
#else
#define dprintf
#define dputs
#endif
#define SCD_MCLKS 50000000
#define CD_BLOCK_CLKS 16934400
#define CDD_MCU_DIVIDER 8
#define SECTORS_PER_SECOND 75
#define SECTOR_CLOCKS (CD_BLOCK_CLKS/SECTORS_PER_SECOND)
#define NIBBLE_CLOCKS (CDD_MCU_DIVIDER * 77)
#define BYTE_CLOCKS (SECTOR_CLOCKS/2352) // 96
#define SUBCODE_CLOCKS (SECTOR_CLOCKS/98)
#define PROCESSING_DELAY 121600 //approximate, based on relative level 4 and level 5 interrupt timing on MCD2 in pause_test
//lead in start max diameter 46 mm
//program area start max diameter 50 mm
//difference 4 mm = 4000 um
//radius difference 2 mm = 2000 um
//track pitch 1.6 um
//1250 physical tracks in between
//linear speed 1.2 m/s - 1.4 m/s
// 1.2 m = 1200 mm
// circumference at 46 mm ~ 144.51 mm
// circumference at 50 mm ~ 157.08 mm
// avg is 150.795
// 75 sectors per second
// 16 mm "typical" length of a sector
// ~9.4 sectors per track in lead-in area
#define LEADIN_SECTORS 11780
static uint32_t cd_block_to_mclks(uint32_t cycles)
{
return ((uint64_t)cycles) * ((uint64_t)SCD_MCLKS) / ((uint64_t)CD_BLOCK_CLKS);
}
static uint32_t mclks_to_cd_block(uint32_t cycles)
{
return ((uint64_t)cycles) * ((uint64_t)CD_BLOCK_CLKS) / ((uint64_t)SCD_MCLKS);
}
void cdd_mcu_init(cdd_mcu *context, system_media *media)
{
context->next_int_cycle = CYCLE_NEVER;
context->next_subcode_int_cycle = CYCLE_NEVER;
context->last_sector_cycle = CYCLE_NEVER;
context->last_nibble_cycle = CYCLE_NEVER;
context->next_byte_cycle = 0;
context->next_subcode_cycle = CYCLE_NEVER;
context->requested_format = SF_NOTREADY;
context->media = media;
context->current_status_nibble = -1;
context->current_cmd_nibble = -1;
context->current_sector_byte = -1;
context->current_subcode_byte = -1;
context->current_subcode_dest = 0;
}
enum {
GAO_CDD_CTRL,
GAO_CDD_STATUS,
GAO_CDD_CMD = GAO_CDD_STATUS+5,
GAO_SUBCODE_ADDR = (0x68-0x36)/2,
GAO_SUBCODE_START = (0x100-0x36)/2
};
//GAO_CDD_CTRL
#define BIT_MUTE 0x100
#define BIT_HOCK 0x0004
#define BIT_DRS 0x0002
#define BIT_DTS 0x0001
static uint8_t checksum(uint8_t *vbuffer)
{
uint8_t *buffer = vbuffer;
uint8_t sum = 0;
for (int i = 0; i < 9; i++)
{
sum += buffer[i];
}
return (~sum) & 0xF;
}
#define MIN_CIRCUMFERENCE 144.51f
#define MAX_CIRCUMFERENCE 364.42f
#define SECTOR_LENGTH 16.0f
// max diameter for program area 116 mm
// circumference ~ 364.42 mm
// ~ 23 sectors per physical track at edge
// ~9 sectors per physical track at start of lead-in
// seek test suggests average somewhere around 54-60 tracks for a long seek, with a peak around 80
// Sonic CD title screen seems to need a much higher value to get reasonable sync
#define COARSE_SEEK_TRACKS 60
static float sectors_per_track_at_pba(uint32_t pba)
{
//TODO: better estimate of sectors per track at current head location
float circumference = (MAX_CIRCUMFERENCE-MIN_CIRCUMFERENCE) * ((float)pba) / ((74 * 60 + 41) * SECTORS_PER_SECOND + LEADIN_SECTORS + 22) + MIN_CIRCUMFERENCE;
return circumference / SECTOR_LENGTH;
}
static void handle_seek(cdd_mcu *context)
{
uint32_t old_coarse = context->coarse_seek;
if (context->seeking == 2) {
context->head_pba = context->seek_pba;
context->coarse_seek = 6;
context->seeking = 0;
} else if (context->seeking) {
if (context->seek_pba == context->head_pba) {
context->seeking = 0;
context->coarse_seek = 0;
if (context->status == DS_PAUSE && !context->pause_pba) {
context->pause_pba = context->head_pba;
}
} else {
//TODO: drive will periodically lose tracking when seeking which slows
//things down periodically, I estimate the average
float sectors_per_track = sectors_per_track_at_pba(context->head_pba);
uint32_t max_seek = sectors_per_track * COARSE_SEEK_TRACKS;
uint32_t min_seek = sectors_per_track;
uint32_t old_pba = context->head_pba;
if (context->seek_pba > context->head_pba) {
uint32_t seek_amount;
for (seek_amount = max_seek; seek_amount >= min_seek; seek_amount >>= 1)
{
if (context->seek_pba - context->head_pba >= seek_amount) {
break;
}
}
if (seek_amount >= min_seek) {
context->head_pba += seek_amount;
} else {
context->head_pba++;
}
} else {
uint32_t seek_amount;
for (seek_amount = max_seek; seek_amount >= min_seek;)
{
uint32_t next_seek = seek_amount >> 1;
if (context->head_pba - context->seek_pba > next_seek) {
break;
}
seek_amount = next_seek;
}
if (seek_amount >= min_seek && context->head_pba >= seek_amount) {
context->head_pba -= seek_amount;
} else if (context->head_pba >= min_seek){
context->head_pba -= min_seek;
} else {
context->head_pba = 0;
}
}
if (context->head_pba != old_pba + 1) {
context->coarse_seek++;
} else {
context->coarse_seek = 0;
}
}
} else {
context->coarse_seek = 0;
}
}
static void lba_to_status(cdd_mcu *context, uint32_t lba)
{
uint32_t seconds = lba / 75;
uint32_t frames = lba % 75;
uint32_t minutes = seconds / 60;
seconds = seconds % 60;
context->status_buffer.b.time.min_high = minutes / 10;
context->status_buffer.b.time.min_low = minutes % 10;
context->status_buffer.b.time.sec_high = seconds / 10;
context->status_buffer.b.time.sec_low = seconds % 10;
context->status_buffer.b.time.frame_high = frames / 10;
context->status_buffer.b.time.frame_low = frames % 10;
}
static void update_status(cdd_mcu *context, uint16_t *gate_array)
{
gate_array[GAO_CDD_CTRL] |= BIT_MUTE;
switch (context->status)
{
case DS_STOP:
handle_seek(context);
break;
case DS_PLAY:
handle_seek(context);
if (!context->seeking) {
context->head_pba++;
}
if (context->head_pba >= LEADIN_SECTORS) {
uint8_t track = context->media->seek(context->media, context->head_pba - LEADIN_SECTORS);
if (!context->seeking && context->media->tracks[track].type == TRACK_AUDIO) {
gate_array[GAO_CDD_CTRL] &= ~BIT_MUTE;
}
}
break;
case DS_PAUSE:
handle_seek(context);
if (!context->seeking) {
context->head_pba++;
if (context->head_pba > context->pause_pba) {
uint32_t back = sectors_per_track_at_pba(context->head_pba) + 0.5f;
if (back > context->head_pba) {
back = context->head_pba;
}
context->head_pba -= back;
context->coarse_seek = 6;
}
}
if (context->head_pba >= LEADIN_SECTORS) {
context->media->seek(context->media, context->head_pba - LEADIN_SECTORS);
}
break;
case DS_TOC_READ:
handle_seek(context);
if (!context->seeking) {
context->head_pba++;
if (context->media && context->media->type == MEDIA_CDROM && context->media->num_tracks) {
if (context->head_pba > 3*(context->media->num_tracks + 2)) {
context->toc_valid = 1;
context->seeking = 1;
context->seek_pba = LEADIN_SECTORS + context->media->tracks[0].start_lba;
context->status = DS_PAUSE;
}
} else {
context->status = DS_NO_DISC;
}
}
break;
case DS_TRACKING:
handle_seek(context);
if (!context->seeking) {
context->status = DS_PAUSE;
context->pause_pba = context->head_pba;
}
if (context->head_pba >= LEADIN_SECTORS) {
uint8_t track = context->media->seek(context->media, context->head_pba - LEADIN_SECTORS);
if (!context->seeking && context->media->tracks[track].type == TRACK_AUDIO) {
gate_array[GAO_CDD_CTRL] &= ~BIT_MUTE;
}
}
break;
}
uint8_t force_not_ready = 0;
if (context->coarse_seek && !(context->coarse_seek % 6)) {
//TODO: adjust seeking for focus error when these bad statuses happen
//BIOS depends on getting a not ready status during seeking to clear certain state
force_not_ready = context->status_buffer.format != SF_NOTREADY;
}
if (context->first_cmd_received) {
switch (force_not_ready ? SF_NOTREADY : context->requested_format)
{
case SF_ABSOLUTE:
if (context->toc_valid && context->head_pba >= LEADIN_SECTORS) {
lba_to_status(context, context->head_pba - LEADIN_SECTORS);
context->status_buffer.format = SF_ABSOLUTE;
} else {
context->status_buffer.format = SF_NOTREADY;
}
break;
case SF_RELATIVE:
if (context->toc_valid && context->head_pba >= LEADIN_SECTORS) {
uint32_t lba =context->head_pba - LEADIN_SECTORS;
for (uint32_t i = 0; i < context->media->num_tracks; i++)
{
if (lba < context->media->tracks[i].end_lba) {
if (lba < context->media->tracks[i].start_lba) {
//relative time counts down to 0 in pregap
lba = context->media->tracks[i].start_lba - lba;
} else {
lba -= context->media->tracks[i].start_lba;
}
break;
}
}
lba_to_status(context, lba);
context->status_buffer.format = SF_RELATIVE;
} else {
context->status_buffer.format = SF_NOTREADY;
}
break;
case SF_TRACK:
if (context->toc_valid && context->head_pba >= LEADIN_SECTORS) {
uint32_t lba =context->head_pba - LEADIN_SECTORS;
uint32_t i;
for (i = 0; i < context->media->num_tracks; i++)
{
if (lba < context->media->tracks[i].end_lba) {
break;
}
}
context->status_buffer.b.track.track_high = (i + 1) / 10;
context->status_buffer.b.track.track_low = (i + 1) % 10;
if (context->media->tracks[i].type == TRACK_DATA) {
context->status_buffer.b.track.control = 4;
} else {
//TODO: pre-emphasis flag
//TODO: copy permitted flag
context->status_buffer.b.track.control = 0;
}
context->status_buffer.b.track.adr = 1;
context->status_buffer.format = SF_TRACK;
} else {
context->status_buffer.format = SF_NOTREADY;
}
break;
case SF_TOCO:
if (context->toc_valid) {
lba_to_status(context, context->media->tracks[context->media->num_tracks - 1].end_lba);
context->status_buffer.format = SF_TOCO;
} else {
context->status_buffer.format = SF_NOTREADY;
}
break;
case SF_TOCT:
if (context->toc_valid) {
context->status_buffer.b.toct.first_track_high = 0;
context->status_buffer.b.toct.first_track_low = 1;
context->status_buffer.b.toct.last_track_high = (context->media->num_tracks) / 10;
context->status_buffer.b.toct.last_track_low = (context->media->num_tracks) % 10;
context->status_buffer.b.toct.version = 0;
context->status_buffer.format = SF_TOCT;
} else {
context->status_buffer.format = SF_NOTREADY;
}
break;
case SF_TOCN:
if (context->toc_valid) {
if (context->requested_track > context->media->num_tracks) {
dprintf("track number %d is bad\n", context->requested_track);
exit(0);
}
uint32_t lba = context->media->tracks[context->requested_track - 1].start_lba;
lba_to_status(context, lba);
if (context->media->tracks[context->requested_track - 1].type == TRACK_DATA) {
context->status_buffer.b.tocn.frame_high |= 0x8;
}
context->status_buffer.b.tocn.track_low = context->requested_track % 10;
context->status_buffer.format = SF_TOCN;
} else {
context->status_buffer.format = SF_NOTREADY;
}
break;
case SF_NOTREADY:
memset(&context->status_buffer, 0, sizeof(context->status_buffer) - 1);
context->status_buffer.format = SF_NOTREADY;
break;
}
if (context->error_status == DS_STOP) {
if (context->requested_format >= SF_TOCO && context->requested_format <= SF_TOCN) {
context->status_buffer.status = DS_TOC_READ;
} else if (context->seeking && context->status != DS_TRACKING) {
context->status_buffer.status = DS_SEEK;
} else {
context->status_buffer.status = context->status;
}
} else {
context->status_buffer.status = context->error_status;
context->status_buffer.format = SF_NOTREADY;
context->error_status = DS_STOP;
}
if (context->requested_format != SF_TOCN) {
context->status_buffer.b.time.flags = !!(gate_array[GAO_CDD_CTRL] & BIT_MUTE); //TODO: populate these
}
} else {
// Did not receive our first command so just send zeroes
memset(&context->status_buffer, 0, sizeof(context->status_buffer) - 1);
}
context->status_buffer.checksum = checksum((uint8_t *)&context->status_buffer);
if (context->status_buffer.format != SF_NOTREADY || (context->status != DS_STOP && context->status < DS_SUM_ERROR)) {
dprintf("CDD Status %X%X.%X%X%X%X%X%X.%X%X (lba %u)\n",
context->status_buffer.status, context->status_buffer.format,
context->status_buffer.b.time.min_high, context->status_buffer.b.time.min_low,
context->status_buffer.b.time.sec_high, context->status_buffer.b.time.sec_low,
context->status_buffer.b.time.frame_high, context->status_buffer.b.time.frame_low,
context->status_buffer.b.time.flags, context->status_buffer.checksum, context->head_pba - LEADIN_SECTORS
);
}
}
static void run_command(cdd_mcu *context)
{
uint8_t check = checksum((uint8_t*)&context->cmd_buffer);
if (check != context->cmd_buffer.checksum) {
context->error_status = DS_SUM_ERROR;
return;
}
if (context->cmd_buffer.must_be_zero) {
context->error_status = DS_CMD_ERROR;
return;
}
context->first_cmd_received = 1;
switch (context->cmd_buffer.cmd_type)
{
case CMD_NOP:
break;
case CMD_STOP:
dputs("CDD CMD: STOP");
context->status = DS_STOP;
context->requested_format = SF_ABSOLUTE;
break;
case CMD_READ:
case CMD_SEEK: {
if (context->status == DS_DOOR_OPEN || context->status == DS_TRAY_MOVING || context->status == DS_DISC_LEADOUT || context->status == DS_DISC_LEADIN) {
context->error_status = DS_CMD_ERROR;
break;
}
if (context->requested_format == SF_TOCT || context->requested_format == SF_TOCN || context->requested_format == SF_TOCO) {
context->requested_format = SF_ABSOLUTE;
}
if (!context->toc_valid) {
context->error_status = DS_CMD_ERROR;
break;
}
uint32_t lba = context->cmd_buffer.b.time.min_high * 10 + context->cmd_buffer.b.time.min_low;
lba *= 60;
lba += context->cmd_buffer.b.time.sec_high * 10 + context->cmd_buffer.b.time.sec_low;
lba *= 75;
lba += context->cmd_buffer.b.time.frame_high * 10 + context->cmd_buffer.b.time.frame_low;
dprintf("CDD CMD: %s cmd for lba %d, MM:SS:FF %u%u:%u%u:%u%u\n",
context->cmd_buffer.cmd_type == CMD_READ ? "READ" : "SEEK", lba,
context->cmd_buffer.b.time.min_high, context->cmd_buffer.b.time.min_low,
context->cmd_buffer.b.time.sec_high, context->cmd_buffer.b.time.sec_low,
context->cmd_buffer.b.time.frame_high, context->cmd_buffer.b.time.frame_low
);
if (lba >= context->media->tracks[context->media->num_tracks - 1].end_lba) {
context->error_status = DS_CMD_ERROR;
break;
}
context->seek_pba = lba + LEADIN_SECTORS - 3;
if (context->cmd_buffer.cmd_type == CMD_SEEK) {
context->pause_pba = lba + LEADIN_SECTORS;
}
context->seeking = 1;
context->status = context->cmd_buffer.cmd_type == CMD_READ ? DS_PLAY : DS_PAUSE;
break;
}
case CMD_REPORT_REQUEST:
switch (context->cmd_buffer.b.format.status_type)
{
case SF_ABSOLUTE:
case SF_RELATIVE:
case SF_TRACK:
context->requested_format = context->cmd_buffer.b.format.status_type;
break;
case SF_TOCO:
if (context->toc_valid) {
context->requested_format = SF_TOCO;
} else {
context->error_status = DS_CMD_ERROR;
context->requested_format = SF_ABSOLUTE;
}
break;
case SF_TOCT:
if (context->toc_valid) {
if (context->status == DS_STOP) {
context->status = DS_TOC_READ;
context->seeking = 1;
context->seek_pba = 0;
}
} else {
context->status = DS_TOC_READ;
context->seeking = 1;
context->seek_pba = 0;
}
context->requested_format = SF_TOCT;
break;
case SF_TOCN:
context->requested_track = context->cmd_buffer.b.format.track_high * 10;
context->requested_track += context->cmd_buffer.b.format.track_low;
if (!context->media || context->requested_track > context->media->num_tracks) {
context->requested_format = SF_ABSOLUTE;
context->error_status = DS_CMD_ERROR;
break;
}
context->status = DS_TOC_READ;
context->seeking = 1;
context->seek_pba = 0;
context->requested_format = SF_TOCN;
break;
}
dprintf("CDD CMD: REPORT REQUEST(%d), format set to %d\n", context->cmd_buffer.b.format.status_type, context->requested_format);
break;
case CMD_PAUSE:
if (context->status == DS_DOOR_OPEN || context->status == DS_TRAY_MOVING || context->status == DS_DISC_LEADOUT || context->status == DS_DISC_LEADIN) {
context->error_status = DS_CMD_ERROR;
break;
}
if (context->requested_format == SF_TOCT || context->requested_format == SF_TOCN) {
context->requested_format = SF_ABSOLUTE;
}
if (!context->toc_valid) {
context->error_status = DS_CMD_ERROR;
break;
}
if (context->status == DS_STOP) {
context->seeking = 1;
context->seek_pba = LEADIN_SECTORS + context->media->tracks[0].start_lba;
dprintf("CDD CMD: PAUSE, seeking to %u\n", context->seek_pba);
} else {
uint32_t lba = context->head_pba - LEADIN_SECTORS;
uint32_t seconds = lba / 75;
uint32_t frames = lba % 75;
uint32_t minutes = seconds / 60;
seconds = seconds % 60;
dprintf("CDD CMD: PAUSE, current lba %u, MM:SS:FF %02u:%02u:%02u\n", lba, minutes, seconds, frames);
}
context->status = DS_PAUSE;
if (context->seeking) {
//handle_seek will populate this
context->pause_pba = 0;
} else {
context->pause_pba = context->head_pba;
uint32_t back = 2.1f * sectors_per_track_at_pba(context->head_pba) + 0.5f;
if (back > context->head_pba) {
back = context->head_pba;
}
context->seek_pba = context->head_pba - back;
context->seeking = 2;
}
break;
case CMD_PLAY:
if (context->status == DS_DOOR_OPEN || context->status == DS_TRAY_MOVING || context->status == DS_DISC_LEADOUT || context->status == DS_DISC_LEADIN) {
context->error_status = DS_CMD_ERROR;
break;
}
if (context->requested_format == SF_TOCT || context->requested_format == SF_TOCN) {
context->requested_format = SF_ABSOLUTE;
}
if (!context->toc_valid) {
context->error_status = DS_CMD_ERROR;
break;
}
if (context->status == DS_STOP || context->status == DS_TOC_READ) {
context->seeking = 1;
context->seek_pba = LEADIN_SECTORS + context->media->tracks[0].start_lba - 4;
dprintf("CDD CMD: PLAY, seeking to %u\n", context->seek_pba);
} else {
dputs("CDD CMD: PLAY");
}
context->status = DS_PLAY;
break;
//TODO: CMD_FFWD, CMD_RWD
case CMD_TRACK_SKIP:
if (context->status != DS_PLAY && context->status != DS_PAUSE && context->status != DS_DISC_LEADOUT) {
context->error_status = DS_CMD_ERROR;
break;
}
if (context->requested_format == SF_TOCT || context->requested_format == SF_TOCN) {
context->requested_format = SF_ABSOLUTE;
}
if (!context->toc_valid) {
context->error_status = DS_CMD_ERROR;
break;
}
{
int32_t to_skip = context->cmd_buffer.b.skip.tracks_highest << 12 | context->cmd_buffer.b.skip.tracks_midhigh << 8
| context->cmd_buffer.b.skip.tracks_midlow << 4 | context->cmd_buffer.b.skip.tracks_lowest;
if (context->cmd_buffer.b.skip.direction) {
to_skip = -to_skip;
}
dprintf("CDD CMD: TRACK_SKIP direction %u, num_tracks %i, delta %i\n", context->cmd_buffer.b.skip.direction, abs(to_skip), to_skip);
//circumference at 83mm point (roughly half way between inner and outer edge of program area)
//~ 260.75cm ~ 15 sectors
context->seek_pba = context->head_pba + to_skip * 15;
context->seeking = 1;
}
context->status = DS_TRACKING;
break;
default:
dprintf("CDD CMD: Unimplemented(%d)\n", context->cmd_buffer.cmd_type);
}
}
void cdd_mcu_run(cdd_mcu *context, uint32_t cycle, uint16_t *gate_array, lc8951* cdc, cdd_fader* fader)
{
uint32_t cd_cycle = mclks_to_cd_block(cycle);
if (!(gate_array[GAO_CDD_CTRL] & BIT_HOCK)) {
//it's a little unclear if this gates the actual cd block clock or just handshaking
//assum it's actually the clock for now
for (; context->cycle < cd_cycle; context->cycle += CDD_MCU_DIVIDER) {
if (context->cycle >= context->next_byte_cycle) {
cdd_fader_data(fader, 0);
lc8951_write_byte(cdc, cd_block_to_mclks(context->cycle), 0, 0);
context->next_byte_cycle += BYTE_CLOCKS;
}
}
gate_array[GAO_CDD_CTRL] |= BIT_MUTE;
return;
}
uint32_t next_subcode = context->last_sector_cycle + SECTOR_CLOCKS;
uint32_t next_nibble;
if (context->current_status_nibble > 0) {
next_nibble = context->last_nibble_cycle + NIBBLE_CLOCKS;
} else if (!context->current_status_nibble) {
next_nibble = context->last_sector_cycle + PROCESSING_DELAY;
if (context->coarse_seek % 3) {
next_nibble += SECTOR_CLOCKS * (3 - (context->coarse_seek % 3));
}
} else {
next_nibble = CYCLE_NEVER;
}
uint32_t next_cmd_nibble = context->current_cmd_nibble >= 0 ? context->last_nibble_cycle + NIBBLE_CLOCKS : CYCLE_NEVER;
for (; context->cycle < cd_cycle; context->cycle += CDD_MCU_DIVIDER)
{
if (context->cycle >= next_subcode) {
uint32_t old_coarse = context->coarse_seek;
context->last_sector_cycle = context->cycle;
next_subcode = context->cycle + SECTOR_CLOCKS;
update_status(context, gate_array);
next_nibble = context->cycle + PROCESSING_DELAY;
if (context->coarse_seek % 3) {
next_nibble += SECTOR_CLOCKS * (3 - (context->coarse_seek % 3));
}
context->current_status_nibble = 0;
if (context->next_subcode_int_cycle != CYCLE_NEVER) {
context->subcode_int_pending = 1;
}
if ((context->status == DS_PLAY || context->status == DS_PAUSE) && context->head_pba >= LEADIN_SECTORS && !context->seeking) {
context->current_sector_byte = 0;
context->current_subcode_byte = 0;
context->next_subcode_cycle = context->cycle;
context->next_subcode_int_cycle = cd_block_to_mclks(next_subcode);
} else {
context->next_subcode_int_cycle = CYCLE_NEVER;
}
if (old_coarse != context->coarse_seek) {
context->next_int_cycle = cd_block_to_mclks(next_nibble + 7 * NIBBLE_CLOCKS);
}
}
if (context->cycle >= next_nibble) {
if (context->current_status_nibble == sizeof(cdd_status)) {
context->current_status_nibble = -1;
gate_array[GAO_CDD_CTRL] &= ~BIT_DRS;
if (context->cmd_recv_pending) {
context->cmd_recv_pending = 0;
context->current_cmd_nibble = 0;
gate_array[GAO_CDD_CTRL] |= BIT_DTS;
next_cmd_nibble = context->cycle + NIBBLE_CLOCKS;
} else {
context->cmd_recv_wait = 1;
}
next_nibble = CYCLE_NEVER;
} else {
gate_array[GAO_CDD_CTRL] |= BIT_DRS;
uint8_t value = ((uint8_t *)&context->status_buffer)[context->current_status_nibble];
int ga_index = GAO_CDD_STATUS + (context->current_status_nibble >> 1);
if (context->current_status_nibble & 1) {
gate_array[ga_index] = value | (gate_array[ga_index] & 0xFF00);
} else {
gate_array[ga_index] = (value << 8) | (gate_array[ga_index] & 0x00FF);
}
if (context->current_status_nibble == 7) {
if (!(context->coarse_seek % 3)) {
context->int_pending = 1;
if (context->coarse_seek) {
context->next_int_cycle = cd_block_to_mclks(context->cycle + 3 * SECTOR_CLOCKS);
} else {
context->next_int_cycle = cd_block_to_mclks(context->cycle + SECTOR_CLOCKS);
}
}
}
context->current_status_nibble++;
context->last_nibble_cycle = context->cycle;
next_nibble = context->cycle + NIBBLE_CLOCKS;
}
} else if (context->cycle >= next_cmd_nibble) {
if (context->current_cmd_nibble == sizeof(cdd_cmd)) {
next_cmd_nibble = CYCLE_NEVER;
context->current_cmd_nibble = -1;
gate_array[GAO_CDD_CTRL] &= ~BIT_DTS;
run_command(context);
} else {
int ga_index = GAO_CDD_CMD + (context->current_cmd_nibble >> 1);
uint8_t value = (context->current_cmd_nibble & 1) ? gate_array[ga_index] : gate_array[ga_index] >> 8;
((uint8_t *)&context->cmd_buffer)[context->current_cmd_nibble] = value;
context->current_cmd_nibble++;
context->last_nibble_cycle = context->cycle;
next_cmd_nibble = context->cycle + NIBBLE_CLOCKS;
}
}
if (context->cycle >= context->next_byte_cycle) {
if (context->current_sector_byte >= 0/* && (!fader->byte_counter || context->current_sector_byte)*/) {
if (!context->current_sector_byte) {
//HACK: things can get a little out of sync currently which causes a mess in the fader code
// since it expects even multiples of 4 bytes (1 stereo sample)
while (fader->byte_counter)
{
lc8951_write_byte(cdc, cd_block_to_mclks(context->cycle), 0, 0);
cdd_fader_data(fader, 0);
}
}
uint8_t byte = context->media->read(context->media, context->current_sector_byte);
if (context->status != DS_PLAY) {
byte = 0;
}
lc8951_write_byte(cdc, cd_block_to_mclks(context->cycle), context->current_sector_byte++, byte);
cdd_fader_data(fader, gate_array[GAO_CDD_CTRL] & BIT_MUTE ? 0 : byte);
} else {
lc8951_write_byte(cdc, cd_block_to_mclks(context->cycle), 0, 0);
cdd_fader_data(fader, 0);
}
if (context->current_sector_byte == 2352) {
context->current_sector_byte = -1;
}
context->next_byte_cycle += BYTE_CLOCKS;
}
if (context->cycle >= context->next_subcode_cycle) {
uint8_t byte;
if (!context->current_subcode_byte) {
byte = 0x9F;
//This probably happens after the second sync symbol, but doing it here simplifies things a little
context->current_subcode_dest &= 0x7E;
gate_array[GAO_SUBCODE_ADDR] = (context->current_subcode_dest - 96) & 0x7E;
} else if (context->current_subcode_byte == 1) {
byte = 0xFD;
} else {
byte = context->media->read_subcodes(context->media, context->current_subcode_byte - 2);
}
int offset = GAO_SUBCODE_START + (context->current_subcode_dest >> 1);
if (context->current_subcode_dest & 1) {
gate_array[offset] &= 0xFF00;
gate_array[offset] |= byte;
} else {
gate_array[offset] &= 0x00FF;
gate_array[offset] |= byte << 8;
}
context->current_subcode_byte++;
if (context->current_subcode_byte == 98) {
context->current_subcode_byte = 0;
} else if (context->current_subcode_byte == 32) {
gate_array[GAO_SUBCODE_ADDR] |= 0x80;
}
context->current_subcode_dest++;
context->current_subcode_dest &= 0x7F;
context->next_subcode_cycle += SUBCODE_CLOCKS;
}
}
}
void cdd_mcu_start_cmd_recv(cdd_mcu *context, uint16_t *gate_array)
{
if (gate_array[GAO_CDD_CTRL] & BIT_DTS) {
return;
}
if (context->cmd_recv_wait) {
context->current_cmd_nibble = 0;
gate_array[GAO_CDD_CTRL] |= BIT_DTS;
context->last_nibble_cycle = context->cycle;
context->cmd_recv_wait = 0;
} else {
context->cmd_recv_pending = 1;
}
}
void cdd_hock_enabled(cdd_mcu *context)
{
context->last_sector_cycle = context->cycle;
context->next_int_cycle = cd_block_to_mclks(context->cycle + SECTOR_CLOCKS + PROCESSING_DELAY + 7 * NIBBLE_CLOCKS);
if (context->coarse_seek % 3) {
context->next_int_cycle += cd_block_to_mclks(SECTOR_CLOCKS * (3 - (context->coarse_seek % 3)));
}
}
void cdd_hock_disabled(cdd_mcu *context)
{
context->last_sector_cycle = CYCLE_NEVER;
context->next_int_cycle = CYCLE_NEVER;
context->last_nibble_cycle = CYCLE_NEVER;
context->current_status_nibble = -1;
context->current_cmd_nibble = -1;
}
void cdd_mcu_adjust_cycle(cdd_mcu *context, uint32_t deduction)
{
uint32_t cd_deduction = mclks_to_cd_block(deduction);
if (context->cycle > cd_deduction) {
context->cycle -= cd_deduction;
} else {
context->cycle = 0;
}
if (context->next_int_cycle != CYCLE_NEVER) {
context->next_int_cycle -= deduction;
}
if (context->last_sector_cycle != CYCLE_NEVER) {
if (context->last_sector_cycle > cd_deduction) {
context->last_sector_cycle -= cd_deduction;
} else {
context->last_sector_cycle = 0;
}
}
if (context->last_nibble_cycle != CYCLE_NEVER) {
if (context->last_nibble_cycle > cd_deduction) {
context->last_nibble_cycle -= cd_deduction;
} else {
context->last_nibble_cycle = 0;
}
}
context->next_byte_cycle -= cd_deduction;
if (context->next_subcode_cycle != CYCLE_NEVER) {
context->next_subcode_cycle -= cd_deduction;
}
}
void cdd_mcu_serialize(cdd_mcu *context, serialize_buffer *buf)
{
save_int32(buf, context->cycle);
save_int32(buf, context->next_int_cycle);
save_int32(buf, context->next_subcode_int_cycle);
save_int32(buf, context->last_sector_cycle);
save_int32(buf, context->last_nibble_cycle);
save_int32(buf, context->next_byte_cycle);
save_int32(buf, context->next_subcode_cycle);
save_int8(buf, context->current_status_nibble);
save_int8(buf, context->current_cmd_nibble);
save_int16(buf, context->current_sector_byte);
save_int8(buf, context->current_subcode_byte);
save_int8(buf, context->current_subcode_dest);
save_int32(buf, context->head_pba);
save_int32(buf, context->seek_pba);
save_int32(buf, context->pause_pba);
save_int32(buf, context->coarse_seek);
save_buffer8(buf, (uint8_t *)&context->cmd_buffer, sizeof(context->cmd_buffer));
save_buffer8(buf, (uint8_t *)&context->status_buffer, sizeof(context->status_buffer));
save_int8(buf, context->requested_format);
save_int8(buf, context->status);
save_int8(buf, context->error_status);
save_int8(buf, context->requested_track);
save_int8(buf, context->cmd_recv_wait);
save_int8(buf, context->cmd_recv_pending);
save_int8(buf, context->int_pending);
save_int8(buf, context->subcode_int_pending);
save_int8(buf, context->toc_valid);
save_int8(buf, context->first_cmd_received);
save_int8(buf, context->seeking);
save_int8(buf, context->in_fake_pregap);
}
static int sign_extend8(uint8_t value)
{
if (value & 0x80) {
return value | 0xFFFFFF00;
} else {
return value;
}
}
static int sign_extend16(uint16_t value)
{
if (value & 0x8000) {
return value | 0xFFFF0000;
} else {
return value;
}
}
void cdd_mcu_deserialize(deserialize_buffer *buf, void *vcontext)
{
cdd_mcu *context = vcontext;
context->cycle = load_int32(buf);
context->next_int_cycle = load_int32(buf);
context->next_subcode_int_cycle = load_int32(buf);
context->last_sector_cycle = load_int32(buf);
context->last_nibble_cycle = load_int32(buf);
context->next_byte_cycle = load_int32(buf);
context->next_subcode_cycle = load_int32(buf);
context->current_status_nibble = sign_extend8(load_int8(buf));
context->current_cmd_nibble = sign_extend8(load_int8(buf));
context->current_sector_byte = sign_extend16(load_int16(buf));
context->current_subcode_byte = sign_extend8(load_int8(buf));
context->current_subcode_dest = sign_extend8(load_int8(buf));
context->head_pba = load_int32(buf);
context->seek_pba = load_int32(buf);
context->pause_pba = load_int32(buf);
context->coarse_seek = load_int32(buf);
load_buffer8(buf, (uint8_t *)&context->cmd_buffer, sizeof(context->cmd_buffer));
load_buffer8(buf, (uint8_t *)&context->status_buffer, sizeof(context->status_buffer));
context->requested_format = load_int8(buf);
context->status = load_int8(buf);
context->error_status = load_int8(buf);
context->requested_track = load_int8(buf);
context->cmd_recv_wait = load_int8(buf);
context->cmd_recv_pending = load_int8(buf);
context->int_pending = load_int8(buf);
context->subcode_int_pending = load_int8(buf);
context->toc_valid = load_int8(buf);
context->first_cmd_received = load_int8(buf);
context->seeking = load_int8(buf);
context->in_fake_pregap = load_int8(buf);
}
+179
View File
@@ -0,0 +1,179 @@
#ifndef CDD_MCU_H_
#define CDD_MCU_H_
#include "system.h"
#include "lc8951.h"
#include "cdd_fader.h"
#include "serialize.h"
typedef enum {
SF_ABSOLUTE,
SF_RELATIVE,
SF_TRACK,
SF_TOCO,
SF_TOCT,
SF_TOCN,
SF_E,
SF_NOTREADY = 0xF
} status_format;
typedef enum {
CMD_NOP,
CMD_STOP,
CMD_REPORT_REQUEST,
CMD_READ,
CMD_SEEK,
CMD_INVALID,
CMD_PAUSE,
CMD_PLAY,
CMD_FFWD,
CMD_RWD,
CMD_TRACK_SKIP,
CMD_TRACK_CUE,
CMD_DOOR_CLOSE,
CMD_DOOR_OPEN
} host_cmd;
typedef enum {
DS_STOP,
DS_PLAY,
DS_SEEK,
DS_SCAN,
DS_PAUSE,
DS_DOOR_OPEN,
DS_SUM_ERROR,
DS_CMD_ERROR,
DS_FUNC_ERROR,
DS_TOC_READ,
DS_TRACKING,
DS_NO_DISC,
DS_DISC_LEADOUT,
DS_DISC_LEADIN,
DS_TRAY_MOVING,
} drive_status;
typedef struct {
uint8_t status;
uint8_t format;
union {
struct {
uint8_t min_high;
uint8_t min_low;
uint8_t sec_high;
uint8_t sec_low;
uint8_t frame_high;
uint8_t frame_low;
uint8_t flags;
} time;
struct {
uint8_t track_high;
uint8_t track_low;
uint8_t padding0;
uint8_t padding1;
uint8_t control;
uint8_t adr;
uint8_t flags;
} track;
struct {
uint8_t first_track_high;
uint8_t first_track_low;
uint8_t last_track_high;
uint8_t last_track_low;
uint8_t version;
uint8_t padding;
uint8_t flags;
} toct;
struct {
uint8_t min_high;
uint8_t min_low;
uint8_t sec_high;
uint8_t sec_low;
uint8_t frame_high;
uint8_t frame_low;
uint8_t track_low;
} tocn;
struct {
} error;
} b;
uint8_t checksum;
} cdd_status;
typedef struct {
uint8_t cmd_type;
uint8_t must_be_zero;
union {
struct {
uint8_t min_high;
uint8_t min_low;
uint8_t sec_high;
uint8_t sec_low;
uint8_t frame_high;
uint8_t frame_low;
} time;
struct {
uint8_t padding;
uint8_t status_type;
uint8_t track_high;
uint8_t track_low;
} format;
struct {
uint8_t padding;
uint8_t direction;
uint8_t tracks_highest;
uint8_t tracks_midhigh;
uint8_t tracks_midlow;
uint8_t tracks_lowest;
} skip;
struct {
uint8_t track_high;
uint8_t track_low;
} track;
} b;
uint8_t padding;
uint8_t checksum;
} cdd_cmd;
typedef struct {
system_media *media;
uint32_t cycle; //this is in CD block CLKS
uint32_t next_int_cycle; //this is in SCD MCLKS
uint32_t next_subcode_int_cycle;
uint32_t last_sector_cycle;
uint32_t last_nibble_cycle;
uint32_t next_byte_cycle;
uint32_t next_subcode_cycle;
int current_status_nibble;
int current_cmd_nibble;
int current_sector_byte;
int current_subcode_byte;
int current_subcode_dest;
uint32_t head_pba;
uint32_t seek_pba;
uint32_t pause_pba;
uint32_t coarse_seek;
cdd_status status_buffer;
cdd_cmd cmd_buffer;
status_format requested_format;
drive_status status;
drive_status error_status;
uint8_t requested_track;
uint8_t cmd_recv_wait;
uint8_t cmd_recv_pending;
uint8_t int_pending;
uint8_t subcode_int_pending;
uint8_t toc_valid;
uint8_t first_cmd_received;
uint8_t seeking;
uint8_t in_fake_pregap;
} cdd_mcu;
void cdd_mcu_init(cdd_mcu *context, system_media *media);
void cdd_mcu_run(cdd_mcu *context, uint32_t cycle, uint16_t *gate_array, lc8951 *cdc, cdd_fader *fader);
void cdd_hock_enabled(cdd_mcu *context);
void cdd_hock_disabled(cdd_mcu *context);
void cdd_mcu_start_cmd_recv(cdd_mcu *context, uint16_t *gate_array);
void cdd_mcu_adjust_cycle(cdd_mcu *context, uint32_t deduction);
void cdd_mcu_serialize(cdd_mcu *context, serialize_buffer *buf);
void cdd_mcu_deserialize(deserialize_buffer *buf, void *vcontext);
#endif //CD_MCU_H_
+732
View File
@@ -0,0 +1,732 @@
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include "system.h"
#include "paths.h"
#include "wave.h"
uint8_t cdrom_scramble(uint16_t *lsfr, uint8_t data)
{
data ^= *lsfr;
for (int i = 0; i < 8; i++)
{
uint16_t new_bit = *lsfr;
*lsfr >>= 1;
new_bit = (new_bit ^ *lsfr) & 1;
*lsfr |= new_bit << 14;
}
return data;
}
static char* cmd_start(char *cur)
{
while (*cur && isblank(*cur))
{
cur++;
}
return cur;
}
static char* cmd_start_sameline(char *cur)
{
while (*cur && isblank(*cur) && *cur != '\n')
{
cur++;
}
return cur;
}
static char* word_end(char *cur)
{
while (*cur && !isblank(*cur))
{
cur++;
}
return cur;
}
static char* next_line(char *cur)
{
while (*cur && *cur != '\n')
{
cur++;
}
if (*cur) {
return cur + 1;
}
return NULL;
}
static char* next_blank(char *cur)
{
while (*cur && !isblank(*cur))
{
cur++;
}
return cur;
}
static uint32_t timecode_to_lba(char *timecode)
{
char *end;
int seconds = 0, minutes = 0;
int frames = strtol(timecode, &end, 10);
if (end && *end == ':') {
timecode = end + 1;
seconds = frames;
frames = strtol(timecode, &end, 10);
if (end && *end == ':') {
minutes = seconds;
seconds = frames;
timecode = end + 1;
frames = strtol(timecode, NULL, 10);
}
}
seconds += minutes * 60;
return seconds * 75 + frames;
}
enum {
FAKE_DATA = 1,
FAKE_AUDIO,
};
static uint8_t bin_seek(system_media *media, uint32_t sector)
{
media->cur_sector = sector;
uint32_t lba = sector;
uint32_t track;
uint32_t rel;
for (track = 0; track < media->num_tracks; track++)
{
rel = lba - media->tracks[track].pregap_lba;
if (rel < media->tracks[track].fake_pregap) {
media->in_fake_pregap = media->tracks[track].type == TRACK_DATA ? FAKE_DATA : FAKE_AUDIO;
break;
}
if (lba < media->tracks[track].end_lba) {
media->in_fake_pregap = 0;
rel -= media->tracks[track].fake_pregap;
break;
}
}
if (track < media->num_tracks) {
media->cur_track = track;
if (!media->in_fake_pregap) {
if (media->tracks[track].flac) {
flac_seek(media->tracks[track].flac, (media->tracks[track].file_offset + rel * media->tracks[track].sector_bytes) / 4);
} else {
if (media->tracks[track].has_subcodes) {
if (!media->tmp_buffer) {
media->tmp_buffer = calloc(1, 96);
}
fseek(media->tracks[track].f, media->tracks[track].file_offset + (rel + 1) * media->tracks[track].sector_bytes - 96, SEEK_SET);
int bytes = fread(media->tmp_buffer, 1, 96, media->tracks[track].f);
if (bytes != 96) {
fprintf(stderr, "Only read %d subcode bytes\n", bytes);
}
}
fseek(media->tracks[track].f, media->tracks[track].file_offset + rel * media->tracks[track].sector_bytes, SEEK_SET);
}
}
if (media->tracks[track].type == TRACK_DATA) {
media->cdrom_scramble_lsfr = 1;
}
}
return track;
}
static uint8_t fake_read(uint32_t sector, uint32_t offset)
{
if (!offset || offset == 11 || (offset >= 16)) {
return 0;
//TODO: error detection and correction bytes
} else if (offset < 11) {
return 0xFF;
} else if (offset == 12) {
uint32_t minute = (sector / 75) / 60;
return (minute % 10) | ((minute / 10 ) << 4);
} else if (offset == 13) {
uint32_t seconds = (sector / 75) % 60;
return (seconds % 10) | ((seconds / 10 ) << 4);
} else if (offset == 14) {
uint32_t frames = sector % 75;
return (frames % 10) | ((frames / 10 ) << 4);
} else {
return 1;
}
}
static uint8_t bin_read(system_media *media, uint32_t offset)
{
uint8_t retval;
if (media->in_fake_pregap == FAKE_DATA) {
retval = fake_read(media->cur_sector, offset);
} else if (media->in_fake_pregap == FAKE_AUDIO) {
retval = 0;
} else if ((media->tracks[media->cur_track].sector_bytes < 2352 && offset < 16) || offset > (media->tracks[media->cur_track].sector_bytes + 16)) {
retval = fake_read(media->cur_sector, offset);
} else if (media->tracks[media->cur_track].flac) {
if (offset & 3) {
retval = media->byte_storage[(offset & 3) - 1];
} else {
int16_t samples[2];
flac_get_sample(media->tracks[media->cur_track].flac, samples, 2);
media->byte_storage[0] = samples[0] >> 8;
media->byte_storage[1] = samples[1];
media->byte_storage[2] = samples[1] >> 8;
retval = samples[0];
}
} else {
if (media->tracks[media->cur_track].need_swap) {
if (offset & 1) {
retval = media->byte_storage[0];
}
media->byte_storage[0] = fgetc(media->tracks[media->cur_track].f);
} else {
retval = fgetc(media->tracks[media->cur_track].f);
}
}
if (offset >= 12 && media->tracks[media->cur_track].type == TRACK_DATA) {
retval = cdrom_scramble(&media->cdrom_scramble_lsfr, retval);
}
return retval;
}
static uint8_t bin_subcode_read(system_media *media, uint32_t offset)
{
if (media->in_fake_pregap || !media->tracks[media->cur_track].has_subcodes) {
//TODO: Fake PQ subcodes
return 0;
}
//TODO: Translate "cooked" subcodes back to raw format
return media->tmp_buffer[offset];
}
static void print_toc(system_media *media)
{
track_info * tracks = media->tracks;
for (uint32_t i = 0; i < media->num_tracks; i++)
{
uint32_t m,s,f;
f = tracks[i].pregap_lba % 75;
s = tracks[i].pregap_lba / 75;
m = s / 60;
s = s % 60;
printf("Track %02u - Index 0 %02u:%02u:%02u, Index 1 ", i + 1, m, s, f);
f = tracks[i].start_lba % 75;
s = tracks[i].start_lba / 75;
m = s / 60;
s = s % 60;
printf("%02u:%02u:%02u, Fake Pregap: %u\n", m, s, f, tracks[i].fake_pregap);
}
}
uint8_t parse_cue(system_media *media)
{
char *line = media->buffer;
media->num_tracks = 0;
do {
char *cmd = cmd_start(line);
if (cmd) {
if (startswith(cmd, "TRACK ")) {
media->num_tracks++;
}
line = next_line(cmd);
} else {
line = NULL;
}
} while (line);
track_info *tracks = calloc(sizeof(track_info), media->num_tracks);
media->tracks = tracks;
line = media->buffer;
int track = -1;
uint8_t audio_byte_swap = 0;
FILE *f = NULL;
flac_file *flac = NULL;
int track_of_file = -1;
uint8_t has_index_0 = 0;
uint32_t extra_offset = 0;
do {
char *cmd = cmd_start(line);
if (*cmd) {
if (startswith(cmd, "TRACK ")) {
track++;
track_of_file++;
has_index_0 = 0;
cmd += 6;
char *end;
int file_track = strtol(cmd, &end, 10);
if (file_track != (track + 1)) {
warning("Expected track %d, but found track %d in CUE sheet\n", track + 1, file_track);
}
tracks[track].f = f;
tracks[track].flac = flac;
cmd = cmd_start(end);
if (*cmd) {
if (startswith(cmd, "AUDIO")) {
tracks[track].type = TRACK_AUDIO;
tracks[track].need_swap = audio_byte_swap;
tracks[track].sector_bytes = 2352;
} else {
tracks[track].type = TRACK_DATA;
tracks[track].need_swap = 0;
tracks[track].sector_bytes = 0;
char *slash = strchr(cmd, '/');
if (slash) {
tracks[track].sector_bytes = atoi(slash+1);
}
if (!tracks[track].sector_bytes) {
warning("Missing sector size for data track %d in cue", track + 1);
tracks[track].sector_bytes = 2352;
}
}
}
} else if (startswith(cmd, "FILE ")) {
cmd += 5;
cmd = strchr(cmd, '"');
if (cmd) {
cmd++;
char *end = strchr(cmd, '"');
if (end) {
char *fname;
//TODO: zipped BIN/CUE support
if (is_absolute_path(cmd)) {
fname = malloc(end-cmd + 1);
memcpy(fname, cmd, end-cmd);
fname[end-cmd] = 0;
} else {
size_t dirlen = strlen(media->dir);
fname = malloc(dirlen + 1 + (end-cmd) + 1);
memcpy(fname, media->dir, dirlen);
fname[dirlen] = PATH_SEP[0];
memcpy(fname + dirlen + 1, cmd, end-cmd);
fname[dirlen + 1 + (end-cmd)] = 0;
}
if (track_of_file >= 0) {
long track_size = 0;
if (flac) {
track_size = flac->total_samples * 4;
} else if (f) {
track_size = file_size(f);
}
track_size -= tracks[track].file_offset;
tracks[track].end_lba = tracks[track].pregap_lba + tracks[track].fake_pregap + track_size / tracks[track].sector_bytes;
}
flac = NULL;
f = fopen(fname, "rb");
if (!f) {
fatal_error("Failed to open %s specified by FILE command in CUE sheet %s.%s\n", fname, media->name, media->extension);
}
track_of_file = -1;
for (end++; *end && *end != '\n' && *end != '\r'; end++)
{
if (!isspace(*end)) {
extra_offset = 0;
if (startswith(end, "BINARY")) {
audio_byte_swap = 0;
} else if (startswith(end, "MOTOROLA")) {
audio_byte_swap = 1;
} else if (startswith(end, "WAVE")) {
audio_byte_swap = 0;
wave_header wave;
if (wave_read_header(f, &wave)) {
if (wave.audio_format != 1 || wave.num_channels != 2 || wave.sample_rate != 44100 || wave.bits_per_sample != 16) {
warning("BlastEm only suports WAVE tracks in 16-bit stereo PCM format at 44100 hz, file %s does not match\n", fname);
}
extra_offset = wave.data_offset;
} else {
fseek(f, 0, SEEK_SET);
flac = flac_file_from_file(f);
if (!flac) {
fatal_error("WAVE file %s in cue sheet %s.%s is neither a valid WAVE nor a valid FLAC file\n", fname, media->name, media->extension);
}
if (flac->sample_rate != 44100 || flac->bits_per_sample != 16 || flac->channels != 2) {
warning("FLAC files in a CUE sheet should match CD audio specs, %s does not\n", fname);
}
}
} else {
warning("Unsupported FILE type in CUE sheet. Only BINARY and MOTOROLA are supported\n");
}
break;
}
}
free(fname);
}
}
} else if (track >= 0) {
if (startswith(cmd, "PREGAP ")) {
tracks[track].fake_pregap = timecode_to_lba(cmd + 7);
} else if (startswith(cmd, "INDEX ")) {
char *after;
int index = strtol(cmd + 6, &after, 10);
uint8_t has_start_lba = 0;
uint32_t start_lba = timecode_to_lba(after);
if (!index) {
tracks[track].file_offset = start_lba * tracks[track].sector_bytes + extra_offset;
if (track > 0) {
if (track_of_file > 0) {
//Previous track end is implicit based on this index position
uint32_t last_track_size = tracks[track].file_offset - tracks[track-1].file_offset;
last_track_size /= tracks[track-1].sector_bytes;
tracks[track-1].end_lba = tracks[track-1].pregap_lba + tracks[track-1].fake_pregap + last_track_size;
}
tracks[track].pregap_lba = tracks[track-1].end_lba;
} else {
tracks[track].pregap_lba = 0;
}
has_index_0 = 1;
has_start_lba = 1;
} else if (index == 1) {
if (has_index_0) {
uint32_t real_pregap_size = start_lba * tracks[track].sector_bytes + extra_offset - tracks[track].file_offset;
real_pregap_size /= tracks[track].sector_bytes;
tracks[track].start_lba = tracks[track].pregap_lba + tracks[track].fake_pregap + real_pregap_size;
} else {
tracks[track].file_offset = start_lba * tracks[track].sector_bytes + extra_offset;
if (track > 0) {
if (track_of_file > 0) {
//Previous track end is implicit based on this index position
uint32_t last_track_size = tracks[track].file_offset - tracks[track-1].file_offset;
last_track_size /= tracks[track-1].sector_bytes;
tracks[track-1].end_lba = tracks[track-1].pregap_lba + tracks[track-1].fake_pregap + last_track_size;
}
tracks[track].pregap_lba = tracks[track-1].end_lba;
tracks[track].start_lba = tracks[track].pregap_lba + tracks[track].fake_pregap;
} else {
tracks[track].pregap_lba = 0;
if (!tracks[track].fake_pregap) {
if (tracks[track].type == TRACK_DATA && tracks[track].sector_bytes == 2352) {
//Infer pregap from position in sector header
fseek(f, start_lba + 12, SEEK_SET);
uint8_t timecode[3];
if (sizeof(timecode) == fread(timecode, 1, sizeof(timecode), f)) {
tracks[track].fake_pregap = (timecode[0] >> 4) * 600;
tracks[track].fake_pregap += (timecode[0] & 0xF) * 60;
tracks[track].fake_pregap += (timecode[1] >> 4) * 10;
tracks[track].fake_pregap += timecode[1] & 0xF;
tracks[track].fake_pregap *= 75;
tracks[track].fake_pregap += (timecode[2] >> 4) * 10;
tracks[track].fake_pregap += timecode[2] & 0xF;
} else {
fatal_error("Failed to read from CD image");
}
} else {
//Just assume a 2-second pre-gap for first track
tracks[track].fake_pregap = 2 * 75;
}
}
tracks[track].start_lba = tracks[track].fake_pregap;
}
}
}
}
}
if (cmd && *cmd) {
line = next_line(cmd);
} else {
line = NULL;
}
} else {
line = NULL;
}
} while (line);
if (media->num_tracks > 0 && media->tracks[0].f) {
//end of last track in a file is implictly based on file size
long track_size = 0;
if (flac) {
track_size = flac->total_samples * 4;
} else if (f) {
track_size = file_size(f);
}
track_size -= tracks[track].file_offset;
tracks[track].end_lba = tracks[track].pregap_lba + tracks[track].fake_pregap + track_size / tracks[track].sector_bytes;
if (tracks[0].type == TRACK_DATA) {
//replace cue sheet with first sector
aligned_free(media->buffer);
media->buffer = calloc(2048, 1);
fseek(tracks[0].f, tracks[0].sector_bytes >= 2352 ? 16 : 0, SEEK_SET);
media->size = fread(media->buffer, 1, 2048, tracks[0].f);
}
media->seek = bin_seek;
media->read = bin_read;
media->read_subcodes = bin_subcode_read;
}
print_toc(media);
uint8_t valid = media->num_tracks > 0 && media->tracks[0].f != NULL;
media->type = valid ? MEDIA_CDROM : MEDIA_CART;
return valid;
}
uint8_t parse_toc(system_media *media)
{
char *line = media->buffer;
media->num_tracks = 0;
do {
char *cmd = cmd_start(line);
if (cmd) {
if (startswith(cmd, "TRACK ")) {
media->num_tracks++;
}
line = next_line(cmd);
} else {
line = NULL;
}
} while (line);
track_info *tracks = calloc(sizeof(track_info), media->num_tracks);
media->tracks = tracks;
line = media->buffer;
char *last_file_name = NULL;
FILE *f = NULL;
int track = -1;
do {
char *cmd = cmd_start(line);
if (*cmd) {
if (startswith(cmd, "TRACK ")) {
track++;
cmd = cmd_start(cmd + 6);
if (startswith(cmd, "AUDIO")) {
tracks[track].type = TRACK_AUDIO;
tracks[track].sector_bytes = 2352;
tracks[track].need_swap = 1;
} else {
tracks[track].type = TRACK_DATA;
tracks[track].need_swap = 0;
if (startswith(cmd, "MODE1_RAW") || startswith(cmd, "MODE2_RAW")) {
tracks[track].sector_bytes = 2352;
} else if (startswith(cmd, "MODE2_FORM2")) {
tracks[track].sector_bytes = 2324;
} else if (startswith(cmd, "MODE1") || startswith(cmd, "MODE2_FORM1")) {
tracks[track].sector_bytes = 2048;
} else if (startswith(cmd, "MODE2")) {
tracks[track].sector_bytes = 2336;
}
}
cmd = word_end(cmd);
if (*cmd && *cmd != '\n') {
cmd = cmd_start_sameline(cmd);
if (*cmd && *cmd != '\n') {
//TODO: record whether subcode is in raw format or not
if (startswith(cmd, "RW_RAW")) {
tracks[track].sector_bytes += 96;
tracks[track].has_subcodes = SUBCODES_RAW;
} else if (startswith(cmd, "RW")) {
tracks[track].sector_bytes += 96;
tracks[track].has_subcodes = SUBCODES_COOKED;
}
}
}
if (track) {
tracks[track].start_lba = tracks[track].pregap_lba = tracks[track].end_lba = tracks[track-1].end_lba;
}
} else if (track >= 0) {
uint8_t is_datafile = startswith(cmd, "DATAFILE");
if (is_datafile || startswith(cmd, "FILE")) {
if (tracks[track].f) {
warning("TOC file has more than one file for track %d, only one is supported\n", track + 1);
} else {
cmd += is_datafile ? 8 : 4;
char *fname_start = strchr(cmd, '"');
if (fname_start) {
++fname_start;
char *fname_end = strchr(fname_start, '"');
if (fname_end) {
if (!last_file_name || strncmp(last_file_name, fname_start, fname_end-fname_start)) {
free(last_file_name);
last_file_name = calloc(1, 1 + fname_end-fname_start);
memcpy(last_file_name, fname_start, fname_end-fname_start);
char *fname;
//TODO: zipped BIN/TOC support
if (is_absolute_path(last_file_name)) {
fname = last_file_name;
} else {
size_t dirlen = strlen(media->dir);
fname = malloc(dirlen + 1 + (fname_end-fname_start) + 1);
memcpy(fname, media->dir, dirlen);
fname[dirlen] = PATH_SEP[0];
memcpy(fname + dirlen + 1, fname_start, fname_end-fname_start);
fname[dirlen + 1 + (fname_end-fname_start)] = 0;
}
f = fopen(fname, "rb");
if (!f) {
fatal_error("Failed to open %s specified by DATAFILE command in TOC file %s.%s\n", fname, media->name, media->extension);
}
if (fname != last_file_name) {
free(fname);
}
}
tracks[track].f = f;
cmd = fname_end + 1;
cmd = cmd_start_sameline(cmd);
if (*cmd == '#') {
char *end;
tracks[track].file_offset = strtol(cmd + 1, &end, 10);
cmd = cmd_start_sameline(end);
}
if (!is_datafile) {
if (isdigit(*cmd)) {
uint32_t start = timecode_to_lba(cmd);
tracks[track].file_offset += start * tracks[track].sector_bytes;
cmd = cmd_start_sameline(word_end(cmd));
}
}
if (isdigit(*cmd)) {
uint32_t length = timecode_to_lba(cmd);
tracks[track].end_lba += length;
} else {
long fsize = file_size(f);
tracks[track].end_lba += (fsize - tracks[track].file_offset) / tracks[track].sector_bytes;
}
}
}
}
} else if (startswith(cmd, "SILENCE")) {
cmd = cmd_start_sameline(cmd + 7);
uint32_t length = timecode_to_lba(cmd);
tracks[track].fake_pregap += length;
tracks[track].end_lba += length;
} else if (startswith(cmd, "START")) {
cmd = cmd_start_sameline(cmd + 5);
tracks[track].start_lba = tracks[track].pregap_lba + timecode_to_lba(cmd);
}
}
if (cmd && *cmd) {
line = next_line(cmd);
} else {
line = NULL;
}
} else {
line = NULL;
}
} while (line);
if (media->num_tracks > 0 && media->tracks[0].f) {
//replace cue sheet with first sector
aligned_free(media->buffer);
media->buffer = calloc(2048, 1);
uint32_t old_fake_pregap = tracks[0].fake_pregap;
if (tracks[0].type == TRACK_DATA && tracks[0].sector_bytes == 2352) {
// if the first track is a data track, don't trust the TOC file and look at the MM:SS:FF from first sector
uint8_t msf[3];
fseek(tracks[0].f, 12, SEEK_SET);
if (sizeof(msf) == fread(msf, 1, sizeof(msf), tracks[0].f)) {
tracks[0].fake_pregap = msf[2] + (msf[0] * 60 + msf[1]) * 75;
}
} else if (!tracks[0].fake_pregap) {
tracks[0].fake_pregap = 2 * 75;
}
if (tracks[0].fake_pregap != old_fake_pregap) {
if (!tracks[0].start_lba) {
tracks[0].start_lba = tracks[0].fake_pregap;
}
uint32_t diff = tracks[0].fake_pregap - old_fake_pregap;
tracks[0].end_lba += diff;
for (uint32_t i = 1; i < media->num_tracks; i++)
{
tracks[i].pregap_lba += diff;
tracks[i].start_lba += diff;
tracks[i].end_lba += diff;
}
}
fseek(tracks[0].f, tracks[0].sector_bytes == 2352 ? 16 : 0, SEEK_SET);
media->size = fread(media->buffer, 1, 2048, tracks[0].f);
media->seek = bin_seek;
media->read = bin_read;
media->read_subcodes = bin_subcode_read;
}
print_toc(media);
uint8_t valid = media->num_tracks > 0 && media->tracks[0].f != NULL;
media->type = valid ? MEDIA_CDROM : MEDIA_CART;
return valid;
}
uint32_t make_iso_media(system_media *media, const char *filename)
{
FILE *f = fopen(filename, "rb");
if (!f) {
return 0;
}
media->buffer = calloc(2048, 1);
media->size = fread(media->buffer, 1, 2048, f);
media->num_tracks = 1;
media->tracks = calloc(sizeof(track_info), 1);
media->tracks[0] = (track_info){
.f = f,
.file_offset = 0,
.fake_pregap = 2 * 75,
.start_lba = 0,
.end_lba = file_size(f),
.sector_bytes = 2048,
.has_subcodes = SUBCODES_NONE,
.need_swap = 0,
.type = TRACK_DATA
};
media->type = MEDIA_CDROM;
media->seek = bin_seek;
media->read = bin_read;
media->read_subcodes = bin_subcode_read;
return media->size;
}
void cdimage_serialize(system_media *media, serialize_buffer *buf)
{
if (media->type != MEDIA_CDROM) {
return;
}
save_int32(buf, media->cur_track);
save_int32(buf, media->cur_sector);
if (media->cur_track < media->num_tracks && media->tracks[media->cur_track].f) {
save_int32(buf, ftell(media->tracks[media->cur_track].f));
} else {
save_int32(buf, 0);
}
save_int8(buf, media->in_fake_pregap);
save_int8(buf, media->byte_storage[0]);
if (media->tmp_buffer) {
save_buffer8(buf, media->tmp_buffer, 96);
}
save_int8(buf, media->byte_storage[1]);
save_int8(buf, media->byte_storage[2]);
}
void cdimage_deserialize(deserialize_buffer *buf, void *vmedia)
{
system_media *media = vmedia;
if (media->type != MEDIA_CDROM) {
return;
}
media->cur_track = load_int32(buf);
media->cur_sector = load_int32(buf);
uint32_t seekpos = load_int32(buf);
if (media->cur_track < media->num_tracks && media->tracks[media->cur_track].f) {
fseek(media->tracks[media->cur_track].f, seekpos, SEEK_SET);
}
media->in_fake_pregap = load_int8(buf);
media->byte_storage[0] = load_int8(buf);
if (media->tmp_buffer) {
load_buffer8(buf, media->tmp_buffer, 96);
}
if (buf->size - buf->cur_pos >= 2) {
media->byte_storage[1] = load_int8(buf);
media->byte_storage[2] = load_int8(buf);
}
}
void cdimage_free(system_media *media)
{
if (media->type != MEDIA_CDROM) {
return;
}
//dir, name, extension and orig_path are handled elsewhere
free(media->buffer);
free(media->tracks);
}
+12
View File
@@ -0,0 +1,12 @@
#ifndef CUE_H_
#define CUE_H_
uint8_t parse_cue(system_media *media);
uint8_t parse_toc(system_media *media);
uint32_t make_iso_media(system_media *media, const char *filename);
void cdimage_serialize(system_media *media, serialize_buffer *buf);
void cdimage_deserialize(deserialize_buffer *buf, void *vmedia);
uint8_t cdrom_scramble(uint16_t *lsfr, uint8_t data);
void cdimage_free(system_media *media);
#endif //CUE_H_
+240
View File
@@ -0,0 +1,240 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include "chd.h"
uint16_t bswap16(uint16_t in)
{
return in << 8 | in >> 8;
}
uint32_t bswap32(uint32_t in)
{
return in << 24 | in >> 24 | (in << 8 & 0xFF0000) | (in >> 8 & 0xFF00);
}
uint64_t bswap64(uint64_t in)
{
return in << 56 | in >> 56 | (in << 40 & 0xFF000000000000ULL) | (in >> 40 & 0xFF00)
| (in << 24 & 0xFF0000000000ULL) | (in >> 24 & 0xFF0000) | (in << 8 & 0xFF00000000ULL) | (in >> 8 & 0xFF000000);
}
static void chd_bswap(chd_header *chd)
{
#ifndef BLASTEM_BIG_ENDIAN
chd->length = bswap32(chd->length);
chd->version = bswap32(chd->version);
if (chd->version < 5) {
chd->v.old.flags = bswap32(chd->v.old.flags);
chd->v.old.compression = bswap32(chd->v.old.compression);
//TODO: other <V5 fields if I ever have reason to support <V5 images
} else if (chd->version == 5) {
for (int i = 0; i < 4; i++)
{
chd->v.v5.compressors[i] = bswap32(chd->v.v5.compressors[i]);
}
chd->v.v5.logical_bytes = bswap64(chd->v.v5.logical_bytes);
chd->v.v5.map_offset = bswap64(chd->v.v5.map_offset);
chd->v.v5.meta_offset = bswap64(chd->v.v5.meta_offset);
chd->v.v5.hunk_bytes = bswap32(chd->v.v5.hunk_bytes);
chd->v.v5.unit_bytes = bswap32(chd->v.v5.unit_bytes);
}
#endif
}
#define DEFAULT_META_STORAGE 8
static uint8_t chd_read_meta(chd *chd, uint64_t meta_offset)
{
while (meta_offset)
{
//TODO: fix this for files >2GB on systems with 32-bit long
fseek(chd->f, meta_offset, SEEK_SET);
uint8_t buf[16];
if (sizeof(buf) != fread(buf, 1, sizeof(buf), chd->f)) {
return 0;
}
uint8_t flags = buf[4];
buf[4] = 0;
chd_meta_list *list = tern_find_ptr(chd->meta, (char *)buf);
if (!list) {
list = calloc(1, sizeof(chd_meta_list) + DEFAULT_META_STORAGE * sizeof(chd_meta));
list->storage = DEFAULT_META_STORAGE;
chd->meta = tern_insert_ptr(chd->meta, (char *)buf, list);
}
if (list->storage == list->num_entries) {
size_t old_size = sizeof(chd_meta_list) + list->storage * sizeof(chd_meta);
list->storage *= 2;
size_t new_size = sizeof(chd_meta_list) + list->storage * sizeof(chd_meta);
chd_meta_list *tmp = list;
list = calloc(1, new_size);
memcpy(list, tmp, old_size);
memset(((char *)list) + old_size, 0, new_size - old_size);
tern_insert_ptr(chd->meta, (char *)buf, list);
}
uint32_t size = buf[5] << 16 | buf[6] << 8 | buf[7];
list->entries[list->num_entries].flags = flags;
list->entries[list->num_entries].data = calloc(1, size + 1);
if (size != fread(list->entries[list->num_entries++].data, 1, size, chd->f)) {
return 1;
}
meta_offset = 0;
for (int i = 0; i < sizeof(buf); i++)
{
meta_offset <<= 8;
meta_offset |= buf[i];
}
}
return 1;
}
static void chd_read_map_v5(chd *chd)
{
fseek(chd->f, chd->header.v.v5.map_offset);
if (chd->header.v.v5.compression[0]) {
struct {
uint32_t length;
uint16_t offset[3]
uint16_t crc;
uint8_t length_bits;
uint8_t hunk_bits;
uint8_t parent_bits;
uint8_t reserved;
} header;
if (1 != fread(&header, sizeof(header), 1, chd->f) {
return 0;
}
#if !defined(BLASTEM_BIG_ENDIAN)
header.length = bswap32(header.length);
header.offset[0] = bswap16(header.offset[0]);
header.offset[1] = bswap16(header.offset[1]);
header.offset[2] = bswap16(header.offset[2]);
header.crc = bswap16(header.c4c);
#endif
uint8_t *compressed_map = calloc(1, header.length);
if (header.length != fread(compressed_map, 1, header.length, chd-.f)) {
return 0;
}
uint64_t offset = (uint64_t)header.offset[0] << 32 | (uint64_t)header.offset[1] << 16 | header.offset[2]
//HERE: do huffman decode
} else {
}
}
static void chd_print_meta_each(char *key, tern_val val, uint8_t valtype, void *data)
{
const char *indent = data;
printf("%s%s\n", indent, key);
fflush(stdout);
chd_meta_list *list = val.ptrval;
for (uint32_t i = 0; i < list->num_entries; i++)
{
printf("%s\tFlags: %0X, Data: %s\n", indent, list->entries[i].flags, list->entries[i].data);
fflush(stdout);
}
}
void chd_print_meta(chd *chd, const char *indent)
{
tern_foreach(chd->meta, chd_print_meta_each, (void *)indent);
}
uint8_t chd_read(FILE *f, chd *out)
{
fseek(f, 0, SEEK_SET);
if (1 != fread(&out->header, sizeof(chd_header), 1, f)) {
return 0;
}
chd_bswap(&out->header);
out->f = f;
uint64_t meta_offset = 0;
switch (out->header.version)
{
case 3:
meta_offset = out->header.v.old.v.v3.meta_offset;
break;
case 4:
meta_offset = out->header.v.old.v.v4.meta_offset;
break;
case 5:
meta_offset = out->header.v.v5.meta_offset;
break;
}
if (!chd_read_meta(out, meta_offset)) {
return 0;
}
if (out->header.version == 5) {
if (!chd_read_map_v5(out)) {
return 0;
}
}
return 1;
}
int headless;
void render_errorbox(char *title, char *message)
{
}
void render_warnbox(char *title, char *message)
{
}
void render_infobox(char *title, char *message)
{
}
int main(int argc, char **argv)
{
FILE *f = fopen(argv[1], "rb");
if (!f) {
return 1;
}
chd chd;
if (!chd_read(f, &chd)) {
return 1;
}
chd_header *head = &chd.header;
printf(
"Tag: %c%c%c%c%c%c%c%c\n"
"Length: %u\n"
"Version: %u\n"
"Compressors:\n",
head->tag[0], head->tag[1], head->tag[2], head->tag[3], head->tag[4], head->tag[5], head->tag[6], head->tag[7],
head->length, head->version);
for (int i = 0; i < 4; i++)
{
switch(head->v.v5.compressors[i])
{
case 0:
i = 4;
break;
case CHD_ZLIB:
puts(" zlib");
break;
case CHD_ZSTD:
puts(" zstd");
break;
case CHD_HUFF:
puts(" huff");
break;
case CHD_FLAC:
puts(" flac");
break;
case CHD_LZMA:
puts(" lzma");
break;
default:
printf(" unknown: %c%c%c%c\n", head->v.v5.compressors[i] >> 24, head->v.v5.compressors[i] >> 16 & 0xFF, head->v.v5.compressors[i] >> 8 & 0xFF, head->v.v5.compressors[i] & 0xFF);
}
}
printf(
"Logical Bytes: %" PRIu64 "\n"
"Hunk Bytes: %u\n"
"Unit Bytes: %u\n"
"Metadata:\n",
head->v.v5.logical_bytes, head->v.v5.hunk_bytes, head->v.v5.unit_bytes
);
chd_print_meta(&chd, "\t");
return 0;
}
+94
View File
@@ -0,0 +1,94 @@
#ifndef CHD_H_
#define CHD_H_
#include <stdint.h>
#include "tern.h"
typedef struct {
char tag[8];
uint32_t length;
uint32_t version;
union {
struct {
uint32_t flags;
uint32_t compression;
union {
struct {
uint32_t hunksize;
uint32_t total_hunks;
uint32_t cylinders;
uint32_t heads;
uint32_t sectors;
uint8_t md5[16];
uint8_t parent_md5[16];
uint32_t seclen;
} v2; //v1 is the same as v2, but lacks seclen
struct {
uint32_t total_hunks;
uint64_t logical_bytes;
uint64_t meta_offset;
uint8_t md5[16];
uint8_t parent_md5[16];
uint32_t hunk_bytes;
uint8_t sha1[20];
uint8_t parentsha1[20];
} v3;
struct {
uint32_t total_hunks;
uint64_t logical_bytes;
uint64_t meta_offset;
uint32_t hunk_bytes;
uint8_t sha1[20];
uint8_t parentsha1[20];
uint8_t rawsha1[20];
} v4;
} v;
} old;
struct {
uint32_t compressors[4];
uint64_t logical_bytes;
uint64_t map_offset;
uint64_t meta_offset;
uint32_t hunk_bytes;
uint32_t unit_bytes;
uint8_t rawsha1[20];
uint8_t sha1[20];
uint8_t parentsha1[20];
} v5;
} v;
} chd_header;
typedef struct {
char *data;
uint8_t flags;
} chd_meta;
typedef struct {
uint32_t num_entries;
uint32_t storage;
chd_meta entries[];
} chd_meta_list;
typedef struct {
FILE *f;
chd_header header;
tern_node *meta;
uint8_t media_type;
} chd;
#define CHD_ZLIB 'zlib'
#define CHD_ZSTD 'zstd'
#define CHD_HUFF 'huff'
#define CHD_FLAC 'flac'
#define CHD_LZMA 'lzma'
enum {
CHD_MEDIA_HD,
CHD_MEDIA_CD,
CHD_MEDIA_GD,
CHD_MEDIA_DVD,
CHD_MEDIA_AV
};
#endif //CHD_H_
+572
View File
@@ -0,0 +1,572 @@
#include <string.h>
#include <stdlib.h>
#include <stddef.h>
#include "coleco.h"
#include "render.h"
#include "io.h"
#include "blastem.h"
#include "paths.h"
#include "debug.h"
#include "bindings.h"
#include "saves.h"
#ifdef NEW_CORE
#define Z80_CYCLE cycles
#define Z80_OPTS opts
#define z80_handle_code_write(...)
#else
#define Z80_CYCLE current_cycle
#define Z80_OPTS options
#endif
static void *coleco_select_write(uint32_t location, void *vcontext, uint8_t value)
{
z80_context *z80 = vcontext;
coleco_context *coleco = z80->system;
location &= 0xFF;
coleco->controller_select = location >= 0xC0;
return vcontext;
}
static uint8_t coleco_controller_read(uint32_t location, void *vcontext)
{
z80_context *z80 = vcontext;
coleco_context *coleco = z80->system;
uint8_t index = coleco->controller_select ? 2 : 0;
if (location & 2) {
++index;
}
return coleco->controller_state[index];
}
static void update_interrupts(coleco_context *coleco)
{
#ifdef NEW_CORE
if (coleco->z80->nmi_cycle == CYCLE_NEVER) {
#else
if (coleco->z80->nmi_start == CYCLE_NEVER) {
#endif
uint32_t nmi = vdp_next_vint(coleco->vdp);
if (nmi != CYCLE_NEVER) {
z80_assert_nmi(coleco->z80, nmi);
}
}
}
static void *coleco_vdp_write(uint32_t location, void *vcontext, uint8_t value)
{
z80_context *z80 = vcontext;
coleco_context *coleco = z80->system;
vdp_run_context_full(coleco->vdp, z80->Z80_CYCLE);
if (location & 1) {
vdp_control_port_write_pbc(coleco->vdp, value);
update_interrupts(coleco);
} else {
vdp_data_port_write_pbc(coleco->vdp, value);
}
return vcontext;
}
static uint8_t coleco_vdp_read(uint32_t location, void *vcontext)
{
z80_context *z80 = vcontext;
coleco_context *coleco = z80->system;
vdp_run_context(coleco->vdp, z80->Z80_CYCLE);
if (location & 1) {
uint8_t ret = vdp_control_port_read(coleco->vdp);
coleco->vdp->flags2 &= ~(FLAG2_VINT_PENDING|FLAG2_HINT_PENDING);
update_interrupts(coleco);
return ret;
} else {
return vdp_data_port_read_pbc(coleco->vdp);
}
}
static void *coleco_psg_write(uint32_t location, void *vcontext, uint8_t value)
{
z80_context *z80 = vcontext;
coleco_context *coleco = z80->system;
psg_run(coleco->psg, z80->Z80_CYCLE);
psg_write(coleco->psg, value);
return vcontext;
}
void coleco_serialize(coleco_context *coleco, serialize_buffer *buf)
{
start_section(buf, SECTION_Z80);
z80_serialize(coleco->z80, buf);
end_section(buf);
start_section(buf, SECTION_VDP);
vdp_serialize(coleco->vdp, buf);
end_section(buf);
start_section(buf, SECTION_PSG);
psg_serialize(coleco->psg, buf);
end_section(buf);
start_section(buf, SECTION_MAIN_RAM);
save_int8(buf, sizeof(coleco->ram) / 1024);
save_buffer8(buf, coleco->ram, sizeof(coleco->ram));
end_section(buf);
start_section(buf, SECTION_COLECO_IO);
save_buffer8(buf, coleco->controller_state, sizeof(coleco->controller_state));
save_int8(buf, coleco->controller_select);
end_section(buf);
}
static uint8_t *serialize(system_header *sys, size_t *size_out)
{
coleco_context *coleco = (coleco_context *)sys;
serialize_buffer state;
init_serialize(&state);
coleco_serialize(coleco, &state);
if (size_out) {
*size_out = state.size;
}
return state.data;
}
static void ram_deserialize(deserialize_buffer *buf, void *vcoleco)
{
coleco_context *coleco = vcoleco;
uint32_t ram_size = load_int8(buf) * 1024;
if (ram_size > sizeof(coleco->ram)) {
fatal_error("State has a RAM size of %d bytes", ram_size);
}
load_buffer8(buf, coleco->ram, ram_size);
}
static void controller_deserialize(deserialize_buffer *buf, void *vcoleco)
{
coleco_context *coleco = vcoleco;
load_buffer8(buf, coleco->controller_state, sizeof(coleco->controller_state));
coleco->controller_select = load_int8(buf);
}
void coleco_deserialize(deserialize_buffer *buf, coleco_context *coleco)
{
register_section_handler(buf, (section_handler){.fun = z80_deserialize, .data = coleco->z80}, SECTION_Z80);
register_section_handler(buf, (section_handler){.fun = vdp_deserialize, .data = coleco->vdp}, SECTION_VDP);
register_section_handler(buf, (section_handler){.fun = psg_deserialize, .data = coleco->psg}, SECTION_PSG);
register_section_handler(buf, (section_handler){.fun = ram_deserialize, .data = coleco}, SECTION_MAIN_RAM);
register_section_handler(buf, (section_handler){.fun = controller_deserialize, .data = coleco}, SECTION_COLECO_IO);
while (buf->cur_pos < buf->size)
{
load_section(buf);
}
z80_invalidate_code_range(coleco->z80, 0x7000, 0x8000);
free(buf->handlers);
buf->handlers = NULL;
}
static void deserialize(system_header *sys, uint8_t *data, size_t size)
{
coleco_context *coleco = (coleco_context *)sys;
deserialize_buffer buffer;
init_deserialize(&buffer, data, size);
coleco_deserialize(&buffer, coleco);
}
static void save_state(coleco_context *coleco, uint8_t slot)
{
char *save_path = get_slot_name(&coleco->header, slot, "state");
serialize_buffer state;
init_serialize(&state);
coleco_serialize(coleco, &state);
save_to_file(&state, save_path);
printf("Saved state to %s\n", save_path);
free(save_path);
free(state.data);
}
static uint8_t load_state_path(coleco_context *coleco, char *path)
{
deserialize_buffer state;
uint8_t ret;
if ((ret = load_from_file(&state, path))) {
coleco_deserialize(&state, coleco);
free(state.data);
printf("Loaded %s\n", path);
}
return ret;
}
static uint8_t load_state(system_header *system, uint8_t slot)
{
coleco_context *coleco = (coleco_context *)system;
char *statepath = get_slot_name(system, slot, "state");
uint8_t ret;
#ifndef NEW_CORE
if (!coleco->z80->native_pc) {
ret = get_modification_time(statepath) != 0;
if (ret) {
system->delayed_load_slot = slot + 1;
}
goto done;
}
#endif
ret = load_state_path(coleco, statepath);
done:
free(statepath);
return ret;
}
static void run_coleco(system_header *system)
{
coleco_context *coleco = (coleco_context *)system;
uint32_t target_cycle = coleco->z80->Z80_CYCLE + 3420*16;
render_set_video_standard(VID_NTSC);
while (!coleco->should_return) {
if (system->delayed_load_slot) {
load_state(system, system->delayed_load_slot - 1);
system->delayed_load_slot = 0;
}
if (coleco->vdp->frame != coleco->last_frame) {
#ifndef IS_LIB
if (coleco->psg->scope) {
scope_render(coleco->psg->scope);
}
#endif
uint32_t elapsed = coleco->vdp->frame - coleco->last_frame;
coleco->last_frame = coleco->vdp->frame;
if (system->enter_debugger_frames) {
if (elapsed >= system->enter_debugger_frames) {
system->enter_debugger_frames = 0;
system->enter_debugger = 1;
} else {
system->enter_debugger_frames -= elapsed;
}
}
if(exit_after){
if (elapsed >= exit_after) {
exit(0);
} else {
exit_after -= elapsed;
}
}
}
if (system->enter_debugger && coleco->z80->pc) {
system->enter_debugger = 0;
#ifndef IS_LIB
zdebugger(coleco->z80, coleco->z80->pc);
#endif
}
if (system->enter_debugger) {
target_cycle = coleco->z80->Z80_CYCLE + 1;
}
update_interrupts(coleco);
z80_run(coleco->z80, target_cycle);
if (coleco->z80->reset) {
z80_clear_reset(coleco->z80, coleco->z80->Z80_CYCLE + 3*15);
}
target_cycle = coleco->z80->Z80_CYCLE;
vdp_run_context(coleco->vdp, target_cycle);
psg_run(coleco->psg, target_cycle);
if (system->save_state) {
while (!coleco->z80->pc) {
//advance Z80 to an instruction boundary
z80_run(coleco->z80, coleco->z80->Z80_CYCLE + 1);
}
save_state(coleco, system->save_state - 1);
system->save_state = 0;
}
target_cycle += 3420*16;
if (target_cycle > 0x40000000) {
uint32_t adjust = coleco->z80->Z80_CYCLE - 3420*262*2;
z80_adjust_cycles(coleco->z80, adjust);
vdp_adjust_cycles(coleco->vdp, adjust);
coleco->psg->cycles -= adjust;
target_cycle -= adjust;
}
}
if (coleco->header.force_release || render_should_release_on_exit()) {
bindings_release_capture();
vdp_release_framebuffer(coleco->vdp);
render_pause_source(coleco->psg->audio);
}
coleco->should_return = 0;
}
static void resume_coleco(system_header *system)
{
coleco_context *coleco = (coleco_context *)system;
if (coleco->header.force_release || render_should_release_on_exit()) {
coleco->header.force_release = 0;
bindings_reacquire_capture();
vdp_reacquire_framebuffer(coleco->vdp);
render_resume_source(coleco->psg->audio);
}
run_coleco(system);
}
static void start_coleco(system_header *system, char *statefile)
{
coleco_context *coleco = (coleco_context *)system;
z80_assert_reset(coleco->z80, 0);
z80_clear_reset(coleco->z80, 128*15);
if (statefile) {
load_state_path(coleco, statefile);
}
if (system->enter_debugger) {
system->enter_debugger = 0;
#ifndef IS_LIB
zinsert_breakpoint(coleco->z80, coleco->z80->pc, (uint8_t *)zdebugger);
#endif
}
run_coleco(system);
}
static void free_coleco(system_header *system)
{
coleco_context *coleco = (coleco_context *)system;
vdp_free(coleco->vdp);
z80_options_free(coleco->z80->Z80_OPTS);
free(coleco->z80);
psg_free(coleco->psg);
free(coleco->rom);
free(coleco->header.info.map);
free(coleco->header.info.name);
free(coleco);
}
static void soft_reset(system_header *system)
{
coleco_context *coleco = (coleco_context *)system;
z80_assert_reset(coleco->z80, coleco->z80->Z80_CYCLE);
#ifndef NEW_CORE
coleco->z80->target_cycle = coleco->z80->sync_cycle = coleco->z80->Z80_CYCLE;
#endif
}
static void set_speed_percent(system_header * system, uint32_t percent)
{
coleco_context *coleco = (coleco_context *)system;
uint32_t old_clock = coleco->master_clock;
coleco->master_clock = ((uint64_t)coleco->normal_clock * (uint64_t)percent) / 100;
psg_adjust_master_clock(coleco->psg, coleco->master_clock);
}
static uint16_t get_open_bus_value(system_header *system)
{
return 0xFFFF;
}
static void request_exit(system_header *system)
{
coleco_context *coleco = (coleco_context *)system;
coleco->should_return = 1;
#ifndef NEW_CORE
coleco->z80->target_cycle = coleco->z80->sync_cycle = coleco->z80->Z80_CYCLE;
#endif
}
static uint8_t button_map[] = {
[DPAD_UP] = 1,
[DPAD_DOWN] = 4,
[DPAD_LEFT] = 8,
[DPAD_RIGHT] = 2,
[BUTTON_A] = 0x40,
[BUTTON_B] = 0x40,
[BUTTON_C] = 9, //*
[BUTTON_START] = 6, //#
[BUTTON_X] = 0xD, //1
[BUTTON_Y] = 0x7, //2
[BUTTON_Z] = 0xC, //3
[BUTTON_MODE] = 0x2 //4
};
static void gamepad_down(system_header *system, uint8_t gamepad_num, uint8_t button)
{
if (gamepad_num > 2) {
return;
}
coleco_context *coleco = (coleco_context *)system;
uint8_t index = gamepad_num - 1;
if (button < BUTTON_B) {
index += 2;
}
if (button > BUTTON_B) {
coleco->controller_state[index] &= 0xF0;
coleco->controller_state[index] |= button_map[button];
} else {
coleco->controller_state[index] &= ~button_map[button];
}
}
static void gamepad_up(system_header *system, uint8_t gamepad_num, uint8_t button)
{
if (gamepad_num > 2) {
return;
}
coleco_context *coleco = (coleco_context *)system;
uint8_t index = gamepad_num - 1;
if (button < BUTTON_B) {
index += 2;
}
if (button > BUTTON_B) {
coleco->controller_state[index] |= 0xF;
} else {
coleco->controller_state[index] |= button_map[button];
}
}
static void config_updated(system_header *system)
{
coleco_context *coleco = (coleco_context *)system;
//sample rate may have changed
psg_adjust_master_clock(coleco->psg, coleco->master_clock);
}
static void inc_debug_mode(system_header *system)
{
coleco_context *coleco = (coleco_context *)system;
vdp_inc_debug_mode(coleco->vdp);
}
static void toggle_debug_view(system_header *system, uint8_t debug_view)
{
#ifndef IS_LIB
coleco_context *coleco = (coleco_context *)system;
if (debug_view < DEBUG_OSCILLOSCOPE) {
vdp_toggle_debug_view(coleco->vdp, debug_view);
} else if (debug_view == DEBUG_OSCILLOSCOPE) {
if (coleco->psg->scope) {
oscilloscope *scope = coleco->psg->scope;
coleco->psg->scope = NULL;
scope_close(scope);
} else {
oscilloscope *scope = create_oscilloscope();
psg_enable_scope(coleco->psg, scope, coleco->normal_clock);
}
}
#endif
}
static void load_save(system_header *system)
{
//Unclear if any Coleco carts have non-volatile memory
//but we need a dummy implementation so the save directory gets setup
}
static void persist_save(system_header *system)
{
}
static vdp_context *get_vdp(system_header *system)
{
coleco_context *coleco = (coleco_context *)system;
return coleco->vdp;
}
coleco_context *alloc_configure_coleco(system_media *media)
{
coleco_context *coleco = calloc(1, sizeof(coleco_context));
char *bios_path = tern_find_path_default(config, "system\0coleco_bios_path\0", (tern_val){.ptrval = "colecovision_bios.col"}, TVAL_PTR).ptrval;
if (is_absolute_path(bios_path)) {
FILE *f = fopen(bios_path, "rb");
if (f) {
fread(coleco->bios, 1, sizeof(coleco->bios), f);
fclose(f);
} else {
warning("Failed to open Colecovision BIOS from %s\n", bios_path);
}
} else {
uint32_t bios_size;
char *tmp = read_bundled_file(bios_path, &bios_size);
if (tmp) {
if (bios_size > sizeof(coleco->bios)) {
bios_size = sizeof(coleco->bios);
}
memcpy(coleco->bios, tmp, bios_size);
free(tmp);
} else {
warning("Failed to open Colecovision BIOS from %s\n", bios_path);
}
}
coleco->rom = media->buffer;
coleco->rom_size = media->size;
const memmap_chunk map[] = {
{0x0000, 0x2000, sizeof(coleco->bios)-1, .flags = MMAP_READ, .buffer = coleco->bios},
{0x8000, 0x10000, nearest_pow2(coleco->rom_size)-1, .flags = MMAP_READ, .buffer = coleco->rom},
{0x7000, 0x8000, sizeof(coleco->ram)-1, .flags = MMAP_READ|MMAP_WRITE|MMAP_CODE, .buffer = coleco->ram}
};
static const memmap_chunk io_map[] = {
{0x80, 0xA0, 0xFF, .write_8 = coleco_select_write},
{0xA0, 0xC0, 0xFF, .read_8 = coleco_vdp_read, .write_8 = coleco_vdp_write},
{0xC0, 0xE0, 0xFF, .write_8 = coleco_select_write},
{0xE0, 0x100, 0xFF, .read_8 = coleco_controller_read, .write_8 = coleco_psg_write}
};
uint32_t rom_size = coleco->header.info.rom_size;
z80_options *zopts = malloc(sizeof(z80_options));
uint32_t num_chunks = sizeof(map)/sizeof(*map);
memmap_chunk *heap_map = calloc(num_chunks, sizeof(memmap_chunk));
memcpy(heap_map, map, sizeof(map));
//Colecovision appears to use a 7.15909 MHz crystal with a /2 divider, but /15 works better with my VDP implementation
init_z80_opts(zopts, heap_map, num_chunks, io_map, sizeof(io_map)/sizeof(*io_map), 15, 0xFF);
coleco->z80 = init_z80_context(zopts);
coleco->z80->system = coleco;
coleco->normal_clock = coleco->master_clock = 53693175;
coleco->psg = malloc(sizeof(psg_context));
psg_init(coleco->psg, coleco->master_clock, 15*16);
coleco->vdp = init_vdp_context(0, 0, VDP_TMS9918A);
coleco->vdp->system = &coleco->header;
memset(coleco->controller_state, 0xFF, sizeof(coleco->controller_state));
coleco->header.info.save_type = SAVE_NONE;
coleco->header.info.map = heap_map;
//TODO: copy name from header if present
coleco->header.info.name = strdup(media->name);
coleco->header.has_keyboard = 0;
coleco->header.set_speed_percent = set_speed_percent;
coleco->header.start_context = start_coleco;
coleco->header.resume_context = resume_coleco;
coleco->header.load_save = load_save;
coleco->header.persist_save = persist_save;
coleco->header.load_state = load_state;
coleco->header.free_context = free_coleco;
coleco->header.get_open_bus_value = get_open_bus_value;
coleco->header.request_exit = request_exit;
coleco->header.soft_reset = soft_reset;
coleco->header.inc_debug_mode = inc_debug_mode;
coleco->header.gamepad_down = gamepad_down;
coleco->header.gamepad_up = gamepad_up;
//coleco->header.keyboard_down = keyboard_down;
//coleco->header.keyboard_up = keyboard_up;
coleco->header.config_updated = config_updated;
coleco->header.serialize = serialize;
coleco->header.deserialize = deserialize;
coleco->header.toggle_debug_view = toggle_debug_view;
coleco->header.get_vdp = get_vdp;
coleco->header.type = SYSTEM_COLECOVISION;
return coleco;
}
+35
View File
@@ -0,0 +1,35 @@
#ifndef COLECO_H_
#define COLECO_H_
#include "system.h"
#include "vdp.h"
#include "psg.h"
#ifdef NEW_CORE
#include "z80.h"
#else
#include "z80_to_x86.h"
#endif
#define COLECO_BIOS_SIZE (8 * 1024)
#define COLECO_RAM_SIZE (1 * 1024)
typedef struct {
system_header header;
z80_context *z80;
vdp_context *vdp;
psg_context *psg;
uint8_t *rom;
uint32_t rom_size;
uint32_t normal_clock;
uint32_t master_clock;
uint32_t last_frame;
uint8_t ram[COLECO_RAM_SIZE];
uint8_t bios[COLECO_BIOS_SIZE];
uint8_t controller_state[4];
uint8_t controller_select;
uint8_t should_return;
} coleco_context;
coleco_context *alloc_configure_coleco(system_media *media);
#endif //COLECO_H_
+59 -30
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
from glob import glob
import subprocess
from sys import exit,argv
@@ -14,50 +14,70 @@ for i in range(1, len(argv)):
if part.endswith('.bin'):
skip.add(part)
f.close()
print 'Skipping',len(skip),'entries from previous report.'
print('Skipping',len(skip),'entries from previous report.')
else:
prefixes.append(argv[i])
def is_sr_in_dreg(a, b):
if not a.startswith('d'):
return False
if not b.startswith('d'):
return False
_,_,a = a.partition(' ')
_,_,b = b.partition(' ')
try:
a = int(a.strip(), 16)
b = int(b.strip(), 16)
except:
return False
return (a & 0xFFE0) == 0x2700 and (b & 0xFFE0) == 0x2700
def print_mismatch(path, b, m):
blines = b.split('\n')
mlines = m.split('\n')
if len(blines) != len(mlines):
print '-----------------------------'
print 'Unknown mismatch in', path
print 'blastem output:'
print b
print 'musashi output:'
print m
print '-----------------------------'
print('-----------------------------')
print('Unknown mismatch in', path)
print('blastem output:')
print(b)
print('clean output:')
print(m)
print('-----------------------------')
return
prevline = ''
differences = []
flagmismatch = False
regmismatch = False
for i in xrange(0, len(blines)):
cyclemismatch = False
for i in range(0, len(blines)):
if blines[i] != mlines[i]:
if prevline == 'XNZVC':
if prevline == 'XNZVC' or is_sr_in_dreg(blines[i], mlines[i]):
differences.append((prevline, prevline))
flagmismatch = True
elif blines[i].startswith('cycles: ') and mlines[i].startswith('cycles: '):
cyclemismatch = True
else:
regmismatch = True
differences.append((blines[i], mlines[i]))
prevline = blines[i]
if flagmismatch and regmismatch:
if (flagmismatch + regmismatch + cyclemismatch) > 1:
mtype = 'General'
elif flagmismatch:
mtype = 'Flag'
elif regmismatch:
mtype = 'Register'
elif cyclemismatch:
mtype = 'Cycle'
else:
mtype = 'Unknown'
print '-----------------------------'
print mtype, 'mismatch in', path
for i in xrange(0, 2):
print 'musashi' if i else 'blastem', 'output:'
print('-----------------------------')
print(mtype, 'mismatch in', path)
for i in range(0, 2):
print('clean' if i else 'blastem', 'output:')
for diff in differences:
print diff[i]
print '-----------------------------'
print(diff[i])
print('-----------------------------')
@@ -74,23 +94,32 @@ for path in glob('generated_tests/*/*.bin'):
if not good:
continue
try:
b = subprocess.check_output(['./trans', path])
b = subprocess.check_output(['./trans', path], timeout=5).decode('utf-8')
try:
m = subprocess.check_output(['musashi/mustrans', path])
m = subprocess.check_output(['../blastem_clean/trans', path], timeout=5).decode('utf-8')
#_,_,b = b.partition('\n')
if b != m:
print_mismatch(path, b, m)
else:
print path, 'passed'
print(path, 'passed')
except subprocess.CalledProcessError as e:
print '-----------------------------'
print 'musashi exited with code', e.returncode, 'for test', path
print 'blastem output:'
print b
print '-----------------------------'
print('-----------------------------')
print('clean exited with code', e.returncode, 'for test', path)
print('blastem output:')
print(b)
print('-----------------------------')
except subprocess.TimeoutExpired as e:
print('-----------------------------')
print('clean timed out ', e, ' for test', path)
print('blastem output:')
print(b)
print('-----------------------------')
except subprocess.CalledProcessError as e:
print '-----------------------------'
print 'blastem exited with code', e.returncode, 'for test', path
print '-----------------------------'
print('-----------------------------')
print('blastem exited with code', e.returncode, 'for test', path)
print('-----------------------------')
except subprocess.TimeoutExpired as e:
print('-----------------------------')
print('blastem timed out ', e, ' for test', path)
print('-----------------------------')
+454 -7
View File
@@ -10,6 +10,9 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef DISABLE_NUKLEAR
#include "nuklear_ui/blastem_nuklear.h"
#endif
#ifdef __MINGW64_VERSION_MAJOR
#define MINGW_W64_VERSION (__MINGW64_VERSION_MAJOR * 1000 + __MINGW64_VERSION_MINOR)
@@ -140,7 +143,7 @@ static void serialize_config_int(tern_node *config, serialize_state *state)
ensure_buf_capacity(1, state);
state->buf[state->size++] = '{';
state->indent++;
tern_foreach(config, serialize_iter, state);
--state->indent;
@@ -239,7 +242,7 @@ tern_node *load_overrideable_config(char *name, char *bundled_name, uint8_t *use
if (used_config_dir) {
*used_config_dir = ret != NULL;
}
if (!ret) {
ret = parse_bundled_config(name);
if (!ret) {
@@ -250,11 +253,344 @@ tern_node *load_overrideable_config(char *name, char *bundled_name, uint8_t *use
return ret;
}
static tern_node *dupe_tree(tern_node *head)
{
if (!head) {
return head;
}
tern_node *out = calloc(1, sizeof(tern_node));
out->left = dupe_tree(head->left);
out->right = dupe_tree(head->right);
out->el = head->el;
out->valtype = head->valtype;
if (out->el) {
out->straight.next = dupe_tree(head->straight.next);
} else if (out->valtype == TVAL_NODE) {
out->straight.value.ptrval = dupe_tree(head->straight.value.ptrval);
} else if (out->valtype == TVAL_PTR) {
out->straight.value.ptrval = strdup(head->straight.value.ptrval);
} else {
out->straight.value = head->straight.value;
}
return out;
}
static void migrate_pads(char *key, tern_val val, uint8_t valtype, void *data)
{
tern_node **pads = data;
if (valtype != TVAL_NODE) {
return;
}
tern_node *existing = tern_find_node(*pads, key);
if (existing) {
return;
}
*pads = tern_insert_node(*pads, key, dupe_tree(val.ptrval));
}
static void update_menu_binding(char *key, tern_val val, uint8_t valtype, void *data)
{
tern_node **key_bindings = data;
if (valtype != TVAL_PTR) {
return;
}
if (strcmp(val.ptrval, "ui.exit")) {
return;
}
*key_bindings = tern_insert_ptr(*key_bindings, key, strdup("ui.menu"));
}
static void update_pad_menu_binding(char *key, tern_val val, uint8_t valtype, void *data)
{
tern_node **pads = data;
if (valtype != TVAL_NODE) {
return;
}
tern_node *buttons = tern_find_node(val.ptrval, "buttons");
if (buttons) {
tern_foreach(buttons, update_menu_binding, &buttons);
val.ptrval = tern_insert_node(val.ptrval, "buttons", buttons);
}
tern_node *axes = tern_find_node(val.ptrval, "axes");
if (axes) {
tern_foreach(axes, update_menu_binding, &axes);
val.ptrval = tern_insert_node(val.ptrval, "axes", axes);
}
*pads = tern_insert_node(*pads, key, val.ptrval);
}
#define CONFIG_VERSION 13
static tern_node *migrate_config(tern_node *config, int from_version)
{
tern_node *def_config = parse_bundled_config("default.cfg");
switch(from_version)
{
case 0: {
//Add CD image formats to ui.extensions
uint32_t num_exts;
char **ext_list = get_extension_list(config, &num_exts);
char *old = num_exts ? ext_list[0] : NULL;
uint32_t new_size = num_exts + 2;
uint8_t need_cue = 1, need_iso = 1;
for (uint32_t i = 0; i < num_exts; i++)
{
if (!strcmp(ext_list[i], "cue")) {
need_cue = 0;
new_size--;
} else if (!strcmp(ext_list[i], "iso")) {
need_iso = 0;
new_size--;
}
}
if (new_size != num_exts) {
ext_list = realloc(ext_list, sizeof(char*) * new_size);
if (need_cue) {
ext_list[num_exts++] = "cue";
}
if (need_iso) {
ext_list[num_exts++] = "iso";
}
}
char *combined = alloc_join(new_size, (char const **)ext_list, ' ');
config = tern_insert_path(config, "ui\0extensions\0", (tern_val){.ptrval = combined}, TVAL_PTR);
//Copy default pad configs if missing
tern_node *pads = tern_find_path(config, "bindings\0pads\0", TVAL_NODE).ptrval;
tern_node *def_pads = tern_find_path(def_config, "bindings\0pads\0", TVAL_NODE).ptrval;
tern_foreach(def_pads, migrate_pads, &pads);
config = tern_insert_path(config, "bindings\0pads\0", (tern_val){.ptrval = pads}, TVAL_NODE);
}
case 1: {
char *l_bind = tern_find_path(config, "bindings\0keys\0l\0", TVAL_PTR).ptrval;
if (!l_bind) {
config = tern_insert_path(config, "bindings\0keys\0l\0", (tern_val){.ptrval = strdup("ui.load_state")}, TVAL_PTR);
}
}
case 2: {
tern_node *sms = tern_find_node(config, "sms");
char *model = tern_find_path_default(sms, "system\0model\0", (tern_val){.ptrval = "md1va3"}, TVAL_PTR).ptrval;
char *io1 = tern_find_path_default(sms, "io\0devices\0""1\0", (tern_val){.ptrval = "gamepad2.1"}, TVAL_PTR).ptrval;
char *io2 = tern_find_path_default(sms, "io\0devices\0""1\0", (tern_val){.ptrval = "gamepad2.2"}, TVAL_PTR).ptrval;
sms = tern_insert_path(sms, "system\0model\0", (tern_val){.ptrval = strdup(model)}, TVAL_PTR);
sms = tern_insert_path(sms, "io\0devices\0""1\0", (tern_val){.ptrval = strdup(io1)}, TVAL_PTR);
sms = tern_insert_path(sms, "io\0devices\0""2\0", (tern_val){.ptrval = strdup(io2)}, TVAL_PTR);
config = tern_insert_node(config, "sms", sms);
}
case 3: {
char *tap11 = tern_find_path_default(config, "io\0sega_multitap.1\0""1\0", (tern_val){.ptrval = "gamepad6.2"}, TVAL_PTR).ptrval;
char *tap12 = tern_find_path_default(config, "io\0sega_multitap.1\0""2\0", (tern_val){.ptrval = "gamepad6.3"}, TVAL_PTR).ptrval;
char *tap13 = tern_find_path_default(config, "io\0sega_multitap.1\0""3\0", (tern_val){.ptrval = "gamepad6.4"}, TVAL_PTR).ptrval;
char *tap14 = tern_find_path_default(config, "io\0sega_multitap.1\0""4\0", (tern_val){.ptrval = "gamepad6.5"}, TVAL_PTR).ptrval;
config = tern_insert_path(config, "io\0sega_multitap.1\0""1\0", (tern_val){.ptrval = strdup(tap11)}, TVAL_PTR);
config = tern_insert_path(config, "io\0sega_multitap.1\0""2\0", (tern_val){.ptrval = strdup(tap12)}, TVAL_PTR);
config = tern_insert_path(config, "io\0sega_multitap.1\0""3\0", (tern_val){.ptrval = strdup(tap13)}, TVAL_PTR);
config = tern_insert_path(config, "io\0sega_multitap.1\0""4\0", (tern_val){.ptrval = strdup(tap14)}, TVAL_PTR);
}
case 4: {
char *tap11 = tern_find_path_default(config, "io\0ea_multitap\0""1\0", (tern_val){.ptrval = "gamepad6.1"}, TVAL_PTR).ptrval;
char *tap12 = tern_find_path_default(config, "io\0ea_multitap\0""2\0", (tern_val){.ptrval = "gamepad6.2"}, TVAL_PTR).ptrval;
char *tap13 = tern_find_path_default(config, "io\0ea_multitap\0""3\0", (tern_val){.ptrval = "gamepad6.3"}, TVAL_PTR).ptrval;
char *tap14 = tern_find_path_default(config, "io\0ea_multitap\0""4\0", (tern_val){.ptrval = "gamepad6.4"}, TVAL_PTR).ptrval;
config = tern_insert_path(config, "io\0ea_multitap\0""1\0", (tern_val){.ptrval = strdup(tap11)}, TVAL_PTR);
config = tern_insert_path(config, "io\0ea_multitap\0""2\0", (tern_val){.ptrval = strdup(tap12)}, TVAL_PTR);
config = tern_insert_path(config, "io\0ea_multitap\0""3\0", (tern_val){.ptrval = strdup(tap13)}, TVAL_PTR);
config = tern_insert_path(config, "io\0ea_multitap\0""4\0", (tern_val){.ptrval = strdup(tap14)}, TVAL_PTR);
}
case 5: {
char *binding_o = tern_find_path_default(config, "bindings\0keys\0o\0", (tern_val){.ptrval = "ui.oscilloscope"}, TVAL_PTR).ptrval;
config = tern_insert_path(config, "bindings\0keys\0o\0", (tern_val){.ptrval = strdup(binding_o)}, TVAL_PTR);
}
case 6: {
tern_node *key_bindings = tern_find_path(config, "bindings\0keys\0", TVAL_NODE).ptrval;
if (key_bindings) {
tern_foreach(key_bindings, update_menu_binding, &key_bindings);
config = tern_insert_path(config, "bindings\0keys\0", (tern_val){.ptrval = key_bindings}, TVAL_NODE);
}
tern_node *pad_bindings = tern_find_path(config, "bindings\0pads\0", TVAL_NODE).ptrval;
if (pad_bindings) {
tern_foreach(pad_bindings, update_pad_menu_binding, &pad_bindings);
config = tern_insert_path(config, "bindings\0pads\0", (tern_val){.ptrval = pad_bindings}, TVAL_NODE);
}
}
case 7: {
uint32_t num_exts;
char **exts = get_extension_list(config, &num_exts);
char *need_add[] = {"vgm", "vgz", "flac", "wav"};
uint32_t num_need_add = sizeof(need_add)/sizeof(*need_add);
for (uint32_t i = 0; i < num_exts && num_need_add; i++)
{
for (uint32_t j = 0; j < num_need_add; j++)
{
if (!strcmp(exts[i], need_add[j])) {
num_need_add--;
need_add[j] = need_add[num_need_add];
break;
}
}
}
if (num_need_add) {
const char **parts = calloc(2 * (num_exts + num_need_add) - 1, sizeof(char*));
uint32_t dest = 0;
for (uint32_t i = 0; i < num_exts; i++)
{
parts[dest++] = exts[i];
parts[dest++] = " ";
}
for (uint32_t i = 0; i < num_need_add - 1; i++)
{
parts[dest++] = need_add[i];
parts[dest++] = " ";
}
parts[dest++] = need_add[num_need_add - 1];
config = tern_insert_path(config, "ui\0extensions\0", (tern_val){.ptrval = alloc_concat_m(dest, parts)}, TVAL_PTR);
free(parts);
}
free(exts[0]);//All extensions in this list share an allocation, first one is a pointer to the buffer
free(exts);
}
case 8: {
uint32_t num_exts;
char **exts = get_extension_list(config, &num_exts);
char *need_add[] = {"col"};
uint32_t num_need_add = sizeof(need_add)/sizeof(*need_add);
for (uint32_t i = 0; i < num_exts && num_need_add; i++)
{
for (uint32_t j = 0; j < num_need_add; j++)
{
if (!strcmp(exts[i], need_add[j])) {
num_need_add--;
need_add[j] = need_add[num_need_add];
break;
}
}
}
if (num_need_add) {
const char **parts = calloc(2 * (num_exts + num_need_add) - 1, sizeof(char*));
uint32_t dest = 0;
for (uint32_t i = 0; i < num_exts; i++)
{
parts[dest++] = exts[i];
parts[dest++] = " ";
}
for (uint32_t i = 0; i < num_need_add - 1; i++)
{
parts[dest++] = need_add[i];
parts[dest++] = " ";
}
parts[dest++] = need_add[num_need_add - 1];
config = tern_insert_path(config, "ui\0extensions\0", (tern_val){.ptrval = alloc_concat_m(dest, parts)}, TVAL_PTR);
free(parts);
}
free(exts[0]);//All extensions in this list share an allocation, first one is a pointer to the buffer
free(exts);
}
case 9: {
//Add pre-SMS 8-bit image formats to ui.extensions
uint32_t num_exts;
char **ext_list = get_extension_list(config, &num_exts);
char *old = num_exts ? ext_list[0] : NULL;
uint32_t new_size = num_exts + 3;
uint8_t need_sc = 1, need_sg = 1, need_sf7 = 1;
for (uint32_t i = 0; i < num_exts; i++)
{
if (!strcmp(ext_list[i], "sc")) {
need_sc = 0;
new_size--;
} else if (!strcmp(ext_list[i], "sg")) {
need_sg = 0;
new_size--;
} else if (!strcmp(ext_list[i], "sf7")) {
need_sf7 = 0;
new_size--;
}
}
if (new_size != num_exts) {
ext_list = realloc(ext_list, sizeof(char*) * new_size);
if (need_sc) {
ext_list[num_exts++] = "sc";
}
if (need_sg) {
ext_list[num_exts++] = "sg";
}
if (need_sf7) {
ext_list[num_exts++] = "sf7";
}
}
char *combined = alloc_join(new_size, (char const **)ext_list, ' ');
config = tern_insert_path(config, "ui\0extensions\0", (tern_val){.ptrval = combined}, TVAL_PTR);
}
case 10: {
//Add default bindings for cassette actions
char *bind = tern_find_path(config, "bindings\0keys\0f2\0", TVAL_PTR).ptrval;
if (!bind) {
config = tern_insert_path(config, "bindings\0keys\0f2\0", (tern_val){.ptrval = strdup("cassette.play")}, TVAL_PTR);
}
bind = tern_find_path(config, "bindings\0keys\0f3\0", TVAL_PTR).ptrval;
if (!bind) {
config = tern_insert_path(config, "bindings\0keys\0f3\0", (tern_val){.ptrval = strdup("cassette.stop")}, TVAL_PTR);
}
bind = tern_find_path(config, "bindings\0keys\0f4\0", TVAL_PTR).ptrval;
if (!bind) {
config = tern_insert_path(config, "bindings\0keys\0f4\0", (tern_val){.ptrval = strdup("cassette.rewind")}, TVAL_PTR);
}
}
case 11: {
//Add default bindings for pause and frame advance
char *bind = tern_find_path(config, "bindings\0keys\0f7\0", TVAL_PTR).ptrval;
if (!bind) {
config = tern_insert_path(config, "bindings\0keys\0f7\0", (tern_val){.ptrval = strdup("ui.pause")}, TVAL_PTR);
}
bind = tern_find_path(config, "bindings\0keys\0f8\0", TVAL_PTR).ptrval;
if (!bind) {
config = tern_insert_path(config, "bindings\0keys\0f8\0", (tern_val){.ptrval = strdup("ui.advance")}, TVAL_PTR);
}
}
case 12: {
uint32_t num_exts;
char **exts = get_extension_list(config, &num_exts);
char *need_add[] = {"32x"};
uint32_t num_need_add = sizeof(need_add)/sizeof(*need_add);
for (uint32_t i = 0; i < num_exts && num_need_add; i++)
{
for (uint32_t j = 0; j < num_need_add; j++)
{
if (!strcmp(exts[i], need_add[j])) {
num_need_add--;
need_add[j] = need_add[num_need_add];
break;
}
}
}
if (num_need_add) {
const char **parts = calloc(2 * (num_exts + num_need_add) - 1, sizeof(char*));
uint32_t dest = 0;
for (uint32_t i = 0; i < num_exts; i++)
{
parts[dest++] = exts[i];
parts[dest++] = " ";
}
for (uint32_t i = 0; i < num_need_add - 1; i++)
{
parts[dest++] = need_add[i];
parts[dest++] = " ";
}
parts[dest++] = need_add[num_need_add - 1];
config = tern_insert_path(config, "ui\0extensions\0", (tern_val){.ptrval = alloc_concat_m(dest, parts)}, TVAL_PTR);
free(parts);
}
free(exts[0]);//All extensions in this list share an allocation, first one is a pointer to the buffer
free(exts);
}
}
char buffer[16];
sprintf(buffer, "%d", CONFIG_VERSION);
return tern_insert_ptr(config, "version", strdup(buffer));
}
static uint8_t app_config_in_config_dir;
tern_node *load_config()
{
tern_node *ret = load_overrideable_config("blastem.cfg", "default.cfg", &app_config_in_config_dir);
if (!ret) {
if (get_config_dir()) {
fatal_error("Failed to find a config file at %s or in the blastem executable directory\n", get_config_dir());
@@ -262,14 +598,23 @@ tern_node *load_config()
fatal_error("Failed to find a config file in the BlastEm executable directory and the config directory path could not be determined\n");
}
}
int config_version = atoi(tern_find_ptr_default(ret, "version", "0"));
if (config_version < CONFIG_VERSION) {
migrate_config(ret, config_version);
}
return ret;
}
uint8_t is_config_in_exe_dir(tern_node *app_config)
{
char*use_exe_dir = tern_find_path_default(app_config, "ui\0config_in_exe_dir\0", (tern_val){.ptrval = "off"}, TVAL_PTR).ptrval;
return !strcmp(use_exe_dir, "on");
}
void persist_config_at(tern_node *app_config, tern_node *to_save, char *fname)
{
char*use_exe_dir = tern_find_path_default(app_config, "ui\0config_in_exe_dir\0", (tern_val){.ptrval = "off"}, TVAL_PTR).ptrval;
char *confpath;
if (!strcmp(use_exe_dir, "on")) {
if (is_config_in_exe_dir(app_config)) {
confpath = path_append(get_exe_dir(), fname);
if (app_config == to_save && app_config_in_config_dir) {
//user switched to "portable" configs this session and there is an
@@ -315,7 +660,7 @@ void delete_custom_config(void)
char **get_extension_list(tern_node *config, uint32_t *num_exts_out)
{
char *ext_filter = strdup(tern_find_path_default(config, "ui\0extensions\0", (tern_val){.ptrval = "bin gen md smd sms gg"}, TVAL_PTR).ptrval);
char *ext_filter = strdup(tern_find_path_default(config, "ui\0extensions\0", (tern_val){.ptrval = "bin gen md smd sms gg zip gz cue iso vgm vgz flac wav"}, TVAL_PTR).ptrval);
uint32_t num_exts = 0, ext_storage = 5;
char **ext_list = malloc(sizeof(char *) * ext_storage);
char *cur_filter = ext_filter;
@@ -350,6 +695,108 @@ tern_node *get_systems_config(void)
tern_node *get_model(tern_node *config, system_type stype)
{
char *model = tern_find_path_default(config, "system\0model\0", (tern_val){.ptrval = "md1va3"}, TVAL_PTR).ptrval;
char *model = tern_find_path_default(config, stype == SYSTEM_SMS ? "sms\0system\0model\0" : "system\0model\0", (tern_val){.ptrval = "md1va3"}, TVAL_PTR).ptrval;
return tern_find_node(get_systems_config(), model);
}
tern_node *set_machine_feeze_choice(tern_node *config, uint8_t choice)
{
char *str;
switch (choice)
{
case CHOICE_FATAL: str = "fatal"; break;
default:
case CHOICE_ASK: str = "ask"; break;
case CHOICE_DEBUG: str = "debug"; break;
case CHOICE_IGNORE: str = "ignore"; break;
}
return tern_insert_path(config, "ui\0machine_freeze_action\0", (tern_val){.ptrval = strdup(str)}, TVAL_PTR);
}
void machine_freeze(tern_node *config, debug_callback callback, void *data, char *format, ...)
{
#ifdef ISLIB
return;
#else
static uint8_t freeze_choice;
if (!freeze_choice) {
#ifdef DISABLE_NUKLEAR
tern_val def = {.ptrval = "fatal"};
freeze_choice = CHOICE_FATAL;
#else
tern_val def = {.ptrval = "ask"};
freeze_choice = CHOICE_ASK;
#endif
char *choice = tern_find_path_default(config, "ui\0machine_freeze_action\0", def, TVAL_PTR).ptrval;
if (!strcmp(choice, "fatal")) {
freeze_choice = CHOICE_FATAL;
#ifndef DISABLE_NUKLEAR
} else if (!strcmp(choice, "ask")) {
freeze_choice = CHOICE_ASK;
#endif
} else if (!strcmp(choice, "debug")) {
freeze_choice = CHOICE_DEBUG;
} else if (!strcmp(choice, "ignore")) {
freeze_choice = CHOICE_IGNORE;
}
}
uint8_t keep_going = 1;
uint8_t cur_choice = freeze_choice;
va_list args;
while (keep_going)
{
keep_going = 0;
switch (cur_choice)
{
default:
case CHOICE_FATAL:
va_start(args, format);
log_msg(format, FATAL, args);
va_end(args);
exit(1);
break;
#ifndef DISABLE_NUKLEAR
case CHOICE_ASK:
{
//take a guess at the final size
int32_t size = strlen(format) * 2;
char *buf = malloc(size);
va_start(args, format);
va_list tmp;
va_copy(tmp, args);
int32_t actual = vsnprintf(buf, size, format, args);
va_end(tmp);
if (actual >= size || actual < 0) {
if (actual < 0) {
//seems on windows, vsnprintf is returning -1 when the buffer is too small
//since we don't know the proper size, a generous multiplier will hopefully suffice
actual = size * 4;
} else {
actual++;
}
free(buf);
buf = malloc(actual);
vsnprintf(buf, actual, format, args);
}
cur_choice = show_freeze_choice(&freeze_choice, strip_ws(buf));
va_end(args);
keep_going = 1;
free(buf);
}
break;
#endif
case CHOICE_DEBUG:
va_start(args, format);
log_msg(format, DEBUG, args);
va_end(args);
callback(data);
break;
case CHOICE_IGNORE:
va_start(args, format);
log_msg(format, DEBUG, args);
va_end(args);
break;
}
}
#endif
}
+14
View File
@@ -8,8 +8,19 @@
#include "tern.h"
#include "system.h"
typedef void (*debug_callback)(void *data);
enum {
CHOICE_NOT_SET,
CHOICE_FATAL,
CHOICE_ASK,
CHOICE_DEBUG,
CHOICE_IGNORE
};
tern_node *parse_config_file(char *config_path);
tern_node *parse_bundled_config(char *config_name);
tern_node *parse_config(char * config_data);
tern_node *load_overrideable_config(char *name, char *bundled_name, uint8_t *used_config_dir);
tern_node *load_config();
char *serialize_config(tern_node *config, uint32_t *size_out);
@@ -22,6 +33,9 @@ char **get_extension_list(tern_node *config, uint32_t *num_exts_out);
uint32_t get_lowpass_cutoff(tern_node *config);
tern_node *get_systems_config(void);
tern_node *get_model(tern_node *config, system_type stype);
uint8_t is_config_in_exe_dir(tern_node *app_config);
tern_node *set_machine_feeze_choice(tern_node *config, uint8_t choice);
void machine_freeze(tern_node *config, debug_callback callback, void *data, char *format, ...);
#endif //CONFIG_H_
+119 -17
View File
@@ -1,5 +1,6 @@
#include <string.h>
#include <stdlib.h>
#include "render.h"
#ifndef USE_FBDEV
#include "render_sdl.h"
#endif
@@ -17,6 +18,7 @@ typedef struct {
static heuristic heuristics[] = {
//TODO: Add more heuristic rules
{"DualShock 4", {.type = TYPE_PSX, .subtype = SUBTYPE_PS4}},
{"PS5", {.type = TYPE_PSX, .subtype = SUBTYPE_PS5}},
{"PS4", {.type = TYPE_PSX, .subtype = SUBTYPE_PS4}},
{"PS3", {.type = TYPE_PSX, .subtype = SUBTYPE_PS3}},
{"X360", {.type = TYPE_XBOX, .subtype = SUBTYPE_X360}},
@@ -27,7 +29,10 @@ static heuristic heuristics[] = {
{"WiiU", {.type = TYPE_NINTENDO, .subtype = SUBTYPE_WIIU}},
{"Wii U", {.type = TYPE_NINTENDO, .subtype = SUBTYPE_WIIU}},
{"Nintendo Switch", {.type = TYPE_NINTENDO, .subtype = SUBTYPE_SWITCH}},
{"Saturn", {.type = TYPE_SEGA, .subtype = SUBTYPE_SATURN}}
{"Saturn", {.type = TYPE_SEGA, .subtype = SUBTYPE_SATURN}},
{"8BitDo M30", {.type = TYPE_SEGA, .subtype = SUBTYPE_GENESIS, .variant = VARIANT_8BUTTON}},
{"Mini 3B Controller", {.type = TYPE_SEGA, .subtype = SUBTYPE_GENESIS, .variant = VARIANT_3BUTTON}},
{"Mini 6B Controller", {.type = TYPE_SEGA, .subtype = SUBTYPE_GENESIS, .variant = VARIANT_6B_BUMPERS}}
};
const uint32_t num_heuristics = sizeof(heuristics)/sizeof(*heuristics);
@@ -38,9 +43,11 @@ static const char *subtype_names[] = {
"xbox",
"xbox 360",
"xbone",
"xbox elite",
"ps2",
"ps3",
"ps4",
"ps5",
"wiiu",
"switch",
"genesis",
@@ -50,10 +57,12 @@ static const char *subtype_human_names[] = {
"unknown",
"Xbos",
"Xbox 360",
"Xbox One",
"Xbox One/Series",
"Xbox Elite",
"PS2",
"PS3",
"PS4",
"PS5",
"Wii-U",
"Switch",
"Genesis",
@@ -64,7 +73,6 @@ static const char *variant_names[] = {
"6b bumpers",
"6b right",
"3button",
"6button",
"8button"
};
@@ -76,6 +84,9 @@ static void load_ctype_config(void)
}
}
#define DEFAULT_DEADZONE 4000
#define DEFAULT_DEADZONE_STR "4000"
controller_info get_controller_info(int joystick)
{
#ifndef USE_FBDEV
@@ -83,7 +94,11 @@ controller_info get_controller_info(int joystick)
char guid_string[33];
SDL_Joystick *stick = render_get_joystick(joystick);
SDL_GameController *control = render_get_controller(joystick);
#if SDL_VERSION_ATLEAST(3, 2, 0)
SDL_GUIDToString(SDL_JoystickGetGUID(stick), guid_string, sizeof(guid_string));
#else
SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(stick), guid_string, sizeof(guid_string));
#endif
tern_node *info = tern_find_node(info_config, guid_string);
if (info) {
controller_info res;
@@ -103,6 +118,7 @@ controller_info get_controller_info(int joystick)
case SUBTYPE_XBOX:
case SUBTYPE_X360:
case SUBTYPE_XBONE:
case SUBTYPE_XBOX_ELITE:
res.type = TYPE_XBOX;
break;
case SUBTYPE_PS2:
@@ -125,15 +141,22 @@ controller_info get_controller_info(int joystick)
char *variant = tern_find_ptr(info, "variant");
res.variant = VARIANT_NORMAL;
if (variant) {
for (int i = 0; i < VARIANT_NUM; i++)
int i;
for (i = 0; i < VARIANT_NUM; i++)
{
if (!strcmp(variant_names[i], variant)) {
res.variant = i;
break;
}
}
if (i == VARIANT_NUM && !strcmp("6button", variant)) {
//workaround for some bad saved configs caused by a silly bug
res.variant = VARIANT_8BUTTON;
}
}
res.name = control ? SDL_GameControllerName(control) : SDL_JoystickName(stick);
res.stick_deadzone = atoi(tern_find_ptr_default(info, "stick_deadzone", DEFAULT_DEADZONE_STR));
res.trigger_deadzone = atoi(tern_find_ptr_default(info, "trigger_deadzone", DEFAULT_DEADZONE_STR));
if (control) {
SDL_GameControllerClose(control);
}
@@ -144,16 +167,37 @@ controller_info get_controller_info(int joystick)
.type = TYPE_UNKNOWN,
.subtype = SUBTYPE_UNKNOWN,
.variant = VARIANT_NORMAL,
.name = SDL_JoystickName(stick)
.name = SDL_JoystickName(stick),
.stick_deadzone = DEFAULT_DEADZONE,
.trigger_deadzone = DEFAULT_DEADZONE
};
}
const char *name = SDL_GameControllerName(control);
SDL_GameControllerClose(control);
for (uint32_t i = 0; i < num_heuristics; i++)
{
if (strstr(name, "Hori Fighting Commander")) {
uint8_t is_xbox = strstr(name, "Xbox") || strstr(name, "ONE");
controller_info res = {
.variant = VARIANT_6B_RIGHT,
.name = name,
.stick_deadzone = DEFAULT_DEADZONE,
.trigger_deadzone = DEFAULT_DEADZONE
};
if (is_xbox) {
res.type = TYPE_XBOX;
res.subtype = strstr(name, "ONE") ? SUBTYPE_XBONE : SUBTYPE_X360;
} else {
res.type = TYPE_PSX;
res.subtype = strstr(name, "PS3") ? SUBTYPE_PS3 : SUBTYPE_PS4;
}
return res;
}
if (strstr(name, heuristics[i].name)) {
controller_info res = heuristics[i].info;
res.name = name;
res.stick_deadzone = DEFAULT_DEADZONE;
res.trigger_deadzone = DEFAULT_DEADZONE;
return res;
}
}
@@ -165,7 +209,9 @@ controller_info get_controller_info(int joystick)
.type = TYPE_GENERIC_MAPPING,
.subtype = SUBTYPE_UNKNOWN,
.variant = VARIANT_NORMAL,
.name = name
.name = name,
.stick_deadzone = DEFAULT_DEADZONE,
.trigger_deadzone = DEFAULT_DEADZONE
};
}
@@ -177,8 +223,15 @@ static void mappings_iter(char *key, tern_val val, uint8_t valtype, void *data)
}
char *mapping = tern_find_ptr(val.ptrval, "mapping");
if (mapping) {
const char *parts[] = {key, ",", mapping};
const char *parts[] = {key, ",", mapping, ","};
#if SDL_VERSION_ATLEAST(2,0,10)
//For reasons that are unclear, in some cases the last mapping element
//seems to get dropped unless there is a trailing comma
char * full = alloc_concat_m(4, parts);
#else
//In SDL 2.0.9 and below, it is not legal to have a trailing comma
char * full = alloc_concat_m(3, parts);
#endif
SDL_GameControllerAddMapping(full);
free(full);
}
@@ -197,28 +250,53 @@ void save_controller_info(int joystick, controller_info *info)
{
#ifndef USE_FBDEV
char guid_string[33];
#if SDL_VERSION_ATLEAST(3, 2, 0)
SDL_GUIDToString(SDL_JoystickGetGUID(render_get_joystick(joystick)), guid_string, sizeof(guid_string));
#else
SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(render_get_joystick(joystick)), guid_string, sizeof(guid_string));
#endif
tern_node *existing = tern_find_node(info_config, guid_string);
existing = tern_insert_ptr(existing, "subtype", strdup(subtype_names[info->subtype]));
existing = tern_insert_ptr(existing, "variant", strdup(variant_names[info->variant]));
char buffer[32];
snprintf(buffer, sizeof(buffer), "%d", info->stick_deadzone);
buffer[31] = 0;
existing = tern_insert_ptr(existing, "stick_deadzone", strdup(buffer));
snprintf(buffer, sizeof(buffer), "%d", info->trigger_deadzone);
buffer[31] = 0;
existing = tern_insert_ptr(existing, "trigger_deadzone", strdup(buffer));
info_config = tern_insert_node(info_config, guid_string, existing);
persist_config_at(config, info_config, "controller_types.cfg");
handle_joy_added(joystick);
#endif
#endif
}
void save_controller_mapping(int joystick, char *mapping_string)
{
#ifndef USE_FBDEV
char guid_string[33];
#if SDL_VERSION_ATLEAST(3, 2, 0)
SDL_GUIDToString(SDL_JoystickGetGUID(render_get_joystick(joystick)), guid_string, sizeof(guid_string));
#else
SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(render_get_joystick(joystick)), guid_string, sizeof(guid_string));
#endif
tern_node *existing = tern_find_node(info_config, guid_string);
existing = tern_insert_ptr(existing, "mapping", mapping_string);
existing = tern_insert_ptr(existing, "mapping", strdup(mapping_string));
info_config = tern_insert_node(info_config, guid_string, existing);
persist_config_at(config, info_config, "controller_types.cfg");
const char *parts[] = {guid_string, ",", mapping_string};
const char *parts[] = {guid_string, ",", mapping_string, ","};
#if SDL_VERSION_ATLEAST(2,0,10)
//For reasons that are unclear, in some cases the last mapping element
//seems to get dropped unless there is a trailing comma
char * full = alloc_concat_m(4, parts);
#else
//In SDL 2.0.9 and below, it is not legal to have a trailing comma
char * full = alloc_concat_m(3, parts);
#endif
uint8_t gc_events = render_are_gamepad_events_enabled();
render_enable_gamepad_events(0);
SDL_GameControllerAddMapping(full);
render_enable_gamepad_events(gc_events);
free(full);
handle_joy_added(joystick);
#endif
@@ -234,13 +312,13 @@ void delete_controller_info(void)
}
char const *labels_xbox[] = {
"A", "B", "X", "Y", "Back", NULL, "Start", "Click", "Click", "White", "Black", "LT", "RT"
"A", "B", "X", "Y", "Back", NULL, "Start", "LS Click", "RS Click", "White", "Black", "LT", "RT"
};
char const *labels_360[] = {
"A", "B", "X", "Y", "Back", "Xbox", "Start", "Click", "Click", "LB", "RB", "LT", "RT"
"A", "B", "X", "Y", "Back", "Xbox", "Start", "LS Click", "RS Click", "LB", "RB", "LT", "RT"
};
static char const *labels_xbone[] = {
"A", "B", "X", "Y", "View", "Xbox", "Menu", "Click", "Click", "LB", "RB", "LT", "RT"
"A", "B", "X", "Y", "View", "Xbox", "Menu", "LS Click", "RS Click", "LB", "RB", "LT", "RT"
};
static char const *labels_ps3[] = {
"cross", "circle", "square", "triangle", "Select", "PS", "Start", "L3", "R3", "L1", "R1", "L2", "R2"
@@ -271,13 +349,13 @@ static const char** label_source(controller_info *info)
} else if (info->type == TYPE_NINTENDO) {
return labels_nintendo;
} else if (info->type == TYPE_PSX) {
if (info->subtype == SUBTYPE_PS4) {
if (info->subtype >= SUBTYPE_PS4) {
return labels_ps4;
} else {
return labels_ps3;
}
} else if (info->type == TYPE_XBOX) {
if (info->subtype == SUBTYPE_XBONE) {
if (info->subtype >= SUBTYPE_XBONE) {
return labels_xbone;
} else {
return labels_xbox;
@@ -300,9 +378,33 @@ static const char** label_source(controller_info *info)
const char *get_button_label(controller_info *info, int button)
{
#ifndef USE_FBDEV
#if SDL_VERSION_ATLEAST(2,0,14)
if (info->subtype == SUBTYPE_XBOX_ELITE && button >= SDL_CONTROLLER_BUTTON_PADDLE1 && button <= SDL_CONTROLLER_BUTTON_PADDLE4) {
static char const * names[] = {"Paddle 1", "Paddle 2", "Paddle 3", "Paddle 4"};
return names[button - SDL_CONTROLLER_BUTTON_PADDLE1];
}
if (button == SDL_CONTROLLER_BUTTON_TOUCHPAD && (info->subtype == SUBTYPE_PS4 || info->subtype == SUBTYPE_PS5)) {
return "Touchpad";
}
if (button == SDL_CONTROLLER_BUTTON_MISC1) {
switch (info->subtype)
{
case SUBTYPE_XBONE:
case SUBTYPE_XBOX_ELITE:
return "Share";
case SUBTYPE_PS5:
return "Microphone";
case SUBTYPE_SWITCH:
return "Capture";
}
}
#endif
if (button >= SDL_CONTROLLER_BUTTON_DPAD_UP) {
static char const * dirs[] = {"Up", "Down", "Left", "Right"};
return dirs[button - SDL_CONTROLLER_BUTTON_DPAD_UP];
if (button <= SDL_CONTROLLER_BUTTON_DPAD_RIGHT) {
static char const * dirs[] = {"Up", "Down", "Left", "Right"};
return dirs[button - SDL_CONTROLLER_BUTTON_DPAD_UP];
}
return NULL;
}
#endif
return label_source(info)[button];
+5 -1
View File
@@ -16,9 +16,11 @@ enum {
SUBTYPE_XBOX,
SUBTYPE_X360,
SUBTYPE_XBONE,
SUBTYPE_XBOX_ELITE,
SUBTYPE_PS2,
SUBTYPE_PS3,
SUBTYPE_PS4,
SUBTYPE_PS5,
SUBTYPE_WIIU,
SUBTYPE_SWITCH,
SUBTYPE_GENESIS,
@@ -37,6 +39,8 @@ enum {
typedef struct {
char const *name;
int16_t stick_deadzone;
int16_t trigger_deadzone;
uint8_t type;
uint8_t subtype;
uint8_t variant;
@@ -52,4 +56,4 @@ void controller_add_mappings(void);
char *make_controller_type_key(controller_info *info);
char *make_human_readable_type_name(controller_info *info);
#endif //CONTROLLER_INFO_H_
#endif //CONTROLLER_INFO_H_
+1609 -285
View File
File diff suppressed because it is too large. Load diff
+6663 -952
View File
File diff suppressed because it is too large. Load diff
+208 -10
View File
@@ -2,33 +2,231 @@
#define DEBUG_H_
#include <stdint.h>
#include "m68k_core.h"
#include "tern.h"
#ifdef NEW_CORE
#include "m68k.h"
#include "z80.h"
#else
#include "m68k_core.h"
#include "z80_to_x86.h"
#endif
#include "upd78k2.h"
#include "sh2.h"
#include "disasm.h"
typedef enum {
TOKEN_NONE,
TOKEN_INT,
TOKEN_DECIMAL,
TOKEN_NAME,
TOKEN_ARRAY,
TOKEN_FUNCALL,
TOKEN_OPER,
TOKEN_SIZE,
TOKEN_LBRACKET,
TOKEN_RBRACKET,
TOKEN_LPAREN,
TOKEN_RPAREN,
TOKEN_STRING
} token_type;
typedef struct {
token_type type;
union {
char *str;
char op[3];
uint32_t num;
float f;
} v;
} token;
typedef enum {
EXPR_NONE,
EXPR_SCALAR,
EXPR_UNARY,
EXPR_BINARY,
EXPR_SIZE,
EXPR_MEM,
EXPR_NAMESPACE,
EXPR_FUNCALL
} expr_type;
typedef struct expr expr;
struct expr {
expr_type type;
expr *left;
expr *right;
token op;
};
enum {
DBG_VAL_U32,
DBG_VAL_F32,
DBG_VAL_ARRAY,
DBG_VAL_STRING,
DBG_VAL_FUNC
};
typedef struct {
union {
uint32_t u32;
float f32;
} v;
uint32_t type;
} debug_val;
typedef struct {
char *raw;
expr *parsed;
debug_val value;
} command_arg;
typedef struct debug_root debug_root;
typedef struct parsed_command parsed_command;
typedef uint8_t (*cmd_fun)(debug_root *root, parsed_command *cmd);
typedef struct {
const char **names;
const char *usage;
const char *desc;
cmd_fun impl;
int min_args;
int max_args;
uint8_t skip_eval;
uint8_t visited;
uint8_t has_block;
uint8_t accepts_else;
uint8_t raw_args;
} command_def;
typedef struct disp_def {
struct disp_def * next;
char * param;
struct disp_def *next;
char *format;
int num_args;
command_arg *args;
uint32_t index;
char format_char;
} disp_def;
typedef struct {
parsed_command *commands;
int num_commands;
} command_block;
typedef debug_val (*debug_native_func)(debug_val *args, int num_args);
typedef struct {
union {
debug_native_func native;
command_block block;
} impl;
char **arg_names;
int max_args;
int min_args;
uint8_t is_native;
} debug_func;
struct parsed_command {
command_def *def;
uint32_t func;
char *format;
char *raw;
command_arg *args;
int num_args;
command_block block;
command_block else_block;
};
enum {
BP_TYPE_CPU,
BP_TYPE_CPU_WATCH,
BP_TYPE_VDPREG,
BP_TYPE_VDPDMA,
BP_TYPE_VDPDATA
};
typedef struct bp_def {
struct bp_def *next;
char *commands;
uint32_t address;
uint32_t index;
struct bp_def *next;
parsed_command *commands;
expr *condition;
uint32_t num_commands;
uint32_t address;
uint32_t index;
uint32_t mask;
uint8_t type;
} bp_def;
bp_def ** find_breakpoint(bp_def ** cur, uint32_t address);
typedef struct debug_array debug_array;
typedef debug_val (*debug_array_get)(debug_array *array, uint32_t index);
typedef void (*debug_array_set)(debug_array *array, uint32_t index, debug_val value);
typedef void (*debug_array_append)(debug_array *array, debug_val value);
struct debug_array{
debug_array_get get;
debug_array_set set;
debug_array_append append;
void *base;
uint32_t size;
uint32_t storage;
};
typedef struct {
char *buffer;
uint32_t size;
uint32_t storage;
} debug_string;
typedef struct debug_var debug_var;
typedef debug_val (*debug_var_get)(debug_var *var);
typedef void (*debug_var_set)(debug_var *var, debug_val val);
struct debug_var {
debug_var_get get;
debug_var_set set;
void *ptr;
debug_val val;
};
typedef debug_var *(*resolver)(debug_root *root, const char *name);
typedef uint8_t (*reader)(debug_root *root, uint32_t *out, char size);
typedef uint8_t (*writer)(debug_root *root, uint32_t address, uint32_t value, char size);
typedef uint32_t (*find_chunk_end)(debug_root *root, uint32_t start_address);
struct debug_root {
void *cpu_context;
bp_def *breakpoints;
disp_def *displays;
tern_node *commands;
tern_node *variables;
tern_node *symbols;
tern_node *other_roots;
disasm_context *disasm;
reader read_mem;
writer write_mem;
find_chunk_end chunk_end;
parsed_command last_cmd;
debug_val retval;
uint32_t bp_index;
uint32_t disp_index;
uint32_t branch_t;
uint32_t branch_f;
void *inst;
uint32_t address;
uint32_t after;
};
debug_root *find_root(void *cpu);
debug_root *find_m68k_root(m68k_context *context);
debug_root *find_z80_root(z80_context *context);
debug_root *find_upd_root(upd78k2_context *upd);
debug_root *find_sh2_root(sh2_context *sh2);
bp_def ** find_breakpoint(bp_def ** cur, uint32_t address, uint8_t type);
bp_def ** find_breakpoint_idx(bp_def ** cur, uint32_t index);
void add_display(disp_def ** head, uint32_t *index, char format_char, char * param);
void remove_display(disp_def ** head, uint32_t index);
void debugger(m68k_context * context, uint32_t address);
void debugger(void * vcontext, uint32_t address);
z80_context * zdebugger(z80_context * context, uint16_t address);
void upd_debugger(upd78k2_context *upd);
void sh2_debugger(sh2_context *sh2);
void print_m68k_help();
void print_z80_help();
+63 -14
View File
@@ -1,4 +1,3 @@
bindings {
keys {
up gamepads.1.up
@@ -18,13 +17,16 @@ bindings {
[ ui.vdp_debug_mode
u ui.enter_debugger
p ui.screenshot
i ui.record_video
b ui.plane_debug
v ui.vram_debug
c ui.cram_debug
n ui.compositing_debug
o ui.oscilloscope
m ui.vgm_log
esc ui.exit
esc ui.menu
` ui.save_state
l ui.load_state
0 ui.set_speed.0
1 ui.set_speed.1
2 ui.set_speed.2
@@ -40,6 +42,11 @@ bindings {
f5 ui.reload
z ui.sms_pause
rctrl ui.toggle_keyboard_captured
f2 cassette.play
f3 cassette.stop
f4 cassette.rewind
f7 ui.pause
f8 ui.advance
}
pads {
default {
@@ -60,7 +67,7 @@ bindings {
leftshoulder gamepads.n.z
back gamepads.n.mode
start gamepads.n.start
guide ui.exit
guide ui.menu
leftstick ui.save_state
}
axes {
@@ -85,7 +92,7 @@ bindings {
a gamepads.n.a
b gamepads.n.b
back ui.sms_pause
guide ui.exit
guide ui.menu
leftshoulder gamepads.n.mode
leftstick ui.save_state
rightshoulder gamepads.n.z
@@ -116,7 +123,7 @@ bindings {
a gamepads.n.a
b gamepads.n.b
back ui.sms_pause
guide ui.exit
guide ui.menu
leftshoulder gamepads.n.mode
leftstick ui.save_state
rightshoulder gamepads.n.z
@@ -147,7 +154,7 @@ bindings {
a gamepads.n.a
b gamepads.n.b
back ui.sms_pause
guide ui.exit
guide ui.menu
leftshoulder gamepads.n.mode
leftstick ui.save_state
rightshoulder gamepads.n.z
@@ -178,7 +185,7 @@ bindings {
a gamepads.n.a
b gamepads.n.b
back ui.sms_pause
guide ui.exit
guide ui.menu
leftshoulder gamepads.n.mode
leftstick ui.save_state
rightshoulder gamepads.n.z
@@ -198,14 +205,14 @@ bindings {
}
genesis_6b_bumpers {
axes {
lefttrigger ui.exit
lefttrigger ui.menu
righttrigger gamepads.n.mode
}
buttons {
a gamepads.n.a
b gamepads.n.b
back ui.sms_pause
guide ui.exit
guide ui.menu
leftshoulder gamepads.n.z
rightshoulder gamepads.n.c
start gamepads.n.start
@@ -223,14 +230,14 @@ bindings {
}
saturn_6b_bumpers {
axes {
lefttrigger ui.exit
lefttrigger ui.menu
righttrigger gamepads.n.mode
}
buttons {
a gamepads.n.a
b gamepads.n.b
back ui.sms_pause
guide ui.exit
guide ui.menu
leftshoulder gamepads.n.z
rightshoulder gamepads.n.c
start gamepads.n.start
@@ -276,6 +283,18 @@ io {
1 gamepad6.1
2 gamepad6.2
}
sega_multitap.1 {
1 gamepad6.2
2 gamepad6.3
3 gamepad6.4
4 gamepad6.5
}
ea_multitap {
1 gamepad6.1
2 gamepad6.2
3 gamepad6.3
4 gamepad6.4
}
}
video {
@@ -290,6 +309,8 @@ video {
scanlines off
vsync off
fullscreen off
#forces an integer ratio between the emulated height and the height on screen
integer_scaling off
#setting gl to off, will force use of the SDL2 fallback renderer
#this is useful for those running on machines with Open GL 2.0 unavailable
#so the warning doesn't display on startup
@@ -308,7 +329,7 @@ video {
#comment out those two lines and uncomment these
#top 11
#bottom 8
#these values will completely hide the horizontal border
left 13
right 14
@@ -326,12 +347,21 @@ video {
#coment out those two lines and uncomment these
#top 30
#bottom 24
#these values will completely hide the horizontal border
left 13
right 14
}
}
gamegear {
overscan {
#generally shouldn't change these
top 51
bot 48
left 61
right 62
}
}
}
audio {
@@ -378,9 +408,11 @@ ui {
#accepts special variables $HOME, $EXEDIR, $USERDATA, $ROMNAME
save_path $USERDATA/blastem/$ROMNAME
#space delimited list of file extensions to filter against in menu
extensions bin gen md smd sms gg zip gz
extensions bin gen md smd sms gg sg sc sf7 zip gz cue iso vgm vgz flac wav col 32x
#specifies the preferred save-state format, set to gst for Genecyst compatible states
state_format native
#set to on to use the native file picker on your OS instead of the builtin one
use_native_filechooser off
}
system {
@@ -390,7 +422,11 @@ system {
sync_source audio
#set this to random to debug initialization bugs
ram_init zero
#Default region to use when the ROM has no region info or there are multiple options
#U = USA/North America, E = Europe, J = Japan
default_region U
#Whether to always force the region selected in default_region
force_region off
#controls whether MegaWiFi support is enabled or not
#MegaWiFi allows ROMs to make connections to the internet
#so it should only be enabled for ROMs you trust
@@ -399,4 +435,17 @@ system {
model md1va3
}
sms {
system {
model md1va3
}
io {
devices {
1 gamepad2.1
2 gamepad2.2
}
}
}
#Don't manually edit `version`, it's used for automatic config migration
version 13
+312 -222
View File
@@ -12,148 +12,82 @@
#include "vos_program_module.h"
#include "tern.h"
#include "util.h"
#include "disasm.h"
uint8_t visited[(16*1024*1024)/16];
uint16_t label[(16*1024*1024)/8];
void fatal_error(char *format, ...)
{
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
exit(1);
}
void visit(uint32_t address)
{
address &= 0xFFFFFF;
visited[address/16] |= 1 << ((address / 2) % 8);
}
void reference(uint32_t address)
{
address &= 0xFFFFFF;
//printf("referenced: %X\n", address);
label[address/16] |= 1 << (address % 16);
}
uint8_t is_visited(uint32_t address)
{
address &= 0xFFFFFF;
return visited[address/16] & (1 << ((address / 2) % 8));
}
uint16_t is_label(uint32_t address)
{
address &= 0xFFFFFF;
return label[address/16] & (1 << (address % 16));
}
typedef struct {
uint32_t num_labels;
uint32_t storage;
char *labels[];
} label_names;
tern_node * add_label(tern_node * head, char * name, uint32_t address)
{
char key[MAX_INT_KEY_SIZE];
address &= 0xFFFFFF;
reference(address);
tern_int_key(address, key);
label_names * names = tern_find_ptr(head, key);
if (names)
{
if (names->num_labels == names->storage)
{
names->storage = names->storage + (names->storage >> 1);
names = realloc(names, sizeof(label_names) + names->storage * sizeof(char *));
}
} else {
names = malloc(sizeof(label_names) + 4 * sizeof(char *));
names->num_labels = 0;
names->storage = 4;
head = tern_insert_ptr(head, key, names);
}
names->labels[names->num_labels++] = strdup(name);
return head;
}
typedef struct deferred {
uint32_t address;
struct deferred *next;
} deferred;
deferred * defer(uint32_t address, deferred * next)
{
if (is_visited(address) || address & 1) {
return next;
}
//printf("deferring %X\n", address);
deferred * d = malloc(sizeof(deferred));
d->address = address;
d->next = next;
return d;
}
void check_reference(m68kinst * inst, m68k_op_info * op)
void check_reference(disasm_context *context, m68kinst * inst, m68k_op_info * op)
{
label_def *def = NULL;
switch(op->addr_mode)
{
case MODE_PC_DISPLACE:
reference(inst->address + 2 + op->params.regs.displacement);
def = reference(context, inst->address + 2 + op->params.regs.displacement);
break;
case MODE_ABSOLUTE:
case MODE_ABSOLUTE_SHORT:
reference(op->params.immed);
def = reference(context, op->params.immed);
break;
}
if (
def && !def->data_size && inst->op != M68K_JMP &&
inst->op != M68K_JSR && inst->op != M68K_LEA && inst->op != M68K_PEA
) {
def->data_count = 1;
def->data_size = inst->extra.size == OPSIZE_WORD ? 2 : inst->extra.size == OPSIZE_BYTE ? 1 : 4;
visit(context, def->full_address);
}
}
int label_fun(char *dst, uint32_t address, void * data)
typedef struct {
uint32_t address_off;
uint32_t address_end;
uint16_t *buffer;
} rom_def;
uint16_t fetch(uint32_t address, void *data)
{
tern_node * labels = data;
char key[MAX_INT_KEY_SIZE];
label_names * names = tern_find_ptr(labels, tern_int_key(address & 0xFFFFFF, key));
if (names)
{
return sprintf(dst, "%s", names->labels[0]);
rom_def *rom = data;
address &= 0xFFFFFF;
if (address >= rom->address_off && address < rom->address_end) {
return rom->buffer[(address - rom->address_off) >> 1];
}
return 0;
}
void print_label_def(char *key, tern_val val, uint8_t valtype, void *data)
{
rom_def *rom = data;
label_def *label = val.ptrval;
uint32_t address = label->full_address & 0xFFFFFFF;
if (address >= rom->address_off && address < rom->address_end) {
return;
}
if (!label->referenced) {
return;
}
if (label->num_labels) {
for (int i = 0; i < label->num_labels; i++)
{
printf("%s equ $%X\n", label->labels[i], label->full_address);
}
} else {
return m68k_default_label_fun(dst, address, NULL);
printf("ADR_%X equ $%X\n", label->full_address, label->full_address);
}
}
char * strip_ws(char * text)
{
while (*text && (!isprint(*text) || isblank(*text)))
{
text++;
}
char * ret = text;
text = ret + strlen(ret) - 1;
while (text > ret && (!isprint(*text) || isblank(*text)))
{
*text = 0;
text--;
}
return ret;
}
int main(int argc, char ** argv)
{
long filesize;
unsigned short *filebuf;
unsigned short *filebuf = NULL;
char disbuf[1024];
m68kinst instbuf;
unsigned short * cur;
deferred *def = NULL, *tmpd;
uint8_t labels = 0, addr = 0, only = 0, vos = 0, reset = 0;
tern_node * named_labels = NULL;
disasm_context *context = create_68000_disasm();
uint32_t address_off = 0, address_end;
uint8_t do_cd_labels = 0, main_cpu = 0;
for(uint8_t opt = 2; opt < argc; ++opt) {
if (argv[opt][0] == '-') {
FILE * address_log;
@@ -174,6 +108,12 @@ int main(int argc, char ** argv)
case 'r':
reset = 1;
break;
case 'c':
do_cd_labels = 1;
break;
case 'm':
main_cpu = 1;
break;
case 's':
opt++;
if (opt >= argc) {
@@ -195,27 +135,13 @@ int main(int argc, char ** argv)
}
while (fgets(disbuf, sizeof(disbuf), address_log)) {
if (disbuf[0]) {
char *end;
uint32_t address = strtol(disbuf, &end, 16);
if (address) {
def = defer(address, def);
reference(address);
if (*end == '=') {
named_labels = add_label(named_labels, strip_ws(end+1), address);
}
}
process_address_def(context, disbuf);
}
}
fclose(address_log);
}
} else {
char *end;
uint32_t address = strtol(argv[opt], &end, 16);
def = defer(address, def);
reference(address);
if (*end == '=') {
named_labels = add_label(named_labels, end+1, address);
}
process_address_def(context, argv[opt]);
}
}
FILE * f = fopen(argv[1], "rb");
@@ -224,6 +150,8 @@ int main(int argc, char ** argv)
fseek(f, 0, SEEK_SET);
char int_key[MAX_INT_KEY_SIZE];
uint8_t is_scd_iso = 0;
uint8_t has_manual_defs = !!context->deferred;
if (vos)
{
vos_program_module header;
@@ -231,15 +159,15 @@ int main(int argc, char ** argv)
vos_read_alloc_module_map(f, &header);
address_off = header.user_boundary;
address_end = address_off + filesize - 0x1000;
def = defer(header.main_entry_link.code_address, def);
named_labels = add_label(named_labels, "main_entry_link", header.main_entry_link.code_address);
defer_disasm(context, header.main_entry_link.code_address);
add_label(context, "main_entry_link", header.main_entry_link.code_address);
for (int i = 0; i < header.n_modules; i++)
{
if (!reset || header.module_map_entries[i].code_address != header.user_boundary)
{
def = defer(header.module_map_entries[i].code_address, def);
defer_disasm(context, header.module_map_entries[i].code_address);
}
named_labels = add_label(named_labels, header.module_map_entries[i].name.str, header.module_map_entries[i].code_address);
add_label(context, header.module_map_entries[i].name.str, header.module_map_entries[i].code_address);
}
fseek(f, 0x1000, SEEK_SET);
filebuf = malloc(filesize - 0x1000);
@@ -254,92 +182,217 @@ int main(int argc, char ** argv)
}
if (reset)
{
def = defer(filebuf[2] << 16 | filebuf[3], def);
named_labels = add_label(named_labels, "reset", filebuf[2] << 16 | filebuf[3]);
defer_disasm(context, filebuf[2] << 16 | filebuf[3]);
add_label(context, "reset", filebuf[2] << 16 | filebuf[3]);
}
} else {
address_end = address_off + filesize;
filebuf = malloc(filesize);
if (fread(filebuf, 2, filesize/2, f) != filesize/2)
{
} else if (filesize > 0x1000) {
long boot_size = filesize > (16*2352) ? 16*2352 : filesize;
filebuf = malloc(boot_size);
if (fread(filebuf, 1, boot_size, f) != boot_size) {
fprintf(stderr, "Failure while reading file %s\n", argv[1]);
return 1;
}
is_scd_iso = !memcmp("SEGADISCSYSTEM ", filebuf, 0x10);
if (!is_scd_iso && !memcmp("SEGADISCSYSTEM ", filebuf + 0x8, 0x10)) {
is_scd_iso = 1;
uint32_t end = 16 * 2352;
if (end > filesize) {
end = (filesize / 2352) * 2352;
}
for(uint32_t offset = 0x10, dst_offset = 0; offset < end; offset += 2352, dst_offset += 2048)
{
memmove(filebuf + dst_offset/2, filebuf + offset/2, 2048);
}
boot_size = (end / 2352) * 2048;
}
if (is_scd_iso) {
fclose(f);
for(cur = filebuf; cur - filebuf < (boot_size/2); ++cur)
{
*cur = (*cur >> 8) | (*cur << 8);
}
if (main_cpu) {
uint32_t main_start = 0x200;
uint32_t extra_start = filebuf[0x30/2] << 16 | filebuf[0x32/2];
uint32_t main_end = (filebuf[0x34/2] << 16 | filebuf[0x36/2]) + extra_start;
if (main_end > boot_size) {
main_end = boot_size;
}
address_off = 0xFF0000;
address_end = address_off + main_end-main_start;
filebuf += main_start / 2;
add_label(context, "start", 0xFF0000);
if (!has_manual_defs || !only) {
defer_disasm(context, 0xFF0000);
}
uint32_t user_start;
if (filebuf[0xA/2] == 0x57A) {
//US
user_start = 0xFF0584;
} else if (filebuf[0xA/2] == 0x564) {
//EU
user_start = 0xFF056E;
} else {
//JP
user_start = 0xFF0156;
}
add_label(context, "user_start", user_start);
do_cd_labels = 1;
} else {
uint32_t sub_start =filebuf[0x40/2] << 16 | filebuf[0x42/2];
sub_start &= ~0x7FF;
uint32_t sub_end = sub_start + (filebuf[0x44/2] << 16 | filebuf[0x46/2]);
if (sub_start > (boot_size - 0x20)) {
fprintf(stderr, "System Program start offset is %X, but image is only %X bytes\n", sub_start, (uint32_t)boot_size);
return 1;
}
if (sub_end > boot_size) {
sub_end = boot_size;
}
uint32_t offset_start = (filebuf[(sub_start + 0x18)/2] << 16 | filebuf[(sub_start + 0x1A)/2]) + sub_start;
for(uint32_t cur = offset_start, index = 0; cur < sub_end && filebuf[cur/2]; cur+=2, ++index)
{
uint32_t offset = offset_start + filebuf[cur/2];
if (offset >= boot_size) {
break;
}
static const char* fixed_names[3] = {
"init",
"main",
"int_2"
};
char namebuf[32];
const char *name;
if (index < 3) {
name = fixed_names[index];
} else {
name = namebuf;
sprintf(namebuf, "usercall%u", index);
}
uint32_t address = 0x6000 + offset - sub_start;
add_label(context, name, address);
if (!has_manual_defs || !only) {
defer_disasm(context, address);
}
}
do_cd_labels = 1;
filebuf += sub_start / 2;
address_off = 0x6000;
address_end = sub_end-sub_start + address_off;
}
}
}
if (!vos && !is_scd_iso) {
if (filebuf) {
if (filesize > (32*1024)) {
filebuf = realloc(filebuf, filesize);
fseek(f, 32*1024, SEEK_SET);
uint32_t to_read = filesize/2 - 16*1024;
if (fread(filebuf + 16*1024, 2, to_read, f) != to_read)
{
fprintf(stderr, "Failure while reading file %s\n", argv[1]);
}
}
} else {
filebuf = malloc(filesize);
if (fread(filebuf, 2, filesize/2, f) != filesize/2)
{
fprintf(stderr, "Failure while reading file %s\n", argv[1]);
}
}
address_end = address_off + filesize;
fclose(f);
for(cur = filebuf; cur - filebuf < (filesize/2); ++cur)
{
*cur = (*cur >> 8) | (*cur << 8);
}
uint32_t start = filebuf[2] << 16 | filebuf[3];
uint32_t int_2 = filebuf[0x68/2] << 16 | filebuf[0x6A/2];
uint32_t int_4 = filebuf[0x70/2] << 16 | filebuf[0x72/2];
uint32_t int_6 = filebuf[0x78/2] << 16 | filebuf[0x7A/2];
named_labels = add_label(named_labels, "start", start);
named_labels = add_label(named_labels, "int_2", int_2);
named_labels = add_label(named_labels, "int_4", int_4);
named_labels = add_label(named_labels, "int_6", int_6);
if (!def || !only) {
def = defer(start, def);
def = defer(int_2, def);
def = defer(int_4, def);
def = defer(int_6, def);
if (!address_off) {
process_m68k_vectors(context, filebuf, context->deferred && only);
}
}
if (do_cd_labels) {
if (main_cpu) {
add_segacd_maincpu_labels(context);
} else {
add_segacd_subcpu_labels(context);
}
}
uint16_t *encoded, *next;
uint32_t size, tmp_addr;
uint32_t address;
while(def) {
rom_def rom = {
.address_off = address_off,
.address_end = address_end,
.buffer = filebuf
};
uint8_t valid_address;
while(context->deferred) {
do {
encoded = NULL;
address = def->address;
if (!is_visited(address)) {
encoded = filebuf + ((address & 0xFFFFFF) - address_off)/2;
}
tmpd = def;
def = def->next;
valid_address = 0;
address = context->deferred->address;
label_def *def = (label_def *)context->deferred->dest;
deferred_addr *tmpd = context->deferred;
context->deferred = context->deferred->next;
free(tmpd);
} while(def && encoded == NULL);
if (!encoded) {
if (def) {
visit(context, address);
if (def->is_pointer) {
for (uint32_t i = 0; i < def->data_count; i++, address += 4)
{
uint32_t masked = address & context->address_mask;
if (masked < address_end - 2 && masked >= address_off) {
uint32_t target = rom.buffer[(masked - rom.address_off) >> 1] << 16;
masked += 2;
target |= rom.buffer[(masked - rom.address_off) >> 1];
defer_disasm(context, target);
reference(context, target);
}
}
}
}else if (!is_visited(context, address)) {
uint32_t masked = address & context->address_mask;
if (masked < address_end && masked >= address_off) {
valid_address = 1;
}
}
} while(context->deferred && !valid_address);
if (!valid_address) {
break;
}
for(;;) {
if ((address & 0xFFFFFF) > address_end || address < address_off) {
if ((address & context->address_mask) > address_end || address < address_off) {
break;
}
visit(address);
next = m68k_decode(encoded, &instbuf, address);
address += (next-encoded)*2;
encoded = next;
visit(context, address);
address = m68k_decode(fetch, &rom, &instbuf, address);
//m68k_disasm(&instbuf, disbuf);
//printf("%X: %s\n", instbuf.address, disbuf);
check_reference(&instbuf, &(instbuf.src));
check_reference(&instbuf, &(instbuf.dst));
check_reference(context, &instbuf, &(instbuf.src));
check_reference(context, &instbuf, &(instbuf.dst));
if (instbuf.op == M68K_ILLEGAL || instbuf.op == M68K_RTS || instbuf.op == M68K_RTE || instbuf.op == M68K_INVALID) {
break;
}
if (instbuf.op == M68K_BCC || instbuf.op == M68K_DBCC || instbuf.op == M68K_BSR) {
tmp_addr = instbuf.address + 2 + instbuf.src.params.immed;
reference(context, tmp_addr);
if (instbuf.op == M68K_BCC && instbuf.extra.cond == COND_TRUE) {
address = instbuf.address + 2 + instbuf.src.params.immed;
encoded = filebuf + (address - address_off)/2;
reference(address);
if (is_visited(address)) {
address = tmp_addr;
if (is_visited(context, address)) {
break;
}
} else {
tmp_addr = instbuf.address + 2 + instbuf.src.params.immed;
reference(tmp_addr);
def = defer(tmp_addr, def);
defer_disasm(context, tmp_addr);
}
} else if(instbuf.op == M68K_JMP) {
if (instbuf.src.addr_mode == MODE_ABSOLUTE || instbuf.src.addr_mode == MODE_ABSOLUTE_SHORT) {
address = instbuf.src.params.immed;
encoded = filebuf + (address - address_off)/2;
if (is_visited(address)) {
if (is_visited(context, address)) {
break;
}
} else if (instbuf.src.addr_mode == MODE_PC_DISPLACE) {
address = instbuf.src.params.regs.displacement + instbuf.address + 2;
encoded = filebuf + (address - address_off)/2;
if (is_visited(address)) {
if (is_visited(context, address)) {
break;
}
} else {
@@ -347,60 +400,97 @@ int main(int argc, char ** argv)
}
} else if(instbuf.op == M68K_JSR) {
if (instbuf.src.addr_mode == MODE_ABSOLUTE || instbuf.src.addr_mode == MODE_ABSOLUTE_SHORT) {
def = defer(instbuf.src.params.immed, def);
defer_disasm(context, instbuf.src.params.immed);
} else if (instbuf.src.addr_mode == MODE_PC_DISPLACE) {
def = defer(instbuf.src.params.regs.displacement + instbuf.address + 2, def);
defer_disasm(context, instbuf.src.params.regs.displacement + instbuf.address + 2);
}
}
}
}
if (labels) {
for (address = 0; address < address_off; address++) {
if (is_label(address)) {
printf("ADR_%X equ $%X\n", address, address);
}
}
for (address = filesize; address < (16*1024*1024); address++) {
char key[MAX_INT_KEY_SIZE];
tern_int_key(address, key);
label_names *names = tern_find_ptr(named_labels, key);
if (names) {
for (int i = 0; i < names->num_labels; i++)
{
printf("%s equ $%X\n", names->labels[i], address);
}
} else if (is_label(address)) {
printf("ADR_%X equ $%X\n", address, address);
}
}
tern_foreach(context->labels, print_label_def, &rom);
puts("");
}
for (address = address_off; address < address_end; address+=2) {
if (is_visited(address)) {
encoded = filebuf + (address-address_off)/2;
m68k_decode(encoded, &instbuf, address);
for (address = address_off; address < address_end;) {
if (is_visited(context, address)) {
if (labels) {
m68k_disasm_labels(&instbuf, disbuf, label_fun, named_labels);
char keybuf[MAX_INT_KEY_SIZE];
label_names * names = tern_find_ptr(named_labels, tern_int_key(address, keybuf));
if (names)
{
for (int i = 0; i < names->num_labels; i++)
{
printf("%s:\n", names->labels[i]);
label_def *label = find_label(context, address);
if (!label && !(address & 1)) {
label = find_label(context, address + 1);
if (label) {
uint8_t val = fetch(address, &rom) >> 8;
printf("\tdc.b %02X\n", val);
address++;
}
} else if (is_label(instbuf.address)) {
printf("ADR_%X:\n", instbuf.address);
}
if (addr) {
printf("\t%s\t;%X\n", disbuf, instbuf.address);
} else {
printf("\t%s\n", disbuf);
if (label) {
if (label->num_labels) {
for (int i = 0; i < label->num_labels; i++)
{
printf("%s:\n", label->labels[i]);
}
} else {
printf("ADR_%X:\n", label->full_address);
}
if (label->data_size) {
uint32_t els_per_line = 16 / label->data_size;
for (uint32_t i = 0; i < label->data_count;)
{
printf("\tdc.%c ", label->data_size < 2 ? 'b' : label->data_size < 4 ? 'w' : 'l');
for (uint32_t j = 0; j < els_per_line && i < label->data_count; i++,j++)
{
uint32_t val = fetch(address & ~1, &rom);
const char *fmt = "$%04X";
if (label->data_size == 1) {
val = address & 1 ? val & 0xFF : val >> 8;
fmt = "$%02X";
} else if (label->data_size == 4) {
val <<= 16;
val |= fetch(address + 2, &rom);
if (label->is_pointer) {
format_label(disbuf, val, context);
fmt = NULL;
fputs(disbuf, stdout);
} else {
fmt = "$%08X";
}
}
if (fmt) {
printf(fmt, val);
}
if (j == els_per_line - 1 || i == label->data_count - 1) {
puts("");
} else {
fputs(", ", stdout);
}
address += label->data_size;
}
}
}
}
if (!label || !label->data_size) {
if (address & 1) {
uint8_t val = fetch(address & ~1, &rom);
printf("\tdc.b %02X\n", val);
address++;
}
uint32_t after = m68k_decode(fetch, &rom, &instbuf, address);
m68k_disasm_labels(&instbuf, disbuf, context);
if (addr) {
printf("\t%s\t;%X\n", disbuf, instbuf.address);
} else {
printf("\t%s\n", disbuf);
}
address = after;
}
} else {
uint32_t after = m68k_decode(fetch, &rom, &instbuf, address);
m68k_disasm(&instbuf, disbuf);
printf("%X: %s\n", instbuf.address, disbuf);
address = after;
}
} else {
address += 2;
}
}
return 0;
+593
View File
@@ -0,0 +1,593 @@
#include "disasm.h"
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <ctype.h>
#include "util.h"
label_def *find_label(disasm_context *context, uint32_t address)
{
char key[MAX_INT_KEY_SIZE];
tern_sortable_int_key(address & context->address_mask, key);
return tern_find_ptr(context->labels, key);
}
label_def *find_label_by_name(disasm_context *context, const char *name)
{
return tern_find_ptr(context->labels_by_name, name);
}
int format_label(char *dst, uint32_t address, disasm_context *context)
{
label_def *def = find_label(context, address);
if (def && def->num_labels) {
return sprintf(dst, "%s", def->labels[0]);
}
return sprintf(dst, "ADR_%X", address);
}
label_def *add_find_label(disasm_context *context, uint32_t address)
{
char key[MAX_INT_KEY_SIZE];
tern_sortable_int_key(address & context->address_mask, key);
label_def *def = tern_find_ptr(context->labels, key);
if (!def)
{
def = calloc(1, sizeof(label_def));
context->labels = tern_insert_ptr(context->labels, key, def);
}
def->full_address = address;
return def;
}
void name_label(disasm_context *context, label_def *def, const char *name)
{
if (def->num_labels == def->storage) {
def->storage = def->storage ? def->storage * 2 : 4;
def->labels = realloc(def->labels, def->storage * sizeof(char*));;
}
def->labels[def->num_labels++] = strdup(name);
context->labels_by_name = tern_insert_ptr(context->labels_by_name, name, def);
}
label_def *weak_label(disasm_context *context, const char *name, uint32_t address)
{
label_def *def = add_find_label(context, address);
name_label(context, def, name);
return def;
}
label_def *reference(disasm_context *context, uint32_t address)
{
label_def *def = add_find_label(context, address);
def->referenced = 1;
return def;
}
label_def *add_label(disasm_context *context, const char *name, uint32_t address)
{
label_def *def = reference(context, address);
name_label(context, def, name);
return def;
}
void visit(disasm_context *context, uint32_t address)
{
if (!context->visited) {
uint32_t size = context->address_mask + 1;
size >>= context->visit_preshift;
size >>= 3;
context->visited = calloc(1, size);
}
address &= context->address_mask;
address >>= context->visit_preshift;
context->visited[address >> 3] |= 1 << (address & 7);
}
uint8_t is_visited(disasm_context *context, uint32_t address)
{
if (!context->visited) {
return 0;
}
address &= context->address_mask;
address >>= context->visit_preshift;
return (context->visited[address >> 3] & (1 << (address & 7))) != 0;
}
void defer_disasm_label(disasm_context *context, uint32_t address, label_def *label)
{
if (is_visited(context, address) || address & context->invalid_inst_addr_mask) {
return;
}
context->deferred = defer_address(context->deferred, address, (uint8_t *)label);
}
void defer_disasm(disasm_context *context, uint32_t address)
{
defer_disasm_label(context, address, NULL);
}
void process_address_def(disasm_context *context, char *def)
{
char *end;
uint32_t address = strtol(def, &end, 16);
if (*end == '=') {
defer_disasm(context, address);
add_label(context, strip_ws(end+1), address);
} else if (*end && !isspace(*end)) {
uint8_t is_table = 0;
if (*end == 't') {
is_table = 1;
end++;
}
uint8_t el_size, is_pointer = 0;;
switch (*end)
{
case 'y':
case 'b': el_size = 1; break;
case 'w': el_size = 2; break;
case 'f':
case 'p': is_pointer = 1;
case 'l': el_size = 4; break;
default:
fprintf(stderr, "Invalid character %c in address definition %s\n", *end, def);
exit(1);
}
uint32_t count = 1;
char *count_end = end + 1;
if (is_table) {
count = strtol(end + 1, &count_end, 10);
if (count_end == end + 2) {
fprintf(stderr, "Table address definition %s missing count\n", def);
exit(1);
}
}
label_def *def = *count_end == '=' ? add_label(context, strip_ws(count_end+1), address) : reference(context, address);
def->data_count = count;
def->data_size = el_size;
def->is_pointer = is_pointer;
if (*end == 'f') {
defer_disasm_label(context, address, def);
} else {
visit(context, address);
}
} else {
defer_disasm(context, address);
reference(context, address);
}
}
void process_m68k_vectors(disasm_context *context, uint16_t *table, uint8_t labels_only)
{
static const char* names[] = {
"access_fault",
"address_error",
"illegal_instruction",
"integer_divide_by_zero",
"chk_exception",
"trap_exception",
"privilege_violation",
"trace_exception",
"line_1010_emulator",
"line_1111_emulator"
};
uint32_t address = table[2] << 16 | table[3];
add_label(context, "start", address);
if (!labels_only) {
defer_disasm(context, address);
}
for (int i = 0; i < sizeof(names)/sizeof(*names); i++)
{
address = table[i*2+4] << 16 | table[i*2 + 5];
add_label(context, names[i], address);
if (!labels_only) {
defer_disasm(context, address);
}
}
char int_name[] = "int_0";
for (int i = 0; i < 7; i++)
{
int_name[4] = '1' + i;
address = table[i*2+(0x64>>1)] << 16 | table[i*2 + (0x64>>1|1)];
add_label(context, int_name, address);
if (!labels_only) {
defer_disasm(context, address);
}
}
char trap_name[] = "trap_0";
for (int i = 0; i < 16; i++)
{
trap_name[5] = i < 0xA ? '0' + i : 'a' + i - 0xA;
address = table[i*2+(0x80>>1)] << 16 | table[i*2 + (0x80>>1|1)];
add_label(context, trap_name, address);
if (!labels_only) {
defer_disasm(context, address);
}
}
}
void add_segacd_maincpu_labels(disasm_context *context)
{
weak_label(context, "_bios_reset", 0x280);
weak_label(context, "_bios_entry", 0x284);
weak_label(context, "_bios_init", 0x288);
weak_label(context, "_bios_init_sp", 0x28C);
weak_label(context, "_bios_vint", 0x290);
weak_label(context, "_bios_set_hint", 0x294);
weak_label(context, "_bios_poll_io", 0x298);
weak_label(context, "_bios_detect_io", 0x29C);
weak_label(context, "_bios_clear_vram", 0x2A0);
weak_label(context, "_bios_clear_nmtbl", 0x2A4);
weak_label(context, "_bios_clear_vsram", 0x2A8);
weak_label(context, "_bios_init_vdp", 0x2AC);
weak_label(context, "_bios_vdp_loadregs", 0x2B0);
weak_label(context, "_bios_vdp_fill", 0x2B4);
weak_label(context, "_bios_clear_vram_range", 0x2B8);
weak_label(context, "_bios_clear_vram_range_dma", 0x2BC);
weak_label(context, "_bios_vram_dma_fill", 0x2C0);
weak_label(context, "_bios_update_nmtbl", 0x2C4);
weak_label(context, "_bios_update_nmtbl_template", 0x2C8);
weak_label(context, "_bios_fill_nmtbl", 0x2CC);
weak_label(context, "_bios_vdp_dma", 0x2D0);
weak_label(context, "_bios_vdp_dma_wordram", 0x2D4);
weak_label(context, "_bios_vdp_display_enable", 0x2D8);
weak_label(context, "_bios_vdp_display_disable", 0x2DC);
weak_label(context, "_bios_pal_buffer", 0x2E0);
weak_label(context, "_bios_pal_buffer_update", 0x2E4);
weak_label(context, "_bios_pal_dma", 0x2E8);
weak_label(context, "_bios_gfx_decomp", 0x2EC);
weak_label(context, "_bios_gfx_decomp_ram", 0x2F0);
weak_label(context, "_bios_update_sprites", 0x2F4);
weak_label(context, "_bios_clear_ram", 0x2F8);
weak_label(context, "_bios_display_sprite", 0x300);
weak_label(context, "_bios_wait_vint", 0x304);
weak_label(context, "_bios_wait_vint_flags", 0x308);
weak_label(context, "_bios_dma_sat", 0x30C);
weak_label(context, "_bios_set_hint_direct", 0x314);
weak_label(context, "_bios_disable_hint", 0x318);
weak_label(context, "_bios_print", 0x31C);
weak_label(context, "_bios_load_user_font", 0x320);
weak_label(context, "_bios_load_bios_font", 0x324);
weak_label(context, "_bios_load_bios_font_default", 0x328);
//TODO: more functions in the middle here
weak_label(context, "_bios_prng_mod", 0x338);
weak_label(context, "_bios_prng", 0x33C);
weak_label(context, "_bios_clear_comm", 0x340);
weak_label(context, "_bios_comm_update", 0x344);
//TODO: more functions in the middle here
weak_label(context, "_bios_sega_logo", 0x364);
weak_label(context, "_bios_set_vint", 0x368);
//TODO: more functions at the end here
weak_label(context, "WORD_RAM", 0x200000);
weak_label(context, "CD_RESET_IFL2", 0xA12000);
weak_label(context, "CD_RESET_IFL2_BYTE", 0xA12001);
weak_label(context, "CD_WRITE_PROTECT", 0xA12002);
weak_label(context, "CD_MEM_MODE", 0xA12003);
weak_label(context, "CDC_CTRL", 0xA12004);
weak_label(context, "HINT_VECTOR", 0xA12006);
weak_label(context, "CDC_HOST_DATA", 0xA12008);
weak_label(context, "STOP_WATCH", 0xA1200C);
weak_label(context, "COMM_MAIN_FLAG", 0xA1200E);
weak_label(context, "COMM_SUB_FLAG", 0xA1200F);
weak_label(context, "COMM_CMD0", 0xA12010);
weak_label(context, "COMM_CMD1", 0xA12012);
weak_label(context, "COMM_CMD2", 0xA12014);
weak_label(context, "COMM_CMD3", 0xA12016);
weak_label(context, "COMM_CMD4", 0xA12018);
weak_label(context, "COMM_CMD5", 0xA1201A);
weak_label(context, "COMM_CMD6", 0xA1201C);
weak_label(context, "COMM_CMD7", 0xA1201E);
weak_label(context, "COMM_STATUS0", 0xA12020);
weak_label(context, "COMM_STATUS1", 0xA12022);
weak_label(context, "COMM_STATUS2", 0xA12024);
weak_label(context, "COMM_STATUS3", 0xA12026);
weak_label(context, "COMM_STATUS4", 0xA12028);
weak_label(context, "COMM_STATUS5", 0xA1202A);
weak_label(context, "COMM_STATUS6", 0xA1202C);
weak_label(context, "COMM_STATUS7", 0xA1202E);
}
void add_segacd_subcpu_labels(disasm_context *context)
{
weak_label(context, "bios_common_work", 0x5E80);
weak_label(context, "_setjmptbl", 0x5F0A);
weak_label(context, "_waitvsync", 0x5F10);
weak_label(context, "_buram", 0x5F16);
weak_label(context, "_cdboot", 0x5F1C);
weak_label(context, "_cdbios", 0x5F22);
weak_label(context, "_usercall0", 0x5F28);
weak_label(context, "_usercall1", 0x5F2E);
weak_label(context, "_usercall2", 0x5F34);
weak_label(context, "_usercall2Address", 0x5F36);
weak_label(context, "_usercall3", 0x5F3A);
weak_label(context, "_adrerr", 0x5F40);
weak_label(context, "_adrerrAddress", 0x5F42);
weak_label(context, "_coderr", 0x5F46);
weak_label(context, "_coderrAddress", 0x5F48);
weak_label(context, "_diverr", 0x5F4C);
weak_label(context, "_diverrAddress", 0x5F4E);
weak_label(context, "_chkerr", 0x5F52);
weak_label(context, "_chkerrAddress", 0x5F54);
weak_label(context, "_trperr", 0x5F58);
weak_label(context, "_trperrAddress", 0x5F5A);
weak_label(context, "_spverr", 0x5F5E);
weak_label(context, "_spverrAddress", 0x5F60);
weak_label(context, "_trace", 0x5F64);
weak_label(context, "_traceAddress", 0x5F66);
weak_label(context, "_nocod0", 0x5F6A);
weak_label(context, "_nocod0Address", 0x5F6C);
weak_label(context, "_nocod1", 0x5F70);
weak_label(context, "_nocod1Address", 0x5F72);
weak_label(context, "_slevel1", 0x5F76);
weak_label(context, "_slevel1Address", 0x5F78);
weak_label(context, "_slevel2", 0x5F7C);
weak_label(context, "_slevel2Address", 0x5F7E);
weak_label(context, "_slevel3", 0x5F82);
weak_label(context, "_slevel3Address", 0x5F84);
weak_label(context, "WORD_RAM_2M", 0x80000);
weak_label(context, "WORD_RAM_1M", 0xC0000);
weak_label(context, "PCM_ENV", 0xFF0001);
weak_label(context, "PCM_PAN", 0xFF0003);
weak_label(context, "PCM_FDL", 0xFF0005);
weak_label(context, "PCM_FDH", 0xFF0007);
weak_label(context, "PCM_LSL", 0xFF0009);
weak_label(context, "PCM_LSH", 0xFF000B);
weak_label(context, "PCM_ST", 0xFF000D);
weak_label(context, "PCM_CTRL", 0xFF000F);
weak_label(context, "PCM_CHAN_ENABLE", 0xFF0011);
weak_label(context, "LED_CONTROL", 0xFFFF8000);
weak_label(context, "VERSION_RESET", 0xFFFF8001);
weak_label(context, "MEM_MODE_WORD", 0xFFFF8002);
weak_label(context, "MEM_MODE_BYTE", 0xFFFF8003);
weak_label(context, "CDC_CTRL", 0xFFFF8004);
weak_label(context, "CDC_AR", 0xFFFF8005);
weak_label(context, "CDC_REG_DATA_WORD", 0xFFFF8006);
weak_label(context, "CDC_REG_DATA", 0xFFFF8007);
weak_label(context, "CDC_HOST_DATA", 0xFFFF8008);
weak_label(context, "CDC_DMA_ADDR", 0xFFFF800A);
weak_label(context, "STOP_WATCH", 0xFFFF800C);
weak_label(context, "COMM_MAIN_FLAG", 0xFFFF800E);
weak_label(context, "COMM_SUB_FLAG", 0xFFFF800F);
weak_label(context, "COMM_CMD0", 0xFFFF8010);
weak_label(context, "COMM_CMD1", 0xFFFF8012);
weak_label(context, "COMM_CMD2", 0xFFFF8014);
weak_label(context, "COMM_CMD3", 0xFFFF8016);
weak_label(context, "COMM_CMD4", 0xFFFF8018);
weak_label(context, "COMM_CMD5", 0xFFFF801A);
weak_label(context, "COMM_CMD6", 0xFFFF801C);
weak_label(context, "COMM_CMD7", 0xFFFF801E);
weak_label(context, "COMM_STATUS0", 0xFFFF8020);
weak_label(context, "COMM_STATUS1", 0xFFFF8022);
weak_label(context, "COMM_STATUS2", 0xFFFF8024);
weak_label(context, "COMM_STATUS3", 0xFFFF8026);
weak_label(context, "COMM_STATUS4", 0xFFFF8028);
weak_label(context, "COMM_STATUS5", 0xFFFF802A);
weak_label(context, "COMM_STATUS6", 0xFFFF802C);
weak_label(context, "COMM_STATUS7", 0xFFFF802E);
weak_label(context, "TIMER_WORD", 0xFFFF8030);
weak_label(context, "TIMER", 0xFFFF8031);
weak_label(context, "INT_MASK_WORD", 0xFFFF8032);
weak_label(context, "INT_MASK", 0xFFFF8033);
weak_label(context, "CDD_FADER", 0xFFFF8034);
weak_label(context, "CDD_CTRL_WORD", 0xFFFF8036);
weak_label(context, "CDD_CTRL_BYTE", 0xFFFF8037);
}
void add_upd7823x_labels(disasm_context *context)
{
weak_label(context, "P0", 0xFF00);
weak_label(context, "P1", 0xFF01);
weak_label(context, "P2", 0xFF02);
weak_label(context, "P3", 0xFF03);
weak_label(context, "P4", 0xFF04);
weak_label(context, "P5", 0xFF05);
weak_label(context, "P6", 0xFF06);
weak_label(context, "P7", 0xFF07);
weak_label(context, "P0L", 0xFF0A);
weak_label(context, "P0B", 0xFF0B);
weak_label(context, "RTPC", 0xFF0C);
weak_label(context, "CR00", 0xFF10);
weak_label(context, "CR01", 0xFF12);
weak_label(context, "CR10", 0xFF14);
weak_label(context, "CR20", 0xFF15);
weak_label(context, "CR21", 0xFF16);
weak_label(context, "CR30", 0xFF17);
weak_label(context, "CR02", 0xFF18);
weak_label(context, "CR22", 0xFF1A);
weak_label(context, "CR11", 0xFF1C);
weak_label(context, "PM0", 0xFF20);
weak_label(context, "PM1", 0xFF21);
weak_label(context, "PM3", 0xFF23);
weak_label(context, "PM5", 0xFF25);
weak_label(context, "PM6", 0xFF26);
weak_label(context, "CRC0", 0xFF30);
weak_label(context, "TOC", 0xFF31);
weak_label(context, "CRC1", 0xFF32);
weak_label(context, "CRC2", 0xFF34);
weak_label(context, "PUO", 0xFF40);
weak_label(context, "PMC3", 0xFF43);
weak_label(context, "TM0", 0xFF50);
weak_label(context, "TM1", 0xFF52);
weak_label(context, "TM2", 0xFF54);
weak_label(context, "TM3", 0xFF56);
weak_label(context, "PRM0", 0xFF5C);
weak_label(context, "TMC0", 0xFF5D);
weak_label(context, "PRM1", 0xFF5E);
weak_label(context, "TMC1", 0xFF5F);
weak_label(context, "DACS0", 0xFF60);
weak_label(context, "DACS1", 0xFF61);
weak_label(context, "ADM", 0xFF68);
weak_label(context, "ADCR", 0xFF6A);
weak_label(context, "PWMC", 0xFF70);
weak_label(context, "PWM0", 0xFF72);
weak_label(context, "PWM1", 0xFF74);
weak_label(context, "OSPC", 0xFF7D);
weak_label(context, "CSIM", 0xFF80);
weak_label(context, "SBIC", 0xFF82);
weak_label(context, "SIO", 0xFF86);
weak_label(context, "ASIM", 0xFF88);
weak_label(context, "ASIS", 0xFF8A);
weak_label(context, "RxB", 0xFF8C);
weak_label(context, "TxS", 0xFF8E);
weak_label(context, "BRGC", 0xFF90);
weak_label(context, "STBC", 0xFFC0);
weak_label(context, "MM", 0xFFC4);
weak_label(context, "PW", 0xFFC5);
weak_label(context, "RFM", 0xFFC6);
weak_label(context, "IMS", 0xFFCF);
weak_label(context, "IF0L", 0xFFE0);
weak_label(context, "IF0H", 0xFFE1);
weak_label(context, "MK0L", 0xFFE4);
weak_label(context, "MK0H", 0xFFE5);
weak_label(context, "PR0L", 0xFFE8);
weak_label(context, "PR0H", 0xFFE9);
weak_label(context, "ISM0L", 0xFFEC);
weak_label(context, "ISM0H", 0xFFED);
weak_label(context, "INTM0", 0xFFF4);
weak_label(context, "INTM1", 0xFFF5);
weak_label(context, "IST", 0xFFF8);
}
static void prefixed_label(disasm_context *context, const char *prefix, const char *name, uint32_t address)
{
if (prefix) {
char *tmp = alloc_concat(prefix, name);
add_label(context, tmp, address);
free(tmp);
} else {
add_label(context, name, address);
}
}
void process_sh2_vectors(disasm_context *context, uint16_t *table, const char *prefix, uint32_t offset, uint8_t labels_only)
{
static const char* names[] = {
"illegal_instruction",
NULL,
"slot_illegal_instruction",
NULL,
"cpu_address_error",
"dma_address_error",
"nmi",
"user_break"
};
uint32_t address = table[0] << 16 | table[1];
prefixed_label(context, prefix, "power_on_reset", address);
if (!labels_only) {
defer_disasm(context, address);
}
address = table[4] << 16 | table[5];
prefixed_label(context, prefix, "manual_reset", address);
if (!labels_only) {
defer_disasm(context, address);
}
for (int i = 0; i < sizeof(names)/sizeof(*names); i++)
{
if (!names[i]) {
continue;
}
address = table[i*2+8] << 16 | table[i*2 + 9];
prefixed_label(context, prefix, names[i], address);
if (!labels_only) {
defer_disasm(context, address);
}
}
if (!labels_only) {
visit(context, offset);
label_def *def = reference(context, offset);
def->data_count = 13;
def->data_size = 4;
def->is_pointer = 1;
}
char trap_name[] = "trap_00";
for (int i = 0; i < 32; i++)
{
if (i < 0x10) {
trap_name[5] = i < 0xA ? '0' + i : 'a' + i - 0xA;
trap_name[6] = 0;
} else {
char c = i >> 4;
trap_name[5] = c < 0xA ? '0' + c : 'a' + c - 0xA;
c = i & 0xF;
trap_name[6] = c < 0xA ? '0' + c : 'a' + c - 0xA;
}
address = table[i*2+64] << 16 | table[i*2 + 65];
prefixed_label(context, prefix, trap_name, address);
if (!labels_only) {
defer_disasm(context, address);
}
}
if (!labels_only) {
visit(context, offset + 0x80);
label_def *def = reference(context, offset + 0x80);
def->data_count = 32;
def->data_size = 4;
def->is_pointer = 1;
}
char int_name[] = "irl_1_0";
for (int i = 0; i < 8; i++)
{
if (i) {
int num = i * 2;
int_name[4] = num < 0xA ? '0' + num : 'A' + num - 0xA;
int_name[5] = '_';
num++;
int_name[6] = num < 0xA ? '0' + num : 'A' + num - 0xA;
} else {
int_name[5] = 0;
}
address = table[i*2+128] << 16 | table[i*2 + 129];
prefixed_label(context, prefix, int_name, address);
if (!labels_only) {
defer_disasm(context, address);
}
}
if (!labels_only) {
visit(context, offset + 0x100);
label_def *def = reference(context, offset + 0x100);
def->data_count = 8;
def->data_size = 4;
def->is_pointer = 1;
}
}
disasm_context *create_68000_disasm(void)
{
disasm_context *context = calloc(1, sizeof(disasm_context));
context->address_mask = 0xFFFFFF;
context->invalid_inst_addr_mask = 1;
context->visit_preshift = 1;
return context;
}
disasm_context *create_z80_disasm(void)
{
disasm_context *context = calloc(1, sizeof(disasm_context));
context->address_mask = 0xFFFF;
context->invalid_inst_addr_mask = 0;
context->visit_preshift = 0;
return context;
}
disasm_context *create_upd78k2_disasm(void)
{
disasm_context *context = calloc(1, sizeof(disasm_context));
context->address_mask = 0xFFFF;
context->invalid_inst_addr_mask = 0;
context->visit_preshift = 0;
return context;
}
disasm_context *create_sh2_disasm(void)
{
disasm_context *context = calloc(1, sizeof(disasm_context));
context->address_mask = 0x7FFFFFF;
context->invalid_inst_addr_mask = 1;
context->visit_preshift = 1;
return context;
}
+53
View File
@@ -0,0 +1,53 @@
#ifndef DISASM_H_
#define DISASM_H_
#include <stdint.h>
#include "tern.h"
#include "backend.h"
typedef struct {
char **labels;
uint32_t num_labels;
uint32_t storage;
uint32_t full_address;
uint32_t data_count;
uint8_t referenced;
uint8_t data_size;
uint8_t is_pointer;
} label_def;
typedef struct {
tern_node *labels;
tern_node *labels_by_name;
uint8_t *visited;
deferred_addr *deferred;
uint32_t address_mask;
uint32_t invalid_inst_addr_mask;
uint32_t visit_preshift;
} disasm_context;
typedef int (*format_label_fun)(char * dst, uint32_t address, disasm_context * context);
label_def *find_label(disasm_context *context, uint32_t address);
label_def *find_label_by_name(disasm_context *context, const char *name);
int format_label(char *dst, uint32_t address, disasm_context *context);
void name_label(disasm_context *context, label_def *def, const char *name);
label_def *weak_label(disasm_context *context, const char *name, uint32_t address);
label_def *reference(disasm_context *context, uint32_t address);
label_def *add_label(disasm_context *context, const char *name, uint32_t address);
void visit(disasm_context *context, uint32_t address);
uint8_t is_visited(disasm_context *context, uint32_t address);
void defer_disasm_label(disasm_context *context, uint32_t address, label_def *label);
void defer_disasm(disasm_context *context, uint32_t address);
void process_address_def(disasm_context *context, char *def);
void process_m68k_vectors(disasm_context *context, uint16_t *table, uint8_t labels_only);
void process_sh2_vectors(disasm_context *context, uint16_t *table, const char *prefix, uint32_t offset, uint8_t labels_only);
void add_segacd_maincpu_labels(disasm_context *context);
void add_segacd_subcpu_labels(disasm_context *context);
disasm_context *create_68000_disasm(void);
disasm_context *create_z80_disasm(void);
disasm_context *create_upd78k2_disasm(void);
disasm_context *create_sh2_disasm(void);
void add_upd7823x_labels(disasm_context *context);
#endif //DISASM_H_
Loaded 100 of 298 files, more files were not shown because too many files have changed in this diff. Show more