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[] =
{
{ &IRQCount, 4, "IRQC" },
{ &IRQa, 4, "IRQA" },
{ &IRQCount, 4 | FCEUSTATE_RLSB, "IRQC" },
{ &IRQa, 4 | FCEUSTATE_RLSB, "IRQA" },
{ &prg, 1, "PRG" },
{ 0 }
};

View File

@@ -28,7 +28,7 @@ static uint32 WRAMSIZE;
static SFORMAT StateRegs[] =
{
{ &IRQa, 1, "IRQA" },
{ &IRQCount, 4, "IRQC" },
{ &IRQCount, 4 | FCEUSTATE_RLSB, "IRQC" },
{ reg, 16, "REGS" },
{ 0 }
};

View File

@@ -76,6 +76,7 @@ static void M112Power(void) {
}
static void StateRestore(int version) {
cmd &= 7; /* reg[] has 8 entries */
Sync();
}

View File

@@ -52,5 +52,5 @@ void Mapper128_Init(CartInfo *info) {
Latch_Init(info, Sync, NULL, 0, 0);
info->Power = M128Power;
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" },
{ &mirr, 1, "MIRR" },
{ &IRQa, 1, "IRQA" },
{ &IRQCount, 4, "IRQC" },
{ &IRQLatch, 4, "IRQL" },
{ &IRQCount, 4 | FCEUSTATE_RLSB, "IRQC" },
{ &IRQLatch, 4 | FCEUSTATE_RLSB, "IRQL" },
{ 0 }
};

View File

@@ -26,7 +26,7 @@ static uint16 areg;
static SFORMAT StateRegs[] =
{
{ mram, 4, "MRAM" },
{ &areg, 2, "AREG" },
{ &areg, 2 | FCEUSTATE_RLSB, "AREG" },
{ &vreg, 1, "VREG" },
{ 0 }
};

View File

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

View File

@@ -30,8 +30,8 @@ static SFORMAT StateRegs[] =
{ regs, 8, "PREG" },
{ &mirr, 1, "MIRR" },
{ &IRQa, 1, "IRQA" },
{ &IRQCount, 2, "IRQC" },
{ &IRQLatch, 2, "IRQL" },
{ &IRQCount, 2 | FCEUSTATE_RLSB, "IRQC" },
{ &IRQLatch, 2 | FCEUSTATE_RLSB, "IRQL" },
{ 0 }
};

View File

@@ -36,7 +36,7 @@ static SFORMAT StateRegs[] =
{ CHR, 8, "CHR" },
{ NTAPage, 4, "NT" },
{ &IRQa, 1, "IRQA" },
{ &IRQCount, 2, "IRQC" },
{ &IRQCount, 2 | FCEUSTATE_RLSB, "IRQC" },
{ 0 }
};
@@ -127,8 +127,15 @@ static void StateRestore(int version) {
FixNTAR();
}
static void M330Close(void) {
if (WRAM)
FCEU_gfree(WRAM);
WRAM = NULL;
}
void Mapper330_Init(CartInfo *info) {
info->Power = M330Power;
info->Close = M330Close;
MapIRQHook = M330IRQHook;
GameStateRestore = StateRestore;
AddExState(&StateRegs, ~0, 0, 0);

View File

@@ -26,7 +26,7 @@ static uint8 submapper;
static SFORMAT StateRegs[] =
{
{ &latchAddr, 2, "ADDR" },
{ &latchAddr, 2 | FCEUSTATE_RLSB, "ADDR" },
{ &latchData, 1, "DATA" },
{ 0 }
};

View File

@@ -117,5 +117,5 @@ void UNL3DBlock_Init(CartInfo *info) {
info->Reset = M355Reset;
info->Close = M355Close;
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) {
dipswitch &= 3; /* outer_bank[] has 4 entries */
Sync();
}

View File

@@ -100,6 +100,7 @@ static void FP_FASTAPASS(1) M368IRQHook(int a) {
}
static void StateRestore(int version) {
preg &= 7; /* banks[] has 8 entries */
Sync();
}

View File

@@ -107,11 +107,18 @@ static void StateRestore(int version) {
Sync();
}
static void M375Close(void) {
if (WRAM)
FCEU_gfree(WRAM);
WRAM = NULL;
}
void Mapper375_Init(CartInfo *info) {
info->Power = M375Power;
info->Reset = M375Reset;
info->Close = M375Close;
GameStateRestore = StateRestore;
AddExState(&addrlatch, 2, 0, "ADDR");
AddExState(&addrlatch, 2, 1, "ADDR");
AddExState(&datalatch, 1, 0, "DATA");
WRAMSIZE = 8192;

View File

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

View File

@@ -31,7 +31,7 @@ extern uint32 timestamp;
static SFORMAT stateRegs[] ={
{ reg, 4, "REGS" },
{ &pcmAddress, 4, "PCMA" },
{ &pcmAddress, 4 | FCEUSTATE_RLSB, "PCMA" },
{ &pcmControl, 1, "PCMC" },
{ &scanlineCounter, 1, "SLCN" },
{ &scanlineReload, 1, "SLRL" },
@@ -82,7 +82,10 @@ static DECLFW(writeReg) {
}
static void horizontalBlanking () {
scanlineCounter =!scanlineCounter? scanlineReload: --scanlineCounter;
if (!scanlineCounter)
scanlineCounter = scanlineReload;
else
scanlineCounter--;
if (!scanlineCounter && scanlineIRQ) X6502_IRQBegin(FCEU_IQEXT);
}

View File

@@ -26,7 +26,7 @@ static uint8 dipswitch;
static SFORMAT StateRegs[] =
{
{ &latch_addr, 4, "ADDR" },
{ &latch_addr, 4 | FCEUSTATE_RLSB, "ADDR" },
{ &latch_data, 1, "DATA" },
{ &dipswitch, 1, "DIPS" },
{ 0 }

View File

@@ -114,5 +114,5 @@ void Mapper416_Init(CartInfo *info) {
AddExState(&reg, 1, 0, "REGS");
AddExState(&smb2j_reg, 1, 0, "SMBJ");
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" },
{ nt, 4, "NREG" },
{ &IRQa, 1, "IRQA" },
{ &IRQCount, 2, "IRQC" },
{ &IRQCount, 2 | FCEUSTATE_RLSB, "IRQC" },
{ 0 }
};

View File

@@ -33,8 +33,8 @@ static SFORMAT StateRegs[] =
{ &preg, 1, "PREG" },
{ &creg, 1, "CREG" },
{ &mirr, 1, "MIRR" },
{ &IRQCount, 4, "IRQC" },
{ &IRQa, 4, "IRQA" },
{ &IRQCount, 4 | FCEUSTATE_RLSB, "IRQC" },
{ &IRQa, 4 | FCEUSTATE_RLSB, "IRQA" },
{ 0 }
};

View File

@@ -28,8 +28,8 @@ static uint32 IRQCount, IRQa;
static SFORMAT StateRegs[] =
{
{ &IRQCount, 4, "IRQC" },
{ &IRQa, 4, "IRQA" },
{ &IRQCount, 4 | FCEUSTATE_RLSB, "IRQC" },
{ &IRQa, 4 | FCEUSTATE_RLSB, "IRQA" },
{ &reg, 1, "REG" },
{ &swap, 1, "SWAP" },
{ 0 }

View File

@@ -63,5 +63,5 @@ void Mapper434_Init(CartInfo *info) {
info->Reset = Mapper434_Reset;
info->Power = Mapper434_Power;
GameStateRestore = StateRestore;
AddExState(&latch, 2, 0, "LATC");
AddExState(&latch, 2, 1, "LATC");
}

View File

@@ -25,7 +25,7 @@ static uint8 latchData;
static SFORMAT StateRegs[] =
{
{ &latchAddr, 2, "ADDR" },
{ &latchAddr, 2 | FCEUSTATE_RLSB, "ADDR" },
{ &latchData, 1, "DATA" },
{ 0 }
};

View File

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

View File

@@ -114,8 +114,15 @@ static void StateRestore(int version) {
Sync();
}
static void M528Close(void) {
if (WRAM)
FCEU_gfree(WRAM);
WRAM = NULL;
}
void Mapper528_Init(CartInfo *info) {
info->Power = M528Power;
info->Close = M528Close;
GameStateRestore = StateRestore;
VRCIRQ_Init();
AddExState(&StateRegs, ~0, 0, 0);

View File

@@ -25,7 +25,7 @@ static uint16 mode;
static SFORMAT StateRegs[] =
{
{ &bank, 1, "BANK" },
{ &mode, 2, "MODE" },
{ &mode, 2 | FCEUSTATE_RLSB, "MODE" },
{ 0 }
};

View File

@@ -30,8 +30,8 @@ static SFORMAT StateRegs[] =
{ creg, 8, "CREG" },
{ &mirr, 1, "MIRR" },
{ &IRQa, 1, "IRQA" },
{ &IRQCount, 2, "IRQC" },
{ &IRQLatch, 2, "IRQL" },
{ &IRQCount, 2 | FCEUSTATE_RLSB, "IRQC" },
{ &IRQLatch, 2 | FCEUSTATE_RLSB, "IRQL" },
{ 0 }
};

View File

@@ -31,8 +31,8 @@ static SFORMAT StateRegs[] =
{ creg, 4, "CREG" },
{ &mirr, 1, "MIRR" },
{ &IRQa, 1, "IRQA" },
{ &IRQCount, 2, "IRQC" },
{ &IRQLatch, 2, "IRQL" },
{ &IRQCount, 2 | FCEUSTATE_RLSB, "IRQC" },
{ &IRQLatch, 2 | FCEUSTATE_RLSB, "IRQL" },
{ 0 }
};

View File

@@ -33,7 +33,7 @@ static SFORMAT StateRegs[] =
{ &mirr, 1, "MIRR" },
{ &prg_reg, 1, "PRG" },
{ &kogame, 1, "KGME" },
{ &count, 4, "CNT" },
{ &count, 4 | FCEUSTATE_RLSB, "CNT" },
{ chr_reg, 4, "CHR" },
{ 0 }
};

View File

@@ -36,7 +36,7 @@ static SFORMAT StateRegs[] =
{ creg, 8, "CREG" },
{ &mirr, 1, "MIRR" },
{ &IRQa, 1, "IRQA" },
{ &IRQCount, 4, "IRQC" },
{ &IRQCount, 4 | FCEUSTATE_RLSB, "IRQC" },
{ 0 }
};
@@ -300,6 +300,7 @@ static void M69IRQHook(int a) {
}
static void StateRestore(int version) {
if (sndcmd >= 14) sndcmd = 0; /* sreg[] has 14 entries */
Sync();
}

View File

@@ -42,7 +42,7 @@ static SFORMAT StateRegs95[] =
{ preg, 3, "PREG" },
{ creg, 6, "CREG" },
{ mcache, 8, "MCCH" },
{ &lastppu, 4, "LPPU" },
{ &lastppu, 4 | FCEUSTATE_RLSB, "LPPU" },
{ 0 }
};
@@ -51,7 +51,7 @@ static SFORMAT StateRegs207[] =
{ preg, 3, "PREG" },
{ creg, 6, "CREG" },
{ mcache, 8, "MCCH" },
{ &lastppu, 4, "LPPU" },
{ &lastppu, 4 | FCEUSTATE_RLSB, "LPPU" },
{ 0 }
};

View File

@@ -64,6 +64,7 @@ static void M88Power(void) {
}
static void StateRestore(int version) {
cmd &= 7; /* reg[] has 8 entries; clamp savestate value */
Sync();
MSync();
}

View File

@@ -33,9 +33,9 @@ static SFORMAT StateRegs[] =
{
{ &mirr, 1, "MIRR" },
{ &reg, 1, "REGS" },
{ &IRQa, 4, "IRQA" },
{ &IRQCount, 4, "IRQC" },
{ &IRQLatch, 4, "IRQL" },
{ &IRQa, 4 | FCEUSTATE_RLSB, "IRQA" },
{ &IRQCount, 4 | FCEUSTATE_RLSB, "IRQC" },
{ &IRQLatch, 4 | FCEUSTATE_RLSB, "IRQL" },
{ &latche, 1, "LATC" },
{ 0 }
};

View File

@@ -94,6 +94,7 @@ static void Close(void) {
}
static void StateRestore(int version) {
cmd &= 7; /* reg[] has 8 entries; clamp savestate value */
WSync();
}

View File

@@ -34,8 +34,8 @@ static SFORMAT StateRegs[] =
{
{ reg, 8, "REGS" },
{ &IRQa, 1, "IRQA" },
{ &IRQCount, 2, "IRQC" },
{ &IRQLatch, 2, "IRQL" },
{ &IRQCount, 2 | FCEUSTATE_RLSB, "IRQC" },
{ &IRQLatch, 2 | FCEUSTATE_RLSB, "IRQL" },
{ 0 }
};

View File

@@ -87,7 +87,7 @@ static void Latch_Init(CartInfo *info, void (*proc)(void), readfunc func, uint16
AddExState(WRAM, WRAMSIZE, 0, "WRAM");
}
GameStateRestore = StateRestore;
AddExState(&latche, 2, 0, "LATC");
AddExState(&latche, 2, 1, "LATC");
}
/*------------------ BMCD1038 ---------------------------*/

View File

@@ -28,7 +28,7 @@ uint16 Latch_address;
uint8 Latch_data;
static SFORMAT Latch_state[] = {
{&Latch_address, 2, "LATA" },
{&Latch_address, 2 | FCEUSTATE_RLSB, "LATA" },
{&Latch_data, 1, "LATD" },
{ 0 }
};

View File

@@ -107,7 +107,10 @@ void MMC3_syncMirror () {
void MMC3_clockCounter () {
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);
MMC3_reloadRequest = 0;
}

View File

@@ -30,8 +30,8 @@ static uint16 VRC3_reload;
static SFORMAT VRC3_state[] = {
{&VRC3_prg, 1, "VC3P" },
{&VRC3_irq, 1, "VC3I" },
{&VRC3_count, 1, "VC3C" },
{&VRC3_reload, 1, "VC3R" },
{&VRC3_count, 2 | FCEUSTATE_RLSB, "VC3C" },
{&VRC3_reload, 2 | FCEUSTATE_RLSB, "VC3R" },
{ 0 }
};

View File

@@ -35,7 +35,7 @@ static SFORMAT StateRegs[] =
{
{ preg, 4, "PREG" },
{ creg, 8, "CREG" },
{ &IRQCount, 4, "IRQC" },
{ &IRQCount, 4 | FCEUSTATE_RLSB, "IRQC" },
{ 0 }
};

View File

