Compare commits

..

12 commits

Author SHA1 Message Date
f868c8669f Enable absolute mouse (todo: add flag to turn it off)
&& fix(?) a crash if mouse events were delivered too early
2025-08-05 09:15:20 -05:00
431e905354 standalone patcher program 2025-03-27 11:42:43 -05:00
ce55830a1b disable via T2 irq for now
it broke more things than it solved
2025-03-22 12:32:53 -05:00
47b254f58e remove duplicate 'via_assess_irq' lines 2025-03-22 12:32:44 -05:00
697c8859c6 Merge remote-tracking branch 'origin/main' into soundemu 2025-03-22 10:24:07 -05:00
f8274158d4 debug prints must die 2025-03-22 10:13:26 -05:00
7b8b26f0e0 Start implementing via "timer 2"
This makes clicking "show notes" in the "Addresses" stack of
HyperCardBootSystem7.img succeed.

It also allows `play "Boing"` to work, but not `play "Boing" "a c e g"`.

It also makes running on RP2350 really, really slow.

It also makes glider come up complaining of a missing math co-processor(!?)
2025-03-22 10:04:44 -05:00
bc6529ea4b Turbocharge mouse movement
.. by storing directly into the vars used by the mac plus ROM,
rather than working through the IRQ & I/O subsystems.
2025-03-20 21:19:59 -05:00
e2a74064a0 Fix sound buffer address in BootBeep 2025-03-20 13:44:59 -05:00
4dea595da2 overlay audio data on screen 2025-03-20 13:09:07 -05:00
0be2290d4a audio: Make the last byte of the audio buffer a trap address
at that point, there should be 370 contiguous, sensible samples
available, so let the front end grab them.

Sadly this adds a test to the "store byte in RAM" fast path, but it seems
to be unavoidable.

In the unix frontend, vsync is now presented based on the SDL audio
callback when enabled, instead of gettimeofday.
2025-03-19 13:31:38 -05:00
c5ac98dffb Minimal audio implementation
This is good enough to play the system beep (which is at a multiple of
the refresh rate, so every buffer contains an integer number of cycles
of the tone)

However, because the audio buffer filling is not synchronized with
vertical retrace, it's probably not good enough for playing other
kinds of audio.
2025-03-19 11:14:10 -05:00
10 changed files with 495 additions and 224 deletions

View file

@ -27,6 +27,7 @@
DEBUG ?= 0
MEMSIZE ?= 128
ENABLE_AUDIO ?= 1
SOURCES = $(wildcard src/*.c)
@ -54,9 +55,12 @@ endif
# Basic support for changing screen res (with the MacPlusV3 ROM)
DISP_WIDTH ?= 512
DISP_HEIGHT ?= 342
CFLAGS_CFG = -DDISP_WIDTH=$(DISP_WIDTH) -DDISP_HEIGHT=$(DISP_HEIGHT)
CFLAGS_CFG = -DDISP_WIDTH=$(DISP_WIDTH) -DDISP_HEIGHT=$(DISP_HEIGHT) -DENABLE_AUDIO=$(ENABLE_AUDIO)
all: main
all: main patcher
patcher: src/rom.c
$(CC) $(CFLAGS) -DUMAC_STANDALONE_PATCHER -o $@ $<
$(MUSASHI_SRC): $(MUSASHI)/m68kops.h
@ -74,7 +78,7 @@ main: $(OBJS)
clean:
make -C $(MUSASHI) clean
rm -f $(MY_OBJS) main
rm -f $(MY_OBJS) main patcher
################################################################################
# Mac driver sources (no need to generally rebuild

View file

@ -81,6 +81,8 @@ extern int overlay;
* But, that should never happen post-boot.
*/
#define CLAMP_RAM_ADDR(x) ((x) >= RAM_SIZE ? (x) % RAM_SIZE : (x))
/* Is the address (previously clamped with CLAMP_RAM_ADDR) the audio trap address? (last byte of audio data) */
#define IS_RAM_AUDIO_TRAP(x) (x == umac_get_audio_offset() + 2 * 369)
#define IS_VIA(x) ((ADR24(x) & 0xe80000) == 0xe80000)
#define IS_IWM(x) ((ADR24(x) >= 0xdfe1ff) && (ADR24(x) < (0xdfe1ff + 0x2000)))

View file

@ -35,6 +35,8 @@ void scc_write(unsigned int address, uint8_t data);
uint8_t scc_read(unsigned int address);
/* Set a new state for the DCD pins: */
void scc_set_dcd(int a, int b);
/* check if scc master interrupt is enabled */
int scc_get_mie();
#endif

View file

@ -34,6 +34,7 @@ int umac_loop(void);
void umac_reset(void);
void umac_opt_disassemble(int enable);
void umac_mouse(int deltax, int deltay, int button);
void umac_absmouse(int x, int y, int button);
void umac_kbd_event(uint8_t scancode, int down);
static inline void umac_vsync_event(void)
@ -53,4 +54,15 @@ static inline unsigned int umac_get_fb_offset(void)
return RAM_SIZE - ((DISP_WIDTH * DISP_HEIGHT / 8) + 0x380);
}
#if ENABLE_AUDIO
#define umac_get_audio_offset() (RAM_SIZE - 768)
#define umac_get_audio_offset_end() (RAM_SIZE - 768 + 2 * 370)
extern unsigned first_audio_sample;
#define umac_get_first_audio_sample() (first_audio_sample)
#define umac_reset_first_audio_sample() (first_audio_sample = 0, (void)0)
void umac_audio_trap();
void umac_audio_cfg(int volume, int sndres);
#endif
#endif

View file

@ -40,7 +40,8 @@ struct via_cb {
void via_init(struct via_cb *cb);
void via_write(unsigned int address, uint8_t data);
uint8_t via_read(unsigned int address);
void via_tick(uint64_t time);
void via_tick(int cycles);
int via_limit_cycles(int cycles);
/* Trigger an event on CA1 or CA2: */
void via_caX_event(int ca);
void via_sr_rx(uint8_t val);

View file

@ -40,6 +40,7 @@
#include <errno.h>
#include <setjmp.h>
#include "umac.h"
#include "machw.h"
#include "m68k.h"
#include "via.h"
@ -70,8 +71,11 @@ static unsigned int g_int_controller_highest_int = 0; /* Highest pending interr
uint8_t *_ram_base;
uint8_t *_rom_base;
#if ENABLE_AUDIO
static int umac_volume, umac_sndres;
#endif
int overlay = 1;
static uint64_t global_time_us = 0;
static uint64_t global_time_us = 0, global_cycles = 0;
static int sim_done = 0;
static jmp_buf main_loop_jb;
@ -154,8 +158,14 @@ static void via_ra_changed(uint8_t val)
MDBG("OVERLAY CHANGING\n");
update_overlay_layout();
}
#if ENABLE_AUDIO
uint8_t vol = val & 7;
if (vol != umac_volume) {
umac_volume = val & 7;
umac_audio_cfg(umac_volume, umac_sndres);
}
oldval = val;
#endif
}
static void via_rb_changed(uint8_t val)
@ -166,7 +176,13 @@ static void via_rb_changed(uint8_t val)
// 4 = mouse4 (in, mouse X2)
// 3 = mouse7 (in, 0 = button pressed)
// [2:0] = RTC controls
(void)val;
#if ENABLE_AUDIO
uint8_t sndres = val >> 7;
if(sndres != umac_sndres) {
umac_sndres = sndres;
umac_audio_cfg(umac_volume, umac_sndres);
}
#endif
}
static uint8_t via_ra_in(void)
@ -437,7 +453,13 @@ unsigned int cpu_read_long_dasm(unsigned int address)
void FAST_FUNC(cpu_write_byte)(unsigned int address, unsigned int value)
{
if (IS_RAM(address)) {
RAM_WR8(CLAMP_RAM_ADDR(address), value);
address = CLAMP_RAM_ADDR(address);
RAM_WR8(address, value);
#if ENABLE_AUDIO
if(IS_RAM_AUDIO_TRAP(address)) {
umac_audio_trap();
}
#endif
return;
}
@ -592,10 +614,35 @@ void umac_opt_disassemble(int enable)
disassemble = enable;
}
#define MOUSE_MAX_PENDING_PIX 30
/* Provide mouse input (movement, button) data.
*
* X is positive going right; Y is positive going upwards.
*/
void umac_absmouse(int x, int y, int button)
{
if (!scc_get_mie()) return;
#define MTemp_h 0x82a
#define MTemp_v 0x828
#define CrsrNew 0x8ce
#define CrsrCouple 0x8cf
static int pending_mouse_deltax = 0;
static int pending_mouse_deltay = 0;
int oldx = RAM_RD16(MTemp_h);
int oldy = RAM_RD16(MTemp_v);
if(x != oldx) {
RAM_WR16(MTemp_h, x);
}
if (y != oldy) {
RAM_WR16(MTemp_v, y);
}
if(x != oldx || y != oldy) {
RAM_WR8(CrsrNew, RAM_RD8(CrsrCouple));
}
via_mouse_pressed = button;
}
/* Provide mouse input (movement, button) data.
*
@ -603,83 +650,22 @@ static int pending_mouse_deltay = 0;
*/
void umac_mouse(int deltax, int deltay, int button)
{
pending_mouse_deltax += deltax;
pending_mouse_deltay += deltay;
/* Clamp if the UI has flooded with lots and lots of steps!
*/
if (pending_mouse_deltax > MOUSE_MAX_PENDING_PIX)
pending_mouse_deltax = MOUSE_MAX_PENDING_PIX;
if (pending_mouse_deltax < -MOUSE_MAX_PENDING_PIX)
pending_mouse_deltax = -MOUSE_MAX_PENDING_PIX;
if (pending_mouse_deltay > MOUSE_MAX_PENDING_PIX)
pending_mouse_deltay = MOUSE_MAX_PENDING_PIX;
if (pending_mouse_deltay < -MOUSE_MAX_PENDING_PIX)
pending_mouse_deltay = -MOUSE_MAX_PENDING_PIX;
/* FIXME: The movement might take a little time, but this
* posts the button status immediately. Probably OK, but the
* mismatch might be perceptible.
*/
via_mouse_pressed = button;
}
static void mouse_tick(void)
{
/* Periodically, check if the mouse X/Y deltas are non-zero.
* If a movement is required, encode one step in X and/or Y
* and deduct from the pending delta.
*
* The step ultimately posts an SCC IRQ, so we _don't_ try to
* make any more steps while an IRQ is currently pending.
* (Currently, that means a previous step's DCD IRQ event
* hasn't yet been consumed by the OS handler. In future, if
* SCC is extended with other IRQ types, then just checking
* the IRQ status is technically too crude, but should still
* be fine given the timeframes.)
*/
if (pending_mouse_deltax == 0 && pending_mouse_deltay == 0)
return;
if (scc_irq_state == 1)
return;
static int old_dcd_a = 0;
static int old_dcd_b = 0;
/* Mouse X/Y quadrature signals are wired to:
* VIA Port B[4] & SCC DCD_A for X
* VIA Port B[5] & SCC DCD_B for Y
*
* As VIA mouse signals aren't sampled until IRQ, can do this
* in one step, toggling existing DCD states and setting VIA
* either equal or opposite to DCD:
*/
int dcd_a = old_dcd_a;
int dcd_b = old_dcd_b;
int deltax = pending_mouse_deltax;
int deltay = pending_mouse_deltay;
uint8_t qb = via_quadbits;
if (deltax) {
dcd_a = !dcd_a;
qb = (qb & ~0x10) | ((deltax < 0) == dcd_a ? 0x10 : 0);
pending_mouse_deltax += (deltax > 0) ? -1 : 1;
MDBG(" px %d, oldpx %d", pending_mouse_deltax, deltax);
if (!scc_get_mie()) return;
if(deltax) {
int16_t temp_h = RAM_RD16(MTemp_h) + deltax;
RAM_WR16(MTemp_h, temp_h);
}
if (deltay) {
dcd_b = !dcd_b;
qb = (qb & ~0x20) | ((deltay < 0) == dcd_b ? 0x20 : 0);
pending_mouse_deltay += (deltay > 0) ? -1 : 1;
MDBG(" py %d, oldpy %d", pending_mouse_deltay, deltay);
int16_t temp_v = RAM_RD16(MTemp_v) - deltay;
RAM_WR16(MTemp_v, temp_v);
}
MDBG("\n");
via_quadbits = qb;
old_dcd_a = dcd_a;
old_dcd_b = dcd_b;
scc_set_dcd(dcd_a, dcd_b);
if(deltax || deltay) {
RAM_WR8(CrsrNew, RAM_RD8(CrsrCouple));
}
via_mouse_pressed = button;
}
void umac_reset(void)
@ -705,13 +691,15 @@ int umac_loop(void)
{
setjmp(main_loop_jb);
const int us = UMAC_EXECLOOP_QUANTUM;
m68k_execute(us*8);
global_time_us += us;
int cycles = UMAC_EXECLOOP_QUANTUM * 8;
cycles = via_limit_cycles(cycles);
int used_cycles = m68k_execute(cycles);
MDBG("Asked to execute %d cycles, actual %d cycles\n", cycles, used_cycles);
global_cycles += used_cycles;
global_time_us = global_cycles / 8;
// Device polling
via_tick(global_time_us);
mouse_tick();
via_tick(used_cycles);
kbd_check_work();
return sim_done;

352
src/rom.c
View file

@ -80,7 +80,7 @@ static uint32_t rom_get_version(uint8_t *rom_base)
} while (0)
static void rom_patch_plusv3(uint8_t *rom_base)
static int rom_patch_plusv3(uint8_t *rom_base, int disp_width, int disp_height, int ram_size)
{
/* Inspired by patches in BasiliskII!
*/
@ -100,138 +100,143 @@ static void rom_patch_plusv3(uint8_t *rom_base)
* - No IWM init
* - new Sound?
*/
#if UMAC_MEMSIZE > 128 && UMAC_MEMSIZE < 512
/* Hack to change memtop: try out a 256K Mac :) */
for (int i = 0x376; i < 0x37e; i += 2)
ROM_WR16(i, M68K_INST_NOP);
ROM_WR16(0x376, 0x2a7c); // moveal #RAM_SIZE, A5
ROM_WR16(0x378, RAM_SIZE >> 16);
ROM_WR16(0x37a, RAM_SIZE & 0xffff);
/* That overrides the probed memory size, but
* P_ChecksumRomAndTestMemory returns a failure code for
* things that aren't 128/512. Skip that:
*/
ROM_WR16(0x132, 0x6000); // Bra (was BEQ)
/* FIXME: We should also remove the memory probe routine, by
* allowing the ROM checksum to fail (it returns failure, then
* we carry on). This avoids wild RAM addrs being accessed.
*/
#endif
if (ram_size > 128*1024 && ram_size < 512*1024) {
/* Hack to change memtop: try out a 256K Mac :) */
for (int i = 0x376; i < 0x37e; i += 2)
ROM_WR16(i, M68K_INST_NOP);
ROM_WR16(0x376, 0x2a7c); // moveal #ram_size, A5
ROM_WR16(0x378, ram_size >> 16);
ROM_WR16(0x37a, ram_size & 0xffff);
/* That overrides the probed memory size, but
* P_ChecksumRomAndTestMemory returns a failure code for
* things that aren't 128/512. Skip that:
*/
ROM_WR16(0x132, 0x6000); // Bra (was BEQ)
/* FIXME: We should also remove the memory probe routine, by
* allowing the ROM checksum to fail (it returns failure, then
* we carry on). This avoids wild RAM addrs being accessed.
*/
#if DISP_WIDTH != 512 || DISP_HEIGHT != 342
#define SCREEN_SIZE (DISP_WIDTH*DISP_HEIGHT/8)
#define SCREEN_DISTANCE_FROM_TOP (SCREEN_SIZE + 0x380)
#if (SCREEN_DISTANCE_FROM_TOP >= 65536)
#error "rom.c: Screen res patching maths won't work for a screen this large"
#endif
#define SCREEN_BASE (0x400000-SCREEN_DISTANCE_FROM_TOP)
#define SCREEN_BASE_L16 (SCREEN_BASE & 0xffff)
#define SBCOORD(x, y) (SCREEN_BASE + ((DISP_WIDTH/8)*(y)) + ((x)/8))
/* Fix up the sound buffer as used by BootBeep */
ROM_WR32(0x292, ram_size - 768);
}
/* Changing video res:
*
* Original 512*342 framebuffer is 0x5580 bytes; the screen
* buffer lands underneath sound/other buffers at top of mem,
* i,e, 0x3fa700 = 0x400000-0x5580-0x380. So any new buffer
* will be placed (and read out from for the GUI) at
* MEM_TOP-0x380-SCREEN_SIZE.
*
* For VGA, size is 0x9600 bytes (0x2580 words)
*/
if(disp_width != 512 || disp_height != 342) {
int screen_size = (disp_width*disp_height/8);
int screen_distance_from_top = screen_size + 0x380;
if (screen_distance_from_top >= 65536) {
RERR("rom.c: screen res patching maths won't work for a screen this large");
return -1;
}
int screen_base = 0x400000-screen_distance_from_top;
int screen_base_l16 = screen_base & 0xffff;
#define SBCOORD(x, y) (screen_base + ((disp_width/8)*(y)) + ((x)/8))
/* We need some space, low down, to create jump-out-and-patch
* routines where a patch is too large to put inline. The
* TestSoftware check at 0x42 isn't used:
*/
ROM_WR16(0x42, 0x6000); /* bra */
ROM_WR16(0x44, 0x62-0x44); /* offset */
/* Now 0x46-0x57 can be used */
unsigned int patch_0 = 0x46;
ROM_WR16(patch_0 + 0, 0x9bfc); /* suba.l #imm32, A5 */
ROM_WR16(patch_0 + 2, 0); /* (Could add more here) */
ROM_WR16(patch_0 + 4, SCREEN_DISTANCE_FROM_TOP);
ROM_WR16(patch_0 + 6, 0x6000); /* bra */
ROM_WR16(patch_0 + 8, 0x3a4 - (patch_0 + 8)); /* Return to 3a4 */
/* Changing video res:
*
* Original 512*342 framebuffer is 0x5580 bytes; the screen
* buffer lands underneath sound/other buffers at top of mem,
* i,e, 0x3fa700 = 0x400000-0x5580-0x380. So any new buffer
* will be placed (and read out from for the GUI) at
* MEM_TOP-0x380-screen_size.
*
* For VGA, size is 0x9600 bytes (0x2580 words)
*/
/* Magic screen-related locations in Mac Plus ROM 4d1f8172:
*
* 8c : screen base addr (usually 3fa700, now 3f6680)
* 148 : screen base addr again
* 164 : u32 screen address of crash Mac/critErr hex numbers
* 188 : u16 bytes per row (critErr)
* 194 : u16 bytes per row (critErr)
* 19c : u16 (bytes per row * 6)-1 (critErr)
* 1a4 : u32 screen address of critErr twiddly pattern
* 1ee : u16 screen sie in words minus one
* 3a2 : u16 screen size in bytes (BUT can't patch immediate)
* 474 : u16 bytes per row
* 494 : u16 screen y
* 498 : u16 screen x
* a0e : y
* a10 : x
* ee2 : u16 bytes per row minus 4 (tPutIcon)
* ef2 : u16 bytes per row (tPutIcon)
* 7e0 : u32 screen address of disk icon (240, 145)
* 7f2 : u32 screen address of disk icon's symbol (248, 160)
* f0c : u32 screen address of Mac icon (240, 145)
* f18 : u32 screen address of Mac icon's face (248, 151)
* f36 : u16 bytes per row minus 2 (mPutSymbol)
* 1cd1 : hidecursor's bytes per line
* 1d48 : xres minus 32 (for cursor rect clipping)
* 1d4e : xres minus 32
* 1d74 : y
* 1d93 : bytes per line (showcursor)
* 1e68 : y
* 1e6e : x
* 1e82 : y
*/
ROM_WR16(0x8c, SCREEN_BASE_L16);
ROM_WR16(0x148, SCREEN_BASE_L16);
ROM_WR32(0x164, SBCOORD(DISP_WIDTH/2 - (48/2), DISP_HEIGHT/2 + 8));
ROM_WR16(0x188, DISP_WIDTH/8);
ROM_WR16(0x194, DISP_WIDTH/8);
ROM_WR16(0x19c, (6*DISP_WIDTH/8)-1);
ROM_WR32(0x1a4, SBCOORD(DISP_WIDTH/2 - 8, DISP_HEIGHT/2 + 8 + 8));
ROM_WR16(0x1ee, (SCREEN_SIZE/4)-1);
/* We need some space, low down, to create jump-out-and-patch
* routines where a patch is too large to put inline. The
* TestSoftware check at 0x42 isn't used:
*/
ROM_WR16(0x42, 0x6000); /* bra */
ROM_WR16(0x44, 0x62-0x44); /* offset */
/* Now 0x46-0x57 can be used */
unsigned int patch_0 = 0x46;
ROM_WR16(patch_0 + 0, 0x9bfc); /* suba.l #imm32, A5 */
ROM_WR16(patch_0 + 2, 0); /* (Could add more here) */
ROM_WR16(patch_0 + 4, screen_distance_from_top);
ROM_WR16(patch_0 + 6, 0x6000); /* bra */
ROM_WR16(patch_0 + 8, 0x3a4 - (patch_0 + 8)); /* Return to 3a4 */
ROM_WR32(0xf0c, SBCOORD(DISP_WIDTH/2 - 16, DISP_HEIGHT/2 - 26));
ROM_WR32(0xf18, SBCOORD(DISP_WIDTH/2 - 8, DISP_HEIGHT/2 - 20));
ROM_WR32(0x7e0, SBCOORD(DISP_WIDTH/2 - 16, DISP_HEIGHT/2 - 26));
ROM_WR32(0x7f2, SBCOORD(DISP_WIDTH/2 - 8, DISP_HEIGHT/2 - 11));
/* Magic screen-related locations in Mac Plus ROM 4d1f8172:
*
* 8c : screen base addr (usually 3fa700, now 3f6680)
* 148 : screen base addr again
* 164 : u32 screen address of crash Mac/critErr hex numbers
* 188 : u16 bytes per row (critErr)
* 194 : u16 bytes per row (critErr)
* 19c : u16 (bytes per row * 6)-1 (critErr)
* 1a4 : u32 screen address of critErr twiddly pattern
* 1ee : u16 screen sie in words minus one
* 3a2 : u16 screen size in bytes (BUT can't patch immediate)
* 474 : u16 bytes per row
* 494 : u16 screen y
* 498 : u16 screen x
* a0e : y
* a10 : x
* ee2 : u16 bytes per row minus 4 (tPutIcon)
* ef2 : u16 bytes per row (tPutIcon)
* 7e0 : u32 screen address of disk icon (240, 145)
* 7f2 : u32 screen address of disk icon's symbol (248, 160)
* f0c : u32 screen address of Mac icon (240, 145)
* f18 : u32 screen address of Mac icon's face (248, 151)
* f36 : u16 bytes per row minus 2 (mPutSymbol)
* 1cd1 : hidecursor's bytes per line
* 1d48 : xres minus 32 (for cursor rect clipping)
* 1d4e : xres minus 32
* 1d74 : y
* 1d93 : bytes per line (showcursor)
* 1e68 : y
* 1e6e : x
* 1e82 : y
*/
ROM_WR16(0x8c, screen_base_l16);
ROM_WR16(0x148, screen_base_l16);
ROM_WR32(0x164, SBCOORD(disp_width/2 - (48/2), disp_height/2 + 8));
ROM_WR16(0x188, disp_width/8);
ROM_WR16(0x194, disp_width/8);
ROM_WR16(0x19c, (6*disp_width/8)-1);
ROM_WR32(0x1a4, SBCOORD(disp_width/2 - 8, disp_height/2 + 8 + 8));
ROM_WR16(0x1ee, (screen_size/4)-1);
/* Patch "SubA #$5900, A5" to subtract 0x9880.
* However... can't just patch the int16 immediate, as that's
* sign-extended (and we end up with a subtract-negative,
* i.e. an add). There isn't space here to turn it into sub.l
* so add some rigamarole to branch to some bytes stolen at
* patch_0 up above.
*/
ROM_WR16(0x3a0, 0x6000); /* bra */
ROM_WR16(0x3a2, patch_0 - 0x3a2); /* ...to patch0, returns at 0x3a4 */
ROM_WR32(0xf0c, SBCOORD(disp_width/2 - 16, disp_height/2 - 26));
ROM_WR32(0xf18, SBCOORD(disp_width/2 - 8, disp_height/2 - 20));
ROM_WR32(0x7e0, SBCOORD(disp_width/2 - 16, disp_height/2 - 26));
ROM_WR32(0x7f2, SBCOORD(disp_width/2 - 8, disp_height/2 - 11));
ROM_WR16(0x474, DISP_WIDTH/8);
ROM_WR16(0x494, DISP_HEIGHT);
ROM_WR16(0x498, DISP_WIDTH);
ROM_WR16(0xa0e, DISP_HEIGHT); /* copybits? */
ROM_WR16(0xa10, DISP_WIDTH);
ROM_WR16(0xee2, (DISP_WIDTH/8)-4); /* tPutIcon bytes per row, minus 4 */
ROM_WR16(0xef2, DISP_WIDTH/8); /* tPutIcon bytes per row */
ROM_WR16(0xf36, (DISP_WIDTH/8)-2); /* tPutIcon bytes per row, minus 2 */
ROM_WR8(0x1cd1, DISP_WIDTH/8); /* hidecursor */
ROM_WR16(0x1d48, DISP_WIDTH-32); /* 1d46+2 was originally 512-32 rite? */
ROM_WR16(0x1d4e, DISP_WIDTH-32); /* 1d4c+2 is 480, same */
ROM_WR16(0x1d6e, DISP_HEIGHT-16); /* showcursor (YESS fixed Y crash bug!) */
ROM_WR16(0x1d74, DISP_HEIGHT); /* showcursor */
ROM_WR8(0x1d93, DISP_WIDTH/8); /* showcursor */
ROM_WR16(0x1e68, DISP_HEIGHT); /* mScrnSize */
ROM_WR16(0x1e6e, DISP_WIDTH); /* mScrnSize */
ROM_WR16(0x1e82, DISP_HEIGHT); /* tScrnBitMap */
/* Patch "SubA #$5900, A5" to subtract 0x9880.
* However... can't just patch the int16 immediate, as that's
* sign-extended (and we end up with a subtract-negative,
* i.e. an add). There isn't space here to turn it into sub.l
* so add some rigamarole to branch to some bytes stolen at
* patch_0 up above.
*/
ROM_WR16(0x3a0, 0x6000); /* bra */
ROM_WR16(0x3a2, patch_0 - 0x3a2); /* ...to patch0, returns at 0x3a4 */
ROM_WR16(0x474, disp_width/8);
ROM_WR16(0x494, disp_height);
ROM_WR16(0x498, disp_width);
ROM_WR16(0xa0e, disp_height); /* copybits? */
ROM_WR16(0xa10, disp_width);
ROM_WR16(0xee2, (disp_width/8)-4); /* tPutIcon bytes per row, minus 4 */
ROM_WR16(0xef2, disp_width/8); /* tPutIcon bytes per row */
ROM_WR16(0xf36, (disp_width/8)-2); /* tPutIcon bytes per row, minus 2 */
ROM_WR8(0x1cd1, disp_width/8); /* hidecursor */
ROM_WR16(0x1d48, disp_width-32); /* 1d46+2 was originally 512-32 rite? */
ROM_WR16(0x1d4e, disp_width-32); /* 1d4c+2 is 480, same */
ROM_WR16(0x1d6e, disp_height-16); /* showcursor (YESS fixed Y crash bug!) */
ROM_WR16(0x1d74, disp_height); /* showcursor */
ROM_WR8(0x1d93, disp_width/8); /* showcursor */
ROM_WR16(0x1e68, disp_height); /* mScrnSize */
ROM_WR16(0x1e6e, disp_width); /* mScrnSize */
ROM_WR16(0x1e82, disp_height); /* tScrnBitMap */
}
/* FIXME: Welcome To Macintosh is drawn at the wrong position. Find where that's done. */
#endif
return 0;
}
int rom_patch(uint8_t *rom_base)
static int rom_patch1(uint8_t *rom_base, int disp_width, int disp_height, int mem_size)
{
uint32_t v = rom_get_version(rom_base);
int r = -1;
@ -239,8 +244,7 @@ int rom_patch(uint8_t *rom_base)
*/
switch(v) {
case ROM_PLUSv3_VERSION:
rom_patch_plusv3(rom_base);
r = 0;
r = rom_patch_plusv3(rom_base, disp_width, disp_height, mem_size);
break;
default:
@ -250,3 +254,103 @@ int rom_patch(uint8_t *rom_base)
return r;
}
#if defined(UMAC_STANDALONE_PATCHER)
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char **argv) {
uint8_t *rom_base;
char *rom_filename = "4D1F8172 - MacPlus v3.ROM";
char *rom_dump_filename = NULL; // Raw bytes
char *rom_header_filename = NULL; // C header
int ch;
int disp_width = 512;
int disp_height = 342;
int ram_size = 128;
while ((ch = getopt(argc, argv, "vm:r:w:W:")) != -1) {
switch (ch) {
case 'v':
disp_width = 640;
disp_height = 480;
break;
case 'm':
ram_size = atoi(optarg);
break;
case 'r':
rom_filename = strdup(optarg);
break;
case 'W':
rom_dump_filename = strdup(optarg);
break;
case 'w':
rom_header_filename = strdup(optarg);
break;
case '?':
abort();
}
}
if (!rom_dump_filename && !rom_header_filename) {
printf("Must specify either a -W (binary) or -w (C header) output file");
abort();
}
printf("Opening ROM '%s'\n", rom_filename);
int ofd = open(rom_filename, O_RDONLY);
if (ofd < 0) {
perror("ROM");
return 1;
}
struct stat sb;
fstat(ofd, &sb);
off_t _rom_size = sb.st_size;
rom_base = mmap(0, _rom_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, ofd, 0);
if (rom_base == MAP_FAILED) {
printf("Can't mmap ROM!\n");
return 1;
}
if (rom_patch1(rom_base, disp_width, disp_height, ram_size*1024)) {
printf("Failed to patch ROM\n");
return 1;
}
if (rom_dump_filename) {
int rfd = open(rom_dump_filename, O_CREAT | O_TRUNC | O_RDWR, 0655);
if (rfd < 0) {
perror("ROM dump");
return 1;
}
ssize_t written = write(rfd, rom_base, _rom_size);
if (written < 0) {
perror("ROM dump write");
return 1;
}
if (written < _rom_size) {
printf("*** WARNING: Short write to %s!\n",
rom_dump_filename);
} else {
printf("Dumped ROM to %s\n", rom_dump_filename);
}
close(rfd);
}
if (rom_header_filename) {
FILE *ofd = fopen(rom_header_filename, "w");
if (!ofd) { perror("fopen"); abort(); }
for(off_t i=0; i<_rom_size; i++) {
fprintf(ofd, "%d,", rom_base[i]);
if(i % 16 == 15) fprintf(ofd, "\n");
}
fprintf(ofd, "\n");
printf("Dumped ROM to %s as header\n", rom_header_filename);
fclose(ofd);
}
}
#else
int rom_patch(uint8_t *rom_base) {
return rom_patch1(rom_base, DISP_WIDTH, DISP_HEIGHT, UMAC_MEMSIZE * 1024);
}
#endif

View file

@ -126,6 +126,8 @@ static void scc_wr3(int AnB, uint8_t data)
}
}
int scc_get_mie() { return scc_mie; }
// WR9: Master Interrupt control and reset commands
static void scc_wr9(uint8_t data)
{

View file

@ -27,6 +27,7 @@
* SOFTWARE.
*/
#include <assert.h>
#include <stdio.h>
#include <fcntl.h>
#include <getopt.h>
@ -35,6 +36,9 @@
#include <sys/time.h>
#include <string.h>
#include <unistd.h>
#if ENABLE_AUDIO
#include <stdatomic.h>
#endif
#include "SDL.h"
#include "rom.h"
@ -78,6 +82,42 @@ static void copy_fb(uint32_t *fb_out, uint8_t *fb_in)
}
}
#if ENABLE_AUDIO
// audio_callback is called from a thread, so pending_v_retrace can't be a regular variable
atomic_int pending_v_retrace;
static int volscale;
uint8_t *audio_base;
int16_t audio[370];
void umac_audio_cfg(int umac_volume, int umac_sndres) {
volscale = umac_sndres ? 0 : 65536 * umac_volume / 7;
}
void umac_audio_trap() {
int32_t offset = 128;
uint16_t *audiodata = (uint16_t*)audio_base;
int scale = volscale;
if (!scale) {
memset(audio, 0, sizeof(audio));
return;
}
int16_t *stream = audio;
for(int i=0; i<370; i++) {
int32_t a = (*audiodata++ & 0xff) - offset;
a = (a * scale) >> 8;
*stream++ = a;
}
}
static void audio_callback(void *userdata, Uint8 *stream_in, int len_bytes) {
(void) userdata;
assert(len_bytes == sizeof(audio));
atomic_store(&pending_v_retrace, 1);
memcpy(stream_in, audio, sizeof(audio));
}
#endif
/**********************************************************************/
/* The emulator core expects to be given ROM and RAM pointers,
@ -232,7 +272,11 @@ int main(int argc, char *argv[])
SDL_Renderer *renderer;
SDL_Texture *texture;
SDL_Init(SDL_INIT_VIDEO);
SDL_Init(SDL_INIT_VIDEO
#if ENABLE_AUDIO
| SDL_INIT_AUDIO
#endif
);
SDL_Window *window = SDL_CreateWindow("umac",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
@ -243,8 +287,14 @@ int main(int argc, char *argv[])
perror("SDL window");
return 1;
}
SDL_SetWindowGrab(window, SDL_TRUE);
SDL_SetRelativeMouseMode(SDL_TRUE);
static const int absmouse = 1;
if (!absmouse) {
SDL_SetWindowGrab(window, SDL_TRUE);
SDL_SetRelativeMouseMode(SDL_TRUE);
} else {
SDL_ShowCursor(SDL_DISABLE);
}
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
@ -264,25 +314,55 @@ int main(int argc, char *argv[])
return 1;
}
#if ENABLE_AUDIO
SDL_AudioSpec desired, obtained;
audio_base = (uint8_t*)ram_base + umac_get_audio_offset();
SDL_zero(desired);
desired.freq = 22256; // wat
desired.channels = 1;
desired.samples = 370;
desired.userdata = (uint8_t*)ram_base + umac_get_audio_offset();
desired.callback = audio_callback;
desired.format = AUDIO_S16;
SDL_AudioDeviceID audio_device = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, 0);
if (!audio_device) {
char buf[500];
SDL_GetErrorMsg(buf, sizeof(buf));
printf("SDL audio_deviceSDL_GetError() -> %s\n", buf);
return 1;
}
#endif
////////////////////////////////////////////////////////////////////////
// Emulator init
umac_init(ram_base, rom_base, discs);
umac_opt_disassemble(opt_disassemble);
#if ENABLE_AUDIO
// Default state is paused, this unpauses it
SDL_PauseAudioDevice(audio_device, 0);
#endif
////////////////////////////////////////////////////////////////////////
// Main loop
int done = 0;
int mouse_button = 0;
#if !ENABLE_AUDIO
uint64_t last_vsync = 0;
#endif
uint64_t last_1hz = 0;
do {
struct timeval tv_now;
SDL_Event event;
int mousex = 0;
int mousey = 0;
int send_mouse = 0;
static int absmousex, absmousey;
if (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_QUIT:
@ -299,21 +379,32 @@ int main(int argc, char *argv[])
} break;
case SDL_MOUSEMOTION:
send_mouse = 1;
absmousex = event.motion.x / DISP_SCALE;
absmousey = event.motion.y / DISP_SCALE;
mousex = event.motion.xrel;
mousey = -event.motion.yrel;
break;
case SDL_MOUSEBUTTONDOWN:
send_mouse = 1;
mouse_button = 1;
break;
case SDL_MOUSEBUTTONUP:
send_mouse = 1;
mouse_button = 0;
break;
}
}
umac_mouse(mousex, mousey, mouse_button);
if (send_mouse) {
if(absmouse) {
umac_absmouse(absmousex, absmousey, mouse_button);
} else {
umac_mouse(mousex, mousey, mouse_button);
}
}
done |= umac_loop();
@ -321,12 +412,27 @@ int main(int argc, char *argv[])
uint64_t now_usec = (tv_now.tv_sec * 1000000) + tv_now.tv_usec;
/* Passage of time: */
if ((now_usec - last_vsync) >= 16667) {
#if ENABLE_AUDIO
int do_v_retrace = atomic_exchange(&pending_v_retrace, 0);
#else
int do_v_retrace = (now_usec - last_vsync) >= 16667;
#endif
if (do_v_retrace) {
umac_vsync_event();
last_vsync = now_usec;
/* Cheapo framerate limiting: */
copy_fb(framebuffer, ram_get_base() + umac_get_fb_offset());
uint16_t *audioptr = (uint16_t*)((uint8_t*)ram_base + umac_get_audio_offset());
for(int i=0; i<DISP_HEIGHT; i++) {
int d = *audioptr++ & 0xff;
for(int j=0; j<8; j++) {
if (d & (1 << j)) {
uint32_t fbdata = framebuffer[j + i * DISP_WIDTH];
fbdata = (fbdata & ~0xff) | ((d & (1 << j)) ? 0xff : 0);
framebuffer[j + i * DISP_WIDTH] = fbdata;
}
}
}
SDL_UpdateTexture(texture, NULL, framebuffer,
DISP_WIDTH * sizeof (Uint32));
/* Scales texture up to window size */

View file

@ -29,6 +29,7 @@
#include <inttypes.h>
#include <stdio.h>
#include "m68kcpu.h"
#include "via.h"
#ifdef DEBUG
@ -47,7 +48,8 @@
#define VIA_T1CH 5
#define VIA_T1LL 6
#define VIA_T1LH 7
#define VIA_T2CL 8
#define VIA_T2CL 8 // write latch
#define VIA_T2LL 8 // read counter
#define VIA_T2CH 9
#define VIA_SR 10
#define VIA_ACR 11
@ -56,6 +58,7 @@
#define VIA_IRQ_CA 0x01
#define VIA_IRQ_CB 0x02
#define VIA_IRQ_SR 0x04
#define VIA_IRQ_T2 0x20
#define VIA_IER 14
#define VIA_RA_ALT 15 // No-handshake version
@ -68,7 +71,7 @@ static const char *dbg_regnames[] = {
"VIA_T1CH",
"VIA_T1LL",
"VIA_T1LH",
"VIA_T2CL",
"VIA_T2xL",
"VIA_T2CH",
"VIA_SR",
"VIA_ACR",
@ -84,6 +87,8 @@ static int irq_status = 0;
static uint8_t irq_active = 0;
static uint8_t irq_enable = 0;
static uint16_t via_t2c;
void via_init(struct via_cb *cb)
{
for (int i = 0; i < 16; i++)
@ -224,6 +229,24 @@ void via_write(unsigned int address, uint8_t data)
case VIA_PCR:
VDBG("VIA PCR %02x\n", data);
break;
case VIA_ACR:
if(data & 0xe0)
VDBG("VIA ACR %02x\n", data);
break;
case VIA_T2CH:
VDBG("VIA T2CH %02x [ACR=%02x]\n", data, via_regs[VIA_ACR]);
// writing T2CH loads the low 8 bits from the latch
via_t2c = via_regs[VIA_T2CL] | (data << 8);
VDBG("VIA Loaded timer 2 with %d cycles\n", via_t2c);
if(via_t2c > 0 && GET_CYCLES() > via_t2c) {
SET_CYCLES(via_t2c);
}
// writing T2CH clears associated IRQ flag
irq_active &= ~VIA_IRQ_T2;
break;
case VIA_T2CL:
VDBG("VIA T2CL %02x [ACR=%02x]\n", data, via_regs[VIA_ACR]);
break;
default:
VDBG("[VIA: unhandled WR %02x to %s (reg 0x%x)]\n", data, rname, r);
}
@ -282,6 +305,14 @@ uint8_t via_read(unsigned int address)
case VIA_IFR:
data = via_read_ifr();
break;
case VIA_T2LL:
// not right, should be reduced according to cycles since
// quantum began
data = via_t2c & 0xff;
// reading T2LL clears associated IRQ flag
irq_active &= ~VIA_IRQ_T2;
break;
default:
VDBG("[VIA: unhandled RD of %s (reg 0x%x)]\n", rname, r);
}
@ -290,10 +321,29 @@ uint8_t via_read(unsigned int address)
return data;
}
int via_limit_cycles(int cycles_in) {
if (via_t2c > 0 && via_t2c < cycles_in) {
return via_t2c;
}
return cycles_in;
}
/* Time param in us */
void via_tick(uint64_t time)
void via_tick(int cycles)
{
(void)time;
int i = via_t2c;
if (i) {
int old = i;
i -= cycles;
VDBG("Timer count down %d -> %d\n", old, i);
if (i <= 0) {
i = 0;
VDBG("[VIA T2 reached zero, IRQ pending]\n");
// irq_active |= VIA_IRQ_T2;
via_assess_irq();
}
via_t2c = i;
}
// FIXME: support actual timers.....!
}