- 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:
Memory Footprint
CYXJson:
Performance Comparison
CYXJson demonstrates superior performance in both speed and memory efficiency.
Benchmarks (RAN Online Default Configuration, 1.2 MB, compiled with /O2)
Highlights
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.
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.
- 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.
- Performs multiple small allocations per key and value.
- Around 3× file size memory footprint with higher fragmentation.
Performance Comparison
| Metric | CYXJson | CIniLoader |
|---|---|---|
| Load Time | Linear read, binary parse | Line-by-line parsing |
| Memory Locality | High | Poor |
| Access Time | O(1) (DOM lookup) | O(log n) (map lookup) |
| Write Time | Single buffer flush | Reconstructed per key |
| Cleanup | Automatic | Manual recursive delete |
CYXJson demonstrates superior performance in both speed and memory efficiency.
Benchmarks (RAN Online Default Configuration, 1.2 MB, compiled with /O2)
| Format | Parse Time | Write Time | File Size |
|---|---|---|---|
| INI Loader | ~6.2 ms | ~3.0 ms | 1.2 MB |
| JSON (Plain, SIMD parsing) | ~2.8 ms | ~2.0 ms | 1.5 MB |
| JSON with AES Encryption | ~3.4 ms | ~2.3 ms | 1.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:
To view the content, you need to sign in or register
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:

