mirror of
https://github.com/raphnet/gc_n64_usb-v3
synced 2024-12-21 23:08:53 -05:00
move tools away to a separate project
This commit is contained in:
parent
ddb63cf8e9
commit
8c3e9c8eec
14
tools/.gitignore
vendored
14
tools/.gitignore
vendored
@ -1,14 +0,0 @@
|
||||
gui.xml
|
||||
gcn64cfg.glade~
|
||||
mempak_convert
|
||||
mempak_extract_note
|
||||
mempak_format
|
||||
mempak_insert_note
|
||||
mempak_ls
|
||||
mempak_rm
|
||||
gcn64ctl
|
||||
gcn64ctl_gui
|
||||
*.swp
|
||||
*.mpk
|
||||
*.n64
|
||||
*.exe
|
@ -1,86 +0,0 @@
|
||||
CC=gcc
|
||||
LD=$(CC)
|
||||
VERSION=1.0
|
||||
|
||||
UNAME := $(shell uname -s)
|
||||
ifeq ($(UNAME), Linux)
|
||||
HIDAPI_NAME=hidapi-hidraw
|
||||
else
|
||||
HIDAPI_NAME=hidapi
|
||||
endif
|
||||
|
||||
|
||||
ifeq ($(shell uname -o), Msys)
|
||||
COMPAT_OBJS=sleep.o memmem.o strcasestr.o app.o
|
||||
PLATFORM_CFLAGS=-DWINDOWS
|
||||
EXTRA_LDFLAGS=-mwindows # uncomment for console output
|
||||
MAKENSIS=/c/Program\ Files\ \(x86\)/NSIS/makensis
|
||||
endif
|
||||
|
||||
CFLAGS=-Wall -g `pkg-config $(HIDAPI_NAME) --cflags` --std=c99 $(PLATFORM_CFLAGS)
|
||||
LDFLAGS=`pkg-config $(HIDAPI_NAME) --libs` -g
|
||||
GTK_CFLAGS=`pkg-config --cflags gtk+-3.0 gmodule-2.0`
|
||||
GTK_LDFLAGS=`pkg-config --libs gtk+-3.0 gmodule-2.0`
|
||||
|
||||
PREFIX=/usr/local
|
||||
|
||||
PROGS=gcn64ctl mempak_ls mempak_format mempak_extract_note mempak_insert_note mempak_rm mempak_convert gcn64ctl_gui
|
||||
MEMPAKLIB_OBJS=mempak.o mempak_fs.o $(COMPAT_OBJS)
|
||||
|
||||
.PHONY : clean install release_windows
|
||||
|
||||
all: $(PROGS) gui.xml
|
||||
|
||||
gcn64ctl_gui: gcn64ctl_gui.o gcn64ctl_gui_mpkedit.o gcn64.o gcn64lib.o hexdump.o ihex.o mempak_gcn64usb.o $(MEMPAKLIB_OBJS)
|
||||
$(LD) $^ $(LDFLAGS) $(GTK_LDFLAGS) -o $@ $(EXTRA_LDFLAGS)
|
||||
|
||||
gcn64ctl: main.o gcn64.o gcn64lib.o hexdump.o gc2n64_adapter.o ihex.o delay.o mempak_gcn64usb.o $(MEMPAKLIB_OBJS)
|
||||
$(LD) $^ $(LDFLAGS) -o $@
|
||||
|
||||
gcn64ctl_gui.o: gcn64ctl_gui.c gcn64ctl_gui.h
|
||||
$(CC) $(CFLAGS) $(GTK_CFLAGS) -c $<
|
||||
|
||||
app.o: app.rc icon.ico
|
||||
windres app.rc -o app.o
|
||||
|
||||
gui.xml: gcn64cfg.glade
|
||||
grep -v requires gcn64cfg.glade > gui.xml
|
||||
|
||||
gcn64ctl_gui_mpkedit.o: gcn64ctl_gui_mpkedit.c gcn64ctl_gui_mpkedit.h
|
||||
$(CC) $(CFLAGS) $(GTK_CFLAGS) -c $<
|
||||
|
||||
mempak_convert: mempak_convert.o $(MEMPAKLIB_OBJS)
|
||||
$(LD) $^ $(LDFLAGS) -o $@
|
||||
|
||||
mempak_rm: mempak_rm.o $(MEMPAKLIB_OBJS)
|
||||
$(LD) $^ $(LDFLAGS) -o $@
|
||||
|
||||
mempak_insert_note: mempak_insert_note.o $(MEMPAKLIB_OBJS)
|
||||
$(LD) $^ $(LDFLAGS) -o $@
|
||||
|
||||
mempak_extract_note: mempak_extract_note.o $(MEMPAKLIB_OBJS)
|
||||
$(LD) $^ $(LDFLAGS) -o $@
|
||||
|
||||
mempak_ls: mempak_ls.o $(MEMPAKLIB_OBJS)
|
||||
$(LD) $^ $(LDFLAGS) -o $@
|
||||
|
||||
mempak_format: mempak_format.o $(MEMPAKLIB_OBJS)
|
||||
$(LD) $^ $(LDFLAGS) -o $@
|
||||
|
||||
|
||||
%.o: %.c %.h
|
||||
$(CC) $(CFLAGS) -c $<
|
||||
|
||||
clean:
|
||||
rm -f *.o *.exe $(PROGS)
|
||||
|
||||
install:
|
||||
@echo "Install not done yet. Sorry"
|
||||
|
||||
release_windows:
|
||||
cp $(addsuffix .exe,$(PROGS)) release_windows
|
||||
cp gui.xml release_windows
|
||||
cp project_image.png release_windows
|
||||
$(MAKENSIS) //DVERSION=$(VERSION) gcn64ctl.nsi
|
||||
ls installers
|
||||
@echo "Done"
|
@ -1 +0,0 @@
|
||||
1 ICON "icon.ico"
|
@ -1,43 +0,0 @@
|
||||
/* gc_n64_usb : Gamecube or N64 controller to USB adapter firmware
|
||||
Copyright (C) 2007-2015 Raphael Assenat <raph@raphnet.net>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifdef LIBDRAGON
|
||||
#include <libdragon.h>
|
||||
|
||||
void _delay_us(unsigned long us)
|
||||
{
|
||||
wait_ms(us/1000);
|
||||
}
|
||||
|
||||
void _delay_s(unsigned long s)
|
||||
{
|
||||
wait_ms(s*1000);
|
||||
}
|
||||
|
||||
#else
|
||||
#include <unistd.h>
|
||||
|
||||
void _delay_us(unsigned long us)
|
||||
{
|
||||
usleep(us);
|
||||
}
|
||||
|
||||
void _delay_s(unsigned long s)
|
||||
{
|
||||
sleep(s);
|
||||
}
|
||||
|
||||
#endif
|
@ -1,2 +0,0 @@
|
||||
void _delay_us(unsigned long us);
|
||||
void _delay_s(unsigned long s);
|
@ -1,863 +0,0 @@
|
||||
/* gc_n64_usb : Gamecube or N64 controller to USB adapter firmware
|
||||
Copyright (C) 2007-2015 Raphael Assenat <raph@raphnet.net>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#define _GNU_SOURCE // for memmem
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "gcn64lib.h"
|
||||
#include "gc2n64_adapter.h"
|
||||
#include "hexdump.h"
|
||||
#include "ihex.h"
|
||||
#include "delay.h"
|
||||
|
||||
#ifndef ARRAY_SIZE
|
||||
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
|
||||
#endif
|
||||
|
||||
int gc2n64_adapter_echotest(gcn64_hdl_t hdl, int channel, int verbose)
|
||||
{
|
||||
unsigned char cmd[30];
|
||||
unsigned char buf[30];
|
||||
int i, n;
|
||||
|
||||
cmd[0] = 'R';
|
||||
cmd[1] = 0x00; // echo
|
||||
for (i=0; i<28; i++) {
|
||||
cmd[i+2] = 'A'+i;
|
||||
}
|
||||
|
||||
n = gcn64lib_rawSiCommand(hdl, channel, cmd, sizeof(buf), buf, sizeof(buf));
|
||||
if (n<0) {
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
if (verbose) {
|
||||
if ((n != sizeof(buf)) || memcmp(cmd, buf, sizeof(buf))) {
|
||||
printf("Test failed\n");
|
||||
printf(" Sent [%d]: ", (int)sizeof(cmd)); printHexBuf(cmd, sizeof(cmd));
|
||||
printf("Received [%d]: ", n); printHexBuf(buf, n);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return (n!= sizeof(buf)) || memcmp(cmd, buf, sizeof(buf));
|
||||
}
|
||||
|
||||
int gc2n64_adapter_storeCurrentMapping(gcn64_hdl_t hdl, int channel, int dst_slot)
|
||||
{
|
||||
int n;
|
||||
unsigned char cmd[3];
|
||||
|
||||
cmd[0] = 'R';
|
||||
cmd[1] = 0x04; // Save current mapping
|
||||
cmd[2] = dst_slot;
|
||||
|
||||
n = gcn64lib_rawSiCommand(hdl, channel, cmd, sizeof(cmd), cmd, 1);
|
||||
if (n<0) {
|
||||
return n;
|
||||
}
|
||||
if (n != 1) {
|
||||
fprintf(stderr, "Communication error while storing mapping\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (cmd[0] == 0x00) {
|
||||
return gc2n64_adapter_waitNotBusy(hdl, channel, 0);
|
||||
}
|
||||
else {
|
||||
fprintf(stderr, "storeCurrentMapping: Command NACKed\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int gc2n64_adapter_setMapping(gcn64_hdl_t hdl, int channel, struct gc2n64_adapter_mapping *mapping)
|
||||
{
|
||||
unsigned char buf[64];
|
||||
unsigned char mapdata[64];
|
||||
int i, n;
|
||||
int maplen, togo, done, chunk;
|
||||
|
||||
|
||||
maplen = mapping->n_pairs * 2;
|
||||
|
||||
if (maplen > sizeof(mapdata)) {
|
||||
fprintf(stderr, "Mapping too large\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i=0; i<mapping->n_pairs; i++) {
|
||||
mapdata[i*2] = mapping->pairs[i].gc;
|
||||
mapdata[i*2 + 1] = mapping->pairs[i].n64;
|
||||
}
|
||||
|
||||
printf("Map data : ");
|
||||
printHexBuf(mapdata, maplen);
|
||||
|
||||
togo = maplen;
|
||||
done = 0;
|
||||
chunk = 0;
|
||||
while (togo) {
|
||||
int len;
|
||||
|
||||
if (togo > 32) {
|
||||
len = 32;
|
||||
} else {
|
||||
len = togo;
|
||||
}
|
||||
|
||||
buf[0] = 'R';
|
||||
buf[1] = 0x03; // set mapping
|
||||
buf[2] = chunk;
|
||||
memcpy(buf + 3, mapdata + done, len);
|
||||
done+= len;
|
||||
|
||||
// printf("Mapping chunk : ");
|
||||
// printHexBuf(buf, len + 2);
|
||||
|
||||
n = gcn64lib_rawSiCommand(hdl, channel, buf, len + 3, buf, 1);
|
||||
if (n<0) {
|
||||
return n;
|
||||
}
|
||||
if (n != 1) {
|
||||
fprintf(stderr, "Communication error setting mapping\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
togo -= len;
|
||||
chunk++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gc2n64_adapter_getMapping(gcn64_hdl_t hdl, int channel, int mapping_id, struct gc2n64_adapter_mapping *dst_mapping)
|
||||
{
|
||||
unsigned char buf[64];
|
||||
unsigned char cmd[4];
|
||||
int n;
|
||||
int mapping_size;
|
||||
int togo;
|
||||
|
||||
cmd[0] = 'R';
|
||||
cmd[1] = 0x02; // Get mapping
|
||||
cmd[2] = mapping_id;
|
||||
cmd[3] = 0; // chunk 0 (size)
|
||||
|
||||
n = gcn64lib_rawSiCommand(hdl, channel, cmd, 4, buf, 1);
|
||||
if (n<0)
|
||||
return n;
|
||||
|
||||
if (n == 1) {
|
||||
int i, pos;
|
||||
mapping_size = buf[0];
|
||||
// printf("Mapping %d size: %d\n", mapping_id, mapping_size);
|
||||
|
||||
togo = mapping_size;
|
||||
for (pos=0, i=0; pos<mapping_size; i++) {
|
||||
cmd[0] = 'R';
|
||||
cmd[1] = 0x02; // Get mapping
|
||||
cmd[2] = mapping_id;
|
||||
cmd[3] = i+1; // chunk 1 is first 32 byte block, 2nd is next 32 bytes, etc
|
||||
// printf("Getting block %d\n", i+1);
|
||||
n = gcn64lib_rawSiCommand(hdl, channel, cmd, 4, buf + pos, togo > 32 ? 32 : togo);
|
||||
if (n<0) {
|
||||
return n;
|
||||
}
|
||||
// printf("ret: %d\n", n);
|
||||
if (n==0)
|
||||
break;
|
||||
pos += n;
|
||||
togo -= n;
|
||||
}
|
||||
|
||||
//printf("Received %d bytes\n", pos);
|
||||
if (n%2) {
|
||||
fprintf(stderr, "Error: Odd length mapping received\n");
|
||||
printHexBuf(buf, pos);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// TODO : Decode this to dst_mapping
|
||||
dst_mapping->n_pairs = pos/2;
|
||||
for (i=0; i<dst_mapping->n_pairs; i++) {
|
||||
dst_mapping->pairs[i].gc = buf[i*2];
|
||||
dst_mapping->pairs[i].n64 = buf[i*2+1];
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *gc2n64_adapter_getMappingSlotName(unsigned char id, int default_context)
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
case MAPPING_SLOT_BUILTIN_CURRENT:
|
||||
if (default_context) {
|
||||
return "[Built-in default]";
|
||||
} else {
|
||||
return "[Current mapping]";
|
||||
}
|
||||
case MAPPING_SLOT_DPAD_UP: return "[D-Pad UP]";
|
||||
case MAPPING_SLOT_DPAD_DOWN: return "[D-Pad DOWN]";
|
||||
case MAPPING_SLOT_DPAD_LEFT: return "[D-Pad LEFT]";
|
||||
case MAPPING_SLOT_DPAD_RIGHT: return "[D-Pad RIGHT]";
|
||||
}
|
||||
return "Invalid ID";
|
||||
}
|
||||
|
||||
const char *gc2n64_adapter_getGCname(unsigned char id)
|
||||
{
|
||||
const char *names[] = {
|
||||
"A","B","Z","Start",
|
||||
"L","R",
|
||||
"C-stick up (50% threshold)",
|
||||
"C-stick down (50% threshold)",
|
||||
"C-stick left (50% threshold)",
|
||||
"C-stick right (50% threshold)",
|
||||
"Dpad-up","Dpad-down","Dpad-left","Dpad-right",
|
||||
"Joystick left-right axis","Joystick up-down axis",
|
||||
// Extras
|
||||
"X","Y",
|
||||
"Joystick up (50% threshold)", "Joystick down (50% threshold)",
|
||||
"Joystick left (50% threshold)", "Joystick right (50% threshold)",
|
||||
"Analogic L slider (50% threshold)",
|
||||
"Analogic R slider (50% threshold)",
|
||||
"C-stick left-right axis","C-stick up-down axis",
|
||||
};
|
||||
|
||||
if (id == 0xff)
|
||||
return "None";
|
||||
|
||||
if (id < 0 || id >= ARRAY_SIZE(names)) {
|
||||
return "Error";
|
||||
}
|
||||
return names[id];
|
||||
}
|
||||
|
||||
const char *gc2n64_adapter_getN64name(unsigned char id)
|
||||
{
|
||||
const char *names[] = {
|
||||
"A","B","Z","Start","L","R",
|
||||
"C-up","C-down","C-left","C-right",
|
||||
"Dpad-up","Dpad-down","Dpad-left","Dpad-right",
|
||||
"Joystick left-right axis","Joystick up-down axis",
|
||||
"Joystick up", "Joystick down",
|
||||
"Joystick left", "Joystick right",
|
||||
"None"
|
||||
};
|
||||
|
||||
if (id == 0xff)
|
||||
return "None";
|
||||
|
||||
if (id < 0 || id >= ARRAY_SIZE(names)) {
|
||||
return "Error";
|
||||
}
|
||||
return names[id];
|
||||
}
|
||||
|
||||
struct gc2n64_adapter_mapping *gc2n64_adapter_loadMapping(const char *srcfile)
|
||||
{
|
||||
FILE *fptr;
|
||||
struct gc2n64_adapter_mapping *map = NULL;;
|
||||
char linebuf[64];
|
||||
int line = 0, pair = 0;
|
||||
|
||||
fptr = fopen(srcfile, "r");
|
||||
if (!fptr) {
|
||||
perror("fopen");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
map = malloc(sizeof(struct gc2n64_adapter_mapping));
|
||||
if (!map) {
|
||||
perror("malloc");
|
||||
goto err;
|
||||
}
|
||||
|
||||
do {
|
||||
if (fgets(linebuf, sizeof(linebuf), fptr)) {
|
||||
int gc, n64, n;
|
||||
line++;
|
||||
|
||||
if (line == 1) {
|
||||
const char *magic = "# gc2n64 mapping";
|
||||
if (strncmp(magic, linebuf, strlen(magic))) {
|
||||
fprintf(stderr, "Does not appear to be a valid mapping file\n");
|
||||
goto err;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
n = sscanf(linebuf, "%03d;%03d", &gc, &n64);
|
||||
if (n != 2) {
|
||||
// printf("Ignoring line %d\n", line);
|
||||
} else {
|
||||
// printf("%d -> %d\n", gc, n64);
|
||||
map->pairs[pair].gc = gc;
|
||||
map->pairs[pair].n64 = n64;
|
||||
|
||||
pair++;
|
||||
if (pair >= GC2N64_MAX_MAPPING_PAIRS) {
|
||||
fprintf(stderr, "too many pairs, cannot load mapping.\n");
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (!feof(fptr));
|
||||
|
||||
map->n_pairs = pair;
|
||||
|
||||
fclose(fptr);
|
||||
return map;
|
||||
|
||||
err:
|
||||
if (map) {
|
||||
free(map);
|
||||
}
|
||||
fclose(fptr);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int gc2n64_adapter_saveMapping(struct gc2n64_adapter_mapping *map, const char *dstfile)
|
||||
{
|
||||
FILE *fptr;
|
||||
int i;
|
||||
|
||||
fptr = fopen(dstfile, "w");
|
||||
if (!fptr) {
|
||||
perror("fopen");
|
||||
return -1;
|
||||
}
|
||||
|
||||
fprintf(fptr, "# gc2n64 mapping\n");
|
||||
for (i=0; i<map->n_pairs; i++) {
|
||||
fprintf(fptr, "%03d;%03d # %s -> %s\n",
|
||||
map->pairs[i].gc, map->pairs[i].n64,
|
||||
gc2n64_adapter_getGCname(map->pairs[i].gc),
|
||||
gc2n64_adapter_getN64name(map->pairs[i].n64));
|
||||
}
|
||||
fflush(fptr);
|
||||
fclose(fptr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void gc2n64_adapter_printMapping(struct gc2n64_adapter_mapping *map)
|
||||
{
|
||||
int i;
|
||||
int is_default;
|
||||
|
||||
for (i=0; i<map->n_pairs; i++) {
|
||||
// Do not display the terminator
|
||||
if (map->pairs[i].gc == 0xff || map->pairs[i].n64 == 0xff) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* 0 .. 15 is a 1:1 (same button name) mapping by default */
|
||||
if (map->pairs[i].gc < 16) {
|
||||
if (map->pairs[i].gc == map->pairs[i].n64) {
|
||||
is_default = 1;
|
||||
}
|
||||
else {
|
||||
is_default = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// 16 and above maps to NONE by default
|
||||
if (map->pairs[i].n64 == 20) {
|
||||
is_default = 1;
|
||||
} else {
|
||||
is_default = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_default) {
|
||||
printf("%s -> %s, ", gc2n64_adapter_getGCname(map->pairs[i].gc),
|
||||
gc2n64_adapter_getN64name(map->pairs[i].n64));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void gc2n64_adapter_printInfo(struct gc2n64_adapter_info *inf)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!inf->in_bootloader) {
|
||||
printf("gc_to_n64 adapter info: {\n");
|
||||
|
||||
printf("\tDefault mapping id: %d (%s)\n", inf->app.default_mapping_id, gc2n64_adapter_getMappingSlotName(inf->app.default_mapping_id, 1) );
|
||||
printf("\tDeadzone enabled: %d\n", inf->app.deadzone_enabled);
|
||||
printf("\tOld v1.5 conversion: %d\n", inf->app.old_v1_5_conversion);
|
||||
printf("\tFirmware version: %s\n", inf->app.version);
|
||||
printf("\tUpgradable: %s\n", inf->app.upgradeable ? "Yes":"No (Atmega8)");
|
||||
for (i=0; i<GC2N64_NUM_MAPPINGS; i++) {
|
||||
printf("\tMapping %d (%-13s): { ", i, gc2n64_adapter_getMappingSlotName(i, 0));
|
||||
gc2n64_adapter_printMapping(&inf->app.mappings[i]);
|
||||
printf(" }\n");
|
||||
}
|
||||
} else {
|
||||
printf("gc_to_n64 adapter in bootloader mode: {\n");
|
||||
|
||||
printf("\tBootloader firmware version: %s\n", inf->bootldr.version);
|
||||
printf("\tMCU page size: %d bytes\n", inf->bootldr.mcu_page_size);
|
||||
printf("\tBootloader code start address: 0x%04x\n", inf->bootldr.bootloader_start_address);
|
||||
}
|
||||
|
||||
printf("}\n");
|
||||
}
|
||||
|
||||
int gc2n64_adapter_getInfo(gcn64_hdl_t hdl, int channel, struct gc2n64_adapter_info *inf)
|
||||
{
|
||||
unsigned char buf[32];
|
||||
int n;
|
||||
|
||||
buf[0] = 'R';
|
||||
buf[1] = 0x01; // Get device info
|
||||
|
||||
n = gcn64lib_rawSiCommand(hdl, channel, buf, 2, buf, sizeof(buf));
|
||||
if (n<0)
|
||||
return n;
|
||||
|
||||
if (n > 0) {
|
||||
// On N64, when receiving an all 0xFF reply, catch it here.
|
||||
if (buf[0] == 0xff)
|
||||
return -1;
|
||||
|
||||
if (!inf)
|
||||
return 0;
|
||||
|
||||
inf->in_bootloader = buf[0];
|
||||
|
||||
if (!inf->in_bootloader) {
|
||||
inf->app.default_mapping_id = buf[1];
|
||||
inf->app.deadzone_enabled = buf[2];
|
||||
inf->app.old_v1_5_conversion = buf[3];
|
||||
inf->app.upgradeable = buf[9];
|
||||
inf->app.version[sizeof(inf->app.version)-1]=0;
|
||||
strncpy(inf->app.version, (char*)buf+10, sizeof(inf->app.version)-1);
|
||||
} else {
|
||||
inf->bootldr.mcu_page_size = buf[1];
|
||||
inf->bootldr.bootloader_start_address = buf[2] << 8 | buf[3];
|
||||
inf->bootldr.version[sizeof(inf->bootldr.version)-1]=0;
|
||||
strncpy(inf->bootldr.version, (char*)buf+10, sizeof(inf->bootldr.version)-1);
|
||||
}
|
||||
|
||||
for (n=0; n<GC2N64_NUM_MAPPINGS; n++) {
|
||||
gc2n64_adapter_getMapping(hdl, channel, n, &inf->app.mappings[n]);
|
||||
}
|
||||
|
||||
} else {
|
||||
printf("No answer (old version?)\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gc2n64_adapter_isBusy(gcn64_hdl_t hdl, int channel)
|
||||
{
|
||||
unsigned char buf[64];
|
||||
int n;
|
||||
|
||||
buf[0] = 'R';
|
||||
buf[1] = 0xf9;
|
||||
|
||||
n = gcn64lib_rawSiCommand(hdl, channel, buf, 2, buf, 1);
|
||||
if (n<0)
|
||||
return n;
|
||||
|
||||
if (n != 1) {
|
||||
return 2; // Busy inferred from lack of answer
|
||||
}
|
||||
|
||||
if (buf[0] != 0x00) {
|
||||
return 1; // Busy
|
||||
}
|
||||
|
||||
return 0; // Idle
|
||||
}
|
||||
|
||||
int gc2n64_adapter_waitNotBusy(gcn64_hdl_t hdl, int channel, int verbose)
|
||||
{
|
||||
char spinner[4] = { '|','/','-','\\' };
|
||||
int busy, no_reply_count=0;
|
||||
int c=0;
|
||||
|
||||
while ((busy = gc2n64_adapter_isBusy(hdl, channel)))
|
||||
{
|
||||
if (busy < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (busy == 2) {
|
||||
no_reply_count++;
|
||||
if (no_reply_count > 200) {
|
||||
fprintf(stderr, "Adapter answer timeout\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
printf("%c\b", spinner[c%4]); fflush(stdout);
|
||||
c++;
|
||||
_delay_us(50000);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gc2n64_adapter_boot_eraseAll(gcn64_hdl_t hdl, int channel)
|
||||
{
|
||||
unsigned char buf[64];
|
||||
int n;
|
||||
|
||||
buf[0] = 'R';
|
||||
buf[1] = 0xf0;
|
||||
|
||||
n = gcn64lib_rawSiCommand(hdl, channel, buf, 2, buf, 1);
|
||||
if (n<0)
|
||||
return n;
|
||||
|
||||
if (n != 1) {
|
||||
fprintf(stderr, "Invalid answer. %d bytes received.\n", n);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (buf[0] != 0x00) {
|
||||
fprintf(stderr, "eraseAll request NACK!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gc2n64_adapter_boot_readBlock(gcn64_hdl_t hdl, int channel, unsigned int block_id, unsigned char dst[32])
|
||||
{
|
||||
unsigned char buf[32];
|
||||
int n;
|
||||
|
||||
buf[0] = 'R';
|
||||
buf[1] = 0xf1;
|
||||
buf[2] = block_id >> 8;
|
||||
buf[3] = block_id & 0xff;
|
||||
|
||||
n = gcn64lib_rawSiCommand(hdl, channel, buf, 4, buf, sizeof(buf));
|
||||
if (n<0)
|
||||
return n;
|
||||
|
||||
if (n != 32) {
|
||||
fprintf(stderr, "Invalid answer\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(dst, buf, 32);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gc2n64_adapter_dumpFlash(gcn64_hdl_t hdl, int channel)
|
||||
{
|
||||
int i;
|
||||
unsigned char buf[0x10000];
|
||||
struct gc2n64_adapter_info inf;
|
||||
|
||||
i = gc2n64_adapter_getInfo(hdl, channel, &inf);
|
||||
if (i)
|
||||
return i;
|
||||
|
||||
if (!inf.in_bootloader) {
|
||||
fprintf(stderr, "dumpFlash: Nnot in bootloader\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Atmega168 : 16K
|
||||
for (i=0; i<16*1024; i+= 32)
|
||||
{
|
||||
gc2n64_adapter_boot_readBlock(hdl, channel, i/32, buf + i);
|
||||
printf("0x%04x: ", i);
|
||||
printHexBuf(buf + i, 32);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gc2n64_adapter_enterBootloader(gcn64_hdl_t hdl, int channel)
|
||||
{
|
||||
unsigned char buf[4];
|
||||
int n;
|
||||
int t = 1000; // > 100ms timeout
|
||||
|
||||
|
||||
/* The bootloader starts the application automatically if it is
|
||||
* installed. To prevent the application from being restarted right
|
||||
* away when are entering the bootloader, the bootloader waits
|
||||
* 50 ms at startup, and if it receives the 'enter bootloader' command
|
||||
* within this window, the application is not started.
|
||||
*
|
||||
* Also, contrary to the application, the bootloader actually answers
|
||||
* this command. So it doubles as a handshake to know the bootloader has
|
||||
* started and is ready to receive instructions.
|
||||
*
|
||||
* */
|
||||
do {
|
||||
buf[0] = 'R';
|
||||
buf[1] = 0xff;
|
||||
|
||||
n = gcn64lib_rawSiCommand(hdl, channel, buf, 2, buf, sizeof(buf));
|
||||
if (n<0) {
|
||||
return n;
|
||||
}
|
||||
|
||||
if (buf[0] == 0xff && buf[1] == 0xff) {
|
||||
n = 0;
|
||||
}
|
||||
_delay_us(1000);
|
||||
t--;
|
||||
if (!t) {
|
||||
fprintf(stderr, "Timeout waiting for bootloader\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
while(n==0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gc2n64_adapter_bootApplication(gcn64_hdl_t hdl, int channel)
|
||||
{
|
||||
unsigned char buf[2];
|
||||
int n;
|
||||
|
||||
buf[0] = 'R';
|
||||
buf[1] = 0xfe;
|
||||
|
||||
n = gcn64lib_rawSiCommand(hdl, channel, buf, 2, buf, 1);
|
||||
if (n<0)
|
||||
return n;
|
||||
|
||||
if (n != 1) {
|
||||
fprintf(stderr, "boot application: Invalid answer\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (buf[0]) {
|
||||
fprintf(stderr, "Boot nack\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Note: eraseAll needs to be performed first
|
||||
int gc2n64_adapter_sendFirmwareBlocks(gcn64_hdl_t hdl, int channel, unsigned char *firmware, int len)
|
||||
{
|
||||
unsigned char buf[64];
|
||||
int i, block_id;
|
||||
int n;
|
||||
|
||||
for (i=0; i<len; i+=32) {
|
||||
block_id = i / 32;
|
||||
buf[0] = 'R';
|
||||
buf[1] = 0xf2;
|
||||
buf[2] = block_id >> 8;
|
||||
buf[3] = block_id & 0xff;
|
||||
memcpy(buf + 4, firmware+i, 32);
|
||||
|
||||
printf("Block %d / %d\r", block_id+1, len / 32); fflush(stdout);
|
||||
|
||||
n = gcn64lib_rawSiCommand(hdl, channel, buf, 4 + 32, buf, 4);
|
||||
if (n<0) {
|
||||
fprintf(stderr, "\nRaw command failed\n");
|
||||
return n;
|
||||
}
|
||||
|
||||
if (n != 4) {
|
||||
fprintf(stderr, "\nInvalid upload block answer\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// [0] ACK (should be 0x00)
|
||||
// [1] Need to poll?
|
||||
// [2] Block ID high
|
||||
// [3] Block ID low
|
||||
|
||||
if (buf[0] != 0x00) {
|
||||
fprintf(stderr, "Busy\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (buf[1]) {
|
||||
if (gc2n64_adapter_waitNotBusy(hdl, channel, 1)) {
|
||||
fprintf(stderr, "Error waiting not busy\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// printf("\n");
|
||||
// printf("Block ID: 0x%04x\n", (buf[2]<<8) | buf[3]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gc2n64_adapter_verifyFirmware(gcn64_hdl_t hdl, int channel, unsigned char *firmware, int len)
|
||||
{
|
||||
unsigned char buf[32];
|
||||
int i;
|
||||
|
||||
for (i=0; i<len; i+=32) {
|
||||
|
||||
gc2n64_adapter_boot_readBlock(hdl, channel, i/32, buf);
|
||||
if (memcmp(buf, firmware + i, 32)) {
|
||||
printf("\nMismatch in block address 0x%04x\n", i);
|
||||
printf("Written: "); printHexBuf(firmware + i, 32);
|
||||
printf(" Read: "); printHexBuf(buf, 32);
|
||||
return -1;
|
||||
} else {
|
||||
printf("Block %d / %d ok\r", i/32 + 1, len / 32); fflush(stdout);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gc2n64_adapter_waitForBootloader(gcn64_hdl_t hdl, int channel, int timeout_s)
|
||||
{
|
||||
struct gc2n64_adapter_info inf;
|
||||
int i;
|
||||
int n;
|
||||
|
||||
for (i=0; i<=timeout_s; i++) {
|
||||
n = gc2n64_adapter_getInfo(hdl, channel, &inf);
|
||||
// Errors (caused by timeouts) are just ignored since they are expected.
|
||||
if (n == 0) {
|
||||
gc2n64_adapter_printInfo(&inf);
|
||||
if (inf.in_bootloader)
|
||||
return 0;
|
||||
}
|
||||
_delay_s(1);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
#define FIRMWARE_BUF_SIZE 0x10000
|
||||
int gc2n64_adapter_updateFirmware(gcn64_hdl_t hdl, int channel, const char *hexfile)
|
||||
{
|
||||
unsigned char *buf;
|
||||
int max_addr;
|
||||
int ret = 0, res;
|
||||
struct gc2n64_adapter_info inf;
|
||||
const char *signature = "41d938a8-6f8a-11e5-a45e-001bfca3c593";
|
||||
|
||||
////////////////////
|
||||
printf("step [1/7] : Load .hex file...\n");
|
||||
buf = malloc(FIRMWARE_BUF_SIZE);
|
||||
if (!buf) {
|
||||
perror("malloc");
|
||||
return -1;
|
||||
}
|
||||
memset(buf, 0xff, FIRMWARE_BUF_SIZE);
|
||||
|
||||
max_addr = load_ihex(hexfile, buf, FIRMWARE_BUF_SIZE);
|
||||
if (max_addr < 0) {
|
||||
fprintf(stderr, "Update failed : Could not load hex file\n");
|
||||
ret = -1;
|
||||
goto err;
|
||||
}
|
||||
|
||||
// look for the signature somewhere in the file to make sure
|
||||
// this firmware is intended for this product
|
||||
if (!memmem(buf, max_addr + 1, signature, strlen(signature))) {
|
||||
fprintf(stderr, "Update aborted : Signature not found. This hex file is not for this adapter.\n");
|
||||
ret = -1;
|
||||
printHexBuf(buf + 0x1bde, 30);
|
||||
goto err;
|
||||
}
|
||||
|
||||
printf("Firmware size: %d bytes\n", max_addr+1);
|
||||
|
||||
|
||||
////////////////////
|
||||
printf("step [2/7] : Get adapter info...\n");
|
||||
res = gc2n64_adapter_getInfo(hdl, channel, &inf);
|
||||
if (res < 0) {
|
||||
fprintf(stderr, "Failed to read adapter info\n");
|
||||
return -1;
|
||||
}
|
||||
gc2n64_adapter_printInfo(&inf);
|
||||
|
||||
if (inf.in_bootloader) {
|
||||
printf("step [3/7] : Enter bootloader... Skipped. Already in bootloader.\n");
|
||||
} else {
|
||||
// Catch Atmega8 adapters programmed with a new firmware but without bootloader.
|
||||
if (!inf.app.upgradeable) {
|
||||
fprintf(stderr, "Error : This adapter is not upgradable. (i.e. No bootloader on Atmega8)\n");
|
||||
ret = -1;
|
||||
goto err;
|
||||
}
|
||||
|
||||
printf("step [3/7] : Enter bootloader...\n");
|
||||
res = gc2n64_adapter_enterBootloader(hdl, channel);
|
||||
if (res < 0) {
|
||||
fprintf(stderr, "Failed to enter the bootloader\n");
|
||||
ret = -1;
|
||||
goto err;
|
||||
}
|
||||
|
||||
// Re-read the info structure, as we will need the bootloader start address.
|
||||
res = gc2n64_adapter_getInfo(hdl, channel, &inf);
|
||||
if (res < 0) {
|
||||
fprintf(stderr, "Failed to read info after enterring bootloader\n");
|
||||
ret = -1;
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////
|
||||
printf("step [4/7] : Erase current firmware... "); fflush(stdout);
|
||||
gc2n64_adapter_boot_eraseAll(hdl, channel);
|
||||
|
||||
if (gc2n64_adapter_waitNotBusy(hdl, channel, 1)) {
|
||||
ret = -1;
|
||||
goto err;
|
||||
}
|
||||
printf("Ok\n");
|
||||
|
||||
|
||||
printf("step [5/7] : Write new firmware...\n");
|
||||
// Note: We write up to the bootloader, even if the firmware was shorter (it usually is).
|
||||
// This is to make sure that the marker we placed at the end gets written.
|
||||
res = gc2n64_adapter_sendFirmwareBlocks(hdl, channel, buf, inf.bootldr.bootloader_start_address);
|
||||
if (res < 0) {
|
||||
ret = -1;
|
||||
goto err;
|
||||
}
|
||||
|
||||
printf("step [6/7] : Verify firmware...\n");
|
||||
res = gc2n64_adapter_verifyFirmware(hdl, channel, buf, inf.bootldr.bootloader_start_address);
|
||||
if (res < 0) {
|
||||
printf("Verify failed : Update failed\n");
|
||||
ret = -1;
|
||||
goto err;
|
||||
}
|
||||
|
||||
printf("step [7/7] : Launch new firmware.\n");
|
||||
gc2n64_adapter_bootApplication(hdl, channel);
|
||||
|
||||
err:
|
||||
free(buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -1,74 +0,0 @@
|
||||
#ifndef _gc2n64_adapter_h__
|
||||
#define _gc2n64_adapter_h__
|
||||
|
||||
#include "gcn64.h"
|
||||
|
||||
#define GC2N64_MAX_MAPPING_PAIRS 32
|
||||
#define GC2N64_NUM_MAPPINGS 5
|
||||
|
||||
struct gc2n64_adapter_mapping_pair {
|
||||
int gc, n64;
|
||||
};
|
||||
|
||||
struct gc2n64_adapter_mapping {
|
||||
int n_pairs;
|
||||
struct gc2n64_adapter_mapping_pair pairs[GC2N64_MAX_MAPPING_PAIRS];
|
||||
};
|
||||
|
||||
struct gc2n64_adapter_info_app {
|
||||
unsigned char default_mapping_id;
|
||||
unsigned char deadzone_enabled;
|
||||
unsigned char old_v1_5_conversion;
|
||||
unsigned char upgradeable;
|
||||
char version[16];
|
||||
struct gc2n64_adapter_mapping mappings[GC2N64_NUM_MAPPINGS];
|
||||
};
|
||||
|
||||
struct gc2n64_adapter_info_bootloader {
|
||||
char version[16];
|
||||
unsigned char mcu_page_size;
|
||||
unsigned short bootloader_start_address;
|
||||
};
|
||||
|
||||
struct gc2n64_adapter_info {
|
||||
int in_bootloader;
|
||||
union {
|
||||
struct gc2n64_adapter_info_app app;
|
||||
struct gc2n64_adapter_info_bootloader bootldr;
|
||||
};
|
||||
};
|
||||
|
||||
int gc2n64_adapter_echotest(gcn64_hdl_t hdl, int channel, int verbosee);
|
||||
int gc2n64_adapter_getInfo(gcn64_hdl_t hdl, int channel, struct gc2n64_adapter_info *inf);
|
||||
void gc2n64_adapter_printInfo(struct gc2n64_adapter_info *inf);
|
||||
void gc2n64_adapter_printMapping(struct gc2n64_adapter_mapping *map);
|
||||
|
||||
#define MAPPING_SLOT_BUILTIN_CURRENT 0
|
||||
#define MAPPING_SLOT_DPAD_UP 1
|
||||
#define MAPPING_SLOT_DPAD_DOWN 2
|
||||
#define MAPPING_SLOT_DPAD_LEFT 3
|
||||
#define MAPPING_SLOT_DPAD_RIGHT 4
|
||||
const char *gc2n64_adapter_getMappingSlotName(unsigned char id, int default_context);
|
||||
|
||||
int gc2n64_adapter_getMapping(gcn64_hdl_t hdl, int channel, int mapping_id, struct gc2n64_adapter_mapping *dst_mapping);
|
||||
int gc2n64_adapter_setMapping(gcn64_hdl_t hdl, int channel, struct gc2n64_adapter_mapping *mapping);
|
||||
int gc2n64_adapter_storeCurrentMapping(gcn64_hdl_t hdl, int channel, int dst_slot);
|
||||
|
||||
int gc2n64_adapter_saveMapping(struct gc2n64_adapter_mapping *map, const char *dstfile);
|
||||
struct gc2n64_adapter_mapping *gc2n64_adapter_loadMapping(const char *srcfile);
|
||||
|
||||
int gc2n64_adapter_waitNotBusy(gcn64_hdl_t hdl, int channel, int verbose);
|
||||
int gc2n64_adapter_boot_isBusy(gcn64_hdl_t hdl, int channel);
|
||||
|
||||
int gc2n64_adapter_boot_eraseAll(gcn64_hdl_t hdl, int channel);
|
||||
int gc2n64_adapter_boot_readBlock(gcn64_hdl_t hdl, int channel, unsigned int block_id, unsigned char dst[32]);
|
||||
int gc2n64_adapter_dumpFlash(gcn64_hdl_t hdl, int channel);
|
||||
int gc2n64_adapter_updateFirmware(gcn64_hdl_t hdl, int channel, const char *hexfile);
|
||||
int gc2n64_adapter_enterBootloader(gcn64_hdl_t hdl, int channel);
|
||||
int gc2n64_adapter_bootApplication(gcn64_hdl_t hdl, int channel);
|
||||
int gc2n64_adapter_sendFirmwareBlocks(gcn64_hdl_t hdl, int channel, unsigned char *firmware, int len);
|
||||
int gc2n64_adapter_verifyFirmware(gcn64_hdl_t hdl, int channel, unsigned char *firmware, int len);
|
||||
int gc2n64_adapter_waitForBootloader(gcn64_hdl_t hdl, int channel, int timeout_s);
|
||||
|
||||
#endif // _gc2n64_adapter_h__
|
||||
|
287
tools/gcn64.c
287
tools/gcn64.c
@ -1,287 +0,0 @@
|
||||
/* gc_n64_usb : Gamecube or N64 controller to USB adapter firmware
|
||||
Copyright (C) 2007-2015 Raphael Assenat <raph@raphnet.net>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "gcn64.h"
|
||||
#include "gcn64_priv.h"
|
||||
#include "../requests.h"
|
||||
|
||||
#include "hidapi.h"
|
||||
|
||||
static int dusbr_verbose = 0;
|
||||
|
||||
#define GCN64_HID_DATA_REPORT_SIZE 40
|
||||
#define IS_VERBOSE() (dusbr_verbose)
|
||||
|
||||
int gcn64_init(int verbose)
|
||||
{
|
||||
dusbr_verbose = verbose;
|
||||
hid_init();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void gcn64_shutdown(void)
|
||||
{
|
||||
hid_exit();
|
||||
}
|
||||
|
||||
static char isProductIdHandled(unsigned short pid, int interface_number)
|
||||
{
|
||||
switch (pid)
|
||||
{
|
||||
case 0x0017: // GC/N64 USB v3
|
||||
if (interface_number == 1)
|
||||
return 1;
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct gcn64_list_ctx *gcn64_allocListCtx(void)
|
||||
{
|
||||
struct gcn64_list_ctx *ctx;
|
||||
ctx = calloc(1, sizeof(struct gcn64_list_ctx));
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void gcn64_freeListCtx(struct gcn64_list_ctx *ctx)
|
||||
{
|
||||
if (ctx) {
|
||||
if (ctx->devs) {
|
||||
hid_free_enumeration(ctx->devs);
|
||||
}
|
||||
free(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief List instances of our rgbleds device on the USB busses.
|
||||
* \param info Pointer to gcn64_info structure to store data
|
||||
* \param dst Destination buffer for device serial number/id.
|
||||
* \param dstbuf_size Destination buffer size.
|
||||
*/
|
||||
struct gcn64_info *gcn64_listDevices(struct gcn64_info *info, struct gcn64_list_ctx *ctx)
|
||||
{
|
||||
memset(info, 0, sizeof(struct gcn64_info));
|
||||
|
||||
if (!ctx) {
|
||||
fprintf(stderr, "gcn64_listDevices: Passed null context\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (ctx->devs)
|
||||
goto jumpin;
|
||||
|
||||
if (IS_VERBOSE()) {
|
||||
printf("Start listing\n");
|
||||
}
|
||||
|
||||
ctx->devs = hid_enumerate(OUR_VENDOR_ID, 0x0000);
|
||||
if (!ctx->devs) {
|
||||
printf("Hid enumerate returned NULL\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (ctx->cur_dev = ctx->devs; ctx->cur_dev; ctx->cur_dev = ctx->cur_dev->next)
|
||||
{
|
||||
if (IS_VERBOSE()) {
|
||||
printf("Considering 0x%04x:0x%04x\n", ctx->cur_dev->vendor_id, ctx->cur_dev->product_id);
|
||||
}
|
||||
if (isProductIdHandled(ctx->cur_dev->product_id, ctx->cur_dev->interface_number))
|
||||
{
|
||||
info->usb_vid = ctx->cur_dev->vendor_id;
|
||||
info->usb_pid = ctx->cur_dev->product_id;
|
||||
wcsncpy(info->str_prodname, ctx->cur_dev->product_string, PRODNAME_MAXCHARS-1);
|
||||
wcsncpy(info->str_serial, ctx->cur_dev->serial_number, SERIAL_MAXCHARS-1);
|
||||
strncpy(info->str_path, ctx->cur_dev->path, PATH_MAXCHARS-1);
|
||||
return info;
|
||||
}
|
||||
|
||||
jumpin:
|
||||
// prevent 'error: label at end of compound statement'
|
||||
continue;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
gcn64_hdl_t gcn64_openDevice(struct gcn64_info *dev)
|
||||
{
|
||||
hid_device *hdev;
|
||||
|
||||
if (!dev)
|
||||
return NULL;
|
||||
|
||||
if (IS_VERBOSE()) {
|
||||
printf("Opening device path: '%s'\n", dev->str_path);
|
||||
}
|
||||
|
||||
hdev = hid_open_path(dev->str_path);
|
||||
if (!hdev) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return hdev;
|
||||
}
|
||||
|
||||
gcn64_hdl_t gcn64_openBy(struct gcn64_info *dev, unsigned char flags)
|
||||
{
|
||||
struct gcn64_list_ctx *ctx;
|
||||
struct gcn64_info inf;
|
||||
gcn64_hdl_t h;
|
||||
|
||||
if (IS_VERBOSE())
|
||||
printf("gcn64_openBy, flags=0x%02x\n", flags);
|
||||
|
||||
ctx = gcn64_allocListCtx();
|
||||
if (!ctx)
|
||||
return NULL;
|
||||
|
||||
while (gcn64_listDevices(&inf, ctx)) {
|
||||
if (IS_VERBOSE())
|
||||
printf("Considering '%s'\n", inf.str_path);
|
||||
|
||||
if (flags & GCN64_FLG_OPEN_BY_SERIAL) {
|
||||
if (wcscmp(inf.str_serial, dev->str_serial))
|
||||
continue;
|
||||
}
|
||||
|
||||
if (flags & GCN64_FLG_OPEN_BY_PATH) {
|
||||
if (strcmp(inf.str_path, dev->str_path))
|
||||
continue;
|
||||
}
|
||||
|
||||
if (flags & GCN64_FLG_OPEN_BY_VID) {
|
||||
if (inf.usb_vid != dev->usb_vid)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (flags & GCN64_FLG_OPEN_BY_PID) {
|
||||
if (inf.usb_pid != dev->usb_pid)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IS_VERBOSE())
|
||||
printf("Found device. opening...\n");
|
||||
|
||||
h = gcn64_openDevice(&inf);
|
||||
gcn64_freeListCtx(ctx);
|
||||
return h;
|
||||
}
|
||||
|
||||
gcn64_freeListCtx(ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void gcn64_closeDevice(gcn64_hdl_t hdl)
|
||||
{
|
||||
hid_device *hdev = (hid_device*)hdl;
|
||||
|
||||
if (hdev) {
|
||||
hid_close(hdev);
|
||||
}
|
||||
}
|
||||
|
||||
int gcn64_send_cmd(gcn64_hdl_t hdl, const unsigned char *cmd, int cmdlen)
|
||||
{
|
||||
hid_device *hdev = (hid_device*)hdl;
|
||||
unsigned char buffer[GCN64_HID_DATA_REPORT_SIZE+1];
|
||||
int n;
|
||||
|
||||
if (cmdlen > (sizeof(buffer)-1)) {
|
||||
fprintf(stderr, "Error: Command too long\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
|
||||
buffer[0] = 0x00; // report ID set to 0 (device has only one)
|
||||
memcpy(buffer + 1, cmd, cmdlen);
|
||||
|
||||
n = hid_send_feature_report(hdev, buffer, sizeof(buffer));
|
||||
if (n < 0) {
|
||||
fprintf(stderr, "Could not send feature report (%ls)\n", hid_error(hdev));
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gcn64_poll_result(gcn64_hdl_t hdl, unsigned char *cmd, int cmd_maxlen)
|
||||
{
|
||||
hid_device *hdev = (hid_device*)hdl;
|
||||
unsigned char buffer[GCN64_HID_DATA_REPORT_SIZE+1];
|
||||
int res_len;
|
||||
int n;
|
||||
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
buffer[0] = 0x00; // report ID set to 0 (device has only one)
|
||||
|
||||
n = hid_get_feature_report(hdev, buffer, sizeof(buffer));
|
||||
if (n < 0) {
|
||||
fprintf(stderr, "Could not send feature report (%ls)\n", hid_error(hdev));
|
||||
return -1;
|
||||
}
|
||||
if (n==0) {
|
||||
return 0;
|
||||
}
|
||||
res_len = n-1;
|
||||
|
||||
if (res_len>0) {
|
||||
int copy_len;
|
||||
|
||||
copy_len = res_len;
|
||||
if (copy_len > cmd_maxlen) {
|
||||
copy_len = cmd_maxlen;
|
||||
}
|
||||
if (cmd) {
|
||||
memcpy(cmd, buffer+1, copy_len);
|
||||
}
|
||||
}
|
||||
|
||||
return res_len;
|
||||
}
|
||||
|
||||
int gcn64_exchange(gcn64_hdl_t hdl, unsigned char *outcmd, int outlen, unsigned char *result, int result_max)
|
||||
{
|
||||
int n;
|
||||
|
||||
n = gcn64_send_cmd(hdl, outcmd, outlen);
|
||||
if (n<0) {
|
||||
fprintf(stderr, "Error sending command\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Answer to the command comes later. For now, this is polled, but in
|
||||
* the future an interrupt-in transfer could be used. */
|
||||
do {
|
||||
n = gcn64_poll_result(hdl, result, result_max);
|
||||
if (n < 0) {
|
||||
fprintf(stderr, "Error\r\n");
|
||||
break;
|
||||
}
|
||||
if (n==0) {
|
||||
// printf("."); fflush(stdout);
|
||||
}
|
||||
|
||||
} while (n==0);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
|
@ -1,53 +0,0 @@
|
||||
#ifndef _gcn64_h__
|
||||
#define _gcn64_h__
|
||||
|
||||
#include <wchar.h>
|
||||
|
||||
#define OUR_VENDOR_ID 0x289b
|
||||
#define PRODNAME_MAXCHARS 256
|
||||
#define SERIAL_MAXCHARS 256
|
||||
#define PATH_MAXCHARS 256
|
||||
|
||||
struct gcn64_info {
|
||||
wchar_t str_prodname[PRODNAME_MAXCHARS];
|
||||
wchar_t str_serial[SERIAL_MAXCHARS];
|
||||
char str_path[PATH_MAXCHARS];
|
||||
int usb_vid, usb_pid;
|
||||
int major, minor;
|
||||
int access; // True unless direct access to read serial/prodname failed due to permissions.
|
||||
};
|
||||
|
||||
struct gcn64_list_ctx;
|
||||
|
||||
typedef void* gcn64_hdl_t; // Cast from hid_device
|
||||
|
||||
int gcn64_init(int verbose);
|
||||
void gcn64_shutdown(void);
|
||||
|
||||
struct gcn64_list_ctx *gcn64_allocListCtx(void);
|
||||
void gcn64_freeListCtx(struct gcn64_list_ctx *ctx);
|
||||
struct gcn64_info *gcn64_listDevices(struct gcn64_info *info, struct gcn64_list_ctx *ctx);
|
||||
|
||||
gcn64_hdl_t gcn64_openDevice(struct gcn64_info *dev);
|
||||
|
||||
#define GCN64_FLG_OPEN_BY_SERIAL 1 /** Serial must match */
|
||||
#define GCN64_FLG_OPEN_BY_PATH 2 /** Path must match */
|
||||
#define GCN64_FLG_OPEN_BY_VID 4 /** USB VID must match */
|
||||
#define GCN64_FLG_OPEN_BY_PID 8 /** USB PID MUST match */
|
||||
/**
|
||||
* \brief Open a device matching a serial number
|
||||
* \param dev The device structure
|
||||
* \param flags Flags
|
||||
* \return A handle to the opened device, or NULL if not found
|
||||
**/
|
||||
gcn64_hdl_t gcn64_openBy(struct gcn64_info *dev, unsigned char flags);
|
||||
|
||||
void gcn64_closeDevice(gcn64_hdl_t hdl);
|
||||
|
||||
int gcn64_send_cmd(gcn64_hdl_t hdl, const unsigned char *cmd, int len);
|
||||
int gcn64_poll_result(gcn64_hdl_t hdl, unsigned char *cmd, int cmdlen);
|
||||
|
||||
int gcn64_exchange(gcn64_hdl_t hdl, unsigned char *outcmd, int outlen, unsigned char *result, int result_max);
|
||||
|
||||
#endif // _gcn64_h__
|
||||
|
@ -1,10 +0,0 @@
|
||||
#ifndef _gcn64_priv_h__
|
||||
#define _gcn64_priv_h__
|
||||
|
||||
#include "hidapi.h"
|
||||
|
||||
struct gcn64_list_ctx {
|
||||
struct hid_device_info *devs, *cur_dev;
|
||||
};
|
||||
|
||||
#endif
|
1213
tools/gcn64cfg.glade
1213
tools/gcn64cfg.glade
File diff suppressed because it is too large
Load Diff
@ -1,97 +0,0 @@
|
||||
; example2.nsi
|
||||
;
|
||||
; This script is based on example1.nsi, but it remember the directory,
|
||||
; has uninstall support and (optionally) installs start menu shortcuts.
|
||||
;
|
||||
; It will install example2.nsi into a directory that the user selects,
|
||||
|
||||
;--------------------------------
|
||||
|
||||
; The name of the installer
|
||||
Name "gcn64ctl"
|
||||
|
||||
; The file to write
|
||||
OutFile "installers/gcn64ctl-install-${VERSION}.exe"
|
||||
|
||||
; The default installation directory
|
||||
InstallDir $PROGRAMFILES\gcn64ctl
|
||||
|
||||
; Registry key to check for directory (so if you install again, it will
|
||||
; overwrite the old one automatically)
|
||||
InstallDirRegKey HKLM "Software\gcn64ctl" "Install_Dir"
|
||||
|
||||
LicenseData ../LICENSE
|
||||
|
||||
; Request application privileges for Windows Vista
|
||||
RequestExecutionLevel admin
|
||||
|
||||
;--------------------------------
|
||||
!include "LogicLib.nsh"
|
||||
!include "x64.nsh"
|
||||
|
||||
; Pages
|
||||
|
||||
Page license
|
||||
Page components
|
||||
Page directory
|
||||
Page instfiles
|
||||
|
||||
UninstPage uninstConfirm
|
||||
UninstPage instfiles
|
||||
|
||||
;--------------------------------
|
||||
|
||||
; The stuff to install
|
||||
Section "gcn64ctl (required)"
|
||||
|
||||
SectionIn RO
|
||||
|
||||
; Set output path to the installation directory.
|
||||
SetOutPath $INSTDIR
|
||||
|
||||
; Put file there
|
||||
File /r "release_windows\*.*"
|
||||
|
||||
; Write the installation path into the registry
|
||||
WriteRegStr HKLM SOFTWARE\gcn64ctl "Install_Dir" "$INSTDIR"
|
||||
|
||||
; Write the uninstall keys for Windows
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\gcn64ctl" "DisplayName" "gcn64ctl"
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\gcn64ctl" "UninstallString" '"$INSTDIR\uninstall.exe"'
|
||||
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\gcn64ctl" "NoModify" 1
|
||||
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\gcn64ctl" "NoRepair" 1
|
||||
WriteUninstaller "uninstall.exe"
|
||||
|
||||
SectionEnd
|
||||
|
||||
; Optional section (can be disabled by the user)
|
||||
Section "Start Menu Shortcuts"
|
||||
|
||||
CreateDirectory "$SMPROGRAMS\raphnet-tech GC/N64 adapter manager"
|
||||
CreateShortCut "$SMPROGRAMS\raphnet-tech GC/N64 adapter manager\Uninstall.lnk" "$INSTDIR\uninstall.exe" "" "$INSTDIR\uninstall.exe" 0
|
||||
CreateShortCut "$SMPROGRAMS\raphnet-tech GC/N64 adapter manager\Commandi-line tools.lnk" "$SYSDIR\cmd.exe" '/K "cd /d $INSTDIR"' "$SYSDIR\cmd.exe" 0
|
||||
CreateShortCut "$SMPROGRAMS\raphnet-tech GC/N64 adapter manager\GC/N64 adapter manager.lnk" "$INSTDIR\gcn64ctl_gui.exe" "" "$INSTDIR\gcn64ctl_gui.exe" 0
|
||||
|
||||
SectionEnd
|
||||
|
||||
;--------------------------------
|
||||
|
||||
; Uninstaller
|
||||
|
||||
Section "Uninstall"
|
||||
|
||||
; Remove registry keys
|
||||
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\gcn64ctl"
|
||||
DeleteRegKey HKLM SOFTWARE\gcn64ctl
|
||||
|
||||
; Remove files and uninstaller
|
||||
Delete "$INSTDIR\*.*"
|
||||
|
||||
; Remove shortcuts, if any
|
||||
Delete "$SMPROGRAMS\raphnet-tech GC/N64 adapter manager\*.*"
|
||||
|
||||
; Remove directories used
|
||||
RMDir "$SMPROGRAMS\gcn64ctl"
|
||||
RMDir "$INSTDIR"
|
||||
|
||||
SectionEnd
|
@ -1,845 +0,0 @@
|
||||
/* gc_n64_usb : Gamecube or N64 controller to USB adapter firmware
|
||||
Copyright (C) 2007-2015 Raphael Assenat <raph@raphnet.net>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "../requests.h"
|
||||
#include "ihex.h"
|
||||
#include "mempak.h"
|
||||
#include "gcn64ctl_gui.h"
|
||||
#include "mempak_gcn64usb.h"
|
||||
#include "gcn64lib.h"
|
||||
|
||||
static void updateGuiFromAdapter(struct application *app);
|
||||
gboolean rebuild_device_list_store(gpointer data);
|
||||
void deselect_adapter(struct application *app);
|
||||
|
||||
gboolean updateDonefunc(gpointer data)
|
||||
{
|
||||
struct application *app = data;
|
||||
GET_UI_ELEMENT(GtkDialog, firmware_update_dialog);
|
||||
|
||||
printf("updateDonefunc\n");
|
||||
gtk_dialog_response(firmware_update_dialog, app->update_dialog_response);
|
||||
g_thread_join(app->updater_thread);
|
||||
|
||||
rebuild_device_list_store(data);
|
||||
updateGuiFromAdapter(app);
|
||||
app->inhibit_periodic_updates = 0;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
gboolean updateProgress(gpointer data)
|
||||
{
|
||||
struct application *app = data;
|
||||
GET_UI_ELEMENT(GtkProgressBar, updateProgress);
|
||||
GET_UI_ELEMENT(GtkLabel, updateStatus);
|
||||
|
||||
gtk_progress_bar_set_fraction(updateProgress, app->update_percent / 100.0);
|
||||
gtk_label_set_text(updateStatus, app->update_status);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
gboolean closeAdapter(gpointer data)
|
||||
{
|
||||
struct application *app = data;
|
||||
|
||||
deselect_adapter(app);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
gpointer updateThreadFunc(gpointer data)
|
||||
{
|
||||
struct application *app = data;
|
||||
int res;
|
||||
FILE *dfu_fp;
|
||||
char linebuf[256];
|
||||
char cmd[256];
|
||||
int retries = 10;
|
||||
|
||||
app->inhibit_periodic_updates = 1;
|
||||
|
||||
app->update_status = "Starting...";
|
||||
app->update_percent = 1;
|
||||
gdk_threads_add_idle(updateProgress, data);
|
||||
|
||||
app->update_percent = 10;
|
||||
app->update_status = "Enter bootloader...";
|
||||
|
||||
gcn64lib_bootloader(app->current_adapter_handle);
|
||||
gdk_threads_add_idle(closeAdapter, data);
|
||||
|
||||
app->update_percent = 19;
|
||||
app->update_status = "Erasing chip...";
|
||||
do {
|
||||
app->update_percent++;
|
||||
gdk_threads_add_idle(updateProgress, data);
|
||||
|
||||
if (app->at90usb1287) {
|
||||
dfu_fp = popen("dfu-programmer at90usb1287 erase", "r");
|
||||
} else {
|
||||
dfu_fp = popen("dfu-programmer atmega32u2 erase", "r");
|
||||
}
|
||||
if (!dfu_fp) {
|
||||
app->update_dialog_response = GTK_RESPONSE_REJECT;
|
||||
gdk_threads_add_idle(updateDonefunc, data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
do {
|
||||
fgets(linebuf, sizeof(linebuf), dfu_fp);
|
||||
} while(!feof(dfu_fp));
|
||||
|
||||
res = pclose(dfu_fp);
|
||||
printf("Pclose: %d\n", res);
|
||||
|
||||
if (res==0) {
|
||||
break;
|
||||
}
|
||||
sleep(1);
|
||||
} while (retries--);
|
||||
|
||||
if (!retries) {
|
||||
app->update_dialog_response = GTK_RESPONSE_REJECT;
|
||||
gdk_threads_add_idle(updateDonefunc, data);
|
||||
}
|
||||
|
||||
app->update_status = "Chip erased";
|
||||
app->update_percent = 20;
|
||||
gdk_threads_add_idle(updateProgress, data);
|
||||
|
||||
|
||||
app->update_status = "Programming ...";
|
||||
app->update_percent = 30;
|
||||
gdk_threads_add_idle(updateProgress, data);
|
||||
|
||||
snprintf(cmd, sizeof(cmd), "dfu-programmer %s flash %s",
|
||||
app->at90usb1287 ? "at90usb1287" : "atmega32u2",
|
||||
app->updateHexFile);
|
||||
dfu_fp = popen(cmd, "r");
|
||||
if (!dfu_fp) {
|
||||
app->update_dialog_response = GTK_RESPONSE_REJECT;
|
||||
gdk_threads_add_idle(updateDonefunc, data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
do {
|
||||
fgets(linebuf, sizeof(linebuf), dfu_fp);
|
||||
printf("ln: %s\n\n", linebuf);
|
||||
if (strstr(linebuf, "Validating")) {
|
||||
app->update_status = "Validating...";
|
||||
app->update_percent = 70;
|
||||
gdk_threads_add_idle(updateDonefunc, data);
|
||||
}
|
||||
} while(!feof(dfu_fp));
|
||||
|
||||
res = pclose(dfu_fp);
|
||||
printf("Pclose: %d\n", res);
|
||||
|
||||
if (res != 0) {
|
||||
app->update_dialog_response = GTK_RESPONSE_REJECT;
|
||||
gdk_threads_add_idle(updateDonefunc, data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
app->update_status = "Starting firmware...";
|
||||
app->update_percent = 80;
|
||||
gdk_threads_add_idle(updateProgress, data);
|
||||
|
||||
dfu_fp = popen("dfu-programmer at90usb1287 start", "r");
|
||||
if (!dfu_fp) {
|
||||
app->update_dialog_response = GTK_RESPONSE_REJECT;
|
||||
gdk_threads_add_idle(updateDonefunc, data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
res = pclose(dfu_fp);
|
||||
printf("Pclose: %d\n", res);
|
||||
|
||||
if (res!=0) {
|
||||
app->update_dialog_response = GTK_RESPONSE_REJECT;
|
||||
gdk_threads_add_idle(updateDonefunc, data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
app->update_status = "Waiting for device...";
|
||||
app->update_percent = 90;
|
||||
gdk_threads_add_idle(updateProgress, data);
|
||||
|
||||
retries = 6;
|
||||
do {
|
||||
app->current_adapter_handle = gcn64_openBy(&app->current_adapter_info, GCN64_FLG_OPEN_BY_SERIAL);
|
||||
if (app->current_adapter_handle)
|
||||
break;
|
||||
sleep(1);
|
||||
app->update_percent++;
|
||||
gdk_threads_add_idle(updateProgress, data);
|
||||
} while (retries--);
|
||||
|
||||
if (!app->current_adapter_handle) {
|
||||
app->update_dialog_response = GTK_RESPONSE_REJECT;
|
||||
gdk_threads_add_idle(updateDonefunc, data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
app->update_status = "Done";
|
||||
app->update_percent = 100;
|
||||
gdk_threads_add_idle(updateProgress, data);
|
||||
|
||||
printf("Update done\n");
|
||||
app->update_dialog_response = GTK_RESPONSE_OK;
|
||||
gdk_threads_add_idle(updateDonefunc, data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
G_MODULE_EXPORT void updatestart_btn_clicked_cb(GtkWidget *w, gpointer data)
|
||||
{
|
||||
struct application *app = data;
|
||||
GET_UI_ELEMENT(GtkButtonBox, update_dialog_btnBox);
|
||||
|
||||
app->updater_thread = g_thread_new("updater", updateThreadFunc, app);
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(update_dialog_btnBox), FALSE);
|
||||
}
|
||||
|
||||
void deselect_adapter(struct application *app)
|
||||
{
|
||||
GET_UI_ELEMENT(GtkComboBox, cb_adapter_list);
|
||||
GtkWidget *adapter_details = GTK_WIDGET( gtk_builder_get_object(app->builder, "adapterDetails") );
|
||||
|
||||
printf("deselect adapter\n");
|
||||
|
||||
if (app->current_adapter_handle) {
|
||||
gcn64_closeDevice(app->current_adapter_handle);
|
||||
app->current_adapter_handle = NULL;
|
||||
gtk_widget_set_sensitive(adapter_details, FALSE);
|
||||
}
|
||||
|
||||
gtk_combo_box_set_active_iter(cb_adapter_list, NULL);
|
||||
}
|
||||
|
||||
void infoPopup(struct application *app, const char *message)
|
||||
{
|
||||
GtkWidget *dialog;
|
||||
|
||||
dialog = gtk_message_dialog_new(app->mainwindow,
|
||||
GTK_DIALOG_DESTROY_WITH_PARENT,
|
||||
GTK_MESSAGE_INFO,
|
||||
GTK_BUTTONS_CLOSE,
|
||||
message);
|
||||
|
||||
gtk_dialog_run(GTK_DIALOG(dialog));
|
||||
gtk_widget_destroy(dialog);
|
||||
}
|
||||
|
||||
|
||||
void errorPopup(struct application *app, const char *message)
|
||||
{
|
||||
GtkWidget *dialog;
|
||||
|
||||
dialog = gtk_message_dialog_new(app->mainwindow,
|
||||
GTK_DIALOG_DESTROY_WITH_PARENT,
|
||||
GTK_MESSAGE_ERROR,
|
||||
GTK_BUTTONS_CLOSE,
|
||||
message);
|
||||
|
||||
gtk_dialog_run(GTK_DIALOG(dialog));
|
||||
gtk_widget_destroy(dialog);
|
||||
}
|
||||
|
||||
#define IHEX_MAX_FILE_SIZE 0x20000
|
||||
char check_ihex_for_signature(const char *filename, const char *signature)
|
||||
{
|
||||
unsigned char *buf;
|
||||
int max_address;
|
||||
|
||||
buf = malloc(IHEX_MAX_FILE_SIZE);
|
||||
if (!buf) {
|
||||
perror("malloc");
|
||||
return 0;
|
||||
}
|
||||
|
||||
max_address= load_ihex(filename, buf, IHEX_MAX_FILE_SIZE);
|
||||
|
||||
if (max_address > 0) {
|
||||
if (!memmem(buf, max_address + 1, signature, strlen(signature))) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
G_MODULE_EXPORT void update_usbadapter_firmware(GtkWidget *w, gpointer data)
|
||||
{
|
||||
struct application *app = data;
|
||||
gint res;
|
||||
GtkWidget *dialog = NULL;
|
||||
GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
|
||||
GET_UI_ELEMENT(GtkFileFilter, hexfilter);
|
||||
GET_UI_ELEMENT(GtkDialog, firmware_update_dialog);
|
||||
GET_UI_ELEMENT(GtkLabel, lbl_firmware_filename);
|
||||
GET_UI_ELEMENT(GtkButtonBox, update_dialog_btnBox);
|
||||
FILE *dfu_fp;
|
||||
char *filename = NULL, *basename = NULL;
|
||||
char adap_sig[64];
|
||||
#ifndef WINDOWS
|
||||
const char *notfound = "dfu-programmer not found. Cannot perform update.";
|
||||
#else
|
||||
const char *notfound = "dfu-programmer.exe not found. Cannot perform update.";
|
||||
#endif
|
||||
|
||||
if (gcn64lib_getSignature(app->current_adapter_handle, adap_sig, sizeof(adap_sig))) {
|
||||
errorPopup(app, "Could not read adapter signature - This file may not be meant for it (Bricking hazard!)");
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Test for dfu-programmer presence in path*/
|
||||
dfu_fp = popen("dfu-programmer --version", "r");
|
||||
//dfu_fp = popen("dfu2-programmer --version", "r");
|
||||
if (!dfu_fp) {
|
||||
perror("popen");
|
||||
errorPopup(app, notfound);
|
||||
return;
|
||||
}
|
||||
res = pclose(dfu_fp);
|
||||
#ifdef WINDOWS
|
||||
// Under Mingw, 0 is returned when dfu-programmmer was found
|
||||
// and executed. Otherwise 1.
|
||||
if (res != 0) {
|
||||
#else
|
||||
// Under Unix, the usual is available.
|
||||
if (!WIFEXITED(res) || (WEXITSTATUS(res)!=1)) {
|
||||
#endif
|
||||
if (res) {
|
||||
errorPopup(app, notfound);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
dialog = gtk_file_chooser_dialog_new("Open .hex file",
|
||||
app->mainwindow,
|
||||
action,
|
||||
"_Cancel",
|
||||
GTK_RESPONSE_CANCEL,
|
||||
"_Open",
|
||||
GTK_RESPONSE_ACCEPT,
|
||||
NULL);
|
||||
|
||||
gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), hexfilter);
|
||||
|
||||
res = gtk_dialog_run (GTK_DIALOG(dialog));
|
||||
if (res == GTK_RESPONSE_ACCEPT) {
|
||||
GtkFileChooser *chooser = GTK_FILE_CHOOSER(dialog);
|
||||
|
||||
filename = gtk_file_chooser_get_filename(chooser);
|
||||
basename = g_path_get_basename(filename);
|
||||
|
||||
gtk_widget_destroy(dialog);
|
||||
dialog = NULL;
|
||||
|
||||
printf("File selected: %s\n", filename);
|
||||
app->updateHexFile = filename;
|
||||
|
||||
if (!check_ihex_for_signature(filename, adap_sig)) {
|
||||
errorPopup(app, "Signature not found - This file is invalid or not meant for this adapter");
|
||||
goto done;
|
||||
}
|
||||
|
||||
// For my development board based on at90usb1287. Need to pass the correct
|
||||
// MCU argument to dfu-programmer
|
||||
if (0 == strcmp("e106420a-7c54-11e5-ae9a-001bfca3c593", adap_sig)) {
|
||||
app->at90usb1287 = 1;
|
||||
} else {
|
||||
app->at90usb1287 = 0;
|
||||
}
|
||||
|
||||
/* Prepare the update dialog widgets... */
|
||||
gtk_label_set_text(lbl_firmware_filename, basename);
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(update_dialog_btnBox), TRUE);
|
||||
app->update_percent = 0;
|
||||
app->update_status = "Ready";
|
||||
updateProgress(data);
|
||||
|
||||
/* Run the dialog */
|
||||
res = gtk_dialog_run(firmware_update_dialog);
|
||||
gtk_widget_hide( GTK_WIDGET(firmware_update_dialog));
|
||||
|
||||
if (res == GTK_RESPONSE_OK) {
|
||||
infoPopup(app, "Update succeeded.");
|
||||
} else if (res == GTK_RESPONSE_REJECT) {
|
||||
errorPopup(app, "Update failed. Suggestion: Do not disconnect the adapter and retry right away!");
|
||||
}
|
||||
printf("Update dialog done\n");
|
||||
|
||||
}
|
||||
|
||||
done:
|
||||
if (filename)
|
||||
g_free(filename);
|
||||
if (basename)
|
||||
g_free(basename);
|
||||
if (dialog)
|
||||
gtk_widget_destroy(dialog);
|
||||
}
|
||||
|
||||
static gboolean periodic_updater(gpointer data)
|
||||
{
|
||||
struct application *app = data;
|
||||
GET_UI_ELEMENT(GtkLabel, label_controller_type);
|
||||
GET_UI_ELEMENT(GtkButton, btn_read_mempak);
|
||||
GET_UI_ELEMENT(GtkButton, btn_write_mempak);
|
||||
GET_UI_ELEMENT(GtkButton, btn_rumble_test);
|
||||
|
||||
if (app->current_adapter_handle && !app->inhibit_periodic_updates) {
|
||||
app->controller_type = gcn64lib_getControllerType(app->current_adapter_handle, 0);
|
||||
gtk_label_set_text(label_controller_type, gcn64lib_controllerName(app->controller_type));
|
||||
|
||||
switch (app->controller_type)
|
||||
{
|
||||
case CTL_TYPE_N64:
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(btn_read_mempak), TRUE);
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(btn_write_mempak), TRUE);
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(btn_rumble_test), TRUE);
|
||||
break;
|
||||
case CTL_TYPE_GC:
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(btn_rumble_test), TRUE);
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(btn_read_mempak), FALSE);
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(btn_write_mempak), FALSE);
|
||||
break;
|
||||
|
||||
default:
|
||||
case CTL_TYPE_NONE:
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(btn_read_mempak), FALSE);
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(btn_write_mempak), FALSE);
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(btn_rumble_test), FALSE);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void updateGuiFromAdapter(struct application *app)
|
||||
{
|
||||
unsigned char buf[32];
|
||||
int n;
|
||||
struct {
|
||||
unsigned char cfg_param;
|
||||
GtkToggleButton *chkbtn;
|
||||
} configurable_bits[] = {
|
||||
// { CFG_PARAM_N64_SQUARE, GET_ELEMENT(GtkCheckButton, chkbtn_n64_square) },
|
||||
// { CFG_PARAM_GC_MAIN_SQUARE, GET_ELEMENT(GtkCheckButton, chkbtn_gc_main_square) },
|
||||
// { CFG_PARAM_GC_CSTICK_SQUARE, GET_ELEMENT(GtkCheckButton, chkbtn_gc_cstick_square) },
|
||||
{ CFG_PARAM_FULL_SLIDERS, GET_ELEMENT(GtkToggleButton, chkbtn_gc_full_sliders) },
|
||||
{ CFG_PARAM_INVERT_TRIG, GET_ELEMENT(GtkToggleButton, chkbtn_gc_invert_trig) },
|
||||
{ },
|
||||
};
|
||||
GET_UI_ELEMENT(GtkLabel, label_product_name);
|
||||
GET_UI_ELEMENT(GtkLabel, label_firmware_version);
|
||||
GET_UI_ELEMENT(GtkLabel, label_usb_id);
|
||||
GET_UI_ELEMENT(GtkLabel, label_device_path);
|
||||
GET_UI_ELEMENT(GtkSpinButton, pollInterval0);
|
||||
int i;
|
||||
struct gcn64_info *info = &app->current_adapter_info;
|
||||
char adap_sig[64];
|
||||
|
||||
if (!app->current_adapter_handle) {
|
||||
deselect_adapter(app);
|
||||
return;
|
||||
}
|
||||
|
||||
if (gcn64lib_getSignature(app->current_adapter_handle, adap_sig, sizeof(adap_sig))) {
|
||||
} else {
|
||||
printf("Adapter signature: %s\n", adap_sig);
|
||||
}
|
||||
|
||||
n = gcn64lib_getConfig(app->current_adapter_handle, CFG_PARAM_POLL_INTERVAL0, buf, sizeof(buf));
|
||||
if (n == 1) {
|
||||
printf("poll interval: %d\n", buf[0]);
|
||||
gtk_spin_button_set_value(pollInterval0, (gdouble)buf[0]);
|
||||
}
|
||||
|
||||
for (i=0; configurable_bits[i].chkbtn; i++) {
|
||||
gcn64lib_getConfig(app->current_adapter_handle, configurable_bits[i].cfg_param, buf, sizeof(buf));
|
||||
printf("config param %02x is %d\n", configurable_bits[i].cfg_param, buf[0]);
|
||||
gtk_toggle_button_set_active(configurable_bits[i].chkbtn, buf[0]);
|
||||
}
|
||||
|
||||
if (sizeof(wchar_t)==4) {
|
||||
gtk_label_set_text(label_product_name, g_ucs4_to_utf8((void*)info->str_prodname, -1, NULL, NULL, NULL));
|
||||
} else {
|
||||
gtk_label_set_text(label_product_name, g_utf16_to_utf8((void*)info->str_prodname, -1, NULL, NULL, NULL));
|
||||
}
|
||||
|
||||
if (0 == gcn64lib_getVersion(app->current_adapter_handle, (char*)buf, sizeof(buf))) {
|
||||
sscanf((char*)buf, "%d.%d.%d", &app->firmware_maj, &app->firmware_min, &app->firmware_build);
|
||||
gtk_label_set_text(label_firmware_version, (char*)buf);
|
||||
|
||||
}
|
||||
|
||||
snprintf((char*)buf, sizeof(buf), "%04x:%04x", info->usb_vid, info->usb_pid);
|
||||
gtk_label_set_text(label_usb_id, (char*)buf);
|
||||
|
||||
gtk_label_set_text(label_device_path, info->str_path);
|
||||
|
||||
periodic_updater(app);
|
||||
}
|
||||
|
||||
G_MODULE_EXPORT void pollIntervalChanged(GtkWidget *win, gpointer data)
|
||||
{
|
||||
struct application *app = data;
|
||||
GET_UI_ELEMENT(GtkSpinButton, pollInterval0);
|
||||
gdouble value;
|
||||
int n;
|
||||
unsigned char buf;
|
||||
|
||||
value = gtk_spin_button_get_value(pollInterval0);
|
||||
printf("Value: %d\n", (int)value);
|
||||
buf = (int)value;
|
||||
|
||||
n = gcn64lib_setConfig(app->current_adapter_handle, CFG_PARAM_POLL_INTERVAL0, &buf, 1);
|
||||
if (n != 0) {
|
||||
errorPopup(app, "Error setting configuration");
|
||||
deselect_adapter(app);
|
||||
rebuild_device_list_store(data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
G_MODULE_EXPORT void config_checkbox_changed(GtkWidget *win, gpointer data)
|
||||
{
|
||||
struct application *app = data;
|
||||
struct {
|
||||
unsigned char cfg_param;
|
||||
GtkToggleButton *chkbtn;
|
||||
} configurable_bits[] = {
|
||||
// { CFG_PARAM_N64_SQUARE, GET_ELEMENT(GtkCheckButton, chkbtn_n64_square) },
|
||||
// { CFG_PARAM_GC_MAIN_SQUARE, GET_ELEMENT(GtkCheckButton, chkbtn_gc_main_square) },
|
||||
// { CFG_PARAM_GC_CSTICK_SQUARE, GET_ELEMENT(GtkCheckButton, chkbtn_gc_cstick_square) },
|
||||
{ CFG_PARAM_FULL_SLIDERS, GET_ELEMENT(GtkToggleButton, chkbtn_gc_full_sliders) },
|
||||
{ CFG_PARAM_INVERT_TRIG, GET_ELEMENT(GtkToggleButton, chkbtn_gc_invert_trig) },
|
||||
{ },
|
||||
};
|
||||
int i, n;
|
||||
unsigned char buf;
|
||||
|
||||
for (i=0; configurable_bits[i].chkbtn; i++) {
|
||||
buf = gtk_toggle_button_get_active(configurable_bits[i].chkbtn);
|
||||
n = gcn64lib_setConfig(app->current_adapter_handle, configurable_bits[i].cfg_param, &buf, 1);
|
||||
if (n != 0) {
|
||||
errorPopup(app, "Error setting configuration");
|
||||
deselect_adapter(app);
|
||||
rebuild_device_list_store(app);
|
||||
break;
|
||||
}
|
||||
printf("cfg param %02x now set to %d\n", configurable_bits[i].cfg_param, buf);
|
||||
}
|
||||
}
|
||||
|
||||
gboolean rebuild_device_list_store(gpointer data)
|
||||
{
|
||||
struct application *app = data;
|
||||
struct gcn64_list_ctx *listctx;
|
||||
struct gcn64_info info;
|
||||
GtkListStore *list_store;
|
||||
GET_UI_ELEMENT(GtkComboBox, cb_adapter_list);
|
||||
|
||||
list_store = GTK_LIST_STORE( gtk_builder_get_object(app->builder, "adaptersList") );
|
||||
|
||||
gtk_list_store_clear(list_store);
|
||||
|
||||
printf("Listing device...\n");
|
||||
listctx = gcn64_allocListCtx();
|
||||
if (!listctx)
|
||||
return FALSE;
|
||||
|
||||
while (gcn64_listDevices(&info, listctx)) {
|
||||
GtkTreeIter iter;
|
||||
printf("Device '%ls'\n", info.str_prodname);
|
||||
gtk_list_store_append(list_store, &iter);
|
||||
if (sizeof(wchar_t)==4) {
|
||||
gtk_list_store_set(list_store, &iter,
|
||||
0, g_ucs4_to_utf8((void*)info.str_serial, -1, NULL, NULL, NULL),
|
||||
1, g_ucs4_to_utf8((void*)info.str_prodname, -1, NULL, NULL, NULL),
|
||||
3, g_memdup(&info, sizeof(struct gcn64_info)),
|
||||
-1);
|
||||
} else {
|
||||
gtk_list_store_set(list_store, &iter,
|
||||
0, g_utf16_to_utf8((void*)info.str_serial, -1, NULL, NULL, NULL),
|
||||
1, g_utf16_to_utf8((void*)info.str_prodname, -1, NULL, NULL, NULL),
|
||||
3, g_memdup(&info, sizeof(struct gcn64_info)),
|
||||
-1);
|
||||
}
|
||||
if (app->current_adapter_handle) {
|
||||
if (!wcscmp(app->current_adapter_info.str_serial, info.str_serial)) {
|
||||
gtk_combo_box_set_active_iter(cb_adapter_list, &iter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gcn64_freeListCtx(listctx);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
G_MODULE_EXPORT void onMainWindowShow(GtkWidget *win, gpointer data)
|
||||
{
|
||||
int res;
|
||||
struct application *app = data;
|
||||
|
||||
res = gcn64_init(1);
|
||||
if (res) {
|
||||
GtkWidget *d = GTK_WIDGET( gtk_builder_get_object(app->builder, "internalError") );
|
||||
gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(d), "gcn64_init failed (returned %d)", res);
|
||||
gtk_widget_show(d);
|
||||
return;
|
||||
}
|
||||
|
||||
rebuild_device_list_store(data);
|
||||
}
|
||||
|
||||
G_MODULE_EXPORT void adapterSelected(GtkComboBox *cb, gpointer data)
|
||||
{
|
||||
struct application *app = data;
|
||||
GtkTreeIter iter;
|
||||
GtkListStore *list_store = GTK_LIST_STORE( gtk_builder_get_object(app->builder, "adaptersList") );
|
||||
GtkWidget *adapter_details = GTK_WIDGET( gtk_builder_get_object(app->builder, "adapterDetails") );
|
||||
struct gcn64_info *info;
|
||||
|
||||
if (app->current_adapter_handle) {
|
||||
gcn64_closeDevice(app->current_adapter_handle);
|
||||
app->current_adapter_handle = NULL;
|
||||
gtk_widget_set_sensitive(adapter_details, FALSE);
|
||||
}
|
||||
|
||||
if (gtk_combo_box_get_active_iter(cb, &iter)) {
|
||||
gtk_tree_model_get(GTK_TREE_MODEL(list_store), &iter, 3, &info, -1);
|
||||
printf("%s\n", info->str_path);
|
||||
|
||||
app->current_adapter_handle = gcn64_openDevice(info);
|
||||
if (!app->current_adapter_handle) {
|
||||
errorPopup(app, "Failed to open adapter");
|
||||
deselect_adapter(app);
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(&app->current_adapter_info, info, sizeof(struct gcn64_info));
|
||||
|
||||
updateGuiFromAdapter(app);
|
||||
gtk_widget_set_sensitive(adapter_details, TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
G_MODULE_EXPORT void onMainWindowHide(GtkWidget *win, gpointer data)
|
||||
{
|
||||
gcn64_shutdown();
|
||||
}
|
||||
|
||||
G_MODULE_EXPORT void suspend_polling(GtkButton *button, gpointer data)
|
||||
{
|
||||
struct application *app = data;
|
||||
|
||||
gcn64lib_suspendPolling(app->current_adapter_handle, 1);
|
||||
}
|
||||
|
||||
G_MODULE_EXPORT void resume_polling(GtkButton *button, gpointer data)
|
||||
{
|
||||
struct application *app = data;
|
||||
|
||||
gcn64lib_suspendPolling(app->current_adapter_handle, 0);
|
||||
}
|
||||
|
||||
G_MODULE_EXPORT void onFileRescan(GtkWidget *wid, gpointer data)
|
||||
{
|
||||
rebuild_device_list_store(data);
|
||||
}
|
||||
|
||||
static int mempak_io_progress_cb(int progress, void *ctx)
|
||||
{
|
||||
struct application *app = ctx;
|
||||
GET_UI_ELEMENT(GtkProgressBar, mempak_io_progress);
|
||||
|
||||
gtk_progress_bar_set_fraction(mempak_io_progress, progress/((gdouble)MEMPAK_MEM_SIZE));
|
||||
while (gtk_events_pending()) {
|
||||
gtk_main_iteration_do(FALSE);
|
||||
}
|
||||
|
||||
return app->stop_mempak_io;
|
||||
}
|
||||
|
||||
G_MODULE_EXPORT void mempak_io_stop(GtkWidget *wid, gpointer data)
|
||||
{
|
||||
struct application *app = data;
|
||||
app->stop_mempak_io = 1;
|
||||
}
|
||||
|
||||
G_MODULE_EXPORT void write_n64_pak(GtkWidget *wid, gpointer data)
|
||||
{
|
||||
struct application *app = data;
|
||||
GET_UI_ELEMENT(GtkWindow, win_mempak_edit);
|
||||
GET_UI_ELEMENT(GtkDialog, mempak_io_dialog);
|
||||
GET_UI_ELEMENT(GtkLabel, mempak_op_label);
|
||||
GtkWidget *confirm_dialog;
|
||||
gint res;
|
||||
|
||||
gtk_widget_show(GTK_WIDGET(win_mempak_edit));
|
||||
|
||||
|
||||
confirm_dialog = gtk_message_dialog_new(GTK_WINDOW(win_mempak_edit), GTK_DIALOG_MODAL,
|
||||
GTK_MESSAGE_QUESTION, 0, "Your memory card will be completely overwritten by the content of the memory pack editor.\n\nAre you sure?");
|
||||
|
||||
gtk_dialog_add_buttons(GTK_DIALOG(confirm_dialog), "Cancel", 1, "Continue", 2, NULL);
|
||||
|
||||
res = gtk_dialog_run(GTK_DIALOG(confirm_dialog));
|
||||
gtk_widget_destroy(confirm_dialog);
|
||||
|
||||
switch(res)
|
||||
{
|
||||
case 2:
|
||||
printf("Confirmed write N64 mempak.\n");
|
||||
|
||||
app->stop_mempak_io = 0;
|
||||
gtk_label_set_text(mempak_op_label, "Writing memory pack...");
|
||||
gtk_widget_show(GTK_WIDGET(mempak_io_dialog));
|
||||
|
||||
res = gcn64lib_mempak_upload(app->current_adapter_handle, 0, mpke_getCurrentMempak(app), mempak_io_progress_cb, app);
|
||||
gtk_widget_hide(GTK_WIDGET(mempak_io_dialog));
|
||||
|
||||
if (res != 0) {
|
||||
switch(res)
|
||||
{
|
||||
case -1: errorPopup(app, "No mempak detected"); break;
|
||||
case -2: errorPopup(app, "I/O error writing to mempak"); break;
|
||||
case -4: errorPopup(app, "Write aborted"); break;
|
||||
default:
|
||||
errorPopup(app, "Error writing to mempak"); break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case 1:
|
||||
default:
|
||||
printf("Write N64 mempak cancelled\n");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
G_MODULE_EXPORT void read_n64_pak(GtkWidget *wid, gpointer data)
|
||||
{
|
||||
struct application *app = data;
|
||||
GET_UI_ELEMENT(GtkWindow, win_mempak_edit);
|
||||
GET_UI_ELEMENT(GtkDialog, mempak_io_dialog);
|
||||
GET_UI_ELEMENT(GtkLabel, mempak_op_label);
|
||||
mempak_structure_t *mpk;
|
||||
int res;
|
||||
|
||||
printf("N64 read mempak\n");
|
||||
if (!app->current_adapter_handle)
|
||||
return;
|
||||
|
||||
gtk_widget_show(GTK_WIDGET(mempak_io_dialog));
|
||||
gtk_label_set_text(mempak_op_label, "Reading memory pack...");
|
||||
|
||||
app->stop_mempak_io = 0;
|
||||
res = gcn64lib_mempak_download(app->current_adapter_handle, 0, &mpk, mempak_io_progress_cb, app);
|
||||
|
||||
gtk_widget_hide(GTK_WIDGET(mempak_io_dialog));
|
||||
if (res != 0) {
|
||||
switch(res)
|
||||
{
|
||||
case -1: errorPopup(app, "No mempak detected"); break;
|
||||
case -2: errorPopup(app, "I/O error reading mempak"); break;
|
||||
case -4: errorPopup(app, "Read aborted"); break;
|
||||
default:
|
||||
case -3: errorPopup(app, "Error reading mempak"); break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
mpke_replaceMpk(app, mpk, NULL);
|
||||
gtk_widget_show(GTK_WIDGET(win_mempak_edit));
|
||||
}
|
||||
}
|
||||
|
||||
G_MODULE_EXPORT void testVibration(GtkWidget *wid, gpointer data)
|
||||
{
|
||||
struct application *app = data;
|
||||
GtkWidget *dialog;
|
||||
|
||||
if ((app->firmware_maj < 3) || (app->firmware_min < 1)) {
|
||||
errorPopup(app, "Firmware 3.1 or later required");
|
||||
} else {
|
||||
if (0 > gcn64lib_forceVibration(app->current_adapter_handle, 0, 1)) {
|
||||
errorPopup(app, "Error setting vibration");
|
||||
} else {
|
||||
dialog = gtk_dialog_new_with_buttons("Vibration test", app->mainwindow, GTK_DIALOG_MODAL, "Stop vibration", GTK_RESPONSE_ACCEPT, NULL);
|
||||
gtk_dialog_run(GTK_DIALOG(dialog));
|
||||
gtk_widget_destroy(dialog);
|
||||
|
||||
gcn64lib_forceVibration(app->current_adapter_handle, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
main( int argc,
|
||||
char **argv )
|
||||
{
|
||||
GtkWindow *window;
|
||||
GError *error = NULL;
|
||||
struct application app = { };
|
||||
|
||||
/* Init GTK+ */
|
||||
gtk_init( &argc, &argv );
|
||||
|
||||
/* Create new GtkBuilder object */
|
||||
app.builder = gtk_builder_new();
|
||||
/* Load UI from file. If error occurs, report it and quit application.
|
||||
* Replace "tut.glade" with your saved project. */
|
||||
if( ! gtk_builder_add_from_file( app.builder, "gui.xml", &error ) )
|
||||
{
|
||||
g_warning( "%s", error->message );
|
||||
g_free( error );
|
||||
return( 1 );
|
||||
}
|
||||
|
||||
app.mpke = mpkedit_new(&app);
|
||||
|
||||
/* Get main window pointer from UI */
|
||||
window = GTK_WINDOW( gtk_builder_get_object( app.builder, "mainWindow" ) );
|
||||
app.mainwindow = window;
|
||||
|
||||
/* Connect signals */
|
||||
gtk_builder_connect_signals( app.builder, &app );
|
||||
|
||||
g_timeout_add_seconds(1, periodic_updater, &app);
|
||||
|
||||
/* Show window. All other widgets are automatically shown by GtkBuilder */
|
||||
gtk_widget_show( GTK_WIDGET(window) );
|
||||
|
||||
/* Start main loop */
|
||||
gtk_main();
|
||||
|
||||
mpkedit_free(app.mpke);
|
||||
|
||||
return( 0 );
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
#include <glib.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "gcn64.h"
|
||||
#include "gcn64lib.h"
|
||||
#include "gcn64ctl_gui_mpkedit.h"
|
||||
|
||||
#define GET_ELEMENT(TYPE, ELEMENT) (TYPE *)gtk_builder_get_object(app->builder, #ELEMENT)
|
||||
#define GET_UI_ELEMENT(TYPE, ELEMENT) TYPE *ELEMENT = GET_ELEMENT(TYPE, ELEMENT)
|
||||
|
||||
void errorPopup(struct application *app, const char *message);
|
||||
|
||||
struct application {
|
||||
GtkBuilder *builder;
|
||||
GtkWindow *mainwindow;
|
||||
|
||||
gcn64_hdl_t current_adapter_handle;
|
||||
struct gcn64_info current_adapter_info;
|
||||
GThread *updater_thread;
|
||||
|
||||
const char *update_status;
|
||||
const char *updateHexFile;
|
||||
int update_percent;
|
||||
int update_dialog_response;
|
||||
|
||||
struct mpkedit_data *mpke;
|
||||
int stop_mempak_io;
|
||||
int inhibit_periodic_updates;
|
||||
int controller_type;
|
||||
int firmware_maj, firmware_min, firmware_build;
|
||||
int at90usb1287;
|
||||
};
|
||||
|
||||
|
@ -1,443 +0,0 @@
|
||||
/* gc_n64_usb : Gamecube or N64 controller to USB adapter firmware
|
||||
Copyright (C) 2007-2015 Raphael Assenat <raph@raphnet.net>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <libgen.h>
|
||||
#include "gcn64ctl_gui.h"
|
||||
#include "mempak.h"
|
||||
|
||||
void mpke_syncModel(struct application *app);
|
||||
|
||||
struct mpkedit_data {
|
||||
struct mempak_structure *mpk;
|
||||
char *filename;
|
||||
int modified;
|
||||
};
|
||||
|
||||
struct mpkedit_data *mpkedit_new(struct application *app)
|
||||
{
|
||||
struct mpkedit_data *mpke;
|
||||
|
||||
mpke = calloc(sizeof(struct mpkedit_data), 1);
|
||||
if (!mpke) {
|
||||
perror("calloc");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mpke->mpk = mempak_new();
|
||||
if (!mpke->mpk) {
|
||||
free(mpke);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return mpke;
|
||||
}
|
||||
|
||||
void mpkedit_free(struct mpkedit_data *mpke)
|
||||
{
|
||||
if (mpke) {
|
||||
free(mpke);
|
||||
}
|
||||
}
|
||||
|
||||
int mpke_getSelection(struct application *app)
|
||||
{
|
||||
GET_UI_ELEMENT(GtkTreeView, n64_notes_treeview);
|
||||
GtkTreeSelection *sel = gtk_tree_view_get_selection(n64_notes_treeview);
|
||||
GList *selected;
|
||||
int sel_id = -1;
|
||||
|
||||
if (!sel) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
selected = gtk_tree_selection_get_selected_rows(sel, NULL);
|
||||
if (selected && selected->data) {
|
||||
GtkTreePath *path;
|
||||
gint *indices;
|
||||
|
||||
path = (GtkTreePath*)selected->data;
|
||||
indices = gtk_tree_path_get_indices(path);
|
||||
sel_id = indices[0];
|
||||
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (selected)
|
||||
g_list_free_full(selected, (GDestroyNotify)gtk_tree_path_free);
|
||||
|
||||
printf("Current selection: %d\n", sel_id);
|
||||
return sel_id;
|
||||
}
|
||||
|
||||
void mpke_syncTitle(struct application *app)
|
||||
{
|
||||
GET_UI_ELEMENT(GtkWindow, win_mempak_edit);
|
||||
char titlebuf[64];
|
||||
|
||||
if (app->mpke->filename) {
|
||||
char *bn = g_path_get_basename(app->mpke->filename);
|
||||
|
||||
snprintf(titlebuf, sizeof(titlebuf), "N64 Mempak editor - %s%s",
|
||||
bn,
|
||||
app->mpke->modified ? " [MODIFIED]":""
|
||||
);
|
||||
g_free(bn);
|
||||
printf("New title: %s\n", titlebuf);
|
||||
gtk_window_set_title(win_mempak_edit, titlebuf);
|
||||
} else {
|
||||
snprintf(titlebuf, sizeof(titlebuf), "N64 Mempak editor%s",
|
||||
app->mpke->modified ? " [NOT SAVED]" : "");
|
||||
}
|
||||
}
|
||||
|
||||
void mpke_updateFilename(struct application *app, char *filename)
|
||||
{
|
||||
if (app->mpke->filename) {
|
||||
// The filename always comes from gtk_file_chooser_get_filename
|
||||
g_free(app->mpke->filename);
|
||||
}
|
||||
|
||||
app->mpke->filename = filename;
|
||||
mpke_syncTitle(app);
|
||||
}
|
||||
|
||||
void mpke_replaceMpk(struct application *app, mempak_structure_t *mpk, char *filename)
|
||||
{
|
||||
|
||||
if (app->mpke->mpk) {
|
||||
mempak_free(app->mpke->mpk);
|
||||
}
|
||||
|
||||
app->mpke->mpk = mpk;
|
||||
|
||||
mpke_syncModel(app);
|
||||
mpke_updateFilename(app, filename);
|
||||
}
|
||||
|
||||
void mpke_syncModel(struct application *app)
|
||||
{
|
||||
GET_UI_ELEMENT(GtkListStore, n64_notes);
|
||||
GET_UI_ELEMENT(GtkTreeView, n64_notes_treeview);
|
||||
GET_UI_ELEMENT(GtkStatusbar, mempak_status_bar);
|
||||
int i, res;
|
||||
char statusbuf[64];
|
||||
|
||||
gtk_list_store_clear(n64_notes);
|
||||
if (!app->mpke->mpk) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (i=0; i<16; i++) {
|
||||
GtkTreeIter iter;
|
||||
entry_structure_t note_data;
|
||||
|
||||
gtk_list_store_append(n64_notes, &iter);
|
||||
|
||||
res = get_mempak_entry(app->mpke->mpk, i, ¬e_data);
|
||||
if (res) {
|
||||
gtk_list_store_set(n64_notes, &iter, 0, i, 1, "!!ERROR!!", 2, 0, -1);
|
||||
} else {
|
||||
if (note_data.valid) {
|
||||
gtk_list_store_set(n64_notes, &iter, 0, i, 1, note_data.name, 2, note_data.blocks, 3, app->mpke->mpk->note_comments[i], -1);
|
||||
} else {
|
||||
gtk_list_store_set(n64_notes, &iter, 0, i, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gtk_tree_view_set_model(n64_notes_treeview, GTK_TREE_MODEL(n64_notes));
|
||||
|
||||
snprintf(statusbuf, sizeof(statusbuf), "Blocks used: %d / %d", 123-get_mempak_free_space(app->mpke->mpk), 123);
|
||||
gtk_statusbar_push(mempak_status_bar, gtk_statusbar_get_context_id(mempak_status_bar, "free blocks"), statusbuf);
|
||||
|
||||
}
|
||||
|
||||
G_MODULE_EXPORT void mpke_export_note(GtkWidget *win, gpointer data)
|
||||
{
|
||||
struct application *app = data;
|
||||
int selection;
|
||||
GtkWidget *dialog;
|
||||
GtkFileChooser *chooser;
|
||||
GET_UI_ELEMENT(GtkWindow, win_mempak_edit);
|
||||
GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_SAVE;
|
||||
GET_UI_ELEMENT(GtkFileFilter, n64_note_filter);
|
||||
int res;
|
||||
|
||||
selection = mpke_getSelection(app);
|
||||
|
||||
if (selection <0) {
|
||||
printf("No selection");
|
||||
return;
|
||||
}
|
||||
|
||||
if (app->mpke->mpk) {
|
||||
entry_structure_t entry;
|
||||
if (0==get_mempak_entry(app->mpke->mpk, selection, &entry)) {
|
||||
char namebuf[64];
|
||||
if (!entry.valid) {
|
||||
errorPopup(app, "Please select a non-empty note");
|
||||
return;
|
||||
}
|
||||
|
||||
dialog = gtk_file_chooser_dialog_new("Save File",
|
||||
win_mempak_edit,
|
||||
action,
|
||||
"_Cancel",
|
||||
GTK_RESPONSE_CANCEL,
|
||||
"_Save",
|
||||
GTK_RESPONSE_ACCEPT,
|
||||
NULL);
|
||||
chooser = GTK_FILE_CHOOSER(dialog);
|
||||
snprintf(namebuf, sizeof(namebuf), "%s.note", entry.name);
|
||||
gtk_file_chooser_set_current_name(chooser, namebuf);
|
||||
gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), n64_note_filter);
|
||||
|
||||
res = gtk_dialog_run (GTK_DIALOG(dialog));
|
||||
if (res == GTK_RESPONSE_ACCEPT) {
|
||||
char *filename;
|
||||
|
||||
filename = gtk_file_chooser_get_filename(chooser);
|
||||
if (mempak_exportNote(app->mpke->mpk, selection, filename)) {
|
||||
errorPopup(app, "Could not export note");
|
||||
} else {
|
||||
printf("Note saved to %s\n", filename);
|
||||
}
|
||||
}
|
||||
|
||||
gtk_widget_destroy(dialog);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
G_MODULE_EXPORT void mpke_insert_note(GtkWidget *win, gpointer data)
|
||||
{
|
||||
struct application *app = data;
|
||||
|
||||
GtkWidget *dialog;
|
||||
GET_UI_ELEMENT(GtkWindow, win_mempak_edit);
|
||||
GET_UI_ELEMENT(GtkFileFilter, n64_note_filter);
|
||||
|
||||
GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
|
||||
int res;
|
||||
|
||||
dialog = gtk_file_chooser_dialog_new("Load N64 mempak image",
|
||||
win_mempak_edit,
|
||||
action,
|
||||
"_Cancel",
|
||||
GTK_RESPONSE_CANCEL,
|
||||
"_Open",
|
||||
GTK_RESPONSE_ACCEPT,
|
||||
NULL);
|
||||
|
||||
gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), n64_note_filter);
|
||||
res = gtk_dialog_run (GTK_DIALOG(dialog));
|
||||
|
||||
if (res == GTK_RESPONSE_ACCEPT) {
|
||||
char *filename;
|
||||
GtkFileChooser *chooser = GTK_FILE_CHOOSER(dialog);
|
||||
entry_structure_t entry;
|
||||
int used_note_id;
|
||||
int dst_id;
|
||||
|
||||
filename = gtk_file_chooser_get_filename(chooser);
|
||||
|
||||
dst_id = mpke_getSelection(app);
|
||||
|
||||
if (0 == get_mempak_entry(app->mpke->mpk, dst_id, &entry)) {
|
||||
if (entry.valid) {
|
||||
// Ask confirmation
|
||||
printf("Ask confirmation\n");
|
||||
}
|
||||
}
|
||||
|
||||
res = mempak_importNote(app->mpke->mpk, filename, dst_id, &used_note_id);
|
||||
if (res) {
|
||||
switch(res)
|
||||
{
|
||||
default:
|
||||
case -1: errorPopup(app, "Error loading file or inserting note\n"); break;
|
||||
case -2: errorPopup(app, "Not enough free blocks to insert note\n"); break;
|
||||
}
|
||||
} else {
|
||||
// Success
|
||||
app->mpke->modified =1;
|
||||
mpke_syncModel(app);
|
||||
mpke_syncTitle(app);
|
||||
}
|
||||
}
|
||||
|
||||
gtk_widget_destroy(dialog);
|
||||
|
||||
}
|
||||
|
||||
G_MODULE_EXPORT void mpke_new(GtkWidget *win, gpointer data)
|
||||
{
|
||||
struct application *app = data;
|
||||
|
||||
app->mpke->modified = 0;
|
||||
mpke_replaceMpk(app, mempak_new(), NULL);
|
||||
}
|
||||
|
||||
G_MODULE_EXPORT void mpke_open(GtkWidget *win, gpointer data)
|
||||
{
|
||||
struct application *app = data;
|
||||
GtkWidget *dialog;
|
||||
GET_UI_ELEMENT(GtkWindow, win_mempak_edit);
|
||||
GET_UI_ELEMENT(GtkFileFilter, n64_mempak_filter);
|
||||
GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
|
||||
int res;
|
||||
|
||||
dialog = gtk_file_chooser_dialog_new("Load N64 mempak image",
|
||||
win_mempak_edit,
|
||||
action,
|
||||
"_Cancel",
|
||||
GTK_RESPONSE_CANCEL,
|
||||
"_Open",
|
||||
GTK_RESPONSE_ACCEPT,
|
||||
NULL);
|
||||
|
||||
gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), n64_mempak_filter);
|
||||
res = gtk_dialog_run (GTK_DIALOG(dialog));
|
||||
if (res == GTK_RESPONSE_ACCEPT) {
|
||||
char *filename;
|
||||
GtkFileChooser *chooser = GTK_FILE_CHOOSER(dialog);
|
||||
mempak_structure_t *mpk;
|
||||
|
||||
filename = gtk_file_chooser_get_filename(chooser);
|
||||
mpk = mempak_loadFromFile(filename);
|
||||
if (mpk) {
|
||||
app->mpke->modified = 0;
|
||||
mpke_replaceMpk(app, mpk, filename);
|
||||
} else {
|
||||
errorPopup(app, "Failed to load mempak");
|
||||
}
|
||||
}
|
||||
|
||||
gtk_widget_destroy(dialog);
|
||||
}
|
||||
|
||||
G_MODULE_EXPORT void mpke_saveas(GtkWidget *win, gpointer data)
|
||||
{
|
||||
struct application *app = data;
|
||||
GtkWidget *dialog;
|
||||
GtkFileChooser *chooser;
|
||||
GET_UI_ELEMENT(GtkWindow, win_mempak_edit);
|
||||
GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_SAVE;
|
||||
GET_UI_ELEMENT(GtkFileFilter, n64_mempak_filter);
|
||||
int res;
|
||||
|
||||
dialog = gtk_file_chooser_dialog_new("Save File",
|
||||
win_mempak_edit,
|
||||
action,
|
||||
"_Cancel",
|
||||
GTK_RESPONSE_CANCEL,
|
||||
"_Save",
|
||||
GTK_RESPONSE_ACCEPT,
|
||||
NULL);
|
||||
chooser = GTK_FILE_CHOOSER(dialog);
|
||||
|
||||
gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), n64_mempak_filter);
|
||||
if (app->mpke->filename) {
|
||||
gchar *bs = g_path_get_basename(app->mpke->filename);
|
||||
gchar *dn = g_path_get_dirname(app->mpke->filename);
|
||||
gtk_file_chooser_set_current_name(chooser, bs);
|
||||
gtk_file_chooser_set_current_folder(chooser, dn);
|
||||
g_free(bs);
|
||||
g_free(dn);
|
||||
} else {
|
||||
gtk_file_chooser_set_current_name(chooser, "mempak.n64");
|
||||
}
|
||||
|
||||
res = gtk_dialog_run (GTK_DIALOG(dialog));
|
||||
if (res == GTK_RESPONSE_ACCEPT) {
|
||||
char *filename;
|
||||
int fmt;
|
||||
|
||||
filename = gtk_file_chooser_get_filename(chooser);
|
||||
fmt = mempak_getFilenameFormat(filename);
|
||||
if (fmt!= MPK_FORMAT_INVALID) {
|
||||
mempak_saveToFile(app->mpke->mpk, filename, fmt);
|
||||
printf("Saved to %s\n", filename);
|
||||
app->mpke->modified = 0;
|
||||
mpke_updateFilename(app,filename);
|
||||
} else {
|
||||
errorPopup(app, "Unknown file format specified");
|
||||
}
|
||||
}
|
||||
|
||||
gtk_widget_destroy(dialog);
|
||||
}
|
||||
|
||||
G_MODULE_EXPORT void mpke_save(GtkWidget *win, gpointer data)
|
||||
{
|
||||
struct application *app = data;
|
||||
|
||||
if (!app->mpke->mpk)
|
||||
return;
|
||||
|
||||
if (!app->mpke->filename) {
|
||||
mpke_saveas(win, data);
|
||||
} else {
|
||||
mempak_saveToFile(app->mpke->mpk, app->mpke->filename, app->mpke->mpk->file_format);
|
||||
app->mpke->modified = 0;
|
||||
mpke_syncTitle(app);
|
||||
}
|
||||
}
|
||||
|
||||
G_MODULE_EXPORT void mpke_delete(GtkWidget *win, gpointer data)
|
||||
{
|
||||
struct application *app = data;
|
||||
int selection;
|
||||
|
||||
selection = mpke_getSelection(app);
|
||||
|
||||
if (selection <0) {
|
||||
printf("No selection");
|
||||
return;
|
||||
}
|
||||
|
||||
if (app->mpke->mpk) {
|
||||
entry_structure_t entry;
|
||||
if (0==get_mempak_entry(app->mpke->mpk, selection, &entry)) {
|
||||
delete_mempak_entry(app->mpke->mpk, &entry);
|
||||
mpke_syncModel(app);
|
||||
app->mpke->modified = 1;
|
||||
mpke_syncTitle(app);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
G_MODULE_EXPORT void n64_note_activated(GtkTreeView *tree_view, GtkTreePath *path, GtkTreeViewColumn *column, gpointer data)
|
||||
{
|
||||
struct application *app = data;
|
||||
|
||||
printf("Yo!\n");
|
||||
}
|
||||
|
||||
G_MODULE_EXPORT void onMempakWindowShow(GtkWidget *win, gpointer data)
|
||||
{
|
||||
struct application *app = data;
|
||||
mpke_syncModel(app);
|
||||
}
|
||||
|
||||
mempak_structure_t *mpke_getCurrentMempak(struct application *app)
|
||||
{
|
||||
return app->mpke->mpk;
|
||||
}
|
||||
|
@ -1,14 +0,0 @@
|
||||
#ifndef gcn64ctl_gui_mpkedit_h__
|
||||
#define gcn64ctl_gui_mpkedit_h__
|
||||
|
||||
#include "mempak.h"
|
||||
|
||||
struct mpkedit_data;
|
||||
struct application;
|
||||
|
||||
struct mpkedit_data *mpkedit_new(struct application *app);
|
||||
void mpkedit_free(struct mpkedit_data *mpke);
|
||||
void mpke_replaceMpk(struct application *app, mempak_structure_t *mpk, char *filename);
|
||||
mempak_structure_t *mpke_getCurrentMempak(struct application *app);
|
||||
|
||||
#endif
|
330
tools/gcn64lib.c
330
tools/gcn64lib.c
@ -1,330 +0,0 @@
|
||||
/* gc_n64_usb : Gamecube or N64 controller to USB adapter firmware
|
||||
Copyright (C) 2007-2015 Raphael Assenat <raph@raphnet.net>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include "gcn64lib.h"
|
||||
#include "../requests.h"
|
||||
#include "../gcn64_protocol.h"
|
||||
#include "hexdump.h"
|
||||
|
||||
int gcn64lib_getConfig(gcn64_hdl_t hdl, unsigned char param, unsigned char *rx, unsigned char rx_max)
|
||||
{
|
||||
unsigned char cmd[2];
|
||||
int n;
|
||||
|
||||
if (!hdl) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
cmd[0] = RQ_GCN64_GET_CONFIG_PARAM;
|
||||
cmd[1] = param;
|
||||
|
||||
n = gcn64_exchange(hdl, cmd, 2, rx, rx_max);
|
||||
if (n<2)
|
||||
return n;
|
||||
|
||||
n -= 2;
|
||||
|
||||
// Drop the leading CMD and PARAM
|
||||
if (n) {
|
||||
memmove(rx, rx+2, n);
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
int gcn64lib_setConfig(gcn64_hdl_t hdl, unsigned char param, unsigned char *data, unsigned char len)
|
||||
{
|
||||
unsigned char cmd[2 + len];
|
||||
int n;
|
||||
|
||||
if (!hdl) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
cmd[0] = RQ_GCN64_SET_CONFIG_PARAM;
|
||||
cmd[1] = param;
|
||||
memcpy(cmd + 2, data, len);
|
||||
|
||||
n = gcn64_exchange(hdl, cmd, 2 + len, cmd, sizeof(cmd));
|
||||
if (n<0)
|
||||
return n;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gcn64lib_suspendPolling(gcn64_hdl_t hdl, unsigned char suspend)
|
||||
{
|
||||
unsigned char cmd[2];
|
||||
int n;
|
||||
|
||||
if (!hdl) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
cmd[0] = RQ_GCN64_SUSPEND_POLLING;
|
||||
cmd[1] = suspend;
|
||||
|
||||
n = gcn64_exchange(hdl, cmd, 2, cmd, sizeof(cmd));
|
||||
if (n<0)
|
||||
return n;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gcn64lib_getVersion(gcn64_hdl_t hdl, char *dst, int dstmax)
|
||||
{
|
||||
unsigned char cmd[32];
|
||||
int n;
|
||||
|
||||
if (!hdl) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (dstmax <= 0)
|
||||
return -1;
|
||||
|
||||
cmd[0] = RQ_GCN64_GET_VERSION;
|
||||
|
||||
n = gcn64_exchange(hdl, cmd, 1, cmd, sizeof(cmd));
|
||||
if (n<0)
|
||||
return n;
|
||||
|
||||
dst[0] = 0;
|
||||
if (n > 1) {
|
||||
strncpy(dst, (char*)cmd+1, n);
|
||||
}
|
||||
dst[dstmax-1] = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gcn64lib_getControllerType(gcn64_hdl_t hdl, int chn)
|
||||
{
|
||||
unsigned char cmd[32];
|
||||
int n;
|
||||
|
||||
if (!hdl) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
cmd[0] = RQ_GCN64_GET_CONTROLLER_TYPE;
|
||||
cmd[1] = chn;
|
||||
|
||||
n = gcn64_exchange(hdl, cmd, 2, cmd, sizeof(cmd));
|
||||
if (n<0)
|
||||
return n;
|
||||
if (n<3)
|
||||
return -1;
|
||||
|
||||
return cmd[2];
|
||||
}
|
||||
|
||||
const char *gcn64lib_controllerName(int type)
|
||||
{
|
||||
switch(type) {
|
||||
case CTL_TYPE_NONE: return "No controller";
|
||||
case CTL_TYPE_N64: return "N64 Controller";
|
||||
case CTL_TYPE_GC: return "GC Controller";
|
||||
case CTL_TYPE_GCKB: return "GC Keyboard";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
int gcn64lib_getSignature(gcn64_hdl_t hdl, char *dst, int dstmax)
|
||||
{
|
||||
unsigned char cmd[40];
|
||||
int n;
|
||||
|
||||
if (!hdl) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (dstmax <= 0)
|
||||
return -1;
|
||||
|
||||
cmd[0] = RQ_GCN64_GET_SIGNATURE;
|
||||
|
||||
n = gcn64_exchange(hdl, cmd, 1, cmd, sizeof(cmd));
|
||||
if (n<0)
|
||||
return n;
|
||||
|
||||
dst[0] = 0;
|
||||
if (n > 1) {
|
||||
strncpy(dst, (char*)cmd+1, n);
|
||||
}
|
||||
dst[dstmax-1] = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gcn64lib_forceVibration(gcn64_hdl_t hdl, unsigned char channel, unsigned char vibrate)
|
||||
{
|
||||
unsigned char cmd[3];
|
||||
int n;
|
||||
|
||||
if (!hdl) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
cmd[0] = RQ_GCN64_SET_VIBRATION;
|
||||
cmd[1] = channel;
|
||||
cmd[2] = vibrate;
|
||||
|
||||
n = gcn64_exchange(hdl, cmd, 3, cmd, sizeof(cmd));
|
||||
if (n<0)
|
||||
return n;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gcn64lib_rawSiCommand(gcn64_hdl_t hdl, unsigned char channel, unsigned char *tx, unsigned char tx_len, unsigned char *rx, unsigned char max_rx)
|
||||
{
|
||||
unsigned char cmd[3 + tx_len];
|
||||
unsigned char rep[3 + 64];
|
||||
int cmdlen, rx_len, n;
|
||||
|
||||
if (!hdl) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!tx) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
cmd[0] = RQ_GCN64_RAW_SI_COMMAND;
|
||||
cmd[1] = channel;
|
||||
cmd[2] = tx_len;
|
||||
memcpy(cmd+3, tx, tx_len);
|
||||
cmdlen = 3 + tx_len;
|
||||
|
||||
n = gcn64_exchange(hdl, cmd, cmdlen, rep, sizeof(rep));
|
||||
if (n<0)
|
||||
return n;
|
||||
|
||||
rx_len = rep[2];
|
||||
if (rx) {
|
||||
memcpy(rx, rep + 3, rx_len);
|
||||
}
|
||||
|
||||
return rx_len;
|
||||
}
|
||||
|
||||
int gcn64lib_16bit_scan(gcn64_hdl_t hdl, unsigned short min, unsigned short max)
|
||||
{
|
||||
int id, n;
|
||||
unsigned char buf[64];
|
||||
|
||||
if (!hdl) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (id = min; id<=max; id++) {
|
||||
buf[0] = id >> 8;
|
||||
buf[1] = id & 0xff;
|
||||
n = gcn64lib_rawSiCommand(hdl, 0, buf, 2, buf, sizeof(buf));
|
||||
if (n > 0) {
|
||||
printf("CMD 0x%04x answer: ", id);
|
||||
printHexBuf(buf, n);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gcn64lib_8bit_scan(gcn64_hdl_t hdl, unsigned char min, unsigned char max)
|
||||
{
|
||||
int id, n;
|
||||
unsigned char buf[64];
|
||||
|
||||
if (!hdl) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (id = min; id<=max; id++) {
|
||||
buf[0] = id;
|
||||
n = gcn64lib_rawSiCommand(hdl, 0, buf, 1, buf, sizeof(buf));
|
||||
if (n > 0) {
|
||||
printf("CMD 0x%02x answer: ", id);
|
||||
printHexBuf(buf, n);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gcn64lib_bootloader(gcn64_hdl_t hdl)
|
||||
{
|
||||
unsigned char cmd[4];
|
||||
int cmdlen;
|
||||
|
||||
if (!hdl) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
cmd[0] = RQ_GCN64_JUMP_TO_BOOTLOADER;
|
||||
cmdlen = 1;
|
||||
|
||||
gcn64_exchange(hdl, cmd, cmdlen, cmd, sizeof(cmd));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gcn64lib_n64_expansionWrite(gcn64_hdl_t hdl, unsigned short addr, unsigned char *data, int len)
|
||||
{
|
||||
unsigned char cmd[3 + len];
|
||||
int cmdlen;
|
||||
int n;
|
||||
|
||||
if (!hdl) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
cmd[0] = N64_EXPANSION_WRITE;
|
||||
cmd[1] = addr>>8; // Address high byte
|
||||
cmd[2] = addr&0xff; // Address low byte
|
||||
memcpy(cmd + 3, data, len);
|
||||
cmdlen = 3 + len;
|
||||
|
||||
n = gcn64lib_rawSiCommand(hdl, 0, cmd, cmdlen, cmd, sizeof(cmd));
|
||||
if (n != 1) {
|
||||
printf("expansion write returned != 1 (%d)\n", n);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return cmd[0];
|
||||
}
|
||||
|
||||
int gcn64lib_n64_expansionRead(gcn64_hdl_t hdl, unsigned short addr, unsigned char *dst, int max_len)
|
||||
{
|
||||
unsigned char cmd[3];
|
||||
int n;
|
||||
|
||||
if (!hdl) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
cmd[0] = N64_EXPANSION_READ;
|
||||
cmd[1] = addr>>8; // Address high byte
|
||||
cmd[2] = addr&0xff; // Address low byte
|
||||
|
||||
n = gcn64lib_rawSiCommand(hdl, 0, cmd, 3, dst, max_len);
|
||||
if (n < 0)
|
||||
return n;
|
||||
|
||||
return n;
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
#ifndef _gcn64_lib_h__
|
||||
#define _gcn64_lib_h__
|
||||
|
||||
#include "gcn64.h"
|
||||
|
||||
#define CTL_TYPE_NONE 0
|
||||
#define CTL_TYPE_N64 1
|
||||
#define CTL_TYPE_GC 2
|
||||
#define CTL_TYPE_GCKB 3
|
||||
|
||||
int gcn64lib_suspendPolling(gcn64_hdl_t hdl, unsigned char suspend);
|
||||
int gcn64lib_setConfig(gcn64_hdl_t hdl, unsigned char param, unsigned char *data, unsigned char len);
|
||||
int gcn64lib_getConfig(gcn64_hdl_t hdl, unsigned char param, unsigned char *rx, unsigned char rx_max);
|
||||
int gcn64lib_rawSiCommand(gcn64_hdl_t hdl, unsigned char channel, unsigned char *tx, unsigned char tx_len, unsigned char *rx, unsigned char max_rx);
|
||||
int gcn64lib_getVersion(gcn64_hdl_t hdl, char *dst, int dstmax);
|
||||
int gcn64lib_getSignature(gcn64_hdl_t hdl, char *dst, int dstmax);
|
||||
int gcn64lib_forceVibration(gcn64_hdl_t hdl, unsigned char channel, unsigned char vibrate);
|
||||
int gcn64lib_getControllerType(gcn64_hdl_t hdl, int chn);
|
||||
const char *gcn64lib_controllerName(int type);
|
||||
int gcn64lib_bootloader(gcn64_hdl_t hdl);
|
||||
|
||||
int gcn64lib_n64_expansionWrite(gcn64_hdl_t hdl, unsigned short addr, unsigned char *data, int len);
|
||||
int gcn64lib_n64_expansionRead(gcn64_hdl_t hdl, unsigned short addr, unsigned char *dst, int max_len);
|
||||
|
||||
int gcn64lib_8bit_scan(gcn64_hdl_t hdl, unsigned char min, unsigned char max);
|
||||
int gcn64lib_16bit_scan(gcn64_hdl_t hdl, unsigned short min, unsigned short max);
|
||||
|
||||
#endif // _gcn64_lib_h__
|
@ -1,5 +0,0 @@
|
||||
#ifndef _gcn64_utils_h__
|
||||
#define _gcn64_utils_h__
|
||||
|
||||
#endif // _gcn64_utils_h__
|
||||
|
@ -1,29 +0,0 @@
|
||||
/* gc_n64_usb : Gamecube or N64 controller to USB adapter firmware
|
||||
Copyright (C) 2007-2015 Raphael Assenat <raph@raphnet.net>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
|
||||
void printHexBuf(unsigned char *buf, int n)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i=0; i<n; i++) {
|
||||
printf("%02x ", buf[i]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
|
@ -1,3 +0,0 @@
|
||||
|
||||
void printHexBuf(unsigned char *buf, int n);
|
||||
|
939
tools/hid.c
939
tools/hid.c
@ -1,939 +0,0 @@
|
||||
/*******************************************************
|
||||
HIDAPI - Multi-Platform library for
|
||||
communication with HID devices.
|
||||
|
||||
Alan Ott
|
||||
Signal 11 Software
|
||||
|
||||
8/22/2009
|
||||
|
||||
Copyright 2009, All Rights Reserved.
|
||||
|
||||
At the discretion of the user of this library,
|
||||
this software may be licensed under the terms of the
|
||||
GNU General Public License v3, a BSD-Style license, or the
|
||||
original HIDAPI license as outlined in the LICENSE.txt,
|
||||
LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt
|
||||
files located at the root of the source distribution.
|
||||
These files may also be found in the public source
|
||||
code repository located at:
|
||||
http://github.com/signal11/hidapi .
|
||||
********************************************************/
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#ifndef _NTDEF_
|
||||
typedef LONG NTSTATUS;
|
||||
#endif
|
||||
|
||||
#ifdef __MINGW32__
|
||||
#include <ntdef.h>
|
||||
#include <winbase.h>
|
||||
#endif
|
||||
|
||||
#ifdef __CYGWIN__
|
||||
#include <ntdef.h>
|
||||
#define _wcsdup wcsdup
|
||||
#endif
|
||||
|
||||
/*#define HIDAPI_USE_DDK*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#include <setupapi.h>
|
||||
#include <winioctl.h>
|
||||
#ifdef HIDAPI_USE_DDK
|
||||
#include <hidsdi.h>
|
||||
#endif
|
||||
|
||||
/* Copied from inc/ddk/hidclass.h, part of the Windows DDK. */
|
||||
#define HID_OUT_CTL_CODE(id) \
|
||||
CTL_CODE(FILE_DEVICE_KEYBOARD, (id), METHOD_OUT_DIRECT, FILE_ANY_ACCESS)
|
||||
#define IOCTL_HID_GET_FEATURE HID_OUT_CTL_CODE(100)
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
#include "hidapi.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
/* Thanks Microsoft, but I know how to use strncpy(). */
|
||||
#pragma warning(disable:4996)
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef HIDAPI_USE_DDK
|
||||
/* Since we're not building with the DDK, and the HID header
|
||||
files aren't part of the SDK, we have to define all this
|
||||
stuff here. In lookup_functions(), the function pointers
|
||||
defined below are set. */
|
||||
typedef struct _HIDD_ATTRIBUTES{
|
||||
ULONG Size;
|
||||
USHORT VendorID;
|
||||
USHORT ProductID;
|
||||
USHORT VersionNumber;
|
||||
} HIDD_ATTRIBUTES, *PHIDD_ATTRIBUTES;
|
||||
|
||||
typedef USHORT USAGE;
|
||||
typedef struct _HIDP_CAPS {
|
||||
USAGE Usage;
|
||||
USAGE UsagePage;
|
||||
USHORT InputReportByteLength;
|
||||
USHORT OutputReportByteLength;
|
||||
USHORT FeatureReportByteLength;
|
||||
USHORT Reserved[17];
|
||||
USHORT fields_not_used_by_hidapi[10];
|
||||
} HIDP_CAPS, *PHIDP_CAPS;
|
||||
typedef void* PHIDP_PREPARSED_DATA;
|
||||
#define HIDP_STATUS_SUCCESS 0x110000
|
||||
|
||||
typedef BOOLEAN (__stdcall *HidD_GetAttributes_)(HANDLE device, PHIDD_ATTRIBUTES attrib);
|
||||
typedef BOOLEAN (__stdcall *HidD_GetSerialNumberString_)(HANDLE device, PVOID buffer, ULONG buffer_len);
|
||||
typedef BOOLEAN (__stdcall *HidD_GetManufacturerString_)(HANDLE handle, PVOID buffer, ULONG buffer_len);
|
||||
typedef BOOLEAN (__stdcall *HidD_GetProductString_)(HANDLE handle, PVOID buffer, ULONG buffer_len);
|
||||
typedef BOOLEAN (__stdcall *HidD_SetFeature_)(HANDLE handle, PVOID data, ULONG length);
|
||||
typedef BOOLEAN (__stdcall *HidD_GetFeature_)(HANDLE handle, PVOID data, ULONG length);
|
||||
typedef BOOLEAN (__stdcall *HidD_GetIndexedString_)(HANDLE handle, ULONG string_index, PVOID buffer, ULONG buffer_len);
|
||||
typedef BOOLEAN (__stdcall *HidD_GetPreparsedData_)(HANDLE handle, PHIDP_PREPARSED_DATA *preparsed_data);
|
||||
typedef BOOLEAN (__stdcall *HidD_FreePreparsedData_)(PHIDP_PREPARSED_DATA preparsed_data);
|
||||
typedef NTSTATUS (__stdcall *HidP_GetCaps_)(PHIDP_PREPARSED_DATA preparsed_data, HIDP_CAPS *caps);
|
||||
typedef BOOLEAN (__stdcall *HidD_SetNumInputBuffers_)(HANDLE handle, ULONG number_buffers);
|
||||
|
||||
static HidD_GetAttributes_ HidD_GetAttributes;
|
||||
static HidD_GetSerialNumberString_ HidD_GetSerialNumberString;
|
||||
static HidD_GetManufacturerString_ HidD_GetManufacturerString;
|
||||
static HidD_GetProductString_ HidD_GetProductString;
|
||||
static HidD_SetFeature_ HidD_SetFeature;
|
||||
static HidD_GetFeature_ HidD_GetFeature;
|
||||
static HidD_GetIndexedString_ HidD_GetIndexedString;
|
||||
static HidD_GetPreparsedData_ HidD_GetPreparsedData;
|
||||
static HidD_FreePreparsedData_ HidD_FreePreparsedData;
|
||||
static HidP_GetCaps_ HidP_GetCaps;
|
||||
static HidD_SetNumInputBuffers_ HidD_SetNumInputBuffers;
|
||||
|
||||
static HMODULE lib_handle = NULL;
|
||||
static BOOLEAN initialized = FALSE;
|
||||
#endif /* HIDAPI_USE_DDK */
|
||||
|
||||
struct hid_device_ {
|
||||
HANDLE device_handle;
|
||||
BOOL blocking;
|
||||
USHORT output_report_length;
|
||||
size_t input_report_length;
|
||||
void *last_error_str;
|
||||
DWORD last_error_num;
|
||||
BOOL read_pending;
|
||||
char *read_buf;
|
||||
OVERLAPPED ol;
|
||||
};
|
||||
|
||||
static hid_device *new_hid_device()
|
||||
{
|
||||
hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device));
|
||||
dev->device_handle = INVALID_HANDLE_VALUE;
|
||||
dev->blocking = TRUE;
|
||||
dev->output_report_length = 0;
|
||||
dev->input_report_length = 0;
|
||||
dev->last_error_str = NULL;
|
||||
dev->last_error_num = 0;
|
||||
dev->read_pending = FALSE;
|
||||
dev->read_buf = NULL;
|
||||
memset(&dev->ol, 0, sizeof(dev->ol));
|
||||
dev->ol.hEvent = CreateEvent(NULL, FALSE, FALSE /*inital state f=nonsignaled*/, NULL);
|
||||
|
||||
return dev;
|
||||
}
|
||||
|
||||
static void free_hid_device(hid_device *dev)
|
||||
{
|
||||
CloseHandle(dev->ol.hEvent);
|
||||
CloseHandle(dev->device_handle);
|
||||
LocalFree(dev->last_error_str);
|
||||
free(dev->read_buf);
|
||||
free(dev);
|
||||
}
|
||||
|
||||
static void register_error(hid_device *device, const char *op)
|
||||
{
|
||||
WCHAR *ptr, *msg;
|
||||
|
||||
FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
||||
FORMAT_MESSAGE_FROM_SYSTEM |
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
NULL,
|
||||
GetLastError(),
|
||||
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
(LPVOID)&msg, 0/*sz*/,
|
||||
NULL);
|
||||
|
||||
/* Get rid of the CR and LF that FormatMessage() sticks at the
|
||||
end of the message. Thanks Microsoft! */
|
||||
ptr = msg;
|
||||
while (*ptr) {
|
||||
if (*ptr == '\r') {
|
||||
*ptr = 0x0000;
|
||||
break;
|
||||
}
|
||||
ptr++;
|
||||
}
|
||||
|
||||
/* Store the message off in the Device entry so that
|
||||
the hid_error() function can pick it up. */
|
||||
LocalFree(device->last_error_str);
|
||||
device->last_error_str = msg;
|
||||
}
|
||||
|
||||
#ifndef HIDAPI_USE_DDK
|
||||
static int lookup_functions()
|
||||
{
|
||||
lib_handle = LoadLibraryA("hid.dll");
|
||||
if (lib_handle) {
|
||||
#define RESOLVE(x) x = (x##_)GetProcAddress(lib_handle, #x); if (!x) return -1;
|
||||
RESOLVE(HidD_GetAttributes);
|
||||
RESOLVE(HidD_GetSerialNumberString);
|
||||
RESOLVE(HidD_GetManufacturerString);
|
||||
RESOLVE(HidD_GetProductString);
|
||||
RESOLVE(HidD_SetFeature);
|
||||
RESOLVE(HidD_GetFeature);
|
||||
RESOLVE(HidD_GetIndexedString);
|
||||
RESOLVE(HidD_GetPreparsedData);
|
||||
RESOLVE(HidD_FreePreparsedData);
|
||||
RESOLVE(HidP_GetCaps);
|
||||
RESOLVE(HidD_SetNumInputBuffers);
|
||||
#undef RESOLVE
|
||||
}
|
||||
else
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static HANDLE open_device(const char *path, BOOL enumerate)
|
||||
{
|
||||
HANDLE handle;
|
||||
DWORD desired_access = (enumerate)? 0: (GENERIC_WRITE | GENERIC_READ);
|
||||
DWORD share_mode = (enumerate)?
|
||||
FILE_SHARE_READ|FILE_SHARE_WRITE:
|
||||
FILE_SHARE_READ;
|
||||
|
||||
handle = CreateFileA(path,
|
||||
desired_access,
|
||||
share_mode,
|
||||
NULL,
|
||||
OPEN_EXISTING,
|
||||
FILE_FLAG_OVERLAPPED,/*FILE_ATTRIBUTE_NORMAL,*/
|
||||
0);
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
int HID_API_EXPORT hid_init(void)
|
||||
{
|
||||
#ifndef HIDAPI_USE_DDK
|
||||
if (!initialized) {
|
||||
if (lookup_functions() < 0) {
|
||||
hid_exit();
|
||||
return -1;
|
||||
}
|
||||
initialized = TRUE;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HID_API_EXPORT hid_exit(void)
|
||||
{
|
||||
#ifndef HIDAPI_USE_DDK
|
||||
if (lib_handle)
|
||||
FreeLibrary(lib_handle);
|
||||
lib_handle = NULL;
|
||||
initialized = FALSE;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id)
|
||||
{
|
||||
BOOL res;
|
||||
struct hid_device_info *root = NULL; /* return object */
|
||||
struct hid_device_info *cur_dev = NULL;
|
||||
|
||||
/* Windows objects for interacting with the driver. */
|
||||
GUID InterfaceClassGuid = {0x4d1e55b2, 0xf16f, 0x11cf, {0x88, 0xcb, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30} };
|
||||
SP_DEVINFO_DATA devinfo_data;
|
||||
SP_DEVICE_INTERFACE_DATA device_interface_data;
|
||||
SP_DEVICE_INTERFACE_DETAIL_DATA_A *device_interface_detail_data = NULL;
|
||||
HDEVINFO device_info_set = INVALID_HANDLE_VALUE;
|
||||
int device_index = 0;
|
||||
int i;
|
||||
|
||||
if (hid_init() < 0)
|
||||
return NULL;
|
||||
|
||||
/* Initialize the Windows objects. */
|
||||
memset(&devinfo_data, 0x0, sizeof(devinfo_data));
|
||||
devinfo_data.cbSize = sizeof(SP_DEVINFO_DATA);
|
||||
device_interface_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
|
||||
|
||||
/* Get information for all the devices belonging to the HID class. */
|
||||
device_info_set = SetupDiGetClassDevsA(&InterfaceClassGuid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
|
||||
|
||||
/* Iterate over each device in the HID class, looking for the right one. */
|
||||
|
||||
for (;;) {
|
||||
HANDLE write_handle = INVALID_HANDLE_VALUE;
|
||||
DWORD required_size = 0;
|
||||
HIDD_ATTRIBUTES attrib;
|
||||
|
||||
res = SetupDiEnumDeviceInterfaces(device_info_set,
|
||||
NULL,
|
||||
&InterfaceClassGuid,
|
||||
device_index,
|
||||
&device_interface_data);
|
||||
|
||||
if (!res) {
|
||||
/* A return of FALSE from this function means that
|
||||
there are no more devices. */
|
||||
break;
|
||||
}
|
||||
|
||||
/* Call with 0-sized detail size, and let the function
|
||||
tell us how long the detail struct needs to be. The
|
||||
size is put in &required_size. */
|
||||
res = SetupDiGetDeviceInterfaceDetailA(device_info_set,
|
||||
&device_interface_data,
|
||||
NULL,
|
||||
0,
|
||||
&required_size,
|
||||
NULL);
|
||||
|
||||
/* Allocate a long enough structure for device_interface_detail_data. */
|
||||
device_interface_detail_data = (SP_DEVICE_INTERFACE_DETAIL_DATA_A*) malloc(required_size);
|
||||
device_interface_detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_A);
|
||||
|
||||
/* Get the detailed data for this device. The detail data gives us
|
||||
the device path for this device, which is then passed into
|
||||
CreateFile() to get a handle to the device. */
|
||||
res = SetupDiGetDeviceInterfaceDetailA(device_info_set,
|
||||
&device_interface_data,
|
||||
device_interface_detail_data,
|
||||
required_size,
|
||||
NULL,
|
||||
NULL);
|
||||
|
||||
if (!res) {
|
||||
/* register_error(dev, "Unable to call SetupDiGetDeviceInterfaceDetail");
|
||||
Continue to the next device. */
|
||||
goto cont;
|
||||
}
|
||||
|
||||
/* Make sure this device is of Setup Class "HIDClass" and has a
|
||||
driver bound to it. */
|
||||
for (i = 0; ; i++) {
|
||||
char driver_name[256];
|
||||
|
||||
/* Populate devinfo_data. This function will return failure
|
||||
when there are no more interfaces left. */
|
||||
res = SetupDiEnumDeviceInfo(device_info_set, i, &devinfo_data);
|
||||
if (!res)
|
||||
goto cont;
|
||||
|
||||
res = SetupDiGetDeviceRegistryPropertyA(device_info_set, &devinfo_data,
|
||||
SPDRP_CLASS, NULL, (PBYTE)driver_name, sizeof(driver_name), NULL);
|
||||
if (!res)
|
||||
goto cont;
|
||||
|
||||
if (strcmp(driver_name, "HIDClass") == 0) {
|
||||
/* See if there's a driver bound. */
|
||||
res = SetupDiGetDeviceRegistryPropertyA(device_info_set, &devinfo_data,
|
||||
SPDRP_DRIVER, NULL, (PBYTE)driver_name, sizeof(driver_name), NULL);
|
||||
if (res)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//wprintf(L"HandleName: %s\n", device_interface_detail_data->DevicePath);
|
||||
|
||||
/* Open a handle to the device */
|
||||
write_handle = open_device(device_interface_detail_data->DevicePath, TRUE);
|
||||
|
||||
/* Check validity of write_handle. */
|
||||
if (write_handle == INVALID_HANDLE_VALUE) {
|
||||
/* Unable to open the device. */
|
||||
//register_error(dev, "CreateFile");
|
||||
goto cont_close;
|
||||
}
|
||||
|
||||
|
||||
/* Get the Vendor ID and Product ID for this device. */
|
||||
attrib.Size = sizeof(HIDD_ATTRIBUTES);
|
||||
HidD_GetAttributes(write_handle, &attrib);
|
||||
//wprintf(L"Product/Vendor: %x %x\n", attrib.ProductID, attrib.VendorID);
|
||||
|
||||
/* Check the VID/PID to see if we should add this
|
||||
device to the enumeration list. */
|
||||
if ((vendor_id == 0x0 || attrib.VendorID == vendor_id) &&
|
||||
(product_id == 0x0 || attrib.ProductID == product_id)) {
|
||||
|
||||
#define WSTR_LEN 512
|
||||
const char *str;
|
||||
struct hid_device_info *tmp;
|
||||
PHIDP_PREPARSED_DATA pp_data = NULL;
|
||||
HIDP_CAPS caps;
|
||||
BOOLEAN res;
|
||||
NTSTATUS nt_res;
|
||||
wchar_t wstr[WSTR_LEN]; /* TODO: Determine Size */
|
||||
size_t len;
|
||||
|
||||
/* VID/PID match. Create the record. */
|
||||
tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info));
|
||||
if (cur_dev) {
|
||||
cur_dev->next = tmp;
|
||||
}
|
||||
else {
|
||||
root = tmp;
|
||||
}
|
||||
cur_dev = tmp;
|
||||
|
||||
/* Get the Usage Page and Usage for this device. */
|
||||
res = HidD_GetPreparsedData(write_handle, &pp_data);
|
||||
if (res) {
|
||||
nt_res = HidP_GetCaps(pp_data, &caps);
|
||||
if (nt_res == HIDP_STATUS_SUCCESS) {
|
||||
cur_dev->usage_page = caps.UsagePage;
|
||||
cur_dev->usage = caps.Usage;
|
||||
}
|
||||
|
||||
HidD_FreePreparsedData(pp_data);
|
||||
}
|
||||
|
||||
/* Fill out the record */
|
||||
cur_dev->next = NULL;
|
||||
str = device_interface_detail_data->DevicePath;
|
||||
if (str) {
|
||||
len = strlen(str);
|
||||
cur_dev->path = (char*) calloc(len+1, sizeof(char));
|
||||
strncpy(cur_dev->path, str, len+1);
|
||||
cur_dev->path[len] = '\0';
|
||||
}
|
||||
else
|
||||
cur_dev->path = NULL;
|
||||
|
||||
/* Serial Number */
|
||||
res = HidD_GetSerialNumberString(write_handle, wstr, sizeof(wstr));
|
||||
wstr[WSTR_LEN-1] = 0x0000;
|
||||
if (res) {
|
||||
cur_dev->serial_number = _wcsdup(wstr);
|
||||
}
|
||||
|
||||
/* Manufacturer String */
|
||||
res = HidD_GetManufacturerString(write_handle, wstr, sizeof(wstr));
|
||||
wstr[WSTR_LEN-1] = 0x0000;
|
||||
if (res) {
|
||||
cur_dev->manufacturer_string = _wcsdup(wstr);
|
||||
}
|
||||
|
||||
/* Product String */
|
||||
res = HidD_GetProductString(write_handle, wstr, sizeof(wstr));
|
||||
wstr[WSTR_LEN-1] = 0x0000;
|
||||
if (res) {
|
||||
cur_dev->product_string = _wcsdup(wstr);
|
||||
}
|
||||
|
||||
/* VID/PID */
|
||||
cur_dev->vendor_id = attrib.VendorID;
|
||||
cur_dev->product_id = attrib.ProductID;
|
||||
|
||||
/* Release Number */
|
||||
cur_dev->release_number = attrib.VersionNumber;
|
||||
|
||||
/* Interface Number. It can sometimes be parsed out of the path
|
||||
on Windows if a device has multiple interfaces. See
|
||||
http://msdn.microsoft.com/en-us/windows/hardware/gg487473 or
|
||||
search for "Hardware IDs for HID Devices" at MSDN. If it's not
|
||||
in the path, it's set to -1. */
|
||||
cur_dev->interface_number = -1;
|
||||
if (cur_dev->path) {
|
||||
char *interface_component = strstr(cur_dev->path, "&mi_");
|
||||
if (interface_component) {
|
||||
char *hex_str = interface_component + 4;
|
||||
char *endptr = NULL;
|
||||
cur_dev->interface_number = strtol(hex_str, &endptr, 16);
|
||||
if (endptr == hex_str) {
|
||||
/* The parsing failed. Set interface_number to -1. */
|
||||
cur_dev->interface_number = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cont_close:
|
||||
CloseHandle(write_handle);
|
||||
cont:
|
||||
/* We no longer need the detail data. It can be freed */
|
||||
free(device_interface_detail_data);
|
||||
|
||||
device_index++;
|
||||
|
||||
}
|
||||
|
||||
/* Close the device information handle. */
|
||||
SetupDiDestroyDeviceInfoList(device_info_set);
|
||||
|
||||
return root;
|
||||
|
||||
}
|
||||
|
||||
void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs)
|
||||
{
|
||||
/* TODO: Merge this with the Linux version. This function is platform-independent. */
|
||||
struct hid_device_info *d = devs;
|
||||
while (d) {
|
||||
struct hid_device_info *next = d->next;
|
||||
free(d->path);
|
||||
free(d->serial_number);
|
||||
free(d->manufacturer_string);
|
||||
free(d->product_string);
|
||||
free(d);
|
||||
d = next;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number)
|
||||
{
|
||||
/* TODO: Merge this functions with the Linux version. This function should be platform independent. */
|
||||
struct hid_device_info *devs, *cur_dev;
|
||||
const char *path_to_open = NULL;
|
||||
hid_device *handle = NULL;
|
||||
|
||||
devs = hid_enumerate(vendor_id, product_id);
|
||||
cur_dev = devs;
|
||||
while (cur_dev) {
|
||||
if (cur_dev->vendor_id == vendor_id &&
|
||||
cur_dev->product_id == product_id) {
|
||||
if (serial_number) {
|
||||
if (wcscmp(serial_number, cur_dev->serial_number) == 0) {
|
||||
path_to_open = cur_dev->path;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
path_to_open = cur_dev->path;
|
||||
break;
|
||||
}
|
||||
}
|
||||
cur_dev = cur_dev->next;
|
||||
}
|
||||
|
||||
if (path_to_open) {
|
||||
/* Open the device */
|
||||
handle = hid_open_path(path_to_open);
|
||||
}
|
||||
|
||||
hid_free_enumeration(devs);
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path)
|
||||
{
|
||||
hid_device *dev;
|
||||
HIDP_CAPS caps;
|
||||
PHIDP_PREPARSED_DATA pp_data = NULL;
|
||||
BOOLEAN res;
|
||||
NTSTATUS nt_res;
|
||||
|
||||
if (hid_init() < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dev = new_hid_device();
|
||||
|
||||
/* Open a handle to the device */
|
||||
dev->device_handle = open_device(path, FALSE);
|
||||
|
||||
/* Check validity of write_handle. */
|
||||
if (dev->device_handle == INVALID_HANDLE_VALUE) {
|
||||
/* Unable to open the device. */
|
||||
register_error(dev, "CreateFile");
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Set the Input Report buffer size to 64 reports. */
|
||||
res = HidD_SetNumInputBuffers(dev->device_handle, 64);
|
||||
if (!res) {
|
||||
register_error(dev, "HidD_SetNumInputBuffers");
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Get the Input Report length for the device. */
|
||||
res = HidD_GetPreparsedData(dev->device_handle, &pp_data);
|
||||
if (!res) {
|
||||
register_error(dev, "HidD_GetPreparsedData");
|
||||
goto err;
|
||||
}
|
||||
nt_res = HidP_GetCaps(pp_data, &caps);
|
||||
if (nt_res != HIDP_STATUS_SUCCESS) {
|
||||
register_error(dev, "HidP_GetCaps");
|
||||
goto err_pp_data;
|
||||
}
|
||||
dev->output_report_length = caps.OutputReportByteLength;
|
||||
dev->input_report_length = caps.InputReportByteLength;
|
||||
HidD_FreePreparsedData(pp_data);
|
||||
|
||||
dev->read_buf = (char*) malloc(dev->input_report_length);
|
||||
|
||||
return dev;
|
||||
|
||||
err_pp_data:
|
||||
HidD_FreePreparsedData(pp_data);
|
||||
err:
|
||||
free_hid_device(dev);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *data, size_t length)
|
||||
{
|
||||
DWORD bytes_written;
|
||||
BOOL res;
|
||||
|
||||
OVERLAPPED ol;
|
||||
unsigned char *buf;
|
||||
memset(&ol, 0, sizeof(ol));
|
||||
|
||||
/* Make sure the right number of bytes are passed to WriteFile. Windows
|
||||
expects the number of bytes which are in the _longest_ report (plus
|
||||
one for the report number) bytes even if the data is a report
|
||||
which is shorter than that. Windows gives us this value in
|
||||
caps.OutputReportByteLength. If a user passes in fewer bytes than this,
|
||||
create a temporary buffer which is the proper size. */
|
||||
if (length >= dev->output_report_length) {
|
||||
/* The user passed the right number of bytes. Use the buffer as-is. */
|
||||
buf = (unsigned char *) data;
|
||||
} else {
|
||||
/* Create a temporary buffer and copy the user's data
|
||||
into it, padding the rest with zeros. */
|
||||
buf = (unsigned char *) malloc(dev->output_report_length);
|
||||
memcpy(buf, data, length);
|
||||
memset(buf + length, 0, dev->output_report_length - length);
|
||||
length = dev->output_report_length;
|
||||
}
|
||||
|
||||
res = WriteFile(dev->device_handle, buf, length, NULL, &ol);
|
||||
|
||||
if (!res) {
|
||||
if (GetLastError() != ERROR_IO_PENDING) {
|
||||
/* WriteFile() failed. Return error. */
|
||||
register_error(dev, "WriteFile");
|
||||
bytes_written = -1;
|
||||
goto end_of_function;
|
||||
}
|
||||
}
|
||||
|
||||
/* Wait here until the write is done. This makes
|
||||
hid_write() synchronous. */
|
||||
res = GetOverlappedResult(dev->device_handle, &ol, &bytes_written, TRUE/*wait*/);
|
||||
if (!res) {
|
||||
/* The Write operation failed. */
|
||||
register_error(dev, "WriteFile");
|
||||
bytes_written = -1;
|
||||
goto end_of_function;
|
||||
}
|
||||
|
||||
end_of_function:
|
||||
if (buf != data)
|
||||
free(buf);
|
||||
|
||||
return bytes_written;
|
||||
}
|
||||
|
||||
|
||||
int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds)
|
||||
{
|
||||
DWORD bytes_read = 0;
|
||||
size_t copy_len = 0;
|
||||
BOOL res;
|
||||
|
||||
/* Copy the handle for convenience. */
|
||||
HANDLE ev = dev->ol.hEvent;
|
||||
|
||||
if (!dev->read_pending) {
|
||||
/* Start an Overlapped I/O read. */
|
||||
dev->read_pending = TRUE;
|
||||
memset(dev->read_buf, 0, dev->input_report_length);
|
||||
ResetEvent(ev);
|
||||
res = ReadFile(dev->device_handle, dev->read_buf, dev->input_report_length, &bytes_read, &dev->ol);
|
||||
|
||||
if (!res) {
|
||||
if (GetLastError() != ERROR_IO_PENDING) {
|
||||
/* ReadFile() has failed.
|
||||
Clean up and return error. */
|
||||
CancelIo(dev->device_handle);
|
||||
dev->read_pending = FALSE;
|
||||
goto end_of_function;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (milliseconds >= 0) {
|
||||
/* See if there is any data yet. */
|
||||
res = WaitForSingleObject(ev, milliseconds);
|
||||
if (res != WAIT_OBJECT_0) {
|
||||
/* There was no data this time. Return zero bytes available,
|
||||
but leave the Overlapped I/O running. */
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Either WaitForSingleObject() told us that ReadFile has completed, or
|
||||
we are in non-blocking mode. Get the number of bytes read. The actual
|
||||
data has been copied to the data[] array which was passed to ReadFile(). */
|
||||
res = GetOverlappedResult(dev->device_handle, &dev->ol, &bytes_read, TRUE/*wait*/);
|
||||
|
||||
/* Set pending back to false, even if GetOverlappedResult() returned error. */
|
||||
dev->read_pending = FALSE;
|
||||
|
||||
if (res && bytes_read > 0) {
|
||||
if (dev->read_buf[0] == 0x0) {
|
||||
/* If report numbers aren't being used, but Windows sticks a report
|
||||
number (0x0) on the beginning of the report anyway. To make this
|
||||
work like the other platforms, and to make it work more like the
|
||||
HID spec, we'll skip over this byte. */
|
||||
bytes_read--;
|
||||
copy_len = length > bytes_read ? bytes_read : length;
|
||||
memcpy(data, dev->read_buf+1, copy_len);
|
||||
}
|
||||
else {
|
||||
/* Copy the whole buffer, report number and all. */
|
||||
copy_len = length > bytes_read ? bytes_read : length;
|
||||
memcpy(data, dev->read_buf, copy_len);
|
||||
}
|
||||
}
|
||||
|
||||
end_of_function:
|
||||
if (!res) {
|
||||
register_error(dev, "GetOverlappedResult");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return copy_len;
|
||||
}
|
||||
|
||||
int HID_API_EXPORT HID_API_CALL hid_read(hid_device *dev, unsigned char *data, size_t length)
|
||||
{
|
||||
return hid_read_timeout(dev, data, length, (dev->blocking)? -1: 0);
|
||||
}
|
||||
|
||||
int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *dev, int nonblock)
|
||||
{
|
||||
dev->blocking = !nonblock;
|
||||
return 0; /* Success */
|
||||
}
|
||||
|
||||
int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length)
|
||||
{
|
||||
BOOL res = HidD_SetFeature(dev->device_handle, (PVOID)data, length);
|
||||
if (!res) {
|
||||
register_error(dev, "HidD_SetFeature");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
|
||||
int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length)
|
||||
{
|
||||
BOOL res;
|
||||
#if 0
|
||||
res = HidD_GetFeature(dev->device_handle, data, length);
|
||||
if (!res) {
|
||||
register_error(dev, "HidD_GetFeature");
|
||||
return -1;
|
||||
}
|
||||
return 0; /* HidD_GetFeature() doesn't give us an actual length, unfortunately */
|
||||
#else
|
||||
DWORD bytes_returned;
|
||||
|
||||
OVERLAPPED ol;
|
||||
memset(&ol, 0, sizeof(ol));
|
||||
|
||||
res = DeviceIoControl(dev->device_handle,
|
||||
IOCTL_HID_GET_FEATURE,
|
||||
data, length,
|
||||
data, length,
|
||||
&bytes_returned, &ol);
|
||||
|
||||
if (!res) {
|
||||
if (GetLastError() != ERROR_IO_PENDING) {
|
||||
/* DeviceIoControl() failed. Return error. */
|
||||
register_error(dev, "Send Feature Report DeviceIoControl");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Wait here until the write is done. This makes
|
||||
hid_get_feature_report() synchronous. */
|
||||
res = GetOverlappedResult(dev->device_handle, &ol, &bytes_returned, TRUE/*wait*/);
|
||||
if (!res) {
|
||||
/* The operation failed. */
|
||||
register_error(dev, "Send Feature Report GetOverLappedResult");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* bytes_returned does not include the first byte which contains the
|
||||
report ID. The data buffer actually contains one more byte than
|
||||
bytes_returned. */
|
||||
bytes_returned++;
|
||||
|
||||
return bytes_returned;
|
||||
#endif
|
||||
}
|
||||
|
||||
void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev)
|
||||
{
|
||||
if (!dev)
|
||||
return;
|
||||
CancelIo(dev->device_handle);
|
||||
free_hid_device(dev);
|
||||
}
|
||||
|
||||
int HID_API_EXPORT_CALL HID_API_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen)
|
||||
{
|
||||
BOOL res;
|
||||
|
||||
res = HidD_GetManufacturerString(dev->device_handle, string, sizeof(wchar_t) * maxlen);
|
||||
if (!res) {
|
||||
register_error(dev, "HidD_GetManufacturerString");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HID_API_EXPORT_CALL HID_API_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen)
|
||||
{
|
||||
BOOL res;
|
||||
|
||||
res = HidD_GetProductString(dev->device_handle, string, sizeof(wchar_t) * maxlen);
|
||||
if (!res) {
|
||||
register_error(dev, "HidD_GetProductString");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HID_API_EXPORT_CALL HID_API_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen)
|
||||
{
|
||||
BOOL res;
|
||||
|
||||
res = HidD_GetSerialNumberString(dev->device_handle, string, sizeof(wchar_t) * maxlen);
|
||||
if (!res) {
|
||||
register_error(dev, "HidD_GetSerialNumberString");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HID_API_EXPORT_CALL HID_API_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen)
|
||||
{
|
||||
BOOL res;
|
||||
|
||||
res = HidD_GetIndexedString(dev->device_handle, string_index, string, sizeof(wchar_t) * maxlen);
|
||||
if (!res) {
|
||||
register_error(dev, "HidD_GetIndexedString");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev)
|
||||
{
|
||||
return (wchar_t*)dev->last_error_str;
|
||||
}
|
||||
|
||||
|
||||
/*#define PICPGM*/
|
||||
/*#define S11*/
|
||||
#define P32
|
||||
#ifdef S11
|
||||
unsigned short VendorID = 0xa0a0;
|
||||
unsigned short ProductID = 0x0001;
|
||||
#endif
|
||||
|
||||
#ifdef P32
|
||||
unsigned short VendorID = 0x04d8;
|
||||
unsigned short ProductID = 0x3f;
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef PICPGM
|
||||
unsigned short VendorID = 0x04d8;
|
||||
unsigned short ProductID = 0x0033;
|
||||
#endif
|
||||
|
||||
|
||||
#if 0
|
||||
int __cdecl main(int argc, char* argv[])
|
||||
{
|
||||
int res;
|
||||
unsigned char buf[65];
|
||||
|
||||
UNREFERENCED_PARAMETER(argc);
|
||||
UNREFERENCED_PARAMETER(argv);
|
||||
|
||||
/* Set up the command buffer. */
|
||||
memset(buf,0x00,sizeof(buf));
|
||||
buf[0] = 0;
|
||||
buf[1] = 0x81;
|
||||
|
||||
|
||||
/* Open the device. */
|
||||
int handle = open(VendorID, ProductID, L"12345");
|
||||
if (handle < 0)
|
||||
printf("unable to open device\n");
|
||||
|
||||
|
||||
/* Toggle LED (cmd 0x80) */
|
||||
buf[1] = 0x80;
|
||||
res = write(handle, buf, 65);
|
||||
if (res < 0)
|
||||
printf("Unable to write()\n");
|
||||
|
||||
/* Request state (cmd 0x81) */
|
||||
buf[1] = 0x81;
|
||||
write(handle, buf, 65);
|
||||
if (res < 0)
|
||||
printf("Unable to write() (2)\n");
|
||||
|
||||
/* Read requested state */
|
||||
read(handle, buf, 65);
|
||||
if (res < 0)
|
||||
printf("Unable to read()\n");
|
||||
|
||||
/* Print out the returned buffer. */
|
||||
for (int i = 0; i < 4; i++)
|
||||
printf("buf[%d]: %d\n", i, buf[i]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
387
tools/hidapi.h
387
tools/hidapi.h
@ -1,387 +0,0 @@
|
||||
/*******************************************************
|
||||
HIDAPI - Multi-Platform library for
|
||||
communication with HID devices.
|
||||
|
||||
Alan Ott
|
||||
Signal 11 Software
|
||||
|
||||
8/22/2009
|
||||
|
||||
Copyright 2009, All Rights Reserved.
|
||||
|
||||
At the discretion of the user of this library,
|
||||
this software may be licensed under the terms of the
|
||||
GNU General Public License v3, a BSD-Style license, or the
|
||||
original HIDAPI license as outlined in the LICENSE.txt,
|
||||
LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt
|
||||
files located at the root of the source distribution.
|
||||
These files may also be found in the public source
|
||||
code repository located at:
|
||||
http://github.com/signal11/hidapi .
|
||||
********************************************************/
|
||||
|
||||
/** @file
|
||||
* @defgroup API hidapi API
|
||||
*/
|
||||
|
||||
#ifndef HIDAPI_H__
|
||||
#define HIDAPI_H__
|
||||
|
||||
#include <wchar.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define HID_API_EXPORT __declspec(dllexport)
|
||||
#define HID_API_CALL
|
||||
#else
|
||||
#define HID_API_EXPORT /**< API export macro */
|
||||
#define HID_API_CALL /**< API call macro */
|
||||
#endif
|
||||
|
||||
#define HID_API_EXPORT_CALL HID_API_EXPORT HID_API_CALL /**< API export and call macro*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
struct hid_device_;
|
||||
typedef struct hid_device_ hid_device; /**< opaque hidapi structure */
|
||||
|
||||
/** hidapi info structure */
|
||||
struct hid_device_info {
|
||||
/** Platform-specific device path */
|
||||
char *path;
|
||||
/** Device Vendor ID */
|
||||
unsigned short vendor_id;
|
||||
/** Device Product ID */
|
||||
unsigned short product_id;
|
||||
/** Serial Number */
|
||||
wchar_t *serial_number;
|
||||
/** Device Release Number in binary-coded decimal,
|
||||
also known as Device Version Number */
|
||||
unsigned short release_number;
|
||||
/** Manufacturer String */
|
||||
wchar_t *manufacturer_string;
|
||||
/** Product string */
|
||||
wchar_t *product_string;
|
||||
/** Usage Page for this Device/Interface
|
||||
(Windows/Mac only). */
|
||||
unsigned short usage_page;
|
||||
/** Usage for this Device/Interface
|
||||
(Windows/Mac only).*/
|
||||
unsigned short usage;
|
||||
/** The USB interface which this logical device
|
||||
represents. Valid on both Linux implementations
|
||||
in all cases, and valid on the Windows implementation
|
||||
only if the device contains more than one interface. */
|
||||
int interface_number;
|
||||
|
||||
/** Pointer to the next device */
|
||||
struct hid_device_info *next;
|
||||
};
|
||||
|
||||
|
||||
/** @brief Initialize the HIDAPI library.
|
||||
|
||||
This function initializes the HIDAPI library. Calling it is not
|
||||
strictly necessary, as it will be called automatically by
|
||||
hid_enumerate() and any of the hid_open_*() functions if it is
|
||||
needed. This function should be called at the beginning of
|
||||
execution however, if there is a chance of HIDAPI handles
|
||||
being opened by different threads simultaneously.
|
||||
|
||||
@ingroup API
|
||||
|
||||
@returns
|
||||
This function returns 0 on success and -1 on error.
|
||||
*/
|
||||
int HID_API_EXPORT HID_API_CALL hid_init(void);
|
||||
|
||||
/** @brief Finalize the HIDAPI library.
|
||||
|
||||
This function frees all of the static data associated with
|
||||
HIDAPI. It should be called at the end of execution to avoid
|
||||
memory leaks.
|
||||
|
||||
@ingroup API
|
||||
|
||||
@returns
|
||||
This function returns 0 on success and -1 on error.
|
||||
*/
|
||||
int HID_API_EXPORT HID_API_CALL hid_exit(void);
|
||||
|
||||
/** @brief Enumerate the HID Devices.
|
||||
|
||||
This function returns a linked list of all the HID devices
|
||||
attached to the system which match vendor_id and product_id.
|
||||
If @p vendor_id is set to 0 then any vendor matches.
|
||||
If @p product_id is set to 0 then any product matches.
|
||||
If @p vendor_id and @p product_id are both set to 0, then
|
||||
all HID devices will be returned.
|
||||
|
||||
@ingroup API
|
||||
@param vendor_id The Vendor ID (VID) of the types of device
|
||||
to open.
|
||||
@param product_id The Product ID (PID) of the types of
|
||||
device to open.
|
||||
|
||||
@returns
|
||||
This function returns a pointer to a linked list of type
|
||||
struct #hid_device, containing information about the HID devices
|
||||
attached to the system, or NULL in the case of failure. Free
|
||||
this linked list by calling hid_free_enumeration().
|
||||
*/
|
||||
struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id);
|
||||
|
||||
/** @brief Free an enumeration Linked List
|
||||
|
||||
This function frees a linked list created by hid_enumerate().
|
||||
|
||||
@ingroup API
|
||||
@param devs Pointer to a list of struct_device returned from
|
||||
hid_enumerate().
|
||||
*/
|
||||
void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs);
|
||||
|
||||
/** @brief Open a HID device using a Vendor ID (VID), Product ID
|
||||
(PID) and optionally a serial number.
|
||||
|
||||
If @p serial_number is NULL, the first device with the
|
||||
specified VID and PID is opened.
|
||||
|
||||
@ingroup API
|
||||
@param vendor_id The Vendor ID (VID) of the device to open.
|
||||
@param product_id The Product ID (PID) of the device to open.
|
||||
@param serial_number The Serial Number of the device to open
|
||||
(Optionally NULL).
|
||||
|
||||
@returns
|
||||
This function returns a pointer to a #hid_device object on
|
||||
success or NULL on failure.
|
||||
*/
|
||||
HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number);
|
||||
|
||||
/** @brief Open a HID device by its path name.
|
||||
|
||||
The path name be determined by calling hid_enumerate(), or a
|
||||
platform-specific path name can be used (eg: /dev/hidraw0 on
|
||||
Linux).
|
||||
|
||||
@ingroup API
|
||||
@param path The path name of the device to open
|
||||
|
||||
@returns
|
||||
This function returns a pointer to a #hid_device object on
|
||||
success or NULL on failure.
|
||||
*/
|
||||
HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path);
|
||||
|
||||
/** @brief Write an Output report to a HID device.
|
||||
|
||||
The first byte of @p data[] must contain the Report ID. For
|
||||
devices which only support a single report, this must be set
|
||||
to 0x0. The remaining bytes contain the report data. Since
|
||||
the Report ID is mandatory, calls to hid_write() will always
|
||||
contain one more byte than the report contains. For example,
|
||||
if a hid report is 16 bytes long, 17 bytes must be passed to
|
||||
hid_write(), the Report ID (or 0x0, for devices with a
|
||||
single report), followed by the report data (16 bytes). In
|
||||
this example, the length passed in would be 17.
|
||||
|
||||
hid_write() will send the data on the first OUT endpoint, if
|
||||
one exists. If it does not, it will send the data through
|
||||
the Control Endpoint (Endpoint 0).
|
||||
|
||||
@ingroup API
|
||||
@param device A device handle returned from hid_open().
|
||||
@param data The data to send, including the report number as
|
||||
the first byte.
|
||||
@param length The length in bytes of the data to send.
|
||||
|
||||
@returns
|
||||
This function returns the actual number of bytes written and
|
||||
-1 on error.
|
||||
*/
|
||||
int HID_API_EXPORT HID_API_CALL hid_write(hid_device *device, const unsigned char *data, size_t length);
|
||||
|
||||
/** @brief Read an Input report from a HID device with timeout.
|
||||
|
||||
Input reports are returned
|
||||
to the host through the INTERRUPT IN endpoint. The first byte will
|
||||
contain the Report number if the device uses numbered reports.
|
||||
|
||||
@ingroup API
|
||||
@param device A device handle returned from hid_open().
|
||||
@param data A buffer to put the read data into.
|
||||
@param length The number of bytes to read. For devices with
|
||||
multiple reports, make sure to read an extra byte for
|
||||
the report number.
|
||||
@param milliseconds timeout in milliseconds or -1 for blocking wait.
|
||||
|
||||
@returns
|
||||
This function returns the actual number of bytes read and
|
||||
-1 on error. If no packet was available to be read within
|
||||
the timeout period, this function returns 0.
|
||||
*/
|
||||
int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds);
|
||||
|
||||
/** @brief Read an Input report from a HID device.
|
||||
|
||||
Input reports are returned
|
||||
to the host through the INTERRUPT IN endpoint. The first byte will
|
||||
contain the Report number if the device uses numbered reports.
|
||||
|
||||
@ingroup API
|
||||
@param device A device handle returned from hid_open().
|
||||
@param data A buffer to put the read data into.
|
||||
@param length The number of bytes to read. For devices with
|
||||
multiple reports, make sure to read an extra byte for
|
||||
the report number.
|
||||
|
||||
@returns
|
||||
This function returns the actual number of bytes read and
|
||||
-1 on error. If no packet was available to be read and
|
||||
the handle is in non-blocking mode, this function returns 0.
|
||||
*/
|
||||
int HID_API_EXPORT HID_API_CALL hid_read(hid_device *device, unsigned char *data, size_t length);
|
||||
|
||||
/** @brief Set the device handle to be non-blocking.
|
||||
|
||||
In non-blocking mode calls to hid_read() will return
|
||||
immediately with a value of 0 if there is no data to be
|
||||
read. In blocking mode, hid_read() will wait (block) until
|
||||
there is data to read before returning.
|
||||
|
||||
Nonblocking can be turned on and off at any time.
|
||||
|
||||
@ingroup API
|
||||
@param device A device handle returned from hid_open().
|
||||
@param nonblock enable or not the nonblocking reads
|
||||
- 1 to enable nonblocking
|
||||
- 0 to disable nonblocking.
|
||||
|
||||
@returns
|
||||
This function returns 0 on success and -1 on error.
|
||||
*/
|
||||
int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *device, int nonblock);
|
||||
|
||||
/** @brief Send a Feature report to the device.
|
||||
|
||||
Feature reports are sent over the Control endpoint as a
|
||||
Set_Report transfer. The first byte of @p data[] must
|
||||
contain the Report ID. For devices which only support a
|
||||
single report, this must be set to 0x0. The remaining bytes
|
||||
contain the report data. Since the Report ID is mandatory,
|
||||
calls to hid_send_feature_report() will always contain one
|
||||
more byte than the report contains. For example, if a hid
|
||||
report is 16 bytes long, 17 bytes must be passed to
|
||||
hid_send_feature_report(): the Report ID (or 0x0, for
|
||||
devices which do not use numbered reports), followed by the
|
||||
report data (16 bytes). In this example, the length passed
|
||||
in would be 17.
|
||||
|
||||
@ingroup API
|
||||
@param device A device handle returned from hid_open().
|
||||
@param data The data to send, including the report number as
|
||||
the first byte.
|
||||
@param length The length in bytes of the data to send, including
|
||||
the report number.
|
||||
|
||||
@returns
|
||||
This function returns the actual number of bytes written and
|
||||
-1 on error.
|
||||
*/
|
||||
int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *device, const unsigned char *data, size_t length);
|
||||
|
||||
/** @brief Get a feature report from a HID device.
|
||||
|
||||
Make sure to set the first byte of @p data[] to the Report
|
||||
ID of the report to be read. Make sure to allow space for
|
||||
this extra byte in @p data[].
|
||||
|
||||
@ingroup API
|
||||
@param device A device handle returned from hid_open().
|
||||
@param data A buffer to put the read data into, including
|
||||
the Report ID. Set the first byte of @p data[] to the
|
||||
Report ID of the report to be read.
|
||||
@param length The number of bytes to read, including an
|
||||
extra byte for the report ID. The buffer can be longer
|
||||
than the actual report.
|
||||
|
||||
@returns
|
||||
This function returns the number of bytes read and
|
||||
-1 on error.
|
||||
*/
|
||||
int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *device, unsigned char *data, size_t length);
|
||||
|
||||
/** @brief Close a HID device.
|
||||
|
||||
@ingroup API
|
||||
@param device A device handle returned from hid_open().
|
||||
*/
|
||||
void HID_API_EXPORT HID_API_CALL hid_close(hid_device *device);
|
||||
|
||||
/** @brief Get The Manufacturer String from a HID device.
|
||||
|
||||
@ingroup API
|
||||
@param device A device handle returned from hid_open().
|
||||
@param string A wide string buffer to put the data into.
|
||||
@param maxlen The length of the buffer in multiples of wchar_t.
|
||||
|
||||
@returns
|
||||
This function returns 0 on success and -1 on error.
|
||||
*/
|
||||
int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *device, wchar_t *string, size_t maxlen);
|
||||
|
||||
/** @brief Get The Product String from a HID device.
|
||||
|
||||
@ingroup API
|
||||
@param device A device handle returned from hid_open().
|
||||
@param string A wide string buffer to put the data into.
|
||||
@param maxlen The length of the buffer in multiples of wchar_t.
|
||||
|
||||
@returns
|
||||
This function returns 0 on success and -1 on error.
|
||||
*/
|
||||
int HID_API_EXPORT_CALL hid_get_product_string(hid_device *device, wchar_t *string, size_t maxlen);
|
||||
|
||||
/** @brief Get The Serial Number String from a HID device.
|
||||
|
||||
@ingroup API
|
||||
@param device A device handle returned from hid_open().
|
||||
@param string A wide string buffer to put the data into.
|
||||
@param maxlen The length of the buffer in multiples of wchar_t.
|
||||
|
||||
@returns
|
||||
This function returns 0 on success and -1 on error.
|
||||
*/
|
||||
int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *device, wchar_t *string, size_t maxlen);
|
||||
|
||||
/** @brief Get a string from a HID device, based on its string index.
|
||||
|
||||
@ingroup API
|
||||
@param device A device handle returned from hid_open().
|
||||
@param string_index The index of the string to get.
|
||||
@param string A wide string buffer to put the data into.
|
||||
@param maxlen The length of the buffer in multiples of wchar_t.
|
||||
|
||||
@returns
|
||||
This function returns 0 on success and -1 on error.
|
||||
*/
|
||||
int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *device, int string_index, wchar_t *string, size_t maxlen);
|
||||
|
||||
/** @brief Get a string describing the last error which occurred.
|
||||
|
||||
@ingroup API
|
||||
@param device A device handle returned from hid_open().
|
||||
|
||||
@returns
|
||||
This function returns a string containing the last error
|
||||
which occurred or NULL if none has occurred.
|
||||
*/
|
||||
HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *device);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
BIN
tools/icon.ico
BIN
tools/icon.ico
Binary file not shown.
Before Width: | Height: | Size: 14 KiB |
159
tools/ihex.c
159
tools/ihex.c
@ -1,159 +0,0 @@
|
||||
/* gc_n64_usb : Gamecube or N64 controller to USB adapter firmware
|
||||
Copyright (C) 2007-2015 Raphael Assenat <raph@raphnet.net>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "hexdump.h"
|
||||
|
||||
static unsigned char chk(unsigned char *buf, int len)
|
||||
{
|
||||
int i;
|
||||
unsigned char r = 0;
|
||||
|
||||
for (i=0; i<len; i++) {
|
||||
r += buf[i];
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/* \return The highest address written to, or negative on errors.
|
||||
*/
|
||||
int load_ihex(const char *file, unsigned char *dstbuf, int bufsize)
|
||||
{
|
||||
FILE *fptr;
|
||||
char linebuf[550];
|
||||
unsigned char databuf[2+1+255+1];
|
||||
int ret = 0;
|
||||
int line = 0;
|
||||
int eof_seen = 0;
|
||||
unsigned int max_address = 0;
|
||||
unsigned int offset = 0;
|
||||
|
||||
fptr = fopen(file, "r");
|
||||
if (!fptr) {
|
||||
perror("fopen");
|
||||
return -1;
|
||||
}
|
||||
|
||||
do {
|
||||
if (fgets(linebuf, sizeof(linebuf), fptr)) {
|
||||
unsigned int data_count;
|
||||
unsigned int address;
|
||||
int input_nibbles, input_bytes;
|
||||
int i;
|
||||
|
||||
line++;
|
||||
|
||||
if (linebuf[0] != ':') {
|
||||
fprintf(stderr, "Ignored invalid line %d\n", line);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (eof_seen) {
|
||||
fprintf(stderr, "extra data after EOF record in hex file\n");
|
||||
ret = -7;
|
||||
goto err;
|
||||
}
|
||||
|
||||
// :10 0000 00 92C064C7ABC0AAC0A9C0A8C0A7C0A6C0 00
|
||||
// ^ ^ ^ ^ ^-- Checksum
|
||||
// | | | +----- Data [data_count]
|
||||
// | | +---- Record type
|
||||
// | +------ Address
|
||||
// +----- data_count
|
||||
//
|
||||
|
||||
input_nibbles = strlen(linebuf) - 1;
|
||||
|
||||
for (input_bytes=0,i=0; i<input_nibbles; i+=2) {
|
||||
unsigned int byte;
|
||||
if (1 != sscanf(linebuf + 1 + i, "%02x", &byte)) {
|
||||
break;
|
||||
}
|
||||
databuf[input_bytes] = byte;
|
||||
input_bytes++;
|
||||
}
|
||||
|
||||
//printf("Input bytes: %d\n", input_bytes);
|
||||
//printHexBuf(databuf, input_bytes);
|
||||
|
||||
// Validate the record checksum
|
||||
if (chk(databuf, input_bytes)) {
|
||||
fprintf(stderr, "Bad checksum at line %d\n", line);
|
||||
ret = -4;
|
||||
goto err;
|
||||
}
|
||||
|
||||
// Data length sanity check
|
||||
data_count = databuf[0];
|
||||
if (input_bytes != 1+2+1+data_count+1) {
|
||||
fprintf(stderr, "Invalid record (less data than expected) at line %d\n", line);
|
||||
ret = -5;
|
||||
goto err;
|
||||
}
|
||||
|
||||
address = databuf[1]<<8 | databuf[2];
|
||||
|
||||
switch(databuf[3])
|
||||
{
|
||||
case 0x00: // Data
|
||||
if (address + offset + data_count > bufsize) {
|
||||
fprintf(stderr, "hex file too large\n");
|
||||
ret = -6;
|
||||
goto err;
|
||||
}
|
||||
if (address + offset + data_count > max_address) {
|
||||
max_address = address + offset + data_count;
|
||||
}
|
||||
memcpy(dstbuf + address + offset, databuf + 4, data_count);
|
||||
break;
|
||||
|
||||
case 0x01: // EOF
|
||||
eof_seen = 1;
|
||||
break;
|
||||
|
||||
case 0x04: // Extended linear address
|
||||
if (data_count != 2) {
|
||||
fprintf(stderr, "ihex parser: Malformatted 0x04 record at line %d\n", line);
|
||||
ret = -8;
|
||||
goto err;
|
||||
}
|
||||
offset = (databuf[4] << 24) | (databuf[5] << 16);
|
||||
//printf("OFfset: 0x%08x\n", offset);
|
||||
break;
|
||||
|
||||
case 0x03: // Start segment address
|
||||
case 0x05: // Start linear address
|
||||
// Ignored
|
||||
break;
|
||||
|
||||
default:
|
||||
case 0x02: // Extended segment address
|
||||
fprintf(stderr, "ihex parser: Unimplemented record type 0x%02x at line %d\n", databuf[3], line);
|
||||
ret = -2;
|
||||
goto err;
|
||||
}
|
||||
|
||||
}
|
||||
} while (!feof(fptr));
|
||||
|
||||
fclose(fptr);
|
||||
return max_address;
|
||||
|
||||
err:
|
||||
fclose(fptr);
|
||||
return ret;
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
#ifndef _ihex_h__
|
||||
#define _ihex_h__
|
||||
|
||||
/* \return File size, or negative value on error.*/
|
||||
int load_ihex(const char *file, unsigned char *dstbuf, int bufsize);
|
||||
|
||||
#endif // _ihex_h__
|
||||
|
1
tools/installers/.gitignore
vendored
1
tools/installers/.gitignore
vendored
@ -1 +0,0 @@
|
||||
*.exe
|
614
tools/main.c
614
tools/main.c
@ -1,614 +0,0 @@
|
||||
/* gc_n64_usb : Gamecube or N64 controller to USB adapter firmware
|
||||
Copyright (C) 2007-2015 Raphael Assenat <raph@raphnet.net>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <getopt.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#include "hexdump.h"
|
||||
#include "version.h"
|
||||
#include "gcn64.h"
|
||||
#include "gcn64lib.h"
|
||||
#include "gc2n64_adapter.h"
|
||||
#include "mempak.h"
|
||||
#include "mempak_gcn64usb.h"
|
||||
#include "../requests.h"
|
||||
#include "../gcn64_protocol.h"
|
||||
|
||||
static void printUsage(void)
|
||||
{
|
||||
printf("./gcn64_ctl [OPTION]... [COMMAND]....\n");
|
||||
printf("Control tool for GC/N64 to USB adapter. Version %s\n", VERSION_STR);
|
||||
printf("\n");
|
||||
printf("Options:\n");
|
||||
printf(" -h, --help Print help\n");
|
||||
printf(" -l, --list List devices\n");
|
||||
printf(" -s serial Operate on specified device (required unless -f is specified)\n");
|
||||
printf(" -f, --force If no serial is specified, use first device detected.\n");
|
||||
printf(" -o, --outfile file Output file for read operations (eg: --n64-mempak-dump)\n");
|
||||
//printf(" -i, --infile file Input file for write operations (eg: --gc_to_n64_update)\n");
|
||||
printf(" --nonstop Continue testing forever or until an error occurs.\n");
|
||||
printf("\n");
|
||||
printf("Configuration commands:\n");
|
||||
printf(" --get_version Read adapter firmware version\n");
|
||||
printf(" --set_serial serial Assign a new device serial number\n");
|
||||
printf(" --get_serial Read serial from eeprom\n");
|
||||
printf(" --set_poll_rate ms Set time between controller polls in milliseconds\n");
|
||||
printf(" --get_poll_rate Read configured poll rate\n");
|
||||
printf(" --get_controller_type Display the type of controller currently connected\n");
|
||||
printf("\n");
|
||||
printf("Advanced commands:\n");
|
||||
printf(" --bootloader Re-enumerate in bootloader mode\n");
|
||||
printf(" --suspend_polling Stop polling the controller\n");
|
||||
printf(" --resume_polling Re-start polling the controller\n");
|
||||
printf(" --get_signature Get the firmware signature\n");
|
||||
printf("\n");
|
||||
printf("Raw controller commands:\n");
|
||||
printf(" --n64_getstatus Read N64 controller status now\n");
|
||||
printf(" --gc_getstatus Read GC controller status now (turns rumble OFF)\n");
|
||||
printf(" --gc_getstatus_rumble Read GC controller status now (turns rumble ON)\n");
|
||||
printf(" --n64_getcaps Get N64 controller capabilities (or status such as pak present)\n");
|
||||
printf(" --n64_mempak_dump Dump N64 mempak contents (Use with --outfile to write to file)\n");
|
||||
printf(" --n64_mempak_write file Write file to N64 mempak\n");
|
||||
printf("\n");
|
||||
printf("GC to N64 adapter commands: (For GC to N64 adapter connected to GC/N64 to USB adapter)\n");
|
||||
printf(" --gc_to_n64_info Display info on adapter (version, config, etc)\n");
|
||||
printf(" --gc_to_n64_update file.hex Update GC to N64 adapter firmware\n");
|
||||
printf(" --gc_to_n64_read_mapping id Dump a mapping (Use with --outfile to write to file)\n");
|
||||
printf(" --gc_to_n64_load_mapping file Load a mapping from a file and send it to the adapter\n");
|
||||
printf(" --gc_to_n64_store_current_mapping slot Store the current mapping to one of the D-Pad slots.\n");
|
||||
printf("\n");
|
||||
printf("GC to N64 adapter, development/debug commands:\n");
|
||||
printf(" --gc_to_n64_echotest Perform a communication test (usable with --nonstop)\n");
|
||||
printf(" --gc_to_n64_dump Display the firmware content in hex.\n");
|
||||
printf(" --gc_to_n64_enter_bootloader Jump to the bootloader.\n");
|
||||
printf(" --gc_to_n64_boot_application Exit bootloader and start application.\n");
|
||||
printf("\n");
|
||||
printf("Development/Experimental/Research commands: (use at your own risk)\n");
|
||||
printf(" --si_8bit_scan Try all possible 1-byte commands, to see which one a controller responds to.\n");
|
||||
printf(" --si_16bit_scan Try all possible 2-byte commands, to see which one a controller responds to.\n");
|
||||
}
|
||||
|
||||
|
||||
#define OPT_OUTFILE 'o'
|
||||
#define OPT_INFILE 'i'
|
||||
#define OPT_SET_SERIAL 257
|
||||
#define OPT_GET_SERIAL 258
|
||||
#define OPT_BOOTLOADER 300
|
||||
#define OPT_N64_GETSTATUS 301
|
||||
#define OPT_GC_GETSTATUS 302
|
||||
#define OPT_GC_GETSTATUS_RUMBLE 303
|
||||
#define OPT_N64_MEMPAK_DUMP 304
|
||||
#define OPT_N64_GETCAPS 305
|
||||
#define OPT_SUSPEND_POLLING 306
|
||||
#define OPT_RESUME_POLLING 307
|
||||
#define OPT_SET_POLL_INTERVAL 308
|
||||
#define OPT_GET_POLL_INTERVAL 309
|
||||
#define OPT_N64_MEMPAK_WRITE 310
|
||||
#define OPT_SI8BIT_SCAN 311
|
||||
#define OPT_SI16BIT_SCAN 312
|
||||
#define OPT_GC_TO_N64_INFO 313
|
||||
#define OPT_GC_TO_N64_TEST 314
|
||||
#define OPT_GC_TO_N64_UPDATE 315
|
||||
#define OPT_GC_TO_N64_DUMP 316
|
||||
#define OPT_GC_TO_N64_ENTER_BOOTLOADER 317
|
||||
#define OPT_GC_TO_N64_BOOT_APPLICATION 318
|
||||
#define OPT_NONSTOP 319
|
||||
#define OPT_GC_TO_N64_READ_MAPPING 320
|
||||
#define OPT_GC_TO_N64_LOAD_MAPPING 321
|
||||
#define OPT_GC_TO_N64_STORE_CURRENT_MAPPING 322
|
||||
#define OPT_GET_VERSION 323
|
||||
#define OPT_GET_SIGNATURE 324
|
||||
#define OPT_GET_CTLTYPE 325
|
||||
|
||||
struct option longopts[] = {
|
||||
{ "help", 0, NULL, 'h' },
|
||||
{ "list", 0, NULL, 'l' },
|
||||
{ "force", 0, NULL, 'f' },
|
||||
{ "set_serial", 1, NULL, OPT_SET_SERIAL },
|
||||
{ "get_serial", 0, NULL, OPT_GET_SERIAL },
|
||||
{ "bootloader", 0, NULL, OPT_BOOTLOADER },
|
||||
{ "n64_getstatus", 0, NULL, OPT_N64_GETSTATUS },
|
||||
{ "gc_getstatus", 0, NULL, OPT_GC_GETSTATUS },
|
||||
{ "gc_getstatus_rumble", 0, NULL, OPT_GC_GETSTATUS_RUMBLE },
|
||||
{ "n64_getcaps", 0, NULL, OPT_N64_GETCAPS },
|
||||
{ "n64_mempak_dump", 0, NULL, OPT_N64_MEMPAK_DUMP },
|
||||
{ "suspend_polling", 0, NULL, OPT_SUSPEND_POLLING },
|
||||
{ "resume_polling", 0, NULL, OPT_RESUME_POLLING },
|
||||
{ "outfile", 1, NULL, OPT_OUTFILE },
|
||||
{ "infile", 1, NULL, OPT_INFILE },
|
||||
{ "set_poll_rate", 1, NULL, OPT_SET_POLL_INTERVAL },
|
||||
{ "get_poll_rate", 0, NULL, OPT_GET_POLL_INTERVAL },
|
||||
{ "n64_mempak_write", 1, NULL, OPT_N64_MEMPAK_WRITE },
|
||||
{ "si_8bit_scan", 0, NULL, OPT_SI8BIT_SCAN },
|
||||
{ "si_16bit_scan", 0, NULL, OPT_SI16BIT_SCAN },
|
||||
{ "gc_to_n64_info", 0, NULL, OPT_GC_TO_N64_INFO },
|
||||
{ "gc_to_n64_echotest", 0, NULL, OPT_GC_TO_N64_TEST },
|
||||
{ "gc_to_n64_update", 1, NULL, OPT_GC_TO_N64_UPDATE },
|
||||
{ "gc_to_n64_dump", 0, NULL, OPT_GC_TO_N64_DUMP },
|
||||
{ "gc_to_n64_enter_bootloader", 0, NULL, OPT_GC_TO_N64_ENTER_BOOTLOADER },
|
||||
{ "gc_to_n64_boot_application", 0, NULL, OPT_GC_TO_N64_BOOT_APPLICATION },
|
||||
{ "gc_to_n64_read_mapping", 1, NULL, OPT_GC_TO_N64_READ_MAPPING },
|
||||
{ "gc_to_n64_load_mapping", 1, NULL, OPT_GC_TO_N64_LOAD_MAPPING },
|
||||
{ "gc_to_n64_store_current_mapping", 1, NULL, OPT_GC_TO_N64_STORE_CURRENT_MAPPING },
|
||||
{ "nonstop", 0, NULL, OPT_NONSTOP },
|
||||
{ "get_version", 0, NULL, OPT_GET_VERSION },
|
||||
{ "get_signature", 0, NULL, OPT_GET_SIGNATURE },
|
||||
{ "get_controller_type", 0, NULL, OPT_GET_CTLTYPE },
|
||||
{ },
|
||||
};
|
||||
|
||||
static int mempak_progress_cb(int addr, void *ctx)
|
||||
{
|
||||
printf("\r%s 0x%04x / 0x%04x ", (char*)ctx, addr, MEMPAK_MEM_SIZE); fflush(stdout);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int listDevices(void)
|
||||
{
|
||||
int n_found = 0;
|
||||
struct gcn64_list_ctx *listctx;
|
||||
struct gcn64_info inf;
|
||||
|
||||
listctx = gcn64_allocListCtx();
|
||||
if (!listctx) {
|
||||
fprintf(stderr, "List context could not be allocated\n");
|
||||
return -1;
|
||||
}
|
||||
while (gcn64_listDevices(&inf, listctx))
|
||||
{
|
||||
n_found++;
|
||||
printf("Found device '%ls', serial '%ls'\n", inf.str_prodname, inf.str_serial);
|
||||
}
|
||||
gcn64_freeListCtx(listctx);
|
||||
printf("%d device(s) found\n", n_found);
|
||||
|
||||
return n_found;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
gcn64_hdl_t hdl;
|
||||
struct gcn64_list_ctx *listctx;
|
||||
int opt, retval = 0;
|
||||
struct gcn64_info inf;
|
||||
struct gcn64_info *selected_device = NULL;
|
||||
int verbose = 0, use_first = 0, serial_specified = 0;
|
||||
int nonstop = 0;
|
||||
int cmd_list = 0;
|
||||
#define TARGET_SERIAL_CHARS 128
|
||||
wchar_t target_serial[TARGET_SERIAL_CHARS];
|
||||
const char *short_optstr = "hls:vfo:";
|
||||
const char *outfile = NULL;
|
||||
const char *infile = NULL;
|
||||
int gc2n64_channel = 0;
|
||||
|
||||
while((opt = getopt_long(argc, argv, short_optstr, longopts, NULL)) != -1) {
|
||||
switch(opt)
|
||||
{
|
||||
case 's':
|
||||
{
|
||||
mbstate_t ps;
|
||||
memset(&ps, 0, sizeof(ps));
|
||||
if (mbsrtowcs(target_serial, (const char **)&optarg, TARGET_SERIAL_CHARS, &ps) < 1) {
|
||||
fprintf(stderr, "Invalid serial number specified\n");
|
||||
return -1;
|
||||
}
|
||||
serial_specified = 1;
|
||||
}
|
||||
break;
|
||||
case 'f':
|
||||
use_first = 1;
|
||||
break;
|
||||
case 'v':
|
||||
verbose = 1;
|
||||
break;
|
||||
case 'h':
|
||||
printUsage();
|
||||
return 0;
|
||||
case 'l':
|
||||
cmd_list = 1;
|
||||
break;
|
||||
case 'o':
|
||||
outfile = optarg;
|
||||
printf("Output file: %s\n", outfile);
|
||||
break;
|
||||
case 'i':
|
||||
infile = optarg;
|
||||
printf("Input file: %s\n", infile);
|
||||
break;
|
||||
case OPT_NONSTOP:
|
||||
nonstop = 1;
|
||||
break;
|
||||
case '?':
|
||||
fprintf(stderr, "Unrecognized argument. Try -h\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
gcn64_init(verbose);
|
||||
|
||||
if (cmd_list) {
|
||||
printf("Simply listing the devices...\n");
|
||||
return listDevices();
|
||||
}
|
||||
|
||||
if (!serial_specified && !use_first) {
|
||||
fprintf(stderr, "A serial number or -f must be used. Try -h for more information.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
listctx = gcn64_allocListCtx();
|
||||
while ((selected_device = gcn64_listDevices(&inf, listctx)))
|
||||
{
|
||||
if (serial_specified) {
|
||||
if (0 == wcscmp(inf.str_serial, target_serial)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// use_first == 1
|
||||
printf("Will use device '%ls' serial '%ls'\n", inf.str_prodname, inf.str_serial);
|
||||
break;
|
||||
}
|
||||
}
|
||||
gcn64_freeListCtx(listctx);
|
||||
|
||||
if (!selected_device) {
|
||||
if (serial_specified) {
|
||||
fprintf(stderr, "Device not found\n");
|
||||
} else {
|
||||
fprintf(stderr, "No device found\n");
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
hdl = gcn64_openDevice(selected_device);
|
||||
if (!hdl) {
|
||||
printf("Error opening device. (Do you have permissions?)\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
optind = 1;
|
||||
while((opt = getopt_long(argc, argv, short_optstr, longopts, NULL)) != -1)
|
||||
{
|
||||
unsigned char cmd[64] = { };
|
||||
int n;
|
||||
int cmdlen = 0;
|
||||
int do_exchange = 0;
|
||||
|
||||
switch (opt)
|
||||
{
|
||||
case OPT_SET_POLL_INTERVAL:
|
||||
cmd[0] = atoi(optarg);
|
||||
printf("Setting poll interval to %d ms\n", cmd[0]);
|
||||
gcn64lib_setConfig(hdl, CFG_PARAM_POLL_INTERVAL0, cmd, 1);
|
||||
break;
|
||||
|
||||
case OPT_GET_POLL_INTERVAL:
|
||||
n = gcn64lib_getConfig(hdl, CFG_PARAM_POLL_INTERVAL0, cmd, sizeof(cmd));
|
||||
if (n == 1) {
|
||||
printf("Poll interval: %d ms\n", cmd[0]);
|
||||
}
|
||||
break;
|
||||
|
||||
case OPT_SET_SERIAL:
|
||||
printf("Setting serial...");
|
||||
if (strlen(optarg) != 6) {
|
||||
fprintf(stderr, "Serial number must be 6 characters\n");
|
||||
return -1;
|
||||
}
|
||||
gcn64lib_setConfig(hdl, CFG_PARAM_SERIAL, (void*)optarg, 6);
|
||||
break;
|
||||
|
||||
case OPT_GET_SERIAL:
|
||||
n = gcn64lib_getConfig(hdl, CFG_PARAM_SERIAL, cmd, sizeof(cmd));
|
||||
if (n==6) {
|
||||
cmd[6] = 0;
|
||||
printf("Serial: %s\n", cmd);
|
||||
}
|
||||
break;
|
||||
|
||||
case OPT_BOOTLOADER:
|
||||
printf("Sending 'jump to bootloader' command...");
|
||||
gcn64lib_bootloader(hdl);
|
||||
break;
|
||||
|
||||
case OPT_SUSPEND_POLLING:
|
||||
gcn64lib_suspendPolling(hdl, 1);
|
||||
break;
|
||||
|
||||
case OPT_RESUME_POLLING:
|
||||
gcn64lib_suspendPolling(hdl, 0);
|
||||
break;
|
||||
|
||||
case OPT_N64_GETSTATUS:
|
||||
cmd[0] = N64_GET_STATUS;
|
||||
n = gcn64lib_rawSiCommand(hdl, 0, cmd, 1, cmd, sizeof(cmd));
|
||||
if (n >= 0) {
|
||||
printf("N64 Get status[%d]: ", n);
|
||||
printHexBuf(cmd, n);
|
||||
}
|
||||
break;
|
||||
|
||||
case OPT_GC_GETSTATUS_RUMBLE:
|
||||
case OPT_GC_GETSTATUS:
|
||||
cmd[0] = GC_GETSTATUS1;
|
||||
cmd[1] = GC_GETSTATUS2;
|
||||
cmd[2] = GC_GETSTATUS3(opt == OPT_GC_GETSTATUS_RUMBLE);
|
||||
n = gcn64lib_rawSiCommand(hdl, 0, cmd, 3, cmd, sizeof(cmd));
|
||||
if (n >= 0) {
|
||||
printf("GC Get status[%d]: ", n);
|
||||
printHexBuf(cmd, n);
|
||||
}
|
||||
break;
|
||||
|
||||
case OPT_N64_GETCAPS:
|
||||
cmd[0] = N64_GET_CAPABILITIES;
|
||||
//cmd[0] = 0xff;
|
||||
n = gcn64lib_rawSiCommand(hdl, 0, cmd, 1, cmd, sizeof(cmd));
|
||||
if (n >= 0) {
|
||||
printf("N64 Get caps[%d]: ", n);
|
||||
printHexBuf(cmd, n);
|
||||
}
|
||||
break;
|
||||
|
||||
case OPT_N64_MEMPAK_DUMP:
|
||||
{
|
||||
mempak_structure_t *pak;
|
||||
int res;
|
||||
|
||||
printf("Reading mempak...\n");
|
||||
res = gcn64lib_mempak_download(hdl, 0, &pak, mempak_progress_cb, "Reading address");
|
||||
printf("\n");
|
||||
switch (res)
|
||||
{
|
||||
case 0:
|
||||
if (outfile) {
|
||||
int file_format;
|
||||
|
||||
file_format = mempak_getFilenameFormat(outfile);
|
||||
if (file_format == MPK_FORMAT_INVALID) {
|
||||
fprintf(stderr, "Unknown file format (neither .MPK nor .N64). Not saving.\n");
|
||||
} else {
|
||||
if (0 == mempak_saveToFile(pak, outfile, file_format)) {
|
||||
printf("Wrote file '%s' in %s format\n", outfile, mempak_format2string(file_format));
|
||||
} else {
|
||||
fprintf(stderr, "error writing file\n");
|
||||
}
|
||||
}
|
||||
} else { // No outfile
|
||||
mempak_hexdump(pak);
|
||||
}
|
||||
mempak_free(pak);
|
||||
break;
|
||||
case -1:
|
||||
fprintf(stderr, "No mempak detected\n");
|
||||
break;
|
||||
case -2:
|
||||
fprintf(stderr, "I/O error reading pak\n");
|
||||
break;
|
||||
default:
|
||||
case -3:
|
||||
fprintf(stderr, "Error\n");
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
|
||||
case OPT_N64_MEMPAK_WRITE:
|
||||
{
|
||||
mempak_structure_t *pak;
|
||||
int res;
|
||||
printf("Input file: %s\n", optarg);
|
||||
|
||||
pak = mempak_loadFromFile(optarg);
|
||||
if (!pak) {
|
||||
fprintf(stderr, "Failed to load mempak\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("Writing to mempak...\n");
|
||||
res = gcn64lib_mempak_upload(hdl, 0, pak, mempak_progress_cb, "Writing address");
|
||||
printf("\n");
|
||||
if (res) {
|
||||
switch(res)
|
||||
{
|
||||
case -1:
|
||||
fprintf(stderr, "Error: No mempak detected.\n");
|
||||
break;
|
||||
case -2:
|
||||
fprintf(stderr, "I/O error writing to pak.\n");
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "Error uploading mempak\n");
|
||||
}
|
||||
} else {
|
||||
printf("Mempak uploaded\n");
|
||||
}
|
||||
mempak_free(pak);
|
||||
}
|
||||
break;
|
||||
|
||||
case OPT_SI8BIT_SCAN:
|
||||
gcn64lib_8bit_scan(hdl, 0, 255);
|
||||
break;
|
||||
|
||||
case OPT_SI16BIT_SCAN:
|
||||
gcn64lib_16bit_scan(hdl, 0, 0xffff);
|
||||
break;
|
||||
|
||||
case OPT_GC_TO_N64_INFO:
|
||||
{
|
||||
struct gc2n64_adapter_info inf;
|
||||
|
||||
gc2n64_adapter_getInfo(hdl, gc2n64_channel, &inf);
|
||||
gc2n64_adapter_printInfo(&inf);
|
||||
}
|
||||
break;
|
||||
|
||||
case OPT_GC_TO_N64_TEST:
|
||||
{
|
||||
int i=0;
|
||||
|
||||
do {
|
||||
n = gc2n64_adapter_echotest(hdl, gc2n64_channel, 1);
|
||||
if (n != 0) {
|
||||
printf("Test failed\n");
|
||||
return -1;
|
||||
}
|
||||
usleep(1000 * (i));
|
||||
i++;
|
||||
if (i>100)
|
||||
i=0;
|
||||
printf("."); fflush(stdout);
|
||||
} while (nonstop);
|
||||
printf("Test ok\n");
|
||||
}
|
||||
break;
|
||||
|
||||
case OPT_GC_TO_N64_UPDATE:
|
||||
gc2n64_adapter_updateFirmware(hdl, gc2n64_channel, optarg);
|
||||
break;
|
||||
|
||||
case OPT_GC_TO_N64_DUMP:
|
||||
gc2n64_adapter_dumpFlash(hdl, gc2n64_channel);
|
||||
break;
|
||||
|
||||
case OPT_GC_TO_N64_ENTER_BOOTLOADER:
|
||||
gc2n64_adapter_enterBootloader(hdl, gc2n64_channel);
|
||||
gc2n64_adapter_waitForBootloader(hdl, gc2n64_channel, 5);
|
||||
break;
|
||||
|
||||
case OPT_GC_TO_N64_BOOT_APPLICATION:
|
||||
gc2n64_adapter_bootApplication(hdl, gc2n64_channel);
|
||||
break;
|
||||
|
||||
case OPT_GC_TO_N64_READ_MAPPING:
|
||||
{
|
||||
struct gc2n64_adapter_info inf;
|
||||
int map_id;
|
||||
|
||||
map_id = atoi(optarg);
|
||||
if ((map_id <= 0) || (map_id > GC2N64_NUM_MAPPINGS)) {
|
||||
fprintf(stderr, "Invalid mapping id (1 to 4)\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
gc2n64_adapter_getInfo(hdl, gc2n64_channel, &inf);
|
||||
printf("Mapping %d : { ", map_id);
|
||||
gc2n64_adapter_printMapping(&inf.app.mappings[map_id-1]);
|
||||
printf(" }\n");
|
||||
if (outfile) {
|
||||
printf("Writing mapping to file '%s'\n", outfile);
|
||||
gc2n64_adapter_saveMapping(&inf.app.mappings[map_id-1], outfile);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case OPT_GC_TO_N64_LOAD_MAPPING:
|
||||
{
|
||||
struct gc2n64_adapter_mapping *mapping;
|
||||
|
||||
printf("Reading mapping from file '%s'\n", optarg);
|
||||
mapping = gc2n64_adapter_loadMapping(optarg);
|
||||
if (!mapping) {
|
||||
fprintf(stderr, "Failed to load mapping\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("Mapping : { ");
|
||||
gc2n64_adapter_printMapping(mapping);
|
||||
printf(" }\n");
|
||||
|
||||
gc2n64_adapter_setMapping(hdl, gc2n64_channel, mapping);
|
||||
|
||||
free(mapping);
|
||||
}
|
||||
break;
|
||||
|
||||
case OPT_GC_TO_N64_STORE_CURRENT_MAPPING:
|
||||
{
|
||||
int slot;
|
||||
|
||||
slot = atoi(optarg);
|
||||
|
||||
if (slot < 1 || slot > 4) {
|
||||
fprintf(stderr, "Mapping out of range (1-4)\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (0 == gc2n64_adapter_storeCurrentMapping(hdl, gc2n64_channel, slot)) {
|
||||
printf("Stored mapping to slot %d (%s)\n", slot, gc2n64_adapter_getMappingSlotName(slot, 0));
|
||||
} else {
|
||||
printf("Error storing mapping\n");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case OPT_GET_VERSION:
|
||||
{
|
||||
char version[64];
|
||||
|
||||
if (0 == gcn64lib_getVersion(hdl, version, sizeof(version))) {
|
||||
printf("Firmware version: %s\n", version);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case OPT_GET_SIGNATURE:
|
||||
{
|
||||
char sig[64];
|
||||
|
||||
if (0 == gcn64lib_getSignature(hdl, sig, sizeof(sig))) {
|
||||
printf("Signature: %s\n", sig);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case OPT_GET_CTLTYPE:
|
||||
{
|
||||
int type;
|
||||
type = gcn64lib_getControllerType(hdl, 0);
|
||||
printf("Controller type 0x%02x: %s\n", type, gcn64lib_controllerName(type));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (do_exchange) {
|
||||
int i;
|
||||
n = gcn64_exchange(hdl, cmd, cmdlen, cmd, sizeof(cmd));
|
||||
if (n<0)
|
||||
break;
|
||||
|
||||
printf("Result: %d bytes: ", n);
|
||||
for (i=0; i<n; i++) {
|
||||
printf("%02x ", cmd[i]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
gcn64_closeDevice(hdl);
|
||||
gcn64_shutdown();
|
||||
|
||||
return retval;
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
/* gc_n64_usb : Gamecube or N64 controller to USB adapter firmware
|
||||
Copyright (C) 2007-2015 Raphael Assenat <raph@raphnet.net>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <string.h>
|
||||
|
||||
void *memmem(const void *haystack, size_t haystack_len,
|
||||
const void *needle, size_t needle_len)
|
||||
{
|
||||
int i;
|
||||
if (needle_len > haystack_len)
|
||||
return NULL;
|
||||
|
||||
for (i=0; i<haystack_len; i++) {
|
||||
if (!memcmp(haystack +i, needle, needle_len))
|
||||
return haystack + i;
|
||||
}
|
||||
return NULL;
|
||||
}
|
472
tools/mempak.c
472
tools/mempak.c
@ -1,472 +0,0 @@
|
||||
/* gc_n64_usb : Gamecube or N64 controller to USB adapter firmware
|
||||
Copyright (C) 2007-2015 Raphael Assenat <raph@raphnet.net>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#define _GNU_SOURCE // for strcasestr
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <ctype.h>
|
||||
#include <libgen.h>
|
||||
#include "mempak.h"
|
||||
|
||||
#define DEXDRIVE_DATA_OFFSET 0x1040
|
||||
#define DEXDRIVE_COMMENT_OFFSET 0x40
|
||||
|
||||
mempak_structure_t *mempak_new(void)
|
||||
{
|
||||
mempak_structure_t *mpk;
|
||||
|
||||
mpk = calloc(1, sizeof(mempak_structure_t));
|
||||
if (!mpk) {
|
||||
perror("calloc");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
format_mempak(mpk);
|
||||
|
||||
return mpk;
|
||||
}
|
||||
|
||||
static int mempak_findFreeNote(mempak_structure_t *mpk, entry_structure_t *entry_data, int *note_id)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!mpk)
|
||||
return -1;
|
||||
if (!entry_data)
|
||||
return -1;
|
||||
|
||||
for (i=0; i<MEMPAK_NUM_NOTES; i++) {
|
||||
|
||||
if (0 != get_mempak_entry(mpk, i, entry_data)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!entry_data->valid) {
|
||||
if (note_id) {
|
||||
*note_id = i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* \param mpk The memory pack to operate on
|
||||
* \param notefile The filename of the note to load
|
||||
* \param dst_note_id 0-15: (Over)write to specific note, -1: auto (first free)
|
||||
* \param note_id Stores the id of the note that was used
|
||||
* \return -1: Error, -2: Not enough space in mempak
|
||||
*/
|
||||
int mempak_importNote(mempak_structure_t *mpk, const char *notefile, int dst_note_id, int *note_id)
|
||||
{
|
||||
int free_blocks = get_mempak_free_space(mpk);
|
||||
FILE *fptr;
|
||||
unsigned char entry_data[32];
|
||||
unsigned char *data = NULL;
|
||||
entry_structure_t entry;
|
||||
int res;
|
||||
|
||||
if (dst_note_id < -1 || dst_note_id > 15) {
|
||||
fprintf(stderr, "Out of bound dst_note_id\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("Current free blocks: %d\n", free_blocks);
|
||||
|
||||
fptr = fopen(notefile, "rb");
|
||||
if (!fptr) {
|
||||
perror("fopen");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (1 != fread(entry_data, 32, 1, fptr)) {
|
||||
perror("fread");
|
||||
fclose(fptr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* I follow the same convention as bryc's javascript mempak editor[1]
|
||||
* by looking for an inode number of 0xCAFE.
|
||||
*
|
||||
* [1] https://github.com/bryc/mempak
|
||||
*
|
||||
* If there are other note formats to support, this will need updating.
|
||||
*/
|
||||
if ((entry_data[0x06] == 0xCA) &&
|
||||
(entry_data[0x07] == 0xFE))
|
||||
{
|
||||
entry_structure_t oldentry;
|
||||
long filesize;
|
||||
|
||||
// Fixup the inode number (0xCAFE is invalid)
|
||||
entry_data[0x06] = 0x00;
|
||||
entry_data[0x07] = 0x05; // BLOCK_VALID_FIRST;
|
||||
|
||||
if (0 != mempak_parse_entry(entry_data, &entry)) {
|
||||
fprintf(stderr, "Error loading note (invalid)\n");
|
||||
fclose(fptr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
fseek(fptr, 0, SEEK_END);
|
||||
filesize = ftell(fptr);
|
||||
fseek(fptr, 32, SEEK_SET);
|
||||
|
||||
// Remove the note header
|
||||
filesize -= 32;
|
||||
if (filesize % MEMPAK_BLOCK_SIZE) {
|
||||
fprintf(stderr, "Invalid note file size\n");
|
||||
fclose(fptr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
entry.blocks = filesize / MEMPAK_BLOCK_SIZE;
|
||||
|
||||
printf("Note size: %d blocks\n", entry.blocks);
|
||||
printf("Note name: %s\n", entry.name);
|
||||
|
||||
if (entry.blocks > free_blocks) {
|
||||
fprintf(stderr, "Not enough space (note is %d blocks and only %d free blocks in mempak)\n",
|
||||
entry.blocks, free_blocks);
|
||||
fclose(fptr);
|
||||
return -2;
|
||||
}
|
||||
|
||||
data = calloc(1, entry.blocks * MEMPAK_BLOCK_SIZE);
|
||||
if (!data) {
|
||||
perror("calloc");
|
||||
fclose(fptr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (1 != fread(data, entry.blocks * MEMPAK_BLOCK_SIZE, 1, fptr)) {
|
||||
perror("could not load note data");
|
||||
free(data);
|
||||
fclose(fptr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (dst_note_id == -1) { // Auto (first free note)
|
||||
if (0 != mempak_findFreeNote(mpk, &oldentry, note_id)) {
|
||||
fprintf(stderr, "Could not find an empty note\n");
|
||||
free(data);
|
||||
fclose(fptr);
|
||||
return -1;
|
||||
}
|
||||
} else { // Specific note
|
||||
get_mempak_entry(mpk, dst_note_id, &oldentry);
|
||||
if (oldentry.valid) {
|
||||
printf("Overwriting note %d\n", dst_note_id);
|
||||
delete_mempak_entry(mpk, &oldentry);
|
||||
} else {
|
||||
fprintf(stderr, "No note id %d\n", dst_note_id);
|
||||
free(data);
|
||||
fclose(fptr);
|
||||
return -1;
|
||||
}
|
||||
if (note_id)
|
||||
*note_id = dst_note_id;
|
||||
}
|
||||
|
||||
res = write_mempak_entry_data(mpk, &entry, data);
|
||||
if (res != 0) {
|
||||
fprintf(stderr, "Failed to write note (error %d)\n", res);
|
||||
free(data);
|
||||
fclose(fptr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
} else {
|
||||
fprintf(stderr, "Input file does not appear to be in a supported format.\n");
|
||||
}
|
||||
|
||||
if (data)
|
||||
free(data);
|
||||
fclose(fptr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int mempak_exportNote(mempak_structure_t *mpk, int note_id, const char *dst_filename)
|
||||
{
|
||||
FILE *fptr;
|
||||
entry_structure_t note_header;
|
||||
unsigned char databuf[0x10000];
|
||||
|
||||
if (!mpk)
|
||||
return -1;
|
||||
|
||||
if (0 != get_mempak_entry(mpk, note_id, ¬e_header)) {
|
||||
fprintf(stderr, "Error accessing note\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!note_header.valid) {
|
||||
fprintf(stderr, "Invaid note\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (0 != read_mempak_entry_data(mpk, ¬e_header, databuf)) {
|
||||
fprintf(stderr, "Error accessing note data\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
fptr = fopen(dst_filename, "wb");
|
||||
if (!fptr) {
|
||||
perror("fopen");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* For compatibility with bryc's javascript mempak editor[1], I set
|
||||
* the inode number to 0xCAFE.
|
||||
*
|
||||
* [1] https://github.com/bryc/mempak
|
||||
*/
|
||||
note_header.raw_data[0x06] = 0xCA;
|
||||
note_header.raw_data[0x07] = 0xFE;
|
||||
|
||||
fwrite(note_header.raw_data, 32, 1, fptr);
|
||||
fwrite(databuf, MEMPAK_BLOCK_SIZE, note_header.blocks, fptr);
|
||||
fclose(fptr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mempak_saveToFile(mempak_structure_t *mpk, const char *dst_filename, unsigned char format)
|
||||
{
|
||||
FILE *fptr;
|
||||
int i;
|
||||
|
||||
if (!mpk)
|
||||
return -1;
|
||||
|
||||
fptr = fopen(dst_filename, "wb");
|
||||
if (!fptr) {
|
||||
perror("fopen");
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch(format)
|
||||
{
|
||||
default:
|
||||
fclose(fptr);
|
||||
return -1;
|
||||
|
||||
case MPK_FORMAT_MPK:
|
||||
fwrite(mpk->data, sizeof(mpk->data), 1, fptr);
|
||||
break;
|
||||
|
||||
case MPK_FORMAT_MPK4:
|
||||
fwrite(mpk->data, sizeof(mpk->data), 1, fptr);
|
||||
fwrite(mpk->data, sizeof(mpk->data), 1, fptr);
|
||||
fwrite(mpk->data, sizeof(mpk->data), 1, fptr);
|
||||
fwrite(mpk->data, sizeof(mpk->data), 1, fptr);
|
||||
break;
|
||||
|
||||
case MPK_FORMAT_N64:
|
||||
// Note: This should work well for files that will
|
||||
// be imported by non-official software which typically
|
||||
// only look for the 123-456-STD header and then
|
||||
// seek to the data.
|
||||
//
|
||||
// Real .N64 files contain more info other info. Often
|
||||
// 0x12: 01 00 00 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 00
|
||||
// ....
|
||||
// 0x3F: 00
|
||||
//
|
||||
// Then at 0x40, there are 0x1000 bytes. I think there are 256
|
||||
// bytes available for each of block. See comments in
|
||||
// mempak_loadFromFile for more info.
|
||||
fprintf(fptr, "123-456-STD");
|
||||
|
||||
fseek(fptr, DEXDRIVE_COMMENT_OFFSET, SEEK_SET);
|
||||
for (i=0; i<MEMPAK_NUM_NOTES; i++) {
|
||||
unsigned char tmp = 0;
|
||||
fwrite(mpk->note_comments[i], 255, 1, fptr);
|
||||
// I'm not sure about the exact convention of the
|
||||
// original format. Is is that comments are zero-terminated,
|
||||
// but if the length is 256 then non-terminated (implcit termination?)
|
||||
//
|
||||
// Just to make sure nothing crashes by loading a file generated
|
||||
// by this tool, I make sure there is always a zero.
|
||||
fwrite(&tmp, 1, 1, fptr);
|
||||
}
|
||||
|
||||
|
||||
fseek(fptr, DEXDRIVE_DATA_OFFSET, SEEK_SET);
|
||||
fwrite(mpk->data, sizeof(mpk->data), 1, fptr);
|
||||
break;
|
||||
}
|
||||
|
||||
fclose(fptr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
mempak_structure_t *mempak_loadFromFile(const char *filename)
|
||||
{
|
||||
FILE *fptr;
|
||||
long file_size;
|
||||
int i;
|
||||
int num_images = -1;
|
||||
long offset = 0;
|
||||
mempak_structure_t *mpk;
|
||||
|
||||
mpk = calloc(1, sizeof(mempak_structure_t));
|
||||
if (!mpk) {
|
||||
perror("calloc");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fptr = fopen(filename, "rb");
|
||||
if (!fptr) {
|
||||
perror("fopen");
|
||||
free(mpk);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fseek(fptr, 0, SEEK_END);
|
||||
file_size = ftell(fptr);
|
||||
fseek(fptr, 0, SEEK_SET);
|
||||
|
||||
printf("File size: %ld bytes\n", file_size);
|
||||
|
||||
/* Raw binary images. Those can contain more than one card's data. For
|
||||
* instance, Mupen64 seems to contain four saves. (I suppose each 32kB block is
|
||||
* for the virtual mempak of one controller) */
|
||||
for (i=1; i<=4; i++) {
|
||||
if (file_size == 0x8000*i) {
|
||||
num_images = i;
|
||||
printf("MPK file Contains %d image(s)\n", num_images);
|
||||
if (file_size == 0x8000) {
|
||||
mpk->file_format = MPK_FORMAT_MPK;
|
||||
} else {
|
||||
mpk->file_format = MPK_FORMAT_MPK4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (num_images < 0) {
|
||||
char header[11];
|
||||
char *magic = "123-456-STD";
|
||||
/* If the size is not a fixed multiple, it could be a .N64 file */
|
||||
fread(header, 11, 1, fptr);
|
||||
if (0 == memcmp(header, magic, sizeof(header))) {
|
||||
printf(".N64 file detected\n");
|
||||
|
||||
/* At 0x40 there are often comments in .N64 files.
|
||||
* The actual memory card data starts at 0x1040.
|
||||
* This means there are exactly 0x1000 bytes for
|
||||
* one large comment, or, since 0x1000 / 256 = 16,
|
||||
* more likely one comment per note? That's what
|
||||
* I'm assuming here. */
|
||||
fseek(fptr, DEXDRIVE_COMMENT_OFFSET, SEEK_SET);
|
||||
#if MAX_NOTE_COMMENT_SIZE != 257
|
||||
#error
|
||||
#endif
|
||||
for (i=0; i<16; i++) {
|
||||
fread(mpk->note_comments[i], 256, 1, fptr);
|
||||
/* The comments appear to be zero terminated, but I don't
|
||||
* know if the original tool allowed entering a maximum
|
||||
* of 256 or 255 bytes. So to be safe, I use buffers of
|
||||
* 257 bytes */
|
||||
mpk->note_comments[i][256] = 0;
|
||||
}
|
||||
|
||||
offset = DEXDRIVE_DATA_OFFSET;
|
||||
mpk->file_format = MPK_FORMAT_N64;
|
||||
}
|
||||
}
|
||||
|
||||
fseek(fptr, offset, SEEK_SET);
|
||||
fread(mpk->data, sizeof(mpk->data), 1, fptr);
|
||||
fclose(fptr);
|
||||
|
||||
return mpk;
|
||||
}
|
||||
|
||||
void mempak_free(mempak_structure_t *mpk)
|
||||
{
|
||||
if (mpk)
|
||||
free(mpk);
|
||||
}
|
||||
|
||||
const char *mempak_format2string(int fmt)
|
||||
{
|
||||
switch(fmt)
|
||||
{
|
||||
case MPK_FORMAT_MPK: return "MPK";
|
||||
case MPK_FORMAT_MPK4: return "MPK4";
|
||||
case MPK_FORMAT_N64: return "N64";
|
||||
case MPK_FORMAT_INVALID: return "Invalid";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
int mempak_string2format(const char *str)
|
||||
{
|
||||
if (0 == strcasecmp(str, "mpk"))
|
||||
return MPK_FORMAT_MPK;
|
||||
|
||||
if (0 == strcasecmp(str, "mpk4"))
|
||||
return MPK_FORMAT_MPK4;
|
||||
|
||||
if (0 == strcasecmp(str, "n64"))
|
||||
return MPK_FORMAT_N64;
|
||||
|
||||
return MPK_FORMAT_INVALID;
|
||||
}
|
||||
|
||||
int mempak_getFilenameFormat(const char *filename)
|
||||
{
|
||||
char *s;
|
||||
|
||||
if ((s = strcasestr(filename, ".N64"))) {
|
||||
if (s[4] == 0)
|
||||
return MPK_FORMAT_N64;
|
||||
}
|
||||
if ((s = strcasestr(filename, ".MPK"))) {
|
||||
if (s[4] == 0)
|
||||
return MPK_FORMAT_MPK4;
|
||||
}
|
||||
|
||||
return MPK_FORMAT_INVALID;
|
||||
}
|
||||
|
||||
int mempak_hexdump(mempak_structure_t *pak)
|
||||
{
|
||||
int i,j;
|
||||
|
||||
for (i=0; i<MEMPAK_MEM_SIZE; i+=0x20) {
|
||||
printf("%04x: ", i);
|
||||
for (j=0; j<0x20; j++) {
|
||||
printf("%02x ", pak->data[i+j]);
|
||||
}
|
||||
printf(" ");
|
||||
|
||||
for (j=0; j<0x20; j++) {
|
||||
printf("%c", isprint(pak->data[i+j]) ? pak->data[i+j] : '.' );
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1,38 +0,0 @@
|
||||
#ifndef _mempak_h__
|
||||
#define _mempak_h__
|
||||
|
||||
#define MEMPAK_MEM_SIZE 0x8000
|
||||
#define MEMPAK_NUM_NOTES 16
|
||||
|
||||
#define MAX_NOTE_COMMENT_SIZE 257 // including 0 termination
|
||||
|
||||
#define MPK_FORMAT_INVALID 0
|
||||
#define MPK_FORMAT_MPK 1
|
||||
#define MPK_FORMAT_MPK4 2 // MPK + 3 times 32kB padding
|
||||
#define MPK_FORMAT_N64 3
|
||||
|
||||
typedef struct mempak_structure
|
||||
{
|
||||
unsigned char data[MEMPAK_MEM_SIZE];
|
||||
unsigned char file_format;
|
||||
|
||||
char note_comments[MEMPAK_NUM_NOTES][MAX_NOTE_COMMENT_SIZE];
|
||||
} mempak_structure_t;
|
||||
|
||||
mempak_structure_t *mempak_new(void);
|
||||
mempak_structure_t *mempak_loadFromFile(const char *filename);
|
||||
|
||||
int mempak_saveToFile(mempak_structure_t *mpk, const char *dst_filename, unsigned char format);
|
||||
int mempak_exportNote(mempak_structure_t *mpk, int note_id, const char *dst_filename);
|
||||
int mempak_importNote(mempak_structure_t *mpk, const char *notefile, int dst_note_id, int *note_id);
|
||||
void mempak_free(mempak_structure_t *mpk);
|
||||
|
||||
int mempak_getFilenameFormat(const char *filename);
|
||||
int mempak_string2format(const char *str);
|
||||
const char *mempak_format2string(int fmt);
|
||||
int mempak_hexdump(mempak_structure_t *pak);
|
||||
|
||||
#include "mempak_fs.h"
|
||||
|
||||
#endif // _mempak_h__
|
||||
|
@ -1,100 +0,0 @@
|
||||
/* gc_n64_usb : Gamecube or N64 controller to USB adapter firmware
|
||||
Copyright (C) 2007-2015 Raphael Assenat <raph@raphnet.net>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <getopt.h>
|
||||
#include "mempak.h"
|
||||
|
||||
#define DEFAULT_FORMAT_STR "n64"
|
||||
|
||||
static void print_usage(void)
|
||||
{
|
||||
printf("Usage: ./mempak_convert in_file out_file <options>\n");
|
||||
printf("\n");
|
||||
printf("Options:\n");
|
||||
printf(" -h Display help\n");
|
||||
printf(" -f format Write file in specified format (default: %s)\n", DEFAULT_FORMAT_STR);
|
||||
printf("\n");
|
||||
printf("Formats:\n");
|
||||
printf(" mpk Standard 32kB .mpk file format\n");
|
||||
printf(" mpk4 128kB .mpk file (4 copies or the 32kB block)\n");
|
||||
printf(" n64 .N64 file format\n");
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
mempak_structure_t *mpk;
|
||||
const char *infile;
|
||||
const char *outfile;
|
||||
unsigned char type;
|
||||
struct option long_options[] = {
|
||||
{ "format", required_argument, 0, 'f' },
|
||||
{ "help", no_argument, 0, 'h' },
|
||||
{ }, // terminator
|
||||
};
|
||||
const char *format = DEFAULT_FORMAT_STR;
|
||||
|
||||
if (argc < 2) {
|
||||
print_usage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
while(1) {
|
||||
int c;
|
||||
|
||||
c = getopt_long(argc, argv, "f:h", long_options, NULL);
|
||||
if (c==-1)
|
||||
break;
|
||||
|
||||
switch(c)
|
||||
{
|
||||
case 'h':
|
||||
print_usage();
|
||||
return 0;
|
||||
|
||||
case 'f':
|
||||
format = optarg;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
fprintf(stderr, "Unknown argument. Try -h\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
type = mempak_string2format(format);
|
||||
if (type == MPK_FORMAT_INVALID) {
|
||||
fprintf(stderr, "Unknown format specified\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
infile = argv[optind];
|
||||
outfile = argv[optind+1];
|
||||
|
||||
mpk = mempak_loadFromFile(infile);
|
||||
if (!mpk) {
|
||||
fprintf(stderr, "Could not load mempak file '%s'\n", infile);
|
||||
return 1;
|
||||
}
|
||||
printf("Loaded file '%s' (%s format)\n", infile, mempak_format2string(mpk->file_format));
|
||||
|
||||
mempak_saveToFile(mpk, outfile, type);
|
||||
mempak_free(mpk);
|
||||
|
||||
printf("Wrote file '%s' in %s format\n", outfile, mempak_format2string(type));
|
||||
|
||||
return 0;
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
/* gc_n64_usb : Gamecube or N64 controller to USB adapter firmware
|
||||
Copyright (C) 2007-2015 Raphael Assenat <raph@raphnet.net>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "mempak.h"
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *infile;
|
||||
const char *outfile;
|
||||
int note_id;
|
||||
mempak_structure_t *mpk;
|
||||
|
||||
if (argc < 4) {
|
||||
printf("Usage: ./mempak_extract_note in_file note_id out_file\n");
|
||||
printf("\n");
|
||||
printf("Where:\n");
|
||||
printf(" in_file The input file (full mempak image .mpk/.n64)\n");
|
||||
printf(" note_id The id of the note (as shown by mempak_ls)\n");
|
||||
printf(" out_file The output filename (eg: abc.note)\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
infile = argv[1];
|
||||
note_id = atoi(argv[2]);
|
||||
outfile = argv[3];
|
||||
|
||||
mpk = mempak_loadFromFile(infile);
|
||||
if (!mpk) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (mempak_exportNote(mpk, note_id, outfile)) {
|
||||
fprintf(stderr, "could not export note\n");
|
||||
}
|
||||
|
||||
printf("Exported note %d to file '%s'\n", note_id, outfile);
|
||||
|
||||
mempak_free(mpk);
|
||||
|
||||
return 0;
|
||||
}
|
@ -1,93 +0,0 @@
|
||||
/* gc_n64_usb : Gamecube or N64 controller to USB adapter firmware
|
||||
Copyright (C) 2007-2015 Raphael Assenat <raph@raphnet.net>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <getopt.h>
|
||||
#include "mempak.h"
|
||||
|
||||
#define DEFAULT_FORMAT_STR "n64"
|
||||
|
||||
static void print_usage(void)
|
||||
{
|
||||
printf("Usage: ./mempak_format file <options>\n");
|
||||
printf("\n");
|
||||
printf("Options:\n");
|
||||
printf(" -h Display help\n");
|
||||
printf(" -f format Write file in specified format (default: %s)\n", DEFAULT_FORMAT_STR);
|
||||
printf("\n");
|
||||
printf("Formats:\n");
|
||||
printf(" mpk Standard 32kB .mpk file format\n");
|
||||
printf(" mpk4 128kB .mpk file (4 copies or the 32kB block)\n");
|
||||
printf(" n64 .N64 file format\n");
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
mempak_structure_t *mpk;
|
||||
const char *outfile;
|
||||
unsigned char type;
|
||||
struct option long_options[] = {
|
||||
{ "format", required_argument, 0, 'f' },
|
||||
{ "help", no_argument, 0, 'h' },
|
||||
{ }, // terminator
|
||||
};
|
||||
const char *format = DEFAULT_FORMAT_STR;
|
||||
|
||||
if (argc < 2) {
|
||||
print_usage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
while(1) {
|
||||
int c;
|
||||
|
||||
c = getopt_long(argc, argv, "f:h", long_options, NULL);
|
||||
if (c==-1)
|
||||
break;
|
||||
|
||||
switch(c)
|
||||
{
|
||||
case 'h':
|
||||
print_usage();
|
||||
return 0;
|
||||
case 'f':
|
||||
format = optarg;
|
||||
break;
|
||||
case '?':
|
||||
fprintf(stderr, "Unknown argument. Try -h\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
type = mempak_string2format(format);
|
||||
if (type == MPK_FORMAT_INVALID) {
|
||||
fprintf(stderr, "Unknown format specified\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
outfile = argv[optind];
|
||||
mpk = mempak_new();
|
||||
if (!mpk) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
mempak_saveToFile(mpk, outfile, type);
|
||||
mempak_free(mpk);
|
||||
|
||||
printf("Wrote empty (formatted) memory card file '%s' in '%s' format\n", outfile, mempak_format2string(type));
|
||||
|
||||
return 0;
|
||||
}
|
1255
tools/mempak_fs.c
1255
tools/mempak_fs.c
File diff suppressed because it is too large
Load Diff
@ -1,82 +0,0 @@
|
||||
/**
|
||||
* @file mempak.h
|
||||
* @brief Mempak Filesystem Routines
|
||||
* @ingroup mempak
|
||||
*/
|
||||
#ifndef __LIBDRAGON_MEMPAK_H
|
||||
#define __LIBDRAGON_MEMPAK_H
|
||||
|
||||
#include "mempak.h"
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @addtogroup mempak
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** @brief Size in bytes of a Mempak block */
|
||||
#define MEMPAK_BLOCK_SIZE 256
|
||||
|
||||
/**
|
||||
* @brief Structure representing a save entry in a mempak
|
||||
*/
|
||||
typedef struct entry_structure
|
||||
{
|
||||
/** @brief Vendor ID */
|
||||
uint32_t vendor;
|
||||
/** @brief Game ID */
|
||||
uint16_t game_id;
|
||||
/** @brief Inode pointer */
|
||||
uint16_t inode;
|
||||
/** @brief Intended region */
|
||||
uint8_t region;
|
||||
/** @brief Number of blocks used by this entry.
|
||||
* @see MEMPAK_BLOCK_SIZE */
|
||||
uint8_t blocks;
|
||||
/** @brief Validity of this entry. */
|
||||
uint8_t valid;
|
||||
/** @brief ID of this entry */
|
||||
uint8_t entry_id;
|
||||
/**
|
||||
* @brief Name of this entry
|
||||
*
|
||||
* The complete list of valid ASCII characters in a note name is:
|
||||
*
|
||||
* <pre>
|
||||
* ABCDEFGHIJKLMNOPQRSTUVWXYZ!"#`*+,-./:=?\@
|
||||
* </pre>
|
||||
*
|
||||
* The space character is also allowed. Any other character will be
|
||||
* converted to a space before writing to the mempak.
|
||||
*
|
||||
* @see #__n64_to_ascii and #__ascii_to_n64
|
||||
*/
|
||||
char name[19];
|
||||
|
||||
/** @brief A copy of the raw note data (from the note table). */
|
||||
unsigned char raw_data[32];
|
||||
} entry_structure_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
int read_mempak_sector( mempak_structure_t *pak, int sector, uint8_t *sector_data );
|
||||
int write_mempak_sector( mempak_structure_t *pak, int sector, uint8_t *sector_data );
|
||||
int validate_mempak( mempak_structure_t *pak );
|
||||
int get_mempak_free_space( mempak_structure_t *pak );
|
||||
int get_mempak_entry( mempak_structure_t *pak, int entry, entry_structure_t *entry_data );
|
||||
int format_mempak( mempak_structure_t *pak );
|
||||
int read_mempak_entry_data( mempak_structure_t *pak, entry_structure_t *entry, uint8_t *data );
|
||||
int write_mempak_entry_data( mempak_structure_t *pak, entry_structure_t *entry, uint8_t *data );
|
||||
int delete_mempak_entry( mempak_structure_t *pak, entry_structure_t *entry );
|
||||
|
||||
int mempak_parse_entry( const uint8_t *tnote, entry_structure_t *note );
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/** @} */ /* mempak */
|
||||
|
||||
#endif
|
@ -1,322 +0,0 @@
|
||||
/* gc_n64_usb : Gamecube or N64 controller to USB adapter firmware
|
||||
Copyright (C) 2007-2015 Raphael Assenat <raph@raphnet.net>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include "gcn64lib.h"
|
||||
#include "gcn64.h"
|
||||
#include "mempak.h"
|
||||
#include "mempak_gcn64usb.h"
|
||||
#include "hexdump.h"
|
||||
#include "../gcn64_protocol.h"
|
||||
#include "../requests.h"
|
||||
|
||||
/* __calc_address_crc is from libdragon which is public domain. */
|
||||
|
||||
/**
|
||||
* @brief Calculate the 5 bit CRC on a mempak address
|
||||
*
|
||||
* This function, given an address intended for a mempak read or write, will
|
||||
* calculate the CRC on the address, returning the corrected address | CRC.
|
||||
*
|
||||
* @param[in] address
|
||||
* The mempak address to calculate CRC over
|
||||
*
|
||||
* @return The mempak address | CRC
|
||||
*/
|
||||
static uint16_t __calc_address_crc( uint16_t address )
|
||||
{
|
||||
/* CRC table */
|
||||
uint16_t xor_table[16] = { 0x0, 0x0, 0x0, 0x0, 0x0, 0x15, 0x1F, 0x0B, 0x16, 0x19, 0x07, 0x0E, 0x1C, 0x0D, 0x1A, 0x01 };
|
||||
uint16_t crc = 0;
|
||||
int i;
|
||||
|
||||
/* Make sure we have a valid address */
|
||||
address &= ~0x1F;
|
||||
|
||||
/* Go through each bit in the address, and if set, xor the right value into the output */
|
||||
for( i = 15; i >= 5; i-- )
|
||||
{
|
||||
/* Is this bit set? */
|
||||
if( ((address >> i) & 0x1) )
|
||||
{
|
||||
crc ^= xor_table[i];
|
||||
}
|
||||
}
|
||||
|
||||
/* Just in case */
|
||||
crc &= 0x1F;
|
||||
|
||||
/* Create a new address with the CRC appended */
|
||||
return address | crc;
|
||||
}
|
||||
|
||||
/* __calc_data_crc is from libdragon which is public domain. */
|
||||
|
||||
/**
|
||||
* @brief Calculate the 8 bit CRC over a 32-byte block of data
|
||||
*
|
||||
* This function calculates the 8 bit CRC appropriate for checking a 32-byte
|
||||
* block of data intended for or retrieved from a mempak.
|
||||
*
|
||||
* @param[in] data
|
||||
* Pointer to 32 bytes of data to run the CRC over
|
||||
*
|
||||
* @return The calculated 8 bit CRC over the data
|
||||
*/
|
||||
static uint8_t __calc_data_crc( uint8_t *data )
|
||||
{
|
||||
uint8_t ret = 0;
|
||||
int i,j;
|
||||
|
||||
for( i = 0; i <= 32; i++ )
|
||||
{
|
||||
for( j = 7; j >= 0; j-- )
|
||||
{
|
||||
int tmp = 0;
|
||||
|
||||
if( ret & 0x80 )
|
||||
{
|
||||
tmp = 0x85;
|
||||
}
|
||||
|
||||
ret <<= 1;
|
||||
|
||||
if( i < 32 )
|
||||
{
|
||||
if( data[i] & (0x01 << j) )
|
||||
{
|
||||
ret |= 0x1;
|
||||
}
|
||||
}
|
||||
|
||||
ret ^= tmp;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int gcn64lib_mempak_readBlock(gcn64_hdl_t hdl, unsigned short addr, unsigned char dst[32])
|
||||
{
|
||||
unsigned char cmd[64];
|
||||
//int cmdlen;
|
||||
int n;
|
||||
uint16_t addr_crc;
|
||||
unsigned char crc;
|
||||
|
||||
addr_crc = __calc_address_crc(addr);
|
||||
|
||||
cmd[0] = N64_EXPANSION_READ;
|
||||
cmd[1] = addr_crc>>8; // Address high byte
|
||||
cmd[2] = addr_crc&0xff; // Address low byte
|
||||
|
||||
n = gcn64lib_rawSiCommand(hdl, 0, cmd, 3, cmd, sizeof(cmd));
|
||||
if (n != 33) {
|
||||
printf("Hey! %d\n", n);
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(dst, cmd, 0x20);
|
||||
|
||||
crc = __calc_data_crc(dst);
|
||||
if (crc != cmd[32]) {
|
||||
fprintf(stderr, "Bad CRC reading address 0x%04x\n", addr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0x20;
|
||||
}
|
||||
|
||||
int gcn64lib_mempak_detect(gcn64_hdl_t hdl)
|
||||
{
|
||||
unsigned char buf[40];
|
||||
int res;
|
||||
unsigned short addr = __calc_address_crc(0x8000);
|
||||
int first_read, second_read;
|
||||
|
||||
buf[0] = N64_GET_CAPABILITIES;
|
||||
res = gcn64lib_rawSiCommand(hdl, 0, buf, 1, buf, sizeof(buf));
|
||||
if (res < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (res != 3) {
|
||||
return -1;
|
||||
}
|
||||
if (!(buf[2] & 0x01)) {
|
||||
printf("No accessory connected\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* first write 32 0xFEs */
|
||||
memset(buf, 0xfe, 32);
|
||||
res = gcn64lib_n64_expansionWrite(hdl, addr, buf, 32);
|
||||
if (res < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (res != 0xE1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Read back (normally zeros) */
|
||||
res = gcn64lib_n64_expansionRead(hdl, addr, buf, sizeof(buf));
|
||||
if (res < 0) {
|
||||
return -1;
|
||||
}
|
||||
first_read = buf[0];
|
||||
|
||||
/* Now write 32 0x80s */
|
||||
memset(buf, 0x80, 32);
|
||||
res = gcn64lib_n64_expansionWrite(hdl, addr, buf, 32);
|
||||
if (res < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Normally, read back (0x00 on memory card, 0x80 on rumble pak)
|
||||
*
|
||||
* But this simple detection method (from libdragon) does not detect one of my
|
||||
* memory cards (it looks like a rumble pack).
|
||||
*
|
||||
* But I found out that the values that are read back are just always equal to while
|
||||
* for other hardware, reading after writing the 0xfe values always seem to return 0x00.
|
||||
*/
|
||||
res = gcn64lib_n64_expansionRead(hdl, addr, buf, sizeof(buf));
|
||||
if (res < 0) {
|
||||
printf("failed to detect mempak: %d\n", res);
|
||||
return -1;
|
||||
}
|
||||
second_read = buf[0];
|
||||
|
||||
|
||||
// Values seen here are
|
||||
//
|
||||
// - Official Nintendo rumble pack: 0x00
|
||||
// - Yobo rumble pack: 0x00
|
||||
// - Yobo mempak: 0x00
|
||||
// - Unknown "super memory card 1000": 0xFE
|
||||
//
|
||||
if (first_read == 0xfe) {
|
||||
printf("super memory card 1000 probably detected\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Values seen here are
|
||||
//
|
||||
// - Official Nintendo rumble pack: 0x80
|
||||
// - Yobo rumble pack: 0x80
|
||||
// - Yobo mempak: 0x00
|
||||
// - Unknown "super memory card 1000": 0x80 (but this card is catched above)
|
||||
if (second_read == 0x80) {
|
||||
return -1; // rumble
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int gcn64lib_mempak_writeBlock(gcn64_hdl_t hdl, unsigned short addr, unsigned char data[32])
|
||||
{
|
||||
return gcn64lib_n64_expansionWrite(hdl, __calc_address_crc(addr), data, 32);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Read a physical mempak
|
||||
* \param hdl The Adapter handler
|
||||
* \param channel The adapter channel (for multi-port adapters)
|
||||
* \param pak Pointer to mempak_structure pointer to store the new mempak
|
||||
* \param progressCb Callback to notify read progress (called after each block). The callback can return non-zero to abort.
|
||||
* \return 0: Success, -1: No mempak, -2: IO/error, -3: Other errors, -4: Aborted
|
||||
*/
|
||||
int gcn64lib_mempak_download(gcn64_hdl_t hdl, int channel, mempak_structure_t **mempak, int (*progressCb)(int cur_addr, void *ctx), void *ctx)
|
||||
{
|
||||
mempak_structure_t *pak;
|
||||
unsigned short addr;
|
||||
|
||||
if (!mempak) {
|
||||
return -3;
|
||||
}
|
||||
|
||||
if (gcn64lib_mempak_detect(hdl)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
pak = calloc(1, sizeof(mempak_structure_t));
|
||||
if (!pak) {
|
||||
return -3;
|
||||
}
|
||||
pak->file_format = MPK_FORMAT_MPK;
|
||||
|
||||
for (addr = 0x0000; addr < MEMPAK_MEM_SIZE; addr+= 0x20)
|
||||
{
|
||||
if (gcn64lib_mempak_readBlock(hdl, addr, &pak->data[addr]) != 0x20) {
|
||||
fprintf(stderr, "Error: Short read\n");
|
||||
free(pak);
|
||||
return -2;
|
||||
}
|
||||
if (progressCb) {
|
||||
if (progressCb(addr, ctx)) {
|
||||
return -4;
|
||||
}
|
||||
}
|
||||
}
|
||||
*mempak = pak;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gcn64lib_mempak_upload(gcn64_hdl_t hdl, int channel, mempak_structure_t *pak, int (*progressCb)(int cur_addr, void *ctx), void *ctx)
|
||||
{
|
||||
unsigned short addr;
|
||||
unsigned char readback[0x20];
|
||||
int res;
|
||||
|
||||
if (!pak) {
|
||||
return -3;
|
||||
}
|
||||
if (gcn64lib_mempak_detect(hdl)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (addr = 0x0000; addr < MEMPAK_MEM_SIZE; addr+= 0x20)
|
||||
{
|
||||
res = gcn64lib_mempak_writeBlock(hdl, addr, &pak->data[addr]);
|
||||
if (res < 0) {
|
||||
fprintf(stderr, "Write error\n");
|
||||
return -2;
|
||||
}
|
||||
|
||||
if (0x20 != gcn64lib_mempak_readBlock(hdl, addr, readback)) {
|
||||
// TODO : Why not retry?
|
||||
fprintf(stderr, "readback failed\n");
|
||||
return -2;
|
||||
}
|
||||
|
||||
if (memcmp(readback, &pak->data[addr], 0x20)) {
|
||||
fprintf(stderr, "Readback compare failed\n");
|
||||
return -2;
|
||||
}
|
||||
|
||||
if (progressCb) {
|
||||
if (progressCb(addr, ctx)) {
|
||||
return -4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1,13 +0,0 @@
|
||||
#ifndef _mempak_gcn64usb_h__
|
||||
#define _mempak_gcn64usb_h__
|
||||
|
||||
#include "mempak.h"
|
||||
|
||||
int gcn64lib_mempak_detect(gcn64_hdl_t hdl);
|
||||
int gcn64lib_mempak_readBlock(gcn64_hdl_t hdl, unsigned short addr, unsigned char dst[32]);
|
||||
int gcn64lib_mempak_writeBlock(gcn64_hdl_t hdl, unsigned short addr, unsigned char data[32]);
|
||||
|
||||
int gcn64lib_mempak_download(gcn64_hdl_t hdl, int channel, mempak_structure_t **mempak, int (*progressCb)(int cur_addr, void *ctx), void *ctx);
|
||||
int gcn64lib_mempak_upload(gcn64_hdl_t hdl, int channel, mempak_structure_t *pak, int (*progressCb)(int cur_addr, void *ctx), void *ctx);
|
||||
|
||||
#endif // _mempak_gcn64usb_h__
|
@ -1,117 +0,0 @@
|
||||
/* gc_n64_usb : Gamecube or N64 controller to USB adapter firmware
|
||||
Copyright (C) 2007-2015 Raphael Assenat <raph@raphnet.net>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <getopt.h>
|
||||
#include "mempak.h"
|
||||
|
||||
static void print_usage(void)
|
||||
{
|
||||
printf("Usage: ./mempak_insert_note pakfile notefile\n");
|
||||
printf("\n");
|
||||
printf("Options:\n");
|
||||
printf(" -h, --help Display help\n");
|
||||
printf(" -c, --comment Comment Note comment (only works for .N64 files)\n");
|
||||
printf(" -d, --dst id Overwrite a specific note slot (0-15)\n");
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *pakfile;
|
||||
const char *notefile;
|
||||
mempak_structure_t *mpk;
|
||||
struct option long_options[] = {
|
||||
{ "help", no_argument, 0, 'h' },
|
||||
{ "comment", required_argument, 0, 'c' },
|
||||
{ "dst", required_argument, 0, 'd' },
|
||||
{ }, // terminator
|
||||
};
|
||||
const char *comment = NULL;
|
||||
int used_note_id = -1;
|
||||
int dst_id = -1;
|
||||
|
||||
if (argc < 3) {
|
||||
print_usage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
while(1) {
|
||||
int c;
|
||||
|
||||
c = getopt_long(argc, argv, "f:hc:d:", long_options, NULL);
|
||||
if (c==-1)
|
||||
break;
|
||||
|
||||
switch(c)
|
||||
{
|
||||
case 'h':
|
||||
print_usage();
|
||||
return 0;
|
||||
case 'c':
|
||||
comment = optarg;
|
||||
if (strlen(optarg) > (MAX_NOTE_COMMENT_SIZE-2)) {
|
||||
fprintf(stderr, "Comment too long (%d characters max.)\n", (MAX_NOTE_COMMENT_SIZE-2));
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
case 'd':
|
||||
dst_id = atoi(optarg);
|
||||
break;
|
||||
case '?':
|
||||
fprintf(stderr, "Unknown argument. Try -h\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pakfile = argv[optind];
|
||||
notefile = argv[optind+1];
|
||||
|
||||
mpk = mempak_loadFromFile(pakfile);
|
||||
if (!mpk) {
|
||||
return 1;
|
||||
}
|
||||
printf("Loaded pakfile in %s format.\n", mempak_format2string(mpk->file_format));
|
||||
|
||||
if (mempak_importNote(mpk, notefile, dst_id, &used_note_id)) {
|
||||
fprintf(stderr, "could not export note\n");
|
||||
mempak_free(mpk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
printf("Note imported and written to slot %d\n", used_note_id);
|
||||
|
||||
if (comment) {
|
||||
if (mpk->file_format != MPK_FORMAT_N64) {
|
||||
printf("Warning: Ignoring comment since it cannot be stored in %s file format. Use the N64 format instead.\n", mempak_format2string(mpk->file_format));
|
||||
} else {
|
||||
strncpy(mpk->note_comments[used_note_id], comment, MAX_NOTE_COMMENT_SIZE);
|
||||
mpk->note_comments[used_note_id][256] = 0;
|
||||
mpk->note_comments[used_note_id][255] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (0 != mempak_saveToFile(mpk, pakfile, mpk->file_format)) {
|
||||
fprintf(stderr, "could not write to memory pak file\n");
|
||||
}
|
||||
|
||||
mempak_free(mpk);
|
||||
|
||||
return 0;
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
/* gc_n64_usb : Gamecube or N64 controller to USB adapter firmware
|
||||
Copyright (C) 2007-2015 Raphael Assenat <raph@raphnet.net>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "mempak.h"
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *infile;
|
||||
mempak_structure_t *mpk;
|
||||
int note;
|
||||
int res;
|
||||
|
||||
if (argc < 2) {
|
||||
printf("Usage: ./mempak_ls file\n");
|
||||
printf("\n");
|
||||
printf("Raw files (.MPK, .BIN) and Dexdrive (.N64) formats accepted.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
infile = argv[1];
|
||||
mpk = mempak_loadFromFile(infile);
|
||||
if (!mpk) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Mempak image loaded. Image type %d (%s)\n", mpk->file_format, mempak_format2string(mpk->file_format));
|
||||
|
||||
if (0 != validate_mempak(mpk)) {
|
||||
printf("Mempak invalid (not formatted or corrupted)\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
printf("Mempak content is valid\n");
|
||||
printf("Block usage: %d / %d\n", 123-get_mempak_free_space(mpk), 123);
|
||||
|
||||
for (note = 0; note<MEMPAK_NUM_NOTES; note++) {
|
||||
entry_structure_t note_data;
|
||||
|
||||
printf("Note %d: ", note);
|
||||
res = get_mempak_entry(mpk, note, ¬e_data);
|
||||
if (res) {
|
||||
printf("Error!\n");
|
||||
} else {
|
||||
if (note_data.valid) {
|
||||
printf("%s (%d blocks) ", note_data.name, note_data.blocks);
|
||||
// printf("%08x ", note_data.vendor);
|
||||
// printf("%04x ", note_data.game_id);
|
||||
// printf("%02x ", note_data.region);
|
||||
if (strlen(mpk->note_comments[note]) > 0) {
|
||||
printf("{ %s }", mpk->note_comments[note]);
|
||||
}
|
||||
printf("\n");
|
||||
} else {
|
||||
printf("Free\n");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
mempak_free(mpk);
|
||||
return 0;
|
||||
}
|
@ -1,106 +0,0 @@
|
||||
/* gc_n64_usb : Gamecube or N64 controller to USB adapter firmware
|
||||
Copyright (C) 2007-2015 Raphael Assenat <raph@raphnet.net>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <getopt.h>
|
||||
#include "mempak.h"
|
||||
|
||||
static void print_usage(void)
|
||||
{
|
||||
printf("Usage: ./mempak_rm pakfile note_id\n");
|
||||
printf("\n");
|
||||
printf("Options:\n");
|
||||
printf(" -h, --help Display help\n");
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *pakfile;
|
||||
mempak_structure_t *mpk;
|
||||
struct option long_options[] = {
|
||||
{ "help", no_argument, 0, 'h' },
|
||||
{ }, // terminator
|
||||
};
|
||||
int noteid;
|
||||
entry_structure_t entry;
|
||||
|
||||
if (argc < 3) {
|
||||
print_usage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
while(1) {
|
||||
int c;
|
||||
|
||||
c = getopt_long(argc, argv, "h", long_options, NULL);
|
||||
if (c==-1)
|
||||
break;
|
||||
|
||||
switch(c)
|
||||
{
|
||||
case 'h':
|
||||
print_usage();
|
||||
return 0;
|
||||
case '?':
|
||||
fprintf(stderr, "Unknown argument. Try -h\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
pakfile = argv[optind];
|
||||
noteid = atoi(argv[optind+1]);
|
||||
|
||||
if (noteid < 0 || noteid > 15) {
|
||||
fprintf(stderr, "Note id out of range (0-15)\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
mpk = mempak_loadFromFile(pakfile);
|
||||
if (!mpk) {
|
||||
return -1;
|
||||
}
|
||||
printf("Loaded pakfile in %s format.\n", mempak_format2string(mpk->file_format));
|
||||
|
||||
if (0 != get_mempak_entry(mpk, noteid, &entry)) {
|
||||
fprintf(stderr, "Could not get note id %d\n", noteid);
|
||||
mempak_free(mpk);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!entry.valid) {
|
||||
fprintf(stderr, "Note %d is already free\n", noteid);
|
||||
mempak_free(mpk);
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("Deleting note %d (%d blocks)\n", noteid, entry.blocks);
|
||||
|
||||
if (0 != delete_mempak_entry(mpk, &entry)) {
|
||||
fprintf(stderr, "Error deleting entry\n");
|
||||
mempak_free(mpk);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (0 != mempak_saveToFile(mpk, pakfile, mpk->file_format)) {
|
||||
fprintf(stderr, "could not write to memory pak file\n");
|
||||
}
|
||||
|
||||
mempak_free(mpk);
|
||||
|
||||
return 0;
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 14 KiB |
@ -1,22 +0,0 @@
|
||||
/* gc_n64_usb : Gamecube or N64 controller to USB adapter firmware
|
||||
Copyright (C) 2007-2015 Raphael Assenat <raph@raphnet.net>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <windows.h>
|
||||
|
||||
void sleep(int s)
|
||||
{
|
||||
Sleep(s*1000);
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
#ifndef _sleep_h__
|
||||
#define _sleep_h__
|
||||
|
||||
void sleep(int s);
|
||||
|
||||
#endif // _sleep_h__
|
@ -1,49 +0,0 @@
|
||||
/* gc_n64_usb : Gamecube or N64 controller to USB adapter firmware
|
||||
Copyright (C) 2007-2015 Raphael Assenat <raph@raphnet.net>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <string.h>
|
||||
|
||||
#ifdef TEST_IMPLEMENTATION
|
||||
#include <stdio.h>
|
||||
|
||||
#define strcasestr my_strcasestr
|
||||
|
||||
char *strcasestr(const char *haystack, const char *needle);
|
||||
int main(void)
|
||||
{
|
||||
const char *a = "sdfsdj kdkf23 34fjsdf lsdf ";
|
||||
|
||||
if (my_strcasestr(a, "11")) {
|
||||
printf("Test 1 failed\n");
|
||||
}
|
||||
if (!my_strcasestr(a, "23")) {
|
||||
printf("Test 2 failed\n");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
char *strcasestr(const char *haystack, const char *needle)
|
||||
{
|
||||
while (*haystack) {
|
||||
if (0==strncasecmp(haystack, needle, strlen(needle))) {
|
||||
return (char*)haystack;
|
||||
}
|
||||
haystack++;
|
||||
}
|
||||
return NULL;
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
#ifndef _strcasestr_h__
|
||||
#define _strcasestr_h_-
|
||||
|
||||
char *strcasestr(const char *haystack, const char *needle);
|
||||
|
||||
#endif // _strcasestr_h__
|
@ -1,6 +0,0 @@
|
||||
#ifndef _version_h__
|
||||
#define _version_h__
|
||||
|
||||
#define VERSION_STR "1.1.1"
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user