如何正确将 F1 23 遥测数据包解包为字典?

huangapple go评论77阅读模式
英文:

How can I unpack a F1 23 Telemetry Packet to a dictionary correctly?

问题

为了提供上下文,我正在尝试调整f1_22_telemetry代码以适应新的F1 23 UDP套接字(我的存储库).
这是EA的文档链接: https://answers.ea.com/t5/General-Discussion/F1-23-UDP-Specification/td-p/12632888?attachment-id=704910
一些数据包似乎工作正常,但在转换为字典后,其他数据包显示奇怪/错误的值。
我使用了Chris Hannam的代码,并根据EA文档中的信息更改了数据包的类。因此,逻辑应该是相同的,并且在F1 22套接字中正常工作。
示例: PacketSessionData
我的类:

class PacketSessionData(Packet):
    _fields_ = [
        ("header", PacketHeader),
        ("weather", ctypes.c_uint8),
        ("trackTemperature", ctypes.c_int8),
        ("airTemperature", ctypes.c_int8),
        ("totalLaps", ctypes.c_uint8),
        ("trackLength", ctypes.c_uint16),
        ("sessionType", ctypes.c_uint8),
        ("trackId", ctypes.c_int8),
        ("formula", ctypes.c_uint8),
        ("sessionTimeLeft", ctypes.c_uint16),
        ("sessionDuration", ctypes.c_uint16),
        ("pitSpeedLimit", ctypes.c_uint8),
        ("gamePaused", ctypes.c_uint8),
        ("isSpectating", ctypes.c_uint8),
        ("spectatorCarIndex", ctypes.c_uint8),
        ("sliProNativeSupport", ctypes.c_uint8),
        ("numMarshalZones", ctypes.c_uint8),
        ("marshalZones", MarshalZone * 21),
        ("safetyCarStatus", ctypes.c_uint8),
        ("networkGame", ctypes.c_uint8),
        ("numWeatherForecastSamples", ctypes.c_uint8),
        ("forecastAccuracy", ctypes.c_uint8),
        ("aiDifficulty", ctypes.c_uint8),
        ("seasonLinkIdentifier", ctypes.c_uint32),
        ("weekendLinkIdentifier", ctypes.c_uint32),
        ("sessionLinkIdentifier", ctypes.c_uint32),
        ("pitStopWindowIdealLap", ctypes.c_uint8),
        ("pitStopWindowLatestLap", ctypes.c_uint8),
        ("pitStopRejoinPosition", ctypes.c_uint8),
        ("steeringAssist", ctypes.c_uint8),
        ("brakingAssist", ctypes.c_uint8),
        ("gearboxAssist", ctypes.c_uint8),
        ("pitAssist", ctypes.c_uint8),
        ("pitReleaseAssist", ctypes.c_uint8),
        ("ERSAssist", ctypes.c_uint8),
        ("DRSAssist", ctypes.c_uint8),
        ("dynamicRacingLine", ctypes.c_uint8),
        ("dynamicRacingLineType", ctypes.c_uint8),
        ("gameMode", ctypes.c_uint8),
        ("ruleSet", ctypes.c_uint8),
        ("timeOfDay", ctypes.c_uint32),
        ("sessionLength", ctypes.c_uint8),
        ("speedUnitsLeadPlayer", ctypes.c_uint8),
        ("speedUnitsSecondaryPlayer", ctypes.c_uint8),
        ("temperatureUnitsLeadPlayer", ctypes.c_uint8),
        ("temperatureUnitsSecondaryPlayer", ctypes.c_uint8),
        ("numSafetyCarPeriods", ctypes.c_uint8),
        ("numVirtualSafetyCarPeriods", ctypes.c_uint8),
        ("numRedFlagPeriods", ctypes.c_uint8),
    ]

文档:

