Module: Lich

Included in:
DragonRealms, Gemstone
Defined in:
documented/lich.rb,
documented/games.rb,
documented/stash.rb,
documented/update.rb,
documented/gemcheck.rb,
documented/messaging.rb,
documented/util/opts.rb,
documented/util/util.rb,
documented/common/gtk.rb,
documented/common/hmr.rb,
documented/common/log.rb,
documented/magic-info.rb,
documented/common/vars.rb,
documented/gemstone/sk.rb,
documented/global_defs.rb,
documented/common/spell.rb,
documented/common/buffer.rb,
documented/common/script.rb,
documented/gemstone/disk.rb,
documented/gemstone/gift.rb,
documented/gemstone/psms.rb,
documented/common/account.rb,
documented/common/gameobj.rb,
documented/common/pipe_io.rb,
documented/gemstone/claim.rb,
documented/gemstone/group.rb,
documented/gemstone/scars.rb,
documented/main/help_text.rb,
documented/attributes/char.rb,
documented/common/db_store.rb,
documented/common/frontend.rb,
documented/common/frontend.rb,
documented/common/postload.rb,
documented/common/settings.rb,
documented/common/throttle.rb,
documented/common/uservars.rb,
documented/common/watchfor.rb,
documented/gemstone/bounty.rb,
documented/gemstone/wounds.rb,
documented/main/early_exit.rb,
documented/attributes/stats.rb,
documented/bundler_recovery.rb,
documented/common/gui/state.rb,
documented/common/gui_login.rb,
documented/common/inventory.rb,
documented/common/watchable.rb,
documented/common/xmlparser.rb,
documented/gemstone/effects.rb,
documented/gemstone/infomon.rb,
documented/gemstone/injured.rb,
documented/gemstone/society.rb,
documented/util/deep_freeze.rb,
documented/attributes/skills.rb,
documented/attributes/spells.rb,
documented/common/arg_parser.rb,
documented/common/gameloader.rb,
documented/common/map/map_dr.rb,
documented/common/map/map_gs.rb,
documented/gemstone/creature.rb,
documented/gemstone/currency.rb,
documented/gemstone/stowlist.rb,
documented/main/argv_options.rb,
documented/util/textstripper.rb,
documented/common/setup_files.rb,
documented/gemstone/armaments.rb,
documented/gemstone/critranks.rb,
documented/gemstone/overwatch.rb,
documented/gemstone/psms/cman.rb,
documented/gemstone/psms/feat.rb,
documented/gemstone/readylist.rb,
documented/main/startup_theme.rb,
documented/api/active_sessions.rb,
documented/common/limitedarray.rb,
documented/common/map/map_base.rb,
documented/common/script_death.rb,
documented/common/sharedbuffer.rb,
documented/common/shutdown_log.rb,
documented/common/upstreamhook.rb,
documented/common/xml_entities.rb,
documented/dependency_recovery.rb,
documented/gemstone/experience.rb,
documented/gemstone/psms/armor.rb,
documented/gemstone/spellranks.rb,
documented/util/gtk_compaction.rb,
documented/util/memoryreleaser.rb,
documented/attributes/enhancive.rb,
documented/attributes/resources.rb,
documented/attributes/spellsong.rb,
documented/common/feature_flags.rb,
documented/common/gui/utilities.rb,
documented/common/hook_registry.rb,
documented/gemstone/bounty/task.rb,
documented/gemstone/infomon/cli.rb,
documented/gemstone/psms/shield.rb,
documented/gemstone/psms/warcry.rb,
documented/gemstone/psms/weapon.rb,
documented/common/downstreamhook.rb,
documented/common/gui/components.rb,
documented/dragonrealms/creature.rb,
documented/gemstone/psms/qstrike.rb,
documented/common/gui/theme_utils.rb,
documented/common/ruby_executable.rb,
documented/common/shutdown_intent.rb,
documented/dragonrealms/drinfomon.rb,
documented/gemstone/bounty/parser.rb,
documented/gemstone/combat/parser.rb,
documented/gemstone/infomon/cache.rb,
documented/main/arg_normalization.rb,
documented/main/reconnect_command.rb,
documented/common/credential_scrub.rb,
documented/common/frontend_locator.rb,
documented/common/orderly_shutdown.rb,
documented/common/process_launcher.rb,
documented/common/session_launcher.rb,
documented/common/socket_read_hook.rb,
documented/gemstone/combat/tracker.rb,
documented/gemstone/infomon/parser.rb,
documented/gemstone/infomon/status.rb,
documented/gemstone/psms/ascension.rb,
documented/main/user_exit_dispatch.rb,
documented/common/frontend_launcher.rb,
documented/common/frontend_settings.rb,
documented/common/gui/accessibility.rb,
documented/common/gui/conversion_ui.rb,
documented/common/session_lifecycle.rb,
documented/common/shutdown_watchdog.rb,
documented/gemstone/combat/defs/ucs.rb,
documented/gemstone/combat/recorder.rb,
documented/main/bind_address_option.rb,
documented/common/authentication/cli.rb,
documented/common/authentication/gui.rb,
documented/common/bind_host_resolver.rb,
documented/common/cli/cli_conversion.rb,
documented/common/gui/game_selection.rb,
documented/common/saga_launch_policy.rb,
documented/common/saga_managed_login.rb,
documented/common/socketconfigurator.rb,
documented/common/update/file_writer.rb,
documented/common/update/script_sync.rb,
documented/gemstone/combat/observers.rb,
documented/gemstone/combat/processor.rb,
documented/common/gui/account_manager.rb,
documented/common/gui/login_tab_utils.rb,
documented/common/gui/password_change.rb,
documented/common/gui/password_cipher.rb,
documented/common/gui/saved_login_tab.rb,
documented/common/gui/window_settings.rb,
documented/common/reusable_tcp_server.rb,
documented/common/update/custom_repos.rb,
documented/common/update/file_updater.rb,
documented/gemstone/infomon/xmlparser.rb,
documented/common/gui/manual_login_tab.rb,
documented/common/gui/password_manager.rb,
documented/common/gui/tab_communicator.rb,
documented/common/settings_transformer.rb,
documented/common/shutdown_coordinator.rb,
documented/common/update/github_client.rb,
documented/common/windows_command_line.rb,
documented/dragonrealms/commons/common.rb,
documented/gemstone/combat/defs/damage.rb,
documented/gemstone/combat/defs/flares.rb,
documented/gemstone/combat/defs/spells.rb,
documented/common/class_exts/stringproc.rb,
documented/common/cli/cli_orchestration.rb,
documented/common/gui/favorites_manager.rb,
documented/common/gui/frontend_selector.rb,
documented/common/gui/parameter_objects.rb,
documented/common/saga_managed_launcher.rb,
documented/common/settings/charsettings.rb,
documented/common/settings/gamesettings.rb,
documented/common/shutdown_script_drain.rb,
documented/gemstone/combat/defs/attacks.rb,
documented/gemstone/infomon/activespell.rb,
documented/internal_api/active_sessions.rb,
documented/common/authentication/eaccess.rb,
documented/common/creature/creature_base.rb,
documented/common/gui/account_manager_ui.rb,
documented/common/update/status_reporter.rb,
documented/common/update/tracked_scripts.rb,
documented/dragonrealms/commons/slackbot.rb,
documented/dragonrealms/drinfomon/drdefs.rb,
documented/dragonrealms/drinfomon/drroom.rb,
documented/dragonrealms/drinfomon/events.rb,
documented/gemstone/combat/defs/assaults.rb,
documented/gemstone/combat/defs/outcomes.rb,
documented/gemstone/combat/defs/statuses.rb,
documented/main/detachable_client_notice.rb,
documented/main/detachable_client_target.rb,
documented/common/client_input_dispatcher.rb,
documented/common/settings/path_navigator.rb,
documented/common/settings/settings_proxy.rb,
documented/common/update/branch_installer.rb,
documented/common/update/channel_resolver.rb,
documented/common/update/snapshot_manager.rb,
documented/dragonrealms/drinfomon/drskill.rb,
documented/dragonrealms/drinfomon/drstats.rb,
documented/dragonrealms/drinfomon/startup.rb,
documented/gemstone/armaments/armor_stats.rb,
documented/gemstone/combat/defs/sequences.rb,
documented/common/authentication/web_login.rb,
documented/common/cli/cli_option_validator.rb,
documented/common/cli/cli_options_registry.rb,
documented/common/gui/frontend_manager_tab.rb,
documented/common/update/release_installer.rb,
documented/dragonrealms/drinfomon/drparser.rb,
documented/dragonrealms/drinfomon/drspells.rb,
documented/gemstone/armaments/shield_stats.rb,
documented/gemstone/armaments/weapon_stats.rb,
documented/gemstone/combat/async_processor.rb,
documented/common/cli/active_sessions_query.rb,
documented/common/settings/database_adapter.rb,
documented/dragonrealms/drinfomon/drbanking.rb,
documented/gemstone/societies/order_of_voln.rb,
documented/common/authentication/entry_store.rb,
documented/common/authentication/launch_data.rb,
documented/common/detachable_client_registry.rb,
documented/common/gui/encryption_mode_change.rb,
documented/common/gui/master_password_change.rb,
documented/common/gui/master_password_prompt.rb,
documented/common/settings/instance_settings.rb,
documented/common/settings/sessions_settings.rb,
documented/common/shutdown_result_predicates.rb,
documented/dragonrealms/commons/common-items.rb,
documented/dragonrealms/commons/common-money.rb,
documented/dragonrealms/commons/equipmanager.rb,
documented/dragonrealms/custom_substitutions.rb,
documented/gemstone/combat/defs/pattern_gate.rb,
documented/gemstone/combat/defs/spell_losses.rb,
documented/common/authentication/cli_password.rb,
documented/common/gui/master_password_manager.rb,
documented/dragonrealms/commons/common-arcana.rb,
documented/dragonrealms/commons/common-travel.rb,
documented/dragonrealms/drinfomon/drvariables.rb,
documented/common/authentication/authenticator.rb,
documented/common/authentication/launch_result.rb,
documented/common/authentication/login_helpers.rb,
documented/common/best_effort_shutdown_cleanup.rb,
documented/common/gui/manual_frontend_selector.rb,
documented/dragonrealms/commons/common-healing.rb,
documented/dragonrealms/commons/common-theurgy.rb,
documented/dragonrealms/drinfomon/drexpmonitor.rb,
documented/gemstone/societies/council_of_light.rb,
documented/internal_api/active_sessions/client.rb,
documented/internal_api/active_sessions/server.rb,
documented/common/class_exts/synchronizedsocket.rb,
documented/common/gui/master_password_prompt_ui.rb,
documented/dragonrealms/commons/common-crafting.rb,
documented/dragonrealms/commons/common-moonmage.rb,
documented/common/cli/cli_encryption_mode_change.rb,
documented/common/gui/windows_credential_manager.rb,
documented/dragonrealms/commons/common-summoning.rb,
documented/gemstone/armaments/weapon_stats_blunt.rb,
documented/gemstone/armaments/weapon_stats_edged.rb,
documented/internal_api/active_sessions/registry.rb,
documented/dragonrealms/commons/common-money-data.rb,
documented/dragonrealms/commons/common-validation.rb,
documented/gemstone/armaments/weapon_stats_hybrid.rb,
documented/gemstone/armaments/weapon_stats_ranged.rb,
documented/gemstone/armaments/weapon_stats_thrown.rb,
documented/internal_api/active_sessions/lifecycle.rb,
documented/dragonrealms/dependency/settings_config.rb,
documented/gemstone/armaments/weapon_stats_natural.rb,
documented/gemstone/armaments/weapon_stats_polearm.rb,
documented/gemstone/armaments/weapon_stats_unarmed.rb,
documented/gemstone/societies/guardians_of_sunfist.rb,
documented/common/settings/session_database_adapter.rb,
documented/dragonrealms/commons/common-healing-data.rb,
documented/gemstone/armaments/weapon_stats_brawling.rb,
documented/gemstone/armaments/weapon_stats_runestave.rb,
documented/gemstone/armaments/weapon_stats_two_handed.rb

Overview

Namespace for the Lich 5 scripting engine and its public API.

Defined Under Namespace

Modules: API, Claim, Common, DragonRealms, GameBase, GemCheck, Gemstone, InternalAPI, Main, Messaging, Resources, Stash, Unknown, Util Classes: BundlerRecovery, DependencyRecovery

Constant Summary collapse

DEFAULT_SQLITE_BUSY_TIMEOUT_MS =

Default timeout in milliseconds for SQLite3 busy waits when the database is locked.

Returns:

  • (Integer) —

    5000 milliseconds

5000
ROOMID_LOCATIONS =

The valid values for Lich.display_roomid_location.

%w[title line both].freeze
MAX_DEBUG_LOGS_DEFAULT =

Default number of debug log files to retain when no user preference is set.

Returns:

  • (Integer) —

    the built-in retention limit

20
MAX_DEBUG_LOGS_MINIMUM =

Minimum allowed value for max_debug_logs to prevent accidental deletion of all log files.

Returns:

  • (Integer) —

    the floor value enforced by the setter

1
@@hosts_file =
nil
@@lich_db =
nil
@@last_warn_deprecated =
0
@@deprecated_log =
[]
@@display_lichid =

settings

nil
@@display_uid =

boolean

nil
@@display_exits =

boolean

nil
@@display_stringprocs =

boolean

nil
nil
@@display_room_mono =

boolean

nil
@@display_expgains =

boolean

nil
@@display_roomid_location =

boolean (DragonRealms only)

nil
@@hide_uid_flag =

string (DragonRealms only): "title" | "line" | "both"

nil
@@track_autosort_state =

boolean

nil
@@track_dark_mode =

boolean

nil
@@track_layout_state =

boolean

nil
@@track_persistent_launcher_mode =

boolean

nil
@@debug_messaging =

boolean

nil
@@max_debug_logs =

boolean

nil

Class Method Summary collapse

Class Method Details

.break_game_host_port(gamehost, gameport) ⇒ Array<(String, Integer)>

Note:

Maps: storm.gs4.game.play.net:10124 and storm.gs4.game.play.net:10024 and dr.simutronics.net:11024 to their original aliases

Converts current game server hostnames and ports back to their deprecated equivalents (inverse of fix_game_host_port).

Parameters:

  • gamehost (String) —

    the game hostname

  • gameport (Integer) —

    the game port

Returns:

  • (Array<(String, Integer)>) —

    the legacy [hostname, port] pair



956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
# File 'documented/lich.rb', line 956

def self.break_game_host_port(gamehost, gameport)
  if (gamehost == 'storm.gs4.game.play.net') and (gameport.to_i == 10324)
    gamehost = 'gs4.simutronics.net'
    gameport = 10321
  elsif (gamehost == 'storm.gs4.game.play.net') and (gameport.to_i == 10124)
    gamehost = 'gs-plat.simutronics.net'
    gameport = 10121
  elsif (gamehost == 'storm.gs4.game.play.net') and (gameport.to_i == 10024)
    gamehost = 'gs3.simutronics.net'
    gameport = 4900
  elsif (gamehost == 'dr.simutronics.net') and (gameport.to_i == 11024)
    gamehost = 'prime.dr.game.play.net'
    gameport = 4901
  end
  [gamehost, gameport]
end

.class_eval(*_a) ⇒ nil

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Stub implementation that prevents dynamic class evaluation.

Returns:

  • (nil) —

    always nil



328
# File 'documented/lich.rb', line 328

def self.class_eval(*_a);         nil; end

.class_variable_get(*_a) ⇒ nil

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Stub implementation that prevents access to class variables.

Returns:

  • (nil) —

    always nil



322
# File 'documented/lich.rb', line 322

def self.class_variable_get(*_a); nil; end

.cleanup_debug_logs(temp_dir) ⇒ void

This method returns an undefined value.

Removes old debug log files from temp_dir, keeping at most max_debug_logs files. Files are sorted lexicographically by name (which encodes a timestamp), so the most recent files are retained.

This method is called once during Lich startup (in init.rb) after the database has been initialized.

Parameters:

  • temp_dir (String) —

    the directory containing debug log files

See Also:



1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
# File 'documented/lich.rb', line 1560

def self.cleanup_debug_logs(temp_dir)
  pattern = /^debug(?:-\d+)+\.log$/
  candidates = Dir.entries(temp_dir).select { |fn| fn.match?(pattern) }
  limit = Lich.max_debug_logs
  return if candidates.length <= limit

  candidates.sort.reverse[limit..-1].each do |old_file|
    begin
      File.delete(File.join(temp_dir, old_file))
    rescue
      Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
    end
  end
end

.configure_sqlite_connection(db) ⇒ SQLite3::Database

Note:

If the database object does not respond to busy_timeout, it is returned unchanged

Configures an SQLite3 database connection with the busy timeout setting.

Parameters:

  • db (SQLite3::Database) —

    an SQLite database connection object

Returns:

  • (SQLite3::Database) —

    the configured database object



22
23
24
25
# File 'documented/lich.rb', line 22

def self.configure_sqlite_connection(db)
  db.busy_timeout(sqlite_busy_timeout_ms) if db.respond_to?(:busy_timeout)
  db
end

.core_updated_with_lich_version ⇒ String

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Note:

Retries on SQLite3::BusyException

Retrieves the Lich version string recorded at the last core update.

Returns:

  • (String) —

    the version string, or empty string if not set



1081
1082
1083
1084
1085
1086
1087
1088
1089
# File 'documented/lich.rb', line 1081

def self.core_updated_with_lich_version
  begin
    val = Lich.db.get_first_value("SELECT value FROM lich_settings WHERE name='core_updated_with_lich_version';")
  rescue SQLite3::BusyException
    sleep 0.1
    retry
  end
  return val.to_s
end

.core_updated_with_lich_version=(val) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Note:

Retries on SQLite3::BusyException

This method returns an undefined value.

Records the Lich version at core update time.

Parameters:

  • val (String) —

    the version string to store



1097
1098
1099
1100
1101
1102
1103
1104
# File 'documented/lich.rb', line 1097

def self.core_updated_with_lich_version=(val)
  begin
    Lich.db.execute("INSERT OR REPLACE INTO lich_settings(name,value) values('core_updated_with_lich_version',?);", [val.to_s.encode('UTF-8')])
  rescue SQLite3::BusyException
    sleep 0.1
    retry
  end
end

.db ⇒ SQLite3::Database

Note:

Connection is cached in @@lich_db; the database file is created if it does not exist

Returns the primary SQLite3 database connection, creating it on first access.

Returns:

  • (SQLite3::Database) —

    the lazily-initialized database object at #DATA_DIR/lich.db3



267
268
269
# File 'documented/lich.rb', line 267

def self.db
  @@lich_db ||= open_sqlite_db("#{DATA_DIR}/lich.db3")
end

.db_maint_due?(months = 6) ⇒ Boolean

Note:

Returns true on parsing errors to allow maintenance to attempt

Checks whether database maintenance is due based on elapsed time since last run.

Parameters:

  • months (Integer) (defaults to: 6) —

    the interval in months; maintenance is due if last run was more than this long ago

Returns:

  • (Boolean) —

    true if maintenance has never run, the timestamp is empty/invalid, or the interval has elapsed



97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'documented/lich.rb', line 97

def self.db_maint_due?(months = 6)
  last = Lich.db_maint_last_at
  return true if last.nil? || last.empty?
  begin
    cutoff = Time.now.utc - (months * 30 * 24 * 60 * 60)
    last_t = Time.parse(last) rescue nil
    return true if last_t.nil?
    last_t < cutoff
  rescue => e
    Lich.log "db_maint_due? parse error: #{e}"
    true
  end
end

.db_maint_last_at ⇒ String?

Note:

Retries on SQLite3::BusyException; returns nil on other errors

Retrieves the timestamp of the last database maintenance operation from lich_settings.

Returns:

  • (String, nil) —

    ISO 8601 UTC timestamp string of the last maintenance, or nil if never run



60
61
62
63
64
65
66
67
68
69
70
71
# File 'documented/lich.rb', line 60

def self.db_maint_last_at
  ts = nil
  begin
    ts = Lich.db.get_first_value("SELECT value FROM lich_settings WHERE name='db_maint_last_at';")
  rescue SQLite3::BusyException
    sleep 0.1
    retry
  rescue => e
    Lich.log "db_maint_last_at error: #{e}"
  end
  ts
end

.db_maint_lock_path ⇒ Object

--- Minimal DB maintenance helpers (simple + safe) --- Stores last maintenance timestamp and summary in lich_settings. Uses an advisory OS file lock so only one Lich instance attempts VACUUM.



52
53
54
# File 'documented/lich.rb', line 52

def self.db_maint_lock_path
  File.join(DATA_DIR, 'lich.db3.maint.lock')
end

.db_maint_set!(iso_utc, note = '') ⇒ void

Note:

Retries on SQLite3::BusyException; creates lich_settings table if needed

This method returns an undefined value.

Records a database maintenance completion timestamp and optional note in lich_settings.

Parameters:

  • iso_utc (String) —

    ISO 8601 UTC timestamp of maintenance completion

  • note (String) (defaults to: '') —

    optional summary of maintenance activity (e.g., "VACUUM ok pages 100->80")



79
80
81
82
83
84
85
86
87
88
89
90
# File 'documented/lich.rb', line 79

def self.db_maint_set!(iso_utc, note = '')
  begin
    Lich.db.execute("CREATE TABLE IF NOT EXISTS lich_settings (name TEXT NOT NULL, value TEXT, PRIMARY KEY(name));")
    Lich.db.execute("INSERT OR REPLACE INTO lich_settings(name, value) VALUES('db_maint_last_at', ?);", [iso_utc])
    Lich.db.execute("INSERT OR REPLACE INTO lich_settings(name, value) VALUES('db_maint_last_note', ?);", [note.to_s.encode('UTF-8')])
  rescue SQLite3::BusyException
    sleep 0.1
    retry
  rescue => e
    Lich.log "db_maint_set! error: #{e}"
  end
end

.db_mutex ⇒ Mutex

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns the module-level mutex used to serialize database access.

Returns:

  • (Mutex) —

    the database access mutex



200
201
202
# File 'documented/lich.rb', line 200

def self.db_mutex
  @@db_mutex
end

.db_vacuum_if_due!(months: 6, lock_timeout_s: 0.5) ⇒ Object

Try VACUUM if due. If the DB is busy (another process), skip and try next run.



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
163
164
165
166
167
168
169
170
# File 'documented/lich.rb', line 112

def self.db_vacuum_if_due!(months: 6, lock_timeout_s: 0.5)
  return :skipped_recent unless Lich.db_maint_due?(months)

  lock_path = Lich.db_maint_lock_path
  File.open(lock_path, File::RDWR | File::CREAT, 0o644) do |f|
    start = Time.now
    got = false
    begin
      got = f.flock(File::LOCK_EX | File::LOCK_NB)
    rescue => e
      Lich.log "db_maint flock error: #{e}"
    end
    unless got
      while (Time.now - start) < lock_timeout_s && !got
        sleep 0.1
        begin
          got = f.flock(File::LOCK_EX | File::LOCK_NB)
        rescue SystemCallError, IOError
          # Transient error acquiring lock on some filesystems; back off and retry.
          sleep 0.05
        end
      end
    end
    return :skipped_lock_held unless got

    begin
      page_count_before     = Lich.db.get_first_value('PRAGMA page_count;').to_i
      freelist_count_before = Lich.db.get_first_value('PRAGMA freelist_count;').to_i
    rescue SQLite3::BusyException
      Lich.log "db_maint: busy reading stats; skipping"
      return :skipped_busy
    end

    begin
      mode = Lich.db.get_first_value('PRAGMA journal_mode;')
      if mode && mode.to_s.strip.upcase == 'WAL'
        Lich.db.execute('PRAGMA wal_checkpoint(TRUNCATE);')
      end
    rescue SQLite3::SQLException => e
      Lich.log "db_maint: checkpoint skipped (#{e.class}: #{e.message})"
    end

    begin
      Lich.db.execute('VACUUM;')
    rescue SQLite3::BusyException => e
      Lich.log "db_maint: VACUUM busy; skipping (#{e.message})"
      return :skipped_busy
    rescue => e
      Lich.log "db_maint: VACUUM error: #{e}"
      return :error
    end

    page_count_after     = Lich.db.get_first_value('PRAGMA page_count;').to_i rescue 0
    freelist_count_after = Lich.db.get_first_value('PRAGMA freelist_count;').to_i rescue 0
    note = "VACUUM ok pages #{page_count_before}->#{page_count_after}, free #{freelist_count_before}->#{freelist_count_after}"
    Lich.db_maint_set!(Time.now.utc.iso8601, note)
    :vacuum_ok
  end
end

.debug_messaging ⇒ Boolean

Note:

Lazily evaluates from database on first call and caches in @@debug_messaging

Returns the debug messaging toggle state, lazily loaded from lich_settings.

Returns:

  • (Boolean) —

    true if debug messaging is enabled, false otherwise



979
980
981
982
983
984
985
986
987
988
989
990
991
# File 'documented/lich.rb', line 979

def self.debug_messaging
  if @@debug_messaging.nil?
    begin
      val = Lich.db.get_first_value("SELECT value FROM lich_settings WHERE name='debug_messaging';")
    rescue SQLite3::BusyException
      sleep 0.1
      retry
    end
    @@debug_messaging = (val.to_s =~ /on|true|yes/ ? true : false)
    Lich.debug_messaging = @@debug_messaging
  end
  return @@debug_messaging
end

.debug_messaging=(val) ⇒ void

Note:

Retries on SQLite3::BusyException

This method returns an undefined value.

Sets and persists the debug messaging toggle state.

Parameters:

  • val (String, Boolean) —

    truthy values are "on", "true", or "yes" (case-insensitive)



998
999
1000
1001
1002
1003
1004
1005
1006
# File 'documented/lich.rb', line 998

def self.debug_messaging=(val)
  @@debug_messaging = (val.to_s =~ /on|true|yes/ ? true : false)
  begin
    Lich.db.execute("INSERT OR REPLACE INTO lich_settings(name,value) values('debug_messaging',?);", [@@debug_messaging.to_s.encode('UTF-8')])
  rescue SQLite3::BusyException
    sleep 0.1
    retry
  end
end

.deprecated(old_object = '', new_object = '', script_location = "#{Script.current.name || 'unknown'}", debug_log: true, fe_log: false, limit_log: true) ⇒ void

Note:

Deduplication tracks messages in @@deprecated_log; use show_deprecated_log to display accumulated notices

This method returns an undefined value.

Records a deprecation notice for an old API call and optionally logs it.

Parameters:

  • old_object (String) (defaults to: '') —

    the deprecated call or object name

  • new_object (String) (defaults to: '') —

    the recommended replacement

  • script_location (String) (defaults to: "#{Script.current.name || 'unknown'}") —

    the script name or location using the deprecated API

  • debug_log (Boolean) (defaults to: true) —

    whether to log to stderr (default true)

  • fe_log (Boolean) (defaults to: false) —

    whether to send to frontend via messaging (default false)

  • limit_log (Boolean) (defaults to: true) —

    whether to deduplicate messages in the log (default true)



355
356
357
358
359
360
361
# File 'documented/lich.rb', line 355

def self.deprecated(old_object = '', new_object = '', script_location = "#{Script.current.name || 'unknown'}", debug_log: true, fe_log: false, limit_log: true)
  msg = "Deprecated call to #{old_object} used in #{script_location}. Please change to #{new_object} instead!"
  return if limit_log && @@deprecated_log.include?(msg)
  Lich.log(msg) if debug_log
  Lich::Messaging.msg("bold", msg) if fe_log
  @@deprecated_log.push(msg) unless @@deprecated_log.include?(msg)
end

.display_exits ⇒ Boolean?

Note:

Default: false

Note:

Lazily loaded from lich_settings and cached

Returns the room exits display toggle.

Returns:

  • (Boolean, nil) —

    true if room exits should be displayed, nil until a game is identified



1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
# File 'documented/lich.rb', line 1188

def self.display_exits
  if @@display_exits.nil?
    begin
      val = Lich.db.get_first_value("SELECT value FROM lich_settings WHERE name='display_exits';")
    rescue SQLite3::BusyException
      sleep 0.1
      retry
    end
    val = false if val.nil? and XMLData.game != ""; # default false
    @@display_exits = (val.to_s =~ /on|true|yes/ ? true : false) if !val.nil?;
  end
  return @@display_exits
end

.display_exits=(val) ⇒ void

Note:

Retries on SQLite3::BusyException

This method returns an undefined value.

Sets and persists the room exits display toggle.

Parameters:

  • val (String, Boolean) —

    truthy values are "on", "true", or "yes" (case-insensitive)



1207
1208
1209
1210
1211
1212
1213
1214
1215
# File 'documented/lich.rb', line 1207

def self.display_exits=(val)
  @@display_exits = (val.to_s =~ /on|true|yes/ ? true : false)
  begin
    Lich.db.execute("INSERT OR REPLACE INTO lich_settings(name,value) values('display_exits',?);", [@@display_exits.to_s.encode('UTF-8')])
  rescue SQLite3::BusyException
    sleep 0.1
    retry
  end
end

.display_expgains ⇒ Boolean?

Note:

Default: true for non-Genie frontends, false for Genie (which has built-in exp tracking)

Note:

Lazily loaded from lich_settings and cached

Returns the experience gains display toggle.

Returns:

  • (Boolean, nil) —

    true if experience gains should be displayed, nil until a game is identified



1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
# File 'documented/lich.rb', line 1331

def self.display_expgains
  if @@display_expgains.nil?
    begin
      val = Lich.db.get_first_value("SELECT value FROM lich_settings WHERE name='display_expgains';")
    rescue SQLite3::BusyException
      sleep 0.1
      retry
    end
    # Default to true for non-Genie frontends (Genie has built-in exp tracking)
    # Once explicitly set, the persisted value takes precedence
    if val.nil? && XMLData.game != ""
      val = ($frontend == 'genie') ? 'false' : 'true'
    end
    @@display_expgains = (val.to_s =~ /on|true|yes/ ? true : false) if !val.nil?
  end
  @@display_expgains
end

.display_expgains=(val) ⇒ void

Note:

Retries on SQLite3::BusyException

This method returns an undefined value.

Sets and persists the experience gains display toggle.

Parameters:

  • val (String, Boolean) —

    truthy values are "on", "true", or "yes" (case-insensitive)



1354
1355
1356
1357
1358
1359
1360
1361
1362
# File 'documented/lich.rb', line 1354

def self.display_expgains=(val)
  @@display_expgains = (val.to_s =~ /on|true|yes/ ? true : false)
  begin
    Lich.db.execute("INSERT OR REPLACE INTO lich_settings(name,value) values('display_expgains',?);", [@@display_expgains.to_s.encode('UTF-8')])
  rescue SQLite3::BusyException
    sleep 0.1
    retry
  end
end

.display_lichid ⇒ Boolean?

Note:

Default: true for GemStone, false for DragonRealms

Note:

Lazily loaded from lich_settings and cached

Returns the room ID display toggle, defaulting based on game type.

Returns:

  • (Boolean, nil) —

    true if room IDs should be displayed, nil until a game is identified



1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
# File 'documented/lich.rb', line 1013

def self.display_lichid
  if @@display_lichid.nil?
    begin
      val = Lich.db.get_first_value("SELECT value FROM lich_settings WHERE name='display_lichid';")
    rescue SQLite3::BusyException
      sleep 0.1
      retry
    end
    val = (XMLData.game =~ /^GS/ ? true : false) if val.nil? and XMLData.game != ""; # default false if DR, otherwise default true
    @@display_lichid = (val.to_s =~ /on|true|yes/ ? true : false) if !val.nil?;
  end
  return @@display_lichid
end

.display_lichid=(val) ⇒ void

Note:

Retries on SQLite3::BusyException

This method returns an undefined value.

Sets and persists the room ID display toggle.

Parameters:

  • val (String, Boolean) —

    truthy values are "on", "true", or "yes" (case-insensitive)



1032
1033
1034
1035
1036
1037
1038
1039
1040
# File 'documented/lich.rb', line 1032

def self.display_lichid=(val)
  @@display_lichid = (val.to_s =~ /on|true|yes/ ? true : false)
  begin
    Lich.db.execute("INSERT OR REPLACE INTO lich_settings(name,value) values('display_lichid',?);", [@@display_lichid.to_s.encode('UTF-8')])
  rescue SQLite3::BusyException
    sleep 0.1
    retry
  end
end

Whether room exits are rendered as clickable command links (a tag) or as plain text in room-display output. Persisted in the lich_settings table and shared across characters. When no value has been saved yet, the default is game-aware: DragonRealms defaults to off (plain text, matching the retired roomnumbers.lic look) while GemStone and any other game default to on (clickable links, the pre-existing core behavior).

Returns:

  • (Boolean, nil) —

    true when exits should be clickable links; nil until the game is identified (XMLData.game still blank), matching the pre-existing display_* getters. Callers treat nil as falsy.



1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
# File 'documented/lich.rb', line 1260

def self.display_room_links
  if @@display_room_links.nil?
    begin
      val = Lich.db.get_first_value("SELECT value FROM lich_settings WHERE name='display_room_links';")
    rescue SQLite3::BusyException
      sleep 0.1
      retry
    end
    val = (XMLData.game =~ /^DR/ ? false : true) if val.nil? and XMLData.game != ""; # default: DR off, others on
    @@display_room_links = (val.to_s =~ /on|true|yes/ ? true : false) if !val.nil?;
  end
  return @@display_room_links
end

.display_room_links=(val) ⇒ Boolean, String

Sets and persists the room-exit link toggle.

Parameters:

  • val (Boolean, String) —

    truthy values are any of on/true/yes

Returns:

  • (Boolean, String) —

    the value passed in - Ruby assignment methods always return their argument, not the method body's result



1278
1279
1280
1281
1282
1283
1284
1285
1286
# File 'documented/lich.rb', line 1278

def self.display_room_links=(val)
  @@display_room_links = (val.to_s =~ /on|true|yes/ ? true : false)
  begin
    Lich.db.execute("INSERT OR REPLACE INTO lich_settings(name,value) values('display_room_links',?);", [@@display_room_links.to_s.encode('UTF-8')])
  rescue SQLite3::BusyException
    sleep 0.1
    retry
  end
end

.display_room_mono ⇒ Boolean?

Whether the Lich-injected room lines (Room Number, Room Exits, StringProcs) are wrapped in the fixed-width mono style. Persisted in the lich_settings table and shared across characters. When no value has been saved yet, the default is game-aware: DragonRealms defaults to on (mono, matching the retired roomnumbers.lic look) while GemStone and any other game default to off (the proportional game font, the pre-existing core behavior). The mono wrapper is only emitted on mono-capable frontends (see Frontend.supports_mono?).

Returns:

  • (Boolean, nil) —

    true when the lines should render in the mono style; nil until the game is identified (XMLData.game still blank), matching the pre-existing display_* getters. Callers treat nil as falsy.



1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
# File 'documented/lich.rb', line 1298

def self.display_room_mono
  if @@display_room_mono.nil?
    begin
      val = Lich.db.get_first_value("SELECT value FROM lich_settings WHERE name='display_room_mono';")
    rescue SQLite3::BusyException
      sleep 0.1
      retry
    end
    val = (XMLData.game =~ /^DR/ ? true : false) if val.nil? and XMLData.game != ""; # default: DR on, others off
    @@display_room_mono = (val.to_s =~ /on|true|yes/ ? true : false) if !val.nil?;
  end
  return @@display_room_mono
end

.display_room_mono=(val) ⇒ Boolean, String

Sets and persists the room-line mono-font toggle.

Parameters:

  • val (Boolean, String) —

    truthy values are any of on/true/yes

Returns:

  • (Boolean, String) —

    the value passed in - Ruby assignment methods always return their argument, not the method body's result



1316
1317
1318
1319
1320
1321
1322
1323
1324
# File 'documented/lich.rb', line 1316

def self.display_room_mono=(val)
  @@display_room_mono = (val.to_s =~ /on|true|yes/ ? true : false)
  begin
    Lich.db.execute("INSERT OR REPLACE INTO lich_settings(name,value) values('display_room_mono',?);", [@@display_room_mono.to_s.encode('UTF-8')])
  rescue SQLite3::BusyException
    sleep 0.1
    retry
  end
end

.display_roomid_location ⇒ String?

Where DragonRealms renders the optional room id / UID chosen via ;display lichid and ;display uid: "title" injects into the room-name line (visible on every front-end), "line" is the historical "Room Number:" line below the room, and "both" does both. GemStone ignores this setting (it always injects into the title line). The value is read fresh from the lich_settings table on first access and then memoized.

Returns:

  • (String, nil) —

    one of ROOMID_LOCATIONS, or nil before a game has been identified



1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
# File 'documented/lich.rb', line 1149

def self.display_roomid_location
  if @@display_roomid_location.nil?
    begin
      val = Lich.db.get_first_value("SELECT value FROM lich_settings WHERE name='display_roomid_location';")
    rescue SQLite3::BusyException
      sleep 0.1
      retry
    end
    # Default to the historical below-room line once a game is known; leave unresolved
    # (nil) until then, matching the other display_* getters.
    val = "line" if val.nil? && XMLData.game != ""
    @@display_roomid_location = (ROOMID_LOCATIONS.include?(val) ? val : "line") unless val.nil?
  end
  return @@display_roomid_location
end

.display_roomid_location=(val) ⇒ void

This method returns an undefined value.

Sets the DragonRealms room-id display placement and persists it. An unrecognized value is ignored (the current setting is retained) so a typo can never blank the display or feed the renderer an invalid placement.

Parameters:

  • val (String, Symbol) —

    one of ROOMID_LOCATIONS ("title", "line", or "both"); case-insensitive



1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
# File 'documented/lich.rb', line 1170

def self.display_roomid_location=(val)
  normalized = val.to_s.downcase
  return unless ROOMID_LOCATIONS.include?(normalized)

  @@display_roomid_location = normalized
  begin
    Lich.db.execute("INSERT OR REPLACE INTO lich_settings(name,value) values('display_roomid_location',?);", [normalized.encode('UTF-8')])
  rescue SQLite3::BusyException
    sleep 0.1
    retry
  end
end

.display_stringprocs ⇒ Boolean?

Note:

Default: false

Note:

Lazily loaded from lich_settings and cached

Returns the StringProcs display toggle.

Returns:

  • (Boolean, nil) —

    true if StringProcs should be displayed, nil until a game is identified



1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
# File 'documented/lich.rb', line 1222

def self.display_stringprocs
  if @@display_stringprocs.nil?
    begin
      val = Lich.db.get_first_value("SELECT value FROM lich_settings WHERE name='display_stringprocs';")
    rescue SQLite3::BusyException
      sleep 0.1
      retry
    end
    val = false if val.nil? and XMLData.game != ""; # default false
    @@display_stringprocs = (val.to_s =~ /on|true|yes/ ? true : false) if !val.nil?;
  end
  return @@display_stringprocs
end

.display_stringprocs=(val) ⇒ void

Note:

Retries on SQLite3::BusyException

This method returns an undefined value.

Sets and persists the StringProcs display toggle.

Parameters:

  • val (String, Boolean) —

    truthy values are "on", "true", or "yes" (case-insensitive)



1241
1242
1243
1244
1245
1246
1247
1248
1249
# File 'documented/lich.rb', line 1241

def self.display_stringprocs=(val)
  @@display_stringprocs = (val.to_s =~ /on|true|yes/ ? true : false)
  begin
    Lich.db.execute("INSERT OR REPLACE INTO lich_settings(name,value) values('display_stringprocs',?);", [@@display_stringprocs.to_s.encode('UTF-8')])
  rescue SQLite3::BusyException
    sleep 0.1
    retry
  end
end

.display_uid ⇒ Boolean?

Note:

Default: true for GemStone, false for DragonRealms

Note:

Lazily loaded from lich_settings and cached

Returns the UID (unique ID) display toggle, defaulting based on game type.

Returns:

  • (Boolean, nil) —

    true if UIDs should be displayed, nil until a game is identified



1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
# File 'documented/lich.rb', line 1111

def self.display_uid
  if @@display_uid.nil?
    begin
      val = Lich.db.get_first_value("SELECT value FROM lich_settings WHERE name='display_uid';")
    rescue SQLite3::BusyException
      sleep 0.1
      retry
    end
    val = (XMLData.game =~ /^GS/ ? true : false) if val.nil? and XMLData.game != ""; # default false if DR, otherwise default true
    @@display_uid = (val.to_s =~ /on|true|yes/ ? true : false) if !val.nil?;
  end
  return @@display_uid
end

.display_uid=(val) ⇒ void

Note:

Retries on SQLite3::BusyException

This method returns an undefined value.

Sets and persists the UID display toggle.

Parameters:

  • val (String, Boolean) —

    truthy values are "on", "true", or "yes" (case-insensitive)



1130
1131
1132
1133
1134
1135
1136
1137
1138
# File 'documented/lich.rb', line 1130

def self.display_uid=(val)
  @@display_uid = (val.to_s =~ /on|true|yes/ ? true : false)
  begin
    Lich.db.execute("INSERT OR REPLACE INTO lich_settings(name,value) values('display_uid',?);", [@@display_uid.to_s.encode('UTF-8')])
  rescue SQLite3::BusyException
    sleep 0.1
    retry
  end
end

.find_hosts_file ⇒ String, false

Note:

On Windows: queries registry, checks default paths, and searches all drives

Note:

On Linux/Mac: checks /etc/hosts and /private/etc/hosts

Note:

Stores result in @@hosts_file

Searches the system for the hosts file and caches its path.

Returns:

  • (String, false) —

    the hosts file path, or false if not found



763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
# File 'documented/lich.rb', line 763

def self.find_hosts_file
  if defined?(Win32)
    begin
      key = Win32.RegOpenKeyEx(:hKey => Win32::HKEY_LOCAL_MACHINE, :lpSubKey => 'System\\CurrentControlSet\\Services\\Tcpip\\Parameters', :samDesired => Win32::KEY_READ)[:phkResult]
      hosts_path = Win32.RegQueryValueEx(:hKey => key, :lpValueName => 'DataBasePath')[:lpData]
    ensure
      Win32.RegCloseKey(:hKey => key) rescue nil
    end
    if hosts_path
      windir = (ENV['windir'] || ENV['SYSTEMROOT'] || 'c:\windows')
      hosts_path.gsub('%SystemRoot%', windir)
      hosts_file = "#{hosts_path}\\hosts"
      if File.exist?(hosts_file)
        return (@@hosts_file = hosts_file)
      end
    end
    if (windir = (ENV['windir'] || ENV['SYSTEMROOT'])) and File.exist?("#{windir}\\system32\\drivers\\etc\\hosts")
      return (@@hosts_file = "#{windir}\\system32\\drivers\\etc\\hosts")
    end

    for drive in ['C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
      for windir in ['winnt', 'windows']
        if File.exist?("#{drive}:\\#{windir}\\system32\\drivers\\etc\\hosts")
          return (@@hosts_file = "#{drive}:\\#{windir}\\system32\\drivers\\etc\\hosts")
        end
      end
    end
  else # Linux/Mac
    if File.exist?('/etc/hosts')
      return (@@hosts_file = '/etc/hosts')
    elsif File.exist?('/private/etc/hosts')
      return (@@hosts_file = '/private/etc/hosts')
    end
  end
  return (@@hosts_file = false)
end

.fix_game_host_port(gamehost, gameport) ⇒ Array<(String, Integer)>

Note:

Maps: gs-plat.simutronics.net:10121 and gs3.simutronics.net:4900 and gs4.simutronics.net:10321 and prime.dr.game.play.net:4901 to current endpoints

Normalizes deprecated game server hostnames and ports to current values.

Parameters:

  • gamehost (String) —

    the game hostname

  • gameport (Integer) —

    the game port

Returns:

  • (Array<(String, Integer)>) —

    the normalized [hostname, port] pair



933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
# File 'documented/lich.rb', line 933

def self.fix_game_host_port(gamehost, gameport)
  if (gamehost == 'gs-plat.simutronics.net') and (gameport.to_i == 10121)
    gamehost = 'storm.gs4.game.play.net'
    gameport = 10124
  elsif (gamehost == 'gs3.simutronics.net') and (gameport.to_i == 4900)
    gamehost = 'storm.gs4.game.play.net'
    gameport = 10024
  elsif (gamehost == 'gs4.simutronics.net') and (gameport.to_i == 10321)
    gamehost = 'storm.gs4.game.play.net'
    gameport = 10324
  elsif (gamehost == 'prime.dr.game.play.net') and (gameport.to_i == 4901)
    gamehost = 'dr.simutronics.net'
    gameport = 11024
  end
  [gamehost, gameport]
end

.get_simu_launcher ⇒ String?

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Note:

Reads from Windows HKEY_LOCAL_MACHINE on Windows; Wine registry on Wine; returns nil on other platforms

Retrieves the path or command of the Simutronics Game Entry (SGE) launcher from the system registry.

Returns:

  • (String, nil) —

    the launcher command, or nil if not found



462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
# File 'documented/lich.rb', line 462

def self.get_simu_launcher
  if defined?(Win32)
    begin
      launcher_key = Win32.RegOpenKeyEx(:hKey => Win32::HKEY_LOCAL_MACHINE, :lpSubKey => 'Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command', :samDesired => (Win32::KEY_ALL_ACCESS | Win32::KEY_WOW64_32KEY))[:phkResult]
      launcher_cmd = Win32.RegQueryValueEx(:hKey => launcher_key, :lpValueName => 'RealCommand')[:lpData]
      if launcher_cmd.nil? or launcher_cmd.empty?
        launcher_cmd = Win32.RegQueryValueEx(:hKey => launcher_key)[:lpData]
      end
      return launcher_cmd
    ensure
      Win32.RegCloseKey(:hKey => launcher_key) rescue nil
    end
  elsif defined?(Wine)
    launcher_cmd = Wine.registry_gets('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\RealCommand')
    unless launcher_cmd and not launcher_cmd.empty?
      launcher_cmd = Wine.registry_gets('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\')
    end
    return launcher_cmd
  else
    return nil
  end
end

.hide_uid_flag ⇒ Boolean?

Note:

Default: false

Note:

Lazily loaded from lich_settings and cached

Returns the UID hiding toggle state.

Returns:

  • (Boolean, nil) —

    true if UIDs should be hidden, nil until a game is identified



1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
# File 'documented/lich.rb', line 1047

def self.hide_uid_flag
  if @@hide_uid_flag.nil?
    begin
      val = Lich.db.get_first_value("SELECT value FROM lich_settings WHERE name='hide_uid_flag';")
    rescue SQLite3::BusyException
      sleep 0.1
      retry
    end
    val = false if val.nil? and XMLData.game != ""; # default false
    @@hide_uid_flag = (val.to_s =~ /on|true|yes/ ? true : false) if !val.nil?;
  end
  return @@hide_uid_flag
end

.hide_uid_flag=(val) ⇒ void

Note:

Retries on SQLite3::BusyException

This method returns an undefined value.

Sets and persists the UID hiding toggle state.

Parameters:

  • val (String, Boolean) —

    truthy values are "on", "true", or "yes" (case-insensitive)



1066
1067
1068
1069
1070
1071
1072
1073
1074
# File 'documented/lich.rb', line 1066

def self.hide_uid_flag=(val)
  @@hide_uid_flag = (val.to_s =~ /on|true|yes/ ? true : false)
  begin
    Lich.db.execute("INSERT OR REPLACE INTO lich_settings(name,value) values('hide_uid_flag',?);", [@@hide_uid_flag.to_s.encode('UTF-8')])
  rescue SQLite3::BusyException
    sleep 0.1
    retry
  end
end

.hosts_file ⇒ String, false

Note:

Caches the result in @@hosts_file for subsequent calls

Returns the path to the system hosts file, locating it on first access.

Returns:

  • (String, false) —

    the hosts file path, or false if not found



752
753
754
755
# File 'documented/lich.rb', line 752

def self.hosts_file
  Lich.find_hosts_file if @@hosts_file.nil?
  return @@hosts_file
end

.init_db ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Note:

Creates: script_setting, script_auto_settings, lich_settings, uservars, session_summary_state, session_summary_state indices, trusted_scripts (Ruby 2.0-2.2 only), simu_game_entry, enable_inventory_boxes

Note:

Safely handles SQLite3::BusyException and idempotently tolerates duplicate-column errors from migrations

This method returns an undefined value.

Initializes the Lich database schema, creating all required tables and indices.



277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
# File 'documented/lich.rb', line 277

def self.init_db
  begin
    Lich.db.execute("CREATE TABLE IF NOT EXISTS script_setting (script TEXT NOT NULL, name TEXT NOT NULL, value BLOB, PRIMARY KEY(script, name));")
    Lich.db.execute("CREATE TABLE IF NOT EXISTS script_auto_settings (script TEXT NOT NULL, scope TEXT, hash BLOB, PRIMARY KEY(script, scope));")
    Lich.db.execute("CREATE TABLE IF NOT EXISTS lich_settings (name TEXT NOT NULL, value TEXT, PRIMARY KEY(name));")
    Lich.db.execute("CREATE TABLE IF NOT EXISTS uservars (scope TEXT NOT NULL, hash BLOB, PRIMARY KEY(scope));")
    # Session summary reporting table for process-level heartbeat metadata.
    # This schema is initialized with the rest of core DB setup to avoid
    # runtime DDL lock contention on first adapter access.
    Lich.db.execute("CREATE TABLE IF NOT EXISTS session_summary_state (pid INTEGER PRIMARY KEY, session_name TEXT, role TEXT, state TEXT, frontend TEXT, game_code TEXT, hidden INTEGER DEFAULT 0, started_at INTEGER, last_heartbeat_at INTEGER, os_seen_at INTEGER, os_seen INTEGER, os_name INTEGER, last_utilization_at INTEGER, metadata_json TEXT);")
    Lich.db.execute("CREATE INDEX IF NOT EXISTS idx_session_summary_state_session_name ON session_summary_state(session_name);")
    Lich.db.execute("CREATE INDEX IF NOT EXISTS idx_session_summary_state_heartbeat ON session_summary_state(last_heartbeat_at);")
    # Backward-compatible migration guards:
    # In dev/test transitions, older local tables may be missing newer columns.
    # We keep these ALTER blocks idempotent by tolerating duplicate-column errors.
    begin
      Lich.db.execute("ALTER TABLE session_summary_state ADD COLUMN os_seen_at INTEGER;")
    rescue SQLite3::SQLException => e
      raise unless e.message.include?('duplicate column name')
    end
    begin
      Lich.db.execute("ALTER TABLE session_summary_state ADD COLUMN os_seen INTEGER;")
    rescue SQLite3::SQLException => e
      raise unless e.message.include?('duplicate column name')
    end
    begin
      Lich.db.execute("ALTER TABLE session_summary_state ADD COLUMN os_name INTEGER;")
    rescue SQLite3::SQLException => e
      raise unless e.message.include?('duplicate column name')
    end
    if (RUBY_VERSION =~ /^2\.[012]\./)
      Lich.db.execute("CREATE TABLE IF NOT EXISTS trusted_scripts (name TEXT NOT NULL);")
    end
    Lich.db.execute("CREATE TABLE IF NOT EXISTS simu_game_entry (character TEXT NOT NULL, game_code TEXT NOT NULL, data BLOB, PRIMARY KEY(character, game_code));")
    Lich.db.execute("CREATE TABLE IF NOT EXISTS enable_inventory_boxes (player_id INTEGER NOT NULL, PRIMARY KEY(player_id));")
  rescue SQLite3::BusyException
    sleep 0.1
    retry
  end
end

.inventory_boxes(player_id) ⇒ Boolean

Note:

Retries on SQLite3::BusyException

Checks whether inventory boxes are enabled for a given player.

Parameters:

  • player_id (Integer) —

    the player ID

Returns:

  • (Boolean) —

    true if inventory boxes are enabled for this player, false otherwise



858
859
860
861
862
863
864
865
866
867
868
869
870
# File 'documented/lich.rb', line 858

def self.inventory_boxes(player_id)
  begin
    v = Lich.db.get_first_value('SELECT player_id FROM enable_inventory_boxes WHERE player_id=?;', [player_id.to_i])
  rescue SQLite3::BusyException
    sleep 0.1
    retry
  end
  if v
    true
  else
    false
  end
end

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Note:

On Windows non-admin: elevates to admin and re-runs self via ShellExecuteEx

Note:

On Wine: modifies the Wine registry for AutoLaunch integration

Note:

Saves the original launcher command for later restoration

Integrates Lich into Simutronics AutoLaunch (browser launcher) by modifying the registry.

Returns:

  • (Boolean, nil) —

    true if successful or already linked, false on error, nil on unsupported platform



623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
# File 'documented/lich.rb', line 623

def self.link_to_sal
  if defined?(Win32)
    if Win32.admin?
      begin
        # fixme: 64 bit browsers?
        launcher_key = Win32.RegOpenKeyEx(:hKey => Win32::HKEY_LOCAL_MACHINE, :lpSubKey => 'Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command', :samDesired => (Win32::KEY_ALL_ACCESS | Win32::KEY_WOW64_32KEY))[:phkResult]
        r = Win32.RegQueryValueEx(:hKey => launcher_key, :lpValueName => 'RealCommand')
        if (r[:return] == 0) and not r[:lpData].empty?
          # already linked
          return true
        end

        r = Win32.GetModuleFileName
        unless r[:return] > 0
          # fixme
          return false
        end

        new_launcher_cmd = "\"#{r[:lpFilename]}\" \"#{File.expand_path($PROGRAM_NAME)}\" %1"
        r = Win32.RegQueryValueEx(:hKey => launcher_key)
        launcher_cmd = r[:lpData]
        r = Win32.RegSetValueEx(:hKey => launcher_key, :lpValueName => 'RealCommand', :dwType => Win32::REG_SZ, :lpData => launcher_cmd)
        return false unless (r == 0)

        r = Win32.RegSetValueEx(:hKey => launcher_key, :dwType => Win32::REG_SZ, :lpData => new_launcher_cmd)
        return (r == 0)
      ensure
        Win32.RegCloseKey(:hKey => launcher_key) rescue nil
      end
    else
      begin
        r = Win32.GetModuleFileName
        file = ((r[:return] > 0) ? r[:lpFilename] : 'rubyw.exe')
        params = "#{$PROGRAM_NAME.split(/\/|\\/).last} --link-to-sal"
        r = Win32.ShellExecuteEx(:lpVerb => 'runas', :lpFile => file, :lpDirectory => LICH_DIR.tr("/", "\\"), :lpParameters => params, :fMask => Win32::SEE_MASK_NOCLOSEPROCESS)
        if r[:return] > 0
          process_id = r[:hProcess]
          sleep 0.2 while Win32.GetExitCodeProcess(:hProcess => process_id)[:lpExitCode] == Win32::STILL_ACTIVE
          sleep 3
        else
          Win32.ShellExecute(:lpOperation => 'runas', :lpFile => file, :lpDirectory => LICH_DIR.tr("/", "\\"), :lpParameters => params)
          sleep 6
        end
      rescue
        Lich.msgbox(:message => $!)
      end
    end
  elsif defined?(Wine)
    launch_cmd = Wine.registry_gets('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\')
    return false unless launch_cmd

    new_launch_cmd = "#{File.expand_path($PROGRAM_NAME)} --wine=#{Wine::BIN} --wine-prefix=#{Wine::PREFIX} %1"
    result = true
    if launch_cmd
      if launch_cmd =~ /lich/i
        $stdout.puts "--- warning: Lich appears to already be installed to the registry"
        Lich.log "warning: Lich appears to already be installed to the registry"
        Lich.log 'info: launch_cmd: ' + launch_cmd
      else
        result = result && Wine.registry_puts('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\RealCommand', launch_cmd)
        result = result && Wine.registry_puts('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\', new_launch_cmd)
      end
    end
    return result
  else
    return false
  end
end

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Note:

On Windows non-admin: elevates to admin and re-runs self via ShellExecuteEx

Note:

On Wine: modifies the Wine registry for Simutronics launcher integration

Note:

Saves the original launcher directory for later restoration

Integrates Lich into the Simutronics Game Entry launcher by modifying the registry.

Returns:

  • (Boolean, nil) —

    true if successful or already linked, false on error, nil on unsupported platform



492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
# File 'documented/lich.rb', line 492

def self.link_to_sge
  if defined?(Win32)
    if Win32.admin?
      begin
        launcher_key = Win32.RegOpenKeyEx(:hKey => Win32::HKEY_LOCAL_MACHINE, :lpSubKey => 'Software\\Simutronics\\Launcher', :samDesired => (Win32::KEY_ALL_ACCESS | Win32::KEY_WOW64_32KEY))[:phkResult]
        r = Win32.RegQueryValueEx(:hKey => launcher_key, :lpValueName => 'RealDirectory')
        if (r[:return] == 0) and not r[:lpData].empty?
          # already linked
          return true
        end

        r = Win32.GetModuleFileName
        unless r[:return] > 0
          # fixme
          return false
        end

        new_launcher_dir = "\"#{r[:lpFilename]}\" \"#{File.expand_path($PROGRAM_NAME)}\" "
        r = Win32.RegQueryValueEx(:hKey => launcher_key, :lpValueName => 'Directory')
        launcher_dir = r[:lpData]
        r = Win32.RegSetValueEx(:hKey => launcher_key, :lpValueName => 'RealDirectory', :dwType => Win32::REG_SZ, :lpData => launcher_dir)
        return false unless (r == 0)

        r = Win32.RegSetValueEx(:hKey => launcher_key, :lpValueName => 'Directory', :dwType => Win32::REG_SZ, :lpData => new_launcher_dir)
        return (r == 0)
      ensure
        Win32.RegCloseKey(:hKey => launcher_key) rescue nil
      end
    else
      begin
        r = Win32.GetModuleFileName
        file = ((r[:return] > 0) ? r[:lpFilename] : 'rubyw.exe')
        params = "#{$PROGRAM_NAME.split(/\/|\\/).last} --link-to-sge"
        r = Win32.ShellExecuteEx(:lpVerb => 'runas', :lpFile => file, :lpDirectory => LICH_DIR.tr("/", "\\"), :lpParameters => params, :fMask => Win32::SEE_MASK_NOCLOSEPROCESS)
        if r[:return] > 0
          process_id = r[:hProcess]
          sleep 0.2 while Win32.GetExitCodeProcess(:hProcess => process_id)[:lpExitCode] == Win32::STILL_ACTIVE
          sleep 3
        else
          Win32.ShellExecute(:lpOperation => 'runas', :lpFile => file, :lpDirectory => LICH_DIR.tr("/", "\\"), :lpParameters => params)
          sleep 6
        end
      rescue
        Lich.msgbox(:message => $!)
      end
    end
  elsif defined?(Wine)
    launch_dir = Wine.registry_gets('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\Directory')
    return false unless launch_dir

    lich_launch_dir = "#{File.expand_path($PROGRAM_NAME)} --wine=#{Wine::BIN} --wine-prefix=#{Wine::PREFIX}  "
    result = true
    if launch_dir
      if launch_dir =~ /lich/i
        $stdout.puts "--- warning: Lich appears to already be installed to the registry"
        Lich.log "warning: Lich appears to already be installed to the registry"
        Lich.log 'info: launch_dir: ' + launch_dir
      else
        result = result && Wine.registry_puts('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\RealDirectory', launch_dir)
        result = result && Wine.registry_puts('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\Directory', lich_launch_dir)
      end
    end
    return result
  else
    return false
  end
end

.log(msg) ⇒ void

Note:

Format: "YYYY-MM-DD HH:MM:SS: msg"

This method returns an undefined value.

Writes a timestamped message to stderr (the debug log).

Parameters:

  • msg (String) —

    the message to log



341
342
343
# File 'documented/lich.rb', line 341

def self.log(msg)
  $stderr.puts "#{Time.now.strftime("%Y-%m-%d %H:%M:%S")}: #{msg}"
end

.max_debug_logs ⇒ Integer

Returns the maximum number of debug log files to retain in the temp directory. The value is lazily loaded from the lich_settings database table on first access, then cached in a class variable for subsequent calls. When no persisted value exists, falls back to MAX_DEBUG_LOGS_DEFAULT.

Examples:

Query the current setting in-game

;e respond Lich.max_debug_logs

Returns:

See Also:



1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
# File 'documented/lich.rb', line 1514

def self.max_debug_logs
  if @@max_debug_logs.nil?
    begin
      val = Lich.db.get_first_value("SELECT value FROM lich_settings WHERE name='max_debug_logs';")
    rescue SQLite3::BusyException
      sleep 0.1
      retry
    end
    @@max_debug_logs = val.nil? ? MAX_DEBUG_LOGS_DEFAULT : [val.to_i, MAX_DEBUG_LOGS_MINIMUM].max
  end
  @@max_debug_logs
end

.max_debug_logs=(val) ⇒ void

This method returns an undefined value.

Persists the maximum number of debug log files to retain. Values below MAX_DEBUG_LOGS_MINIMUM are clamped to prevent accidental deletion of all logs.

Examples:

Set retention to 50 files in-game

;e Lich.max_debug_logs = 50

Parameters:

  • val (#to_i) —

    the desired retention limit

See Also:



1539
1540
1541
1542
1543
1544
1545
1546
1547
# File 'documented/lich.rb', line 1539

def self.max_debug_logs=(val)
  @@max_debug_logs = [val.to_i, MAX_DEBUG_LOGS_MINIMUM].max
  begin
    Lich.db.execute("INSERT OR REPLACE INTO lich_settings(name,value) values('max_debug_logs',?);", [@@max_debug_logs.to_s.encode('UTF-8')])
  rescue SQLite3::BusyException
    sleep 0.1
    retry
  end
end

.method_missing(arg1, arg2 = '') ⇒ Object

Deprecated.

Use Vars class instead

Note:

Emits a deprecation warning (throttled to once per 5 minutes); scripts should use Vars.* instead

Deprecated delegation to Vars.method_missing for legacy Lich.* variable access.

Parameters:

  • arg1 (String) —

    the variable name

  • arg2 (String) (defaults to: '') —

    optional value for assignment

Returns:

  • (Object) —

    delegated result from Vars.method_missing



239
240
241
242
243
244
245
# File 'documented/lich.rb', line 239

def self.method_missing(arg1, arg2 = '')
  if (Time.now.to_i - @@last_warn_deprecated) > 300
    respond "--- warning: Lich.* variables will stop working in a future version of Lich.  Use Vars.* (offending script: #{Script.current.name || 'unknown'})"
    @@last_warn_deprecated = Time.now.to_i
  end
  Vars.method_missing(arg1, arg2)
end

.modify_hosts(game_host) ⇒ Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Note:

Backs up the original hosts file to hosts_file.bak and registers an at_exit handler to restore it

Adds a hosts file entry redirecting a game hostname to 127.0.0.1 for local testing.

Parameters:

  • game_host (String) —

    the hostname to redirect (e.g., "gs4.simutronics.net")

Returns:

  • (Boolean) —

    true if successful, false if the hosts file does not exist or backup creation fails



806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
# File 'documented/lich.rb', line 806

def self.modify_hosts(game_host)
  if Lich.hosts_file and File.exist?(Lich.hosts_file)
    at_exit { Lich.restore_hosts }
    Lich.restore_hosts
    if File.exist?("#{Lich.hosts_file}.bak")
      return false
    end

    begin
      # copy hosts to hosts.bak
      File.open("#{Lich.hosts_file}.bak", 'w') { |hb| File.open(Lich.hosts_file) { |h| hb.write(h.read) } }
    rescue
      File.unlink("#{Lich.hosts_file}.bak") if File.exist?("#{Lich.hosts_file}.bak")
      return false
    end
    File.open(Lich.hosts_file, 'a') { |f| f.write "\r\n127.0.0.1\t\t#{game_host}" }
    return true
  else
    return false
  end
end

.module_eval(*_a) ⇒ nil

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Stub implementation that prevents dynamic module evaluation.

Returns:

  • (nil) —

    always nil



334
# File 'documented/lich.rb', line 334

def self.module_eval(*_a);        nil; end

.msgbox(args) ⇒ Symbol?

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Note:

Uses Win32 API on Windows, Gtk on Linux/Mac with Gtk, or stdout.puts on text terminals

Displays a modal message box dialog using the native platform GUI or terminal fallback.

Parameters:

  • args (Hash) —

    dialog configuration

Options Hash (args):

  • :message (String) —

    the message body (required)

  • :title (String) —

    the window title (default: "Lich vLICH_VERSION")

  • :buttons (Symbol) —

    :ok (default), :ok_cancel, or :yes_no

  • :icon (Symbol) —

    :error, :question, :warning, or nil (default)

Returns:

  • (Symbol, nil) —

    :ok, :cancel, :yes, :no, or nil if closed without selection



383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
# File 'documented/lich.rb', line 383

def self.msgbox(args)
  if defined?(Win32)
    if args[:buttons] == :ok_cancel
      buttons = Win32::MB_OKCANCEL
    elsif args[:buttons] == :yes_no
      buttons = Win32::MB_YESNO
    else
      buttons = Win32::MB_OK
    end
    if args[:icon] == :error
      icon = Win32::MB_ICONERROR
    elsif args[:icon] == :question
      icon = Win32::MB_ICONQUESTION
    elsif args[:icon] == :warning
      icon = Win32::MB_ICONWARNING
    else
      icon = 0
    end
    args[:title] ||= "Lich v#{LICH_VERSION}"
    r = Win32.MessageBox(:lpText => args[:message], :lpCaption => args[:title], :uType => (buttons | icon))
    if r == Win32::IDIOK
      return :ok
    elsif r == Win32::IDICANCEL
      return :cancel
    elsif r == Win32::IDIYES
      return :yes
    elsif r == Win32::IDINO
      return :no
    else
      return nil
    end
  elsif defined?(Gtk)
    if args[:buttons] == :ok_cancel
      buttons = :ok_cancel
    elsif args[:buttons] == :yes_no
      buttons = :yes_no
    else
      buttons = :ok
    end
    if args[:icon] == :error
      type = :error
    elsif args[:icon] == :question
      type = :question
    elsif args[:icon] == :warning
      type = :warning
    else
      type = :info
    end
    dialog = Gtk::MessageDialog.new(parent: nil, flags: :modal, type: type, buttons: buttons, message: args[:message])
    args[:title] ||= "Lich v#{LICH_VERSION}"
    dialog.title = args[:title]
    begin
      # GTK3 returns the response; it does not yield to a block passed to run.
      response = dialog.run
    ensure
      dialog.destroy
    end
    if response == Gtk::ResponseType::OK
      return :ok
    elsif response == Gtk::ResponseType::CANCEL
      return :cancel
    elsif response == Gtk::ResponseType::YES
      return :yes
    elsif response == Gtk::ResponseType::NO
      return :no
    else
      return nil
    end
  elsif $stdout.isatty
    $stdout.puts(args[:message])
    return nil
  end
end

.mutex_lock ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Note:

Logs and responds to user on lock errors; safe to call multiple times

This method returns an undefined value.

Acquires the database mutex lock if not already held by the current thread.



209
210
211
212
213
214
215
216
# File 'documented/lich.rb', line 209

def self.mutex_lock
  begin
    self.db_mutex.lock unless self.db_mutex.owned?
  rescue StandardError
    respond "--- Lich: error: Lich.mutex_lock: #{$!}"
    Lich.log "error: Lich.mutex_lock: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
  end
end

.mutex_unlock ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Note:

Logs and responds to user on unlock errors; safe to call when not locked

This method returns an undefined value.

Releases the database mutex lock if currently held by the current thread.



223
224
225
226
227
228
229
230
# File 'documented/lich.rb', line 223

def self.mutex_unlock
  begin
    self.db_mutex.unlock if self.db_mutex.owned?
  rescue StandardError
    respond "--- Lich: error: Lich.mutex_unlock: #{$!}"
    Lich.log "error: Lich.mutex_unlock: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
  end
end

.open_sequel_sqlite(path) ⇒ Sequel::Database

Note:

Sequel does not expose SQLite3's busy_timeout API directly; the PRAGMA is used instead

Opens an SQLite database via the Sequel ORM and sets the busy timeout via PRAGMA.

Parameters:

  • path (String) —

    the file path to the database

Returns:

  • (Sequel::Database) —

    the configured Sequel database object



42
43
44
45
46
47
# File 'documented/lich.rb', line 42

def self.open_sequel_sqlite(path)
  db = Sequel.sqlite(path)
  # Sequel does not expose sqlite3's busy_timeout API on its database wrapper.
  db.run("PRAGMA busy_timeout = #{sqlite_busy_timeout_ms.to_i}")
  db
end

.open_sqlite_db(path) ⇒ SQLite3::Database

Opens or creates an SQLite3 database at the specified path and applies busy timeout configuration.

Examples:

db = Lich.open_sqlite_db("#{Lich::DATA_DIR}/lich.db3")

Parameters:

  • path (String) —

    the file path to the database

Returns:

  • (SQLite3::Database) —

    the configured database connection



33
34
35
# File 'documented/lich.rb', line 33

def self.open_sqlite_db(path)
  configure_sqlite_connection(SQLite3::Database.new(path))
end

.restore_hosts ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Note:

Exits the process with code 1 on restoration error

This method returns an undefined value.

Restores the hosts file from its backup if present.



833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
# File 'documented/lich.rb', line 833

def self.restore_hosts
  if Lich.hosts_file and File.exist?(Lich.hosts_file)
    begin
      # fixme: use rename instead?  test rename on windows
      if File.exist?("#{Lich.hosts_file}.bak")
        File.open("#{Lich.hosts_file}.bak") { |infile|
          File.open(Lich.hosts_file, 'w') { |outfile|
            outfile.write(infile.read)
          }
        }
        File.unlink "#{Lich.hosts_file}.bak"
      end
    rescue
      $stdout.puts "--- error: restore_hosts: #{$!}"
      Lich.log "error: restore_hosts: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
      exit(1)
    end
  end
end

.seek(fe) ⇒ String?

Note:

Prefers Lich::Common::FrontendLocator if available; falls back to legacy global variables

Locates the installation directory of a frontend by name.

Parameters:

  • fe (String) —

    the frontend identifier (e.g., "wizard", "stormfront")

Returns:

  • (String, nil) —

    the frontend installation path, or nil if not found



252
253
254
255
256
257
258
259
260
261
# File 'documented/lich.rb', line 252

def self.seek(fe)
  if defined?(Lich::Common::FrontendLocator)
    return Lich::Common::FrontendLocator.compatibility_location(fe)
  end

  return $wiz_fe_loc if fe =~ /wizard/
  return $sf_fe_loc if fe =~ /stormfront/

  nil
end

.set_inventory_boxes(player_id, enabled) ⇒ void

Note:

Retries on SQLite3::BusyException

This method returns an undefined value.

Enables or disables inventory boxes for a given player.

Parameters:

  • player_id (Integer) —

    the player ID

  • enabled (Boolean) —

    true to enable, false to disable



878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
# File 'documented/lich.rb', line 878

def self.set_inventory_boxes(player_id, enabled)
  if enabled
    begin
      Lich.db.execute('INSERT OR REPLACE INTO enable_inventory_boxes values(?);', [player_id.to_i])
    rescue SQLite3::BusyException
      sleep 0.1
      retry
    end
  else
    begin
      Lich.db.execute('DELETE FROM enable_inventory_boxes where player_id=?;', [player_id.to_i])
    rescue SQLite3::BusyException
      sleep 0.1
      retry
    end
  end
  nil
end

.show_deprecated_log ⇒ void

This method returns an undefined value.

Displays all recorded deprecation notices to the user via respond().

See Also:



367
368
369
370
371
# File 'documented/lich.rb', line 367

def self.show_deprecated_log
  @@deprecated_log.each do |msg|
    respond(msg)
  end
end

.sqlite_busy_timeout_ms ⇒ Integer

Returns the configured SQLite busy timeout in milliseconds.

Returns:

  • (Integer) —

    the busy timeout value (5000 by default)



13
14
15
# File 'documented/lich.rb', line 13

def self.sqlite_busy_timeout_ms
  DEFAULT_SQLITE_BUSY_TIMEOUT_MS
end

.track_autosort_state ⇒ Boolean

Note:

Lazily loaded from lich_settings and cached

Returns the autosort state tracking toggle.

Returns:

  • (Boolean) —

    true if autosort state should be tracked, false otherwise



1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
# File 'documented/lich.rb', line 1368

def self.track_autosort_state
  if @@track_autosort_state.nil?
    begin
      val = Lich.db.get_first_value("SELECT value FROM lich_settings WHERE name='track_autosort_state';")
    rescue SQLite3::BusyException
      sleep 0.1
      retry
    end
    @@track_autosort_state = (val.to_s =~ /on|true|yes/ ? true : false)
  end
  return @@track_autosort_state
end

.track_autosort_state=(val) ⇒ void

Note:

Retries on SQLite3::BusyException

This method returns an undefined value.

Sets and persists the autosort state tracking toggle.

Parameters:

  • val (String, Boolean) —

    truthy values are "on", "true", or "yes" (case-insensitive)



1386
1387
1388
1389
1390
1391
1392
1393
1394
# File 'documented/lich.rb', line 1386

def self.track_autosort_state=(val)
  @@track_autosort_state = (val.to_s =~ /on|true|yes/ ? true : false)
  begin
    Lich.db.execute("INSERT OR REPLACE INTO lich_settings(name,value) values('track_autosort_state',?);", [@@track_autosort_state.to_s.encode('UTF-8')])
  rescue SQLite3::BusyException
    sleep 0.1
    retry
  end
end

.track_dark_mode ⇒ Boolean

Note:

Lazily loaded from lich_settings and cached

Returns the dark mode state tracking toggle.

Returns:

  • (Boolean) —

    true if dark mode state should be tracked, false otherwise



1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
# File 'documented/lich.rb', line 1400

def self.track_dark_mode
  if @@track_dark_mode.nil?
    begin
      val = Lich.db.get_first_value("SELECT value FROM lich_settings WHERE name='track_dark_mode';")
    rescue SQLite3::BusyException
      sleep 0.1
      retry
    end
    @@track_dark_mode = (val.to_s =~ /on|true|yes/ ? true : false)
  end
  return @@track_dark_mode
end

.track_dark_mode=(val) ⇒ void

Note:

Retries on SQLite3::BusyException

This method returns an undefined value.

Sets and persists the dark mode state tracking toggle.

Parameters:

  • val (String, Boolean) —

    truthy values are "on", "true", or "yes" (case-insensitive)



1418
1419
1420
1421
1422
1423
1424
1425
1426
# File 'documented/lich.rb', line 1418

def self.track_dark_mode=(val)
  @@track_dark_mode = (val.to_s =~ /on|true|yes/ ? true : false)
  begin
    Lich.db.execute("INSERT OR REPLACE INTO lich_settings(name,value) values('track_dark_mode',?);", [@@track_dark_mode.to_s.encode('UTF-8')])
  rescue SQLite3::BusyException
    sleep 0.1
    retry
  end
end

.track_layout_state ⇒ Boolean

Note:

Lazily loaded from lich_settings and cached

Returns the layout state tracking toggle.

Returns:

  • (Boolean) —

    true if layout state should be tracked, false otherwise



1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
# File 'documented/lich.rb', line 1432

def self.track_layout_state
  if @@track_layout_state.nil?
    begin
      val = Lich.db.get_first_value("SELECT value FROM lich_settings WHERE name='track_layout_state';")
    rescue SQLite3::BusyException
      sleep 0.1
      retry
    end
    @@track_layout_state = (val.to_s =~ /on|true|yes/ ? true : false)
  end
  return @@track_layout_state
end

.track_layout_state=(val) ⇒ void

Note:

Retries on SQLite3::BusyException

This method returns an undefined value.

Sets and persists the layout state tracking toggle.

Parameters:

  • val (String, Boolean) —

    truthy values are "on", "true", or "yes" (case-insensitive)



1450
1451
1452
1453
1454
1455
1456
1457
1458
# File 'documented/lich.rb', line 1450

def self.track_layout_state=(val)
  @@track_layout_state = (val.to_s =~ /on|true|yes/ ? true : false)
  begin
    Lich.db.execute("INSERT OR REPLACE INTO lich_settings(name,value) values('track_layout_state',?);", [@@track_layout_state.to_s.encode('UTF-8')])
  rescue SQLite3::BusyException
    sleep 0.1
    retry
  end
end

.track_persistent_launcher_mode ⇒ Boolean

Returns persisted launcher mode state for GUI login flow.

Returns:

  • (Boolean) —

    true when persistent multi-launch mode is enabled



1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
# File 'documented/lich.rb', line 1463

def self.track_persistent_launcher_mode
  if @@track_persistent_launcher_mode.nil?
    begin
      val = Lich.db.get_first_value("SELECT value FROM lich_settings WHERE name='track_persistent_launcher_mode';")
    rescue SQLite3::BusyException
      sleep 0.1
      retry
    end
    @@track_persistent_launcher_mode = (val.to_s =~ /on|true|yes/ ? true : false)
  end
  return @@track_persistent_launcher_mode
end

.track_persistent_launcher_mode=(val) ⇒ void

This method returns an undefined value.

Persists launcher mode state for GUI login flow.

Parameters:

  • val (Object) —

    truthy/falsey value parsed to Boolean



1480
1481
1482
1483
1484
1485
1486
1487
1488
# File 'documented/lich.rb', line 1480

def self.track_persistent_launcher_mode=(val)
  @@track_persistent_launcher_mode = (val.to_s =~ /on|true|yes/ ? true : false)
  begin
    Lich.db.execute("INSERT OR REPLACE INTO lich_settings(name,value) values('track_persistent_launcher_mode',?);", [@@track_persistent_launcher_mode.to_s.encode('UTF-8')])
  rescue SQLite3::BusyException
    sleep 0.1
    retry
  end
end

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Note:

On Windows non-admin: elevates to admin and re-runs self via ShellExecuteEx

Note:

On Wine: restores the Wine registry for AutoLaunch

Removes Lich integration from Simutronics AutoLaunch by restoring the original registry.

Returns:

  • (Boolean, nil) —

    true if successful or not linked, false on error, nil on unsupported platform



698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
# File 'documented/lich.rb', line 698

def self.unlink_from_sal
  if defined?(Win32)
    if Win32.admin?
      begin
        launcher_key = Win32.RegOpenKeyEx(:hKey => Win32::HKEY_LOCAL_MACHINE, :lpSubKey => 'Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command', :samDesired => (Win32::KEY_ALL_ACCESS | Win32::KEY_WOW64_32KEY))[:phkResult]
        real_directory = Win32.RegQueryValueEx(:hKey => launcher_key, :lpValueName => 'RealCommand')[:lpData]
        if real_directory.nil? or real_directory.empty?
          # not linked
          return true
        end

        r = Win32.RegSetValueEx(:hKey => launcher_key, :dwType => Win32::REG_SZ, :lpData => real_directory)
        return false unless (r == 0)

        r = Win32.RegDeleteValue(:hKey => launcher_key, :lpValueName => 'RealCommand')
        return (r == 0)
      ensure
        Win32.RegCloseKey(:hKey => launcher_key) rescue nil
      end
    else
      begin
        r = Win32.GetModuleFileName
        file = ((r[:return] > 0) ? r[:lpFilename] : 'rubyw.exe')
        params = "#{$PROGRAM_NAME.split(/\/|\\/).last} --unlink-from-sal"
        r = Win32.ShellExecuteEx(:lpVerb => 'runas', :lpFile => file, :lpDirectory => LICH_DIR.tr("/", "\\"), :lpParameters => params, :fMask => Win32::SEE_MASK_NOCLOSEPROCESS)
        if r[:return] > 0
          process_id = r[:hProcess]
          sleep 0.2 while Win32.GetExitCodeProcess(:hProcess => process_id)[:lpExitCode] == Win32::STILL_ACTIVE
          sleep 3
        else
          Win32.ShellExecute(:lpOperation => 'runas', :lpFile => file, :lpDirectory => LICH_DIR.tr("/", "\\"), :lpParameters => params)
          sleep 6
        end
      rescue
        Lich.msgbox(:message => $!)
      end
    end
  elsif defined?(Wine)
    real_launch_cmd = Wine.registry_gets('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\RealCommand')
    result = true
    if real_launch_cmd and not real_launch_cmd.empty?
      result = result && Wine.registry_puts('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\', real_launch_cmd)
      result = result && Wine.registry_puts('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\RealCommand', '')
    end
    return result
  else
    return false
  end
end

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Note:

On Windows non-admin: elevates to admin and re-runs self via ShellExecuteEx

Note:

On Wine: restores the Wine registry for Simutronics launcher

Removes Lich integration from the Simutronics Game Entry launcher by restoring the original registry.

Returns:

  • (Boolean, nil) —

    true if successful or not linked, false on error, nil on unsupported platform



566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
# File 'documented/lich.rb', line 566

def self.unlink_from_sge
  if defined?(Win32)
    if Win32.admin?
      begin
        launcher_key = Win32.RegOpenKeyEx(:hKey => Win32::HKEY_LOCAL_MACHINE, :lpSubKey => 'Software\\Simutronics\\Launcher', :samDesired => (Win32::KEY_ALL_ACCESS | Win32::KEY_WOW64_32KEY))[:phkResult]
        real_directory = Win32.RegQueryValueEx(:hKey => launcher_key, :lpValueName => 'RealDirectory')[:lpData]
        if real_directory.nil? or real_directory.empty?
          # not linked
          return true
        end

        r = Win32.RegSetValueEx(:hKey => launcher_key, :lpValueName => 'Directory', :dwType => Win32::REG_SZ, :lpData => real_directory)
        return false unless (r == 0)

        r = Win32.RegDeleteValue(:hKey => launcher_key, :lpValueName => 'RealDirectory')
        return (r == 0)
      ensure
        Win32.RegCloseKey(:hKey => launcher_key) rescue nil
      end
    else
      begin
        r = Win32.GetModuleFileName
        file = ((r[:return] > 0) ? r[:lpFilename] : 'rubyw.exe')
        params = "#{$PROGRAM_NAME.split(/\/|\\/).last} --unlink-from-sge"
        r = Win32.ShellExecuteEx(:lpVerb => 'runas', :lpFile => file, :lpDirectory => LICH_DIR.tr("/", "\\"), :lpParameters => params, :fMask => Win32::SEE_MASK_NOCLOSEPROCESS)
        if r[:return] > 0
          process_id = r[:hProcess]
          sleep 0.2 while Win32.GetExitCodeProcess(:hProcess => process_id)[:lpExitCode] == Win32::STILL_ACTIVE
          sleep 3
        else
          Win32.ShellExecute(:lpOperation => 'runas', :lpFile => file, :lpDirectory => LICH_DIR.tr("/", "\\"), :lpParameters => params)
          sleep 6
        end
      rescue
        Lich.msgbox(:message => $!)
      end
    end
  elsif defined?(Wine)
    real_launch_dir = Wine.registry_gets('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\RealDirectory')
    result = true
    if real_launch_dir and not real_launch_dir.empty?
      result = result && Wine.registry_puts('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\Directory', real_launch_dir)
      result = result && Wine.registry_puts('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\RealDirectory', '')
    end
    return result
  else
    return false
  end
end

.win32_launch_method ⇒ String?

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Note:

Retries on SQLite3::BusyException

Retrieves the stored Windows launch method preference from lich_settings.

Returns:

  • (String, nil) —

    the launch method value, or nil if not set



902
903
904
905
906
907
908
909
910
# File 'documented/lich.rb', line 902

def self.win32_launch_method
  begin
    val = Lich.db.get_first_value("SELECT value FROM lich_settings WHERE name='win32_launch_method';")
  rescue SQLite3::BusyException
    sleep 0.1
    retry
  end
  val
end

.win32_launch_method=(val) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Note:

Retries on SQLite3::BusyException

This method returns an undefined value.

Stores a Windows launch method preference in lich_settings.

Parameters:

  • val (String) —

    the launch method value



918
919
920
921
922
923
924
925
# File 'documented/lich.rb', line 918

def self.win32_launch_method=(val)
  begin
    Lich.db.execute("INSERT OR REPLACE INTO lich_settings(name,value) values('win32_launch_method',?);", [val.to_s.encode('UTF-8')])
  rescue SQLite3::BusyException
    sleep 0.1
    retry
  end
end