PythonでSteamSDKを利用する

# SteamWorksPy
まず挙げられる方法としてはラッパを使用することだと思います。
こんなのがあります

https://github.com/philippj/SteamworksPy/tree/master
マッチメイキング機能から、基本的なsteamの情報を取得する機能までがある。
使い方はこの動画がとても参考になりました。
https://www.youtube.com/watch?v=Ka9LIGYm_b0

ただしp2p方式の通信機能までは備わっていなかったので(もしかしたらあるのかもしれない)
今後の拡張性を考慮して、C++から拡張しPythonで使えるようにする。

# VS
VSを使う
まずはプロジェクトのプロパティーから、SteamSDKを読み込む
https://partner.steamgames.com/downloads/list
SteamSDKはこのサイトからダウンロードできる
1. VC++ディレクトリー -> ライブラリディレクトリー -> C:\steamworks_sdk\redistributable_bin\win64\steam_api64.lib
... ライブラリの読
2. C/C++ -> 追加のインクルードディレクトリ -> C:\steamworks_sdk\public
これでおそらく、cppファイルで#include <steam/steam_api.h>が読み込めるようになった。
3. C/C++ -> SDLチェック -> いいえ
これはC言語の低レベルな関数を使うためにチェックを外す必要がある...らしい？(これがTrueだとstring.hが読み込めなかった)
4. リンカー -> 追加の依存ファイル -> steam_api64.lib
5. リンカー -> 全般 -> 使いのライブラリディレクトリー -> C:\steamworks_sdk\redistributable_bin\win64
これでデバッグ時に依存ファイルをチェックしてくれる。
憶測なので確実な情報じゃないよ

また実行ファイルと同じディレクトリーにsteam_api64.dllをセットしておく必要がある。
スクリプトも例を乗せておく