@@ -2255,6 +2255,11 @@ static void COOLGIRL_Restore(int version) {
}
#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) {
size_t i;
@@ -2296,27 +2301,27 @@ void COOLGIRL_Init(CartInfo *info) {
ExState(mirroring, "MIRR");
ExState(four_screen, "4SCR");
ExState(lockout, "LOCK");
ExState(prg_base, "PBAS");
ExState(prg_mask, "PMSK");
ExStateLE(prg_base, "PBAS");
ExStateLE(prg_mask, "PMSK");
ExState(prg_mode, "PMOD");
ExState(prg_bank_6000, "P6BN");
ExState(prg_bank_a, "PABN");
ExState(prg_bank_b, "PBBN");
ExState(prg_bank_c, "PCBN");
ExState(prg_bank_d, "PDBN");
ExState(chr_mask, "CMSK");
ExStateLE(chr_mask, "CMSK");
ExState(chr_mode, "CMOD");
ExState(chr_bank_a, "CABN");
ExState(chr_bank_b, "CBBN");
ExState(chr_bank_c, "CCBN");
ExState(chr_bank_d, "CDBN");
ExState(chr_bank_e, "CEBN");
ExState(chr_bank_f, "CFBN");
ExState(chr_bank_g, "CGBN");
ExState(chr_bank_h, "CHBN");
ExStateLE(chr_bank_a, "CABN");
ExStateLE(chr_bank_b, "CBBN");
ExStateLE(chr_bank_c, "CCBN");
ExStateLE(chr_bank_d, "CDBN");
ExStateLE(chr_bank_e, "CEBN");
ExStateLE(chr_bank_f, "CFBN");
ExStateLE(chr_bank_g, "CGBN");
ExStateLE(chr_bank_h, "CHBN");
ExState(ppu_latch0, "PPU0");
ExState(ppu_latch1, "PPU1");
ExState(lreset, "LRST");
ExStateLE(lreset, "LRST");
ExState(mmc1_load_register, "M01R");
ExState(mmc3_internal, "M01I");
ExState(mapper69_internal, "M69I");
@@ -2337,32 +2342,32 @@ void COOLGIRL_Init(CartInfo *info) {
ExState(mmc5_irq_enabled, "M5IE");
ExState(mmc5_irq_line, "M5IL");
ExState(mmc5_irq_out, "M5IO");
ExState(mapper18_irq_value, "18IV");
ExStateLE(mapper18_irq_value, "18IV");
ExState(mapper18_irq_control, "18IC");
ExState(mapper18_irq_latch, "18IL");
ExStateLE(mapper18_irq_latch, "18IL");
ExState(mapper65_irq_enabled, "65IE");
ExState(mapper65_irq_value, "65IV");
ExState(mapper65_irq_latch, "65IL");
ExStateLE(mapper65_irq_value, "65IV");
ExStateLE(mapper65_irq_latch, "65IL");
ExState(mapper69_irq_enabled, "69IE");
ExState(mapper69_counter_enabled, "69CE");
ExState(mapper69_irq_value, "69IV");
ExStateLE(mapper69_irq_value, "69IV");
ExState(vrc4_irq_value, "V4IV");
ExState(vrc4_irq_control, "V4IC");
ExState(vrc4_irq_latch, "V4IL");
ExState(vrc4_irq_prescaler, "V4PP");
ExState(vrc4_irq_prescaler_counter, "V4PC");
ExState(vrc3_irq_value, "V3IV");
ExStateLE(vrc3_irq_value, "V3IV");
ExState(vrc3_irq_control, "V3IC");
ExState(vrc3_irq_latch, "V3IL");
ExStateLE(vrc3_irq_latch, "V3IL");
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, "M83I");
ExState(mapper83_irq_counter, "M83C");
ExStateLE(mapper83_irq_counter, "M83C");
ExState(mapper90_xor, "90XR");
ExState(mapper67_irq_enabled, "67IE");
ExState(mapper67_irq_latch, "67IL");
ExState(mapper67_irq_counter, "67IC");
ExStateLE(mapper67_irq_counter, "67IC");
ExState(flash_state, "FLST");
ExState(flash_buffer_a, "FLBA");
ExState(flash_buffer_v, "FLBV");

View File

@@ -194,16 +194,20 @@ static void clockIRQ (void)
if (irqEnabled)
switch (irqControl &0xC0)
{
case 0x40:
irqPrescaler =(irqPrescaler &~mask) | (++irqPrescaler &mask);
if ((irqPrescaler &mask) ==0x00 && (irqControl &0x08? irqCounter: ++irqCounter) ==0x00)
case 0x40: {
uint8_t newp = (irqPrescaler + 1) & mask;
irqPrescaler = (irqPrescaler & ~mask) | newp;
if (newp == 0x00 && (irqControl &0x08? irqCounter: ++irqCounter) ==0x00)
X6502_IRQBegin(FCEU_IQEXT);
break;
case 0x80:
irqPrescaler =(irqPrescaler &~mask) | (--irqPrescaler &mask);
if ((irqPrescaler &mask) ==mask && (irqControl &0x08? irqCounter: --irqCounter) ==0xFF)
}
case 0x80: {
uint8_t newp = (irqPrescaler - 1) & mask;
irqPrescaler = (irqPrescaler & ~mask) | newp;
if (newp == mask && (irqControl &0x08? irqCounter: --irqCounter) ==0xFF)
X6502_IRQBegin(FCEU_IQEXT);
break;
}
}
}

View File

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

View File

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

View File

@@ -103,6 +103,11 @@ static void MMC2and4Power(void) {
}
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();
}

View File

@@ -1520,8 +1520,8 @@ static uint32 m555_count_target = 0x20000000;
static SFORMAT M555StateRegs[] = {
{ m555_reg, 2, "REGS" },
{ &m555_count, 2, "CNTR" },
{ &m555_count_expired, 2, "CNTE" },
{ &m555_count, 4 | FCEUSTATE_RLSB, "CNTR" },
{ &m555_count_expired, 1, "CNTE" },
{ 0 }
};

View File

@@ -670,6 +670,20 @@ void NSFMMC5_Close(void) {
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) {
int x;
@@ -798,6 +812,7 @@ static void GenMMC5_Init(CartInfo *info, int wsize, int battery) {
BuildWRAMSizeTable();
GameStateRestore = MMC5_StateRestore;
info->Power = GenMMC5Reset;
info->Close = GenMMC5_Close;
if (battery) {
info->SaveGame[0] = WRAM;

View File

@@ -61,10 +61,10 @@ static SFORMAT StateRegs[] =
{ &IRQa, 1, "IRQA" },
{ &pcm_enable, 1, "PCME" },
{ &pcm_irq, 1, "PCMI" },
{ &pcm_addr, 2, "PCMA" },
{ &pcm_size, 2, "PCMS" },
{ &pcm_latch, 2, "PCML" },
{ &pcm_clock, 2, "PCMC" },
{ &pcm_addr, 2 | FCEUSTATE_RLSB, "PCMA" },
{ &pcm_size, 2 | FCEUSTATE_RLSB, "PCMS" },
{ &pcm_latch, 2 | FCEUSTATE_RLSB, "PCML" },
{ &pcm_clock, 2 | FCEUSTATE_RLSB, "PCMC" },
{ &reg4242, 1, "4242" },
{ &dipswitch, 1, "DIPS" },
{ 0 }
@@ -375,6 +375,12 @@ void UNLOneBus_Close(void) {
if (WRAM)
FCEU_gfree(WRAM);
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) {
@@ -385,8 +391,16 @@ void UNLOneBus_Init(CartInfo *info) {
if (info->iNES2)
submapper =info->submapper;
else
submapper =(((*(uint32*)&(info->MD5)) == 0x305fcdc3) || ((*(uint32*)&(info->MD5)) == 0x6abfce8e))? 2: 0; /* PowerJoy Supermax Carts */
else {
/* 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];
ppuMangle =ppuMangles[submapper];

View File

@@ -28,7 +28,7 @@ static uint32 WRAMSIZE;
static SFORMAT StateRegs[] =
{
{ reg, 2, "REG" },
{ &lastnt, 4, "LNT" },
{ &lastnt, 4 | FCEUSTATE_RLSB, "LNT" },
{ 0 }
};

View File

@@ -977,25 +977,25 @@ void pic16c5x_run(void)
void AddExState(void *v, uint32 s, int type, char *desc);
void pic16c5x_add_statesinfo(void) {
AddExState(&m_PC, sizeof(m_PC), 0, "PC00");
AddExState(&m_PREVPC, sizeof(m_PREVPC), 0, "PRPC");
AddExState(&m_PC, sizeof(m_PC), 1, "PC00");
AddExState(&m_PREVPC, sizeof(m_PREVPC), 1, "PRPC");
AddExState(&m_W, sizeof(m_W), 0, "W000");
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_WDT, sizeof(m_WDT), 0, "WDT");
AddExState(&m_WDT, sizeof(m_WDT), 1, "WDT");
AddExState(&m_TRISA, sizeof(m_TRISA), 0, "TRSA");
AddExState(&m_TRISB, sizeof(m_TRISB), 0, "TRSB");
AddExState(&m_TRISC, sizeof(m_TRISC), 0, "TRSC");
AddExState(&m_STACK[0], sizeof(m_STACK[0]), 0, "STC0");
AddExState(&m_STACK[1], sizeof(m_STACK[1]), 0, "STC1");
AddExState(&m_prescaler, sizeof(m_prescaler), 0, "PSCL");
AddExState(&m_opcode, sizeof(m_opcode), 0, "OPS0");
AddExState(&m_STACK[0], sizeof(m_STACK[0]), 1, "STC0");
AddExState(&m_STACK[1], sizeof(m_STACK[1]), 1, "STC1");
AddExState(&m_prescaler, sizeof(m_prescaler), 1, "PSCL");
AddExState(&m_opcode, sizeof(m_opcode), 1, "OPS0");
AddExState(m_internalram, sizeof(m_internalram), 0, "IRAM");
AddExState(&m_icount, sizeof(m_icount), 0, "ICNT");
AddExState(&m_delay_timer, sizeof(m_delay_timer), 0, "DTIM");
AddExState(&m_rtcc, sizeof(m_rtcc), 0, "RTCC");
AddExState(&m_icount, sizeof(m_icount), 1, "ICNT");
AddExState(&m_delay_timer, sizeof(m_delay_timer), 1, "DTIM");
AddExState(&m_rtcc, sizeof(m_rtcc), 1, "RTCC");
AddExState(&m_count_pending, sizeof(m_count_pending), 0, "CNTP");
AddExState(&m_inst_cycles, sizeof(m_inst_cycles), 0, "ICYC");
AddExState(&m_clock2cycle, sizeof(m_clock2cycle), 0, "CL2C");
AddExState(&m_inst_cycles, sizeof(m_inst_cycles), 1, "ICYC");
AddExState(&m_clock2cycle, sizeof(m_clock2cycle), 1, "CL2C");
}

View File

@@ -90,6 +90,7 @@ static void S8259Reset(void) {
}
static void S8259Restore(int version) {
cmd &= 7; /* latch[] has 8 entries; clamp savestate value */
S8259Synco();
}
@@ -298,6 +299,7 @@ static DECLFW(S74LS374NWrite) {
}
static void S74LS374NRestore(int version) {
cmd &= 7; /* latch[] has 8 entries; clamp savestate value */
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) {
flash_write_count[(bank * 4) + ((A & 0x3000) >> 12)]++;
if (!flash_write_count[(bank * 4) + ((A & 0x3000) >> 12)])
flash_write_count[(bank * 4) + ((A & 0x3000) >> 12)]++;
uint32 idx = (bank * 4) + ((A & 0x3000) >> 12);
uint32 v = fwc_load(idx) + 1;
if (v == 0) v = 1; /* avoid wrap to 0 (which means "never written") */
fwc_store(idx, v);
}
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) {
/* 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();
}
@@ -261,7 +295,7 @@ void UNROM512_Init(CartInfo *info) {
AddExState(&flash_state, 1, 0, "FLASH_STATE");
AddExState(&flash_mode, 1, 0, "FLASH_MODE");
AddExState(&flash_bank, 1, 0, "FLASH_BANK");
AddExState(&latcha, 2, 0, "LATA");
AddExState(&latcha, 2, 1, "LATA");
}
AddExState(&latche, 1, 0, "LATC");
AddExState(&bus_conflict, 1, 0, "BUSC");

View File

@@ -37,8 +37,8 @@ static SFORMAT StateRegs[] =
{ &IRQa, 1, "IRQA" },
{ &IRQx, 1, "IRQX" },
{ &IRQm, 1, "IRQM" },
{ &IRQReload, 2, "IRQR" },
{ &IRQCount, 2, "IRQC" },
{ &IRQReload, 2 | FCEUSTATE_RLSB, "IRQR" },
{ &IRQCount, 2 | FCEUSTATE_RLSB, "IRQC" },
{ 0 }
};

View File

@@ -38,8 +38,8 @@ static SFORMAT StateRegs[] =
{ &IRQa, 1, "IRQA" },
{ &IRQd, 1, "IRQD" },
{ &IRQLatch, 1, "IRQL" },
{ &IRQCount, 4, "IRQC" },
{ &CycleCount, 4, "CYCC" },
{ &IRQCount, 4 | FCEUSTATE_RLSB, "IRQC" },
{ &CycleCount, 4 | FCEUSTATE_RLSB, "CYCC" },
{ 0 }
};

View File

@@ -38,8 +38,8 @@ static SFORMAT StateRegs[] =
{ &IRQa, 1, "IRQA" },
{ &IRQd, 1, "IRQD" },
{ &IRQLatch, 1, "IRQL" },
{ &IRQCount, 4, "IRQC" },
{ &CycleCount, 4, "CYCC" },
{ &IRQCount, 4 | FCEUSTATE_RLSB, "IRQC" },
{ &CycleCount, 4 | FCEUSTATE_RLSB, "CYCC" },
{ 0 }
};

View File

@@ -34,8 +34,8 @@ static SFORMAT StateRegs[] =
{ &IRQa, 1, "IRQA" },
{ &IRQd, 1, "IRQD" },
{ &IRQLatch, 1, "IRQL" },
{ &IRQCount, 4, "IRQC" },
{ &CycleCount, 4, "CYCC" },
{ &IRQCount, 4 | FCEUSTATE_RLSB, "IRQC" },
{ &CycleCount, 4 | FCEUSTATE_RLSB, "CYCC" },
{ 0 }
};

View File

@@ -10,7 +10,7 @@ static uint8 IRQm;
static SFORMAT StateRegs[] =
{
{ &IRQPrescaler, 2, "PREC" },
{ &IRQPrescaler, 2 | FCEUSTATE_RLSB, "PREC" },
{ &IRQCount, 1, "IRQC" },
{ &IRQLatch, 1, "IRQL" },
{ &IRQd, 1, "IRQD" },

View File

@@ -151,8 +151,18 @@ DECLFR(CartBROB) {
}
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];
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) {
@@ -160,8 +170,23 @@ void FASTAPASS(2) setprg2(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];
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) {
@@ -220,6 +245,7 @@ void FASTAPASS(2) setprg32(uint32 A, uint32 V) {
void FASTAPASS(3) setchr1r(int r, uint32 A, uint32 V) {
if (!CHRptr[r]) return;
if (CHRsize[r] < 1024) return; /* mask underflow guard */
FCEUPPU_LineUpdate();
V &= CHRmask1[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) {
if (!CHRptr[r]) return;
if (CHRsize[r] < 2048) return; /* mask underflow guard */
FCEUPPU_LineUpdate();
V &= CHRmask2[r];
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) {
if (!CHRptr[r]) return;
if (CHRsize[r] < 4096) return; /* mask underflow guard */
FCEUPPU_LineUpdate();
V &= CHRmask4[r];
VPageR[(A) >> 10] = VPageR[((A) >> 10) + 1] =
@@ -257,7 +285,15 @@ void FASTAPASS(2) setchr8r(int r, uint32 V) {
if (!CHRptr[r]) return;
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--)
VPageR[x] = &CHRptr[r][V << 13];
if (CHRram[r])

View File

@@ -114,13 +114,21 @@ void RebuildSubCheats(void) {
if (c->type == 1 && c->status) {
if (GetReadHandler(c->addr) == SubCheatsRead) {
/* Prevent a catastrophe by this check. */
} else {
} else if (numsubcheats < (int)(sizeof(SubCheats) / sizeof(SubCheats[0]))) {
SubCheats[numsubcheats].PrevRead = GetReadHandler(c->addr);
SubCheats[numsubcheats].addr = c->addr;
SubCheats[numsubcheats].val = c->val;
SubCheats[numsubcheats].compare = c->compare;
SetReadHandler(c->addr, c->addr, SubCheatsRead);
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;

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 = (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,
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(
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_pos = 0;
@@ -1433,7 +1449,7 @@ void retro_set_controller_port_device(unsigned port, unsigned device)
{
if (device != RETRO_DEVICE_AUTO)
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]));
}
else
@@ -1460,7 +1476,7 @@ void retro_set_controller_port_device(unsigned port, unsigned device)
{
if (device != RETRO_DEVICE_FC_AUTO)
update_nes_controllers(4, device);
else
else if (GameInfo) /* GameInfo may be NULL pre-load */
update_nes_controllers(4, fc_to_libretro(GameInfo->inputfc));
}
@@ -1921,7 +1937,9 @@ void retro_deinit (void)
FCEUI_Sound(0);
FCEUI_Kill();
#if defined(_3DS)
linearFree(fceu_video_out);
if (fceu_video_out)
linearFree(fceu_video_out);
fceu_video_out = NULL;
#else
if (fceu_video_out)
free(fceu_video_out);
@@ -3057,6 +3075,8 @@ size_t retro_serialize_size(void)
{
/* Something arbitrarily big.*/
uint8_t *buffer = (uint8_t*)malloc(1000000);
if (!buffer)
return 0;
memstream_set_buffer(buffer, 1000000);
FCEUSS_Save_Mem();
@@ -3130,7 +3150,7 @@ void retro_cheat_reset(void)
void retro_cheat_set(unsigned index, bool enabled, const char *code)
{
char name[256];
char temp[256];
char temp[1024];
char *codepart;
uint16 a;
uint8 v;
@@ -3140,8 +3160,12 @@ void retro_cheat_set(unsigned index, bool enabled, const char *code)
if (code == NULL)
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");
strcpy(temp, code);
strlcpy(temp, code, sizeof(temp));
codepart = strtok(temp, "+,;._ ");
while (codepart)
@@ -3685,6 +3709,17 @@ bool retro_load_game(const struct retro_game_info *info)
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
/* An error message here is superfluous - the frontend
* will report that content loading has failed */
@@ -3858,7 +3893,7 @@ void *retro_get_memory_data(unsigned type)
return iNESCart.SaveGame[0];
else if (UNIFCart.battery && UNIFCart.SaveGame[0] && UNIFCart.SaveGameLen[0])
return UNIFCart.SaveGame[0];
else if (GameInfo->type == GIT_FDS)
else if (GameInfo && GameInfo->type == GIT_FDS)
return FDSROM_ptr();
else
data = NULL;
@@ -3885,7 +3920,7 @@ size_t retro_get_memory_size(unsigned type)
size = iNESCart.SaveGameLen[0];
else if (UNIFCart.battery && UNIFCart.SaveGame[0] && UNIFCart.SaveGameLen[0])
size = UNIFCart.SaveGameLen[0];
else if (GameInfo->type == GIT_FDS)
else if (GameInfo && GameInfo->type == GIT_FDS)
size = FDSROM_size();
else
size = 0;

View File

@@ -28,12 +28,17 @@
void FlipByteOrder(uint8 *src, uint32 count)
{
uint8 *start = src;
uint8 *end = src + count - 1;
uint8 *end;
if ((count & 1) || !count)
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;

View File

@@ -45,7 +45,7 @@ void *FCEU_malloc(uint32 size)
if (!ret)
{
FCEU_PrintError("Error allocating memory!");
ret = 0;
return NULL;
}
memset(ret, 0, size);
return ret;

View File

@@ -82,7 +82,15 @@ int AllocGenieRW(void)
if (!BWriteG)
{
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;
}
}
else
memset(BWriteG, 0, 0x8000 * sizeof(writefunc));
@@ -240,6 +248,8 @@ FCEUGI *FCEUI_LoadGame(const char *name, const uint8_t *databuf, size_t databufs
ResetGameLoaded();
GameInfo = malloc(sizeof(FCEUGI));
if (!GameInfo)
return NULL;
memset(GameInfo, 0, sizeof(FCEUGI));
GameInfo->soundchan = 0;
@@ -461,7 +471,10 @@ void FCEU_printf(char *format, ...)
va_list ap;
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);
va_end(ap);
@@ -474,7 +487,7 @@ void FCEU_PrintError(char *format, ...)
va_list ap;
va_start(ap, format);
vsprintf(temp, format, ap);
vsnprintf(temp, sizeof(temp), format, ap);
FCEUD_PrintError(temp);
va_end(ap);

View File

@@ -120,6 +120,38 @@ void FDSGI(int h) {
static void FDSStateRestore(int version) {
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);
if (version >= 9810)
@@ -577,6 +609,14 @@ static int SubLoad(FCEUFILE *fp) {
struct md5_context md5;
uint8 header[16];
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);
@@ -679,6 +719,19 @@ int FDSLoad(const char *name, FCEUFILE *fp) {
for (x = 0; x < TotalSides; x++) {
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);
}
@@ -705,22 +758,22 @@ int FDSLoad(const char *name, FCEUFILE *fp) {
AddExState(&FDSRegs[3], 1, 0, "REG4");
AddExState(&FDSRegs[4], 1, 0, "REG5");
AddExState(&FDSRegs[5], 1, 0, "REG6");
AddExState(&IRQCount, 4 | FCEUSTATE_RLSB, 1, "IRQC");
AddExState(&IRQLatch, 4 | FCEUSTATE_RLSB, 1, "IQL1");
AddExState(&IRQCount, 4, 1, "IRQC");
AddExState(&IRQLatch, 4, 1, "IQL1");
AddExState(&IRQa, 1, 0, "IRQA");
AddExState(&writeskip, 1, 0, "WSKI");
AddExState(&DiskPtr, 4 | FCEUSTATE_RLSB, 1, "DPTR");
AddExState(&DiskSeekIRQ, 4 | FCEUSTATE_RLSB, 1, "DSIR");
AddExState(&DiskPtr, 4, 1, "DPTR");
AddExState(&DiskSeekIRQ, 4, 1, "DSIR");
AddExState(&SelectDisk, 1, 0, "SELD");
AddExState(&InDisk, 1, 0, "INDI");
AddExState(&DiskWritten, 1, 0, "DSKW");
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_blockstart, 2 | FCEUSTATE_RLSB, 1, "BLKS");
AddExState(&mapperFDS_blocklen, 2 | FCEUSTATE_RLSB, 1, "BLKL");
AddExState(&mapperFDS_diskaddr, 2 | FCEUSTATE_RLSB, 1, "DADR");
AddExState(&mapperFDS_blockstart, 2, 1, "BLKS");
AddExState(&mapperFDS_blocklen, 2, 1, "BLKL");
AddExState(&mapperFDS_diskaddr, 2, 1, "DADR");
AddExState(&mapperFDS_diskaccess, 1, 0, "DACC");
CHRRAMSize = 8192;

View File

@@ -40,23 +40,30 @@
static MEMWRAP *MakeMemWrap(RFILE *tz)
{
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))))
goto doret;
tmp->location = 0;
tmp->size = (size_t)fsize;
filestream_seek(tz, 0, RETRO_VFS_SEEK_POSITION_END);
tmp->size = filestream_tell(tz);
filestream_seek(tz, 0, RETRO_VFS_SEEK_POSITION_START);
if (!(tmp->data_int = (uint8*)FCEU_malloc(tmp->size)))
if (tmp->size && !(tmp->data_int = (uint8*)FCEU_malloc(tmp->size)))
{
free(tmp);
tmp = NULL;
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;
doret:
@@ -82,8 +89,19 @@ FCEUFILE * FCEU_fopen(const char *path, const uint8 *buffer, size_t bufsize)
{
FCEUFILE *fceufp = (FCEUFILE*)malloc(sizeof(FCEUFILE));
if (!fceufp)
return NULL;
fceufp->fp = NULL;
if (buffer)
{
fceufp->fp = MakeMemWrapBuffer(buffer, bufsize);
if (!fceufp->fp)
{
free(fceufp);
return NULL;
}
}
else
{
RFILE *t = NULL;
@@ -101,6 +119,11 @@ FCEUFILE * FCEU_fopen(const char *path, const uint8 *buffer, size_t bufsize)
fceufp->fp = MakeMemWrap(t);
filestream_close(t);
if (!fceufp->fp)
{
free(fceufp);
return NULL;
}
}
return fceufp;
}

View File

@@ -85,11 +85,21 @@ uint32 uppow2(uint32 n)
{
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--)
if (n & (1 << x))
if (n & (1u << x))
{
if ((1 << x) != n)
return(1 << (x + 1));
if ((1u << x) != n)
return(1u << (x + 1));
break;
}
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.CHRRAM_size & 0x0F) iNESCart.CHRRamSize = 64 << ((head.CHRRAM_size >> 0) & 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);
iNESCart.CHRRomSize =VROM_size >=0xF00? (pow(2, head.VROM_size>>2)*((head.VROM_size&3)*2+1)): (VROM_size*0x2000);;
/* iNES 2.0 exponent encoding: when the 12-bit count >= 0xF00, the byte
* 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.miscROMSize =iNESCart.miscROMNumber? (iNESCart.totalFileSize -16 -(head.ROM_type &4? 512: 0) -iNESCart.PRGRomSize -iNESCart.CHRRomSize): 0;
if (iNESCart.miscROMSize &0x8000000) iNESCart.miscROMSize =0;
if (iNESCart.miscROMNumber) {
/* 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 {
iNESCart.submapper = iNESCart.miscROMNumber = iNESCart.miscROMSize = 0;
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) {
/* 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;
if (!w) {
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) {
if (*(uint8*)data) {
size_t i;
const uint8 *src = (uint8*)data + 1;
*(uint8*)data = 0;
seq = ptr = 0;
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);
}
}

View File

@@ -135,9 +135,14 @@ static INLINE void BANKSET(uint32 A, uint32 bank) {
int NSFLoad(FCEUFILE *fp) {
int x;
uint64 fsize;
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))
return 0;
NSFHeader.SongName[31] = NSFHeader.Artist[31] = NSFHeader.Copyright[31] = 0;
@@ -155,10 +160,32 @@ int NSFLoad(FCEUFILE *fp) {
PlayAddr = NSFHeader.PlayAddressLow;
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 = 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)))
return 0;

View File

@@ -267,7 +267,7 @@ void FCEUSS_Save_Mem(void)
totalsize += WriteStateChunk(mem, 0x10, SFMDATA);
if (SPreSave)
if (SPostSave)
SPostSave();
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 */
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));
if (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;
if (type)
SFMDATA[SFEXINDEX].s |= RLSB;
if (SFEXINDEX < 63)
SFEXINDEX++;
SFEXINDEX++;
SFMDATA[SFEXINDEX].v = 0; /* End marker. */
}

View File

@@ -93,6 +93,10 @@ static int FixRomSize(uint32 size, uint32 minimum) {
if (size < 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)
x <<= 1;
return x;
@@ -198,6 +202,8 @@ static int NAME(FCEUFILE *fp) {
if (!GameInfo->name) {
GameInfo->name = malloc(strlen(namebuf) + 1);
if (!GameInfo->name)
return(0);
strcpy((char*)GameInfo->name, namebuf);
}
return(1);
@@ -229,7 +235,11 @@ static int DINF(FCEUFILE *fp) {
"January", "February", "March", "April", "May", "June", "July",
"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);
}
@@ -286,11 +296,18 @@ static int EnableBattery(FCEUFILE *fp) {
}
static int LoadPRG(FCEUFILE *fp) {
int z, t;
int z;
uint32 t;
z = uchead.ID[3] - '0';
if (z < 0 || z > 15)
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);
if (malloced[z])
free(malloced[z]);
@@ -298,7 +315,6 @@ static int LoadPRG(FCEUFILE *fp) {
if (!(malloced[z] = (uint8*)FCEU_malloc(t)))
return(0);
mallocedsizes[z] = t;
memset(malloced[z] + uchead.info, 0xFF, t - uchead.info);
if (FCEU_fread(malloced[z], 1, uchead.info, fp) != uchead.info) {
FCEU_printf("Read Error!\n");
return(0);
@@ -312,6 +328,14 @@ static int LoadPRG(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)))
return(0);
FCEU_fread(boardname, 1, uchead.info, fp);
@@ -329,10 +353,13 @@ static int SetBoardName(FCEUFILE *fp) {
}
static int LoadCHR(FCEUFILE *fp) {
int z, t;
int z;
uint32 t;
z = uchead.ID[3] - '0';
if (z < 0 || z > 15)
return(0);
if (uchead.info == 0 || uchead.info > (64u << 20))
return(0);
FCEU_printf(" CHR ROM %d size: %d\n", z, (int)uchead.info);
if (malloced[16 + z])
free(malloced[16 + z]);
@@ -340,7 +367,6 @@ static int LoadCHR(FCEUFILE *fp) {
if (!(malloced[16 + z] = (uint8*)FCEU_malloc(t)))
return(0);
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) {
FCEU_printf("Read Error!\n");
return(0);
@@ -750,7 +776,12 @@ int UNIFLoad(const char *name, FCEUFILE *fp) {
int x = 0;
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))
return 0;

View File

@@ -58,7 +58,12 @@ int FCEU_InitVirtualVideo(void)
XDBuf = (uint8*)(FCEU_malloc(256 * (256 + extrascanlines + 8)));
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;
}
memset(XBuf, 128, 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;
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);
FCEUD_DispMessage(level, duration, msg);