7 Commits
14 changed files with 425 additions and 305 deletions
+2 -2
View File
@@ -1,2 +1,2 @@
coverage* coverage*
*.txt *.txt
+3 -3
View File
@@ -1,3 +1,3 @@
test: test:
go test -coverprofile=coverage.out ./... -cover -bench= go test -coverprofile=coverage.out ./... -cover -bench=
go tool cover -html=coverage.out -o coverage.html go tool cover -html=coverage.out -o coverage.html
+17 -17
View File
@@ -1,17 +1,17 @@
# About # About
This project will be able to parse `.ibt` files, read live data from races and This project will be able to parse `.ibt` files, read live data from races and
broadcast messages to the service. Its still not very mature at all, but after broadcast messages to the service. Its still not very mature at all, but after
this commit I will turn it into a package instead and give it a stable API. this commit I will turn it into a package instead and give it a stable API.
Will also put some examples then. Will also put some examples then.
## The SDK ## The SDK
I used various sources to develop and understand how `iRacing` works, and learn I used various sources to develop and understand how `iRacing` works, and learn
a lot with it. Once I have matured this project a bit I will document its a lot with it. Once I have matured this project a bit I will document its
inner workings too. inner workings too.
## SharedMem ## SharedMem
I vendored in the code from [hidez8891/shm](https://github.com/hidez8891/shm) I vendored in the code from [hidez8891/shm](https://github.com/hidez8891/shm)
since the repo has been archived. I took the opportunity to update some of its since the repo has been archived. I took the opportunity to update some of its
code. code.
+27
View File
@@ -1,6 +1,8 @@
package goirsdk package goirsdk
import ( import (
"github.com/ESilva15/goirsdk/logger"
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
@@ -19,6 +21,31 @@ type DiskSubHeader struct {
RecordCount int32 // RecordCount holds the number of data frames RecordCount int32 // RecordCount holds the number of data frames
} }
// readSubheader will read the subheader contents out of the telemetry data
func (i *IBT) readSubheader() error {
log := logger.GetInstance()
var subheaderRaw [SubHeaderSize]byte
_, err := i.File.ReadAt(subheaderRaw[:], HeaderSize)
if err != nil {
return fmt.Errorf("Failed to read disk subheaders from file: %v", err)
}
i.SubHeaders, err = parseTelemetrySubHeader(subheaderRaw)
if err != nil {
return fmt.Errorf("Unable to parse disk subheaders from file: %v", err)
}
// Write to the output file - TODO add the check
if i.IBTExport != nil {
err = i.exportIBT(subheaderRaw[:], HeaderSize)
if err != nil {
log.Printf("Failed to export subheaders: %v\n", err)
}
}
return nil
}
// parseTelemetrySubHeader will return a pointer to a DiskSubHeader variable // parseTelemetrySubHeader will return a pointer to a DiskSubHeader variable
// or nil if an error occurs. In which case the error return value is more // or nil if an error occurs. In which case the error return value is more
// valuable // valuable
+38 -38
View File
@@ -1,38 +1,38 @@
package goirsdk package goirsdk
import ( import (
"testing" "testing"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
) )
// TestParseTelemetrySubHeader_WithGoodBuffer // TestParseTelemetrySubHeader_WithGoodBuffer
// Given a well structured buffer it will output the expected // Given a well structured buffer it will output the expected
// DiskSubHeader struct // DiskSubHeader struct
func TestParseTelemetrySubHeader_WithGoodBuffer(t *testing.T) { func TestParseTelemetrySubHeader_WithGoodBuffer(t *testing.T) {
// Arrange // Arrange
header := [32]byte{ header := [32]byte{
0x54, 0x1e, 0x14, 0x67, 0x00, 0x00, 0x00, 0x00, 0x54, 0x09, 0x00, 0xf0, 0x54, 0x1e, 0x14, 0x67, 0x00, 0x00, 0x00, 0x00, 0x54, 0x09, 0x00, 0xf0,
0xee, 0x7e, 0x6b, 0x40, 0x53, 0x74, 0x88, 0x44, 0x44, 0x86, 0x8f, 0x40, 0xee, 0x7e, 0x6b, 0x40, 0x53, 0x74, 0x88, 0x44, 0x44, 0x86, 0x8f, 0x40,
0x08, 0x00, 0x00, 0x00, 0xe1, 0xb8, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0xe1, 0xb8, 0x00, 0x00,
} }
expectedHeader := DiskSubHeader{ expectedHeader := DiskSubHeader{
StartDate: 1729371732, StartDate: 1729371732,
StartTime: 219.96666717536084, StartTime: 219.96666717536084,
EndTime: 1008.7833338413715, EndTime: 1008.7833338413715,
LapCount: 8, LapCount: 8,
RecordCount: 47329, RecordCount: 47329,
} }
// Act // Act
headers, err := parseTelemetrySubHeader(header) headers, err := parseTelemetrySubHeader(header)
// Assert // Assert
if err != nil { if err != nil {
t.Fatalf("Error parsing buffer: %v", err) t.Fatalf("Error parsing buffer: %v", err)
} }
if !cmp.Equal(&expectedHeader, headers) { if !cmp.Equal(&expectedHeader, headers) {
t.Fatalf("Expected:\n%#v\nGot:\n%#v\n", expectedHeader, headers) t.Fatalf("Expected:\n%#v\nGot:\n%#v\n", expectedHeader, headers)
} }
} }
+6 -1
View File
@@ -4,7 +4,12 @@ go 1.23.2
require ( require (
github.com/google/go-cmp v0.6.0 github.com/google/go-cmp v0.6.0
golang.org/x/sys v0.26.0 golang.org/x/sys v0.29.0
golang.org/x/text v0.19.0 golang.org/x/text v0.19.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
require (
golang.org/x/lint v0.0.0-20241112194109-818c5a804067 // indirect
golang.org/x/tools v0.29.0 // indirect
)
+17
View File
@@ -1,9 +1,26 @@
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/lint v0.0.0-20241112194109-818c5a804067 h1:adDmSQyFTCiv19j015EGKJBoaa7ElV0Q1Wovb/4G7NA=
golang.org/x/lint v0.0.0-20241112194109-818c5a804067/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+43 -16
View File
@@ -1,6 +1,8 @@
package goirsdk package goirsdk
import ( import (
"github.com/ESilva15/goirsdk/logger"
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
@@ -39,6 +41,47 @@ type TelemetryHeaders struct {
BufOffset int32 BufOffset int32
} }
// readHeader will read the header out of the telemetry data
func (i *IBT) readHeader() error {
log := logger.GetInstance()
var headerRaw [FileHeaderSize]byte
_, err := i.File.ReadAt(headerRaw[:], 0)
if err != nil {
return fmt.Errorf("Failed to read headers from file: %v", err)
}
i.Headers, err = parseTelemetryHeader(headerRaw)
if err != nil {
return fmt.Errorf("Unable to read headers from file: %v", err)
}
// Write to the output file - TODO: this should only write if necessary
if i.IBTExport != nil {
err = i.exportIBT(headerRaw[:], 0)
if err != nil {
log.Printf("Failed to export headers: %v\n", err)
}
}
return nil
}
// parseTelemetryHeader will read the IBT file headers from a correctly sized
// buffer.
// You need to pass a the first FILE_HEADER_SIZE bytes of the buffer
func parseTelemetryHeader(buf [FileHeaderSize]byte) (*TelemetryHeaders, error) {
// utils.HexDump(buf[:])
// fmt.Printf("Len: %d\n", len(buf))
dst := TelemetryHeaders{}
err := binary.Read(bytes.NewBuffer(buf[:]), binary.LittleEndian, &dst)
if err != nil {
return nil, fmt.Errorf("unable to unpack data: %v", err)
}
return &dst, nil
}
// ToString renders a string showing the values of the struct // ToString renders a string showing the values of the struct
func (th *TelemetryHeaders) ToString() string { func (th *TelemetryHeaders) ToString() string {
return fmt.Sprintf( return fmt.Sprintf(
@@ -61,19 +104,3 @@ func (th *TelemetryHeaders) ToString() string {
th.NumBuf, th.NumBuf, th.BufLen, th.BufLen, th.BufOffset, th.BufOffset, th.NumBuf, th.NumBuf, th.BufLen, th.BufLen, th.BufOffset, th.BufOffset,
) )
} }
// parseTelemetryHeader will read the IBT file headers from a correctly sized
// buffer.
// You need to pass a the first FILE_HEADER_SIZE bytes of the buffer
func parseTelemetryHeader(buf [FileHeaderSize]byte) (*TelemetryHeaders, error) {
// utils.HexDump(buf[:])
// fmt.Printf("Len: %d\n", len(buf))
dst := TelemetryHeaders{}
err := binary.Read(bytes.NewBuffer(buf[:]), binary.LittleEndian, &dst)
if err != nil {
return nil, fmt.Errorf("unable to unpack data: %v", err)
}
return &dst, nil
}
+52 -52
View File
@@ -1,52 +1,52 @@
package goirsdk package goirsdk
import ( import (
"testing" "testing"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
) )
// TestParseTelemetryHeader_WithGoodBuffer // TestParseTelemetryHeader_WithGoodBuffer
// Given a well structured buffer it will output the expected // Given a well structured buffer it will output the expected
// TelemetryHeaders struct // TelemetryHeaders struct
func TestParseTelemetryHeader_WithGoodBuffer(t *testing.T) { func TestParseTelemetryHeader_WithGoodBuffer(t *testing.T) {
// Arrange // Arrange
header := [112]byte{ header := [112]byte{
0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x05, 0x3f, 0x00, 0x00, 0x90, 0x99, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x3f, 0x00, 0x00, 0x90, 0x99, 0x00, 0x00,
0x10, 0x01, 0x00, 0x00, 0x90, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x90, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x1d, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x60, 0x2b, 0x00, 0x00, 0x95, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x2b, 0x00, 0x00, 0x95, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
} }
expectedHeader := TelemetryHeaders{ expectedHeader := TelemetryHeaders{
Version: 2, Version: 2,
Status: 1, Status: 1,
TickRate: 60, TickRate: 60,
SessionInfoUpdate: 0, SessionInfoUpdate: 0,
SessionInfoLength: 16133, SessionInfoLength: 16133,
SessionInfoOffset: 39312, SessionInfoOffset: 39312,
NumVars: 272, NumVars: 272,
VarHeaderOffset: 144, VarHeaderOffset: 144,
NumBuf: 1, NumBuf: 1,
BufLen: 1053, BufLen: 1053,
Padding: [12]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x60, 0x2b, 0x0, 0x0}, Padding: [12]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x60, 0x2b, 0x0, 0x0},
BufOffset: 55445, BufOffset: 55445,
} }
// Act // Act
headers, err := parseTelemetryHeader(header) headers, err := parseTelemetryHeader(header)
// Assert // Assert
if err != nil { if err != nil {
t.Fatalf("Error parsing buffer: %v", err) t.Fatalf("Error parsing buffer: %v", err)
} }
if !cmp.Equal(&expectedHeader, headers) { if !cmp.Equal(&expectedHeader, headers) {
t.Fatalf("Expected:\n%#v\nGot:\n%#v\n", expectedHeader, headers) t.Fatalf("Expected:\n%#v\nGot:\n%#v\n", expectedHeader, headers)
} }
} }
+76 -114
View File
@@ -2,22 +2,26 @@
package goirsdk package goirsdk
import ( import (
"fmt" // "github.com/ESilva15/goirsdk/logger"
"log"
"os"
// "os"
"io"
// "log"
// "time"
// conv "ibtReader/conversions" "fmt"
"os"
"io"
"github.com/ESilva15/goirsdk/logger"
"github.com/ESilva15/goirsdk/winutils" "github.com/ESilva15/goirsdk/winutils"
"gopkg.in/yaml.v3"
) )
const ( const (
ibtFile = "./telemetryFiles/mx5_2016Okayama_full_2024_10_19_22_02_12.ibt" ibtFile = "./telemetryFiles/mx5_2016Okayama_full_2024_10_19_22_02_12.ibt"
) )
func msToKph(v float32) int {
return int((3600 * v) / 1000)
}
// Reader is an interface to represent the readable data that can be either // Reader is an interface to represent the readable data that can be either
// a .ibt file (or live data, hopefully) // a .ibt file (or live data, hopefully)
type Reader interface { type Reader interface {
@@ -28,13 +32,16 @@ type Reader interface {
// IBT struct will hold the relevant data for a given IBT file // IBT struct will hold the relevant data for a given IBT file
type IBT struct { type IBT struct {
File Reader // Source of the data File Reader // Source of the data
FileToExport string // If set, it will export the IBT data to the file IBTExport *os.File // If set, it will export the IBT data to the file
Headers *TelemetryHeaders // IBT file Headers IBTExportPath string // Path for IBT export
SubHeaders *DiskSubHeader // IBT file Sub Headers YAMLExport *os.File // If set, it will export the session YAML to the file
SessionInfo *SessionInfoYAML // IBT file Session Info YAMLExportPath string // Path for YAML export
Vars *TelemetryVars // Vars will hold the telemetry data Headers *TelemetryHeaders // IBT file Headers
winUtils *winutils.IRacingWinUtils // WinUtils gives access to the system utilities SubHeaders *DiskSubHeader // IBT file Sub Headers
SessionInfo *SessionInfoYAML // IBT file Session Info
Vars *TelemetryVars // Vars will hold the telemetry data
winUtils *winutils.IRacingWinUtils // WinUtils gives access to the system utilities
} }
func (i *IBT) IsConnected() bool { func (i *IBT) IsConnected() bool {
@@ -50,28 +57,64 @@ func (i *IBT) IsConnected() bool {
return false return false
} }
func (i *IBT) ExportToIBT(filepath string) { func (i *IBT) exportYAML() error {
rbuf := make([]byte, fileMapSize) log := logger.GetInstance()
_, err := i.File.ReadAt(rbuf, 0) file, err := os.OpenFile(i.YAMLExportPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil { if err != nil {
log.Fatal(err) log.Printf("Failed to open file for YAML export: %v\n", err)
return fmt.Errorf("failed to open output file for YAML: %v", err)
}
defer file.Close()
enc := yaml.NewEncoder(file)
err = enc.Encode(i.SessionInfo)
if err != nil {
log.Printf("Failed to write into file for YAML export: %v\n", err)
return fmt.Errorf("failed to write YAML contents to file: %v", err)
} }
err = os.WriteFile(filepath, rbuf, 0644) return nil
if err != nil { }
log.Fatal(err)
} func (i *IBT) exportIBT(data []byte, offset int64) error {
log := logger.GetInstance()
_, err := i.IBTExport.WriteAt(data, offset)
if err != nil {
i.IBTExport.Close()
i.IBTExport = nil
log.Println("Won't attempt to export anymore")
return err
}
return nil
} }
// Init serves to initialize and get a hold of a IBT struct // Init serves to initialize and get a hold of a IBT struct
func Init(f Reader) (*IBT, error) { func Init(f Reader, exportTelem string, exportYAML string) (*IBT, error) {
// log := logger.GetInstance()
// Read the header of the file // Read the header of the file
var err error var err error
ibt := IBT{ ibt := IBT{
File: f, File: f,
Vars: &TelemetryVars{}, IBTExport: nil,
winUtils: nil, IBTExportPath: exportTelem,
YAMLExport: nil,
YAMLExportPath: exportYAML,
Vars: &TelemetryVars{},
winUtils: nil,
}
// If requested to output to a telemetry file
if exportTelem != "" {
ibt.IBTExport, err = os.OpenFile(exportTelem, os.O_CREATE|os.O_RDWR, 0644)
if err != nil {
return nil, fmt.Errorf("failed to open ibt export file: %v", err)
}
} }
if ibt.File == nil { if ibt.File == nil {
@@ -103,36 +146,21 @@ func Init(f Reader) (*IBT, error) {
} }
// Read the file headers // Read the file headers
var headerRaw [FileHeaderSize]byte err = ibt.readHeader()
_, err = ibt.File.ReadAt(headerRaw[:], 0)
if err != nil { if err != nil {
return nil, fmt.Errorf("Failed to read headers from file: %v", err) return nil, err
}
ibt.Headers, err = parseTelemetryHeader(headerRaw)
if err != nil {
return nil, fmt.Errorf("Unable to read headers from file: %v", err)
} }
// Read the disk sub headers // Read the disk sub headers
var subheaderRaw [SubHeaderSize]byte err = ibt.readSubheader()
_, err = ibt.File.ReadAt(subheaderRaw[:], 112)
if err != nil { if err != nil {
return nil, fmt.Errorf("Failed to read disk subheaders from file: %v", err) return nil, err
}
ibt.SubHeaders, err = parseTelemetrySubHeader(subheaderRaw)
if err != nil {
return nil, fmt.Errorf("Unable to parse disk subheaders from file: %v", err)
} }
// Read session info string // Read session info string
sessionInfoStringRaw := make([]byte, ibt.Headers.SessionInfoLength) err = ibt.readSessionInfo()
_, err = ibt.File.ReadAt(sessionInfoStringRaw, int64(ibt.Headers.SessionInfoOffset))
if err != nil { if err != nil {
return nil, fmt.Errorf("Failed to read sessionInfoString from file: %v", err) return nil, err
}
ibt.SessionInfo, err = parseSessionInfo(sessionInfoStringRaw, ibt.Headers.SessionInfoLength)
if err != nil {
return nil, fmt.Errorf("Unable to parse SessionInfoString from file: %v", err)
} }
// Read the telemetry vars info // Read the telemetry vars info
@@ -151,69 +179,3 @@ func (i *IBT) Close() {
i.winUtils.Close() i.winUtils.Close()
} }
} }
// func main() {
// fmt.Println("================== IBT FILE PARSER ==================")
//
// file, err := os.Open(ibtFile)
// if err != nil {
// log.Fatalf("Failed to open IBT file: %v", err)
// }
//
// ibt, err := Init(file)
// if err != nil {
// log.Fatalf("Failed to create irsdk instance: %v", err)
// }
// // fmt.Printf("%s\n", ibt.Headers.ToString())
// // fmt.Printf("%s\n", ibt.SubHeaders.ToString())
// // fmt.Printf("%s\n", ibt.SessionInfo.ToString())
//
// // Display the human readable start date
// // unixStartDate := time.Unix(ibt.SubHeaders.StartDate, 0)
// // startDate := unixStartDate.Format("2006/01/02 15:04:05 -0700 MST")
// // fmt.Println("StartDate:", startDate)
//
// // Display the human readable version of start time
// // unixStartTime := time.Unix(ibt.SubHeaders.StartDate+int64(ibt.SubHeaders.StartTime), 0)
// // startTime := unixStartTime.Format("2006/01/02 15:04:05 -0700 MST")
// // fmt.Println("StartTime:", startTime)
//
// // Display the human readable version of end time
// // unixEndTime := time.Unix(ibt.SubHeaders.StartDate+int64(ibt.SubHeaders.EndTime), 0)
// // endTime := unixEndTime.Format("2006/01/02 15:04:05 -0700 MST")
// // fmt.Println("EndTime: ", endTime)
//
// last := time.Now().UnixMilli()
// for {
// time.Sleep(time.Second / 60)
// res, err := ibt.Update(100 * time.Millisecond)
// if res == Unknown {
// log.Fatalf("Some unknown error occurred: %v\n", err)
// }
//
// if res == Paused {
// fmt.Printf("\r \r")
// fmt.Println("GAME IS PAUSED")
// continue
// }
//
// curTime := time.Now().UnixMilli()
//
// if curTime-last > 250 {
// fmt.Printf(" \r")
// if val, ok := ibt.Vars.Vars["Speed"]; ok {
// fmt.Printf("\r%d %d", ibt.Vars.Tick/60, conv.MsToKph(val.Value.(float32)))
// } else {
// fmt.Printf("\r%d %s", ibt.Vars.Tick/60, "KEY DOESN'T EXIST")
// }
// }
//
// if res == Ended {
// fmt.Println("\nEnd of file found...")
// break
// }
// }
// fmt.Printf("%d\n", ibt.Vars.Tick)
//
// ibt.Close()
// }
+23
View File
@@ -0,0 +1,23 @@
package logger
import (
"log"
"os"
"sync"
)
var l *log.Logger
var once sync.Once
func createLogger() {
l = log.New(os.Stdout, "[ibtReader] ", log.LstdFlags | log.Lshortfile)
}
func GetInstance() *log.Logger {
once.Do(func() {
createLogger()
})
return l
}
+37
View File
@@ -1,7 +1,10 @@
package goirsdk package goirsdk
import ( import (
"github.com/ESilva15/goirsdk/logger"
"encoding/json" "encoding/json"
"fmt"
"log" "log"
"strings" "strings"
@@ -307,6 +310,40 @@ type Driver struct {
TeamIncidentCount int `yaml:"TeamIncidentCount"` TeamIncidentCount int `yaml:"TeamIncidentCount"`
} }
// readSessionInfo will read the session info yaml out of the telemetry data
func (i *IBT) readSessionInfo() error {
log := logger.GetInstance()
sessionInfoStringRaw := make([]byte, i.Headers.SessionInfoLength)
_, err := i.File.ReadAt(sessionInfoStringRaw, int64(i.Headers.SessionInfoOffset))
if err != nil {
return fmt.Errorf("Failed to read sessionInfoString from file: %v", err)
}
// Write to the output file
if i.IBTExport != nil {
err := i.exportIBT(sessionInfoStringRaw[:], int64(i.Headers.SessionInfoOffset))
if err != nil {
log.Printf("Failed to export offline telemetry data: %v", err)
}
}
i.SessionInfo, err = parseSessionInfo(sessionInfoStringRaw, i.Headers.SessionInfoLength)
if err != nil {
return fmt.Errorf("Unable to parse SessionInfoString from file: %v", err)
}
// Write to YAML output file
if i.YAMLExportPath != "" {
err := i.exportYAML()
if err != nil {
log.Printf("Failed to export YAML string: %v\n", err)
}
}
return nil
}
// parseSessionInfo will parse the sessionInfo buffer into the SessionInfoYAML // parseSessionInfo will parse the sessionInfo buffer into the SessionInfoYAML
// struct // struct
func parseSessionInfo(buf []byte, len int32) (*SessionInfoYAML, error) { func parseSessionInfo(buf []byte, len int32) (*SessionInfoYAML, error) {
+31 -9
View File
@@ -91,8 +91,9 @@ type varBuffer struct {
} }
type TelemetryVars struct { type TelemetryVars struct {
Tick int32 Tick int32 // Keeps track of the current data buffer tick
Vars map[string]Var RecorderTick int32 // Counts from 0 when creating a telemetry file from a replay or live data
Vars map[string]Var // Variables content
} }
func (i *IBT) readVariablerHeaders() error { func (i *IBT) readVariablerHeaders() error {
@@ -107,6 +108,14 @@ func (i *IBT) readVariablerHeaders() error {
return err return err
} }
if i.IBTExport != nil {
err = i.exportIBT(rbuf, int64(i.Headers.VarHeaderOffset+k*VarHeaderSize))
if err != nil {
// Don't outright kill it here - maybe nowhere else
log.Printf("Failed to export variable contents: %v\n", err)
}
}
var dst IBTVar var dst IBTVar
err = binary.Read(bytes.NewBuffer(rbuf[:]), binary.LittleEndian, &dst) err = binary.Read(bytes.NewBuffer(rbuf[:]), binary.LittleEndian, &dst)
if err != nil { if err != nil {
@@ -197,6 +206,13 @@ func (i *IBT) Update(timeout time.Duration) (IRacingState, error) {
return Failed, err return Failed, err
} }
if i.IBTExport != nil {
err = i.exportIBT(buf, int64(i.Headers.BufOffset+i.Vars.RecorderTick*i.Headers.BufLen))
if err != nil {
log.Printf("Failed to export live telemetry data: %v", err)
}
}
err = i.readData(buf) err = i.readData(buf)
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
return Unknown, err return Unknown, err
@@ -205,11 +221,23 @@ func (i *IBT) Update(timeout time.Duration) (IRacingState, error) {
if err == io.EOF { if err == io.EOF {
return Ended, nil return Ended, nil
} }
i.Vars.RecorderTick++
} else { } else {
// This will get the dataframe corresponding to a give tick // This will get the dataframe corresponding to a given tick
start := i.Headers.BufOffset + i.Vars.Tick*i.Headers.BufLen start := i.Headers.BufOffset + i.Vars.Tick*i.Headers.BufLen
buf := make([]byte, i.Headers.BufLen) buf := make([]byte, i.Headers.BufLen)
_, err := i.File.ReadAt(buf, int64(start)) _, err := i.File.ReadAt(buf, int64(start))
// Make this happen in a different thread, or have this send to a queue that has a thread
// writing to a file
if i.IBTExport != nil {
err = i.exportIBT(buf, int64(start))
if err != nil {
log.Printf("Failed to export offline telemetry data: %v", err)
}
}
if err == io.EOF { if err == io.EOF {
return Ended, nil return Ended, nil
} }
@@ -226,11 +254,5 @@ func (i *IBT) Update(timeout time.Duration) (IRacingState, error) {
i.Vars.Tick++ i.Vars.Tick++
} }
// Make this happen in a different thread, or have this send to a queue that has a thread
// writing to a file
if i.FileToExport != "" {
i.ExportToIBT(i.FileToExport)
}
return Running, nil return Running, nil
} }
+53 -53
View File
@@ -1,53 +1,53 @@
//go:build (linux && cgo) || (darwin && cgo) //go:build (linux && cgo) || (darwin && cgo)
// +build linux,cgo darwin,cgo // +build linux,cgo darwin,cgo
package winutils package winutils
import ( import (
"errors" "errors"
"sync" "sync"
"time" "time"
) )
const ( const (
WAIT_OBJECT_0 = 0 WAIT_OBJECT_0 = 0
WAIT_TIMEOUT = 258 WAIT_TIMEOUT = 258
) )
var ( var (
once sync.Once once sync.Once
ErrUnsupportedOS = errors.New("not found") ErrUnsupportedOS = errors.New("not found")
) )
type utils struct { type utils struct {
} }
// INITIALIZATION // INITIALIZATION
func newUtils() (*utils, error) { func newUtils() (*utils, error) {
return nil, ErrUnsupportedOS return nil, ErrUnsupportedOS
} }
func (u *utils) Close() { func (u *utils) Close() {
} }
// openEvent opens a windows.Handle for a given event // openEvent opens a windows.Handle for a given event
func (u *utils) OpenEvent(eventName string) error { func (u *utils) OpenEvent(eventName string) error {
return ErrUnsupportedOS return ErrUnsupportedOS
} }
// OpenBroadcastChannel opens up a broadcast channel to send commands to iracing // OpenBroadcastChannel opens up a broadcast channel to send commands to iracing
func (u *utils) OpenBroadcastChannel(name string) error { func (u *utils) OpenBroadcastChannel(name string) error {
return ErrUnsupportedOS return ErrUnsupportedOS
} }
// INITIALIZATION // INITIALIZATION
// openEvent waits for a good response for some given time // openEvent waits for a good response for some given time
func (u *utils) CheckValidDataEvent(timeout time.Duration) bool { func (u *utils) CheckValidDataEvent(timeout time.Duration) bool {
return false return false
} }
// SendBroadcastMessage sends a message trough the broadcast channel // SendBroadcastMessage sends a message trough the broadcast channel
func (u *utils) SendBroadcastMessage(id, p1, p2 uintptr) error { func (u *utils) SendBroadcastMessage(id, p1, p2 uintptr) error {
return ErrUnsupportedOS return ErrUnsupportedOS
} }