Verwirrung bzgl. Unicode/Ascii bei PeekS()

Für allgemeine Fragen zur Programmierung mit PureBasic.
Benutzeravatar
Kurzer
Beiträge: 1621
Registriert: 25.04.2006 17:29
Wohnort: Nähe Hamburg

Verwirrung bzgl. Unicode/Ascii bei PeekS()

Beitrag von Kurzer »

Hallo,

ich habe hier eine Testprozedur die folgendes mit einem String macht:

1) Den String AES verschlüsseln
2) Das Ergebnis aus 1) dann Base64 kodieren
3) Den Base64 kodierten String wieder Base64 dekodieren
4) Den aus 3) dekodierten Speicherblock wieder AES entschlüsseln

Also einen String verschlüsseln, kodieren und dann alles wieder Rückwarts so, dass der originale String wieder hergestellt ist.

Das ganze sollte mal im Unicode-Modus und mal im Ascii-Modus kompiliert werden.

Es gibt in der Prozedur folgende relevanten Debugausgaben:

Code: Alles auswählen

						  		Debug "AESDecoded Unicode ohne Länge: '"+PeekS(*AESDecodedString, #PB_Unicode)+"'"
						  		Debug "AESDecoded Unicode mit Länge : '"+PeekS(*AESDecodedString, AESDecodedLength, #PB_Unicode)+"'"
						  		
						  		Debug "AESDecoded Ascii ohne Länge  : '"+PeekS(*AESDecodedString, #PB_Ascii)+"'"
						  		Debug "AESDecoded Ascii mit  Länge  : '"+PeekS(*AESDecodedString, AESDecodedLength, #PB_Ascii)+"'"
						  		
						  		Debug "AESDecoded Nix ohne Länge    : '"+PeekS(*AESDecodedString)+"'"
						  		Debug "AESDecoded Nix mit  Länge    : '"+PeekS(*AESDecodedString, AESDecodedLength)+"'"
Hier wird der wiederhergestellte String auf sechs verschiedene Arten aus dem Memory gepeekt.

1) Im Unicode-Modus ohne Angabe einer Stringlänge
2) Im Unicode-Modus mit Angabe einer Stringlänge

3) Im Ascii-Modus ohne Angabe einer Stringlänge
4) Im Ascii-Modus mit Angabe einer Stringlänge

5) Ohne Modusvorgabe und ohne Angabe einer Stringlänge
6) Ohne Modusvorgabe und mit Angabe einer Stringlänge

Was mich verwirrt ist, dass die Ausgaben im Modus Unicode und Ascii nur dann funktionieren, wenn explizit die Länge des auszuPEEKenden Strings mit angegeben wird. Und das, obwohl das Programm im entsprechenden Modus (Unicode / Ascii) kompiliert wurde.

Einzig die beiden Ausgaben ohne die explizite Angabe von #PB_Ascii oder #PB_Unicode geben auch ohne eine Längenangabe immer den korrekten String aus.

Warum ist das so? Ich dachte, dass ich mit der Programmierung im Unicode-Modus und der expliziten Angabe von #PB_Unicode zukunftssicher bin. So ganz sicher in ich mir jetzt bzgl. dieser expliziten Angabe von #PB_Unicode nicht mehr.

Wie muss ich das in meinem Beispiel handhaben, damit der Code auch in zukünftigen PB Versionen läuft?

Hier der Testcode:

Code: Alles auswählen

EnableExplicit

Procedure.s Encode(sKey.s, sInitVector.s, sText.s)
	Protected.i *AESEncodedString, *AESDecodedString, *B64EncodedString, *B64DecodedString
	Protected.i AESEncodedLength, AESDecodedLength, B64EncodedLength, B64DecodedLength
	
	If Len(sKey) < 32
		sKey + "12345678901234567890123456789012"
	EndIf
	
	If Len(sInitVector) < 16
		sInitVector + "1234567890123456"
	EndIf
	
;	sText = Right("00000000" + Len(sText), 8) + sText
	If Len(sText) < 16
		sText + Space(16-Len(sText))
	EndIf
	
	Debug "Originaltext: '" + sText + "'"
	
	AESEncodedLength = StringByteLength(sText, #PB_Unicode) + StringByteLength(" ", #PB_Unicode) ; Die abschliessende 0 an Ende des Stringspeichers wird damit mitkodiert
	*AESEncodedString = AllocateMemory(AESEncodedLength)
 	Debug "AESEncodedLength: " + AESEncodedLength
	
	If *AESEncodedString > 0
	  If AESEncoder(@sText, *AESEncodedString, AESEncodedLength, @sKey, 256, @sInitVector) <> 0
	  	
	  	B64EncodedLength = AESEncodedLength * 1.5
	  	*B64EncodedString = AllocateMemory(B64EncodedLength)
	  	Debug "B64EncodedLength: " + B64EncodedLength
	  	
	  	If *B64EncodedString > 0
	  		B64EncodedLength = Base64Encoder(*AESEncodedString, AESEncodedLength, *B64EncodedString, B64EncodedLength)
	  		Debug "Real B64EncodedLength: " + B64EncodedLength
	  		If B64EncodedLength > 0
	  			Debug "Base64 Encoded: '" + PeekS(*B64EncodedString, B64EncodedLength, #PB_Ascii)+"'"
		  		
		  		B64DecodedLength = B64EncodedLength * 0.75
			  	*B64DecodedString = AllocateMemory(B64DecodedLength)
			  	If *B64DecodedString > 0
			  		B64DecodedLength = Base64Decoder(*B64EncodedString, B64EncodedLength, *B64DecodedString, B64DecodedLength)
			  		If B64DecodedLength > 0
		  		
					  	AESDecodedLength = B64DecodedLength
					  	*AESDecodedString = AllocateMemory(AESDecodedLength)
					  	If *AESDecodedString > 0
						  	If AESDecoder(*B64DecodedString, *AESDecodedString, AESDecodedLength, @sKey, 256, @sInitVector) <> 0
						  		Debug "AESDecoded Unicode ohne Länge: '"+PeekS(*AESDecodedString, #PB_Unicode)+"'"
						  		Debug "AESDecoded Unicode mit Länge : '"+PeekS(*AESDecodedString, AESDecodedLength, #PB_Unicode)+"'"
						  		
						  		Debug "AESDecoded Ascii ohne Länge  : '"+PeekS(*AESDecodedString, #PB_Ascii)+"'"
						  		Debug "AESDecoded Ascii mit  Länge  : '"+PeekS(*AESDecodedString, AESDecodedLength, #PB_Ascii)+"'"
						  		
						  		Debug "AESDecoded Nix ohne Länge    : '"+PeekS(*AESDecodedString)+"'"
						  		Debug "AESDecoded Nix mit  Länge    : '"+PeekS(*AESDecodedString, AESDecodedLength)+"'"
							  Else  
						  		Debug "Fehler bei AESDecoder."	
							  EndIf
							Else
								Debug "Speicher für *AESDecodedString konnte nicht alloziert werden."
							EndIf
				  	Else
				  		Debug "Fehler bei Base64Decoder."	
				  	EndIf
					Else
						Debug "Speicher für *B64DecodedString konnte nicht alloziert werden."
					EndIf
		  	Else
		  		Debug "Fehler bei Base64Encoder."	
		  	EndIf
			Else
				Debug "Speicher für *B64EncodedString konnte nicht alloziert werden."
			EndIf
		Else
			Debug "Fehler bei AESEncoder."
		EndIf
	Else
		Debug "Speicher für *AESEncodedString konnte nicht alloziert werden."
	EndIf

  If *B64DecodedString <> 0
  	FreeMemory(*B64DecodedString)	
  EndIf
  
  If *B64EncodedString <> 0
  	FreeMemory(*B64EncodedString)	
  EndIf
  
  If *AESDecodedString <> 0
  	FreeMemory(*AESDecodedString)	
  EndIf
  
  If *AESEncodedString <> 0
  	FreeMemory(*AESEncodedString)	
  EndIf
  
  
	ProcedureReturn sText
EndProcedure
;-

Encode("", "", "Dieser Text dienst nur einem Zweck, dem Demonstrationszweck.")
"Never run a changing system!" | "Unterhalten sich zwei Alleinunterhalter... Paradox, oder?"
PB 6.12 x64, OS: Win 11 24H2 x64, Desktopscaling: 150%, CPU: I7 12700 H, RAM: 32 GB, GPU: Intel(R) Iris(R) Xe Graphics | NVIDIA GeForce RTX 3070
Useralter in 2025: 57 Jahre.
Benutzeravatar
Kurzer
Beiträge: 1621
Registriert: 25.04.2006 17:29
Wohnort: Nähe Hamburg

Re: Verwirrung bzgl. Unicode/Ascii bei PeekS()

Beitrag von Kurzer »

Hmm, nun das nächste Problem mit diesem Unicode-Modus.

Ich habe die obigen Prozeduren nun getrennt vorliegen (Encode und Decode). Beide geben je einen String zurück.
Encode gibt einen Base64 kodierten String zurück und Decode einen normalen unverschlüsselten String.

Nun habe ich zum testen ein Fenster mit zwei EditorGadgets. Ich möchte nun jeweils diese beiden Strings in je eines der EditorGadgets schreiben. In das obere den Base64 kodierten String und in das untere den dekodierten Originalstring.

Das Programm wird wieder im Unicode-Modus kompiliert.

Das Problem ist, dass ich einem EditorGadget offenbar keinen Unicode-String andrehen kann, der wird nämlich nicht richtig angezeigt. Im folgenden Beispiel habe ich den Effekt, dass ich niemals beide Strings gleichzeitig in den beiden EditorGadgets sehen kann.

Zum testen sollte jeweils eine dieser Zeilen im Beispielcode aus-/einkommentiert werden.

Code: Alles auswählen

	sText = Encode("", "", "Dieser Text dient nur einem Zweck, dem Demonstrationszweck.", 0)
	;sText = Encode("", "", "Dieser Text dient nur einem Zweck, dem Demonstrationszweck.", #PB_Ascii)
	;sText = Encode("", "", "Dieser Text dient nur einem Zweck, dem Demonstrationszweck.", #PB_Unicode)
Entweder wird der korrekte Base64 String im oberen Editorgadeget angezeigt (weil er als ASCII übergeben wurde), dann ist aber im unteren Gadget nichts zu sehen, weil der Decoder mit dem ASCII String nicht arbeiten kann und nichts dekodiert.

Oder aber man kodiert den Base64 String in Unicode, dann kann er korrekt dekodiert und das dekotierte Ergebnis im unteren EditorGadget angezeigt werden, aber dann bleibt das obere EditorGadget leer, weil es den Base64 String im Unicodemodus offenbar nicht anzeigen kann.

In welchem Modus der Base64 String zurückgegeben werden soll kann über den dritten Parameter von Encode() angegeben werden.

Wird das Programm im Ascii Modus compiliert (also in den Compileroptionen ist "Unicode-Exe" deaktiviert., dann klappt das übrigens. Ich würde aber gern für die Zukunft diese Routinen bereits im Unicode-Modus erstellen.

Was muss man hier tun, damit ich im Unicode Modus korrekt kodieren und dekodieren, aber die Daten auch gleichzeitig an ein Gadget senden kann?

Code: Alles auswählen

EnableExplicit

; Das hier bitte im Modus UNICODE compilieren

Procedure.s Encode(sKey.s, sInitVector.s, sText.s, iModus.i)
	; +-----------------------------------------------------------------
	; |Description  : Kodiert eine unverschlüsselte Zeichenkette mittels AES und Base64 in eine verschlüsselte Zeichenkette
	; |Arguments    : sKey:        Schlüssel für die AES Kodierung (mind. 32 Zeichen)
	; |             : sInitVector: InitVector für die AES Kodierung (exakt 16 Zeichen)
	; |             : sText:       Die zu verschlüsselnde Zeichenkette
	; |Results      : Die verschlüsselte, Base64 kodierte Zeichenkette oder "", wenn ein Fehler aufgetreten ist
	; |Remarks      : 
	; +-----------------------------------------------------------------
	Protected.i *AESEncodedString, *B64EncodedString
	Protected.i iAESEncodedLength, iB64EncodedLength, bError=#False
	
	If Len(sKey) < 32
		sKey + "12345678901234567890123456789012"
	EndIf
	
	If Len(sInitVector) < 16
		sInitVector + "1234567890123456"
	EndIf
	
	; Wir ketten die Länge der Zeichenkette mit an (8 stellig), damit bei Zeichenketten kleiner als 16
	; später ermittelt werden kann wie lang/kurz die originale Zeichenkette einmal war.
	; Die AES Verschlüsselung braucht zwingend mind. 16 Zeichen, daher wird die Zeichenkette notfalls
	; mit Spaces aufgefüllt.
	sText = Right("00000000" + Len(sText), 8) + sText
	If Len(sText) < 16
		sText + Space(16-Len(sText))
	EndIf
	
	iAESEncodedLength = StringByteLength(sText + " ") ; + " " -> Die abschliessende 0 an Ende des Stringspeichers wird damit mitkodiert
	*AESEncodedString = AllocateMemory(iAESEncodedLength)
	
	If *AESEncodedString > 0
		If AESEncoder(@sText, *AESEncodedString, iAESEncodedLength, @sKey, 256, @sInitVector) <> 0
			
			iB64EncodedLength = iAESEncodedLength * 1.5
			*B64EncodedString = AllocateMemory(iB64EncodedLength)
			
			If *B64EncodedString > 0
				iB64EncodedLength = Base64Encoder(*AESEncodedString, iAESEncodedLength, *B64EncodedString, iB64EncodedLength)
				
				If iB64EncodedLength > 0
					If iModus = 0
						sText = PeekS(*B64EncodedString, iB64EncodedLength)
					Else
						sText = PeekS(*B64EncodedString, iB64EncodedLength, iModus)
					EndIf
				Else
					;Debug "Fehler bei Base64Encoder."	
					bError=#True
				EndIf
				FreeMemory(*B64EncodedString)
			Else
				;Debug "Speicher für *B64EncodedString konnte nicht alloziert werden."
				bError=#True
			EndIf
		Else
			;Debug "Fehler bei AESEncoder."
			bError=#True
		EndIf
		FreeMemory(*AESEncodedString)
	Else
		;Debug "Speicher für *AESEncodedString konnte nicht alloziert werden."
		bError=#True
	EndIf
	
	If bError=#True
		ProcedureReturn ""
	Else
		ProcedureReturn sText
	EndIf
	
EndProcedure
;-
Procedure.s Decode(sKey.s, sInitVector.s, sText.s)
	; +-----------------------------------------------------------------
	; |Description  : Dekodiert eine verschlüsselte Zeichenkette mittels AES und Base64 in eine unverschlüsselte Zeichenkette
	; |Arguments    : sKey:        Schlüssel für die AES Dekodierung (mind. 32 Zeichen)
	; |             : sInitVector: InitVector für die AES Dekodierung (exakt 16 Zeichen)
	; |             : sText:       Die zu entschlüsselnde Zeichenkette
	; |Results      : Die entschlüsselte, Base64 dekodierte Zeichenkette oder "", wenn ein Fehler aufgetreten ist
	; |Remarks      : -
	; +-----------------------------------------------------------------
	Protected.i *AESDecodedString, *B64DecodedString
	Protected.i iAESEncodedLength, iB64EncodedLength, iB64DecodedLength, bError=#False
	
	If Len(sKey) < 32
		sKey + "12345678901234567890123456789012"
	EndIf
	
	If Len(sInitVector) < 16
		sInitVector + "1234567890123456"
	EndIf
	
	If Len(sText) < 16
		ProcedureReturn ""
	EndIf
	
	iB64EncodedLength = StringByteLength(sText)
	iB64DecodedLength = iB64EncodedLength * 0.75
	*B64DecodedString = AllocateMemory(iB64DecodedLength)
	
	If *B64DecodedString > 0
		iB64DecodedLength = Base64Decoder(@sText, iB64EncodedLength, *B64DecodedString, iB64DecodedLength)
		If iB64DecodedLength > 0
			
			iAESEncodedLength = iB64DecodedLength
			*AESDecodedString = AllocateMemory(iAESEncodedLength)
			
			If *AESDecodedString > 0
				If AESDecoder(*B64DecodedString, *AESDecodedString, iAESEncodedLength, @sKey, 256, @sInitVector) <> 0
					sText = PeekS(*AESDecodedString + StringByteLength("00000000"), Val(PeekS(*AESDecodedString, 8)))
				Else  
					;Debug "Fehler bei AESDecoder."
					bError=#True
				EndIf
				FreeMemory(*AESDecodedString)
			Else
				; Debug "Speicher für *AESDecodedString konnte nicht alloziert werden."
				bError=#True
			EndIf
		Else
			;Debug "Fehler bei Base64Decoder."	
			bError=#True
		EndIf
		FreeMemory(*B64DecodedString)
	Else
		;Debug "Speicher für *B64DecodedString konnte nicht alloziert werden."
		bError=#True
	EndIf
	
	If bError=#True
		ProcedureReturn ""
	Else
		ProcedureReturn sText
	EndIf
	
EndProcedure

Define.s sText

If OpenWindow(0, #PB_Ignore, #PB_Ignore, 600, 260, "Test", #PB_Window_SystemMenu | #PB_Window_MinimizeGadget | #PB_Window_MaximizeGadget)
	
  EditorGadget(0, 10, 10, 580, 115)
  EditorGadget(1, 10, 135, 580, 115)
  
	sText = Encode("", "", "Dieser Text dient nur einem Zweck, dem Demonstrationszweck.", 0)
	;sText = Encode("", "", "Dieser Text dient nur einem Zweck, dem Demonstrationszweck.", #PB_Ascii)
	;sText = Encode("", "", "Dieser Text dient nur einem Zweck, dem Demonstrationszweck.", #PB_Unicode)
	SetGadgetText(0, sText)
	sText = Decode("", "", sText)
	SetGadgetText(1, sText)
  
  Repeat
  Until WaitWindowEvent() = #PB_Event_CloseWindow
  
EndIf

End


So, ich habe den Base64 kodierten Output im Compilermodus Unicode jetzt mal in eine Datei schreiben lassen:

Leider kann ich die Daten nicht korrekt anzeigen. In was für einem komischen Format liegen die in PB denn vor, dass man die partou nicht korrekt anzeigen kann?

Bild

Bild

Bild


Alle drei Bilder zeigen definitiv nicht den Base64 kodierten String an, den man im reinen ASCII Modus sehen kann.

Das ist jetzt das erste Programm, das ich im Unicode Modus schreibe und das so exzessiv mit Strings arbeitet.
Kann es sein, dass ich hier noch was Essentielles übersehe bei der ganzen Sache?
"Never run a changing system!" | "Unterhalten sich zwei Alleinunterhalter... Paradox, oder?"
PB 6.12 x64, OS: Win 11 24H2 x64, Desktopscaling: 150%, CPU: I7 12700 H, RAM: 32 GB, GPU: Intel(R) Iris(R) Xe Graphics | NVIDIA GeForce RTX 3070
Useralter in 2025: 57 Jahre.
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8820
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Re: Verwirrung bzgl. Unicode/Ascii bei PeekS()

Beitrag von NicTheQuick »

Erster Punkt: PeekS erwartet die zu lesenden Zeichen als Längenangabe, nicht die zu lesenden Bytes.
Außerdem finde ich die Konstruktion mit '* 1.5 und' '* *.75' nicht gut gelungen. Man bleibt bei Integern, wenn man sowas macht.

Ansonsten schaue ich es mir gleich nochmal genauer an und editiere.
hoerbie
Beiträge: 37
Registriert: 06.12.2013 13:53

Re: Verwirrung bzgl. Unicode/Ascii bei PeekS()

Beitrag von hoerbie »

Hi,

fand Deinen Post sehr interessant, da ich in den letzten Wochen auch viel mit der Kombi aus Aes256 und Base64 rumgebastelt und geflucht habe.

Ein Fehler in Deinem ersten Skript bei den Debug-Ausgaben ist, daß Du die optionalen Parameter falsch angegeben hast. In der Doku steht:

Code: Alles auswählen

Text$ = PeekS(*Speicherpuffer [, Länge [, Format]])
Darunter die Beschreibung suggiert bei "Länge", daß man den Parameter nicht angeben muß, die Klammerung beim Befehl selbst, zeigt aber, daß es den schon braucht.

Die Klammerung bedeutet, daß Du den "*Speicherpuffer" angeben musst, und wenn Du "Format" angeben willst, Du auch zwingend einen Wert für "Länge" angeben mußt. Bei Dir wird aber so der zum Format #PB_Unicode (25) oder #PB_Ascii (24) gehörende von mir in Klammern genannte Zahlenwert zur "Länge".

Laut Doku bedeutet Länge = -1, daß bis zum nächsten Null Zeichen gelesen wird.

Ändere also bitte zwei Zeilen:

Code: Alles auswählen

Debug "AESDecoded Unicode ohne Länge: '"+PeekS(*AESDecodedString, -1, #PB_Unicode)+"'"
Debug "AESDecoded Ascii ohne Länge  : '"+PeekS(*AESDecodedString, -1, #PB_Ascii)+"'"
und Du bekommst konsequente und stimmige Ausgaben.

Beide Ascii werden dann korrekt nach dem D abgeschnitten, weil generell immer nur bis zum ersten 0 gelesen wird.

Denn bei einem wegen Unicode-Kompiliat auch Unicode-codierten String, auf den Du dann falsch "als Ascii" zugreifst, steht im ersten Byte das D, das zweite Byte ist Null, das dritte Byte steht das i etc, folglich bricht PeekS da auf jeden Fall an der ersten Null ab.

Gruß, Jens
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8820
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Re: Verwirrung bzgl. Unicode/Ascii bei PeekS()

Beitrag von NicTheQuick »

Ich hatte gerade etwas Zeit, deswegen hab ich mal alles etwas umgebaut. Zum einen lasse ich nun den Schlüssel durch SHA2 erstellen und den Initialisierungsvektor durch MD5. Zum anderen habe ich einen optionalen Parameter hinzugefügt, mit dem man angeben kann, in welchem Format der String interpretiert werden soll. So kann man zum Beispiel immer Unicode nutzen, egal was in den Compiler-Einstellungen steht.

Code: Alles auswählen

EnableExplicit

Procedure hex2mem(*in.Character, *out.Ascii)
	While *in\c
		*out\a = *in\c - '0' - Bool(*in\c > '9') * ('a' - '9' - 1)
		*out\a << 4
		*in + SizeOf(Character)
		*out\a + *in\c - '0' - Bool(*in\c > '9') * ('a' - '9' - 1)
		
		*in + SizeOf(Character)
		*out + SizeOf(Ascii)
	Wend
EndProcedure

UseSHA2Fingerprint()
; Erzeuge Schlüssel für AES-En-/Decoder aus SHA2-Hash
Procedure.i initKey(sKey.s)
	Protected hash.s = StringFingerprint(sKey, #PB_Cipher_SHA2, 256, #PB_UTF8)
	Protected *pKey = AllocateMemory(32)
	If Not *pKey
		ProcedureReturn #False
	EndIf
	
	hex2mem(@hash, *pKey)
	
	ProcedureReturn *pKey
EndProcedure

UseMD5Fingerprint()
Procedure initInitVector(sInitVector.s)
	Protected hash.s = StringFingerprint(sInitVector, #PB_Cipher_MD5)
	Protected *pInitVector = AllocateMemory(16)
	If Not *pInitVector
		ProcedureReturn #False
	EndIf
	
	hex2mem(@hash, *pInitVector)
	
	ProcedureReturn *pInitVector
EndProcedure

Procedure.s Encode(sKey.s, sInitVector.s, sText.s, format = #PB_Ignore)
	Protected *pKey = initKey(sKey)
	Protected *pInit = initInitVector(sInitVector)
	Protected *in = @sText, freeIn.i = #False
	Protected result.s
	
	If format = #PB_Ignore
		CompilerIf #PB_Compiler_Unicode
			format = #PB_Unicode
		CompilerElse
			format = #PB_Ascii
		CompilerEndIf
	EndIf
	
	; Berechne Länge des Textes im Speicher inklusive Nullzeichen
	Protected size.i = StringByteLength(sText, format) + 1 + Bool(format = #PB_Unicode)
	
	; Falls die Länge kleiner als 16 ist, alloziere einen eigenen 16-Byte-Buffer, der am Ende mit Nullen gefüllt ist.
	If size < 16
		*in = AllocateMemory(16)
		If Not *in
			Debug "*in: AllocateMemory returned 0."
			Goto free1
		EndIf
		PokeS(*in, sText, -1, format)
		size = 16
		freeIn = #True
	
	; Falls wir im Unicode-Modus sind, aber Ascii verschlüsselt wollen oder umgekehrt, konvertiere das Format vorher noch mit PokeS()
	ElseIf #PB_Compiler_Unicode XOr format = #PB_Unicode
		*in = AllocateMemory(size)
		If Not *in
			Debug "*in: AllocateMemory returned 0."
			Goto free1
		EndIf
		PokeS(*in, sText, -1, format)
		freeIn = #True
	EndIf
	
	; Speicherplatz für Ausgabepuffer allozieren
	Protected *out = AllocateMemory(size)
	If Not *out
		Debug "*out: AllocateMemory returned 0."
		Goto free2
	EndIf
	
	; Text verschlüsseln
	If Not AESEncoder(*in, *out, size, *pKey, 256, *pInit, #PB_Cipher_CBC)
		Debug "AESEncoder returned 0."
		Goto free3
	EndIf
	
	; Ursprünglichen Text zur Sicherheit aus dem Speicher löschen
	FillMemory(*in, size, 0)
	
	; Eingabepuffer freigeben, falls nötig
	If freeIn
		FreeMemory(*in)
		freeIn = #False
	EndIf
	
	; Größe um Base64-kodierten String zu kodieren, berechnen (+ 35%)
	Protected b64Size.i = 135 * size / 100
	If b64Size < 64
		b64Size = 64
	EndIf
	Protected *b64 = AllocateMemory(b64Size)
	If Not *b64
		Debug "*b64: AllocateMemory returned 0."
		Goto free3
	EndIf
	
	; Base64 Kodierer anwerfen
	b64Size = Base64Encoder(*out, size, *b64, b64Size)
	
	; Verschlüsselten Speicherbereich freigeben.
	FreeMemory(*out)
	
	; Kodierten Base64-Speicher in String umwandeln
	result = PeekS(*b64, b64Size, #PB_Ascii)
	
	; *b64 freigeben
	FreeMemory(*b64)
	
	Goto free1
	
	free3:
	FreeMemory(*out)
	
	free2:
	If freeIn
		FreeMemory(*in)
	EndIf
	
	free1:
	FreeMemory(*pKey)
	FreeMemory(*pInit)
	
	ProcedureReturn result
EndProcedure

Procedure.s Decode(sKey.s, sInitVector.s, sBase64.s, format = #PB_Ignore)
	Protected *pKey = initKey(sKey)
	Protected *pInit = initInitVector(sInitVector)
	Protected result.s
	
	Protected b64Size = Len(sBase64)
	
	Protected *b64 = AllocateMemory(b64Size)
	If Not *b64
		Debug "*b64: AllocateMemory returned 0."
		Goto free1
	EndIf
	
	Protected *out = AllocateMemory(b64Size)
	If Not *out
		Debug "*out: AllocateMemory returned 0."
		Goto free2
	EndIf
	
	PokeS(*b64, sBase64, -1, #PB_Ascii | #PB_String_NoZero)
	
	Protected size.i = Base64Decoder(*b64, b64Size, *out, b64Size)
	
	FreeMemory(*b64) : *b64 = 0
	
	Protected *in = AllocateMemory(size)
	If Not *in
		Debug "*in: AllocateMemory returned 0."
		Goto free3
	EndIf
	
	If Not AESDecoder(*out, *in, size, *pKey, 256, *pInit, #PB_Cipher_CBC)
		Debug "AESDecoder returned 0."
		Goto free4
	EndIf
	
	FreeMemory(*out)
	
	If format = #PB_Ignore
		CompilerIf #PB_Compiler_Unicode
			format = #PB_Unicode
		CompilerElse
			format = #PB_Ascii
		CompilerEndIf
	EndIf
	
	result = PeekS(*in, -1, format)
	
	FreeMemory(*in)
	
	Goto free1
	
	free4:
	FreeMemory(*in)
	
	free3:
	FreeMemory(*out)
	
	free2:
	If *b64
		FreeMemory(*b64)
	EndIf
	
	free1:
	FreeMemory(*pKey)
	FreeMemory(*pInit)
	
	ProcedureReturn result
EndProcedure

Define encoded.s = Encode("password", "12345", "Das ist das Haus vom NicTheQuick", #PB_UTF8)
Debug encoded
Debug Decode("password", "12345", encoded, #PB_UTF8)
Benutzeravatar
Kurzer
Beiträge: 1621
Registriert: 25.04.2006 17:29
Wohnort: Nähe Hamburg

Re: Verwirrung bzgl. Unicode/Ascii bei PeekS()

Beitrag von Kurzer »

Wow ihr seid super. :allright:
Vielen Dank für die Hinweise bzgl. der Längenangabe beim PeekS. Man sollte doch ab und zu auch bei den Befehlen in die Hilfe gucken, von denen man denkt, dass man sei kennt.

NicktheQuick: Du hast Dir ja echt Arbeit gemacht. Quasi alles neu und alles anders. :) Aber ein lehrreiches Beispiel ist es schon. Wobei einige Dinge für mich recht kompliziert anmuten, aber das liegt wohl eher daran, dass Du mit Zahlen und Logikoperationen weitaus besser umgehen kannst als ich.

Vielleicht kannst Du zu einigen Dingen noch was sagen, weil's für mich weitere Fragen aufwirft.

- "...Schlüssel durch SHA2 erstellen und den Initialisierungsvektor durch MD5"
Abgesehen davon, dass meine Version mit den Strings als Key und Vector im Unicode-Modus eben an jedem zweiten Byte eine 0 zu stehen hat, macht es aus anderen Gründen Sinn, SHA2 und MD5 zu nutzen? Oder macht man das in dem Bereich einfach so, weil's alle machen oder weil es sicherer ist?

- "hex2mem(*in.Character, *out.Ascii)"
:shock: Ich muss zugeben, dass ich mir diese Prozedur dreimal angucken musste.
Prinzipell machst Du dort eine Umwandung von Hexzahl als String in den betreffenden numerischen Wert? Also quasi wie mit Val("$2D")

Da sowas für mich immer recht schwer auf den ersten Blick nachvollziehbar ist, sollte es für mich etwas weniger mathematisch angehaucht sein. Ich habe versucht deine Logik nachzubauen. Wenn ich Deine Prozedur richtig verstanden habe, tut sie etwas wie diese hier, korrekt?

Code: Alles auswählen

EnableExplicit
Define.s sKey, sTestMem
Define i.i, iBlockLen, iFormat, *TestMem.Byte

sTestMem = Space(1024)
*TestMem = @sTestMem

CompilerIf #PB_Compiler_Unicode
   iFormat = #PB_Unicode
CompilerElse
   iFormat = #PB_Ascii
CompilerEndIf

UseSHA2Fingerprint()
sKey = StringFingerprint("Hallo Welt", #PB_Cipher_SHA2, 256, #PB_Ascii) ; <- Wir wollen immer den gleichen Hash haben, egal ob das PRG in ASCII oder Unicode compiliert wurde
Debug sKey

iBlockLen = StringByteLength("  ", iFormat)
While i < StringByteLength(sKey, iFormat)
	*TestMem\b = Val("$"+PeekS(@sKey + i, 2, iFormat))
	*TestMem + 1
	i + iBlockLen
Wend

ShowMemoryViewer(@sTestMem, 1024)
Kann ich iBlockLen auch irgendwie mit SizeOf(irgendwas) bestimmen?

Okay, ich denke, ich habe mir durch das Erstellen dieses Pendants die erste Frage von ganz oben schon selbst beantwortet.
Man will natürlich bei Angabe eines Schlüssels immer die gleichen binären Schlüsseldaten dafür erhalten - egal, ob das Programm im Ascii oder im Unicode Modus compiliert wurde.

Das ist bei meiner ersten "Nur-String-Version" nicht der Fall. sKey = "Mein geheimer Schlüssel" sieht direkt im Speicher natürlich unterschiedlich aus, wenn ich das Programm mal im Ascii, mal im unicode compiliere. Mal besteht ein Buchstabe aus einem Byte, mal aus zwei Bytes. Aus dem Grund würde man nicht das selbe Verschlüsselungsergebnis bekommen. Fein, wieder was gelernt. :allright:

Die Version, die ich jetzt da oben gepostet habe, generiert immer die gleichen Bytefolgen für den Key (sowohl im Ascii als auch Unicode Modus), da StringFingerprint() den Hash immer im Ascii Format zurückgibt. Damit sollte ich also auf der sicheren Seite sein, oder?

- "...= StringByteLength(sText, format) + 1 + Bool(format = #PB_Unicode)"
So habe ich Bool noch gar nicht eingesetzt.

Oder solche Dinge wie "#PB_Compiler_Unicode XOr format = #PB_Unicode". Da muss ich auch erst zweimal drüber nachdenken. :wink:

Summa sumarum sehe ich, dass ich doch noch einiges beachten muss, wenn man diese Funktionen sowohl unter Ascii als auch unter Unicode betreiben möchte. Ich werde Eure Hinweise nutzen, um meinen Code entsprechend anzupassen.

Danke, das Forum ist schon eine echte Hilfe. :allright:
"Never run a changing system!" | "Unterhalten sich zwei Alleinunterhalter... Paradox, oder?"
PB 6.12 x64, OS: Win 11 24H2 x64, Desktopscaling: 150%, CPU: I7 12700 H, RAM: 32 GB, GPU: Intel(R) Iris(R) Xe Graphics | NVIDIA GeForce RTX 3070
Useralter in 2025: 57 Jahre.
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8820
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Re: Verwirrung bzgl. Unicode/Ascii bei PeekS()

Beitrag von NicTheQuick »

Kurzer hat geschrieben:NicTheQuick: Du hast Dir ja echt Arbeit gemacht. Quasi alles neu und alles anders. :) Aber ein lehrreiches Beispiel ist es schon. Wobei einige Dinge für mich recht kompliziert anmuten, aber das liegt wohl eher daran, dass Du mit Zahlen und Logikoperationen weitaus besser umgehen kannst als ich.

Vielleicht kannst Du zu einigen Dingen noch was sagen, weil's für mich weitere Fragen aufwirft.

- "...Schlüssel durch SHA2 erstellen und den Initialisierungsvektor durch MD5"
Abgesehen davon, dass meine Version mit den Strings als Key und Vector im Unicode-Modus eben an jedem zweiten Byte eine 0 zu stehen hat, macht es aus anderen Gründen Sinn, SHA2 und MD5 zu nutzen? Oder macht man das in dem Bereich einfach so, weil's alle machen oder weil es sicherer ist?
Der Key bei AESDecoder muss bei einer 256-Bit-Verschlüsselung genau 32 Byte lang sein. Würde man jetzt mit Unicode kompilieren und ein Passwort mit mehr als 16 Zeichen eingeben, würden alle Zeichen nach dem 16. nicht mehr genutzt werden, weil jedes zweite ein Nullbyte ist. Dadurch, dass ich das Passwort vorher mit einem beliebigen 256-Bit-Hashverfahren hashe, bekomme ich eine wesentlich höhere Entropie und vor allem würden auch Passwörter mit wesentlich mehr als 16 Zeichen (bei Unicode) immer noch das Verschlüsselungsergebnis ändern. Gleichzeitig muss ich mich nicht darum kümmern, falls das Passwort zu kurz ist.
Das selbe gilt in dem Fall auch für den Initialisierungsvektor. Da habe ich MD5 genommen, weil es zufällig einen 128-Bit-Hash generiert. Man könnte hier auch einen anderen 128-Bit-Hash nutzen.
- "hex2mem(*in.Character, *out.Ascii)"
:shock: Ich muss zugeben, dass ich mir diese Prozedur dreimal angucken musste.
Prinzipell machst Du dort eine Umwandung von Hexzahl als String in den betreffenden numerischen Wert? Also quasi wie mit Val("$2D")

Da sowas für mich immer recht schwer auf den ersten Blick nachvollziehbar ist, sollte es für mich etwas weniger mathematisch angehaucht sein. Ich habe versucht deine Logik nachzubauen. Wenn ich Deine Prozedur richtig verstanden habe, tut sie etwas wie diese hier, korrekt?

Code: Alles auswählen

EnableExplicit
Define.s sKey, sTestMem
Define i.i, iBlockLen, iFormat, *TestMem.Byte

sTestMem = Space(1024)
*TestMem = @sTestMem

CompilerIf #PB_Compiler_Unicode
   iFormat = #PB_Unicode
CompilerElse
   iFormat = #PB_Ascii
CompilerEndIf

UseSHA2Fingerprint()
sKey = StringFingerprint("Hallo Welt", #PB_Cipher_SHA2, 256, #PB_Ascii) ; <- Wir wollen immer den gleichen Hash haben, egal ob das PRG in ASCII oder Unicode compiliert wurde
Debug sKey

iBlockLen = StringByteLength("  ", iFormat)
While i < StringByteLength(sKey, iFormat)
	*TestMem\b = Val("$"+PeekS(@sKey + i, 2, iFormat))
	*TestMem + 1
	i + iBlockLen
Wend

ShowMemoryViewer(@sTestMem, 1024)
Auch wenn ich vielleicht etwas strikt klingen mag, aber Strings sind nicht dazu da, binäre Daten zu speichern. Dafür nutzt man 'AllocateMemory()'. Oder alternativ auch gerne Byte-Arrays oder vergleichbares, wenn man lieber den Stack statt des Heaps nutzen möchte.
Bei der Bedingung im While-Schleifenkopf musst du daran denken, dass 'StringByteLength()' in jeder Runde erneut aufgerufen wird. Purebasic optimiert das nicht weg. Deswegen ist es besser, den Rückgabewert der Funktion vorher in einer Variable zu speichern.
Kann ich iBlockLen auch irgendwie mit SizeOf(irgendwas) bestimmen?
'SizeOf(Character)' gibt dir immer die Größe in Bytes eines Zeichens in einem String zurück, d.h. es passt sich an die verwendete Compiler-Einstellung an. Du hättest den Code sogar noch etwas verständlicher schreiben können, wenn du ganz auf Pointer verzichtet hättest, z.B. so:

Code: Alles auswählen

EnableExplicit

UseSHA2Fingerprint()
Define sKey.s = StringFingerprint("Hallo Welt", #PB_Cipher_SHA2, 256, #PB_Ascii)
Debug sKey

Dim binaryKey.a(31) ; 0..31 entspricht 32 Bytes

Define i.i
For i = 1 To 64 Step 2
	binaryKey((i - 1) / 2) = Val("$" + Mid(sKey, i, 2))
Next
; oder so
; For i = 0 To 31
; 	binaryKey(i) = Val("$" + Mid(sKey, i * 2 + 1, 2))
; Next

ShowMemoryViewer(@binaryKey(), 32)
Okay, ich denke, ich habe mir durch das Erstellen dieses Pendants die erste Frage von ganz oben schon selbst beantwortet.
Man will natürlich bei Angabe eines Schlüssels immer die gleichen binären Schlüsseldaten dafür erhalten - egal, ob das Programm im Ascii oder im Unicode Modus compiliert wurde.

Das ist bei meiner ersten "Nur-String-Version" nicht der Fall. sKey = "Mein geheimer Schlüssel" sieht direkt im Speicher natürlich unterschiedlich aus, wenn ich das Programm mal im Ascii, mal im unicode compiliere. Mal besteht ein Buchstabe aus einem Byte, mal aus zwei Bytes. Aus dem Grund würde man nicht das selbe Verschlüsselungsergebnis bekommen. Fein, wieder was gelernt. :allright:
Genau das ist der zweite Punkt. Ich möchte bei der Verwirrung mit Ascii und Unicode bei Purebasic meine Beispiele immer möglichst so gestalten, dass sie in beiden Einstellungen immer gleich funktionieren. Denn sonst ergeben sich oft Fehler, die man lange sucht.
Die Version, die ich jetzt da oben gepostet habe, generiert immer die gleichen Bytefolgen für den Key (sowohl im Ascii als auch Unicode Modus), da StringFingerprint() den Hash immer im Ascii Format zurückgibt. Damit sollte ich also auf der sicheren Seite sein, oder?
Entweder hast du dich gerade falsch ausgedrückt, oder es nicht verstanden.
Der Hash wird immer als normaler String zurück gegeben, also so wie es in den Compiler-Einstellungen steht. Allerdings wird der Eingabestring als Ascii behandelt, was zu Probleme führen könnte, wenn man Sonderzeichen nutzt. Deswegen habe ich UTF8 genutzt. Da gibt es keine Nullbytes, wo keine sein müssen und trotzdem unterstützt es mehr Sonderzeichen als Unicode.
- "...= StringByteLength(sText, format) + 1 + Bool(format = #PB_Unicode)"
So habe ich Bool noch gar nicht eingesetzt.
'Bool()' gibt immer entweder 0 oder 1 zurück. Und da ich mir ein If sparen wollte, habe ich es so gelöst. Das nutze ich übrigens auch in meiner hex2mem-Procedure. In einem anderen Thread wurde aber eine wesentlich schnellere Version dieser Procedure vorgestellt. Also falls es dir um Speed gehen sollte, schau auch da mal rein.
Oder solche Dinge wie "#PB_Compiler_Unicode XOr format = #PB_Unicode". Da muss ich auch erst zweimal drüber nachdenken. :wink:
Lustigerweise hatte ich das zuerst durch komplizierte If-Verschachtelungen gelöst und dann aber gemerkt, dass ich gerade ein XOr nachgebaut habe. So war es also einfacher. Nur, wenn 'format' nicht mit dem in den Compiler-Optionen eingestellten Format übereinstimmt, konvertieren wir dein Eingabestring in einen extra Speicherbereich. Andernfalls können wir direkt den String als Speicherbereich nutzen (natürlich nur zum Lesen und nicht für Binärdaten).
Summa sumarum sehe ich, dass ich doch noch einiges beachten muss, wenn man diese Funktionen sowohl unter Ascii als auch unter Unicode betreiben möchte. Ich werde Eure Hinweise nutzen, um meinen Code entsprechend anzupassen.

Danke, das Forum ist schon eine echte Hilfe. :allright:
Bitte sehr! Es freut mich, dass du davon lernen konntest.
Benutzeravatar
Kurzer
Beiträge: 1621
Registriert: 25.04.2006 17:29
Wohnort: Nähe Hamburg

Re: Verwirrung bzgl. Unicode/Ascii bei PeekS()

Beitrag von Kurzer »

NicTheQuick hat geschrieben: Entweder hast du dich gerade falsch ausgedrückt, oder es nicht verstanden.
Der Hash wird immer als normaler String zurück gegeben, also so wie es in den Compiler-Einstellungen steht. Allerdings wird der Eingabestring als Ascii behandelt, was zu Probleme führen könnte, wenn man Sonderzeichen nutzt. Deswegen habe ich UTF8 genutzt. Da gibt es keine Nullbytes, wo keine sein müssen und trotzdem unterstützt es mehr Sonderzeichen als Unicode.
Ich hatte es in der Tat falsch verstanden. Das heißt, wenn ich dem Befeht ASCII als Eingabestringformat aufzwinge und übergebe einen Unicodestring, dann kann es sein, dass nur der erste Buchstabe genutzt wird, weil dann schon die erste Null kommt?
Ich nutze den StringFingerprint() jetzt gänzlich ohne eine Formatvorgabe, so dass das Format vom Compilermodus abhängt.

Durch Deinen Code hat sich übrigens meine Einstellung zum Goto verändert. Bisher habe ich nie Goto genutzt, weil ich auch zu den "Goto ist böse" Jüngern gehöre. Aber gerade in dem Bereich, wo man am Ende einer Prozedur eine Reihe von allozierten Speicherbereichen wieder freigeben muss, ergibt sich ohne Goto ein Wust aus verschachtelten If Blöcken. So gesehen finde ich die Lösung mit "Goto Freexyz" sehr praktisch. In dem Fall vereinfacht es sogar den Code und vermindert dadurch die Gefahr einen Logikfehler einzubauen. Ich sollte öfter mal in anderer Leute code gucken. :lol:
Auch wenn ich vielleicht etwas strikt klingen mag, aber Strings sind nicht dazu da, binäre Daten zu speichern. Dafür nutzt man 'AllocateMemory()'. Oder alternativ auch gerne Byte-Arrays oder vergleichbares, wenn man lieber den Stack statt des Heaps nutzen möchte.
Ja nur keine Scheu mit Kritik, ich bin da nicht so. :)
In dem Fall war mir das allerdings in der Tat bewusst und ich habe es nur aus Faulheit im Testcode mit einem String realisiert. In meiner regulären Prozedur werde ich Speicher dafür allozieren.
"Never run a changing system!" | "Unterhalten sich zwei Alleinunterhalter... Paradox, oder?"
PB 6.12 x64, OS: Win 11 24H2 x64, Desktopscaling: 150%, CPU: I7 12700 H, RAM: 32 GB, GPU: Intel(R) Iris(R) Xe Graphics | NVIDIA GeForce RTX 3070
Useralter in 2025: 57 Jahre.
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8820
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Re: Verwirrung bzgl. Unicode/Ascii bei PeekS()

Beitrag von NicTheQuick »

Kurzer hat geschrieben:Das heißt, wenn ich dem Befeht ASCII als Eingabestringformat aufzwinge und übergebe einen Unicodestring, dann kann es sein, dass nur der erste Buchstabe genutzt wird, weil dann schon die erste Null kommt?
Nein, der übergebene String wird intern zunächst in das angegebene Format konvertiert. Aber bei Sonderzeichen kann es da zu Probleme kommen, wenn du nur Ascii nutzt. Siehe folgendes Beispiel:

Code: Alles auswählen

UseMD5Fingerprint()

Debug StringFingerprint("€€€a", #PB_Cipher_MD5, 0, #PB_Ascii)
Debug StringFingerprint("a", #PB_Cipher_MD5, 0, #PB_Ascii)
Das Euro-Zeichen kann nicht in Ascii konvertiert werden und wird deshalb zum Nullstring. Deswegen geben beide Zeilen den selben Hash aus. Bei UTF8 oder Unicode passiert das nicht.
Kurzer hat geschrieben:Ich nutze den StringFingerprint() jetzt gänzlich ohne eine Formatvorgabe, so dass das Format vom Compilermodus abhängt.
Wenn du in die Hilfe schaust, wirst du entdecken, dass dann standardmäßig UTF8 benutzt wird. :mrgreen:
Benutzeravatar
Kurzer
Beiträge: 1621
Registriert: 25.04.2006 17:29
Wohnort: Nähe Hamburg

Re: Verwirrung bzgl. Unicode/Ascii bei PeekS()

Beitrag von Kurzer »

NicTheQuick hat geschrieben: Nein, der übergebene String wird intern zunächst in das angegebene Format konvertiert. Aber bei Sonderzeichen kann es da zu Probleme kommen, wenn du nur Ascii nutzt.
Japp, jetzt wird es klarer.
Was ich mich noch frage ist aber, warum man die Angabe eines Format nur bei SHA2 und SHA3 angeben kann? Um bei StringFingerprint() das Format anzugeben, muss ja zwangsläufig auch die Bitanzahl angegeben werden.

Ergebnis$ = StringFingerprint(String$, Plugin [, Bits [, Format]])

Die kann/darf man aber nur für die beiden SHA2 und SHA3 Algos angeben. Oder kann man hier auch mit -1 zum Ignorieren dieses Parameters arbeiten?

Bits (optional) Die für die Prüfsumme zu verwendende Anzahl Bits. Dies wird nur bei den folgenden Plugins unterstützt:
#PB_Cipher_SHA2 : kann 224, 256 (Standard), 384 oder 512 lauten.
#PB_Cipher_SHA3 : kann 224, 256 (Standard), 384 oder 512 lauten.



NicTheQuick hat geschrieben: Wenn du in die Hilfe schaust, wirst du entdecken, dass dann standardmäßig UTF8 benutzt wird. :mrgreen:
Hmm, stimmt auffallend. :lol: :allright:
"Never run a changing system!" | "Unterhalten sich zwei Alleinunterhalter... Paradox, oder?"
PB 6.12 x64, OS: Win 11 24H2 x64, Desktopscaling: 150%, CPU: I7 12700 H, RAM: 32 GB, GPU: Intel(R) Iris(R) Xe Graphics | NVIDIA GeForce RTX 3070
Useralter in 2025: 57 Jahre.
Antworten