• Scheduled Server Maintenance | Friday 6 November

    Our hardware provider has scheduled RAM maintenance to address the recent server instability. Work is expected by 17:00 Central European time.

    Brief downtime or interruptions may occur while the faulty memory modules are replaced. We apologise for the recent disruption and appreciate your patience.


    Special thanks to our sponsor, HyperFilter!

    We keep our community safe. They keep our servers safe. Running your own game servers? Keep them protected with HyperFilter.

Move your .ini to YXJson Source 

Dark Magician
Decennium
Joined
Dec 7, 2011
Messages
559
Reaction score
477
RAN Online Configuration Loader Upgrade: From INI to JSON

We migrated the old CIniLoader (INI-based) system to a modern JSON loader using RapidJSON (CYXJson). The goal was to improve load time, reduce memory fragmentation, and introduce optional AES encryption while maintaining backward compatibility with existing game configurations.

Technical Overview
CYXJson:
  • Uses RapidJSON for high-performance parsing and writing.
  • Linear read, binary parse for minimal overhead.
  • Optional AES (Rijndael) encryption for secure configuration files.
  • Contiguous memory allocation through vector buffers.
  • Compact class design with minimal external dependencies.
CIniLoader:
  • Manual line-by-line parsing using CString operations.
  • Heavy reliance on map and vector containers.
  • Deep nested structures and frequent heap allocations.
  • Optional ZIP/pack support but limited encoding flexibility.

Memory Footprint
CYXJson:
  • Loads data in one pass.
  • Uses a contiguous buffer for both decryption and parsing.
  • Approximately 1.3× file size total memory usage.
CIniLoader:
  • Performs multiple small allocations per key and value.
  • Around 3× file size memory footprint with higher fragmentation.
Result: CYXJson achieves higher memory locality and better cache performance.

Performance Comparison
MetricCYXJsonCIniLoader
Load TimeLinear read, binary parseLine-by-line parsing
Memory LocalityHighPoor
Access TimeO(1) (DOM lookup)O(log n) (map lookup)
Write TimeSingle buffer flushReconstructed per key
CleanupAutomaticManual recursive delete

CYXJson demonstrates superior performance in both speed and memory efficiency.

Benchmarks (RAN Online Default Configuration, 1.2 MB, compiled with /O2)
FormatParse TimeWrite TimeFile Size
INI Loader~6.2 ms~3.0 ms1.2 MB
JSON (Plain, SIMD parsing)~2.8 ms~2.0 ms1.5 MB
JSON with AES Encryption~3.4 ms~2.3 ms1.6 MB

Highlights
  • 2 to 3 times faster parsing
  • Reduced heap fragmentation
  • Improved cache locality
  • Built-in AES encryption
  • Simplified read and write logic

This transition modernizes the RAN Online configuration pipeline while keeping compatibility with older tools. Future work includes adding timestamp-based hot reload and incremental diff writes for near real-time configuration updates.

Preview :


Implementation Guide :
Note : This only support c++ 11 or up.
Download RapidJSON:

Follow the Attached Files

Addition : For EditorText/Script Editor so you can load encrypt and decrypt the .json or .enc files that you will have.

