pi: add support for IS Viewer 64

When -is-viewer is passed on the command line, create an IS Viewer
object that intercepts writes at 0x13FF0000. This is used by Ocarina of
Time Master Quest Debug to log debug messages.

The messages are encoded in EUC-JP, so we bring in iconv to convert that
to UTF-8 so the messages can be printed to modern consoles.

Props to jrra (@jkbenaim) and spinout for reverse engineering the
interface and documenting it: http://wiki.spinout182.com/w/IS64
This commit is contained in:
Mike Ryan 2017-02-28 19:53:10 -08:00
parent ac8f2283af
commit 8d0e7bb627
10 changed files with 144 additions and 7 deletions

View file

@ -20,6 +20,7 @@ endif(APPLE)
find_package(OpenAL REQUIRED)
find_package(Threads REQUIRED)
find_package(Iconv REQUIRED)
# If using GCC, configure it accordingly.
if (${CMAKE_C_COMPILER_ID} MATCHES GNU)
@ -310,6 +311,7 @@ set(OS_X11_SOURCES
set(PI_SOURCES
${PROJECT_SOURCE_DIR}/pi/controller.c
${PROJECT_SOURCE_DIR}/pi/is_viewer.c
)
set(RDP_SOURCES
@ -441,6 +443,7 @@ target_link_libraries(cen64
${EXTRA_OS_LIBS}
${OPENAL_LIBRARY}
${OPENGL_gl_LIBRARY}
${ICONV_LIBRARIES}
${X11_X11_LIB}
${CMAKE_THREAD_LIBS_INIT}
)

14
cen64.c
View file

@ -20,6 +20,7 @@
#include "os/common/rom_file.h"
#include "os/common/save_file.h"
#include "os/cpuid.h"
#include "pi/is_viewer.h"
#include "thread.h"
#include <stdlib.h>
@ -49,6 +50,7 @@ int cen64_main(int argc, const char **argv) {
struct save_file eeprom;
struct save_file sram;
struct save_file flashram;
struct is_viewer is, *is_in = NULL;
if (!cart_db_is_well_formed()) {
printf("Internal cart detection database is not well-formed.\n");
@ -84,6 +86,7 @@ int cen64_main(int argc, const char **argv) {
memset(&eeprom, 0, sizeof(eeprom));
memset(&sram, 0, sizeof(sram));
memset(&flashram, 0, sizeof(flashram));
memset(&is, 0, sizeof(is));
dd_variant = NULL;
if (load_roms(options.ddipl_path, options.ddrom_path, options.pifrom_path,
@ -122,6 +125,15 @@ int cen64_main(int argc, const char **argv) {
memset(flashram.ptr, 0xFF, FLASHRAM_SIZE);
}
if (options.is_viewer_present) {
if (!is_viewer_init(&is)) {
cen64_alloc_cleanup();
return EXIT_FAILURE;
} else {
is_in = &is;
}
}
// Allocate memory for and create the device.
if (cen64_alloc(&cen64_device_mem, sizeof(*device), false) == NULL) {
printf("Failed to allocate enough memory for a device.\n");
@ -133,7 +145,7 @@ int cen64_main(int argc, const char **argv) {
if (device_create(device, &ddipl, dd_variant, &ddrom,
&pifrom, &cart, &eeprom, &sram,
&flashram, controller, options.no_audio, options.no_video) == NULL) {
&flashram, is_in, controller, options.no_audio, options.no_video) == NULL) {
printf("Failed to create a device.\n");
status = EXIT_FAILURE;
}

View file

@ -43,7 +43,8 @@ struct cen64_device *device_create(struct cen64_device *device,
const struct rom_file *ddrom,
const struct rom_file *pifrom, const struct rom_file *cart,
const struct save_file *eeprom, const struct save_file *sram,
const struct save_file *flashram, const struct controller *controller,
const struct save_file *flashram, const struct is_viewer *is,
const struct controller *controller,
bool no_audio, bool no_video) {
// Initialize the bus.
@ -78,7 +79,7 @@ struct cen64_device *device_create(struct cen64_device *device,
}
// Initialize the PI.
if (pi_init(&device->pi, &device->bus, cart->ptr, cart->size, sram, flashram)) {
if (pi_init(&device->pi, &device->bus, cart->ptr, cart->size, sram, flashram, is)) {
debug("create_device: Failed to initialize the PI.\n");
return NULL;
}

View file

@ -60,7 +60,8 @@ cen64_cold struct cen64_device *device_create(struct cen64_device *device,
const struct rom_file *ddrom,
const struct rom_file *pifrom, const struct rom_file *cart,
const struct save_file *eeprom, const struct save_file *sram,
const struct save_file *flashram, const struct controller *controller,
const struct save_file *flashram, const struct is_viewer *is,
const struct controller *controller,
bool no_audio, bool no_video);
cen64_cold void device_exit(struct bus_controller *bus);

View file

@ -24,6 +24,7 @@ const struct cen64_options default_cen64_options = {
0, // eeprom_size
NULL, // sram_path
NULL, // flashram_path
0, // is_viewer_present
NULL, // controller
#ifdef _WIN32
false, // console
@ -128,6 +129,9 @@ int parse_options(struct cen64_options *options, int argc, const char *argv[]) {
options->flashram_path = argv[++i];
}
else if (!strcmp(argv[i], "-is-viewer"))
options->is_viewer_present = 1;
else if (!strcmp(argv[i], "-controller")) {
int num;
struct controller opt = { 0, };
@ -258,6 +262,7 @@ void print_command_line_usage(const char *invokation_string) {
" -headless : Run emulator without user-interface components.\n"
" -noaudio : Run emulator without audio.\n"
" -novideo : Run emulator without video.\n"
" -is-viewer : IS Viewer 64 present.\n"
"\n"
"Controller Options:\n"
" -controller num=<1-4> : Controller with no pak.\n"

View file

@ -23,6 +23,7 @@ struct cen64_options {
size_t eeprom_size;
const char *sram_path;
const char *flashram_path;
int is_viewer_present;
struct controller *controller;

View file

@ -13,6 +13,7 @@
#include "bus/controller.h"
#include "dd/controller.h"
#include "pi/controller.h"
#include "pi/is_viewer.h"
#include "ri/controller.h"
#include "vr4300/interface.h"
#include <assert.h>
@ -154,13 +155,14 @@ static int pi_dma_write(struct pi_controller *pi) {
// Initializes the PI.
int pi_init(struct pi_controller *pi, struct bus_controller *bus,
const uint8_t *rom, size_t rom_size, const struct save_file *sram,
const struct save_file *flashram) {
const struct save_file *flashram, struct is_viewer *is_viewer) {
pi->bus = bus;
pi->rom = rom;
pi->rom_size = rom_size;
pi->sram = sram;
pi->flashram_file = flashram;
pi->flashram.data = flashram->ptr;
pi->is_viewer = is_viewer;
pi->bytes_to_copy = 0;
return 0;
@ -171,6 +173,9 @@ int read_cart_rom(void *opaque, uint32_t address, uint32_t *word) {
struct pi_controller *pi = (struct pi_controller *) opaque;
unsigned offset = (address - ROM_CART_BASE_ADDRESS) & ~0x3;
if (pi->is_viewer && is_viewer_map(pi->is_viewer, address))
return read_is_viewer(pi->is_viewer, address, word);
// TODO: Need to figure out correct behaviour.
// Should this even happen to begin with?
if (pi->rom == NULL || offset > (pi->rom_size - sizeof(*word))) {
@ -197,7 +202,11 @@ int read_pi_regs(void *opaque, uint32_t address, uint32_t *word) {
// Writes a word to cartridge ROM.
int write_cart_rom(void *opaque, uint32_t address, uint32_t word, uint32_t dqm) {
//assert(0 && "Attempt to write to cart ROM.");
struct pi_controller *pi = (struct pi_controller *) opaque;
if (pi->is_viewer && is_viewer_map(pi->is_viewer, address))
return write_is_viewer(pi->is_viewer, address, word, dqm);
return 0;
}

View file

@ -52,6 +52,7 @@ struct pi_controller {
const struct save_file *sram;
const struct save_file *flashram_file;
struct flashram flashram;
struct is_viewer *is_viewer;
uint64_t counter;
uint32_t bytes_to_copy;
@ -62,7 +63,7 @@ struct pi_controller {
cen64_cold int pi_init(struct pi_controller *pi, struct bus_controller *bus,
const uint8_t *rom, size_t rom_size, const struct save_file *sram,
const struct save_file *flashram);
const struct save_file *flashram, struct is_viewer *is);
// Only invoke pi_cycle_ when the counter has expired (timeout).
void pi_cycle_(struct pi_controller *pi);

77
pi/is_viewer.c Normal file
View file

@ -0,0 +1,77 @@
#include <stdlib.h>
#include <string.h>
#include "common.h"
#include "is_viewer.h"
// arbitrarily chosen
#define IS_BUFFER_SIZE 0x200
int is_viewer_init(struct is_viewer *is) {
memset(is, 0, sizeof(*is));
// TODO support other addresses
is->base_address = IS_VIEWER_BASE_ADDRESS;
is->len = IS_VIEWER_ADDRESS_LEN;
is->buffer = calloc(IS_BUFFER_SIZE, 1);
is->output_buffer = calloc(IS_BUFFER_SIZE, 1);
is->output_buffer_conv = calloc(IS_BUFFER_SIZE * 3, 1);
is->cd = iconv_open("UTF-8", "EUC-JP");
if (is->buffer == NULL || is->output_buffer == NULL ||
is->output_buffer_conv == NULL)
return 0;
else
return 1;
}
int is_viewer_map(struct is_viewer *is, uint32_t address) {
return address >= is->base_address && address + 4 <= is->base_address + is->len;
}
int read_is_viewer(struct is_viewer *is, uint32_t address, uint32_t *word) {
uint32_t offset = address - is->base_address;
assert(offset + 4 <= is->len);
memcpy(word, is->buffer + offset, sizeof(*word));
*word = byteswap_32(*word);
return 0;
}
int write_is_viewer(struct is_viewer *is, uint32_t address, uint32_t word, uint32_t dqm) {
uint32_t offset = address - is->base_address;
assert(offset + 4 <= is->len);
if (offset == 0x14) {
if (word > 0) {
assert(is->output_buffer_pos + word < is->len);
memcpy(is->output_buffer + is->output_buffer_pos, is->buffer + 0x20, word);
is->output_buffer_pos += word;
is->output_buffer[is->output_buffer_pos] = '\0';
// once a full line is present, convert the output from EUC to UTF-8
if (memchr(is->output_buffer, '\n', is->output_buffer_pos)) {
char *inptr = (char *)is->output_buffer;
size_t len = strlen(inptr);
size_t outlen = 3 * len;
char *outptr = (char *)is->output_buffer_conv;
memset(is->output_buffer_conv, 0, IS_BUFFER_SIZE * 3);
iconv(is->cd, &inptr, &len, &outptr, &outlen);
printf("%s", is->output_buffer_conv);
memset(is->output_buffer, 0, is->output_buffer_pos);
is->output_buffer_pos = 0;
}
}
memset(is->buffer + 0x20, 0, word);
} else {
word = byteswap_32(word);
memcpy(is->buffer + offset, &word, sizeof(word));
}
return 0;
}

27
pi/is_viewer.h Normal file
View file

@ -0,0 +1,27 @@
#ifndef __IS_VIEWER_H__
#define __IS_VIEWER_H__
#include <iconv.h>
// IS Viewer
#define IS_VIEWER_BASE_ADDRESS 0x13FF0000
#define IS_VIEWER_ADDRESS_LEN 0x00001000
struct is_viewer {
uint32_t base_address;
uint32_t len;
uint8_t *buffer;
uint8_t *output_buffer;
size_t output_buffer_pos;
uint8_t *output_buffer_conv;
iconv_t cd;
};
int is_viewer_init(struct is_viewer *is);
int is_viewer_map(struct is_viewer *is, uint32_t address);
int read_is_viewer(struct is_viewer *is, uint32_t address, uint32_t *word);
int write_is_viewer(struct is_viewer *is, uint32_t address, uint32_t word, uint32_t dqm);
#endif /* __IS_VIEWER_H__ */