Maybe I actually forgot to copy the file? Checking the modification date, the file was a few weeks old… Silly me!

Making extra sure to copy the right assembly this time, I tried again and… same result. Re-checking the file modification date, I noted it was back to a few weeks ago. Clearly, the application was overwriting it at startup, probably because of the auto-update feature. How to prevent that? I could find the auto-update code and patch it, but I decided it would be simpler to just remove the write permissions from the file. By chance, the auto-updater logged an error when failing to replace the file but wouldn’t prevent the application from starting.

After starting the application with the assembly properly patched, the aforementioned exception was gone. But the “Mods” page still wouldn’t load. I found other exceptions in the logs, more patching to do?

After fixing two more of those exceptions and still seeing no change with the bug, I decided to take a step back. It looked like all those exceptions were caused by uninitialized variables and were just the consequence of using the default settings in the app. They were logged but properly handled in the code, so I probably was on the wrong track.

Following a new lead: IPC

I decided to spend more time analyzing the logs of TwitchAgent, this time ignoring the exceptions. After a while, I noticed this sequence of events:

Connecting to IPC via pipe Twitch-App-Pipe-21572

Attempting Desktop Service connection.

Service connection timed out!

Attempting to reconnect. 2 attempts remaining

Attempting Desktop Service connection.

Service connection timed out!

Attempting to reconnect. 1 attempts remaining

Attempting Desktop Service connection.

Service connection timed out!

No connection retries remaining; performing safe shutdown.

This seemed to match the logs I saw on TwitchUI! It seemed that the inter-process communication was failing for some reason. I suspected earlier that the application was using named pipes, so I started by confirming that in Process Explorer:

Sure enough, there was a named pipe with the naming pattern seen in the logs. Maybe a permission issue? I tried launching the app with administrator rights, but it didn’t change anything. I also cross-checked the logs of the two processes to make sure they were using the same name to connect to the pipe.

I decided to check the code in TwitchAgent responsible for connecting to the pipe to see if I could find an obvious mistake. Unfortunately, the inter-process communication was handled by a native library, TwitchIPC.dll, and decompiling native code is outside of my skill set. Instead, I tried to write two custom console applications that communicated using that library… and it worked perfectly. So the issue wasn’t the library itself, but how the Twitch desktop app was using it. While writing my custom console application, I discovered that the TwitchIPC library had an integrated logger that could be activated by the caller. Seeing this, I decided to patch the TwitchAgent to enable those logs, the same way I did when fixing the exceptions. This allowed me to get the following logs:

9:44:10 PM: Creating server for pipe Twitch-App-Pipe-16688

9:44:10 PM: Twitch-App-Pipe-16688: starting connection.

9:44:10 PM: Twitch-App-Pipe-16688: starting with endpoint: \\.\pipe\Twitch-App-Pipe-16688–0

9:44:10 PM: Twitch-App-Pipe-16688: started successfully

9:44:10 PM: Twitch-App-Pipe-16688: connecting to endpoint \\.\pipe\Twitch-App-Pipe-16688–1

9:44:10 PM: Twitch-App-Pipe-16688: connection finished with status: connectFailed

9:44:10 PM: Twitch-App-Pipe-16688: remote does not exist yet. waiting for incomming connection.

9:44:10 PM: Twitch-App-Pipe-16688: sending message of length: 138

9:44:10 PM: Twitch-App-Pipe-16688: sending message of length: 138

9:44:10 PM: Twitch-App-Pipe-16688: sending message of length: 138

9:44:10 PM: Twitch-App-Pipe-16688: sending message of length: 138

9:44:10 PM: Twitch-App-Pipe-16688: sending message of length: 138

9:44:10 PM: Twitch-App-Pipe-16688: sending message of length: 138

9:44:11 PM: Twitch-App-Pipe-16688: sending message of length: 218

9:44:11 PM: Twitch-App-Pipe-16688: sending message of length: 129

9:44:11 PM: Twitch-App-Pipe-16688: sending message of length: 116

9:44:13 PM: Twitch-App-Pipe-16688: disconnecting

9:44:13 PM: Twitch-App-Pipe-16688: shutting down

What surprised me was how the agent was trying to send data (sending message of length: 138) even though the connection wasn’t properly established (remote does not exist yet. waiting for incoming connection). Maybe there was no buffering in TwitchIPC.dll, and TwitchAgent would send all the data before TwitchUI was ready to receive it? To test this hypothesis, I patched TwitchAgent once more, adding a semaphore to prevent the process from sending the data before the communication was established. But even so, the bug was still there! The logs would stop at remote does not exist yet. waiting for incoming connection , and nothing more would happen. An unexpected side-effect of my semaphore was that it also prevented the process from auto-exiting, since the main thread was blocked on my lock. I used that opportunity to connect my custom console app to the named pipe… And I instantly received all the data! This meant that TwitchAgent was in fact working properly, the issue was on TwitchUI side.

Investigating TwitchUI

From that point, I started digging further in the logs of the TwitchUI process. Another interesting side-effect of my semaphore was helping me to see the moment when TwitchAgent tried to establish the connection: if the CPU usage of the process fell to 0%, it meant that the process was waiting on the lock. Thanks to this, and by watching closely the sequence of logs from TwitchUI during the startup of the application, I finally noticed something peculiar: the TwitchUI process would log Reached attempt limit for process restarts; try to restart or reinstall before TwitchAgent established the connection! I double-checked with the timestamps in the logs to confirm and indeed: TwitchAgent took about 20 seconds to start, but TwitchUI would make 3 connection attempts of 3 seconds each, giving up after 9 seconds in total. The bug was probably that TwitchAgent took longer to start after the update, but the developers didn’t think of adjusting the timeout on TwitchUI side. It also explained the randomness of the workarounds found on the forums, since it was a timing issue.

One final step left

Finding the issue is great, but we still need to fix it. Given that I already had patched a few assemblies used by TwitchAgent, I was confident that I could do the same thing with TwitchUI to adjust the timeout. That’s when I discovered that TwitchUI was an Electron app. DotPeek wouldn’t help me with that one.

Fortunately, by searching “decompile electron app” on Google, I quickly found this article that explained everything I needed to know. It was easy to decompile TwitchUI (or really more “unpack”, since an Electron app is just a Chromium frame running a web page) using a tool named “asar”. Using it, I was able to extract the Javascript code that ran in the process. It was minified, but after pasting it in a beautifier and looking for the IPC Connection timed out message I saw in the logs, I was able to find this code:

That confirmed the 3 seconds timeout I noticed in the logs. After changing the value to 60 seconds and re-launching the app, the bug was gone!