Diving into the FDI Files: Building a PC Calcio 7 Player Editor with Go and Fyne

As a long-time fan of retro football management games, PC Calcio 7 holds a special place in my heart. Recently, I embarked on a personal project to explore its data files, specifically the .fdi files that I suspected contained player information. This exploration led to the development of a dedicated player editor using the Go programming language and the Fyne.io cross-platform UI toolkit. In this article, I'll walk you through the journey of reverse-engineering the file format and building the editor.

Unraveling the Mystery of the .FDI File

My first step was to dive into the raw bytes of an .fdi file. A simple Go program ( see: https://github.com/mcesarini/fdi-analyzer ) allowed me to read and analyze the contents. It quickly became apparent that the file had a structured format. The initial findings revealed:

  • A header block, with the first few bytes clearly stating "DMFIv1.0". This likely identifies the file format version, providing a crucial starting point for understanding the file's structure.
  • Following the header, I observed repeating patterns of what appeared to be offsets and lengths. This hinted at an index or table section, likely pointing to the actual data blocks within the file.

Based on this initial analysis, I hypothesized the .fdi file structure to be something like this:

[Header (Format Version, Table Counts?)]
[Index/Table Section (Pointers to Data Blocks)]
[Data Blocks (Player Information)]

Decoding Player Data in Go

With this structural understanding, the next challenge was to decipher the actual player data. This involved meticulously examining the byte sequences within the data blocks to identify fields like player names, surnames, and other attributes. Through careful analysis, I was able to map the byte encodings used for names and surnames.

var CharMap = map[byte]rune{
    // Row 0
    0x00: 'a', 0x01: '`', 0x02: 'c', 0x03: 'b', 0x04: 'e', 0x05: 'd', 0x06: 'g', 0x07: 'f',
    0x08: 'i', 0x09: 'h', 0x0A: 'k', 0x0B: 'j', 0x0C: 'm', 0x0D: 'l', 0x0E: 'o', 0x0F: 'n',
// Row 1
0x10: 'q', 0x11: 'p', 0x12: 's', 0x13: 'r', 0x14: 'u', 0x15: 't', 0x16: 'w', 0x17: 'v',
0x18: 'y', 0x19: 'x', 0x1A: '{', 0x1B: 'z', 0x1C: '}', 0x1D: '|', 0x1E: ' ', 0x1F: '~',

// Row 2
0x20: 'A', 0x21: '@', 0x22: 'C', 0x23: 'B', 0x24: 'E', 0x25: 'D', 0x26: 'G', 0x27: 'F',
0x28: 'I', 0x29: 'H', 0x2A: 'K', 0x2B: 'J', 0x2C: 'M', 0x2D: 'L', 0x2E: 'O', 0x2F: 'N',

// Row 3
0x30: 'Q', 0x31: 'P', 0x32: 'S', 0x33: 'R', 0x34: 'U', 0x35: 'T', 0x36: 'W', 0x37: 'V',
0x38: 'Y', 0x39: 'X', 0x3A: '[', 0x3B: 'Z', 0x3C: ']', 0x3D: '\\', 0x3E: '_', 0x3F: '^',

// Row 4
0x40: '!', 0x41: ' ', 0x42: '#', 0x43: '"', 0x44: '%', 0x45: '$', 0x46: '\'', 0x47: '&',
0x48: ')', 0x49: '(', 0x4A: '+', 0x4B: '*', 0x4C: '-', 0x4D: ',', 0x4E: '/', 0x4F: '.',

// Row 5
0x50: '1', 0x51: '0', 0x52: '3', 0x53: '2', 0x54: '5', 0x55: '4', 0x56: '7', 0x57: '6',
0x58: '9', 0x59: '8', 0x5A: ';', 0x5B: ':', 0x5C: '=', 0x5D: '<' , 0x5E: '?', 0x5F: '>',

// Row 6: 0x60-0x6F - Control characters represented as runes
// Note: These would typically be handled specially in actual code
0x60: 'S', 0x61: 'N', 0x62: 'E', 0x63: 'S', 0x64: 'E', 0x65: 'E', 0x66: 'B', 0x67: 'A',
0x68: 'H', 0x69: 'B', 0x6A: 'V', 0x6B: 'L', 0x6C: 'C', 0x6D: 'F', 0x6E: 'S', 0x6F: 'S',

// Row 7: 0x70-0x7F - Control characters represented as runes
0x70: 'D', 0x71: 'D', 0x72: 'D', 0x73: 'D', 0x74: 'N', 0x75: 'D', 0x76: 'E', 0x77: 'S',
0x78: 'E', 0x79: 'C', 0x7A: 'E', 0x7B: 'S', 0x7C: 'G', 0x7D: 'F', 0x7E: 'U', 0x7F: 'R',

// Row 8 (Accented characters)
0x80: 'á', 0x81: 'à', 0x82: 'ã', 0x83: 'â', 0x84: 'å', 0x85: 'ä', 0x86: 'ç', 0x87: 'æ',
0x88: 'é', 0x89: 'è', 0x8A: 'ë', 0x8B: 'ê', 0x8C: 'í', 0x8D: 'ì', 0x8E: 'ï', 0x8F: 'î',

// Row 9
0x90: 'ñ', 0x91: 'ð', 0x92: 'ó', 0x93: 'ò', 0x94: 'õ', 0x95: 'ô', 0x96: '÷', 0x97: 'ö',
0x98: 'ù', 0x99: 'ø', 0x9A: 'û', 0x9B: 'ú', 0x9C: 'ý', 0x9D: 'ü', 0x9E: 'ÿ', 0x9F: 'þ',

// Row A
0xA0: 'Á', 0xA1: 'À', 0xA2: 'Ã', 0xA3: 'Â', 0xA4: 'Å', 0xA5: 'Ä', 0xA6: 'Ç', 0xA7: 'Æ',
0xA8: 'É', 0xA9: 'È', 0xAA: 'Ë', 0xAB: 'Ê', 0xAC: 'Í', 0xAD: 'Ì', 0xAE: 'Ï', 0xAF: 'Î',

// Row B
0xB0: 'Ñ', 0xB1: 'Ð', 0xB2: 'Ó', 0xB3: 'Ò', 0xB4: 'Õ', 0xB5: 'Ô', 0xB6: '×', 0xB7: 'Ö',
0xB8: 'Ù', 0xB9: 'Ø', 0xBA: 'Û', 0xBB: 'Ú', 0xBC: 'Ý', 0xBD: 'Ü', 0xBE: 'ß', 0xBF: 'Þ',

// Row C
0xC0: '¡', 0xC1: ' ', 0xC2: '£', 0xC3: '¢', 0xC4: '¥', 0xC5: '¤', 0xC6: '§', 0xC7: '¦',
0xC8: '©', 0xC9: '¨', 0xCA: '«', 0xCB: 'ª', 0xCC: '­', 0xCD: '¬', 0xCE: '¯', 0xCF: '®',

// Row D
0xD0: '±', 0xD1: '°', 0xD2: '³', 0xD3: '²', 0xD4: 'µ', 0xD5: '´', 0xD6: '·', 0xD7: '¶',
0xD8: '¹', 0xD9: '¸', 0xDA: '»', 0xDB: 'º', 0xDC: '½', 0xDD: '¼', 0xDE: '¿', 0xDF: '¾',

// Row F
0xF0: '‘', 0xF1: '', 0xF2: '“', 0xF3: '’', 0xF4: '•', 0xF5: '”', 0xF6: '—', 0xF7: '–',
0xF8: '™', 0xF9: '˜', 0xFA: '›', 0xFB: 'š', 0xFC: '', 0xFD: 'œ', 0xFE: 'Ÿ', 0xFF: 'ž',

}

 

This led to the definition of Go structs to represent player data within my program. For example, a basic player struct might look something like this:

Go
type Player struct {
    Begin          string
    Id             string
    Number         int
    SurNameDigits  int
    SurName        string
    FullNameDigits int
    FullName       string
    Position       int
    Role1          string
    Role2          string
    Role3          string
    Role4          string
    Role5          string
    Role6          string
    Nationality    string
    SkinColor      string
    HairColor      string
    PlayerType     string
    DayOfBirth     int
    MonthOfBirth   int
    Year1OfBirth   int
    Height         int
    Weight         int
    NationOfBirth  string
    Speed          int
    Stamina        int
    Aggression     int
    Quality        int
    Finishing      int
    Dribbling      int
    Passing        int
    Shooting       int
    Tackling       int
    Handling       int
    PreferredFoot  string
    Penalties      int
    LeftCorner     int
    RightCorner    int
    LeftFreeKick   int
    RightFreeKick  int
    RawData        []byte
}

Once the data structures were defined, I wrote Go functions to:

  • Locate all players: These functions would traverse the index section and extract the pointers to each player's data block.
  • Modify player data: These functions would take a player object and update the corresponding bytes in the .fdi file, allowing for editing of player attributes.

Building the User Interface with Fyne

With the backend logic in place, the next exciting step was to create a user-friendly interface. I chose Fyne.io, a Go-based UI toolkit that allows for the creation of cross-platform applications with relative ease.

Using Fyne, I implemented:

  • A way to load and browse .fdi files.
  • A visual representation of the player data, allowing users to view and edit names, surnames, and all other attributes.
  • Functionality to save the modified data back to the .fdi file.

What's Next?

This project is still ongoing, and there are many potential avenues for future development. This could include adding support for more player attributes, implementing more sophisticated data validation and teams modifications.


PC Calcio Golang Fyne