Understanding team structure, participation, and divisions in GZCTF
Teams are groups of users that compete together in CTF games. Each team can participate in multiple games, with separate participation records and scoring for each game.
public class Team{ public int Id { get; set; } public string Name { get; set; } // Max 15 characters public string? Bio { get; set; } // Team description public string? AvatarHash { get; set; } public bool Locked { get; set; } // Prevents modifications}
public class Participation{ public int Id { get; set; } public ParticipationStatus Status { get; set; } public string Token { get; set; } // Unique participation token // Relationships public int GameId { get; set; } public Game Game { get; set; } public int TeamId { get; set; } public Team Team { get; set; } public int? DivisionId { get; set; } public Division? Division { get; set; } // Competition data public HashSet<UserParticipation> Members { get; set; } public HashSet<GameChallenge> Challenges { get; set; } public List<GameInstance> Instances { get; set; } public List<Submission> Submissions { get; set; } public List<FirstSolve> FirstSolves { get; set; } // Blood bonuses earned public LocalFile? Writeup { get; set; }}
The participation token is a cryptographically signed unique identifier used for:
public class Division{ public int Id { get; set; } public int GameId { get; set; } public string Name { get; set; } public string? InviteCode { get; set; } public GamePermission DefaultPermissions { get; set; }}
Divisions use the GamePermission flag system to control access:
Division-Level
Challenge-Level
Permissions that apply to the entire division:
JoinGame: Can register for the game through this division
RankOverall: Appears on the overall scoreboard
RequireReview: Requires admin approval (overrides game setting)
Per-challenge permissions via DivisionChallengeConfig:
ViewChallenge: Can see challenge details
SubmitFlags: Can submit flag answers
GetScore: Receives points for correct flags
GetBlood: Eligible for blood bonuses
AffectDynamicScore: Submissions affect scoring for all teams
public class DivisionChallengeConfig{ public int DivisionId { get; set; } public int ChallengeId { get; set; } public GamePermission Permissions { get; set; } = GamePermission.All;}
Use GamePermission.All (int.MaxValue) to grant all current and future permissions by default.
var officialDivision = new Division{ Name = "Official", InviteCode = "OFFICIAL2024", DefaultPermissions = GamePermission.All // Full access};var observerDivision = new Division{ Name = "Observer", InviteCode = "OBSERVER2024", DefaultPermissions = GamePermission.JoinGame | GamePermission.ViewChallenge | GamePermission.SubmitFlags // No GetScore or RankOverall - can play but not compete};
public class Team{ public int Id { get; set; } public string Name { get; set; } public string? Bio { get; set; } public string? AvatarHash { get; set; } public bool Locked { get; set; } public string InviteToken { get; set; } // Relationships public Guid CaptainId { get; set; } public UserInfo? Captain { get; set; } public HashSet<UserInfo> Members { get; set; } public List<Participation> Participations { get; set; } public HashSet<Game>? Games { get; set; }}
public class Participation{ public int Id { get; set; } public ParticipationStatus Status { get; set; } public string Token { get; set; } public LocalFile? Writeup { get; set; } // Foreign keys public int GameId { get; set; } public Game Game { get; set; } public int TeamId { get; set; } public Team Team { get; set; } public int? DivisionId { get; set; } public Division? Division { get; set; } // Competition data public HashSet<UserParticipation> Members { get; set; } public HashSet<GameChallenge> Challenges { get; set; } public List<GameInstance> Instances { get; set; } public List<Submission> Submissions { get; set; } public List<FirstSolve> FirstSolves { get; set; }}
public partial class Division{ public int Id { get; set; } public int GameId { get; set; } public string Name { get; set; } public string? InviteCode { get; set; } public GamePermission DefaultPermissions { get; set; } // Relationships public Game? Game { get; set; } public HashSet<DivisionChallengeConfig> ChallengeConfigs { get; set; }}