Dialog Wishlist

According to our robot overlords, yes, as long as:

  • Header Files: Include <windows.h> or <objbase.h> to get the CoCreateGuid declaration and GUID structure definition.
  • Linking: Use the -lole32 flag in your compilation command to link the required Windows library.
  • COM Initialization: While CoCreateGuid doesn’t strictly require CoInitialize in all Windows versions, it is best practice to call CoInitialize(NULL) and CoUninitialize() when using COM-related functions.
  • MinGW-w64: Ensure you are using the modern mingw-w64 fork, which has excellent support for Windows APIs.
1 Like

Back in the ancient days, we just seeded off of time(NULL). It’s not entropy but the number of Dialog users isn’t so great that two of them will start a new game at the same second of the same day.

1 Like

I’m more worried about there only being 216 possible seeds. If 120 different games are ever made by Dialog-users on Windows, there’s more than a 10% chance that two of them will end up with the same IFID—which isn’t huge, but it’s higher than I’m comfortable with building into the tools!

(And that’s assuming the 16-bit PRNG is implemented properly; rand() can’t even guarantee that, and the documentation is full of warnings about bad implementations.)

1 Like

Regardless, though, the current behavior is clearly not ideal. Whether I can find some way to generate appropriate UUIDs in cross-platform C code, or just provide a console command or link to an appropriate service, the compiler could definitely be doing more to help than it is now.

1 Like

Poking around a bit, it looks like there are multiple permissively-licensed drop-in libraries (1, 2) that will handle all the “getting sufficiently random numbers across different platforms” parts for us, and don’t bring in any external dependencies. So yeah, it should be very possible to add IFID generation to the compiler on the most common platforms (Windows, Mac, Linux) while keeping the whole thing written in portable C; any platform that doesn’t provide a decent source of randomness will just direct users to an appropriate website instead, rather than failing to compile.

3 Likes

Oh, that was way easier than I expected.

When compiling a minimal program to zblorb format:

Error: An IFID is mandatory for the blorb output format.
Error: Add this one to your story:
Error: (story ifid) C3C4B2E0-D37E-4338-B0EA-B559FBDABEDA

Or compiling a large program to any format:

Warning: No IFID declared.
Warning: Add this one to your story:
Warning: (story ifid) CB491C97-24F7-495D-AEEF-B5EB88BD4B17

Compiling a small (less than 100 lines, not including the library) to a non-zblorb format produces no warning or error.

And if this is compiled on a platform that doesn’t support good randomness sources (currently meaning not Windows, Linux, Apple, or BSD—I guess more accurately, a platform where the compiler doesn’t know where to find good randomness sources), you get this instead:

Warning: No IFID declared.
Warning: You can get one at <https://www.tads.org/ifidgen/ifidgen>.

Or the same thing but an error if compiling to the zblorb format. Is anyone building Dialog on a platform that’s not any of those four? Dunno, but as long as it has a POSIX C99 compiler, you still can.

I do need to request one favor, though. I don’t have a Windows machine around to test this on, and that needs to get randomness in a different way—which might have changed in the past decade since this library was written. If any brave Windows-users are willing to try an experimental EXE that I send you, and make sure it gives the same output, hit me up. (Windows is the only supported platform that doesn’t use /dev/random, so if it works on Linux, it should work on all the others too.)

3 Likes

I also changed the default output format to Z8 while I’m at it. The main reason someone will be compiling without explicitly specifying the output format is because they’re trying the toy examples from the manual, and adding an IFID to those is just a hassle.

EDIT: Done! Warnings/errors about missing IFIDs now propose a new one if possible by dstelzer · Pull Request #175 · Dialog-IF/dialog · GitHub

1 Like

Sorry to disagree, but I don’t feel that bolting this type of stuff onto the Dialog tooling is in keeping with the original spirit of how it works, and I think there will be additional issues on some older platforms which don’t have reliable entropy.

If people are really struggling to find a reliable IFID, shouldn’t the solution be something that works for all authoring systems?

Just displaying the warnings and the link seems reasonable to me.

(Side note: automatically inserting an IFID seems an easy way to accidentally release the same game with multiple IFIDs. I would suggest there is more value in deterministic output, and something that is an actual problem is the automatically generated serial number with no option to manually set it.)

3 Likes

A fair concern! In this PR, (hopefully) it just fails if there isn’t enough entropy available. It only needs 128 bits, after all—not a ton, just more than rand() can guarantee. Any competently-implemented /dev/random should be more than capable of supplying that.

My main concern is that currently, compiling the manual’s “hello world” example requires going to Google and searching for “ifid generator”, which feels distinctly unfriendly to new users. With this change, the user still has to explicitly copy the IFID into the source themself if they want it; the compiler will never insert it automatically. To me, that feels equivalent to the compiler suggesting which command-line options to change if heap space runs out—just a bit of advice that users are free to ignore.

But maybe switching the default output format from zblorb to z8 is enough. What does the community think?

  • Change the default output format to Z8, AND suggest an IFID if one is needed
  • Change the default output format to Z8, but do NOT suggest IFIDs
  • Suggest IFIDs when needed, but do NOT change the default output format
  • Do neither, the current behavior is fine
0 voters

I don’t think multiple IFIDs for the same game will be a concern, because the compiler doesn’t add it automatically: it just prints it in the warning message, so the user can copy it into the source code if they want. If they do, the warning will never print again; and if they don’t, it’ll disappear as soon as their terminal scrolls away.

For the serial number, though, I could also see an argument there. What would the use case be? I imagine the reason it can’t currently be overridden is because it would make it very easy for users to shoot themselves in the foot, giving all their builds the same release and serial number by hard-coding it in the Makefile. To get deterministic builds in our compiler tests, we just make sure to never call (serial number).

2 Likes

For I6, part of the regression testing is to recompile Advent.inf, Library of Horror, and a few other sample games which have a standard serial number display. So there is value in being able to set it for testing.

However, I’ve set up those tests to ignore the serial number, Zcode checksum, and I6 version number. (E.g. I have a tool that computes the MD5 of a Zcode file with the serial number etc zeroed out). So I don’t in fact rely on setting the serial number for testing.

1 Like

Makes sense, yeah; here’s what we currently do for all our test cases:

stdlib.dglib: $(BASEPATH)/stdlib.dg
	cp $(BASEPATH)/stdlib.dg stdlib.dglib
	perl -i -pe 's/\(serial number\)/<SERIAL>/g' stdlib.dglib
	perl -i -pe 's/\(compiler version\)/<COMPILER>/g' stdlib.dglib
	perl -i -pe 's/\s\(library version\)/\t<LIBRARY>/g' stdlib.dglib

This line from the Makefile takes the latest version of the standard library, but strips out all calls to (serial number), (compiler version), and (library version). That way we don’t need to change all the test cases when we bump the version. (Since the compiler is still changing in significant ways, we only look at the output of running the game, not the story file itself.)

I wouldn’t think this is much of a concern when the software doesn’t have a GUI and “installing” it requires an understanding of how executables are located when you want to run them. If someone gets as far as successfully running the compiler with their game source files, using the web browser to generate the IFID shouldn’t be much of challenge.

If it really is that easy to build a correct command line IFID generator (and I’m not convinced it is that easy without carefully looking at the entropy sources), why not build it as a separate program and just include it with a Dialog release? People releasing other authoring systems can include it in their releases too.

Taking any sort of automatic action is probably better handled by dgt (or any other external wrapper that is assisting the user by calling the compiler or debugger on their behalf, or setting up a new project).

Having the option to use a fixed serial number would help with some basic test cases (i.e. diffing two outputs from the same game source files) and with build environments where the build outputs must be deterministic to be classed as successful (see https://guix.gnu.org/en/blog/2024/adventures-on-the-quest-for-long-term-reproducible-deployment/ for examples of why the build date isn’t meant to change the output.).

I definitely agree that the compiler shouldn’t take action automatically; but I think suggesting to the user what action they might take is fair game. Analogically, “Heap space exhausted; use the -H switch to increase it” or “File too large; switch from z5 to z8 for more space” feel more friendly than just “Heap space exhausted” and “File too large”. Whether to make the change remains the user’s prerogative, but a newcomer who just wants it to stop crashing now knows where to look first.

Mostly because it feels like reinventing the wheel; Mac and Linux already have uuidgen, Windows has powershell -command new-guid. As far as I can tell, they use the same sources of randomness (OS-supplied CSPRNGs) as the new PR does. The reason to put it into the compiler error message specifically is for user-friendliness.

I guess I don’t quite see the analogy here. If you wanted to compile an old version of the Linux date utility, you’d presumably want it to still say “20 February 2026” now; if you wanted it to still say “1 January 1990”, you’d change the system clock as well as building from the old source, right?

The feature isn’t that hard to add, technically; I’m just not convinced the benefits outweigh the risks. Right now, people are incentivized to design their testing workflows to not require a consistent serial number, which means their builds will be date-stamped and easier to tell apart. If we instead incentivize fixing the serial number in a Makefile, then as soon as someone forgets to update the Makefile, the serial number becomes useless for distinguishing different builds. I want the default to be “the compiler tracks builds automatically”, not “you have to track them manually and never slip up”.

I guess we could implement it, but make it awkward to use (e.g. it has to be set in an environment variable), but the potential drawbacks currently feel too big to be worth it. I’m happy to be convinced otherwise, though.

As it stands now, it would be impossible to package the game for such a system because the game file that is built cannot be referenced purely by the build inputs, which also means that nothing can then depend on that game file as a build input.

Maybe a better analogy, then: does this C program mean that gcc is impossible to use in such a system?

#include <stdio.h>
const char *date = __DATE__;
int main(){
    printf("This program was compiled on %s.", date);
    return 0;
}

I wouldn’t call this a failure on gcc’s part; if you want a reproducible build, it stands to reason that that build needs to not ask for the date of compilation within the source code.

Or is the problem that the serial number is embedded into the game file, even if (serial number) is never queried?

Yes, you don’t need to run the game for it to be a problem. Two people who build the game using the same inputs should end up with the same game file at the end of the process.

BTW, the man pages here say this about /dev/random :

The /dev/random interface is considered a legacy interface, and /dev/urandom is preferred and sufficient in all use cases, with the exception of applications which require randomness during early boot time; for these applications, getrandom(2) must be used instead, because it will block until the entropy pool is initialized.

1 Like

Ooh, that’s good to know. I think I’m currently using /dev/urandom (since the randomness code was copied in from another library), but I didn’t realize there was any real difference between them!

In the meantime, I’ve made the wording sound a bit less authoritative:

Warning: No IFID declared.
Warning: For example, you could use:
Warning: (story ifid) XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
Warning: Or get one at <https://www.tads.org/ifidgen/ifidgen>.
2 Likes

I can’t quite tell if you’ve completely settled the cross-OS randomness issue, but consulting Glulxe could be helpful as it recently changed its random implementation. It has functions for Unix (& Linux), MacOS and Windows.

1 Like