README
¶
cfb-dynasty
Go library and CLI for reading EA Sports College Football dynasty save files on PC and exporting structured league data as JSON.
Status: feature-complete for read-only export. Against CFB 27 PC dynasty saves (with a matching schema bundle such as C27_468_2.gz), the tool covers the main dynasty surfaces: season/teams/rosters, schedule and per-game stats, recruiting, coaches, injuries, depth charts, awards, and the full record book. Remaining gaps are narrow (see Known limitations); writing or editing saves is intentionally out of scope.
Install
go install github.com/leaguelines/cfb-dynasty/cmd/cfb-dynasty@latest
Or add the library to your project:
go get github.com/leaguelines/cfb-dynasty/dynasty
What exports today
| Area | Coverage |
|---|---|
| Season / teams | Year, week, phase; schools with conference, W–L, poll ranks |
| Rosters | Active players with ratings, archetypes, skill-group caps |
| Games | Schedule, scores, team box scores, attributed player lines (offense / defense / special teams) |
| Season stats | Player and team season totals |
| Recruiting | Board + player attributes; pursuit state, NIL, visits, top school |
| Staff / roster mgmt | Coaches, injuries, depth charts, leaving / graduation pipeline |
| History | Player awards, league awards, conference champions, full record book (league / conference / team × career / season / game) |
Stable Team.TeamIndex IDs are used throughout for joins (see Team IDs).
Schema bundles
Table decoding requires a gzip-compressed JSON schema bundle derived from the game install. Each bundle lists thousands of table definitions (field names, types, enums) used by the FranTk-style save format.
File format and naming
Bundles use the same JSON layout as madden-franchise .gz schemas:
{
"meta": { "major": 468, "minor": 2, "gameYear": 27 },
"schemas": [ ... ],
"schemaMap": { ... }
}
Place one or more files in a directory and pass it to --schema-dir. Recognized filenames:
| Pattern | Example | Meaning |
|---|---|---|
C{year}_{major}_{minor}.gz |
C27_468_2.gz |
CFB, game year 27, bundle major 468, minor 2 |
{major}_{minor}.gz |
468_2.gz |
Major/minor only (game year optional) |
M{year}_{major}_{minor}.gz |
M27_468_2.gz |
Madden-style prefix also accepted |
inspect prints the schema version embedded in your save and which bundle was loaded:
cfb-dynasty inspect -schema-dir ./schemas /path/to/Dynasty1
# schema: major=809 minor=1 ← from save header
# loaded schema: major=468 minor=2 ... ← picked bundle
The save header version and the bundle meta version use different numbering (for example save 809.1 vs bundle 468.2). When several bundles are present, the loader picks the closest major/minor match; with only one file in the directory, that file is used. Prefer the newest CFB 27 bundle you have — older majors (for example 441.0) mis-decode team and roster fields.
How to obtain a bundle
You need a legal copy of the game on PC. Schema data lives inside the install assets; it is not shipped with this repository.
1. Extract raw schema assets (Frosty)
The Madden modding workflow applies to CFB as well:
- Install Frosty Editor and point it at your College Football 27 install.
- Open Legacy Explorer and search for franchise / FranTk schema assets (Madden uses
franchise-schemas.ftxundercommon → franchise; CFB paths are still being mapped — see internal RE notes). - Export the schema
.ftx/.xmlfiles to a folder on disk.
2. Convert to gzip JSON
The CLI expects the evaluated .gz bundle, not raw .ftx. The usual path is the madden-franchise schema generator (same engine family):
- Use
madden-franchiseFranchiseSchema/schemaGeneratorto load the extracted.ftxand emit a.gzJSON bundle, or - Use tooling from the Madden Franchise Editor ecosystem (
schemaSearchServicecan pull schemas from CAS/LZ4 game assets).
3. Name and verify
Rename the output to match your game year and bundle meta (for example C27_468_2.gz), then confirm decoding works:
cfb-dynasty inspect -schema-dir ./schemas /path/to/Dynasty1
cfb-dynasty export -schema-dir ./schemas --teams /path/to/Dynasty1 | head
If you already have a working bundle (like C27_468_2.gz from local RE), you can use it directly — no re-extraction needed until EA ships a patch that changes table layouts.
Should schema bundles be committed to git?
No — do not commit them to a public repository.
| Concern | Notes |
|---|---|
| Copyright | Bundles are derived from EA game assets (table/field names, types, enums). Redistributing them is likely outside EA's terms, even though the files are not executable game content. |
| Size | ~3 MB compressed, ~30 MB inflated — poor fit for git history. |
| Patch churn | EA title updates can change schema major/minor; bundles go stale quickly. |
Recommended approach: keep bundles in a local data/ or schemas/ directory (gitignored), document extraction steps (this section), and let each developer generate or copy their own from an owned game install. Integration tests already skip when a local schema bundle / test save is absent.
For private teams, a shared drive or internal artifact bucket is fine; just avoid publishing the files in the open-source repo or release tarballs.
CLI
Inspect a save
cfb-dynasty inspect /path/to/Dynasty1
cfb-dynasty inspect -schema-dir ./schemas /path/to/Dynasty1
cfb-dynasty inspect -json /path/to/Dynasty1
Shows compression, format, size, SHA-256, and table marker counts without full parsing.
Export to JSON
# Full export (all sections)
cfb-dynasty export -schema-dir ./schemas /path/to/Dynasty1 -o dynasty.json
# Selective export — only the listed sections are included
cfb-dynasty export -schema-dir ./schemas --teams --rosters /path/to/Dynasty1
cfb-dynasty export -schema-dir ./schemas --games --no-game-stats /path/to/Dynasty1
Export sections
| Flag | JSON fields | Contents |
|---|---|---|
| (default) | all below | Everything when no section flags are set |
--season |
season |
Current year, week, phase |
--teams |
teams |
Schools, records, poll ranks |
--rosters |
rosters |
Active rosters with player ratings |
--games |
games |
Schedule, scores, optional per-game stats |
--recruits |
recruits |
Recruiting board + nested player attributes |
--recruiting |
recruiting |
Pursuit state, NIL, visits, top school interest |
--season-stats |
seasonPlayerStats, seasonTeamStats |
Season stat totals |
--coaches |
coaches |
Staff, contracts, career records |
--leaving-players |
leavingPlayers |
Graduation / exit pipeline |
--injuries |
injuries |
Active injuries |
--depth-charts |
depthCharts |
Depth chart slots by team |
--history |
playerAwards, leagueAwards, conferenceChampions, recordBook |
Awards and the full stat record book |
Additional flags:
--no-game-stats— omit team/player stat lines from game exports-o path— write JSON to a file (stdout if omitted)--pretty=false— compact JSON
Run cfb-dynasty -h or cfb-dynasty export -h for full usage.
Team IDs
Exported teamId / teams[].id / player.teamIndex values are the game's
stable Team.TeamIndex IDs (Akron = 1, Alabama = 2, … Sacramento State = 137),
not the Team table row order. Newer programs keep high TeamIndex values while
sitting earlier in the table (for example Appalachian State is row 3 with id
125), so row numbers must not be used as join keys. Air Force is id 0. FCS
placeholder slots (TeamIndex 255) are omitted.
Example: recruits with jq
cfb-dynasty export -schema-dir ./schemas --recruits /path/to/Dynasty1 | \
jq '.recruits[] | select(.nationalRank != null and .player != null) |
{rank: .nationalRank, name: "\(.player.firstName) \(.player.lastName)",
position: .player.position, archetype: .player.archetypeLabel, overall: .player.overall,
skillCaps: .player.skillGroupCaps, skillCapTotal: .player.skillGroupCapTotal}'
Skill group caps
Every player (including a recruit's linked player) exports its six skill-group
caps read straight from the save:
skillGroupCaps— the six positional caps (SkillGroupCap1..6).skillGroupCapTotal— their sum (a strong proxy for a recruit's ceiling / star tier).
The caps are exported as opaque, positional values — the array is ordered by
SkillGroupCap1..6 as stored in the save. The game buckets these into six
position-specific skill groups, but the group names are not present in the
dynasty save (they live in the tuning FTC, which is not yet extractable for
CFB 27). Rather than guess at labels, the export intentionally leaves the slots
unnamed until definitive names are available. Note also that only the per-group
cap is known — the individual ratings inside each group are tuning-driven and
not in the save.
Per-game player stats
When game stats are included (--games, on by default), each game carries a
playerGameStats list. Each entry is one player's line for that game:
playerId— the player's row index (join torostersfor the full record).player— a lightweight identity (firstName,lastName,position,jersey,teamIndex) so lines are readable without a join.offense/defense— the stat line(s); a player with both merges into one entry.specialTeams— kick/punt return line (attempts, yards, longest, TDs) when the player returned kicks or punts that game.
The game-stat rows themselves store no player reference — ownership lives on the
Player side via a GameStats[] array store that points at each player's rows.
The exporter inverts those arrays to attribute every stat line, and rows are
bucketed into games by their direct SeasonGame record index (stale references
to other seasons are dropped).
Kick/punt returns live in separate KPReturnStats tables (the box-score
TeamStats table has no special-teams TD field, so team totals cover yardage
only). The same GameStats[] / SeasonStats[] array stores link those rows back
to their players, and the specialTeams block appears on both per-game
(playerGameStats) and per-season (seasonPlayerStats) lines.
Record book
--history exports the complete stat record book as a flat recordBook list.
The game keeps a record book for three scopes — the whole FBS (league),
each conference, and every team — and three periods (career, season,
game). Each entry carries:
scope—league,conference, orteam.scopeName— the conference or team the board belongs to (empty for league).period—career,season, orgame.statType,statValue,rank— the record and its rank within the board.firstName,lastName,position,teamName,calendarYear— the holder.
League boards are ranked top-N per category; conference and team boards store a
single holder (rank 1) per category. Positions come straight from the record
row when present and are otherwise inferred from the stat category (the game
leaves lower league ranks at the schema default).
# Every team's career passing-yards record holder
cfb-dynasty export -schema-dir ./schemas --history /path/to/Dynasty1 | \
jq '.recordBook[] | select(.scope=="team" and .period=="career" and .statType=="PassYards") |
{team: .scopeName, holder: "\(.firstName) \(.lastName)", yards: .statValue}'
Library
package main
import (
"fmt"
"log"
"github.com/leaguelines/cfb-dynasty/dynasty"
)
func main() {
settings := dynasty.DefaultSettings()
settings.SchemaDir = "/path/to/schemas"
settings.AutoParse = true
file, err := dynasty.Open("/path/to/Dynasty1", &settings)
if err != nil {
log.Fatal(err)
}
info, err := file.Inspect()
if err != nil {
log.Fatal(err)
}
fmt.Printf("size=%d compressed=%v format=%q\n", info.Size, info.Compressed, info.Format)
// Parse is called automatically when AutoParse is true.
// Otherwise: if err := file.Parse(); err != nil { ... }
export, err := file.ExportWithOptions(dynasty.ExportOptions{
Sections: dynasty.ExportSections{
Teams: true,
Rosters: true,
Games: true,
},
})
if err != nil {
log.Fatal(err)
}
data, err := export.ToJSON()
if err != nil {
log.Fatal(err)
}
fmt.Printf("exported %d bytes\n", len(data))
}
Lower-level access to parsed tables:
team, ok := file.PrimaryTableByName("Team")
if ok {
_ = team.ReadRecords()
for _, row := range team.Records {
_ = row.Get("LongName")
}
}
Package layout
| Package | Purpose |
|---|---|
dynasty |
Public API — open saves, parse tables, export data |
internal/binary |
Low-level byte scanning helpers |
internal/bitview |
Bitfield read helpers for record decoding |
internal/compress |
Decompression (zlib) |
cmd/cfb-dynasty |
Command-line tool |
Expected save location (PC)
%USERPROFILE%\Documents\College Football 27\Saves\
Dynasty saves are typically named like Dynasty1.
Known limitations
- Schema required — export and record decoding need a matching
C27_*_*.gzbundle (not shipped here). - Read-only — no save writing or editing.
- Recruiting depth — each prospect’s
topSchoolexports; the full multi-school interest list is not expanded yet. - Skill group names — caps export as ordered
skillGroupCaps[6]; UI bucket names live in game tuning assets, not the save. - Record-book team names — league ranks below #1 often omit a stored team name in the save (the exporter does not invent one).
- Unplayed saves — game and season player stats may be empty until games are simmed.
- Large exports — full exports with
--recruitsand--rosterscan be tens of MB; use section flags to trim output.
Goals
- Reliable read-only parsing of local PC dynasty saves.
- Export the full set of league surfaces listed above as stable JSON for apps, bots, and pipelines.
- Stay headless-friendly: copy save → parse → JSON → sync elsewhere.
Non-goals
- Console save extraction
- Writing, repairing, or live-editing save files
- Redistributing EA schema bundles or other game assets
Related projects
| Project | Notes |
|---|---|
| madden-franchise | Reference parser for Madden FranTk-style table DBs |
| Madden Franchise Editor | Desktop editor built on madden-franchise |
CFB shares the broad EA engine family with Madden, but dynasty saves are a distinct format with their own schema bundles.
Development
go build ./...
go test ./...
go run ./cmd/cfb-dynasty export -schema-dir ./schemas --help
Place test saves and schema bundles in a local data/ directory. Dynasty saves may contain personal league data; schema bundles are game-derived assets — neither should be committed (see Schema bundles).
Contributing
Issues and PRs welcome. Useful contributions include schema version coverage after EA patches, filling the remaining limitations above, better docs/examples, and regression tests against real saves.
License
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
cfb-dynasty
command
|
|
|
Package dynasty reads EA Sports College Football dynasty save files and exposes parsed league data for export.
|
Package dynasty reads EA Sports College Football dynasty save files and exposes parsed league data for export. |
|
internal
|
|
|
binary
Package binary provides low-level helpers for scanning dynasty save bytes.
|
Package binary provides low-level helpers for scanning dynasty save bytes. |
|
bitview
Package bitview reads big-endian bit fields from FranTk table records.
|
Package bitview reads big-endian bit fields from FranTk table records. |
|
compress
Package compress unpacks dynasty save containers.
|
Package compress unpacks dynasty save containers. |