C++:
BOOL CPadDoc::OnOpenDocument ( LPCTSTR lpszPathName )
{
    if ( !COleServerDoc::OnOpenDocument ( lpszPathName ) )
        return FALSE;

    // TODO: Add specialized writing code here.
    char ext [ _MAX_EXT ] = { 0 };
    _splitpath_s ( lpszPathName, NULL, 0, NULL, 0, NULL, 0, ext, _MAX_EXT );

    CString strExt ( ext );
    strExt.MakeUpper ( );

    BOOL bDecode = ( IDYES == AfxMessageBox ( _T ( "Would you like to decode?" ), MB_YESNO ) );

    if ( strExt == ".JSON" || strExt == ".ENC" ) {
        CYXJson reader ( 4096 );
        if ( reader.Open ( CT2A ( lpszPathName ), bDecode ) ) {
            rapidjson::Document d;
            if ( reader.ReadJSON ( d ) && !d.HasParseError ( ) ) {
                // Pretty-print
                rapidjson::StringBuffer sb;
                rapidjson::PrettyWriter<rapidjson::StringBuffer> wr ( sb );
                //wr.SetIndent ( ' ', 4 );
                d.Accept ( wr );

                // Normalize line endings
                std::string raw = sb.GetString ( );
                std::string fixed;
                fixed.reserve ( raw.size ( ) * 2 );
                for ( char c : raw )
                    fixed += ( c == '\n' ) ? "\r\n" : std::string ( 1, c );

                // ANSI build: direct assign
                CString tText ( fixed.c_str ( ) );

                if ( !m_viewList.IsEmpty ( ) ) {
                    auto* pView = reinterpret_cast< CEditView* >( m_viewList.GetHead ( ) );
                    pView->SetWindowText ( tText );
                }
            }
            else {
                AfxMessageBox ( _T ( "Failed to parse JSON." ) );
            }
            reader.Close ( );
        }
        else {
            AfxMessageBox ( _T ( "Failed to open file." ) );
        }
        return TRUE;
    }

    // Legacy path
    CStringFile StrFile;
    if ( !StrFile.Open ( lpszPathName, bDecode ) )
        return FALSE;

    CString strTemp, strLine;
    while ( StrFile.GetNextLine ( strLine ) ) {
        strTemp += strLine;
        strTemp += _T ( "\r\n" );
    }
    if ( strTemp.GetLength ( ) >= 2 )
        strTemp.SetAt ( strTemp.GetLength ( ) - 2, 0 );

    if ( !m_viewList.IsEmpty ( ) ) {
        reinterpret_cast< CEditView* >( m_viewList.GetHead ( ) )
            ->SetWindowText ( strTemp.GetString ( ) );
    }

    return TRUE;
}

BOOL CPadDoc::OnSaveDocument ( LPCTSTR lpszPathName )
{
    // TODO: Add specialized code here and / or call base classes.
    char ext [ _MAX_EXT ] = { 0 };
    _splitpath_s ( lpszPathName, NULL, 0, NULL, 0, NULL, 0, ext, _MAX_EXT );

    CString strExt ( ext );
    strExt.MakeUpper ( );

    BOOL bEncode = ( IDYES == AfxMessageBox ( _T ( "Would you like to encode?" ), MB_YESNO ) );

    if ( strExt == _T ( ".JSON" ) || strExt == _T ( ".ENC" ) ) {
        CString strTemp;
        reinterpret_cast< CEditView* >( m_viewList.GetHead ( ) )->GetWindowText ( strTemp );

        if ( bEncode ) {
            rapidjson::Document d;
            d.Parse ( strTemp );
            if ( d.HasParseError ( ) ) {
                AfxMessageBox ( _T ( "Invalid JSON format. Cannot save." ) );
                return FALSE;
            }

            CYXJson writer ( 4096 );
            if ( !writer.WriteJSON ( lpszPathName, d, TRUE ) ) {
                AfxMessageBox ( _T ( "Failed to save JSON file." ) );
                return FALSE;
            }
        }
        else {
            FILE* fOut = nullptr;
            if ( fopen_s ( &fOut, lpszPathName, "wb" ) != 0 || !fOut )
                return FALSE;

            fwrite ( strTemp, 1, strTemp.GetLength ( ), fOut );
            fclose ( fOut );
        }

        SetModifiedFlag ( FALSE );
        return TRUE;
    }

    CString strTemp;

    reinterpret_cast< CEditView* >( m_viewList.GetHead ( ) )->GetWindowText ( strTemp );

    CRijndael oRijndael;
    oRijndael.Initialize ( CRijndael::sm_Version [ CRijndael::VERSION - 1 ].c_str ( ), CRijndael::sm_KeyLength [ CRijndael::VERSION - 1 ], CRijndael::VERSION_DATE, CRijndael::VERSION );

    int nLen = oRijndael.GetEncryptStringLength ( strTemp );
    char* szBuffer = new char [ nLen ];

    oRijndael.EncryptEx ( strTemp.GetString ( ), szBuffer, strTemp.GetLength ( ) );
    int nVersion = CRijndael::VERSION;

    CFile file;
    file.Open ( lpszPathName, CFile::modeCreate | CFile::modeWrite );    // Open file
    file.Write ( &nVersion, sizeof ( int ) );                            // Write version
    file.Write ( szBuffer, nLen );        // Write version
    file.Close ( );

    delete [ ] szBuffer;

    SetModifiedFlag ( FALSE );
    return TRUE;
}
 

Attachments

Last edited:
Note : It has auto converter in the code, so now you can automatically convert it and add // Coded by whoever you are.
 
Back