/* * Apple // emulator for *ix * * This software package is subject to the GNU General Public License * version 3 or later (your choice) as published by the Free Software * Foundation. * * Copyright 1994 Alexander Jean-Claude Bottema * Copyright 1995 Stephen Lee * Copyright 1997, 1998 Aaron Culliney * Copyright 1998, 1999, 2000 Michael Deutschmann * Copyright 2013-2015 Aaron Culliney * */ #include "common.h" #define SAVE_MAGICK "A2VM" #define SAVE_MAGICK2 "A2V2" #define SAVE_VERSION 2 #define SAVE_MAGICK_LEN sizeof(SAVE_MAGICK) typedef struct module_ctor_node_s { struct module_ctor_node_s *next; long order; startup_callback_f ctor; } module_ctor_node_s; static module_ctor_node_s *head = NULL; static bool emulatorShuttingDown = false; bool do_logging = true; // also controlled by NDEBUG FILE *error_log = NULL; const char *data_dir = NULL; char **argv = NULL; int argc = 0; CrashHandler_s *crashHandler = NULL; #if defined(CONFIG_DATADIR) static void _init_common(void) { LOG("Initializing common..."); data_dir = STRDUP(CONFIG_DATADIR PATH_SEPARATOR PACKAGE_NAME); } static __attribute__((constructor)) void __init_common(void) { emulator_registerStartupCallback(CTOR_PRIORITY_FIRST, &_init_common); } #elif defined(ANDROID) || defined(__APPLE__) // data_dir is set up elsewhere #else # error "Specify a CONFIG_DATADIR and PACKAGE_NAME" #endif static bool _save_state(int fd, const uint8_t * outbuf, ssize_t outmax) { ssize_t outlen = 0; do { if (TEMP_FAILURE_RETRY(outlen = write(fd, outbuf, outmax)) == -1) { ERRLOG("OOPS, error writing emulator save-state file"); break; } outbuf += outlen; outmax -= outlen; } while (outmax > 0); return outmax == 0; } static bool _load_state(int fd, uint8_t * inbuf, ssize_t inmax) { ssize_t inlen = 0; assert(inmax > 0); struct stat stat_buf; if (UNLIKELY(fstat(fd, &stat_buf) < 0)) { ERRLOG("OOPS, could not stat FD"); return false; } off_t fileSiz = stat_buf.st_size; off_t filePos = lseek(fd, 0, SEEK_CUR); if (UNLIKELY(filePos < 0)) { ERRLOG("OOPS, could not lseek FD"); return false; } if (UNLIKELY(filePos + inmax > fileSiz)) { LOG("OOPS, encountered truncated save-state file"); return false; } do { if (TEMP_FAILURE_RETRY(inlen = read(fd, inbuf, inmax)) == -1) { ERRLOG("error reading emulator save-state file"); break; } if (inlen == 0) { ERRLOG("error reading emulator save-state file (truncated)"); break; } inbuf += inlen; inmax -= inlen; } while (inmax > 0); return inmax == 0; } static int _load_magick(int fd) { // load header uint8_t magick[SAVE_MAGICK_LEN] = { 0 }; if (!_load_state(fd, magick, SAVE_MAGICK_LEN)) { return -1; } // check header if (memcmp(magick, SAVE_MAGICK2, SAVE_MAGICK_LEN) == 0) { return 2; } else if (memcmp(magick, SAVE_MAGICK, SAVE_MAGICK_LEN) == 0) { return 1; } ERRLOG("bad header magick in emulator save state file"); return -1; } bool emulator_saveState(int fd) { bool saved = false; #if !TESTING assert(cpu_isPaused() && "should be paused to save state"); #endif do { // save header if (!_save_state(fd, (const uint8_t *)SAVE_MAGICK2, SAVE_MAGICK_LEN)) { break; } StateHelper_s helper = { .fd = fd, .version = SAVE_VERSION, .save = &_save_state, .load = &_load_state, }; if (!disk6_saveState(&helper)) { break; } if (!vm_saveState(&helper)) { break; } if (!cpu65_saveState(&helper)) { break; } if (!timing_saveState(&helper)) { break; } if (!video_saveState(&helper)) { break; } if (!mb_saveState(&helper)) { break; } TEMP_FAILURE_RETRY(fsync(fd)); saved = true; } while (0); return saved; } bool emulator_loadState(int fd, int fdA, int fdB) { bool loaded = false; #if !TESTING assert(cpu_isPaused() && "should be paused to load state"); #endif video_setDirty(A2_DIRTY_FLAG); do { int version = _load_magick(fd); if (version < 0) { break; } StateHelper_s helper = { .fd = fd, .version = version, .diskFdA = fdA, .diskFdB = fdB, .save = &_save_state, .load = &_load_state, }; if (!disk6_loadState(&helper)) { break; } if (!vm_loadState(&helper)) { break; } if (!cpu65_loadState(&helper)) { break; } if (version >= 2) { if (!timing_loadState(&helper)) { break; } } if (!video_loadState(&helper)) { break; } if (version >= 2) { if (!mb_loadState(&helper)) { break; } } // sanity-check whole file was read struct stat stat_buf; if (fstat(fd, &stat_buf) < 0) { ERRLOG("OOPS, could not stat FD"); } off_t fileSiz = stat_buf.st_size; off_t filePos = lseek(fd, 0, SEEK_CUR); if (filePos < 0) { ERRLOG("OOPS, could not lseek FD"); } if (UNLIKELY(filePos != fileSiz)) { LOG("OOPS, state file read: %lu total: %lu", filePos, fileSiz); } loaded = true; } while (0); if (!loaded) { LOG("OOPS, problem(s) encountered loading emulator save-state file"); } return loaded; } bool emulator_stateExtractDiskPaths(int fd, JSON_ref json) { bool loaded = false; do { int version = _load_magick(fd); if (version < 0) { break; } StateHelper_s helper = { .fd = fd, .version = version, .diskFdA = -1, .diskFdB = -1, .save = &_save_state, .load = &_load_state, }; if (!disk6_stateExtractDiskPaths(&helper, json)) { break; } loaded = true; } while (0); if (fd >= 0) { // Ensure that we leave the file descriptor ready for a call to emulator_loadState() off_t ret = lseek(fd, 0, SEEK_SET); if (ret != 0) { ERRLOG("OOPS : state file lseek() failed!"); } } if (!loaded) { LOG("OOPS, problem(s) encountered loading emulator save-state file"); } return loaded; } static void _shutdown_threads(void) { #if defined(__linux__) && !defined(ANDROID) LOG("Emulator waiting for other threads to clean up..."); do { DIR *dir = opendir("/proc/self/task"); if (!dir) { ERRLOG("Cannot open /proc/self/task !"); break; } int thread_count = 0; struct dirent *d = NULL; while ((d = readdir(dir)) != NULL) { if (strncmp(".", d->d_name, 2) == 0) { // ignore } else if (strncmp("..", d->d_name, 3) == 0) { // ignore } else { ++thread_count; } } closedir(dir); assert(thread_count >= 1 && "there must at least be one thread =P"); if (thread_count == 1) { break; } static struct timespec ts = { .tv_sec=0, .tv_nsec=33333333 }; nanosleep(&ts, NULL); // 30Hz framerate } while (1); #endif } void emulator_registerStartupCallback(long order, startup_callback_f ctor) { static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_lock(&mutex); module_ctor_node_s *node = MALLOC(sizeof(module_ctor_node_s)); assert(node); node->next = NULL; node->order = order; node->ctor = ctor; module_ctor_node_s *p0 = NULL; module_ctor_node_s *p = head; while (p && (order > p->order)) { p0 = p; p = p->next; } if (p0) { p0->next = node; } else { head = node; } node->next = p; pthread_mutex_unlock(&mutex); } void emulator_ctors(void) { module_ctor_node_s *p = head; head = NULL; while (p) { p->ctor(); module_ctor_node_s *next = p->next; FREE(p); p = next; } head = NULL; } void emulator_start(void) { emulator_ctors(); prefs_load(); // user prefs prefs_sync(NULL); #if defined(INTERFACE_CLASSIC) && !TESTING c_keys_set_key(kF8); // show credits before emulation start #endif #if !defined(__APPLE__) && !defined(ANDROID) video_init(); #endif timing_startCPU(); } void emulator_shutdown(void) { emulatorShuttingDown = true; video_shutdown(); prefs_shutdown(); timing_stopCPU(); _shutdown_threads(); } bool emulator_isShuttingDown(void) { return emulatorShuttingDown; } #if !defined(__APPLE__) && !defined(ANDROID) int main(int _argc, char **_argv) { argc = _argc; argv = _argv; #if TESTING # if TEST_CPU // Currently this test is the only one that blocks current thread and runs as a black screen extern int test_cpu(int, char *[]); test_cpu(argc, argv); # elif TEST_DISK extern int test_disk(int, char *[]); test_disk(argc, argv); # elif TEST_DISPLAY extern int test_display(int, char *[]); test_display(argc, argv); # elif TEST_PREFS extern void test_prefs(int, char *[]); test_prefs(argc, argv); # elif TEST_TRACE extern void test_trace(int, char *[]); test_trace(argc, argv); # elif TEST_UI extern int test_ui(int, char *[]); test_ui(argc, argv); # elif TEST_VM extern int test_vm(int, char *[]); test_vm(argc, argv); # else # error "OOPS, no testsuite specified" # endif #endif cpu_pause(); emulator_start(); cpu_resume(); video_main_loop(); emulator_shutdown(); LOG("Emulator exit ..."); return 0; } #endif