Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

show deck hash even when its invalid #4595

Merged
merged 2 commits into from
Apr 10, 2023
Merged

Conversation

skwerlman
Copy link
Contributor

Related Ticket(s)

  • N/A

Short roundup of the initial problem

@Lachee is working to add the ability to search Chickatrice replays by deck, but decks with extra zones (such as a maybeboard) get the string 'INVALID' instead of a unique hash string. This prevent associating a deck with a replay because replays only include the deck hashes, not the full decks.

What will change with this Pull Request?

  • Decks with extra zones will still be marked as invalid, but now the calculated deck hash is appended so decks can be associated with replays.

Screenshots

Before:
2022-03-17-013149_112x42_scrot

After:
2022-03-17-012408_188x51_scrot

@ebbit1q
Copy link
Member

ebbit1q commented Mar 17, 2022

it's probably a bad idea to use the hash for this, on the other hand cockatrice doesn't really support extra zones, you have to go out of your way to edit the deck file to add them.

@ebbit1q
Copy link
Member

ebbit1q commented Aug 10, 2022

"replays only include the deck hashes" this is not true, a replay contains every single message sent in the game including each time they upload a deck, this event includes the full decklist as xml in a protobuf message

@Lachee
Copy link

Lachee commented Aug 11, 2022

"replays only include the deck hashes" this is not true, a replay contains every single message sent in the game including each time they upload a deck, this event includes the full decklist as xml in a protobuf message

What event is this specifically. Its been a while now, but last time i tried implementing this I wasnt able to determine the decklist, only cards as they get drawn.

@ebbit1q
Copy link
Member

ebbit1q commented Aug 11, 2022

I created a little tool to read (and edit) replays ebbit1q/crow@4302717

I always assumed the replays contained the same information as you see when using the client with message debug output but I was wrong, a replay only contains the events you'd see as if you are a spectator, a lot of information is lost, including the deck list.

here is a small replay converted to human readable json:

