Fix parsing large MVC files causing lockup/crash in GUI (fixes # 829).

Still TODO is deal with large files other than MVC, and also ZIP files.
At least the logic for analyzing size is now in one method (OSystem::openROM).
This commit is contained in:
Stephen Anthony 2022-04-16 19:25:50 -02:30
parent df4900828c
commit fed3fa2241
7 changed files with 70 additions and 50 deletions

View File

@ -351,8 +351,8 @@ size_t FilesystemNode::read(ByteBuffer& buffer, size_t size) const
if (sizeRead == 0) if (sizeRead == 0)
throw runtime_error("Zero-byte file"); throw runtime_error("Zero-byte file");
else if (size != 0) else if (size > 0) // If a requested size to read is provided, honour it
sizeRead = size; sizeRead = std::min(sizeRead, size);
buffer = make_unique<uInt8[]>(sizeRead); buffer = make_unique<uInt8[]>(sizeRead);
in.read(reinterpret_cast<char*>(buffer.get()), sizeRead); in.read(reinterpret_cast<char*>(buffer.get()), sizeRead);

View File

@ -317,7 +317,7 @@ string hash(const ByteBuffer& buffer, size_t length)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string hash(const uInt8* buffer, size_t length) string hash(const uInt8* buffer, size_t length)
{ {
constexpr char hex[] = "0123456789abcdef"; static constexpr char hex[] = "0123456789abcdef";
MD5_CTX context{}; MD5_CTX context{};
uInt8 md5[16] = {0}; uInt8 md5[16] = {0};
const uInt32 len32 = static_cast<uInt32>(length); // Always use 32-bit for now const uInt32 len32 = static_cast<uInt32>(length); // Always use 32-bit for now
@ -336,22 +336,4 @@ string hash(const uInt8* buffer, size_t length)
return result; return result;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string hash(const FilesystemNode& node)
{
ByteBuffer image;
size_t size = 0;
try
{
size = node.read(image);
}
catch(...)
{
return EmptyString;
}
const string& md5 = hash(image, size);
return md5;
}
} // Namespace MD5 } // Namespace MD5

View File

@ -33,29 +33,22 @@ namespace MD5 {
Based on the size of data we currently use, this may never Based on the size of data we currently use, this may never
actually happen. actually happen.
@param buffer The message to compute the digest of @param buffer The message to compute the digest of
@param length The length of the message @param length The length of the message
@return The message-digest
@return The message-digest
*/ */
string hash(const ByteBuffer& buffer, size_t length); string hash(const ByteBuffer& buffer, size_t length);
string hash(const uInt8* buffer, size_t length); string hash(const uInt8* buffer, size_t length);
/** /**
Dito. Dito.
@param buffer The message to compute the digest of @param buffer The message to compute the digest of
@return The message - digest
@return The message - digest
*/ */
string hash(const string& buffer); string hash(const string& buffer);
/**
Get the MD5 Message-Digest of the file contained in 'node'.
The digest consists of 32 hexadecimal digits.
@param node The file node to compute the digest of
@return The message-digest
*/
string hash(const FilesystemNode& node);
} // Namespace MD5 } // Namespace MD5
#endif #endif

View File

@ -753,6 +753,34 @@ ByteBuffer OSystem::openROM(const FilesystemNode& rom, string& md5, size_t& size
// but also adds a properties entry if the one for the ROM doesn't // but also adds a properties entry if the one for the ROM doesn't
// contain a valid name // contain a valid name
ByteBuffer image = openROM(rom, size);
if(image)
{
// If we get to this point, we know we have a valid file to open
// Now we make sure that the file has a valid properties entry
// To save time, only generate an MD5 if we really need one
if(md5 == "")
md5 = MD5::hash(image, size);
// Make sure to load a per-ROM properties entry, if one exists
myPropSet->loadPerROM(rom, md5);
}
return image;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string OSystem::getROMMD5(const FilesystemNode& rom) const
{
size_t size = 0;
const ByteBuffer image = openROM(rom, size);
return image ? MD5::hash(image, size) : EmptyString;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ByteBuffer OSystem::openROM(const FilesystemNode& rom, size_t& size) const
{
// First check if this is a 'streaming' ROM (one where we only read // First check if this is a 'streaming' ROM (one where we only read
// a portion of the file) // a portion of the file)
const size_t sizeToRead = CartDetector::isProbablyMVC(rom); const size_t sizeToRead = CartDetector::isProbablyMVC(rom);
@ -762,17 +790,15 @@ ByteBuffer OSystem::openROM(const FilesystemNode& rom, string& md5, size_t& size
// if it's not a ZIP file (that size should be higher; still TBD) // if it's not a ZIP file (that size should be higher; still TBD)
ByteBuffer image; ByteBuffer image;
if((size = rom.read(image, sizeToRead)) == 0) try
return nullptr; {
if((size = rom.read(image, sizeToRead)) == 0)
// If we get to this point, we know we have a valid file to open return nullptr;
// Now we make sure that the file has a valid properties entry }
// To save time, only generate an MD5 if we really need one catch(const runtime_error& e)
if(md5 == "") {
md5 = MD5::hash(image, size); cerr << "ERROR: Couldn't open ROM (" << e.what() << ")";
}
// Make sure to load a per-ROM properties entry, if one exists
myPropSet->loadPerROM(rom, md5);
return image; return image;
} }

View File

@ -338,6 +338,15 @@ class OSystem
*/ */
ByteBuffer openROM(const FilesystemNode& rom, string& md5, size_t& size); ByteBuffer openROM(const FilesystemNode& rom, string& md5, size_t& size);
/**
Open the given ROM and return the MD5sum of the data.
@param rom The file node of the ROM to open (contains path)
@return MD5 of the ROM image (if valid), otherwise EmptyString
*/
string getROMMD5(const FilesystemNode& rom) const;
/** /**
Creates a new game console from the specified romfile, and correctly Creates a new game console from the specified romfile, and correctly
initializes the system state to start emulation of the Console. initializes the system state to start emulation of the Console.
@ -596,7 +605,6 @@ class OSystem
static bool ourOverrideBaseDirWithApp; static bool ourOverrideBaseDirWithApp;
private: private:
/** /**
This method should be called to initiate the process of loading settings This method should be called to initiate the process of loading settings
from the config file. It takes care of loading settings, applying from the config file. It takes care of loading settings, applying
@ -609,13 +617,24 @@ class OSystem
*/ */
void createSound(); void createSound();
/**
Open the given ROM and return an array containing its contents.
This method takes care of using only a valid size for the
@param romfile The file node of the ROM to open (contains path)
@param size The amount of data read into the image array
@return Unique pointer to the array, otherwise nullptr
*/
ByteBuffer openROM(const FilesystemNode& romfile, size_t& size) const;
/** /**
Creates an actual Console object based on the given info. Creates an actual Console object based on the given info.
@param romfile The file node of the ROM to use (contains path) @param romfile The file node of the ROM to use (contains path)
@param md5 The MD5sum of the ROM @param md5 The MD5sum of the ROM
@return The actual Console object, otherwise nullptr. @return The actual Console object, otherwise nullptr
*/ */
unique_ptr<Console> openConsole(const FilesystemNode& romfile, string& md5); unique_ptr<Console> openConsole(const FilesystemNode& romfile, string& md5);

View File

@ -395,7 +395,7 @@ const string& LauncherDialog::selectedRomMD5()
// Lookup MD5, and if not present, cache it // Lookup MD5, and if not present, cache it
const auto iter = myMD5List.find(currentNode().getPath()); const auto iter = myMD5List.find(currentNode().getPath());
if(iter == myMD5List.end()) if(iter == myMD5List.end())
myMD5List[currentNode().getPath()] = MD5::hash(currentNode()); myMD5List[currentNode().getPath()] = instance().getROMMD5(currentNode());
return myMD5List[currentNode().getPath()]; return myMD5List[currentNode().getPath()];
} }

View File

@ -139,7 +139,7 @@ void RomAuditDialog::auditRoms()
// Calculate the MD5 so we can get the rest of the info // Calculate the MD5 so we can get the rest of the info
// from the PropertiesSet (stella.pro) // from the PropertiesSet (stella.pro)
const string& md5 = MD5::hash(files[idx]); const string& md5 = instance().getROMMD5(files[idx]);
if(instance().propSet().getMD5(md5, props)) if(instance().propSet().getMD5(md5, props))
{ {
const string& name = props.get(PropType::Cart_Name); const string& name = props.get(PropType::Cart_Name);