<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://nee.lv</id>
    <title>meh.</title>
    <link href="https://nee.lv" />
    <updated>2021-02-28T14:00:00.000Z</updated>
    <entry>
        <id>https://nee.lv/2021/02/28/How-I-cut-GTA-Online-loading-times-by-70/</id>
        <title>How I cut GTA Online loading times by 70%</title>
        <link rel="alternate" href="https://nee.lv/2021/02/28/How-I-cut-GTA-Online-loading-times-by-70/"/>
        <content type="html">&lt;p&gt;GTA Online. &lt;a href=&#34;https://www.reddit.com/r/gtaonline/comments/9vgo0g/how_the_fuck_are_20_minute_load_times_acceptable/&#34;&gt;Infamous&lt;/a&gt; for its slow loading times. Having picked up the game again to finish some of the newer heists I was &lt;em&gt;shocked&lt;/em&gt; (/s) to discover that it still loads just as slow as the day it was released 7 years ago.&lt;/p&gt;
&lt;p&gt;It was time. Time to get to the bottom of this.&lt;/p&gt;
&lt;h2 id=&#34;Recon&#34;&gt;&lt;a href=&#34;#Recon&#34; class=&#34;headerlink&#34; title=&#34;Recon&#34;&gt;&lt;/a&gt;Recon&lt;/h2&gt;&lt;p&gt;First I wanted to check if someone had already solved this problem. Most of the results I found pointed towards anecdata about &lt;a href=&#34;https://metro.co.uk/2017/11/01/why-does-gta-v-take-so-long-to-load-7041927/&#34;&gt;how the game is so sophisticated&lt;/a&gt; that it needs to load so long, stories on how the &lt;a href=&#34;https://steamcommunity.com/app/271590/discussions/0/217690940938819317/&#34;&gt;p2p network architecture&lt;/a&gt; is rubbish (not saying that it isn’t), some elaborate ways of &lt;a href=&#34;https://gtaforums.com/topic/908000-fastest-way-to-load-into-gtao-single-player-first-or-straight-in/&#34;&gt;loading into story mode and a solo session after that&lt;/a&gt; and a couple of mods that allowed skipping the startup R* logo video. Some more reading told me we could save a whopping 10-30 seconds with these combined!&lt;/p&gt;
&lt;p&gt;Meanwhile on my PC…&lt;/p&gt;
&lt;h2 id=&#34;Benchmark&#34;&gt;&lt;a href=&#34;#Benchmark&#34; class=&#34;headerlink&#34; title=&#34;Benchmark&#34;&gt;&lt;/a&gt;Benchmark&lt;/h2&gt;&lt;figure class=&#34;highlight plain&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;Story mode load time:  ~1m 10s&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;Online mode load time: ~6m flat&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;Startup menu disabled, time from R* logo until in-game (social club login time isn&amp;#39;t counted).&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;Old but decent CPU:   AMD FX-8350&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;Cheap-o SSD:          KINGSTON SA400S37120G&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;We have to have RAM:  2x Kingston 8192 MB (DDR3-1337) 99U5471&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;Good-ish GPU:         NVIDIA GeForce GTX 1070&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;I know my setup is dated but what on &lt;em&gt;earth&lt;/em&gt; could take 6x longer to load into online mode? I couldn’t measure any difference using the story-to-online loading technique &lt;a href=&#34;https://www.reddit.com/r/gtaonline/comments/kycy7a/gtao_loading_times_using_different_methods/&#34;&gt;as others have found before me&lt;/a&gt;. Even if it did work the results would be down in the noise.&lt;/p&gt;
&lt;h2 id=&#34;I-Am-Not-Alone&#34;&gt;&lt;a href=&#34;#I-Am-Not-Alone&#34; class=&#34;headerlink&#34; title=&#34;I Am (Not) Alone&#34;&gt;&lt;/a&gt;I Am (Not) Alone&lt;/h2&gt;&lt;p&gt;If &lt;a href=&#34;https://www.reddit.com/r/gtaonline/comments/ht4i56/your_average_online_loading_time/&#34;&gt;this poll&lt;/a&gt; is to be trusted then the issue is widespread enough to mildly annoy more than 80% of the player base. It’s been 7 years R*!&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/images/pasted-0.png&#34; alt=&#34;🎵What does the poll say?🎵&#34;&gt;&lt;/p&gt;
&lt;p&gt;Looking around a bit to find who are the lucky ~20% that get sub 3 minute load times I came across &lt;a href=&#34;https://www.youtube.com/watch?v=RdCqDdjp6iU&#34;&gt;a&lt;/a&gt; &lt;a href=&#34;https://www.youtube.com/watch?v=pJzr3qfyCyg&#34;&gt;few&lt;/a&gt; &lt;a href=&#34;https://www.youtube.com/watch?v=RK7BUFx_NGk&#34;&gt;benchmarks&lt;/a&gt; with high-end gaming PCs and an online mode load time of about 2 minutes. I would &lt;del&gt;kill&lt;/del&gt; &lt;em&gt;hack&lt;/em&gt; for a 2 minute load time! It does seem to be hardware-dependent but something doesn’t add up here…&lt;/p&gt;
&lt;p&gt;How come their story mode still takes near a minute to load? (The M.2 one didn’t count the startup logos btw.) Also, loading story to online takes them only a minute more while I’m getting about five more. I know that their hardware specs are a lot better but surely not 5x better.&lt;/p&gt;
&lt;h2 id=&#34;Highly-accurate-measurements&#34;&gt;&lt;a href=&#34;#Highly-accurate-measurements&#34; class=&#34;headerlink&#34; title=&#34;Highly accurate measurements&#34;&gt;&lt;/a&gt;Highly accurate measurements&lt;/h2&gt;&lt;p&gt;Armed with such powerful tools as &lt;em&gt;the Task Manager&lt;/em&gt; I began to investigate what resources could be the bottleneck.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/images/pasted-1.png&#34; alt=&#34;Can you smell it?&#34;&gt;&lt;/p&gt;
&lt;p&gt;After taking a minute to load the common resources used for both story and online modes (which is near on par with high-end PCs) GTA decides to max out a single core on my machine for four minutes and do nothing else.&lt;/p&gt;
&lt;p&gt;Disk usage? None! Network usage? There’s a bit, but it drops basically to zero after a few seconds (apart from loading the rotating info banners). GPU usage? Zero. Memory usage? Completely flat…&lt;/p&gt;
&lt;p&gt;What, is it mining crypto or something? I smell code. &lt;em&gt;Really bad code&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&#34;Single-thread-bound&#34;&gt;&lt;a href=&#34;#Single-thread-bound&#34; class=&#34;headerlink&#34; title=&#34;Single thread-bound&#34;&gt;&lt;/a&gt;Single thread-bound&lt;/h2&gt;&lt;p&gt;While my old AMD CPU has 8 cores and it does pack a punch, it was made in the olden days. Back when AMD’s &lt;a href=&#34;http://valid.x86.fr/bench/6u7sdy/1&#34;&gt;single-thread performance&lt;/a&gt; was &lt;em&gt;way&lt;/em&gt; behind Intel’s. This might not explain all of the load time differences but it should explain most of it.&lt;/p&gt;
&lt;p&gt;What’s odd is that it’s using up &lt;em&gt;just&lt;/em&gt; the CPU. I was expecting vast amounts of disk reads loading up resources or loads of network requests trying to negotiate a session in the p2p network. But this? This is probably a bug.&lt;/p&gt;
&lt;h2 id=&#34;Profiling&#34;&gt;&lt;a href=&#34;#Profiling&#34; class=&#34;headerlink&#34; title=&#34;Profiling&#34;&gt;&lt;/a&gt;Profiling&lt;/h2&gt;&lt;p&gt;Profilers are a great way of finding CPU bottlenecks. There’s only one problem - most of them rely on instrumenting the source code to get a perfect picture of what’s happening in the process. And I don’t have the source code. Nor do I need microsecond-perfect readings - I have 4 minutes’ worth of a bottleneck.&lt;/p&gt;
&lt;p&gt;Enter stack sampling: for closed source applications there’s only one option. Dump the running process’ stack and current instruction pointer’s location to build a calling tree in set intervals. Then add them up to get statistics on what’s going on. There’s only one profiler that I know of (might be ignorant here) that can do this on Windows. And it hasn’t been updated in over 10 years. It’s &lt;a href=&#34;http://lukestackwalker.sourceforge.net/&#34;&gt;Luke Stackwalker&lt;/a&gt;! Someone, please give this project some love :)&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/images/pasted-2.png&#34; alt=&#34;The power of statistics compels you!&#34;&gt;&lt;/p&gt;
&lt;p&gt;Normally Luke would group the same functions together but since I don’t have debugging symbols I had to eyeball nearby addresses to guess if it’s the same place. And what do we see? Not one bottleneck but two of them!&lt;/p&gt;
&lt;h2 id=&#34;Down-the-rabbit-hole&#34;&gt;&lt;a href=&#34;#Down-the-rabbit-hole&#34; class=&#34;headerlink&#34; title=&#34;Down the rabbit hole&#34;&gt;&lt;/a&gt;Down the rabbit hole&lt;/h2&gt;&lt;p&gt;Having borrowed &lt;em&gt;my friend’s&lt;/em&gt; completely legitimate copy of &lt;em&gt;the industry-standard disassembler&lt;/em&gt; (no, I really can’t afford the thing… gonna learn to &lt;a href=&#34;https://ghidra-sre.org/&#34;&gt;ghidra&lt;/a&gt; one of these days) I went to take GTA apart.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/images/pasted-3.png&#34; alt=&#34;Gibberish Galore&#34;&gt;&lt;/p&gt;
&lt;p&gt;That doesn’t look right at all. Most high-profile games come with built-in protection against reverse engineering to keep away pirates, cheaters, and modders. Not that it has ever stopped them.&lt;/p&gt;
&lt;p&gt;There seems to be some sort of an obfuscation/encryption at play here that has replaced most instructions with gibberish. Not to worry, we simply need to dump the game’s memory while it’s executing the part we want to look at. The instructions have to be de-obfuscated before running one way or another. I had &lt;a href=&#34;https://github.com/glmcdona/Process-Dump&#34;&gt;Process Dump&lt;/a&gt; lying around, so I used that, but there are plenty of other tools available to do this sort of thing.&lt;/p&gt;
&lt;h2 id=&#34;Problem-one-It’s…-strlen&#34;&gt;&lt;a href=&#34;#Problem-one-It’s…-strlen&#34; class=&#34;headerlink&#34; title=&#34;Problem one: It’s… strlen?!&#34;&gt;&lt;/a&gt;Problem one: It’s… strlen?!&lt;/h2&gt;&lt;p&gt;Disassembling the now-less-obfuscated dump reveals that one of the addresses has a label pulled out of somewhere! It’s &lt;code&gt;strlen&lt;/code&gt;? Going down the call stack the next one is labeled &lt;code&gt;vscan_fn&lt;/code&gt; and after that the labels end, tho I’m fairly confident it’s &lt;a href=&#34;https://github.com/chakra-core/ChakraCore/blob/master/pal/src/safecrt/sscanf.c#L47&#34;&gt;&lt;code&gt;sscanf&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/images/pasted-4.png&#34; alt=&#34;A graph a day keeps the skeptics away&#34;&gt;&lt;/p&gt;
&lt;p&gt;It’s parsing something. Parsing what? Untangling the disassembly would take forever so I decided to dump some samples from the running process using &lt;a href=&#34;https://x64dbg.com/&#34;&gt;x64dbg&lt;/a&gt;. Some debug-stepping later it turns out it’s… JSON! They’re parsing JSON. A whopping &lt;strong&gt;10 megabytes&lt;/strong&gt; worth of JSON with some &lt;strong&gt;63k item entries&lt;/strong&gt;.&lt;/p&gt;
&lt;figure class=&#34;highlight&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;...,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;attr&#34;&gt;&amp;quot;key&amp;quot;&lt;/span&gt;: &lt;span class=&#34;string&#34;&gt;&amp;quot;WP_WCT_TINT_21_t2_v9_n2&amp;quot;&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;attr&#34;&gt;&amp;quot;price&amp;quot;&lt;/span&gt;: &lt;span class=&#34;number&#34;&gt;45000&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;attr&#34;&gt;&amp;quot;statName&amp;quot;&lt;/span&gt;: &lt;span class=&#34;string&#34;&gt;&amp;quot;CHAR_KIT_FM_PURCHASE20&amp;quot;&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;attr&#34;&gt;&amp;quot;storageType&amp;quot;&lt;/span&gt;: &lt;span class=&#34;string&#34;&gt;&amp;quot;BITFIELD&amp;quot;&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;attr&#34;&gt;&amp;quot;bitShift&amp;quot;&lt;/span&gt;: &lt;span class=&#34;number&#34;&gt;7&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;attr&#34;&gt;&amp;quot;bitSize&amp;quot;&lt;/span&gt;: &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;attr&#34;&gt;&amp;quot;category&amp;quot;&lt;/span&gt;: [&lt;span class=&#34;string&#34;&gt;&amp;quot;CATEGORY_WEAPON_MOD&amp;quot;&lt;/span&gt;]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;...&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;What is it? It appears to be data for a “net shop catalog” according to some references. I assume it contains a list of all the possible items and upgrades you can buy in GTA Online.&lt;/p&gt;
&lt;p&gt;But 10 megs? That’s nothing! And using &lt;code&gt;sscanf&lt;/code&gt; may not be optimal but surely it’s not that bad? Well…&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/images/pasted-5.png&#34; alt=&#34;Ouch!&#34;&gt;&lt;/p&gt;
&lt;p&gt;Yeah, that’s gonna take a while… To be fair I had no idea most &lt;code&gt;sscanf&lt;/code&gt; implementations called &lt;code&gt;strlen&lt;/code&gt; so I can’t blame the developer who wrote this. I would assume it just scanned byte by byte and could stop on a &lt;code&gt;NULL&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&#34;Problem-two-Let’s-use-a-Hash-…-Array&#34;&gt;&lt;a href=&#34;#Problem-two-Let’s-use-a-Hash-…-Array&#34; class=&#34;headerlink&#34; title=&#34;Problem two: Let’s use a Hash- … Array?&#34;&gt;&lt;/a&gt;Problem two: Let’s use a Hash- … Array?&lt;/h2&gt;&lt;p&gt;Turns out the second offender is called right next to the first one. They’re both even called in the same &lt;code&gt;if&lt;/code&gt; statement as seen in this ugly decompilation:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/images/pasted-6.png&#34; alt=&#34;Beggar thy neighbour&#34;&gt;&lt;/p&gt;
&lt;p&gt;All labels are mine, no idea what the functions/parameters are actually called.&lt;/p&gt;
&lt;p&gt;The second problem? Right after parsing an item, it’s stored in an array (or an inlined C++ list? not sure). Each entry looks something like this:&lt;/p&gt;
&lt;figure class=&#34;highlight c&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;class&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;struct&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;uint64_t&lt;/span&gt; *hash;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;item_t&lt;/span&gt;   *item;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125; entry;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;But before it’s stored? It checks the &lt;em&gt;entire&lt;/em&gt; array, one by one, comparing the hash of the item to see if it’s in the list or not. With ~63k entries that’s &lt;code&gt;(n^2+n)/2 = (63000^2+63000)/2 = 1984531500&lt;/code&gt; checks if my math is right. Most of them useless. You have unique &lt;em&gt;hashes&lt;/em&gt; why not use a &lt;em&gt;hash map&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/images/pasted-7.png&#34; alt=&#34;Oof!&#34;&gt;&lt;/p&gt;
&lt;p&gt;I named it &lt;code&gt;hashmap&lt;/code&gt; while reversing but it’s clearly &lt;code&gt;not_a_hashmap&lt;/code&gt;. And it gets even better. The hash-array-list-thing is empty before loading the JSON. And all of the items in the JSON are unique! They don’t even &lt;em&gt;need&lt;/em&gt; to check if it’s in the list or not! They even have a function to directly insert the items! Just use that! Srsly, WAT!?&lt;/p&gt;
&lt;h2 id=&#34;PoC&#34;&gt;&lt;a href=&#34;#PoC&#34; class=&#34;headerlink&#34; title=&#34;PoC&#34;&gt;&lt;/a&gt;PoC&lt;/h2&gt;&lt;p&gt;Now that’s nice and all, but no one is going to take me seriously unless I test this so I can write a clickbait title for the post.&lt;/p&gt;
&lt;p&gt;The plan? Write a &lt;code&gt;.dll&lt;/code&gt;, inject it in GTA, &lt;a href=&#34;https://github.com/TsudaKageyu/minhook&#34;&gt;hook&lt;/a&gt; some functions, ???, profit.&lt;/p&gt;
&lt;p&gt;The JSON problem is hairy, I can’t realistically replace their parser. Replacing &lt;code&gt;sscanf&lt;/code&gt; with one that doesn’t depend on &lt;code&gt;strlen&lt;/code&gt; would be more realistic. But there’s an even easier way.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;hook strlen&lt;/li&gt;
&lt;li&gt;wait for a long string&lt;/li&gt;
&lt;li&gt;“cache” the start and length of it&lt;/li&gt;
&lt;li&gt;if it’s called again within the string’s range, return cached value&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Something like:&lt;/p&gt;
&lt;figure class=&#34;highlight c&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;33&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;34&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;35&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;36&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;function&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;size_t&lt;/span&gt; &lt;span class=&#34;title&#34;&gt;strlen_cacher&lt;/span&gt;&lt;span class=&#34;params&#34;&gt;(&lt;span class=&#34;keyword&#34;&gt;char&lt;/span&gt;* str)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;function&#34;&gt;&lt;/span&gt;&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;keyword&#34;&gt;static&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;char&lt;/span&gt;* start;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;keyword&#34;&gt;static&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;char&lt;/span&gt;* end;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;keyword&#34;&gt;size_t&lt;/span&gt; len;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;keyword&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;size_t&lt;/span&gt; cap = &lt;span class=&#34;number&#34;&gt;20000&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;comment&#34;&gt;// if we have a &amp;quot;cached&amp;quot; string and current pointer is within it&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; (start &amp;amp;&amp;amp; str &amp;gt;= start &amp;amp;&amp;amp; str &amp;lt;= end) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;comment&#34;&gt;// calculate the new strlen&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    len = end - str;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;comment&#34;&gt;// if we&amp;#x27;re near the end, unload self&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;comment&#34;&gt;// we don&amp;#x27;t want to mess something else up&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; (len &amp;lt; cap / &lt;span class=&#34;number&#34;&gt;2&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      MH_DisableHook((LPVOID)strlen_addr);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;comment&#34;&gt;// super-fast return!&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; len;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;comment&#34;&gt;// count the actual length&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;comment&#34;&gt;// we need at least one measurement of the large JSON&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;comment&#34;&gt;// or normal strlen for other strings&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  len = builtin_strlen(str);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;comment&#34;&gt;// if it was the really long string&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;comment&#34;&gt;// save it&amp;#x27;s start and end addresses&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; (len &amp;gt; cap) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    start = str;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    end = str + len;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;comment&#34;&gt;// slow, boring return&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; len;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;And as for the hash-array problem, it’s more straightforward - just skip the duplicate checks entirely and insert the items directly since we know the values are unique.&lt;/p&gt;
&lt;figure class=&#34;highlight c&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;21&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;function&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;char&lt;/span&gt; __fastcall &lt;span class=&#34;title&#34;&gt;netcat_insert_dedupe_hooked&lt;/span&gt;&lt;span class=&#34;params&#34;&gt;(&lt;span class=&#34;keyword&#34;&gt;uint64_t&lt;/span&gt; catalog, &lt;span class=&#34;keyword&#34;&gt;uint64_t&lt;/span&gt;* key, &lt;span class=&#34;keyword&#34;&gt;uint64_t&lt;/span&gt;* item)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;function&#34;&gt;&lt;/span&gt;&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;comment&#34;&gt;// didn&amp;#x27;t bother reversing the structure&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;keyword&#34;&gt;uint64_t&lt;/span&gt; not_a_hashmap = catalog + &lt;span class=&#34;number&#34;&gt;88&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;comment&#34;&gt;// no idea what this does, but repeat what the original did&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; (!(*(&lt;span class=&#34;keyword&#34;&gt;uint8_t&lt;/span&gt;(__fastcall**)(&lt;span class=&#34;keyword&#34;&gt;uint64_t&lt;/span&gt;*))(*item + &lt;span class=&#34;number&#34;&gt;48&lt;/span&gt;))(item))&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;comment&#34;&gt;// insert directly&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  netcat_insert_direct(not_a_hashmap, key, &amp;amp;item);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;comment&#34;&gt;// remove hooks when the last item&amp;#x27;s hash is hit&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;comment&#34;&gt;// and unload the .dll, we are done here :)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; (*key == &lt;span class=&#34;number&#34;&gt;0x7FFFD6BE&lt;/span&gt;) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    MH_DisableHook((LPVOID)netcat_insert_dedupe_addr);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    unload();&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;Full source of PoC &lt;a href=&#34;https://github.com/tostercx/GTAO_Booster_PoC&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;Results&#34;&gt;&lt;a href=&#34;#Results&#34; class=&#34;headerlink&#34; title=&#34;Results&#34;&gt;&lt;/a&gt;Results&lt;/h2&gt;&lt;p&gt;Well, did it work then?&lt;/p&gt;
&lt;figure class=&#34;highlight plain&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;Original online mode load time:        ~6m flat&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;Time with only duplication check patch: 4m 30s&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;Time with only JSON parser patch:       2m 50s&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;Time with both issues patched:          1m 50s&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;(6*60 - (1*60+50)) &amp;#x2F; (6*60) &amp;#x3D; 69.4% load time improvement (nice!)&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;Hell yes, it did! :))&lt;/p&gt;
&lt;p&gt;Most likely, this won’t solve everyone’s load times - there might be other bottlenecks on different systems, but it’s such a gaping hole that I have no idea how R* has missed it all these years.&lt;/p&gt;
&lt;h2 id=&#34;tl-dr&#34;&gt;&lt;a href=&#34;#tl-dr&#34; class=&#34;headerlink&#34; title=&#34;tl;dr&#34;&gt;&lt;/a&gt;tl;dr&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;There’s a single thread CPU bottleneck while starting up GTA Online&lt;/li&gt;
&lt;li&gt;It turns out GTA struggles to parse a 10MB JSON file&lt;/li&gt;
&lt;li&gt;The JSON parser itself is poorly built / naive and&lt;/li&gt;
&lt;li&gt;After parsing there’s a slow item de-duplication routine&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;R-please-fix&#34;&gt;&lt;a href=&#34;#R-please-fix&#34; class=&#34;headerlink&#34; title=&#34;R* please fix&#34;&gt;&lt;/a&gt;R* please fix&lt;/h2&gt;&lt;p&gt;If this somehow reaches Rockstar: the problems shouldn’t take more than a day for a single dev to solve. Please do something about it :&amp;lt;&lt;/p&gt;
&lt;p&gt;You could either switch to a hashmap for the de-duplication or completely skip it on startup as a faster fix. For the JSON parser - just swap out the library for a more performant one. I don’t think there’s any easier way out.&lt;/p&gt;
&lt;p&gt;ty &amp;lt;3&lt;/p&gt;
&lt;h2 id=&#34;Small-update&#34;&gt;&lt;a href=&#34;#Small-update&#34; class=&#34;headerlink&#34; title=&#34;Small update&#34;&gt;&lt;/a&gt;Small update&lt;/h2&gt;&lt;p&gt;I was expecting to get some attention but nowhere near this much! After reaching the top of &lt;a href=&#34;https://news.ycombinator.com/item?id=26296339&#34;&gt;HN&lt;/a&gt; this post has spread like wildfire! &lt;strong&gt;Thank you for the overwhelming response :)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I’ll do more writing if something interesting comes along, but don’t expect anything of this scale soon - there was a lot of luck involved.&lt;/p&gt;
&lt;p&gt;A few people suggested spamming this post to Rockstar’s support - please don’t! I’m sure they’ve seen this by now. Continuing would only bog down support tickets for everyone else. Social media is fair game in my book tho.&lt;/p&gt;
&lt;p&gt;Several HN comments suggested I add a donate button, as they would like to buy me a beer (thank you!) so I’m placing a link in the footer.&lt;/p&gt;
&lt;p&gt;Thank you for reading and all the support :)&lt;/p&gt;
&lt;h2 id=&#34;Update-2021-03-15&#34;&gt;&lt;a href=&#34;#Update-2021-03-15&#34; class=&#34;headerlink&#34; title=&#34;Update 2021-03-15&#34;&gt;&lt;/a&gt;Update 2021-03-15&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Got confirmation from R* that this is getting a fix soon&lt;/li&gt;
&lt;li&gt;Just got awarded $10k through their H1 in-game bounty as an exception :)) (usually only for security issues)&lt;/li&gt;
&lt;li&gt;Trying to figure out what’s a W8 and how to fill it (lol)&lt;/li&gt;
&lt;li&gt;I did try asking for more technical details but they couldn’t say anything&lt;/li&gt;
&lt;li&gt;Will do another  benchmark on my same old setup as soon as the update is out, I’m sure their engineers won’t disappoint :)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;Update-2021-03-16&#34;&gt;&lt;a href=&#34;#Update-2021-03-16&#34; class=&#34;headerlink&#34; title=&#34;Update 2021-03-16&#34;&gt;&lt;/a&gt;Update 2021-03-16&lt;/h2&gt;&lt;p&gt;R* &lt;a href=&#34;https://support.rockstargames.com/articles/360061161574/&#34;&gt;released the update&lt;/a&gt;! Downloaded it and got my first run results - same hardware, same measurement - from R* logo to fully online.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/images/pasted-8.png&#34; alt=&#34;upload successful&#34;&gt;&lt;/p&gt;
&lt;p&gt;Fully fixed! t0st approves!&lt;/p&gt;
&lt;p&gt;Thanks again for all the coffees, and thanks to R* for taking the time to look into this and the generous bounty!&lt;/p&gt;
</content>
        <updated>2021-02-28T14:00:00.000Z</updated>
    </entry>
</feed>
