Skip to content

Commit cf75906

Browse files
committed
Fix issue with iRacing not supporting Latin-1 characters properly (workaround bandaid)
1 parent c13d559 commit cf75906

2 files changed

Lines changed: 156 additions & 33 deletions

File tree

Classes/Misc.cs

Lines changed: 141 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11

2+
using IWshRuntimeLibrary;
3+
using PInvoke;
4+
using System;
25
using System.Collections;
36
using System.ComponentModel.Design;
47
using System.Diagnostics;
@@ -11,10 +14,6 @@
1114
using System.Windows;
1215
using System.Windows.Controls.Primitives;
1316
using System.Windows.Media;
14-
15-
using IWshRuntimeLibrary;
16-
using PInvoke;
17-
1817
using Point = System.Windows.Point;
1918

2019
namespace MarvinsAIRARefactored.Classes;
@@ -253,33 +252,154 @@ public static void MoveCursorToElement( FrameworkElement element )
253252
User32.SetCursorPos( (int) Math.Round( p.X ), (int) Math.Round( p.Y ) );
254253
}
255254

256-
private static readonly Encoding Latin1Encoding = Encoding.GetEncoding( "iso-8859-1", new EncoderReplacementFallback( "?" ), new DecoderReplacementFallback( "?" ) );
257-
258-
public static string ToBestEffortLatin1( string input )
255+
private static readonly Dictionary<char, string> IracingChatMap = new()
256+
{
257+
// German (and friends)
258+
[ 'Ä' ] = "Ae",
259+
[ 'Ö' ] = "Oe",
260+
[ 'Ü' ] = "Ue",
261+
[ 'ä' ] = "ae",
262+
[ 'ö' ] = "oe",
263+
[ 'ü' ] = "ue",
264+
[ 'ß' ] = "ss",
265+
266+
// Turkish
267+
[ 'İ' ] = "I",
268+
[ 'ı' ] = "i",
269+
[ 'Ş' ] = "S",
270+
[ 'ş' ] = "s",
271+
[ 'Ğ' ] = "G",
272+
[ 'ğ' ] = "g",
273+
274+
// Romanian (comma-below forms)
275+
[ 'Ș' ] = "S",
276+
[ 'ș' ] = "s",
277+
[ 'Ț' ] = "T",
278+
[ 'ț' ] = "t",
279+
280+
// Polish
281+
[ 'Ł' ] = "L",
282+
[ 'ł' ] = "l",
283+
[ 'Ą' ] = "A",
284+
[ 'ą' ] = "a",
285+
[ 'Ę' ] = "E",
286+
[ 'ę' ] = "e",
287+
[ 'Ń' ] = "N",
288+
[ 'ń' ] = "n",
289+
[ 'Ś' ] = "S",
290+
[ 'ś' ] = "s",
291+
[ 'Ź' ] = "Z",
292+
[ 'ź' ] = "z",
293+
[ 'Ż' ] = "Z",
294+
[ 'ż' ] = "z",
295+
[ 'Ć' ] = "C",
296+
[ 'ć' ] = "c",
297+
[ 'Ó' ] = "O",
298+
[ 'ó' ] = "o",
299+
300+
// Czech
301+
[ 'Č' ] = "C",
302+
[ 'č' ] = "c",
303+
[ 'Ď' ] = "D",
304+
[ 'ď' ] = "d",
305+
[ 'Ě' ] = "E",
306+
[ 'ě' ] = "e",
307+
[ 'Ň' ] = "N",
308+
[ 'ň' ] = "n",
309+
[ 'Ř' ] = "R",
310+
[ 'ř' ] = "r",
311+
[ 'Š' ] = "S",
312+
[ 'š' ] = "s",
313+
[ 'Ť' ] = "T",
314+
[ 'ť' ] = "t",
315+
[ 'Ů' ] = "U",
316+
[ 'ů' ] = "u",
317+
[ 'Ž' ] = "Z",
318+
[ 'ž' ] = "z",
319+
320+
// Hungarian
321+
[ 'Ő' ] = "O",
322+
[ 'ő' ] = "o",
323+
[ 'Ű' ] = "U",
324+
[ 'ű' ] = "u",
325+
};
326+
327+
private static bool IsLatin1( char ch ) => ch <= '\u00FF';
328+
329+
public static string ToIracingChatSafeText( string input )
259330
{
260-
// 1) Decompose accented letters into base letter + combining marks
261-
var normalized = input.Normalize( NormalizationForm.FormD );
331+
if ( string.IsNullOrEmpty( input ) )
332+
{
333+
return input;
334+
}
262335

263-
// 2) Remove combining marks (diacritics)
264-
var stringBuilder = new StringBuilder( normalized.Length );
336+
var output = new StringBuilder( input.Length );
265337

266-
foreach ( var ch in normalized )
338+
foreach ( var ch in input )
267339
{
268-
var category = CharUnicodeInfo.GetUnicodeCategory( ch );
340+
// Try language-aware mapping first (Ł->L, Ş->S, etc.)
269341

270-
if ( ( category != UnicodeCategory.NonSpacingMark ) && ( category != UnicodeCategory.SpacingCombiningMark ) && ( category != UnicodeCategory.EnclosingMark ) )
342+
if ( IracingChatMap.TryGetValue( ch, out var mapped ) )
271343
{
272-
stringBuilder.Append( ch );
344+
output.Append( mapped );
345+
346+
continue;
347+
}
348+
349+
// Keep true Latin-1 as-is (ä stays ä)
350+
351+
if ( IsLatin1( ch ) )
352+
{
353+
output.Append( ch );
354+
355+
continue;
273356
}
357+
358+
// Try diacritic stripping for remaining Latin letters
359+
360+
var stripped = StripDiacritics( ch.ToString() );
361+
362+
var appendedAnything = false;
363+
364+
foreach ( var strippedChar in stripped )
365+
{
366+
if ( IsLatin1( strippedChar ) )
367+
{
368+
output.Append( strippedChar );
369+
370+
appendedAnything = true;
371+
}
372+
}
373+
374+
if ( appendedAnything )
375+
{
376+
continue;
377+
}
378+
379+
// Non-Latin scripts (ru-RU, hy-AM, ja-JP, zh-Hans) -> no good in iRacing chat
380+
381+
output.Append( '?' );
274382
}
275383

276-
// 3) Re-compose (optional but tidy)
277-
var noDiacritics = stringBuilder.ToString().Normalize( NormalizationForm.FormC );
384+
return output.ToString();
385+
}
386+
387+
private static string StripDiacritics( string value )
388+
{
389+
var normalized = value.Normalize( NormalizationForm.FormD );
390+
391+
var stringBuilder = new StringBuilder( normalized.Length );
278392

279-
// 4) Encode to Latin-1 with '?' replacement for anything still unsupported
280-
var bytes = Latin1Encoding.GetBytes( noDiacritics );
393+
foreach ( var c in normalized )
394+
{
395+
var category = CharUnicodeInfo.GetUnicodeCategory( c );
396+
397+
if ( category != UnicodeCategory.NonSpacingMark && category != UnicodeCategory.SpacingCombiningMark && category != UnicodeCategory.EnclosingMark )
398+
{
399+
stringBuilder.Append( c );
400+
}
401+
}
281402

282-
// 5) Convert back to a .NET string that contains only U+0000..U+00FF chars
283-
return Latin1Encoding.GetString( bytes );
403+
return stringBuilder.ToString().Normalize( NormalizationForm.FormC );
284404
}
285405
}

Components/ChatQueue.cs

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11

2-
using MarvinsAIRARefactored.Classes;
3-
42
using IRSDKSharper;
3+
using MarvinsAIRARefactored.Classes;
54
using PInvoke;
6-
5+
using System.Text;
76
using static PInvoke.User32;
87

98
namespace MarvinsAIRARefactored.Components;
@@ -32,6 +31,8 @@ private class Message
3231

3332
private int _updateCounter = UpdateInterval + 0;
3433

34+
private static readonly Encoding Latin1Encoding = Encoding.GetEncoding( "iso-8859-1", new EncoderReplacementFallback( "?" ), new DecoderReplacementFallback( "?" ) );
35+
3536
public void SendMessage( string messageTemplate, string? value = null )
3637
{
3738
var app = App.Instance!;
@@ -86,22 +87,24 @@ private void Update( App app )
8687
{
8788
var message = _messageList[ 0 ];
8889

89-
var stringToSend = Misc.ToBestEffortLatin1( message.MessageTemplate );
90-
90+
var stringToSend = Misc.ToIracingChatSafeText( message.MessageTemplate );
91+
9192
if ( message.Value != null )
9293
{
9394
stringToSend += $" = {message.Value}";
9495
}
9596

97+
stringToSend += '\r';
98+
9699
app.Logger.WriteLine( $"[ChatQueue] Sending message: {stringToSend}" );
97100

98-
foreach ( var ch in stringToSend )
101+
var latin1Bytes = Latin1Encoding.GetBytes( stringToSend );
102+
103+
foreach ( var latin1Byte in latin1Bytes )
99104
{
100-
SendKey( app, ch );
105+
SendKey( app, latin1Byte );
101106
}
102107

103-
SendKey( app, '\r' );
104-
105108
_messageList.RemoveAt( 0 );
106109

107110
if ( _messageList.Count == 0 )
@@ -138,11 +141,11 @@ private void Update( App app )
138141
}
139142
}
140143

141-
private static void SendKey( App app, char key )
144+
private static void SendKey( App app, byte key )
142145
{
143146
if ( key == '\r' )
144147
{
145-
var virtualKey = PInvoke.User32.VkKeyScanW( key );
148+
var virtualKey = PInvoke.User32.VkKeyScanW( '\r' );
146149

147150
var scanCode = User32.MapVirtualKey( virtualKey, MapVirtualKeyTranslation.MAPVK_VK_TO_VSC );
148151

@@ -156,7 +159,7 @@ private static void SendKey( App app, char key )
156159
}
157160
else
158161
{
159-
User32.PostMessage( (IntPtr) app.Simulator.WindowHandle!, User32.WindowMessage.WM_CHAR, key, IntPtr.Zero );
162+
User32.PostMessage( (IntPtr) app.Simulator.WindowHandle!, User32.WindowMessage.WM_CHAR, (IntPtr) key, IntPtr.Zero );
160163
}
161164
}
162165

0 commit comments

Comments
 (0)