Undertale's 5th anniversary was a couple of weeks ago, so I decided to play through it again. However, when I tried to start it up, it just crashed right away. There was a lot of diagnostic output, but no error messages, just an exit. These kinds of bugs are difficult to diagnose, but I wasn't going to let that stop me from enjoying those goat hugs.
I ran Steam in a console window, and changed the launch options to run the game under a debugger:
That at least told me what the error was: SIGFPE - usually caused by an integer division by zero.
Reading symbols from /media/disk1/lstuff/steam/steamapps/common/Undertale/runner...(no debugging symbols found)...done. (gdb) r Starting program: /media/disk1/lstuff/steam/steamapps/common/Undertale/runner . . . Thread 1 "runner" received signal SIGFPE, Arithmetic exception. 0x081d7332 in ?? () (gdb)
What could it be trying to divide by zero? Maybe the rest of the output might give me a hint.
*************************************** * YoYo Games Linux Runner V1.3 * *************************************** CommandLine: -game game.unx ExeName= /media/disk1/lstuff/steam/steamapps/common/Undertale/runner MemoryManager allocated: 4031222 INI DisplayName=UNDERTALE SavePrePend /home/ktpanda/.config/UNDERTALE/ GAMEPAD: Initialising Ubuntu support Attempting to set gamepadcount to 4 display=0x9682cd0 Display Size(Pixels): 1920,1080 CreateDesktopWindow 640,480 Win #1 XF86VidModeExtension-Version 2.2 Got Doublebuffered Visual! glX-Version 1.4 Icon: w=64 h=64 Creating window of width 640, height 480 sw=0 wh=0 WindowCentre: 640,300 Depth 24 Congrats, you have Direct Rendering! sync = 1 **** GLX Extensions *** GLX_EXT_visual_info GLX_EXT_visual_rating ... Checking for GLX_EXT_swap_control Vsync: GLX_EXT DOUBLE BUFFERED OpenGL: version string 4.6.0 NVIDIA 440.66.12 OpenGL: vendor string NVIDIA Corporation OpenGL GLSL: version string 4.60 NVIDIA Extensions: GL_AMD_multi_draw_indirect GL_AMD_seamless_cubemap_per_texture ... Anisotropic filtering supported, max aniso 16 This is where it would have set them fullscreen= 0, they are 0,0 displaywidth/h 0,0 Texture #1 16,16 Texture #2 16,16 Texture #1 16,16 Texture #2 16,16 finished(2)!! Texture #1 1,1 Texture #2 1,1 finished(2)!! Total memory used = 81515771(0x04dbd4fb) bytes
So one thing that stands out is the line that says displaywidth/h 0,0. If for some reason it was seeing the display resolution as 0x0, it is very plausible that trying to calculate something related to the aspect ratio would result in division by zero. But where is it getting that size from? Especially since earlier, it detects it correctly: Display Size(Pixels): 1920,1080.
Time to fire up xtrace. This utility intercepts all communication between the program and the X display server and dumps it. Undertale must be calling some weird function and misinterpreting the results.
*************************************** * YoYo Games Linux Runner V1.3 * *************************************** CommandLine: -game game.unx ExeName= /media/disk1/lstuff/steam/steamapps/common/Undertale/runner MemoryManager allocated: 4031222 INI DisplayName=UNDERTALE SavePrePend /home/ktpanda/.config/UNDERTALE/ GAMEPAD: Initialising Ubuntu support Attempting to set gamepadcount to 4 000:<: am lsb-first want 11:0 authorising with 'MIT-MAGIC-COOKIE-1' of length 16 000:>: Success, version is 11:0 vendor='The X.Org Foundation' release=11906000 resource-id=0x0a200000 resource-mask=0x001fffff [...] width[pixel]=1920 height[pixel]=1080 [...] 000:<:0001: 20: Request(98): QueryExtension name='BIG-REQUESTS' 000:>:0001:32: Reply to QueryExtension: present=true(0x01) major-opcode=133 first-event=0 first-error=0 000:<:0002: 4: BIG-REQUESTS-Request(133,0): Enable 000:>:0002:32: Reply to Enable: maximum-request-length=4194303 000:<:0003: 20: Request(55): CreateGC cid=0x0a200000 drawable=0x00000245 values={background=0x00ffffff} 000:<:0004: 24: Request(20): GetProperty delete=false(0x00) window=0x00000245 property=0x17("RESOURCE_MANAGER") type=0x1f("STRING") long-offset=0x00000000 long-length=0x05f5e100 000:>:0004:2168: Reply to GetProperty: type=0x1f("STRING") bytes-after=0x00000000 data='[...]' 000:<:0005: 20: Request(98): QueryExtension name='XKEYBOARD' 000:>:0005:32: Reply to QueryExtension: present=true(0x01) major-opcode=135 first-event=85 first-error=137 000:<:0006: 8: XKEYBOARD-Request(135,0): UseExtension major=1 minor=0 000:>:0006:32: Reply to UseExtension: major=1 minor=0 display=0x95bacd0 Display Size(Pixels): 1920,1080 CreateDesktopWindow 640,480 Win #1 . . . 000:<:003a: 16: Request(98): QueryExtension name='RANDR' 000:>:003a:32: Reply to QueryExtension: present=true(0x01) major-opcode=140 first-event=89 first-error=147 000:<:003b: 12: RANDR-Request(140,0): QueryVersion major-version=1 minor-version=5 000:<:003c: 8: RANDR-Request(140,8): GetScreenResources window=0x0a200003 000:>:003b:32: Reply to QueryVersion: major-version=1 minor-version=5 000:>:003c:3680: Reply to GetScreenResources: timestamp=0x42ae9f12 config-timestamp=0x0003cf23 [...] 000:<:003d: 12: RANDR-Request(140,20): GetCrtcInfo crtc=0x0000023a config-timestamp=0x0003cf23 000:>:003d:44: Reply to GetCrtcInfo: status=Success(0x00) timestamp=0x42ae9f12 x=0 y=0 width=0 height=0 mode=0x00000000 current rr=0 possible rr=Rotate_0,Rotate_90,Rotate_180,Rotate_270,Reflect_X,Reflect_Y outputs=; possible outputs=0x0000023e,0x0000023f,0x00000240; sw=0 wh=0 WindowCentre: 640,300 Depth 24
Well, would you look at that!? GetCrtcInfo returns 0 for both width and height. This is an XRandR function, so its actual name is XRRGetCrtcInfo. This function is so obscure that searching for it brings up a few examples, some StackOverflow questions, and the header file where it's defined, but no actual documentation for it. I played around with the xrandr utility, hoping to figure out what's causing it to return zero, but nothing worked.
But it's still just a hypothesis that this one call is what's causing the crash. If I could just trick the game into thinking that it returned the correct size, then maybe it will work. The definition of XRRGetCrtcInfo looks like this:
typedef struct _XRRCrtcInfo { Time timestamp; int x, y; unsigned int width, height; RRMode mode; Rotation rotation; int noutput; RROutput *outputs; Rotation rotations; int npossible; RROutput *possible; } XRRCrtcInfo; XRRCrtcInfo * XRRGetCrtcInfo (Display *dpy, XRRScreenResources *resources, RRCrtc crtc);
I can use the debugger and break on the call to XRRGetCrtcInfo, then step out, then poke values into the structure it returns:
Reading symbols from /media/disk1/lstuff/steam/steamapps/common/Undertale/runner...(no debugging symbols found)...done. (gdb) (gdb) break XRRGetCrtcInfo Breakpoint 1 at 0x804f4a0 (gdb) r Starting program: /media/disk1/lstuff/steam/steamapps/common/Undertale/runner [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". *************************************** * YoYo Games Linux Runner V1.3 * *************************************** CommandLine: -game game.unx ExeName= /media/disk1/lstuff/steam/steamapps/common/Undertale/runner MemoryManager allocated: 4031222 INI DisplayName=UNDERTALE SavePrePend /home/ktpanda/.config/UNDERTALE/ GAMEPAD: Initialising Ubuntu support Attempting to set gamepadcount to 4 display=0x880ccd0 Display Size(Pixels): 1920,1080 CreateDesktopWindow 640,480 Win #1 XF86VidModeExtension-Version 2.2 Got Doublebuffered Visual! glX-Version 1.4 Icon: w=64 h=64 Creating window of width 640, height 480 Breakpoint 1, 0xf7829d50 in XRRGetCrtcInfo () from /usr/lib/i386-linux-gnu/libXrandr.so.2 (gdb) finish Run till exit from #0 0xf7829d50 in XRRGetCrtcInfo () from /usr/lib/i386-linux-gnu/libXrandr.so.2 0x082fc8f7 in ?? () (gdb) info registers eax 0x888ccd0 143183056 ecx 0x0 0 edx 0x89606e8 144049896 ebx 0x873fae4 141818596 esp 0xffffa8a0 0xffffa8a0 ebp 0xf709db00 0xf709db00 esi 0x1002 4098 edi 0x895c170 144032112 eip 0x82fc8f7 0x82fc8f7 eflags 0x200282 [ SF IF ID ] cs 0x23 35 ss 0x2b 43 ds 0x2b 43 es 0x2b 43 fs 0x0 0 gs 0x63 99 (gdb) p ((unsigned int*)0x888ccd0)[0] $1 = 1133426070 (gdb) p ((unsigned int*)0x888ccd0)[1] $2 = 0 (gdb) p ((unsigned int*)0x888ccd0)[2] $3 = 0 (gdb) p ((unsigned int*)0x888ccd0)[3] $4 = 0 (gdb) p (((unsigned int*)0x888ccd0)[3] = 1920) $5 = 1920 (gdb) p (((unsigned int*)0x888ccd0)[4] = 1080) $6 = 1080 (gdb) c Continuing. sw=1920 wh=1080 WindowCentre: 640,300 Depth 24
Success! It sees the proper display size now, and the game starts up!
But that's a lot of work I'd have to do every time I launch the game, especially given what happens near the end. Fortunately, Linux and some other Unix-like operating systems support a feature called LD_PRELOAD, which allows injecting a library into a program before it loads, allowing it to override functions in other libraries. I created a simple C library that I could use with LD_PRELOAD for an automated fix. I'm currently working on cleaning up the code to upload it.
Let me know if you see something similar! I couldn't find anyone else posting about this bug, so it must not be common.