@@ -10,6 +10,10 @@ namespace GeometryDashAPI.Data
1010{
1111 public class GameData
1212 {
13+ // This is xored gzip magick bytes: 'C?'
14+ // see more https://en.wikipedia.org/wiki/Gzip
15+ private static readonly byte [ ] XorDatFileMagickBytes = [ 0x43 , 0x3f ] ;
16+
1317 public Plist DataPlist { get ; set ; }
1418
1519 private readonly GameDataType ? type ;
@@ -34,27 +38,41 @@ public virtual async Task LoadAsync(string fileName)
3438 using var file = new FileStream ( fileName , FileMode . Open , FileAccess . Read , FileShare . Read , 4096 , useAsync : true ) ;
3539#endif
3640 var data = new byte [ file . Length ] ;
37- await file . ReadAsync ( data , 0 , data . Length ) ;
41+ _ = await file . ReadAsync ( data , 0 , data . Length ) ;
42+
43+ if ( data . AsSpan ( ) . Slice ( 0 , XorDatFileMagickBytes . Length ) . IndexOf ( XorDatFileMagickBytes ) != 0 )
44+ {
45+ // mac files
46+ var decryptedData = Crypt . LoadSaveAsMacOS ( data ) ;
47+ DataPlist = new Plist ( Encoding . ASCII . GetBytes ( decryptedData ) ) ;
48+ return ;
49+ }
3850
51+ // windows files
3952 var xor = Crypt . XOR ( data , 0xB ) ;
4053 var index = xor . AsSpan ( ) . IndexOf ( ( byte ) 0 ) ;
41- var gZipDecompress = Crypt . GZipDecompress ( GameConvert . FromBase64 ( Encoding . ASCII . GetString ( xor , 0 , index >= 0 ? index : xor . Length ) ) ) ;
42-
54+ var gZipDecompress =
55+ Crypt . GZipDecompress (
56+ GameConvert . FromBase64 ( Encoding . ASCII . GetString ( xor , 0 , index >= 0 ? index : xor . Length ) ) ) ;
4357 DataPlist = new Plist ( Encoding . ASCII . GetBytes ( gZipDecompress ) ) ;
4458 }
45-
59+
4660 /// <summary>
4761 /// Saves class data to a file as a game save<br/><br/>
4862 /// Before saving, make sure that you have closed the game. Otherwise, after closing, the game will overwrite the file<br/>
4963 /// </summary>
5064 /// <param name="fullName">File to write the data.<br />
5165 /// use <b>null</b> value for default resolving
5266 /// </param>
53- public void Save ( string ? fullName = null )
67+ /// <param name="format">
68+ /// Specify if you want to save the file in a format specific to another operating system.<br />
69+ /// Leave <b>null</b> to save the file for the current operating system
70+ /// </param>
71+ public void Save ( string ? fullName = null , DatFileFormat ? format = null )
5472 {
5573 using var memory = new MemoryStream ( ) ;
5674 DataPlist . SaveToStream ( memory ) ;
57- File . WriteAllBytes ( fullName ?? ResolveFileName ( type ) , GetFileContent ( memory ) ) ;
75+ File . WriteAllBytes ( fullName ?? ResolveFileName ( type ) , GetFileContent ( memory , format ?? ResolveFileFormat ( ) ) ) ;
5876 }
5977
6078 /// <summary>
@@ -64,15 +82,19 @@ public void Save(string? fullName = null)
6482 /// <param name="fileName">File to write the data.<br />
6583 /// use <b>null</b> value for default resolving
6684 /// </param>
67- public async Task SaveAsync ( string ? fileName = null )
85+ /// <param name="format">
86+ /// Specify if you want to save the file in a format specific to another operating system.<br />
87+ /// Leave <b>null</b> to save the file for the current operating system
88+ /// </param>
89+ public async Task SaveAsync ( string ? fileName = null , DatFileFormat ? format = null )
6890 {
6991 using var memory = new MemoryStream ( ) ;
7092 await DataPlist . SaveToStreamAsync ( memory ) ;
7193#if NETSTANDARD2_1
72- await File . WriteAllBytesAsync ( fileName ?? ResolveFileName ( type ) , GetFileContent ( memory ) ) ;
94+ await File . WriteAllBytesAsync ( fileName ?? ResolveFileName ( type ) , GetFileContent ( memory , format ?? ResolveFileFormat ( ) ) ) ;
7395#else
7496 using var file = new FileStream ( fileName ?? ResolveFileName ( type ) , FileMode . Create , FileAccess . ReadWrite , FileShare . Read , 4096 , useAsync : true ) ;
75- var data = GetFileContent ( memory ) ;
97+ var data = GetFileContent ( memory , format ?? ResolveFileFormat ( ) ) ;
7698 await file . WriteAsync ( data , 0 , data . Length ) ;
7799#endif
78100 }
@@ -83,13 +105,40 @@ public static string ResolveFileName(GameDataType? type)
83105 throw new InvalidOperationException ( "can't resolve the directory with the saves for undefined file type. Use certain file name" ) ;
84106 if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
85107 return $@ "{ Environment . GetEnvironmentVariable ( "LocalAppData" ) } \GeometryDash\CC{ type } .dat";
108+ if ( RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) )
109+ return $ "/Users/{ Environment . GetEnvironmentVariable ( "USER" ) } /Library/Application Support/GeometryDash/CC{ type } .dat";
86110 throw new InvalidOperationException ( $ "can't resolve the directory with the saves on your operating system: '{ RuntimeInformation . OSDescription } '. Use certain file name") ;
87111 }
88112
89- private static byte [ ] GetFileContent ( MemoryStream memory )
113+ public static DatFileFormat ResolveFileFormat ( )
90114 {
115+ if ( RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) )
116+ return DatFileFormat . Mac ;
117+ return DatFileFormat . Windows ;
118+ }
119+
120+ private static byte [ ] GetFileContent ( MemoryStream memory , DatFileFormat format )
121+ {
122+ if ( format == DatFileFormat . Mac )
123+ return Crypt . SavingSaveAsMacOS ( memory . ToArray ( ) ) ;
124+
91125 var base64 = GameConvert . ToBase64 ( Crypt . GZipCompress ( memory . ToArray ( ) ) ) ;
92126 return Crypt . XOR ( Encoding . ASCII . GetBytes ( base64 ) , 0xB ) ;
93127 }
128+
129+ private static bool StartsWith ( Stream stream , ReadOnlySpan < byte > prefix )
130+ {
131+ if ( ! stream . CanSeek )
132+ throw new ArgumentException ( $ "{ nameof ( stream ) } is not seekable. This can lead to bugs.") ;
133+ if ( stream . Length < prefix . Length )
134+ return false ;
135+ var position = stream . Position ;
136+ var buffer = new byte [ prefix . Length ] ;
137+ var read = 0 ;
138+ while ( read != buffer . Length )
139+ read += stream . Read ( buffer , read , buffer . Length - read ) ;
140+ stream . Seek ( position , SeekOrigin . Begin ) ;
141+ return buffer . AsSpan ( ) . IndexOf ( prefix ) == 0 ;
142+ }
94143 }
95144}
0 commit comments