{
  "replay_id": "45",
  "game_info": {
    "room_id": 0,
    "game_id": 44,
    "description": "",
    "with_password": false,
    "max_players": 1,
    "creator_info": {
      "name": "test",
      "user_level": 31,
      "accountage_secs": "18361686",
      "privlevel": "NONE"
    },
    "only_buddies": false,
    "only_registered": false,
    "spectators_allowed": true,
    "spectators_need_password": false,
    "spectators_can_chat": false,
    "spectators_omniscient": false,
    "player_count": 0,
    "spectators_count": 0,
    "started": false,
    "start_time": 1660205497
  },
  "event_list": [
    {
      "event_list": [
        {
          "[Event_Join.ext]": {
            "player_properties": {
              "player_id": 0,
              "user_info": {
                "name": "test",
                "user_level": 31,
                "avatar_bmp": "",
                "accountage_secs": "18361686",
                "privlevel": "NONE"
              },
              "spectator": false,
              "conceded": false,
              "ready_start": false,
              "ping_seconds": 0,
              "sideboard_locked": true,
              "judge": false
            }
          }
        }
      ],
      "seconds_elapsed": 0
    },
    {
      "event_list": [
        {
          "player_id": 0,
          "[Event_GameHostChanged.ext]": {}
        }
      ],
      "seconds_elapsed": 0
    },
    {
      "event_list": [
        {
          "player_id": 0,
          "[Event_PlayerPropertiesChanged.ext]": {
            "player_properties": {
              "deck_hash": "nhed40dn",
              "sideboard_locked": true
            }
          }
        }
      ],
      "context": {
        "[Context_DeckSelect.ext]": {
          "deck_hash": "nhed40dn",
          "sideboard_size": 0
        }
      },
      "seconds_elapsed": 9
    },
    {
      "event_list": [
        {
          "player_id": 0,
          "[Event_PlayerPropertiesChanged.ext]": {
            "player_properties": {
              "deck_hash": "nhed40dn",
              "sideboard_locked": true
            }
          }
        }
      ],
      "context": {
        "[Context_DeckSelect.ext]": {
          "deck_hash": "nhed40dn",
          "sideboard_size": 0
        }
      },
      "seconds_elapsed": 29
    },
    {
      "event_list": [
        {
          "player_id": 0,
          "[Event_PlayerPropertiesChanged.ext]": {
            "player_properties": {
              "ready_start": true
            }
          }
        }
      ],
      "context": {
        "[Context_ReadyStart.ext]": {}
      },
      "seconds_elapsed": 35
    },
    {
      "event_list": [
        {
          "[Event_GameStateChanged.ext]": {
            "player_list": [
              {
                "properties": {
                  "player_id": 0,
                  "spectator": false,
                  "conceded": false,
                  "ready_start": false,
                  "deck_hash": "nhed40dn",
                  "ping_seconds": 0,
                  "sideboard_locked": true,
                  "judge": false
                },
                "zone_list": [
                  {
                    "name": "deck",
                    "type": "HiddenZone",
                    "with_coords": false,
                    "card_count": 60,
                    "always_reveal_top_card": false,
                    "always_look_at_top_card": false
                  },
                  {
                    "name": "grave",
                    "type": "PublicZone",
                    "with_coords": false,
                    "card_count": 0,
                    "always_reveal_top_card": false,
                    "always_look_at_top_card": false
                  },
                  {
                    "name": "hand",
                    "type": "PrivateZone",
                    "with_coords": false,
                    "card_count": 0,
                    "always_reveal_top_card": false,
                    "always_look_at_top_card": false
                  },
                  {
                    "name": "rfg",
                    "type": "PublicZone",
                    "with_coords": false,
                    "card_count": 0,
                    "always_reveal_top_card": false,
                    "always_look_at_top_card": false
                  },
                  {
                    "name": "sb",
                    "type": "HiddenZone",
                    "with_coords": false,
                    "card_count": 0,
                    "always_reveal_top_card": false,
                    "always_look_at_top_card": false
                  },
                  {
                    "name": "stack",
                    "type": "PublicZone",
                    "with_coords": false,
                    "card_count": 0,
                    "always_reveal_top_card": false,
                    "always_look_at_top_card": false
                  },
                  {
                    "name": "table",
                    "type": "PublicZone",
                    "with_coords": true,
                    "card_count": 0,
                    "always_reveal_top_card": false,
                    "always_look_at_top_card": false
                  }
                ],
                "counter_list": [
                  {
                    "id": 0,
                    "name": "life",
                    "counter_color": {
                      "r": 255,
                      "g": 255,
                      "b": 255
                    },
                    "radius": 25,
                    "count": 20
                  },
                  {
                    "id": 1,
                    "name": "w",
                    "counter_color": {
                      "r": 255,
                      "g": 255,
                      "b": 150
                    },
                    "radius": 20,
                    "count": 0
                  },
                  {
                    "id": 2,
                    "name": "u",
                    "counter_color": {
                      "r": 150,
                      "g": 150,
                      "b": 255
                    },
                    "radius": 20,
                    "count": 0
                  },
                  {
                    "id": 3,
                    "name": "b",
                    "counter_color": {
                      "r": 150,
                      "g": 150,
                      "b": 150
                    },
                    "radius": 20,
                    "count": 0
                  },
                  {
                    "id": 4,
                    "name": "r",
                    "counter_color": {
                      "r": 250,
                      "g": 150,
                      "b": 150
                    },
                    "radius": 20,
                    "count": 0
                  },
                  {
                    "id": 5,
                    "name": "g",
                    "counter_color": {
                      "r": 150,
                      "g": 255,
                      "b": 150
                    },
                    "radius": 20,
                    "count": 0
                  },
                  {
                    "id": 6,
                    "name": "x",
                    "counter_color": {
                      "r": 255,
                      "g": 255,
                      "b": 255
                    },
                    "radius": 20,
                    "count": 0
                  },
                  {
                    "id": 7,
                    "name": "storm",
                    "counter_color": {
                      "r": 255,
                      "g": 150,
                      "b": 30
                    },
                    "radius": 20,
                    "count": 0
                  }
                ]
              }
            ],
            "game_started": true,
            "active_player_id": 0,
            "active_phase": 0,
            "seconds_elapsed": 35
          }
        }
      ],
      "seconds_elapsed": 35
    },
    {
      "event_list": [
        {
          "[Event_SetActivePlayer.ext]": {
            "active_player_id": 0
          }
        }
      ],
      "seconds_elapsed": 35
    },
    {
      "event_list": [
        {
          "[Event_SetActivePhase.ext]": {
            "phase": 0
          }
        }
      ],
      "seconds_elapsed": 35
    },
    {
      "event_list": [
        {
          "player_id": 0,
          "[Event_Leave.ext]": {
            "reason": "USER_LEFT"
          }
        }
      ],
      "seconds_elapsed": 43
    },
    {
      "event_list": [
        {
          "[Event_GameClosed.ext]": {}
        }
      ],
      "seconds_elapsed": 43
    }
  ],
  "duration_seconds": 43
}

as you can see each time the deck is uploaded users are indeed only able to get the hash, other information is lost.
the hash appears again when the user starts the game, note that players can start multiple games with different hashes in the same replay.

@ebbit1q
Copy link
Member

ebbit1q commented Aug 12, 2022

I guess using the hashes is at least something, and while people probably shouldn't be adding unused zones to their deck, you can't cheat with them at all so I see no problem there.

@skwerlman
Copy link
Contributor Author

I guess using the hashes is at least something, and while people probably shouldn't be adding unused zones to their deck, you can't cheat with them at all so I see no problem there.

You can cheat with them, in drafts. The hash is short enough that it can be trivially brute-forced by adding bogus data in an extra zone while leaving only totally valid cards in your library and sideboard. This lets you use basically any deck list you want while making it appear like your list is the same one given to you by dr4ft or whatever, and without marking the hash as invalid, theres no way for the other player to tell.

@ebbit1q
Copy link
Member

ebbit1q commented Aug 12, 2022

only the mainboard and sideboard are hashed, everything else is ignored

@ebbit1q
Copy link
Member

ebbit1q commented Aug 12, 2022

actually, adding the full decklist used by the player for the replay might be very useful for those cases where it's the only way to check if someone cheated like that

@ebbit1q
Copy link
Member

ebbit1q commented Aug 20, 2022

I thought of how I'd like to see that implemented and ticketed it #4661 it's out of scope here but would be cool to see

Copy link
Member

@ebbit1q ebbit1q left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this has been up long enough, as noone objects I'm merging this now.

@ebbit1q ebbit1q merged commit 8746239 into Cockatrice:master Apr 10, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants