1 पॉइंट द्वारा GN⁺ 2025-04-24 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • Windows 11 24H2 में Skimmer seaplane के गायब होने या spawn होते ही player के असामान्य रूप से बहुत ऊँचे आसमान में उछल जाने की समस्या reproduce हुई, और वजह OS नहीं बल्कि game के अंदर का पुराना data processing bug निकला
  • Skimmer की vehicles.ide row में plane के लिए जरूरी wheel scale के 2 values गायब थे, लेकिन CFileLoader::LoadVehicleObject ने sscanf का return value check नहीं किया और uninitialized local variables वैसे ही इस्तेमाल कर लिए
  • पुराने Windows environments में ठीक पिछली vehicle TopFun का 0.7 wheel scale value संयोग से stack पर बचा रहता था, जिससे Skimmer normal जैसा दिखता था; लेकिन Windows 11 24H2 में LeaveCriticalSection का stack usage बदलने से वह संयोग टूट गया
  • गलत wheel scale ने suspension calculation और collision box के Z coordinates को corrupt किया, और यह spawn height व blade speed calculation तक फैलकर camera position anomaly, burn-in effect, और SilentPatch environment में loop freeze तक पहुँचा
  • समाधान है vehicles.ide की Skimmer row में -1, 0.7, 0.7, -1 जोड़ना या अगला SilentPatch hotfix लागू करना; input data validation और compile warning management long-term compatibility को सीधे प्रभावित करते हैं

Windows 11 24H2 में सामने आए Skimmer symptoms

  • SilentPatch issue tracker पर Windows 11 24H2 update के बाद Skimmer plane के game से पूरी तरह गायब हो जाने की report आई
    • trainer से भी spawn नहीं होता था, और अपने original spawn location पर भी नहीं मिलता था
    • mods वाले game और सिर्फ SilentPatch लगे vanilla copy, दोनों में reproduce हुआ
  • GTAForums पर भी November 2024 से यही समस्या report हुई, और कुछ users ने SilentPatch पर शक किया, लेकिन पूरी तरह unmodded game में भी यही behavior दिखा
  • Windows 10 22H2 और Windows 11 23H2 में Skimmer normal spawn हुआ, जबकि Windows 11 24H2 users ने वही bug देखा
  • 24H2 virtual machine में remote debugging करने पर बाकी planes और boats normal थे, और सिर्फ Skimmer गायब था

असामान्य altitude और कभी खत्म न होने वाला blade loop

  • script से Skimmer को force-create करके CJ को उसमें बैठाने पर player 1.0287648030984853e+0031m, यानी लगभग 10.3 nonillion meters ऊँचाई तक उछल गया
  • SilentPatch installed होने पर game player को ऊपर उछालने के तुरंत बाद loop में फँसकर रुक गया
  • SilentPatch न होने पर game रुकता नहीं, लेकिन camera के infinity के करीब position पर जाने से पैदा होने वाला प्रसिद्ध burn-in effect दिखा
  • freeze की जगह CPlane::PreRender का rotor blade angle normalization loop था
    • m_fBladeSpeed value 3.73340132e+29 तक बढ़ गई
    • 6.2831855 को बार-बार घटाने पर भी floating-point representation में value बदलती नहीं थी, इसलिए loop खत्म नहीं हुआ
  • blade speed plane altitude के proportional value से derived होती है, इसलिए यह संकेत था कि Skimmer शुरुआत से ही असामान्य रूप से ऊँची position पर create हुआ था

Suspension calculation ने collision box को corrupt किया

  • script creation function CCarCtrl::CreateCarForScript दिए गए Z coordinate में GetDistanceFromCentreOfMassToBaseOfModel का result जोड़ता है
  • Skimmer का collision box देखने पर bbox.sup.z -4.30747210e+33 जैसे बेतुके value से corrupt था
  • data breakpoint से trace करने पर initial loading के समय collision box values normal थे
    • initial bbox.sup.z -2.21952772 था
    • बाद में vehicle के पहली बार spawn होने पर SetupSuspensionLines suspension height reflect करते हुए collision box के Z coordinate को update करता है
  • समस्या suspension line calculation में जाने वाले inputs में से एक थी
    • calculation में handling.cfg की suspension upper/lower limits और vehicles.ide के wheel scale इस्तेमाल होते हैं
    • Skimmer के handling.cfg values दूसरे planes से बहुत अलग नहीं थे

Skimmer की छोटी vehicles.ide row

  • Skimmer की vehicles.ide definition दूसरे planes से छोटी है, और आखिरी 4 parameters गायब हैं
  • missing values में से 2 front और rear wheel scale हैं
  • boats में ये values न हों तो problem नहीं होती, लेकिन Skimmer planes में इकलौता है जिसने ये parameters omit किए
  • लगता है Skimmer को Vice City में boat के रूप में define किया गया था और San Andreas में plane में बदलते समय नए required parameters add नहीं किए गए
  • missing parameters वापस डालने पर Skimmer normal काम करता है

sscanf return value check न करने वाला loader

  • CFileLoader::LoadVehicleObject vehicles.ide की एक line को sscanf से parse करते समय मान लेता है कि सभी parameters हमेशा मौजूद होंगे
  • यह function sscanf का return value check नहीं करता, और आखिरी parameters में से ज्यादातर को default values भी नहीं देता
    • wheelModelID uninitialized रहता है
    • frontWheelScale, rearWheelScale भी uninitialized रहते हैं
    • सिर्फ wheelUpgradeClass -1 से initialized है
  • Skimmer जैसी missing values वाली row में wheel scale variables uninitialized state में रह जाते हैं, और वही values vehicle data में propagate हो जाते हैं
  • SilentPatch fix sscanf call को wrap करके आखिरी 4 values के लिए defaults देने के तरीके से किया गया है
    • wheelModelID = -1
    • frontWheelSize = 0.7f
    • rearWheelSize = 0.7f
    • wheelUpgradeClass = -1
  • fix commit SilentPatch repository में reflect हो गया है

20 साल तक छिपे रहने की वजह

  • San Andreas statically compiled CRT इस्तेमाल करता है, इसलिए Windows के CRT-level hotfix ने sscanf behavior बदला हो ऐसा नहीं है
  • Windows 10 में Skimmer parsing से ठीक पहले local variable location पर 0.7 value बची हुई थी
    • यह value Skimmer से ठीक पहले defined TopFun के wheel scale से match करती थी
    • TopFun row में -1, 0.7, 0.7, -1 है
  • vehicles.ide क्रम से read होती है, और हर row पर LoadVehicleObject call होता है
  • Windows 10 में LoadVehicleObject calls के बीच उस stack location पर overwrite नहीं हुआ, इसलिए Skimmer ने संयोग से TopFun का wheel scale inherit कर लिया
  • Windows 11 24H2 में अगली row पढ़ने की प्रक्रिया में fgets के अंदर LeaveCriticalSection ने ज्यादा stack space इस्तेमाल किया, और परिणामस्वरूप बची हुई value overwrite हो गई

Windows 11 24H2 सिर्फ trigger था

  • internal WinAPI function stack का इस्तेमाल कैसे करते हैं, यह contracted behavior नहीं है और बिना advance notice बदल सकता है
  • Windows 11 24H2 ने केवल game द्वारा depend किए जा रहे accidental stack residue values को हटाया; असली वजह game का undefined behavior है
  • Windows 10 में भी wheel scale के तुरंत बाद वाला local variable पहले से LeaveCriticalSection द्वारा overwrite हो चुका था, और game कई साल पहले भी इस bug से टकरा सकता था
  • San Andreas Windows 98 भी support करता था, इसलिए यह bug कम-से-कम 10 से ज्यादा Windows versions और कई Wine releases में संयोग से सामने नहीं आया
  • official 1.01 PC patch में यह bug fix नहीं हुआ, लेकिन original Xbox release में default value 1.0 डालने वाला fix मौजूद था
    • Steam 3.0, newsteam, RGL Xbox code branch पर based हैं, इसलिए उन्हें यह fix inherit हुआ
    • War Drum Studios के Android, X360, PS3 releases और Definitive Edition भी प्रभावित हैं

SilentPatch ने default के लिए 0.7 क्यों चुना

  • SilentPatch Rockstar के Xbox fix की तरह 1.0 नहीं, बल्कि 0.7 को default wheel scale के रूप में इस्तेमाल करता है
  • चुनाव के तीन आधार हैं
    • PC version में Skimmer अब तक वास्तव में TopFun के wheel scale 0.7 के साथ ही चलता आया है
    • पानी पर floating करने वाली दूसरी non-boat vehicles Sea Sparrow और Vortex का wheel scale भी 0.7 है
    • game में कई cars का wheel scale भी 0.7 है

खुद fix करने का तरीका

  • code fix अगले SilentPatch hotfix में शामिल होगा
  • तुरंत ठीक करने के लिए San Andreas directory में data\vehicles.ide को Notepad से खोलें और 460, skimmer से शुरू होने वाली row replace करें
  • replace करने वाली row यह है
460, 	skimmer,	skimmer, 	plane,		SEAPLANE,	SKIMMER,	null,	ignore,		5,	0,	0,		-1, 0.7, 0.7,		-1

पुराने games की compatibility से मिली सीख

  • यह समस्या San Andreas का एक simple bug थी, और संबंधित function शुरू से ही सही तरह काम करने में सक्षम code नहीं था
  • internal implementation के stack layout में बदलाव भी, अगर buggy application किसी specific behavior पर संयोग से depend करता है, तो compatibility issue बन सकता है
  • इसी तरह का उदाहरण Windows 10 में टूट चुका Bully: Scholarship Edition है, जो गलत assumption पर depend कर रहा था और OS change से problem सामने आई
  • San Andreas की root problem incomplete configuration rows को filter न कर पाने वाली input data validation की कमी थी
  • इस code ने originally compile warnings दिए होने की संभावना ज्यादा है, और warnings को ignore या disable करने पर लंबे समय तक छिपे bugs वास्तविक user problems के रूप में सामने आ सकते हैं

1 टिप्पणियां

 
GN⁺ 2025-04-24
Hacker News की राय
  • ऐसा लेख तो Raymond Chen से ही उम्मीद करने लायक स्तर का है, और यह बहुत बड़ी तारीफ़ है
    यह देखकर अच्छा लगा कि उन्होंने और गहराई में जाकर ठीक-ठीक पता लगाया कि ऐसा क्यों हुआ

  • निजी तौर पर मुझे लगता है कि अगर कोई व्यवहार contract में शामिल नहीं है, तो उसे randomize कर देना चाहिए
    उदाहरण के लिए, अगर भाषा map traversal order की गारंटी नहीं देती, तो order को जानबूझकर randomize करना चाहिए
    वरना ऐसा fragile code बनता है जो “चलता रहता है, जब तक एक दिन अचानक टूट न जाए”

    • -ftrivial-auto-var-init जैसे compiler options कई तरह के हैं, जो uninitialized variables को किसी तय value या random value से initialize करते हैं
      लेकिन हर function call पर पूरे stack contents को randomize करना या 0 से भरना performance के लिए भयानक होगा, इसलिए आम तौर पर ऐसा नहीं किया जाता
    • इस स्तर की randomization की लागत बहुत ज्यादा है
      debugging के लिए ऐसा करने वाले tools हैं, लेकिन उस mode में program बहुत धीमा चलता है
    • contract के नजरिए से मूल लेख में यह सीख भी है: “compatibility में यह एक दिलचस्प सबक है। अगर application में bug है और वह अनजाने में किसी खास behavior पर निर्भर है, तो internal implementation की stack layout में बदलाव तक compatibility impact पैदा कर सकता है”
      शायद यही वजह है कि Linux kernel maintainers user space को कभी न तोड़ने पर इतना जोर देते हैं
    • नहीं। https://www.hyrumslaw.com/ याद रखना चाहिए
      अगर API users काफी ज्यादा हैं, तो contract ने क्या promise किया है यह मायने नहीं रखता; system के हर observable behavior पर कोई न कोई निर्भर हो जाएगा
      अगर आप randomization का promise करेंगे, तो कोई उस randomization पर भी निर्भर हो जाएगा
      फिर उसे भी हमेशा के लिए हटाया नहीं जा सकेगा
    • C जैसी भाषाओं की एक खूबी यह भी मानी जा सकती है कि आप सिर्फ चुनी हुई features की cost चुकाते हैं
      unused variable initialization जैसे unnecessary overhead को जबरन नहीं झेलना पड़ता
  • “compiler warnings को ignore न करें” वाले हिस्से में, मुझे नहीं पता यहां किस compiler error की उम्मीद की जा सकती थी
    शायद इतना कि scanf की return value arguments की संख्या से match करती है या नहीं, यह check नहीं किया गया? इसके अलावा यह compiler को पता न चल सकने वाली data file error जैसी लगती है

    • g++ 11.4 से करने पर sscanf की return value check न करने पर भी default warning नहीं आती
      छोटे example में g++ -Wall -Wextra -Wunused-result देने पर भी warning नहीं आती
    • uninitialized memory access करना undefined behavior है, इसलिए sanitizer ने इसे पकड़ लिया होता
    • अच्छा point है। पढ़ते समय मैंने धुंधले तौर पर सोचा था कि “uninitialized memory use” warning इसे पकड़ लेगी
      लेकिन चूंकि पूरी line को एक ही sscanf call से parse किया जा रहा है, compiler की static analysis को मानना ही पड़ेगा कि values अब initialize हो चुकी हैं
      इस bug को पकड़ने का कोई सामान्य static analysis तरीका नजर नहीं आता
      हालांकि scanf के लिए dedicated warning बनाई जा सकती है, जो पहले से initialized value pass करने या return value check करने को मजबूर करे
  • ऐसे गहरे technical analysis लेख पढ़ना हमेशा सुखद होता है
    सोचता हूं कि AI के दौर में ऐसे लेख और दुर्लभ होंगे या नहीं

    • मुझे नहीं लगता कि वे और दुर्लभ होंगे। हमेशा ऐसे top-tier engineers रहेंगे जो गहराई से खोदते हैं
      AI उन्हें replace नहीं करेगा, और पिछले 50+ साल की software development innovations भी ऐसा नहीं कर पाईं
      high-level programming languages के लाखों, शायद करोड़ों developers stack और heap के फर्क को सिर्फ school में हल्का-फुल्का पढ़ी हुई theory की तरह जानते हैं, और रोजमर्रा के काम में इसकी जरूरत न होने से इसमें रुचि भी नहीं लेते
    • सामान्य software engineer शायद craftsperson से technician जैसी भूमिका की ओर जा सकता है, लेकिन ऐसे लेख अपने आप में craftsmanship-style से निकलते लगते हैं
  • मुझे ज्यादा जिज्ञासा यह है कि इस Windows version में critical section lock/unlock implementation में क्या बदला

    • लगता है used stack size या stack guard area बढ़ गया है
  • क्या सिर्फ मुझे ही यह code खटक रहा है?
    while (this->m_fBladeAngle > 6.2831855) { this->m_fBladeAngle = this->m_fBladeAngle - 6.2831855; }
    ऐसा लगता है जैसे division करने की झंझट से बचने के लिए ऐसा while loop लिखा गया है जो infinite loop भी बन सकता है

    • मैं मानना चाहूंगा कि GTA developers ने PlayStation 2 जैसे environment में यह hack इसलिए किया क्योंकि यह floating-point division से तेज था
      लेकिन sscanf से JSON parse करके GTA5 loading को 5 मिनट तक बढ़ा देने वाली बात देखते हुए उम्मीद बहुत ज्यादा नहीं है
    • मुझे लगता है performance की वजह होने की संभावना ज्यादा है। subtraction, floating-point division से सस्ता है
      compiler के पास इसे और optimize करने की techniques भी हो सकती हैं
      इसके infinite loop बनने का practically कोई तरीका नहीं है। underflow possible है, लेकिन इसके लिए angle पहले से ही 2*pi से छोटा होना चाहिए, इसलिए loop से बाहर निकल जाएगा
    • संभावना कम है, लेकिन अगर value छोटी हो तो यह loop division से तेज हो सकता है
    • सच में ऐसा ही है। लगता है लेखक को fmod के बारे में बिल्कुल पता नहीं था
  • जिन लोगों को access में दिक्कत है, वे यह link इस्तेमाल कर सकते हैं
    https://web.archive.org/web/20250423144746/https://cookieplm...

  • C/C++ जानने की वजह से blog की शुरुआत से ही मोटे तौर पर अंदाजा हो गया था कि क्या हो रहा है, यानी uninitialized variable की समस्या
    यह हैरान करने वाला है कि कोई भाषा variables को uninitialized छोड़ने की अनुमति देती है। इससे अनगिनत bugs बने हैं, जिनमें वे production bugs भी शामिल हैं जिन्हें मैंने खुद देखा है, और इन्हें पकड़ने के लिए अक्सर extra compiler flags, static analysis tools, Valgrind वगैरह पर निर्भर रहना पड़ता है
    नई languages default zero values इस्तेमाल करने या use से पहले initialization force करने जैसे अलग समाधान अपनाती हैं, फिर भी लोग बार-बार C/C++ पर लौटते हैं

  • “ये सभी discoveries साबित करती हैं कि bug Windows 11 24H2 की समस्या नहीं है। internal WinAPI function stack का उपयोग कैसे करता है जैसी चीजें contract नहीं हैं, और बिना advance notice के कभी भी बदल सकती हैं” वाला हिस्सा मुझे पहले पढ़े एक शानदार लेख की याद दिलाता है
    उसका सार यह था कि किसी पर्याप्त सफल API में private API जैसी कोई चीज नहीं होती

    • अच्छा होगा अगर आप वह लेख ढूंढकर link कर दें। logic जानने की उत्सुकता है
    • मुझे याद है कि इससे संबंधित एक XKCD comic है