pmpch
Active member
- Registriert
- 14 August 2001
- Beiträge
- 545
- Punkte Reaktionen
- 0
Der Erfahrungs-Bug (2. Ballwelle im Thronsaal) - Eine Analyse
Der sogenannte Exp-Bug der 2. Baalwelle war kein Gerücht! Seit v1.10 gibt es ihn tatsächlich!
Die Ursache findet sich darin, dass die Erfahrungswertberechnung mit "signed integer" Werten, also vorzeichenbehaftet geschieht. Der folgende Codeabschnitt aus d2game.dll zeigt die Partybonusberechnung in v1.10 (v, und erklärt was genau passiert.
Implementation
Standardmässig gelten "integer" in C++ (damit ist D2 geschriben) als "signed", und gehen im positiven Bereich von 0 bis 2^31 - 1 (Hex: 0x00000000 bis 0x7FFFFFFF) und im negativen von -2^31 bis -1 (0x80000000 bis 0xFFFFFFFFh). Wird eine positive Zahl zu gross gibts einen Overflow ins Negative. Folgt darauf eine Operation (wie z.B SAR), die auch für negative Zahlen funktioniert, wird die Operation für negativen Zahlen gültig angewandt, was jedoch, nur für positive Operationen gedachte Zahlen, komplett unbrauchbar macht.
Wann gibt es einen Overflow?
Ergibt der Term baseExp * (nSpieler + 1) / 2 * nPartySpieler * 89 einen grösseren Wert als 2^31 - 1 (~ 2.147 Mrd.) gibt es diesen Overfloweffekt und die Kalkulation wird mit negativen Zahlen weitergeführt und somit werden alle Boni danach falsch angewendet, was im Endeffekt zu einem negativen Wert führt, der schlussendlich in die "Exp Malus Routine" jedes Spielers übertragen wird. Diese wird für negative Werte abgebrochen, und man erhält nur den minimalsten Erfahrungswert, nämlich 1.
Betroffene im Thronsaal
Nehmen wir die maximal mögliche Werte an, passiert der Overflow ab einer "baseExp" von > 766'000 ~= (2^31 - 1) / 4.5 / 7 / 89
Im Thronraum betrifft das genau eine Gattung, nämlich Bosse / Diener der Horadrim-Vorfahren (auch bekannt als 2. Welle), bei der die Monster je eine 'BaseExp' von 767'925 als haben. Diese Monster geben in v1.10 nur 1 Exp. Aufgrund der Skelette (welche normal Exp geben) bemerkt man das jedoch nur in der Gesamtmenge an Exp von Welle 2, die entsprchend schlecht ist bei 8 Leuten.
Der Unterschied mal rechnerisch zur Anschauung
BaseWert: 767'925
Modifiziert (8 Spieler): 3'455'662
Vorfahr im Thronsaal bei 8 Leuten in Party
{...} 80524D72 => 2'152'877'426 <==> -2'142'089'870 (Overflow!!)
{ADD} 80524E71 => -2'142'089'615 <==> 2'152'877'681 (+ 255)
{SAR} FF80524E => -8'367'538 = Int(-2'142'089'870 / 256)
{ADD} FFB50CFC => -4'911'876 = (-8'367'538 + 3'455'662)
Wert = -4'911'876 (damit erfolgt die Verteilung auf die einzelnen Spieler)
Endwert = 1
Vorfahr im Thronsaal bei 7 Leuten in Party
{...} 6DFD66F4 => +1'845'323'508 (Ok!)
{ADD} 6DFD66F4 => +1'845'323'508 (+0)
{SAR} 6DFD66F4 => +7'208'294 = Int(+1'845'323'508 / 256)
{ADD} 6E3221A2 => +10'663'956 = (7'208'294 + 3'455'662)
Wert = 10'663'956
Version 1.11
Ja, was soll ich sagen, so tragisch wie es ist, in Version 1.11b (der aktuellen also) hat die Partybonusberechnung zwar eine schöne eigene Funktion (P4 optimiert ) vom compiler erhalten, intern funktioniert die Sache aber immer noch exakt gleich. Somit gilt:
Die 2 Baalwelle gibt in v1.11b genau 1 Exp pro Horadrim Vorfahr, ausser einer verlässt die Party!!
Partybonusberechnung v1.11b
Die Erfahrungslimitierung pro Spieler (0x7FFFFF)
Neben dem Partybonus-Bug, gibt es zusätzlich eine pro-Spieler Erfahrungsbegrenzung von 2²³-1 = 8'388'607 (alias 7FFFFF in hex). Baal Hell gibt, wenn man ihn alleine killt, nicht mehr Punkte in einem players4-8 Game, als in einem players3 Game!
Die Erfahrungspunkte-Matrix von Hell Baal
Hier ein paar Werte, was Baal Hell bei verschiedenen Konstellationen an Exp gibt, dabei ist p die Anzahl Spieler beim Spawn des Monsters. Die andern Werte sind Spieler in Party : Gesamtexp der Party (Exp pro Spieler bei gleichen Charakterlevel, sowie ohne clvl/mlvl Abzug). Wie man sieht, sind es relativ wenige Aufstellungen, die vollkommen ins negative kippen!
pmpch
Der sogenannte Exp-Bug der 2. Baalwelle war kein Gerücht! Seit v1.10 gibt es ihn tatsächlich!
Die Ursache findet sich darin, dass die Erfahrungswertberechnung mit "signed integer" Werten, also vorzeichenbehaftet geschieht. Der folgende Codeabschnitt aus d2game.dll zeigt die Partybonusberechnung in v1.10 (v, und erklärt was genau passiert.
Code:
...
6FCC2E0E |. 83FF 01 CMP EDI,1 ; ? EDI = 1
6FCC2E11 |. 74 7D JE SHORT D2Game.6FCC2E90 ; Anz. Spieler = 1 (überspringen)
6FCC2E13 |. 8B7424 10 MOV ESI,DWORD PTR SS:[ESP+10] ; ESI = baseexp (inkl. spielerbonus)
6FCC2E17 |. 8D4F FF LEA ECX,DWORD PTR DS:[EDI-1] ; ECX = partyleute (0-7)
6FCC2E1A |. 0FAFCE IMUL ECX,ESI ; ECX = ESI * ECX
6FCC2E1D |. 8D0489 LEA EAX,DWORD PTR DS:[ECX+ECX*4] ; EAX = ECX * 5
6FCC2E20 |. 8D04C0 LEA EAX,DWORD PTR DS:[EAX+EAX*8] ; EAX = EAX * 9
6FCC2E23 |. 03C0 ADD EAX,EAX ; EAX = 2 * EAX
6FCC2E25 |. 2BC1 SUB EAX,ECX ; EAX = EAX - ECX (89 = 5*9*2-1)
6FCC2E27 |. 99 CDQ ; EDX = 0 oder -1 (für > 2.147 Mrd)
6FCC2E28 |. 81E2 FF000000 AND EDX,0FF ; EDX = 0 oder 255
6FCC2E2E |. 03C2 ADD EAX,EDX ; EAX = EAX + EDX (Rundungsmethode)
6FCC2E30 |. C1F8 08 SAR EAX,8 ; <-- PROBLEM -> Division nach Overflow!!
6FCC2E33 |. 03C6 ADD EAX,ESI
6FCC2E35 |. 33F6 XOR ESI,ESI
6FCC2E35 |. 33F6 XOR ESI,ESI
6FCC2E37 |. 894424 6C MOV DWORD PTR SS:[ESP+6C],EAX
6FCC2E3B |. DB4424 6C FILD DWORD PTR SS:[ESP+6C]
6FCC2E3F |. 85FF TEST EDI,EDI
6FCC2E41 |. DA7424 64 FIDIV DWORD PTR SS:[ESP+64]
6FCC2E45 |. D95C24 18 FSTP DWORD PTR SS:[ESP+18]
6FCC2E49 |. 7E 64 JLE SHORT D2Game.6FCC2EAF
6FCC2E4B |> 8B6CB4 40 /MOV EBP,DWORD PTR SS:[ESP+ESI*4+40] ; player share calc
6FCC2E4F |. 8B7CB4 20 |MOV EDI,DWORD PTR SS:[ESP+ESI*4+20]
6FCC2E53 |. 896C24 6C |MOV DWORD PTR SS:[ESP+6C],EBP
6FCC2E57 |. DB4424 6C |FILD DWORD PTR SS:[ESP+6C]
6FCC2E5B |. D84C24 18 |FMUL DWORD PTR SS:[ESP+18]
6FCC2E5F |. E8 90A40500 |CALL D2Game.6FD1D2F4 ; float-2-qword
6FCC2E64 |. 8B4C24 14 |MOV ECX,DWORD PTR SS:[ESP+14]
6FCC2E68 |. 50 |PUSH EAX
6FCC2E69 |. 51 |PUSH ECX
6FCC2E6A |. 55 |PUSH EBP
6FCC2E6B |. 8BD7 |MOV EDX,EDI
6FCC2E6D |. 8BCB |MOV ECX,EBX
6FCC2E6F |. E8 4C000000 |CALL D2Game.6FCC2EC0 ; exp malus routine
...
Implementation
Standardmässig gelten "integer" in C++ (damit ist D2 geschriben) als "signed", und gehen im positiven Bereich von 0 bis 2^31 - 1 (Hex: 0x00000000 bis 0x7FFFFFFF) und im negativen von -2^31 bis -1 (0x80000000 bis 0xFFFFFFFFh). Wird eine positive Zahl zu gross gibts einen Overflow ins Negative. Folgt darauf eine Operation (wie z.B SAR), die auch für negative Zahlen funktioniert, wird die Operation für negativen Zahlen gültig angewandt, was jedoch, nur für positive Operationen gedachte Zahlen, komplett unbrauchbar macht.
Wann gibt es einen Overflow?
Ergibt der Term baseExp * (nSpieler + 1) / 2 * nPartySpieler * 89 einen grösseren Wert als 2^31 - 1 (~ 2.147 Mrd.) gibt es diesen Overfloweffekt und die Kalkulation wird mit negativen Zahlen weitergeführt und somit werden alle Boni danach falsch angewendet, was im Endeffekt zu einem negativen Wert führt, der schlussendlich in die "Exp Malus Routine" jedes Spielers übertragen wird. Diese wird für negative Werte abgebrochen, und man erhält nur den minimalsten Erfahrungswert, nämlich 1.
Code:
...
6FCC2EC0 51 PUSH ECX ; Exp Malus Routine v1.10
6FCC2EC1 53 PUSH EBX
6FCC2EC2 55 PUSH EBP
6FCC2EC3 57 PUSH EDI
6FCC2EC4 8B7C24 1C MOV EDI,DWORD PTR SS:[ESP+1C] ; EDI = Exp für diesen Spieler
6FCC2EC8 81FF FFFF7F00 CMP EDI,7FFFFF ; maximale Exp = 2^23 - 1 = 8'388'607
6FCC2ECE 8BDA MOV EBX,EDX
6FCC2ED0 894C24 0C MOV DWORD PTR SS:[ESP+C],ECX
6FCC2ED4 7E 13 JLE SHORT D2Game.6FCC2EE9
6FCC2ED6 BF FFFF7F00 MOV EDI,7FFFFF
6FCC2EDB 85DB TEST EBX,EBX
6FCC2EDD 74 1A JE SHORT D2Game.6FCC2EF9
6FCC2EDF 833B 00 CMP DWORD PTR DS:[EBX],0
6FCC2EE2 75 15 JNZ SHORT D2Game.6FCC2EF9
6FCC2EE4 8B43 04 MOV EAX,DWORD PTR DS:[EBX+4]
6FCC2EE7 EB 12 JMP SHORT D2Game.6FCC2EFB
6FCC2EE9 85FF TEST EDI,EDI
6FCC2EEB ^7F EE JG SHORT D2Game.6FCC2EDB ; Exp < 1 (abbruch)
6FCC2EED 5F POP EDI
6FCC2EEE 5D POP EBP
6FCC2EEF B8 01000000 MOV EAX,1 ; Exp wird auf 1 gesetzt
6FCC2EF4 5B POP EBX
6FCC2EF5 59 POP ECX
6FCC2EF6 C2 0C00 RETN 0C ; abbruch zu mainexpfunc
...
Betroffene im Thronsaal
Nehmen wir die maximal mögliche Werte an, passiert der Overflow ab einer "baseExp" von > 766'000 ~= (2^31 - 1) / 4.5 / 7 / 89
Im Thronraum betrifft das genau eine Gattung, nämlich Bosse / Diener der Horadrim-Vorfahren (auch bekannt als 2. Welle), bei der die Monster je eine 'BaseExp' von 767'925 als haben. Diese Monster geben in v1.10 nur 1 Exp. Aufgrund der Skelette (welche normal Exp geben) bemerkt man das jedoch nur in der Gesamtmenge an Exp von Welle 2, die entsprchend schlecht ist bei 8 Leuten.
Der Unterschied mal rechnerisch zur Anschauung
BaseWert: 767'925
Modifiziert (8 Spieler): 3'455'662
Vorfahr im Thronsaal bei 8 Leuten in Party
{...} 80524D72 => 2'152'877'426 <==> -2'142'089'870 (Overflow!!)
{ADD} 80524E71 => -2'142'089'615 <==> 2'152'877'681 (+ 255)
{SAR} FF80524E => -8'367'538 = Int(-2'142'089'870 / 256)
{ADD} FFB50CFC => -4'911'876 = (-8'367'538 + 3'455'662)
Wert = -4'911'876 (damit erfolgt die Verteilung auf die einzelnen Spieler)
Endwert = 1
Vorfahr im Thronsaal bei 7 Leuten in Party
{...} 6DFD66F4 => +1'845'323'508 (Ok!)
{ADD} 6DFD66F4 => +1'845'323'508 (+0)
{SAR} 6DFD66F4 => +7'208'294 = Int(+1'845'323'508 / 256)
{ADD} 6E3221A2 => +10'663'956 = (7'208'294 + 3'455'662)
Wert = 10'663'956
Version 1.11
Ja, was soll ich sagen, so tragisch wie es ist, in Version 1.11b (der aktuellen also) hat die Partybonusberechnung zwar eine schöne eigene Funktion (P4 optimiert ) vom compiler erhalten, intern funktioniert die Sache aber immer noch exakt gleich. Somit gilt:
Die 2 Baalwelle gibt in v1.11b genau 1 Exp pro Horadrim Vorfahr, ausser einer verlässt die Party!!
Partybonusberechnung v1.11b
Code:
6FC9DFD0 /$ 83EC 4C SUB ESP,4C
6FC9DFD3 |. 53 PUSH EBX
6FC9DFD4 |. 55 PUSH EBP
6FC9DFD5 |. 8B6C24 58 MOV EBP,DWORD PTR SS:[ESP+58]
6FC9DFD9 |. 56 PUSH ESI
6FC9DFDA |. 57 PUSH EDI
6FC9DFDB |. 8BF0 MOV ESI,EAX
6FC9DFDD |. 33C0 XOR EAX,EAX
6FC9DFDF |. B9 13000000 MOV ECX,13
6FC9DFE4 |. 8D7C24 10 LEA EDI,DWORD PTR SS:[ESP+10]
6FC9DFE8 |. F3:AB REP STOS DWORD PTR ES:[EDI]
6FC9DFEA |. 8B4424 64 MOV EAX,DWORD PTR SS:[ESP+64]
6FC9DFEE |. 8D4C24 10 LEA ECX,DWORD PTR SS:[ESP+10]
6FC9DFF2 |. 51 PUSH ECX
6FC9DFF3 |. 894424 14 MOV DWORD PTR SS:[ESP+14],EAX
6FC9DFF7 |. 68 80A2C96F PUSH D2Game.6FC9A280
6FC9DFFC |. 8BC6 MOV EAX,ESI
6FC9DFFE |. 8BDD MOV EBX,EBP
6FC9E000 |. E8 7B3E0600 CALL D2Game.6FD01E80
6FC9E005 |. 8B4C24 54 MOV ECX,DWORD PTR SS:[ESP+54]
6FC9E009 |. 85C9 TEST ECX,ECX
6FC9E00B |. 0F8E BA000000 JLE D2Game.6FC9E0CB
6FC9E011 |. 8B4424 58 MOV EAX,DWORD PTR SS:[ESP+58]
6FC9E015 |. 85C0 TEST EAX,EAX
6FC9E017 |. 0F8E AE000000 JLE D2Game.6FC9E0CB
6FC9E01D |. 83F9 01 CMP ECX,1
6FC9E020 |. 75 2B JNZ SHORT D2Game.6FC9E04D
6FC9E022 |. 8B5424 6C MOV EDX,DWORD PTR SS:[ESP+6C]
6FC9E026 |. 8B4424 70 MOV EAX,DWORD PTR SS:[ESP+70]
6FC9E02A |. 8B7C24 68 MOV EDI,DWORD PTR SS:[ESP+68]
6FC9E02E |. 52 PUSH EDX
6FC9E02F |. 55 PUSH EBP
6FC9E030 |. 8BDE MOV EBX,ESI
6FC9E032 |. E8 A9D3FFFF CALL D2Game.6FC9B3E0
6FC9E037 |. 50 PUSH EAX
6FC9E038 |. 8BC7 MOV EAX,EDI
6FC9E03A |. 50 PUSH EAX
6FC9E03B |. 55 PUSH EBP
6FC9E03C |. 8BC6 MOV EAX,ESI
6FC9E03E |. E8 6DFDFFFF CALL D2Game.6FC9DDB0
6FC9E043 |. 5F POP EDI
6FC9E044 |. 5E POP ESI
6FC9E045 |. 5D POP EBP
6FC9E046 |. 5B POP EBX
6FC9E047 |. 83C4 4C ADD ESP,4C
6FC9E04A |. C2 1400 RETN 14
6FC9E04D |> 8B7424 70 MOV ESI,DWORD PTR SS:[ESP+70]
6FC9E051 |. 8D41 FF LEA EAX,DWORD PTR DS:[ECX-1]
[b]6FC9E054 |. 0FAFC6 IMUL EAX,ESI
6FC9E057 |. 6BC0 59 IMUL EAX,EAX,59
6FC9E05A |. 99 CDQ
6FC9E05B |. 81E2 FF000000 AND EDX,0FF
6FC9E061 |. 03C2 ADD EAX,EDX
6FC9E063 |. C1F8 08 SAR EAX,8
6FC9E066 |. 03C6 ADD EAX,ESI[/b]
6FC9E068 |. 894424 68 MOV DWORD PTR SS:[ESP+68],EAX
6FC9E06C |. 33F6 XOR ESI,ESI
6FC9E06E |. 85C9 TEST ECX,ECX
6FC9E070 |. DB4424 68 FILD DWORD PTR SS:[ESP+68]
6FC9E074 |. 9C PUSHFD
6FC9E075 |. 833D 7C9FD36F >CMP DWORD PTR DS:[6FD39F7C],0
6FC9E07C |. 75 06 JNZ SHORT D2Game.6FC9E084
6FC9E07E |. DA7424 5C FIDIV DWORD PTR SS:[ESP+5C]
6FC9E082 |. EB 09 JMP SHORT D2Game.6FC9E08D
6FC9E084 |> FF7424 5C PUSH DWORD PTR SS:[ESP+5C]
6FC9E088 |. E8 6B660700 CALL D2Game.6FD146F8
6FC9E08D |> 9D POPFD
6FC9E08E |. D95C24 70 FSTP DWORD PTR SS:[ESP+70]
6FC9E092 |. 7E 37 JLE SHORT D2Game.6FC9E0CB
6FC9E094 |> 8B7CB4 34 /MOV EDI,DWORD PTR SS:[ESP+ESI*4+34]
6FC9E098 |. 8B5CB4 14 |MOV EBX,DWORD PTR SS:[ESP+ESI*4+14]
6FC9E09C |. 897C24 68 |MOV DWORD PTR SS:[ESP+68],EDI
6FC9E0A0 |. DB4424 68 |FILD DWORD PTR SS:[ESP+68]
6FC9E0A4 |. D84C24 70 |FMUL DWORD PTR SS:[ESP+70]
6FC9E0A8 |. E8 876D0700 |CALL D2Game.6FD14E34
6FC9E0AD |. 8B4C24 6C |MOV ECX,DWORD PTR SS:[ESP+6C]
6FC9E0B1 |. 51 |PUSH ECX
6FC9E0B2 |. 55 |PUSH EBP
6FC9E0B3 |. E8 28D3FFFF |CALL D2Game.6FC9B3E0
6FC9E0B8 |. 50 |PUSH EAX
6FC9E0B9 |. 57 |PUSH EDI
6FC9E0BA |. 55 |PUSH EBP
6FC9E0BB |. 8BC3 |MOV EAX,EBX
6FC9E0BD |. E8 EEFCFFFF |CALL D2Game.6FC9DDB0
6FC9E0C2 |. 8B4424 54 |MOV EAX,DWORD PTR SS:[ESP+54]
6FC9E0C6 |. 46 |INC ESI
6FC9E0C7 |. 3BF0 |CMP ESI,EAX
6FC9E0C9 |.^7C C9 \JL SHORT D2Game.6FC9E094
6FC9E0CB |> 5F POP EDI
6FC9E0CC |. 5E POP ESI
6FC9E0CD |. 5D POP EBP
6FC9E0CE |. 5B POP EBX
6FC9E0CF |. 83C4 4C ADD ESP,4C
6FC9E0D2 \. C2 1400 RETN 14
Die Erfahrungslimitierung pro Spieler (0x7FFFFF)
Neben dem Partybonus-Bug, gibt es zusätzlich eine pro-Spieler Erfahrungsbegrenzung von 2²³-1 = 8'388'607 (alias 7FFFFF in hex). Baal Hell gibt, wenn man ihn alleine killt, nicht mehr Punkte in einem players4-8 Game, als in einem players3 Game!
Die Erfahrungspunkte-Matrix von Hell Baal
Hier ein paar Werte, was Baal Hell bei verschiedenen Konstellationen an Exp gibt, dabei ist p die Anzahl Spieler beim Spawn des Monsters. Die andern Werte sind Spieler in Party : Gesamtexp der Party (Exp pro Spieler bei gleichen Charakterlevel, sowie ohne clvl/mlvl Abzug). Wie man sieht, sind es relativ wenige Aufstellungen, die vollkommen ins negative kippen!
Code:
p=1
0:4536276(4536276)
1:6113340(3056670)
2:7690405(2563468)
3:9267470(2316867)
4:10844534(2168906)
5:12421599(2070266)
6:-2778552(1)
7:-1201488(1)
p=2
0:6804414(6804414)
1:9170011(4585005)
2:11535608(3845202)
3:13901205(3475301)
4:-510414(1)
5:1855183(309197)
6:4220780(602968)
7:6586377(823297)
p=3
0:9072552(8388607)
1:12226681(6113340)
2:15380810(5126936)
3:1757724(439431)
4:4911853(982370)
5:8065983(1344330)
6:11220112(1602873)
7:14374241(1796780)
p=4
0:11340690(8388607)
1:15283351(7641675)
2:19226013(6408671)
3:6391459(1597864)
4:10334121(2066824)
5:14276782(2379463)
6:18219444(2602777)
7:5384890(673111)
p=5
0:13608828(8388607)
1:18340022(8388607)
2:6294000(2098000)
3:11025194(2756298)
4:15756388(3151277)
5:20487582(3414597)
6:8441560(1205937)
7:13172754(1646594)
p=6
0:15876966(8388607)
1:21396692(8388607)
2:10139202(3379734)
3:15658929(3914732)
4:21178655(4235731)
5:9921166(1653527)
6:15440892(2205841)
7:20960619(2620077)
p=7
0:18145104(8388607)
1:24453362(8388607)
2:13984405(4661468)
3:20292664(5073166)
4:9823707(1964741)
5:16131966(2688661)
6:22440224(3205746)
7:11971267(1496408)
p=8
0:20413242(8388607)
1:27510033(8388607)
2:17829608(5943202)
3:24926399(6231599)
4:15245974(3049194)
5:22342765(3723794)
6:12662340(1808905)
7:19759132(2469891)
pmpch
Zuletzt bearbeitet: