view menu.c @ 1478:da1dce39e846 nuklear_ui

Refactored save slot related logic to reduce duplication and allow reuse in new UI. Get state loading/saving mostly working in new UI
author Michael Pavone <pavone@retrodev.com>
date Fri, 24 Nov 2017 12:04:02 -0800
parents 152a60c6787e
children 77a401044935
line wrap: on
line source

#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include "genesis.h"
#include "menu.h"
#include "backend.h"
#include "util.h"
#include "gst.h"
#include "paths.h"
#include "saves.h"

static menu_context *get_menu(genesis_context *gen)
{
	menu_context *menu = gen->extra;
	if (!menu) {
		gen->extra = menu = calloc(1, sizeof(menu_context));
		get_initial_browse_path(&menu->curpath);
	}
	return menu;
}

uint16_t menu_read_w(uint32_t address, void * vcontext)
{
	if ((address >> 1) == 14) {
		m68k_context *context = vcontext;
		menu_context *menu = get_menu(context->system);
		uint16_t value = menu->external_game_load;
		if (value) {
			printf("Read: %X\n", value);
		}
		menu->external_game_load = 0;
		return value;
	} else {
		//This should return the status of the last request with 0
		//meaning either the request is complete or no request is pending
		//in the current implementation, the operations happen instantly
		//in emulated time so we can always return 0
		return 0;
	}
}

int menu_dir_sort(const void *a, const void *b)
{
	const dir_entry *da, *db;
	da = a;
	db = b;
	if (da->is_dir != db->is_dir) {
		return db->is_dir - da->is_dir;
	}
	return strcasecmp(((dir_entry *)a)->name, ((dir_entry *)b)->name);
}

void copy_string_from_guest(m68k_context *m68k, uint32_t guest_addr, char *buf, size_t maxchars)
{
	char *cur;
	char *src = NULL;
	for (cur = buf; cur < buf+maxchars; cur+=2, guest_addr+=2, src+=2)
	{
		if (!src || !(guest_addr & 0xFFFF)) {
			//we may have walked off the end of a memory block, get a fresh native pointer
			src = get_native_pointer(guest_addr, (void **)m68k->mem_pointers, &m68k->options->gen);
			if (!src) {
				break;
			}
		}
		*cur = src[1];
		cur[1] = *src;
		if (!*src || !src[1]) {
			break;
		}
	}
	//make sure we terminate the string even if we did not hit a null terminator in the source
	buf[maxchars-1] = 0;
}

void copy_to_guest(m68k_context *m68k, uint32_t guest_addr, char *src, size_t tocopy)
{
	char *dst = NULL;
	for (char *cur = src; cur < src+tocopy; cur+=2, guest_addr+=2, dst+=2)
	{
		if (!dst || !(guest_addr & 0xFFFF)) {
			//we may have walked off the end of a memory block, get a fresh native pointer
			dst = get_native_pointer(guest_addr, (void **)m68k->mem_pointers, &m68k->options->gen);
			if (!dst) {
				break;
			}
		}
		dst[1] = *cur;
		*dst = cur[1];
	}
}

#define SAVE_INFO_BUFFER_SIZE (11*40)

uint32_t copy_dir_entry_to_guest(uint32_t dst, m68k_context *m68k, char *name, uint8_t is_dir)
{
	uint8_t *dest = get_native_pointer(dst, (void **)m68k->mem_pointers, &m68k->options->gen);
	if (!dest) {
		return 0;
	}
	*(dest++) = is_dir;
	*(dest++) = 1;
	dst += 2;
	uint8_t term = 0;
	for (char *cpos = name; *cpos; cpos++)
	{
		dest[1] = *cpos;
		dest[0] = cpos[1];
		if (cpos[1]) {
			cpos++;
		} else {
			term = 1;
		}
		dst += 2;
		if (!(dst & 0xFFFF)) {
			//we may have walked off the end of a memory block, get a fresh native pointer
			dest = get_native_pointer(dst, (void **)m68k->mem_pointers, &m68k->options->gen);
			if (!dest) {
				break;
			}
		} else {
			dest += 2;
		}
	}
	if (!term) {
		*(dest++) = 0;
		*dest = 0;
		dst += 2;
	}
	return dst;
}

void * menu_write_w(uint32_t address, void * context, uint16_t value)
{
	m68k_context *m68k = context;
	genesis_context *gen = m68k->system;
	menu_context *menu = get_menu(gen);
	if (menu->state) {
		uint32_t dst = menu->latch << 16 | value;
		switch (address >> 2)
		{
		case 0: {
#ifdef _WIN32
			//handle virtual "drives" directory
			if (menu->curpath[0] == PATH_SEP[0]) {
				char drivestrings[4096];
				if (sizeof(drivestrings) >= GetLogicalDriveStrings(sizeof(drivestrings), drivestrings)) {
					for (char *cur = drivestrings; *cur; cur += strlen(cur) + 1)
					{
						dst = copy_dir_entry_to_guest(dst, m68k, cur, 1);
					}
				}
				//terminate list
				uint8_t *dest = get_native_pointer(dst, (void **)m68k->mem_pointers, &m68k->options->gen);
				if (dest) {
					*dest = dest[1] = 0;
				}
				break;
			}
#endif
			size_t num_entries;
			dir_entry *entries = get_dir_list(menu->curpath, &num_entries);
			if (entries) {
				qsort(entries, num_entries, sizeof(dir_entry), menu_dir_sort);
			} else {
				warning("Failed to open directory %s: %s\n", menu->curpath, strerror(errno));
				entries = malloc(sizeof(dir_entry));
				entries->name = strdup("..");
				entries->is_dir = 1;
				num_entries = 1;
			}
#ifdef _WIN32
			if (menu->curpath[1] == ':' && !menu->curpath[2]) {
				//Add fake .. entry to allow navigation to virtual "drives" directory
				dst = copy_dir_entry_to_guest(dst, m68k, "..", 1);
			}
#endif
			char *ext_filter = strdup(tern_find_path_default(config, "ui\0extensions\0", (tern_val){.ptrval = "bin gen md smd sms gg"}, TVAL_PTR).ptrval);
			uint32_t num_exts = 0, ext_storage = 5;
			char **ext_list = malloc(sizeof(char *) * ext_storage);
			char *cur_filter = ext_filter;
			while (*cur_filter)
			{
				if (num_exts == ext_storage) {
					ext_storage *= 2;
					ext_list = realloc(ext_list, sizeof(char *) * ext_storage);
				}
				ext_list[num_exts++] = cur_filter;
				cur_filter = split_keyval(cur_filter);
			}
			for (size_t i = 0; dst && i < num_entries; i++)
			{
				if (num_exts && !entries[i].is_dir) {
					char *ext = path_extension(entries[i].name);
					if (!ext) {
						continue;
					}
					uint32_t extidx;
					for (extidx = 0; extidx < num_exts; extidx++)
					{
						if (!strcasecmp(ext, ext_list[extidx])) {
							break;
						}
					}
					if (extidx == num_exts) {
						continue;
					}
				}
				dst = copy_dir_entry_to_guest(dst,  m68k, entries[i].name, entries[i].is_dir);
			}
			free(ext_filter);
			free(ext_list);
			//terminate list
			uint8_t *dest = get_native_pointer(dst, (void **)m68k->mem_pointers, &m68k->options->gen);
			if (dest) {
				*dest = dest[1] = 0;
			}
			free_dir_list(entries, num_entries);
			break;
		}
		case 1: {
			char buf[4096];
			copy_string_from_guest(m68k, dst, buf, sizeof(buf));
			if (!strcmp(buf, "..")) {
#ifdef _WIN32
				if (menu->curpath[1] == ':' && !menu->curpath[2]) {
					menu->curpath[0] = PATH_SEP[0];
					menu->curpath[1] = 0;
					break;
				}
#endif
				size_t len = strlen(menu->curpath);
				while (len > 0) {
					--len;
					if (is_path_sep(menu->curpath[len])) {
						if (!len) {
							//special handling for /
							menu->curpath[len+1] = 0;
						} else {
							menu->curpath[len] = 0;
						}
						break;
					}
				}
			} else {
				char *tmp = menu->curpath;
#ifdef _WIN32
				if (menu->curpath[0] == PATH_SEP[0] && !menu->curpath[1]) {
					menu->curpath = strdup(buf);
				} else
#endif
				if (is_path_sep(menu->curpath[strlen(menu->curpath) - 1])) {
					menu->curpath = alloc_concat(menu->curpath, buf);
				} else {
					char const *pieces[] = {menu->curpath, PATH_SEP, buf};
					menu->curpath = alloc_concat_m(3, pieces);
				}
				free(tmp);
			}
			break;
		}
		case 2:
		case 8: {
			char buf[4096];
			copy_string_from_guest(m68k, dst, buf, sizeof(buf));
			char const *pieces[] = {menu->curpath, PATH_SEP, buf};
			char *selected = alloc_concat_m(3, pieces);
			if ((address >> 2) == 2) {
				gen->header.next_rom = selected;
				m68k->should_return = 1;
			} else {
				lockon_media(selected);
				free(selected);
			}
			break;
		}
		case 3: {
			switch (dst)
			{
			case 1:
				m68k->should_return = 1;
				gen->header.should_exit = 1;
				break;
			case 2:
				m68k->should_return = 1;
				break;
			}
			
			break;
		}
		case 4: {
			char *buffer = malloc(SAVE_INFO_BUFFER_SIZE);
			char *cur = buffer;
			if (gen->header.next_context && gen->header.next_context->save_dir) {
				char *end = buffer + SAVE_INFO_BUFFER_SIZE;
				uint32_t num_slots;
				save_slot_info *slots = get_slot_info(gen->header.next_context, &num_slots);
				for (uint32_t i = 0; i < num_slots; i++)
				{
					size_t desc_len = strlen(slots[i].desc) + 1;//+1 for string terminator
					char *after = cur + desc_len + 1;//+1 for list terminator
					if (after > cur) {
						desc_len -= after - cur;
					}
					memcpy(cur, slots[i].desc, desc_len);
					cur = after;
				}
				*cur = 0;//terminate list
			} else {
				*(cur++) = 0;
				*(cur++) = 0;
			}
			copy_to_guest(m68k, dst, buffer, cur-buffer);
			break;
		}
		case 5:
			//save state
			if (gen->header.next_context) {
				gen->header.next_context->save_state = dst + 1;
			}
			m68k->should_return = 1;
			break;
		case 6:
			//load state
			if (gen->header.next_context && gen->header.next_context->save_dir) {
				if (!gen->header.next_context->load_state(gen->header.next_context, dst)) {
					break;
				}
			}
			m68k->should_return = 1;
			break;
		case 7: 
			//read only port
			break;
		default:
			fprintf(stderr, "WARNING: write to undefined menu port %X\n", address);
		}
		menu->state = 0;
	} else {
		menu->latch = value;
		menu->state = 1;
	}
	if (m68k->should_return) {
		m68k->target_cycle = m68k->current_cycle;
	}

	return context;
}