struct PacketSessionData
{
    PacketHeader    m_header;               	// Header
    uint8           m_weather;              	// 天气 - 0 = 晴朗, 1 = 轻云, 2 = 阴天, 3 = 小雨, 4 = 大雨, 5 = 暴风雨
    int8	        m_trackTemperature;    	// 赛道温度(摄氏度)
    int8	        m_airTemperature;      	// 空气温度(摄氏度)
    uint8           m_totalLaps;           	// 此比赛的总圈数
    uint16          m_trackLength;           // 赛道长度(米)
    uint8           m_sessionType;         	// 0 = 未知, 1 = P1, 2 = P2, 3 = P3, 4 = 短P
                                          	// 5 = Q1, 6 = Q2, 7 = Q3, 8 = 短Q, 9 = OSQ
                                          	// 10 = R, 11 = R2, 12 = R3, 13 = 计时赛
    int8            m_trackId;         		// -1表示未知,见附录
    uint8           m_formula;              	// Formula, 0 = 现代F1, 1 = 经典F1, 2 = F2,
                                             // 3 = 通用F1, 4 = Beta, 5 = 超级跑车
                                             // 6 = 电子竞技, 7 = F2 2021
    uint16          m_sessionTimeLeft;    	// 会话剩余时间(秒)
    uint16          m_sessionDuration;     	// 会话持续时间(秒)
    uint8           m_pitSpeedLimit;      	// 限制坑道速度(千米每小时)
    uint8           m_gamePaused;          	// 游戏是否暂停(仅限网络游戏)
    uint8           m_isSpectating;        	// 玩家是否在旁观
    uint8           m_spectatorCarIndex;  	// 正在旁观的赛车索引
    uint8           m_sliProNativeSupport;	// SLI Pro支持,0 = 不活动,1 = 活动
    uint8           m_numMarshalZones;      // 要跟踪的警告区域数量
    MarshalZone     m_marshalZones[21];      // 警告区域列表 - 最多21个
    uint8           m_safetyCarStatus;       // 0 = 无安全车,1 = 完全安全车
                                             // 2 = 虚拟安全车,3 = 成组环节
    uint8           m_networkGame;           // 0 = 离线,1 = 在线
    uint8           m_numWeatherForecastSamples; //

<details>
<summary>英文:</summary>

For context: I&#39;m trying to adjust the [f1_22_telemetry](https://github.com/chrishannam/f1-22-telemetry) code to the new F1 23 UDP Socket ([my repo](https://github.com/JulMai/f1_telemetry_socket)).&lt;br&gt;
Here is the Documentation by EA: https://answers.ea.com/t5/General-Discussion/F1-23-UDP-Specification/td-p/12632888?attachment-id=704910
&lt;br&gt;
Some packets appear to be working fine, but others show weird/wrong values after being converted to a dictionary.

I took Chris Hannam&#39;s Code and changed the classes for the Packets with the information in the EA documentation. So the logic should be the same and worked fine with the F1 22 Socket.

Example: PacketSessionData&lt;br&gt;
My Class:

class PacketSessionData(Packet):
fields = [
("header", PacketHeader),
("weather", ctypes.c_uint8),
("trackTemperature", ctypes.c_int8),
("airTemperature", ctypes.c_int8),
("totalLaps", ctypes.c_uint8),
("trackLength", ctypes.c_uint16),
("sessionType", ctypes.c_uint8),
("trackId", ctypes.c_int8),
("formula", ctypes.c_uint8),
("sessionTimeLeft", ctypes.c_uint16),
("sessionDuration", ctypes.c_uint16),
("pitSpeedLimit", ctypes.c_uint8),
("gamePaused", ctypes.c_uint8),
("isSpectating", ctypes.c_uint8),
("spectatorCarIndex", ctypes.c_uint8),
("sliProNativeSupport", ctypes.c_uint8),
("numMarshalZones", ctypes.c_uint8),
("marshalZones", MarshalZone * 21),
("safetyCarStatus", ctypes.c_uint8),
("networkGame", ctypes.c_uint8),
("numWeatherForecastSamples", ctypes.c_uint8),
("forecastAccuracy", ctypes.c_uint8),
("aiDifficulty", ctypes.c_uint8),
("seasonLinkIdentifier", ctypes.c_uint32),
("weekendLinkIdentifier", ctypes.c_uint32),
("sessionLinkIdentifier", ctypes.c_uint32),
("pitStopWindowIdealLap", ctypes.c_uint8),
("pitStopWindowLatestLap", ctypes.c_uint8),
("pitStopRejoinPosition", ctypes.c_uint8),
("steeringAssist", ctypes.c_uint8),
("brakingAssist", ctypes.c_uint8),
("gearboxAssist", ctypes.c_uint8),
("pitAssist", ctypes.c_uint8),
("pitReleaseAssist", ctypes.c_uint8),
("ERSAssist", ctypes.c_uint8),
("DRSAssist", ctypes.c_uint8),
("dynamicRacingLine", ctypes.c_uint8),
("dynamicRacingLineType", ctypes.c_uint8),
("gameMode", ctypes.c_uint8),
("ruleSet", ctypes.c_uint8),
("timeOfDay", ctypes.c_uint32),
("sessionLength", ctypes.c_uint8),
("speedUnitsLeadPlayer", ctypes.c_uint8),
("speedUnitsSecondaryPlayer", ctypes.c_uint8),
("numSafetyCarPeriods", ctypes.c_uint8),
("numRedFlagPeriods", ctypes.c_uint8),
]

The Documentation:

struct PacketSessionData
{
PacketHeader m_header; // Header

uint8           m_weather;              	// Weather - 0 = clear, 1 = light cloud, 2 = overcast
                                        	// 3 = light rain, 4 = heavy rain, 5 = storm
int8	            m_trackTemperature;    	// Track temp. in degrees celsius
int8	            m_airTemperature;      	// Air temp. in degrees celsius
uint8           m_totalLaps;           	// Total number of laps in this race
uint16          m_trackLength;           	// Track length in metres
uint8           m_sessionType;         	// 0 = unknown, 1 = P1, 2 = P2, 3 = P3, 4 = Short P
                                        	// 5 = Q1, 6 = Q2, 7 = Q3, 8 = Short Q, 9 = OSQ
                                        	// 10 = R, 11 = R2, 12 = R3, 13 = Time Trial
int8            m_trackId;         		// -1 for unknown, see appendix
uint8           m_formula;                  	// Formula, 0 = F1 Modern, 1 = F1 Classic, 2 = F2,
                                             // 3 = F1 Generic, 4 = Beta, 5 = Supercars

// 6 = Esports, 7 = F2 2021
uint16 m_sessionTimeLeft; // Time left in session in seconds
uint16 m_sessionDuration; // Session duration in seconds
uint8 m_pitSpeedLimit; // Pit speed limit in kilometres per hour
uint8 m_gamePaused; // Whether the game is paused – network game only
uint8 m_isSpectating; // Whether the player is spectating
uint8 m_spectatorCarIndex; // Index of the car being spectated
uint8 m_sliProNativeSupport; // SLI Pro support, 0 = inactive, 1 = active
uint8 m_numMarshalZones; // Number of marshal zones to follow
MarshalZone m_marshalZones[21]; // List of marshal zones – max 21
uint8 m_safetyCarStatus; // 0 = no safety car, 1 = full
// 2 = virtual, 3 = formation lap
uint8 m_networkGame; // 0 = offline, 1 = online
uint8 m_numWeatherForecastSamples; // Number of weather samples to follow
WeatherForecastSample m_weatherForecastSamples[56]; // Array of weather forecast samples
uint8 m_forecastAccuracy; // 0 = Perfect, 1 = Approximate
uint8 m_aiDifficulty; // AI Difficulty rating – 0-110
uint32 m_seasonLinkIdentifier; // Identifier for season - persists across saves
uint32 m_weekendLinkIdentifier; // Identifier for weekend - persists across saves
uint32 m_sessionLinkIdentifier; // Identifier for session - persists across saves
uint8 m_pitStopWindowIdealLap; // Ideal lap to pit on for current strategy (player)
uint8 m_pitStopWindowLatestLap; // Latest lap to pit on for current strategy (player)
uint8 m_pitStopRejoinPosition; // Predicted position to rejoin at (player)
uint8 m_steeringAssist; // 0 = off, 1 = on
uint8 m_brakingAssist; // 0 = off, 1 = low, 2 = medium, 3 = high
uint8 m_gearboxAssist; // 1 = manual, 2 = manual & suggested gear, 3 = auto
uint8 m_pitAssist; // 0 = off, 1 = on
uint8 m_pitReleaseAssist; // 0 = off, 1 = on
uint8 m_ERSAssist; // 0 = off, 1 = on
uint8 m_DRSAssist; // 0 = off, 1 = on
uint8 m_dynamicRacingLine; // 0 = off, 1 = corners only, 2 = full
uint8 m_dynamicRacingLineType; // 0 = 2D, 1 = 3D
uint8 m_gameMode; // Game mode id - see appendix
uint8 m_ruleSet; // Ruleset - see appendix
uint32 m_timeOfDay; // Local time of day - minutes since midnight
uint8 m_sessionLength; // 0 = None, 2 = Very Short, 3 = Short, 4 = Medium
// 5 = Medium Long, 6 = Long, 7 = Full
uint8 m_speedUnitsLeadPlayer; // 0 = MPH, 1 = KPH
uint8 m_temperatureUnitsLeadPlayer; // 0 = Celsius, 1 = Fahrenheit
uint8 m_speedUnitsSecondaryPlayer; // 0 = MPH, 1 = KPH
uint8 m_temperatureUnitsSecondaryPlayer; // 0 = Celsius, 1 = Fahrenheit
uint8 m_numSafetyCarPeriods; // Number of safety cars called during session
uint8 m_numVirtualSafetyCarPeriods; // Number of virtual safety cars called
uint8 m_numRedFlagPeriods; // Number of red flags called during session
};

When I receive a SessionData-Packet, there are values that should be impossible to get according to the documentation.
e.g.: &lt;br&gt;
gearBoxAssist: 33&lt;br&gt;
pitReleaseAssist: 27&lt;br&gt;

So I assume something goes wrong during translating the bytes to the dictionary.

</details>


# 答案1
**得分**: 1

以下是代码的翻译部分:

```py
import ctypes as ct

class PacketHeader(ct.LittleEndianStructure):
    _pack_ = 1
    _fields_ = (('m_packetFormat', ct.c_uint16),
                ('m_gameYear', ct.c_uint8),
                ('m_gameMajorVersion', ct.c_uint8),
                ('m_gameMinorVersion', ct.c_uint8),
                ('m_packetVersion', ct.c_uint8),
                ('m_packetId', ct.c_uint8),
                ('m_sessionUID', ct.c_uint64),
                ('m_sessionTime', ct.c_float),
                ('m_frameIdentifier', ct.c_uint32),
                ('m_overallFrameIdentifier', ct.c_uint32),
                ('m_playerCarIndex', ct.c_uint8),
                ('m_secondaryPlayerCarIndex', ct.c_uint8))

class MarshalZone(ct.LittleEndianStructure):
    _pack_ = 1
    _fields_ = ('m_zoneStart', ct.c_float),
                ('m_zoneFlag', ct.c_int8))

class WeatherForecastSample(ct.LittleEndianStructure):
    _pack_ = 1
    _fields_ = (('m_sessionType', ct.c_uint8),
                ('m_timeOffset', ct.c_uint8),
                ('m_weather', ct.c_uint8),
                ('m_trackTemperature', ct.c_int8),
                ('m_trackTemperatureChange', ct.c_int8),
                ('m_airTemperature', ct.c_int8),
                ('m_airTemperatureChange', ct.c_int8),
                ('m_rainPercentage', ct.c_uint8))

class PacketSessionData(ct.LittleEndianStructure):
    _pack_ = 1
    _fields_ = (('m_header', PacketHeader),
                ('m_weather', ct.c_uint8),
                ('m_trackTemperature', ct.c_int8),
                ('m_airTemperature', ct.c_int8),
                ('m_totalLaps', ct.c_uint8),
                ('m_trackLength', ct.c_uint16),
                ('m_sessionType', ct.c_uint8),
                ('m_trackId', ct.c_int8),
                ('m_formula', ct.c_uint8),
                ('m_sessionTimeLeft', ct.c_uint16),
                ('m_sessionDuration', ct.c_uint16),
                ('m_pitSpeedLimit', ct.c_uint8),
                ('m_gamePaused', ct.c_uint8),
                ('m_isSpectating', ct.c_uint8),
                ('m_spectatorCarIndex', ct.c_uint8),
                ('m_sliProNativeSupport', ct.c_uint8),
                ('m_numMarshalZones', ct.c_uint8),
                ('m_marshalZones', MarshalZone * 21),
                ('m_safetyCarStatus', ct.c_uint8),
                ('m_networkGame', ct.c_uint8),
                ('m_numWeatherForecastSamples', ct.c_uint8),
                ('m_weatherForecastSamples', WeatherForecastSample * 56),
                ('m_forecastAccuracy', ct.c_uint8),
                ('m_aiDifficulty', ct.c_uint8),
                ('m_seasonLinkIdentifier', ct.c_uint32),
                ('m_weekendLinkIdentifier', ct.c_uint32),
                ('m_sessionLinkIdentifier', ct.c_uint32),
                ('m_pitStopWindowIdealLap', ct.c_uint8),
                ('m_pitStopWindowLatestLap', ct.c_uint8),
                ('m_pitStopRejoinPosition', ct.c_uint8),
                ('m_steeringAssist', ct.c_uint8),
                ('m_brakingAssist', ct.c_uint8),
                ('m_gearboxAssist', ct.c_uint8),
                ('m_pitAssist', ct.c_uint8),
                ('m_pitReleaseAssist', ct.c_uint8),
                ('m_ERSAssist', ct.c_uint8),
                ('m_DRSAssist', ct.c_uint8),
                ('m_dynamicRacingLine', ct.c_uint8),
                ('m_dynamicRacingLineType', ct.c_uint8),
                ('m_gameMode', ct.c_uint8),
                ('m_ruleSet', ct.c_uint8),
                ('m_timeOfDay', ct.c_uint32),
                ('m_sessionLength', ct.c_uint8),
                ('m_speedUnitsLeadPlayer', ct.c_uint8),
                ('m_temperatureUnitsLeadPlayer', ct.c_uint8),
                ('m_speedUnitsSecondaryPlayer', ct.c_uint8),
                ('m_temperatureUnitsSecondaryPlayer', ct.c_uint8),
                ('m_numSafetyCarPeriods', ct.c_uint8),
                ('m_numVirtualSafetyCarPeriods', ct.c_uint8),
                ('m_numRedFlagPeriods', ct.c_uint8))

assert ct.sizeof(PacketSessionData) == 644  # 用于验证
英文:

From the documentation:

> Packet Types
>
> [...] Please note that all values are encoded using Little Endian format. All data is packed.

> Session Packet
>
> [...] Size: 644 bytes

Here's a complete definition that matches the documented structure size of 644 bytes.

The problems were:

  1. Sub-structures missing.
  2. Structures weren't packed.
  3. Four members of PacketSessionData were missing.

LittleEndianStructure is used in the off-chance portability is needed.

import ctypes as ct

class PacketHeader(ct.LittleEndianStructure):
    _pack_ = 1
    _fields_ = ((&#39;m_packetFormat&#39;, ct.c_uint16),
                (&#39;m_gameYear&#39;, ct.c_uint8),
                (&#39;m_gameMajorVersion&#39;, ct.c_uint8),
                (&#39;m_gameMinorVersion&#39;, ct.c_uint8),
                (&#39;m_packetVersion&#39;, ct.c_uint8),
                (&#39;m_packetId&#39;, ct.c_uint8),
                (&#39;m_sessionUID&#39;, ct.c_uint64),
                (&#39;m_sessionTime&#39;, ct.c_float),
                (&#39;m_frameIdentifier&#39;, ct.c_uint32),
                (&#39;m_overallFrameIdentifier&#39;, ct.c_uint32),
                (&#39;m_playerCarIndex&#39;, ct.c_uint8),
                (&#39;m_secondaryPlayerCarIndex&#39;, ct.c_uint8))

class MarshalZone(ct.LittleEndianStructure):
    _pack_ = 1
    _fields_ = ((&#39;m_zoneStart&#39;, ct.c_float),
                (&#39;m_zoneFlag&#39;, ct.c_int8))

class WeatherForecastSample(ct.LittleEndianStructure):
    _pack_ = 1
    _fields_ = ((&#39;m_sessionType&#39;, ct.c_uint8),
                (&#39;m_timeOffset&#39;, ct.c_uint8),
                (&#39;m_weather&#39;, ct.c_uint8),
                (&#39;m_trackTemperature&#39;, ct.c_int8),
                (&#39;m_trackTemperatureChange&#39;, ct.c_int8),
                (&#39;m_airTemperature&#39;, ct.c_int8),
                (&#39;m_airTemperatureChange&#39;, ct.c_int8),
                (&#39;m_rainPercentage&#39;, ct.c_uint8))

class PacketSessionData(ct.LittleEndianStructure):
    _pack_ = 1
    _fields_ = ((&#39;m_header&#39;, PacketHeader),
                (&#39;m_weather&#39;, ct.c_uint8),
                (&#39;m_trackTemperature&#39;, ct.c_int8),
                (&#39;m_airTemperature&#39;, ct.c_int8),
                (&#39;m_totalLaps&#39;, ct.c_uint8),
                (&#39;m_trackLength&#39;, ct.c_uint16),
                (&#39;m_sessionType&#39;, ct.c_uint8),
                (&#39;m_trackId&#39;, ct.c_int8),
                (&#39;m_formula&#39;, ct.c_uint8),
                (&#39;m_sessionTimeLeft&#39;, ct.c_uint16),
                (&#39;m_sessionDuration&#39;, ct.c_uint16),
                (&#39;m_pitSpeedLimit&#39;, ct.c_uint8),
                (&#39;m_gamePaused&#39;, ct.c_uint8),
                (&#39;m_isSpectating&#39;, ct.c_uint8),
                (&#39;m_spectatorCarIndex&#39;, ct.c_uint8),
                (&#39;m_sliProNativeSupport&#39;, ct.c_uint8),
                (&#39;m_numMarshalZones&#39;, ct.c_uint8),
                (&#39;m_marshalZones&#39;, MarshalZone * 21),
                (&#39;m_safetyCarStatus&#39;, ct.c_uint8),
                (&#39;m_networkGame&#39;, ct.c_uint8),
                (&#39;m_numWeatherForecastSamples&#39;, ct.c_uint8),
                (&#39;m_weatherForecastSamples&#39;, WeatherForecastSample * 56),  # missing
                (&#39;m_forecastAccuracy&#39;, ct.c_uint8),
                (&#39;m_aiDifficulty&#39;, ct.c_uint8),
                (&#39;m_seasonLinkIdentifier&#39;, ct.c_uint32),
                (&#39;m_weekendLinkIdentifier&#39;, ct.c_uint32),
                (&#39;m_sessionLinkIdentifier&#39;, ct.c_uint32),
                (&#39;m_pitStopWindowIdealLap&#39;, ct.c_uint8),
                (&#39;m_pitStopWindowLatestLap&#39;, ct.c_uint8),
                (&#39;m_pitStopRejoinPosition&#39;, ct.c_uint8),
                (&#39;m_steeringAssist&#39;, ct.c_uint8),
                (&#39;m_brakingAssist&#39;, ct.c_uint8),
                (&#39;m_gearboxAssist&#39;, ct.c_uint8),
                (&#39;m_pitAssist&#39;, ct.c_uint8),
                (&#39;m_pitReleaseAssist&#39;, ct.c_uint8),
                (&#39;m_ERSAssist&#39;, ct.c_uint8),
                (&#39;m_DRSAssist&#39;, ct.c_uint8),
                (&#39;m_dynamicRacingLine&#39;, ct.c_uint8),
                (&#39;m_dynamicRacingLineType&#39;, ct.c_uint8),
                (&#39;m_gameMode&#39;, ct.c_uint8),
                (&#39;m_ruleSet&#39;, ct.c_uint8),
                (&#39;m_timeOfDay&#39;, ct.c_uint32),
                (&#39;m_sessionLength&#39;, ct.c_uint8),
                (&#39;m_speedUnitsLeadPlayer&#39;, ct.c_uint8),
                (&#39;m_temperatureUnitsLeadPlayer&#39;, ct.c_uint8),  # missing
                (&#39;m_speedUnitsSecondaryPlayer&#39;, ct.c_uint8),
                (&#39;m_temperatureUnitsSecondaryPlayer&#39;, ct.c_uint8),  # missing
                (&#39;m_numSafetyCarPeriods&#39;, ct.c_uint8),
                (&#39;m_numVirtualSafetyCarPeriods&#39;, ct.c_uint8),  # missing
                (&#39;m_numRedFlagPeriods&#39;, ct.c_uint8))

assert ct.sizeof(PacketSessionData) == 644  # for verification

huangapple
  • 本文由 发表于 2023年6月22日 01:41:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/76525898.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定