Autor |
Beitrag |
user32
Beiträge: 55
Erhaltene Danke: 5
|
Verfasst: Sa 28.02.15 16:31
Gibt es eine Möglichkeit, den DWORD den man mittels GetTickCount abrufen kann, manuell wieder auf 0 zu setzen?
Ohne Windows neuzustarten natürlich.
Wo wird dieser Wert gespeichert?
Nach 49 Tagen landet er automatisch wieder auf 0 wenn der DWORD überläuft,
aber so lange möchte ich eigentlich nicht warten
|
|
Nersgatt
Beiträge: 1581
Erhaltene Danke: 279
Delphi 10 Seattle Prof.
|
Verfasst: Sa 28.02.15 18:58
Nein, das ist wohl nicht wirklich möglich.
Aber Du könntest es ja mit einem Offset rausrechnen und dafür eine eingene GetTickCount-Funktion machen.
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12:
| var TickCountOffset : DWord;
function MyGetTickCount : DWord; begin result := GetTickCount - TickCountOffset; end;
procedure ResetTickCount; begin TickCountOffset := GetTickCount; end; |
So lange TickCountOffset 0 ist, gibt MyGetTickCount den selben Wert zurück, wie GetTickCount. Wenn Du ResetTickCount aufruft, wird der Offset auf den aktuellen Wert gesetzt. In dem Moment würde also MyGetTickCount (wenn theoretisch zwischen den Aufrufen keine Zeit verginge) wieder 0 zurückgeben.
_________________ Gruß, Jens
Zuerst ignorieren sie dich, dann lachen sie über dich, dann bekämpfen sie dich und dann gewinnst du. (Mahatma Gandhi)
|
|
SMO
Beiträge: 120
Erhaltene Danke: 18
D2005 Personal
|
Verfasst: Sa 28.02.15 20:59
Falls du eine neuere Delphi-Version hast, solltest du vielleicht besser TStopwatch aus der Unit System.Diagnostics benutzen. Benutzt Timer mit höherer Präzision wenn möglich (Stichwort QueryPerformanceCounter). Damit könntest du einfach ein TStopwatch.Start machen und die Eigenschaften Elapsed/ElapsedTicks/ElapsedMilliseconds benutzen, für 0-basierte Werte.
|
|
user32
Beiträge: 55
Erhaltene Danke: 5
|
Verfasst: So 01.03.15 11:58
Mhhja, die Sache ist, ich brauche das nicht selbst im Programm sondern schon global in Windows...
Es geht um ein altes Spiel, welches GetTickCount halt oft verwendet. Deswegen muss ich schon irgendwie an den Zähler selbst rankommen.
|
|
jaenicke
Beiträge: 19284
Erhaltene Danke: 1742
W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: So 01.03.15 12:30
Dann kannst du GetTickCount in dem Programm hooken.
// EDIT:
Na gut, das ist nicht ganz so einfach, aber hier findest du weitere Informationen:
code.google.com/p/de.../issues/detail?id=10
stackoverflow.com/qu...-gettickcount-with-c
|
|
user32
Beiträge: 55
Erhaltene Danke: 5
|
Verfasst: So 01.03.15 12:50
Oje. Hab eigentlich gedacht, dass ist einfach nur irgendeine Adresse im RAM, die man vielleicht überschreiben kann.
Aber ich seh schon, das war von Microsoft wohl nicht so vorgesehen :/
Dann bleibe ich wohl lieber beim Neustarten..
Trotzdem danke euch allen
|
|
SMO
Beiträge: 120
Erhaltene Danke: 18
D2005 Personal
|
Verfasst: So 01.03.15 15:04
Ach so ist das! Es geht also gar nicht um Zeitmessung, sondern darum, einem anderen Programm etwas vorzugaukeln.
Das ist auch nur eine Adresse im RAM.
Wenn du GetTickCount nicht global für ALLE Windows-Programme ändern willst, sondern nur für ein bestimmtes Programm, dann habe ich eine Lösung für dich.
Das ganze ist nicht soooo schwierig. Im Prinzip musst du eine kleine DLL programmieren, die von deinem Zielprogramm (dem Spiel) geladen wird. Deine DLL kann dann den Import-Eintrag von GetTickCount auf deine eigene Version umbiegen. Wie bekommst du aber das Spiel dazu, deine DLL zu laden? Da gibt es zwei Möglichkeiten:
1. Finde heraus, welche DLLs das Spiel importiert. Wenn du Glück hast, importiert es eine oder wenige Funktionen irgendeiner nicht-Standard-DLL. Dann köntest du deine DLL genau so nennen und neben der EXE des Spiels platzieren. Somit würde Windows bei jedem Start der EXE deine DLL in das Spiel laden. Nachteil: du musst alle Funktionen der echten DLL, die du imitierst, nachbauen oder "durchschleifen". Und das funktioniert nicht mit "Known DLLs" (Standard DLLs), wie z.B. kernel32, user32, usw., denn Windows lädt die immer aus dem Systemordner, selbst wenn es beim startenden Programm eine lokale Kopie gibt.
Es gibt durchaus Fälle, in denen ein Programm nur eine einzige Funktion einer nicht-Standard-DLL importiert (und dann vielleicht noch nicht mal im normalen Programmverlauf benutzt). So etwas wäre ideal, wird aber recht selten sein.
2. Injiziere deine DLL in den Prozess des Spiels. Dazu programmierst du dir ein kleines "Launcher"-Programm, das Windows anweist, das Spiel in den Speicher zu laden aber noch nicht zu starten. Dann startet der Launcher einen neuen Thread im Prozess des Spiels, der deine DLL lädt. Schließlich wird der Hauptthread des Spiels gestartet und deine DLL verweilt dort automatisch bis das Spiel wieder beendet wird.
Zu deinem Glück habe ich sowas schon öfter programmiert. Hier ist Code von mir, der funktionieren sollte:
Launcher, speichern als "DllInjector.dpr"
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127:
| program DllInjector;
{$R *.res}
uses Winapi.Windows;
const ExeFileName = 'GAME.EXE'; DllFileName = 'mydll.dll';
function FileExists(FileName: string): Boolean; var Flags: Cardinal; begin Flags := GetFileAttributes(PChar(FileName)); Result := (Flags <> INVALID_FILE_ATTRIBUTES) and (Flags and FILE_ATTRIBUTE_DIRECTORY = 0); end;
procedure ShowMessage(const Msg: string); begin MessageBox(0, PChar(Msg), nil, MB_ICONERROR or MB_OK); end;
function MyCreateProcess(AppName, CmdLine: string; out PI: TProcessInformation): Boolean; var SI: TStartupInfo; begin FillChar(PI, SizeOf(PI), 0); FillChar(SI, SizeOf(SI), 0); Result := CreateProcess(PChar(AppName), PChar(CmdLine), nil, nil, False, CREATE_SUSPENDED, nil, nil, SI, PI); end;
function InjectDll(const PI: TProcessInformation; LibName: string): Boolean; const ProcName = {$IFDEF UNICODE} 'LoadLibraryW' {$ELSE} 'LoadLibraryA' {$ENDIF}; var TID: DWORD; hProcess, hThread: THandle; ByteSize: SIZE_T; Parameters: Pointer; PThreadStartAddr: Pointer; MustCloseHandle: Boolean; begin MustCloseHandle := False; hProcess := PI.hProcess; if hProcess = 0 then begin hProcess := OpenProcess(PROCESS_ALL_ACCESS, False, PI.dwProcessId); MustCloseHandle := True; end; ByteSize := (Length(LibName) + 1) * SizeOf(Char); Parameters := VirtualAllocEx(hProcess, nil, ByteSize, MEM_COMMIT or MEM_RESERVE, PAGE_READWRITE); Result := WriteProcessMemory(hProcess, Parameters, PChar(LibName), ByteSize, ByteSize); PThreadStartAddr := GetProcAddress(GetModuleHandle('kernel32.dll'), ProcName); hThread := CreateRemoteThread(hProcess, nil, 0, PThreadStartAddr, Parameters, 0, TID); WaitForSingleObject(hThread, INFINITE); Result := VirtualFreeEx(hProcess, Parameters, 0, MEM_RELEASE) and Result; Result := CloseHandle(hThread) and Result; if MustCloseHandle then Result := CloseHandle(hProcess) and Result; end;
function CloseHandles(var PI: TProcessInformation): Boolean; begin Result := CloseHandle(PI.hProcess); Result := CloseHandle(PI.hThread) and Result; PI.hProcess := 0; PI.hThread := 0; end;
var PI: TProcessInformation;
begin if not FileExists(ExeFileName) then begin ShowMessage('Kann das Programm "' + ExeFileName + '" nicht finden!'#10 + 'Dieser Launcher sollte im selben Verzeichnis wie "' + ExeFileName + ' gestartet werden".'#10); Exit; end; if not FileExists(DllFileName) then begin ShowMessage('Kann die DLL "' + DllFileName + '" nicht finden!'#10 + 'Diese DLL sollte sich im selben Verzeichnis wie dieser Launcher und "' + ExeFileName + ' befinden".'#10); Exit; end;
if MyCreateProcess(ExeFileName, '', PI) then begin if InjectDll(PI, DllFileName) then ResumeThread(PI.hThread) else ShowMessage('Konnte DLL "' + DllFileName + '" nicht in "' + ExeFileName + '" injizieren!'); CloseHandles(PI); end else ShowMessage('Konnte "' + ExeFileName + '" nicht starten!'); end. |
Und die DLL (mydll.dpr):
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162:
| library mydll;
uses System.AnsiStrings, Winapi.Windows;
{$R *.res}
function GetImportTablePointer(Module: HMODULE; const LibName, ProcName: string): Pointer; var LibNameA, ProcNameA: AnsiString; DosHeader: PImageDosHeader; NtHeaders: PImageNtHeaders; ImportDesc: PImageImportDescriptor; PName: PAnsiChar; Thunk, OrigThunk: PImageThunkData32; ImportName: PImageImportByName; begin DosHeader := PImageDosHeader(Module); if DosHeader.e_magic <> IMAGE_DOS_SIGNATURE then Exit(nil); NtHeaders := PImageNtHeaders(IntPtr(Module) + DosHeader._lfanew); if (NtHeaders.Signature <> IMAGE_NT_SIGNATURE) or (NtHeaders.OptionalHeader.Magic <> IMAGE_NT_OPTIONAL_HDR32_MAGIC) then Exit(nil);
with NtHeaders.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] do UIntPtr(ImportDesc) := VirtualAddress + UIntPtr(Module);
LibNameA := AnsiString(LibName); ProcNameA := AnsiString(ProcName); while ImportDesc.OriginalFirstThunk > 0 do begin PName := PAnsiChar(ImportDesc.Name + UIntPtr(Module)); UIntPtr(OrigThunk) := ImportDesc.OriginalFirstThunk + UIntPtr(Module); UIntPtr(Thunk) := ImportDesc.FirstThunk + UIntPtr(Module); if SameText(PName, LibNameA) then while OrigThunk.AddressOfData > 0 do begin if not Odd(OrigThunk.Ordinal) then begin UIntPtr(ImportName) := OrigThunk.AddressOfData + UIntPtr(Module); PName := PAnsiChar(UIntPtr(ImportName) + SizeOf(ImportName.Hint)); if SameText(PName, ProcNameA) then Exit(Thunk); end; Inc(OrigThunk); Inc(Thunk); end; Inc(ImportDesc); end; Result := nil; end;
function InstallImportHook(Module: HMODULE; const LibName, ProcName: string; NewProc: Pointer; out OrigProc: Pointer): Boolean; var P: PPointer; OldProtect: DWORD; begin P := GetImportTablePointer(Module, LibName, ProcName); if Assigned(P) and Assigned(NewProc) then begin OldProtect := PAGE_EXECUTE_WRITECOPY; Result := VirtualProtect(P, SizeOf(P^), OldProtect, OldProtect); if Result then begin OrigProc := P^; P^ := NewProc; end; VirtualProtect(P, SizeOf(P^), OldProtect, OldProtect); end else Result := False; end;
var HMainModule: HMODULE; PreviousDLLProcEx: TDLLProcEx; OrigGetTickCount: function: DWORD; stdcall; InitialTickCountValue: DWORD;
function MyGetTickCount: DWORD; stdcall; begin Result := OrigGetTickCount - InitialTickCountValue end;
procedure InstallGetTickCountHook; begin if InstallImportHook(HMainModule, kernel32, 'GetTickCount', @MyGetTickCount, @OrigGetTickCount) then InitialTickCountValue := OrigGetTickCount; end;
procedure UninstallGetTickCountHook; var Dummy: Pointer; begin InstallImportHook(HMainModule, kernel32, 'GetTickCount', @OrigGetTickCount, Dummy); end;
procedure DllMainEx(Reason: Integer; Reserved: Pointer); begin case Reason of DLL_PROCESS_ATTACH: InstallGetTickCountHook; DLL_PROCESS_DETACH: UninstallGetTickCountHook; end; if Assigned(PreviousDLLProcEx) then PreviousDLLProcEx(Reason, Reserved); end;
begin HMainModule := GetModuleHandle(nil); DisableThreadLibraryCalls(HInstance); PreviousDLLProcEx := DllProcEx; DllProcEx := @DllMainEx; DllProcEx(DLL_PROCESS_ATTACH, nil); end. |
Eine kleine Erklärung zum DLL-Grundgerüst habe ich hier geschrieben.
In diesem einfach Fall könnte man sich das Hantieren mit DllProc natürlich sparen und "InstallGetTickCountHook" direkt aus dem Hauptprogrammblock aufrufen. Das Ausführen von "UninstallGetTickCountHook" ist nicht unbedingt nötig, da die DLL erst wieder entladen wird, wenn das Programm selbst beendet wurde. Aber so wie im Code gezeigt ist es wahrscheinlich formell korrekter.
Falls es Fragen zum Code gibt, lass es mich wissen!
Zuletzt bearbeitet von SMO am Mo 02.03.15 17:07, insgesamt 6-mal bearbeitet
Für diesen Beitrag haben gedankt: jaenicke
|
|
OlafSt
Beiträge: 486
Erhaltene Danke: 99
Win7, Win81, Win10
Tokyo, VS2017
|
Verfasst: Mo 02.03.15 10:48
Da ist wohl der Testzeitraum abgelaufen und es wird versucht, diesen "irgendwie" zu verlängern.
Gibt es eine Möglichkeit, eine solche Manipulation zu erkennen ?
_________________ Lies, was da steht. Denk dann drüber nach. Dann erst fragen.
|
|
SMO
Beiträge: 120
Erhaltene Danke: 18
D2005 Personal
|
Verfasst: Mo 02.03.15 13:22
Ob das der Anwendungszweck ist, habe ich auch kurz überlegt. Das wäre allerdings eine äußerst dumme Methode, einen Testzeitraum zu implementieren, da der TickCount ja sowieso bei jedem Neustart von Windows zurückgesetzt wird. Und wenn die Prüfung so plump ist, muss man nicht extra eine DLL programmieren, sondern könnte das Programm auch disassemblieren und die Abfrage direkt durch Ändern von ein paar Bytes aushebeln.
Klar gibt es Möglichkeiten sowas zu erkennen. Und natürlich auch wieder Möglichkeiten, der Erkennung zu entgehen. Das Programm könnte z.B. GetProcAddress(GetModuleHandle('kernel32.dll'), 'GetTickCount') aufrufen, um die Adresse vom echten GetTickCount zu bekommen. Aber dann biegt man eben auch GetProcAddress auf eine eigene Variante um...
Ich habe DLL-Injektion und Api-Hooking für recht legitime Zwecke benutzt. Nämlich um ein paar alte Windows 9x Spiele wieder zum Laufen zu bringen, in Windows 7 und neuer. Im Prinzip macht Microsofts eigene Kompatibilitätsschicht nichts anderes, funktioniert aber oft nicht zufriedenstellend, weil sie eben allgemein gehalten ist und nicht maßgeschneidert wurde auf die Anforderungen spezieller Programme.
Zuletzt bearbeitet von SMO am Di 03.03.15 12:46, insgesamt 1-mal bearbeitet
|
|
user32
Beiträge: 55
Erhaltene Danke: 5
|
Verfasst: Mo 02.03.15 15:07
Nein nein, keine Sorge. Habe das Spiel gekauft und kann das auch beweisen (sogar 2x weil ich die CD zerkratzt hatte), aber das interessiert hier auch gar nicht nicht.
GetTickCount wird hier in irgendeiner Rendering Funktion benutzt, so wie es viele alte OpenGL Programme damals taten. Und wenn die Zahl zu groß wird scheint es irgendwelche Glitches zu geben. Und umso länger der PC an ist, desto schlimmer.
Werde mir deine Methode mal später genauer angucken, Danke SMO.
|
|
SMO
Beiträge: 120
Erhaltene Danke: 18
D2005 Personal
|
Verfasst: Mo 02.03.15 16:37
Na dann hoffe ich mal, dass dieses Problem auch wirklich an GetTickCount liegt.
Ich habe übrigens gerade noch ein paar Kommentare zum Code hinzugefügt und auch einen kleinen Fehler korrigiert.
|
|
user32
Beiträge: 55
Erhaltene Danke: 5
|
Verfasst: Mo 02.03.15 17:14
Doch ich denke schon, weil es tritt wirklich nur auf, wenn ich den PC länger anhabe
(wir reden hier übrigens von Tagen, nicht ein paar stunden oder so) .
Der Verdacht kam, als ich vor einiger Zeit ein Programm machen wollte, das anzeigt wie lange der PC schon läuft - eben mit GetTickCount. Und irgendwann stellte ich mit erschrecken fest, dass der Zähler rückwärts läuft
Wenn dieses altes Uptime-Programm anfängt zu "spinnen", dann fangen zufällig auch die Glitches in dem Spiel an. Eine klare Korrelation. Aber ich hab das jetzt nicht alles zu 100% erforscht
|
|
jaenicke
Beiträge: 19284
Erhaltene Danke: 1742
W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: Mo 02.03.15 20:45
Der Timer funktioniert eigentlich bis zu knapp 50 Tagen normal. In der Zeit startest du doch wohl den PC ein paarmal neu?!?
|
|
Sinspin
Beiträge: 1327
Erhaltene Danke: 117
Win 10
RIO, CE, Lazarus
|
Verfasst: Di 03.03.15 07:19
jaenicke hat folgendes geschrieben : | Der Timer funktioniert eigentlich bis zu knapp 50 Tagen normal. In der Zeit startest du doch wohl den PC ein paarmal neu?!? |
Ob Du es glaubst oder nicht, es gibt Rechner die 24/7 laufen und das über Monate hinweg. Einzig und allein Windows Updates zwingen einen immer mal zum Neustart des Rechners. Denke zum Beispiel mal an Industrie PCs die als Maschienen Interface im 3 Schicht Betrieb laufen. Da ist nicht ersthaft Zeit für eine Unterbrechung.
Wenn ein Rechner nie ins Internet geht oder nur mit einem bestimmten Server redet sind Neustarts genauso mehr als nervige Ausfallzeiten.
Mir ist aufgefallen das ein Win Rechner nach ca. einem Monat Laufzeit noch ein paar mehr Macken bekommt, Je nachdem was M$ gerade malwieder rumgemurkst (auf hohem Niveau natürlich) hat. Auf einem Rechner mit FTP Server drauf gehen mir die TCP/IP Ports aus. Die Übersicht der offenen Ports zeigt aber keine verlorenen Ports an. Das habe ich auf einem anderen Rechner auch schon erlebt. Eine Zeit lang gab es mal nen Fehler mit einem Integer überlauf beim Zähler für gesendete oder empfangene Daten über eine bestimmte Netzwerkkarte. Schön wenn man zwei hat!
Interessant ist jedenfalls das selbst M$ Server Systeme Windowsupdates bekommen und durch die sie zwangsweise neu gestartet werden müssen. Gerade bei Servern halte ich das aber für ziemlich dämlich.
_________________ Wir zerstören die Natur und Wälder der Erde. Wir töten wilde Tiere für Trophäen. Wir produzieren Lebewesen als Massenware um sie nach wenigen Monaten zu töten. Warum sollte unser aller Mutter, die Natur, nicht die gleichen Rechte haben?
|
|
jaenicke
Beiträge: 19284
Erhaltene Danke: 1742
W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: Di 03.03.15 08:16
Industrie-PCs nutzen aber dann erstens eher GetTickCount64 und außerdem meinte ich natürlich den vorliegenden Fall und somit einen einfachen privaten PC. Oder meinst du, dass das Spiel auf einem solchen Industrie-PC laufen soll?
|
|
Sinspin
Beiträge: 1327
Erhaltene Danke: 117
Win 10
RIO, CE, Lazarus
|
Verfasst: Di 03.03.15 15:44
Eher unwahrscheinlich. Industrie PCs waren nur ein Beispiel für Rechner die 24/7 laufen. Aber auch meine Privatrechner laufen gerne mal als einen Monat am Stück. Ich hatte den GetTickCount Fehler selber auch schon. Bin dann auf QueryPerformanceCounter umgestiegen.
_________________ Wir zerstören die Natur und Wälder der Erde. Wir töten wilde Tiere für Trophäen. Wir produzieren Lebewesen als Massenware um sie nach wenigen Monaten zu töten. Warum sollte unser aller Mutter, die Natur, nicht die gleichen Rechte haben?
|
|
Blup
Beiträge: 174
Erhaltene Danke: 43
|
Verfasst: Mi 04.03.15 11:59
Der Fehler, das ein Counter rückwärts läuft, ist aber eindeutig dem Programmierer der Anwendung anzulasten.
GetTickCount liefert eine Ganzzahl ohne Vorzeichen.
Wenn man das höchste Bit aber als Vorzeichen interpretiert, wird daraus irgendwann eine negative Zahl, deren Absolutbetrag stetig kleiner wird.
Falls die Anwendung auch mehrere Tage läuft, währe es wahrscheinliche sinnvoll, auch das oberste Bit bei GetTickCount per Maske auszublenden.
Delphi-Quelltext 1: 2: 3: 4:
| function MyGetTickCount: DWORD; stdcall; begin Result := (OrigGetTickCount - InitialTickCountValue) and $7FFFFFFF; end; |
Das Problem mit dem Überlauf dieses Zählers ist schon so lange bekannt, wie diese Funktion existiert.
Es gibt verscheidene Lösungen, der Programmierer musste sich nur die passende auswählen.
GetTickCount64 gibt es auch schon eine ganze Weile.
|
|
user32
Beiträge: 55
Erhaltene Danke: 5
|
Verfasst: Do 05.03.15 15:48
Moderiert von Narses: Komplettzitat des letzten Beitrags entfernt.
Ja wie gesagt, es war ein altes Programm von mir. Aber genau dann wenn er anfängt rückwärts zu laufen, gingen die besagten Glitches im Spiel los. Das heißt wohl, die Entwickler von dem Spiel haben den Fehler wohl auch gemacht
|
|
|