From 2bc756a2f051bb845ef64ae6432c9415fa209723 Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Sun, 27 Oct 2024 22:48:32 +0000 Subject: [PATCH] Rolled back some stuff here like the package stuff This will add support to view live data --- constants.go | 117 ++++++ diskSubHeader.go | 94 ++--- go.mod | 7 +- go.sum | 2 + headers.go | 160 ++++----- irsdk.go | 290 ++++++++------- sessionInfo.go | 646 +++++++++++++++++----------------- sharedMem/sharedMem.go | 88 +++++ sharedMem/sharedMemDarwin.go | 157 +++++++++ sharedMem/sharedMemLinux.go | 148 ++++++++ sharedMem/sharedMemWindows.go | 82 +++++ sharedMem/utils.go | 15 + variables.go | 354 +++++++++---------- winutils/winutils.go | 105 ++++++ 14 files changed, 1500 insertions(+), 765 deletions(-) create mode 100644 constants.go create mode 100644 sharedMem/sharedMem.go create mode 100644 sharedMem/sharedMemDarwin.go create mode 100644 sharedMem/sharedMemLinux.go create mode 100644 sharedMem/sharedMemWindows.go create mode 100644 sharedMem/utils.go create mode 100644 winutils/winutils.go diff --git a/constants.go b/constants.go new file mode 100644 index 0000000..0d59b60 --- /dev/null +++ b/constants.go @@ -0,0 +1,117 @@ +package main + +type Msg struct { + Cmd int + P1 int32 + P2 int32 + P3 int32 +} + +const ( + SimStatusUrl string = "http://127.0.0.1:32034/get_sim_status?object=simStatus" + IRSDK_DATAVALIDEVENTNAME string = "Local\\IRSDKDataValidEvent" + IRSDK_MEMMAPFILENAME string = "Local\\IRSDKMemMapFileName" + IRSDK_BROADCASTMSGNAME string = "IRSDK_BROADCASTMSG" + fileMapSize uint32 = 1164 * 1024 + connTimeout int64 = 30 + stConnected int = 1 +) + +const ( + BroadcastCamSwitchPos int = 0 // car position, group, camera + BroadcastCamSwitchNum int = 1 // driver #, group, camera + BroadcastCamSetState int = 2 // irsdk_CameraState, unused, unused + BroadcastReplaySetPlaySpeed int = 3 // speed, slowMotion, unused + BroadcastReplaySetPlayPosition int = 4 // irsdk_RpyPosMode, Frame Number (high, low) + BroadcastReplaySearch int = 5 // irsdk_RpySrchMode, unused, unused + BroadcastReplaySetState int = 6 // irsdk_RpyStateMode, unused, unused + BroadcastReloadTextures int = 7 // irsdk_ReloadTexturesMode, carIdx, unused + BroadcastChatComand int = 8 // irsdk_ChatCommandMode, subCommand, unused + BroadcastPitCommand int = 9 // irsdk_PitCommandMode, parameter + BroadcastTelemCommand int = 10 // irsdk_TelemCommandMode, unused, unused + BroadcastFFBCommand int = 11 // irsdk_FFBCommandMode, value (float, high, low) + BroadcastReplaySearchSessionTime int = 12 // sessionNum, sessionTimeMS (high, low) + BroadcastLast int = 13 // unused placeholder +) + +const ( + ChatCommandMacro int = 0 // pass in a number from 1-15 representing the chat macro to launch + ChatCommandBeginChat int = 1 // Open up a new chat window + ChatCommandReply int = 2 // Reply to last private chat + ChatCommandCancel int = 3 // Close chat window +) + +// this only works when the driver is in the car +const ( + PitCommandClear int = 0 // Clear all pit checkboxes + PitCommandWS int = 1 // Clean the winshield, using one tear off + PitCommandFuel int = 2 // Add fuel, optionally specify the amount to add in liters or pass '0' to use existing amount + PitCommandLF int = 3 // Change the left front tire, optionally specifying the pressure in KPa or pass '0' to use existing pressure + PitCommandRF int = 4 // right front + PitCommandLR int = 5 // left rear + PitCommandRR int = 6 // right rear + PitCommandClearTires int = 7 // Clear tire pit checkboxes + PitCommandFR int = 8 // Request a fast repair + PitCommandClearWS int = 9 // Uncheck Clean the winshield checkbox + PitCommandClearFR int = 10 // Uncheck request a fast repair + PitCommandClearFuel int = 11 // Uncheck add fuel +) + +// You can call this any time, but telemtry only records when driver is in there car +const ( + TelemCommandStop int = 0 // Turn telemetry recording off + TelemCommandStart int = 1 // Turn telemetry recording on + TelemCommandRestart int = 2 // Write current file to disk and start a new one +) + +const ( + RpyStateEraseTape int = 0 // clear any data in the replay tape + RpyStateLast int = 1 // unused place holder +) + +const ( + ReloadTexturesAll int = 0 // reload all textuers + ReloadTexturesCarIdx int = 1 // reload only textures for the specific carIdx +) + +// Search replay tape for events +const ( + RpySrchToStart int = 0 + RpySrchToEnd int = 1 + RpySrchPrevSession int = 2 + RpySrchNextSession int = 3 + RpySrchPrevLap int = 4 + RpySrchNextLap int = 5 + RpySrchPrevFrame int = 6 + RpySrchNextFrame int = 7 + RpySrchPrevIncident int = 8 + RpySrchNextIncident int = 9 + RpySrchLast int = 10 // unused placeholder +) + +const ( + RpyPosBegin int = 0 + RpyPosCurrent int = 1 + RpyPosEnd int = 2 + RpyPosLast int = 3 // unused placeholder +) + +// You can call this any time +const ( + FFBCommandMaxForce int = 0 // Set the maximum force when mapping steering torque force to direct input units (float in Nm) + FFBCommandLast int = 1 // unused placeholder +) + +// irsdk_BroadcastCamSwitchPos or irsdk_BroadcastCamSwitchNum camera focus defines +// pass these in for the first parameter to select the 'focus at' types in the camera system. +const ( + csFocusAtIncident int = -3 + csFocusAtLeader int = -2 + csFocusAtExiting int = -1 + csFocusAtDriver int = 0 // ctFocusAtDriver + car number... +) + +// Camera positions +const ( +// CamNose +) diff --git a/diskSubHeader.go b/diskSubHeader.go index 5250d7a..2d233e4 100644 --- a/diskSubHeader.go +++ b/diskSubHeader.go @@ -1,47 +1,47 @@ -package ibtReader - -import ( - "bytes" - "encoding/binary" - "fmt" -) - -const ( - SubHeaderSize = 32 // SubHeaderSize is the size of the subheader -) - -// DiskSubHeader represents the IBT sub headers -type DiskSubHeader struct { - StartDate int64 // StartDate represents the start date of the telemetry - StartTime float64 // StartTime of file relative to start of session - EndTime float64 // EndTime of file relative to start of session - LapCount int32 // LapCount represents the total number laps - RecordCount int32 // RecordCount holds the number of data frames -} - -// parseTelemetrySubHeader will return a pointer to a DiskSubHeader variable -// or nil if an error occurs. In which case the error return value is more -// valuable -func parseTelemetrySubHeader(buf [SubHeaderSize]byte) (*DiskSubHeader, error) { - dst := DiskSubHeader{} - err := binary.Read(bytes.NewBuffer(buf[:]), binary.LittleEndian, &dst) - if err != nil { - return nil, err - } - - return &dst, nil -} - -// ToString renders a string showing the values of the struct -func (d *DiskSubHeader) ToString() string { - return fmt.Sprintf( - "StartDate: %13d (0x%04x)\n"+ - "StartTime: %13f (0x%08x)\n"+ - "EndTime: %13f (0x%08x)\n"+ - "LapCount: %13d (0x%04x)\n"+ - "RecordCount: %13d (0x%04x)\n", - d.StartDate, d.StartDate, d.StartTime, d.StartTime, - d.EndTime, d.EndTime, d.LapCount, d.LapCount, - d.RecordCount, d.RecordCount, - ) -} +package main + +import ( + "bytes" + "encoding/binary" + "fmt" +) + +const ( + SubHeaderSize = 32 // SubHeaderSize is the size of the subheader +) + +// DiskSubHeader represents the IBT sub headers +type DiskSubHeader struct { + StartDate int64 // StartDate represents the start date of the telemetry + StartTime float64 // StartTime of file relative to start of session + EndTime float64 // EndTime of file relative to start of session + LapCount int32 // LapCount represents the total number laps + RecordCount int32 // RecordCount holds the number of data frames +} + +// parseTelemetrySubHeader will return a pointer to a DiskSubHeader variable +// or nil if an error occurs. In which case the error return value is more +// valuable +func parseTelemetrySubHeader(buf [SubHeaderSize]byte) (*DiskSubHeader, error) { + dst := DiskSubHeader{} + err := binary.Read(bytes.NewBuffer(buf[:]), binary.LittleEndian, &dst) + if err != nil { + return nil, err + } + + return &dst, nil +} + +// ToString renders a string showing the values of the struct +func (d *DiskSubHeader) ToString() string { + return fmt.Sprintf( + "StartDate: %13d (0x%04x)\n"+ + "StartTime: %13f (0x%08x)\n"+ + "EndTime: %13f (0x%08x)\n"+ + "LapCount: %13d (0x%04x)\n"+ + "RecordCount: %13d (0x%04x)\n", + d.StartDate, d.StartDate, d.StartTime, d.StartTime, + d.EndTime, d.EndTime, d.LapCount, d.LapCount, + d.RecordCount, d.RecordCount, + ) +} diff --git a/go.mod b/go.mod index eb2dc25..1f03bf6 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,8 @@ -module esilvalabs.org/ibtReader.git +module ibtReader go 1.23.2 -require gopkg.in/yaml.v3 v3.0.1 +require ( + golang.org/x/sys v0.26.0 + gopkg.in/yaml.v3 v3.0.1 +) diff --git a/go.sum b/go.sum index a62c313..934da4d 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/headers.go b/headers.go index 1587b17..f89b110 100644 --- a/headers.go +++ b/headers.go @@ -1,80 +1,80 @@ -package ibtReader - -import ( - "bytes" - "encoding/binary" - "fmt" -) - -const ( - FileHeaderSize = 112 // FileHeaderSize is the size of the headers - HeaderSize = 4 // HeaderSize is the size of a single header -) - -// TelemetryHeaders struct to hold an IBT file's headers -type TelemetryHeaders struct { - Version int32 - // Status of 1 indicates a completed session and status of 0 a live session - Status int32 - // TickRate indicates the frequency of writes (usually 60) - TickRate int32 - // SessionInfoUpdate indicates the number of times the SessionInfo was - // updated. 0 for finished sessions and >1 for active sessions - SessionInfoUpdate int32 - // SessionInfoLength is the length of the session info buffer - SessionInfoLength int32 - // SessionInfoOffset is the offset of the session info in the buffer - SessionInfoOffset int32 - // NumVars is the number of variables in each input - NumVars int32 - // VarHeaderOffset is the offset of the VarHeader - VarHeaderOffset int32 - // NumBuf will be 1 for static files and 3 for live telemetry files - NumBuf int32 - // BufLen is the length for parsing VarHeader values - BufLen int32 - // Padding - Padding [12]byte - // I still don't know what this is: - BufOffset int32 -} - -// ToString renders a string showing the values of the struct -func (th *TelemetryHeaders) ToString() string { - return fmt.Sprintf( - "Version: %5d (0x%04x)\n"+ - "Status: %5d (0x%04x)\n"+ - "TickRate: %5d (0x%04x)\n"+ - "SIUpdate: %5d (0x%04x)\n"+ - "SILength: %5d (0x%04x)\n"+ - "SIOffset: %5d (0x%04x)\n"+ - "NumVars: %5d (0x%04x)\n"+ - "VarHeaderOffset: %5d (0x%04x)\n"+ - "NumBuf: %5d (0x%04x)\n"+ - "BufLen: %5d (0x%04x)\n"+ - "BufOffset: %5d (0x%04x)\n", - th.Version, th.Version, th.Status, th.Status, th.TickRate, th.TickRate, - th.SessionInfoUpdate, th.SessionInfoUpdate, - th.SessionInfoLength, th.SessionInfoLength, - th.SessionInfoOffset, th.SessionInfoOffset, - th.NumVars, th.NumVars, th.VarHeaderOffset, th.VarHeaderOffset, - 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) { - if len(buf)%HeaderSize != 0 { - return nil, fmt.Errorf("buffer must be multiple of size: %d", HeaderSize) - } - - 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 -} +package main + +import ( + "bytes" + "encoding/binary" + "fmt" +) + +const ( + FileHeaderSize = 112 // FileHeaderSize is the size of the headers + HeaderSize = 4 // HeaderSize is the size of a single header +) + +// TelemetryHeaders struct to hold an IBT file's headers +type TelemetryHeaders struct { + Version int32 + // Status of 1 indicates a completed session and status of 0 a live session + Status int32 + // TickRate indicates the frequency of writes (usually 60) + TickRate int32 + // SessionInfoUpdate indicates the number of times the SessionInfo was + // updated. 0 for finished sessions and >1 for active sessions + SessionInfoUpdate int32 + // SessionInfoLength is the length of the session info buffer + SessionInfoLength int32 + // SessionInfoOffset is the offset of the session info in the buffer + SessionInfoOffset int32 + // NumVars is the number of variables in each input + NumVars int32 + // VarHeaderOffset is the offset of the VarHeader + VarHeaderOffset int32 + // NumBuf will be 1 for static files and 3 for live telemetry files + NumBuf int32 + // BufLen is the length for parsing VarHeader values + BufLen int32 + // Padding + Padding [12]byte + // I still don't know what this is: + BufOffset int32 +} + +// ToString renders a string showing the values of the struct +func (th *TelemetryHeaders) ToString() string { + return fmt.Sprintf( + "Version: %5d (0x%04x)\n"+ + "Status: %5d (0x%04x)\n"+ + "TickRate: %5d (0x%04x)\n"+ + "SIUpdate: %5d (0x%04x)\n"+ + "SILength: %5d (0x%04x)\n"+ + "SIOffset: %5d (0x%04x)\n"+ + "NumVars: %5d (0x%04x)\n"+ + "VarHeaderOffset: %5d (0x%04x)\n"+ + "NumBuf: %5d (0x%04x)\n"+ + "BufLen: %5d (0x%04x)\n"+ + "BufOffset: %5d (0x%04x)\n", + th.Version, th.Version, th.Status, th.Status, th.TickRate, th.TickRate, + th.SessionInfoUpdate, th.SessionInfoUpdate, + th.SessionInfoLength, th.SessionInfoLength, + th.SessionInfoOffset, th.SessionInfoOffset, + th.NumVars, th.NumVars, th.VarHeaderOffset, th.VarHeaderOffset, + 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) { + if len(buf)%HeaderSize != 0 { + return nil, fmt.Errorf("buffer must be multiple of size: %d", HeaderSize) + } + + 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 +} diff --git a/irsdk.go b/irsdk.go index 7a055aa..5267569 100644 --- a/irsdk.go +++ b/irsdk.go @@ -1,136 +1,154 @@ -// Package IbtReader is all you need for you iRacing telemetry parsing -package ibtReader - -import ( - "fmt" - "io" -) - -const ( - ibtFile = "./telemetryFiles/mx5_2016Okayama_full_2024_10_19_22_02_12.ibt" -) - -// Reader is an interface to represent the readable data that can be either -// a .ibt file (or live data, hopefully) -type Reader interface { - io.Reader - io.ReaderAt - io.ReadCloser -} - -// IBT struct will hold the relevant data for a given IBT file -type IBT struct { - File Reader // Source of the data - Headers *TelemetryHeaders // IBT file Headers - SubHeaders *DiskSubHeader // IBT file Sub Headers - SessionInfo *SessionInfoYAML // IBT file Session Info - Vars *TelemetryVars // Vars will hold the telemetry data - Tick int32 // Tick holds the cound of the reads -} - -// Init serves to initialize and get a hold of a IBT struct -func Init(f Reader) (*IBT, error) { - // Read the header of the file - var err error - ibt := IBT{ - File: f, - Vars: &TelemetryVars{}, - } - - // Read the file headers - var headerRaw [FileHeaderSize]byte - _, err = ibt.File.ReadAt(headerRaw[:], 0) - if err != nil { - return nil, fmt.Errorf("Failed to read headers from file: %v", 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 - var subheaderRaw [SubHeaderSize]byte - _, err = ibt.File.ReadAt(subheaderRaw[:], 112) - if err != nil { - return nil, fmt.Errorf("Failed to read disk subheaders from file: %v", 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 - sessionInfoStringRaw := make([]byte, ibt.Headers.SessionInfoLength) - _, err = ibt.File.ReadAt(sessionInfoStringRaw, int64(ibt.Headers.SessionInfoOffset)) - if err != nil { - return nil, fmt.Errorf("Failed to read sessionInfoString from file: %v", err) - } - ibt.SessionInfo, err = parseSessionInfo(sessionInfoStringRaw) - if err != nil { - return nil, fmt.Errorf("Unable to parse SessionInfoString from file: %v", err) - } - - // Read the telemetry vars info - err = ibt.readVariablerHeaders() - if err != nil { - return nil, fmt.Errorf("Unable to parser variable headers from file: %v", err) - } - - return &ibt, nil -} - -func msToKph(v float32) int { - return int((3600 * v) / 1000) -} - -// 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) -// fmt.Printf("%s\n", ibt.Headers.ToString()) -// fmt.Printf("%s\n", ibt.SubHeaders.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 := ibt.Update() -// -// 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.Tick/60, msToKph(val.Value.(float32))) -// } else { -// fmt.Printf("\r%d %s", ibt.Tick/60, "KEY DOESN'T EXIST") -// } -// } -// -// if !res { -// fmt.Println("\nEnd of file found...") -// break -// } -// } -// -// fmt.Printf("%d\n", ibt.Tick) -// } +// Package IbtReader is all you need for you iRacing telemetry parsing +package main + +import ( + "fmt" + "io" + "log" + "time" + + "ibtReader/sharedMem" +) + +const ( + ibtFile = "./telemetryFiles/mx5_2016Okayama_full_2024_10_19_22_02_12.ibt" +) + +// Reader is an interface to represent the readable data that can be either +// a .ibt file (or live data, hopefully) +type Reader interface { + io.Reader + io.ReaderAt + io.ReadCloser +} + +// IBT struct will hold the relevant data for a given IBT file +type IBT struct { + File Reader // Source of the data + Headers *TelemetryHeaders // IBT file Headers + SubHeaders *DiskSubHeader // IBT file Sub Headers + SessionInfo *SessionInfoYAML // IBT file Session Info + Vars *TelemetryVars // Vars will hold the telemetry data + Tick int32 // Tick holds the cound of the reads + LiveData bool +} + +// Init serves to initialize and get a hold of a IBT struct +func Init(f Reader) (*IBT, error) { + // Read the header of the file + var err error + ibt := IBT{ + File: f, + Vars: &TelemetryVars{}, + LiveData: false, + } + + if ibt.File == nil { + // User is requesting us to read live data - present in the mem map file + ibt.File, err = sharedMem.Open(IRSDK_MEMMAPFILENAME, fileMapSize) + if err != nil { + return nil, fmt.Errorf("Failed to open memory mapped file: %v", err) + } + ibt.LiveData = true + } + + // Read the file headers + var headerRaw [FileHeaderSize]byte + _, err = ibt.File.ReadAt(headerRaw[:], 0) + if err != nil { + return nil, fmt.Errorf("Failed to read headers from file: %v", 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 + var subheaderRaw [SubHeaderSize]byte + _, err = ibt.File.ReadAt(subheaderRaw[:], 112) + if err != nil { + return nil, fmt.Errorf("Failed to read disk subheaders from file: %v", 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 + sessionInfoStringRaw := make([]byte, ibt.Headers.SessionInfoLength) + _, err = ibt.File.ReadAt(sessionInfoStringRaw, int64(ibt.Headers.SessionInfoOffset)) + if err != nil { + return nil, fmt.Errorf("Failed to read sessionInfoString from file: %v", err) + } + ibt.SessionInfo, err = parseSessionInfo(sessionInfoStringRaw) + if err != nil { + return nil, fmt.Errorf("Unable to parse SessionInfoString from file: %v", err) + } + + // Read the telemetry vars info + err = ibt.readVariablerHeaders() + if err != nil { + return nil, fmt.Errorf("Unable to parser variable headers from file: %v", err) + } + + return &ibt, nil +} + +func msToKph(v float32) int { + return int((3600 * v) / 1000) +} + +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(nil) + 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()) + + // 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 := ibt.Update() + + 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.Tick/60, msToKph(val.Value.(float32))) + } else { + fmt.Printf("\r%d %s", ibt.Tick/60, "KEY DOESN'T EXIST") + } + } + + if !res { + fmt.Println("\nEnd of file found...") + break + } + } + + fmt.Printf("%d\n", ibt.Tick) +} diff --git a/sessionInfo.go b/sessionInfo.go index 1003f45..6cd7932 100644 --- a/sessionInfo.go +++ b/sessionInfo.go @@ -1,323 +1,323 @@ -package ibtReader - -import ( - "encoding/json" - - "gopkg.in/yaml.v3" -) - -// SessionInfoYAML is a string with session info in the IBT file -type SessionInfoYAML struct { - WeekendInfo struct { - TrackName string `yaml:"TrackName"` - TrackID int `yaml:"TrackID"` - TrackLength string `yaml:"TrackLength"` - TrackDisplayName string `yaml:"TrackDisplayName"` - TrackDisplayShortName string `yaml:"TrackDisplayShortName"` - TrackConfigName string `yaml:"TrackConfigName"` - TrackCity string `yaml:"TrackCity"` - TrackCountry string `yaml:"TrackCountry"` - TrackAltitude string `yaml:"TrackAltitude"` - TrackLatitude string `yaml:"TrackLatitude"` - TrackLongitude string `yaml:"TrackLongitude"` - TrackNorthOffset string `yaml:"TrackNorthOffset"` - TrackNumTurns int `yaml:"TrackNumTurns"` - TrackPitSpeedLimit string `yaml:"TrackPitSpeedLimit"` - TrackType string `yaml:"TrackType"` - TrackDirection string `yaml:"TrackDirection"` - TrackWeatherType string `yaml:"TrackWeatherType"` - TrackSkies string `yaml:"TrackSkies"` - TrackSurfaceTemp string `yaml:"TrackSurfaceTemp"` - TrackAirTemp string `yaml:"TrackAirTemp"` - TrackAirPressure string `yaml:"TrackAirPressure"` - TrackWindVel string `yaml:"TrackWindVel"` - TrackWindDir string `yaml:"TrackWindDir"` - TrackRelativeHumidity string `yaml:"TrackRelativeHumidity"` - TrackFogLevel string `yaml:"TrackFogLevel"` - TrackCleanup int `yaml:"TrackCleanup"` - TrackDynamicTrack int `yaml:"TrackDynamicTrack"` - TrackVersion string `yaml:"TrackVersion"` - SeriesID int `yaml:"SeriesID"` - SeasonID int `yaml:"SeasonID"` - SessionID int `yaml:"SessionID"` - SubSessionID int `yaml:"SubSessionID"` - LeagueID int `yaml:"LeagueID"` - Official int `yaml:"Official"` - RaceWeek int `yaml:"RaceWeek"` - EventType string `yaml:"EventType"` - Category string `yaml:"Category"` - SimMode string `yaml:"SimMode"` - TeamRacing int `yaml:"TeamRacing"` - MinDrivers int `yaml:"MinDrivers"` - MaxDrivers int `yaml:"MaxDrivers"` - DCRuleSet string `yaml:"DCRuleSet"` - QualifierMustStartRace int `yaml:"QualifierMustStartRace"` - NumCarClasses int `yaml:"NumCarClasses"` - NumCarTypes int `yaml:"NumCarTypes"` - HeatRacing int `yaml:"HeatRacing"` - BuildType string `yaml:"BuildType"` - BuildTarget string `yaml:"BuildTarget"` - BuildVersion string `yaml:"BuildVersion"` - WeekendOptions struct { - NumStarters int `yaml:"NumStarters"` - StartingGrid string `yaml:"StartingGrid"` - QualifyScoring string `yaml:"QualifyScoring"` - CourseCautions string `yaml:"CourseCautions"` - StandingStart int `yaml:"StandingStart"` - ShortParadeLap int `yaml:"ShortParadeLap"` - Restarts string `yaml:"Restarts"` - WeatherType string `yaml:"WeatherType"` - Skies string `yaml:"Skies"` - WindDirection string `yaml:"WindDirection"` - WindSpeed string `yaml:"WindSpeed"` - WeatherTemp string `yaml:"WeatherTemp"` - RelativeHumidity string `yaml:"RelativeHumidity"` - FogLevel string `yaml:"FogLevel"` - TimeOfDay string `yaml:"TimeOfDay"` - Date string `yaml:"Date"` - EarthRotationSpeedupFactor int `yaml:"EarthRotationSpeedupFactor"` - Unofficial int `yaml:"Unofficial"` - CommercialMode string `yaml:"CommercialMode"` - NightMode string `yaml:"NightMode"` - IsFixedSetup int `yaml:"IsFixedSetup"` - StrictLapsChecking string `yaml:"StrictLapsChecking"` - HasOpenRegistration int `yaml:"HasOpenRegistration"` - HardcoreLevel int `yaml:"HardcoreLevel"` - NumJokerLaps int `yaml:"NumJokerLaps"` - IncidentLimit string `yaml:"IncidentLimit"` - FastRepairsLimit string `yaml:"FastRepairsLimit"` - GreenWhiteCheckeredLimit int `yaml:"GreenWhiteCheckeredLimit"` - } `yaml:"WeekendOptions"` - TelemetryOptions struct { - TelemetryDiskFile string `yaml:"TelemetryDiskFile"` - } `yaml:"TelemetryOptions"` - } `yaml:"WeekendInfo"` - SessionInfo struct { - Sessions []struct { - SessionNum int `yaml:"SessionNum"` - SessionLaps string `yaml:"SessionLaps"` - SessionTime string `yaml:"SessionTime"` - SessionNumLapsToAvg int `yaml:"SessionNumLapsToAvg"` - SessionType string `yaml:"SessionType"` - SessionTrackRubberState string `yaml:"SessionTrackRubberState"` - SessionName string `yaml:"SessionName"` - SessionSubType interface{} `yaml:"SessionSubType"` - SessionSkipped int `yaml:"SessionSkipped"` - SessionRunGroupsUsed int `yaml:"SessionRunGroupsUsed"` - ResultsPositions interface{} `yaml:"ResultsPositions"` - ResultsFastestLap []struct { - CarIdx int `yaml:"CarIdx"` - FastestLap int `yaml:"FastestLap"` - FastestTime int `yaml:"FastestTime"` - } `yaml:"ResultsFastestLap"` - ResultsAverageLapTime int `yaml:"ResultsAverageLapTime"` - ResultsNumCautionFlags int `yaml:"ResultsNumCautionFlags"` - ResultsNumCautionLaps int `yaml:"ResultsNumCautionLaps"` - ResultsNumLeadChanges int `yaml:"ResultsNumLeadChanges"` - ResultsLapsComplete int `yaml:"ResultsLapsComplete"` - ResultsOfficial int `yaml:"ResultsOfficial"` - } `yaml:"Sessions"` - } `yaml:"SessionInfo"` - CameraInfo struct { - Groups []struct { - GroupNum int `yaml:"GroupNum"` - GroupName string `yaml:"GroupName"` - Cameras []struct { - CameraNum int `yaml:"CameraNum"` - CameraName string `yaml:"CameraName"` - } `yaml:"Cameras"` - IsScenic bool `yaml:"IsScenic,omitempty"` - } `yaml:"Groups"` - } `yaml:"CameraInfo"` - RadioInfo struct { - SelectedRadioNum int `yaml:"SelectedRadioNum"` - Radios []struct { - RadioNum int `yaml:"RadioNum"` - HopCount int `yaml:"HopCount"` - NumFrequencies int `yaml:"NumFrequencies"` - TunedToFrequencyNum int `yaml:"TunedToFrequencyNum"` - ScanningIsOn int `yaml:"ScanningIsOn"` - Frequencies []struct { - FrequencyNum int `yaml:"FrequencyNum"` - FrequencyName string `yaml:"FrequencyName"` - Priority int `yaml:"Priority"` - CarIdx int `yaml:"CarIdx"` - EntryIdx int `yaml:"EntryIdx"` - ClubID int `yaml:"ClubID"` - CanScan int `yaml:"CanScan"` - CanSquawk int `yaml:"CanSquawk"` - Muted int `yaml:"Muted"` - IsMutable int `yaml:"IsMutable"` - IsDeletable int `yaml:"IsDeletable"` - } `yaml:"Frequencies"` - } `yaml:"Radios"` - } `yaml:"RadioInfo"` - DriverInfo struct { - DriverCarIdx int `yaml:"DriverCarIdx"` - DriverUserID int `yaml:"DriverUserID"` - PaceCarIdx int `yaml:"PaceCarIdx"` - DriverHeadPosX float64 `yaml:"DriverHeadPosX"` - DriverHeadPosY float64 `yaml:"DriverHeadPosY"` - DriverHeadPosZ float64 `yaml:"DriverHeadPosZ"` - DriverCarIdleRPM float64 `yaml:"DriverCarIdleRPM"` - DriverCarRedLine float64 `yaml:"DriverCarRedLine"` - DriverCarEngCylinderCount int `yaml:"DriverCarEngCylinderCount"` - DriverCarFuelKgPerLtr float64 `yaml:"DriverCarFuelKgPerLtr"` - DriverCarFuelMaxLtr float64 `yaml:"DriverCarFuelMaxLtr"` - DriverCarMaxFuelPct float64 `yaml:"DriverCarMaxFuelPct"` - DriverCarGearNumForward int `yaml:"DriverCarGearNumForward"` - DriverCarGearNeutral int `yaml:"DriverCarGearNeutral"` - DriverCarGearReverse int `yaml:"DriverCarGearReverse"` - DriverCarSLFirstRPM float64 `yaml:"DriverCarSLFirstRPM"` - DriverCarSLShiftRPM float64 `yaml:"DriverCarSLShiftRPM"` - DriverCarSLLastRPM float64 `yaml:"DriverCarSLLastRPM"` - DriverCarSLBlinkRPM float64 `yaml:"DriverCarSLBlinkRPM"` - DriverCarVersion string `yaml:"DriverCarVersion"` - DriverPitTrkPct float64 `yaml:"DriverPitTrkPct"` - DriverCarEstLapTime float64 `yaml:"DriverCarEstLapTime"` - DriverSetupName string `yaml:"DriverSetupName"` - DriverSetupIsModified int `yaml:"DriverSetupIsModified"` - DriverSetupLoadTypeName string `yaml:"DriverSetupLoadTypeName"` - DriverSetupPassedTech int `yaml:"DriverSetupPassedTech"` - DriverIncidentCount int `yaml:"DriverIncidentCount"` - Drivers []Driver `yaml:"Drivers"` - } `yaml:"DriverInfo"` - SplitTimeInfo struct { - Sectors []struct { - SectorNum int `yaml:"SectorNum"` - SectorStartPct float64 `yaml:"SectorStartPct"` - } `yaml:"Sectors"` - } `yaml:"SplitTimeInfo"` - CarSetup struct { - UpdateCount int `yaml:"UpdateCount"` - TiresAero struct { - LeftFront struct { - StartingPressure string `yaml:"StartingPressure"` - LastHotPressure string `yaml:"LastHotPressure"` - LastTempsOMI string `yaml:"LastTempsOMI"` - TreadRemaining string `yaml:"TreadRemaining"` - } `yaml:"LeftFront"` - LeftRear struct { - StartingPressure string `yaml:"StartingPressure"` - LastHotPressure string `yaml:"LastHotPressure"` - LastTempsOMI string `yaml:"LastTempsOMI"` - TreadRemaining string `yaml:"TreadRemaining"` - } `yaml:"LeftRear"` - RightFront struct { - StartingPressure string `yaml:"StartingPressure"` - LastHotPressure string `yaml:"LastHotPressure"` - LastTempsIMO string `yaml:"LastTempsIMO"` - TreadRemaining string `yaml:"TreadRemaining"` - } `yaml:"RightFront"` - RightRear struct { - StartingPressure string `yaml:"StartingPressure"` - LastHotPressure string `yaml:"LastHotPressure"` - LastTempsIMO string `yaml:"LastTempsIMO"` - TreadRemaining string `yaml:"TreadRemaining"` - } `yaml:"RightRear"` - } `yaml:"TiresAero"` - Chassis struct { - Front struct { - ArbSetting int `yaml:"ArbSetting"` - ToeIn string `yaml:"ToeIn"` - FuelLevel string `yaml:"FuelLevel"` - CrossWeight string `yaml:"CrossWeight"` - } `yaml:"Front"` - LeftFront struct { - CornerWeight string `yaml:"CornerWeight"` - RideHeight string `yaml:"RideHeight"` - SpringPerchOffset string `yaml:"SpringPerchOffset"` - Camber string `yaml:"Camber"` - } `yaml:"LeftFront"` - LeftRear struct { - CornerWeight string `yaml:"CornerWeight"` - RideHeight string `yaml:"RideHeight"` - SpringPerchOffset string `yaml:"SpringPerchOffset"` - Camber string `yaml:"Camber"` - ToeIn string `yaml:"ToeIn"` - } `yaml:"LeftRear"` - InCarDials struct { - DisplayPage string `yaml:"DisplayPage"` - BrakePressureBias string `yaml:"BrakePressureBias"` - } `yaml:"InCarDials"` - RightFront struct { - CornerWeight string `yaml:"CornerWeight"` - RideHeight string `yaml:"RideHeight"` - SpringPerchOffset string `yaml:"SpringPerchOffset"` - Camber string `yaml:"Camber"` - } `yaml:"RightFront"` - RightRear struct { - CornerWeight string `yaml:"CornerWeight"` - RideHeight string `yaml:"RideHeight"` - SpringPerchOffset string `yaml:"SpringPerchOffset"` - Camber string `yaml:"Camber"` - ToeIn string `yaml:"ToeIn"` - } `yaml:"RightRear"` - Rear struct { - ArbSetting int `yaml:"ArbSetting"` - WingSetting int `yaml:"WingSetting"` - } `yaml:"Rear"` - } `yaml:"Chassis"` - } `yaml:"CarSetup"` -} - -// Driver ... -type Driver struct { - CarIdx int `yaml:"CarIdx"` - UserName string `yaml:"UserName"` - AbbrevName string `yaml:"AbbrevName"` - Initials string `yaml:"Initials"` - UserID int `yaml:"UserID"` - TeamID int `yaml:"TeamID"` - TeamName string `yaml:"TeamName"` - CarNumber string `yaml:"CarNumber"` - CarNumberRaw int `yaml:"CarNumberRaw"` - CarPath string `yaml:"CarPath"` - CarClassID int `yaml:"CarClassID"` - CarID int `yaml:"CarID"` - CarIsPaceCar int `yaml:"CarIsPaceCar"` - CarIsAI int `yaml:"CarIsAI"` - CarScreenName string `yaml:"CarScreenName"` - CarScreenNameShort string `yaml:"CarScreenNameShort"` - CarClassShortName string `yaml:"CarClassShortName"` - CarClassRelSpeed int `yaml:"CarClassRelSpeed"` - CarClassLicenseLevel int `yaml:"CarClassLicenseLevel"` - CarClassMaxFuelPct string `yaml:"CarClassMaxFuelPct"` - CarClassWeightPenalty string `yaml:"CarClassWeightPenalty"` - CarClassPowerAdjust string `yaml:"CarClassPowerAdjust"` - CarClassDryTireSetLimit string `yaml:"CarClassDryTireSetLimit"` - CarClassColor int `yaml:"CarClassColor"` - CarClassEstLapTime float64 `yaml:"CarClassEstLapTime"` - IRating int `yaml:"IRating"` - LicLevel int `yaml:"LicLevel"` - LicSubLevel int `yaml:"LicSubLevel"` - LicString string `yaml:"LicString"` - LicColor string `yaml:"LicColor"` - IsSpectator int `yaml:"IsSpectator"` - CarDesignStr string `yaml:"CarDesignStr"` - HelmetDesignStr string `yaml:"HelmetDesignStr"` - SuitDesignStr string `yaml:"SuitDesignStr"` - CarNumberDesignStr string `yaml:"CarNumberDesignStr"` - CarSponsor1 int `yaml:"CarSponsor_1"` - CarSponsor2 int `yaml:"CarSponsor_2"` - CurDriverIncidentCount int `yaml:"CurDriverIncidentCount"` - TeamIncidentCount int `yaml:"TeamIncidentCount"` -} - -// parseSessionInfo will parse the sessionInfo buffer into the SessionInfoYAML -// struct -func parseSessionInfo(data []byte) (*SessionInfoYAML, error) { - var sessionInfo SessionInfoYAML - err := yaml.Unmarshal(data, &sessionInfo) - if err != nil { - return nil, err - } - - return &sessionInfo, nil -} - -// ToString will return a readable string of the struct -func (s *SessionInfoYAML) ToString() string { - stringified, _ := json.MarshalIndent(s, "", " ") - return string(stringified) -} +package main + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// SessionInfoYAML is a string with session info in the IBT file +type SessionInfoYAML struct { + WeekendInfo struct { + TrackName string `yaml:"TrackName"` + TrackID int `yaml:"TrackID"` + TrackLength string `yaml:"TrackLength"` + TrackDisplayName string `yaml:"TrackDisplayName"` + TrackDisplayShortName string `yaml:"TrackDisplayShortName"` + TrackConfigName string `yaml:"TrackConfigName"` + TrackCity string `yaml:"TrackCity"` + TrackCountry string `yaml:"TrackCountry"` + TrackAltitude string `yaml:"TrackAltitude"` + TrackLatitude string `yaml:"TrackLatitude"` + TrackLongitude string `yaml:"TrackLongitude"` + TrackNorthOffset string `yaml:"TrackNorthOffset"` + TrackNumTurns int `yaml:"TrackNumTurns"` + TrackPitSpeedLimit string `yaml:"TrackPitSpeedLimit"` + TrackType string `yaml:"TrackType"` + TrackDirection string `yaml:"TrackDirection"` + TrackWeatherType string `yaml:"TrackWeatherType"` + TrackSkies string `yaml:"TrackSkies"` + TrackSurfaceTemp string `yaml:"TrackSurfaceTemp"` + TrackAirTemp string `yaml:"TrackAirTemp"` + TrackAirPressure string `yaml:"TrackAirPressure"` + TrackWindVel string `yaml:"TrackWindVel"` + TrackWindDir string `yaml:"TrackWindDir"` + TrackRelativeHumidity string `yaml:"TrackRelativeHumidity"` + TrackFogLevel string `yaml:"TrackFogLevel"` + TrackCleanup int `yaml:"TrackCleanup"` + TrackDynamicTrack int `yaml:"TrackDynamicTrack"` + TrackVersion string `yaml:"TrackVersion"` + SeriesID int `yaml:"SeriesID"` + SeasonID int `yaml:"SeasonID"` + SessionID int `yaml:"SessionID"` + SubSessionID int `yaml:"SubSessionID"` + LeagueID int `yaml:"LeagueID"` + Official int `yaml:"Official"` + RaceWeek int `yaml:"RaceWeek"` + EventType string `yaml:"EventType"` + Category string `yaml:"Category"` + SimMode string `yaml:"SimMode"` + TeamRacing int `yaml:"TeamRacing"` + MinDrivers int `yaml:"MinDrivers"` + MaxDrivers int `yaml:"MaxDrivers"` + DCRuleSet string `yaml:"DCRuleSet"` + QualifierMustStartRace int `yaml:"QualifierMustStartRace"` + NumCarClasses int `yaml:"NumCarClasses"` + NumCarTypes int `yaml:"NumCarTypes"` + HeatRacing int `yaml:"HeatRacing"` + BuildType string `yaml:"BuildType"` + BuildTarget string `yaml:"BuildTarget"` + BuildVersion string `yaml:"BuildVersion"` + WeekendOptions struct { + NumStarters int `yaml:"NumStarters"` + StartingGrid string `yaml:"StartingGrid"` + QualifyScoring string `yaml:"QualifyScoring"` + CourseCautions string `yaml:"CourseCautions"` + StandingStart int `yaml:"StandingStart"` + ShortParadeLap int `yaml:"ShortParadeLap"` + Restarts string `yaml:"Restarts"` + WeatherType string `yaml:"WeatherType"` + Skies string `yaml:"Skies"` + WindDirection string `yaml:"WindDirection"` + WindSpeed string `yaml:"WindSpeed"` + WeatherTemp string `yaml:"WeatherTemp"` + RelativeHumidity string `yaml:"RelativeHumidity"` + FogLevel string `yaml:"FogLevel"` + TimeOfDay string `yaml:"TimeOfDay"` + Date string `yaml:"Date"` + EarthRotationSpeedupFactor int `yaml:"EarthRotationSpeedupFactor"` + Unofficial int `yaml:"Unofficial"` + CommercialMode string `yaml:"CommercialMode"` + NightMode string `yaml:"NightMode"` + IsFixedSetup int `yaml:"IsFixedSetup"` + StrictLapsChecking string `yaml:"StrictLapsChecking"` + HasOpenRegistration int `yaml:"HasOpenRegistration"` + HardcoreLevel int `yaml:"HardcoreLevel"` + NumJokerLaps int `yaml:"NumJokerLaps"` + IncidentLimit string `yaml:"IncidentLimit"` + FastRepairsLimit string `yaml:"FastRepairsLimit"` + GreenWhiteCheckeredLimit int `yaml:"GreenWhiteCheckeredLimit"` + } `yaml:"WeekendOptions"` + TelemetryOptions struct { + TelemetryDiskFile string `yaml:"TelemetryDiskFile"` + } `yaml:"TelemetryOptions"` + } `yaml:"WeekendInfo"` + SessionInfo struct { + Sessions []struct { + SessionNum int `yaml:"SessionNum"` + SessionLaps string `yaml:"SessionLaps"` + SessionTime string `yaml:"SessionTime"` + SessionNumLapsToAvg int `yaml:"SessionNumLapsToAvg"` + SessionType string `yaml:"SessionType"` + SessionTrackRubberState string `yaml:"SessionTrackRubberState"` + SessionName string `yaml:"SessionName"` + SessionSubType interface{} `yaml:"SessionSubType"` + SessionSkipped int `yaml:"SessionSkipped"` + SessionRunGroupsUsed int `yaml:"SessionRunGroupsUsed"` + ResultsPositions interface{} `yaml:"ResultsPositions"` + ResultsFastestLap []struct { + CarIdx int `yaml:"CarIdx"` + FastestLap int `yaml:"FastestLap"` + FastestTime int `yaml:"FastestTime"` + } `yaml:"ResultsFastestLap"` + ResultsAverageLapTime int `yaml:"ResultsAverageLapTime"` + ResultsNumCautionFlags int `yaml:"ResultsNumCautionFlags"` + ResultsNumCautionLaps int `yaml:"ResultsNumCautionLaps"` + ResultsNumLeadChanges int `yaml:"ResultsNumLeadChanges"` + ResultsLapsComplete int `yaml:"ResultsLapsComplete"` + ResultsOfficial int `yaml:"ResultsOfficial"` + } `yaml:"Sessions"` + } `yaml:"SessionInfo"` + CameraInfo struct { + Groups []struct { + GroupNum int `yaml:"GroupNum"` + GroupName string `yaml:"GroupName"` + Cameras []struct { + CameraNum int `yaml:"CameraNum"` + CameraName string `yaml:"CameraName"` + } `yaml:"Cameras"` + IsScenic bool `yaml:"IsScenic,omitempty"` + } `yaml:"Groups"` + } `yaml:"CameraInfo"` + RadioInfo struct { + SelectedRadioNum int `yaml:"SelectedRadioNum"` + Radios []struct { + RadioNum int `yaml:"RadioNum"` + HopCount int `yaml:"HopCount"` + NumFrequencies int `yaml:"NumFrequencies"` + TunedToFrequencyNum int `yaml:"TunedToFrequencyNum"` + ScanningIsOn int `yaml:"ScanningIsOn"` + Frequencies []struct { + FrequencyNum int `yaml:"FrequencyNum"` + FrequencyName string `yaml:"FrequencyName"` + Priority int `yaml:"Priority"` + CarIdx int `yaml:"CarIdx"` + EntryIdx int `yaml:"EntryIdx"` + ClubID int `yaml:"ClubID"` + CanScan int `yaml:"CanScan"` + CanSquawk int `yaml:"CanSquawk"` + Muted int `yaml:"Muted"` + IsMutable int `yaml:"IsMutable"` + IsDeletable int `yaml:"IsDeletable"` + } `yaml:"Frequencies"` + } `yaml:"Radios"` + } `yaml:"RadioInfo"` + DriverInfo struct { + DriverCarIdx int `yaml:"DriverCarIdx"` + DriverUserID int `yaml:"DriverUserID"` + PaceCarIdx int `yaml:"PaceCarIdx"` + DriverHeadPosX float64 `yaml:"DriverHeadPosX"` + DriverHeadPosY float64 `yaml:"DriverHeadPosY"` + DriverHeadPosZ float64 `yaml:"DriverHeadPosZ"` + DriverCarIdleRPM float64 `yaml:"DriverCarIdleRPM"` + DriverCarRedLine float64 `yaml:"DriverCarRedLine"` + DriverCarEngCylinderCount int `yaml:"DriverCarEngCylinderCount"` + DriverCarFuelKgPerLtr float64 `yaml:"DriverCarFuelKgPerLtr"` + DriverCarFuelMaxLtr float64 `yaml:"DriverCarFuelMaxLtr"` + DriverCarMaxFuelPct float64 `yaml:"DriverCarMaxFuelPct"` + DriverCarGearNumForward int `yaml:"DriverCarGearNumForward"` + DriverCarGearNeutral int `yaml:"DriverCarGearNeutral"` + DriverCarGearReverse int `yaml:"DriverCarGearReverse"` + DriverCarSLFirstRPM float64 `yaml:"DriverCarSLFirstRPM"` + DriverCarSLShiftRPM float64 `yaml:"DriverCarSLShiftRPM"` + DriverCarSLLastRPM float64 `yaml:"DriverCarSLLastRPM"` + DriverCarSLBlinkRPM float64 `yaml:"DriverCarSLBlinkRPM"` + DriverCarVersion string `yaml:"DriverCarVersion"` + DriverPitTrkPct float64 `yaml:"DriverPitTrkPct"` + DriverCarEstLapTime float64 `yaml:"DriverCarEstLapTime"` + DriverSetupName string `yaml:"DriverSetupName"` + DriverSetupIsModified int `yaml:"DriverSetupIsModified"` + DriverSetupLoadTypeName string `yaml:"DriverSetupLoadTypeName"` + DriverSetupPassedTech int `yaml:"DriverSetupPassedTech"` + DriverIncidentCount int `yaml:"DriverIncidentCount"` + Drivers []Driver `yaml:"Drivers"` + } `yaml:"DriverInfo"` + SplitTimeInfo struct { + Sectors []struct { + SectorNum int `yaml:"SectorNum"` + SectorStartPct float64 `yaml:"SectorStartPct"` + } `yaml:"Sectors"` + } `yaml:"SplitTimeInfo"` + CarSetup struct { + UpdateCount int `yaml:"UpdateCount"` + TiresAero struct { + LeftFront struct { + StartingPressure string `yaml:"StartingPressure"` + LastHotPressure string `yaml:"LastHotPressure"` + LastTempsOMI string `yaml:"LastTempsOMI"` + TreadRemaining string `yaml:"TreadRemaining"` + } `yaml:"LeftFront"` + LeftRear struct { + StartingPressure string `yaml:"StartingPressure"` + LastHotPressure string `yaml:"LastHotPressure"` + LastTempsOMI string `yaml:"LastTempsOMI"` + TreadRemaining string `yaml:"TreadRemaining"` + } `yaml:"LeftRear"` + RightFront struct { + StartingPressure string `yaml:"StartingPressure"` + LastHotPressure string `yaml:"LastHotPressure"` + LastTempsIMO string `yaml:"LastTempsIMO"` + TreadRemaining string `yaml:"TreadRemaining"` + } `yaml:"RightFront"` + RightRear struct { + StartingPressure string `yaml:"StartingPressure"` + LastHotPressure string `yaml:"LastHotPressure"` + LastTempsIMO string `yaml:"LastTempsIMO"` + TreadRemaining string `yaml:"TreadRemaining"` + } `yaml:"RightRear"` + } `yaml:"TiresAero"` + Chassis struct { + Front struct { + ArbSetting int `yaml:"ArbSetting"` + ToeIn string `yaml:"ToeIn"` + FuelLevel string `yaml:"FuelLevel"` + CrossWeight string `yaml:"CrossWeight"` + } `yaml:"Front"` + LeftFront struct { + CornerWeight string `yaml:"CornerWeight"` + RideHeight string `yaml:"RideHeight"` + SpringPerchOffset string `yaml:"SpringPerchOffset"` + Camber string `yaml:"Camber"` + } `yaml:"LeftFront"` + LeftRear struct { + CornerWeight string `yaml:"CornerWeight"` + RideHeight string `yaml:"RideHeight"` + SpringPerchOffset string `yaml:"SpringPerchOffset"` + Camber string `yaml:"Camber"` + ToeIn string `yaml:"ToeIn"` + } `yaml:"LeftRear"` + InCarDials struct { + DisplayPage string `yaml:"DisplayPage"` + BrakePressureBias string `yaml:"BrakePressureBias"` + } `yaml:"InCarDials"` + RightFront struct { + CornerWeight string `yaml:"CornerWeight"` + RideHeight string `yaml:"RideHeight"` + SpringPerchOffset string `yaml:"SpringPerchOffset"` + Camber string `yaml:"Camber"` + } `yaml:"RightFront"` + RightRear struct { + CornerWeight string `yaml:"CornerWeight"` + RideHeight string `yaml:"RideHeight"` + SpringPerchOffset string `yaml:"SpringPerchOffset"` + Camber string `yaml:"Camber"` + ToeIn string `yaml:"ToeIn"` + } `yaml:"RightRear"` + Rear struct { + ArbSetting int `yaml:"ArbSetting"` + WingSetting int `yaml:"WingSetting"` + } `yaml:"Rear"` + } `yaml:"Chassis"` + } `yaml:"CarSetup"` +} + +// Driver ... +type Driver struct { + CarIdx int `yaml:"CarIdx"` + UserName string `yaml:"UserName"` + AbbrevName string `yaml:"AbbrevName"` + Initials string `yaml:"Initials"` + UserID int `yaml:"UserID"` + TeamID int `yaml:"TeamID"` + TeamName string `yaml:"TeamName"` + CarNumber string `yaml:"CarNumber"` + CarNumberRaw int `yaml:"CarNumberRaw"` + CarPath string `yaml:"CarPath"` + CarClassID int `yaml:"CarClassID"` + CarID int `yaml:"CarID"` + CarIsPaceCar int `yaml:"CarIsPaceCar"` + CarIsAI int `yaml:"CarIsAI"` + CarScreenName string `yaml:"CarScreenName"` + CarScreenNameShort string `yaml:"CarScreenNameShort"` + CarClassShortName string `yaml:"CarClassShortName"` + CarClassRelSpeed int `yaml:"CarClassRelSpeed"` + CarClassLicenseLevel int `yaml:"CarClassLicenseLevel"` + CarClassMaxFuelPct string `yaml:"CarClassMaxFuelPct"` + CarClassWeightPenalty string `yaml:"CarClassWeightPenalty"` + CarClassPowerAdjust string `yaml:"CarClassPowerAdjust"` + CarClassDryTireSetLimit string `yaml:"CarClassDryTireSetLimit"` + CarClassColor int `yaml:"CarClassColor"` + CarClassEstLapTime float64 `yaml:"CarClassEstLapTime"` + IRating int `yaml:"IRating"` + LicLevel int `yaml:"LicLevel"` + LicSubLevel int `yaml:"LicSubLevel"` + LicString string `yaml:"LicString"` + LicColor string `yaml:"LicColor"` + IsSpectator int `yaml:"IsSpectator"` + CarDesignStr string `yaml:"CarDesignStr"` + HelmetDesignStr string `yaml:"HelmetDesignStr"` + SuitDesignStr string `yaml:"SuitDesignStr"` + CarNumberDesignStr string `yaml:"CarNumberDesignStr"` + CarSponsor1 int `yaml:"CarSponsor_1"` + CarSponsor2 int `yaml:"CarSponsor_2"` + CurDriverIncidentCount int `yaml:"CurDriverIncidentCount"` + TeamIncidentCount int `yaml:"TeamIncidentCount"` +} + +// parseSessionInfo will parse the sessionInfo buffer into the SessionInfoYAML +// struct +func parseSessionInfo(data []byte) (*SessionInfoYAML, error) { + var sessionInfo SessionInfoYAML + err := yaml.Unmarshal(data, &sessionInfo) + if err != nil { + return nil, err + } + + return &sessionInfo, nil +} + +// ToString will return a readable string of the struct +func (s *SessionInfoYAML) ToString() string { + stringified, _ := json.MarshalIndent(s, "", " ") + return string(stringified) +} diff --git a/sharedMem/sharedMem.go b/sharedMem/sharedMem.go new file mode 100644 index 0000000..d4c4ee9 --- /dev/null +++ b/sharedMem/sharedMem.go @@ -0,0 +1,88 @@ +package sharedMem + +import ( + "fmt" + "io" +) + +// Memory is shared memory struct +type Memory struct { + m *shmi + pos int64 +} + +// Create is create shared memory +func Create(name string, size uint32) (*Memory, error) { + m, err := create(name, size) + if err != nil { + return nil, err + } + return &Memory{m, 0}, nil +} + +// Open is open exist shared memory +func Open(name string, size uint32) (*Memory, error) { + m, err := open(name, size) + if err != nil { + return nil, err + } + return &Memory{m, 0}, nil +} + +// Close is close & discard shared memory +func (o *Memory) Close() (err error) { + if o.m != nil { + err = o.m.close() + if err == nil { + o.m = nil + } + } + return err +} + +// Read is read shared memory (current position) +func (o *Memory) Read(p []byte) (n int, err error) { + n, err = o.ReadAt(p, o.pos) + if err != nil { + return 0, err + } + o.pos += int64(n) + return n, nil +} + +// ReadAt is read shared memory (offset) +func (o *Memory) ReadAt(p []byte, off int64) (n int, err error) { + return o.m.readAt(p, off) +} + +// Seek is move read/write position at shared memory +func (o *Memory) Seek(offset int64, whence int) (int64, error) { + switch whence { + case io.SeekStart: + offset += int64(0) + case io.SeekCurrent: + offset += o.pos + case io.SeekEnd: + offset += int64(o.m.size) + } + if offset < 0 || offset >= int64(o.m.size) { + return 0, fmt.Errorf("invalid offset") + } + o.pos = offset + return offset, nil +} + +// Write is write shared memory (current position) +func (o *Memory) Write(p []byte) (n int, err error) { + n, err = o.WriteAt(p, o.pos) + if err != nil { + return 0, err + } + o.pos += int64(n) + return n, nil +} + +// WriteAt is write shared memory (offset) +func (o *Memory) WriteAt(p []byte, off int64) (n int, err error) { + return o.m.writeAt(p, off) +} diff --git a/sharedMem/sharedMemDarwin.go b/sharedMem/sharedMemDarwin.go new file mode 100644 index 0000000..b6ebb5b --- /dev/null +++ b/sharedMem/sharedMemDarwin.go @@ -0,0 +1,157 @@ +//go:build darwin && cgo +// +build darwin,cgo + +package sharedMem + +/* +#include +#include +#include +#include +#include +#include +#include + +int _create(const char* name, int size, int flag) { + mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP; + + int fd = shm_open(name, flag, mode); + if (fd < 0) { + return -1; + } + + struct stat mapstat; + int ret = fstat(fd, &mapstat); + if (ret != -1 && mapstat.st_size == 0) { + if (ftruncate(fd, size) != 0) { + close(fd); + return -2; + } + } else if (ret == -1) { + close(fd); + return -3; + } + + return fd; +} + +int Create(const char* name, int size) { + int flag = O_RDWR | O_CREAT; + return _create(name, size, flag); +} + +int Open(const char* name, int size) { + int flag = O_RDWR; + return _create(name, size, flag); +} + +void* Map(int fd, int size) { + void* p = mmap( + NULL, size, + PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 0); + if (p == MAP_FAILED) { + return NULL; + } + return p; +} + +void Close(int fd, void* p, int size) { + if (p != NULL) { + munmap(p, size); + } + if (fd != 0) { + close(fd); + } +} + +void Delete(const char* name) { + shm_unlink(name); +} +*/ +import "C" + +import ( + "fmt" + "io" + "unsafe" +) + +type shmi struct { + name string + fd C.int + v unsafe.Pointer + size int32 + parent bool +} + +// create shared memory. return shmi object. +// name should not be more than 31 bytes. +func create(name string, size int32) (*shmi, error) { + name = "/" + name + + fd := C.Create(C.CString(name), C.int(size)) + if fd < 0 { + return nil, fmt.Errorf("create") + } + + v := C.Map(fd, C.int(size)) + if v == nil { + C.Close(fd, nil, C.int(size)) + C.Delete(C.CString(name)) + } + + return &shmi{name, fd, v, size, true}, nil +} + +// open shared memory. return shmi object. +// name should not be more than 31 bytes. +func open(name string, size int32) (*shmi, error) { + name = "/" + name + + fd := C.Open(C.CString(name), C.int(size)) + if fd < 0 { + return nil, fmt.Errorf("open") + } + + v := C.Map(fd, C.int(size)) + if v == nil { + C.Close(fd, nil, C.int(size)) + C.Delete(C.CString(name)) + } + + return &shmi{name, fd, v, size, false}, nil +} + +func (o *shmi) close() error { + if o.v != nil { + C.Close(o.fd, o.v, C.int(o.size)) + o.v = nil + } + if o.parent { + C.Delete(C.CString(o.name)) + } + return nil +} + +// read shared memory. return read size. +func (o *shmi) readAt(p []byte, off int64) (n int, err error) { + if off >= int64(o.size) { + return 0, io.EOF + } + if max := int64(o.size) - off; int64(len(p)) > max { + p = p[:max] + } + return copyPtr2Slice(uintptr(o.v), p, off, o.size), nil +} + +// write shared memory. return write size. +func (o *shmi) writeAt(p []byte, off int64) (n int, err error) { + if off >= int64(o.size) { + return 0, io.EOF + } + if max := int64(o.size) - off; int64(len(p)) > max { + p = p[:max] + } + return copySlice2Ptr(p, uintptr(o.v), off, o.size), nil +} diff --git a/sharedMem/sharedMemLinux.go b/sharedMem/sharedMemLinux.go new file mode 100644 index 0000000..5e35dc1 --- /dev/null +++ b/sharedMem/sharedMemLinux.go @@ -0,0 +1,148 @@ +//go:build linux && cgo +// +build linux,cgo + +package sharedMem + +/* +#cgo LDFLAGS: -lrt + +#include +#include +#include +#include +#include +#include + +int _create(const char* name, int size, int flag) { + mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP; + + int fd = shm_open(name, flag, mode); + if (fd < 0) { + return -1; + } + + if (ftruncate(fd, size) != 0) { + close(fd); + return -2; + } + return fd; +} + +int Create(const char* name, int size) { + int flag = O_RDWR | O_CREAT; + return _create(name, size, flag); +} + +int Open(const char* name, int size) { + int flag = O_RDWR; + return _create(name, size, flag); +} + +void* Map(int fd, int size) { + void* p = mmap( + NULL, size, + PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 0); + if (p == MAP_FAILED) { + return NULL; + } + return p; +} + +void Close(int fd, void* p, int size) { + if (p != NULL) { + munmap(p, size); + } + if (fd != 0) { + close(fd); + } +} + +void Delete(const char* name) { + shm_unlink(name); +} +*/ +import "C" + +import ( + "fmt" + "io" + "unsafe" +) + +type shmi struct { + name string + fd C.int + v unsafe.Pointer + size int32 + parent bool +} + +// create shared memory. return shmi object. +func create(name string, size int32) (*shmi, error) { + name = "/" + name + + fd := C.Create(C.CString(name), C.int(size)) + if fd < 0 { + return nil, fmt.Errorf("create") + } + + v := C.Map(fd, C.int(size)) + if v == nil { + C.Close(fd, nil, C.int(size)) + C.Delete(C.CString(name)) + } + + return &shmi{name, fd, v, size, true}, nil +} + +// open shared memory. return shmi object. +func open(name string, size int32) (*shmi, error) { + name = "/" + name + + fd := C.Open(C.CString(name), C.int(size)) + if fd < 0 { + return nil, fmt.Errorf("open") + } + + v := C.Map(fd, C.int(size)) + if v == nil { + C.Close(fd, nil, C.int(size)) + C.Delete(C.CString(name)) + } + + return &shmi{name, fd, v, size, false}, nil +} + +func (o *shmi) close() error { + if o.v != nil { + C.Close(o.fd, o.v, C.int(o.size)) + o.v = nil + } + if o.parent { + C.Delete(C.CString(o.name)) + } + return nil +} + +// read shared memory. return read size. +func (o *shmi) readAt(p []byte, off int64) (n int, err error) { + if off >= int64(o.size) { + return 0, io.EOF + } + if max := int64(o.size) - off; int64(len(p)) > max { + p = p[:max] + } + return copyPtr2Slice(uintptr(o.v), p, off, o.size), nil +} + +// write shared memory. return write size. +func (o *shmi) writeAt(p []byte, off int64) (n int, err error) { + if off >= int64(o.size) { + return 0, io.EOF + } + if max := int64(o.size) - off; int64(len(p)) > max { + p = p[:max] + } + return copySlice2Ptr(p, uintptr(o.v), off, o.size), nil +} diff --git a/sharedMem/sharedMemWindows.go b/sharedMem/sharedMemWindows.go new file mode 100644 index 0000000..b0776d0 --- /dev/null +++ b/sharedMem/sharedMemWindows.go @@ -0,0 +1,82 @@ +package sharedMem + +import ( + "io" + "log" + + "golang.org/x/sys/windows" +) + +type shmi struct { + h windows.Handle + v uintptr + size uint32 +} + +// create shared memory. return shmi object. +func create(name string, size uint32) (*shmi, error) { + fnPtr, _ := windows.UTF16PtrFromString(name) + + flProtect := uint32(windows.PAGE_READONLY) + + h, errno := windows.CreateFileMapping( + windows.InvalidHandle, + nil, + flProtect, + 0, + size, + fnPtr) + if h == 0 { + log.Fatal("could not open memmap file: ", errno) + } + + addr, errno := windows.MapViewOfFile(h, + windows.FILE_MAP_READ, + 0, + 0, + uintptr(size)) + if addr == 0 { + log.Printf("error in MapViewOfFile: %v", errno) + } + + return &shmi{h, addr, size}, nil +} + +// open shared memory. return shmi object. +func open(name string, size uint32) (*shmi, error) { + return create(name, size) +} + +func (o *shmi) close() error { + if o.v != uintptr(0) { + windows.UnmapViewOfFile(o.v) + o.v = uintptr(0) + } + if o.h != windows.InvalidHandle { + windows.CloseHandle(o.h) + o.h = windows.InvalidHandle + } + return nil +} + +// read shared memory. return read size. +func (o *shmi) readAt(p []byte, off int64) (n int, err error) { + if off >= int64(o.size) { + return 0, io.EOF + } + if max := int64(o.size) - off; int64(len(p)) > max { + p = p[:max] + } + return copyPtr2Slice(o.v, p, off, o.size), nil +} + +// write shared memory. return write size. +func (o *shmi) writeAt(p []byte, off int64) (n int, err error) { + if off >= int64(o.size) { + return 0, io.EOF + } + if max := int64(o.size) - off; int64(len(p)) > max { + p = p[:max] + } + return copySlice2Ptr(p, o.v, off, o.size), nil +} diff --git a/sharedMem/utils.go b/sharedMem/utils.go new file mode 100644 index 0000000..2bab882 --- /dev/null +++ b/sharedMem/utils.go @@ -0,0 +1,15 @@ +package sharedMem + +import ( + "unsafe" +) + +func copySlice2Ptr(b []byte, p uintptr, off int64, size uint32) int { + bb := unsafe.Slice((*byte)(*(*unsafe.Pointer)(unsafe.Pointer(&p))), int(size)) + return copy(bb[off:], b) +} + +func copyPtr2Slice(p uintptr, b []byte, off int64, size uint32) int { + bb := unsafe.Slice((*byte)(*(*unsafe.Pointer)(unsafe.Pointer(&p))), int(size)) + return copy(b, bb[off:size]) +} diff --git a/variables.go b/variables.go index 6c6ecf5..8db21d7 100644 --- a/variables.go +++ b/variables.go @@ -1,177 +1,177 @@ -package ibtReader - -import ( - "bytes" - "encoding/binary" - "fmt" - "io" - "log" - "math" - "strings" -) - -const ( - VarHeaderSize = 144 - IRSDK_char = 0 - IRSDK_bool = 1 - IRSDK_int = 2 - IRSDK_bitField = 3 - IRSDK_float = 4 - IRSDK_double = 5 -) - -// I think I can make an interface if IRSDK types with available types and -// that they need a parser (reads and type coerces I guess) -var ( - VarTypes = map[int]VarType{ - IRSDK_char: {1, "irsdk_char"}, - IRSDK_bool: {1, "irsdk_bool"}, - IRSDK_int: {4, "irsdk_int"}, - IRSDK_bitField: {4, "irsdk_bitField"}, - IRSDK_float: {4, "irsdk_float"}, - IRSDK_double: {8, "irsdk_double"}, - } -) - -type VarType struct { - Size int // Size is the var type size in bytes - Name string // Name is the irsdk var name -} - -type IBTVar struct { - Type int32 - Offset int32 - Count int32 - CountAsTime bool - Padding [3]byte - Name [32]byte - Description [64]byte - Unit [32]byte -} - -type Var struct { - Type int32 - Offset int32 - Count int32 - CountAsTime bool - Name string - Description string - Unit string - // TODO - // Create an interface for this value - // Represent the IRSDK var types with a struct each that implements the Parse - // method or something like that I guess - Value interface{} -} - -func (v *IBTVar) ToString() string { - return fmt.Sprintf( - "Type: %5d (0x%08x)\n"+ - "Offset: %5d (0x%08x)\n"+ - "Count: %5d (0x%08x)\n"+ - "CountAsTime: %5t\n"+ - "Name: %s\n"+ - "Description: %s\n"+ - "Unit: %s", - v.Type, v.Type, v.Offset, v.Offset, v.Count, v.Count, - v.CountAsTime, v.Name, v.Description, v.Unit, - ) -} - -type varBuffer struct { - tickCount int - bufOffset int -} - -type TelemetryVars struct { - LastVersion int - Vars map[string]Var -} - -func (i *IBT) readVariablerHeaders() error { - i.Vars = &TelemetryVars{Vars: make(map[string]Var, i.Headers.NumVars)} - - var k int32 - for k = 0; k < i.Headers.NumVars; k++ { - rbuf := make([]byte, VarHeaderSize) - - _, err := i.File.ReadAt(rbuf, int64(i.Headers.VarHeaderOffset+k*VarHeaderSize)) - if err != nil { - return err - } - - var dst IBTVar - err = binary.Read(bytes.NewBuffer(rbuf[:]), binary.LittleEndian, &dst) - if err != nil { - return err - } - - v := Var{ - Type: dst.Type, - Offset: dst.Offset, - Count: dst.Count, - CountAsTime: dst.CountAsTime, - Name: strings.TrimRight(string(dst.Name[:]), "\x00"), - Description: strings.TrimRight(string(dst.Description[:]), "\x00"), - Unit: strings.TrimRight(string(dst.Unit[:]), "\x00"), - Value: nil, - } - - i.Vars.Vars[v.Name] = v - } - - return nil -} - -func (i *IBT) readData() error { - // I think that we can add one extra check or verification here - // The file headers tells us how many data frames there are, we can probably - // cap it at that instead of waiting for the read to fail - // Probably wouldn't work on live data tho - start := i.Headers.BufOffset + i.Tick*i.Headers.BufLen - buf := make([]byte, i.Headers.BufLen) - _, err := i.File.ReadAt(buf, int64(start)) - if err != nil { - return err - } - - for k, v := range i.Vars.Vars { - rbuf := buf[v.Offset : v.Offset+int32(VarTypes[int(v.Type)].Size)] - - // Read the value - switch v.Type { - case IRSDK_char: - v.Value = string(rbuf[0]) - case IRSDK_bool: - v.Value = int(rbuf[0]) > 0 - case IRSDK_int: - v.Value = int(binary.LittleEndian.Uint32(rbuf)) - case IRSDK_bitField: - v.Value = fmt.Sprintf("0x%x", int(binary.LittleEndian.Uint32(rbuf))) - case IRSDK_float: - v.Value = math.Float32frombits(uint32(binary.LittleEndian.Uint32(rbuf))) - case IRSDK_double: - v.Value = math.Float64frombits(uint64(binary.LittleEndian.Uint64(rbuf))) - } - // -------------- - - i.Vars.Vars[k] = v - } - - i.Tick++ - - return nil -} - -func (i *IBT) Update() bool { - err := i.readData() - if err != nil && err != io.EOF { - log.Fatalf("What happened?\n%v\n", err) - } - - if err == io.EOF { - return false - } - - return true -} +package main + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "log" + "math" + "strings" +) + +const ( + VarHeaderSize = 144 + IRSDK_char = 0 + IRSDK_bool = 1 + IRSDK_int = 2 + IRSDK_bitField = 3 + IRSDK_float = 4 + IRSDK_double = 5 +) + +// I think I can make an interface if IRSDK types with available types and +// that they need a parser (reads and type coerces I guess) +var ( + VarTypes = map[int]VarType{ + IRSDK_char: {1, "irsdk_char"}, + IRSDK_bool: {1, "irsdk_bool"}, + IRSDK_int: {4, "irsdk_int"}, + IRSDK_bitField: {4, "irsdk_bitField"}, + IRSDK_float: {4, "irsdk_float"}, + IRSDK_double: {8, "irsdk_double"}, + } +) + +type VarType struct { + Size int // Size is the var type size in bytes + Name string // Name is the irsdk var name +} + +type IBTVar struct { + Type int32 + Offset int32 + Count int32 + CountAsTime bool + Padding [3]byte + Name [32]byte + Description [64]byte + Unit [32]byte +} + +type Var struct { + Type int32 + Offset int32 + Count int32 + CountAsTime bool + Name string + Description string + Unit string + // TODO + // Create an interface for this value + // Represent the IRSDK var types with a struct each that implements the Parse + // method or something like that I guess + Value interface{} +} + +func (v *IBTVar) ToString() string { + return fmt.Sprintf( + "Type: %5d (0x%08x)\n"+ + "Offset: %5d (0x%08x)\n"+ + "Count: %5d (0x%08x)\n"+ + "CountAsTime: %5t\n"+ + "Name: %s\n"+ + "Description: %s\n"+ + "Unit: %s", + v.Type, v.Type, v.Offset, v.Offset, v.Count, v.Count, + v.CountAsTime, v.Name, v.Description, v.Unit, + ) +} + +type varBuffer struct { + tickCount int + bufOffset int +} + +type TelemetryVars struct { + LastVersion int + Vars map[string]Var +} + +func (i *IBT) readVariablerHeaders() error { + i.Vars = &TelemetryVars{Vars: make(map[string]Var, i.Headers.NumVars)} + + var k int32 + for k = 0; k < i.Headers.NumVars; k++ { + rbuf := make([]byte, VarHeaderSize) + + _, err := i.File.ReadAt(rbuf, int64(i.Headers.VarHeaderOffset+k*VarHeaderSize)) + if err != nil { + return err + } + + var dst IBTVar + err = binary.Read(bytes.NewBuffer(rbuf[:]), binary.LittleEndian, &dst) + if err != nil { + return err + } + + v := Var{ + Type: dst.Type, + Offset: dst.Offset, + Count: dst.Count, + CountAsTime: dst.CountAsTime, + Name: strings.TrimRight(string(dst.Name[:]), "\x00"), + Description: strings.TrimRight(string(dst.Description[:]), "\x00"), + Unit: strings.TrimRight(string(dst.Unit[:]), "\x00"), + Value: nil, + } + + i.Vars.Vars[v.Name] = v + } + + return nil +} + +func (i *IBT) readData() error { + // I think that we can add one extra check or verification here + // The file headers tells us how many data frames there are, we can probably + // cap it at that instead of waiting for the read to fail + // Probably wouldn't work on live data tho + start := i.Headers.BufOffset + i.Tick*i.Headers.BufLen + buf := make([]byte, i.Headers.BufLen) + _, err := i.File.ReadAt(buf, int64(start)) + if err != nil { + return err + } + + for k, v := range i.Vars.Vars { + rbuf := buf[v.Offset : v.Offset+int32(VarTypes[int(v.Type)].Size)] + + // Read the value + switch v.Type { + case IRSDK_char: + v.Value = string(rbuf[0]) + case IRSDK_bool: + v.Value = int(rbuf[0]) > 0 + case IRSDK_int: + v.Value = int(binary.LittleEndian.Uint32(rbuf)) + case IRSDK_bitField: + v.Value = fmt.Sprintf("0x%x", int(binary.LittleEndian.Uint32(rbuf))) + case IRSDK_float: + v.Value = math.Float32frombits(uint32(binary.LittleEndian.Uint32(rbuf))) + case IRSDK_double: + v.Value = math.Float64frombits(uint64(binary.LittleEndian.Uint64(rbuf))) + } + // -------------- + + i.Vars.Vars[k] = v + } + + i.Tick++ + + return nil +} + +func (i *IBT) Update() bool { + err := i.readData() + if err != nil && err != io.EOF { + log.Fatalf("What happened?\n%v\n", err) + } + + if err == io.EOF { + return false + } + + return true +} diff --git a/winutils/winutils.go b/winutils/winutils.go new file mode 100644 index 0000000..abb1a76 --- /dev/null +++ b/winutils/winutils.go @@ -0,0 +1,105 @@ +package winutils + +import ( + "fmt" + "sync" + "time" + "unsafe" + + "golang.org/x/sys/windows" +) + +const ( + WAIT_OBJECT_0 = 0 + WAIT_TIMEOUT = 258 +) + +var ( + once sync.Once + user32DLL *windows.LazyDLL = nil +) + +// openEvent opens a windows.Handle for a given event +func OpenEvent(eventName string) (windows.Handle, error) { + name, err := windows.UTF16PtrFromString(eventName) + if err != nil { + return 0, err + } + + handle, err := windows.OpenEvent(windows.SYNCHRONIZE, false, name) + if err != nil { + return 0, fmt.Errorf("error opening event %s: %w", eventName, err) + } + + return handle, nil +} + +// closeEvent closes a given windows.Handle +func CloseEvent(h windows.Handle) { + windows.CloseHandle(h) +} + +// openEvent waits for a good response for some given time +func CheckValidDataEvent(handle windows.Handle, timeout time.Duration) bool { + t0 := time.Now().UnixNano() + timeoutInt := uint32(timeout / time.Millisecond) + + result, err := windows.WaitForSingleObject(handle, timeoutInt) + if err != nil { + remaining := timeoutInt - uint32((time.Now().UnixNano()-t0)/1000000) + if remaining > 0 { + time.Sleep(time.Duration(remaining) * time.Millisecond) + } + return false + } + + // Check the result of the wait + if result == WAIT_OBJECT_0 { + return true + } else if result == WAIT_TIMEOUT { + return false + } + + return false +} + +// loadUser32DLL loads the user32.dll which is used to create some processes +func loadUser32DLL() { + user32DLL = windows.NewLazyDLL("user32.dll") +} + +// OpenBroadcastChannel opens up a broadcast channel to send commands to iracing +func OpenBroadcastChannel(name string) (uintptr, error) { + if user32DLL == nil { + once.Do(loadUser32DLL) + } + registerWindowsMessageW := user32DLL.NewProc("RegisterWindowMessageW") + + msgPtr, err := windows.UTF16PtrFromString(name) + if err != nil { + return 0, err + } + + ret, _, err := registerWindowsMessageW.Call(uintptr(unsafe.Pointer(msgPtr))) + if ret == 0 { + return 0, err + } + + return ret, nil +} + +// SendBroadcastMessage sends a message trough the broadcast channel +func SendBroadcastMessage(id, p1, p2 uintptr) error { + if user32DLL == nil { + once.Do(loadUser32DLL) + } + + sendMsg := user32DLL.NewProc("SendNotifyMessageW") + ret, _, err := sendMsg.Call(0xffff, id, p1, p2) + + if ret == 1 { + return nil + } else { + return err + } +}