Files
test/Documentation/ConfigVersion_Analysis.md
T
2026-03-13 16:03:47 +07:00

7.3 KiB

Config Version Mismatch Analysis

Problem Summary

When LoadConfigsFromServer calls LoadUserConfigData, the C# version throws an exception about version mismatch (dwVer > EC_CONFIG_VERSION).

Root Cause Analysis

Data Flow Comparison

C++ Version (EC_GameRun.cpp lines 2067-2169)

bool CECGameRun::LoadConfigsFromServer(const void* pDataBuf, int iDataSize)
{
    // 1. Read USERCFG_VERSION (version 3)
    DWORD dwVer = *(DWORD*)pData;
    pData += sizeof (DWORD);
    
    if (dwVer > USERCFG_VERSION)  // USERCFG_VERSION = 3
    {
        return false;
    }
    
    // 2. Uncompress data if version >= 3
    if (dwVer >= 3)
    {
        dwRealLen = 4096;
        pUncompBuf = a_malloctemp(dwRealLen);
        AFilePackage::Uncompress(pData, iDataSize-sizeof(DWORD), pUncompBuf, &dwRealLen);
        pData = (BYTE*)pUncompBuf;  // Point to uncompressed data
    }
    
    // 3. Create data reader with uncompressed data
    CECDataReader dr(pData, (int)dwRealLen);
    
    // 4. Read host configs
    int iSize = dr.Read_int();
    pHost->LoadConfigData(dr.Read_Data(iSize));
    
    // 5. Read UI configs
    iSize = dr.Read_int();
    pGameUI->SetUserLayout(dr.Read_Data(iSize), iSize);
    
    // 6. Read user settings (if dwVer >= 2)
    if (dwVer >= 2)
    {
        iSize = dr.Read_int();
        g_pGame->GetConfigs()->LoadUserConfigData(dr.Read_Data(iSize), iSize);
        // This data starts with EC_CONFIG_VERSION (36)
    }
}

C# Version (CECGameRun.cs lines 273-392)

public bool LoadConfigsFromServer(byte[] pDataBuf, int iDataSize)
{
    const uint USERCFG_VERSION = 3;
    
    int offset = 0;
    
    // 1. Read USERCFG_VERSION
    uint dwVer = System.BitConverter.ToUInt32(pDataBuf, offset);
    offset += sizeof(uint);
    
    if (dwVer > USERCFG_VERSION)  // USERCFG_VERSION = 3
    {
        Debug.LogError($"version {dwVer} > {USERCFG_VERSION}");
        return false;
    }
    
    byte[] pUncompBuf = null;
    uint dwRealLen = (uint)(iDataSize - sizeof(uint));
    byte[] pData = pDataBuf;
    int dataOffset = offset;  // ⚠️ This is set but never used!
    
    // 2. Uncompress if version >= 3
    if (dwVer >= 3)
    {
        dwRealLen = 4096;
        pUncompBuf = new byte[dwRealLen];
        
        byte[] compressedData = new byte[iDataSize - sizeof(uint)];
        System.Array.Copy(pDataBuf, offset, compressedData, 0, compressedData.Length);
        
        int iRes = AFilePackage.Uncompress(compressedData, compressedData.Length, 
                                          pUncompBuf, ref dwRealLen);
        if (iRes != 0)
        {
            return false;
        }
        
        pData = pUncompBuf;  // ⚠️ Point to uncompressed buffer
    }
    
    // 3. Create data reader - ⚠️ PROBLEM HERE!
    // In C++, pData points to the uncompressed data
    // In C#, we should use the same approach
    CECDataReader dr = new CECDataReader(pData, (int)dwRealLen);
    
    // 4. Read host configs
    int iSize = dr.ReadInt();
    byte[] hostConfigData = dr.ReadData(iSize);
    pHost.LoadConfigData(hostConfigData);
    
    // 5. Read UI configs
    iSize = dr.ReadInt();
    byte[] uiConfigData = dr.ReadData(iSize);
    
    // 6. Read user settings
    if (dwVer >= 2)
    {
        iSize = dr.ReadInt();
        byte[] settingsData = dr.ReadData(iSize);
        
        // ⚠️ HERE IS WHERE THE ERROR OCCURS
        if (!EC_Game.GetConfigs().LoadUserConfigData(settingsData, iSize))
        {
            return false;
        }
    }
}

LoadUserConfigData Comparison

C++ Version (EC_Configs.cpp lines 628-671)

bool CECConfigs::LoadUserConfigData(const void* pDataBuf, int iDataSize)
{
    CECDataReader dr((void*)pDataBuf, iDataSize);
    
    // Read EC_CONFIG_VERSION (should be 36)
    DWORD dwVer = dr.Read_DWORD();
    
    if (dwVer < 15)
    {
        DefaultUserConfigData();
        goto End;
    }
    else if (dwVer > EC_CONFIG_VERSION)  // EC_CONFIG_VERSION = 36
    {
        throw CECException(CECException::TYPE_DATAERR);
    }
    
    m_vs.Read(dr, dwVer);
    m_gs.Read(dr, dwVer);
    m_bs.Read(dr, dwVer);
    m_cas.Read(dr, dwVer);
}

C# Version (EC_Configs.cs lines 1070-1106)

public bool LoadUserConfigData(byte[] pDataBuf, int iDataSize)
{
    using (MemoryStream ms = new MemoryStream(pDataBuf, 0, iDataSize))
    using (BinaryReader reader = new BinaryReader(ms))
    {
        // Read EC_CONFIG_VERSION (expecting 36)
        uint dwVer = reader.ReadUInt32();
        
        if (dwVer < 15)
        {
            DefaultUserConfigData();
            goto End;
        }
        else if (dwVer > EC_ConfigConstants.EC_CONFIG_VERSION)  // 36
        {
            throw new Exception("version mismatch dwVer=" + dwVer);
        }
        
        m_vs.Read(reader, dwVer);
        m_gs.Read(reader, dwVer);
        m_bs.Read(reader, dwVer);
        m_cas.Read(reader, dwVer);
    }
}

The Actual Problem

The issue is that settingsData does NOT start with a proper version number. When LoadUserConfigData tries to read the first 4 bytes as dwVer, it's reading garbage data that happens to be larger than 36.

Possible Causes

1. CECDataReader.ReadData() Implementation Issue

The C# CECDataReader.ReadData(int size) method may not be returning the correct data. Let me check if this method exists and how it's implemented.

2. Data Alignment/Packing Issue

The structs being saved in C++ may have different memory layout than the C# structs due to packing/alignment differences.

3. SaveConfigsToServer Format Issue

The data being saved might not match the expected format. The C++ version (lines 2040-2063) shows:

  • Version is NOT compressed
  • Only the config data after version is compressed
  • But when loading, after uncompressing, the data should contain all three config sections

Solution Steps

  1. Add Debug Logging: Log the first 16 bytes of settingsData to see what version number is being read
  2. Verify CECDataReader: Ensure ReadData() returns the correct bytes
  3. Check Struct Sizes: Verify that Marshal.SizeOf() for each struct matches the C++ sizeof()
  4. Verify Decompression: Ensure the uncompressed data length matches expectations

Immediate Fix

Add debug logging in CECGameRun.cs after line 377:

byte[] settingsData = dr.ReadData(iSize);

// DEBUG: Log the first 16 bytes
BMLogger.LogError($"LoadConfigsFromServer - settingsData size: {iSize}, first 16 bytes: " +
    $"{BitConverter.ToString(settingsData.Take(Math.Min(16, settingsData.Length)).ToArray())}");

// DEBUG: Read the version to see what we're getting
uint debugVer = System.BitConverter.ToUInt32(settingsData, 0);
BMLogger.LogError($"LoadConfigsFromServer - Version read from settingsData: {debugVer} (expected <= 36)");

This will help identify what data is actually being passed to LoadUserConfigData.

Expected Data Format

After uncompression, the data should be:

[4 bytes: host config size] [host config data...]
[4 bytes: UI config size] [UI config data...]  
[4 bytes: user config size] [user config data...]
                              ↑ This should start with EC_CONFIG_VERSION (36)

If the version being read is > 36, it means either:

  1. The data reader is at the wrong position
  2. The size being read is incorrect
  3. The data format from server doesn't match expectations