Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ jobs:
files: |
./artifact-download/VioletWing.exe
prerelease: ${{ github.event.inputs.prerelease }}

- name: Send Telegram Notification
if: success()
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/copilot-setup-steps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ on:
- .github/workflows/copilot-setup-steps.yml

jobs:
# This name is required Copilot will not pick up the job under any other name.
# This name is required - Copilot will not pick up the job under any other name.
copilot-setup-steps:
# NOTE: VioletWing depends on pywin32 and pyMeow, which are Windows-only at runtime.
# Ubuntu is used here so Copilot gets a fast, standard environment for code analysis
Expand Down
4 changes: 2 additions & 2 deletions classes/bunnyhop.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,11 @@ def stop(self) -> None:
logger.debug("Bunnyhop stopped.")

def _init_address(self) -> bool:
if self.memory_manager.dwForceJump is None:
if self.memory_manager.jump is None:
Logger.error_code(EC.E3001)
return False
try:
self.force_jump_address = self.memory_manager.client_base + self.memory_manager.dwForceJump
self.force_jump_address = self.memory_manager.client_base + self.memory_manager.jump
return True
except Exception as exc:
logger.error("Error setting force-jump address: %s", exc)
Expand Down
45 changes: 25 additions & 20 deletions classes/client_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,24 +55,23 @@ def _stop_feature(self, feature_name: str, feature_obj) -> bool:
return False

def start_client(self) -> None:
if not self.main_window.offsets:
if self.main_window._offsets_fetching:
self.main_window.update_client_status("Fetching offsets…", "#f59e0b")
return
self.main_window.update_client_status("Fetching offsets…", "#f59e0b")
self.main_window.fetch_offsets_async(on_success=self.start_client)
return

# cs2-dumper needs CS2 running before it can dump -- check upfront so
# the user sees a clear error rather than a cryptic subprocess failure.
if not is_game_running():
messagebox.showerror(
"Game Not Running",
"Could not find cs2.exe. Make sure the game is running.",
"Could not find cs2.exe. Launch CS2 before starting the client.",
)
return

# Only initialize if we don't already have a valid memory handle.
# Re-initializing while features are running replaces the shared pymem
# handle underneath active threads.
if not self.main_window.offsets:
if self.main_window._offsets_fetching:
self.main_window.update_client_status("Dumping offsets…", "#f59e0b")
return
self.main_window.update_client_status("Dumping offsets…", "#f59e0b")
self.main_window.fetch_offsets_async(on_success=self.start_client)
return

if not self.memory_manager.is_initialized:
if not self.memory_manager.initialize():
messagebox.showerror(
Expand All @@ -88,7 +87,7 @@ def start_client(self) -> None:
if not config["General"].get(config_key, False):
continue
name = feature_data["name"]
obj = feature_data["instance"]
obj = feature_data["instance"]
if getattr(obj, "is_running", False):
logger.info("%s is already running.", name)
any_started = True
Expand All @@ -113,6 +112,16 @@ def stop_client(self) -> None:
# Reset the memory handle so the next start_client gets a fresh attach.
self.memory_manager.reset()

# Always clear the offset cache on Stop so the next Start re-dumps
# from live memory -- guarantees fresh offsets after a CS2 update.
self.main_window.offsets = {}
self.main_window.client_data = {}
self.main_window.buttons_data = {}
self.main_window.memory_manager.offsets = {}
self.main_window.memory_manager.client_data = {}
self.main_window.memory_manager.buttons_data = {}
logger.debug("Offset cache cleared -- will re-dump on next Start.")

if stopped_any:
self.main_window.update_client_status("Inactive", "#ef4444")
else:
Expand All @@ -121,19 +130,16 @@ def stop_client(self) -> None:
def apply_feature_state_changes(self, old_config: dict, new_config: dict) -> None:
"""Stop features whose enabled flag was turned off.

Intentionally does NOT start features that is exclusively the job of
Intentionally does NOT start features - that is exclusively the job of
start_client(). Toggling a checkbox in General Settings is a config
change, not a start command.
"""
for key, feature_data in self.features.items():
old_on = old_config["General"].get(key, False)
new_on = new_config["General"].get(key, False)
old_on = old_config["General"].get(key, False)
new_on = new_config["General"].get(key, False)
running = getattr(feature_data["instance"], "is_running", False)

if old_on == new_on:
continue

# Only act on features that were turned OFF while running.
if not new_on and running:
self._stop_feature(feature_data["name"], feature_data["instance"])

Expand All @@ -151,6 +157,5 @@ def update_running_feature_configs(self, new_config: dict) -> None:
instance.update_config(new_config)
logger.debug("Config updated for %s.", feature_data["name"])
any_running = True

status, color = ("Active", "#22c55e") if any_running else ("Inactive", "#ef4444")
self.main_window.update_client_status(status, color)
Loading
Loading