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:
U-DESKTOP-SPFP6AQ\twistedtechre
2026-05-04 02:15:40 +02:00
parent 448a231618
commit 1185db89c1
73 changed files with 595 additions and 165 deletions

View File

@@ -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 }
}; };

View File

@@ -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 }
}; };

View File

@@ -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();
} }

View File

@@ -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");
} }

View File

@@ -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 }
}; };

View File

@@ -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 }
}; };

View File

@@ -66,7 +66,7 @@ static void power () {
} }
static void reset () { static void reset () {
pad = ++pad %24; pad = (pad + 1) % 24;
Latch_clear(); Latch_clear();
} }

View File

@@ -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 }
}; };

View File

@@ -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);

View File

@@ -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 }
}; };

View File

@@ -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");
} }

View File

@@ -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();
} }

View File

@@ -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();
} }

View File

@@ -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;

View File

@@ -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" },
{ &reg, 1, "REG" }, { &reg, 1, "REG" },
{ &reg, 1, "OUTE" }, { &reg, 1, "OUTE" },
{ 0 } { 0 }

View File

@@ -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);
} }

View File

@@ -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 }

View File

@@ -114,5 +114,5 @@ void Mapper416_Init(CartInfo *info) {
AddExState(&reg, 1, 0, "REGS"); AddExState(&reg, 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");
} }

View File

@@ -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 }
}; };

View File

@@ -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 }
}; };

View File

@@ -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" },
{ &reg, 1, "REG" }, { &reg, 1, "REG" },
{ &swap, 1, "SWAP" }, { &swap, 1, "SWAP" },
{ 0 } { 0 }

View File

@@ -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");
} }

View File

@@ -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 }
}; };

View File

@@ -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" },
{ &reg, 1, "REG" }, { &reg, 1, "REG" },
{ 0 } { 0 }
}; };

View File

@@ -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);

View File

@@ -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 }
}; };

View File

@@ -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 }
}; };

View File

@@ -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 }
}; };

View File

@@ -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 }
}; };

View File

@@ -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();
} }

View File

@@ -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 }
}; };

View File

@@ -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();
} }

View File

@@ -33,9 +33,9 @@ static SFORMAT StateRegs[] =
{ {
{ &mirr, 1, "MIRR" }, { &mirr, 1, "MIRR" },
{ &reg, 1, "REGS" }, { &reg, 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 }
}; };

View File

@@ -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();
} }

View File

@@ -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 }
}; };

View File

@@ -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 ---------------------------*/

View File

@@ -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 }
}; };

View File

@@ -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;
} }

View File

@@ -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 }
}; };

View File

@@ -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 }
}; };

View File

@@ -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");

View File

@@ -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;
}
} }
} }

View File

@@ -33,7 +33,7 @@ static SFORMAT StateRegs[] =
{ {
{ &reg, 1, "REG" }, { &reg, 1, "REG" },
{ &IRQa, 1, "IRQA" }, { &IRQa, 1, "IRQA" },
{ &IRQCount, 4, "IRQC" }, { &IRQCount, 4 | FCEUSTATE_RLSB, "IRQC" },
{ 0 } { 0 }
}; };

View File

@@ -24,7 +24,7 @@ static uint16 latche;
static SFORMAT StateRegs[] = static SFORMAT StateRegs[] =
{ {
{ &latche, 2, "LATC" }, { &latche, 2 | FCEUSTATE_RLSB, "LATC" },
{ 0 } { 0 }
}; };

View File

@@ -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();
} }

View File

@@ -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 }
}; };

View File

@@ -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;

View File

@@ -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" },
{ &reg4242, 1, "4242" }, { &reg4242, 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];

View File

@@ -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 }
}; };

View File

@@ -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");
} }

View File

@@ -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();
} }

View File

@@ -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");

View File

@@ -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 }
}; };

View File

@@ -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 }
}; };

View File

@@ -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 }
}; };

View File

@@ -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 }
}; };

View File

@@ -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" },

View File

@@ -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])

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;
} }

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);
} }
} }

View File

@@ -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;

View File

@@ -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. */
} }

View File

@@ -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;

View File

@@ -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);