44import com .faforever .commons .replay .body .ReplayBodyParser ;
55import com .faforever .commons .replay .body .ReplayBodyToken ;
66import com .faforever .commons .replay .body .ReplayBodyTokenizer ;
7+ import com .faforever .commons .replay .shared .LoadUtils ;
78import com .faforever .commons .replay .shared .LuaData ;
89import com .fasterxml .jackson .databind .ObjectMapper ;
910import com .google .common .annotations .VisibleForTesting ;
1011import com .google .common .io .BaseEncoding ;
11- import com .google .common .io .LittleEndianDataInputStream ;
1212import lombok .Getter ;
1313import lombok .extern .slf4j .Slf4j ;
1414import org .apache .commons .compress .compressors .CompressorException ;
1515import org .apache .commons .compress .compressors .CompressorInputStream ;
1616import org .apache .commons .compress .compressors .CompressorStreamFactory ;
17- import org .apache .commons .compress . utils .IOUtils ;
17+ import org .apache .commons .io .IOUtils ;
1818import org .jetbrains .annotations .NotNull ;
1919
2020import java .io .ByteArrayInputStream ;
2121import java .io .ByteArrayOutputStream ;
2222import java .io .IOException ;
23+ import java .nio .ByteBuffer ;
24+ import java .nio .ByteOrder ;
2325import java .nio .charset .StandardCharsets ;
2426import java .nio .file .Files ;
2527import java .nio .file .Path ;
2628import java .time .Duration ;
2729import java .util .*;
28- import java .util .concurrent .atomic .AtomicInteger ;
2930import java .util .stream .Collectors ;
3031
31-
3232@ SuppressWarnings ("unused" )
3333@ Slf4j
3434public class ReplayDataParser {
@@ -59,7 +59,7 @@ public class ReplayDataParser {
5959 @ Getter
6060 private final List <ModeratorEvent > moderatorEvents = new ArrayList <>();
6161 @ Getter
62- private final Map <Integer , Map < Integer , AtomicInteger >> commandsPerMinuteByPlayer = new HashMap <>() ;
62+ private Map <String , Integer > playerIdsByName ;
6363
6464 private int ticks ;
6565
@@ -80,48 +80,49 @@ public ReplayDataParser(Path path, ObjectMapper objectMapper) throws IOException
8080 }
8181
8282 @ VisibleForTesting
83- static String readString (LittleEndianDataInputStream dataStream ) throws IOException {
84- ByteArrayOutputStream out = new ByteArrayOutputStream ();
85- byte tempByte ;
86- while ((tempByte = dataStream .readByte ()) != 0 ) {
87- out .write (tempByte );
83+ static String readString (ByteBuffer buffer ) {
84+ final int offset = buffer .position ();
85+ while (buffer .get () != 0 ) {
8886 }
89- return out .toString (StandardCharsets .UTF_8 );
87+ final int length = buffer .position () - 1 - offset ;
88+ byte [] stringBytes = new byte [length ];
89+ buffer .get (offset , stringBytes );
90+ return new String (stringBytes , StandardCharsets .UTF_8 );
9091 }
9192
92- private Object parseLua (LittleEndianDataInputStream dataStream ) throws IOException {
93- int type = dataStream . readUnsignedByte ( );
93+ private Object parseLua (ByteBuffer buffer ) {
94+ int type = LoadUtils . getUnsignedByte ( buffer );
9495 switch (type ) {
9596 case LUA_NUMBER :
96- return dataStream . readFloat ();
97+ return buffer . getFloat ();
9798 case LUA_STRING :
98- return readString (dataStream );
99+ return readString (buffer );
99100 case LUA_NIL :
100- dataStream . skipBytes ( 1 );
101+ buffer . get ( );
101102 return null ;
102103 case LUA_BOOL : // bool
103- return dataStream . readUnsignedByte ( ) == 0 ;
104+ return LoadUtils . getUnsignedByte ( buffer ) == 0 ;
104105 case LUA_TABLE_START : // lua
105106 Map <String , Object > result = new HashMap <>();
106- while (peek (dataStream ) != LUA_TABLE_END ) {
107- Object key = parseLua (dataStream );
107+ while (peek (buffer ) != LUA_TABLE_END ) {
108+ Object key = parseLua (buffer );
108109 if (key instanceof Number ) {
109110 key = ((Number ) key ).intValue ();
110111 }
111- result .put (String .valueOf (key ), parseLua (dataStream ));
112- dataStream .mark (1 );
112+ result .put (String .valueOf (key ), parseLua (buffer ));
113+ buffer .mark ();
113114 }
114- dataStream . skipBytes ( 1 );
115+ buffer . get ( );
115116 return result ;
116117 default :
117118 throw new IllegalStateException ("Unexpected data type: " + type );
118119 }
119120 }
120121
121- private int peek (LittleEndianDataInputStream dataStream ) throws IOException {
122- dataStream .mark (1 );
123- int next = dataStream . readUnsignedByte ( );
124- dataStream .reset ();
122+ private int peek (ByteBuffer buffer ) {
123+ buffer .mark ();
124+ int next = LoadUtils . getUnsignedByte ( buffer );
125+ buffer .reset ();
125126 return next ;
126127 }
127128
@@ -164,51 +165,51 @@ private byte[] decompress(byte[] data, @NotNull ReplayMetadata metadata) throws
164165 }
165166
166167 @ SuppressWarnings ("unchecked" )
167- private void parseHeader (LittleEndianDataInputStream dataStream ) throws IOException {
168- replayPatchFieldId = readString (dataStream );
169- String arg13 = readString (dataStream ); // always \r\n
168+ private void parseHeader (ByteBuffer buffer ) {
169+ replayPatchFieldId = readString (buffer );
170+ String arg13 = readString (buffer ); // always \r\n
170171
171- String [] split = readString (dataStream ).split ("\\ r\\ n" );
172+ String [] split = readString (buffer ).split ("\\ r\\ n" );
172173 String replayVersionId = split [0 ];
173174 map = split [1 ];
174- String arg23 = readString ((dataStream )); // always \r\n and some unknown character
175+ String arg23 = readString ((buffer )); // always \r\n and some unknown character
175176
176- int sizeModsInBytes = dataStream . readInt ();
177- mods = (Map <String , Map <String , ?>>) parseLua (dataStream );
177+ int sizeModsInBytes = buffer . getInt ();
178+ mods = (Map <String , Map <String , ?>>) parseLua (buffer );
178179
179- int sizeGameOptionsInBytes = dataStream . readInt ();
180- this .gameOptions = ((Map <String , Object >) parseLua (dataStream )).entrySet ().stream ()
180+ int sizeGameOptionsInBytes = buffer . getInt ();
181+ this .gameOptions = ((Map <String , Object >) parseLua (buffer )).entrySet ().stream ()
181182 .filter (entry -> "Options" .equals (entry .getKey ()))
182183 .flatMap (entry -> ((Map <String , Object >) entry .getValue ()).entrySet ().stream ())
183184 .map (entry -> new GameOption (entry .getKey (), entry .getValue ()))
184185 .collect (Collectors .toList ());
185186
186- int numberOfSources = dataStream . readUnsignedByte ( );
187+ int numberOfSources = LoadUtils . getUnsignedByte ( buffer );
187188
188- Map < String , Object > playerIdsByName = new HashMap <>();
189+ playerIdsByName = new HashMap <>();
189190 for (int i = 0 ; i < numberOfSources ; i ++) {
190- String playerName = readString (dataStream );
191- int playerId = dataStream . readInt ();
191+ String playerName = readString (buffer );
192+ int playerId = buffer . getInt ();
192193 playerIdsByName .put (playerName , playerId );
193194 }
194195
195- boolean cheatsEnabled = dataStream . readUnsignedByte ( ) > 0 ;
196+ boolean cheatsEnabled = LoadUtils . getUnsignedByte ( buffer ) > 0 ;
196197
197- int numberOfArmies = dataStream . readUnsignedByte ( );
198+ int numberOfArmies = LoadUtils . getUnsignedByte ( buffer );
198199 for (int i = 0 ; i < numberOfArmies ; i ++) {
199- int sizePlayerDataInBytes = dataStream . readInt ();
200- Map <String , Object > playerData = (Map <String , Object >) parseLua (dataStream );
201- int playerSource = dataStream . readUnsignedByte ( );
200+ int sizePlayerDataInBytes = buffer . getInt ();
201+ Map <String , Object > playerData = (Map <String , Object >) parseLua (buffer );
202+ int playerSource = LoadUtils . getUnsignedByte ( buffer );
202203
203204 armies .put (playerSource , playerData );
204205 playerData .put ("commands" , new ArrayList <>());
205206
206207 if (playerSource != 255 ) {
207- dataStream . skipBytes ( 1 );
208+ buffer . get ( );
208209 }
209210 }
210211
211- randomSeed = dataStream . readInt ();
212+ randomSeed = buffer . getInt ();
212213 }
213214
214215 private void interpretEvents (List <Event > events ) {
@@ -288,19 +289,13 @@ private void interpretEvents(List<Event> events) {
288289 case Event .IssueCommand (
289290 Event .CommandUnits commandUnits , Event .CommandData commandData
290291 ) -> {
291- commandsPerMinuteByPlayer
292- .computeIfAbsent (player , p -> new HashMap <>())
293- .computeIfAbsent (ticks , t -> new AtomicInteger ())
294- .incrementAndGet ();
292+
295293 }
296294
297295 case Event .IssueFactoryCommand (
298296 Event .CommandUnits commandUnits , Event .CommandData commandData
299297 ) -> {
300- commandsPerMinuteByPlayer
301- .computeIfAbsent (player , p -> new HashMap <>())
302- .computeIfAbsent (ticks , t -> new AtomicInteger ())
303- .incrementAndGet ();
298+
304299 }
305300
306301 case Event .IncreaseCommandCount (int commandId , int delta ) -> {
@@ -364,10 +359,13 @@ private void interpretEvents(List<Event> events) {
364359 }
365360
366361 private void parseGiveResourcesToPlayer (LuaData .Table lua ) {
367- if (lua .value ().containsKey ("Msg" ) && lua .value ().containsKey ("From" ) && lua .value ().containsKey ("Sender" )) {
362+ LuaData msg ;
363+ LuaData from ;
364+ LuaData sender ;
365+ if ((msg = lua .value ().get ("Msg" )) != null && (from = lua .value ().get ("From" )) != null && (sender = lua .value ().get ("Sender" )) != null ) {
368366
369367 // TODO: use the command source (player value) instead of the values from the callback. The values from the callback can be manipulated
370- if (!(lua . value (). get ( "From" ) instanceof LuaData .Number (float luaFromArmy ))) {
368+ if (!(from instanceof LuaData .Number (float luaFromArmy ))) {
371369 return ;
372370 }
373371
@@ -376,11 +374,11 @@ private void parseGiveResourcesToPlayer(LuaData.Table lua) {
376374 return ;
377375 }
378376
379- if (!(lua . value (). get ( "Msg" ) instanceof LuaData .Table (Map <String , LuaData > luaMsg ))) {
377+ if (!(msg instanceof LuaData .Table (Map <String , LuaData > luaMsg ))) {
380378 return ;
381379 }
382380
383- if (!(lua . value (). get ( "Sender" ) instanceof LuaData .String (String luaSender ))) {
381+ if (!(sender instanceof LuaData .String (String luaSender ))) {
384382 return ;
385383 }
386384
@@ -442,12 +440,18 @@ private Duration tickToTime(int tick) {
442440 }
443441
444442 private void parse () throws IOException , CompressorException {
445- readReplayData (path );
446- try (LittleEndianDataInputStream dataStream = new LittleEndianDataInputStream (new ByteArrayInputStream (data ))) {
447- parseHeader (dataStream );
448- tokens = ReplayBodyTokenizer .tokenize (dataStream );
449- }
450- events = ReplayBodyParser .parseTokens (tokens );
451- interpretEvents (events );
443+ readReplayData (path );
444+
445+ final ByteBuffer buffer = ByteBuffer .wrap (data );
446+ buffer .order (ByteOrder .LITTLE_ENDIAN );
447+
448+ parseHeader (buffer );
449+
450+ var rewindPosition = buffer .position ();
451+ tokens = ReplayBodyTokenizer .tokenize (buffer );
452+ buffer .position (rewindPosition );
453+
454+ events = ReplayBodyParser .parseTokens (tokens , buffer );
455+ interpretEvents (events );
452456 }
453457}
0 commit comments