Xemu [doxygen]  hyppo 0a42be3a057156924bc1b626a687bd6e27349c45 @ Sat 19 Mar 02:15:11 CET 2022
inject.c
Go to the documentation of this file.
1 /* A work-in-progess MEGA65 (Commodore 65 clone origins) emulator
2  Part of the Xemu project, please visit: https://github.com/lgblgblgb/xemu
3  Copyright (C)2016-2021 LGB (Gábor Lénárt) <lgblgblgb@gmail.com>
4 
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9 
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
18 
19 
20 #include "xemu/emutools.h"
21 #include "inject.h"
22 #include "xemu/emutools_files.h"
23 #include "memory_mapper.h"
24 #include "vic4.h"
25 #include "xemu/emutools_hid.h"
26 #include "xemu/f011_core.h"
27 
28 #define C64_BASIC_LOAD_ADDR 0x0801
29 #define C65_BASIC_LOAD_ADDR 0x2001
30 
32 
33 static void *inject_ready_userdata;
34 static void (*inject_ready_callback)(void*);
35 static struct {
37  int size;
38  int load_addr;
39  int c64_mode;
40  int run_it;
41 } prg;
42 static Uint8 *under_ready_p;
43 
44 
45 static XEMU_INLINE int get_screen_width ( void )
46 {
47  // C65 $D031.7 VIC-III:H640 Enable C64 640 horizontal pixels / 80 column mode
48  // Used to determine if C64 or C65 "power-on" screen
49  // TODO: this should be revised in the future, as MEGA65 can other means have
50  // different screens!!!
51  return (vic_registers[0x31] & 0x80) ? 80 : 40;
52 }
53 
54 
55 static void _cbm_screen_write ( Uint8 *p, const char *s )
56 {
57  while (*s) {
58  if (*s == '@')
59  *p++ = 0;
60  else if (*s >= 'a' && *s <= 'z')
61  *p++ = *s - 'a' + 1;
62  else if (*s >= 'A' && *s <= 'Z')
63  *p++ = *s - 'A' + 1;
64  else
65  *p++ = *s;
66  s++;
67  }
68 }
69 
70 
71 #define CBM_SCREEN_PRINTF(scrp, ...) do { \
72  char __buffer__[80]; \
73  sprintf(__buffer__, ##__VA_ARGS__); \
74  _cbm_screen_write(scrp, __buffer__); \
75  } while (0)
76 
77 
78 static void prg_inject_callback ( void *unused )
79 {
80  DEBUGPRINT("INJECT: hit 'READY.' trigger, about to inject %d bytes from $%04X." NL, prg.size, prg.load_addr);
82  memcpy(main_ram + prg.load_addr, prg.stream, prg.size);
83  clear_emu_events(); // clear keyboard & co state, ie for C64 mode, probably had MEGA key pressed still
84  CBM_SCREEN_PRINTF(under_ready_p - get_screen_width() + 7, "<$%04X-$%04X,%d bytes>", prg.load_addr, prg.load_addr + prg.size - 1, prg.size);
85  if (prg.run_it) {
86  // We must modify BASIC pointers ... Important to know the C64/C65 differences!
87  if (prg.c64_mode) {
88  main_ram[0x2D] = prg.size + prg.load_addr;
89  main_ram[0x2E] = (prg.size + prg.load_addr) >> 8;
90  } else {
91  main_ram[0xAE] = prg.size + prg.load_addr;
92  main_ram[0xAF] = (prg.size + prg.load_addr) >> 8;
93  main_ram[0x82] = prg.size + prg.load_addr;
94  main_ram[0x83] = (prg.size + prg.load_addr) >> 8;
95  }
96  // If program was detected as BASIC (by load-addr) we want to auto-RUN it
97  CBM_SCREEN_PRINTF(under_ready_p, " ?\"@\":RUN:");
98  KBD_PRESS_KEY(0x01); // press RETURN
99  under_ready_p[get_screen_width()] = 0x20; // be sure no "@" (screen code 0) at the trigger position
100  inject_ready_check_status = 100; // go into special mode, to see "@" character printed by PRINT, to release RETURN by that trigger
101  } else {
102  // In this case we DO NOT press RETURN for user, as maybe the SYS addr is different, or user does not want this at all!
103  CBM_SCREEN_PRINTF(under_ready_p, " SYS%d:REM **YOU CAN PRESS RETURN**", prg.load_addr);
104  }
105  free(prg.stream);
106  prg.stream = NULL;
107 }
108 
109 
110 static void allow_disk_access_callback ( void *unused )
111 {
112  DEBUGPRINT("INJECT: re-enable disk access on READY. prompt" NL);
114 }
115 
116 
117 int inject_register_ready_status ( const char *debug_msg, void (*callback)(void*), void *userdata )
118 {
120  DEBUGPRINT("WARNING: INJECT: cannot register 'READY.' event, already having one in progress!" NL);
121  //return 1;
122  }
123  DEBUGPRINT("INJECT: registering 'READY.' event: %s" NL, debug_msg);
124  inject_ready_userdata = userdata;
125  inject_ready_callback = callback;
127  memset(main_ram + 1024, 0, 1024 * 3); // be sure, no READY. can be seen already on the screen
128  return 0;
129 }
130 
131 
133 {
135  // register event for the READY. prompt for re-enable
136  inject_register_ready_status("Disk access re-enabled", allow_disk_access_callback, NULL);
137 }
138 
139 
140 int inject_register_prg ( const char *prg_fn, int prg_mode )
141 {
142  prg.stream = NULL;
143  prg.size = xemu_load_file(prg_fn, NULL, 3, 0x1F800, "Cannot load PRG to be injected");
144  if (prg.size < 3)
145  goto error;
146  prg.stream = xemu_load_buffer_p;
147  xemu_load_buffer_p = NULL;
148  prg.load_addr = prg.stream[0] + (prg.stream[1] << 8);
149  prg.size -= 2;
150  memmove(prg.stream, prg.stream + 2, prg.size);
151  // TODO: needs to be fixed to check ROM boundary (or eg from addr zero)
152  if (prg.load_addr + prg.size >= 0x1F800) {
153  ERROR_WINDOW("Program to be injected is too large (%d bytes, load address: $%04X)\nFile: %s",
154  prg.size,
155  prg.load_addr,
156  prg_fn
157  );
158  goto error;
159  }
160  switch (prg_mode) {
161  case 0: // auto detection
162  if (prg.load_addr == C64_BASIC_LOAD_ADDR) {
163  prg.c64_mode = 1;
164  prg.run_it = 1;
165  } else if (prg.load_addr == C65_BASIC_LOAD_ADDR) {
166  prg.c64_mode = 0;
167  prg.run_it = 1;
168  } else {
169  char msg[512];
170  snprintf(msg, sizeof msg, "PRG to load: %s\nCannot detect C64/C65 mode for non-BASIC (load address: $%04X) PRG, please specify:", prg_fn, prg.load_addr);
171  prg.c64_mode = QUESTION_WINDOW("C65|C64|Ouch, CANCEL!", msg);
172  if (prg.c64_mode != 0 && prg.c64_mode != 1)
173  goto error;
174  prg.run_it = 0;
175  }
176  break;
177  case 64:
178  prg.c64_mode = 1;
179  prg.run_it = (prg.load_addr == C64_BASIC_LOAD_ADDR);
180  break;
181  case 65:
182  prg.c64_mode = 0;
183  prg.run_it = (prg.load_addr == C65_BASIC_LOAD_ADDR);
184  break;
185  default:
186  ERROR_WINDOW("Invalid parameter for prg-mode!");
187  goto error;
188  }
189  DEBUGPRINT("INJECT: prepare for C6%c mode, %s program, $%04X load address, %d bytes from file: %s" NL,
190  prg.c64_mode ? '4' : '5',
191  prg.run_it ? "BASIC (RUNable)" : "ML (SYSable)",
192  prg.load_addr,
193  prg.size,
194  prg_fn
195  );
197  if (prg.c64_mode)
198  KBD_PRESS_KEY(0x75); // "MEGA" key is hold down for C64 mode
199  if (inject_register_ready_status("PRG memory injection", prg_inject_callback, NULL)) // prg inject does not use the userdata ...
200  goto error;
201  fdc_allow_disk_access(FDC_DENY_DISK_ACCESS); // deny now, to avoid problem on PRG load while autoboot disk is mounted
202  return 0;
203 error:
204  if (prg.stream) {
205  free(prg.stream);
206  prg.stream = NULL;
207  }
208  return 1;
209 }
210 
211 
212 static const Uint8 ready_msg[] = { 0x12, 0x05, 0x01, 0x04, 0x19, 0x2E }; // "READY." in screen codes
213 
214 
215 static int is_ready_on_screen ( void )
216 {
217  int width = get_screen_width();
218  // TODO: this should be revised in the future, as MEGA65 can other means have
219  // different screen starting addresses, and not even dependent on the 40/80 column mode!!!
220  int start = (width == 80) ? 2048 : 1024;
221  // Check every lines of the screen (not the "0th" line, because we need "READY." in the previous line!)
222  // NOTE: I cannot rely on exact position as different ROMs can have different line position for the "READY." text!
223  for (int i = 1; i < 23; i++) {
224  // We need this pointer later, to "fake" a command on the screen
225  under_ready_p = main_ram + start + i * width;
226  // 0XA0 -> cursor is shown, and the READY. in the previous line
227  if (*under_ready_p == 0xA0 && !memcmp(under_ready_p - width, ready_msg, sizeof ready_msg))
228  return 1;
229  }
230  return 0;
231 }
232 
233 
235 {
237  return;
238  if (inject_ready_check_status == 1) { // we're in "waiting for READY." phase
239  if (is_ready_on_screen())
241  } else if (inject_ready_check_status == 100) { // special mode ...
242  // This is used to check the @ char printed by our tricky RUN line to see it's time to release RETURN (or just simply clear all the keyboard)
243  // Also check for 'READY.' if it's still there, run program running maybe cleared the screen and we'll miss '@'!
244  // TODO: this logic is too error-proon! Consider for some timing only, ie wait some frames after virtually pressing RETURN then release it.
245  int width = get_screen_width();
246  if (under_ready_p[width] == 0x00 || memcmp(under_ready_p - width, ready_msg, sizeof ready_msg)) {
248  clear_emu_events(); // reset keyboard state & co
249  DEBUGPRINT("INJECT: clearing keyboard status on '@' trigger." NL);
250  }
251  } else if (inject_ready_check_status > 10) {
252  inject_ready_check_status = 0; // turn off "ready check" mode, we have our READY.
253  inject_ready_callback(inject_ready_userdata); // callback is activated now
254  } else
255  inject_ready_check_status++; // we're "let's wait some time after READY." phase
256 }
inject_ready_check_do
void inject_ready_check_do(void)
Definition: inject.c:234
vic4.h
emutools.h
fdc_allow_disk_access
void fdc_allow_disk_access(int in)
Definition: f011_core.c:171
f011_core.h
inject_register_ready_status
int inject_register_ready_status(const char *debug_msg, void(*callback)(void *), void *userdata)
Definition: inject.c:117
inject_register_allow_disk_access
void inject_register_allow_disk_access(void)
Definition: inject.c:132
XEMU_INLINE
#define XEMU_INLINE
Definition: emutools_basicdefs.h:126
FDC_ALLOW_DISK_ACCESS
#define FDC_ALLOW_DISK_ACCESS
Definition: f011_core.h:23
load_addr
int load_addr
Definition: inject.c:38
Uint8
uint8_t Uint8
Definition: fat32.c:51
inject_ready_check_status
int inject_ready_check_status
Definition: inject.c:31
FDC_DENY_DISK_ACCESS
#define FDC_DENY_DISK_ACCESS
Definition: f011_core.h:22
inject.h
main_ram
Uint8 main_ram[512<< 10]
Definition: memory_mapper.c:47
emutools_files.h
DEBUGPRINT
#define DEBUGPRINT(...)
Definition: emutools_basicdefs.h:171
prg
char * prg
Definition: commodore_vic20.c:96
ERROR_WINDOW
#define ERROR_WINDOW(...)
Definition: xep128.h:116
memory_mapper.h
XEMU_LIKELY
#define XEMU_LIKELY(__x__)
Definition: emutools_basicdefs.h:124
NL
#define NL
Definition: fat32.c:37
xemu_load_file
int xemu_load_file(const char *filename, void *store_to, int min_size, int max_size, const char *cry)
Definition: emutools_files.c:674
run_it
int run_it
Definition: inject.c:40
xemu_load_buffer_p
void * xemu_load_buffer_p
Definition: emutools_files.c:34
size
int size
Definition: inject.c:37
CBM_SCREEN_PRINTF
#define CBM_SCREEN_PRINTF(scrp,...)
Definition: inject.c:71
clear_emu_events
void clear_emu_events(void)
Definition: commodore_65.c:193
C64_BASIC_LOAD_ADDR
#define C64_BASIC_LOAD_ADDR
Definition: inject.c:28
C65_BASIC_LOAD_ADDR
#define C65_BASIC_LOAD_ADDR
Definition: inject.c:29
inject_register_prg
int inject_register_prg(const char *prg_fn, int prg_mode)
Definition: inject.c:140
stream
Uint8 * stream
Definition: inject.c:36
c64_mode
int c64_mode
Definition: inject.c:39
emutools_hid.h
QUESTION_WINDOW
#define QUESTION_WINDOW(items, msg)
Definition: xep128.h:124
vic_registers
Uint8 vic_registers[0x80]
Definition: vic4.c:43
KBD_PRESS_KEY
#define KBD_PRESS_KEY(a)
Definition: emutools_hid.h:46