package de.lighti; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; import com.valve.dota2.Demo.CDemoClassInfo; import com.valve.dota2.Demo.CDemoClassInfo.class_t; import com.valve.dota2.Demo.CDemoFileHeader; import com.valve.dota2.Demo.CDemoFileInfo; import com.valve.dota2.Demo.CDemoFullPacket; import com.valve.dota2.Demo.CDemoPacket; import com.valve.dota2.Demo.CDemoSendTables; import com.valve.dota2.Demo.CGameInfo; import com.valve.dota2.Demo.CGameInfo.CDotaGameInfo; import com.valve.dota2.Demo.CGameInfo.CDotaGameInfo.CPlayerInfo; import com.valve.dota2.Demo.EDemoCommands; import com.valve.dota2.Netmessages.NET_Messages; import com.valve.dota2.Netmessages.SVC_Messages; import de.lighti.model.state.ParseState; import de.lighti.model.state.StateUtils.SP_Type; import de.lighti.model.state.StateUtils.SendProp; import de.lighti.model.state.StateUtils.SendTable; import de.lighti.packet.SVCMessageHandler; import de.lighti.util.Utils; /** * Facade class for reading Dota2 replay files. It encapsulate the complete functionality of the library. * Users should add their own listeners to the listeners structure and call loadFile. * @author Tobias Mahlmann * */ public class DotaPlay { public interface ProgressListener { void bytesRemaining( int position ); } /** * The current parsing state. */ private static ParseState state; private static List<GameEventListener> listeners = new ArrayList<GameEventListener>(); private static int tick; private final static Logger LOGGER = Logger.getLogger( DotaPlay.class.getName() ); public static void addListener( GameEventListener l ) { listeners.add( l ); } public static List<GameEventListener> getListeners() { return listeners; } public static ParseState getState() { return state; } public static int getTick() { return tick; } public static long getTickMs() { return state.getTickAsMs( tick ); } private static void handleClassInfo( CDemoClassInfo info ) { if (state == null) { throw new IllegalStateException( "DEM_ClassInfo but no state." ); } for (int i = 0; i < info.getClassesCount(); ++i) { final class_t clazz = info.getClasses( i ); state.createClass( clazz.getClassId(), clazz.getTableName(), clazz.getNetworkName() ); } for (final SendTable table : state.getSendTables()) { for (int i = 0; i < table.getProps().size(); ++i) { final SendProp prop = table.getProps().get( i ); prop.setInTable( table ); if (prop.getType() == SP_Type.SP_Array) { if (i == 0) { throw new IllegalStateException( "Array prop " + prop.getName() + " is at index zero." ); } prop.setArrayProp( table.getProps().get( i - 1 ) ); } } } LOGGER.info( "Received " + info.getClassesCount() + " classes." ); state.compileSendTables(); } private static void handleDemoCommand( EDemoCommands type, byte[] message, int tick ) throws IOException { switch (type) { case DEM_FileHeader: final CDemoFileHeader header = CDemoFileHeader.parseFrom( message ); handleHeader( header ); break; case DEM_Stop: handleStop(); break; case DEM_FullPacket: final CDemoFullPacket full = CDemoFullPacket.parseFrom( message ); state.clearEntities(); handlePacket( full.getPacket() ); break; case DEM_Packet: case DEM_SignonPacket: final CDemoPacket p = CDemoPacket.parseFrom( message ); handlePacket( p ); break; case DEM_ClassInfo: final CDemoClassInfo info = CDemoClassInfo.parseFrom( message ); handleClassInfo( info ); break; case DEM_SendTables: final CDemoSendTables tables = CDemoSendTables.parseFrom( message ); SVCMessageHandler.handleSendTables( tables, state ); break; case DEM_FileInfo: final CDemoFileInfo finfo = CDemoFileInfo.parseFrom( message ); handleFileInfo( finfo ); break; case DEM_StringTables: case DEM_ConsoleCmd: case DEM_CustomData: case DEM_CustomDataCallbacks: case DEM_Error: case DEM_IsCompressed: case DEM_Max: case DEM_SyncTick: case DEM_UserCmd: default: LOGGER.info( "Skipping paket of type " + type + " in tick " + tick ); break; } } private static void handleFileInfo( CDemoFileInfo finfo ) { final CGameInfo info = finfo.getGameInfo(); final CDotaGameInfo dInfo = info.getDota(); for (final CPlayerInfo p : dInfo.getPlayerInfoList()) { state.setPlayerInfo( p.getPlayerName(), p.getHeroName() ); } } private static void handleHeader( CDemoFileHeader header ) { LOGGER.info( "Server name: " + header.getServerName() ); LOGGER.info( "Server client name: " + header.getClientName() ); } private static void handleNetMessage( int cmd, int size, ByteBuffer buffer ) { final NET_Messages msg = NET_Messages.valueOf( cmd ); Logger.getLogger( DotaPlay.class.getName() ).finest( msg.name() ); final byte[] data = new byte[size]; buffer.get( data ); switch (msg) { case net_Disconnect: case net_File: case net_NOP: case net_SetConVar: case net_SignonState: case net_SplitScreenUser: case net_StringCmd: case net_Tick: break; } } private static void handlePacket( CDemoPacket p ) throws IOException { final ByteBuffer buffer = p.getData().asReadOnlyByteBuffer(); while (buffer.hasRemaining()) { final int cmd = Utils.readVarInt( buffer ); final int size = Utils.readVarInt( buffer ); if (SVC_Messages.valueOf( cmd ) != null) { SVCMessageHandler.handleSVCMessage( cmd, size, buffer ); } else if (NET_Messages.valueOf( cmd ) != null) { handleNetMessage( cmd, size, buffer ); } else { throw new IllegalStateException( "CDemo Packet with unknown command " + cmd + " and size " + size ); } } } private static void handleStop() { LOGGER.info( "Demo ended after " + getTickMs() + " ms" ); } public static void loadFile( String path ) { loadFile( path, (ProgressListener[]) null ); } public static void loadFile( String path, ProgressListener... pl ) { setState( null ); final DemoFile file = new DemoFile(); try { file.open( path ); while (!file.isDone()) { final int[] pTick = new int[1]; final boolean[] compressed = new boolean[1]; final EDemoCommands type = file.readMessageType( pTick, compressed ); setTick( pTick[0] ); Logger.getLogger( DotaPlay.class.getName() ).fine( "Tick: " + pTick[0] ); final byte[] message = file.readMessage( compressed[0] ); handleDemoCommand( type, message, pTick[0] ); if (pl != null) { for (final ProgressListener p : pl) { p.bytesRemaining( file.bytesRemaining() ); } } } file.close(); for (final GameEventListener l : listeners) { l.parseComplete( getTickMs(), getState() ); } } catch (final IOException e) { Logger.getLogger( DotaPlay.class.getName() ).severe( e.getLocalizedMessage() ); e.printStackTrace(); } } public static void removeListener( GameEventListener l ) { listeners.remove( l ); } public static void setState( ParseState state ) { DotaPlay.state = state; } public static void setTick( int i ) { tick = i; } }