Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mpqapi: saner handling of hash/block tables #2855

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

vlolteanu
Copy link
Contributor

The rationale for this is two-fold:

  • std::vector is used to store the tables; new and delete are gone
  • Caching of the tables was brittle: basically, the programmer basically promised that the same archive would be loaded next time, and that was that. This PR checks the name of the file; if it doesn't match, the cache is disregarded.

@AJenbo
Copy link
Member

AJenbo commented Sep 12, 2021

Haven't looked at this, but i'm pretty excited :D

Source/mpqapi.cpp Outdated Show resolved Hide resolved
if (!cur_archive.exists) {
InitDefaultMpqHeader(&cur_archive, &fhdr);
} else if (!ReadMPQHeader(&cur_archive, &fhdr)) {
goto on_error;
}
cur_archive.sgpBlockTbl = new _BLOCKENTRY[BlockEntrySize / sizeof(_BLOCKENTRY)];
std::memset(cur_archive.sgpBlockTbl, 0, BlockEntrySize);
cur_archive.blockTbl.resize(INDEX_ENTRIES);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This always uses a static size? Can blockTbl be made a std::array instead of a vector?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can be made a std::array, but if you want to move the tables quickly to/from the cache (rather than copying them), you'd have to go the extra step and make it a std::unique_ptr<std::array>>. This would mess up the syntax when accessing the tables.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than a vector for a fixed-size array, use a unique_ptr<T[]>. It is specialised for square brackets access.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would the advantage of using unique_ptr<T[]> over vector be? We lose the ability to do range-based for loops.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A vector is not the appropriate type for a fixed-size container. While it offers the nicest syntax among the options in stdlib, that alone not a good reason to use the wrong abstraction. In this case, unique_ptr<array> is also a good option.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really don't see how unique_ptr<T[]> is superior to vector in this case, when expressing and enforcing the container size is the main issue:

  • vector is resizable when it ideally shouldn't be.
  • unique_ptr<T[]> has indeterminate size, and can be replaced with an array of a different size.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like unique_ptr<array> is the most appropriate type here, despite somewhat awkward access syntax

@vlolteanu
Copy link
Contributor Author

Making this a draft for the moment. I've got a few more commits coming.

@vlolteanu vlolteanu marked this pull request as draft September 13, 2021 02:00
@vlolteanu
Copy link
Contributor Author

I've noticed problems w.r.t. error handling, or rather, lack thereof. Both mpqapi_write_file and mpqapi_flush_and_close can return false is case of error, but the result is always ignored. Both errors have something to do with writing stuff to the save file.

Should Diablo outright crash when something like this happens?

@StephenCWills
Copy link
Member

For one thing, the FStreamWrapper class uses a CheckError() function to log the errors when they occur so they're not entirely ignored. I imagine that in the worst case, a failure to write could corrupt a save file. That's not something that would necessarily prevent the player from continuing their game so ignoring it isn't going to cause any immediate problems.

On the other hand, the game doesn't produce any log files so the log messages would get lost in most cases. I believe a failure to write is typically an indication of either a full disk, a permissions issue, or a hardware failure. Such errors should be rare, should typically manifest early on, and often require the user to take action.

I'd say crashing is probably better than doing nothing, but given how unlikely these errors probably are, I don't feel very strongly one way or the other. Just my two cents.

@vlolteanu
Copy link
Contributor Author

Avoiding corrupt saved games would, in essence, require atomic modifications to the MPQs. It would probably be done as follows:

  • Create a backup before the first modification of an MPQ, and call fsync (I'm not sure if there's a portable version). Crash if the backup can't be made. (Newly-created MPQs are exempt from this.)
  • Before closing a modified MPQ, call fsync. Delete the backup if successful. Crash if the backup can't be deleted.
  • When attempting to open an MPQ with an existing backup, attempt to restore the backup and call fsync. Delete the backup if successful, crash otherwise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants