Documentation Index
Fetch the complete documentation index at: https://mintlify.com/danielkrupinski/Osiris/llms.txt
Use this file to discover all available pages before exploring further.
Osiris embeds its entire user interface directly into Counter-Strike 2’s main menu using the game’s built-in Panorama UI system. This approach provides a native-looking interface and avoids the need for external overlays.
Overview
Panorama is Valve’s UI framework used throughout CS2. Osiris hijacks this system to inject custom panels, creating a seamless configuration menu that looks and feels like part of the game.
Architecture
PanoramaGUI Class
The main GUI controller is a template class that manages the entire interface:
// Source/UI/Panorama/PanoramaGUI.h:99
template <typename HookContext>
class PanoramaGUI {
public:
explicit PanoramaGUI(HookContext& hookContext) noexcept
: hookContext{hookContext}
{
}
void init(auto&& mainMenu) noexcept;
void run(UnloadFlag& unloadFlag) const noexcept;
void updateFromConfig() noexcept;
void onUnload() const noexcept;
private:
HookContext& hookContext;
};
Key responsibilities:
- Initialize UI panels in the main menu
- Handle user interactions
- Synchronize with configuration state
- Clean up on unload
Initialization
The GUI is injected into the game’s main menu during initialization:
// Source/UI/Panorama/PanoramaGUI.h:106
void init(auto&& mainMenu) noexcept
{
if (!mainMenu)
return;
// Ensure settings tab is loaded because we use CSS classes from settings
uiEngine().runScript(mainMenu,
"if (!$('#JsSettings')) MainMenu.NavigateToTab('JsSettings', 'settings/settings');");
const auto settings = mainMenu.findChildInLayoutFile("JsSettings");
if (settings)
state().settingsPanelHandle = settings.getHandle();
// Inject the main GUI creation script
uiEngine().runScript(settings, reinterpret_cast<const char*>(
#include "CreateGUI.js"
));
// Add navigation button to main menu
uiEngine().runScript(mainMenu, R"(
(function () {
$('#JsSettings').FindChildInLayoutFile('OsirisMenuTab').SetParent($('#JsMainMenuContent'));
var openMenuButton = $.CreatePanel('RadioButton', $.GetContextPanel().FindChildTraverse('MainMenuNavBarSettings').GetParent(), 'OsirisOpenMenuButton', {
class: "mainmenu-top-navbar__radio-iconbtn",
group: "NavBar",
onactivate: "MainMenu.NavigateToTab('OsirisMenuTab', '');"
});
$.CreatePanel('Image', openMenuButton, '', {
class: "mainmenu-top-navbar__radio-btn__icon",
src: "s2r://panorama/images/icons/ui/bug.vsvg"
});
$.DispatchEvent('Activated', $.GetContextPanel().FindChildTraverse("MainMenuNavBarHome"), 'mouse');
})();
)")
This:
- Ensures the settings tab is loaded (for CSS classes)
- Injects the GUI creation script
- Creates a navigation button with a bug icon
- Integrates into the main menu navbar
JavaScript Integration
The bulk of the UI is defined in embedded JavaScript:
// Source/UI/Panorama/CreateGUI.js (partial)
$.Osiris = (function () {
var activeTab;
var activeSubTab = {};
return {
rootPanel: (function () {
const rootPanel = $.CreatePanel('Panel', $.GetContextPanel(), 'OsirisMenuTab', {
class: "mainmenu-content__container",
useglobalcontext: "true"
});
rootPanel.visible = false;
rootPanel.SetReadyForDisplay(false);
rootPanel.RegisterForReadyEvents(true);
return rootPanel;
})(),
addCommand: function (command, value = '') {
var existingCommands = this.rootPanel.GetAttributeString('cmd', '');
this.rootPanel.SetAttributeString('cmd', existingCommands + command + ' ' + value);
},
navigateToTab: function (tabID) {
if (activeTab === tabID)
return;
if (activeTab) {
var panelToHide = this.rootPanel.FindChildInLayoutFile(activeTab);
panelToHide.RemoveClass('Active');
}
this.rootPanel.FindChildInLayoutFile(tabID + '_button').checked = true;
activeTab = tabID;
var activePanel = this.rootPanel.FindChildInLayoutFile(tabID);
activePanel.AddClass('Active');
activePanel.visible = true;
activePanel.SetReadyForDisplay(true);
}
};
})();
Key features:
- Tab navigation system
- Command queue for C++ communication
- Panel visibility management
- Proper event handling
UI Structure
The interface is organized into tabs:
// Combat Tab
var createCombatNavbar = function () {
var navbar = $.CreatePanel('Panel', $.Osiris.rootPanel.FindChildInLayoutFile('combat'), '', {
class: "content-navbar__tabs content-navbar__tabs--dark content-navbar__tabs--noflow"
});
var sniperRiflesTabButton = $.CreatePanel('RadioButton', centerContainer, 'sniper_rifles_button', {
group: "CombatNavBar",
class: "content-navbar__tabs__btn",
onactivate: "$.Osiris.navigateToSubTab('combat', 'sniper_rifles');"
});
};
// HUD Tab
var hud = createTab('hud');
var bomb = createSection(hud, 'Bomb');
createYesNoDropDown(bomb, "Show Bomb Explosion Countdown And Site", 'hud', 'bomb_timer');
createYesNoDropDown(bomb, "Show Bomb Defuse Countdown", 'hud', 'defusing_alert');
// Visuals Tab with subtabs
var visuals = createVisualsTab();
var playerInfoTab = createSubTab(visuals, 'player_info');
var outlineGlowTab = createSubTab(visuals, 'outline_glow');
var modelGlowTab = createSubTab(visuals, 'model_glow');
// Sound Tab
var sound = createTab('sound');
var playerSoundVisualization = createSection(sound, 'Player Sound Visualization');
Hierarchy:
- Root panel (
OsirisMenuTab)
- Combat tab
- HUD tab
- Bomb section
- Killfeed section
- Time section
- Visuals tab
- Player Info subtab
- Outline Glow subtab
- Model Glow subtab
- Viewmodel subtab
- Sound tab
UI Components
Dropdowns
var createDropDown = function (parent, labelText, section, feature, options) {
var container = $.CreatePanel('Panel', parent, '', {
class: "SettingsMenuDropdownContainer"
});
$.CreatePanel('Label', container, '', {
class: "half-width",
text: labelText
});
var dropdown = $.CreatePanel('CSGOSettingsEnumDropDown', container, feature, {
class: "PopupButton White"
});
for (let i = 0; i < options.length; ++i) {
dropdown.AddOption($.CreatePanel('Label', dropdown, i, {
value: i,
text: options[i]
}));
}
};
Sliders
var createHueSlider = function (parent, name, id, min, max) {
var slider = $.CreatePanel('Slider', sliderContainer, '', {
class: "HorizontalSlider",
style: "width: 200px; vertical-align: center;",
direction: "horizontal"
});
slider.min = min;
slider.max = max;
slider.increment = 1.0;
var textEntry = $.CreatePanel('TextEntry', sliderContainer, id + '_text', {
maxchars: "3",
textmode: "numeric",
style: "width: 75px; margin-left: 10px; text-align: center;"
});
// Color preview panel
$.CreatePanel('Panel', sliderContainer, id + '_color', {
style: "border: 2px solid #000000a0; border-radius: 5px; width: 25px; height: 25px;"
});
}
Preview Panels
var createPlayerModelGlowPreview = function (parent, id, labelId, playerModel, itemId) {
var previewPanel = $.CreatePanel('MapPlayerPreviewPanel', container, id, {
map: "ui/buy_menu",
camera: "cam_loadoutmenu_ct",
"require-composition-layer": true,
playermodel: playerModel,
animgraphcharactermode: "buy-menu",
player: true,
mouse_rotate: false,
"transparent-background": true,
style: "width: 300px; height: 300px;"
});
previewPanel.EquipPlayerWithItem(itemId);
}
C++ to JavaScript Communication
Commands are passed from JavaScript to C++ via panel attributes:
// JavaScript: Queue a command
$.Osiris.addCommand('set', 'visuals/player_outline_glow_blue_hue/220');
// C++: Process commands
// Source/UI/Panorama/PanoramaGUI.h:180
void run(UnloadFlag& unloadFlag) const noexcept
{
auto&& guiPanel = uiEngine().getPanelFromHandle(state().guiPanelHandle);
if (!guiPanel)
return;
const auto cmdSymbol = uiEngine().makeSymbol(0, "cmd");
const auto cmd = guiPanel.getAttributeString(cmdSymbol, "");
PanoramaCommandDispatcher{cmd, unloadFlag, hookContext}();
guiPanel.setAttributeString(cmdSymbol, "");
}
The PanoramaCommandDispatcher parses and executes commands:
set - Update configuration value
unload - Unload Osiris
restore_defaults - Reset configuration
State Synchronization
The GUI maintains its own state separate from the configuration:
// Source/UI/Panorama/PanoramaGuiState.h
struct PanoramaGuiState {
cs2::PanelHandle guiPanelHandle;
cs2::PanelHandle guiButtonHandle;
cs2::PanelHandle settingsPanelHandle;
cs2::PanelHandle modelGlowPreviewPlayerLabelHandleTT;
cs2::PanelHandle modelGlowPreviewPlayerLabelHandleCT;
cs2::PanelHandle viewmodelPreviewPanelHandle;
};
Panel handles are cached to avoid repeated lookups.
Preview System
The Model Glow preview shows live updates:
// Source/UI/Panorama/PanoramaGUI.h:186
auto&& playerModelGlowPreview = hookContext.template make<PlayerModelGlowPreview>();
if (!playerModelGlowPreview.isPreviewPlayerSetTT())
playerModelGlowPreview.setPreviewPlayerTT(
guiPanel.findChildInLayoutFile("ModelGlowPreviewPlayerTT")
.clientPanel().template as<Ui3dPanel>()
.portraitWorld().findPreviewPlayer());
if (!playerModelGlowPreview.isPreviewPlayerSetCT())
playerModelGlowPreview.setPreviewPlayerCT(
guiPanel.findChildInLayoutFile("ModelGlowPreviewPlayerCT")
.clientPanel().template as<Ui3dPanel>()
.portraitWorld().findPreviewPlayer());
playerModelGlowPreview.update();
playerModelGlowPreview.hookPreviewPlayersSceneObjectUpdaters();
This:
- Finds the 3D preview panel
- Gets the preview player entities
- Hooks their scene object updaters
- Applies glow effects in real-time
Slider Handling
Hue sliders with live color preview:
// Source/UI/Panorama/PanoramaGUI.h:160
template <typename ConfigVariable>
void onHueSliderValueChanged(const char* panelId, float value) const
{
const auto newVariableValue = handleHueSlider(
panelId, value,
ConfigVariable::ValueType::kMin,
ConfigVariable::ValueType::kMax,
GET_CONFIG_VAR(ConfigVariable));
hookContext.config().template setVariable<ConfigVariable>(
typename ConfigVariable::ValueType{newVariableValue});
}
[[nodiscard]] color::HueInteger handleHueSlider(
const char* sliderId, float value,
color::HueInteger min, color::HueInteger max,
color::HueInteger current) const noexcept
{
const auto hueIntegral = static_cast<color::HueInteger::UnderlyingType>(value);
if (hueIntegral < min || hueIntegral > max || hueIntegral == current)
return current;
const auto hue = color::HueInteger{hueIntegral};
auto&& hueSlider = getHueSlider(sliderId);
hueSlider.updateTextEntry(hue);
hueSlider.updateColorPreview(hue);
return hue;
}
Features:
- Range validation
- Synchronization between slider and text entry
- Live color preview update
- Direct config modification
Cleanup
Proper cleanup on unload:
// Source/UI/Panorama/PanoramaGUI.h:220
void onUnload() const noexcept
{
uiEngine().deletePanelByHandle(state().guiButtonHandle);
uiEngine().deletePanelByHandle(state().guiPanelHandle);
if (auto&& settingsPanel = uiEngine().getPanelFromHandle(state().settingsPanelHandle))
uiEngine().runScript(settingsPanel, "delete $.Osiris");
}
This removes:
- The navigation button
- The main panel
- The JavaScript
$.Osiris object
Tabs Implementation
Each major tab has its own C++ class:
// Source/UI/Panorama/CombatTab.h
template <typename HookContext>
class CombatTab {
void init(auto&& guiPanel) noexcept;
void updateFromConfig(auto&& mainMenu) noexcept;
};
// Source/UI/Panorama/HudTab.h
template <typename HookContext>
class HudTab {
void init(auto&& guiPanel) noexcept;
void updateFromConfig(auto&& mainMenu) noexcept;
};
// Source/UI/Panorama/VisualsTab.h
template <typename HookContext>
class VisualsTab {
void init(auto&& guiPanel) noexcept;
void updateFromConfig(auto&& mainMenu) noexcept;
};
Each handles:
- Dropdown selection change callbacks
- Config synchronization
- Feature-specific UI logic
Benefits of Panorama Integration
- Native Look: UI matches game’s style perfectly
- No Overlay: Runs inside the game’s UI system
- Performance: Uses game’s rendering pipeline
- Accessibility: Full keyboard/controller support
- Reliability: No external window management
- Undetectable: Appears as game UI to anti-cheat