Skip to content

Commit b53140b

Browse files
committed
MEGA65: better cartridge + new CRT support #380
Still preliminary, no proper check of CRT files at every level, also no bank support, no ROM/RAM support (everything is treated as ROM). [DEPLOYMENT]
1 parent 4341fd8 commit b53140b

File tree

7 files changed

+178
-64
lines changed

7 files changed

+178
-64
lines changed

targets/mega65/cart.c

Lines changed: 140 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* A work-in-progess MEGA65 (Commodore 65 clone origins) emulator
22
Part of the Xemu project, please visit: https://github.com/lgblgblgb/xemu
3-
Copyright (C)2016-2024 LGB (Gábor Lénárt) <[email protected]>
3+
Copyright (C)2016-2025 LGB (Gábor Lénárt) <[email protected]>
44
55
This program is free software; you can redistribute it and/or modify
66
it under the terms of the GNU General Public License as published by
@@ -20,31 +20,45 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
2020
#include "cart.h"
2121
#include "xemu/emutools_files.h"
2222
#include "audio65.h"
23+
#include "string.h"
24+
#include "errno.h"
2325

24-
static Uint8 cart_mem[0x10000];
25-
static int loaded = 0;
26+
27+
static struct {
28+
bool loaded;
29+
bool autostart;
30+
char *fn;
31+
char name[0x21];
32+
Uint8 mem[0x10000];
33+
int total_size;
34+
int sections;
35+
} cart;
2636

2737

2838
void cart_init ( void )
2939
{
30-
memset(cart_mem, 0xFF, sizeof cart_mem);
40+
memset(cart.mem, 0xFF, sizeof cart.mem);
41+
cart.loaded = false;
42+
cart.autostart = false;
43+
cart.name[0] = 0;
44+
cart.fn = NULL;
3145
}
3246

3347

3448
Uint8 cart_read_byte ( unsigned int addr )
3549
{
3650
Uint8 data = 0xFF;
37-
if (addr < sizeof(cart_mem))
38-
data = cart_mem[addr];
39-
if (loaded && addr != 0x3FFDF60) // see the comment about OPL at cart_write_byte()
40-
DEBUGPRINT("CART: reading byte ($%02X) at $%X" NL, data, addr + 0x4000000);
51+
if (addr < sizeof(cart.mem))
52+
data = cart.mem[addr];
53+
//if (cart.loaded && addr != 0x3FFDF60) // see the comment about OPL at cart_write_byte()
54+
// DEBUGPRINT("CART: reading byte ($%02X) at $%X" NL, data, addr + 0x4000000);
4155
return data;
4256
}
4357

4458

