Como reduzi o tempo de carregamento do GTA Online em 70%

By | Junho 9, 2022

GTA Online. Infame devido ao tempo de carregamento lento. Depois que eu assumi o jogo novamente para terminar alguns dos roubos mais recentes chocado (/s) para descobrir que ele ainda está carregando tão lentamente quanto no dia em que foi publicado há 7 anos.

Já era hora. É hora de chegar ao fundo disso.

Reconhecimento

Primeiro eu queria verificar se alguém já havia resolvido esse problema. A maioria dos resultados que encontrei apontam para anedotas sobre isso como o jogo é tão sofisticado que precisa ser carregado por tanto tempo, histórias de como arquitetura de rede p2p é lixo (não estou dizendo que não é), algumas formas elaboradas carregando no modo história e uma sessão solo depois e vários modos que permitiam pular vídeos com o logo de inicialização R*. Um pouco mais de leitura me disse que com isso juntos poderíamos economizar incríveis 10 a 30 segundos!

Enquanto isso no meu PC…

Referência

1
2
3
4
5
6
7
8
Story mode load time:  ~1m 10s
Online mode load time: ~6m flat
Startup menu disabled, time from R* logo until in-game (social club login time isn't counted).

Old but decent CPU: AMD FX-8350
Cheap-o SSD: KINGSTON SA400S37120G
We have to have RAM: 2x Kingston 8192 MB (DDR3-1337) 99U5471
Good-ish GPU: NVIDIA GeForce GTX 1070

Eu sei que minha configuração está desatualizada, mas na medida do possível Terra pode demorar 6x mais para carregar no modo de rede? Não consegui medir nenhuma diferença usando a técnica de upload de histórias na internet como outros descobriram antes de mim. Mesmo que funcionasse, os resultados estariam no barulho.

Eu não estou sozinho

Se esta pesquisa acreditar que o problema é generalizado o suficiente para irritar moderadamente mais de 80% da base de jogadores. Já se foram 7 anos R*!

Olhando em volta um pouco para descobrir quem é o sortudo ~ 20% que tem um tempo de carregamento de menos de 3 minutos me deparei E de várias balança com computadores de jogos de alto nível e um tempo de carregamento online de cerca de 2 minutos. Eu poderia matar hackear para 2 minutos de tempo de carregamento! Parece depender do hardware, mas algo não se encaixa aqui…

Como é que o modo de contar histórias ainda carrega por quase um minuto? (M.2 não contou o logotipo da startup, aliás.) Além disso, carregar a história online leva apenas um minuto a mais, enquanto eu recebo mais cinco. Eu sei que suas especificações de hardware são muito melhores, mas certamente não 5x melhores.

Medições altamente precisas

Armado com ferramentas tão poderosas como gerenciador de tarefas Comecei a explorar quais recursos poderiam ser um gargalo.

Você pode sentir o cheiro?

Depois de levar um minuto para carregar os recursos comuns usados ​​nos modos de narrativa e online (o que está quase no mesmo nível dos computadores de última geração), o GTA decide aproveitar ao máximo um núcleo na minha máquina por quatro minutos e não fazer mais nada.

Usando um disco? Não! Usando a rede? Existem alguns, mas cai basicamente para zero após alguns segundos (exceto para carregar banners de informações rotativos). Uso de GPU? Zero. Uso de memória? Totalmente plano…

O que, é mineração de criptomoeda ou algo assim? Eu sinto o código. Código muito ruim.

Ligado por um fio

Embora meu antigo processador AMD tenha 8 núcleos e seja muito bom, ele foi feito nos velhos tempos. De volta quando a AMD desempenho de thread único biografia Tempo atrás da Intel. Isso pode não explicar todas as diferenças no tempo de carregamento, mas deve explicar a maior parte.

O estranho é que é desperdiçado CPU. Eu estava esperando grandes quantidades de leituras de disco carregando recursos ou cargas de solicitações de rede tentando organizar uma sessão em uma rede p2p. Mas isso? Isso é provavelmente um erro.

Perfil

Profilers são uma ótima maneira de encontrar gargalos de CPU. Há apenas um problema – a maioria deles depende da instrumentação do código-fonte para obter uma imagem perfeita do que está acontecendo no processo. E não tenho o código fonte. Eu nem preciso de leituras perfeitas em microssegundos – tenho 4 minutos de gargalos.

Insira a amostragem, portanto: há apenas uma opção para aplicativos de código fechado. Ejete a pilha em execução e a localização atual do ponteiro de instrução para construir a árvore de chamadas em intervalos especificados. Em seguida, some-os para obter estatísticas sobre o que está acontecendo. Há apenas um criador de perfil que eu conheço (talvez eu não saiba) que pode fazer isso no Windows. E não foi atualizado em mais de 10 anos. Isso é Luke Stackwalker!! Alguém, por favor, dê um pouco de amor a este projeto 🙂

O poder das estatísticas impulsiona você!

Caso contrário, Luke teria agrupado as mesmas funções, mas como não tenho símbolos de depuração, tive que olhar os endereços próximos para adivinhar se era o mesmo lugar. E o que vemos? Não um gargalo, mas dois!

No buraco do coelho

Empréstimo meus amigos uma cópia completamente legítima seccionador padrão industrial (Não, eu realmente não posso pagar… vou aprender gidra um dia desses) fui desmontar GTA.

Abundância sem sentido

Isso não parece nada certo. A maioria dos jogos de alto perfil vem com proteção de engenharia reversa integrada para evitar piratas, trapaceiros e modders. Não é que isso os tenha parado.

Parece haver algum tipo de desfoque / criptografia em jogo aqui que substituiu a maioria das instruções por sem sentido. Não se preocupe, só precisamos tirar a memória do jogo enquanto ele executa a parte que queremos assistir. As instruções devem ser desmascaradas antes de iniciar de uma forma ou de outra. Eu tive Processo de impressão por aí, então eu usei, mas existem muitas outras ferramentas disponíveis para essas coisas.

Primeiro problema: É… tiro?!

Desmontar o aterro agora menos borrado revela que um dos endereços foi extraído de algum lugar com um adesivo! Isso é strlen? Abaixar a string de chamada é destacado a seguir vscan_fn e depois disso os adesivos acabam, embora eu tenha certeza de que é sscanf.

O gráfico diário afasta os céticos

Analisando algo. Analisando o quê? Desembaraçar a desmontagem levaria uma eternidade, então decidi jogar fora algumas amostras do processo em andamento usando x64dbg. Mais tarde acabou por ser… JSON! Eles analisam JSON. Enorme 10 megabytes vale JSON com alguns 63.000 itens de entrada.

1
2
3
4
5
6
7
8
9
10
11
...,
{
"key": "WP_WCT_TINT_21_t2_v9_n2",
"price": 45000,
"statName": "CHAR_KIT_FM_PURCHASE20",
"storageType": "BITFIELD",
"bitShift": 7,
"bitSize": 1,
"category": ["CATEGORY_WEAPON_MOD"]
},
...

O que é isso? Parece que estes são dados para o “catálogo de lojas de rede” de acordo com algumas referências. Acho que contém uma lista de todos os itens e atualizações possíveis que você pode comprar no GTA Online.

Esclareça alguma confusão: acredito que sejam itens que podem ser comprados com dinheiro do jogo e não estão diretamente relacionados a microtransações.

Mas 10 megas? Não é nada! E usando sscanf talvez não ideal, mas certamente não tão ruim? Bom…

Infelizmente!

Sim, vai demorar… Sinceramente, não fazia ideia sscanf implementação do chamado strlen então não posso culpar o desenvolvedor que escreveu isso. Acho que ele escaneou byte por byte e pode parar em um NULL.

Segundo problema: Usamos Hash-… string?

Descobriu-se que o segundo infrator foi chamado ao lado do primeiro. Mesmo ambos são convidados o mesmo if declaração como visto nesta descompilação feia:

Pergunte ao seu vizinho

Todos os rótulos são meus, não tenho ideia de como as funções/parâmetros são realmente chamadas.

Outro problema? Imediatamente após a análise do item, ele é armazenado em uma string (ou insere uma lista C++? Não tenho certeza). Cada entrada se parece com isso:

1
2
3
4
struct {
uint64_t *hash;
item_t *item;
} entry;

Mas antes de ser armazenado? Ele está verificando todo string, um por um, comparando itens de hash para ver se está listado ou não. Com ~ 63.000 entradas é (n^2+n)/2 = (63000^2+63000)/2 = 1984531500 verifica se minha matemática está correta. A maioria deles são inúteis. Você tem um único haxixe por que não usar um mapa de hash.

Eca!

eu chamei isso hashmap ao se mover para trás, mas é claro not_a_hashmap. E fica ainda melhor. O hash-array-list-thing está vazio antes de carregar o JSON. E todos os itens em JSON são únicos! Eles nem sabem precisar para verificar se está na lista ou não! Eles ainda têm a função de inserir objetos diretamente! Basta usá-lo! Querida, O QUE!?

PoC

Agora isso é legal e tudo, mas ninguém vai me levar a sério a menos que eu esteja testando isso para que eu possa escrever um título para iscas de clique.

Plano? Escreva um .dllcoloque no GTA, gancho alguns recursos, ???, lucro.

O problema do JSON é complicado, não posso substituir o analisador de forma realista. Substituição sscanf com aquele de que não depende strlen seria mais realista. Mas existe uma maneira ainda mais fácil.

  • tiro de gancho
  • espere um longo tópico
  • início e comprimento do “cache”
  • se chamado novamente dentro do intervalo de strings, retorna o valor em cache

Algo como:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
size_t strlen_cacher(char* str)
{
static char* start;
static char* end;
size_t len;
const size_t cap = 20000;


if (start && str >= start && str <= end) {

len = end - str;



if (len < cap / 2)
MH_DisableHook((LPVOID)strlen_addr);


return len;
}




len = builtin_strlen(str);



if (len > cap) {
start = str;
end = str + len;
}


return len;
}

E quanto ao problema com array disperso, é mais simples – basta pular as verificações duplas completamente e inserir os itens diretamente porque sabemos que os valores são únicos.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
char __fastcall netcat_insert_dedupe_hooked(uint64_t catalog, uint64_t* key, uint64_t* item)
{

uint64_t not_a_hashmap = catalog + 88;


if (!(*(uint8_t(__fastcall**)(uint64_t*))(*item + 48))(item))
return 0;


netcat_insert_direct(not_a_hashmap, key, &item);



if (*key == 0x7FFFD6BE) {
MH_DisableHook((LPVOID)netcat_insert_dedupe_addr);
unload();
}

return 1;
}

Fonte completa de PoC aqui.

os resultados

Bem, funcionou então?

1
2
3
4
5
6
Original online mode load time:        ~6m flat
Time with only duplication check patch: 4m 30s
Time with only JSON parser patch: 2m 50s
Time with both issues patched: 1m 50s

(6*60 - (1*60+50)) / (6*60) = 69.4% load time improvement (nice!)

Inferno, sim, é! :))

Muito provavelmente, isso não resolverá o tempo de carregamento de todos – pode haver outros gargalos em sistemas diferentes, mas é um buraco tão grande que não tenho ideia de como o R * perdeu todos esses anos.

tl; dr

  • Há um único gargalo de CPU ao executar o GTA Online
  • Acontece que o GTA está lutando para analisar um arquivo JSON de 10 MB
  • O próprio analisador JSON é mal construído / ingênuo e
  • Após a análise, há uma rotina lenta de remoção de itens duplicados

R* por favor corrija

Se isso de alguma forma chegar à Rockstar: os problemas não devem levar mais de um dia para um camelo resolver. Por favor, faça algo sobre isso:

Você pode alternar para o hashmap para remover a duplicação ou ignorá-lo completamente na inicialização como uma correção mais rápida. Para analisador JSON – basta substituir a biblioteca por uma mais eficiente. Não acho que haja uma saída mais fácil.

você <3

Uma pequena atualização

Eu esperava obter um pouco de atenção, mas nem de perto tanto! Depois de chegar ao topo HN este post se espalhou como fogo! Obrigado pela enorme resposta 🙂

Escreverei mais se surgir algo interessante, mas não espere algo dessa magnitude em breve – houve muita sorte.

Várias pessoas sugeriram enviar esta postagem como spam para o suporte da Rockstar – não faça isso! Tenho certeza que eles já viram isso. A sequência apenas perderia ingressos para apoiar todos os outros. A mídia social é um jogo justo no meu livro.

Alguns comentários da HN sugeriram que eu adicionasse um botão de doação porque eles gostariam de me comprar uma cerveja (obrigado!). Então eu coloquei o link no rodapé.

Obrigado pela leitura e pelo apoio 🙂

Atualização 2021-03-15

  • Recebi a confirmação do R* que isso será resolvido em breve
  • Acabei de receber $ 10.000 através do meu prêmio H1 no jogo como uma exceção :)) (geralmente apenas por questões de segurança)
  • Estou tentando descobrir o que é o W8 e como carregá-lo (lol)
  • Tentei pedir mais detalhes técnicos, mas eles não puderam dizer nada
  • Farei outro benchmark na minha mesma configuração antiga assim que a atualização sair, tenho certeza que seus engenheiros não vão decepcionar 🙂

Atualização 2021-03-16

R* publicou uma atualização!! Baixei e obtive os primeiros resultados – o mesmo hardware, a mesma medida – desde o logo R* até completamente online.

transferência com sucesso

Completamente corrigido! t0st aprova!

Obrigado novamente por todos os cafés e obrigado a R * por dedicar um tempo para olhar isso e a generosa recompensa!

Deixe uma resposta

O seu endereço de email não será publicado.