Skip to content

Commit

Permalink
snes.c: detect SNES ROM type
Browse files Browse the repository at this point in the history
Need a small core change, too
  • Loading branch information
gyurco committed Sep 21, 2024
1 parent 5541297 commit 2102ac4
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 3 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ TODAY = `date +"%m/%d/%y"`

PRJ = firmware
SRC = hw/AT91SAM/Cstartup_SAM7.c hw/AT91SAM/hardware.c hw/AT91SAM/spi.c hw/AT91SAM/mmc.c hw/AT91SAM/at91sam_usb.c hw/AT91SAM/usbdev.c
SRC += fdd.c firmware.c fpga.c hdd.c main.c menu.c menu-minimig.c menu-8bit.c osd.c state.c syscalls.c user_io.c settings.c data_io.c boot.c idxfile.c config.c tos.c ikbd.c xmodem.c ini_parser.c cue_parser.c mist_cfg.c archie.c pcecd.c neocd.c arc_file.c font.c utils.c
SRC += fdd.c firmware.c fpga.c hdd.c main.c menu.c menu-minimig.c menu-8bit.c osd.c state.c syscalls.c user_io.c settings.c data_io.c boot.c idxfile.c config.c tos.c ikbd.c xmodem.c ini_parser.c cue_parser.c mist_cfg.c archie.c pcecd.c neocd.c snes.c arc_file.c font.c utils.c
SRC += usb/usb.c usb/max3421e.c usb/usb-max3421e.c usb/usbdebug.c usb/hub.c usb/hid.c usb/hidparser.c usb/xboxusb.c usb/timer.c usb/asix.c usb/pl2303.c usb/usbrtc.c usb/storage.c usb/joymapping.c usb/joystick.c
SRC += fat_compat.c
SRC += FatFs/diskio.c FatFs/ff.c FatFs/ffunicode.c
Expand Down
2 changes: 1 addition & 1 deletion Makefile.SAMV71
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ TODAY = `date +"%m/%d/%y"`
PRJ = firmware
SRC = hw/ATSAMV71/cstartup.c hw/ATSAMV71/hardware.c hw/ATSAMV71/spi.c hw/ATSAMV71/qspi.c hw/ATSAMV71/mmc.c hw/ATSAMV71/usbdev.c hw/ATSAMV71/eth.c hw/ATSAMV71/irq/nvic.c
SRC += hw/ATSAMV71/network/intmath.c hw/ATSAMV71/network/gmac.c hw/ATSAMV71/network/gmacd.c hw/ATSAMV71/network/phy.c hw/ATSAMV71/network/ethd.c
SRC += fdd.c firmware.c fpga.c hdd.c main.c menu.c menu-minimig.c menu-8bit.c osd.c state.c syscalls.c user_io.c settings.c data_io.c boot.c idxfile.c config.c tos.c ikbd.c xmodem.c ini_parser.c cue_parser.c mist_cfg.c archie.c pcecd.c neocd.c psx.c arc_file.c font.c utils.c
SRC += fdd.c firmware.c fpga.c hdd.c main.c menu.c menu-minimig.c menu-8bit.c osd.c state.c syscalls.c user_io.c settings.c data_io.c boot.c idxfile.c config.c tos.c ikbd.c xmodem.c ini_parser.c cue_parser.c mist_cfg.c archie.c pcecd.c neocd.c psx.c snes.c arc_file.c font.c utils.c
SRC += it6613/HDMI_TX.c it6613/it6613_drv.c it6613/it6613_sys.c it6613/EDID.c it6613/hdmitx_mist.c
SRC += usb/usbdebug.c usb/hub.c usb/xboxusb.c usb/hid.c usb/hidparser.c usb/timer.c usb/asix.c usb/pl2303.c usb/usbrtc.c usb/joymapping.c usb/joystick.c usb/storage.c
SRC += usb/usb.c usb/max3421e.c usb/usb-max3421e.c
Expand Down
7 changes: 7 additions & 0 deletions debug.h
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,13 @@
#define psx_debugf(...)
#endif

#if 0
// SNES debug output
#define snes_debugf(a, ...) iprintf("\033[1;34mSNES : " a "\033[0m\n",## __VA_ARGS__)
#else
#define snes_debugf(...)
#endif

#if 1
// HDMI debug output
#define hdmi_debugf(a, ...) iprintf("\033[1;34mHDMI : " a "\033[0m",## __VA_ARGS__)
Expand Down
9 changes: 8 additions & 1 deletion menu-8bit.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "data_io.h"
#include "fat_compat.h"
#include "cue_parser.h"
#include "snes.h"

