All Activity
- Today
-
SharkMagick96 joined the community
-
to N-Drew, I like it a lot.
-
More great additions to the game! And I like the new hanger door in Davik's Estate. Wonder if it will be overridden if I use a texture mod for Taris.
-
Iriaz. Another RC-K1CP update is almost ready, and as per tradition I would release an update, add something completely obscure, and then I'll get the endless moaning of "where's the Outcast Children and where are the Iriaz" instead of "wow, I sure do love the restored line from that one NPC I didn't even pay attention to". Well, you can all stop moaning... for now, for one of those things that you guys just love to complain about is actually being added in the next update! But no Outcast Children just yet... But when that update does come out I may or may not announce it with this: So, how do Iriaz work in RC-K1CP? Well, I believe in K1R the Iriaz would walk around the map and follow certain way point directions similar to how some Kath Hounds behave, however, the Iriaz will largely wander around aimlessly as the left over Iriaz .UTC file in the Sandrale Estate exterior module was programmed to do just that. Instead of just "walking around" doing nothing the Iriaz will do something... and it's going to be an absolute bloodbath as you journey across the Dantooine planes now. When you enter a Dantooine module, somewhere in that module there will be Iriaz and Kath Hounds fighting each other. Sometimes an Iriaz may take down a Kath Hound or two, but more often than not the Kath Hounds always wipe out the Iriaz. Of course, that happens without Player intervention. Through Player intervention it can be possible for you to save some of the doomed Iriaz, however, simply standing around and letting the Iriaz get mowed down will in-fact chip down some of the Kath Hounds health so there is some benefit to letting them die. And of course, the Iriaz will also be attacked by Mandalorians. In lore, the Iriaz are docile and can get 'provoked' into aggression, and combined with the pre-existing warrior lore of the Mandalorians it does in-fact make sense for these two groups to be fighting. I have taken measures to try and make sure that Iriaz herds are not too close to Mandalorians so there shouldn't be too many instances of Mandalorians being led away from their group by rogue Iriaz's or something stupid like Sherruk running after an Iriaz and leaving his men to die. Now, K1R does have a bug wherein the Iriaz actually break that one cutscene with the Mandalorian and the "wife and children" bit. I have made it so that the Iriaz in that particular area are hostile to the Player meaning they should not break that cutscene and the Player will be able to kill some of these Iriaz themselves. Again, they are "docile" and not "friendly" and so it makes sense why some would want to kill you when others would rather fight Kath Hounds. There is a bug where the Iriaz stand completely still for about five to ten seconds before their AI kicks in, here is an example of this: Here is when their AI kicks in: And this is the result when the Player lets them die: As you can see, despite being outnumbered the Kath Hounds still win the fight. Since this is near Bolook his line "there are plenty of Iriaz around" now makes sense as you literally either just saw a bunch of Iriaz die or saved them and now they're roaming about nearby. I do not yet have screenshots of the Player being attacked by Iriaz, however, I do plan to add screenshots of restored content for the main mod page so I may add them there when this update is finally ready. Now that the one thing you all wanted is out, time for the obscure garbage no one cares about (this list may not be complete/some of it you may recognize if you ever played my ancient K1GI mod from back in the day/some of it may not be added if I can't get it to work flawlessly): * Restored an unused civilian dialogue for one of the male patrons of the Lower City Cantina. * Restored the "search database for planetary launch codes" option for all the Sith Base terminals, this will always fail as the Governor has them. * The Receptionist Terminal, the Computer Terminal, and the Control Center Terminal now have three unique interactions. The Receptionist Terminal offers almost no real benefits besides upload area schematics. The Computer Terminal offers most functions except for open elevator, disable assault droid shield, and overload power conduit. The Control Center Terminal has every function available. * The Hangar Terminal and the Barracks Terminal now have two unique interactions. The Hangar Terminal has no actual function besides the ability to disable the Hangar Security whilst the Barracks Terminal has all the functions except the ability to disable the Hangar Security. * You can now download the Ebon Hawk codes via the Barracks terminal, allowing you to bypass Hudrow. * Restored the cut door model for Davik's Hangar. * You can now attempt to open the Hangar Bay doors via the terminals in the Prison Block and Command Deck, this'll always fail. * You can now overload the power conduit in the Sith Trooper barracks in the Prison Block, this is an alternative to starting a prison riot and then gassing both the Sith and the prisoners. * You'll now have to unlock the Elevator after rescuing the Player. * The Command Deck terminal will now mention the Bridge lockdown before you login. This'll be removed when you open the Hangar doors and the lockdown is lifted. * You can no longer unlock all security doors on the Command Deck as options to unlock specific doors have been restored. * You can now attempt to open the Bridge Security Door, this'll always fail. * You can now use the Command Deck terminal to discover that you can use a Space Suit to access the airlock to bypass the Bridge Security Door. * You can no longer return to the Prison Block if the Hangar is open. That's all for today, please let me know what you think!
-
I had a test of it on my end and it worked as expected, though I believe I might've discovered your problem. Soundsets are annoying in these games because to add the sounds they have to be added to something called a "dialog.tlk" file. The dialog.tlk file contains all the text you see in-game, and since my game is in English I believe the soundsets don't work for you because my mod is in English whereas your Kotor isn't and since your game doesn't use English text in its dialog.tlk this mod won't work on non-English Kotor games unless someone where to translate this mod into other languages. That means there'd need to be translated versions for Chinese, French, Russian, Italian, etc.. I am sorry I could not be of more help 😞
- 32 comments
-
- sith soldier
- marius fett
-
(and 1 more)
Tagged with:
-
- 32 comments
-
- sith soldier
- marius fett
-
(and 1 more)
Tagged with:
-
Valkas joined the community
-
MrPr3sidenT joined the community
-
Sith3928391 joined the community
-
Leilukin started following Quanon & Dark Hope Twi'lek Pack and HQ Vibro Weapons + Effects - K1
-
miltonmilky joined the community
-
Linguine joined the community
-
daleretto joined the community
-
MostlyTired joined the community
-
Imperialis21 joined the community
- Yesterday
-
bekosok joined the community
-
-
spencerdiaz started following New_Lightsaber_Blade_Model_TSL
-
- 32 comments
-
- sith soldier
- marius fett
-
(and 1 more)
Tagged with:
-
- 32 comments
-
- sith soldier
- marius fett
-
(and 1 more)
Tagged with:
-
90SK started following Tulak Hord's lore accurate mask
-
skwatter changed their profile photo
-
- 4 comments
-
- 1
-
-
- modders resource
- hoods
- (and 5 more)
-
Kotor 1 - Stuck before Dantooine Star Map
DarthParametric replied to keegs's topic in Knights of the Old Republic General
Here you go. I'm not sure if your game being non-English will be a problem after being saved in my English version. Dantooine_Ruins_Door_Fix_xispect.zip Crash? There shouldn't be any crash. What you encountered is a "soft lock". You managed to break the script that triggers the cutscene, leaving you with no way to continue the main quest. I still don't know how people manage to break it, hence why it's not fixed. I can't replicate the problem on my end. - Last week
-
Here is the text converted to BBCode, styled cleanly for forum posts: [h1]KOTOR 1 has a built-in frame limiter — BioWare shipped it switched off[/h1] [b]TL;DR:[/b] [i]Star Wars: Knights of the Old Republic[/i] (2003, Odyssey engine) contains a complete busy-wait frame limiter in its main loop. It is controlled by a single [font=monospace]float[/font] in the executable that defaults to [font=monospace]0.0[/font], which leaves the limiter dormant, so the game free-runs at uncapped framerate. On high-refresh displays this uncapped rate is what causes the long-standing [b]movement / "stuck after combat turn" freeze[/b] and related timing bugs, which the community has only ever worked around with external FPS caps or by dropping the monitor to 60 Hz. Writing [font=monospace]60.0[/font] (or any target) to that one float [b]arms the engine's own limiter[/b] — a native, self-contained fix. No injected code, no wrappers, no driver settings. It's a 4-byte data patch. Verified: on a 144 Hz display with all external caps removed, the patched exe holds a hard 60 FPS and the freeze does not occur. [hr] [h2]The bug[/h2] KOTOR's main loop ([font=monospace]WinMain[/font], [font=monospace]FUN_004041f0[/font] in a Ghidra auto-analysis of the standard PC exe) is a classic [font=monospace]PeekMessage[/font] real-time loop: [code] while (quit_flag == 0) { read timer (game clock) if no pending message: render scene (delta-time based) SwapBuffers (present) ... optional limiter here ... } [/code] Gameplay logic — movement, and the combat round/turn state machine — advances by frame [b]delta-time[/b]. With no frame cap, delta-times on a fast display become tiny and irregular, and the 2003-era state machine mis-steps: the controlled character's action queue stalls until something rebuilds it (switching party member, or a save/load clears it). That is the freeze players have reported for 20 years. [h2]The dormant limiter[/h2] Immediately after [font=monospace]SwapBuffers[/font], the loop contains this (Ghidra decompilation, lightly annotated): [code=c] if (DAT_007a3c58 != 0) // a separate, also-off coarse limiter Sleep(DAT_0078d1e8); if (0.0f < cap) { // cap = _DAT_007a3c64 (the frame-cap float) target = 1000.0f / cap; // target frame time // measure elapsed since frame start, then: if (elapsed < target) { do { // busy-spin: re-read the game timer } while (elapsed < target); // hold the frame until target reached } } [/code] With real constant values read from the binary: [table] [tr] [th]Symbol[/th] [th]RVA[/th] [th]Value[/th] [th]Meaning[/th] [/tr] [tr] [td][font=monospace]_DAT_007a3c64[/font][/td] [td][font=monospace]0x7a3c64[/font][/td] [td][b]0.0[/b][/td] [td]frame cap (FPS). [b]This is it.[/b][/td] [/tr] [tr] [td]numerator const[/td] [td][font=monospace]0x73d6fc[/font][/td] [td]1000.0[/td] [td][font=monospace]target_ms = 1000.0 / cap[/font][/td] [/tr] [tr] [td]zero const[/td] [td][font=monospace]0x73d700[/font][/td] [td]0.0[/td] [td]used in the [font=monospace]0.0 < cap[/font] gate[/td] [/tr] [tr] [td]timer scale const[/td] [td][font=monospace]0x73d708[/font][/td] [td]0.001[/td] [td]raw timer -> milliseconds[/td] [/tr] [/table] The gate is literally [b]"if cap > 0, limit the frame."[/b] With [font=monospace]cap = 60[/font], the loop busy-waits until each frame has taken [font=monospace]1000/60 ≈ 16.67 ms[/font], i.e. it caps at 60 FPS. This is the [i]same[/i] formula the engine uses elsewhere — the movie-playback path caps to 30 with it — so the mechanism is proven, not speculative. [h2]Why it's off[/h2] [font=monospace]_DAT_007a3c64[/font] starts at [font=monospace]0.0[/font]. The [b]only[/b] code that writes it is a render-setup function ([font=monospace]FUN_004c5880[/font]), and that write is guarded: [code=c] if (DAT_00832904 != 0) { ... _DAT_007a3c64 = (float)DAT_00832904; // arm the cap } [/code] [font=monospace]DAT_00832904[/font] lives in [b]BSS[/b] — zero-initialized, and it has [b]no writer anywhere in the binary[/b] (it's a config variable exposed by address, never set by default). So the guard is never true, the cap value is never assigned, and [font=monospace]_DAT_007a3c64[/font] stays [font=monospace]0.0[/font] for the entire session. The limiter is complete, correct, and permanently asleep. Because nothing ever writes the cap during normal play, [b]patching its initial [font=monospace].data[/font] value is safe and permanent[/b] — no code path overwrites it. [h2]The patch[/h2] Standard PC v1.03 / GOG / "editable" executable: [code] file offset 0x3A3C64 : 00 00 00 00 (float 0.0, limiter off) -> 00 00 70 42 (float 60.0, 60 FPS cap) [/code] Other caps if you prefer: [font=monospace]30.0[/font] = [font=monospace]00 00 F0 41[/font], [font=monospace]72.0[/font] = [font=monospace]00 00 90 42[/font], [font=monospace]120.0[/font] = [font=monospace]00 00 F0 42[/font]. A Python patcher ([font=monospace]kotor_fpscap_patch.py[/font]) is included; it fingerprints the two [font=monospace].rdata[/font] constants before writing, backs up the exe, and refuses to touch an executable whose layout it doesn't recognize: [code] python kotor_fpscap_patch.py "path\to\swkotor.exe" 60 [/code] [h2]Verification[/h2] [],144 Hz display, all external FPS caps and driver "wait for vertical refresh" overrides removed. [],Patched exe launched directly. [*],Framerate held a hard [b]60[/b]; the movement/turn freeze did not reproduce on transitions that previously triggered it. [h2]Caveats[/h2] [],[b]Busy-wait, by design.[/b] The limiter spins a CPU core while pacing each frame (this is BioWare's own implementation — the movie path spins the same way). On a modern machine, one pegged core for a single-threaded 2003 game is harmless, but it is not a [font=monospace]Sleep[/font]-based idle cap. A future refinement could redirect the gate to the engine's own [font=monospace]Sleep(DAT_0078d1e8)[/font] limiter instead. [],[b]Offset is per-exe-layout.[/b] [font=monospace]0x3A3C64[/font] is verified for the standard PC exe. Other builds (Steam-with-different-packing, 4-CD, Mac) may differ; find [font=monospace]_DAT_007a3c64[/font] by locating the [font=monospace]1000.0 / cap[/font] main-loop math. [],[b]Steam "Verify integrity" reverts it[/b] (restores the stock exe). Re-apply after any verify. [],Stacks cleanly with the 4GB LARGE_ADDRESS_AWARE patch, UniWS widescreen, and the community mod builds — it only touches one unrelated float. [h2]Method[/h2] Static reverse engineering only (Ghidra). Chain: locate timing-API imports ([font=monospace]QueryPerformanceCounter[/font], [font=monospace]GetTickCount[/font], [font=monospace]Sleep[/font]) → their callers → the [font=monospace]PeekMessage[/font] message pump → its caller ([font=monospace]WinMain[/font]) → the frame-limiter block in the loop → the cap float and its dormant guard. Full decompiled C of the engine was exported and searched locally to trace the call chain. [hr] [i]Reverse-engineered from a legally-owned copy for interoperability and bug-fixing. Not affiliated with BioWare/LucasArts/Aspyr.[/i]
-
-
xispect started following Kotor 1 - Stuck before Dantooine Star Map
-
Kotor 1 - Stuck before Dantooine Star Map
xispect replied to keegs's topic in Knights of the Old Republic General
I'm having the same problem: I go into a room where there's supposed to be a map, but nothing happens. It turns out there's supposed to be a cutscene there, but for some reason it's not there. Can you help me, please? What exactly do you have to do to crash your game like that? And what should you do if you manage to recover a save file from such a crash? I'll keep this in mind for the future and make sure to save my game. 000015 - Game14.zip -
-
- modorganizer
- mo2
-
(and 1 more)
Tagged with:
-
imposterzed started following Larger Text Fonts for KOTOR
-
Vabulletizer started following Kotor 1 (GoG) Reverse Engineering
-
imposterzed started following Iriaz on Dantooine , Bodies Stay and Party Leveler K1
-
Much appreciated. By the way, you wrote that the limiter could be reintroduced at different values. Would you still recommend 60 generally? Cheers! 🥂
-
Ah yeah my bad, you need K1CP for the mod to work. Lemme fix that real quick. Edit : Who the heck would install this kind of mod alone though... When I was looking for mods for this game 2 years ago the first thing I stumbled upon was K1R, right before understanding K1CP+RC-K1CP makes more sense nowadays. Edit 2 ; Done. You can download the new version if needed. A clean .mod file has been included and added to the Patcher. Installation instructions don't change.
- 6 comments
-
- tulak hord
- korriban
-
(and 3 more)
Tagged with:
-
You are right...I was sleepy yesterday night I will make it more friendly
-
PartySwap has been updated to 1.5, by making the PartySwap item to an optional add-on, since the Full Party Select patch from KotOR Patch Manager provides another method to allow you to keep being able to add Handmaiden to your party after Disciple joins your party.
- 452 replies
-
- 3
-
-
-
-
- handmaiden
- party
-
(and 2 more)
Tagged with:
-
PartySwap has been updated to 1.5, by making the PartySwap item to an optional add-on, since the Full Party Select patch from KotOR Patch Manager provides another method to allow you to keep being able to add Handmaiden to your party after Disciple joins your party.
-
Sounds interesting, but I would welcome a more detailed installation instructions for Windows users.
-
View File Playable Ewok with 2 Hand Animations This is a total overhaul of my previous Ewok Mod using the method I used on my Jawa mod so that the Ewok now has 2 handed weapon animations. Aware of small glitch where because Ewok is too fat 2h lightsaber clips through model slightly. Can be fixed easily probably will later. This Kotor 1 mod adds a playable custom ewok mdl that can be placed into the override folder to replace some npcs or used in conjunction with KSE as a playable character with combat animations. No 2 hand mele combat animations at this time. This .Zip file contains a ReadMe.txt a brief tutorial.txt on how to recreate the mod process, the custom .tga files for the textures, and finally the custom .mdl and .mdx files that can be dropped into override following the instructions in the ReadMe file. I also included a link to how to recreate the process incase anyone is interested. Disclaimer: This mod utilizes a modified asset from the community-maintained SWBF2 Mod Tools SDK 2020 available at ModDB, alongside original assets from Star Wars: Knights of the Old Republic. Full credit goes to the original developers, artists, and respective intellectual property rights holders (Lucasfilm, Pandemic Studios, and BioWare). This is a strictly non-profit, fan-made educational project intended exclusively as a technical proof-of-concept demonstrating asset handling for the KOTOR modding community. Furthermore, I do not own or claim rights to any of the third-party tools used or described in this project; all rights belong entirely to their respective developers Submitter EwokKotor Submitted 07/21/2026 Category Mods K1R Compatible Yes
-
View File Playable Jawa See comment bellow for new optional addon files. This mod adds a playable Jawa to K1. Shoutout to DarthParametric for helping me to figure out the bug on the prototype! Simply drop the files into Override to install. Feel free to use this mod in your own works but plz give credit. Video on process coming soon. Submitter EwokKotor Submitted 07/21/2026 Category Mods K1R Compatible Yes
-
View File KOTOR_FPS_CAP # KOTOR 1 has a built-in frame limiter — BioWare shipped it switched off **TL;DR:** *Star Wars: Knights of the Old Republic* (2003, Odyssey engine) contains a complete busy-wait frame limiter in its main loop. It is controlled by a single `float` in the executable that defaults to `0.0`, which leaves the limiter dormant, so the game free-runs at uncapped framerate. On high-refresh displays this uncapped rate is what causes the long-standing **movement / "stuck after combat turn" freeze** and related timing bugs, which the community has only ever worked around with external FPS caps or by dropping the monitor to 60 Hz. Writing `60.0` (or any target) to that one float **arms the engine's own limiter** — a native, self-contained fix. No injected code, no wrappers, no driver settings. It's a 4-byte data patch. Verified: on a 144 Hz display with all external caps removed, the patched exe holds a hard 60 FPS and the freeze does not occur. --- ## The bug KOTOR's main loop (`WinMain`, `FUN_004041f0` in a Ghidra auto-analysis of the standard PC exe) is a classic `PeekMessage` real-time loop: ``` while (quit_flag == 0) { read timer (game clock) if no pending message: render scene (delta-time based) SwapBuffers (present) ... optional limiter here ... } ``` Gameplay logic — movement, and the combat round/turn state machine — advances by frame **delta-time**. With no frame cap, delta-times on a fast display become tiny and irregular, and the 2003-era state machine mis-steps: the controlled character's action queue stalls until something rebuilds it (switching party member, or a save/load clears it). That is the freeze players have reported for 20 years. ## The dormant limiter Immediately after `SwapBuffers`, the loop contains this (Ghidra decompilation, lightly annotated): ```c if (DAT_007a3c58 != 0) // a separate, also-off coarse limiter Sleep(DAT_0078d1e8); if (0.0f < cap) { // cap = _DAT_007a3c64 (the frame-cap float) target = 1000.0f / cap; // target frame time // measure elapsed since frame start, then: if (elapsed < target) { do { // busy-spin: re-read the game timer } while (elapsed < target); // hold the frame until target reached } } ``` With real constant values read from the binary: | Symbol | RVA | Value | Meaning | |-------------------|-----------|----------|----------------------------------| | `_DAT_007a3c64` | `0x7a3c64` | **0.0** | frame cap (FPS). **This is it.** | | numerator const | `0x73d6fc` | 1000.0 | `target_ms = 1000.0 / cap` | | zero const | `0x73d700` | 0.0 | used in the `0.0 < cap` gate | | timer scale const | `0x73d708` | 0.001 | raw timer -> milliseconds | The gate is literally **"if cap > 0, limit the frame."** With `cap = 60`, the loop busy-waits until each frame has taken `1000/60 ≈ 16.67 ms`, i.e. it caps at 60 FPS. This is the *same* formula the engine uses elsewhere — the movie-playback path caps to 30 with it — so the mechanism is proven, not speculative. ## Why it's off `_DAT_007a3c64` starts at `0.0`. The **only** code that writes it is a render-setup function (`FUN_004c5880`), and that write is guarded: ```c if (DAT_00832904 != 0) { ... _DAT_007a3c64 = (float)DAT_00832904; // arm the cap } ``` `DAT_00832904` lives in **BSS** — zero-initialized, and it has **no writer anywhere in the binary** (it's a config variable exposed by address, never set by default). So the guard is never true, the cap value is never assigned, and `_DAT_007a3c64` stays `0.0` for the entire session. The limiter is complete, correct, and permanently asleep. Because nothing ever writes the cap during normal play, **patching its initial `.data` value is safe and permanent** — no code path overwrites it. ## The patch Standard PC v1.03 / GOG / "editable" executable: ``` file offset 0x3A3C64 : 00 00 00 00 (float 0.0, limiter off) -> 00 00 70 42 (float 60.0, 60 FPS cap) ``` Other caps if you prefer: `30.0` = `00 00 F0 41`, `72.0` = `00 00 90 42`, `120.0` = `00 00 F0 42`. A Python patcher (`kotor_fpscap_patch.py`) is included; it fingerprints the two `.rdata` constants before writing, backs up the exe, and refuses to touch an executable whose layout it doesn't recognize: ``` python kotor_fpscap_patch.py "path\to\swkotor.exe" 60 ``` ## Verification - 144 Hz display, all external FPS caps and driver "wait for vertical refresh" overrides removed. - Patched exe launched directly. - Framerate held a hard **60**; the movement/turn freeze did not reproduce on transitions that previously triggered it. ## Caveats - **Busy-wait, by design.** The limiter spins a CPU core while pacing each frame (this is BioWare's own implementation — the movie path spins the same way). On a modern machine, one pegged core for a single-threaded 2003 game is harmless, but it is not a `Sleep`-based idle cap. A future refinement could redirect the gate to the engine's own `Sleep(DAT_0078d1e8)` limiter instead. - **Offset is per-exe-layout.** `0x3A3C64` is verified for the standard PC exe. Other builds (Steam-with-different-packing, 4-CD, Mac) may differ; find `_DAT_007a3c64` by locating the `1000.0 / cap` main-loop math. - **Steam "Verify integrity" reverts it** (restores the stock exe). Re-apply after any verify. - Stacks cleanly with the 4GB LARGE_ADDRESS_AWARE patch, UniWS widescreen, and the community mod builds — it only touches one unrelated float. ## Method Static reverse engineering only (Ghidra). Chain: locate timing-API imports (`QueryPerformanceCounter`, `GetTickCount`, `Sleep`) → their callers → the `PeekMessage` message pump → its caller (`WinMain`) → the frame-limiter block in the loop → the cap float and its dormant guard. Full decompiled C of the engine was exported and searched locally to trace the call chain. --- *Reverse-engineered from a legally-owned copy for interoperability and bug-fixing. Not affiliated with BioWare/LucasArts/Aspyr.* Submitter VexFlint Submitted 07/21/2026 Category Mods K1R Compatible Yes
-
Version 1.0.0
25 downloads
# KOTOR 1 has a built-in frame limiter — BioWare shipped it switched off **TL;DR:** *Star Wars: Knights of the Old Republic* (2003, Odyssey engine) contains a complete busy-wait frame limiter in its main loop. It is controlled by a single `float` in the executable that defaults to `0.0`, which leaves the limiter dormant, so the game free-runs at uncapped framerate. On high-refresh displays this uncapped rate is what causes the long-standing **movement / "stuck after combat turn" freeze** and related timing bugs, which the community has only ever worked around with external FPS caps or by dropping the monitor to 60 Hz. Writing `60.0` (or any target) to that one float **arms the engine's own limiter** — a native, self-contained fix. No injected code, no wrappers, no driver settings. It's a 4-byte data patch. Verified: on a 144 Hz display with all external caps removed, the patched exe holds a hard 60 FPS and the freeze does not occur. --- ## The bug KOTOR's main loop (`WinMain`, `FUN_004041f0` in a Ghidra auto-analysis of the standard PC exe) is a classic `PeekMessage` real-time loop: ``` while (quit_flag == 0) { read timer (game clock) if no pending message: render scene (delta-time based) SwapBuffers (present) ... optional limiter here ... } ``` Gameplay logic — movement, and the combat round/turn state machine — advances by frame **delta-time**. With no frame cap, delta-times on a fast display become tiny and irregular, and the 2003-era state machine mis-steps: the controlled character's action queue stalls until something rebuilds it (switching party member, or a save/load clears it). That is the freeze players have reported for 20 years. ## The dormant limiter Immediately after `SwapBuffers`, the loop contains this (Ghidra decompilation, lightly annotated): ```c if (DAT_007a3c58 != 0) // a separate, also-off coarse limiter Sleep(DAT_0078d1e8); if (0.0f < cap) { // cap = _DAT_007a3c64 (the frame-cap float) target = 1000.0f / cap; // target frame time // measure elapsed since frame start, then: if (elapsed < target) { do { // busy-spin: re-read the game timer } while (elapsed < target); // hold the frame until target reached } } ``` With real constant values read from the binary: | Symbol | RVA | Value | Meaning | |-------------------|-----------|----------|----------------------------------| | `_DAT_007a3c64` | `0x7a3c64` | **0.0** | frame cap (FPS). **This is it.** | | numerator const | `0x73d6fc` | 1000.0 | `target_ms = 1000.0 / cap` | | zero const | `0x73d700` | 0.0 | used in the `0.0 < cap` gate | | timer scale const | `0x73d708` | 0.001 | raw timer -> milliseconds | The gate is literally **"if cap > 0, limit the frame."** With `cap = 60`, the loop busy-waits until each frame has taken `1000/60 ≈ 16.67 ms`, i.e. it caps at 60 FPS. This is the *same* formula the engine uses elsewhere — the movie-playback path caps to 30 with it — so the mechanism is proven, not speculative. ## Why it's off `_DAT_007a3c64` starts at `0.0`. The **only** code that writes it is a render-setup function (`FUN_004c5880`), and that write is guarded: ```c if (DAT_00832904 != 0) { ... _DAT_007a3c64 = (float)DAT_00832904; // arm the cap } ``` `DAT_00832904` lives in **BSS** — zero-initialized, and it has **no writer anywhere in the binary** (it's a config variable exposed by address, never set by default). So the guard is never true, the cap value is never assigned, and `_DAT_007a3c64` stays `0.0` for the entire session. The limiter is complete, correct, and permanently asleep. Because nothing ever writes the cap during normal play, **patching its initial `.data` value is safe and permanent** — no code path overwrites it. ## The patch Standard PC v1.03 / GOG / "editable" executable: ``` file offset 0x3A3C64 : 00 00 00 00 (float 0.0, limiter off) -> 00 00 70 42 (float 60.0, 60 FPS cap) ``` Other caps if you prefer: `30.0` = `00 00 F0 41`, `72.0` = `00 00 90 42`, `120.0` = `00 00 F0 42`. A Python patcher (`kotor_fpscap_patch.py`) is included; it fingerprints the two `.rdata` constants before writing, backs up the exe, and refuses to touch an executable whose layout it doesn't recognize: ``` python kotor_fpscap_patch.py "path\to\swkotor.exe" 60 ``` ## Verification - 144 Hz display, all external FPS caps and driver "wait for vertical refresh" overrides removed. - Patched exe launched directly. - Framerate held a hard **60**; the movement/turn freeze did not reproduce on transitions that previously triggered it. ## Caveats - **Busy-wait, by design.** The limiter spins a CPU core while pacing each frame (this is BioWare's own implementation — the movie path spins the same way). On a modern machine, one pegged core for a single-threaded 2003 game is harmless, but it is not a `Sleep`-based idle cap. A future refinement could redirect the gate to the engine's own `Sleep(DAT_0078d1e8)` limiter instead. - **Offset is per-exe-layout.** `0x3A3C64` is verified for the standard PC exe. Other builds (Steam-with-different-packing, 4-CD, Mac) may differ; find `_DAT_007a3c64` by locating the `1000.0 / cap` main-loop math. - **Steam "Verify integrity" reverts it** (restores the stock exe). Re-apply after any verify. - Stacks cleanly with the 4GB LARGE_ADDRESS_AWARE patch, UniWS widescreen, and the community mod builds — it only touches one unrelated float. ## Method Static reverse engineering only (Ghidra). Chain: locate timing-API imports (`QueryPerformanceCounter`, `GetTickCount`, `Sleep`) → their callers → the `PeekMessage` message pump → its caller (`WinMain`) → the frame-limiter block in the loop → the cap float and its dormant guard. Full decompiled C of the engine was exported and searched locally to trace the call chain. --- *Reverse-engineered from a legally-owned copy for interoperability and bug-fixing. Not affiliated with BioWare/LucasArts/Aspyr.*