├── .gitignore ├── package.json ├── README.md ├── example.js ├── AccBroadcast.js ├── structs.js └── enums.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "acc-broadcast", 3 | "version": "1.0.1", 4 | "description": "ACC UDP Broadcast Listener", 5 | "main": "AccBroadcast.js", 6 | "keywords": [ 7 | "Assetto Corsa", 8 | "UDP" 9 | ], 10 | "author": "Flopsterr", 11 | "license": "ISC", 12 | "dependencies": { 13 | "binutils": "^0.1.0", 14 | "utf8-bytes": "0.0.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Quickstart 2 | 3 | `npm install acc-broadcast` 4 | 5 | ```JS 6 | const AccBroadcast = require("acc-broadcast"); 7 | const accBroadcast = new AccBroadcast("My Application", "asd"); 8 | 9 | accBroadcast.on("realtime_car_update", (update) => { 10 | console.clear(); 11 | console.log(`Gear: ${update.Gear}`) 12 | console.log(`Speed: ${update.Kmh} km/h`); 13 | console.log(`Delta: ${update.Delta} ms`); 14 | }) 15 | ``` 16 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | const AccBroadcast = require("./AccBroadcast"); 2 | 3 | const accBroadcast = new AccBroadcast("My Application", "asd"); 4 | 5 | accBroadcast.on("registration_result", console.log); 6 | accBroadcast.on("realtime_update", console.log); 7 | accBroadcast.on("realtime_car_update", console.log); 8 | accBroadcast.on("track_data", console.log); 9 | accBroadcast.on("entry_list", console.log); 10 | accBroadcast.on("entry_list_car", console.log); 11 | accBroadcast.on("broadcasting_event", console.log); 12 | -------------------------------------------------------------------------------- /AccBroadcast.js: -------------------------------------------------------------------------------- 1 | const dgram = require("dgram"); 2 | const EventEmitter = require("events"); 3 | const binutils = require("binutils"); 4 | const structs = require("./structs"); 5 | 6 | class AccBroadcast extends EventEmitter { 7 | constructor(name, password, cmdPassword = "", updateMS = 250) { 8 | super(); 9 | this.socket = dgram.createSocket("udp4"); 10 | this.socket.on("message", this.onMessage.bind(this)); 11 | 12 | this.connect(name, password, cmdPassword, updateMS); 13 | this.cars = new Map(); 14 | 15 | process.on("SIGINT", this.disconnect.bind(this)); 16 | } 17 | 18 | onMessage(message) { 19 | const br = new binutils.BinaryReader(message, "little"); 20 | const messageType = br.ReadUInt8(); 21 | 22 | switch (messageType) { 23 | case 1: //REGISTRATION_RESULT 24 | this.registration = structs.parseRegistrationResult(br); 25 | this.emit("registration_result", this.registration); 26 | 27 | if (!this.registration.isReadOnly) { 28 | this.requestTrackData(); 29 | this.requestEntryList(); 30 | } 31 | break; 32 | case 2: //REALTIME_UPDATE 33 | this.emit("realtime_update", structs.parseRealTimeUpdate(br)); 34 | break; 35 | case 3: //REALTIME_CAR_UPDATE 36 | this.emit("realtime_car_update", structs.parseRealTimeCarUpdate(br)); 37 | break; 38 | case 4: //ENTRY_LIST 39 | this.cars.clear(); 40 | structs.parseEntryList(br).forEach((carId) => this.cars.set(carId, {})); 41 | this.emit("entry_list", this.cars); 42 | break; 43 | case 5: //TRACK_DATA 44 | this.emit("track_data", structs.parseTrackData(br)); 45 | break; 46 | case 6: //ENTRY_LIST_CAR 47 | this.emit("entry_list_car", structs.parseEntryListCar(br, this.cars)); 48 | break; 49 | case 7: //BROADCASTING_EVENT 50 | this.emit( 51 | "broadcasting_event", 52 | structs.parseBroadcastEvent(br, this.cars) 53 | ); 54 | break; 55 | default: 56 | console.error(`unknown messageType ${messageType}`); 57 | } 58 | } 59 | 60 | requestTrackData() { 61 | this.send(structs.RequestTrackData(this.registration.ConnectionId)); 62 | } 63 | 64 | requestEntryList() { 65 | this.send(structs.RequestEntryList(this.registration.ConnectionId)); 66 | } 67 | 68 | connect(name, password, cmdPassword, updateMS = 250) { 69 | this.send( 70 | structs.RegisterConnection(name, password, cmdPassword, updateMS) 71 | ); 72 | } 73 | 74 | disconnect() { 75 | this.send(structs.DeregisterConnection()); 76 | process.exit(0); 77 | } 78 | 79 | send(buffer) { 80 | this.socket.send(buffer, 0, buffer.length, 9000, "127.0.0.1"); 81 | } 82 | } 83 | 84 | module.exports = AccBroadcast; 85 | -------------------------------------------------------------------------------- /structs.js: -------------------------------------------------------------------------------- 1 | const utf8 = require("utf8-bytes"); 2 | const binutils = require("binutils"); 3 | 4 | const RegisterConnection = ( 5 | displayName, 6 | connectionPassword, 7 | commandPassword, 8 | updateMS 9 | ) => { 10 | const displayNameArray = utf8(displayName); 11 | const connectionPasswordArray = utf8(connectionPassword); 12 | const commandPasswordArray = utf8(commandPassword); 13 | 14 | const writer = new binutils.BinaryWriter("little"); 15 | writer.WriteUInt8(1); 16 | writer.WriteUInt8(4); 17 | writer.WriteUInt16(displayNameArray.length); 18 | writer.WriteBytes(displayNameArray); 19 | writer.WriteUInt16(connectionPasswordArray.length); 20 | writer.WriteBytes(connectionPasswordArray); 21 | writer.WriteUInt32(updateMS); 22 | writer.WriteUInt16(commandPasswordArray.length); 23 | writer.WriteBytes(commandPasswordArray); 24 | 25 | return writer.ByteBuffer; 26 | }; 27 | 28 | const DeregisterConnection = () => { 29 | const writer = new binutils.BinaryWriter("little"); 30 | writer.WriteUInt8(9); 31 | return writer.ByteBuffer; 32 | }; 33 | 34 | const RequestEntryList = (ConnectionId) => { 35 | const writer = new binutils.BinaryWriter("little"); 36 | writer.WriteUInt8(10); 37 | writer.WriteInt32(ConnectionId); 38 | 39 | return writer.ByteBuffer; 40 | }; 41 | 42 | const RequestTrackData = (ConnectionId) => { 43 | const writer = new binutils.BinaryWriter("little"); 44 | writer.WriteUInt8(11); 45 | writer.WriteInt32(ConnectionId); 46 | 47 | return writer.ByteBuffer; 48 | }; 49 | 50 | const parseEntryList = (br) => { 51 | const entryList = []; 52 | 53 | const connectionId = br.ReadInt32(); 54 | const carEntryCount = br.ReadUInt16(); 55 | for (let i = 0; i < carEntryCount; i++) { 56 | entryList.push(br.ReadUInt16()); 57 | } 58 | 59 | return entryList; 60 | }; 61 | 62 | const parseRegistrationResult = (br) => { 63 | const result = {}; 64 | result.ConnectionId = br.ReadInt32(); 65 | result.ConnectionSuccess = !!br.ReadUInt8(1); 66 | result.isReadOnly = !br.ReadUInt8(1); 67 | result.errMsg = parseString(br); 68 | 69 | return result; 70 | }; 71 | 72 | const parseRealTimeUpdate = (br) => { 73 | const update = {}; 74 | update.EventIndex = br.ReadUInt16(); 75 | update.SessionIndex = br.ReadUInt16(); 76 | update.SessionType = br.ReadUInt8(); 77 | update.Phase = br.ReadUInt8(); 78 | update.sessionTime = br.ReadFloat(); 79 | update.sessionEndTime = br.ReadFloat(); 80 | update.FocusedCarIndex = br.ReadInt32(); 81 | update.ActiveCameraSet = parseString(br); 82 | update.ActiveCamera = parseString(br); 83 | update.CurrentHudPage = parseString(br); 84 | 85 | update.IsReplayPlaying = !!br.ReadUInt8(); 86 | if (update.IsReplayPlaying) { 87 | update.ReplaySessionTime = br.ReadFloat(); 88 | update.ReplayRemainingTime = br.ReadFloat(); 89 | } 90 | 91 | update.TimeOfDay = br.ReadFloat(); 92 | update.AmbientTemp = br.ReadUInt8(); 93 | update.TrackTemp = br.ReadUInt8(); 94 | update.Clouds = br.ReadUInt8() / 10.0; 95 | update.RainLevel = br.ReadUInt8() / 10.0; 96 | update.Wetness = br.ReadUInt8() / 10.0; 97 | 98 | update.BestSessionLap = parseLap(br); 99 | 100 | return update; 101 | }; 102 | 103 | const parseRealTimeCarUpdate = (br) => { 104 | return { 105 | CarIndex: br.ReadUInt16(), 106 | DriverIndex: br.ReadUInt16(), 107 | DriverCount: br.ReadUInt8(), 108 | Gear: br.ReadUInt8() - 1, 109 | WorldPosX: br.ReadFloat(), 110 | WorldPosY: br.ReadFloat(), 111 | Yaw: br.ReadFloat(), 112 | CarLocation: br.ReadUInt8(), 113 | Kmh: br.ReadUInt16(), 114 | Position: br.ReadUInt16(), 115 | CupPosition: br.ReadUInt16(), 116 | TrackPosition: br.ReadUInt16(), 117 | SplinePosition: br.ReadFloat(), 118 | Laps: br.ReadUInt16(), 119 | Delta: br.ReadInt32(), 120 | BestSessionLap: parseLap(br), 121 | LastLap: parseLap(br), 122 | CurrentLap: parseLap(br), 123 | }; 124 | }; 125 | 126 | const parseEntryListCar = (br, cars) => { 127 | const carInfo = {}; 128 | const carId = br.ReadUInt16(); 129 | 130 | carInfo.CarModelType = br.ReadUInt8(); 131 | carInfo.TeamName = parseString(br); 132 | carInfo.RaceNumber = br.ReadInt32(); 133 | carInfo.CupCategory = br.ReadUInt8(); // Cup: Overall/Pro = 0, ProAm = 1, Am = 2, Silver = 3, National = 4 134 | carInfo.CurrentDriverIndex = br.ReadUInt8(); 135 | carInfo.Nationality = br.ReadUInt16(); 136 | carInfo.Drivers = []; 137 | 138 | const driversOnCarCount = br.ReadUInt8(); 139 | for (let i = 0; i < driversOnCarCount; i++) { 140 | carInfo.Drivers.push({ 141 | FirstName: parseString(br), 142 | LastName: parseString(br), 143 | ShortName: parseString(br), 144 | Category: br.ReadUInt8(), 145 | Nationality: br.ReadUInt16(), 146 | }); 147 | } 148 | 149 | carInfo.CurrentDriver = carInfo.Drivers[carInfo.CurrentDriverIndex]; 150 | 151 | cars.set(carId, carInfo); 152 | return carInfo; 153 | }; 154 | 155 | const parseBroadcastEvent = (br, cars) => { 156 | const event = { 157 | Type: br.ReadUInt8(), 158 | Msg: parseString(br), 159 | TimeMS: br.ReadInt32(), 160 | CarId: br.ReadInt32(), 161 | }; 162 | 163 | event.Car = cars.get(event.CarId); 164 | 165 | return event; 166 | }; 167 | 168 | const parseTrackData = (br) => { 169 | const trackData = { 170 | CameraSets: {}, 171 | HUDPages: [], 172 | }; 173 | 174 | trackData.ConnectionId = br.ReadInt32(); 175 | trackData.TrackName = parseString(br); 176 | trackData.TrackId = br.ReadInt32(); 177 | trackData.TrackMeters = br.ReadInt32(); 178 | 179 | const cameraSetCount = br.ReadUInt8(); 180 | for (let i = 0; i < cameraSetCount; i++) { 181 | const camSetName = parseString(br); 182 | trackData.CameraSets[camSetName] = []; 183 | 184 | const cameraCount = br.ReadUInt8(); 185 | for (let j = 0; j < cameraCount; j++) { 186 | const cameraName = parseString(br); 187 | trackData.CameraSets[camSetName].push(cameraName); 188 | } 189 | } 190 | 191 | const hudPagesCount = br.ReadUInt8(); 192 | for (let i = 0; i < hudPagesCount; i++) { 193 | trackData.HUDPages.push(parseString(br)); 194 | } 195 | 196 | return trackData; 197 | }; 198 | 199 | const parseLap = (br) => { 200 | const lap = { Splits: [] }; 201 | 202 | lap.LaptimeMS = br.ReadInt32(); 203 | lap.CarIndex = br.ReadUInt16(); 204 | lap.DriverIndex = br.ReadUInt16(); 205 | 206 | const splitCount = br.ReadUInt8(); 207 | 208 | for (let i = 0; i < splitCount; i++) { 209 | lap.Splits.push(br.ReadInt32()); 210 | } 211 | 212 | lap.IsInvalid = !!br.ReadUInt8(); 213 | lap.IsValidForBest = !!br.ReadUInt8(); 214 | lap.isOutlap = !!br.ReadUInt8(); 215 | lap.isInlap = !!br.ReadUInt8(); 216 | 217 | if (lap.LaptimeMS === 2147483647) lap.LaptimeMS = null; 218 | lap.Splits = lap.Splits.map((split) => (split === 2147483647 ? null : split)); 219 | 220 | return lap; 221 | }; 222 | 223 | const parseString = (br) => { 224 | const length = br.ReadUInt16(); 225 | const string = br.ReadBytes(length); 226 | return string.toString("utf8"); 227 | }; 228 | 229 | module.exports = { 230 | RegisterConnection, 231 | DeregisterConnection, 232 | RequestTrackData, 233 | RequestEntryList, 234 | parseRegistrationResult, 235 | parseRealTimeCarUpdate, 236 | parseRealTimeUpdate, 237 | parseTrackData, 238 | parseEntryList, 239 | parseEntryListCar, 240 | parseBroadcastEvent, 241 | }; 242 | -------------------------------------------------------------------------------- /enums.md: -------------------------------------------------------------------------------- 1 | # Events 2 | 3 | ### realtime_update 4 | 5 | ```JS 6 | { 7 | EventIndex: 0, 8 | SessionIndex: 0, 9 | SessionType: 11, // @ENUM RaceSessionType 10 | Phase: 2, // @ENUM SessionPhase 11 | sessionTime: -1, 12 | sessionEndTime: -1, 13 | FocusedCarIndex: 0, 14 | ActiveCameraSet: "Drivable", 15 | ActiveCamera: "DashPro", 16 | CurrentHudPage: "Basic HUD", 17 | IsReplayPlaying: false, 18 | TimeOfDay: 82800, 19 | AmbientTemp: 21, 20 | TrackTemp: 21, 21 | Clouds: 0, 22 | RainLevel: 0, 23 | Wetness: 0, 24 | BestSessionLap: { 25 | Splits: [null, null, null], 26 | LaptimeMS: null, 27 | CarIndex: 0, 28 | DriverIndex: 0, 29 | IsInvalid: false, 30 | IsValidForBest: true, 31 | isOutlap: false, 32 | isInlap: false, 33 | }, 34 | } 35 | ``` 36 | 37 | ### registration_results 38 | 39 | ```JS 40 | { 41 | ConnectionId: 1, // Incrementing ID 42 | ConnectionSuccess: true, 43 | isReadOnly: false, 44 | errMsg: "", 45 | } 46 | ``` 47 | 48 | --- 49 | 50 | ### realtime_car_update 51 | 52 | ```JS 53 | { 54 | CarIndex: 0, 55 | DriverIndex: 0, 56 | DriverCount: 1, 57 | Gear: 0, 58 | WorldPosX: 0.10451728850603104, 59 | WorldPosY: 0.0009660766809247434, 60 | Yaw: -0.0001782241160981357, 61 | CarLocation: 1, // @ENUM CarLocationEnum 62 | Kmh: 0, 63 | Position: 1, 64 | CupPosition: 1, 65 | TrackPosition: 0, 66 | SplinePosition: 0.8307161927223206, 67 | Laps: 0, 68 | Delta: 0, 69 | BestSessionLap: { 70 | Splits: [ null, null, null ], 71 | LaptimeMS: null, 72 | CarIndex: 0, 73 | DriverIndex: 0, 74 | IsInvalid: false, 75 | IsValidForBest: true, 76 | isOutlap: false, 77 | isInlap: false 78 | }, 79 | LastLap: { 80 | Splits: [], 81 | LaptimeMS: null, 82 | CarIndex: 0, 83 | DriverIndex: 0, 84 | IsInvalid: false, 85 | IsValidForBest: true, 86 | isOutlap: false, 87 | isInlap: false 88 | }, 89 | CurrentLap: { 90 | Splits: [], 91 | LaptimeMS: 0, 92 | CarIndex: 0, 93 | DriverIndex: 0, 94 | IsInvalid: false, 95 | IsValidForBest: true, 96 | isOutlap: false, 97 | isInlap: false 98 | } 99 | } 100 | ``` 101 | 102 | ### track_data 103 | 104 | ```JS 105 | { 106 | CameraSets: { 107 | Drivable: [ 108 | 'Chase', 'FarChase', 109 | 'Bonnet', 'DashPro', 110 | 'Cockpit', 'Dash', 111 | 'Helmet' 112 | ], 113 | Helicam: [ 'Helicam' ], 114 | Onboard: [ 'Onboard0', 'Onboard1', 'Onboard2', 'Onboard3' ], 115 | pitlane: [ 'CameraPit1' ], 116 | set1: [ 117 | 'CameraTV4', 'CameraTV4B', 118 | 'CameraTV5', 'CameraTV6_0', 119 | 'CameraTV7_0', 'CameraTV8', 120 | 'CameraTV9', 'CameraTV10_16', 121 | 'CameraTV11', 'CameraTV12_5', 122 | 'CameraTV13', 'CameraTV14', 123 | 'CameraTV15', 'CameraTV16_1', 124 | 'CameraTV1', 'CameraTV1B_5', 125 | 'CameraTV2', 'CameraTV3' 126 | ], 127 | set2: [ 128 | 'CameraTV20', 'CameraTV21', 129 | 'CameraTV22', 'CameraTV23', 130 | 'CameraTV24', 'CameraTV25', 131 | 'CameraTV26', 'CameraTV27', 132 | 'CameraTV17', 'CameraTV18', 133 | 'CameraTV19' 134 | ], 135 | setVR: [ 136 | 'CameraVR3', 'CameraVR4', 137 | 'CameraVR5', 'CameraVR6', 138 | 'CameraVR7', 'CameraVR8', 139 | 'CameraVR9', 'CameraVR1', 140 | 'CameraVR2' 141 | ] 142 | }, 143 | HUDPages: [ 144 | 'Blank', 145 | 'Basic HUD', 146 | 'Help', 147 | 'TimeTable', 148 | 'Broadcasting', 149 | 'TrackMap' 150 | ], 151 | ConnectionId: 4, 152 | TrackName: 'Monza Circuit', 153 | TrackId: 3, 154 | TrackMeters: 5793 155 | } 156 | ``` 157 | 158 | ### entry_list_car 159 | 160 | ```JS 161 | { 162 | CarModelType: 22, 163 | TeamName: 'Ema Group 59 Racing', 164 | RaceNumber: 60, 165 | CupCategory: 0, 166 | CurrentDriverIndex: 0, 167 | Nationality: 41, // @ENUM NationalityEnum 168 | Drivers: [ 169 | { 170 | FirstName: 'Martin', 171 | LastName: 'Kodric', 172 | ShortName: 'KOD', 173 | Category: 1, // @ENUM DriverCategory 174 | Nationality: 33 // @ENUM NationalityEnum 175 | } 176 | ], 177 | CurrentDriver: { 178 | FirstName: 'Martin', 179 | LastName: 'Kodric', 180 | ShortName: 'KOD', 181 | Category: 1, // @ENUM DriverCategory 182 | Nationality: 33 // @ENUM NationalityEnum 183 | } 184 | } 185 | ``` 186 | 187 | ### broadcasting_event 188 | 189 | ```JS 190 | { 191 | Type: 7, // @ENUM BroadcastingCarEventType 192 | Msg: '01:50.748', 193 | TimeMS: 12054, 194 | CarId: 1052, 195 | Car: { 196 | CarModelType: 22, 197 | TeamName: '', 198 | RaceNumber: 27, 199 | CupCategory: 0, 200 | CurrentDriverIndex: 0, 201 | Nationality: 0, // @ENUM NationalityEnum 202 | Drivers: [ 203 | { 204 | FirstName: 'Martin', 205 | LastName: 'Kodric', 206 | ShortName: 'KOD', 207 | Category: 1, // @ENUM DriverCategory 208 | Nationality: 33 // @ENUM NationalityEnum 209 | } 210 | ], 211 | CurrentDriver: { 212 | FirstName: 'Martin', 213 | LastName: 'Kodric', 214 | ShortName: 'KOD', 215 | Category: 1, // @ENUM DriverCategory 216 | Nationality: 33 // @ENUM NationalityEnum 217 | } 218 | } 219 | } 220 | ``` 221 | 222 | # Enums 223 | 224 | ```C# 225 | enum DriverCategory 226 | { 227 | Platinum = 3, 228 | Gold = 2, 229 | Silver = 1, 230 | Bronze = 0, 231 | Error = 255 232 | } 233 | 234 | enum LapType 235 | { 236 | ERROR = 0, 237 | Outlap = 1, 238 | Regular = 2, 239 | Inlap = 3 240 | } 241 | 242 | enum CarLocationEnum 243 | { 244 | NONE = 0, 245 | Track = 1, 246 | Pitlane = 2, 247 | PitEntry = 3, 248 | PitExit = 4 249 | } 250 | 251 | enum SessionPhase 252 | { 253 | NONE = 0, 254 | Starting = 1, 255 | PreFormation = 2, 256 | FormationLap = 3, 257 | PreSession = 4, 258 | Session = 5, 259 | SessionOver = 6, 260 | PostSession = 7, 261 | ResultUI = 8 262 | }; 263 | 264 | enum RaceSessionType 265 | { 266 | Practice = 0, 267 | Qualifying = 4, 268 | Superpole = 9, 269 | Race = 10, 270 | Hotlap = 11, 271 | Hotstint = 12, 272 | HotlapSuperpole = 13, 273 | Replay = 14 274 | }; 275 | 276 | enum BroadcastingCarEventType 277 | { 278 | None = 0, 279 | GreenFlag = 1, 280 | SessionOver = 2, 281 | PenaltyCommMsg = 3, 282 | Accident = 4, 283 | LapCompleted = 5, 284 | BestSessionLap = 6, 285 | BestPersonalLap = 7 286 | }; 287 | 288 | enum NationalityEnum 289 | { 290 | Any = 0, 291 | Italy = 1, 292 | Germany = 2, 293 | France = 3, 294 | Spain = 4, 295 | GreatBritain = 5, 296 | Hungary = 6, 297 | Belgium = 7, 298 | Switzerland = 8, 299 | Austria = 9, 300 | Russia = 10, 301 | Thailand = 11, 302 | Netherlands = 12, 303 | Poland = 13, 304 | Argentina = 14, 305 | Monaco = 15, 306 | Ireland = 16, 307 | Brazil = 17, 308 | SouthAfrica = 18, 309 | PuertoRico = 19, 310 | Slovakia = 20, 311 | Oman = 21, 312 | Greece = 22, 313 | SaudiArabia = 23, 314 | Norway = 24, 315 | Turkey = 25, 316 | SouthKorea = 26, 317 | Lebanon = 27, 318 | Armenia = 28, 319 | Mexico = 29, 320 | Sweden = 30, 321 | Finland = 31, 322 | Denmark = 32, 323 | Croatia = 33, 324 | Canada = 34, 325 | China = 35, 326 | Portugal = 36, 327 | Singapore = 37, 328 | Indonesia = 38, 329 | USA = 39, 330 | NewZealand = 40, 331 | Australia = 41, 332 | SanMarino = 42, 333 | UAE = 43, 334 | Luxembourg = 44, 335 | Kuwait = 45, 336 | HongKong = 46, 337 | Colombia = 47, 338 | Japan = 48, 339 | Andorra = 49, 340 | Azerbaijan = 50, 341 | Bulgaria = 51, 342 | Cuba = 52, 343 | CzechRe= 53, 344 | Estonia = 54, 345 | Georgia = 55, 346 | India = 56, 347 | Israel = 57, 348 | Jamaica = 58, 349 | Latvia = 59, 350 | Lithuania = 60, 351 | Macau = 61, 352 | Malaysia = 62, 353 | Nepal = 63, 354 | NewCaledonia = 64, 355 | Nigeria = 65, 356 | NorthernIreland = 66, 357 | PapuaNewGuinea = 67, 358 | Philippines = 68, 359 | Qatar = 69, 360 | Romania = 70, 361 | Scotland = 71, 362 | Serbia = 72, 363 | Slovenia = 73, 364 | Taiwan = 74, 365 | Ukraine = 75, 366 | Venezuela = 76, 367 | Wales = 77, 368 | Iran = 78, 369 | Bahrain = 79, 370 | Zimbabwe = 80, 371 | ChineseTaipei = 81, 372 | Chile = 82, 373 | Uruguay = 83, 374 | Madagascar = 84 375 | }; 376 | ``` 377 | --------------------------------------------------------------------------------