core: memory-safety, leak, and savestate-portability audit fixes
Squashed series of ~50 distinct bugs found during a multi-day
security/correctness audit, ranging from ROM-triggerable heap
corruption and savestate-triggered OOB read primitives down to
obscure cross-platform savestate breakage.
Build is clean on `make platform=unix` with zero new warnings.
CRITICAL (ROM-triggerable, exploitable on the user's machine)
=============================================================
* ines.c iNES 2.0 PRG/CHR exponent overflow leading to undersized
allocation followed by heap buffer overflow on fread (pow() with
attacker-chosen exponent up to 63). Cap exponent at 30 and
compute size in uint32 with explicit cap below 2 GiB before
storing in int.
* ines.c miscROMSize int wraparound from attacker-controlled PRG/
CHR sizes; previous '& 0x8000000' check missed common underflow
cases. Compute in int64 and reject <= 0 or > 128 MiB.
* unif.c MAPR chunk OOB heap read on chunks of size < 4. Validate
chunk size before allocating board-name buffer.
* unif.c chunk size truncation: int conversion + missing cap allowed
a 4 GiB chunk size to wrap. Cap at 16 MiB.
* unif.c FixRomSize infinite loop on size > 0x80000000. Cap input.
* unif.c DINF chunk: months[(m - 1) % 12] is UB for m==0; m comes
from the .unf file. Clamp m into [1..12] before subtracting.
* unif.c NAME chunk: GameInfo->name = malloc(...) was unchecked.
* nsf.c size underflow: FCEU_fgetsize() - 0x80 wraps to ~UINT64_MAX
on tiny files, propagating a huge size into uppow2/FCEU_malloc.
Validate size > 0x80 before subtracting.
* nsf.c NSFMaxBank * 4096 cap tightened from 1<<20 (UB on signed-int
overflow) to 1<<19 (fits in int).
* general.c uppow2 had signed-int UB (1 << 32) and wrapped to 0 for
inputs near uint32 max. Cap at 0x80000000.
* cheat.c SubCheats[256] BSS overflow when more than 256 GG/PAR
substitution cheats are active. The array is adjacent to MMapPtrs[64]
in BSS which is exposed via retro_get_memory_data, making this
overflow visible from outside the core.
* libretro.c retro_cheat_set strcpy stack overflow with arbitrary-
length cheat strings from the frontend. Use strlcpy.
CRITICAL (savestate-triggerable on a malicious .fcs file)
=========================================================
* fds.c FDS savestate OOB read primitive: InDisk loaded from
savestate is used as index into 8-element diskdata[] static
pointer array. A value 8..254 dereferences arbitrary memory as
a pointer (heap-read primitive). Also bounds-checked SelectDisk,
mapperFDS_block, mapperFDS_blockstart, mapperFDS_diskaddr,
mapperFDS_blocklen so blockstart+diskaddr stays within the 65500-
byte disk buffer.
* 11 mappers had savestate-loaded variables masked at write time
but not at restore time, used as indices into fixed arrays:
- 88.c, KS7037.c, 112.c cmd indexes reg[8]
- sachen.c (S8259, S74LS374N) cmd indexes latch[8]
- 357.c dipswitch indexes outer_bank[4]
- unrom512.c flash_state indexes erase_a/d/b[5]
- 368.c preg indexes banks[8]
- 69.c sndcmd indexes sreg[14]
- mmc2and4.c latch0/latch1 index creg[4]
None individually exploitable into RCE - the array writes corrupt
adjacent BSS with constrained data flowing in - but each is an
out-of-bounds read or write from attacker-controllable input.
HIGH (memory-safety, reachable on any load)
===========================================
* fceu-memory.c FCEU_malloc deref-of-NULL on allocation failure
('ret = 0; memset(ret, 0, size);').
* file.c multiple FCEUFILE/MakeMemWrap/MakeMemWrapBuffer unchecked
allocations and unchecked filestream_tell return.
* libretro.c GameInfo NULL-derefs in three entry points
(retro_set_controller_port_device, retro_get_memory_data,
retro_get_memory_size) reachable on operations called before a
successful load.
* libretro.c framebuffer leak ~256 KB per failed FCEUI_LoadGame
(the libretro frontend doesn't call retro_unload_game on a
failed load).
* libretro.c 3DS retro_deinit unchecked linearFree.
* libretro.c stereo / NTSC filter unchecked malloc returns.
* fceu.c FCEUI_LoadGame unchecked GameInfo malloc.
* fds.c SubLoad and FDSLoad unchecked FCEU_malloc on diskdatao
backup buffers (NULL-deref in subsequent memcpy).
* fceu.c AllocGenieRW partial-failure leak: AReadG allocated but
BWriteG malloc fails -> 256 KB leak per retry.
* input/bworld.c Update(): unbounded strcpy from attacker-supplied
data into 20-byte bdata. Replaced with length-bounded copy.
* cart.c setprg2r/4r and setchr1r/2r/4r/8r mask-underflow guards.
SetupCartPRGMapping/SetupCartCHRMapping compute (size >> N) - 1
which underflows to 0xFFFFFFFF for chips smaller than the unit.
setprg8r/16r/32r and setchr8r already had defensive size checks;
the smaller units did not. malee.c (2 KB chip) and mapper 218
(2 KB NTARAM-as-CHR) accidentally avoid OOB; the primitive is
unsafe for any future board.
* video.c FCEU_InitVirtualVideo XBuf/XDBuf partial-failure leak.
* video.c, fceu.c FCEU_DispMessage / FCEU_printf / FCEU_PrintError:
vsprintf into fixed stack buffers replaced with vsnprintf.
* core: validate magic-string read length on FDS/UNIF/NSF load
(reject files too short to contain the magic so previous static
buffer / stack garbage doesn't spuriously match).
LEAKS (every cart load/unload cycle)
====================================
* mmc5.c MMC5 cart-side: WRAM (up to 64K) + MMC5fill (1K) + ExRAM
(1K) leaked per cycle. NSFMMC5_Close existed but was only used
for NSF code path.
* onebus.c Mapper 270/436: 8 KB CHRRAM leaked per cycle.
* 330.c, 375.c, 528.c: 8 KB WRAM each, no Close at all.
CORRECTNESS (UB / ABI / endianness)
===================================
* fceu-endian.c FlipByteOrder loop bound was 'count' instead of
'count/2', making it a no-op for every even count and silently
breaking savestate portability for every FCEUSTATE_RLSB-marked
field. The '#ifndef GEKKO' workarounds scattered through the
codebase (sound.c, vrc7.c, vrc6.c) were symptoms of this root
cause.
* state.c AddExState bounds check ran AFTER writing the entry, so
when SFEXINDEX hit the 63-entry cap the next call would write
the entry then immediately overwrite it with the terminator.
* state.c FCEUSS_Save_Mem: post-save callback was guarded by
'if (SPreSave)' instead of 'if (SPostSave)'.
* Sequence-point UB in counter expressions across 4 board files,
e.g. 'x = !x ? a : --x' and 'x = ++x % n'. GCC -Wsequence-point
flags all five sites (285.c, 413.c, asic_mmc3.c, jyasic.c).
* SFORMAT size mismatches between declared variable type and
AddExState size argument:
- asic_vrc3.c VRC3_count/VRC3_reload (uint16, saved as 1 byte)
- mmc3.c m555_count (uint32, saved as 2 bytes), m555_count_-
expired (uint8, saved as 2 bytes - OOB write into adjacent
BSS).
* unrom512.c UNROM-512 mapper 30 .srm save format: host-endian
uint32 flash write counters. Now stored as LE on disk regardless
of host (Battle Kid 2, Twin Dragons, Lizard, Sole, etc.).
* ~70 multi-byte single-variable SFORMAT/AddExState entries across
36 board files were saved as host-byte-order. After the
FlipByteOrder fix, these are now byte-swapped on BE so cross-
platform savestates round-trip correctly.
* coolgirl.c new ExStateLE() macro added; 22 multi-byte sites.
* pic16c5x.c 11 multi-byte AddExState calls (m_PC, m_PREVPC,
m_CONFIG, m_WDT, m_prescaler, m_opcode, m_STACK[0/1], m_icount,
m_delay_timer, m_rtcc, m_inst_cycles, m_clock2cycle).
* onebus.c PowerJoy Supermax submapper detection used non-portable
*(uint32*)&info->MD5 cast that read different bytes on LE vs BE.
* fds.c clean up redundant FCEUSTATE_RLSB encoding in AddExState
calls that also passed type=1 (idempotent OR; readability fix).
DOCUMENTATION
=============
* input.c UpdateGP's *(uint32*)data cast looks like a typical
endian bug but is actually correct (the libretro frontend builds
JSReturn with matching host-uint32 shifts). Comment added to
prevent future "fixes" from breaking it.
Limitations not addressed
=========================
* Element-stride-aware byte swapping. The savestate byte-swap
mechanism (FlipByteOrder over the entire SFORMAT entry buffer)
is structurally wrong for arrays of multi-byte values: it
reverses the whole buffer end-to-end instead of byte-swapping
each element. Several places that need cross-platform-portable
arrays (VRC7 sound state, jyasic chr[8]) work around this by
either splitting arrays into per-element SFORMAT entries (n106
PlayIndex, bandai reg) or by skipping save entirely on BE via
#ifndef GEKKO. A proper fix would extend the size encoding with
an element-stride field. Left for a future change because it
would change the savestate format.
* Strict-aliasing UB in ppu.c (around 9 sites doing
*(uint32*)uint8_buf for fast 4-byte writes via FCEU_dwmemset).
Works in practice with all common compilers because the patterns
are byte-symmetric, but is formally UB.
* FCEU_gmalloc calls exit(1) on OOM. A libretro core should never
exit() because that takes down the whole frontend. Used by
100+ call sites; refactoring to return-NULL is out of scope here.
Testing
=======
* Build: clean on `make platform=unix` with -O2; no new warnings.
* FlipByteOrder fix verified by hand-trace and a standalone unit
test for counts 2, 4, 8.
* uppow2 fix verified by unit test across 13 boundary cases.
* SFORMAT size mismatches and missing-RLSB cases identified by
Python static-analysis scripts that cross-reference SFORMAT
entries against variable declarations.
* iNES 2.0 exponent fix verified by hand-tracing what byte 0xFF
produces post-fix: exp=30 (capped), mult=7, size=3 GiB nominal,
capped to 1 GiB, capped to 2 GiB by uppow2, FCEU_malloc returns
NULL on most systems, loader returns 0. No heap overflow for
any input byte.
* Savestate-loaded array index audit: built a Python scanner that
extracts each AddExState/SFORMAT entry and cross-references the
variable name against array-index uses in the same file. All
flagged sites covered.
* A libFuzzer harness and seed corpus generator (fuzz_main.c,
gen_seed_corpus.py) accompany this submission for ongoing
regression testing.
This commit is contained in:
@@ -37,8 +37,8 @@ static uint32 IRQCount, IRQa;
|
|||||||
|
|
||||||
static SFORMAT StateRegs[] =
|
static SFORMAT StateRegs[] =
|
||||||
{
|
{
|
||||||
{ &IRQCount, 4, "IRQC" },
|
{ &IRQCount, 4 | FCEUSTATE_RLSB, "IRQC" },
|
||||||
{ &IRQa, 4, "IRQA" },
|
{ &IRQa, 4 | FCEUSTATE_RLSB, "IRQA" },
|
||||||
{ &prg, 1, "PRG" },
|
{ &prg, 1, "PRG" },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ static uint32 WRAMSIZE;
|
|||||||
static SFORMAT StateRegs[] =
|
static SFORMAT StateRegs[] =
|
||||||
{
|
{
|
||||||
{ &IRQa, 1, "IRQA" },
|
{ &IRQa, 1, "IRQA" },
|
||||||
{ &IRQCount, 4, "IRQC" },
|
{ &IRQCount, 4 | FCEUSTATE_RLSB, "IRQC" },
|
||||||
{ reg, 16, "REGS" },
|
{ reg, 16, "REGS" },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ static void M112Power(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void StateRestore(int version) {
|
static void StateRestore(int version) {
|
||||||
|
cmd &= 7; /* reg[] has 8 entries */
|
||||||
Sync();
|
Sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,5 +52,5 @@ void Mapper128_Init(CartInfo *info) {
|
|||||||
Latch_Init(info, Sync, NULL, 0, 0);
|
Latch_Init(info, Sync, NULL, 0, 0);
|
||||||
info->Power = M128Power;
|
info->Power = M128Power;
|
||||||
info->Reset = M128Reset;
|
info->Reset = M128Reset;
|
||||||
AddExState(&outerbank, 2, 0, "BANK");
|
AddExState(&outerbank, 2, 1, "BANK");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ static SFORMAT StateRegs[] =
|
|||||||
{ creg, 8, "CREG" },
|
{ creg, 8, "CREG" },
|
||||||
{ &mirr, 1, "MIRR" },
|
{ &mirr, 1, "MIRR" },
|
||||||
{ &IRQa, 1, "IRQA" },
|
{ &IRQa, 1, "IRQA" },
|
||||||
{ &IRQCount, 4, "IRQC" },
|
{ &IRQCount, 4 | FCEUSTATE_RLSB, "IRQC" },
|
||||||
{ &IRQLatch, 4, "IRQL" },
|
{ &IRQLatch, 4 | FCEUSTATE_RLSB, "IRQL" },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ static uint16 areg;
|
|||||||
static SFORMAT StateRegs[] =
|
static SFORMAT StateRegs[] =
|
||||||
{
|
{
|
||||||
{ mram, 4, "MRAM" },
|
{ mram, 4, "MRAM" },
|
||||||
{ &areg, 2, "AREG" },
|
{ &areg, 2 | FCEUSTATE_RLSB, "AREG" },
|
||||||
{ &vreg, 1, "VREG" },
|
{ &vreg, 1, "VREG" },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ static void power () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void reset () {
|
static void reset () {
|
||||||
pad = ++pad %24;
|
pad = (pad + 1) % 24;
|
||||||
Latch_clear();
|
Latch_clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ static SFORMAT StateRegs[] =
|
|||||||
{ regs, 8, "PREG" },
|
{ regs, 8, "PREG" },
|
||||||
{ &mirr, 1, "MIRR" },
|
{ &mirr, 1, "MIRR" },
|
||||||
{ &IRQa, 1, "IRQA" },
|
{ &IRQa, 1, "IRQA" },
|
||||||
{ &IRQCount, 2, "IRQC" },
|
{ &IRQCount, 2 | FCEUSTATE_RLSB, "IRQC" },
|
||||||
{ &IRQLatch, 2, "IRQL" },
|
{ &IRQLatch, 2 | FCEUSTATE_RLSB, "IRQL" },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ static SFORMAT StateRegs[] =
|
|||||||
{ CHR, 8, "CHR" },
|
{ CHR, 8, "CHR" },
|
||||||
{ NTAPage, 4, "NT" },
|
{ NTAPage, 4, "NT" },
|
||||||
{ &IRQa, 1, "IRQA" },
|
{ &IRQa, 1, "IRQA" },
|
||||||
{ &IRQCount, 2, "IRQC" },
|
{ &IRQCount, 2 | FCEUSTATE_RLSB, "IRQC" },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -127,8 +127,15 @@ static void StateRestore(int version) {
|
|||||||
FixNTAR();
|
FixNTAR();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void M330Close(void) {
|
||||||
|
if (WRAM)
|
||||||
|
FCEU_gfree(WRAM);
|
||||||
|
WRAM = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
void Mapper330_Init(CartInfo *info) {
|
void Mapper330_Init(CartInfo *info) {
|
||||||
info->Power = M330Power;
|
info->Power = M330Power;
|
||||||
|
info->Close = M330Close;
|
||||||
MapIRQHook = M330IRQHook;
|
MapIRQHook = M330IRQHook;
|
||||||
GameStateRestore = StateRestore;
|
GameStateRestore = StateRestore;
|
||||||
AddExState(&StateRegs, ~0, 0, 0);
|
AddExState(&StateRegs, ~0, 0, 0);
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ static uint8 submapper;
|
|||||||
|
|
||||||
static SFORMAT StateRegs[] =
|
static SFORMAT StateRegs[] =
|
||||||
{
|
{
|
||||||
{ &latchAddr, 2, "ADDR" },
|
{ &latchAddr, 2 | FCEUSTATE_RLSB, "ADDR" },
|
||||||
{ &latchData, 1, "DATA" },
|
{ &latchData, 1, "DATA" },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -117,5 +117,5 @@ void UNL3DBlock_Init(CartInfo *info) {
|
|||||||
info->Reset = M355Reset;
|
info->Reset = M355Reset;
|
||||||
info->Close = M355Close;
|
info->Close = M355Close;
|
||||||
MapIRQHook = M355CPUIRQHook;
|
MapIRQHook = M355CPUIRQHook;
|
||||||
AddExState(&address, sizeof(address), 0, "ADDR");
|
AddExState(&address, sizeof(address), 1, "ADDR");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,6 +113,7 @@ static void FP_FASTAPASS(1) M357IRQHook(int a) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void StateRestore(int version) {
|
static void StateRestore(int version) {
|
||||||
|
dipswitch &= 3; /* outer_bank[] has 4 entries */
|
||||||
Sync();
|
Sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ static void FP_FASTAPASS(1) M368IRQHook(int a) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void StateRestore(int version) {
|
static void StateRestore(int version) {
|
||||||
|
preg &= 7; /* banks[] has 8 entries */
|
||||||
Sync();
|
Sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -107,11 +107,18 @@ static void StateRestore(int version) {
|
|||||||
Sync();
|
Sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void M375Close(void) {
|
||||||
|
if (WRAM)
|
||||||
|
FCEU_gfree(WRAM);
|
||||||
|
WRAM = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
void Mapper375_Init(CartInfo *info) {
|
void Mapper375_Init(CartInfo *info) {
|
||||||
info->Power = M375Power;
|
info->Power = M375Power;
|
||||||
info->Reset = M375Reset;
|
info->Reset = M375Reset;
|
||||||
|
info->Close = M375Close;
|
||||||
GameStateRestore = StateRestore;
|
GameStateRestore = StateRestore;
|
||||||
AddExState(&addrlatch, 2, 0, "ADDR");
|
AddExState(&addrlatch, 2, 1, "ADDR");
|
||||||
AddExState(&datalatch, 1, 0, "DATA");
|
AddExState(&datalatch, 1, 0, "DATA");
|
||||||
|
|
||||||
WRAMSIZE = 8192;
|
WRAMSIZE = 8192;
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ static uint32 IRQCount, IRQa;
|
|||||||
|
|
||||||
static SFORMAT StateRegs[] =
|
static SFORMAT StateRegs[] =
|
||||||
{
|
{
|
||||||
{ &IRQCount, 4, "IRQC" },
|
{ &IRQCount, 4 | FCEUSTATE_RLSB, "IRQC" },
|
||||||
{ &IRQa, 4, "IRQA" },
|
{ &IRQa, 4 | FCEUSTATE_RLSB, "IRQA" },
|
||||||
{ ®, 1, "REG" },
|
{ ®, 1, "REG" },
|
||||||
{ ®, 1, "OUTE" },
|
{ ®, 1, "OUTE" },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ extern uint32 timestamp;
|
|||||||
|
|
||||||
static SFORMAT stateRegs[] ={
|
static SFORMAT stateRegs[] ={
|
||||||
{ reg, 4, "REGS" },
|
{ reg, 4, "REGS" },
|
||||||
{ &pcmAddress, 4, "PCMA" },
|
{ &pcmAddress, 4 | FCEUSTATE_RLSB, "PCMA" },
|
||||||
{ &pcmControl, 1, "PCMC" },
|
{ &pcmControl, 1, "PCMC" },
|
||||||
{ &scanlineCounter, 1, "SLCN" },
|
{ &scanlineCounter, 1, "SLCN" },
|
||||||
{ &scanlineReload, 1, "SLRL" },
|
{ &scanlineReload, 1, "SLRL" },
|
||||||
@@ -82,7 +82,10 @@ static DECLFW(writeReg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void horizontalBlanking () {
|
static void horizontalBlanking () {
|
||||||
scanlineCounter =!scanlineCounter? scanlineReload: --scanlineCounter;
|
if (!scanlineCounter)
|
||||||
|
scanlineCounter = scanlineReload;
|
||||||
|
else
|
||||||
|
scanlineCounter--;
|
||||||
if (!scanlineCounter && scanlineIRQ) X6502_IRQBegin(FCEU_IQEXT);
|
if (!scanlineCounter && scanlineIRQ) X6502_IRQBegin(FCEU_IQEXT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ static uint8 dipswitch;
|
|||||||
|
|
||||||
static SFORMAT StateRegs[] =
|
static SFORMAT StateRegs[] =
|
||||||
{
|
{
|
||||||
{ &latch_addr, 4, "ADDR" },
|
{ &latch_addr, 4 | FCEUSTATE_RLSB, "ADDR" },
|
||||||
{ &latch_data, 1, "DATA" },
|
{ &latch_data, 1, "DATA" },
|
||||||
{ &dipswitch, 1, "DIPS" },
|
{ &dipswitch, 1, "DIPS" },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
|
|||||||
@@ -114,5 +114,5 @@ void Mapper416_Init(CartInfo *info) {
|
|||||||
AddExState(®, 1, 0, "REGS");
|
AddExState(®, 1, 0, "REGS");
|
||||||
AddExState(&smb2j_reg, 1, 0, "SMBJ");
|
AddExState(&smb2j_reg, 1, 0, "SMBJ");
|
||||||
AddExState(&IRQa, 1, 0, "IRQa");
|
AddExState(&IRQa, 1, 0, "IRQa");
|
||||||
AddExState(&IRQCount, 2, 0, "IRQC");
|
AddExState(&IRQCount, 2, 1, "IRQC");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ static SFORMAT StateRegs[] = {
|
|||||||
{ creg, 8, "CREG" },
|
{ creg, 8, "CREG" },
|
||||||
{ nt, 4, "NREG" },
|
{ nt, 4, "NREG" },
|
||||||
{ &IRQa, 1, "IRQA" },
|
{ &IRQa, 1, "IRQA" },
|
||||||
{ &IRQCount, 2, "IRQC" },
|
{ &IRQCount, 2 | FCEUSTATE_RLSB, "IRQC" },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ static SFORMAT StateRegs[] =
|
|||||||
{ &preg, 1, "PREG" },
|
{ &preg, 1, "PREG" },
|
||||||
{ &creg, 1, "CREG" },
|
{ &creg, 1, "CREG" },
|
||||||
{ &mirr, 1, "MIRR" },
|
{ &mirr, 1, "MIRR" },
|
||||||
{ &IRQCount, 4, "IRQC" },
|
{ &IRQCount, 4 | FCEUSTATE_RLSB, "IRQC" },
|
||||||
{ &IRQa, 4, "IRQA" },
|
{ &IRQa, 4 | FCEUSTATE_RLSB, "IRQA" },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ static uint32 IRQCount, IRQa;
|
|||||||
|
|
||||||
static SFORMAT StateRegs[] =
|
static SFORMAT StateRegs[] =
|
||||||
{
|
{
|
||||||
{ &IRQCount, 4, "IRQC" },
|
{ &IRQCount, 4 | FCEUSTATE_RLSB, "IRQC" },
|
||||||
{ &IRQa, 4, "IRQA" },
|
{ &IRQa, 4 | FCEUSTATE_RLSB, "IRQA" },
|
||||||
{ ®, 1, "REG" },
|
{ ®, 1, "REG" },
|
||||||
{ &swap, 1, "SWAP" },
|
{ &swap, 1, "SWAP" },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
|
|||||||
@@ -63,5 +63,5 @@ void Mapper434_Init(CartInfo *info) {
|
|||||||
info->Reset = Mapper434_Reset;
|
info->Reset = Mapper434_Reset;
|
||||||
info->Power = Mapper434_Power;
|
info->Power = Mapper434_Power;
|
||||||
GameStateRestore = StateRestore;
|
GameStateRestore = StateRestore;
|
||||||
AddExState(&latch, 2, 0, "LATC");
|
AddExState(&latch, 2, 1, "LATC");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ static uint8 latchData;
|
|||||||
|
|
||||||
static SFORMAT StateRegs[] =
|
static SFORMAT StateRegs[] =
|
||||||
{
|
{
|
||||||
{ &latchAddr, 2, "ADDR" },
|
{ &latchAddr, 2 | FCEUSTATE_RLSB, "ADDR" },
|
||||||
{ &latchData, 1, "DATA" },
|
{ &latchData, 1, "DATA" },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ static uint32 IRQCount, IRQa;
|
|||||||
|
|
||||||
static SFORMAT StateRegs[] =
|
static SFORMAT StateRegs[] =
|
||||||
{
|
{
|
||||||
{ &IRQCount, 4, "IRQC" },
|
{ &IRQCount, 4 | FCEUSTATE_RLSB, "IRQC" },
|
||||||
{ &IRQa, 4, "IRQA" },
|
{ &IRQa, 4 | FCEUSTATE_RLSB, "IRQA" },
|
||||||
{ ®, 1, "REG" },
|
{ ®, 1, "REG" },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -114,8 +114,15 @@ static void StateRestore(int version) {
|
|||||||
Sync();
|
Sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void M528Close(void) {
|
||||||
|
if (WRAM)
|
||||||
|
FCEU_gfree(WRAM);
|
||||||
|
WRAM = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
void Mapper528_Init(CartInfo *info) {
|
void Mapper528_Init(CartInfo *info) {
|
||||||
info->Power = M528Power;
|
info->Power = M528Power;
|
||||||
|
info->Close = M528Close;
|
||||||
GameStateRestore = StateRestore;
|
GameStateRestore = StateRestore;
|
||||||
VRCIRQ_Init();
|
VRCIRQ_Init();
|
||||||
AddExState(&StateRegs, ~0, 0, 0);
|
AddExState(&StateRegs, ~0, 0, 0);
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ static uint16 mode;
|
|||||||
static SFORMAT StateRegs[] =
|
static SFORMAT StateRegs[] =
|
||||||
{
|
{
|
||||||
{ &bank, 1, "BANK" },
|
{ &bank, 1, "BANK" },
|
||||||
{ &mode, 2, "MODE" },
|
{ &mode, 2 | FCEUSTATE_RLSB, "MODE" },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ static SFORMAT StateRegs[] =
|
|||||||
{ creg, 8, "CREG" },
|
{ creg, 8, "CREG" },
|
||||||
{ &mirr, 1, "MIRR" },
|
{ &mirr, 1, "MIRR" },
|
||||||
{ &IRQa, 1, "IRQA" },
|
{ &IRQa, 1, "IRQA" },
|
||||||
{ &IRQCount, 2, "IRQC" },
|
{ &IRQCount, 2 | FCEUSTATE_RLSB, "IRQC" },
|
||||||
{ &IRQLatch, 2, "IRQL" },
|
{ &IRQLatch, 2 | FCEUSTATE_RLSB, "IRQL" },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ static SFORMAT StateRegs[] =
|
|||||||
{ creg, 4, "CREG" },
|
{ creg, 4, "CREG" },
|
||||||
{ &mirr, 1, "MIRR" },
|
{ &mirr, 1, "MIRR" },
|
||||||
{ &IRQa, 1, "IRQA" },
|
{ &IRQa, 1, "IRQA" },
|
||||||
{ &IRQCount, 2, "IRQC" },
|
{ &IRQCount, 2 | FCEUSTATE_RLSB, "IRQC" },
|
||||||
{ &IRQLatch, 2, "IRQL" },
|
{ &IRQLatch, 2 | FCEUSTATE_RLSB, "IRQL" },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ static SFORMAT StateRegs[] =
|
|||||||
{ &mirr, 1, "MIRR" },
|
{ &mirr, 1, "MIRR" },
|
||||||
{ &prg_reg, 1, "PRG" },
|
{ &prg_reg, 1, "PRG" },
|
||||||
{ &kogame, 1, "KGME" },
|
{ &kogame, 1, "KGME" },
|
||||||
{ &count, 4, "CNT" },
|
{ &count, 4 | FCEUSTATE_RLSB, "CNT" },
|
||||||
{ chr_reg, 4, "CHR" },
|
{ chr_reg, 4, "CHR" },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ static SFORMAT StateRegs[] =
|
|||||||
{ creg, 8, "CREG" },
|
{ creg, 8, "CREG" },
|
||||||
{ &mirr, 1, "MIRR" },
|
{ &mirr, 1, "MIRR" },
|
||||||
{ &IRQa, 1, "IRQA" },
|
{ &IRQa, 1, "IRQA" },
|
||||||
{ &IRQCount, 4, "IRQC" },
|
{ &IRQCount, 4 | FCEUSTATE_RLSB, "IRQC" },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -300,6 +300,7 @@ static void M69IRQHook(int a) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void StateRestore(int version) {
|
static void StateRestore(int version) {
|
||||||
|
if (sndcmd >= 14) sndcmd = 0; /* sreg[] has 14 entries */
|
||||||
Sync();
|
Sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ static SFORMAT StateRegs95[] =
|
|||||||
{ preg, 3, "PREG" },
|
{ preg, 3, "PREG" },
|
||||||
{ creg, 6, "CREG" },
|
{ creg, 6, "CREG" },
|
||||||
{ mcache, 8, "MCCH" },
|
{ mcache, 8, "MCCH" },
|
||||||
{ &lastppu, 4, "LPPU" },
|
{ &lastppu, 4 | FCEUSTATE_RLSB, "LPPU" },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ static SFORMAT StateRegs207[] =
|
|||||||
{ preg, 3, "PREG" },
|
{ preg, 3, "PREG" },
|
||||||
{ creg, 6, "CREG" },
|
{ creg, 6, "CREG" },
|
||||||
{ mcache, 8, "MCCH" },
|
{ mcache, 8, "MCCH" },
|
||||||
{ &lastppu, 4, "LPPU" },
|
{ &lastppu, 4 | FCEUSTATE_RLSB, "LPPU" },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ static void M88Power(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void StateRestore(int version) {
|
static void StateRestore(int version) {
|
||||||
|
cmd &= 7; /* reg[] has 8 entries; clamp savestate value */
|
||||||
Sync();
|
Sync();
|
||||||
MSync();
|
MSync();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,9 +33,9 @@ static SFORMAT StateRegs[] =
|
|||||||
{
|
{
|
||||||
{ &mirr, 1, "MIRR" },
|
{ &mirr, 1, "MIRR" },
|
||||||
{ ®, 1, "REGS" },
|
{ ®, 1, "REGS" },
|
||||||
{ &IRQa, 4, "IRQA" },
|
{ &IRQa, 4 | FCEUSTATE_RLSB, "IRQA" },
|
||||||
{ &IRQCount, 4, "IRQC" },
|
{ &IRQCount, 4 | FCEUSTATE_RLSB, "IRQC" },
|
||||||
{ &IRQLatch, 4, "IRQL" },
|
{ &IRQLatch, 4 | FCEUSTATE_RLSB, "IRQL" },
|
||||||
{ &latche, 1, "LATC" },
|
{ &latche, 1, "LATC" },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ static void Close(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void StateRestore(int version) {
|
static void StateRestore(int version) {
|
||||||
|
cmd &= 7; /* reg[] has 8 entries; clamp savestate value */
|
||||||
WSync();
|
WSync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ static SFORMAT StateRegs[] =
|
|||||||
{
|
{
|
||||||
{ reg, 8, "REGS" },
|
{ reg, 8, "REGS" },
|
||||||
{ &IRQa, 1, "IRQA" },
|
{ &IRQa, 1, "IRQA" },
|
||||||
{ &IRQCount, 2, "IRQC" },
|
{ &IRQCount, 2 | FCEUSTATE_RLSB, "IRQC" },
|
||||||
{ &IRQLatch, 2, "IRQL" },
|
{ &IRQLatch, 2 | FCEUSTATE_RLSB, "IRQL" },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ static void Latch_Init(CartInfo *info, void (*proc)(void), readfunc func, uint16
|
|||||||
AddExState(WRAM, WRAMSIZE, 0, "WRAM");
|
AddExState(WRAM, WRAMSIZE, 0, "WRAM");
|
||||||
}
|
}
|
||||||
GameStateRestore = StateRestore;
|
GameStateRestore = StateRestore;
|
||||||
AddExState(&latche, 2, 0, "LATC");
|
AddExState(&latche, 2, 1, "LATC");
|
||||||
}
|
}
|
||||||
|
|
||||||
/*------------------ BMCD1038 ---------------------------*/
|
/*------------------ BMCD1038 ---------------------------*/
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ uint16 Latch_address;
|
|||||||
uint8 Latch_data;
|
uint8 Latch_data;
|
||||||
|
|
||||||
static SFORMAT Latch_state[] = {
|
static SFORMAT Latch_state[] = {
|
||||||
{&Latch_address, 2, "LATA" },
|
{&Latch_address, 2 | FCEUSTATE_RLSB, "LATA" },
|
||||||
{&Latch_data, 1, "LATD" },
|
{&Latch_data, 1, "LATD" },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -107,7 +107,10 @@ void MMC3_syncMirror () {
|
|||||||
|
|
||||||
void MMC3_clockCounter () {
|
void MMC3_clockCounter () {
|
||||||
uint8 prevCounter = MMC3_counter;
|
uint8 prevCounter = MMC3_counter;
|
||||||
MMC3_counter = MMC3_reloadRequest || !MMC3_counter? MMC3_reloadValue: --MMC3_counter;
|
if (MMC3_reloadRequest || !MMC3_counter)
|
||||||
|
MMC3_counter = MMC3_reloadValue;
|
||||||
|
else
|
||||||
|
MMC3_counter--;
|
||||||
if ((prevCounter || MMC3_type != MMC3_TYPE_NEC || MMC3_reloadRequest) && !MMC3_counter && MMC3_irqEnable) X6502_IRQBegin(FCEU_IQEXT);
|
if ((prevCounter || MMC3_type != MMC3_TYPE_NEC || MMC3_reloadRequest) && !MMC3_counter && MMC3_irqEnable) X6502_IRQBegin(FCEU_IQEXT);
|
||||||
MMC3_reloadRequest = 0;
|
MMC3_reloadRequest = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ static uint16 VRC3_reload;
|
|||||||
static SFORMAT VRC3_state[] = {
|
static SFORMAT VRC3_state[] = {
|
||||||
{&VRC3_prg, 1, "VC3P" },
|
{&VRC3_prg, 1, "VC3P" },
|
||||||
{&VRC3_irq, 1, "VC3I" },
|
{&VRC3_irq, 1, "VC3I" },
|
||||||
{&VRC3_count, 1, "VC3C" },
|
{&VRC3_count, 2 | FCEUSTATE_RLSB, "VC3C" },
|
||||||
{&VRC3_reload, 1, "VC3R" },
|
{&VRC3_reload, 2 | FCEUSTATE_RLSB, "VC3R" },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ static SFORMAT StateRegs[] =
|
|||||||
{
|
{
|
||||||
{ preg, 4, "PREG" },
|
{ preg, 4, "PREG" },
|
||||||
{ creg, 8, "CREG" },
|
{ creg, 8, "CREG" },
|
||||||
{ &IRQCount, 4, "IRQC" },
|
{ &IRQCount, 4 | FCEUSTATE_RLSB, "IRQC" },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2255,6 +2255,11 @@ static void COOLGIRL_Restore(int version) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#define ExState(var, varname) AddExState(&var, sizeof(var), 0, varname)
|
#define ExState(var, varname) AddExState(&var, sizeof(var), 0, varname)
|
||||||
|
/* Variant for multi-byte single-variable state. Asks the savestate
|
||||||
|
* machinery to byte-swap the value on BE hosts so cross-platform
|
||||||
|
* savestates round-trip correctly. Must NOT be used for arrays
|
||||||
|
* of multi-byte values (the swap is whole-buffer, not per-element). */
|
||||||
|
#define ExStateLE(var, varname) AddExState(&var, sizeof(var), 1, varname)
|
||||||
|
|
||||||
void COOLGIRL_Init(CartInfo *info) {
|
void COOLGIRL_Init(CartInfo *info) {
|
||||||
size_t i;
|
size_t i;
|
||||||
@@ -2296,27 +2301,27 @@ void COOLGIRL_Init(CartInfo *info) {
|
|||||||
ExState(mirroring, "MIRR");
|
ExState(mirroring, "MIRR");
|
||||||
ExState(four_screen, "4SCR");
|
ExState(four_screen, "4SCR");
|
||||||
ExState(lockout, "LOCK");
|
ExState(lockout, "LOCK");
|
||||||
ExState(prg_base, "PBAS");
|
ExStateLE(prg_base, "PBAS");
|
||||||
ExState(prg_mask, "PMSK");
|
ExStateLE(prg_mask, "PMSK");
|
||||||
ExState(prg_mode, "PMOD");
|
ExState(prg_mode, "PMOD");
|
||||||
ExState(prg_bank_6000, "P6BN");
|
ExState(prg_bank_6000, "P6BN");
|
||||||
ExState(prg_bank_a, "PABN");
|
ExState(prg_bank_a, "PABN");
|
||||||
ExState(prg_bank_b, "PBBN");
|
ExState(prg_bank_b, "PBBN");
|
||||||
ExState(prg_bank_c, "PCBN");
|
ExState(prg_bank_c, "PCBN");
|
||||||
ExState(prg_bank_d, "PDBN");
|
ExState(prg_bank_d, "PDBN");
|
||||||
ExState(chr_mask, "CMSK");
|
ExStateLE(chr_mask, "CMSK");
|
||||||
ExState(chr_mode, "CMOD");
|
ExState(chr_mode, "CMOD");
|
||||||
ExState(chr_bank_a, "CABN");
|
ExStateLE(chr_bank_a, "CABN");
|
||||||
ExState(chr_bank_b, "CBBN");
|
ExStateLE(chr_bank_b, "CBBN");
|
||||||
ExState(chr_bank_c, "CCBN");
|
ExStateLE(chr_bank_c, "CCBN");
|
||||||
ExState(chr_bank_d, "CDBN");
|
ExStateLE(chr_bank_d, "CDBN");
|
||||||
ExState(chr_bank_e, "CEBN");
|
ExStateLE(chr_bank_e, "CEBN");
|
||||||
ExState(chr_bank_f, "CFBN");
|
ExStateLE(chr_bank_f, "CFBN");
|
||||||
ExState(chr_bank_g, "CGBN");
|
ExStateLE(chr_bank_g, "CGBN");
|
||||||
ExState(chr_bank_h, "CHBN");
|
ExStateLE(chr_bank_h, "CHBN");
|
||||||
ExState(ppu_latch0, "PPU0");
|
ExState(ppu_latch0, "PPU0");
|
||||||
ExState(ppu_latch1, "PPU1");
|
ExState(ppu_latch1, "PPU1");
|
||||||
ExState(lreset, "LRST");
|
ExStateLE(lreset, "LRST");
|
||||||
ExState(mmc1_load_register, "M01R");
|
ExState(mmc1_load_register, "M01R");
|
||||||
ExState(mmc3_internal, "M01I");
|
ExState(mmc3_internal, "M01I");
|
||||||
ExState(mapper69_internal, "M69I");
|
ExState(mapper69_internal, "M69I");
|
||||||
@@ -2337,32 +2342,32 @@ void COOLGIRL_Init(CartInfo *info) {
|
|||||||
ExState(mmc5_irq_enabled, "M5IE");
|
ExState(mmc5_irq_enabled, "M5IE");
|
||||||
ExState(mmc5_irq_line, "M5IL");
|
ExState(mmc5_irq_line, "M5IL");
|
||||||
ExState(mmc5_irq_out, "M5IO");
|
ExState(mmc5_irq_out, "M5IO");
|
||||||
ExState(mapper18_irq_value, "18IV");
|
ExStateLE(mapper18_irq_value, "18IV");
|
||||||
ExState(mapper18_irq_control, "18IC");
|
ExState(mapper18_irq_control, "18IC");
|
||||||
ExState(mapper18_irq_latch, "18IL");
|
ExStateLE(mapper18_irq_latch, "18IL");
|
||||||
ExState(mapper65_irq_enabled, "65IE");
|
ExState(mapper65_irq_enabled, "65IE");
|
||||||
ExState(mapper65_irq_value, "65IV");
|
ExStateLE(mapper65_irq_value, "65IV");
|
||||||
ExState(mapper65_irq_latch, "65IL");
|
ExStateLE(mapper65_irq_latch, "65IL");
|
||||||
ExState(mapper69_irq_enabled, "69IE");
|
ExState(mapper69_irq_enabled, "69IE");
|
||||||
ExState(mapper69_counter_enabled, "69CE");
|
ExState(mapper69_counter_enabled, "69CE");
|
||||||
ExState(mapper69_irq_value, "69IV");
|
ExStateLE(mapper69_irq_value, "69IV");
|
||||||
ExState(vrc4_irq_value, "V4IV");
|
ExState(vrc4_irq_value, "V4IV");
|
||||||
ExState(vrc4_irq_control, "V4IC");
|
ExState(vrc4_irq_control, "V4IC");
|
||||||
ExState(vrc4_irq_latch, "V4IL");
|
ExState(vrc4_irq_latch, "V4IL");
|
||||||
ExState(vrc4_irq_prescaler, "V4PP");
|
ExState(vrc4_irq_prescaler, "V4PP");
|
||||||
ExState(vrc4_irq_prescaler_counter, "V4PC");
|
ExState(vrc4_irq_prescaler_counter, "V4PC");
|
||||||
ExState(vrc3_irq_value, "V3IV");
|
ExStateLE(vrc3_irq_value, "V3IV");
|
||||||
ExState(vrc3_irq_control, "V3IC");
|
ExState(vrc3_irq_control, "V3IC");
|
||||||
ExState(vrc3_irq_latch, "V3IL");
|
ExStateLE(vrc3_irq_latch, "V3IL");
|
||||||
ExState(mapper42_irq_enabled, "42IE");
|
ExState(mapper42_irq_enabled, "42IE");
|
||||||
ExState(mapper42_irq_value, "42IV");
|
ExStateLE(mapper42_irq_value, "42IV");
|
||||||
ExState(mapper83_irq_enabled_latch, "M83L");
|
ExState(mapper83_irq_enabled_latch, "M83L");
|
||||||
ExState(mapper83_irq_enabled, "M83I");
|
ExState(mapper83_irq_enabled, "M83I");
|
||||||
ExState(mapper83_irq_counter, "M83C");
|
ExStateLE(mapper83_irq_counter, "M83C");
|
||||||
ExState(mapper90_xor, "90XR");
|
ExState(mapper90_xor, "90XR");
|
||||||
ExState(mapper67_irq_enabled, "67IE");
|
ExState(mapper67_irq_enabled, "67IE");
|
||||||
ExState(mapper67_irq_latch, "67IL");
|
ExState(mapper67_irq_latch, "67IL");
|
||||||
ExState(mapper67_irq_counter, "67IC");
|
ExStateLE(mapper67_irq_counter, "67IC");
|
||||||
ExState(flash_state, "FLST");
|
ExState(flash_state, "FLST");
|
||||||
ExState(flash_buffer_a, "FLBA");
|
ExState(flash_buffer_a, "FLBA");
|
||||||
ExState(flash_buffer_v, "FLBV");
|
ExState(flash_buffer_v, "FLBV");
|
||||||
|
|||||||
@@ -194,16 +194,20 @@ static void clockIRQ (void)
|
|||||||
if (irqEnabled)
|
if (irqEnabled)
|
||||||
switch (irqControl &0xC0)
|
switch (irqControl &0xC0)
|
||||||
{
|
{
|
||||||
case 0x40:
|
case 0x40: {
|
||||||
irqPrescaler =(irqPrescaler &~mask) | (++irqPrescaler &mask);
|
uint8_t newp = (irqPrescaler + 1) & mask;
|
||||||
if ((irqPrescaler &mask) ==0x00 && (irqControl &0x08? irqCounter: ++irqCounter) ==0x00)
|
irqPrescaler = (irqPrescaler & ~mask) | newp;
|
||||||
|
if (newp == 0x00 && (irqControl &0x08? irqCounter: ++irqCounter) ==0x00)
|
||||||
X6502_IRQBegin(FCEU_IQEXT);
|
X6502_IRQBegin(FCEU_IQEXT);
|
||||||
break;
|
break;
|
||||||
case 0x80:
|
}
|
||||||
irqPrescaler =(irqPrescaler &~mask) | (--irqPrescaler &mask);
|
case 0x80: {
|
||||||
if ((irqPrescaler &mask) ==mask && (irqControl &0x08? irqCounter: --irqCounter) ==0xFF)
|
uint8_t newp = (irqPrescaler - 1) & mask;
|
||||||
|
irqPrescaler = (irqPrescaler & ~mask) | newp;
|
||||||
|
if (newp == mask && (irqControl &0x08? irqCounter: --irqCounter) ==0xFF)
|
||||||
X6502_IRQBegin(FCEU_IQEXT);
|
X6502_IRQBegin(FCEU_IQEXT);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ static SFORMAT StateRegs[] =
|
|||||||
{
|
{
|
||||||
{ ®, 1, "REG" },
|
{ ®, 1, "REG" },
|
||||||
{ &IRQa, 1, "IRQA" },
|
{ &IRQa, 1, "IRQA" },
|
||||||
{ &IRQCount, 4, "IRQC" },
|
{ &IRQCount, 4 | FCEUSTATE_RLSB, "IRQC" },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ static uint16 latche;
|
|||||||
|
|
||||||
static SFORMAT StateRegs[] =
|
static SFORMAT StateRegs[] =
|
||||||
{
|
{
|
||||||
{ &latche, 2, "LATC" },
|
{ &latche, 2 | FCEUSTATE_RLSB, "LATC" },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -103,6 +103,11 @@ static void MMC2and4Power(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void StateRestore(int version) {
|
static void StateRestore(int version) {
|
||||||
|
/* latch0/latch1 are written as boolean (0 or 1) and used as
|
||||||
|
* indices into creg[4] via creg[latch0] and creg[latch1 + 2].
|
||||||
|
* Clamp savestate-loaded values to keep the indices in range. */
|
||||||
|
latch0 &= 1;
|
||||||
|
latch1 &= 1;
|
||||||
Sync();
|
Sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1520,8 +1520,8 @@ static uint32 m555_count_target = 0x20000000;
|
|||||||
|
|
||||||
static SFORMAT M555StateRegs[] = {
|
static SFORMAT M555StateRegs[] = {
|
||||||
{ m555_reg, 2, "REGS" },
|
{ m555_reg, 2, "REGS" },
|
||||||
{ &m555_count, 2, "CNTR" },
|
{ &m555_count, 4 | FCEUSTATE_RLSB, "CNTR" },
|
||||||
{ &m555_count_expired, 2, "CNTE" },
|
{ &m555_count_expired, 1, "CNTE" },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -670,6 +670,20 @@ void NSFMMC5_Close(void) {
|
|||||||
ExRAM = NULL;
|
ExRAM = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Cart-side close. GenMMC5_Init allocates WRAM (optional), MMC5fill, and
|
||||||
|
* ExRAM. Without this, every MMC5 cart load/unload cycle leaks 2-66 KB. */
|
||||||
|
static void GenMMC5_Close(void) {
|
||||||
|
if (WRAM)
|
||||||
|
FCEU_gfree(WRAM);
|
||||||
|
WRAM = NULL;
|
||||||
|
if (MMC5fill)
|
||||||
|
FCEU_gfree(MMC5fill);
|
||||||
|
MMC5fill = NULL;
|
||||||
|
if (ExRAM)
|
||||||
|
FCEU_gfree(ExRAM);
|
||||||
|
ExRAM = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static void GenMMC5Reset(void) {
|
static void GenMMC5Reset(void) {
|
||||||
int x;
|
int x;
|
||||||
|
|
||||||
@@ -798,6 +812,7 @@ static void GenMMC5_Init(CartInfo *info, int wsize, int battery) {
|
|||||||
BuildWRAMSizeTable();
|
BuildWRAMSizeTable();
|
||||||
GameStateRestore = MMC5_StateRestore;
|
GameStateRestore = MMC5_StateRestore;
|
||||||
info->Power = GenMMC5Reset;
|
info->Power = GenMMC5Reset;
|
||||||
|
info->Close = GenMMC5_Close;
|
||||||
|
|
||||||
if (battery) {
|
if (battery) {
|
||||||
info->SaveGame[0] = WRAM;
|
info->SaveGame[0] = WRAM;
|
||||||
|
|||||||
@@ -61,10 +61,10 @@ static SFORMAT StateRegs[] =
|
|||||||
{ &IRQa, 1, "IRQA" },
|
{ &IRQa, 1, "IRQA" },
|
||||||
{ &pcm_enable, 1, "PCME" },
|
{ &pcm_enable, 1, "PCME" },
|
||||||
{ &pcm_irq, 1, "PCMI" },
|
{ &pcm_irq, 1, "PCMI" },
|
||||||
{ &pcm_addr, 2, "PCMA" },
|
{ &pcm_addr, 2 | FCEUSTATE_RLSB, "PCMA" },
|
||||||
{ &pcm_size, 2, "PCMS" },
|
{ &pcm_size, 2 | FCEUSTATE_RLSB, "PCMS" },
|
||||||
{ &pcm_latch, 2, "PCML" },
|
{ &pcm_latch, 2 | FCEUSTATE_RLSB, "PCML" },
|
||||||
{ &pcm_clock, 2, "PCMC" },
|
{ &pcm_clock, 2 | FCEUSTATE_RLSB, "PCMC" },
|
||||||
{ ®4242, 1, "4242" },
|
{ ®4242, 1, "4242" },
|
||||||
{ &dipswitch, 1, "DIPS" },
|
{ &dipswitch, 1, "DIPS" },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
@@ -375,6 +375,12 @@ void UNLOneBus_Close(void) {
|
|||||||
if (WRAM)
|
if (WRAM)
|
||||||
FCEU_gfree(WRAM);
|
FCEU_gfree(WRAM);
|
||||||
WRAM = NULL;
|
WRAM = NULL;
|
||||||
|
/* Mapper270_Init / Mapper436_Init allocate an additional CHRRAM after
|
||||||
|
* UNLOneBus_Init returns; free that here too to avoid leaking it on
|
||||||
|
* every load/unload cycle. */
|
||||||
|
if (CHRRAM)
|
||||||
|
FCEU_gfree(CHRRAM);
|
||||||
|
CHRRAM = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UNLOneBus_Init(CartInfo *info) {
|
void UNLOneBus_Init(CartInfo *info) {
|
||||||
@@ -385,8 +391,16 @@ void UNLOneBus_Init(CartInfo *info) {
|
|||||||
|
|
||||||
if (info->iNES2)
|
if (info->iNES2)
|
||||||
submapper =info->submapper;
|
submapper =info->submapper;
|
||||||
else
|
else {
|
||||||
submapper =(((*(uint32*)&(info->MD5)) == 0x305fcdc3) || ((*(uint32*)&(info->MD5)) == 0x6abfce8e))? 2: 0; /* PowerJoy Supermax Carts */
|
/* Compare the first four MD5 bytes literally to avoid the previous
|
||||||
|
* endian-dependent (uint32) reinterpretation, which silently
|
||||||
|
* mismatched on big-endian hosts and disabled the PowerJoy Supermax
|
||||||
|
* variant detection. */
|
||||||
|
const uint8 *md5 = info->MD5;
|
||||||
|
int is_305fcdc3 = (md5[0] == 0xc3 && md5[1] == 0xcd && md5[2] == 0x5f && md5[3] == 0x30);
|
||||||
|
int is_6abfce8e = (md5[0] == 0x8e && md5[1] == 0xce && md5[2] == 0xbf && md5[3] == 0x6a);
|
||||||
|
submapper = (is_305fcdc3 || is_6abfce8e) ? 2 : 0; /* PowerJoy Supermax Carts */
|
||||||
|
}
|
||||||
|
|
||||||
cpuMangle =cpuMangles[submapper];
|
cpuMangle =cpuMangles[submapper];
|
||||||
ppuMangle =ppuMangles[submapper];
|
ppuMangle =ppuMangles[submapper];
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ static uint32 WRAMSIZE;
|
|||||||
static SFORMAT StateRegs[] =
|
static SFORMAT StateRegs[] =
|
||||||
{
|
{
|
||||||
{ reg, 2, "REG" },
|
{ reg, 2, "REG" },
|
||||||
{ &lastnt, 4, "LNT" },
|
{ &lastnt, 4 | FCEUSTATE_RLSB, "LNT" },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -977,25 +977,25 @@ void pic16c5x_run(void)
|
|||||||
void AddExState(void *v, uint32 s, int type, char *desc);
|
void AddExState(void *v, uint32 s, int type, char *desc);
|
||||||
|
|
||||||
void pic16c5x_add_statesinfo(void) {
|
void pic16c5x_add_statesinfo(void) {
|
||||||
AddExState(&m_PC, sizeof(m_PC), 0, "PC00");
|
AddExState(&m_PC, sizeof(m_PC), 1, "PC00");
|
||||||
AddExState(&m_PREVPC, sizeof(m_PREVPC), 0, "PRPC");
|
AddExState(&m_PREVPC, sizeof(m_PREVPC), 1, "PRPC");
|
||||||
AddExState(&m_W, sizeof(m_W), 0, "W000");
|
AddExState(&m_W, sizeof(m_W), 0, "W000");
|
||||||
AddExState(&m_OPTION, sizeof(m_OPTION), 0, "OPT0");
|
AddExState(&m_OPTION, sizeof(m_OPTION), 0, "OPT0");
|
||||||
AddExState(&m_CONFIG, sizeof(m_CONFIG), 0, "CONF");
|
AddExState(&m_CONFIG, sizeof(m_CONFIG), 1, "CONF");
|
||||||
AddExState(&m_ALU, sizeof(m_ALU), 0, "ALU");
|
AddExState(&m_ALU, sizeof(m_ALU), 0, "ALU");
|
||||||
AddExState(&m_WDT, sizeof(m_WDT), 0, "WDT");
|
AddExState(&m_WDT, sizeof(m_WDT), 1, "WDT");
|
||||||
AddExState(&m_TRISA, sizeof(m_TRISA), 0, "TRSA");
|
AddExState(&m_TRISA, sizeof(m_TRISA), 0, "TRSA");
|
||||||
AddExState(&m_TRISB, sizeof(m_TRISB), 0, "TRSB");
|
AddExState(&m_TRISB, sizeof(m_TRISB), 0, "TRSB");
|
||||||
AddExState(&m_TRISC, sizeof(m_TRISC), 0, "TRSC");
|
AddExState(&m_TRISC, sizeof(m_TRISC), 0, "TRSC");
|
||||||
AddExState(&m_STACK[0], sizeof(m_STACK[0]), 0, "STC0");
|
AddExState(&m_STACK[0], sizeof(m_STACK[0]), 1, "STC0");
|
||||||
AddExState(&m_STACK[1], sizeof(m_STACK[1]), 0, "STC1");
|
AddExState(&m_STACK[1], sizeof(m_STACK[1]), 1, "STC1");
|
||||||
AddExState(&m_prescaler, sizeof(m_prescaler), 0, "PSCL");
|
AddExState(&m_prescaler, sizeof(m_prescaler), 1, "PSCL");
|
||||||
AddExState(&m_opcode, sizeof(m_opcode), 0, "OPS0");
|
AddExState(&m_opcode, sizeof(m_opcode), 1, "OPS0");
|
||||||
AddExState(m_internalram, sizeof(m_internalram), 0, "IRAM");
|
AddExState(m_internalram, sizeof(m_internalram), 0, "IRAM");
|
||||||
AddExState(&m_icount, sizeof(m_icount), 0, "ICNT");
|
AddExState(&m_icount, sizeof(m_icount), 1, "ICNT");
|
||||||
AddExState(&m_delay_timer, sizeof(m_delay_timer), 0, "DTIM");
|
AddExState(&m_delay_timer, sizeof(m_delay_timer), 1, "DTIM");
|
||||||
AddExState(&m_rtcc, sizeof(m_rtcc), 0, "RTCC");
|
AddExState(&m_rtcc, sizeof(m_rtcc), 1, "RTCC");
|
||||||
AddExState(&m_count_pending, sizeof(m_count_pending), 0, "CNTP");
|
AddExState(&m_count_pending, sizeof(m_count_pending), 0, "CNTP");
|
||||||
AddExState(&m_inst_cycles, sizeof(m_inst_cycles), 0, "ICYC");
|
AddExState(&m_inst_cycles, sizeof(m_inst_cycles), 1, "ICYC");
|
||||||
AddExState(&m_clock2cycle, sizeof(m_clock2cycle), 0, "CL2C");
|
AddExState(&m_clock2cycle, sizeof(m_clock2cycle), 1, "CL2C");
|
||||||
}
|
}
|
||||||
@@ -90,6 +90,7 @@ static void S8259Reset(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void S8259Restore(int version) {
|
static void S8259Restore(int version) {
|
||||||
|
cmd &= 7; /* latch[] has 8 entries; clamp savestate value */
|
||||||
S8259Synco();
|
S8259Synco();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,6 +299,7 @@ static DECLFW(S74LS374NWrite) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void S74LS374NRestore(int version) {
|
static void S74LS374NRestore(int version) {
|
||||||
|
cmd &= 7; /* latch[] has 8 entries; clamp savestate value */
|
||||||
S74LS374NSynco();
|
S74LS374NSynco();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,17 +83,51 @@ void setfprg16(uint32 A, uint32 V) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The flash write count region (the first flash_write_count_size bytes of
|
||||||
|
* fceumm_flash_buf) is exposed verbatim to the frontend as battery save
|
||||||
|
* RAM. To keep .srm files portable between LE and BE builds, we always
|
||||||
|
* store the counters as little-endian on disk; on BE hosts that means
|
||||||
|
* byte-swapping at every counter access. */
|
||||||
|
static INLINE uint32 fwc_load(uint32 idx) {
|
||||||
|
#ifdef MSB_FIRST
|
||||||
|
uint8 *p = (uint8 *)&flash_write_count[idx];
|
||||||
|
return (uint32)p[0] | ((uint32)p[1] << 8) | ((uint32)p[2] << 16) | ((uint32)p[3] << 24);
|
||||||
|
#else
|
||||||
|
return flash_write_count[idx];
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static INLINE void fwc_store(uint32 idx, uint32 v) {
|
||||||
|
#ifdef MSB_FIRST
|
||||||
|
uint8 *p = (uint8 *)&flash_write_count[idx];
|
||||||
|
p[0] = (uint8)v;
|
||||||
|
p[1] = (uint8)(v >> 8);
|
||||||
|
p[2] = (uint8)(v >> 16);
|
||||||
|
p[3] = (uint8)(v >> 24);
|
||||||
|
#else
|
||||||
|
flash_write_count[idx] = v;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void inc_flash_write_count(uint8 bank, uint32 A) {
|
void inc_flash_write_count(uint8 bank, uint32 A) {
|
||||||
flash_write_count[(bank * 4) + ((A & 0x3000) >> 12)]++;
|
uint32 idx = (bank * 4) + ((A & 0x3000) >> 12);
|
||||||
if (!flash_write_count[(bank * 4) + ((A & 0x3000) >> 12)])
|
uint32 v = fwc_load(idx) + 1;
|
||||||
flash_write_count[(bank * 4) + ((A & 0x3000) >> 12)]++;
|
if (v == 0) v = 1; /* avoid wrap to 0 (which means "never written") */
|
||||||
|
fwc_store(idx, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32 GetFlashWriteCount(uint8 bank, uint32 A) {
|
uint32 GetFlashWriteCount(uint8 bank, uint32 A) {
|
||||||
return flash_write_count[(bank * 4) + ((A & 0x3000) >> 12)];
|
return fwc_load((bank * 4) + ((A & 0x3000) >> 12));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void StateRestore(int version) {
|
static void StateRestore(int version) {
|
||||||
|
/* erase_a/d/b[] are 5-element arrays indexed by flash_state.
|
||||||
|
* Legitimate values are 0..4; clamp savestate values that exceed
|
||||||
|
* the array to a safe value to avoid OOB reads. */
|
||||||
|
if (flash_state >= 5)
|
||||||
|
flash_state = 0;
|
||||||
|
if (flash_mode > 2)
|
||||||
|
flash_mode = 0;
|
||||||
WHSync();
|
WHSync();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,7 +295,7 @@ void UNROM512_Init(CartInfo *info) {
|
|||||||
AddExState(&flash_state, 1, 0, "FLASH_STATE");
|
AddExState(&flash_state, 1, 0, "FLASH_STATE");
|
||||||
AddExState(&flash_mode, 1, 0, "FLASH_MODE");
|
AddExState(&flash_mode, 1, 0, "FLASH_MODE");
|
||||||
AddExState(&flash_bank, 1, 0, "FLASH_BANK");
|
AddExState(&flash_bank, 1, 0, "FLASH_BANK");
|
||||||
AddExState(&latcha, 2, 0, "LATA");
|
AddExState(&latcha, 2, 1, "LATA");
|
||||||
}
|
}
|
||||||
AddExState(&latche, 1, 0, "LATC");
|
AddExState(&latche, 1, 0, "LATC");
|
||||||
AddExState(&bus_conflict, 1, 0, "BUSC");
|
AddExState(&bus_conflict, 1, 0, "BUSC");
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ static SFORMAT StateRegs[] =
|
|||||||
{ &IRQa, 1, "IRQA" },
|
{ &IRQa, 1, "IRQA" },
|
||||||
{ &IRQx, 1, "IRQX" },
|
{ &IRQx, 1, "IRQX" },
|
||||||
{ &IRQm, 1, "IRQM" },
|
{ &IRQm, 1, "IRQM" },
|
||||||
{ &IRQReload, 2, "IRQR" },
|
{ &IRQReload, 2 | FCEUSTATE_RLSB, "IRQR" },
|
||||||
{ &IRQCount, 2, "IRQC" },
|
{ &IRQCount, 2 | FCEUSTATE_RLSB, "IRQC" },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ static SFORMAT StateRegs[] =
|
|||||||
{ &IRQa, 1, "IRQA" },
|
{ &IRQa, 1, "IRQA" },
|
||||||
{ &IRQd, 1, "IRQD" },
|
{ &IRQd, 1, "IRQD" },
|
||||||
{ &IRQLatch, 1, "IRQL" },
|
{ &IRQLatch, 1, "IRQL" },
|
||||||
{ &IRQCount, 4, "IRQC" },
|
{ &IRQCount, 4 | FCEUSTATE_RLSB, "IRQC" },
|
||||||
{ &CycleCount, 4, "CYCC" },
|
{ &CycleCount, 4 | FCEUSTATE_RLSB, "CYCC" },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ static SFORMAT StateRegs[] =
|
|||||||
{ &IRQa, 1, "IRQA" },
|
{ &IRQa, 1, "IRQA" },
|
||||||
{ &IRQd, 1, "IRQD" },
|
{ &IRQd, 1, "IRQD" },
|
||||||
{ &IRQLatch, 1, "IRQL" },
|
{ &IRQLatch, 1, "IRQL" },
|
||||||
{ &IRQCount, 4, "IRQC" },
|
{ &IRQCount, 4 | FCEUSTATE_RLSB, "IRQC" },
|
||||||
{ &CycleCount, 4, "CYCC" },
|
{ &CycleCount, 4 | FCEUSTATE_RLSB, "CYCC" },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ static SFORMAT StateRegs[] =
|
|||||||
{ &IRQa, 1, "IRQA" },
|
{ &IRQa, 1, "IRQA" },
|
||||||
{ &IRQd, 1, "IRQD" },
|
{ &IRQd, 1, "IRQD" },
|
||||||
{ &IRQLatch, 1, "IRQL" },
|
{ &IRQLatch, 1, "IRQL" },
|
||||||
{ &IRQCount, 4, "IRQC" },
|
{ &IRQCount, 4 | FCEUSTATE_RLSB, "IRQC" },
|
||||||
{ &CycleCount, 4, "CYCC" },
|
{ &CycleCount, 4 | FCEUSTATE_RLSB, "CYCC" },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ static uint8 IRQm;
|
|||||||
|
|
||||||
static SFORMAT StateRegs[] =
|
static SFORMAT StateRegs[] =
|
||||||
{
|
{
|
||||||
{ &IRQPrescaler, 2, "PREC" },
|
{ &IRQPrescaler, 2 | FCEUSTATE_RLSB, "PREC" },
|
||||||
{ &IRQCount, 1, "IRQC" },
|
{ &IRQCount, 1, "IRQC" },
|
||||||
{ &IRQLatch, 1, "IRQL" },
|
{ &IRQLatch, 1, "IRQL" },
|
||||||
{ &IRQd, 1, "IRQD" },
|
{ &IRQd, 1, "IRQD" },
|
||||||
|
|||||||
42
src/cart.c
42
src/cart.c
@@ -151,8 +151,18 @@ DECLFR(CartBROB) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void FASTAPASS(3) setprg2r(int r, uint32 A, uint32 V) {
|
void FASTAPASS(3) setprg2r(int r, uint32 A, uint32 V) {
|
||||||
|
/* If the registered chip size is < 2KB, PRGmask2[r] underflowed to
|
||||||
|
* 0xFFFFFFFF in SetupCartPRGMapping. Clear the page rather than
|
||||||
|
* indexing past PRGptr[r]. (The only currently-shipped caller that
|
||||||
|
* registers a chip this small is malee.c with size==2048, which
|
||||||
|
* gives mask==0 and works fine; this guard is defensive for any
|
||||||
|
* future board.) */
|
||||||
|
if (!PRGptr[r] || PRGsize[r] < 2048) {
|
||||||
|
setpageptr(2, A, NULL, PRGram[r]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
V &= PRGmask2[r];
|
V &= PRGmask2[r];
|
||||||
setpageptr(2, A, PRGptr[r] ? (&PRGptr[r][V << 11]) : 0, PRGram[r]);
|
setpageptr(2, A, &PRGptr[r][V << 11], PRGram[r]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FASTAPASS(2) setprg2(uint32 A, uint32 V) {
|
void FASTAPASS(2) setprg2(uint32 A, uint32 V) {
|
||||||
@@ -160,8 +170,23 @@ void FASTAPASS(2) setprg2(uint32 A, uint32 V) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void FASTAPASS(3) setprg4r(int r, uint32 A, uint32 V) {
|
void FASTAPASS(3) setprg4r(int r, uint32 A, uint32 V) {
|
||||||
|
if (!PRGptr[r] || PRGsize[r] < 4096) {
|
||||||
|
/* Fall back to two 2KB pages if size is at least 2KB; else
|
||||||
|
* clear both 2KB pages. */
|
||||||
|
if (PRGptr[r] && PRGsize[r] >= 2048) {
|
||||||
|
uint32 mask2 = PRGmask2[r];
|
||||||
|
uint32 VA = V << 1;
|
||||||
|
int x;
|
||||||
|
for (x = 0; x < 2; x++)
|
||||||
|
setpageptr(2, A + (x << 11), &PRGptr[r][((VA + x) & mask2) << 11], PRGram[r]);
|
||||||
|
} else {
|
||||||
|
setpageptr(2, A, NULL, PRGram[r]);
|
||||||
|
setpageptr(2, A + 0x800, NULL, PRGram[r]);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
V &= PRGmask4[r];
|
V &= PRGmask4[r];
|
||||||
setpageptr(4, A, PRGptr[r] ? (&PRGptr[r][V << 12]) : 0, PRGram[r]);
|
setpageptr(4, A, &PRGptr[r][V << 12], PRGram[r]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FASTAPASS(2) setprg4(uint32 A, uint32 V) {
|
void FASTAPASS(2) setprg4(uint32 A, uint32 V) {
|
||||||
@@ -220,6 +245,7 @@ void FASTAPASS(2) setprg32(uint32 A, uint32 V) {
|
|||||||
|
|
||||||
void FASTAPASS(3) setchr1r(int r, uint32 A, uint32 V) {
|
void FASTAPASS(3) setchr1r(int r, uint32 A, uint32 V) {
|
||||||
if (!CHRptr[r]) return;
|
if (!CHRptr[r]) return;
|
||||||
|
if (CHRsize[r] < 1024) return; /* mask underflow guard */
|
||||||
FCEUPPU_LineUpdate();
|
FCEUPPU_LineUpdate();
|
||||||
V &= CHRmask1[r];
|
V &= CHRmask1[r];
|
||||||
if (CHRram[r])
|
if (CHRram[r])
|
||||||
@@ -231,6 +257,7 @@ void FASTAPASS(3) setchr1r(int r, uint32 A, uint32 V) {
|
|||||||
|
|
||||||
void FASTAPASS(3) setchr2r(int r, uint32 A, uint32 V) {
|
void FASTAPASS(3) setchr2r(int r, uint32 A, uint32 V) {
|
||||||
if (!CHRptr[r]) return;
|
if (!CHRptr[r]) return;
|
||||||
|
if (CHRsize[r] < 2048) return; /* mask underflow guard */
|
||||||
FCEUPPU_LineUpdate();
|
FCEUPPU_LineUpdate();
|
||||||
V &= CHRmask2[r];
|
V &= CHRmask2[r];
|
||||||
VPageR[(A) >> 10] = VPageR[((A) >> 10) + 1] = &CHRptr[r][(V) << 11] - (A);
|
VPageR[(A) >> 10] = VPageR[((A) >> 10) + 1] = &CHRptr[r][(V) << 11] - (A);
|
||||||
@@ -242,6 +269,7 @@ void FASTAPASS(3) setchr2r(int r, uint32 A, uint32 V) {
|
|||||||
|
|
||||||
void FASTAPASS(3) setchr4r(int r, uint32 A, uint32 V) {
|
void FASTAPASS(3) setchr4r(int r, uint32 A, uint32 V) {
|
||||||
if (!CHRptr[r]) return;
|
if (!CHRptr[r]) return;
|
||||||
|
if (CHRsize[r] < 4096) return; /* mask underflow guard */
|
||||||
FCEUPPU_LineUpdate();
|
FCEUPPU_LineUpdate();
|
||||||
V &= CHRmask4[r];
|
V &= CHRmask4[r];
|
||||||
VPageR[(A) >> 10] = VPageR[((A) >> 10) + 1] =
|
VPageR[(A) >> 10] = VPageR[((A) >> 10) + 1] =
|
||||||
@@ -257,7 +285,15 @@ void FASTAPASS(2) setchr8r(int r, uint32 V) {
|
|||||||
|
|
||||||
if (!CHRptr[r]) return;
|
if (!CHRptr[r]) return;
|
||||||
FCEUPPU_LineUpdate();
|
FCEUPPU_LineUpdate();
|
||||||
V &= CHRmask8[r];
|
if (CHRsize[r] < 8192) {
|
||||||
|
/* Undersized chip: clamp V to 0 to avoid OOB indexing. The 2KB
|
||||||
|
* NTARAM-as-CHR pattern in mapper 218 currently relies on this
|
||||||
|
* (always called with V=0); treat anything else as a logic
|
||||||
|
* error and clamp. */
|
||||||
|
V = 0;
|
||||||
|
} else {
|
||||||
|
V &= CHRmask8[r];
|
||||||
|
}
|
||||||
for (x = 7; x >= 0; x--)
|
for (x = 7; x >= 0; x--)
|
||||||
VPageR[x] = &CHRptr[r][V << 13];
|
VPageR[x] = &CHRptr[r][V << 13];
|
||||||
if (CHRram[r])
|
if (CHRram[r])
|
||||||
|
|||||||
10
src/cheat.c
10
src/cheat.c
@@ -114,13 +114,21 @@ void RebuildSubCheats(void) {
|
|||||||
if (c->type == 1 && c->status) {
|
if (c->type == 1 && c->status) {
|
||||||
if (GetReadHandler(c->addr) == SubCheatsRead) {
|
if (GetReadHandler(c->addr) == SubCheatsRead) {
|
||||||
/* Prevent a catastrophe by this check. */
|
/* Prevent a catastrophe by this check. */
|
||||||
} else {
|
} else if (numsubcheats < (int)(sizeof(SubCheats) / sizeof(SubCheats[0]))) {
|
||||||
SubCheats[numsubcheats].PrevRead = GetReadHandler(c->addr);
|
SubCheats[numsubcheats].PrevRead = GetReadHandler(c->addr);
|
||||||
SubCheats[numsubcheats].addr = c->addr;
|
SubCheats[numsubcheats].addr = c->addr;
|
||||||
SubCheats[numsubcheats].val = c->val;
|
SubCheats[numsubcheats].val = c->val;
|
||||||
SubCheats[numsubcheats].compare = c->compare;
|
SubCheats[numsubcheats].compare = c->compare;
|
||||||
SetReadHandler(c->addr, c->addr, SubCheatsRead);
|
SetReadHandler(c->addr, c->addr, SubCheatsRead);
|
||||||
numsubcheats++;
|
numsubcheats++;
|
||||||
|
} else {
|
||||||
|
/* SubCheats[] table is full; further substitution
|
||||||
|
* cheats are dropped silently rather than overflowing. */
|
||||||
|
static int warned = 0;
|
||||||
|
if (!warned) {
|
||||||
|
FCEU_PrintError(" Too many active substitution cheats; some have been ignored.\n");
|
||||||
|
warned = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c = c->next;
|
c = c->next;
|
||||||
|
|||||||
@@ -1050,6 +1050,13 @@ static void stereo_filter_apply_delay(int32_t *sound_buffer, size_t size)
|
|||||||
tmp_buffer_size = (tmp_buffer_size << 1) - (tmp_buffer_size >> 1);
|
tmp_buffer_size = (tmp_buffer_size << 1) - (tmp_buffer_size >> 1);
|
||||||
tmp_buffer = (int32_t *)malloc(tmp_buffer_size * sizeof(int32_t));
|
tmp_buffer = (int32_t *)malloc(tmp_buffer_size * sizeof(int32_t));
|
||||||
|
|
||||||
|
if (!tmp_buffer)
|
||||||
|
{
|
||||||
|
/* On allocation failure, drop this batch rather than dereferencing
|
||||||
|
* a NULL buffer in the memcpy below. The next batch will retry. */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
memcpy(tmp_buffer, stereo_filter_delay.samples,
|
memcpy(tmp_buffer, stereo_filter_delay.samples,
|
||||||
stereo_filter_delay.samples_pos * sizeof(int32_t));
|
stereo_filter_delay.samples_pos * sizeof(int32_t));
|
||||||
|
|
||||||
@@ -1137,6 +1144,15 @@ static void stereo_filter_init_delay(void)
|
|||||||
|
|
||||||
stereo_filter_delay.samples = (int32_t *)malloc(
|
stereo_filter_delay.samples = (int32_t *)malloc(
|
||||||
initial_samples_size * sizeof(int32_t));
|
initial_samples_size * sizeof(int32_t));
|
||||||
|
if (!stereo_filter_delay.samples)
|
||||||
|
{
|
||||||
|
/* Fall back to the null filter on allocation failure rather than
|
||||||
|
* leaving a NULL samples buffer that the apply path would deref. */
|
||||||
|
stereo_filter_delay.samples_size = 0;
|
||||||
|
stereo_filter_delay.samples_pos = 0;
|
||||||
|
stereo_filter_apply = stereo_filter_apply_null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
stereo_filter_delay.samples_size = initial_samples_size;
|
stereo_filter_delay.samples_size = initial_samples_size;
|
||||||
stereo_filter_delay.samples_pos = 0;
|
stereo_filter_delay.samples_pos = 0;
|
||||||
|
|
||||||
@@ -1433,7 +1449,7 @@ void retro_set_controller_port_device(unsigned port, unsigned device)
|
|||||||
{
|
{
|
||||||
if (device != RETRO_DEVICE_AUTO)
|
if (device != RETRO_DEVICE_AUTO)
|
||||||
update_nes_controllers(port, device);
|
update_nes_controllers(port, device);
|
||||||
else
|
else if (GameInfo) /* GameInfo may be NULL pre-load */
|
||||||
update_nes_controllers(port, nes_to_libretro(GameInfo->input[port]));
|
update_nes_controllers(port, nes_to_libretro(GameInfo->input[port]));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -1460,7 +1476,7 @@ void retro_set_controller_port_device(unsigned port, unsigned device)
|
|||||||
{
|
{
|
||||||
if (device != RETRO_DEVICE_FC_AUTO)
|
if (device != RETRO_DEVICE_FC_AUTO)
|
||||||
update_nes_controllers(4, device);
|
update_nes_controllers(4, device);
|
||||||
else
|
else if (GameInfo) /* GameInfo may be NULL pre-load */
|
||||||
update_nes_controllers(4, fc_to_libretro(GameInfo->inputfc));
|
update_nes_controllers(4, fc_to_libretro(GameInfo->inputfc));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1921,7 +1937,9 @@ void retro_deinit (void)
|
|||||||
FCEUI_Sound(0);
|
FCEUI_Sound(0);
|
||||||
FCEUI_Kill();
|
FCEUI_Kill();
|
||||||
#if defined(_3DS)
|
#if defined(_3DS)
|
||||||
linearFree(fceu_video_out);
|
if (fceu_video_out)
|
||||||
|
linearFree(fceu_video_out);
|
||||||
|
fceu_video_out = NULL;
|
||||||
#else
|
#else
|
||||||
if (fceu_video_out)
|
if (fceu_video_out)
|
||||||
free(fceu_video_out);
|
free(fceu_video_out);
|
||||||
@@ -3057,6 +3075,8 @@ size_t retro_serialize_size(void)
|
|||||||
{
|
{
|
||||||
/* Something arbitrarily big.*/
|
/* Something arbitrarily big.*/
|
||||||
uint8_t *buffer = (uint8_t*)malloc(1000000);
|
uint8_t *buffer = (uint8_t*)malloc(1000000);
|
||||||
|
if (!buffer)
|
||||||
|
return 0;
|
||||||
memstream_set_buffer(buffer, 1000000);
|
memstream_set_buffer(buffer, 1000000);
|
||||||
|
|
||||||
FCEUSS_Save_Mem();
|
FCEUSS_Save_Mem();
|
||||||
@@ -3130,7 +3150,7 @@ void retro_cheat_reset(void)
|
|||||||
void retro_cheat_set(unsigned index, bool enabled, const char *code)
|
void retro_cheat_set(unsigned index, bool enabled, const char *code)
|
||||||
{
|
{
|
||||||
char name[256];
|
char name[256];
|
||||||
char temp[256];
|
char temp[1024];
|
||||||
char *codepart;
|
char *codepart;
|
||||||
uint16 a;
|
uint16 a;
|
||||||
uint8 v;
|
uint8 v;
|
||||||
@@ -3140,8 +3160,12 @@ void retro_cheat_set(unsigned index, bool enabled, const char *code)
|
|||||||
if (code == NULL)
|
if (code == NULL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
/* Cheat strings can be arbitrarily long (multiple codes joined by
|
||||||
|
* separators). The previous strcpy into a 256-byte stack buffer was
|
||||||
|
* trivially overflowable. Use strlcpy and a larger buffer; truncate
|
||||||
|
* if necessary rather than overflow. */
|
||||||
sprintf(name, "N/A");
|
sprintf(name, "N/A");
|
||||||
strcpy(temp, code);
|
strlcpy(temp, code, sizeof(temp));
|
||||||
codepart = strtok(temp, "+,;._ ");
|
codepart = strtok(temp, "+,;._ ");
|
||||||
|
|
||||||
while (codepart)
|
while (codepart)
|
||||||
@@ -3685,6 +3709,17 @@ bool retro_load_game(const struct retro_game_info *info)
|
|||||||
|
|
||||||
if (!GameInfo)
|
if (!GameInfo)
|
||||||
{
|
{
|
||||||
|
/* On load failure the libretro frontend won't call retro_unload_game,
|
||||||
|
* so free the framebuffer we just allocated to avoid leaking ~256 KB
|
||||||
|
* per failed load. */
|
||||||
|
#if defined(_3DS)
|
||||||
|
if (fceu_video_out)
|
||||||
|
linearFree(fceu_video_out);
|
||||||
|
#elif !defined(PSP)
|
||||||
|
if (fceu_video_out)
|
||||||
|
free(fceu_video_out);
|
||||||
|
#endif
|
||||||
|
fceu_video_out = NULL;
|
||||||
#if 0
|
#if 0
|
||||||
/* An error message here is superfluous - the frontend
|
/* An error message here is superfluous - the frontend
|
||||||
* will report that content loading has failed */
|
* will report that content loading has failed */
|
||||||
@@ -3858,7 +3893,7 @@ void *retro_get_memory_data(unsigned type)
|
|||||||
return iNESCart.SaveGame[0];
|
return iNESCart.SaveGame[0];
|
||||||
else if (UNIFCart.battery && UNIFCart.SaveGame[0] && UNIFCart.SaveGameLen[0])
|
else if (UNIFCart.battery && UNIFCart.SaveGame[0] && UNIFCart.SaveGameLen[0])
|
||||||
return UNIFCart.SaveGame[0];
|
return UNIFCart.SaveGame[0];
|
||||||
else if (GameInfo->type == GIT_FDS)
|
else if (GameInfo && GameInfo->type == GIT_FDS)
|
||||||
return FDSROM_ptr();
|
return FDSROM_ptr();
|
||||||
else
|
else
|
||||||
data = NULL;
|
data = NULL;
|
||||||
@@ -3885,7 +3920,7 @@ size_t retro_get_memory_size(unsigned type)
|
|||||||
size = iNESCart.SaveGameLen[0];
|
size = iNESCart.SaveGameLen[0];
|
||||||
else if (UNIFCart.battery && UNIFCart.SaveGame[0] && UNIFCart.SaveGameLen[0])
|
else if (UNIFCart.battery && UNIFCart.SaveGame[0] && UNIFCart.SaveGameLen[0])
|
||||||
size = UNIFCart.SaveGameLen[0];
|
size = UNIFCart.SaveGameLen[0];
|
||||||
else if (GameInfo->type == GIT_FDS)
|
else if (GameInfo && GameInfo->type == GIT_FDS)
|
||||||
size = FDSROM_size();
|
size = FDSROM_size();
|
||||||
else
|
else
|
||||||
size = 0;
|
size = 0;
|
||||||
|
|||||||
@@ -28,12 +28,17 @@
|
|||||||
void FlipByteOrder(uint8 *src, uint32 count)
|
void FlipByteOrder(uint8 *src, uint32 count)
|
||||||
{
|
{
|
||||||
uint8 *start = src;
|
uint8 *start = src;
|
||||||
uint8 *end = src + count - 1;
|
uint8 *end;
|
||||||
|
|
||||||
if ((count & 1) || !count)
|
if ((count & 1) || !count)
|
||||||
return; /* This shouldn't happen. */
|
return; /* This shouldn't happen. */
|
||||||
|
|
||||||
while (count--)
|
end = src + count - 1;
|
||||||
|
/* Iterate while start < end, not 'count' times: the original loop
|
||||||
|
* over-iterated and effectively performed a no-op on every even count.
|
||||||
|
* That broke savestate portability across LE<->BE for every
|
||||||
|
* FCEUSTATE_RLSB-marked field. */
|
||||||
|
while (start < end)
|
||||||
{
|
{
|
||||||
uint8 tmp;
|
uint8 tmp;
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ void *FCEU_malloc(uint32 size)
|
|||||||
if (!ret)
|
if (!ret)
|
||||||
{
|
{
|
||||||
FCEU_PrintError("Error allocating memory!");
|
FCEU_PrintError("Error allocating memory!");
|
||||||
ret = 0;
|
return NULL;
|
||||||
}
|
}
|
||||||
memset(ret, 0, size);
|
memset(ret, 0, size);
|
||||||
return ret;
|
return ret;
|
||||||
|
|||||||
17
src/fceu.c
17
src/fceu.c
@@ -82,7 +82,15 @@ int AllocGenieRW(void)
|
|||||||
if (!BWriteG)
|
if (!BWriteG)
|
||||||
{
|
{
|
||||||
if (!(BWriteG = (writefunc*)FCEU_malloc(0x8000 * sizeof(writefunc))))
|
if (!(BWriteG = (writefunc*)FCEU_malloc(0x8000 * sizeof(writefunc))))
|
||||||
|
{
|
||||||
|
/* Free AReadG too on partial-failure to avoid leaking a
|
||||||
|
* 256 KB read-handler table. Subsequent calls would otherwise
|
||||||
|
* see AReadG != NULL and skip its alloc, but RWWrap stays 0
|
||||||
|
* because we return 0, so the table is never used or freed. */
|
||||||
|
free(AReadG);
|
||||||
|
AReadG = NULL;
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
memset(BWriteG, 0, 0x8000 * sizeof(writefunc));
|
memset(BWriteG, 0, 0x8000 * sizeof(writefunc));
|
||||||
@@ -240,6 +248,8 @@ FCEUGI *FCEUI_LoadGame(const char *name, const uint8_t *databuf, size_t databufs
|
|||||||
ResetGameLoaded();
|
ResetGameLoaded();
|
||||||
|
|
||||||
GameInfo = malloc(sizeof(FCEUGI));
|
GameInfo = malloc(sizeof(FCEUGI));
|
||||||
|
if (!GameInfo)
|
||||||
|
return NULL;
|
||||||
memset(GameInfo, 0, sizeof(FCEUGI));
|
memset(GameInfo, 0, sizeof(FCEUGI));
|
||||||
|
|
||||||
GameInfo->soundchan = 0;
|
GameInfo->soundchan = 0;
|
||||||
@@ -461,7 +471,10 @@ void FCEU_printf(char *format, ...)
|
|||||||
va_list ap;
|
va_list ap;
|
||||||
|
|
||||||
va_start(ap, format);
|
va_start(ap, format);
|
||||||
vsprintf(temp, format, ap);
|
/* vsnprintf instead of vsprintf so a long expansion (e.g. of a ROM
|
||||||
|
* filename or an attacker-controlled string field) is truncated
|
||||||
|
* rather than overflowing the stack buffer. */
|
||||||
|
vsnprintf(temp, sizeof(temp), format, ap);
|
||||||
FCEUD_Message(temp);
|
FCEUD_Message(temp);
|
||||||
|
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
@@ -474,7 +487,7 @@ void FCEU_PrintError(char *format, ...)
|
|||||||
va_list ap;
|
va_list ap;
|
||||||
|
|
||||||
va_start(ap, format);
|
va_start(ap, format);
|
||||||
vsprintf(temp, format, ap);
|
vsnprintf(temp, sizeof(temp), format, ap);
|
||||||
FCEUD_PrintError(temp);
|
FCEUD_PrintError(temp);
|
||||||
|
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
|
|||||||
69
src/fds.c
69
src/fds.c
@@ -120,6 +120,38 @@ void FDSGI(int h) {
|
|||||||
static void FDSStateRestore(int version) {
|
static void FDSStateRestore(int version) {
|
||||||
int x;
|
int x;
|
||||||
|
|
||||||
|
/* Sanity-check disk indices and block parameters. A malicious
|
||||||
|
* savestate could otherwise:
|
||||||
|
* - set InDisk to a value 0..254 (255 means ejected) that's >=
|
||||||
|
* TotalSides, causing the read path to index past the
|
||||||
|
* 8-element diskdata[] array and dereference an arbitrary
|
||||||
|
* pointer-shaped 8 bytes (heap-read primitive);
|
||||||
|
* - set mapperFDS_block to an out-of-enum value (no current
|
||||||
|
* case crashes but better to clamp);
|
||||||
|
* - set mapperFDS_blockstart + mapperFDS_diskaddr beyond 65500
|
||||||
|
* (BYTES_PER_SIDE), causing GET_FDS_DISK() to OOB-read up
|
||||||
|
* to 65 KB past the disk buffer. */
|
||||||
|
if (InDisk != 255 && InDisk >= TotalSides)
|
||||||
|
InDisk = 255; /* eject */
|
||||||
|
if (SelectDisk >= TotalSides)
|
||||||
|
SelectDisk = 0;
|
||||||
|
if (mapperFDS_block > DSK_FILEDATA) /* enum values 0..4 are valid */
|
||||||
|
mapperFDS_block = DSK_INIT;
|
||||||
|
/* BYTES_PER_SIDE is 65500. The read/write sites compute
|
||||||
|
* (blockstart + diskaddr) as the offset into a 65500-byte buffer
|
||||||
|
* after gating on diskaddr < blocklen. Clamp each component such
|
||||||
|
* that any allowed (blockstart + diskaddr) stays within the buffer:
|
||||||
|
* - blockstart < BYTES_PER_SIDE (so the base is in-range)
|
||||||
|
* - diskaddr fits in the remaining space, capped by blocklen anyway
|
||||||
|
* - blocklen capped to BYTES_PER_SIDE - blockstart so the gating
|
||||||
|
* check 'diskaddr < blocklen' is sufficient. */
|
||||||
|
if (mapperFDS_blockstart >= BYTES_PER_SIDE)
|
||||||
|
mapperFDS_blockstart = 0;
|
||||||
|
if (mapperFDS_blocklen > BYTES_PER_SIDE - mapperFDS_blockstart)
|
||||||
|
mapperFDS_blocklen = BYTES_PER_SIDE - mapperFDS_blockstart;
|
||||||
|
if (mapperFDS_diskaddr > mapperFDS_blocklen)
|
||||||
|
mapperFDS_diskaddr = mapperFDS_blocklen;
|
||||||
|
|
||||||
setmirror(((FDSRegs[5] & 8) >> 3) ^ 1);
|
setmirror(((FDSRegs[5] & 8) >> 3) ^ 1);
|
||||||
|
|
||||||
if (version >= 9810)
|
if (version >= 9810)
|
||||||
@@ -577,6 +609,14 @@ static int SubLoad(FCEUFILE *fp) {
|
|||||||
struct md5_context md5;
|
struct md5_context md5;
|
||||||
uint8 header[16];
|
uint8 header[16];
|
||||||
int x;
|
int x;
|
||||||
|
uint64 fsize = FCEU_fgetsize(fp);
|
||||||
|
|
||||||
|
/* Reject files too short to contain a 16-byte header. Otherwise the
|
||||||
|
* subsequent FCEU_fread leaves header[] partially uninitialised, and
|
||||||
|
* the magic-string memcmps and side-count read at header[4] could
|
||||||
|
* spuriously succeed on stack garbage. */
|
||||||
|
if (fsize < 16)
|
||||||
|
return(0);
|
||||||
|
|
||||||
FCEU_fread(header, 16, 1, fp);
|
FCEU_fread(header, 16, 1, fp);
|
||||||
|
|
||||||
@@ -679,6 +719,19 @@ int FDSLoad(const char *name, FCEUFILE *fp) {
|
|||||||
|
|
||||||
for (x = 0; x < TotalSides; x++) {
|
for (x = 0; x < TotalSides; x++) {
|
||||||
diskdatao[x] = (uint8*)FCEU_malloc(65500);
|
diskdatao[x] = (uint8*)FCEU_malloc(65500);
|
||||||
|
if (!diskdatao[x]) {
|
||||||
|
int y;
|
||||||
|
for (y = 0; y < x; y++) {
|
||||||
|
free(diskdatao[y]);
|
||||||
|
diskdatao[y] = NULL;
|
||||||
|
}
|
||||||
|
if (FDSBIOS)
|
||||||
|
free(FDSBIOS);
|
||||||
|
FDSBIOS = NULL;
|
||||||
|
free(FDSROM);
|
||||||
|
FDSROM = NULL;
|
||||||
|
return(0);
|
||||||
|
}
|
||||||
memcpy(diskdatao[x], diskdata[x], 65500);
|
memcpy(diskdatao[x], diskdata[x], 65500);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -705,22 +758,22 @@ int FDSLoad(const char *name, FCEUFILE *fp) {
|
|||||||
AddExState(&FDSRegs[3], 1, 0, "REG4");
|
AddExState(&FDSRegs[3], 1, 0, "REG4");
|
||||||
AddExState(&FDSRegs[4], 1, 0, "REG5");
|
AddExState(&FDSRegs[4], 1, 0, "REG5");
|
||||||
AddExState(&FDSRegs[5], 1, 0, "REG6");
|
AddExState(&FDSRegs[5], 1, 0, "REG6");
|
||||||
AddExState(&IRQCount, 4 | FCEUSTATE_RLSB, 1, "IRQC");
|
AddExState(&IRQCount, 4, 1, "IRQC");
|
||||||
AddExState(&IRQLatch, 4 | FCEUSTATE_RLSB, 1, "IQL1");
|
AddExState(&IRQLatch, 4, 1, "IQL1");
|
||||||
AddExState(&IRQa, 1, 0, "IRQA");
|
AddExState(&IRQa, 1, 0, "IRQA");
|
||||||
AddExState(&writeskip, 1, 0, "WSKI");
|
AddExState(&writeskip, 1, 0, "WSKI");
|
||||||
AddExState(&DiskPtr, 4 | FCEUSTATE_RLSB, 1, "DPTR");
|
AddExState(&DiskPtr, 4, 1, "DPTR");
|
||||||
AddExState(&DiskSeekIRQ, 4 | FCEUSTATE_RLSB, 1, "DSIR");
|
AddExState(&DiskSeekIRQ, 4, 1, "DSIR");
|
||||||
AddExState(&SelectDisk, 1, 0, "SELD");
|
AddExState(&SelectDisk, 1, 0, "SELD");
|
||||||
AddExState(&InDisk, 1, 0, "INDI");
|
AddExState(&InDisk, 1, 0, "INDI");
|
||||||
AddExState(&DiskWritten, 1, 0, "DSKW");
|
AddExState(&DiskWritten, 1, 0, "DSKW");
|
||||||
|
|
||||||
AddExState(&mapperFDS_control, 1, 0, "CTRG");
|
AddExState(&mapperFDS_control, 1, 0, "CTRG");
|
||||||
AddExState(&mapperFDS_filesize, 2 | FCEUSTATE_RLSB, 1, "FLSZ");
|
AddExState(&mapperFDS_filesize, 2, 1, "FLSZ");
|
||||||
AddExState(&mapperFDS_block, 1, 0, "BLCK");
|
AddExState(&mapperFDS_block, 1, 0, "BLCK");
|
||||||
AddExState(&mapperFDS_blockstart, 2 | FCEUSTATE_RLSB, 1, "BLKS");
|
AddExState(&mapperFDS_blockstart, 2, 1, "BLKS");
|
||||||
AddExState(&mapperFDS_blocklen, 2 | FCEUSTATE_RLSB, 1, "BLKL");
|
AddExState(&mapperFDS_blocklen, 2, 1, "BLKL");
|
||||||
AddExState(&mapperFDS_diskaddr, 2 | FCEUSTATE_RLSB, 1, "DADR");
|
AddExState(&mapperFDS_diskaddr, 2, 1, "DADR");
|
||||||
AddExState(&mapperFDS_diskaccess, 1, 0, "DACC");
|
AddExState(&mapperFDS_diskaccess, 1, 0, "DACC");
|
||||||
|
|
||||||
CHRRAMSize = 8192;
|
CHRRAMSize = 8192;
|
||||||
|
|||||||
35
src/file.c
35
src/file.c
@@ -40,23 +40,30 @@
|
|||||||
static MEMWRAP *MakeMemWrap(RFILE *tz)
|
static MEMWRAP *MakeMemWrap(RFILE *tz)
|
||||||
{
|
{
|
||||||
MEMWRAP *tmp = NULL;
|
MEMWRAP *tmp = NULL;
|
||||||
|
int64_t fsize;
|
||||||
|
|
||||||
|
filestream_seek(tz, 0, RETRO_VFS_SEEK_POSITION_END);
|
||||||
|
fsize = filestream_tell(tz);
|
||||||
|
filestream_seek(tz, 0, RETRO_VFS_SEEK_POSITION_START);
|
||||||
|
/* filestream_tell may return -1 on failure; reject before passing the
|
||||||
|
* value (cast to a huge unsigned size) to malloc. */
|
||||||
|
if (fsize < 0)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
if (!(tmp = (MEMWRAP*)FCEU_malloc(sizeof(MEMWRAP))))
|
if (!(tmp = (MEMWRAP*)FCEU_malloc(sizeof(MEMWRAP))))
|
||||||
goto doret;
|
goto doret;
|
||||||
tmp->location = 0;
|
tmp->location = 0;
|
||||||
|
tmp->size = (size_t)fsize;
|
||||||
|
|
||||||
filestream_seek(tz, 0, RETRO_VFS_SEEK_POSITION_END);
|
if (tmp->size && !(tmp->data_int = (uint8*)FCEU_malloc(tmp->size)))
|
||||||
tmp->size = filestream_tell(tz);
|
|
||||||
filestream_seek(tz, 0, RETRO_VFS_SEEK_POSITION_START);
|
|
||||||
|
|
||||||
if (!(tmp->data_int = (uint8*)FCEU_malloc(tmp->size)))
|
|
||||||
{
|
{
|
||||||
free(tmp);
|
free(tmp);
|
||||||
tmp = NULL;
|
tmp = NULL;
|
||||||
goto doret;
|
goto doret;
|
||||||
}
|
}
|
||||||
|
|
||||||
filestream_read(tz, tmp->data_int, tmp->size);
|
if (tmp->size)
|
||||||
|
filestream_read(tz, tmp->data_int, tmp->size);
|
||||||
tmp->data = tmp->data_int;
|
tmp->data = tmp->data_int;
|
||||||
|
|
||||||
doret:
|
doret:
|
||||||
@@ -82,8 +89,19 @@ FCEUFILE * FCEU_fopen(const char *path, const uint8 *buffer, size_t bufsize)
|
|||||||
{
|
{
|
||||||
FCEUFILE *fceufp = (FCEUFILE*)malloc(sizeof(FCEUFILE));
|
FCEUFILE *fceufp = (FCEUFILE*)malloc(sizeof(FCEUFILE));
|
||||||
|
|
||||||
|
if (!fceufp)
|
||||||
|
return NULL;
|
||||||
|
fceufp->fp = NULL;
|
||||||
|
|
||||||
if (buffer)
|
if (buffer)
|
||||||
|
{
|
||||||
fceufp->fp = MakeMemWrapBuffer(buffer, bufsize);
|
fceufp->fp = MakeMemWrapBuffer(buffer, bufsize);
|
||||||
|
if (!fceufp->fp)
|
||||||
|
{
|
||||||
|
free(fceufp);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
RFILE *t = NULL;
|
RFILE *t = NULL;
|
||||||
@@ -101,6 +119,11 @@ FCEUFILE * FCEU_fopen(const char *path, const uint8 *buffer, size_t bufsize)
|
|||||||
|
|
||||||
fceufp->fp = MakeMemWrap(t);
|
fceufp->fp = MakeMemWrap(t);
|
||||||
filestream_close(t);
|
filestream_close(t);
|
||||||
|
if (!fceufp->fp)
|
||||||
|
{
|
||||||
|
free(fceufp);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return fceufp;
|
return fceufp;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,11 +85,21 @@ uint32 uppow2(uint32 n)
|
|||||||
{
|
{
|
||||||
int x;
|
int x;
|
||||||
|
|
||||||
|
if (n == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* If n already has bit 31 set and isn't exactly 0x80000000, the next
|
||||||
|
* power of two would be 1u << 32 which is undefined; clamp to 0x80000000
|
||||||
|
* instead so callers don't end up with a wrap-to-0 allocation that's
|
||||||
|
* smaller than the requested size. */
|
||||||
|
if (n > 0x80000000u)
|
||||||
|
return 0x80000000u;
|
||||||
|
|
||||||
for (x = 31; x >= 0; x--)
|
for (x = 31; x >= 0; x--)
|
||||||
if (n & (1 << x))
|
if (n & (1u << x))
|
||||||
{
|
{
|
||||||
if ((1 << x) != n)
|
if ((1u << x) != n)
|
||||||
return(1 << (x + 1));
|
return(1u << (x + 1));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return n;
|
return n;
|
||||||
|
|||||||
48
src/ines.c
48
src/ines.c
@@ -1076,11 +1076,51 @@ static void iNES_read_header_info(void) {
|
|||||||
if (head.PRGRAM_size & 0xF0) iNESCart.PRGRamSaveSize = 64 << ((head.PRGRAM_size >> 4) & 0x0F);
|
if (head.PRGRAM_size & 0xF0) iNESCart.PRGRamSaveSize = 64 << ((head.PRGRAM_size >> 4) & 0x0F);
|
||||||
if (head.CHRRAM_size & 0x0F) iNESCart.CHRRamSize = 64 << ((head.CHRRAM_size >> 0) & 0x0F);
|
if (head.CHRRAM_size & 0x0F) iNESCart.CHRRamSize = 64 << ((head.CHRRAM_size >> 0) & 0x0F);
|
||||||
if (head.CHRRAM_size & 0xF0) iNESCart.CHRRamSaveSize = 64 << ((head.CHRRAM_size >> 4) & 0x0F);
|
if (head.CHRRAM_size & 0xF0) iNESCart.CHRRamSaveSize = 64 << ((head.CHRRAM_size >> 4) & 0x0F);
|
||||||
iNESCart.PRGRomSize = ROM_size >=0xF00? (pow(2, head.ROM_size >>2)*((head.ROM_size &3)*2+1)): (ROM_size*0x4000);
|
/* iNES 2.0 exponent encoding: when the 12-bit count >= 0xF00, the byte
|
||||||
iNESCart.CHRRomSize =VROM_size >=0xF00? (pow(2, head.VROM_size>>2)*((head.VROM_size&3)*2+1)): (VROM_size*0x2000);;
|
* encodes (multiplier * 2^exponent) where exponent = byte>>2 (0..63) and
|
||||||
|
* multiplier = (byte&3)*2+1. Cap exponent so the result stays well
|
||||||
|
* within uint32 (and a sane size); otherwise pow(2, 63) overflows
|
||||||
|
* uint32 implicitly with undefined results, and downstream uppow2()
|
||||||
|
* truncates back to a small allocation, leading to a heap overflow on
|
||||||
|
* the subsequent fread. Cap at 30 so the maximum is 7 << 30 = ~7 GiB
|
||||||
|
* which still fits in uint32 (truncated to ~3 GiB) but is far above
|
||||||
|
* any real cart and gets caught by sane validation. We additionally
|
||||||
|
* clamp the final value to a safe ceiling. */
|
||||||
|
{
|
||||||
|
uint32 exp_prg = head.ROM_size >> 2;
|
||||||
|
uint32 exp_chr = head.VROM_size >> 2;
|
||||||
|
uint32 prg, chr;
|
||||||
|
if (exp_prg > 30) exp_prg = 30;
|
||||||
|
if (exp_chr > 30) exp_chr = 30;
|
||||||
|
prg = ROM_size >= 0xF00 ? ((uint32)1 << exp_prg) * ((head.ROM_size & 3) * 2 + 1) : (ROM_size * 0x4000);
|
||||||
|
chr = VROM_size >= 0xF00 ? ((uint32)1 << exp_chr) * ((head.VROM_size & 3) * 2 + 1) : (VROM_size * 0x2000);
|
||||||
|
/* Cap below 2 GiB so the value fits comfortably in the
|
||||||
|
* downstream int PRGRomSize / CHRRomSize fields without
|
||||||
|
* going negative, and so uppow2 returns a finite power-of-two
|
||||||
|
* within int range. Anything larger is bogus anyway. */
|
||||||
|
if (prg > 0x40000000u) prg = 0x40000000u;
|
||||||
|
if (chr > 0x40000000u) chr = 0x40000000u;
|
||||||
|
iNESCart.PRGRomSize = (int)prg;
|
||||||
|
iNESCart.CHRRomSize = (int)chr;
|
||||||
|
}
|
||||||
iNESCart.miscROMNumber =head.MiscRoms;
|
iNESCart.miscROMNumber =head.MiscRoms;
|
||||||
iNESCart.miscROMSize =iNESCart.miscROMNumber? (iNESCart.totalFileSize -16 -(head.ROM_type &4? 512: 0) -iNESCart.PRGRomSize -iNESCart.CHRRomSize): 0;
|
if (iNESCart.miscROMNumber) {
|
||||||
if (iNESCart.miscROMSize &0x8000000) iNESCart.miscROMSize =0;
|
/* Compute miscROMSize as int64 to avoid silent wraparound when
|
||||||
|
* the (attacker-controlled) PRGRomSize / CHRRomSize fields
|
||||||
|
* exceed the file size. Reject negative or absurdly large
|
||||||
|
* results rather than passing a wrapped value to malloc. */
|
||||||
|
int64 misc = (int64)iNESCart.totalFileSize
|
||||||
|
- 16
|
||||||
|
- ((head.ROM_type & 4) ? 512 : 0)
|
||||||
|
- (int64)iNESCart.PRGRomSize
|
||||||
|
- (int64)iNESCart.CHRRomSize;
|
||||||
|
if (misc <= 0 || misc > 0x8000000) /* > 128 MiB is suspect */
|
||||||
|
iNESCart.miscROMSize = 0;
|
||||||
|
else
|
||||||
|
iNESCart.miscROMSize = (int)misc;
|
||||||
|
} else {
|
||||||
|
iNESCart.miscROMSize = 0;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
iNESCart.submapper = iNESCart.miscROMNumber = iNESCart.miscROMSize = 0;
|
iNESCart.submapper = iNESCart.miscROMNumber = iNESCart.miscROMSize = 0;
|
||||||
iNESCart.PRGRomSize =ROM_size*0x4000;
|
iNESCart.PRGRomSize =ROM_size*0x4000;
|
||||||
|
|||||||
@@ -187,6 +187,10 @@ static uint8 FP_FASTAPASS(1) ReadGP(int w) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void FP_FASTAPASS(3) UpdateGP(int w, void *data, int arg) {
|
static void FP_FASTAPASS(3) UpdateGP(int w, void *data, int arg) {
|
||||||
|
/* JSReturn is constructed by the libretro frontend as a uint32 with
|
||||||
|
* player N at bits (N<<3)..(N<<3)+7. Reading via host-endian uint32
|
||||||
|
* cast and shifting gives the correct player byte regardless of host
|
||||||
|
* endianness, because the writer used the matching shift. */
|
||||||
uint32 *ptr = (uint32*)data;
|
uint32 *ptr = (uint32*)data;
|
||||||
if (!w) {
|
if (!w) {
|
||||||
joy[0] = *(uint32*)ptr;
|
joy[0] = *(uint32*)ptr;
|
||||||
|
|||||||
@@ -48,10 +48,19 @@ static void FP_FASTAPASS(1) Write(uint8 V) {
|
|||||||
|
|
||||||
static void FP_FASTAPASS(2) Update(void *data, int arg) {
|
static void FP_FASTAPASS(2) Update(void *data, int arg) {
|
||||||
if (*(uint8*)data) {
|
if (*(uint8*)data) {
|
||||||
|
size_t i;
|
||||||
|
const uint8 *src = (uint8*)data + 1;
|
||||||
*(uint8*)data = 0;
|
*(uint8*)data = 0;
|
||||||
seq = ptr = 0;
|
seq = ptr = 0;
|
||||||
have = 1;
|
have = 1;
|
||||||
strcpy((char*)bdata, (const char*)((uint8*)data + 1));
|
/* bdata is 20 bytes total; bytes 13..19 are filled by the
|
||||||
|
* memcpy below. Copy at most 13 bytes from `src`, ensuring
|
||||||
|
* NUL termination at index 13 or earlier. */
|
||||||
|
for (i = 0; i < 13; i++) {
|
||||||
|
if (!src[i]) break;
|
||||||
|
bdata[i] = src[i];
|
||||||
|
}
|
||||||
|
bdata[i] = 0;
|
||||||
memcpy((char*)&bdata[13], "SUNSOFT", 7);
|
memcpy((char*)&bdata[13], "SUNSOFT", 7);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
31
src/nsf.c
31
src/nsf.c
@@ -135,9 +135,14 @@ static INLINE void BANKSET(uint32 A, uint32 bank) {
|
|||||||
|
|
||||||
int NSFLoad(FCEUFILE *fp) {
|
int NSFLoad(FCEUFILE *fp) {
|
||||||
int x;
|
int x;
|
||||||
|
uint64 fsize;
|
||||||
|
|
||||||
FCEU_fseek(fp, 0, SEEK_SET);
|
FCEU_fseek(fp, 0, SEEK_SET);
|
||||||
FCEU_fread(&NSFHeader, 1, 0x80, fp);
|
/* NSFHeader is file-scope static; if a previous successful load left
|
||||||
|
* it populated, a shorter file that doesn't fully overwrite it could
|
||||||
|
* bypass the ID memcmp on stale data. Validate read length. */
|
||||||
|
if (FCEU_fread(&NSFHeader, 1, 0x80, fp) != 0x80)
|
||||||
|
return 0;
|
||||||
if (memcmp(NSFHeader.ID, "NESM\x1a", 5))
|
if (memcmp(NSFHeader.ID, "NESM\x1a", 5))
|
||||||
return 0;
|
return 0;
|
||||||
NSFHeader.SongName[31] = NSFHeader.Artist[31] = NSFHeader.Copyright[31] = 0;
|
NSFHeader.SongName[31] = NSFHeader.Artist[31] = NSFHeader.Copyright[31] = 0;
|
||||||
@@ -155,10 +160,32 @@ int NSFLoad(FCEUFILE *fp) {
|
|||||||
PlayAddr = NSFHeader.PlayAddressLow;
|
PlayAddr = NSFHeader.PlayAddressLow;
|
||||||
PlayAddr |= NSFHeader.PlayAddressHigh << 8;
|
PlayAddr |= NSFHeader.PlayAddressHigh << 8;
|
||||||
|
|
||||||
NSFSize = FCEU_fgetsize(fp) - 0x80;
|
/* Validate that there is data past the 0x80-byte header before
|
||||||
|
* subtracting; otherwise NSFSize underflows uint64 and propagates a
|
||||||
|
* huge size into NSFMaxBank/uppow2/FCEU_malloc, leading to a heap
|
||||||
|
* overflow on the subsequent fread. Also cap at a sane ceiling. */
|
||||||
|
fsize = FCEU_fgetsize(fp);
|
||||||
|
if (fsize <= 0x80) {
|
||||||
|
FCEUD_PrintError("NSF file truncated (no song data after header).");
|
||||||
|
return(0);
|
||||||
|
}
|
||||||
|
NSFSize = fsize - 0x80;
|
||||||
|
/* Ceiling: 16 MiB of song data is far beyond reality */
|
||||||
|
if (NSFSize > (16u << 20)) {
|
||||||
|
FCEUD_PrintError("NSF song data unreasonably large.");
|
||||||
|
return(0);
|
||||||
|
}
|
||||||
|
|
||||||
NSFMaxBank = ((NSFSize + (LoadAddr & 0xfff) + 4095) / 4096);
|
NSFMaxBank = ((NSFSize + (LoadAddr & 0xfff) + 4095) / 4096);
|
||||||
NSFMaxBank = uppow2(NSFMaxBank);
|
NSFMaxBank = uppow2(NSFMaxBank);
|
||||||
|
/* Cap at 2^19 so NSFMaxBank * 4096 fits in signed int (the type
|
||||||
|
* of NSFMaxBank). 2^19 * 4096 = 2 GiB which is already absurd for
|
||||||
|
* an NSF file; the previous cap of 2^20 produced 4 GiB which
|
||||||
|
* overflows signed int and is undefined behaviour. */
|
||||||
|
if (!NSFMaxBank || NSFMaxBank > (1u << 19)) {
|
||||||
|
FCEUD_PrintError("NSF bank count out of range.");
|
||||||
|
return(0);
|
||||||
|
}
|
||||||
|
|
||||||
if (!(NSFDATA = (uint8*)FCEU_malloc(NSFMaxBank * 4096)))
|
if (!(NSFDATA = (uint8*)FCEU_malloc(NSFMaxBank * 4096)))
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
21
src/state.c
21
src/state.c
@@ -267,7 +267,7 @@ void FCEUSS_Save_Mem(void)
|
|||||||
|
|
||||||
totalsize += WriteStateChunk(mem, 0x10, SFMDATA);
|
totalsize += WriteStateChunk(mem, 0x10, SFMDATA);
|
||||||
|
|
||||||
if (SPreSave)
|
if (SPostSave)
|
||||||
SPostSave();
|
SPostSave();
|
||||||
|
|
||||||
memstream_seek(mem, 4, SEEK_SET);
|
memstream_seek(mem, 4, SEEK_SET);
|
||||||
@@ -326,6 +326,22 @@ void AddExState(void *v, uint32 s, int type, char *desc)
|
|||||||
{
|
{
|
||||||
/* prevent adding a terminator to the list if a NULL pointer was provided */
|
/* prevent adding a terminator to the list if a NULL pointer was provided */
|
||||||
if (v == NULL) return;
|
if (v == NULL) return;
|
||||||
|
/* Need at least one slot for the new entry plus one for the terminator.
|
||||||
|
* If we're already at the second-to-last slot (62), we can write the
|
||||||
|
* entry there and the terminator at slot 63, but no further entries
|
||||||
|
* may be added. Beyond that, drop the entry to avoid silently
|
||||||
|
* overwriting the previous one with the terminator. */
|
||||||
|
if (SFEXINDEX >= (int)(sizeof(SFMDATA) / sizeof(SFMDATA[0])) - 1)
|
||||||
|
{
|
||||||
|
static int warned = 0;
|
||||||
|
if (!warned)
|
||||||
|
{
|
||||||
|
FCEU_PrintError(" SFMDATA[] full; some state entries dropped (\"%s\").\n",
|
||||||
|
desc ? desc : "?");
|
||||||
|
warned = 1;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
memset(SFMDATA[SFEXINDEX].desc, 0, sizeof(SFMDATA[SFEXINDEX].desc));
|
memset(SFMDATA[SFEXINDEX].desc, 0, sizeof(SFMDATA[SFEXINDEX].desc));
|
||||||
if (desc)
|
if (desc)
|
||||||
strncpy(SFMDATA[SFEXINDEX].desc, desc, sizeof(SFMDATA[SFEXINDEX].desc));
|
strncpy(SFMDATA[SFEXINDEX].desc, desc, sizeof(SFMDATA[SFEXINDEX].desc));
|
||||||
@@ -333,8 +349,7 @@ void AddExState(void *v, uint32 s, int type, char *desc)
|
|||||||
SFMDATA[SFEXINDEX].s = s;
|
SFMDATA[SFEXINDEX].s = s;
|
||||||
if (type)
|
if (type)
|
||||||
SFMDATA[SFEXINDEX].s |= RLSB;
|
SFMDATA[SFEXINDEX].s |= RLSB;
|
||||||
if (SFEXINDEX < 63)
|
SFEXINDEX++;
|
||||||
SFEXINDEX++;
|
|
||||||
SFMDATA[SFEXINDEX].v = 0; /* End marker. */
|
SFMDATA[SFEXINDEX].v = 0; /* End marker. */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
43
src/unif.c
43
src/unif.c
@@ -93,6 +93,10 @@ static int FixRomSize(uint32 size, uint32 minimum) {
|
|||||||
|
|
||||||
if (size < minimum)
|
if (size < minimum)
|
||||||
return minimum;
|
return minimum;
|
||||||
|
/* Cap input to avoid the infinite loop where x doubles past 0x80000000
|
||||||
|
* and wraps to 0, never reaching size. */
|
||||||
|
if (size > 0x80000000u)
|
||||||
|
return 0x80000000u;
|
||||||
while (x < size)
|
while (x < size)
|
||||||
x <<= 1;
|
x <<= 1;
|
||||||
return x;
|
return x;
|
||||||
@@ -198,6 +202,8 @@ static int NAME(FCEUFILE *fp) {
|
|||||||
|
|
||||||
if (!GameInfo->name) {
|
if (!GameInfo->name) {
|
||||||
GameInfo->name = malloc(strlen(namebuf) + 1);
|
GameInfo->name = malloc(strlen(namebuf) + 1);
|
||||||
|
if (!GameInfo->name)
|
||||||
|
return(0);
|
||||||
strcpy((char*)GameInfo->name, namebuf);
|
strcpy((char*)GameInfo->name, namebuf);
|
||||||
}
|
}
|
||||||
return(1);
|
return(1);
|
||||||
@@ -229,7 +235,11 @@ static int DINF(FCEUFILE *fp) {
|
|||||||
"January", "February", "March", "April", "May", "June", "July",
|
"January", "February", "March", "April", "May", "June", "July",
|
||||||
"August", "September", "October", "November", "December"
|
"August", "September", "October", "November", "December"
|
||||||
};
|
};
|
||||||
FCEU_printf(" Dumped on: %s %d, %d\n", months[(m - 1) % 12], d, y);
|
/* m comes from a (possibly malicious) .unf file. If m is 0 or
|
||||||
|
* out of range, '(m - 1) % 12' would underflow / wrap signed and
|
||||||
|
* could index off the months[] array. Clamp into [1..12] first. */
|
||||||
|
unsigned mi = (m >= 1 && m <= 12) ? (unsigned)(m - 1) : 0;
|
||||||
|
FCEU_printf(" Dumped on: %s %d, %d\n", months[mi], d, y);
|
||||||
}
|
}
|
||||||
return(1);
|
return(1);
|
||||||
}
|
}
|
||||||
@@ -286,11 +296,18 @@ static int EnableBattery(FCEUFILE *fp) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int LoadPRG(FCEUFILE *fp) {
|
static int LoadPRG(FCEUFILE *fp) {
|
||||||
int z, t;
|
int z;
|
||||||
|
uint32 t;
|
||||||
z = uchead.ID[3] - '0';
|
z = uchead.ID[3] - '0';
|
||||||
|
|
||||||
if (z < 0 || z > 15)
|
if (z < 0 || z > 15)
|
||||||
return(0);
|
return(0);
|
||||||
|
/* uchead.info is uint32 from the file; reject sizes that would either
|
||||||
|
* overflow when added below, or that are obviously bogus. The largest
|
||||||
|
* legitimate single PRG chunk is in the low MiB. Cap at 64 MiB which
|
||||||
|
* is well above reality but still safe. */
|
||||||
|
if (uchead.info == 0 || uchead.info > (64u << 20))
|
||||||
|
return(0);
|
||||||
FCEU_printf(" PRG ROM %d size: %d\n", z, (int)uchead.info);
|
FCEU_printf(" PRG ROM %d size: %d\n", z, (int)uchead.info);
|
||||||
if (malloced[z])
|
if (malloced[z])
|
||||||
free(malloced[z]);
|
free(malloced[z]);
|
||||||
@@ -298,7 +315,6 @@ static int LoadPRG(FCEUFILE *fp) {
|
|||||||
if (!(malloced[z] = (uint8*)FCEU_malloc(t)))
|
if (!(malloced[z] = (uint8*)FCEU_malloc(t)))
|
||||||
return(0);
|
return(0);
|
||||||
mallocedsizes[z] = t;
|
mallocedsizes[z] = t;
|
||||||
memset(malloced[z] + uchead.info, 0xFF, t - uchead.info);
|
|
||||||
if (FCEU_fread(malloced[z], 1, uchead.info, fp) != uchead.info) {
|
if (FCEU_fread(malloced[z], 1, uchead.info, fp) != uchead.info) {
|
||||||
FCEU_printf("Read Error!\n");
|
FCEU_printf("Read Error!\n");
|
||||||
return(0);
|
return(0);
|
||||||
@@ -312,6 +328,14 @@ static int LoadPRG(FCEUFILE *fp) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int SetBoardName(FCEUFILE *fp) {
|
static int SetBoardName(FCEUFILE *fp) {
|
||||||
|
/* The subsequent 4-byte memcmp()s for "NES-", "UNL-", "HVC-", "BTL-",
|
||||||
|
* "BMC-" require the boardname buffer to be at least 4 bytes long.
|
||||||
|
* A malformed UNIF file can declare a MAPR chunk with size < 4, which
|
||||||
|
* would otherwise produce a heap out-of-bounds read. */
|
||||||
|
if (uchead.info < 4) {
|
||||||
|
FCEU_PrintError(" MAPR chunk too small (%u bytes); ignoring.\n", (unsigned)uchead.info);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
if (!(boardname = (uint8*)FCEU_malloc(uchead.info + 1)))
|
if (!(boardname = (uint8*)FCEU_malloc(uchead.info + 1)))
|
||||||
return(0);
|
return(0);
|
||||||
FCEU_fread(boardname, 1, uchead.info, fp);
|
FCEU_fread(boardname, 1, uchead.info, fp);
|
||||||
@@ -329,10 +353,13 @@ static int SetBoardName(FCEUFILE *fp) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int LoadCHR(FCEUFILE *fp) {
|
static int LoadCHR(FCEUFILE *fp) {
|
||||||
int z, t;
|
int z;
|
||||||
|
uint32 t;
|
||||||
z = uchead.ID[3] - '0';
|
z = uchead.ID[3] - '0';
|
||||||
if (z < 0 || z > 15)
|
if (z < 0 || z > 15)
|
||||||
return(0);
|
return(0);
|
||||||
|
if (uchead.info == 0 || uchead.info > (64u << 20))
|
||||||
|
return(0);
|
||||||
FCEU_printf(" CHR ROM %d size: %d\n", z, (int)uchead.info);
|
FCEU_printf(" CHR ROM %d size: %d\n", z, (int)uchead.info);
|
||||||
if (malloced[16 + z])
|
if (malloced[16 + z])
|
||||||
free(malloced[16 + z]);
|
free(malloced[16 + z]);
|
||||||
@@ -340,7 +367,6 @@ static int LoadCHR(FCEUFILE *fp) {
|
|||||||
if (!(malloced[16 + z] = (uint8*)FCEU_malloc(t)))
|
if (!(malloced[16 + z] = (uint8*)FCEU_malloc(t)))
|
||||||
return(0);
|
return(0);
|
||||||
mallocedsizes[16 + z] = t;
|
mallocedsizes[16 + z] = t;
|
||||||
memset(malloced[16 + z] + uchead.info, 0xFF, t - uchead.info);
|
|
||||||
if (FCEU_fread(malloced[16 + z], 1, uchead.info, fp) != uchead.info) {
|
if (FCEU_fread(malloced[16 + z], 1, uchead.info, fp) != uchead.info) {
|
||||||
FCEU_printf("Read Error!\n");
|
FCEU_printf("Read Error!\n");
|
||||||
return(0);
|
return(0);
|
||||||
@@ -750,7 +776,12 @@ int UNIFLoad(const char *name, FCEUFILE *fp) {
|
|||||||
int x = 0;
|
int x = 0;
|
||||||
|
|
||||||
FCEU_fseek(fp, 0, SEEK_SET);
|
FCEU_fseek(fp, 0, SEEK_SET);
|
||||||
FCEU_fread(&unhead, 1, 4, fp);
|
/* unhead is a file-scope static; if FCEU_fread reads fewer than 4 bytes
|
||||||
|
* (e.g. on a malformed empty file), the static would retain whatever
|
||||||
|
* was left from a previous successful load and the memcmp could
|
||||||
|
* spuriously succeed. Verify the read first. */
|
||||||
|
if (FCEU_fread(&unhead, 1, 4, fp) != 4)
|
||||||
|
return 0;
|
||||||
if (memcmp(&unhead, "UNIF", 4))
|
if (memcmp(&unhead, "UNIF", 4))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
|||||||
11
src/video.c
11
src/video.c
@@ -58,7 +58,12 @@ int FCEU_InitVirtualVideo(void)
|
|||||||
XDBuf = (uint8*)(FCEU_malloc(256 * (256 + extrascanlines + 8)));
|
XDBuf = (uint8*)(FCEU_malloc(256 * (256 + extrascanlines + 8)));
|
||||||
|
|
||||||
if (!XBuf || !XDBuf)
|
if (!XBuf || !XDBuf)
|
||||||
|
{
|
||||||
|
/* Free whatever did allocate to avoid leaking on retry */
|
||||||
|
if (XBuf) { free(XBuf); XBuf = NULL; }
|
||||||
|
if (XDBuf) { free(XDBuf); XDBuf = NULL; }
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
memset(XBuf, 128, 256 * (256 + extrascanlines + 8));
|
memset(XBuf, 128, 256 * (256 + extrascanlines + 8));
|
||||||
memset(XDBuf, 0, 256 * (256 + extrascanlines + 8));
|
memset(XDBuf, 0, 256 * (256 + extrascanlines + 8));
|
||||||
@@ -93,7 +98,11 @@ void FCEU_DispMessage(enum retro_log_level level, unsigned duration, const char
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
va_start(ap, format);
|
va_start(ap, format);
|
||||||
vsprintf(msg, format, ap);
|
/* Use vsnprintf to bound the write to msg[]. The previous vsprintf
|
||||||
|
* had no length cap; format strings are currently all literals with
|
||||||
|
* controlled args, but a future caller passing a long ROM name into
|
||||||
|
* a %s format would overrun the 512-byte buffer. */
|
||||||
|
vsnprintf(msg, sizeof(msg), format, ap);
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
|
|
||||||
FCEUD_DispMessage(level, duration, msg);
|
FCEUD_DispMessage(level, duration, msg);
|
||||||
|
|||||||
Reference in New Issue
Block a user