extern char s[FF_LFN_BUF + 1];

Expand All @@ -39,6 +40,7 @@ extern char fs_pFileExt[13];
/////// 8-bit menu ///////
//////////////////////////
static unsigned char selected_drive_slot;
static unsigned char isSnesROM;


static void substrcpy(char *d, char *s, char idx) {
Expand Down Expand Up @@ -116,9 +118,11 @@ static unsigned long long getStatusMask(char *opt) {

static char RomFileSelected(uint8_t idx, const char *SelectedName) {
FIL file;
char snes_romtype;
// this assumes that further file entries only exist if the first one also exists
if (f_open(&file, SelectedName, FA_READ) == FR_OK) {
data_io_file_tx(&file, user_io_ext_idx(SelectedName, fs_pFileExt)<<6 | selected_drive_slot, GetExtension(SelectedName));
if (isSnesROM) snes_romtype = snes_getromtype(&file);
data_io_file_tx(&file, (isSnesROM ? snes_romtype : user_io_ext_idx(SelectedName, fs_pFileExt))<<6 | selected_drive_slot, GetExtension(SelectedName));
f_close(&file);
}
// close menu afterwards
Expand Down Expand Up @@ -192,6 +196,7 @@ static char GetMenuItem_8bit(uint8_t idx, char action, menu_item_t *item) {
strncpy(ext, p, 13);
while(strlen(ext) < 3) strcat(ext, " ");
selected_drive_slot = 1;
isSnesROM = 0;
SelectFileNG(ext, SCAN_DIR | SCAN_LFN, RomFileSelected, 1);
} else if (action == MENU_ACT_GET) {
//menumask = 1;
Expand Down Expand Up @@ -244,6 +249,8 @@ static char GetMenuItem_8bit(uint8_t idx, char action, menu_item_t *item) {
iscue = 1;
}
if (p[1]>='0' && p[1]<='9') selected_drive_slot = p[1]-'0';
isSnesROM = 0;
if (p[1] && p[1] != ',' && p[2] && p[2] != ',' && !strncmp(&p[2], "SNES", 4)) isSnesROM = 1; // F1SNES
substrcpy(ext, p, 1);
while(strlen(ext) < 3) strcat(ext, " ");
SelectFileNG(ext, SCAN_DIR | SCAN_LFN, (p[0] == 'F')?RomFileSelected:iscue?CueFileSelected:ImageFileSelected, 1);
Expand Down
166 changes: 166 additions & 0 deletions snes.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/*
This file is part of MiST-firmware
MiST-firmware 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.
MiST-firmware 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 <stdint.h>
#include <stdio.h>
#include "snes.h"
#include "fat_compat.h"
#include "debug.h"

enum HeaderField {
CartName = 0x00,
Mapper = 0x15,
RomType = 0x16,
RomSize = 0x17,
RamSize = 0x18,
CartRegion = 0x19,
Company = 0x1a,
Version = 0x1b,
Complement = 0x1c, //inverse checksum
Checksum = 0x1e,
ResetVector = 0x3c,
};

// From Main_MiSTer/support/snes/snes.cpp
static uint32_t score_header(FIL *file, uint32_t offset, uint32_t addr)
{
int score = 0;
UINT br;
uint8_t *data = sector_buffer;

snes_debugf("Header address: %08x offset: %d", addr, offset);

if ((f_lseek(file, offset + addr) != FR_OK) ||
(f_tell(file) != (offset + addr)) ||
(f_read(file, data, 64, &br) != FR_OK)) {
return 0;
}

uint16_t resetvector = data[ResetVector] | (data[ResetVector + 1] << 8);
uint16_t checksum = data[Checksum] | (data[Checksum + 1] << 8);
uint16_t complement = data[Complement] | (data[Complement + 1] << 8);

snes_debugf("Reset vector: %04x, checksum: %04x, complement: %04x", resetvector, checksum, complement);

//$00:[0000-7fff] contains uninitialized RAM and MMIO.
//reset vector must point to ROM at $00:[8000-ffff] to be considered valid.
if (resetvector < 0x8000) return 0;

uint8_t resetop = 0;
if (f_lseek(file, ((addr & ~0x7fff) | (resetvector & 0x7fff)) + offset ) != FR_OK) return 0;
if (f_read(file, &resetop, 1, &br) != FR_OK) return 0;
//uint8_t resetop = data[(addr & ~0x7fff) | (resetvector & 0x7fff)]; //first opcode executed upon reset
uint8_t mapper = data[Mapper] & ~0x10; //mask off irrelevent FastROM-capable bit

//some images duplicate the header in multiple locations, and others have completely
//invalid header information that cannot be relied upon.
//below code will analyze the first opcode executed at the specified reset vector to
//determine the probability that this is the correct header.

//most likely opcodes
if (resetop == 0x78 //sei
|| resetop == 0x18 //clc (clc; xce)
|| resetop == 0x38 //sec (sec; xce)
|| resetop == 0x9c //stz $nnnn (stz $4200)
|| resetop == 0x4c //jmp $nnnn
|| resetop == 0x5c //jml $nnnnnn
) score += 8;

//plausible opcodes
if (resetop == 0xc2 //rep #$nn
|| resetop == 0xe2 //sep #$nn
|| resetop == 0xad //lda $nnnn
|| resetop == 0xae //ldx $nnnn
|| resetop == 0xac //ldy $nnnn
|| resetop == 0xaf //lda $nnnnnn
|| resetop == 0xa9 //lda #$nn
|| resetop == 0xa2 //ldx #$nn
|| resetop == 0xa0 //ldy #$nn
|| resetop == 0x20 //jsr $nnnn
|| resetop == 0x22 //jsl $nnnnnn
) score += 4;

//implausible opcodes
if (resetop == 0x40 //rti
|| resetop == 0x60 //rts
|| resetop == 0x6b //rtl
|| resetop == 0xcd //cmp $nnnn
|| resetop == 0xec //cpx $nnnn
|| resetop == 0xcc //cpy $nnnn
) score -= 4;

//least likely opcodes
if (resetop == 0x00 //brk #$nn
|| resetop == 0x02 //cop #$nn
|| resetop == 0xdb //stp
|| resetop == 0x42 //wdm
|| resetop == 0xff //sbc $nnnnnn,x
) score -= 8;

//at times, both the header and reset vector's first opcode will match ...
//fallback and rely on info validity in these cases to determine more likely header.

//a valid checksum is the biggest indicator of a valid header.
if ((checksum + complement) == 0xffff && (checksum != 0) && (complement != 0)) score += 4;


if (addr == 0x007fc0 && mapper == 0x20) score += 2; //0x20 is usually LoROM
if (addr == 0x00ffc0 && mapper == 0x21) score += 2; //0x21 is usually HiROM
if (addr == 0x007fc0 && mapper == 0x22) score += 2; //0x22 is usually SDD1
if (addr == 0x40ffc0 && mapper == 0x25) score += 2; //0x25 is usually ExHiROM

if (data[Company] == 0x33) score += 2; //0x33 indicates extended header
if (data[RomType] < 0x08) score++;
if (data[RomSize] < 0x10) score++;
if (data[RamSize] < 0x08) score++;
if (data[CartRegion] < 14) score++;

snes_debugf("Resetop: %02x mapper: %02x score: %d",
resetop, mapper, score);

if (score < 0) score = 0;
return score;
}

char snes_getromtype(FIL *file)
{
uint32_t score_lo, score_hi, score_ex;
UINT br;
FSIZE_t size = f_size(file);
FSIZE_t offset = size & 0x1ff;

score_lo = score_header(file, offset, (FSIZE_t) 0x007fc0);
score_hi = score_header(file, offset, (FSIZE_t) 0x00ffc0);
score_ex = score_header(file, offset, (FSIZE_t) 0x40ffc0);
f_rewind(file);

if (score_ex) score_ex += 4; //favor ExHiROM on images > 32mbits
if (score_lo >= score_hi && score_lo >= score_ex) {
iprintf("Detected LoROM\n");
return 0; // probably LoROM
}
if (score_hi >= score_ex) {
iprintf("Detected HiROM\n");
return 1; // probably HiROM
}
if (score_ex) {
iprintf("Detected ExHiROM\n");
return 2; // probably ExHiROM
}
iprintf("No clue about ROM type\n");
return 0; // no idea, fall back to LoROM
}
25 changes: 25 additions & 0 deletions snes.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
This file is part of MiST-firmware
MiST-firmware 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.
MiST-firmware 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/>.
*/

#ifndef SNES_H
#define SNES_H

#include "FatFs/ff.h"

char snes_getromtype(FIL *file);

#endif // SNES_H

0 comments on commit 2102ac4

Please sign in to comment.