4559
void cart_write_byte ( unsigned int addr, Uint8 data )
4660
{
47-
if (!loaded) {
61+
if (!cart.loaded) {
4862
// a hack OPL to be able to work in the "slow device area" [if no cartridge is loaded]
4963
static Uint8 opl_reg_sel = 0;
5064
if (addr == 0x3FFDF40) {
@@ -55,43 +69,138 @@ void cart_write_byte ( unsigned int addr, Uint8 data )
5569
return;
5670
}
5771
}
58-
DEBUGPRINT("CART: writing byte ($%02X) at $%X" NL, data, addr + 0x4000000);
72+
//DEBUGPRINT("CART: writing byte ($%02X) at $%X" NL, data, addr + 0x4000000);
5973
}
6074

6175

62-
int cart_load_bin ( const char *fn, const unsigned int addr, const char *cry )
76+
bool cart_is_attached ( void )
6377
{
64-
if (!fn || !*fn)
65-
return 0;
66-
loaded = 0;
67-
if (addr >= sizeof(cart_mem)) {
68-
if (cry)
69-
ERROR_WINDOW("%s\nOutside of 64K range\n%s", cry, fn);
70-
return -1;
78+
return cart.loaded;
79+
}
80+
81+
82+
void cart_detach ( void )
83+
{
84+
memset(cart.mem, 0xFF, sizeof cart.mem);
85+
cart.autostart = false;
86+
free(cart.fn);
87+
cart.fn = NULL;
88+
if (cart.loaded) {
89+
DEBUGPRINT("CART: cartridge has been detached" NL);
90+
cart.loaded = false;
7191
}
72-
const int ret = xemu_load_file(fn, cart_mem + addr, 1, sizeof(cart_mem) - addr, cry);
73-
if (ret <= 0)
74-
return -1;
75-
DEBUGPRINT("CART: %d byte(s) loaded at offset $%04X from file %s" NL, ret, addr, fn);
76-
loaded = 1;
77-
return 0;
7892
}
7993

8094

81-
int cart_detect_id ( void )
95+
static bool _check_cartridge_str ( const Uint8 *p )
8296
{
83-
static const Uint8 cart_id[] = {'M', '6', '5'};
84-
return memcmp(cart_mem + 0x8007, cart_id, sizeof cart_id);
97+
static const char cartridge_bytestr[] = { ' ', 'C', 'A', 'R', 'T', 'R', 'I', 'D', 'G', 'E' };
98+
for (unsigned int a = 3; a < 7; a++)
99+
if (!memcmp(p + a, cartridge_bytestr, sizeof cartridge_bytestr))
100+
return true;
101+
return false;
85102
}
86103

87104

88-
int cart_is_loaded ( void )
105+
int cart_attach ( const char *fn )
89106
{
90-
return loaded;
107+
static const char m65_bytestr[] = { 'M', '6', '5' };
108+
static const char mega65_bytestr[] = { 'M', 'E', 'G', 'A', '6', '5' };
109+
static const char chip_bytestr[] = { 'C', 'H', 'I', 'P' };
110+
cart_detach();
111+
if (!fn || !*fn)
112+
return -1;
113+
const int fd = xemu_open_file(fn, O_RDONLY, NULL, NULL);
114+
if (fd < 0) {
115+
ERROR_WINDOW("Cannot open cartridge file %s\nError: %s", fn, strerror(errno));
116+
return -1;
117+
}
118+
cart.fn = xemu_realloc(cart.fn, strlen(fn) + 1);
119+
strcpy(cart.fn, fn);
120+
Uint8 buf[0x40 + 1];
121+
if (xemu_safe_read(fd, buf, 0x40) != 0x40)
122+
goto read_error;
123+
if (!_check_cartridge_str(buf)) {
124+
// " CARTRIDGE" (with space) cannot be found in the first 0x10 bytes: not a VICE CRT format
125+
// let's check if it's a "raw ROM" with the "M65" mark
126+
if (memcmp(buf + 7, m65_bytestr, sizeof m65_bytestr)) {
127+
ERROR_WINDOW("Not a CRT/raw file: %s", fn); // not that either -> error
128+
goto error;
129+
}
130+
// Raw binary file, with "M65" mark: the best I can do is loading from $8000
131+
memcpy(cart.mem + 0x8000, buf, 0x40); // copy what we have already
132+
const int ret = xemu_safe_read(fd, cart.mem + 0x8000 + 0x40, 0x10000 - 0x8000 - 0x40); // load the rest
133+
if (ret < 0)
134+
goto read_error;
135+
cart.loaded = true;
136+
cart.sections = 0;
137+
cart.total_size = ret + 0x40;
138+
cart.autostart = true; // raw image is always auto start as I detected with 'M65' signature ...
139+
DEBUGPRINT("CART: raw cartridge ROM image \"%s\" has been loaded to $8000-$%X" NL, fn, 0x8000 + cart.total_size - 1);
140+
close(fd);
141+
return 1;
142+
}
143+
if (memcmp(buf, mega65_bytestr, sizeof mega65_bytestr)) {
144+
ERROR_WINDOW("Non-MEGA65 CRT file: %s", fn);
145+
goto error;
146+
}
147+
// VICE-like CRT file (MEGA65-specific though)
148+
DEBUGPRINT("CART: trying to attach VICE-like CRT file %s" NL, fn);
149+
buf[0x40] = 0;
150+
strcpy(cart.name, (const char*)buf + 0x20);
151+
cart.sections = 0;
152+
cart.total_size = 0;
153+
for (;;) {
154+
const int ret = xemu_safe_read(fd, buf, 0x10); // read "CHIP" section header
155+
if (!ret)
156+
break;
157+
if (ret != 0x10)
158+
goto read_error;
159+
if (memcmp(buf, chip_bytestr, sizeof chip_bytestr)) {
160+
ERROR_WINDOW("Bad CRT file, missing CHIP section ID");
161+
goto error;
162+
}
163+
const unsigned int size = (buf[0xE] << 8) + buf[0xF];
164+
const unsigned int addr = (buf[0xC] << 8) + buf[0xD];
165+
DEBUGPRINT("CART: ... new CHIP section (#%d), $%04X-$%04X (%u bytes)" NL, cart.sections, addr, addr + size - 1, size);
166+
if (size + addr > 0x10000) {
167+
ERROR_WINDOW("Bad CRT file, CHIP section overflows memory");
168+
goto error;
169+
}
170+
if (size) {
171+
if (xemu_safe_read(fd, cart.mem + addr, size) != size)
172+
goto read_error;
173+
}
174+
cart.sections++;
175+
cart.total_size += size;
176+
}
177+
if (!cart.sections) {
178+
ERROR_WINDOW("No CHIP sections");
179+
goto error;
180+
}
181+
cart.loaded = true;
182+
cart.autostart = !memcmp(cart.mem + 0x8007, m65_bytestr, sizeof m65_bytestr);
183+
DEBUGPRINT("CART: attached, autostart = %d, name = \"%s\"" NL, (int)cart.autostart, cart.name);
184+
close(fd);
185+
return (int)cart.autostart;
186+
read_error:
187+
ERROR_WINDOW("Cannot read (or truncated/too small) cartridge file %s", fn);
188+
error:
189+
cart_detach();
190+
if (fd >= 0)
191+
close(fd);
192+
return -1;
91193
}
92194

93195

94-
void cart_copy_from ( const Uint16 cart_addr, Uint8 *target, const Uint16 size )
196+
void cart_info ( char *p, size_t size )
95197
{
96-
memcpy(target, cart_mem + cart_addr, size);
198+
if (!cart.loaded) {
199+
snprintf(p, size, "No cartridge is attached");
200+
return;
201+
}
202+
snprintf(p, size, "Filename: %s\nCartridge: %s\nAuto-start: %d\nSections: %d\nTotal binary size: %d\nFormat: %s",
203+
cart.fn, cart.name, (int)cart.autostart, cart.sections, cart.total_size,
204+
cart.sections ? "CRT" : "RAW"
205+
);
97206
}

targets/mega65/cart.h

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* A work-in-progess MEGA65 (Commodore 65 clone origins) emulator
22
Part of the Xemu project, please visit: https://github.com/lgblgblgb/xemu
3-
Copyright (C)2016-2024 LGB (Gábor Lénárt) <[email protected]>
3+
Copyright (C)2016-2025 LGB (Gábor Lénárt) <[email protected]>
44
55
This program is free software; you can redistribute it and/or modify
66
it under the terms of the GNU General Public License as published by
@@ -19,12 +19,12 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
1919
#ifndef XEMU_MEGA65_CART_H_INCLUDED
2020
#define XEMU_MEGA65_CART_H_INCLUDED
2121

22-
extern void cart_init ( void );
23-
extern Uint8 cart_read_byte ( unsigned int addr );
24-
extern void cart_write_byte ( unsigned int addr, Uint8 data );
25-
extern int cart_load_bin ( const char *fn, const unsigned int addr, const char *cry );
26-
extern void cart_copy_from ( const Uint16 cart_addr, Uint8 *target, const Uint16 size );
27-
extern int cart_detect_id ( void );
28-
extern int cart_is_loaded ( void );
22+
extern void cart_init ( void );
23+
extern Uint8 cart_read_byte ( unsigned int addr );
24+
extern void cart_write_byte ( unsigned int addr, Uint8 data );
25+
extern int cart_attach ( const char *fn );
26+
extern void cart_detach ( void );
27+
extern bool cart_is_attached ( void );
28+
extern void cart_info ( char *p, size_t size );
2929

3030
#endif

targets/mega65/configdb.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ static const struct xemutools_configdef_str_st str_options[] = {
7474
#endif
7575
{ "gui", NULL, "Select GUI type for usage. Specify some insane str to get a list", &configdb.selectedgui },
7676
{ "importbas", NULL, "Import and RUN BASIC65 program from TEXT file", &configdb.importbas },
77-
{ "cartbin8000",NULL, "Load binary cartridge image from $8000", &configdb.cartbin8000 },
77+
{ "cart", NULL, "Load cartridge file", &configdb.cart },
7878
{ "winpos", NULL, "Window position: x,y (integers)", &configdb.winpos },
7979
{ "initattic", NULL, "Pre-fill the Attic RAM with the content of a file", &configdb.init_attic },
8080
{ NULL }
@@ -164,7 +164,7 @@ static const void *do_not_save_opts[] = {
164164
&configdb.screenshot_and_exit,
165165
#endif
166166
&configdb.testing, &configdb.hyperdebug, &configdb.hyperdebugfreezer, &configdb.usestubrom, &configdb.useinitrom, &configdb.useutilmenu,
167-
&configdb.cartbin8000, &configdb.winpos, &configdb.ramcheckread, &configdb.init_attic,
167+
&configdb.cart, &configdb.winpos, &configdb.ramcheckread, &configdb.init_attic,
168168
&configdb.prg_test, &configdb.prg_exit,
169169
NULL
170170
};

targets/mega65/configdb.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ struct configdb_st {
9797
// $60/$61/$62 = qmtecha100t/qmtecha200t/qmtecha325t, $21/$22=megaphoner1/megaphoner4
9898
int mega65_model;
9999
int colour_effect;
100-
char *cartbin8000;
100+
char *cart;
101101
int hicked;
102102
int prgmode;
103103
int rtc_hour_offset;

targets/mega65/mega65.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ static void mega65_init ( void )
363363
} while (0);
364364
// *** Initializes memory subsystem of MEGA65 emulation itself
365365
memory_init();
366-
cart_load_bin(configdb.cartbin8000, 0x8000, "Cannot load binary cartridge image from $8000");
366+
cart_attach(configdb.cart);
367367
if (xemu_load_file(I2C_FILE_NAME, i2c_regs, sizeof i2c_regs, sizeof i2c_regs,
368368
#ifndef XEMU_ARCH_HTML
369369
"Cannot load I2C reg-space. Maybe first run or upgrade of Xemu?\nFor the next Xemu launch, it should have been already corrected automatically.\nSo no need to worry."
@@ -564,6 +564,8 @@ int reset_mega65 ( const unsigned int options )
564564
if (!ARE_YOU_SURE("Are you sure you want to RESET your emulated machine?", i_am_sure_override | ARE_YOU_SURE_DEFAULT_YES))
565565
return 0;
566566
}
567+
if ((options & RESET_MEGA65_NO_CART))
568+
cart_detach();
567569
switch (options & 0xFF) {
568570
case RESET_MEGA65_HARD:
569571
last_reset_type = "HARD";

targets/mega65/mega65.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ extern void machine_set_speed ( int verbose );
5959
#define RESET_MEGA65_HYPPO 0x003
6060
#define RESET_MEGA65_LAST_ID RESET_MEGA65_HYPPO
6161
#define RESET_MEGA65_ASK 0x100
62+
#define RESET_MEGA65_NO_CART 0x200
6263
extern int reset_mega65 ( const unsigned int options );
6364

6465
extern int mega65_set_model ( const Uint8 id );

0 commit comments

Comments
 (0)