...

Source file src/go.formulabun.club/srb2kart/lump/replay/readwrite.go

Documentation: go.formulabun.club/srb2kart/lump/replay

     1  package replay
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  
    10  	"go.formulabun.club/functional/array"
    11  )
    12  
    13  var demoHeader = "KartReplay"
    14  
    15  func ReadReplayData(data []byte) (result ReplayRaw, err error) {
    16  	dataReader := bytes.NewReader(data)
    17  	return ReadReplay(dataReader)
    18  }
    19  
    20  func ReadReplay(data io.Reader) (result ReplayRaw, err error) {
    21  	var headerPreReplays HeaderPreFileEntries
    22  	err = binary.Read(data, binary.LittleEndian, &headerPreReplays)
    23  	if err != nil {
    24  		return result, fmt.Errorf("Could not read the replay header before addons: %s", err)
    25  	}
    26  	result.HeaderPreFileEntries = headerPreReplays
    27  
    28  	fileCount := int(result.FileCount)
    29  	result.WadEntries = make([]WadEntry, fileCount)
    30  	readCount := 0
    31  	for readCount < fileCount {
    32  		entry, err := readWadEntry(data)
    33  		if err != nil {
    34  			return result, fmt.Errorf("Could not read the file entry number %d: %s", readCount+1, err)
    35  		}
    36  		result.WadEntries[readCount] = entry
    37  		readCount++
    38  	}
    39  
    40  	var headerPostReplays HeaderPostFileEntries
    41  	err = binary.Read(data, binary.LittleEndian, &headerPostReplays)
    42  	if err != nil {
    43  		return result, fmt.Errorf("Could not read the replay header before addons: %s", err)
    44  	}
    45  	result.HeaderPostFileEntries = headerPostReplays
    46  
    47  	cvarCount := int(result.CVarCount)
    48    result.CVarEntries = make([]CVarEntry, cvarCount)
    49  	readCount = 0
    50  	for readCount < cvarCount {
    51  		entry, err := readCVarEntry(data)
    52  		if err != nil {
    53  			return result, fmt.Errorf("Could not read cvar entry number %d: %s", readCount+1, err)
    54  		}
    55  		result.CVarEntries[readCount] = entry
    56  		readCount++
    57  	}
    58  
    59  	players, end, err := readPlayerEntries(data)
    60  	if err != nil {
    61  		return result, fmt.Errorf("Could not read player entries from file: %s", err)
    62  	}
    63  	result.PlayerEntries = players
    64  	result.PlayerListingEnd = end
    65  
    66  	return result, validate(result)
    67  }
    68  
    69  func (R *ReplayRaw) Write(writer io.Writer) error {
    70  	err := binary.Write(writer, binary.LittleEndian, R.HeaderPreFileEntries)
    71  	if err != nil {
    72  		return fmt.Errorf("Could not write the replay header: %s", err)
    73  	}
    74  	for _, replayEntry := range R.WadEntries {
    75  		_, err = io.WriteString(writer, replayEntry.FileName)
    76  		if err != nil {
    77  			return fmt.Errorf("Could not write replay file name: %s", err)
    78  		}
    79  		_, err = writer.Write(replayEntry.WadMd5[:])
    80  		if err != nil {
    81  			return fmt.Errorf("Could not write replay file checksum: %s", err)
    82  		}
    83  	}
    84  
    85  	err = binary.Write(writer, binary.LittleEndian, R.HeaderPostFileEntries)
    86  	if err != nil {
    87  		return err
    88  	}
    89  
    90  	for _, cvarEntry := range R.CVarEntries {
    91  		err = binary.Write(writer, binary.LittleEndian, cvarEntry.CVarId)
    92  		_, err = io.WriteString(writer, cvarEntry.Value)
    93  		err = binary.Write(writer, binary.LittleEndian, cvarEntry.False)
    94  		if err != nil {
    95  			return err
    96  		}
    97  	}
    98  
    99  	for _, playerEntry := range R.PlayerEntries {
   100  		err = binary.Write(writer, binary.LittleEndian, playerEntry)
   101  		if err != nil {
   102  			return err
   103  		}
   104  	}
   105  
   106  	err = binary.Write(writer, binary.LittleEndian, R.PlayerListingEnd)
   107  	return err
   108  }
   109  
   110  func readWadEntry(data io.Reader) (result WadEntry, err error) {
   111  	filename, extra, err := readNullTerminatedString(data, 16)
   112  	if err != nil {
   113  		return result, err
   114  	}
   115  
   116  	result.FileName = filename
   117  	copy(result.WadMd5[:len(extra)], extra)
   118  
   119  	n, err := data.Read(result.WadMd5[len(extra):])
   120  	if err != nil {
   121  		return result, fmt.Errorf("Could not read a file entry from the replay: %s", err)
   122  	}
   123  	if n < 16-len(extra) {
   124  		return result, fmt.Errorf("Unexpected end to the replay file.")
   125  	}
   126  	return result, nil
   127  }
   128  
   129  func readCVarEntry(data io.Reader) (result CVarEntry, err error) {
   130  	err = binary.Read(data, binary.LittleEndian, &result.CVarId)
   131  	if err != nil {
   132  		return result, fmt.Errorf("Could not read CVar ID: %s", err)
   133  	}
   134  
   135  	cvarValue, extra, err := readNullTerminatedString(data, 1)
   136  	if err != nil {
   137  		return result, err
   138  	}
   139  
   140  	result.Value = cvarValue
   141  	if len(extra) != 2 {
   142  		binary.Read(data, binary.LittleEndian, &result.False)
   143  	} else {
   144  		result.False = extra[1]
   145  	}
   146  	return result, err
   147  }
   148  
   149  func readPlayerEntries(data io.Reader) (result PlayerEntries, end byte, err error) {
   150  	var spec byte
   151  
   152  	err = binary.Read(data, binary.LittleEndian, &spec)
   153  	if err != nil {
   154  		return result, spec, fmt.Errorf("Could not read player spec value: %s", err)
   155  	}
   156  
   157  	for spec != 0xff {
   158  		var playerEntry PlayerEntry
   159  
   160  		err = binary.Read(data, binary.LittleEndian, &playerEntry.PlayerEntryData)
   161  		if err != nil {
   162  			return result, spec, fmt.Errorf("Could not read player entry: %s", err)
   163  		}
   164  		playerEntry.Spec = spec
   165  		result = append(result, playerEntry)
   166  
   167  		err = binary.Read(data, binary.LittleEndian, &spec)
   168  		if err != nil {
   169  			return result, spec, fmt.Errorf("Could not read player spec value: %s", err)
   170  		}
   171  	}
   172  	return result, spec, nil
   173  }
   174  
   175  func validate(replay ReplayRaw) error {
   176  	headerText := string(replay.DemoHeader[1:11])
   177  	badFileError := errors.New("Not a kart replay file")
   178  
   179  	if demoHeader != headerText && replay.DemoHeader[0] == 0xf0 && replay.DemoHeader[11] == 0x0f {
   180  		return badFileError
   181  	}
   182  	if string(replay.Play[:]) != "PLAY" {
   183  		return badFileError
   184  	}
   185  	if replay.DemoFlags&0x2 == 0 {
   186  		return badFileError
   187  	}
   188  	return nil
   189  }
   190  
   191  func readNullTerminatedString(data io.Reader, bufferSize int) (string, []byte, error) {
   192  	var result bytes.Buffer
   193  	buffer := make([]byte, bufferSize)
   194  
   195  	for {
   196  		n, err := data.Read(buffer)
   197  		if err != nil {
   198  			return result.String(), buffer, fmt.Errorf("Could not read from the replay: %s", err)
   199  		}
   200  		if n < bufferSize {
   201  			return result.String(), buffer, fmt.Errorf("Unexpected end to the replay file.")
   202  		}
   203  
   204  		found := array.FindFirstIndexMatching(buffer, 0x00)
   205  		if found >= 0 {
   206  			result.Write(buffer[:found])
   207  			return result.String(), buffer[found+1:], nil
   208  		}
   209  		result.Write(buffer)
   210  	}
   211  }
   212  

View as plain text