@astefanski/storm-parser 0.0.1 → 0.0.2

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.js CHANGED
@@ -1,2 +1,3 @@
1
- "use strict";var $=Object.create;var P=Object.defineProperty;var C=Object.getOwnPropertyDescriptor;var j=Object.getOwnPropertyNames;var q=Object.getPrototypeOf,G=Object.prototype.hasOwnProperty;var V=(i,e)=>{for(var t in e)P(i,t,{get:e[t],enumerable:!0})},N=(i,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of j(e))!G.call(i,n)&&n!==t&&P(i,n,{get:()=>e[n],enumerable:!(r=C(e,n))||r.enumerable});return i};var M=(i,e,t)=>(t=i!=null?$(q(i)):{},N(e||!i||!i.__esModule?P(t,"default",{value:i,enumerable:!0}):t,i)),X=i=>N(P({},"__esModule",{value:!0}),i);var ne={};V(ne,{ReplayAnalyzer:()=>L,ReplayParser:()=>k});module.exports=X(ne);var F=M(require("fs")),S=M(require("zlib")),J=require("seek-bzip"),Q=512,K=65536,W=16777216,Y=67108864,Z=2147483648,ee={TABLE_OFFSET:0,HASH_A:1,HASH_B:2,TABLE:3};function te(){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 z=te(),R=class{file;header;hashTable;blockTable;files;constructor(e,t=!0){if(Buffer.isBuffer(e)?this.file=e:this.file=F.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&Z))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&W)s.flags&Q&&(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&Y&&(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&Q&&(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=(z[(ee[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+z[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"}},g=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 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}},v=class{_buffer;_typeinfos;constructor(e,t){this._buffer=new T(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()}};var E=M(require("fs")),w=M(require("path"));function re(i){let e=i;for(;e!==w.default.dirname(e);){if(E.default.existsSync(w.default.join(e,"package.json")))return e;e=w.default.dirname(e)}throw new Error("@astefanski/storm-parser: Could not find package root (no package.json found)")}function A(){let i;try{i=__dirname}catch{i=process.cwd()}let e=re(i),t=w.default.join(e,"protocols");if(E.default.existsSync(t))return t;throw new Error("@astefanski/storm-parser: Protocols directory not found at "+t+". Did postinstall run? Try: npx tsx node_modules/@astefanski/storm-parser/scripts/postinstall.ts")}var U=new Map;function H(i){if(U.has(i))return U.get(i);let e=A(),t=w.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 O(){let i=A();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 k=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 v(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=O(),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 v(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 v(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 k(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],x=p[1].replace(/[zØ]/g,"");if(f++,D===d){let y=`${b.m_toon.m_region}-${b.m_toon.m_programId}-${b.m_toon.m_realm}-${b.m_toon.m_id}`;l[y]&&(l[y].tag=x);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,x=c[D];if(x&&l[x]){let y=typeof p[0]=="object"&&p[0]!==null&&"m_value"in p[0]?p[0].m_value:p[0];y!=null&&(l[x].gameStats[b]=y)}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 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});
package/dist/index.mjs CHANGED
@@ -1,2 +1,3 @@
1
- var F=(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 N from"fs";import*as E from"zlib";var A=F("seek-bzip"),D=512,O=65536,$=16777216,C=67108864,j=2147483648,q={TABLE_OFFSET:0,HASH_A:1,HASH_B:2,TABLE:3};function G(){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 U=G(),P=class{file;header;hashTable;blockTable;files;constructor(e,t=!0){if(Buffer.isBuffer(e)?this.file=e:this.file=N.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 E.inflateSync(c.subarray(1));if(_===16)return A.decode(c.subarray(1));try{return E.inflateSync(c.subarray(1))}catch{return E.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&O)throw new Error("Encryption is not supported");if(s.flags&$)s.flags&D&&(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&C&&(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&D&&(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=(U[(q[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+U[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"}},x=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 S=class{_buffer;_typeinfos;constructor(e,t){this._buffer=new x(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}},v=class{_buffer;_typeinfos;constructor(e,t){this._buffer=new x(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 B from"path";function V(i){let e=i;for(;e!==B.dirname(e);){if(T.existsSync(B.join(e,"package.json")))return e;e=B.dirname(e)}throw new Error("@astefanski/storm-parser: Could not find package root (no package.json found)")}function Q(){let i;try{i=__dirname}catch{i=process.cwd()}let e=V(i),t=B.join(e,"protocols");if(T.existsSync(t))return t;throw new Error("@astefanski/storm-parser: Protocols directory not found at "+t+". Did postinstall run? Try: npx tsx node_modules/@astefanski/storm-parser/scripts/postinstall.ts")}var H=new Map;function M(i){if(H.has(i))return H.get(i);let e=Q(),t=B.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 z(){let i=Q();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 v(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=z(),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 v(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 S(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 v(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 S(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 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],w=p[1].replace(/[zØ]/g,"");if(f++,R===d){let y=`${b.m_toon.m_region}-${b.m_toon.m_programId}-${b.m_toon.m_realm}-${b.m_toon.m_id}`;l[y]&&(l[y].tag=w);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,w=c[R];if(w&&l[w]){let y=typeof p[0]=="object"&&p[0]!==null&&"m_value"in p[0]?p[0].m_value:p[0];y!=null&&(l[w].gameStats[b]=y)}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{L as ReplayAnalyzer,I as ReplayParser};
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};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astefanski/storm-parser",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
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",