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.
No files matched your search
@@ -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
|
||||
|
||||
@@ -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_
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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_
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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.
|
||||
@@ -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>
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
../../..
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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>
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../default.cfg
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../images
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../rom.db
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../shaders
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
../../../jni/SDL/android-project/app/src/main/java/org
|
||||
|
After Width: | Height: | Size: 834 B |
|
After Width: | Height: | Size: 755 B |
|
After Width: | Height: | Size: 897 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
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>
|
||||
@@ -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 +0,0 @@
|
||||
../../images
|
||||
@@ -1 +0,0 @@
|
||||
../../menu.bin
|
||||
@@ -1 +0,0 @@
|
||||
../../rom.db
|
||||
@@ -1 +0,0 @@
|
||||
../../shaders
|
||||
@@ -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
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 "$@"
|
||||
@@ -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
|
||||
@@ -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 +0,0 @@
|
||||
../..
|
||||
@@ -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 *;
|
||||
#}
|
||||
@@ -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
|
||||
|
Before Width: | Height: | Size: 306 B |
|
Before Width: | Height: | Size: 240 B |
|
Before Width: | Height: | Size: 368 B |
|
Before Width: | Height: | Size: 484 B |
@@ -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>
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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_
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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_
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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=$?
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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_
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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_
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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_
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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_
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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_
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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_
|
||||
@@ -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('-----------------------------')
|
||||
@@ -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
|
||||
}
|
||||
@@ -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_
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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_
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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_
|
||||