``` C++
#include <iostream>
#include <chrono>
#include <thread>
#include <steam/steam_api.h>
#include <vector>
#include <string>

static std::vector<uint64_t> friendLobbyIDs;
static std::vector<uint64_t> publicLobbyIDs;

class LobbyCreatedCallback {
public:
    STEAM_CALLBACK(LobbyCreatedCallback, OnLobbyCreated, LobbyCreated_t);
    CSteamID createdLobbyID;
    bool success = false;
};

void LobbyCreatedCallback::OnLobbyCreated(LobbyCreated_t* pCallback) {
    if (pCallback->m_eResult == k_EResultOK) {
        createdLobbyID.SetFromUint64(pCallback->m_ulSteamIDLobby);
        success = true;
    }
    else {
        success = false;
    }
}

class LobbyChatUpdateCallback {
public:
    STEAM_CALLBACK(LobbyChatUpdateCallback, OnLobbyChatUpdate, LobbyChatUpdate_t);
    uint64 lastJoinedSteamID = 0;
    uint64 lastLeftSteamID = 0;
    uint64 lastLobbyID = 0;
    bool joinedUpdated = false;
    bool leftUpdated = false;
};

void LobbyChatUpdateCallback::OnLobbyChatUpdate(LobbyChatUpdate_t* pCallback) {
    lastLobbyID = pCallback->m_ulSteamIDLobby;

    // **参加したプレイヤー**
    if (pCallback->m_rgfChatMemberStateChange & k_EChatMemberStateChangeEntered) {
        lastJoinedSteamID = pCallback->m_ulSteamIDUserChanged;
        joinedUpdated = true;
        std::cout << "Player " << lastJoinedSteamID << " joined lobby " << lastLobbyID << std::endl;
    }

    // **退室したプレイヤー**
    if (pCallback->m_rgfChatMemberStateChange & k_EChatMemberStateChangeLeft) {
        lastLeftSteamID = pCallback->m_ulSteamIDUserChanged;
        leftUpdated = true;
        std::cout << "Player " << lastLeftSteamID << " left lobby " << lastLobbyID << std::endl;
    }
}

class LobbyListCallback {
public:
    STEAM_CALLBACK(LobbyListCallback, OnLobbyMatchList, LobbyMatchList_t);
    int lobbyCount = 0;
    bool updated = false;
};

void LobbyListCallback::OnLobbyMatchList(LobbyMatchList_t* pCallback) {
    lobbyCount = pCallback->m_nLobbiesMatching;
    updated = true;
}
class LobbyEnterCallback {
public:
    STEAM_CALLBACK(LobbyEnterCallback, OnLobbyEnter, LobbyEnter_t);
    bool lobbyEnterSuccess = false;
    CSteamID joinedLobbyID;
    EChatRoomEnterResponse response;
};

void LobbyEnterCallback::OnLobbyEnter(LobbyEnter_t* pCallback) {
    joinedLobbyID = pCallback->m_ulSteamIDLobby;
    response = static_cast<EChatRoomEnterResponse>(pCallback->m_EChatRoomEnterResponse);

    if (response == k_EChatRoomEnterResponseSuccess) {
        lobbyEnterSuccess = true;
        std::cout << "[LobbyEnter] Successfully joined lobby "
            << joinedLobbyID.ConvertToUint64() << std::endl;
    }
    else {
        lobbyEnterSuccess = false;
        std::cout << "[LobbyEnter] Failed to enter lobby (Error code: "
            << static_cast<int>(response) << ")" << std::endl;
    }
}
LobbyEnterCallback       lobbyEnterCallback;

// **グローバル変数としてコールバックオブジェクトを登録**
LobbyChatUpdateCallback chatUpdateCallback;


// ロビー検索用のコールバックオブジェクト
LobbyListCallback lobbyListCallback;


// ロビー作成用のコールバックオブジェクト
LobbyCreatedCallback lobbyCallback;

extern "C" {
    // **P2P セッションリクエストを受け入れる**
    __declspec(dllexport) bool AcceptP2PSession(uint64 steamID) {
        return SteamNetworking()->AcceptP2PSessionWithUser(CSteamID(steamID));
    }
    __declspec(dllexport) void RunSteamCallbacks() {
        SteamAPI_RunCallbacks();
    }
    // Steam API の初期化
    __declspec(dllexport) bool InitializeSteam() {
        return SteamAPI_Init();
    }

    // Steam ID の取得
    __declspec(dllexport) uint64 GetSteamID() {
        if (SteamUser()) {
            CSteamID steamID = SteamUser()->GetSteamID();
            return steamID.ConvertToUint64();
        }
        return 0;
    }

    // P2P メッセージ送信
    __declspec(dllexport) bool SendP2PMessage(uint64 steamID, const char* message) {
        CSteamID targetID = CSteamID(steamID);
        SteamNetworking()->AllowP2PPacketRelay(true);

        return SteamNetworking()->SendP2PPacket(targetID, message, strlen(message) + 1, k_EP2PSendReliable);
    }

    // P2P メッセージ受信
    __declspec(dllexport) bool ReceiveP2PMessage(char* buffer, int bufferSize, uint64* senderSteamID) {
        uint32 msgSize;
        CSteamID senderID;
        SteamNetworking()->AllowP2PPacketRelay(true);
        if (SteamNetworking()->IsP2PPacketAvailable(&msgSize)) {
            SteamNetworking()->ReadP2PPacket(buffer, bufferSize, &msgSize, &senderID);
            *senderSteamID = senderID.ConvertToUint64();
            return true;
        }
        return false;
    }
    // **PING メッセージを送信**
    __declspec(dllexport) bool SendPing(uint64 steamID) {
        const char* pingMessage = "PING";
        return SteamNetworking()->SendP2PPacket(CSteamID(steamID), pingMessage, 4, k_EP2PSendReliable);
    }
    // **フレンドのロビーを検索→一覧を保存
    __declspec(dllexport) int RefreshFriendLobbies()
    {
        friendLobbyIDs.clear();        // 前回結果をクリア

        // 1) ロビーリストをリクエスト
        lobbyListCallback.updated = false;
        SteamMatchmaking()->AddRequestLobbyListResultCountFilter(100);
        SteamMatchmaking()->RequestLobbyList();

        // 2) コールバック完了まで待機
        for (int i = 0; i < 200; i++) {
            SteamAPI_RunCallbacks();
            if (lobbyListCallback.updated) {
                break;
            }
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }

        // 3) 結果を取得してフレンドチェック
        int count = lobbyListCallback.lobbyCount;
        for (int i = 0; i < count; i++) {
            CSteamID lobbyID = SteamMatchmaking()->GetLobbyByIndex(i);
            CSteamID ownerID = SteamMatchmaking()->GetLobbyOwner(lobbyID);

            // ownerが0なら無効
            if (ownerID.ConvertToUint64() == 0) continue;

            // オーナーがフレンドなら保存
            if (SteamFriends()->HasFriend(ownerID, k_EFriendFlagImmediate)) {
                friendLobbyIDs.push_back(lobbyID.ConvertToUint64());
            }
        }

        // 4) 見つかった数を返す
        return static_cast<int>(friendLobbyIDs.size());
    }
	//** 公開lobbyを検索して一覧を保存**
    __declspec(dllexport) int RefreshPublicLobbies()
    {
        publicLobbyIDs.clear();

        lobbyListCallback.updated = false;
        SteamMatchmaking()->AddRequestLobbyListResultCountFilter(300);
        SteamMatchmaking()->RequestLobbyList();

        for (int i = 0; i < 200; i++) {
            SteamAPI_RunCallbacks();
            if (lobbyListCallback.updated) {
                break;
            }
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }

        int count = lobbyListCallback.lobbyCount;
        for (int i = 0; i < count; i++) {
            CSteamID lobbyID = SteamMatchmaking()->GetLobbyByIndex(i);
            CSteamID ownerID = SteamMatchmaking()->GetLobbyOwner(lobbyID);

            // オーナー不在なら無視
            if (ownerID.ConvertToUint64() == 0) continue;

            // ここで必要があれば、さらにロビー人数などをチェックして除外もOK
            //int numMembers = SteamMatchmaking()->GetNumLobbyMembers(lobbyID);

            publicLobbyIDs.push_back(lobbyID.ConvertToUint64());
        }
        return static_cast<int>(publicLobbyIDs.size());
    }
    // **一般lobby一覧を取り出す
    __declspec(dllexport) uint64 GetPublicLobbyIDByIndex(int index)
    {
        if (index < 0 || index >= (int)publicLobbyIDs.size()) {
            return 0;
        }
        return publicLobbyIDs[index];
    }
	// **フレンドlobby一覧を取り出す**
    __declspec(dllexport) uint64 GetFriendLobbyIDByIndex(int index)
    {
        if (index < 0 || index >= (int)friendLobbyIDs.size()) {
            return 0;
        }
        return friendLobbyIDs[index];
    }
    // **指定したオーナーのロビー ID を取得**
    __declspec(dllexport) uint64 FindLobbyByOwner(uint64 ownerID) {
        lobbyListCallback.updated = false;
        SteamMatchmaking()->RequestLobbyList();

        // **コールバックが完了するまで待つ**
        for (int i = 0; i < 100; i++) {
            SteamAPI_RunCallbacks();
            if (lobbyListCallback.updated) {
                break;
            }
            std::this_thread::sleep_for(std::chrono::milliseconds(10)); // 10ミリ秒待機
        }

        // **取得したロビーの数を確認**
        int count = lobbyListCallback.lobbyCount;
        for (int i = 0; i < count; i++) {
            CSteamID lobbyID = SteamMatchmaking()->GetLobbyByIndex(i);
            uint64 lobbyOwnerID = SteamMatchmaking()->GetLobbyOwner(lobbyID).ConvertToUint64();

            if (lobbyOwnerID == ownerID) {
                return lobbyID.ConvertToUint64(); // 該当するロビーの ID を返す
            }
        }

        return 0; // 該当するロビーが見つからなかった
    }

    // **ロビー ID をインデックスから取得**
    __declspec(dllexport) uint64 GetLobbyByIndex(int index) {
        if (index < 0 || index >= SteamMatchmaking()->GetFavoriteGameCount()) {
            return 0; // 無効なインデックス
        }
        return SteamMatchmaking()->GetLobbyByIndex(index).ConvertToUint64();
    }
    // **ロビーのオーナー（ホスト）の Steam ID を取得**
    __declspec(dllexport) uint64 GetLobbyOwner(uint64 lobbyID) {
        CSteamID lobby(lobbyID);
        CSteamID owner = SteamMatchmaking()->GetLobbyOwner(lobby);
        return owner.ConvertToUint64();
    }
    // **ロビーのメンバー数を取得**
    __declspec(dllexport) int GetNumLobbyMembers(uint64 lobbyID) {
        CSteamID lobby(lobbyID);
        return SteamMatchmaking()->GetNumLobbyMembers(lobby);
    }

    // **指定インデックスのロビーメンバーの Steam ID を取得**
    __declspec(dllexport) uint64 GetLobbyMemberByIndex(uint64 lobbyID, int index) {
        CSteamID lobby(lobbyID);
        CSteamID member = SteamMatchmaking()->GetLobbyMemberByIndex(lobby, index);
        return member.ConvertToUint64();
    }


    // **SteamID からプレイヤー名を取得**
    __declspec(dllexport) void GetSteamName(uint64 steamID, char* buffer, int bufferSize) {
        CSteamID playerID(steamID);
        const char* name = SteamFriends()->GetFriendPersonaName(playerID);
        strncpy(buffer, name, bufferSize - 1);
        buffer[bufferSize - 1] = '\0';  // Null 終端
    }



    // **ロビーを作成**
    __declspec(dllexport) uint64 CreateLobby(int lobbyType, int maxPlayers) {
        lobbyCallback.success = false;
        SteamMatchmaking()->CreateLobby((ELobbyType)lobbyType, maxPlayers);

        // 一定時間待機してロビーが作成されたかチェック
        for (int i = 0; i < 100; i++) {
            SteamAPI_RunCallbacks(); // コールバックを処理
            if (lobbyCallback.success) {
                return lobbyCallback.createdLobbyID.ConvertToUint64();
            }
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }

        return 0; // 作成失敗
    }
    // ロビーに参加
    __declspec(dllexport) bool JoinLobby(uint64 lobbyID) {
        lobbyEnterCallback.lobbyEnterSuccess = false;
        SteamMatchmaking()->JoinLobby(CSteamID(lobbyID));

        // 参加完了を待機 (OnLobbyEnter コールバック)
        for (int i = 0; i < 100; i++) {
            SteamAPI_RunCallbacks();
            if (lobbyEnterCallback.lobbyEnterSuccess) {
                // 成功
                return true;
            }
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
        // 失敗
        return false;
    }


    // **ロビー参加を検出**
    __declspec(dllexport) bool CheckLobbyJoin(uint64* joinedSteamID, uint64* lobbyID) {
        SteamAPI_RunCallbacks();  // **Steam コールバックを処理**
        if (chatUpdateCallback.joinedUpdated) {
            *joinedSteamID = chatUpdateCallback.lastJoinedSteamID;
            *lobbyID = chatUpdateCallback.lastLobbyID;
            chatUpdateCallback.joinedUpdated = false;
            return true;
        }
        return false;
    }

    // **ロビー退室を検出**
    __declspec(dllexport) bool CheckLobbyLeave(uint64* leftSteamID, uint64* lobbyID) {
        SteamAPI_RunCallbacks();  // **Steam コールバックを処理**
        if (chatUpdateCallback.leftUpdated) {
            *leftSteamID = chatUpdateCallback.lastLeftSteamID;
            *lobbyID = chatUpdateCallback.lastLobbyID;
            chatUpdateCallback.leftUpdated = false;
            return true;
        }
        return false;
    }

    // ========================
    // Rich Presence 関連
    // ========================

    // 自分が所属するロビーIDを Rich Presence にセット
    __declspec(dllexport) void SetLobbyRichPresence(uint64 lobbyID) {
        // key="steam_player_group" にロビーIDを文字列で入れるのが慣例
        char lobbyStr[32];
        sprintf(lobbyStr, "%llu", (unsigned long long)lobbyID);

        // ※ "steam_player_group" と "connect" の2つに同じ値をセットする手法が多い
        //    "connect" を見て参加しようとするタイトルもあるため
        SteamFriends()->SetRichPresence("steam_player_group", lobbyStr);
        SteamFriends()->SetRichPresence("connect", lobbyStr);

        // 任意でステータス文言をセットしてもOK
        SteamFriends()->SetRichPresence("status", "In a lobby!");
        std::cout << "[SetLobbyRichPresence] lobbyID=" << lobbyStr << std::endl;
    }

    // Rich Presence をクリア (ゲーム終了時やロビー退出時など)
    __declspec(dllexport) void ClearRichPresence() {
        SteamFriends()->ClearRichPresence();
        std::cout << "[ClearRichPresence] All presence data cleared.\n";
    }

    // フレンドの Rich Presence から "steam_player_group" を取得
    // (フレンドがロビーIDをセットしていれば、それが返ってくる)
    __declspec(dllexport) uint64 GetFriendLobbyRichPresence(uint64 friendSteamID) {
        CSteamID friendID(friendSteamID);
        const char* presence = SteamFriends()->GetFriendRichPresence(friendID, "steam_player_group");
        if (presence && presence[0] != '\0') {
            return std::stoull(presence);
        }
        return 0; // 見つからない場合は 0
    }
    __declspec(dllexport) int RefreshFriendLobbiesRichPresence()
    {
        friendLobbyIDs.clear();

        // (1) 今のフレンド数を取得
        int friendCount = SteamFriends()->GetFriendCount(k_EFriendFlagImmediate);
        if (friendCount < 1) {
            std::cout << "[RefreshFriendLobbiesRichPresence] No friends found.\n";
            return 0;
        }

        // (2) 各フレンドを走査し、Rich Presenceの "steam_player_group" をチェック
        for (int i = 0; i < friendCount; i++) {
            CSteamID friendID = SteamFriends()->GetFriendByIndex(i, k_EFriendFlagImmediate);
            if (!friendID.IsValid()) continue;

            // ここでキー"steam_player_group"の値を取得 (ロビーIDが入っている可能性がある)
            const char* presence = SteamFriends()->GetFriendRichPresence(friendID, "steam_player_group");
            if (presence && presence[0] != '\0') {
                // 文字列 -> uint64 に変換
                uint64_t lobbyID = std::strtoull(presence, nullptr, 10);
                if (lobbyID != 0) {
                    friendLobbyIDs.push_back(lobbyID);
                    std::cout << "[RefreshFriendLobbiesRichPresence] Friend "
                        << friendID.ConvertToUint64()
                        << " has lobbyID=" << lobbyID << "\n";
                }
            }
        }

        // フレンドが参加中のロビー数を返す
        return static_cast<int>(friendLobbyIDs.size());
    }
    __declspec(dllexport) uint64_t GetFriendLobbyIDByIndex_RichPresence(int index)
    {
        if (index < 0 || index >= static_cast<int>(friendLobbyIDs.size())) {
            return 0; // 無効な場合は0
        }
        return friendLobbyIDs[index];
    }

}

```
仮のスクリプトですので、整理する必要があります。
私はこんな感じで分けてみました。
source - SteamLobby.cpp SteamNetworking.cpp SteamCallbacks.cpp
header - SteamLobby.h 以下同じ
```
cl.exe /LD /I "C:\steamworks_sdk\public" SteamCallbacks.cpp SteamNetworking.cpp SteamLobby.cpp "C:\steamworks_sdk\redistributable_bin\win64\steam_api64.lib" /FeSteamNetworkingWrapper.dll
```
Pythonではこんな感じで読み込みます。
``` python
import ctypes
import os

# DLL をロード
dll_path = os.path.abspath("./SteamNetworkingWrapper.dll")
steam_dll = ctypes.CDLL(dll_path)
# SteamCallbacks を実行
run_steam_callbacks = steam_dll.RunSteamCallbacks
run_steam_callbacks.restype = None
```
コールバックしかないんですけど全部こんな感じでかけます。
フレンドのルーム検索は二つあって、私のおすすめは、RichPresenceです