/* * TilEm II * * Copyright (c) 2011-2012 Benjamin Moody * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include "gui.h" #include "emucore.h" #include "msgbox.h" #include "filedlg.h" #define MILLISEC_PER_FRAME 30 #define MICROSEC_PER_FRAME (MILLISEC_PER_FRAME * 1000) #define GRAY_WINDOW_SIZE 4 #define GRAY_SAMPLE_INT 200 /* Lock emulator. Notify the core loop that we wish to do so - note that if the core is running at full speed, it keeps the mutex locked almost all the time, so we need to explicitly ask it to yield. (This may not be necessary with all mutex implementations, but definitely is necessary with some.) */ void tilem_calc_emulator_lock(TilemCalcEmulator *emu) { g_atomic_int_inc(&emu->calc_lock_waiting); g_mutex_lock(emu->calc_mutex); } /* Unlock emulator and (if no other threads are waiting to lock it) wake up the core thread. This also serves to resume emulation if the calculator has been powered off. */ void tilem_calc_emulator_unlock(TilemCalcEmulator *emu) { if (g_atomic_int_dec_and_test(&emu->calc_lock_waiting)) g_cond_signal(emu->calc_wakeup_cond); g_mutex_unlock(emu->calc_mutex); } static gboolean refresh_lcd(gpointer data) { TilemCalcEmulator* emu = data; if (emu->ewin) tilem_emulator_window_refresh_lcd(emu->ewin); return FALSE; } static void tmr_screen_update(TilemCalc *calc, void *data) { TilemCalcEmulator *emu = data; g_mutex_lock(emu->lcd_mutex); if (emu->glcd) tilem_gray_lcd_get_frame(emu->glcd, emu->lcd_buffer); else tilem_lcd_get_frame(calc, emu->lcd_buffer); if (emu->anim) { if (emu->anim_grayscale) { tilem_animation_append_frame(emu->anim, emu->lcd_buffer, MILLISEC_PER_FRAME); } else { tilem_lcd_get_frame(calc, emu->tmp_lcd_buffer); tilem_animation_append_frame(emu->anim, emu->tmp_lcd_buffer, MILLISEC_PER_FRAME); } } if (!emu->lcd_update_pending) { emu->lcd_update_pending = TRUE; g_idle_add_full(G_PRIORITY_DEFAULT, &refresh_lcd, emu, NULL); } g_mutex_unlock(emu->lcd_mutex); } static void cancel_animation(TilemCalcEmulator *emu) { if (emu->anim) g_object_unref(emu->anim); emu->anim = NULL; } static GtkWidget *get_toplevel(TilemCalcEmulator *emu) { if (emu->ewin) return emu->ewin->window; else return NULL; } static void link_update_nop() { } TilemCalcEmulator *tilem_calc_emulator_new() { TilemCalcEmulator *emu = g_new0(TilemCalcEmulator, 1); CalcUpdate *update; emu->calc_mutex = g_mutex_new(); emu->calc_wakeup_cond = g_cond_new(); emu->lcd_mutex = g_mutex_new(); tilem_config_get("emulation", "grayscale/b=1", &emu->grayscale, "limit_speed/b=1", &emu->limit_speed, NULL); emu->task_queue = g_queue_new(); emu->task_finished_cond = g_cond_new(); emu->timer = g_timer_new(); emu->pbar_mutex = g_mutex_new(); update = g_new0(CalcUpdate, 1); update->start = &link_update_nop; update->stop = &link_update_nop; update->refresh = &link_update_nop; update->pbar = &link_update_nop; update->label = &link_update_nop; emu->link_update = update; return emu; } void tilem_calc_emulator_free(TilemCalcEmulator *emu) { g_return_if_fail(emu != NULL); tilem_calc_emulator_cancel_tasks(emu); tilem_calc_emulator_lock(emu); cancel_animation(emu); emu->exiting = TRUE; tilem_calc_emulator_unlock(emu); if (emu->z80_thread) g_thread_join(emu->z80_thread); g_free(emu->key_queue); g_free(emu->rom_file_name); g_free(emu->state_file_name); g_mutex_free(emu->calc_mutex); g_mutex_free(emu->lcd_mutex); g_cond_free(emu->calc_wakeup_cond); g_cond_free(emu->task_finished_cond); g_queue_free(emu->task_queue); g_timer_destroy(emu->timer); g_mutex_free(emu->pbar_mutex); g_free(emu->link_update); if (emu->lcd_buffer) tilem_lcd_buffer_free(emu->lcd_buffer); if (emu->tmp_lcd_buffer) tilem_lcd_buffer_free(emu->tmp_lcd_buffer); if (emu->glcd) tilem_gray_lcd_free(emu->glcd); if (emu->calc) tilem_calc_free(emu->calc); g_free(emu); } static char *get_sav_name(const char *romname) { char *dname, *bname, *sname, *suff; dname = g_path_get_dirname(romname); bname = g_path_get_basename(romname); if ((suff = strrchr(bname, '.'))) *suff = 0; sname = g_strconcat(dname, G_DIR_SEPARATOR_S, bname, ".sav", NULL); g_free(dname); g_free(bname); return sname; } gboolean tilem_calc_emulator_load_state(TilemCalcEmulator *emu, const char *romfname, const char *statefname, int model, GError **err) { const char *modelname; FILE *romfile, *savfile; char *rname = NULL, *sname = NULL; TilemCalc *calc; char *dname; int errnum; g_return_val_if_fail(emu != NULL, FALSE); g_return_val_if_fail(err == NULL || *err == NULL, FALSE); tilem_calc_emulator_cancel_tasks(emu); if (romfname) rname = g_strdup(romfname); if (!sname && statefname) sname = g_strdup(statefname); /* Choose ROM file */ if (!rname && model) { modelname = model_to_name(model); g_return_val_if_fail(modelname != NULL, FALSE); if (sname) g_free(sname); tilem_config_get(modelname, "rom_file/f", &rname, "state_file/f", &sname, NULL); } if (!rname) { g_set_error(err, TILEM_EMULATOR_ERROR, TILEM_EMULATOR_ERROR_NO_ROM, "No ROM file specified"); g_free(rname); g_free(sname); return FALSE; } /* Open ROM file */ romfile = g_fopen(rname, "rb"); if (!romfile) { errnum = errno; dname = g_filename_display_basename(rname); g_set_error(err, G_FILE_ERROR, g_file_error_from_errno(errnum), "Unable to open %s for reading: %s", dname, g_strerror(errnum)); g_free(dname); g_free(rname); g_free(sname); return FALSE; } /* Open state file */ if (!sname) sname = get_sav_name(rname); savfile = g_fopen(sname, "rb"); if (!savfile && errno != ENOENT) { errnum = errno; dname = g_filename_display_basename(sname); g_set_error(err, G_FILE_ERROR, g_file_error_from_errno(errnum), "Unable to open %s for reading: %s", dname, g_strerror(errnum)); g_free(dname); g_free(rname); g_free(sname); fclose(romfile); return FALSE; } /* Determine model from state file, if possible */ if (!model && savfile) model = tilem_get_sav_type(savfile); /* Otherwise, guess from ROM file; ask user if ambiguous */ if (!model) { model = tilem_guess_rom_type(romfile); if (model) { model = choose_rom_popup(get_toplevel(emu), rname, model); } else { dname = g_filename_display_basename(rname); g_set_error(err, TILEM_EMULATOR_ERROR, TILEM_EMULATOR_ERROR_INVALID_ROM, "The file %s is not a recognized" " calculator ROM file.", dname); g_free(dname); } } if (!model) { fclose(romfile); if (savfile) fclose(savfile); g_free(rname); g_free(sname); return FALSE; } /* Create new calc, and load state */ calc = tilem_calc_new(model); if (tilem_calc_load_state(calc, romfile, savfile)) { g_set_error(err, TILEM_EMULATOR_ERROR, TILEM_EMULATOR_ERROR_INVALID_STATE, "The specified ROM or state file is invalid."); fclose(romfile); if (savfile) fclose(savfile); g_free(rname); g_free(sname); return FALSE; } if (!savfile) { /* save model as default for the future */ savfile = g_fopen(sname, "wb"); if (savfile) fprintf(savfile, "MODEL = %s\n", calc->hw.name); } fclose(romfile); if (savfile) fclose(savfile); /* Switch to new calc */ tilem_calc_emulator_lock(emu); cancel_animation(emu); if (emu->glcd) tilem_gray_lcd_free(emu->glcd); if (emu->calc) tilem_calc_free(emu->calc); emu->calc = calc; emu->lcd_buffer = tilem_lcd_buffer_new(); emu->tmp_lcd_buffer = tilem_lcd_buffer_new(); if (emu->grayscale) emu->glcd = tilem_gray_lcd_new(calc, GRAY_WINDOW_SIZE, GRAY_SAMPLE_INT); else emu->glcd = NULL; tilem_z80_add_timer(calc, MICROSEC_PER_FRAME, MICROSEC_PER_FRAME, 1, &tmr_screen_update, emu); tilem_calc_emulator_unlock(emu); if (emu->rom_file_name) g_free(emu->rom_file_name); emu->rom_file_name = rname; if (emu->state_file_name) g_free(emu->state_file_name); emu->state_file_name = sname; tilem_keybindings_init(emu, calc->hw.name); if (emu->ewin) tilem_emulator_window_calc_changed(emu->ewin); if (emu->dbg) tilem_debugger_calc_changed(emu->dbg); if (emu->rcvdlg) tilem_receive_dialog_free(emu->rcvdlg); emu->rcvdlg = NULL; return TRUE; } gboolean tilem_calc_emulator_revert_state(TilemCalcEmulator *emu, GError **err) { FILE *romfile, *savfile; char *dname; int errnum = 0; gboolean status = TRUE; g_return_val_if_fail(emu != NULL, FALSE); g_return_val_if_fail(emu->calc != NULL, FALSE); g_return_val_if_fail(emu->rom_file_name != NULL, FALSE); g_return_val_if_fail(emu->state_file_name != NULL, FALSE); g_return_val_if_fail(err == NULL || *err == NULL, FALSE); /* Open ROM file */ if (emu->calc->hw.flags & TILEM_CALC_HAS_FLASH) { romfile = g_fopen(emu->rom_file_name, "rb"); if (!romfile) { errnum = errno; dname = g_filename_display_basename(emu->rom_file_name); g_set_error(err, G_FILE_ERROR, g_file_error_from_errno(errnum), "Unable to open %s for reading: %s", dname, g_strerror(errnum)); g_free(dname); return FALSE; } } else { romfile = NULL; } /* Open state file */ savfile = g_fopen(emu->state_file_name, "rb"); if (!savfile) { errnum = errno; dname = g_filename_display_basename(emu->state_file_name); g_set_error(err, G_FILE_ERROR, g_file_error_from_errno(errnum), "Unable to open %s for reading: %s", dname, g_strerror(errnum)); g_free(dname); if (romfile) fclose(romfile); return FALSE; } /* Read state */ tilem_calc_emulator_lock(emu); if (tilem_calc_load_state(emu->calc, romfile, savfile)) { g_set_error(err, TILEM_EMULATOR_ERROR, TILEM_EMULATOR_ERROR_INVALID_STATE, "The specified ROM or state file is invalid."); status = FALSE; } tilem_calc_emulator_unlock(emu); if (emu->dbg) tilem_debugger_refresh(emu->dbg, TRUE); if (romfile) fclose(romfile); fclose(savfile); return status; } gboolean tilem_calc_emulator_save_state(TilemCalcEmulator *emu, GError **err) { FILE *romfile, *savfile; char *dname; int errnum = 0; g_return_val_if_fail(emu != NULL, FALSE); g_return_val_if_fail(emu->calc != NULL, FALSE); g_return_val_if_fail(emu->rom_file_name != NULL, FALSE); g_return_val_if_fail(emu->state_file_name != NULL, FALSE); g_return_val_if_fail(err == NULL || *err == NULL, FALSE); /* Open ROM file */ if (emu->calc->hw.flags & TILEM_CALC_HAS_FLASH) { romfile = g_fopen(emu->rom_file_name, "r+b"); if (!romfile) { errnum = errno; dname = g_filename_display_basename(emu->rom_file_name); g_set_error(err, G_FILE_ERROR, g_file_error_from_errno(errnum), "Unable to open %s for writing: %s", dname, g_strerror(errnum)); g_free(dname); return FALSE; } } else { romfile = NULL; } /* Open state file */ savfile = g_fopen(emu->state_file_name, "wb"); if (!savfile) { errnum = errno; dname = g_filename_display_basename(emu->state_file_name); g_set_error(err, G_FILE_ERROR, g_file_error_from_errno(errnum), "Unable to open %s for writing: %s", dname, g_strerror(errnum)); g_free(dname); if (romfile) fclose(romfile); return FALSE; } /* Write state */ tilem_calc_emulator_lock(emu); if (romfile && tilem_calc_save_state(emu->calc, romfile, NULL)) errnum = errno; if (romfile && fclose(romfile)) errnum = errno; if (errnum) { dname = g_filename_display_basename(emu->rom_file_name); g_set_error(err, G_FILE_ERROR, g_file_error_from_errno(errnum), "Error writing %s: %s", dname, g_strerror(errnum)); g_free(dname); fclose(savfile); tilem_calc_emulator_unlock(emu); return FALSE; } if (tilem_calc_save_state(emu->calc, NULL, savfile)) errnum = errno; if (fclose(savfile)) errnum = errno; tilem_calc_emulator_unlock(emu); if (errnum) { dname = g_filename_display_basename(emu->state_file_name); g_set_error(err, G_FILE_ERROR, g_file_error_from_errno(errnum), "Error writing %s: %s", dname, g_strerror(errnum)); g_free(dname); return FALSE; } return TRUE; } void tilem_calc_emulator_reset(TilemCalcEmulator *emu) { g_return_if_fail(emu != NULL); g_return_if_fail(emu->calc != NULL); tilem_calc_emulator_lock(emu); tilem_calc_reset(emu->calc); tilem_calc_emulator_unlock(emu); if (emu->dbg) tilem_debugger_refresh(emu->dbg, TRUE); } void tilem_calc_emulator_pause(TilemCalcEmulator *emu) { g_return_if_fail(emu != NULL); tilem_calc_emulator_lock(emu); emu->paused = TRUE; tilem_calc_emulator_unlock(emu); } void tilem_calc_emulator_run(TilemCalcEmulator *emu) { g_return_if_fail(emu != NULL); g_return_if_fail(emu->calc != NULL); tilem_calc_emulator_lock(emu); emu->paused = FALSE; tilem_calc_emulator_unlock(emu); if (!emu->z80_thread) emu->z80_thread = g_thread_create(&tilem_em_main, emu, TRUE, NULL); } void tilem_calc_emulator_set_limit_speed(TilemCalcEmulator *emu, gboolean limit) { emu->limit_speed = limit; } void tilem_calc_emulator_set_grayscale(TilemCalcEmulator *emu, gboolean grayscale) { emu->grayscale = grayscale; if (grayscale && emu->calc && !emu->glcd) { tilem_calc_emulator_lock(emu); emu->glcd = tilem_gray_lcd_new(emu->calc, GRAY_WINDOW_SIZE, GRAY_SAMPLE_INT); tilem_calc_emulator_unlock(emu); } else if (!grayscale && emu->glcd) { tilem_calc_emulator_lock(emu); tilem_gray_lcd_free(emu->glcd); emu->glcd = NULL; tilem_calc_emulator_unlock(emu); } } /* If currently recording a macro, record a keypress */ static void record_key(TilemCalcEmulator* emu, int code) { char* codechar; int type = 0; if (emu->isMacroRecording) { codechar = g_strdup_printf("%04d", code); tilem_macro_add_action(emu->macro, type, codechar); g_free(codechar); } } void tilem_calc_emulator_press_key(TilemCalcEmulator *emu, int key) { g_return_if_fail(emu != NULL); if (key == 0) return; tilem_calc_emulator_lock(emu); tilem_keypad_press_key(emu->calc, key); tilem_calc_emulator_unlock(emu); record_key(emu, key); if (emu->dbg && emu->dbg->keypad_dialog) tilem_keypad_dialog_refresh(emu->dbg->keypad_dialog); } void tilem_calc_emulator_release_key(TilemCalcEmulator *emu, int key) { g_return_if_fail(emu != NULL); if (key == 0) return; tilem_calc_emulator_lock(emu); tilem_keypad_release_key(emu->calc, key); tilem_calc_emulator_unlock(emu); if (emu->dbg && emu->dbg->keypad_dialog) tilem_keypad_dialog_refresh(emu->dbg->keypad_dialog); } static gboolean refresh_kpd(gpointer data) { TilemCalcEmulator *emu = data; if (emu->dbg && emu->dbg->keypad_dialog) tilem_keypad_dialog_refresh(emu->dbg->keypad_dialog); return FALSE; } /* Timer callback for key sequences */ static void tmr_key_queue(TilemCalc* calc, void* data) { TilemCalcEmulator *emu = data; int nextkey; if (emu->key_queue_pressed) { if (emu->key_queue_len > 0 || !emu->key_queue_hold) { tilem_keypad_release_key(calc, emu->key_queue_cur); emu->key_queue_pressed = 0; emu->key_queue_cur = 0; tilem_z80_set_timer(calc, emu->key_queue_timer, 50000, 0, 1); } else { tilem_z80_remove_timer(calc, emu->key_queue_timer); emu->key_queue_timer = 0; } } else { if (emu->key_queue_len > 0) { nextkey = emu->key_queue[--emu->key_queue_len]; tilem_keypad_press_key(calc, nextkey); emu->key_queue_pressed = 1; emu->key_queue_cur = nextkey; tilem_z80_set_timer(calc, emu->key_queue_timer, 20000, 0, 1); } else { tilem_z80_remove_timer(calc, emu->key_queue_timer); emu->key_queue_timer = 0; } } g_idle_add(&refresh_kpd, emu); } static void queue_keys(TilemCalcEmulator *emu, const byte *keys, int nkeys) { byte *q; int i; q = g_new(byte, emu->key_queue_len + nkeys); for (i = 0; i < nkeys; i++) { q[nkeys - i - 1] = keys[i]; record_key(emu, keys[i]); } if (emu->key_queue_len) memcpy(q + nkeys, emu->key_queue, emu->key_queue_len); g_free(emu->key_queue); emu->key_queue = q; emu->key_queue_len += nkeys; emu->key_queue_hold = 1; if (!emu->key_queue_timer) { emu->key_queue_timer = tilem_z80_add_timer(emu->calc, 1, 0, 1, &tmr_key_queue, emu); } } void tilem_calc_emulator_queue_keys(TilemCalcEmulator *emu, const byte *keys, int nkeys) { g_return_if_fail(emu != NULL); g_return_if_fail(keys != NULL); g_return_if_fail(nkeys > 0); tilem_calc_emulator_lock(emu); queue_keys(emu, keys, nkeys); tilem_calc_emulator_unlock(emu); } void tilem_calc_emulator_release_queued_key(TilemCalcEmulator *emu) { g_return_if_fail(emu != NULL); tilem_calc_emulator_lock(emu); if (emu->key_queue_timer) { emu->key_queue_hold = 0; } else if (emu->key_queue_pressed) { tilem_keypad_release_key(emu->calc, emu->key_queue_cur); emu->key_queue_pressed = 0; emu->key_queue_cur = 0; } tilem_calc_emulator_unlock(emu); if (emu->dbg && emu->dbg->keypad_dialog) tilem_keypad_dialog_refresh(emu->dbg->keypad_dialog); } gboolean tilem_calc_emulator_press_or_queue(TilemCalcEmulator *emu, int key) { byte b; gboolean status; g_return_val_if_fail(emu != NULL, FALSE); tilem_calc_emulator_lock(emu); if (emu->key_queue_timer) { b = key; queue_keys(emu, &b, 1); status = TRUE; } else { tilem_keypad_press_key(emu->calc, key); status = FALSE; } tilem_calc_emulator_unlock(emu); if (emu->dbg && emu->dbg->keypad_dialog) tilem_keypad_dialog_refresh(emu->dbg->keypad_dialog); return status; } TilemAnimation * tilem_calc_emulator_get_screenshot(TilemCalcEmulator *emu, gboolean grayscale) { TilemAnimation *anim; g_return_val_if_fail(emu != NULL, NULL); g_return_val_if_fail(emu->calc != NULL, NULL); anim = tilem_animation_new(emu->calc->hw.lcdwidth, emu->calc->hw.lcdheight); tilem_calc_emulator_lock(emu); if (grayscale && emu->glcd) tilem_gray_lcd_get_frame(emu->glcd, emu->tmp_lcd_buffer); else tilem_lcd_get_frame(emu->calc, emu->tmp_lcd_buffer); tilem_animation_append_frame(anim, emu->tmp_lcd_buffer, 1); tilem_calc_emulator_unlock(emu); return anim; } void tilem_calc_emulator_begin_animation(TilemCalcEmulator *emu, gboolean grayscale) { g_return_if_fail(emu != NULL); g_return_if_fail(emu->calc != NULL); tilem_calc_emulator_lock(emu); cancel_animation(emu); emu->anim = tilem_animation_new(emu->calc->hw.lcdwidth, emu->calc->hw.lcdheight); emu->anim_grayscale = grayscale; tilem_calc_emulator_unlock(emu); } TilemAnimation * tilem_calc_emulator_end_animation(TilemCalcEmulator *emu) { TilemAnimation *anim; g_return_val_if_fail(emu != NULL, NULL); g_return_val_if_fail(emu->anim != NULL, NULL); tilem_calc_emulator_lock(emu); anim = emu->anim; emu->anim = NULL; tilem_calc_emulator_unlock(emu); return anim; } /* Prompt for a ROM file to open */ int tilem_calc_emulator_prompt_open_rom(TilemCalcEmulator *emu) { char *dir, *filename; GError *err = NULL; const char *modelname; if (emu->rom_file_name) dir = g_path_get_dirname(emu->rom_file_name); else dir = g_get_current_dir(); filename = prompt_open_file("Open Calculator", GTK_WINDOW(get_toplevel(emu)), dir, "ROM files", "*.rom;*.clc;*.bin", "All files", "*", NULL); g_free(dir); if (!filename) return 0; if (tilem_calc_emulator_load_state(emu, filename, NULL, 0, &err)) { modelname = emu->calc->hw.name; tilem_config_set(modelname, "rom_file/f", emu->rom_file_name, "state_file/f", emu->state_file_name, NULL); tilem_config_set("recent", "last_model/s", modelname, NULL); } g_free(filename); if (err) { messagebox01(GTK_WINDOW(get_toplevel(emu)), GTK_MESSAGE_ERROR, "Unable to load calculator state", "%s", err->message); g_error_free(err); return -1; } else { return 1; } } /* Run slowly to play macro (used instead run_with_key() function) */ void run_with_key_slowly(TilemCalc* calc, int key) { tilem_z80_run_time(calc, 5000000, NULL); /* Wait */ tilem_keypad_press_key(calc, key); /* Press */ tilem_z80_run_time(calc, 10000, NULL); /* Wait (don't forget to wait) */ tilem_keypad_release_key(calc, key); /* Release */ tilem_z80_run_time(calc, 50, NULL); /* Wait */ }