Bagaimana saya memotong waktu muat GTA Online hingga 70%

gambar


GTA Online terkenal karena kecepatan pemuatannya yang lambat. Setelah baru-baru ini meluncurkan game untuk menyelesaikan misi penyerbuan baru, saya terkejut saat mengetahui bahwa game tersebut dimuat dengan lambat seperti saat dirilis tujuh tahun lalu.



Saatnya telah tiba. Untuk saat ini, cari tahu alasannya.



Badan intelijen



Untuk memulainya, saya ingin memeriksa apakah ada yang sudah memecahkan masalah ini. Sebagian besar hasil yang ditemukan terdiri dari data anekdotal tentang betapa sulitnya game tersebut , yang harus dimuat dalam waktu lama, cerita tentang ketimpangan arsitektur jaringan p2p (dan ini benar), cara-cara rumit untuk memuat ke mode cerita, dan lalu ke satu sesi dan pasang mod yang memungkinkan Anda melewati video pembukaan dengan logo R *. Beberapa sumber melaporkan bahwa ketika semua metode ini digunakan bersama, Anda dapat menghemat sebanyak 10-30 detik!



Sementara itu, di PC saya ...



Tolok ukur



: 1 10

-: 6

, R* ( social club ).



, : AMD FX-8350

SSD: KINGSTON SA400S37120G

: 2 Kingston 8192 (DDR3-1337) 99U5471

GPU: NVIDIA GeForce GTX 1070


Saya tahu mobil saya sudah usang, tapi kenapa mode online memuat enam kali lebih lambat? Saya tidak menemukan perbedaan dalam teknik mengupload "cerita dulu, baru online", seperti yang dilakukan orang lain sebelum saya . Tetapi bahkan jika itu berhasil, hasilnya masih dalam batas kesalahan.



aku tidak sendirian



Menurut jajak pendapat ini , masalahnya begitu luas sehingga sedikit membuat marah lebih dari 80% basis pemain. Guys from R *, sebenarnya tujuh tahun telah berlalu!





18,8% pemain memiliki komputer atau konsol paling kuat, 81,2% cukup menyedihkan, 35,1% cukup sedih.



Setelah mencari 20% dari mereka yang beruntung yang memuat Kurang dari tiga menit, saya menemukan sebuah sejumlah benchmark dengan PC gaming yang kuat dan waktu pemuatan online sekitar dua menit. Untuk mendapatkan waktu buka dua menit saya akan membunuh apa pun yang meretas ! Sepertinya waktu pemuatan tergantung pada perangkat kerasnya, tetapi jumlahnya tidak bertambah ...



Bagaimana mungkin orang yang melakukan tolok ukur ini masih membutuhkan waktu sekitar satu menit untuk memuat mode cerita? (Ngomong-ngomong, tolok ukur dengan M.2 tidak memperhitungkan waktu tampilan logo di awal.) Selain itu, memuat dari mode cerita ke mode online hanya membutuhkan satu menit, sementara milik saya membutuhkan lebih dari lima. Saya tahu bahwa teknik mereka jauh lebih baik dari saya, tapi jelas tidak lima kali.



Pengukuran yang sangat akurat



Berbekal alat canggih seperti Task Manager , saya mulai menyelidiki untuk mencari tahu sumber daya apa yang mungkin menjadi penghambat.





Dalam satu menit, sumber daya standar mode cerita dimuat, setelah itu game memuat prosesor selama lebih dari empat menit.



Setelah satu menit memuat sumber daya bersama yang digunakan dalam mode cerita dan online (indikator yang hampir sama dengan tolok ukur PC yang kuat), GTA memutuskan untuk memuat satu inti mesin saya sebanyak mungkin selama empat menit dan tidak melakukan yang lain.



Akses disk? Dia tidak ada di sana! Penggunaan jaringan? Tidak banyak, tetapi setelah beberapa detik, lalu lintas turun menjadi hampir nol (kecuali untuk memuat spanduk yang berputar dengan informasi). Penggunaan GPU? Dengan nol. Penggunaan memori? Grafik datar sempurna ...



Apa yang terjadi, game ini menambang crypto atau semacamnya? Mulai berbau kode. Kode yang sangat buruk .



Membatasi satu aliran



Meskipun CPU AMD lama saya memiliki delapan inti dan masih dapat bekerja dengan baik, itu dibangun di masa lalu. Saat itu, kinerja single-threaded prosesor AMD jauh di belakang prosesor Intel. Ini mungkin tidak menjelaskan semua perbedaan waktu muat, tetapi harus menjelaskan hal yang paling penting.



Anehnya, game tersebut hanya menggunakan CPU. Saya mengharapkan sejumlah besar sumber daya dimuat dari disk atau banyak permintaan jaringan untuk membuat sesi di jaringan p2p. Tapi ini? Ini kemungkinan besar bug.



Profiling



Profiler adalah cara terbaik untuk menemukan kemacetan CPU. Hanya ada satu masalah - kebanyakan dari mereka menggunakan kode sumber untuk mendapatkan gambaran sempurna tentang apa yang terjadi dalam proses tersebut. Dan saya tidak memilikinya. Tetapi saya juga tidak membutuhkan pembacaan yang akurat hingga mikrodetik - kemacetan berlangsung selama empat menit.



Stack sampling hadir: ini adalah satu-satunya cara untuk menjelajahi aplikasi sumber tertutup. Kami melakukan tumpukan dump dari proses yang sedang berjalan dan lokasi penunjuk perintah saat ini untuk membangun pohon panggilan pada interval yang ditentukan. Kemudian kami menambahkannya untuk mendapatkan statistik tentang apa yang terjadi. Hanya ada satu profiler yang saya tahu (saya mungkin salah di sini) yang dapat melakukan ini di Windows. Dan itu belum diperbarui selama lebih dari sepuluh tahun. Ini Luke Stackwalker! Biarkan seseorang memberikan cintanya pada proyek ini.





Pelaku # 1 dan # 2.



Luke biasanya mengelompokkan fungsi yang sama, tetapi karena saya tidak memiliki simbol debug, saya perlu melihat alamat terdekat dengan mata saya untuk memahami bahwa mereka berada di tempat yang sama. Dan apa yang kita lihat? Bukan satu, tapi dua kemacetan!



Masuk kedalam lubang kelinci



Setelah meminjam salinan resmi pembongkaran populer dari seorang teman (tidak, saya tidak mampu membelinya ... entah bagaimana saya harus belajar ghidra ), saya mulai membongkar GTA.





Semuanya tampak salah. Banyak game dengan anggaran tinggi memiliki perlindungan rekayasa terbalik bawaan untuk melindungi dari pembajak, penipu, dan modder (bukan berarti game tersebut pernah menghentikan mereka).



Sepertinya semacam penyamaran / enkripsi digunakan di sini, karena sebagian besar perintahnya diganti dengan omong kosong. Tapi jangan khawatir, kita hanya perlu membuang memori game saat kita menjalankan bagian yang ingin kita pelajari. Sebelum dieksekusi, perintah harus disederhanakan dengan satu atau lain cara. Saya sudah dekat dengan Process Dump , tetapi ada banyak alat lain di luar sana yang dapat melakukan hal serupa.



Masalah # 1: apakah ini ... strlen?!



Membongkar tempat sampah yang sekarang tidak terlalu dikaburkan mengungkapkan bahwa salah satu alamat memiliki label yang diambil entah dari mana! Apakah itu strlen



? Yang berikutnya di tumpukan panggilan ditandai sebagai vscan_fn



, setelah itu labelnya habis, namun saya cukup yakin itu sscanf



.





Mereka mengikis sesuatu. Tapi apa? Mengurai kode yang dibongkar akan memakan waktu tak terbatas, jadi saya memutuskan untuk membuang beberapa sampel dari proses yang sedang berjalan menggunakan x64dbg . Setelah sedikit debugging, saya menemukan bahwa ini adalah ... JSON! Mereka mengurai JSON. Kekalahan 10 megabyte data JSON dengan hampir 63.000 item .



...,
{
    "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"]
},
...
      
      





Apa itu? Menurut beberapa sumber, ini terlihat seperti data "direktori toko online". Saya akan berasumsi bahwa mereka berisi daftar semua item yang mungkin dan peningkatan yang dapat dibeli di GTA Online.



Klarifikasi: Saya yakin ini adalah item yang dibeli dengan uang dalam game dan tidak terkait langsung dengan transaksi mikro .



Tapi 10 megabyte itu sepele! Dan penggunaannya sscanf



mungkin tidak optimal, tetapi tidak bisa seburuk itu? Nah ...





10 megabyte string C dalam memori. 1. Pindahkan penunjuk beberapa byte ke nilai berikutnya. 2. Kami menelepon sscanf(p, "%d", ...)



. 3. Kami membaca setiap karakter dalam 10 megabyte sambil membaca setiap nilai kecil (!?). 4. Kembalikan nilai yang dipindai.




Ya, itu akan memakan waktu yang lama ... Sejujurnya, saya tidak tahu apa yang kebanyakan implementasi yang sscanf



menelepon strlen



, jadi saya tidak bisa menyalahkan pengembang yang menulis ini. Saya menyarankan bahwa data ini hanya dipindai byte demi byte dan pemrosesan mungkin berhenti di NULL



.



Masalah # 2: Mari kita gunakan array hash ...?



Ternyata pelaku kedua dipanggil langsung di sebelah yang pertama. Mereka berdua bahkan disebut dalam pernyataan yang sama if



, seperti yang dapat dipahami dalam dekompilasi jelek ini:





Kedua masalah tersebut berada di dalam satu loop penguraian besar dari semua item. Masalah # 1 adalah penguraian, masalah # 2 adalah menyimpan.



Semua label ditentukan oleh saya, saya tidak tahu apa sebenarnya fungsi dan parameter itu.



Apa masalah kedua? Segera setelah item diurai, item tersebut disimpan ke dalam array (atau ke dalam daftar yang disematkan C ++? Tidak sepenuhnya jelas) Setiap item terlihat seperti ini:



struct {
    uint64_t *hash;
    item_t   *item;
} entry;
      
      





Tapi apa yang terjadi sebelum menabung? Kode memeriksa seluruh larik, elemen demi elemen, membandingkan hash item untuk melihat apakah itu ada dalam daftar. Jika perhitungan saya benar, maka dengan sekitar 63 ribu elemen ini memberikan (n^2+n)/2 = (63000^2+63000)/2 = 1984531500



pemeriksaan. Kebanyakan dari mereka tidak berguna. Kami memiliki hash yang unik , jadi mengapa tidak menggunakan peta hash ?





Profiler menunjukkan bahwa dua baris pertama memuat prosesor. Pernyataan if



itu dijalankan hanya di bagian paling akhir. Baris kedua dari belakang menyisipkan subjek.




Dalam rekayasa terbalik, saya menamai struktur ini hashmap



, tetapi jelas sekali not_a_hashmap



. Dan kemudian semuanya menjadi lebih baik. Hash / larik / daftar ini kosong sebelum memuat JSON. Dan semua item di JSON unik! Kode tersebut bahkan tidak perlu memeriksa apakah item tersebut ada dalam daftar! Bahkan ada fungsi untuk memasukkan item secara langsung, gunakan saja! Serius, apa-apaan ini!?



Bukti dari konsep



Ini semua bagus, tentu saja, tetapi tidak ada yang akan menganggap saya serius sampai saya mengujinya sehingga saya dapat menulis headline clickbait untuk sebuah posting.



Apa rencananya? Menulis .dll



, menyuntikkan GTA-nya, mencegat beberapa fungsi, ???, LABA!



Masalah JSON membingungkan, dan mengganti parser akan sangat memakan waktu. Jauh lebih realistis untuk mencoba menggantinya sscanf



dengan fungsi yang tidak bergantung pada strlen



. Tapi ada cara yang lebih mudah.



  • mencegat strlen
  • tunggu antrean panjang
  • "Cache" mulai dan panjangnya
  • jika dipanggil lagi dalam string, kembalikan nilai yang di-cache


Sesuatu seperti ini:



size_t strlen_cacher(char* str)
{
  static char* start;
  static char* end;
  size_t len;
  const size_t cap = 20000;

  // if we have a "cached" string and current pointer is within it
  if (start && str >= start && str <= end) {
    // calculate the new strlen
    len = end - str;

    // if we're near the end, unload self
    // we don't want to mess something else up
    if (len < cap / 2)
      MH_DisableHook((LPVOID)strlen_addr);

    // super-fast return!
    return len;
  }

  // count the actual length
  // we need at least one measurement of the large JSON
  // or normal strlen for other strings
  len = builtin_strlen(str);

  // if it was the really long string
  // save it's start and end addresses
  if (len > cap) {
    start = str;
    end = str + len;
  }

  // slow, boring return
  return len;
}
      
      





Adapun masalah hash array, lebih mudah untuk mengatasinya - Anda bisa melewati pemeriksaan duplikat dan menyisipkan item secara langsung, karena kita tahu bahwa nilainya unik.



char __fastcall netcat_insert_dedupe_hooked(uint64_t catalog, uint64_t* key, uint64_t* item)
{
  // didn't bother reversing the structure
  uint64_t not_a_hashmap = catalog + 88;

  // no idea what this does, but repeat what the original did
  if (!(*(uint8_t(__fastcall**)(uint64_t*))(*item + 48))(item))
    return 0;

  // insert directly
  netcat_insert_direct(not_a_hashmap, key, &item);

  // remove hooks when the last item's hash is hit
  // and unload the .dll, we are done here :)
  if (*key == 0x7FFFD6BE) {
    MH_DisableHook((LPVOID)netcat_insert_dedupe_addr);
    unload();
  }

  return 1;
}
      
      





Sumber bukti konsep lengkap dapat ditemukan di sini .



hasil



Jadi bagaimana cara kerjanya?



Waktu muat awal untuk mode online: sekitar 6 menit

Waktu dengan hanya pemeriksaan duplikat yang ditambal: 4 menit 30 detik

Waktu dengan hanya patch parser JSON: 2 menit 50 detik

Waktu dengan patch dari kedua masalah: 1 menit 50 detik



(6 * 60 - (1 * 60 + 50)) / (6 * 60) = waktu muat berkurang 69,4% (hebat!)


Oh ya, cara kerjanya!



Kemungkinan besar, ini tidak akan mengurangi waktu muat untuk semua pemain - mungkin ada kemacetan lain di sistem lain, tetapi ini adalah masalah yang sangat jelas sehingga saya tidak mengerti bagaimana R * tidak menyadarinya selama ini.



tl; dr



  • Ada kemacetan CPU saat meluncurkan GTA Online karena eksekusi thread tunggal
  • Ternyata GTA sedang berjuang untuk mengurai file JSON 10MB saat ini.
  • Parser JSON itu sendiri ditulis dengan buruk / diimplementasikan secara naif dan
  • Setelah penguraian, prosedur lambat dilakukan untuk memeriksa bahwa tidak ada item duplikat


R * tolong pecahkan masalahnya



Tolong, jika artikel ini entah bagaimana berhasil sampai ke Rockstar, tidak perlu lebih dari satu hari bagi satu pengembang untuk memperbaiki masalah ini. Tolong lakukan sesuatu tentang itu.



Anda dapat beralih ke hashmap untuk menghilangkan duplikat, atau melewati pemeriksaan ini sepenuhnya, yang akan lebih cepat. Di parser JSON, ganti pustaka dengan yang lebih efisien. Saya rasa tidak ada solusi yang lebih mudah di sini.



Terima kasih.



All Articles