HearthstoneLogWatcher::HearthstoneLogWatcher( QObject *parent, const QString& path )
  : QObject( parent ),
    mPath( path ),
    mLastSeekPos( 0 )
{
  // We used QFileSystemWatcher before but it fails on windows
  // Windows File Notification seems to be very tricky with files
  // which are not explicitly flushed (which is the case for the Hearthstone Log)
  // QFileSystemWatcher fails, manual implemention with FindFirstChangeNotification
  // fails. So instead of putting too much work into a file-system depending solution
  // just use a low-overhead polling strategy
  //
  // Start/stop timer when hearthstone starts/stops
  // Otherwise we produce a plethora of idle wake ups
  connect( &mTimer, &QTimer::timeout, this, &HearthstoneLogWatcher::CheckForLogChanges );

  connect( Hearthstone::Instance(), &Hearthstone::GameStarted, this, &HearthstoneLogWatcher::HandleGameStart );
  connect( Hearthstone::Instance(), &Hearthstone::GameStopped, this, &HearthstoneLogWatcher::HandleGameStop );

  DBG( "Watch log %s",  qt2cstr( mPath ) );

  QFile file( mPath );
  if( file.exists() ) {
    mLastSeekPos = file.size();
  }
}
xcb_window_t LinuxWindowCapture::FindWindow( const QString& instanceName, const QString& windowClass ) {
  xcb_window_t winID = XCB_WINDOW_NONE;
  QList< xcb_window_t > windows = listWindowsRecursive( QX11Info::appRootWindow() );
  xcb_connection_t* xcbConn = QX11Info::connection();

  foreach( const xcb_window_t& win, windows ) {

    xcb_icccm_get_wm_class_reply_t wmNameR;
    xcb_get_property_cookie_t wmClassC = xcb_icccm_get_wm_class( xcbConn, win );

    if ( xcb_icccm_get_wm_class_reply( xcbConn, wmClassC, &wmNameR, nullptr ) ) {
      if( !qstricmp( wmNameR.class_name, qt2cstr( windowClass ) ) ||
          !qstricmp( wmNameR.instance_name, qt2cstr ( instanceName ) ) ) {
        winID = win;
        break;
      }
    }
  }
Exemple #3
0
void Hearthstone::EnableLogging() {
  QString path = LogConfigPath();
  QFile file( path );

  bool logModified = false;

  // Read file contents
  QString contents;
  if( file.exists() ) {
    file.open( QIODevice::ReadOnly | QIODevice::Text );
    QTextStream in( &file );
    contents = in.readAll();
    file.close();
  }


  // Check what modules we have to activate
  QStringList modulesToActivate;
  for( int i = 0; i < NUM_LOG_MODULES; i++ ) {
    const char *moduleName = LOG_MODULE_NAMES[ i ];
    QString moduleLine = QString( "[%1]" ).arg( moduleName ) ;
    if( !contents.contains( moduleLine ) ) {
      contents += "\n";
      contents += moduleLine + "\n";
      contents += "LogLevel=1\n";
      contents += "FilePrinting=true\n";

      DBG( "Activate module %s", moduleName );
      logModified = true;
    }
  }

  if( contents.contains( "ConsolePrinting=true" ) ) {
    contents.replace( "ConsolePrinting=true", "FilePrinting=true" );

    DBG( "ConsolePrinting replaced by FilePrinting" );
    logModified = true;
  }

  // Finally write updated log.config
  if( logModified ) {
    DBG( "Log modified. Write new version" );

    if( !file.open( QIODevice::WriteOnly | QIODevice::Text ) ) {
      ERR( "Couldn't create file %s", qt2cstr( path ) );
    } else {
      QTextStream out( &file );
      out << contents;
    }
  }

  // Notify about restart if game is running
  SetRestartRequired( Running() && logModified );
}
Exemple #4
0
void Overlay::UpdateHistoryFor( Player player, const ::CardHistoryList& list ) {
  QMap< QString, QVariantMap > entries;

  for( const CardHistoryItem& it : list ) {
    const QString& cardId = it.cardId;

    if( cardId.isEmpty() ) {
      continue;
    }

    if( !mCardDB.Contains( cardId ) ) {
      DBG( "Card %s not found", qt2cstr( cardId ) );
      continue;
    }

    if( mCardDB.Type( cardId ) == "hero" ) {
      continue;
    }

    if( it.player != player ) {
      continue;
    }

    QVariantMap& entry = entries[ cardId ];
    entry[ "count" ] = entry.value( "count", 0 ).toInt() + 1;
    entry[ "mana" ] = mCardDB.Mana( cardId );
    entry[ "name" ] = mCardDB.Name( cardId );
  }

  OverlayHistoryList* ref;
  if( player == PLAYER_SELF ) {
    ref = &mPlayerHistory;
  } else {
    ref = &mOpponentHistory;
  }

  *ref = entries.values();

  qSort( ref->begin(), ref->end(), []( const QVariantMap& a, const QVariantMap& b ) {
    if( a["mana"].toInt() == b["mana"].toInt() ) {
      return a["name"].toString() < b["name"].toString();
    } else {
      return a["mana"].toInt() < b["mana"].toInt();
    }
  });
}
int Trackobot::Run() {
  if( IsAlreadyRunning() )
    return 1;

  LOG( "Launch v%s on %s", VERSION, qt2cstr( QDate::currentDate().toString( Qt::ISODate ) ) );

  SetupUpdater();

  CreateUI();

  Initialize();

  SetupLogging();

  int exitCode = mApp.exec();

  // Tear down
  LOG( "Shutdown" );

  return exitCode;
}
Exemple #6
0
QString Hearthstone::ReadAgentAttribute( const char *attributeName ) const {
#ifdef Q_OS_MAC
  QString path = "/Users/Shared/Battle.net/Agent/agent.db";
#elif defined Q_OS_WIN
  wchar_t buffer[ MAX_PATH ];
  SHGetSpecialFolderPathW( NULL, buffer, CSIDL_COMMON_APPDATA, FALSE );
  QString programData = QString::fromWCharArray( buffer );
  QString path = programData + "\\Battle.net\\Agent\\agent.db";
#endif

  QFile file( path );
  if( !file.open( QIODevice::ReadOnly | QIODevice::Text ) ) {
    ERR( "Couldn't open %s (%d)", qt2cstr( path ), file.error() );
    return "";
  }

  QString contents = file.readAll();
  QJsonDocument doc = QJsonDocument::fromJson( contents.toUtf8() );
  QJsonObject root = doc.object();

  QJsonObject hs = root["/game/hs_beta"].toObject()["resource"].toObject()["game"].toObject();
  return hs[ QString( attributeName ) ].toString();
}
void HearthstoneLogTracker::HandleLogLine( const QString& line ) {
  if( line.trimmed().isEmpty() || line.startsWith( "(Filename:" )  ) {
    return;
  }

  // CardPlayed / CardReturned / PlayerDied
  static QRegExp regex( "ProcessChanges.*\\[.*id=(\\d+).*cardId=(\\w+|).*\\].*zone from (.*) ->\\s?(.*)" );
  if( regex.indexIn(line) != -1 ) {
    QStringList captures = regex.capturedTexts();
    int id = captures[1].toInt();
    QString cardId = captures[2];
    QString from = captures[3];
    QString to = captures[4];

    bool draw = from.contains( "DECK" ) && to.contains( "HAND" );
    bool mulligan = from.contains( "HAND" ) && to.contains( "DECK" );

    // Discarded cards by playing Soulfire, Doomguard etc.
    bool discard = from.contains( "HAND" ) && to.contains( "GRAVEYARD" );

    if( !draw && !mulligan && !discard ) {
      if( from.contains( "FRIENDLY HAND" ) ) {
        CardPlayed( PLAYER_SELF, cardId.toStdString(), id );
      } else if( from.contains( "OPPOSING HAND" ) ) {
        CardPlayed( PLAYER_OPPONENT, cardId.toStdString(), id );
      } else if( from.contains( "OPPOSING SECRET" ) && to.contains( "OPPOSING GRAVEYARD" ) ) {
        SecretResolved( PLAYER_OPPONENT, cardId.toStdString(), id );
      }
    }

    if( from.contains( "FRIENDLY PLAY" ) && to.contains( "FRIENDLY HAND" ) ) {
      CardReturned( PLAYER_SELF, cardId.toStdString() );
    }

    DBG( "Card %s from %s -> %s. (draw: %d, mulligan %d, discard %d) [%d]", qt2cstr( cardId ), qt2cstr( from ), qt2cstr( to ), draw, mulligan, discard, id );
  }

  // Outcome
  static QRegExp regexOutcome( "name=(victory|defeat)_screen_start" );
  if( regexOutcome.indexIn(line) != -1 ) {
    QStringList captures = regexOutcome.capturedTexts();
    QString outcome = captures[1];

    if( outcome == "victory" ) {
      emit HandleOutcome( OUTCOME_VICTORY );
    } else if( outcome == "defeat" ) {
      emit HandleOutcome( OUTCOME_DEFEAT );
    }
    emit HandleMatchEnd( mCardHistoryList );
    Reset();
  }

  // Coin
  static QRegExp regexCoin( "ProcessChanges.*zonePos=5.*zone from  -> (.*)" );  // unique because from is nothing -> " "
  if( regexCoin.indexIn(line) != -1 ) {
    QStringList captures = regexCoin.capturedTexts();
    QString to = captures[1];

    if( to.contains( "FRIENDLY HAND" ) ) {
      // I go second because I get the coin
      emit HandleOrder( ORDER_SECOND );
    } else if( to.contains( "OPPOSING HAND" ) ) {
      // Opponent got coin, so I go first
      emit HandleOrder( ORDER_FIRST );
    }
  }

  // Turn Info
  static QRegExp regexTurn( "change=powerTask.*tag=NEXT_STEP value=MAIN_ACTION" );
  if( regexTurn.indexIn(line) != -1 ) {
    mTurnCounter++;

    emit HandleTurn( mTurnCounter );

    // reset hero power usage on turn change
    mHeroPowerUsed = false;
  }

  // Hero Power
  static QRegExp regexHeroPowerEquip( "player=(\\d+).*-> FRIENDLY PLAY \\(Hero Power\\)" );
  if( regexHeroPowerEquip.indexIn(line) != -1 ) {
    QStringList captures = regexHeroPowerEquip.capturedTexts();
    QString playerId = captures[1];

    mHeroPlayerId = playerId.toInt();
    DBG( "Hero Power Equip -> My Player Id: %d", mHeroPlayerId );
  }

  static QRegExp regexHeroPower( "PowerProcessor\\.DoTaskListForCard.*cardId=(\\w+).*player=(\\d+)" );
  if( regexHeroPower.indexIn(line) != -1 ) {
    QStringList captures = regexHeroPower.capturedTexts();
    QString cardId = captures[1];
    int playerId = captures[2].toInt();
    Player player = ( playerId == mHeroPlayerId ) ? PLAYER_SELF : PLAYER_OPPONENT;

    bool isHeroPower = false;
    for( int i = 0; i < NUM_HERO_POWER_CARDS; i++ ) {
      if( cardId == HERO_POWER_CARD_IDS[ i ] ) {
        isHeroPower = true;
        break;
      }
    }

    // Power log line is emitted multiple times
    // Make sure we only account for first occurrence
    // Plus line is emitted when match starts, so ignore turn 0
    if( isHeroPower && !mHeroPowerUsed && CurrentTurn() > 0 ) {
      CardPlayed( player, cardId.toStdString() );
      mHeroPowerUsed = true;
    }
  }

  // Hero Equip
  static QRegExp regexHeroEquip( "cardId=(\\w+).*-> (\\w+) PLAY \\(Hero\\)" );
  if( regexHeroEquip.indexIn(line) != -1 ) {
    QStringList captures = regexHeroEquip.capturedTexts();
    QString cardId = captures[1];
    QString type = captures[2];

    // This log line can be emitted when hero swaps (Lord Jaraxxus)
    // So make sure we only account for the "initial" playable heroes
    Class hero = CLASS_UNKNOWN;
    for( int i = 0; i < NUM_HEROES; i++ ) {
      // startsWith instead of exact match to support
      // the new reasonably priced hero skins
      // (e.g. HERO_01a instead of HERO_01)
      if( cardId.startsWith( HERO_IDS[ i ] ) ) {
        hero = ( Class )i;
      }
    }

    // Set solo mode when encountering naxxramas/blackrock mountain heroes
    if( hero == CLASS_UNKNOWN ) {
      if( cardId.startsWith("NAX") || cardId.startsWith("BRM") ) {
        HandleGameMode( MODE_SOLO_ADVENTURES );
      }
    }

    if( hero != CLASS_UNKNOWN ) {
      if( type == "FRIENDLY" ) {
        emit HandleMatchStart();
        emit HandleOwnClass( hero );
      } else {
        emit HandleOpponentClass( hero );
      }
    }
  }

  // Game Mode
  // Practice, Casual/Ranked, ScreenForge
  static QRegExp regexMode( "---(\\w+)---" );
  if( regexMode.indexIn(line) != -1 ) {
    QStringList captures = regexMode.capturedTexts();
    QString screen = captures[1];

    if( screen == "RegisterScreenPractice" ) {
      HandleGameMode( MODE_SOLO_ADVENTURES );
    } else if( screen == "RegisterScreenTourneys") {
      HandleGameMode( MODE_CASUAL ); // or ranked resp.
    } else if( screen == "RegisterScreenForge" ) {
      HandleGameMode( MODE_ARENA );
    } else if( screen == "RegisterScreenFriendly" ) {
      HandleGameMode( MODE_FRIENDLY );
    }
  }

  // Tavern Brawl
  static QRegExp regexTavernBrawl( "SAVE --> NetCacheTavernBrawlRecord" );
  if( regexTavernBrawl.indexIn(line) != -1 ) {
    HandleGameMode( MODE_TAVERN_BRAWL );
  }

  // Rank
  // Rank events via log are unreliable

  // Legend
  // Emitted at the end of the game twice, make sure we capture only the first time
  static QRegExp regexLegend( "legend rank (\\d+)" );
  if( !mLegendTracked && regexLegend.indexIn(line) != -1 ) {
    QStringList captures = regexLegend.capturedTexts();
    int legend = captures[1].toInt();
    if( legend > 0 ) {
      mLegendTracked = true;
      HandleLegend( legend );
    }
  }

  // Casual/Ranked distinction
  static QRegExp regexRanked( "name=rank_window" );
  if( regexRanked.indexIn(line) != -1 ) {
    HandleGameMode( MODE_RANKED );
  }

  // flag current GAME as spectated
  static QRegExp regexBeginSpectating( "Start Spectator Game" );
  if( regexBeginSpectating.indexIn(line) != -1 ) {
    DBG( "Begin spectator game" );
    emit HandleSpectating( true );
  }

  // disable spectating flag if we leave the spectator MODE
  static QRegExp regexEndSpectating( "End Spectator Mode" );
  if( regexEndSpectating.indexIn(line) != -1 ) {
    DBG( "End spectator mode" );
    emit HandleSpectating( false );
  }
}