@astefanski/storm-parser 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -36,39 +36,271 @@ declare class ReplayParser {
36
36
  getTrackerEvents(): ReplayEvent[];
37
37
  getGameEvents(): ReplayEvent[];
38
38
  extractFile(filename: string): Buffer | null;
39
+ getHeader(): Record<string, unknown> | undefined;
40
+ getBuild(): number;
39
41
  }
40
42
 
41
- interface PlayerStat {
43
+ interface ReplayVersion {
44
+ m_flags: number;
45
+ m_major: number;
46
+ m_minor: number;
47
+ m_revision: number;
48
+ m_build: number;
49
+ m_baseBuild: number;
50
+ }
51
+ interface KillParticipant {
52
+ player: string;
42
53
  hero: string;
54
+ }
55
+ interface TakedownEvent {
56
+ loop: number;
57
+ time: number;
58
+ x: number;
59
+ y: number;
60
+ killers: KillParticipant[];
61
+ victim: KillParticipant;
62
+ }
63
+ interface LevelTime {
64
+ loop: number;
65
+ level: number;
66
+ team: string;
67
+ time: number;
68
+ }
69
+ interface BanEntry {
70
+ hero: string;
71
+ order: number;
72
+ absolute: number;
73
+ }
74
+ interface PickData {
75
+ 0: string[];
76
+ 1: string[];
77
+ first: number;
78
+ }
79
+ interface XPValues {
80
+ GameTime: number;
81
+ PreviousGameTime: number;
82
+ MinionXP: number;
83
+ CreepXP: number;
84
+ StructureXP: number;
85
+ HeroXP: number;
86
+ TrickleXP: number;
87
+ }
88
+ interface XPBreakdownEntry {
89
+ loop: number;
90
+ time: number;
91
+ team: number;
92
+ teamLevel?: number;
93
+ breakdown: XPValues;
94
+ theoreticalMinionXP: number;
95
+ }
96
+ interface MercCapture {
97
+ loop: number;
98
+ type: string;
99
+ team: number;
100
+ time: number;
101
+ }
102
+ interface MercUnitLocation {
103
+ x: number;
104
+ y: number;
105
+ }
106
+ interface MercUnit {
107
+ loop: number;
108
+ team: number;
109
+ type: string;
110
+ locations: MercUnitLocation[];
111
+ time: number;
112
+ duration: number;
113
+ }
114
+ interface MercsData {
115
+ captures: MercCapture[];
116
+ units: Record<string, MercUnit>;
117
+ }
118
+ interface StructureInfo {
119
+ type: string;
43
120
  name: string;
44
- tag: string;
121
+ tag: number;
122
+ rtag: number;
123
+ x: number;
124
+ y: number;
45
125
  team: number;
46
- win: boolean;
47
- gameStats: Record<string, number>;
126
+ destroyedLoop?: number;
127
+ destroyed?: number;
128
+ }
129
+ interface ObjectiveEvent {
130
+ team: number;
131
+ score?: number;
132
+ loop: number;
133
+ time: number;
134
+ duration?: number;
135
+ endLoop?: number;
136
+ end?: number;
137
+ }
138
+ interface TeamObjective {
139
+ count: number;
140
+ events: ObjectiveEvent[];
141
+ }
142
+ interface ObjectiveData {
143
+ 0: TeamObjective;
144
+ 1: TeamObjective;
145
+ type: string;
146
+ }
147
+ interface StructureStats {
148
+ lost: number;
149
+ destroyed: number;
150
+ first: number;
151
+ }
152
+ interface UptimeEntry {
153
+ time: number;
154
+ heroes: number;
155
+ }
156
+ interface TeamTotals {
157
+ DamageTaken: number;
158
+ CreepDamage: number;
159
+ Healing: number;
160
+ HeroDamage: number;
161
+ MinionDamage: number;
162
+ SelfHealing: number;
163
+ SiegeDamage: number;
164
+ ProtectionGivenToAllies: number;
165
+ TeamfightDamageTaken: number;
166
+ TeamfightHealingDone: number;
167
+ TeamfightHeroDamage: number;
168
+ TimeCCdEnemyHeroes: number;
169
+ TimeRootingEnemyHeroes: number;
170
+ TimeSpentDead: number;
171
+ TimeStunningEnemyHeroes: number;
172
+ TimeSilencingEnemyHeroes: number;
173
+ avgTimeSpentDead: number;
174
+ timeDeadPct: number;
175
+ }
176
+ interface TeamStats {
177
+ mercCaptures: number;
178
+ mercUptime: number;
179
+ mercUptimePercent: number;
180
+ structures: Record<string, StructureStats>;
181
+ KDA: number;
182
+ PPK: number;
183
+ timeTo10: number;
184
+ totals: TeamTotals;
185
+ levelAdvTime: number;
186
+ maxLevelAdv: number;
187
+ avgLevelAdv: number;
188
+ levelAdvPct: number;
189
+ uptime: UptimeEntry[];
190
+ uptimeHistogram: Record<string, number>;
191
+ wipes: number;
192
+ avgHeroesAlive: number;
193
+ aces: number;
194
+ timeWithHeroAdv: number;
195
+ pctWithHeroAdv: number;
196
+ passiveXPRate: number;
197
+ passiveXPDiff: number;
198
+ passiveXPGain: number;
48
199
  }
49
200
  interface TeamStat {
50
201
  level: number;
51
202
  takedowns: number;
52
203
  ids: string[];
204
+ names: string[];
205
+ heroes: string[];
206
+ tags: number[];
207
+ stats: TeamStats;
208
+ }
209
+ interface LevelAdvSegment {
210
+ start: number;
211
+ end: number;
212
+ levelDiff: number;
213
+ length: number;
214
+ }
215
+ interface TalentChoices {
216
+ Tier1Choice?: string;
217
+ Tier2Choice?: string;
218
+ Tier3Choice?: string;
219
+ Tier4Choice?: string;
220
+ Tier5Choice?: string;
221
+ Tier6Choice?: string;
222
+ Tier7Choice?: string;
223
+ }
224
+ interface UnitPosition {
225
+ x: number;
226
+ y: number;
227
+ time: number;
228
+ }
229
+ interface UnitLife {
230
+ born: number;
231
+ locations: UnitPosition[];
232
+ died?: number;
233
+ duration: number;
234
+ }
235
+ interface PlayerUnit {
236
+ lives: UnitLife[];
237
+ }
238
+ interface PlayerStat {
239
+ hero: string;
240
+ name: string;
241
+ uuid: number;
242
+ region: number;
243
+ realm: number;
244
+ ToonHandle: string;
245
+ tag: number;
246
+ team: number;
247
+ win: boolean;
248
+ gameStats: Record<string, number>;
249
+ awards: string[];
250
+ talents: TalentChoices;
251
+ takedowns: TakedownEvent[];
252
+ deaths: TakedownEvent[];
253
+ units: Record<string, PlayerUnit>;
53
254
  }
54
255
  interface MatchStat {
256
+ version: ReplayVersion;
257
+ type?: number;
258
+ mode?: number;
55
259
  map?: string;
56
260
  date: string;
261
+ rawDate?: number;
57
262
  length: number;
58
263
  winner: number;
59
- version: {
60
- m_build: number;
61
- };
264
+ region?: number;
265
+ loopLength?: number;
266
+ loopGameStart?: number;
267
+ playerIDs: string[];
268
+ heroes: string[];
269
+ levelTimes: Record<string, Record<string, LevelTime>>;
270
+ bans: Record<string, BanEntry[]>;
271
+ picks: PickData;
272
+ XPBreakdown: XPBreakdownEntry[];
273
+ takedowns: TakedownEvent[];
274
+ mercs: MercsData;
275
+ team0Takedowns: number;
276
+ team1Takedowns: number;
277
+ structures: Record<string, StructureInfo>;
278
+ objective: ObjectiveData;
62
279
  teams: Record<string, TeamStat>;
280
+ winningPlayers: string[];
281
+ levelAdvTimeline: LevelAdvSegment[];
282
+ firstPickWin: boolean;
283
+ firstObjective?: number;
284
+ firstObjectiveWin?: boolean;
285
+ firstFort?: number;
286
+ firstKeep?: number;
287
+ firstFortWin?: boolean;
288
+ firstKeepWin?: boolean;
63
289
  }
290
+ interface AnalysisResult {
291
+ status: number;
292
+ match?: MatchStat;
293
+ players?: Record<string, PlayerStat>;
294
+ error?: string;
295
+ }
296
+
64
297
  declare class ReplayAnalyzer {
65
- static analyze(filePath: string): Promise<{
66
- status: number;
67
- match?: MatchStat;
68
- players?: Record<string, PlayerStat>;
69
- error?: string;
70
- }>;
298
+ static analyze(filePath: string): Promise<AnalysisResult>;
299
+ private static emptyTeam;
300
+ private static extractBattleTags;
301
+ private static extractDraft;
302
+ private static processScoreEvents;
71
303
  private static fileTimeToDate;
72
304
  }
73
305
 
74
- export { type Protocol, ReplayAnalyzer, type ReplayEvent, ReplayParser };
306
+ export { type AnalysisResult, type BanEntry, type KillParticipant, type LevelAdvSegment, type LevelTime, type MatchStat, type MercCapture, type MercUnit, type MercUnitLocation, type MercsData, type ObjectiveData, type ObjectiveEvent, type PickData, type PlayerStat, type PlayerUnit, type Protocol, ReplayAnalyzer, type ReplayEvent, ReplayParser, type ReplayVersion, type StructureInfo, type StructureStats, type TakedownEvent, type TalentChoices, type TeamObjective, type TeamStat, type TeamStats, type TeamTotals, type UnitLife, type UnitPosition, type UptimeEntry, type XPBreakdownEntry, type XPValues };
package/dist/index.d.ts CHANGED
@@ -36,39 +36,271 @@ declare class ReplayParser {
36
36
  getTrackerEvents(): ReplayEvent[];
37
37
  getGameEvents(): ReplayEvent[];
38
38
  extractFile(filename: string): Buffer | null;
39
+ getHeader(): Record<string, unknown> | undefined;
40
+ getBuild(): number;
39
41
  }
40
42
 
41
- interface PlayerStat {
43
+ interface ReplayVersion {
44
+ m_flags: number;
45
+ m_major: number;
46
+ m_minor: number;
47
+ m_revision: number;
48
+ m_build: number;
49
+ m_baseBuild: number;
50
+ }
51
+ interface KillParticipant {
52
+ player: string;
42
53
  hero: string;
54
+ }
55
+ interface TakedownEvent {
56
+ loop: number;
57
+ time: number;
58
+ x: number;
59
+ y: number;
60
+ killers: KillParticipant[];
61
+ victim: KillParticipant;
62
+ }
63
+ interface LevelTime {
64
+ loop: number;
65
+ level: number;
66
+ team: string;
67
+ time: number;
68
+ }
69
+ interface BanEntry {
70
+ hero: string;
71
+ order: number;
72
+ absolute: number;
73
+ }
74
+ interface PickData {
75
+ 0: string[];
76
+ 1: string[];
77
+ first: number;
78
+ }
79
+ interface XPValues {
80
+ GameTime: number;
81
+ PreviousGameTime: number;
82
+ MinionXP: number;
83
+ CreepXP: number;
84
+ StructureXP: number;
85
+ HeroXP: number;
86
+ TrickleXP: number;
87
+ }
88
+ interface XPBreakdownEntry {
89
+ loop: number;
90
+ time: number;
91
+ team: number;
92
+ teamLevel?: number;
93
+ breakdown: XPValues;
94
+ theoreticalMinionXP: number;
95
+ }
96
+ interface MercCapture {
97
+ loop: number;
98
+ type: string;
99
+ team: number;
100
+ time: number;
101
+ }
102
+ interface MercUnitLocation {
103
+ x: number;
104
+ y: number;
105
+ }
106
+ interface MercUnit {
107
+ loop: number;
108
+ team: number;
109
+ type: string;
110
+ locations: MercUnitLocation[];
111
+ time: number;
112
+ duration: number;
113
+ }
114
+ interface MercsData {
115
+ captures: MercCapture[];
116
+ units: Record<string, MercUnit>;
117
+ }
118
+ interface StructureInfo {
119
+ type: string;
43
120
  name: string;
44
- tag: string;
121
+ tag: number;
122
+ rtag: number;
123
+ x: number;
124
+ y: number;
45
125
  team: number;
46
- win: boolean;
47
- gameStats: Record<string, number>;
126
+ destroyedLoop?: number;
127
+ destroyed?: number;
128
+ }
129
+ interface ObjectiveEvent {
130
+ team: number;
131
+ score?: number;
132
+ loop: number;
133
+ time: number;
134
+ duration?: number;
135
+ endLoop?: number;
136
+ end?: number;
137
+ }
138
+ interface TeamObjective {
139
+ count: number;
140
+ events: ObjectiveEvent[];
141
+ }
142
+ interface ObjectiveData {
143
+ 0: TeamObjective;
144
+ 1: TeamObjective;
145
+ type: string;
146
+ }
147
+ interface StructureStats {
148
+ lost: number;
149
+ destroyed: number;
150
+ first: number;
151
+ }
152
+ interface UptimeEntry {
153
+ time: number;
154
+ heroes: number;
155
+ }
156
+ interface TeamTotals {
157
+ DamageTaken: number;
158
+ CreepDamage: number;
159
+ Healing: number;
160
+ HeroDamage: number;
161
+ MinionDamage: number;
162
+ SelfHealing: number;
163
+ SiegeDamage: number;
164
+ ProtectionGivenToAllies: number;
165
+ TeamfightDamageTaken: number;
166
+ TeamfightHealingDone: number;
167
+ TeamfightHeroDamage: number;
168
+ TimeCCdEnemyHeroes: number;
169
+ TimeRootingEnemyHeroes: number;
170
+ TimeSpentDead: number;
171
+ TimeStunningEnemyHeroes: number;
172
+ TimeSilencingEnemyHeroes: number;
173
+ avgTimeSpentDead: number;
174
+ timeDeadPct: number;
175
+ }
176
+ interface TeamStats {
177
+ mercCaptures: number;
178
+ mercUptime: number;
179
+ mercUptimePercent: number;
180
+ structures: Record<string, StructureStats>;
181
+ KDA: number;
182
+ PPK: number;
183
+ timeTo10: number;
184
+ totals: TeamTotals;
185
+ levelAdvTime: number;
186
+ maxLevelAdv: number;
187
+ avgLevelAdv: number;
188
+ levelAdvPct: number;
189
+ uptime: UptimeEntry[];
190
+ uptimeHistogram: Record<string, number>;
191
+ wipes: number;
192
+ avgHeroesAlive: number;
193
+ aces: number;
194
+ timeWithHeroAdv: number;
195
+ pctWithHeroAdv: number;
196
+ passiveXPRate: number;
197
+ passiveXPDiff: number;
198
+ passiveXPGain: number;
48
199
  }
49
200
  interface TeamStat {
50
201
  level: number;
51
202
  takedowns: number;
52
203
  ids: string[];
204
+ names: string[];
205
+ heroes: string[];
206
+ tags: number[];
207
+ stats: TeamStats;
208
+ }
209
+ interface LevelAdvSegment {
210
+ start: number;
211
+ end: number;
212
+ levelDiff: number;
213
+ length: number;
214
+ }
215
+ interface TalentChoices {
216
+ Tier1Choice?: string;
217
+ Tier2Choice?: string;
218
+ Tier3Choice?: string;
219
+ Tier4Choice?: string;
220
+ Tier5Choice?: string;
221
+ Tier6Choice?: string;
222
+ Tier7Choice?: string;
223
+ }
224
+ interface UnitPosition {
225
+ x: number;
226
+ y: number;
227
+ time: number;
228
+ }
229
+ interface UnitLife {
230
+ born: number;
231
+ locations: UnitPosition[];
232
+ died?: number;
233
+ duration: number;
234
+ }
235
+ interface PlayerUnit {
236
+ lives: UnitLife[];
237
+ }
238
+ interface PlayerStat {
239
+ hero: string;
240
+ name: string;
241
+ uuid: number;
242
+ region: number;
243
+ realm: number;
244
+ ToonHandle: string;
245
+ tag: number;
246
+ team: number;
247
+ win: boolean;
248
+ gameStats: Record<string, number>;
249
+ awards: string[];
250
+ talents: TalentChoices;
251
+ takedowns: TakedownEvent[];
252
+ deaths: TakedownEvent[];
253
+ units: Record<string, PlayerUnit>;
53
254
  }
54
255
  interface MatchStat {
256
+ version: ReplayVersion;
257
+ type?: number;
258
+ mode?: number;
55
259
  map?: string;
56
260
  date: string;
261
+ rawDate?: number;
57
262
  length: number;
58
263
  winner: number;
59
- version: {
60
- m_build: number;
61
- };
264
+ region?: number;
265
+ loopLength?: number;
266
+ loopGameStart?: number;
267
+ playerIDs: string[];
268
+ heroes: string[];
269
+ levelTimes: Record<string, Record<string, LevelTime>>;
270
+ bans: Record<string, BanEntry[]>;
271
+ picks: PickData;
272
+ XPBreakdown: XPBreakdownEntry[];
273
+ takedowns: TakedownEvent[];
274
+ mercs: MercsData;
275
+ team0Takedowns: number;
276
+ team1Takedowns: number;
277
+ structures: Record<string, StructureInfo>;
278
+ objective: ObjectiveData;
62
279
  teams: Record<string, TeamStat>;
280
+ winningPlayers: string[];
281
+ levelAdvTimeline: LevelAdvSegment[];
282
+ firstPickWin: boolean;
283
+ firstObjective?: number;
284
+ firstObjectiveWin?: boolean;
285
+ firstFort?: number;
286
+ firstKeep?: number;
287
+ firstFortWin?: boolean;
288
+ firstKeepWin?: boolean;
63
289
  }
290
+ interface AnalysisResult {
291
+ status: number;
292
+ match?: MatchStat;
293
+ players?: Record<string, PlayerStat>;
294
+ error?: string;
295
+ }
296
+
64
297
  declare class ReplayAnalyzer {
65
- static analyze(filePath: string): Promise<{
66
- status: number;
67
- match?: MatchStat;
68
- players?: Record<string, PlayerStat>;
69
- error?: string;
70
- }>;
298
+ static analyze(filePath: string): Promise<AnalysisResult>;
299
+ private static emptyTeam;
300
+ private static extractBattleTags;
301
+ private static extractDraft;
302
+ private static processScoreEvents;
71
303
  private static fileTimeToDate;
72
304
  }
73
305
 
74
- export { type Protocol, ReplayAnalyzer, type ReplayEvent, ReplayParser };
306
+ export { type AnalysisResult, type BanEntry, type KillParticipant, type LevelAdvSegment, type LevelTime, type MatchStat, type MercCapture, type MercUnit, type MercUnitLocation, type MercsData, type ObjectiveData, type ObjectiveEvent, type PickData, type PlayerStat, type PlayerUnit, type Protocol, ReplayAnalyzer, type ReplayEvent, ReplayParser, type ReplayVersion, type StructureInfo, type StructureStats, type TakedownEvent, type TalentChoices, type TeamObjective, type TeamStat, type TeamStats, type TeamTotals, type UnitLife, type UnitPosition, type UptimeEntry, type XPBreakdownEntry, type XPValues };
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- "use strict";var C=Object.create;var P=Object.defineProperty;var q=Object.getOwnPropertyDescriptor;var j=Object.getOwnPropertyNames;var G=Object.getPrototypeOf,V=Object.prototype.hasOwnProperty;var W=(i,e)=>{for(var t in e)P(i,t,{get:e[t],enumerable:!0})},Q=(i,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of j(e))!V.call(i,n)&&n!==t&&P(i,n,{get:()=>e[n],enumerable:!(r=q(e,n))||r.enumerable});return i};var M=(i,e,t)=>(t=i!=null?C(G(i)):{},Q(e||!i||!i.__esModule?P(t,"default",{value:i,enumerable:!0}):t,i)),X=i=>Q(P({},"__esModule",{value:!0}),i);var se={};W(se,{ReplayAnalyzer:()=>L,ReplayParser:()=>x});module.exports=X(se);var A=M(require("fs")),S=M(require("zlib")),J=require("seek-bzip"),z=512,K=65536,Y=16777216,Z=67108864,ee=2147483648,te={TABLE_OFFSET:0,HASH_A:1,HASH_B:2,TABLE:3};function re(){let i=1048577,e=new Uint32Array(256*5);for(let t=0;t<256;t++){let r=t;for(let n=0;n<5;n++){i=(i*125+3)%2796203;let s=(i&65535)<<16;i=(i*125+3)%2796203;let l=i&65535;e[r]=(s|l)>>>0,r+=256}}return e}var F=re(),R=class{file;header;hashTable;blockTable;files;constructor(e,t=!0){if(Buffer.isBuffer(e)?this.file=e:this.file=A.readFileSync(e),this.header=this.readHeader(),this.hashTable=this.readTable("hash"),this.blockTable=this.readTable("block"),t){let r=this.readFile("(listfile)");r?this.files=r.toString("utf8").trim().split(`\r
2
- `):this.files=null}else this.files=null}readHeader(){let e=this.file.toString("utf8",0,4),t;if(e==="MPQ")t=this.readMPQHeader(),t.offset=0;else if(e==="MPQ\x1B"){let r=this.readMPQUserDataHeader();t=this.readMPQHeader(r.mpqHeaderOffset),t.offset=r.mpqHeaderOffset,t.userDataHeader=r}else throw new Error("Invalid MPQ file header");return t}readMPQHeader(e=0){let t=this.file.subarray(e,e+32),r={magic:t.toString("utf8",0,4),headerSize:t.readUInt32LE(4),archiveSize:t.readUInt32LE(8),formatVersion:t.readUInt16LE(12),sectorSizeShift:t.readUInt16LE(14),hashTableOffset:t.readUInt32LE(16),blockTableOffset:t.readUInt32LE(20),hashTableEntries:t.readUInt32LE(24),blockTableEntries:t.readUInt32LE(28)};if(r.formatVersion===1){let n=this.file.subarray(e+32,e+32+12);r.extendedBlockTableOffset=n.readUInt32LE(0)+n.readUInt32LE(4)*4294967296,r.hashTableOffsetHigh=n.readInt8(8),r.blockTableOffsetHigh=n.readInt8(10)}return r}readMPQUserDataHeader(){let e=this.file.subarray(0,16),t={magic:e.toString("utf8",0,4),userDataSize:e.readUInt32LE(4),mpqHeaderOffset:e.readUInt32LE(8),userDataHeaderSize:e.readUInt32LE(12)};return t.content=this.file.subarray(16,16+t.userDataHeaderSize),t}readTable(e){let t=e==="hash"?"hashTableOffset":"blockTableOffset",r=e==="hash"?"hashTableEntries":"blockTableEntries",n=this.header[t],s=this.header[r];if(n==null||s==null)throw new Error("Missing "+e+" offset or entries");let l=this.hash("("+e+" table)","TABLE"),a=this.file.subarray(n+(this.header.offset||0),n+(this.header.offset||0)+s*16);a=this.decrypt(a,l);let c=[];for(let _=0;_<s;_++){let h=a.subarray(_*16,_*16+16);e==="hash"?c.push({hashA:h.readUInt32LE(0),hashB:h.readUInt32LE(4),locale:h.readUInt16LE(8),platform:h.readUInt16LE(10),blockTableIndex:h.readUInt32LE(12)}):c.push({offset:h.readUInt32LE(0),archivedSize:h.readUInt32LE(4),size:h.readUInt32LE(8),flags:h.readUInt32LE(12)})}return c}getHashTableEntry(e){let t=this.hash(e,"HASH_A"),r=this.hash(e,"HASH_B");for(let n of this.hashTable)if(n.hashA===t&&n.hashB===r)return n}readFile(e,t=!1){function r(c){let _=c[0];if(_===0)return c;if(_===2)return S.inflateSync(c.subarray(1));if(_===16)return J.decode(c.subarray(1));try{return S.inflateSync(c.subarray(1))}catch{return S.inflateRawSync(c.subarray(1))}}let n=this.getHashTableEntry(e);if(!n)return null;let s=this.blockTable[n.blockTableIndex];if(!s||!(s.flags&ee))return null;if(s.archivedSize===0)return Buffer.alloc(0);let l=s.offset+(this.header.offset||0),a=this.file.subarray(l,l+s.archivedSize);if(s.flags&K)throw new Error("Encryption is not supported");if(s.flags&Y)s.flags&z&&(t||s.size>s.archivedSize)&&(a=r(a));else{let c=512<<this.header.sectorSizeShift,_=Math.trunc(s.size/c)+1,h=!1;s.flags&Z&&(h=!0,_+=1);let o=[];for(let d=0;d<_+1;d++)o.push(a.readUInt32LE(4*d));let u=o.length-(h?2:1),f=[],b=s.size;for(let d=0;d<u;d++){let m=a.subarray(o[d],o[d+1]);s.flags&z&&(t||b>m.length)&&(m=r(m)),b-=m.length,f.push(m)}a=Buffer.concat(f)}return a}hash(e,t){let r=2146271213,n=4008636142;for(let s=0;s<e.length;s++){let l=e.toUpperCase().charCodeAt(s);r=(F[(te[t]<<8)+l]^r+n)>>>0,n=l+r+n+(n<<5)+3>>>0}return r}decrypt(e,t){let r=t>>>0,n=4008636142,s=Buffer.alloc(e.length),l=e.length/4;for(let a=0;a<l;a++){n=n+F[1024+(r&255)]>>>0;let c=e.readUInt32LE(a*4);c=(c^r+n)>>>0,r=((~r<<21)+286331153|r>>>11)>>>0,n=c+n+(n<<5)+3>>>0,s.writeUInt32LE(c,a*4)}return s}};var B=class extends Error{constructor(e="Truncated Buffer"){super(e),this.name="TruncatedError"}},y=class extends Error{constructor(e="Corrupted Buffer"){super(e),this.name="CorruptedError"}},T=class{_data;_used;_next;_nextbits;_bigendian;constructor(e,t="big"){this._data=e,this._used=0,this._next=0,this._nextbits=0,this._bigendian=t==="big"}done(){return this._nextbits===0&&this._used>=this._data.length}used_bits(){return this._used*8-this._nextbits}byte_align(){this._nextbits=0}read_aligned_bytes(e){if(this.byte_align(),this._used+e>this._data.length)throw new B;let t=this._data.subarray(this._used,this._used+e);return this._used+=e,t}read_bits(e){let t=0,r=0;for(;r!==e;){if(this._nextbits===0){if(this.done())throw new B;this._next=this._data[this._used],this._used+=1,this._nextbits=8}let n=Math.min(e-r,this._nextbits),s=this._next&(1<<n)-1;this._bigendian?t+=s*Math.pow(2,e-r-n):t+=s*Math.pow(2,r),this._next>>=n,this._nextbits-=n,r+=n}return t}read_bits_bigint(e){let t=0n,r=0;for(;r!==e;){if(this._nextbits===0){if(this.done())throw new B;this._next=this._data[this._used],this._used+=1,this._nextbits=8}let n=Math.min(e-r,this._nextbits),s=BigInt(this._next&(1<<n)-1);this._bigendian?t|=s<<BigInt(e-r-n):t|=s<<BigInt(r),this._next>>=n,this._nextbits-=n,r+=n}return t}read_unaligned_bytes(e){let t=Buffer.alloc(e);for(let r=0;r<e;r++)t[r]=this.read_bits(8);return t}};var I=class{_buffer;_typeinfos;constructor(e,t){this._buffer=new T(e),this._typeinfos=t}instance(e){if(e>=this._typeinfos.length)throw new y(`Invalid typeid ${e}`);let t=this._typeinfos[e],r=t[0],n=t[1]||[],s=this[r];if(typeof s!="function")throw new Error(`Decoder method ${r} not implemented`);return s.apply(this,n)}byte_align(){this._buffer.byte_align()}done(){return this._buffer.done()}used_bits(){return this._buffer.used_bits()}_array(e,t){let r=this._int(e),n=new Array(r);for(let s=0;s<r;s++)n[s]=this.instance(t);return n}_bitarray(e){let t=this._int(e);return[t,this._buffer.read_bits(t)]}_blob(e){let t=this._int(e);return this._buffer.read_aligned_bytes(t)}_bool(){return this._int([0,1])!==0}_choice(e,t){let r=this._int(e);if(!(r in t))throw new y(`Choice tag ${r} not found`);let n=t[r];return{[n[0]]:this.instance(n[1])}}_fourcc(){let e=this._buffer.read_bits(32),t=Buffer.alloc(4);return t.writeUInt32BE(e,0),t.toString("ascii")}_int(e){return e[0]+this._buffer.read_bits(e[1])}_null(){return null}_optional(e){return this._bool()?this.instance(e):null}_real32(){return this._buffer.read_unaligned_bytes(4).readFloatBE(0)}_real64(){return this._buffer.read_unaligned_bytes(8).readDoubleBE(0)}_struct(e){let t={};for(let r of e)if(r[0]==="__parent"){let n=this.instance(r[1]);if(typeof n=="object"&&n!==null)t={...t,...n};else{if(e.length===1)return n;t[r[0]]=n}}else t[r[0]]=this.instance(r[1]);return t}},w=class{_buffer;_typeinfos;constructor(e,t){this._buffer=new T(e),this._typeinfos=t}instance(e){if(e>=this._typeinfos.length)throw new y(`Invalid typeid ${e}`);let t=this._typeinfos[e],r=t[0],n=t[1]||[],s=this[r];if(typeof s!="function")throw new Error(`Decoder method ${r} not implemented`);return s.apply(this,n)}byte_align(){this._buffer.byte_align()}done(){return this._buffer.done()}used_bits(){return this._buffer.used_bits()}_expect_skip(e){if(this._buffer.read_bits(8)!==e)throw new y(`Expected skip ${e}`)}_vint(){let e=this._buffer.read_bits(8),t=(e&1)!==0,r=BigInt(e>>1&63),n=6n;for(;(e&128)!==0;)e=this._buffer.read_bits(8),r|=BigInt(e&127)<<n,n+=7n;let s=t?-r:r;return s>=BigInt(Number.MIN_SAFE_INTEGER)&&s<=BigInt(Number.MAX_SAFE_INTEGER)?Number(s):s}_array(e,t){this._expect_skip(0);let r=Number(this._vint()),n=new Array(r);for(let s=0;s<r;s++)n[s]=this.instance(t);return n}_bitarray(e){this._expect_skip(1);let t=Number(this._vint());return[t,this._buffer.read_aligned_bytes(Math.floor((t+7)/8))]}_blob(e){this._expect_skip(2);let t=Number(this._vint());return this._buffer.read_aligned_bytes(t)}_bool(){return this._expect_skip(6),this._buffer.read_bits(8)!==0}_choice(e,t){this._expect_skip(3);let r=Number(this._vint());if(!(r in t))return this._skip_instance(),{};let n=t[r];return{[n[0]]:this.instance(n[1])}}_fourcc(){return this._expect_skip(7),this._buffer.read_aligned_bytes(4)}_int(e){return this._expect_skip(9),Number(this._vint())}_null(){return null}_optional(e){return this._expect_skip(4),this._buffer.read_bits(8)!==0?this.instance(e):null}_real32(){return this._expect_skip(7),this._buffer.read_aligned_bytes(4).readFloatBE(0)}_real64(){return this._expect_skip(8),this._buffer.read_aligned_bytes(8).readDoubleBE(0)}_struct(e){this._expect_skip(5);let t={},r=Number(this._vint());for(let n=0;n<r;n++){let s=Number(this._vint()),l=e.find(a=>a[2]===s);if(l)if(l[0]==="__parent"){let a=this.instance(l[1]);typeof a=="object"&&a!==null?t={...t,...a}:e.length===1?t=a:t[l[0]]=a}else t[l[0]]=this.instance(l[1]);else this._skip_instance()}return t}_skip_instance(){let e=this._buffer.read_bits(8);if(e===0){let t=Number(this._vint());for(let r=0;r<t;r++)this._skip_instance()}else if(e===1){let t=Number(this._vint());this._buffer.read_aligned_bytes(Math.floor((t+7)/8))}else if(e===2){let t=Number(this._vint());this._buffer.read_aligned_bytes(t)}else if(e===3)this._vint(),this._skip_instance();else if(e===4)this._buffer.read_bits(8)!==0&&this._skip_instance();else if(e===5){let t=Number(this._vint());for(let r=0;r<t;r++)this._vint(),this._skip_instance()}else e===6?this._buffer.read_aligned_bytes(1):e===7?this._buffer.read_aligned_bytes(4):e===8?this._buffer.read_aligned_bytes(8):e===9&&this._vint()}};var E=M(require("fs")),g=M(require("path"));function ne(){let i=[];try{i.push(g.default.resolve(__dirname,"..","protocols")),i.push(g.default.resolve(__dirname,"..","..","protocols"))}catch{}i.push(g.default.resolve(process.cwd(),"protocols"));let e=process.cwd();for(;e!==g.default.dirname(e);){let t=g.default.join(e,"node_modules","@astefanski","storm-parser","protocols");i.push(t),e=g.default.dirname(e)}for(let t of i)if(E.default.existsSync(t)&&E.default.readdirSync(t).some(r=>r.endsWith(".json")))return t;throw new Error(`@astefanski/storm-parser: Protocols directory not found. Did postinstall run? Try: npx tsx node_modules/@astefanski/storm-parser/scripts/postinstall.ts
3
- Searched: `+i.join(", "))}var U=new Map,N=null;function O(){return N||(N=ne()),N}function H(i){if(U.has(i))return U.get(i);let e=O(),t=g.default.join(e,`protocol${i}.json`);if(!E.default.existsSync(t))return null;let r=JSON.parse(E.default.readFileSync(t,"utf-8"));return U.set(i,r),r}function $(){let i=O();return E.default.readdirSync(i).filter(e=>/^protocol\d+\.json$/.test(e)).map(e=>parseInt(e.match(/\d+/)[0],10)).sort((e,t)=>e-t)}var x=class{mpq;header;build=0;protocol;baseProtocol;constructor(e){this.mpq=new R(e,!1)}init(){let e=H(29406);if(!e)throw new Error("Base protocol29406 not found. Did postinstall run? Try: npx tsx node_modules/@astefanski/storm-parser/scripts/postinstall.ts");this.baseProtocol=e;let t=this.mpq.header.userDataHeader;if(!t||!t.content)throw new Error("Replay does not have a user data header");let r=new w(t.content,this.baseProtocol.typeinfos);this.header=r.instance(this.baseProtocol.replay_header_typeid),this.build=this.header.m_version.m_baseBuild,this.protocol=this.loadProtocolForBuild(this.build)}loadProtocolForBuild(e){let t=H(e);if(!t){let r=$(),n=r[0];for(let s of r)s<=e&&s>n&&(n=s);t=H(n)}if(!t)throw new Error(`No protocol found for build ${e}`);return t}*decodeEventStream(e,t,r,n){if(!this.protocol)throw new Error("Protocol not loaded");let s=0;for(;!e.done();){let l=e.used_bits(),a=e.instance(this.protocol.svaruint32_typeid),c=Object.keys(a)[0],_=a[c];s+=_;let h=n?e.instance(this.protocol.replay_userid_typeid):void 0,o=Number(e.instance(t)),u=r[o];if(!u)throw new Error(`Unknown eventid(${o})`);let f=u[0],b=u[1],d=e.instance(f);d._event=b,d._eventid=o,d._gameloop=s,n&&(d._userid=h),e.byte_align(),d._bits=e.used_bits()-l,yield d}}getDetails(){if(!this.protocol)throw new Error("Protocol not loaded");let e=this.mpq.readFile("replay.details");return e?new w(e,this.protocol.typeinfos).instance(this.protocol.game_details_typeid):null}getInitData(){if(!this.protocol)throw new Error("Protocol not loaded");let e=this.mpq.readFile("replay.initData");return e?new I(e,this.protocol.typeinfos).instance(this.protocol.replay_initdata_typeid):null}getTrackerEvents(){if(!this.protocol)throw new Error("Protocol not loaded");let e=this.mpq.readFile("replay.tracker.events");if(!e)return[];let t=new w(e,this.protocol.typeinfos);return Array.from(this.decodeEventStream(t,this.protocol.tracker_eventid_typeid,this.protocol.tracker_event_types,!1))}getGameEvents(){if(!this.protocol)throw new Error("Protocol not loaded");let e=this.mpq.readFile("replay.game.events");if(!e)return[];let t=new I(e,this.protocol.typeinfos);return Array.from(this.decodeEventStream(t,this.protocol.game_eventid_typeid,this.protocol.game_event_types,!0))}extractFile(e){return this.mpq.readFile(e)}};var L=class{static async analyze(e){try{let t=new x(e);await t.init();let r=t.getDetails(),n=t.getTrackerEvents();if(!r)throw new Error("Missing replay.details from parsed MPQ archive");console.log("Raw TimeUTC:",r?.m_timeUTC,"Type:",typeof r?.m_timeUTC);let s={map:r?.m_title?.toString("utf8"),date:r?this.fileTimeToDate(r.m_timeUTC).toISOString():new Date().toISOString(),length:0,winner:-1,version:{m_build:t.build},teams:{0:{level:0,takedowns:0,ids:[]},1:{level:0,takedowns:0,ids:[]}}},l={};for(let o of r.m_playerList){if(!o||!o.m_toon)continue;let u=o.m_toon,f=`${u.m_region}-${u.m_programId}-${u.m_realm}-${u.m_id}`;l[f]={hero:o.m_hero?.toString("utf8")||"",name:o.m_name?.toString("utf8")||"",tag:"",team:o.m_teamId,win:o.m_result===1,gameStats:{}}}let a=t.extractFile("replay.server.battlelobby");if(a)try{let o=new RegExp("([\\p{L}\\d]{3,24}#\\d{4,10})[z\xD8]?","gu"),u=a.toString("utf8").match(o);if(u){let f=0;for(let b of r.m_playerList){if(!b||!b.m_toon)continue;let d=b.m_name?.toString("utf8");for(;f<u.length;){let p=u[f].split("#"),D=p[0],k=p[1].replace(/[zØ]/g,"");if(f++,D===d){let v=`${b.m_toon.m_region}-${b.m_toon.m_programId}-${b.m_toon.m_realm}-${b.m_toon.m_id}`;l[v]&&(l[v].tag=k);break}}}}}catch(o){console.error("BattleTag regex error:",o)}let c={},_=0,h=0;for(let o of n){if(o._event==="NNet.Replay.Tracker.SStatGameEvent"){let u=o.m_eventName?.toString("utf8");if(u==="PlayerInit"){let f=o.m_intData,b=o.m_stringData,d=f[0].m_value,m=b[1].m_value?.toString("utf8");m&&l[m]&&(c[d]=m)}else u==="GatesOpen"&&(_=o._gameloop)}h=Math.max(h,o._gameloop)}s.length=Math.floor((h-_)/16);for(let o of n)if(o._event==="NNet.Replay.Tracker.SScoreResultEvent"){let u=o.m_instanceList;for(let f of u){let b=f.m_name?.toString("utf8"),d=f.m_values,m=0;if(!b.startsWith("EndOfMatchAward"))for(let p of d)if(p&&p.length>0&&p[0]!==void 0){let D=m+1,k=c[D];if(k&&l[k]){let v=typeof p[0]=="object"&&p[0]!==null&&"m_value"in p[0]?p[0].m_value:p[0];v!=null&&(l[k].gameStats[b]=v)}m++}else p&&p.length===0&&m++}}for(let[o,u]of Object.entries(l)){u.win&&(s.winner=u.team);let f=s.teams[u.team.toString()];f&&(f.level=Math.max(f.level,u.gameStats.Level||0),f.takedowns+=u.gameStats.Takedowns||0,f.ids.push(o))}return{status:1,match:s,players:l}}catch(t){return console.error("ReplayAnalyzer Error:",t),{status:-2,error:String(t)}}}static fileTimeToDate(e){return new Date(Number(e)/1e4-116444736e5)}};0&&(module.exports={ReplayAnalyzer,ReplayParser});
1
+ "use strict";var Z=Object.create;var L=Object.defineProperty;var ee=Object.getOwnPropertyDescriptor;var te=Object.getOwnPropertyNames;var ne=Object.getPrototypeOf,re=Object.prototype.hasOwnProperty;var ie=(r,e)=>{for(var t in e)L(r,t,{get:e[t],enumerable:!0})},G=(r,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of te(e))!re.call(r,i)&&i!==t&&L(r,i,{get:()=>e[i],enumerable:!(n=ee(e,i))||n.enumerable});return r};var A=(r,e,t)=>(t=r!=null?Z(ne(r)):{},G(e||!r||!r.__esModule?L(t,"default",{value:r,enumerable:!0}):t,r)),se=r=>G(L({},"__esModule",{value:!0}),r);var Re={};ie(Re,{ReplayAnalyzer:()=>N,ReplayParser:()=>D});module.exports=se(Re);var z=A(require("fs")),M=A(require("zlib")),oe=require("seek-bzip"),F=512,ae=65536,le=16777216,ce=67108864,ue=2147483648,de={TABLE_OFFSET:0,HASH_A:1,HASH_B:2,TABLE:3};function fe(){let r=1048577,e=new Uint32Array(256*5);for(let t=0;t<256;t++){let n=t;for(let i=0;i<5;i++){r=(r*125+3)%2796203;let s=(r&65535)<<16;r=(r*125+3)%2796203;let a=r&65535;e[n]=(s|a)>>>0,n+=256}}return e}var Q=fe(),C=class{file;header;hashTable;blockTable;files;constructor(e,t=!0){if(Buffer.isBuffer(e)?this.file=e:this.file=z.readFileSync(e),this.header=this.readHeader(),this.hashTable=this.readTable("hash"),this.blockTable=this.readTable("block"),t){let n=this.readFile("(listfile)");n?this.files=n.toString("utf8").trim().split(`\r
2
+ `):this.files=null}else this.files=null}readHeader(){let e=this.file.toString("utf8",0,4),t;if(e==="MPQ")t=this.readMPQHeader(),t.offset=0;else if(e==="MPQ\x1B"){let n=this.readMPQUserDataHeader();t=this.readMPQHeader(n.mpqHeaderOffset),t.offset=n.mpqHeaderOffset,t.userDataHeader=n}else throw new Error("Invalid MPQ file header");return t}readMPQHeader(e=0){let t=this.file.subarray(e,e+32),n={magic:t.toString("utf8",0,4),headerSize:t.readUInt32LE(4),archiveSize:t.readUInt32LE(8),formatVersion:t.readUInt16LE(12),sectorSizeShift:t.readUInt16LE(14),hashTableOffset:t.readUInt32LE(16),blockTableOffset:t.readUInt32LE(20),hashTableEntries:t.readUInt32LE(24),blockTableEntries:t.readUInt32LE(28)};if(n.formatVersion===1){let i=this.file.subarray(e+32,e+32+12);n.extendedBlockTableOffset=i.readUInt32LE(0)+i.readUInt32LE(4)*4294967296,n.hashTableOffsetHigh=i.readInt8(8),n.blockTableOffsetHigh=i.readInt8(10)}return n}readMPQUserDataHeader(){let e=this.file.subarray(0,16),t={magic:e.toString("utf8",0,4),userDataSize:e.readUInt32LE(4),mpqHeaderOffset:e.readUInt32LE(8),userDataHeaderSize:e.readUInt32LE(12)};return t.content=this.file.subarray(16,16+t.userDataHeaderSize),t}readTable(e){let t=e==="hash"?"hashTableOffset":"blockTableOffset",n=e==="hash"?"hashTableEntries":"blockTableEntries",i=this.header[t],s=this.header[n];if(i==null||s==null)throw new Error("Missing "+e+" offset or entries");let a=this.hash("("+e+" table)","TABLE"),o=this.file.subarray(i+(this.header.offset||0),i+(this.header.offset||0)+s*16);o=this.decrypt(o,a);let l=[];for(let u=0;u<s;u++){let c=o.subarray(u*16,u*16+16);e==="hash"?l.push({hashA:c.readUInt32LE(0),hashB:c.readUInt32LE(4),locale:c.readUInt16LE(8),platform:c.readUInt16LE(10),blockTableIndex:c.readUInt32LE(12)}):l.push({offset:c.readUInt32LE(0),archivedSize:c.readUInt32LE(4),size:c.readUInt32LE(8),flags:c.readUInt32LE(12)})}return l}getHashTableEntry(e){let t=this.hash(e,"HASH_A"),n=this.hash(e,"HASH_B");for(let i of this.hashTable)if(i.hashA===t&&i.hashB===n)return i}readFile(e,t=!1){function n(l){let u=l[0];if(u===0)return l;if(u===2)return M.inflateSync(l.subarray(1));if(u===16)return oe.decode(l.subarray(1));try{return M.inflateSync(l.subarray(1))}catch{return M.inflateRawSync(l.subarray(1))}}let i=this.getHashTableEntry(e);if(!i)return null;let s=this.blockTable[i.blockTableIndex];if(!s||!(s.flags&ue))return null;if(s.archivedSize===0)return Buffer.alloc(0);let a=s.offset+(this.header.offset||0),o=this.file.subarray(a,a+s.archivedSize);if(s.flags&ae)throw new Error("Encryption is not supported");if(s.flags&le)s.flags&F&&(t||s.size>s.archivedSize)&&(o=n(o));else{let l=512<<this.header.sectorSizeShift,u=Math.trunc(s.size/l)+1,c=!1;s.flags&ce&&(c=!0,u+=1);let d=[];for(let h=0;h<u+1;h++)d.push(o.readUInt32LE(4*h));let f=d.length-(c?2:1),b=[],m=s.size;for(let h=0;h<f;h++){let p=o.subarray(d[h],d[h+1]);s.flags&F&&(t||m>p.length)&&(p=n(p)),m-=p.length,b.push(p)}o=Buffer.concat(b)}return o}hash(e,t){let n=2146271213,i=4008636142;for(let s=0;s<e.length;s++){let a=e.toUpperCase().charCodeAt(s);n=(Q[(de[t]<<8)+a]^n+i)>>>0,i=a+n+i+(i<<5)+3>>>0}return n}decrypt(e,t){let n=t>>>0,i=4008636142,s=Buffer.alloc(e.length),a=e.length/4;for(let o=0;o<a;o++){i=i+Q[1024+(n&255)]>>>0;let l=e.readUInt32LE(o*4);l=(l^n+i)>>>0,n=((~n<<21)+286331153|n>>>11)>>>0,i=l+i+(i<<5)+3>>>0,s.writeUInt32LE(l,o*4)}return s}};var H=class extends Error{constructor(e="Truncated Buffer"){super(e),this.name="TruncatedError"}},w=class extends Error{constructor(e="Corrupted Buffer"){super(e),this.name="CorruptedError"}},B=class{_data;_used;_next;_nextbits;_bigendian;constructor(e,t="big"){this._data=e,this._used=0,this._next=0,this._nextbits=0,this._bigendian=t==="big"}done(){return this._nextbits===0&&this._used>=this._data.length}used_bits(){return this._used*8-this._nextbits}byte_align(){this._nextbits=0}read_aligned_bytes(e){if(this.byte_align(),this._used+e>this._data.length)throw new H;let t=this._data.subarray(this._used,this._used+e);return this._used+=e,t}read_bits(e){let t=0,n=0;for(;n!==e;){if(this._nextbits===0){if(this.done())throw new H;this._next=this._data[this._used],this._used+=1,this._nextbits=8}let i=Math.min(e-n,this._nextbits),s=this._next&(1<<i)-1;this._bigendian?t+=s*Math.pow(2,e-n-i):t+=s*Math.pow(2,n),this._next>>=i,this._nextbits-=i,n+=i}return t}read_bits_bigint(e){let t=0n,n=0;for(;n!==e;){if(this._nextbits===0){if(this.done())throw new H;this._next=this._data[this._used],this._used+=1,this._nextbits=8}let i=Math.min(e-n,this._nextbits),s=BigInt(this._next&(1<<i)-1);this._bigendian?t|=s<<BigInt(e-n-i):t|=s<<BigInt(n),this._next>>=i,this._nextbits-=i,n+=i}return t}read_unaligned_bytes(e){let t=Buffer.alloc(e);for(let n=0;n<e;n++)t[n]=this.read_bits(8);return t}};var R=class{_buffer;_typeinfos;constructor(e,t){this._buffer=new B(e),this._typeinfos=t}instance(e){if(e>=this._typeinfos.length)throw new w(`Invalid typeid ${e}`);let t=this._typeinfos[e],n=t[0],i=t[1]||[],s=this[n];if(typeof s!="function")throw new Error(`Decoder method ${n} not implemented`);return s.apply(this,i)}byte_align(){this._buffer.byte_align()}done(){return this._buffer.done()}used_bits(){return this._buffer.used_bits()}_array(e,t){let n=this._int(e),i=new Array(n);for(let s=0;s<n;s++)i[s]=this.instance(t);return i}_bitarray(e){let t=this._int(e);return[t,this._buffer.read_bits(t)]}_blob(e){let t=this._int(e);return this._buffer.read_aligned_bytes(t)}_bool(){return this._int([0,1])!==0}_choice(e,t){let n=this._int(e);if(!(n in t))throw new w(`Choice tag ${n} not found`);let i=t[n];return{[i[0]]:this.instance(i[1])}}_fourcc(){let e=this._buffer.read_bits(32),t=Buffer.alloc(4);return t.writeUInt32BE(e,0),t.toString("ascii")}_int(e){return e[0]+this._buffer.read_bits(e[1])}_null(){return null}_optional(e){return this._bool()?this.instance(e):null}_real32(){return this._buffer.read_unaligned_bytes(4).readFloatBE(0)}_real64(){return this._buffer.read_unaligned_bytes(8).readDoubleBE(0)}_struct(e){let t={};for(let n of e)if(n[0]==="__parent"){let i=this.instance(n[1]);if(typeof i=="object"&&i!==null)t={...t,...i};else{if(e.length===1)return i;t[n[0]]=i}}else t[n[0]]=this.instance(n[1]);return t}},x=class{_buffer;_typeinfos;constructor(e,t){this._buffer=new B(e),this._typeinfos=t}instance(e){if(e>=this._typeinfos.length)throw new w(`Invalid typeid ${e}`);let t=this._typeinfos[e],n=t[0],i=t[1]||[],s=this[n];if(typeof s!="function")throw new Error(`Decoder method ${n} not implemented`);return s.apply(this,i)}byte_align(){this._buffer.byte_align()}done(){return this._buffer.done()}used_bits(){return this._buffer.used_bits()}_expect_skip(e){if(this._buffer.read_bits(8)!==e)throw new w(`Expected skip ${e}`)}_vint(){let e=this._buffer.read_bits(8),t=(e&1)!==0,n=BigInt(e>>1&63),i=6n;for(;(e&128)!==0;)e=this._buffer.read_bits(8),n|=BigInt(e&127)<<i,i+=7n;let s=t?-n:n;return s>=BigInt(Number.MIN_SAFE_INTEGER)&&s<=BigInt(Number.MAX_SAFE_INTEGER)?Number(s):s}_array(e,t){this._expect_skip(0);let n=Number(this._vint()),i=new Array(n);for(let s=0;s<n;s++)i[s]=this.instance(t);return i}_bitarray(e){this._expect_skip(1);let t=Number(this._vint());return[t,this._buffer.read_aligned_bytes(Math.floor((t+7)/8))]}_blob(e){this._expect_skip(2);let t=Number(this._vint());return this._buffer.read_aligned_bytes(t)}_bool(){return this._expect_skip(6),this._buffer.read_bits(8)!==0}_choice(e,t){this._expect_skip(3);let n=Number(this._vint());if(!(n in t))return this._skip_instance(),{};let i=t[n];return{[i[0]]:this.instance(i[1])}}_fourcc(){return this._expect_skip(7),this._buffer.read_aligned_bytes(4)}_int(e){return this._expect_skip(9),Number(this._vint())}_null(){return null}_optional(e){return this._expect_skip(4),this._buffer.read_bits(8)!==0?this.instance(e):null}_real32(){return this._expect_skip(7),this._buffer.read_aligned_bytes(4).readFloatBE(0)}_real64(){return this._expect_skip(8),this._buffer.read_aligned_bytes(8).readDoubleBE(0)}_struct(e){this._expect_skip(5);let t={},n=Number(this._vint());for(let i=0;i<n;i++){let s=Number(this._vint()),a=e.find(o=>o[2]===s);if(a)if(a[0]==="__parent"){let o=this.instance(a[1]);typeof o=="object"&&o!==null?t={...t,...o}:e.length===1?t=o:t[a[0]]=o}else t[a[0]]=this.instance(a[1]);else this._skip_instance()}return t}_skip_instance(){let e=this._buffer.read_bits(8);if(e===0){let t=Number(this._vint());for(let n=0;n<t;n++)this._skip_instance()}else if(e===1){let t=Number(this._vint());this._buffer.read_aligned_bytes(Math.floor((t+7)/8))}else if(e===2){let t=Number(this._vint());this._buffer.read_aligned_bytes(t)}else if(e===3)this._vint(),this._skip_instance();else if(e===4)this._buffer.read_bits(8)!==0&&this._skip_instance();else if(e===5){let t=Number(this._vint());for(let n=0;n<t;n++)this._vint(),this._skip_instance()}else e===6?this._buffer.read_aligned_bytes(1):e===7?this._buffer.read_aligned_bytes(4):e===8?this._buffer.read_aligned_bytes(8):e===9&&this._vint()}};var I=A(require("fs")),S=A(require("path"));function me(){let r=[];try{r.push(S.default.resolve(__dirname,"..","protocols")),r.push(S.default.resolve(__dirname,"..","..","protocols"))}catch{}r.push(S.default.resolve(process.cwd(),"protocols"));let e=process.cwd();for(;e!==S.default.dirname(e);){let t=S.default.join(e,"node_modules","@astefanski","storm-parser","protocols");r.push(t),e=S.default.dirname(e)}for(let t of r)if(I.default.existsSync(t)&&I.default.readdirSync(t).some(n=>n.endsWith(".json")))return t;throw new Error(`@astefanski/storm-parser: Protocols directory not found. Did postinstall run? Try: npx tsx node_modules/@astefanski/storm-parser/scripts/postinstall.ts
3
+ Searched: `+r.join(", "))}var O=new Map,j=null;function $(){return j||(j=me()),j}function U(r){if(O.has(r))return O.get(r);let e=$(),t=S.default.join(e,`protocol${r}.json`);if(!I.default.existsSync(t))return null;let n=JSON.parse(I.default.readFileSync(t,"utf-8"));return O.set(r,n),n}function V(){let r=$();return I.default.readdirSync(r).filter(e=>/^protocol\d+\.json$/.test(e)).map(e=>parseInt(e.match(/\d+/)[0],10)).sort((e,t)=>e-t)}var D=class{mpq;header;build=0;protocol;baseProtocol;constructor(e){this.mpq=new C(e,!1)}init(){let e=U(29406);if(!e)throw new Error("Base protocol29406 not found. Did postinstall run? Try: npx tsx node_modules/@astefanski/storm-parser/scripts/postinstall.ts");this.baseProtocol=e;let t=this.mpq.header.userDataHeader;if(!t||!t.content)throw new Error("Replay does not have a user data header");let n=new x(t.content,this.baseProtocol.typeinfos);this.header=n.instance(this.baseProtocol.replay_header_typeid),this.build=this.header.m_version.m_baseBuild,this.protocol=this.loadProtocolForBuild(this.build)}loadProtocolForBuild(e){let t=U(e);if(!t){let n=V(),i=n[0];for(let s of n)s<=e&&s>i&&(i=s);t=U(i)}if(!t)throw new Error(`No protocol found for build ${e}`);return t}*decodeEventStream(e,t,n,i){if(!this.protocol)throw new Error("Protocol not loaded");let s=0;for(;!e.done();){let a=e.used_bits(),o=e.instance(this.protocol.svaruint32_typeid),l=Object.keys(o)[0],u=o[l];s+=u;let c=i?e.instance(this.protocol.replay_userid_typeid):void 0,d=Number(e.instance(t)),f=n[d];if(!f)throw new Error(`Unknown eventid(${d})`);let b=f[0],m=f[1],h=e.instance(b);h._event=m,h._eventid=d,h._gameloop=s,i&&(h._userid=c),e.byte_align(),h._bits=e.used_bits()-a,yield h}}getDetails(){if(!this.protocol)throw new Error("Protocol not loaded");let e=this.mpq.readFile("replay.details");return e?new x(e,this.protocol.typeinfos).instance(this.protocol.game_details_typeid):null}getInitData(){if(!this.protocol)throw new Error("Protocol not loaded");let e=this.mpq.readFile("replay.initData");return e?new R(e,this.protocol.typeinfos).instance(this.protocol.replay_initdata_typeid):null}getTrackerEvents(){if(!this.protocol)throw new Error("Protocol not loaded");let e=this.mpq.readFile("replay.tracker.events");if(!e)return[];let t=new x(e,this.protocol.typeinfos);return Array.from(this.decodeEventStream(t,this.protocol.tracker_eventid_typeid,this.protocol.tracker_event_types,!1))}getGameEvents(){if(!this.protocol)throw new Error("Protocol not loaded");let e=this.mpq.readFile("replay.game.events");if(!e)return[];let t=new R(e,this.protocol.typeinfos);return Array.from(this.decodeEventStream(t,this.protocol.game_eventid_typeid,this.protocol.game_event_types,!0))}extractFile(e){return this.mpq.readFile(e)}getHeader(){return this.header}getBuild(){return this.build}};var W={TownCannonTowerL2:"Fort Tower",TownCannonTowerL3:"Keep Tower",TownTownHallL2:"Fort",TownTownHallL3:"Keep",TownMoonwellL2:"Fort Well",TownMoonwellL3:"Keep Well"},pe={MercLanerMeleeKnight:"Bruiser Camp",MercLanerRangedMage:"Bruiser Camp",MercLanerSiegeGiant:"Siege Camp",MercLanerRangedMinion:"Siege Camp"};function E(r){return Buffer.isBuffer(r)?r.toString("utf8"):typeof r=="string"?r:String(r??"")}function _(r,e){if(!r)return;let t=r.find(n=>E(n.m_key)===e);return t!==void 0?t.m_value:void 0}function X(r,e){if(!r)return;let t=r.find(n=>E(n.m_key)===e);return t!==void 0?E(t.m_value):void 0}function v(r,e){if(!r)return;let t=r.find(n=>E(n.m_key)===e);return t!==void 0?t.m_value:void 0}function K(r,e){return`${r}-${e}`}function k(r,e){return(r-e)/16}function q(r,e,t){let n={playerIDMap:{},loopGameStart:0,loopGameEnd:0,unitIndex:{},heroUnits:{},heroLives:{}};t.levelTimes={0:{},1:{}},t.takedowns=[],t.structures={},t.XPBreakdown=[],t.mercs={captures:[],units:{}},t.objective={0:{count:0,events:[]},1:{count:0,events:[]},type:t.map||""};for(let i of r)switch(n.loopGameEnd=Math.max(n.loopGameEnd,i._gameloop),i._event){case"NNet.Replay.Tracker.SStatGameEvent":he(i,n,e,t);break;case"NNet.Replay.Tracker.SUnitBornEvent":_e(i,n,t);break;case"NNet.Replay.Tracker.SUnitDiedEvent":ye(i,n,t);break;case"NNet.Replay.Tracker.SUnitOwnerChangeEvent":ve(i,n);break}return t.loopGameStart=n.loopGameStart,t.loopLength=n.loopGameEnd,t.length=(n.loopGameEnd-n.loopGameStart)/16,Te(n,e),{playerIDMap:n.playerIDMap}}function he(r,e,t,n){let i=E(r.m_eventName),s=r.m_intData,a=r.m_stringData,o=r.m_fixedData;switch(i){case"PlayerInit":{let l=_(s,"PlayerID"),u=X(a,"ToonHandle");l!==void 0&&u&&t[u]&&(e.playerIDMap[l]=u);break}case"GatesOpen":e.loopGameStart=r._gameloop;break;case"LevelUp":{let l=_(s,"PlayerID"),u=_(s,"Level");if(l===void 0||u===void 0)break;let c;if(l>=1&&l<=5)c="0";else if(l>=6&&l<=10)c="1";else break;n.levelTimes[c][String(u)]||(n.levelTimes[c][String(u)]={loop:r._gameloop,level:u,team:c,time:k(r._gameloop,e.loopGameStart)});break}case"TalentChosen":{let l=_(s,"PlayerID"),u=X(a,"PurchaseName");if(l===void 0||!u)break;let c=e.playerIDMap[l];if(!c||!t[c])break;let d=["Tier1Choice","Tier2Choice","Tier3Choice","Tier4Choice","Tier5Choice","Tier6Choice","Tier7Choice"];for(let f of d)if(!t[c].talents[f]){t[c].talents[f]=u;break}break}case"PlayerDeath":{let l=_(s,"PlayerID"),u=_(s,"KillingPlayer"),c=v(o,"PositionX")??0,d=v(o,"PositionY")??0;if(l===void 0)break;let f=e.playerIDMap[l];if(!f)break;let b={player:f,hero:t[f]?.hero||""},m=[],h=t[f]?.team;if(u&&u>0){let g=e.playerIDMap[u];g&&t[g]&&m.push({player:g,hero:t[g].hero})}for(let[g,y]of Object.entries(e.playerIDMap)){let T=t[y];!T||T.team===h||parseInt(g)===u||m.push({player:y,hero:T.hero})}let p={loop:r._gameloop,time:k(r._gameloop,e.loopGameStart),x:c,y:d,killers:m,victim:b};n.takedowns.push(p),t[f]&&t[f].deaths.push(p);for(let g of m)t[g.player]&&t[g.player].takedowns.push(p);break}case"PeriodicXPBreakdown":{let l=_(s,"Team");if(l===void 0)break;let u={GameTime:_(s,"GameTime")??0,PreviousGameTime:_(s,"PreviousGameTime")??0,MinionXP:v(o,"MinionXP")??0,CreepXP:v(o,"CreepXP")??0,StructureXP:v(o,"StructureXP")??0,HeroXP:v(o,"HeroXP")??0,TrickleXP:v(o,"TrickleXP")??0};n.XPBreakdown.push({loop:r._gameloop,time:k(r._gameloop,e.loopGameStart),team:l,teamLevel:_(s,"TeamLevel")??0,breakdown:u,theoreticalMinionXP:_(s,"TheoreticalMinionXP")??0});break}case"EndOfGameXPBreakdown":{let l=_(s,"Team");if(l===void 0)break;n.XPBreakdown.push({loop:r._gameloop,time:k(r._gameloop,e.loopGameStart),team:l,theoreticalMinionXP:_(s,"TheoreticalMinionXP")??0,breakdown:{GameTime:0,PreviousGameTime:0,MinionXP:v(o,"MinionXP")??0,CreepXP:v(o,"CreepXP")??0,StructureXP:v(o,"StructureXP")??0,HeroXP:v(o,"HeroXP")??0,TrickleXP:v(o,"TrickleXP")??0}});break}case"JungleCampCapture":{let l=_(s,"CampTeam")??_(s,"Team")??0;n.mercs.captures.push({loop:r._gameloop,type:X(a,"CampType")??X(a,"Result")??"Unknown Camp",team:l,time:k(r._gameloop,e.loopGameStart)});break}case"EndOfGameTalentChoices":{let l=_(s,"PlayerID");if(l===void 0)break;let u=e.playerIDMap[l];if(!u||!t[u])break;let c={},d=["Tier1Choice","Tier2Choice","Tier3Choice","Tier4Choice","Tier5Choice","Tier6Choice","Tier7Choice"];if(a)for(let f=0;f<a.length&&f<d.length;f++){let b=E(a[f].m_value);b&&(c[d[f]]=b)}t[u].talents=c;break}default:ge(i,r,e,n,s,o);break}}var be=new Set(["SoulEatersSpawned","TributeCollected","RavenCurseActivated","AltarCaptured","SkyTempleShotsFired","DragonKnightActivated","GardenTerrorActivated","InfernalShrineCaptured","PunisherKilled","VolskayaVehicleCapture","BraxisWaveStart","ImmortalDefeated","NukeExploded","PayloadDelivered","AlteracCavalryCharge","AlteracCavalry"]);function ge(r,e,t,n,i,s){if(!be.has(r)||!i)return;let a=_(i,"Team")??_(i,"Event")??0,o=a===0||a===1?a:0,l={team:a,loop:e._gameloop,time:k(e._gameloop,t.loopGameStart),score:_(i,"Score"),duration:v(s,"Duration")};n.objective[o].events.push(l),n.objective[o].count=n.objective[o].events.length}function _e(r,e,t){let n=E(r.m_unitTypeName),i=r.m_unitTagIndex,s=r.m_unitTagRecycle,a=K(i,s),o=r.m_controlPlayerId??r.m_upkeepPlayerId,l=r.m_x??0,u=r.m_y??0,c;if(o>=1&&o<=5?c=0:o>=6&&o<=10?c=1:o===11?c=0:o===12?c=1:c=o<=5?0:1,e.unitIndex[a]={type:n,playerId:o,team:c,x:l,y:u,bornLoop:r._gameloop},n.startsWith("Town")&&W[n]&&(t.structures[a]={type:n,name:W[n],tag:i,rtag:s,x:l,y:u,team:c}),n.startsWith("Hero")&&o>=1&&o<=10){e.heroUnits[a]=o,e.heroLives[o]||(e.heroLives[o]=[]);let d=k(r._gameloop,e.loopGameStart);e.heroLives[o].push({born:d,locations:[{x:l,y:u,time:d}],duration:0})}if(pe[n]){let f=t.mercs.captures[t.mercs.captures.length-1]?.loop??r._gameloop;t.mercs.units[a]={loop:f,team:c,type:n,locations:[{x:l,y:u}],time:k(f,e.loopGameStart),duration:0}}}function ye(r,e,t){let n=K(r.m_unitTagIndex,r.m_unitTagRecycle),i=k(r._gameloop,e.loopGameStart);t.structures[n]&&(t.structures[n].destroyedLoop=r._gameloop,t.structures[n].destroyed=i),t.mercs.units[n]&&(t.mercs.units[n].duration=i-t.mercs.units[n].time);let s=e.heroUnits[n];if(s!==void 0&&e.heroLives[s]){let a=e.heroLives[s],o=a[a.length-1];o&&o.died===void 0&&(o.died=i,o.duration=i-o.born)}}function ve(r,e){let t=K(r.m_unitTagIndex,r.m_unitTagRecycle),n=r.m_controlPlayerId??r.m_upkeepPlayerId;e.unitIndex[t]&&(e.unitIndex[t].playerId=n,n>=1&&n<=5?e.unitIndex[t].team=0:n>=6&&n<=10&&(e.unitIndex[t].team=1))}function Te(r,e){for(let[t,n]of Object.entries(r.heroLives)){let i=parseInt(t,10),s=r.playerIDMap[i];if(!s||!e[s])continue;for(let o of n)if(o.died===void 0){let l=o.locations[o.locations.length-1];o.duration=l?l.time-o.born:0}let a="";for(let[o,l]of Object.entries(r.heroUnits))if(l===i){a=o;break}a&&(e[s].units[a]={lives:n})}}var ke=["DamageTaken","CreepDamage","Healing","HeroDamage","MinionDamage","SelfHealing","SiegeDamage","ProtectionGivenToAllies","TeamfightDamageTaken","TeamfightHealingDone","TeamfightHeroDamage","TimeCCdEnemyHeroes","TimeRootingEnemyHeroes","TimeSpentDead","TimeStunningEnemyHeroes","TimeSilencingEnemyHeroes"];function Se(){return{DamageTaken:0,CreepDamage:0,Healing:0,HeroDamage:0,MinionDamage:0,SelfHealing:0,SiegeDamage:0,ProtectionGivenToAllies:0,TeamfightDamageTaken:0,TeamfightHealingDone:0,TeamfightHeroDamage:0,TimeCCdEnemyHeroes:0,TimeRootingEnemyHeroes:0,TimeSpentDead:0,TimeStunningEnemyHeroes:0,TimeSilencingEnemyHeroes:0,avgTimeSpentDead:0,timeDeadPct:0}}function Pe(){return{mercCaptures:0,mercUptime:0,mercUptimePercent:0,structures:{},KDA:0,PPK:0,timeTo10:0,totals:Se(),levelAdvTime:0,maxLevelAdv:0,avgLevelAdv:0,levelAdvPct:0,uptime:[],uptimeHistogram:{},wipes:0,avgHeroesAlive:0,aces:0,timeWithHeroAdv:0,pctWithHeroAdv:0,passiveXPRate:0,passiveXPDiff:0,passiveXPGain:0}}function J(r,e){we(r,e),xe(r,e),Ie(r),De(r),Me(r,e),Be(r)}function we(r,e){for(let t of["0","1"]){let n=r.teams[t];if(!n)continue;n.names=[],n.heroes=[],n.tags=[],n.stats=Pe();for(let d of n.ids){let f=e[d];f&&(n.names.push(f.name),n.heroes.push(f.hero),n.tags.push(f.tag))}for(let d of n.ids){let f=e[d];if(f)for(let b of ke){let m=f.gameStats[b];typeof m=="number"&&(n.stats.totals[b]+=m)}}let i=n.ids.reduce((d,f)=>d+(e[f]?.gameStats.Deaths??0),0);i>0&&(n.stats.totals.avgTimeSpentDead=n.stats.totals.TimeSpentDead/i),r.length>0&&(n.stats.totals.timeDeadPct=n.stats.totals.TimeSpentDead/(r.length*n.ids.length));let s=n.takedowns,a=i;n.stats.KDA=a>0?s/a:s,n.stats.PPK=s>0?r.takedowns.filter(d=>n.ids.includes(d.killers[0]?.player)).reduce((d,f)=>d+f.killers.length,0)/s:0;let o=r.levelTimes[t];o?.["10"]&&(n.stats.timeTo10=o[10].time);let l=parseInt(t,10),u=r.mercs.captures.filter(d=>d.team===l);n.stats.mercCaptures=u.length;let c=0;for(let d of Object.values(r.mercs.units))d.team===l&&d.duration>0&&(c+=d.duration);n.stats.mercUptime=c,n.stats.mercUptimePercent=r.length>0?c/r.length:0,Ee(r,n,t)}}function Ee(r,e,t){let n=parseInt(t,10),i=n===0?1:0,s={};for(let a of Object.values(r.structures)){let o=a.name;s[o]||(s[o]={lost:0,destroyed:0,first:r.length}),a.team===i&&a.destroyed!==void 0&&(s[o].destroyed++,s[o].first=Math.min(s[o].first,a.destroyed)),a.team===n&&a.destroyed!==void 0&&s[o].lost++}e.stats.structures=s}function xe(r,e){let t=r.length/60;for(let n of Object.values(e)){let i=n.gameStats,s=i.Deaths??0,a=i.TeamTakedowns??0;t>0&&(i.DPM=(i.HeroDamage??0)/t,i.HPM=((i.Healing??0)+(i.SelfHealing??0))/t,i.XPM=(i.ExperienceContribution??0)/t),i.KDA=s>0?(i.Takedowns??0)/s:i.Takedowns??0,i.KillParticipation=a>0?(i.Takedowns??0)/a:0,i.damageDonePerDeath=s>0?(i.HeroDamage??0)/s:i.HeroDamage??0,i.damageTakenPerDeath=s>0?(i.DamageTaken??0)/s:i.DamageTaken??0,i.healingDonePerDeath=s>0?((i.Healing??0)+(i.SelfHealing??0))/s:(i.Healing??0)+(i.SelfHealing??0),i.length=r.length}}function Ie(r){let e=0,t=0,n=new Set(r.teams[0]?.ids||[]);for(let i of r.takedowns)n.has(i.victim.player)?t++:e++;r.team0Takedowns=e,r.team1Takedowns=t}function De(r){let e=r.levelTimes[0]||{},t=r.levelTimes[1]||{},n=[];for(let u of Object.values(e))n.push({time:u.time,team:0,level:u.level});for(let u of Object.values(t))n.push({time:u.time,team:1,level:u.level});if(n.sort((u,c)=>u.time-c.time),n.length===0){r.levelAdvTimeline=[];return}let i=[],s=0,a=0,o=n[0].time;for(let u of n){let c=s-a;u.time>o&&i.push({start:o,end:u.time,levelDiff:c,length:u.time-o}),u.team===0?s=u.level:a=u.level,o=u.time}let l=s-a;r.length>o&&i.push({start:o,end:r.length,levelDiff:l,length:r.length-o}),r.levelAdvTimeline=i;for(let u of["0","1"]){let c=r.teams[u];if(!c)continue;let d=u==="0"?1:-1,f=0,b=0,m=0,h=0;for(let p of i){let g=p.levelDiff*d;g>0&&(f+=p.length),b=Math.max(b,g),m+=g*p.length,h+=p.length}c.stats.levelAdvTime=f,c.stats.maxLevelAdv=b,c.stats.avgLevelAdv=h>0?m/h:0,c.stats.levelAdvPct=r.length>0?f/r.length:0}}function Me(r,e){for(let s of["0","1"]){let a=r.teams[s];if(!a)continue;let o=[];for(let p of a.ids){let g=e[p];if(g)for(let y of g.deaths){o.push({time:y.time,delta:-1});let T=y.time+He(g.gameStats.Level??1,y.time,r.length);T<r.length&&o.push({time:T,delta:1})}}o.sort((p,g)=>p.time-g.time);let l=[{time:0,heroes:a.ids.length}],u=a.ids.length;for(let p of o)u+=p.delta,u=Math.max(0,Math.min(a.ids.length,u)),l.push({time:p.time,heroes:u});a.stats.uptime=l;let c={};for(let p=0;p<l.length;p++){let y=(p<l.length-1?l[p+1].time:r.length)-l[p].time,T=String(l[p].heroes);c[T]=(c[T]||0)+y}a.stats.uptimeHistogram=c;let d=0,f=0;for(let p=0;p<l.length;p++){let y=(p<l.length-1?l[p+1].time:r.length)-l[p].time;d+=l[p].heroes*y,f+=y}a.stats.avgHeroesAlive=f>0?d/f:a.ids.length,a.stats.wipes=l.filter(p=>p.heroes===0).length,a.stats.aces=0;let b=s==="0"?"1":"0",m=r.teams[b];m?.stats?.uptime&&(a.stats.aces=m.stats.uptime.filter(p=>p.heroes===0).length);let h=r.XPBreakdown.filter(p=>p.team===parseInt(s,10));if(h.length>0){let g=h[h.length-1].breakdown.TrickleXP;a.stats.passiveXPGain=g,a.stats.passiveXPRate=r.length>0?g/(r.length/60):0}}for(let s of["0","1"]){let a=r.teams[s],o=s==="0"?"1":"0",l=r.teams[o];if(!a||!l)continue;let u=a.stats.uptime,c=l.stats.uptime;if(!u.length||!c.length)continue;let d=new Set;for(let m of u)d.add(m.time);for(let m of c)d.add(m.time);let f=Array.from(d).sort((m,h)=>m-h),b=0;for(let m=0;m<f.length;m++){let h=f[m],g=(m<f.length-1?f[m+1]:r.length)-h,y=Y(u,h),T=Y(c,h);y>T&&(b+=g)}a.stats.timeWithHeroAdv=b,a.stats.pctWithHeroAdv=r.length>0?b/r.length:0}let t=r.teams[0]?.stats.passiveXPRate??0,n=r.teams[1]?.stats.passiveXPRate??0,i=(t+n)/2;r.teams[0]&&(r.teams[0].stats.passiveXPDiff=i>0?t/i:0),r.teams[1]&&(r.teams[1].stats.passiveXPDiff=i>0?n/i:0)}function Y(r,e){let t=0;for(let n of r)if(n.time<=e)t=n.heroes;else break;return t}function He(r,e,t){return r<=1?15:r<=5?15+(r-1)*2:r<=10?23+(r-5)*3:r<=15?38+(r-10)*4:58+(r-15)*5}function Be(r){let e=1/0,t=-1,n=1/0,i=-1;for(let a of Object.values(r.structures))a.destroyed!==void 0&&(a.name==="Fort"&&a.destroyed<e&&(e=a.destroyed,t=a.team===0?1:0),a.name==="Keep"&&a.destroyed<n&&(n=a.destroyed,i=a.team===0?1:0));t>=0&&(r.firstFort=t,r.firstFortWin=t===r.winner),i>=0&&(r.firstKeep=i,r.firstKeepWin=i===r.winner);let s=[...r.objective[0].events.map(a=>({...a,assignedTeam:0})),...r.objective[1].events.map(a=>({...a,assignedTeam:1}))].sort((a,o)=>a.loop-o.loop);s.length>0&&(r.firstObjective=s[0].assignedTeam,r.firstObjectiveWin=s[0].assignedTeam===r.winner),r.firstPickWin=r.picks.first===r.winner}function P(r){return Buffer.isBuffer(r)?r.toString("utf8"):typeof r=="string"?r:String(r??"")}var N=class{static async analyze(e){try{let t=new D(e);await t.init();let n=t.getDetails();if(!n)throw new Error("Missing replay.details from parsed MPQ archive");let i=t.getTrackerEvents(),s=t.getInitData(),o=t.getHeader()?.m_version??{},l={m_flags:o.m_flags??0,m_major:o.m_major??0,m_minor:o.m_minor??0,m_revision:o.m_revision??0,m_build:o.m_baseBuild??t.getBuild(),m_baseBuild:o.m_baseBuild??t.getBuild()},u=n.m_playerList.find(b=>b?.m_toon)?.m_toon,c={version:l,map:P(n.m_title),date:this.fileTimeToDate(n.m_timeUTC).toISOString(),rawDate:Number(n.m_timeUTC),length:0,winner:-1,region:u?.m_region,playerIDs:[],heroes:[],levelTimes:{0:{},1:{}},bans:{0:[],1:[]},picks:{0:[],1:[],first:0},XPBreakdown:[],takedowns:[],mercs:{captures:[],units:{}},team0Takedowns:0,team1Takedowns:0,structures:{},objective:{0:{count:0,events:[]},1:{count:0,events:[]},type:""},teams:{0:this.emptyTeam(),1:this.emptyTeam()},winningPlayers:[],levelAdvTimeline:[],firstPickWin:!1};c.objective.type=c.map||"";let d={};for(let b of n.m_playerList){if(!b?.m_toon)continue;let m=b.m_toon,h=P(m.m_programId),p=`${m.m_region}-${h}-${m.m_realm}-${m.m_id}`,g=P(b.m_hero);d[p]={hero:g,name:P(b.m_name),uuid:m.m_id,region:m.m_region,realm:m.m_realm,ToonHandle:p,tag:0,team:b.m_teamId,win:b.m_result===1,gameStats:{},awards:[],talents:{},takedowns:[],deaths:[],units:{}},c.playerIDs.push(p),c.heroes.push(g)}this.extractBattleTags(t,n,d),this.extractDraft(s,c,n);let{playerIDMap:f}=q(i,d,c);this.processScoreEvents(i,f,d);for(let[b,m]of Object.entries(d)){m.win&&(c.winner=m.team,c.winningPlayers.push(b));let h=c.teams[m.team.toString()];h&&(h.level=Math.max(h.level,m.gameStats.Level||0),h.takedowns+=m.gameStats.Takedowns||0,h.ids.push(b))}return J(c,d),{status:1,match:c,players:d}}catch(t){return console.error("ReplayAnalyzer Error:",t),{status:-2,error:String(t)}}}static emptyTeam(){return{level:0,takedowns:0,ids:[],names:[],heroes:[],tags:[],stats:{mercCaptures:0,mercUptime:0,mercUptimePercent:0,structures:{},KDA:0,PPK:0,timeTo10:0,totals:{DamageTaken:0,CreepDamage:0,Healing:0,HeroDamage:0,MinionDamage:0,SelfHealing:0,SiegeDamage:0,ProtectionGivenToAllies:0,TeamfightDamageTaken:0,TeamfightHealingDone:0,TeamfightHeroDamage:0,TimeCCdEnemyHeroes:0,TimeRootingEnemyHeroes:0,TimeSpentDead:0,TimeStunningEnemyHeroes:0,TimeSilencingEnemyHeroes:0,avgTimeSpentDead:0,timeDeadPct:0},levelAdvTime:0,maxLevelAdv:0,avgLevelAdv:0,levelAdvPct:0,uptime:[],uptimeHistogram:{},wipes:0,avgHeroesAlive:0,aces:0,timeWithHeroAdv:0,pctWithHeroAdv:0,passiveXPRate:0,passiveXPDiff:0,passiveXPGain:0}}}static extractBattleTags(e,t,n){let i=e.extractFile("replay.server.battlelobby");if(i)try{let s=new RegExp("([\\p{L}\\d]{3,24}#\\d{4,10})[z\xD8]?","gu"),a=i.toString("utf8").match(s);if(!a)return;let o=0;for(let l of t.m_playerList){if(!l?.m_toon)continue;let u=P(l.m_name);for(;o<a.length;){let d=a[o].split("#"),f=d[0],b=d[1].replace(/[zØ]/g,"");if(o++,f===u){let m=l.m_toon,h=`${m.m_region}-${P(m.m_programId)}-${m.m_realm}-${m.m_id}`;n[h]&&(n[h].tag=parseInt(b,10)||0);break}}}}catch(s){console.error("BattleTag regex error:",s)}}static extractDraft(e,t,n){if(e)try{let i=e.m_syncLobbyState;if(!i)return;let s=i.m_lobbyState;if(!s)return;let a=[],o=[];for(let c of n.m_playerList){if(!c?.m_toon)continue;let d=P(c.m_hero);c.m_teamId===0?a.push(d):c.m_teamId===1&&o.push(d)}if(t.picks={0:a,1:o,first:0},typeof s.m_gameMode=="number"&&(t.mode=s.m_gameMode),typeof s.m_gameType=="number"&&(t.type=s.m_gameType),!s.m_slots)return;if(s.m_pickedMapTag!==void 0){let c=s.m_firstPickTeam??0;t.picks.first=c}}catch{}}static processScoreEvents(e,t,n){for(let i of e){if(i._event!=="NNet.Replay.Tracker.SScoreResultEvent")continue;let s=i.m_instanceList;for(let a of s){let o=P(a.m_name),l=a.m_values,u=o.startsWith("EndOfMatchAward"),c=0;for(let d of l)if(d&&d.length>0&&d[0]!==void 0){let f=c+1,b=t[f];if(b&&n[b]){let m=typeof d[0]=="object"&&d[0]!==null&&"m_value"in d[0]?d[0].m_value:d[0];m!=null&&(u?m===1&&n[b].awards.push(o):n[b].gameStats[o]=m)}c++}else d&&d.length===0&&c++}}}static fileTimeToDate(e){return new Date(Number(e)/1e4-116444736e5)}};0&&(module.exports={ReplayAnalyzer,ReplayParser});
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
- var A=(i=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(i,{get:(e,t)=>(typeof require<"u"?require:e)[t]}):i)(function(i){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+i+'" is not supported')});import*as Q from"fs";import*as x from"zlib";var O=A("seek-bzip"),U=512,$=65536,C=16777216,q=67108864,j=2147483648,G={TABLE_OFFSET:0,HASH_A:1,HASH_B:2,TABLE:3};function V(){let i=1048577,e=new Uint32Array(256*5);for(let t=0;t<256;t++){let r=t;for(let n=0;n<5;n++){i=(i*125+3)%2796203;let s=(i&65535)<<16;i=(i*125+3)%2796203;let l=i&65535;e[r]=(s|l)>>>0,r+=256}}return e}var N=V(),P=class{file;header;hashTable;blockTable;files;constructor(e,t=!0){if(Buffer.isBuffer(e)?this.file=e:this.file=Q.readFileSync(e),this.header=this.readHeader(),this.hashTable=this.readTable("hash"),this.blockTable=this.readTable("block"),t){let r=this.readFile("(listfile)");r?this.files=r.toString("utf8").trim().split(`\r
2
- `):this.files=null}else this.files=null}readHeader(){let e=this.file.toString("utf8",0,4),t;if(e==="MPQ")t=this.readMPQHeader(),t.offset=0;else if(e==="MPQ\x1B"){let r=this.readMPQUserDataHeader();t=this.readMPQHeader(r.mpqHeaderOffset),t.offset=r.mpqHeaderOffset,t.userDataHeader=r}else throw new Error("Invalid MPQ file header");return t}readMPQHeader(e=0){let t=this.file.subarray(e,e+32),r={magic:t.toString("utf8",0,4),headerSize:t.readUInt32LE(4),archiveSize:t.readUInt32LE(8),formatVersion:t.readUInt16LE(12),sectorSizeShift:t.readUInt16LE(14),hashTableOffset:t.readUInt32LE(16),blockTableOffset:t.readUInt32LE(20),hashTableEntries:t.readUInt32LE(24),blockTableEntries:t.readUInt32LE(28)};if(r.formatVersion===1){let n=this.file.subarray(e+32,e+32+12);r.extendedBlockTableOffset=n.readUInt32LE(0)+n.readUInt32LE(4)*4294967296,r.hashTableOffsetHigh=n.readInt8(8),r.blockTableOffsetHigh=n.readInt8(10)}return r}readMPQUserDataHeader(){let e=this.file.subarray(0,16),t={magic:e.toString("utf8",0,4),userDataSize:e.readUInt32LE(4),mpqHeaderOffset:e.readUInt32LE(8),userDataHeaderSize:e.readUInt32LE(12)};return t.content=this.file.subarray(16,16+t.userDataHeaderSize),t}readTable(e){let t=e==="hash"?"hashTableOffset":"blockTableOffset",r=e==="hash"?"hashTableEntries":"blockTableEntries",n=this.header[t],s=this.header[r];if(n==null||s==null)throw new Error("Missing "+e+" offset or entries");let l=this.hash("("+e+" table)","TABLE"),a=this.file.subarray(n+(this.header.offset||0),n+(this.header.offset||0)+s*16);a=this.decrypt(a,l);let c=[];for(let _=0;_<s;_++){let h=a.subarray(_*16,_*16+16);e==="hash"?c.push({hashA:h.readUInt32LE(0),hashB:h.readUInt32LE(4),locale:h.readUInt16LE(8),platform:h.readUInt16LE(10),blockTableIndex:h.readUInt32LE(12)}):c.push({offset:h.readUInt32LE(0),archivedSize:h.readUInt32LE(4),size:h.readUInt32LE(8),flags:h.readUInt32LE(12)})}return c}getHashTableEntry(e){let t=this.hash(e,"HASH_A"),r=this.hash(e,"HASH_B");for(let n of this.hashTable)if(n.hashA===t&&n.hashB===r)return n}readFile(e,t=!1){function r(c){let _=c[0];if(_===0)return c;if(_===2)return x.inflateSync(c.subarray(1));if(_===16)return O.decode(c.subarray(1));try{return x.inflateSync(c.subarray(1))}catch{return x.inflateRawSync(c.subarray(1))}}let n=this.getHashTableEntry(e);if(!n)return null;let s=this.blockTable[n.blockTableIndex];if(!s||!(s.flags&j))return null;if(s.archivedSize===0)return Buffer.alloc(0);let l=s.offset+(this.header.offset||0),a=this.file.subarray(l,l+s.archivedSize);if(s.flags&$)throw new Error("Encryption is not supported");if(s.flags&C)s.flags&U&&(t||s.size>s.archivedSize)&&(a=r(a));else{let c=512<<this.header.sectorSizeShift,_=Math.trunc(s.size/c)+1,h=!1;s.flags&q&&(h=!0,_+=1);let o=[];for(let d=0;d<_+1;d++)o.push(a.readUInt32LE(4*d));let u=o.length-(h?2:1),f=[],b=s.size;for(let d=0;d<u;d++){let m=a.subarray(o[d],o[d+1]);s.flags&U&&(t||b>m.length)&&(m=r(m)),b-=m.length,f.push(m)}a=Buffer.concat(f)}return a}hash(e,t){let r=2146271213,n=4008636142;for(let s=0;s<e.length;s++){let l=e.toUpperCase().charCodeAt(s);r=(N[(G[t]<<8)+l]^r+n)>>>0,n=l+r+n+(n<<5)+3>>>0}return r}decrypt(e,t){let r=t>>>0,n=4008636142,s=Buffer.alloc(e.length),l=e.length/4;for(let a=0;a<l;a++){n=n+N[1024+(r&255)]>>>0;let c=e.readUInt32LE(a*4);c=(c^r+n)>>>0,r=((~r<<21)+286331153|r>>>11)>>>0,n=c+n+(n<<5)+3>>>0,s.writeUInt32LE(c,a*4)}return s}};var k=class extends Error{constructor(e="Truncated Buffer"){super(e),this.name="TruncatedError"}},g=class extends Error{constructor(e="Corrupted Buffer"){super(e),this.name="CorruptedError"}},S=class{_data;_used;_next;_nextbits;_bigendian;constructor(e,t="big"){this._data=e,this._used=0,this._next=0,this._nextbits=0,this._bigendian=t==="big"}done(){return this._nextbits===0&&this._used>=this._data.length}used_bits(){return this._used*8-this._nextbits}byte_align(){this._nextbits=0}read_aligned_bytes(e){if(this.byte_align(),this._used+e>this._data.length)throw new k;let t=this._data.subarray(this._used,this._used+e);return this._used+=e,t}read_bits(e){let t=0,r=0;for(;r!==e;){if(this._nextbits===0){if(this.done())throw new k;this._next=this._data[this._used],this._used+=1,this._nextbits=8}let n=Math.min(e-r,this._nextbits),s=this._next&(1<<n)-1;this._bigendian?t+=s*Math.pow(2,e-r-n):t+=s*Math.pow(2,r),this._next>>=n,this._nextbits-=n,r+=n}return t}read_bits_bigint(e){let t=0n,r=0;for(;r!==e;){if(this._nextbits===0){if(this.done())throw new k;this._next=this._data[this._used],this._used+=1,this._nextbits=8}let n=Math.min(e-r,this._nextbits),s=BigInt(this._next&(1<<n)-1);this._bigendian?t|=s<<BigInt(e-r-n):t|=s<<BigInt(r),this._next>>=n,this._nextbits-=n,r+=n}return t}read_unaligned_bytes(e){let t=Buffer.alloc(e);for(let r=0;r<e;r++)t[r]=this.read_bits(8);return t}};var B=class{_buffer;_typeinfos;constructor(e,t){this._buffer=new S(e),this._typeinfos=t}instance(e){if(e>=this._typeinfos.length)throw new g(`Invalid typeid ${e}`);let t=this._typeinfos[e],r=t[0],n=t[1]||[],s=this[r];if(typeof s!="function")throw new Error(`Decoder method ${r} not implemented`);return s.apply(this,n)}byte_align(){this._buffer.byte_align()}done(){return this._buffer.done()}used_bits(){return this._buffer.used_bits()}_array(e,t){let r=this._int(e),n=new Array(r);for(let s=0;s<r;s++)n[s]=this.instance(t);return n}_bitarray(e){let t=this._int(e);return[t,this._buffer.read_bits(t)]}_blob(e){let t=this._int(e);return this._buffer.read_aligned_bytes(t)}_bool(){return this._int([0,1])!==0}_choice(e,t){let r=this._int(e);if(!(r in t))throw new g(`Choice tag ${r} not found`);let n=t[r];return{[n[0]]:this.instance(n[1])}}_fourcc(){let e=this._buffer.read_bits(32),t=Buffer.alloc(4);return t.writeUInt32BE(e,0),t.toString("ascii")}_int(e){return e[0]+this._buffer.read_bits(e[1])}_null(){return null}_optional(e){return this._bool()?this.instance(e):null}_real32(){return this._buffer.read_unaligned_bytes(4).readFloatBE(0)}_real64(){return this._buffer.read_unaligned_bytes(8).readDoubleBE(0)}_struct(e){let t={};for(let r of e)if(r[0]==="__parent"){let n=this.instance(r[1]);if(typeof n=="object"&&n!==null)t={...t,...n};else{if(e.length===1)return n;t[r[0]]=n}}else t[r[0]]=this.instance(r[1]);return t}},w=class{_buffer;_typeinfos;constructor(e,t){this._buffer=new S(e),this._typeinfos=t}instance(e){if(e>=this._typeinfos.length)throw new g(`Invalid typeid ${e}`);let t=this._typeinfos[e],r=t[0],n=t[1]||[],s=this[r];if(typeof s!="function")throw new Error(`Decoder method ${r} not implemented`);return s.apply(this,n)}byte_align(){this._buffer.byte_align()}done(){return this._buffer.done()}used_bits(){return this._buffer.used_bits()}_expect_skip(e){if(this._buffer.read_bits(8)!==e)throw new g(`Expected skip ${e}`)}_vint(){let e=this._buffer.read_bits(8),t=(e&1)!==0,r=BigInt(e>>1&63),n=6n;for(;(e&128)!==0;)e=this._buffer.read_bits(8),r|=BigInt(e&127)<<n,n+=7n;let s=t?-r:r;return s>=BigInt(Number.MIN_SAFE_INTEGER)&&s<=BigInt(Number.MAX_SAFE_INTEGER)?Number(s):s}_array(e,t){this._expect_skip(0);let r=Number(this._vint()),n=new Array(r);for(let s=0;s<r;s++)n[s]=this.instance(t);return n}_bitarray(e){this._expect_skip(1);let t=Number(this._vint());return[t,this._buffer.read_aligned_bytes(Math.floor((t+7)/8))]}_blob(e){this._expect_skip(2);let t=Number(this._vint());return this._buffer.read_aligned_bytes(t)}_bool(){return this._expect_skip(6),this._buffer.read_bits(8)!==0}_choice(e,t){this._expect_skip(3);let r=Number(this._vint());if(!(r in t))return this._skip_instance(),{};let n=t[r];return{[n[0]]:this.instance(n[1])}}_fourcc(){return this._expect_skip(7),this._buffer.read_aligned_bytes(4)}_int(e){return this._expect_skip(9),Number(this._vint())}_null(){return null}_optional(e){return this._expect_skip(4),this._buffer.read_bits(8)!==0?this.instance(e):null}_real32(){return this._expect_skip(7),this._buffer.read_aligned_bytes(4).readFloatBE(0)}_real64(){return this._expect_skip(8),this._buffer.read_aligned_bytes(8).readDoubleBE(0)}_struct(e){this._expect_skip(5);let t={},r=Number(this._vint());for(let n=0;n<r;n++){let s=Number(this._vint()),l=e.find(a=>a[2]===s);if(l)if(l[0]==="__parent"){let a=this.instance(l[1]);typeof a=="object"&&a!==null?t={...t,...a}:e.length===1?t=a:t[l[0]]=a}else t[l[0]]=this.instance(l[1]);else this._skip_instance()}return t}_skip_instance(){let e=this._buffer.read_bits(8);if(e===0){let t=Number(this._vint());for(let r=0;r<t;r++)this._skip_instance()}else if(e===1){let t=Number(this._vint());this._buffer.read_aligned_bytes(Math.floor((t+7)/8))}else if(e===2){let t=Number(this._vint());this._buffer.read_aligned_bytes(t)}else if(e===3)this._vint(),this._skip_instance();else if(e===4)this._buffer.read_bits(8)!==0&&this._skip_instance();else if(e===5){let t=Number(this._vint());for(let r=0;r<t;r++)this._vint(),this._skip_instance()}else e===6?this._buffer.read_aligned_bytes(1):e===7?this._buffer.read_aligned_bytes(4):e===8?this._buffer.read_aligned_bytes(8):e===9&&this._vint()}};import T from"fs";import y from"path";function W(){let i=[];try{i.push(y.resolve(__dirname,"..","protocols")),i.push(y.resolve(__dirname,"..","..","protocols"))}catch{}i.push(y.resolve(process.cwd(),"protocols"));let e=process.cwd();for(;e!==y.dirname(e);){let t=y.join(e,"node_modules","@astefanski","storm-parser","protocols");i.push(t),e=y.dirname(e)}for(let t of i)if(T.existsSync(t)&&T.readdirSync(t).some(r=>r.endsWith(".json")))return t;throw new Error(`@astefanski/storm-parser: Protocols directory not found. Did postinstall run? Try: npx tsx node_modules/@astefanski/storm-parser/scripts/postinstall.ts
3
- Searched: `+i.join(", "))}var H=new Map,L=null;function z(){return L||(L=W()),L}function M(i){if(H.has(i))return H.get(i);let e=z(),t=y.join(e,`protocol${i}.json`);if(!T.existsSync(t))return null;let r=JSON.parse(T.readFileSync(t,"utf-8"));return H.set(i,r),r}function F(){let i=z();return T.readdirSync(i).filter(e=>/^protocol\d+\.json$/.test(e)).map(e=>parseInt(e.match(/\d+/)[0],10)).sort((e,t)=>e-t)}var I=class{mpq;header;build=0;protocol;baseProtocol;constructor(e){this.mpq=new P(e,!1)}init(){let e=M(29406);if(!e)throw new Error("Base protocol29406 not found. Did postinstall run? Try: npx tsx node_modules/@astefanski/storm-parser/scripts/postinstall.ts");this.baseProtocol=e;let t=this.mpq.header.userDataHeader;if(!t||!t.content)throw new Error("Replay does not have a user data header");let r=new w(t.content,this.baseProtocol.typeinfos);this.header=r.instance(this.baseProtocol.replay_header_typeid),this.build=this.header.m_version.m_baseBuild,this.protocol=this.loadProtocolForBuild(this.build)}loadProtocolForBuild(e){let t=M(e);if(!t){let r=F(),n=r[0];for(let s of r)s<=e&&s>n&&(n=s);t=M(n)}if(!t)throw new Error(`No protocol found for build ${e}`);return t}*decodeEventStream(e,t,r,n){if(!this.protocol)throw new Error("Protocol not loaded");let s=0;for(;!e.done();){let l=e.used_bits(),a=e.instance(this.protocol.svaruint32_typeid),c=Object.keys(a)[0],_=a[c];s+=_;let h=n?e.instance(this.protocol.replay_userid_typeid):void 0,o=Number(e.instance(t)),u=r[o];if(!u)throw new Error(`Unknown eventid(${o})`);let f=u[0],b=u[1],d=e.instance(f);d._event=b,d._eventid=o,d._gameloop=s,n&&(d._userid=h),e.byte_align(),d._bits=e.used_bits()-l,yield d}}getDetails(){if(!this.protocol)throw new Error("Protocol not loaded");let e=this.mpq.readFile("replay.details");return e?new w(e,this.protocol.typeinfos).instance(this.protocol.game_details_typeid):null}getInitData(){if(!this.protocol)throw new Error("Protocol not loaded");let e=this.mpq.readFile("replay.initData");return e?new B(e,this.protocol.typeinfos).instance(this.protocol.replay_initdata_typeid):null}getTrackerEvents(){if(!this.protocol)throw new Error("Protocol not loaded");let e=this.mpq.readFile("replay.tracker.events");if(!e)return[];let t=new w(e,this.protocol.typeinfos);return Array.from(this.decodeEventStream(t,this.protocol.tracker_eventid_typeid,this.protocol.tracker_event_types,!1))}getGameEvents(){if(!this.protocol)throw new Error("Protocol not loaded");let e=this.mpq.readFile("replay.game.events");if(!e)return[];let t=new B(e,this.protocol.typeinfos);return Array.from(this.decodeEventStream(t,this.protocol.game_eventid_typeid,this.protocol.game_event_types,!0))}extractFile(e){return this.mpq.readFile(e)}};var D=class{static async analyze(e){try{let t=new I(e);await t.init();let r=t.getDetails(),n=t.getTrackerEvents();if(!r)throw new Error("Missing replay.details from parsed MPQ archive");console.log("Raw TimeUTC:",r?.m_timeUTC,"Type:",typeof r?.m_timeUTC);let s={map:r?.m_title?.toString("utf8"),date:r?this.fileTimeToDate(r.m_timeUTC).toISOString():new Date().toISOString(),length:0,winner:-1,version:{m_build:t.build},teams:{0:{level:0,takedowns:0,ids:[]},1:{level:0,takedowns:0,ids:[]}}},l={};for(let o of r.m_playerList){if(!o||!o.m_toon)continue;let u=o.m_toon,f=`${u.m_region}-${u.m_programId}-${u.m_realm}-${u.m_id}`;l[f]={hero:o.m_hero?.toString("utf8")||"",name:o.m_name?.toString("utf8")||"",tag:"",team:o.m_teamId,win:o.m_result===1,gameStats:{}}}let a=t.extractFile("replay.server.battlelobby");if(a)try{let o=new RegExp("([\\p{L}\\d]{3,24}#\\d{4,10})[z\xD8]?","gu"),u=a.toString("utf8").match(o);if(u){let f=0;for(let b of r.m_playerList){if(!b||!b.m_toon)continue;let d=b.m_name?.toString("utf8");for(;f<u.length;){let p=u[f].split("#"),R=p[0],E=p[1].replace(/[zØ]/g,"");if(f++,R===d){let v=`${b.m_toon.m_region}-${b.m_toon.m_programId}-${b.m_toon.m_realm}-${b.m_toon.m_id}`;l[v]&&(l[v].tag=E);break}}}}}catch(o){console.error("BattleTag regex error:",o)}let c={},_=0,h=0;for(let o of n){if(o._event==="NNet.Replay.Tracker.SStatGameEvent"){let u=o.m_eventName?.toString("utf8");if(u==="PlayerInit"){let f=o.m_intData,b=o.m_stringData,d=f[0].m_value,m=b[1].m_value?.toString("utf8");m&&l[m]&&(c[d]=m)}else u==="GatesOpen"&&(_=o._gameloop)}h=Math.max(h,o._gameloop)}s.length=Math.floor((h-_)/16);for(let o of n)if(o._event==="NNet.Replay.Tracker.SScoreResultEvent"){let u=o.m_instanceList;for(let f of u){let b=f.m_name?.toString("utf8"),d=f.m_values,m=0;if(!b.startsWith("EndOfMatchAward"))for(let p of d)if(p&&p.length>0&&p[0]!==void 0){let R=m+1,E=c[R];if(E&&l[E]){let v=typeof p[0]=="object"&&p[0]!==null&&"m_value"in p[0]?p[0].m_value:p[0];v!=null&&(l[E].gameStats[b]=v)}m++}else p&&p.length===0&&m++}}for(let[o,u]of Object.entries(l)){u.win&&(s.winner=u.team);let f=s.teams[u.team.toString()];f&&(f.level=Math.max(f.level,u.gameStats.Level||0),f.takedowns+=u.gameStats.Takedowns||0,f.ids.push(o))}return{status:1,match:s,players:l}}catch(t){return console.error("ReplayAnalyzer Error:",t),{status:-2,error:String(t)}}}static fileTimeToDate(e){return new Date(Number(e)/1e4-116444736e5)}};export{D as ReplayAnalyzer,I as ReplayParser};
1
+ var q=(r=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(r,{get:(e,t)=>(typeof require<"u"?require:e)[t]}):r)(function(r){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+r+'" is not supported')});import*as G from"fs";import*as I from"zlib";var Y=q("seek-bzip"),j=512,J=65536,Z=16777216,ee=67108864,te=2147483648,ne={TABLE_OFFSET:0,HASH_A:1,HASH_B:2,TABLE:3};function re(){let r=1048577,e=new Uint32Array(256*5);for(let t=0;t<256;t++){let n=t;for(let i=0;i<5;i++){r=(r*125+3)%2796203;let s=(r&65535)<<16;r=(r*125+3)%2796203;let a=r&65535;e[n]=(s|a)>>>0,n+=256}}return e}var K=re(),L=class{file;header;hashTable;blockTable;files;constructor(e,t=!0){if(Buffer.isBuffer(e)?this.file=e:this.file=G.readFileSync(e),this.header=this.readHeader(),this.hashTable=this.readTable("hash"),this.blockTable=this.readTable("block"),t){let n=this.readFile("(listfile)");n?this.files=n.toString("utf8").trim().split(`\r
2
+ `):this.files=null}else this.files=null}readHeader(){let e=this.file.toString("utf8",0,4),t;if(e==="MPQ")t=this.readMPQHeader(),t.offset=0;else if(e==="MPQ\x1B"){let n=this.readMPQUserDataHeader();t=this.readMPQHeader(n.mpqHeaderOffset),t.offset=n.mpqHeaderOffset,t.userDataHeader=n}else throw new Error("Invalid MPQ file header");return t}readMPQHeader(e=0){let t=this.file.subarray(e,e+32),n={magic:t.toString("utf8",0,4),headerSize:t.readUInt32LE(4),archiveSize:t.readUInt32LE(8),formatVersion:t.readUInt16LE(12),sectorSizeShift:t.readUInt16LE(14),hashTableOffset:t.readUInt32LE(16),blockTableOffset:t.readUInt32LE(20),hashTableEntries:t.readUInt32LE(24),blockTableEntries:t.readUInt32LE(28)};if(n.formatVersion===1){let i=this.file.subarray(e+32,e+32+12);n.extendedBlockTableOffset=i.readUInt32LE(0)+i.readUInt32LE(4)*4294967296,n.hashTableOffsetHigh=i.readInt8(8),n.blockTableOffsetHigh=i.readInt8(10)}return n}readMPQUserDataHeader(){let e=this.file.subarray(0,16),t={magic:e.toString("utf8",0,4),userDataSize:e.readUInt32LE(4),mpqHeaderOffset:e.readUInt32LE(8),userDataHeaderSize:e.readUInt32LE(12)};return t.content=this.file.subarray(16,16+t.userDataHeaderSize),t}readTable(e){let t=e==="hash"?"hashTableOffset":"blockTableOffset",n=e==="hash"?"hashTableEntries":"blockTableEntries",i=this.header[t],s=this.header[n];if(i==null||s==null)throw new Error("Missing "+e+" offset or entries");let a=this.hash("("+e+" table)","TABLE"),o=this.file.subarray(i+(this.header.offset||0),i+(this.header.offset||0)+s*16);o=this.decrypt(o,a);let l=[];for(let u=0;u<s;u++){let c=o.subarray(u*16,u*16+16);e==="hash"?l.push({hashA:c.readUInt32LE(0),hashB:c.readUInt32LE(4),locale:c.readUInt16LE(8),platform:c.readUInt16LE(10),blockTableIndex:c.readUInt32LE(12)}):l.push({offset:c.readUInt32LE(0),archivedSize:c.readUInt32LE(4),size:c.readUInt32LE(8),flags:c.readUInt32LE(12)})}return l}getHashTableEntry(e){let t=this.hash(e,"HASH_A"),n=this.hash(e,"HASH_B");for(let i of this.hashTable)if(i.hashA===t&&i.hashB===n)return i}readFile(e,t=!1){function n(l){let u=l[0];if(u===0)return l;if(u===2)return I.inflateSync(l.subarray(1));if(u===16)return Y.decode(l.subarray(1));try{return I.inflateSync(l.subarray(1))}catch{return I.inflateRawSync(l.subarray(1))}}let i=this.getHashTableEntry(e);if(!i)return null;let s=this.blockTable[i.blockTableIndex];if(!s||!(s.flags&te))return null;if(s.archivedSize===0)return Buffer.alloc(0);let a=s.offset+(this.header.offset||0),o=this.file.subarray(a,a+s.archivedSize);if(s.flags&J)throw new Error("Encryption is not supported");if(s.flags&Z)s.flags&j&&(t||s.size>s.archivedSize)&&(o=n(o));else{let l=512<<this.header.sectorSizeShift,u=Math.trunc(s.size/l)+1,c=!1;s.flags&ee&&(c=!0,u+=1);let d=[];for(let h=0;h<u+1;h++)d.push(o.readUInt32LE(4*h));let f=d.length-(c?2:1),b=[],m=s.size;for(let h=0;h<f;h++){let p=o.subarray(d[h],d[h+1]);s.flags&j&&(t||m>p.length)&&(p=n(p)),m-=p.length,b.push(p)}o=Buffer.concat(b)}return o}hash(e,t){let n=2146271213,i=4008636142;for(let s=0;s<e.length;s++){let a=e.toUpperCase().charCodeAt(s);n=(K[(ne[t]<<8)+a]^n+i)>>>0,i=a+n+i+(i<<5)+3>>>0}return n}decrypt(e,t){let n=t>>>0,i=4008636142,s=Buffer.alloc(e.length),a=e.length/4;for(let o=0;o<a;o++){i=i+K[1024+(n&255)]>>>0;let l=e.readUInt32LE(o*4);l=(l^n+i)>>>0,n=((~n<<21)+286331153|n>>>11)>>>0,i=l+i+(i<<5)+3>>>0,s.writeUInt32LE(l,o*4)}return s}};var D=class extends Error{constructor(e="Truncated Buffer"){super(e),this.name="TruncatedError"}},P=class extends Error{constructor(e="Corrupted Buffer"){super(e),this.name="CorruptedError"}},M=class{_data;_used;_next;_nextbits;_bigendian;constructor(e,t="big"){this._data=e,this._used=0,this._next=0,this._nextbits=0,this._bigendian=t==="big"}done(){return this._nextbits===0&&this._used>=this._data.length}used_bits(){return this._used*8-this._nextbits}byte_align(){this._nextbits=0}read_aligned_bytes(e){if(this.byte_align(),this._used+e>this._data.length)throw new D;let t=this._data.subarray(this._used,this._used+e);return this._used+=e,t}read_bits(e){let t=0,n=0;for(;n!==e;){if(this._nextbits===0){if(this.done())throw new D;this._next=this._data[this._used],this._used+=1,this._nextbits=8}let i=Math.min(e-n,this._nextbits),s=this._next&(1<<i)-1;this._bigendian?t+=s*Math.pow(2,e-n-i):t+=s*Math.pow(2,n),this._next>>=i,this._nextbits-=i,n+=i}return t}read_bits_bigint(e){let t=0n,n=0;for(;n!==e;){if(this._nextbits===0){if(this.done())throw new D;this._next=this._data[this._used],this._used+=1,this._nextbits=8}let i=Math.min(e-n,this._nextbits),s=BigInt(this._next&(1<<i)-1);this._bigendian?t|=s<<BigInt(e-n-i):t|=s<<BigInt(n),this._next>>=i,this._nextbits-=i,n+=i}return t}read_unaligned_bytes(e){let t=Buffer.alloc(e);for(let n=0;n<e;n++)t[n]=this.read_bits(8);return t}};var H=class{_buffer;_typeinfos;constructor(e,t){this._buffer=new M(e),this._typeinfos=t}instance(e){if(e>=this._typeinfos.length)throw new P(`Invalid typeid ${e}`);let t=this._typeinfos[e],n=t[0],i=t[1]||[],s=this[n];if(typeof s!="function")throw new Error(`Decoder method ${n} not implemented`);return s.apply(this,i)}byte_align(){this._buffer.byte_align()}done(){return this._buffer.done()}used_bits(){return this._buffer.used_bits()}_array(e,t){let n=this._int(e),i=new Array(n);for(let s=0;s<n;s++)i[s]=this.instance(t);return i}_bitarray(e){let t=this._int(e);return[t,this._buffer.read_bits(t)]}_blob(e){let t=this._int(e);return this._buffer.read_aligned_bytes(t)}_bool(){return this._int([0,1])!==0}_choice(e,t){let n=this._int(e);if(!(n in t))throw new P(`Choice tag ${n} not found`);let i=t[n];return{[i[0]]:this.instance(i[1])}}_fourcc(){let e=this._buffer.read_bits(32),t=Buffer.alloc(4);return t.writeUInt32BE(e,0),t.toString("ascii")}_int(e){return e[0]+this._buffer.read_bits(e[1])}_null(){return null}_optional(e){return this._bool()?this.instance(e):null}_real32(){return this._buffer.read_unaligned_bytes(4).readFloatBE(0)}_real64(){return this._buffer.read_unaligned_bytes(8).readDoubleBE(0)}_struct(e){let t={};for(let n of e)if(n[0]==="__parent"){let i=this.instance(n[1]);if(typeof i=="object"&&i!==null)t={...t,...i};else{if(e.length===1)return i;t[n[0]]=i}}else t[n[0]]=this.instance(n[1]);return t}},x=class{_buffer;_typeinfos;constructor(e,t){this._buffer=new M(e),this._typeinfos=t}instance(e){if(e>=this._typeinfos.length)throw new P(`Invalid typeid ${e}`);let t=this._typeinfos[e],n=t[0],i=t[1]||[],s=this[n];if(typeof s!="function")throw new Error(`Decoder method ${n} not implemented`);return s.apply(this,i)}byte_align(){this._buffer.byte_align()}done(){return this._buffer.done()}used_bits(){return this._buffer.used_bits()}_expect_skip(e){if(this._buffer.read_bits(8)!==e)throw new P(`Expected skip ${e}`)}_vint(){let e=this._buffer.read_bits(8),t=(e&1)!==0,n=BigInt(e>>1&63),i=6n;for(;(e&128)!==0;)e=this._buffer.read_bits(8),n|=BigInt(e&127)<<i,i+=7n;let s=t?-n:n;return s>=BigInt(Number.MIN_SAFE_INTEGER)&&s<=BigInt(Number.MAX_SAFE_INTEGER)?Number(s):s}_array(e,t){this._expect_skip(0);let n=Number(this._vint()),i=new Array(n);for(let s=0;s<n;s++)i[s]=this.instance(t);return i}_bitarray(e){this._expect_skip(1);let t=Number(this._vint());return[t,this._buffer.read_aligned_bytes(Math.floor((t+7)/8))]}_blob(e){this._expect_skip(2);let t=Number(this._vint());return this._buffer.read_aligned_bytes(t)}_bool(){return this._expect_skip(6),this._buffer.read_bits(8)!==0}_choice(e,t){this._expect_skip(3);let n=Number(this._vint());if(!(n in t))return this._skip_instance(),{};let i=t[n];return{[i[0]]:this.instance(i[1])}}_fourcc(){return this._expect_skip(7),this._buffer.read_aligned_bytes(4)}_int(e){return this._expect_skip(9),Number(this._vint())}_null(){return null}_optional(e){return this._expect_skip(4),this._buffer.read_bits(8)!==0?this.instance(e):null}_real32(){return this._expect_skip(7),this._buffer.read_aligned_bytes(4).readFloatBE(0)}_real64(){return this._expect_skip(8),this._buffer.read_aligned_bytes(8).readDoubleBE(0)}_struct(e){this._expect_skip(5);let t={},n=Number(this._vint());for(let i=0;i<n;i++){let s=Number(this._vint()),a=e.find(o=>o[2]===s);if(a)if(a[0]==="__parent"){let o=this.instance(a[1]);typeof o=="object"&&o!==null?t={...t,...o}:e.length===1?t=o:t[a[0]]=o}else t[a[0]]=this.instance(a[1]);else this._skip_instance()}return t}_skip_instance(){let e=this._buffer.read_bits(8);if(e===0){let t=Number(this._vint());for(let n=0;n<t;n++)this._skip_instance()}else if(e===1){let t=Number(this._vint());this._buffer.read_aligned_bytes(Math.floor((t+7)/8))}else if(e===2){let t=Number(this._vint());this._buffer.read_aligned_bytes(t)}else if(e===3)this._vint(),this._skip_instance();else if(e===4)this._buffer.read_bits(8)!==0&&this._skip_instance();else if(e===5){let t=Number(this._vint());for(let n=0;n<t;n++)this._vint(),this._skip_instance()}else e===6?this._buffer.read_aligned_bytes(1):e===7?this._buffer.read_aligned_bytes(4):e===8?this._buffer.read_aligned_bytes(8):e===9&&this._vint()}};import B from"fs";import w from"path";function ie(){let r=[];try{r.push(w.resolve(__dirname,"..","protocols")),r.push(w.resolve(__dirname,"..","..","protocols"))}catch{}r.push(w.resolve(process.cwd(),"protocols"));let e=process.cwd();for(;e!==w.dirname(e);){let t=w.join(e,"node_modules","@astefanski","storm-parser","protocols");r.push(t),e=w.dirname(e)}for(let t of r)if(B.existsSync(t)&&B.readdirSync(t).some(n=>n.endsWith(".json")))return t;throw new Error(`@astefanski/storm-parser: Protocols directory not found. Did postinstall run? Try: npx tsx node_modules/@astefanski/storm-parser/scripts/postinstall.ts
3
+ Searched: `+r.join(", "))}var U=new Map,X=null;function F(){return X||(X=ie()),X}function A(r){if(U.has(r))return U.get(r);let e=F(),t=w.join(e,`protocol${r}.json`);if(!B.existsSync(t))return null;let n=JSON.parse(B.readFileSync(t,"utf-8"));return U.set(r,n),n}function Q(){let r=F();return B.readdirSync(r).filter(e=>/^protocol\d+\.json$/.test(e)).map(e=>parseInt(e.match(/\d+/)[0],10)).sort((e,t)=>e-t)}var R=class{mpq;header;build=0;protocol;baseProtocol;constructor(e){this.mpq=new L(e,!1)}init(){let e=A(29406);if(!e)throw new Error("Base protocol29406 not found. Did postinstall run? Try: npx tsx node_modules/@astefanski/storm-parser/scripts/postinstall.ts");this.baseProtocol=e;let t=this.mpq.header.userDataHeader;if(!t||!t.content)throw new Error("Replay does not have a user data header");let n=new x(t.content,this.baseProtocol.typeinfos);this.header=n.instance(this.baseProtocol.replay_header_typeid),this.build=this.header.m_version.m_baseBuild,this.protocol=this.loadProtocolForBuild(this.build)}loadProtocolForBuild(e){let t=A(e);if(!t){let n=Q(),i=n[0];for(let s of n)s<=e&&s>i&&(i=s);t=A(i)}if(!t)throw new Error(`No protocol found for build ${e}`);return t}*decodeEventStream(e,t,n,i){if(!this.protocol)throw new Error("Protocol not loaded");let s=0;for(;!e.done();){let a=e.used_bits(),o=e.instance(this.protocol.svaruint32_typeid),l=Object.keys(o)[0],u=o[l];s+=u;let c=i?e.instance(this.protocol.replay_userid_typeid):void 0,d=Number(e.instance(t)),f=n[d];if(!f)throw new Error(`Unknown eventid(${d})`);let b=f[0],m=f[1],h=e.instance(b);h._event=m,h._eventid=d,h._gameloop=s,i&&(h._userid=c),e.byte_align(),h._bits=e.used_bits()-a,yield h}}getDetails(){if(!this.protocol)throw new Error("Protocol not loaded");let e=this.mpq.readFile("replay.details");return e?new x(e,this.protocol.typeinfos).instance(this.protocol.game_details_typeid):null}getInitData(){if(!this.protocol)throw new Error("Protocol not loaded");let e=this.mpq.readFile("replay.initData");return e?new H(e,this.protocol.typeinfos).instance(this.protocol.replay_initdata_typeid):null}getTrackerEvents(){if(!this.protocol)throw new Error("Protocol not loaded");let e=this.mpq.readFile("replay.tracker.events");if(!e)return[];let t=new x(e,this.protocol.typeinfos);return Array.from(this.decodeEventStream(t,this.protocol.tracker_eventid_typeid,this.protocol.tracker_event_types,!1))}getGameEvents(){if(!this.protocol)throw new Error("Protocol not loaded");let e=this.mpq.readFile("replay.game.events");if(!e)return[];let t=new H(e,this.protocol.typeinfos);return Array.from(this.decodeEventStream(t,this.protocol.game_eventid_typeid,this.protocol.game_event_types,!0))}extractFile(e){return this.mpq.readFile(e)}getHeader(){return this.header}getBuild(){return this.build}};var z={TownCannonTowerL2:"Fort Tower",TownCannonTowerL3:"Keep Tower",TownTownHallL2:"Fort",TownTownHallL3:"Keep",TownMoonwellL2:"Fort Well",TownMoonwellL3:"Keep Well"},se={MercLanerMeleeKnight:"Bruiser Camp",MercLanerRangedMage:"Bruiser Camp",MercLanerSiegeGiant:"Siege Camp",MercLanerRangedMinion:"Siege Camp"};function E(r){return Buffer.isBuffer(r)?r.toString("utf8"):typeof r=="string"?r:String(r??"")}function _(r,e){if(!r)return;let t=r.find(n=>E(n.m_key)===e);return t!==void 0?t.m_value:void 0}function C(r,e){if(!r)return;let t=r.find(n=>E(n.m_key)===e);return t!==void 0?E(t.m_value):void 0}function v(r,e){if(!r)return;let t=r.find(n=>E(n.m_key)===e);return t!==void 0?t.m_value:void 0}function N(r,e){return`${r}-${e}`}function k(r,e){return(r-e)/16}function $(r,e,t){let n={playerIDMap:{},loopGameStart:0,loopGameEnd:0,unitIndex:{},heroUnits:{},heroLives:{}};t.levelTimes={0:{},1:{}},t.takedowns=[],t.structures={},t.XPBreakdown=[],t.mercs={captures:[],units:{}},t.objective={0:{count:0,events:[]},1:{count:0,events:[]},type:t.map||""};for(let i of r)switch(n.loopGameEnd=Math.max(n.loopGameEnd,i._gameloop),i._event){case"NNet.Replay.Tracker.SStatGameEvent":oe(i,n,e,t);break;case"NNet.Replay.Tracker.SUnitBornEvent":ce(i,n,t);break;case"NNet.Replay.Tracker.SUnitDiedEvent":ue(i,n,t);break;case"NNet.Replay.Tracker.SUnitOwnerChangeEvent":de(i,n);break}return t.loopGameStart=n.loopGameStart,t.loopLength=n.loopGameEnd,t.length=(n.loopGameEnd-n.loopGameStart)/16,fe(n,e),{playerIDMap:n.playerIDMap}}function oe(r,e,t,n){let i=E(r.m_eventName),s=r.m_intData,a=r.m_stringData,o=r.m_fixedData;switch(i){case"PlayerInit":{let l=_(s,"PlayerID"),u=C(a,"ToonHandle");l!==void 0&&u&&t[u]&&(e.playerIDMap[l]=u);break}case"GatesOpen":e.loopGameStart=r._gameloop;break;case"LevelUp":{let l=_(s,"PlayerID"),u=_(s,"Level");if(l===void 0||u===void 0)break;let c;if(l>=1&&l<=5)c="0";else if(l>=6&&l<=10)c="1";else break;n.levelTimes[c][String(u)]||(n.levelTimes[c][String(u)]={loop:r._gameloop,level:u,team:c,time:k(r._gameloop,e.loopGameStart)});break}case"TalentChosen":{let l=_(s,"PlayerID"),u=C(a,"PurchaseName");if(l===void 0||!u)break;let c=e.playerIDMap[l];if(!c||!t[c])break;let d=["Tier1Choice","Tier2Choice","Tier3Choice","Tier4Choice","Tier5Choice","Tier6Choice","Tier7Choice"];for(let f of d)if(!t[c].talents[f]){t[c].talents[f]=u;break}break}case"PlayerDeath":{let l=_(s,"PlayerID"),u=_(s,"KillingPlayer"),c=v(o,"PositionX")??0,d=v(o,"PositionY")??0;if(l===void 0)break;let f=e.playerIDMap[l];if(!f)break;let b={player:f,hero:t[f]?.hero||""},m=[],h=t[f]?.team;if(u&&u>0){let g=e.playerIDMap[u];g&&t[g]&&m.push({player:g,hero:t[g].hero})}for(let[g,y]of Object.entries(e.playerIDMap)){let T=t[y];!T||T.team===h||parseInt(g)===u||m.push({player:y,hero:T.hero})}let p={loop:r._gameloop,time:k(r._gameloop,e.loopGameStart),x:c,y:d,killers:m,victim:b};n.takedowns.push(p),t[f]&&t[f].deaths.push(p);for(let g of m)t[g.player]&&t[g.player].takedowns.push(p);break}case"PeriodicXPBreakdown":{let l=_(s,"Team");if(l===void 0)break;let u={GameTime:_(s,"GameTime")??0,PreviousGameTime:_(s,"PreviousGameTime")??0,MinionXP:v(o,"MinionXP")??0,CreepXP:v(o,"CreepXP")??0,StructureXP:v(o,"StructureXP")??0,HeroXP:v(o,"HeroXP")??0,TrickleXP:v(o,"TrickleXP")??0};n.XPBreakdown.push({loop:r._gameloop,time:k(r._gameloop,e.loopGameStart),team:l,teamLevel:_(s,"TeamLevel")??0,breakdown:u,theoreticalMinionXP:_(s,"TheoreticalMinionXP")??0});break}case"EndOfGameXPBreakdown":{let l=_(s,"Team");if(l===void 0)break;n.XPBreakdown.push({loop:r._gameloop,time:k(r._gameloop,e.loopGameStart),team:l,theoreticalMinionXP:_(s,"TheoreticalMinionXP")??0,breakdown:{GameTime:0,PreviousGameTime:0,MinionXP:v(o,"MinionXP")??0,CreepXP:v(o,"CreepXP")??0,StructureXP:v(o,"StructureXP")??0,HeroXP:v(o,"HeroXP")??0,TrickleXP:v(o,"TrickleXP")??0}});break}case"JungleCampCapture":{let l=_(s,"CampTeam")??_(s,"Team")??0;n.mercs.captures.push({loop:r._gameloop,type:C(a,"CampType")??C(a,"Result")??"Unknown Camp",team:l,time:k(r._gameloop,e.loopGameStart)});break}case"EndOfGameTalentChoices":{let l=_(s,"PlayerID");if(l===void 0)break;let u=e.playerIDMap[l];if(!u||!t[u])break;let c={},d=["Tier1Choice","Tier2Choice","Tier3Choice","Tier4Choice","Tier5Choice","Tier6Choice","Tier7Choice"];if(a)for(let f=0;f<a.length&&f<d.length;f++){let b=E(a[f].m_value);b&&(c[d[f]]=b)}t[u].talents=c;break}default:le(i,r,e,n,s,o);break}}var ae=new Set(["SoulEatersSpawned","TributeCollected","RavenCurseActivated","AltarCaptured","SkyTempleShotsFired","DragonKnightActivated","GardenTerrorActivated","InfernalShrineCaptured","PunisherKilled","VolskayaVehicleCapture","BraxisWaveStart","ImmortalDefeated","NukeExploded","PayloadDelivered","AlteracCavalryCharge","AlteracCavalry"]);function le(r,e,t,n,i,s){if(!ae.has(r)||!i)return;let a=_(i,"Team")??_(i,"Event")??0,o=a===0||a===1?a:0,l={team:a,loop:e._gameloop,time:k(e._gameloop,t.loopGameStart),score:_(i,"Score"),duration:v(s,"Duration")};n.objective[o].events.push(l),n.objective[o].count=n.objective[o].events.length}function ce(r,e,t){let n=E(r.m_unitTypeName),i=r.m_unitTagIndex,s=r.m_unitTagRecycle,a=N(i,s),o=r.m_controlPlayerId??r.m_upkeepPlayerId,l=r.m_x??0,u=r.m_y??0,c;if(o>=1&&o<=5?c=0:o>=6&&o<=10?c=1:o===11?c=0:o===12?c=1:c=o<=5?0:1,e.unitIndex[a]={type:n,playerId:o,team:c,x:l,y:u,bornLoop:r._gameloop},n.startsWith("Town")&&z[n]&&(t.structures[a]={type:n,name:z[n],tag:i,rtag:s,x:l,y:u,team:c}),n.startsWith("Hero")&&o>=1&&o<=10){e.heroUnits[a]=o,e.heroLives[o]||(e.heroLives[o]=[]);let d=k(r._gameloop,e.loopGameStart);e.heroLives[o].push({born:d,locations:[{x:l,y:u,time:d}],duration:0})}if(se[n]){let f=t.mercs.captures[t.mercs.captures.length-1]?.loop??r._gameloop;t.mercs.units[a]={loop:f,team:c,type:n,locations:[{x:l,y:u}],time:k(f,e.loopGameStart),duration:0}}}function ue(r,e,t){let n=N(r.m_unitTagIndex,r.m_unitTagRecycle),i=k(r._gameloop,e.loopGameStart);t.structures[n]&&(t.structures[n].destroyedLoop=r._gameloop,t.structures[n].destroyed=i),t.mercs.units[n]&&(t.mercs.units[n].duration=i-t.mercs.units[n].time);let s=e.heroUnits[n];if(s!==void 0&&e.heroLives[s]){let a=e.heroLives[s],o=a[a.length-1];o&&o.died===void 0&&(o.died=i,o.duration=i-o.born)}}function de(r,e){let t=N(r.m_unitTagIndex,r.m_unitTagRecycle),n=r.m_controlPlayerId??r.m_upkeepPlayerId;e.unitIndex[t]&&(e.unitIndex[t].playerId=n,n>=1&&n<=5?e.unitIndex[t].team=0:n>=6&&n<=10&&(e.unitIndex[t].team=1))}function fe(r,e){for(let[t,n]of Object.entries(r.heroLives)){let i=parseInt(t,10),s=r.playerIDMap[i];if(!s||!e[s])continue;for(let o of n)if(o.died===void 0){let l=o.locations[o.locations.length-1];o.duration=l?l.time-o.born:0}let a="";for(let[o,l]of Object.entries(r.heroUnits))if(l===i){a=o;break}a&&(e[s].units[a]={lives:n})}}var me=["DamageTaken","CreepDamage","Healing","HeroDamage","MinionDamage","SelfHealing","SiegeDamage","ProtectionGivenToAllies","TeamfightDamageTaken","TeamfightHealingDone","TeamfightHeroDamage","TimeCCdEnemyHeroes","TimeRootingEnemyHeroes","TimeSpentDead","TimeStunningEnemyHeroes","TimeSilencingEnemyHeroes"];function pe(){return{DamageTaken:0,CreepDamage:0,Healing:0,HeroDamage:0,MinionDamage:0,SelfHealing:0,SiegeDamage:0,ProtectionGivenToAllies:0,TeamfightDamageTaken:0,TeamfightHealingDone:0,TeamfightHeroDamage:0,TimeCCdEnemyHeroes:0,TimeRootingEnemyHeroes:0,TimeSpentDead:0,TimeStunningEnemyHeroes:0,TimeSilencingEnemyHeroes:0,avgTimeSpentDead:0,timeDeadPct:0}}function he(){return{mercCaptures:0,mercUptime:0,mercUptimePercent:0,structures:{},KDA:0,PPK:0,timeTo10:0,totals:pe(),levelAdvTime:0,maxLevelAdv:0,avgLevelAdv:0,levelAdvPct:0,uptime:[],uptimeHistogram:{},wipes:0,avgHeroesAlive:0,aces:0,timeWithHeroAdv:0,pctWithHeroAdv:0,passiveXPRate:0,passiveXPDiff:0,passiveXPGain:0}}function W(r,e){be(r,e),_e(r,e),ye(r),ve(r),Te(r,e),Se(r)}function be(r,e){for(let t of["0","1"]){let n=r.teams[t];if(!n)continue;n.names=[],n.heroes=[],n.tags=[],n.stats=he();for(let d of n.ids){let f=e[d];f&&(n.names.push(f.name),n.heroes.push(f.hero),n.tags.push(f.tag))}for(let d of n.ids){let f=e[d];if(f)for(let b of me){let m=f.gameStats[b];typeof m=="number"&&(n.stats.totals[b]+=m)}}let i=n.ids.reduce((d,f)=>d+(e[f]?.gameStats.Deaths??0),0);i>0&&(n.stats.totals.avgTimeSpentDead=n.stats.totals.TimeSpentDead/i),r.length>0&&(n.stats.totals.timeDeadPct=n.stats.totals.TimeSpentDead/(r.length*n.ids.length));let s=n.takedowns,a=i;n.stats.KDA=a>0?s/a:s,n.stats.PPK=s>0?r.takedowns.filter(d=>n.ids.includes(d.killers[0]?.player)).reduce((d,f)=>d+f.killers.length,0)/s:0;let o=r.levelTimes[t];o?.["10"]&&(n.stats.timeTo10=o[10].time);let l=parseInt(t,10),u=r.mercs.captures.filter(d=>d.team===l);n.stats.mercCaptures=u.length;let c=0;for(let d of Object.values(r.mercs.units))d.team===l&&d.duration>0&&(c+=d.duration);n.stats.mercUptime=c,n.stats.mercUptimePercent=r.length>0?c/r.length:0,ge(r,n,t)}}function ge(r,e,t){let n=parseInt(t,10),i=n===0?1:0,s={};for(let a of Object.values(r.structures)){let o=a.name;s[o]||(s[o]={lost:0,destroyed:0,first:r.length}),a.team===i&&a.destroyed!==void 0&&(s[o].destroyed++,s[o].first=Math.min(s[o].first,a.destroyed)),a.team===n&&a.destroyed!==void 0&&s[o].lost++}e.stats.structures=s}function _e(r,e){let t=r.length/60;for(let n of Object.values(e)){let i=n.gameStats,s=i.Deaths??0,a=i.TeamTakedowns??0;t>0&&(i.DPM=(i.HeroDamage??0)/t,i.HPM=((i.Healing??0)+(i.SelfHealing??0))/t,i.XPM=(i.ExperienceContribution??0)/t),i.KDA=s>0?(i.Takedowns??0)/s:i.Takedowns??0,i.KillParticipation=a>0?(i.Takedowns??0)/a:0,i.damageDonePerDeath=s>0?(i.HeroDamage??0)/s:i.HeroDamage??0,i.damageTakenPerDeath=s>0?(i.DamageTaken??0)/s:i.DamageTaken??0,i.healingDonePerDeath=s>0?((i.Healing??0)+(i.SelfHealing??0))/s:(i.Healing??0)+(i.SelfHealing??0),i.length=r.length}}function ye(r){let e=0,t=0,n=new Set(r.teams[0]?.ids||[]);for(let i of r.takedowns)n.has(i.victim.player)?t++:e++;r.team0Takedowns=e,r.team1Takedowns=t}function ve(r){let e=r.levelTimes[0]||{},t=r.levelTimes[1]||{},n=[];for(let u of Object.values(e))n.push({time:u.time,team:0,level:u.level});for(let u of Object.values(t))n.push({time:u.time,team:1,level:u.level});if(n.sort((u,c)=>u.time-c.time),n.length===0){r.levelAdvTimeline=[];return}let i=[],s=0,a=0,o=n[0].time;for(let u of n){let c=s-a;u.time>o&&i.push({start:o,end:u.time,levelDiff:c,length:u.time-o}),u.team===0?s=u.level:a=u.level,o=u.time}let l=s-a;r.length>o&&i.push({start:o,end:r.length,levelDiff:l,length:r.length-o}),r.levelAdvTimeline=i;for(let u of["0","1"]){let c=r.teams[u];if(!c)continue;let d=u==="0"?1:-1,f=0,b=0,m=0,h=0;for(let p of i){let g=p.levelDiff*d;g>0&&(f+=p.length),b=Math.max(b,g),m+=g*p.length,h+=p.length}c.stats.levelAdvTime=f,c.stats.maxLevelAdv=b,c.stats.avgLevelAdv=h>0?m/h:0,c.stats.levelAdvPct=r.length>0?f/r.length:0}}function Te(r,e){for(let s of["0","1"]){let a=r.teams[s];if(!a)continue;let o=[];for(let p of a.ids){let g=e[p];if(g)for(let y of g.deaths){o.push({time:y.time,delta:-1});let T=y.time+ke(g.gameStats.Level??1,y.time,r.length);T<r.length&&o.push({time:T,delta:1})}}o.sort((p,g)=>p.time-g.time);let l=[{time:0,heroes:a.ids.length}],u=a.ids.length;for(let p of o)u+=p.delta,u=Math.max(0,Math.min(a.ids.length,u)),l.push({time:p.time,heroes:u});a.stats.uptime=l;let c={};for(let p=0;p<l.length;p++){let y=(p<l.length-1?l[p+1].time:r.length)-l[p].time,T=String(l[p].heroes);c[T]=(c[T]||0)+y}a.stats.uptimeHistogram=c;let d=0,f=0;for(let p=0;p<l.length;p++){let y=(p<l.length-1?l[p+1].time:r.length)-l[p].time;d+=l[p].heroes*y,f+=y}a.stats.avgHeroesAlive=f>0?d/f:a.ids.length,a.stats.wipes=l.filter(p=>p.heroes===0).length,a.stats.aces=0;let b=s==="0"?"1":"0",m=r.teams[b];m?.stats?.uptime&&(a.stats.aces=m.stats.uptime.filter(p=>p.heroes===0).length);let h=r.XPBreakdown.filter(p=>p.team===parseInt(s,10));if(h.length>0){let g=h[h.length-1].breakdown.TrickleXP;a.stats.passiveXPGain=g,a.stats.passiveXPRate=r.length>0?g/(r.length/60):0}}for(let s of["0","1"]){let a=r.teams[s],o=s==="0"?"1":"0",l=r.teams[o];if(!a||!l)continue;let u=a.stats.uptime,c=l.stats.uptime;if(!u.length||!c.length)continue;let d=new Set;for(let m of u)d.add(m.time);for(let m of c)d.add(m.time);let f=Array.from(d).sort((m,h)=>m-h),b=0;for(let m=0;m<f.length;m++){let h=f[m],g=(m<f.length-1?f[m+1]:r.length)-h,y=V(u,h),T=V(c,h);y>T&&(b+=g)}a.stats.timeWithHeroAdv=b,a.stats.pctWithHeroAdv=r.length>0?b/r.length:0}let t=r.teams[0]?.stats.passiveXPRate??0,n=r.teams[1]?.stats.passiveXPRate??0,i=(t+n)/2;r.teams[0]&&(r.teams[0].stats.passiveXPDiff=i>0?t/i:0),r.teams[1]&&(r.teams[1].stats.passiveXPDiff=i>0?n/i:0)}function V(r,e){let t=0;for(let n of r)if(n.time<=e)t=n.heroes;else break;return t}function ke(r,e,t){return r<=1?15:r<=5?15+(r-1)*2:r<=10?23+(r-5)*3:r<=15?38+(r-10)*4:58+(r-15)*5}function Se(r){let e=1/0,t=-1,n=1/0,i=-1;for(let a of Object.values(r.structures))a.destroyed!==void 0&&(a.name==="Fort"&&a.destroyed<e&&(e=a.destroyed,t=a.team===0?1:0),a.name==="Keep"&&a.destroyed<n&&(n=a.destroyed,i=a.team===0?1:0));t>=0&&(r.firstFort=t,r.firstFortWin=t===r.winner),i>=0&&(r.firstKeep=i,r.firstKeepWin=i===r.winner);let s=[...r.objective[0].events.map(a=>({...a,assignedTeam:0})),...r.objective[1].events.map(a=>({...a,assignedTeam:1}))].sort((a,o)=>a.loop-o.loop);s.length>0&&(r.firstObjective=s[0].assignedTeam,r.firstObjectiveWin=s[0].assignedTeam===r.winner),r.firstPickWin=r.picks.first===r.winner}function S(r){return Buffer.isBuffer(r)?r.toString("utf8"):typeof r=="string"?r:String(r??"")}var O=class{static async analyze(e){try{let t=new R(e);await t.init();let n=t.getDetails();if(!n)throw new Error("Missing replay.details from parsed MPQ archive");let i=t.getTrackerEvents(),s=t.getInitData(),o=t.getHeader()?.m_version??{},l={m_flags:o.m_flags??0,m_major:o.m_major??0,m_minor:o.m_minor??0,m_revision:o.m_revision??0,m_build:o.m_baseBuild??t.getBuild(),m_baseBuild:o.m_baseBuild??t.getBuild()},u=n.m_playerList.find(b=>b?.m_toon)?.m_toon,c={version:l,map:S(n.m_title),date:this.fileTimeToDate(n.m_timeUTC).toISOString(),rawDate:Number(n.m_timeUTC),length:0,winner:-1,region:u?.m_region,playerIDs:[],heroes:[],levelTimes:{0:{},1:{}},bans:{0:[],1:[]},picks:{0:[],1:[],first:0},XPBreakdown:[],takedowns:[],mercs:{captures:[],units:{}},team0Takedowns:0,team1Takedowns:0,structures:{},objective:{0:{count:0,events:[]},1:{count:0,events:[]},type:""},teams:{0:this.emptyTeam(),1:this.emptyTeam()},winningPlayers:[],levelAdvTimeline:[],firstPickWin:!1};c.objective.type=c.map||"";let d={};for(let b of n.m_playerList){if(!b?.m_toon)continue;let m=b.m_toon,h=S(m.m_programId),p=`${m.m_region}-${h}-${m.m_realm}-${m.m_id}`,g=S(b.m_hero);d[p]={hero:g,name:S(b.m_name),uuid:m.m_id,region:m.m_region,realm:m.m_realm,ToonHandle:p,tag:0,team:b.m_teamId,win:b.m_result===1,gameStats:{},awards:[],talents:{},takedowns:[],deaths:[],units:{}},c.playerIDs.push(p),c.heroes.push(g)}this.extractBattleTags(t,n,d),this.extractDraft(s,c,n);let{playerIDMap:f}=$(i,d,c);this.processScoreEvents(i,f,d);for(let[b,m]of Object.entries(d)){m.win&&(c.winner=m.team,c.winningPlayers.push(b));let h=c.teams[m.team.toString()];h&&(h.level=Math.max(h.level,m.gameStats.Level||0),h.takedowns+=m.gameStats.Takedowns||0,h.ids.push(b))}return W(c,d),{status:1,match:c,players:d}}catch(t){return console.error("ReplayAnalyzer Error:",t),{status:-2,error:String(t)}}}static emptyTeam(){return{level:0,takedowns:0,ids:[],names:[],heroes:[],tags:[],stats:{mercCaptures:0,mercUptime:0,mercUptimePercent:0,structures:{},KDA:0,PPK:0,timeTo10:0,totals:{DamageTaken:0,CreepDamage:0,Healing:0,HeroDamage:0,MinionDamage:0,SelfHealing:0,SiegeDamage:0,ProtectionGivenToAllies:0,TeamfightDamageTaken:0,TeamfightHealingDone:0,TeamfightHeroDamage:0,TimeCCdEnemyHeroes:0,TimeRootingEnemyHeroes:0,TimeSpentDead:0,TimeStunningEnemyHeroes:0,TimeSilencingEnemyHeroes:0,avgTimeSpentDead:0,timeDeadPct:0},levelAdvTime:0,maxLevelAdv:0,avgLevelAdv:0,levelAdvPct:0,uptime:[],uptimeHistogram:{},wipes:0,avgHeroesAlive:0,aces:0,timeWithHeroAdv:0,pctWithHeroAdv:0,passiveXPRate:0,passiveXPDiff:0,passiveXPGain:0}}}static extractBattleTags(e,t,n){let i=e.extractFile("replay.server.battlelobby");if(i)try{let s=new RegExp("([\\p{L}\\d]{3,24}#\\d{4,10})[z\xD8]?","gu"),a=i.toString("utf8").match(s);if(!a)return;let o=0;for(let l of t.m_playerList){if(!l?.m_toon)continue;let u=S(l.m_name);for(;o<a.length;){let d=a[o].split("#"),f=d[0],b=d[1].replace(/[zØ]/g,"");if(o++,f===u){let m=l.m_toon,h=`${m.m_region}-${S(m.m_programId)}-${m.m_realm}-${m.m_id}`;n[h]&&(n[h].tag=parseInt(b,10)||0);break}}}}catch(s){console.error("BattleTag regex error:",s)}}static extractDraft(e,t,n){if(e)try{let i=e.m_syncLobbyState;if(!i)return;let s=i.m_lobbyState;if(!s)return;let a=[],o=[];for(let c of n.m_playerList){if(!c?.m_toon)continue;let d=S(c.m_hero);c.m_teamId===0?a.push(d):c.m_teamId===1&&o.push(d)}if(t.picks={0:a,1:o,first:0},typeof s.m_gameMode=="number"&&(t.mode=s.m_gameMode),typeof s.m_gameType=="number"&&(t.type=s.m_gameType),!s.m_slots)return;if(s.m_pickedMapTag!==void 0){let c=s.m_firstPickTeam??0;t.picks.first=c}}catch{}}static processScoreEvents(e,t,n){for(let i of e){if(i._event!=="NNet.Replay.Tracker.SScoreResultEvent")continue;let s=i.m_instanceList;for(let a of s){let o=S(a.m_name),l=a.m_values,u=o.startsWith("EndOfMatchAward"),c=0;for(let d of l)if(d&&d.length>0&&d[0]!==void 0){let f=c+1,b=t[f];if(b&&n[b]){let m=typeof d[0]=="object"&&d[0]!==null&&"m_value"in d[0]?d[0].m_value:d[0];m!=null&&(u?m===1&&n[b].awards.push(o):n[b].gameStats[o]=m)}c++}else d&&d.length===0&&c++}}}static fileTimeToDate(e){return new Date(Number(e)/1e4-116444736e5)}};export{O as ReplayAnalyzer,R as ReplayParser};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astefanski/storm-parser",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "Storm Parser is a tool for parsing Heroes of the Storm replays.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",