Files
ac-decomp/src/static/Famicom/famicom.cpp
T

2818 lines
96 KiB
C++

#include "Famicom/famicomPriv.h"
#include "Famicom/famicomInternal.hpp"
#include "libc/string.h"
#include "dolphin/gx.h"
#include "dolphin/os.h"
#include "dolphin/mtx.h"
#include "dolphin/card.h"
#include "_mem.h"
#include "libultra/libultra.h"
#include "m_personal_id.h"
#include "JSystem/JUtility/JUTDbPrint.h"
#include "JSystem/JKernel/JKRFileFinder.h"
#include "JSystem/JKernel/JKRFileLoader.h"
#include "JSystem/JKernel/JKRArchive.h"
#include "JSystem/JKernel/JKRAram.h"
#include "JSystem/ResTIMG.h"
#include "JSystem/J2D/J2DGrafContext.h"
#include "libc64/sprintf.h"
#include "libjsys/jsyswrapper.h"
#include "jsyswrap_cpp.h"
#include "MSL_C/printf.h"
#include "jaudio_NES/emusound.h"
#include "JSystem/JUtility/JUTProcBar.h"
#include "JSystem/JUtility/JUTGamePad.h"
#include "nintendo_hi_0.h"
#include "m_nmibuf.h"
// #include "JSystem/J2D/J2DGrafContext.h" // needed for ~J2DOrthoGraph
/* For some reason, there are a bunch of unused implicit one-byte 4-byte-aligned bss variables */
#ifdef MUST_MATCH
static u8 __unused_implicit[0x40] ATTRIBUTE_ALIGN(32);
#endif
static u8 commentImageBuffer[CARD_COMMENT_SIZE + 0x5800];
u8 save_game_image = false;
FamicomCommon famicomCommon;
u8 famicomCommonSave[0x1980 + sizeof(FamicomSaveDataHeader)];
u8** nesrom_filename_ptrs = nullptr;
static char* nesrom_filename_strbuf = nullptr;
u8 InputValid[4];
u32 InputData[4];
u32 InputButton[4];
u32 InputTrigger[4];
u32 InputRepeat[4];
u32 InputRepeatCount[4];
static u8 filer_mode_enable = FALSE;
static u8 filer_mode = 0;
static u8 filer_demo_mode = FILER_DEMO_MODE_NORMAL;
static int filer_demo_count = 0;
static int nesrom_idx_loaded = 0;
static int nesrom_idx = 0;
static int nesrom_count = 0;
static int rapid_count = 0;
static u8 rapid_mode_a = FALSE;
static u8 rapid_mode_b = FALSE;
static u8 nines_over_double_click = FALSE;
static u8 nines_over_mode = FALSE;
static u8 hispeed_mode = FALSE;
static u8 slow_mode = FALSE;
static u8 slow_mode_sub = FALSE;
static u8 speed_show = FALSE;
static FAMICOM_GETSAVECHAN_PROC famicom_getSaveChanCallback = nullptr;
Famicom_MallocInfo* my_malloc_current = nullptr;
static JKRArchive* famicom_arc = nullptr;
static bool famicom_arc_mounting = FALSE;
static OSThread ksnes_thread;
static s8 errorChan = -1;
/* @unused decode_szs__FPUcPUc - static void decode_szs(u8*, u8*) */
static u8 calcSum(u8* data, size_t len) {
u8 checksum = 0;
size_t i;
for (i = 0; i != len; i++) {
checksum += *data++;
}
return checksum;
}
extern int famicom_getErrorChan() {
return errorChan;
}
static void famicom_save_data_setup(FamicomSaveDataHeader* header, u32 size, u8* identifier) {
if (strncmp((char*)header->name, (char*)identifier, FAMICOM_SAVE_DATA_NAME_LEN) == 0 &&
calcSum((u8*)header, FAMICOM_SAVE_HEADER_SIZE) == 0 &&
header->_08 == 1 &&
header->_09 == 0 &&
header->headerSize == FAMICOM_SAVE_HEADER_SIZE &&
header->size == size) {
// The header ID & checksum are correct.
OSReport("ヘッダID&チェックサムは正しいです\n");
}
else {
// Repairing the header.
OSReport("ヘッダを修復します\n");
header->_08 = 1;
header->_09 = 0;
header->headerSize = FAMICOM_SAVE_HEADER_SIZE;
header->size = size;
memcpy(header->name, identifier, FAMICOM_SAVE_DATA_NAME_LEN);
int checksum = calcSum((u8*)header, FAMICOM_SAVE_HEADER_SIZE);
header->checksum -= checksum;
}
u8* data = ((u8*)header) + header->headerSize;
for (u32 i = 0; i < PLAYER_NUM; i++) {
int versionTag = data[1] >> 4;
int versionNo = data[1] & 0xF;
if (data[0] == 'D' && calcSum(data, size) == 0 &&
versionTag == 3 && versionNo <= 1 && // check version character is either '0' or '1'
data[3] == (u8)i) {
// The personal data checksum %d is correct.
OSReport("個人データチェックサム%dは正しいです\n", i);
}
else {
// Repairing personal data %d.
OSReport("個人データ%dを修復します\n", i);
data[0] = 'D';
data[1] = '1';
data[3] = i;
data[2] -= calcSum(data, size);
}
data += size;
}
}
static void famicom_save_data_init(FamicomSaveDataHeader* header, u32 size, u8* identifier) {
bzero(header, FAMICOM_SAVE_HEADER_SIZE + size * PLAYER_NUM);
header->no_save = TRUE;
famicom_save_data_setup(header, size, identifier);
}
static int famicom_save_data_check(const FamicomSaveDataHeader* header, int ignore_player_no, const u8* identifier) {
if (strncmp((char*)header->name, (char*)identifier, FAMICOM_SAVE_DATA_NAME_LEN) == 0) {
// The header ID is correct.
OSReport("ヘッダIDは正しいです\n");
if (calcSum((u8*)header, FAMICOM_SAVE_HEADER_SIZE) == 0) {
// The header checksum is correct.
OSReport("ヘッダチェックサムは正しいです\n");
if (((header->_08 == 1 && header->_09 == 0) || (header->_08 == 0 && header->_09 == 1)) && header->headerSize == FAMICOM_SAVE_HEADER_SIZE) {
// The version and header size match.
OSReport("バージョンとヘッダサイズが合致しました\n");
int i;
u8* data = ((u8*)header) + header->headerSize;
for (i = 0; i < PLAYER_NUM; i++) {
if (i != ignore_player_no) {
if (data[0] != 'D' || calcSum(data, header->size) != 0) {
goto fail;
}
// The personal ID & data checksum %d is correct.
OSReport("個人ID&データチェックサム%dは正しいです\n", i);
}
data += header->size;
}
return 0;
}
}
}
fail:
// The Famicom common save is invalid.
OSReport("ファミコン共通セーブは不正です\n");
return -1;
}
static void process_filer_mode() {
static const Vec camPt = { 0.0f, 0.0f, 800.0f };
static const Vec at = { 0.0f, 0.0f, -100.0f };
static const Vec up = { 0.0f, 1.0f, 0.0f };
Mtx44 m;
Mtx34 lookat_m;
GXInvalidateTexAll();
GXInvalidateVtxCache();
GXSetClipMode(GX_CLIP_DISABLE);
GXSetPixelFmt(GX_PF_RGB8_Z24, GX_ZC_LINEAR);
GXSetCopyFilter(GX_FALSE, nullptr, GX_FALSE, nullptr);
GXSetZMode(GX_FALSE, GX_ALWAYS, GX_FALSE);
GXSetZTexture(GX_ZT_DISABLE, GX_TF_Z8, 0);
GXSetZCompLoc(GX_FALSE);
GXSetColorUpdate(GX_TRUE);
GXSetTevSwapModeTable(GX_TEV_SWAP0, GX_CH_RED, GX_CH_GREEN, GX_CH_BLUE, GX_CH_ALPHA);
GXSetTevSwapMode(GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP0);
GXSetTevSwapMode(GX_TEVSTAGE1, GX_TEV_SWAP0, GX_TEV_SWAP0);
GXSetTevSwapMode(GX_TEVSTAGE2, GX_TEV_SWAP0, GX_TEV_SWAP0);
GXSetTevSwapMode(GX_TEVSTAGE3, GX_TEV_SWAP0, GX_TEV_SWAP0);
C_MTXOrtho(m, 0.0, -480.0, 0.0, 640.0, 0.0, 2000.0);
GXSetProjection(m, GX_ORTHOGRAPHIC);
C_MTXLookAt(lookat_m, &camPt, &up, &at);
GXSetNumChans(1);
GXSetNumTexGens(0);
GXSetNumTevStages(1);
GXSetNumIndStages(0);
GXSetTevDirect(GX_TEVSTAGE0);
GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0);
GXSetTevOp(GX_TEVSTAGE0, GX_PASSCLR);
GXSetChanCtrl(GX_COLOR0A0, GX_FALSE, GX_SRC_REG, GX_SRC_VTX, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE);
GXSetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0);
GXClearVtxDesc();
GXSetVtxDesc(GX_VA_POS, GX_DIRECT);
GXSetVtxDesc(GX_VA_CLR0, GX_DIRECT);
GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XY, GX_S16, 0);
GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);
GXSetCurrentMtx(GX_PNMTX0);
GXLoadPosMtxImm(lookat_m, GX_PNMTX0);
GXBegin(GX_QUADS, GX_VTXFMT0, 4);
// Vertex 0
GXPosition2s16(0, 0);
GXColor1u32(0x00500000);
// Vertex 1
GXPosition2s16(640, 0);
GXColor1u32(0x00000000);
// Vertex 2
GXPosition2s16(640, -480);
GXColor1u32(0x00005000);
// Vertex 3
GXPosition2s16(0, -480);
GXColor1u32(0x00000000);
int start_idx;
if (nesrom_idx < 10 || nesrom_count < 18) {
start_idx = 0;
}
else if ((nesrom_idx + 10) > nesrom_count) {
start_idx = nesrom_count - 18;
}
else {
start_idx = nesrom_idx - 9;
}
JUTReport(20, 60, 1, "QFC ver.011012 (C)2001 Nintendo");
if (filer_demo_mode != FILER_DEMO_MODE_NORMAL) {
JUTReport(30, 80, 1, "[%d/%d] -auto demo- R: cancel", nesrom_idx + 1, nesrom_count);
}
else {
JUTReport(30, 80, 1, "[%d/%d] R: back B: demo", nesrom_idx + 1, nesrom_count);
}
if (famicomCommon.nesrom_memcard == FALSE) {
for (u32 i = 0; i < 18; i++) {
u32 idx = start_idx + i;
if (idx < nesrom_count) {
JUTReport(30, 100 + i * 20, 1, "%s%s", (nesrom_idx == idx && (filer_demo_mode == FILER_DEMO_MODE_NORMAL || (filer_demo_count & 0x20) == 0)) ? "->" : " ", nesrom_filename_ptrs[idx]);
}
}
}
if (famicomCommon.nesrom_memcard == FALSE) {
u32 cur_idx;
if ((InputTrigger[0] & (JUTGamePad::MAINSTICK_LEFT | JUTGamePad::DPAD_LEFT))) {
cur_idx = nesrom_idx;
nesrom_idx--;
do {
nesrom_idx--;
while (nesrom_idx < 0) {
nesrom_idx += nesrom_count;
}
} while (nesrom_filename_ptrs[nesrom_idx][0] != ':' && cur_idx != nesrom_idx);
}
if ((InputTrigger[0] & (JUTGamePad::MAINSTICK_RIGHT | JUTGamePad::DPAD_RIGHT))) {
cur_idx = nesrom_idx;
do {
nesrom_idx++;
if (nesrom_idx >= nesrom_count) {
nesrom_idx = 0;
}
} while (nesrom_filename_ptrs[nesrom_idx][0] != ':' && cur_idx != nesrom_idx);
}
if ((InputRepeat[0] & (JUTGamePad::MAINSTICK_UP | JUTGamePad::DPAD_UP))) {
cur_idx = nesrom_idx;
do {
if (nesrom_idx > 0) {
nesrom_idx--;
}
else {
nesrom_idx = nesrom_count - 1;
}
} while (nesrom_filename_ptrs[nesrom_idx][0] == ':' && cur_idx != nesrom_idx);
}
if ((InputRepeat[0] & (JUTGamePad::MAINSTICK_DOWN | JUTGamePad::DPAD_DOWN)) != 0) {
if (nesrom_idx < nesrom_count - 1) {
nesrom_idx++;
}
else {
nesrom_idx = 0;
}
}
}
if ((InputTrigger[0] & BUTTON_DLEFT)) {
filer_mode = 0;
filer_demo_mode = FILER_DEMO_MODE_AUTO;
filer_demo_count = 0;
}
if ((InputTrigger[0] & BUTTON_START)) {
filer_mode = 0;
filer_demo_mode = FILER_DEMO_MODE_NORMAL;
}
}
extern void famicom_setCallback_getSaveChan(FAMICOM_GETSAVECHAN_PROC proc) {
famicom_getSaveChanCallback = proc;
}
static s32 memcard_mount(int, void*);
static const s32 checkTableNG[] = {
CARD_RESULT_FATAL_ERROR,
CARD_RESULT_IOERROR,
CARD_RESULT_BROKEN,
CARD_RESULT_ENCODING,
CARD_RESULT_WRONGDEVICE
};
static s32 famicom_getSaveChan(int nesrom_memcard, s32* result) {
s32 chan_result[2];
s32 card_err;
int chan;
int mounted = FALSE;
void* workArea = nullptr;
char* file_name;
chan_result[0] = CARD_RESULT_READY;
chan_result[1] = CARD_RESULT_READY;
if (nesrom_memcard == FALSE) {
if (famicom_getSaveChanCallback != NULL) {
chan = famicom_getSaveChanCallback(nullptr, chan_result);
if (chan >= 0) {
errorChan = chan;
}
}
else {
chan = -1;
card_err = CARD_RESULT_FATAL_ERROR;
goto exit;
}
if (chan < 0 && famicomCommon._8C) {
nesrom_memcard = TRUE;
}
}
if (nesrom_memcard != FALSE) {
file_name = (char*)famicomCommon.mura_save_name;
workArea = my_malloc(CARD_WORKAREA_SIZE, 32);
if (workArea == nullptr) {
card_err = CARD_RESULT_FATAL_ERROR;
chan = -1;
goto exit;
}
for (chan = 0; chan < CARD_NUM_CHANS; chan++) {
if (mounted) {
CARDUnmount(chan);
}
s32 mount_res = memcard_mount(chan, workArea);
chan_result[chan] = mount_res;
mounted = mount_res == CARD_RESULT_READY;
if (mounted) {
if (file_name != nullptr) {
CARDFileInfo file_info;
card_err = CARDOpen(chan, file_name, &file_info);
chan_result[chan] = card_err;
if (card_err == CARD_RESULT_READY) {
CARDClose(&file_info);
OSReport("チェックファイル存在\n"); // Check file exists.
errorChan = chan;
goto exit;
}
}
CARDUnmount(chan);
mounted = FALSE;
}
}
chan = -1;
card_err = CARD_RESULT_NOFILE;
}
if (chan < 0) {
for (int i = 0; i < ARRAY_COUNT(checkTableNG); i++) {
if (chan_result[0] == checkTableNG[i]) {
card_err = *(s32*)(chan_result + 0);
errorChan = 0;
goto exit;
}
if (chan_result[1] == checkTableNG[i]) {
card_err = *(s32*)(chan_result + 1);
errorChan = 1;
goto exit;
}
}
if ((chan_result[0] == CARD_RESULT_NOFILE || chan_result[0] == CARD_RESULT_NOCARD || chan_result[0] == CARD_RESULT_READY) &&
(chan_result[1] == CARD_RESULT_NOFILE || chan_result[1] == CARD_RESULT_NOCARD || chan_result[1] == CARD_RESULT_READY)) {
card_err = nesrom_memcard ? CARD_RESULT_NOFILE : CARD_RESULT_NOCARD;
errorChan = CARD_NUM_CHANS;
}
else {
card_err = CARD_RESULT_FATAL_ERROR;
}
}
exit:
if (mounted) {
CARDUnmount(chan);
}
if (workArea != nullptr) {
my_free(workArea);
}
if (result != nullptr) {
*result = card_err;
}
return chan;
}
static s32 memcard_mount(int chan, void* workArea) {
s32 result;
s32 sector_size;
// Checking whether the memory card is inserted in the slot.
OSReport("メモリーカードが、スロットに挿入されているかどうかを調べます。\n");
do {
result = CARDProbeEx(chan, nullptr, &sector_size);
} while (result == CARD_RESULT_BUSY);
if (result == CARD_RESULT_READY) {
if (sector_size != 0x2000) {
// The sector size is not 8KB.
OSReport("セクターサイズが8KBではありません。\n");
result = CARD_RESULT_WRONGDEVICE;
}
else {
// Mounting the memory card.
OSReport("メモリーカードをマウントする\n");
result = CARDMount(chan, workArea, nullptr);
if (result == CARD_RESULT_READY || result == CARD_RESULT_BROKEN || result == CARD_RESULT_ENCODING) {
// The memory card has been mounted.
OSReport("メモリーカードはマウント状態になった\n");
if (result == CARD_RESULT_READY || result == CARD_RESULT_BROKEN) {
if (result == CARD_RESULT_BROKEN) {
// The memory card is damaged.
OSReport("メモリーカードは壊れています\n");
// Attempting to repair the memory card.
OSReport("メモリーカードの修復を試みます\n");
}
result = CARDCheck(chan);
if (result != CARD_RESULT_READY) {
// Failed to repair the memory card.
OSReport("メモリーカードの修復に失敗しました\n");
CARDUnmount(chan);
}
}
else {
// The character encoding of the memory card does not match.
OSReport("メモリーカードの文字コードがあっていません\n");
CARDUnmount(chan);
}
}
}
}
else if (result != CARD_RESULT_NOCARD) {
// The memory card is not inserted.
OSReport("メモリーカードは刺さってない\n");
}
if (result != CARD_RESULT_READY && result != CARD_RESULT_NOCARD && result != CARD_RESULT_FATAL_ERROR) {
errorChan = chan;
}
return result;
}
static s32 memcard_getFreeBlocks(s32 chan, s32* unused_bytes, s32* unused_files) {
s32 result;
int mounted = FALSE;
void* workArea = my_malloc(CARD_WORKAREA_SIZE, 32);
if (workArea == nullptr) {
result = CARD_RESULT_FATAL_ERROR;
}
else {
result = memcard_mount(chan, workArea);
mounted = result == CARD_RESULT_READY;
if (mounted) {
result = CARDFreeBlocks(chan, unused_bytes, unused_files);
}
}
if (result != CARD_RESULT_READY && result != CARD_RESULT_NOCARD && result != CARD_RESULT_FATAL_ERROR) {
errorChan = chan;
}
if (result != CARD_RESULT_READY) {
// Error code = %d
OSReport("エラーコード=%d\n", result);
}
if (mounted) {
CARDUnmount(chan);
}
if (workArea != nullptr) {
my_free(workArea);
}
return result;
}
/* @unused @fabricated - strings necessary for matching */
static s32 memcard_list(int chan) {
OSReport("No. Name Co B FileName (YYYY/MM/DD hh:mm:ss) IconAddr IFmt ISpd Pm Cp StB Lng Com\n");
OSReport("%3d %.4s %.2s %d %-32.32s (%04d/%02d/%02d %02d:%02d:%02d) %03x %04x %04x %02x %2d %3d %3d %04x\n");
}
static size_t getBannerSizeFromFormat(u8 format) {
switch (format) {
case CARD_STAT_BANNER_C8:
return 0xE00;
case CARD_STAT_BANNER_RGB5A3:
return 0x1800;
default:
return 0;
}
}
static size_t getIconSizeFromFormat(u16 format) {
size_t size = 0;
int has_palette = FALSE;
for (int i = 0; i < CARD_ICON_MAX; i++) {
switch ((format >> (i * 2)) & CARD_STAT_ICON_MASK) {
case CARD_STAT_ICON_C8:
{
has_palette = TRUE;
size += 0x400;
break;
}
case CARD_STAT_ICON_RGB5A3:
{
size += 0x800;
break;
}
}
}
if (has_palette) {
size += 0x200;
}
return size;
}
static int SetupResBanner(const ResTIMG*, u8*, size_t, size_t*, u8*);
static int SetupResIcon(const ResTIMG*, u8*, size_t, size_t*, u16*, u16*);
static void SetupExternCommentImage(u8* embedded_save_comment_img, u8* dst, u8* rom_file_comment_img) {
u32 size;
switch (famicomCommon.memcard_game_header.flags0.comment_type) {
case MEMCARD_COMMENT_TYPE_NONE:
break;
case MEMCARD_COMMENT_TYPE_COPY_ROM: {
memcpy(dst, rom_file_comment_img, CARD_COMMENT_SIZE);
u8 temp = dst[CARD_COMMENT_SIZE];
dst[CARD_COMMENT_SIZE] = '\0';
char* image_name_end = strstr((char*)dst + 32, "] ROM ");
dst[CARD_COMMENT_SIZE] = temp;
if (image_name_end != nullptr) {
strncpy(image_name_end, "] SAVE", 6);
dst += CARD_COMMENT_SIZE;
break;
}
// Fallthrough in the case where "] ROM " doesn't exist
}
case MEMCARD_COMMENT_TYPE_DEFAULT: {
strncpy((char*)dst, "Animal Crossing", 32);
strncpy((char*)dst + 32, "NES Cassette Save Data ", 32);
dst += CARD_COMMENT_SIZE;
break;
}
case MEMCARD_COMMENT_TYPE_COPY_EMBEDDED: {
memcpy(dst, embedded_save_comment_img, CARD_COMMENT_SIZE);
dst += CARD_COMMENT_SIZE;
embedded_save_comment_img += CARD_COMMENT_SIZE;
break;
}
}
rom_file_comment_img += CARD_COMMENT_SIZE;
switch (famicomCommon.memcard_game_header.flags0.banner_type) {
case MEMCARD_BANNER_TYPE_NONE:
break;
case MEMCARD_BANNER_TYPE_DEFAULT: {
void* banner = JKRFileLoader::getGlbResource("/FAMICOM/newdisk_system.bti.szs");
ResTIMG* banner_data = (ResTIMG*)banner;
if (banner_data != nullptr) {
u8 banner_fmt;
SetupResBanner(banner_data, dst, 0x1800, &size, &banner_fmt);
JKRFileLoader::removeResource(banner_data, nullptr);
dst += size;
famicomCommon.memcard_game_header.flags1.banner_fmt = banner_fmt;
}
break;
}
case MEMCARD_BANNER_TYPE_COPY_ROM: {
size = getBannerSizeFromFormat(famicomCommon.memcard_game_header.flags1.banner_fmt);
memcpy(dst, rom_file_comment_img, size);
dst += size;
break;
}
case MEMCARD_BANNER_TYPE_COPY_EMBEDDED: {
size = getBannerSizeFromFormat(famicomCommon.memcard_game_header.flags1.banner_fmt);
memcpy(dst, embedded_save_comment_img, size);
dst += size;
embedded_save_comment_img += size;
break;
}
}
size_t banner_size = getBannerSizeFromFormat(famicomCommon.memcard_game_header.flags1.banner_fmt);
rom_file_comment_img += banner_size;
switch (famicomCommon.memcard_game_header.flags0.icon_type) {
case MEMCARD_ICON_TYPE_NONE:
break;
case MEMCARD_ICON_TYPE_DEFAULT: {
void* icon = JKRFileLoader::getGlbResource("/FAMICOM/newdisk.bti.szs");
ResTIMG* icon_data = (ResTIMG*)icon;
if (icon_data != nullptr) {
u16 icon_fmts;
u16 icon_flags;
SetupResIcon(icon_data, dst, 0x800, nullptr, &icon_fmts, &icon_flags);
JKRFileLoader::removeResource(icon_data, nullptr);
famicomCommon.memcard_game_header.icon_format = icon_fmts;
famicomCommon.memcard_game_header.icon_flags = icon_flags;
size = getIconSizeFromFormat(icon_fmts);
}
break;
}
case MEMCARD_ICON_TYPE_COPY_ROM: {
size = getIconSizeFromFormat(famicomCommon.memcard_game_header.icon_format);
memcpy(dst, rom_file_comment_img, size);
break;
}
case MEMCARD_ICON_TYPE_COPY_EMBEDDED: {
size = getIconSizeFromFormat(famicomCommon.memcard_game_header.icon_format);
memcpy(dst, embedded_save_comment_img, size);
break;
}
}
size_t icon_size = getIconSizeFromFormat(famicomCommon.memcard_game_header.icon_format); // unnecessary
}
static void SetupInternalCommentImage(u8* data) {
u8* data_p = data;
// Setup comment
famicomCommon.memcard_game_header.flags0.comment_type = MEMCARD_COMMENT_TYPE_DEFAULT;
strncpy((char*)data, "Animal Crossing", 32);
strncpy((char*)data + 32, "NES Save Data ", 32);
data += CARD_COMMENT_SIZE;
// Setup banner
famicomCommon.memcard_game_header.flags0.banner_type = MEMCARD_BANNER_TYPE_NONE;
famicomCommon.memcard_game_header.flags1.banner_fmt = CARD_STAT_BANNER_NONE;
// Setup icon
void* icon = JKRFileLoader::getGlbResource("/FAMICOM/famikon.bti.szs");
ResTIMG* icon_res = (ResTIMG*)icon;
if (icon_res != nullptr) {
u16 icon_fmts;
u16 icon_flags;
SetupResIcon(icon_res, data, 0x800, nullptr, &icon_fmts, &icon_flags);
JKRFileLoader::removeResource(icon_res, nullptr);
famicomCommon.memcard_game_header.flags0.icon_type = MEMCARD_ICON_TYPE_DEFAULT;
famicomCommon.memcard_game_header.icon_format = icon_fmts;
famicomCommon.memcard_game_header.icon_flags = icon_flags;
data += getIconSizeFromFormat(icon_fmts);
}
else {
famicomCommon.memcard_game_header.flags0.icon_type = MEMCARD_ICON_TYPE_NONE;
famicomCommon.memcard_game_header.icon_format = 0;
famicomCommon.memcard_game_header.icon_flags = 0;
}
famicomCommon.memcard_game_header.comment_img_size = (u32)data - (u32)data_p;
}
static s32 memcard_data_save(
int chan,
const char* file_name,
const char* check_file_name,
const FamicomSaveDataHeader* header,
size_t save_data_size,
const u8* comment_img,
u8 banner_fmt,
u32 icon_addr,
u16 icon_fmts,
u16 icon_flags,
u32 comment_addr,
int do_not_save
) {
char temp_name[33];
s32 result;
size_t save_data_size_block_aligned;
int mounted = FALSE;
int opened = FALSE;
void* workArea;
u8* save_data = nullptr;
void* buf = nullptr;
CARDFileInfo fileInfo;
u8 cardAttr;
size_t save_size = 0;
size_t comment_img_size;
// format temp string using file_name as "file_name~"
sprintf(temp_name, "%-.31s~", file_name);
workArea = my_malloc(CARD_WORKAREA_SIZE, 32);
if (workArea == nullptr) {
result = CARD_RESULT_FATAL_ERROR;
goto exit;
}
save_size = icon_addr != 0xFFFFFFFF ? icon_addr : 0;
save_size += getBannerSizeFromFormat(banner_fmt);
save_size += getIconSizeFromFormat(icon_fmts);
comment_img_size = save_size;
save_size += save_data_size;
save_data_size_block_aligned = ALIGN_NEXT(save_size, 0x2000);
save_data = (u8*)my_malloc(save_data_size_block_aligned, 32);
if (save_data == nullptr) {
result = CARD_RESULT_FATAL_ERROR;
goto exit;
}
bzero(save_data, save_data_size_block_aligned);
memcpy(save_data, comment_img, comment_img_size); // copy memcard file info data
memcpy(save_data + comment_img_size, header, save_data_size); // copy data header & save data
result = memcard_mount(chan, workArea);
mounted = result == CARD_RESULT_READY;
if (mounted) {
if (check_file_name != nullptr) {
result = CARDOpen(chan, check_file_name, &fileInfo);
if (result != CARD_RESULT_READY) {
// The check file does not exist.
OSReport("チェックファイル存在しないよ\n");
goto exit;
}
CARDClose(&fileInfo);
}
buf = my_malloc(save_data_size_block_aligned, 32);
if (buf == nullptr) {
result = CARD_RESULT_FATAL_ERROR;
// Not enough memory!
OSReport("メモリが足りん!\n");
goto exit;
}
// Add the contents of the file (check for need to save).
OSReport("ファイルの内容を取り込む(セーブの必要性チェック)\n");
result = CARDOpen(chan, file_name, &fileInfo);
if (result == CARD_RESULT_READY) {
if (do_not_save) {
result = CARD_RESULT_FATAL_ERROR;
goto exit;
}
result = CARDRead(&fileInfo, buf, save_data_size_block_aligned, 0);
if (result != CARD_RESULT_READY) {
goto exit;
}
bool save_updated = bcmp(save_data, buf, save_data_size_block_aligned) == 0;
if (save_updated) {
// No need to save, so the process has completed successfully.
OSReport("セーブする必要が無いので正常終了\n");
goto exit;
}
// Need to save (update mode).
OSReport("セーブする必要がある(更新モード)\n");
}
else {
if (result != CARD_RESULT_NOFILE) {
goto exit;
}
// Need to save (new creation mode).
OSReport("セーブする必要がある(新規作成モード)\n");
}
result = CARDRename(chan, file_name, temp_name);
if (result == CARD_RESULT_EXIST) {
result = CARDDelete(chan, temp_name); // delete temp file
if (result != CARD_RESULT_READY) {
goto exit; // failed to delete
}
result = CARDRename(chan, file_name, temp_name); // try renaming again
if (result != CARD_RESULT_READY) {
goto exit; // failed to rename after deleting
}
}
else if (result != CARD_RESULT_NOFILE && result != CARD_RESULT_READY) {
goto exit;
}
// Creating file chan=%d [%s] length=%d.
OSReport("ファイル作成 chan=%d [%s] length=%d\n", chan, file_name, save_data_size_block_aligned);
result = CARDCreate(chan, file_name, save_data_size_block_aligned, &fileInfo);
switch (result) {
case CARD_RESULT_READY:
break; // success
case CARD_RESULT_NOENT:
case CARD_RESULT_INSSPACE: {
s32 del_res = CARDDelete(chan, temp_name);
if (del_res == CARD_RESULT_NOFILE) {
goto exit; // temp file didn't exist?
}
// check for other errors
if (del_res != CARD_RESULT_READY) {
result = del_res;
goto exit;
}
result = CARDCreate(chan, file_name, save_data_size_block_aligned, &fileInfo);
if (result != CARD_RESULT_READY) {
goto exit; // still failed to create
}
break;
}
default:
// Failed to create file.
OSReport("ファイル作成失敗\n");
goto exit;
}
CARDGetAttributes(chan, fileInfo.fileNo, &cardAttr);
u8 saved_attr = cardAttr;
if ((famicomCommon.memcard_game_header.flags0.no_copy_flag)) {
saved_attr |= CARD_ATTR_NO_COPY;
}
if ((famicomCommon.memcard_game_header.flags1.no_move_flag)) {
saved_attr |= CARD_ATTR_NO_MOVE;
}
saved_attr |= cardAttr;
if ((saved_attr) != 0) {
CARDSetAttributes(chan, fileInfo.fileNo, saved_attr);
}
// Writing the entire file.
OSReport("ファイル全体の書き込み\n");
result = CARDWrite(&fileInfo, save_data, save_data_size_block_aligned, 0);
if (result == CARD_RESULT_READY) {
// Verifying the entire file.
OSReport("ファイル全体のベリファイ\n");
result = CARDRead(&fileInfo, buf, save_data_size_block_aligned, 0);
if (result == CARD_RESULT_READY) {
if (bcmp(save_data, buf, save_data_size_block_aligned) == 0) {
// Verification successful.
OSReport("ベリファイ成功\n");
}
else {
// Verification failed.
OSReport("ベリファイ失敗\n");
result = CARD_RESULT_IOERROR;
goto exit;
}
// Close.
OSReport("クローズ\n");
result = CARDClose(&fileInfo);
if (result != CARD_RESULT_READY) {
// Failed to close?
OSReport("クローズに失敗って?\n");
goto exit;
}
else {
// Get status.
opened = FALSE;
OSReport("ステータス取得\n");
}
CARDStat status;
result = CARDGetStatus(chan, fileInfo.fileNo, &status);
if (result != CARD_RESULT_READY) {
// Failed to get status.
OSReport("ステータス取得失敗\n");
goto exit;
}
status.bannerFormat = banner_fmt;
status.iconSpeed = icon_flags;
status.iconAddr = icon_addr;
status.iconFormat = icon_fmts;
status.commentAddr = comment_addr;
// Change status.
OSReport("ステータス変更\n");
result = CARDSetStatus(chan, fileInfo.fileNo, &status);
if (result != CARD_RESULT_READY) {
// Failed to change status.
OSReport("ステータス変更失敗\n");
}
else {
result = CARDDelete(chan, temp_name);
if (result == CARD_RESULT_NOFILE) {
result = CARD_RESULT_READY; // successfully deleted temp file
}
}
}
}
}
exit:
if (result != CARD_RESULT_READY && result != CARD_RESULT_NOCARD && result != CARD_RESULT_FATAL_ERROR) {
errorChan = chan;
}
if (result != CARD_RESULT_READY) {
// Error code = %d.
OSReport("エラーコード=%d\n", result);
}
if (buf != nullptr) {
my_free(buf);
}
if (save_data != nullptr) {
my_free(save_data);
}
if (opened) {
CARDClose(&fileInfo);
}
if (mounted) {
CARDUnmount(chan);
}
if (workArea != nullptr) {
my_free(workArea);
}
return result;
}
static s32 memcard_data_load(
int chan,
const char* file_name,
const char* check_file_name,
FamicomSaveDataHeader* save_header,
size_t save_data_size,
const u8* comment_img
) {
char temp_name[33];
s32 result;
int mounted = FALSE;
void* workArea;
u8* save_data = nullptr;
int i;
void* buf = nullptr;
CARDFileInfo fileInfo;
CARDStat status;
// format temp string using file_name as "file_name~"
sprintf(temp_name, "%-.31s~", file_name);
workArea = my_malloc(CARD_WORKAREA_SIZE, 32);
if (workArea == nullptr) {
// Not enough memory!
OSReport("メモリが足りん!!\n");
result = CARD_RESULT_FATAL_ERROR;
goto exit;
}
result = memcard_mount(chan, workArea);
mounted = result == CARD_RESULT_READY;
if (!mounted) {
// Can't mount!
OSReport("マウントできない!\n");
goto exit;
}
if (check_file_name != nullptr) {
result = CARDOpen(chan, check_file_name, &fileInfo);
if (result != CARD_RESULT_READY) {
// The check file does not exist.
OSReport("チェックファイル存在しないよ\n");
goto exit;
}
CARDClose(&fileInfo);
}
for (i = 0; i < 2; i++) {
// Opening file [%s]
OSReport("ファイルのオープン [%s]\n", i == 0 ? file_name : temp_name);
result = CARDOpen(chan, i == 0 ? file_name : temp_name, &fileInfo);
if (result != CARD_RESULT_READY) {
// Can't open the file.
OSReport("ファイルがオープンできない\n");
continue;
}
if (buf != nullptr) {
my_free(buf);
buf = nullptr;
}
result = CARDGetStatus(chan, fileInfo.fileNo, &status);
if (status.offsetData == 0) {
// File has no icon
OSReport("アイコンのないファイル\n");
if (status.commentAddr == 0xFFFFFFFF) {
// File has no comment
OSReport("コメントもないファイル\n");
// The file is currently being saved
OSReport("どう考えてもセーブ中のファイル\n");
if (CARDDelete(chan, file_name) == CARD_RESULT_READY) {
// Erased the file that was being saved.
OSReport("セーブ中のファイルを消しました\n");
}
continue;
}
}
size_t len = status.length;
buf = my_malloc(len, 32);
if (buf == nullptr) {
// Not enough memory!
OSReport("メモリが足りん!!\n");
result = CARD_RESULT_FATAL_ERROR;
goto exit;
}
// Read the file.
OSReport("ファイル読む\n");
result = CARDRead(&fileInfo, buf, len, 0);
if (result != CARD_RESULT_READY) {
// Can't read the file.
OSReport("ファイル読めない\n");
continue;
}
// Close the file.
OSReport("ファイル閉じる\n");
result = CARDClose(&fileInfo);
if (result != CARD_RESULT_READY) {
// Can't close the file.
OSReport("ファイル閉じれない\n");
continue;
}
// Reading successful!
OSReport("読み込み成功!!\n");
FamicomSaveDataHeader* read_save_header = (FamicomSaveDataHeader*)((u32)buf + status.offsetData);
if (famicom_save_data_check(read_save_header, -1, comment_img) == 0) {
// The data is normal!
OSReport("データは正常!!\n");
if (i == 0) {
if (CARDDelete(chan, temp_name) == CARD_RESULT_READY) {
// Deleted the old data backup.
OSReport("旧データバックアップを消しました\n");
}
}
else {
if (CARDDelete(chan, file_name) == CARD_RESULT_READY) {
// Deleted invalid 'real' file.
OSReport("不正なホンチャンファイルを消しました\n");
}
if (CARDRename(chan, temp_name, file_name) == CARD_RESULT_READY) {
// Renamed backup file to 'real' file.
OSReport("バックアップをホンチャンにリネームしました\n");
}
}
if (save_header != nullptr) {
memcpy(save_header, read_save_header, save_data_size);
}
break;
}
result = CARD_RESULT_NOFILE;
}
exit:
if (result != CARD_RESULT_READY && result != CARD_RESULT_FATAL_ERROR) {
errorChan = chan;
}
if (result != CARD_RESULT_READY) {
// Error code = %d.
OSReport("エラーコード=%d\n", result);
}
if (buf != nullptr) {
my_free(buf);
}
if (save_data != nullptr) {
my_free(save_data);
}
if (mounted) {
CARDUnmount(chan);
}
if (workArea != nullptr) {
my_free(workArea);
}
return result;
}
static s32 memcard_game_load(
u8* nesromp,
int rom_idx,
u8** tags_pp,
char* mura_save_name,
char* famicom_save_name,
MemcardGameHeader_t* game_header,
u8* memcard_save_comment,
size_t unused_save_data_start_ofs,
u8* chr_to_i8_bufp,
size_t chr_to_i8_buf_size
) {
s32 result;
s32 chan;
CARDFileInfo fileInfo;
int mounted = FALSE;
CARDStat cardStatus;
int now_rom_idx = 0;
void* workArea;
s32 fileNo;
workArea = my_malloc(CARD_WORKAREA_SIZE, 32);
if (workArea != nullptr) {
for (chan = 0; chan < CARD_NUM_CHANS; chan++) {
result = memcard_mount(chan, workArea);
mounted = result == CARD_RESULT_READY;
if (mounted) {
for (fileNo = 0; fileNo < 127; fileNo++) {
result = CARDGetStatus(chan, fileNo, &cardStatus);
if (result == CARD_RESULT_READY) {
if (
memcmp(cardStatus.gameName, "GAFE", 4) == 0 &&
memcmp(cardStatus.company, "01", 2) == 0 &&
memcmp(cardStatus.fileName, "DobutsunomoriP_F_", 17) == 0 &&
strcmp(&cardStatus.fileName[17], "SAVE") != 0 /* filename can't be Doubutsunomori_P_F_SAVE* */
) {
if (rom_idx == now_rom_idx) {
/* Incorporate the contents of the file. */
OSReport("ファイルの内容を取り込む\n");
result = CARDFastOpen(chan, fileNo, &fileInfo);
if (result != CARD_RESULT_READY) {
goto exit;
}
if (cardStatus.offsetData == 0) {
result = CARD_RESULT_FATAL_ERROR;
goto exit;
}
/* Reading the entire file. */
OSReport("ファイル全体の読み出し\n");
u8* data_bufp = cardStatus.length <= chr_to_i8_buf_size ? chr_to_i8_bufp : nullptr;
if (data_bufp == nullptr) {
result = CARD_RESULT_FATAL_ERROR;
goto exit;
}
result = CARDRead(&fileInfo, data_bufp, cardStatus.length, 0);
if (result != CARD_RESULT_READY) {
goto exit;
}
/* File close. */
OSReport("ファイルクローズ\n");
result = CARDClose(&fileInfo);
if (result != CARD_RESULT_READY) {
goto exit;
}
/* Unmount. */
OSReport("アンマウント\n");
result = CARDUnmount(chan);
mounted = FALSE;
if (result != CARD_RESULT_READY) {
goto exit;
}
/* Reading successful! */
OSReport("読み込み成功!!\n");
if (calcSum(data_bufp, cardStatus.length) != 0 || cardStatus.offsetData == 0) {
result = CARD_RESULT_FATAL_ERROR;
goto exit;
}
if (mura_save_name != nullptr) {
memcpy(mura_save_name, cardStatus.fileName, 32);
}
if (famicom_save_name != nullptr) {
memcpy(famicom_save_name, cardStatus.fileName, 32);
famicom_save_name[15] = 'S';
}
u8* datap = &data_bufp[cardStatus.offsetData];
memcpy(game_header, datap, sizeof(MemcardGameHeader_t));
datap += sizeof(MemcardGameHeader_t);
/* Copy nesinfo tags */
size_t tags_size = game_header->nestags_size;
if (tags_size != 0) {
if (tags_pp != nullptr) {
*tags_pp = (u8*)my_malloc(tags_size, 1);
}
/* @BUG - tags_pp might be a nullptr but we're dereferencing it anyway */
memcpy(*tags_pp, datap, tags_size);
}
else {
/* @BUG - tags_pp might be a nullptr but we're dereferencing it anyway */
*tags_pp = nullptr;
}
datap += ALIGN_NEXT(tags_size, 16);
size_t comment_img_size;
if (game_header->flags0.has_comment_img && (comment_img_size = game_header->comment_img_size, comment_img_size != 0)) {
if (JC_JKRDecomp_checkCompressed(datap) == JKRCOMPRESSION_NONE) {
SetupExternCommentImage(datap, memcard_save_comment, data_bufp);
}
else {
JC_JKRDecomp_decode(datap, nesromp, CHR_TO_I8_BUF_SIZE, 0);
SetupExternCommentImage(nesromp, memcard_save_comment, data_bufp);
}
datap += ALIGN_NEXT(comment_img_size, 16);
}
else {
SetupExternCommentImage(nullptr, memcard_save_comment, data_bufp);
}
if (game_header->nesrom_size != 0) {
u32 real_size = game_header->nesrom_size << 4;
if (JC_JKRDecomp_checkCompressed(datap) == JKRCOMPRESSION_NONE) {
memcpy(nesromp, datap, real_size);
nesinfo_data_size = real_size;
}
else {
JC_JKRDecomp_decode(datap, nesromp, CHR_TO_I8_BUF_SIZE, 0);
nesinfo_data_size = (u32)((datap[4] << 24) | (datap[5] << 16) | (datap[6]<< 8) | datap[7]);
}
}
result = CARD_RESULT_READY;
goto exit; // success
}
else {
now_rom_idx++;
}
}
}
else if (result != CARD_RESULT_NOFILE) {
goto exit;
}
}
if (fileNo >= 127) {
/* There was no target game image in slot %d. */
OSReport("スロット%dには目的のゲームイメージがありませんでした\n", chan);
CARDUnmount(chan);
mounted = FALSE;
}
}
}
/* The rom file was not found */
result = CARD_RESULT_NOFILE;
}
else {
result = CARD_RESULT_FATAL_ERROR;
}
exit:
if (result != CARD_RESULT_READY && result != CARD_RESULT_FATAL_ERROR) {
errorChan = chan;
}
if (result != CARD_RESULT_READY) {
// Error code = %d.
OSReport("エラーコード=%d\n", result);
}
if (mounted) {
CARDUnmount(chan);
}
if (workArea != nullptr) {
my_free(workArea);
}
return result;
}
static s32 memcard_game_list(
int* n_games,
char* namebufp,
int namebuf_size
) {
CARDFileInfo fileInfo;
CARDStat cardStatus;
s32 result;
s32 chan;
int mounted = FALSE;
int game_count = 0;
void* workArea;
s32 fileNo;
void* buf = nullptr;
u32 data_ofs;
workArea = my_malloc(CARD_WORKAREA_SIZE, 32);
if (workArea == nullptr) {
result = CARD_RESULT_FATAL_ERROR;
goto exit;
}
buf = my_malloc(0x200, 32);
if (buf == nullptr) {
result = CARD_RESULT_FATAL_ERROR;
goto exit;
}
for (chan = 0; chan < CARD_NUM_CHANS; chan++) {
mounted = memcard_mount(chan, workArea) == CARD_RESULT_READY;
if (mounted) {
for (fileNo = 0; fileNo < 127; fileNo++) {
result = CARDGetStatus(chan, fileNo, &cardStatus);
if (result == CARD_RESULT_READY) {
if (
memcmp(cardStatus.gameName, "GAFE", 4) == 0 &&
memcmp(cardStatus.company, "01", 2) == 0 &&
memcmp(cardStatus.fileName, "DobutsunomoriP_F_", 17) == 0 &&
strcmp(&cardStatus.fileName[17], "SAVE") != 0 /* filename can't be Doubutsunomori_P_F_SAVE* */
) {
if (namebuf_size >= 16) {
int invalid_game = FALSE;
/* Incorporate the contents of the file. */
OSReport("ファイルの内容を取り込む\n");
static char brokenTitle[] = "##BROKEN GAME##";
if (CARDFastOpen(chan, fileNo, &fileInfo) != CARD_RESULT_READY) {
invalid_game = TRUE;
}
else if (cardStatus.offsetData == 0) {
invalid_game = TRUE;
}
else {
/* Reading the beginning of the data. */
OSReport("データ先頭の読み出し\n");
data_ofs = cardStatus.offsetData - ALIGN_PREV(cardStatus.offsetData, 0x200);
if (CARDRead(&fileInfo, buf, 0x200, ALIGN_PREV(cardStatus.offsetData, 0x200)) != CARD_RESULT_READY) {
invalid_game = TRUE;
}
else {
/* File close. */
OSReport("ファイルクローズ\n");
if (CARDClose(&fileInfo) != CARD_RESULT_READY) {
invalid_game = TRUE;
}
}
}
if (invalid_game == FALSE) {
/* Reading successful! */
OSReport("読み込み成功!!\n");
memcpy(namebufp, (u8*)buf + data_ofs + 2, MURA_GAME_NAME_SIZE); // read mori name
}
else {
/* Reading failed! */
OSReport("読み込み失敗!!\n");
memcpy(namebufp, brokenTitle, MURA_GAME_NAME_SIZE);
}
namebufp += MURA_GAME_NAME_SIZE;
namebuf_size -= MURA_GAME_NAME_SIZE;
}
(*n_games)++;
game_count++;
}
}
else if (result != CARD_RESULT_NOFILE) {
goto exit;
}
}
if (fileNo >= 127) {
/* Finished searching slot %d. */
OSReport("スロット%d検索終了\n", chan);
CARDUnmount(chan);
mounted = FALSE;
}
}
}
result = CARD_RESULT_READY;
*n_games = game_count;
exit:
if (result != CARD_RESULT_READY) {
// Error code = %d.
OSReport("エラーコード=%d\n", result);
}
if (buf != nullptr) {
my_free(buf);
}
if (mounted) {
CARDUnmount(chan);
}
if (workArea != nullptr) {
my_free(workArea);
}
return result;
}
inline JKRFileFinder* getFirstFile(const char* dir) {
return JKRFileLoader::findFirstFile(dir);
}
static size_t entrylist(const char* dir, char* filename_strbuf) {
char pathbuf[256];
u8* filename_buf = (u8*)filename_strbuf;
JKRFileFinder* fileFinder;
s32 dir_path_len;
size_t len;
fileFinder = getFirstFile(dir);
dir_path_len = strlen(dir);
if (filename_strbuf != nullptr) {
filename_strbuf[0] = ':';
memcpy(filename_strbuf + 1, dir, dir_path_len + 1);
nesrom_filename_ptrs[nesrom_count] = filename_buf;
}
nesrom_count++;
len = dir_path_len + 2;
if (fileFinder != nullptr) {
while (fileFinder->isAvailable()) {
if (fileFinder->mBase.mFileName[0] != '.') {
snprintf(pathbuf, sizeof(pathbuf), "%s/%s", dir, fileFinder->mBase.mFileName);
dir_path_len = strlen(pathbuf);
if (fileFinder->mIsDir) {
len += entrylist(pathbuf, filename_strbuf != nullptr ? filename_strbuf + len : nullptr);
}
else {
if (filename_strbuf != nullptr) {
u8* romname_p = filename_buf + len;
memcpy(romname_p, pathbuf, dir_path_len + 1);
nesrom_filename_ptrs[nesrom_count] = romname_p;
}
nesrom_count++;
len = 1 + dir_path_len + len;
}
}
fileFinder->findNextFile();
}
delete fileFinder;
}
return len;
}
static void select_game() {
int rom_no = famicomCommon.rom_no;
int rom = rom_no;
if (famicomCommon.nesrom_memcard == FALSE) {
nesrom_idx = 0;
while (TRUE) {
if (*nesrom_filename_ptrs[nesrom_idx] != ':' &&--rom <= 0) {
break;
}
nesrom_idx++;
if (nesrom_idx >= nesrom_count) {
if (nesrom_idx_loaded < 0) {
OSPanic(__FILE__, 4239, "no nesfile specified");
continue;
}
nesrom_idx = 0;
}
}
}
else {
nesrom_idx = rom_no;
}
}
extern int famicom_mount_archive_end_check() {
return famicom_arc != nullptr;
}
extern void famicom_mount_archive() {
if (famicom_arc == nullptr && famicom_arc_mounting == false) {
famicom_arc_mounting = true;
famicom_arc = (JKRArchive*)JC__JKRMountArchive("famicom.arc", JKRArchive::MOUNT_COMP, JC__JKRGetSystemHeap(), JKRArchive::MOUNT_DIRECTION_HEAD);
if (famicom_arc == nullptr) {
OSDVDFatalError();
}
famicom_arc_mounting = false;
}
JKRAram::getAramHeap()->dump();
}
static void famicom_mount_archive_wait() {
if (famicom_arc == nullptr) {
// Waiting because famicom.arc has not been mounted yet.
OSReport("famicom.arc がまだマウントされていないので待っています\n");
do {
VIWaitForRetrace();
} while (famicom_arc == nullptr);
}
}
// clang-format off
#define MALLOC_MALLOC(type, size, dst, line) \
if (dst != nullptr) { \
OSPanic(__FILE__, line, "MALLOC_MALLOC: " #dst " isn't NULL"); \
} \
dst = (type *)my_malloc(size, 32); \
if (dst == nullptr) { \
/* MALLOC_MALLOC: %s %s %dByte allocation failed. */ \
OSReport("MALLOC_MALLOC: %s %s %dByte 確保失敗\n", #type, #size, size); \
goto exit; \
} \
/* MALLOC_MALLOC: %s %s %dByte successfully allocated. */ \
OSReport("MALLOC_MALLOC: %s %s %dByte 確保成功\n", #type, #size, size);
// clang-format on
#define CONV_BYTE_KB(bytes) ((f32)(bytes) / 1024.0f)
extern int famicom_init(int rom_idx, Famicom_MallocInfo* malloc_info, int player_no) {
famicom_mount_archive_wait();
bzero(&famicomCommon, sizeof(FamicomCommon));
my_malloc_current = malloc_info;
if (player_no >= 0 && player_no < PLAYER_NUM) {
famicomCommon.save_pl_no = player_no;
}
else {
famicomCommon.save_pl_no = 0;
}
if (rom_idx == 0) {
filer_mode_enable = TRUE;
}
else {
filer_mode_enable = FALSE;
}
filer_mode = 0;
filer_demo_mode = FILER_DEMO_MODE_NORMAL;
filer_demo_count = 0;
nesrom_idx_loaded = -1;
if (rom_idx == 0) {
famicomCommon.nesrom_memcard = FALSE;
famicomCommon.rom_no = 0;
nesrom_idx = 1;
}
else if (rom_idx > 0) {
famicomCommon.nesrom_memcard = FALSE;
famicomCommon.rom_no = rom_idx;
if (famicomCommon.save_pl_no >= 0 && save_game_image == 0) {
famicomCommon.save_data_name = (u8*)"GAFEFSC\x1A";
famicomCommon.save_data_single_size = 0x660;
famicomCommon.save_data_total_size = sizeof(FamicomSaveDataHeader) + 0x660 * PLAYER_NUM;
famicomCommon.save_data_header = (FamicomSaveDataHeader*)famicomCommonSave;
famicomCommon.internal_save_datap = famicomCommonSave + sizeof(FamicomSaveDataHeader) + 0x660 * famicomCommon.save_pl_no;
}
nesrom_idx = famicomCommon.rom_no + 1;
strncpy((char*)famicomCommon.mura_save_name, "DobutsunomoriP_MURA", 32);
strncpy((char*)famicomCommon.famicom_save_name, "DobutsunomoriP_F_SAVE", 32);
famicomCommon.unused_save_data_start_ofs = 0x640;
}
else if (rom_idx < 0) {
famicomCommon.nesrom_memcard = TRUE;
famicomCommon.rom_no = -rom_idx;
famicomCommon.unused_save_data_start_ofs = 0x3040;
}
nesrom_count = 0;
rapid_count = 0;
rapid_mode_a = 0;
rapid_mode_b = 0;
nines_over_double_click = 0;
nines_over_mode = 0;
hispeed_mode = 0;
slow_mode = 0;
slow_mode_sub = 0;
speed_show = 0;
JKRFileLoader::changeDirectory("/FAMICOM/");
size_t unused_totalfreesize = my_gettotalfreesize();
MALLOC_MALLOC(u8, CHR_TO_I8_BUF_SIZE, famicomCommon.chr_to_i8_bufp, 4497);
MALLOC_MALLOC(u8, KS_NES_NESFILE_HEADER_SIZE + KS_NES_PRGROM_SIZE + KS_NES_CHRROM_SIZE, famicomCommon.nesromp, 4499);
MALLOC_MALLOC(ksNesCommonWorkObj, sizeof(ksNesCommonWorkObj), famicomCommon.wp, 4501);
MALLOC_MALLOC(ksNesStateObj, sizeof(ksNesStateObj), famicomCommon.sp, 4502);
MALLOC_MALLOC(u8, KS_NES_CHRRAM_SIZE, famicomCommon.chrramp, 4505);
MALLOC_MALLOC(u8, KS_NES_BBRAM_SIZE, famicomCommon.bbramp, 4510);
MALLOC_MALLOC(u8, KS_NES_NOISE_DATA_SIZE, famicomCommon.noise_bufp, 4515);
MALLOC_MALLOC(u8, KS_NES_DRAW_RESULT_BUF_SIZE, famicomCommon.result_bufp, 4517);
famicomCommon.memcard_save_comment = commentImageBuffer;
OSReport("buffer used:\n");
OSReport("wp =%8.3fKB\n", CONV_BYTE_KB(my_getmemblocksize(famicomCommon.wp)));
OSReport("sp =%8.3fKB\n", CONV_BYTE_KB(my_getmemblocksize(famicomCommon.sp)));
OSReport("nesromp =%8.3fKB\n", CONV_BYTE_KB(my_getmemblocksize(famicomCommon.nesromp)));
OSReport("chrramp =%8.3fKB\n", CONV_BYTE_KB(my_getmemblocksize(famicomCommon.chrramp)));
OSReport("bbramp =%8.3fKB\n", CONV_BYTE_KB(my_getmemblocksize(famicomCommon.bbramp)));
OSReport("noise_bufp =%8.3fKB\n", CONV_BYTE_KB(my_getmemblocksize(famicomCommon.noise_bufp)));
OSReport("chr_to_i8_bufp=%8.3fKB\n", CONV_BYTE_KB(my_getmemblocksize(famicomCommon.chr_to_i8_bufp)));
OSReport("result_bufp =%8.3fKB\n", CONV_BYTE_KB(my_getmemblocksize(famicomCommon.result_bufp)));
OSReport("total %8.3fKB.\nif chrrom-type, and no bbram, minimum total size: %8.3fKB + nesrom size max + (nesrom chr size max * 4) bytes.\n",
CONV_BYTE_KB(my_getmemblocksize(famicomCommon.wp)) +
CONV_BYTE_KB(my_getmemblocksize(famicomCommon.sp)) +
CONV_BYTE_KB(my_getmemblocksize(famicomCommon.nesromp)) +
CONV_BYTE_KB(my_getmemblocksize(famicomCommon.chrramp)) +
CONV_BYTE_KB(my_getmemblocksize(famicomCommon.bbramp)) +
CONV_BYTE_KB(my_getmemblocksize(famicomCommon.noise_bufp)) +
CONV_BYTE_KB(my_getmemblocksize(famicomCommon.chr_to_i8_bufp)) +
CONV_BYTE_KB(my_getmemblocksize(famicomCommon.result_bufp)),
CONV_BYTE_KB(my_getmemblocksize(famicomCommon.wp)) +
CONV_BYTE_KB(my_getmemblocksize(famicomCommon.sp)) +
CONV_BYTE_KB(my_getmemblocksize(famicomCommon.noise_bufp)) +
CONV_BYTE_KB(my_getmemblocksize(famicomCommon.result_bufp))
);
if (famicomCommon.noise_bufp == nullptr || JKRFileLoader::readGlbResource(famicomCommon.noise_bufp, KS_NES_NOISE_DATA_SIZE, "noise.bin.szs", EXPAND_SWITCH_DECOMPRESS)) {
if (famicomCommon.noise_bufp != nullptr) {
EmuSound_Start(famicomCommon.noise_bufp + 0x2000);
}
nesrom_idx_loaded = -1;
if (famicomCommon.nesrom_memcard == FALSE) {
for (u32 i = 0; i < 2; i++) {
nesrom_count = 0;
size_t length = entrylist("GAME", nesrom_filename_strbuf);
if (i == 0) {
if (nesrom_count == 0) {
OSReport("no nesfile specified\n");
goto exit;
}
MALLOC_MALLOC(u8 *, nesrom_count * sizeof(u8*), nesrom_filename_ptrs, 4676);
MALLOC_MALLOC(char, length, nesrom_filename_strbuf, 4677);
}
}
}
else {
nesrom_count = 0;
}
select_game();
JUTProcBar::getManager()->setVisible(false);
famicomCommon.low_res_mode = true;
JW_SetFamicomMode(TRUE);
JW_SetLowResoMode(famicomCommon.low_res_mode);
return 0; // success
}
// error
exit:
if (famicomCommon.wp != nullptr) {
my_free(famicomCommon.wp);
}
famicomCommon.wp = nullptr;
if (famicomCommon.sp != nullptr) {
my_free(famicomCommon.sp);
}
famicomCommon.sp = nullptr;
if (famicomCommon.nesromp != nullptr) {
my_free(famicomCommon.nesromp);
}
famicomCommon.nesromp = nullptr;
if (famicomCommon.chrramp != nullptr) {
my_free(famicomCommon.chrramp);
}
famicomCommon.chrramp = nullptr;
if (famicomCommon.bbramp != nullptr) {
my_free(famicomCommon.bbramp);
}
famicomCommon.bbramp = nullptr;
if (famicomCommon.noise_bufp != nullptr) {
my_free(famicomCommon.noise_bufp);
}
famicomCommon.noise_bufp = nullptr;
if (famicomCommon.chr_to_i8_bufp != nullptr) {
my_free(famicomCommon.chr_to_i8_bufp);
}
famicomCommon.chr_to_i8_bufp = nullptr;
if (famicomCommon.result_bufp != nullptr) {
my_free(famicomCommon.result_bufp);
}
famicomCommon.result_bufp = nullptr;
if (famicomCommon.highscore_flagsp != nullptr) {
my_free(famicomCommon.highscore_flagsp);
}
famicomCommon.highscore_flagsp = nullptr;
if (famicomCommon.nesinfo_tagsp != nullptr) {
my_free(famicomCommon.nesinfo_tagsp);
}
famicomCommon.nesinfo_tagsp = nullptr;
return -1;
}
static int SetupResBanner(const ResTIMG* img, u8* dst, size_t max_size, size_t* size, u8* type) {
size_t banner_size;
u8* data_p = dst;
int banner_type;
if (img != nullptr) {
banner_size = img->getWidth() * img->getHeight();
if (img->mTextureFormat != ResTIMG_FORMAT_C8) {
banner_size *= sizeof(u16);
banner_type = CARD_STAT_BANNER_RGB5A3;
}
else {
banner_type = CARD_STAT_BANNER_C8;
}
bcopy(((u8*)img) + img->mImageDataOffset, data_p, banner_size);
data_p += banner_size;
if (img->mPaletteFormat != ResTIMG_NO_PALETTE) {
size_t pal_size = img->mPaletteEntryCount * sizeof(u16);
bcopy(((u8*)img) + img->mPaletteOffset, data_p, pal_size);
bzero(data_p + pal_size, (256 * sizeof(u16)) - pal_size); // zero out unused entries
data_p += 256 * sizeof(u16);
}
}
else {
banner_type = CARD_STAT_BANNER_NONE;
}
if (size != nullptr) {
*size = (u32)data_p - (u32)dst;
}
if (type != nullptr) {
*type = banner_type;
}
return 0;
}
static int SetupResIcon(const ResTIMG* img, u8* dst, size_t max_size, size_t* size_p, u16* icon_fmt_p, u16* icon_flags_p) {
u8* data_p = dst;
size_t icon_size;
u16 icon_fmt;
u16 icon_flags;
int banner_type;
u32 icon_type;
int i;
u32 icon_count;
int j;
if (img != nullptr) {
icon_size = img->getWidth() * img->getHeight();
icon_count = icon_size / 0x400;
if (img->mTextureFormat != ResTIMG_FORMAT_C8) {
icon_size *= sizeof(u16);
icon_type = CARD_STAT_ICON_RGB5A3;
}
else {
icon_type = CARD_STAT_ICON_C8;
}
/* Make icon format flags & speed flags for each frame */
icon_fmt = 0;
icon_flags = 0;
i = 0;
for (j = 0; j < icon_count; j++) {
icon_fmt |= (icon_type << (i * 2));
icon_flags |= (CARD_STAT_SPEED_MIDDLE << (i * 2));
i++;
}
bcopy(((u8*)img) + img->mImageDataOffset, data_p, icon_size);
data_p += icon_size;
if (img->mPaletteFormat != ResTIMG_NO_PALETTE) {
size_t pal_size = img->mPaletteEntryCount * sizeof(u16);
bcopy(((u8*)img) + img->mPaletteOffset, data_p, pal_size);
bzero(data_p + pal_size, (256 * sizeof(u16)) - pal_size); // zero out unused entries
data_p += 256 * sizeof(u16);
}
}
else {
icon_fmt = CARD_STAT_ICON_NONE;
}
if (size_p != nullptr) {
*size_p = (u32)data_p - (u32)dst;
}
if (icon_fmt_p != nullptr) {
*icon_fmt_p = icon_fmt;
}
if (icon_flags_p != nullptr) {
*icon_flags_p = icon_flags;
}
return 0;
}
extern int famicom_cleanup() {
int res = FALSE;
if (nesrom_idx_loaded != -1 && save_game_image == 0) {
OSReport("nesrom_memcard = %d nesrom_idx = %d\n", famicomCommon.nesrom_memcard, nesrom_idx);
if (famicomCommon.internal_save_datap != nullptr) {
nesinfo_tag_process3(famicomCommon.internal_save_datap);
}
if (famicomCommon.save_pl_no >= 0 && famicomCommon.save_data_header != nullptr) {
if (famicom_save_data_check((FamicomSaveDataHeader*)famicomCommon.save_data_header, famicomCommon.save_pl_no, famicomCommon.save_data_name) == 0) {
famicom_save_data_setup((FamicomSaveDataHeader*)famicomCommon.save_data_header, famicomCommon.save_data_single_size, famicomCommon.save_data_name);
}
else {
// Will not save because the common save area is damaged.
OSReport(VT_COL(RED, WHITE) "共通セーブ領域が壊れているのでセーブしません\n" VT_RST);
res = TRUE;
}
}
}
if (famicomCommon.noise_bufp != nullptr) {
EmuSound_Exit();
}
if (famicomCommon.wp != nullptr) {
my_free(famicomCommon.wp);
}
famicomCommon.wp = nullptr;
if (famicomCommon.sp != nullptr) {
my_free(famicomCommon.sp);
}
famicomCommon.sp = nullptr;
if (famicomCommon.nesromp != nullptr) {
my_free(famicomCommon.nesromp);
}
famicomCommon.nesromp = nullptr;
if (famicomCommon.chrramp != nullptr) {
my_free(famicomCommon.chrramp);
}
famicomCommon.chrramp = nullptr;
if (famicomCommon.bbramp != nullptr) {
my_free(famicomCommon.bbramp);
}
famicomCommon.bbramp = nullptr;
if (famicomCommon.noise_bufp != nullptr) {
my_free(famicomCommon.noise_bufp);
}
famicomCommon.noise_bufp = nullptr;
if (famicomCommon.chr_to_i8_bufp != nullptr) {
my_free(famicomCommon.chr_to_i8_bufp);
}
famicomCommon.chr_to_i8_bufp = nullptr;
if (famicomCommon.result_bufp != nullptr) {
my_free(famicomCommon.result_bufp);
}
famicomCommon.result_bufp = nullptr;
if (famicomCommon.highscore_flagsp != nullptr) {
my_free(famicomCommon.highscore_flagsp);
}
famicomCommon.highscore_flagsp = nullptr;
if (nesrom_filename_ptrs != nullptr) {
my_free(nesrom_filename_ptrs);
}
nesrom_filename_ptrs = nullptr;
if (nesrom_filename_strbuf != nullptr) {
my_free(nesrom_filename_strbuf);
}
nesrom_filename_strbuf = nullptr;
my_malloc_current = nullptr;
JW_SetLowResoMode(FALSE);
JW_SetFamicomMode(FALSE);
return res;
}
static s32 famicom_common_save_initial();
static int famicom_rom_load() {
int nesrom_buffer_size = my_getmemblocksize(famicomCommon.nesromp);
nesinfo_init();
OSReport("nesrom_buffer_size=%d\n", nesrom_buffer_size);
if (nesrom_buffer_size >= KS_NES_CHRROM_SIZE) {
bzero(famicomCommon.nesromp, KS_NES_CHRROM_SIZE);
}
if (famicomCommon.nesrom_memcard != FALSE) {
/* Loading the game image from the memory card. */
OSReport("ゲームイメージをメモリーカードから読み込みます\n");
s32 load_res = memcard_game_load(
famicomCommon.nesromp,
famicomCommon.rom_no - 1,
&famicomCommon.nesinfo_tagsp,
(char*)famicomCommon.mura_save_name,
(char*)famicomCommon.famicom_save_name,
&famicomCommon.memcard_game_header,
famicomCommon.memcard_save_comment, // comment & images
famicomCommon.unused_save_data_start_ofs,
famicomCommon.chr_to_i8_bufp,
CHR_TO_I8_BUF_SIZE
);
if (load_res == CARD_RESULT_READY) {
/* Loaded the game image from the memory card. */
OSReport("ゲームイメージをメモリーカードから読み込みました\n");
nesinfo_tags_start = famicomCommon.nesinfo_tagsp;
nesinfo_tags_end = nesinfo_tags_start + famicomCommon.memcard_game_header.nestags_size;
nesinfo_tags_size = nesinfo_tags_end - nesinfo_tags_start;
nesinfo_data_start = famicomCommon.nesromp;
nesinfo_data_end = nesinfo_data_start + nesinfo_data_size;
}
else {
/* Failed to load game [%d]. */
OSReport("ゲーム読み込みに失敗しました [%d]\n", load_res);
return -1;
}
}
else {
const char* filename = (char*)nesrom_filename_ptrs[nesrom_idx];
OSReport("%d:<%s>\n", nesrom_idx, filename);
bzero(famicomCommon.nesromp, nesrom_buffer_size);
size_t loaded_size = JKRFileLoader::readGlbResource(famicomCommon.nesromp, nesrom_buffer_size, filename, EXPAND_SWITCH_DECOMPRESS);
if (loaded_size == 0) {
return -1;
}
nesinfo_tags_set(famicomCommon.rom_no - 1);
nesinfo_data_size = loaded_size;
nesinfo_data_start = famicomCommon.nesromp;
nesinfo_data_end = nesinfo_data_start + loaded_size;
MemcardGameHeader_t* memcard_game_header = &famicomCommon.memcard_game_header;
bzero(memcard_game_header, sizeof(MemcardGameHeader_t));
memcard_game_header->_00 = 'G';
memcard_game_header->_01 = 0;
strncpy((char*)memcard_game_header->mori_name, (char*)nesinfo_get_moriName(), MURA_GAME_NAME_SIZE);
memcard_game_header->nesrom_size = 0;
memcard_game_header->nestags_size = 0;
memcard_game_header->flags0.has_comment_img = TRUE;
memcard_game_header->flags0.no_copy_flag = FALSE;
memcard_game_header->flags1.no_move_flag = FALSE;
SetupInternalCommentImage(famicomCommon.memcard_save_comment);
}
u32 max_ofs = 0;
tcs_bad = false;
ics_bad = false;
nesinfo_tag_process1(nullptr, 0, &max_ofs);
if (ics_bad || tcs_bad) {
if (tcs_bad) {
// Tag checksum is incorrect.
OSReport(VT_COL(YELLOW, BLACK) "タグチェックサムが違います\n" VT_RST);
}
if (ics_bad) {
// Image checksum is incorrect.
OSReport(VT_COL(YELLOW, BLACK) "イメージチェックサムが違います\n" VT_RST);
}
return -1;
}
if (famicomCommon.nesrom_memcard && famicomCommon.save_pl_no >= 0) {
famicomCommon.save_data_name = (u8*)"GAFEFSE\x1A";
famicomCommon.save_data_single_size = max_ofs + 0xB;
famicomCommon.save_data_total_size = famicomCommon.save_data_single_size * PLAYER_NUM + sizeof(FamicomSaveDataHeader);
famicomCommon.save_data_header = (FamicomSaveDataHeader*)nintendo_hi_0; // overwritten for use with save data
famicomCommon.internal_save_datap = nintendo_hi_0 + sizeof(FamicomSaveDataHeader) + famicomCommon.save_data_single_size * famicomCommon.save_pl_no;
bzero(famicomCommon.save_data_header, famicomCommon.save_data_total_size);
}
if (famicomCommon.nesrom_memcard == FALSE) {
famicomCommon.internal_save_datap = famicomCommonSave + sizeof(FamicomSaveDataHeader) + 0x660 * famicomCommon.save_pl_no;
}
else if (famicomCommon.save_data_header != nullptr) {
famicom_common_save_initial();
}
if (famicomCommon.internal_save_datap != nullptr) {
nesinfo_tag_process1(famicomCommon.internal_save_datap, 1, nullptr);
}
famicomCommon.highscore_flagsp = (u8*)my_malloc(nesinfo_get_highscore_num(), 32);
highscore_setup_flags(famicomCommon.highscore_flagsp);
nesinfo_tag_process2();
ksNesCommonWorkObj* wp = famicomCommon.wp;
wp->nesromp = famicomCommon.nesromp;
u32 flags = 0;
wp->prg_size = KS_NES_PRGROM_SIZE;
wp->noise_bufp = famicomCommon.noise_bufp;
wp->_001D = 4;
wp->chr_to_u8_bufp = famicomCommon.chr_to_i8_bufp;
wp->chr_to_i8_buf_size = CHR_TO_I8_BUF_SIZE;
wp->result_bufp = famicomCommon.result_bufp;
wp->_0018 = 0;
if (famicomCommon.noise_bufp == nullptr) {
flags |= 0x40; // no sound?
}
int reset_res = ksNesReset(wp, famicomCommon.sp, flags | 0x10, famicomCommon.chrramp, famicomCommon.bbramp);
if (reset_res != 0) {
OSReport("err code=%d (0x%x), %x,%x,%x,%x,%x,%x\n",
reset_res, reset_res, famicomCommon.wp, famicomCommon.sp,
famicomCommon.wp->draw_ctx.sprite_scanline_limit, famicomCommon.wp->draw_ctx.ppu_scanline_regs,
famicomCommon.wp->draw_ctx.post_process_lut, famicomCommon.sp->ppu_chr_banks
);
OSReport("NES emu reset failed!!");
}
else {
OSReport("ksNesReset() OK PC=%x, prg_size = 0x%x, chr_size = 0x%x\n",
famicomCommon.sp->PC, famicomCommon.sp->prg_size, famicomCommon.sp->chr_size);
nesrom_idx_loaded = nesrom_idx;
return 0;
}
return -1;
}
static void famicom_key_convert() {
u32 input;
for (int i = 0; i < PAD_MAX_CONTROLLERS; i++) {
if (InputValid[i]) {
input = InputData[i];
}
else {
input = 0;
}
if (rapid_mode_a && (input & JUTGamePad::A) && (rapid_count & 0x80000000)) {
input ^= JUTGamePad::A;
}
if (rapid_mode_b && (input & JUTGamePad::B) && (rapid_count & 0x40000000)) {
input ^= JUTGamePad::B;
}
if (filer_mode != 0 || filer_demo_mode != FILER_DEMO_MODE_NORMAL) {
input &= ~(JUTGamePad::A | JUTGamePad::B);
}
input =
(((input >> 2) & 3) << 26) // DPad up & down
| (((input >> 0) & 1) << 25) // DPad left (0x02000000)
| (((input >> 1) & 1) << 24) // DPad right (0x01000000)
| (((input >> 8) & 1) << 31) // A -> A (0x80000000)
| (((input >> 9) & 1) << 30) // B -> B (0x40000000)
| (((input >> 10) & 1) << 29) // X -> Select (0x20000000)
| (((input >> 4) & 1) << 29) // Z -> Select (0x20000000)
| (((input >> 11) & 1) << 28) // Y -> Start (0x10000000)
| (((input >> 12) & 1) << 28) // Start -> Start (0x10000000)
| (((input >> 24) & 1) ? (1 << 25) : (0 << 25)) // Left analog stick -> Pad Left
| (((input >> 25) & 1) ? (1 << 24) : (0 << 24)) // Right analog stick -> Pad Right
| (((input >> 26) & 1) ? (1 << 26) : (0 << 26)) // Down analog stick -> Pad Down
| (((input >> 27) & 1) ? (1 << 27) : (0 << 27)) // Up analog stick -> Pad Up
;
switch (i) {
case 0:
famicomCommon.wp->pads[0] = input;
break;
case 1:
famicomCommon.wp->pads[2] = input;
break;
case 2:
famicomCommon.wp->pads[1] = input;
break;
case 3:
famicomCommon.wp->pads[3] = input;
break;
}
}
rapid_count += 0xABCD0000;
}
static void famicom_draw() {
GXTexObj famicomTexObj;
GXSetScissor(0, 0, 640, 480);
GXSetViewport(0.0f, 0.0f, 640.0f, 480.0f, 0.0f, 1.0f);
ksNesDrawInit(famicomCommon.wp);
GXSetNumChans(0);
GXSetNumTexGens(1);
GXSetNumTevStages(1);
GXSetNumIndStages(0);
GXClearVtxDesc();
GXSetVtxDesc(GX_VA_POS, GX_DIRECT);
GXSetVtxDesc(GX_VA_TEX0, GX_DIRECT);
GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_CLR_RGB, GX_RGBA4, 0);
GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_CLR_RGBA, GX_RGBX8, 9);
GXInitTexObj(&famicomTexObj, famicomCommon.wp->result_bufp, (u16)KS_NES_WIDTH, (u16)KS_NES_HEIGHT, GX_TF_RGB565, GX_CLAMP, GX_CLAMP, GX_FALSE);
GXInitTexObjLOD(&famicomTexObj, GX_NEAR, GX_NEAR, 0.0f, 0.0f, 0.0f, GX_FALSE, GX_FALSE, GX_ANISO_1);
GXLoadTexObj(&famicomTexObj, GX_TEXMAP0);
GXSetTexCoordGen2(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY, GX_FALSE, GX_PTIDENTITY);
GXSetTexCoordScaleManually(GX_TEXCOORD0, GX_FALSE, 0, 0);
GXSetTevDirect(GX_TEVSTAGE0);
GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL);
GXSetTevOp(GX_TEVSTAGE0, GX_REPLACE);
GXSetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0);
GXBegin(GX_QUADS, GX_VTXFMT0, 4);
int n2; // r0
int n0; // r4
int n3; // r3
int n1; // r6
if (filer_mode != 0 || filer_demo_mode != FILER_DEMO_MODE_NORMAL) {
n2 = 256;
n0 = KS_NES_HEIGHT;
n3 = -288;
n1 = KS_NES_HEIGHT;
}
else {
n2 = 256;
n0 = KS_NES_HEIGHT;
n3 = 30;
n1 = 6;
}
// vertex 0
GXPosition2s16(320 - n3, -8 - n1);
GXTexCoord2s16(512, 0);
// vertex 1
GXPosition2s16(320 - n3, -8 - n1 - n0);
GXTexCoord2s16(512, 512);
// vertex 2
GXPosition2s16(320 - n3 - n2, -8 - n1 - n0);
GXTexCoord2s16(0, 512);
// vertex 3
GXPosition2s16(320 - n3 - n2, -8 - n1);
GXTexCoord2s16(0, 0);
ksNesDrawEnd();
}
static void* ksnes_proc(void* param) {
u32* flags_p = (u32*)param;
ksNesEmuFrame(famicomCommon.wp, famicomCommon.sp, *flags_p);
return nullptr;
}
static void ksnes_thread_kill(OSAlarm* alarm, OSContext* context) {
/* Famicom emulator crashed. */
OSReport(VT_COL(YELLOW, BLACK) "ファミコンエミュ死亡\n" VT_RST);
OSCancelThread(&ksnes_thread);
}
static int ksnes_thread_exec(u32 flags) {
int res;
u8* ksnes_emu_stack = (u8*)my_malloc(KS_NES_EMU_STACK_SIZE, 32);
if (ksnes_emu_stack != nullptr) {
OSTime timeout = OSMillisecondsToTicks(2000); // 2 second timeout
if (famicomCommon.sp->motor_timer != 0) {
// Disk motor is on.
OSReport("ディスクモーターオン\n");
}
OSTime frame_start_time = OSGetTime();
s32 prio = OSGetThreadPriority(OSGetCurrentThread());
OSAlarm alarm;
OSCreateThread(&ksnes_thread, &ksnes_proc, &flags, ksnes_emu_stack + KS_NES_EMU_STACK_SIZE, KS_NES_EMU_STACK_SIZE, prio + 1, 0);
OSCreateAlarm(&alarm);
OSResumeThread(&ksnes_thread);
OSSetAlarm(&alarm, timeout, &ksnes_thread_kill);
OSJoinThread(&ksnes_thread, (void**)&res);
OSCancelAlarm(&alarm);
OSTime frame_end_time = OSGetTime();
if ((frame_end_time - frame_start_time) > OSMillisecondsToTicks(16)) {
OSTime ms = OSTicksToMicroseconds(frame_end_time - frame_start_time); // isn't this supposed to be OSTicksToMilliseconds?
// Emulator slow %d ms.
OSReport(VT_COL(YELLOW, BLACK) "エミュ遅い %d ms\n" VT_RST, ms);
}
my_free(ksnes_emu_stack);
return res;
}
else {
return -2; // failed to allocate stack
}
}
#pragma dont_inline on // @HACK - necessary to not inline JUTGamePad::getPortStatus
static void nogbaInput() {
int port;
u32 disconnected_ports = 0;
for (port = 0; port < PAD_MAX_CONTROLLERS; port++) {
InputValid[port] = false;
// switch (GetPortStatus(port)) {
switch ((int)JUTGamePad::getPortStatus((JUTGamePad::EPadPort)port)) {
case PAD_ERR_NONE:
{
InputValid[port] = true;
InputData[port] = ((JUTGamePad*)gamePad)[port].mButtons.mButton;
if (((JUTGamePad*)gamePad)[port].mButtons.mAnalogL != 0) {
InputData[port] |= BUTTON_X;
}
if (((JUTGamePad*)gamePad)[port].mButtons.mAnalogR != 0) {
InputData[port] |= BUTTON_L;
}
break;
}
case PAD_ERR_NO_CONTROLLER:
{
disconnected_ports |= (0x80000000 >> port);
break;
}
}
}
if (disconnected_ports != 0) {
PADReset(disconnected_ports);
}
for (int port = 0; port < PAD_MAX_CONTROLLERS; port++) {
if (InputValid[port]) {
InputTrigger[port] = InputData[port] & (InputButton[port] ^ InputData[port]); // Update trigger to only newly pressed buttons
InputButton[port] = InputData[port]; // update pressed buttons
if (InputButton[port] == 0) {
InputRepeat[port] = 0;
}
else if (InputTrigger[port] != 0) {
InputRepeat[port] = InputButton[port];
InputRepeatCount[port] = 15;
}
else {
if (InputRepeatCount[port] != 0) {
InputRepeatCount[port]--;
InputRepeat[port] = 0;
}
else {
InputRepeatCount[port] = 0;
InputRepeat[port] = InputButton[port];
}
}
}
else {
InputButton[port] = 0;
InputTrigger[port] = 0;
InputRepeat[port] = 0;
}
}
}
#pragma dont_inline reset
// @HACK - we need to force J2DOrthoGraph's dtor to emit
static void fake() {
J2DOrthoGraph graph;
graph.~J2DOrthoGraph();
}
extern void famicom_1frame() {
nogbaInput();
u32 flags = 0;
if (famicomCommon.noise_bufp == nullptr) {
flags |= 0x1000;
}
famicom_key_convert();
famicomCommon.wp->_0030 = 0;
famicomCommon.wp->_0034 = 0;
famicomCommon.wp->_0038 = 0;
/* Special input modes activated when L & R are held */
/*
Always - L + R + START = reset console
Zurumode 1 - L + R + D-Left = Toggle obj 9 'over' mode
Zurumode 1 - L + R + A = Toggle rapid A mode
Zurumode 1 - L + R + B = Toggle rapid B mode
*/
if ((InputButton[0] & (JUTGamePad::L | JUTGamePad::R)) == (JUTGamePad::L | JUTGamePad::R) &&
filer_mode == 0 && filer_demo_mode == FILER_DEMO_MODE_NORMAL) {
if ((InputTrigger[0] & JUTGamePad::START)) {
if (famicomCommon.internal_save_datap != nullptr) {
nesinfo_update_highscore(famicomCommon.internal_save_datap, 1);
}
ksNesPushResetButton(famicomCommon.sp);
}
if (APPNMI_ZURUMODE_GET()) {
if ((InputTrigger[0] & JUTGamePad::DPAD_LEFT)) {
nines_over_mode ^= TRUE;
JUTReport(30, 46, 30, "OBJ 9'S OVER %s", nines_over_mode ? "DISABLED" : "ENABLED");
}
if ((InputTrigger[0] & JUTGamePad::DPAD_RIGHT)) {
famicomCommon.low_res_mode ^= TRUE;
}
if ((InputTrigger[0] & JUTGamePad::A)) {
rapid_mode_a ^= TRUE;
}
if ((InputTrigger[0] & JUTGamePad::B)) {
rapid_mode_b ^= TRUE;
}
}
}
if (nesrom_idx_loaded != -1 &&
((InputButton[0] & (JUTGamePad::L | JUTGamePad::R)) != (JUTGamePad::L | JUTGamePad::R) || ((InputButton[0] & JUTGamePad::START) == 0 && APPNMI_ZURUMODE_GET() == 0))) {
u32 frames = 1;
if (APPNMI_ZURUMODE_GET() && (InputButton[0] & JUTGamePad::L)) {
/* When zurumode 1 is enabled, L + C-Stick (Left/Right) adjusts emulation speed */
JUTGamePad* pad = (JUTGamePad*)&gamePad[0];
if (pad->mSubStick.mX > 0.1) {
hispeed_mode = 2.0f + (pad->mSubStick.mX - 0.1f) * 8.9f;
slow_mode = 0;
speed_show = 60;
}
else if (pad->mSubStick.mX < -0.1) {
slow_mode = 2.0f + (-pad->mSubStick.mX - 0.1f) * 3.4f;
hispeed_mode = 0;
speed_show = 60;
}
else {
slow_mode = 0;
hispeed_mode = 0;
speed_show = 0;
}
}
if (nines_over_mode) {
flags |= KS_NES_FLAG_NINES_OVER_MODE;
}
if (slow_mode != 0) {
frames = 1;
if (speed_show != 0) {
JUTReport(32, 48, 1, "1/%d SPEED", slow_mode);
}
}
if (hispeed_mode != 0) {
frames = hispeed_mode;
if (speed_show != 0) {
JUTReport(32, 48, 1, "%2dX SPEED", hispeed_mode);
}
}
if (speed_show != 0) {
speed_show--;
}
famicomCommon.wp->_0014 = 0;
famicomCommon.wp->frames = frames;
do {
if (slow_mode != 0) {
slow_mode_sub++;
if (slow_mode_sub < slow_mode) {
for (int j = 0; j < 262; j++) {
Sound_Write(0, 0, j * 114);
}
break;
}
slow_mode_sub = 0;
}
ksnes_thread_exec(flags);
} while (--frames > 0);
}
else {
for (int j = 0; j < 262; j++) {
Sound_Write(0, 0, j * 114);
}
}
GXSetScissor(0, 0, 640, 480);
GXSetViewport(0.0f, 0.0f, 640.0f, 480.0f, 0.0f, 1.0f);
ksNesDraw(famicomCommon.wp, famicomCommon.sp);
if (filer_mode != 0 || filer_demo_mode != FILER_DEMO_MODE_NORMAL) {
JW_SetLowResoMode(FALSE);
process_filer_mode();
}
else {
JW_SetLowResoMode(famicomCommon.low_res_mode);
}
JW_SetFamicomMode(TRUE);
famicom_draw();
/* If filer mode is enabled, controller 4 L press toggles process profiling bar */
if (filer_mode_enable && (((JUTGamePad*)gamePad)[3].mButtons.mTrigger & JUTGamePad::L)) {
JUTProcBar::getManager()->setVisible(JUTProcBar::getManager()->isVisible() ? FALSE : TRUE);
}
if (filer_demo_mode != FILER_DEMO_MODE_NORMAL) {
filer_demo_count++;
if (filer_demo_count > 1200) {
filer_demo_count = 0;
nesrom_idx = nesrom_idx < (nesrom_count - 1) ? (nesrom_idx + 1) : 0;
}
if ((InputTrigger[0] & (
JUTGamePad::MAINSTICK_LEFT |
JUTGamePad::MAINSTICK_RIGHT |
JUTGamePad::MAINSTICK_DOWN |
JUTGamePad::MAINSTICK_UP |
JUTGamePad::DPAD_LEFT |
JUTGamePad::DPAD_RIGHT |
JUTGamePad::DPAD_DOWN |
JUTGamePad::DPAD_UP |
JUTGamePad::L |
JUTGamePad::R |
JUTGamePad::A |
JUTGamePad::X |
JUTGamePad::Y
))) {
filer_demo_mode = FILER_DEMO_MODE_NORMAL;
filer_mode = 1;
}
}
if (filer_mode_enable != FALSE && (InputTrigger[0] & JUTGamePad::R)) {
filer_mode ^= 1;
filer_demo_mode = FILER_DEMO_MODE_NORMAL;
}
if ((flags & 0x400) != 0 && famicomCommon.sp->motor_timer != 0 && (famicomCommon.sp->vblank_frame_counter & 0x10)) {
// Please wait
JUTReport(400, 350, 1, "omati kudasai...");
}
if (famicomCommon.internal_save_datap != nullptr) {
nesinfo_update_highscore(famicomCommon.internal_save_datap, 0);
}
}
extern int famicom_rom_load_check() {
if (famicomCommon.nesrom_memcard == FALSE) {
while (*nesrom_filename_ptrs[nesrom_idx] == ':') {
nesrom_idx++;
if (nesrom_idx >= nesrom_count) {
if (nesrom_idx_loaded < 0) {
OSPanic(__FILE__, 6072, "no nesfile specified");
continue;
}
nesrom_idx = 0;
}
}
}
if (nesrom_idx_loaded != nesrom_idx) {
if (famicom_rom_load() == 0) {
nesrom_idx_loaded = nesrom_idx;
}
else {
nesrom_idx_loaded = -1;
}
}
return nesrom_idx_loaded;
}
static int famicom_cardResultToMiyake(s32 cardResult) {
switch (cardResult) {
case CARD_RESULT_READY:
return FAMICOM_RESULT_OK;
case CARD_RESULT_INSSPACE:
return FAMICOM_RESULT_NOSPACE;
case CARD_RESULT_NOENT:
return FAMICOM_RESULT_NOENTRY;
case CARD_RESULT_BROKEN:
return FAMICOM_RESULT_BROKEN;
case CARD_RESULT_WRONGDEVICE:
return FAMICOM_RESULT_WRONGDEVICE;
case CARD_RESULT_ENCODING:
return FAMICOM_RESULT_WRONGENCODING;
case CARD_RESULT_NOCARD:
return FAMICOM_RESULT_NOCARD;
case CARD_RESULT_NOFILE:
return FAMICOM_RESULT_NOFILE;
default:
return -1;
}
}
extern int famicom_internal_data_load() {
famicom_mount_archive_wait();
bzero(&famicomCommon, sizeof(FamicomCommon));
famicomCommon.nesrom_memcard = FALSE;
strncpy((char*)famicomCommon.mura_save_name, "DobutsunomoriP_MURA", 32);
strncpy((char*)famicomCommon.famicom_save_name, "DobutsunomoriP_F_SAVE", 32);
famicomCommon.save_data_name = (u8*)"GAFEFSC\x1A";
famicomCommon.save_data_single_size = 0x660;
famicomCommon.save_data_total_size = sizeof(FamicomSaveDataHeader) + 0x660 * PLAYER_NUM;
famicomCommon.save_data_header = (FamicomSaveDataHeader*)famicomCommonSave;
famicomCommon.internal_save_datap = nullptr;
s32 cardResult = famicom_common_save_initial();
s32 chan;
if (cardResult == CARD_RESULT_NOFILE) {
chan = famicom_getSaveChan(famicomCommon.nesrom_memcard, &cardResult);
if (chan >= 0) {
s32 freeBytes;
s32 freeEntries;
cardResult = memcard_getFreeBlocks(chan, &freeBytes, &freeEntries);
if (cardResult == CARD_RESULT_READY) {
if (freeEntries < 1) {
cardResult = CARD_RESULT_NOENT;
}
else if (freeBytes < 0x2000) {
cardResult = CARD_RESULT_INSSPACE;
}
}
}
}
/* @BUG - chan can be an invalid value due to being allocted on the stack & cardResult not being CARD_RESULT_NOFILE */
if (cardResult != CARD_RESULT_READY && cardResult != CARD_RESULT_FATAL_ERROR && chan >= 0) {
errorChan = chan;
}
return famicom_cardResultToMiyake(cardResult);
}
extern int famicom_internal_data_save() {
s32 result;
s32 chan;
famicom_mount_archive_wait();
bzero(&famicomCommon, sizeof(FamicomCommon));
famicomCommon.nesrom_memcard = FALSE;
strncpy((char*)famicomCommon.mura_save_name, "DobutsunomoriP_MURA", 32);
strncpy((char*)famicomCommon.famicom_save_name, "DobutsunomoriP_F_SAVE", 32);
famicomCommon.save_data_name = (u8*)"GAFEFSC\x1A";
famicomCommon.save_data_single_size = 0x660;
famicomCommon.save_data_total_size = sizeof(FamicomSaveDataHeader) + 0x660 * PLAYER_NUM;
famicomCommon.save_data_header = (FamicomSaveDataHeader*)famicomCommonSave;
famicomCommon.internal_save_datap = nullptr;
chan = famicom_getSaveChan(FALSE, &result);
if (chan >= 0) {
famicomCommon.unused_save_data_start_ofs = 0x640;
famicomCommon.memcard_save_comment = commentImageBuffer;
famicomCommon.memcard_game_header.flags0.no_copy_flag = FALSE;
famicomCommon.memcard_game_header.flags1.no_move_flag = FALSE;
SetupInternalCommentImage(commentImageBuffer);
if (famicom_save_data_check(famicomCommon.save_data_header, famicomCommon.save_pl_no, famicomCommon.save_data_name) == 0) {
int no_save = famicomCommon.save_data_header->no_save;
famicomCommon.save_data_header->no_save = FALSE;
famicom_save_data_setup(famicomCommon.save_data_header, famicomCommon.save_data_single_size, famicomCommon.save_data_name);
memcard_data_load(chan, (char*)famicomCommon.famicom_save_name, (char*)famicomCommon.mura_save_name, nullptr, famicomCommon.save_data_total_size, famicomCommon.save_data_name);
result = memcard_data_save(
chan,
(char*)famicomCommon.famicom_save_name,
(char*)famicomCommon.mura_save_name,
famicomCommon.save_data_header,
famicomCommon.save_data_total_size,
famicomCommon.memcard_save_comment,
famicomCommon.memcard_game_header.flags1.banner_fmt,
famicomCommon.memcard_game_header.flags0.comment_type == MEMCARD_COMMENT_TYPE_NONE ? 0xFFFFFFFF : CARD_COMMENT_SIZE,
famicomCommon.memcard_game_header.icon_format,
famicomCommon.memcard_game_header.icon_flags,
famicomCommon.memcard_game_header.flags0.comment_type == MEMCARD_COMMENT_TYPE_NONE ? 0xFFFFFFFF : 0,
no_save
);
}
}
if (result != CARD_RESULT_READY && result != CARD_RESULT_FATAL_ERROR && chan >= 0) {
errorChan = chan;
}
return famicom_cardResultToMiyake(result);
}
extern int famicom_external_data_save() {
s32 result;
s32 chan;
famicom_mount_archive_wait();
if (famicomCommon.nesrom_memcard == FALSE) {
result = CARD_RESULT_FATAL_ERROR;
}
else if (famicomCommon.save_data_header == nullptr) {
result = CARD_RESULT_NOCARD;
chan = 2;
}
else {
chan = famicom_getSaveChan(TRUE, &result);
if (chan >= 0) {
if (famicom_save_data_check((FamicomSaveDataHeader*)famicomCommon.save_data_header, -1, famicomCommon.save_data_name) == 0) {
if (famicomCommon.nesrom_memcard && famicomCommon.memcard_game_header.flags0.has_comment_img) {
int no_save = ((FamicomSaveDataHeader*)famicomCommon.save_data_header)->no_save;
((FamicomSaveDataHeader*)famicomCommon.save_data_header)->no_save = FALSE;
famicom_save_data_setup((FamicomSaveDataHeader*)famicomCommon.save_data_header, famicomCommon.save_data_single_size, famicomCommon.save_data_name);
memcard_data_load(chan, (char*)famicomCommon.famicom_save_name, (char*)famicomCommon.mura_save_name, nullptr, famicomCommon.save_data_total_size, famicomCommon.save_data_name);
result = memcard_data_save(
chan,
(char*)famicomCommon.famicom_save_name,
(char*)famicomCommon.mura_save_name,
(FamicomSaveDataHeader*)famicomCommon.save_data_header,
famicomCommon.save_data_total_size,
famicomCommon.memcard_save_comment,
famicomCommon.memcard_game_header.flags1.banner_fmt,
famicomCommon.memcard_game_header.flags0.comment_type == MEMCARD_COMMENT_TYPE_NONE ? 0xFFFFFFFF : CARD_COMMENT_SIZE,
famicomCommon.memcard_game_header.icon_format,
famicomCommon.memcard_game_header.icon_flags,
famicomCommon.memcard_game_header.flags0.comment_type == MEMCARD_COMMENT_TYPE_NONE ? 0xFFFFFFFF : 0,
no_save
);
}
else {
result = CARD_RESULT_READY;
}
}
else {
result = CARD_RESULT_FATAL_ERROR;
}
}
}
if (result != CARD_RESULT_READY && result != CARD_RESULT_FATAL_ERROR && chan >= 0) {
errorChan = chan;
}
return famicom_cardResultToMiyake(result);
}
extern int famicom_external_data_save_check() {
return FALSE;
}
static s32 famicom_common_save_initial() {
s32 result;
s32 chan = famicom_getSaveChan(famicomCommon.nesrom_memcard, &result);
if (chan >= 0) {
// Loading Famicom save data.
OSReport("ファミコンセーブデータを読み込みます\n");
result = memcard_data_load(chan, (char*)famicomCommon.famicom_save_name, (char*)famicomCommon.mura_save_name, (FamicomSaveDataHeader*)famicomCommon.save_data_header, famicomCommon.save_data_total_size, famicomCommon.save_data_name);
if (famicom_save_data_check((FamicomSaveDataHeader*)famicomCommon.save_data_header, -1, famicomCommon.save_data_name) != 0) {
famicom_save_data_init((FamicomSaveDataHeader*)famicomCommon.save_data_header, famicomCommon.save_data_single_size, famicomCommon.save_data_name);
}
}
return result;
}
extern int famicom_get_disksystem_titles(int* n_games, char* title_name_bufp, int namebuf_size) {
famicom_mount_archive_wait();
return memcard_game_list(n_games, title_name_bufp, namebuf_size) == CARD_RESULT_READY;
}