Files
LibretroAdmin dcfdb7fecf zipfile: only match entries the reader can decode
Generalising the MSU1 pack reader lost a property the original had. Its
find_entry() tested the compression method as part of matching, so an
entry it could not decode simply did not match and the scan continued.
zip_find_*() matched on name alone, so the first hit could be an entry
using some other method: the caller got an index, zip_file_open()
refused it, and there was no way to tell that a usable entry existed
further down the directory.

Reproduced with a pack holding an lzma "a-1.pcm" ahead of a deflated
"b-1.pcm": snes9x2010 plays the track, this core reported status 0x0a
(AudioError) and stayed silent. The same shape applies to ROM loading
and to patches inside a zip.

Make lookup skip undecodable entries, so a match is always readable,
and apply the same rule when loadzip picks the largest entry.

Verified: the mixed-method pack now plays and matches 2010 exactly. A
zip whose only ROM entry is lzma still fails the load cleanly rather
than crashing; one where an lzma entry precedes a deflated copy loads
the deflated one. The stored, deflated, decoy and program.rom
selection cases and the MSU1 loose/stored/deflated matrix are
unchanged.
2026-08-07 23:27:36 +02:00

173 lines
3.7 KiB
C++

/*****************************************************************************\
Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
This file is licensed under the Snes9x License.
For further information, consult the LICENSE file in the root directory.
\*****************************************************************************/
#include <assert.h>
#include <ctype.h>
#include <stdlib.h>
#include "snes9x.h"
#include "zipfile.h"
#include "memmap.h"
/* Selection rules, unchanged from the minizip implementation: prefer a file
named program.rom or one whose extension is ".1", otherwise take the largest
entry that fits in a cart. A .msu1 pack only ever loads program.rom, since
its other entries are MSU1 companion data rather than the ROM. */
static int find_rom_entry (const struct zip_archive *ar, uint32 *out_size)
{
int best = -1;
uint32 best_size = 0;
int i;
for (i = 0; i < ar->count; i++)
{
const char *name = ar->entries[i].name;
uint32 size = ar->entries[i].uncomp_size;
int len = (int) strlen(name);
if (size > CMemory::MAX_ROM_SIZE + 512)
continue;
/* Skip entries this reader cannot decode rather than selecting one
and failing the load. */
if (ar->entries[i].method != ZIP_METHOD_STORE &&
ar->entries[i].method != ZIP_METHOD_DEFLATE)
continue;
if (size > best_size)
{
best = i;
best_size = size;
}
if (len > 2 && name[len - 2] == '.' && name[len - 1] == '1')
{
best = i;
best_size = size;
break;
}
if (strncasecmp(name, "program.rom", 11) == 0)
{
best = i;
best_size = size;
break;
}
}
*out_size = best_size;
return (best);
}
bool8 LoadZip (const char *zipname, uint32 *TotalFileSize, uint8 *buffer)
{
struct zip_archive ar;
char filename[ZIP_MAX_NAME];
uint32 filesize = 0;
int idx;
int len;
*TotalFileSize = 0;
if (!zip_archive_open(&ar, zipname))
return (FALSE);
idx = find_rom_entry(&ar, &filesize);
if (idx < 0 || filesize == 0)
{
zip_archive_close(&ar);
return (FALSE);
}
strcpy(filename, ar.entries[idx].name);
len = (int) strlen(zipname);
if (len > 5 && strcasecmp(zipname + len - 5, ".msu1") == 0 &&
strcasecmp(filename, "program.rom") != 0)
{
zip_archive_close(&ar);
return (FALSE);
}
// find extension
char tmp[2] = { 0, 0 };
char *ext = strrchr(filename, '.');
if (ext)
ext++;
else
ext = tmp;
uint8 *ptr = buffer;
bool8 more = FALSE;
do
{
uint32 FileSize = ar.entries[idx].uncomp_size;
uint32 got;
assert(FileSize <= CMemory::MAX_ROM_SIZE + 512);
{
struct zip_file zf;
if (!zip_file_open(&zf, &ar, idx))
{
zip_archive_close(&ar);
return (FALSE);
}
got = zip_file_read(&zf, 0, ptr, FileSize);
zip_file_close(&zf);
}
if (got != FileSize)
{
zip_archive_close(&ar);
return (FALSE);
}
FileSize = Memory.HeaderRemove(FileSize, ptr);
ptr += FileSize;
*TotalFileSize += FileSize;
if (ptr - Memory.ROM < CMemory::MAX_ROM_SIZE + 512 && (isdigit(ext[0]) && ext[1] == 0 && ext[0] < '9'))
{
more = TRUE;
ext[0]++;
}
else
if (ptr - Memory.ROM < CMemory::MAX_ROM_SIZE + 512)
{
int nlen;
if (ext == tmp)
nlen = (int) strlen(filename);
else
nlen = (int) (ext - filename - 1);
if ((nlen == 7 || nlen == 8) && strncasecmp(filename, "sf", 2) == 0 &&
isdigit(filename[2]) && isdigit(filename[3]) && isdigit(filename[4]) &&
isdigit(filename[5]) && isalpha(filename[nlen - 1]))
{
more = TRUE;
filename[nlen - 1]++;
}
}
else
more = FALSE;
if (more)
{
idx = zip_find_name(&ar, filename, 0);
if (idx < 0)
break;
}
} while (more);
zip_archive_close(&ar);
return (TRUE);
}