-
-
Notifications
You must be signed in to change notification settings - Fork 431
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
Conversation
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. |
"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. |
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. |
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. |
only the mainboard and sideboard are hashed, everything else is ignored |
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 |
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 |
There was a problem hiding this 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.
Related Ticket(s)
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?
Screenshots
Before:
After: