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

Alternate Steam Libraries #872

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

scfinniss
Copy link
Contributor

I noticed while working on the Watcom C stuff that Chocolate Doom was not locating my IWADs.

What I've done here is scrape the alternative library locations from Steam's libraryfolders.vdf file and use them to add more search paths. The file itself is mostly key value pairs with a header. Here is the file from my setup for reference.

"LibraryFolders"
{
	"TimeNextStatsReport"		"1488133041"
	"ContentStatsID"		"198096677914456296"
	"1"		"D:\\Steam"
	"2"		"E:\\Steam"
	"3"		"C:\\Steam"
}

I specifically ignore LibraryFolders, thereafter I assume every second string is a path, as long as the preceding key is numeric. I make no real effort to understand the structure of the file beyond this.

It's important to note that the unlisted C:\Program Files (x86)\Steam is also a library. Presumably Steam always considers its install path a library.

I believe this may resolve issue #482 (excluding Mac and Linux support).

src/d_iwad.c Outdated

static void CheckSteamLibrary(long library_file_size, char *library_file_buffer)
{
long i, start;
Copy link
Member

Choose a reason for hiding this comment

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

Please don't use type long, since it means very different things in different compilers. Please remove all uses of long.

src/d_iwad.c Outdated
{
long i, start;
boolean key, key_valid, value;
char* buffer;
Copy link
Member

Choose a reason for hiding this comment

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

char *buffer;

src/d_iwad.c Outdated
else if (start > -1 && key)
{
//Keys for libraries are always numeric
if (library_file_buffer[i] < '0' || library_file_buffer[i] > '9')
Copy link
Member

Choose a reason for hiding this comment

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

Please use isdigit().

src/d_iwad.c Outdated

if (probe != NULL)
{
library_file = fopen(probe, "rb");
Copy link
Member

Choose a reason for hiding this comment

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

Please factor this whole block out into a separate function.

src/d_iwad.c Outdated

for (i=0;i<library_file_size; ++i)
{
if (library_file_buffer[i] == '"')
Copy link
Member

Choose a reason for hiding this comment

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

The logic in this function is very complicated and hard to follow. It looks like you're trying to do tokenization and higher-level logic in a single function. I think you can probably simplify this greatly by splitting out these into separate concerns.

@scfinniss
Copy link
Contributor Author

The main change here is restructuring based on the feedback.
Changes in CheckSteamEdition have been mostly removed, replaced with one call to CheckSteamLibraries.

Then CheckSteamLibrary was replaced with

  • GetSteamNextLibPath
  • IsValidSteamLibKey
  • GetNextSteamLibToken
    This hopefully makes the logic easier to follow.

Instances of long replaced with size_t, hopefully I've not introduced any style issues this time.

@scfinniss
Copy link
Contributor Author

I'm going to use the reference from Valve to make some changes to how I parse the libraryfolders.vdf.

Is it worth moving this logic to a new file? (eg m_steam.h, m_steam.c)

Copy link
Member

@fragglet fragglet left a comment

Choose a reason for hiding this comment

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

Sorry that I haven't responded to this sooner.


if (library_file_size != count)
{
return NULL;
Copy link
Member

Choose a reason for hiding this comment

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

free(library_file_buffer);

src/d_iwad.c Outdated
if (buffer[i] == '"')
{
//first " is the start of the token
if (token == NULL)
Copy link
Member

Choose a reason for hiding this comment

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

It becomes simpler if you just do two loops: one to find the starting ", another to find the ending ".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, this has helped.

static char *steam_libraries[MAX_STEAM_LIBRARIES];
static int num_steam_libraries = 0;

static void AddSteamLibraryDir(char *dir)
Copy link
Member

Choose a reason for hiding this comment

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

Do you need the steam_libraries[] array? Can't you just make this a wrapper around AddIWADDir?

Copy link
Contributor Author

@scfinniss scfinniss Sep 16, 2017

Choose a reason for hiding this comment

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

Yes, that array is not needed.

src/d_iwad.c Outdated
return NULL;
}

//file always starts with this line before key/value pairs
Copy link
Member

Choose a reason for hiding this comment

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

So in that case, just put a call before the start of the loop in CheckSteamLibraries() that calls GetNextSteamLibToken() and checks it's equal to "LibraryFolders". Then this function should become much simpler.

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've changed how I parse the file. I treat the whole VDF file as key value pairs, and I don't bother checking that it starts with "LibraryFolders". The code is a lot simpler now.

@scfinniss
Copy link
Contributor Author

I've addressed the reviews, and also made the tokenizer understand VDF more completely.

The tokenizer now handles tokens that are not always quoted, as well as ones with escaped characters.

To make the parsing simpler I've avoided the issue of subkeys completely. I ignore closing braces and treat the opening ones as part of a regular unquoted token.

So

"LibraryFolders"
{
	"TimeNextStatsReport"		"1505818818"
	"ContentStatsID"		"198096677914456296"
	"1"		"D:\\Steam"
	"2"		"E:\\Steam"
	"3"		"C:\\Steam"
}

Get tokenized to

"LibraryFolders"
{
"TimeNextStatsReport"
"1505818818"
"ContentStatsID"
"198096677914456296"
"1"
"D:\\Steam"
"2"
"E:\\Steam"
"3"
"C:\\Steam"

Every odd token is a key, every even token is a value.

Copy link
Member

@fragglet fragglet left a comment

Choose a reason for hiding this comment

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

Hi,

I hope my feedback here isn't too disheartening, since I have a bunch of comments. I want you to understand that I hold the Chocolate Doom codebase to a very high standard, and I want to work with you to get this into a satisfactory state, because I do really want to get this feature merged.

Every odd token is a key, every even token is a value.

This seems like a strange approach to me, and not how one would usually structure a parser. Doing this essentially ignores the syntactic structure of the file, which is a recipe for fragile code that will potentially break in unexpected ways. For example, if the parser were to read something that begins "ArgleBargle" "ASDFASDF" instead of "LibraryFolders" { you'd expect parsing fail, but in the current approach it is silently accepted. Similarly the LibraryFolders block is supposed to end in a } but the current parser doesn't care if it ends in one, none, or a hundred of them.

If you'd like some philosophical background I'd suggest reading the chapter Programming by Coincidence from the book The Pragmatic Programmer, which is a great read in general.

//Read whole of libraryfolders.vdf as string
//silently fail is something goes wrong reading the file

static char *GetSteamLibText(char *library_path)
Copy link
Member

Choose a reason for hiding this comment

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

Can you just use M_ReadFile()?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I've got it working with M_ReadFile().

The first difference I see between M_ReadFile() and my code is that I'm adding the \0 to the end of the buffer. I assume I did that to ensure strlen() is guaranteed to work later. Not sure if that was ever necessary.

The other difference is that I'm using malloc(), where M_ReadFile() uses Z_Malloc().

There are a few places where malloc() is used over Z_Malloc(), are these intentional or should Z_Malloc() always be used?

{
if (isspace(c) || c == '}')
{
success = true;
Copy link
Member

Choose a reason for hiding this comment

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

Just:

return NULL;

Then you can eliminate the success variable and the if (success) check below.

{
c = buffer[i];

//{ and } are control characters in VDF used to indicate subkeys.
Copy link
Member

Choose a reason for hiding this comment

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

A tokenizer with baked-in knowledge of syntax is probably a bad design. The usual approach would be to define an enum representing the different token types, then let the higher level parsing take care of things.

continue;
}

quote = c == '"';
Copy link
Member

Choose a reason for hiding this comment

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

This is essentially a loop to skip past leading whitespace. Why is this part of the loop body?

break;
}

for (; i<buff_size; ++i)
Copy link
Member

Choose a reason for hiding this comment

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

This ends up being complicated because you're trying to process different types of things using the same loop. You'd be better off just treating them separately. For example:

c = ... whatever
if (c == '"') {
    return ReadStringToken(.....);
} else if (c == '{') {
    return TOKEN_OPEN_BRACE;
} else if (c == '}') {
    return TOKEN_CLOSE_BRACE;
} else if (isalpha(c)) {
    return ReadKeyword(......);
} else {
    return TOKEN_UNKNOWN;
}

@scfinniss
Copy link
Contributor Author

Thank you

I'm not disheartened, I appreciate the feedback and the time you've spent on it. I'm quite happy to improve this code so it better fits the Chocolate Doom codebase.

@mfrancis95
Copy link
Contributor

@scfinniss any updates?

Otherwise I don't think it's good to keep a nearly 3 year-old PR up.

@scfinniss
Copy link
Contributor Author

Real life got between me and finishing this, and I guess I forgot about it after awhile.

I'm not sure how much Chocolate Doom has changed since I worked on this, but I don't mind looking into it again if there's still interest in this feature?

@mfrancis95
Copy link
Contributor

mfrancis95 commented Jun 4, 2020

There have always been efforts to improve WAD-locating, even in recent times. I would say it's still worth it, but it just happens to be a fairly sizable undertaking.

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.

None yet

3 participants