Skip to content

Instantly share code, notes, and snippets.

@Lexicality
Last active March 21, 2023 00:17
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Lexicality/df4af28e4f05312dbfd0136211859a9c to your computer and use it in GitHub Desktop.
Save Lexicality/df4af28e4f05312dbfd0136211859a9c to your computer and use it in GitHub Desktop.
Binary search utility for finding troublesome addons

Instructions for use

  1. Copy addon_bs.lua to garrysmod/lua
  2. Edit garrysmod/lua/menu/menu.lua and add
    include("addon_bs.lua")
    to the end of it
  3. Open/restart gmod
  4. Type abs start 107821465 into console before starting a game
  5. The script will automatically mount and unmount addons for you. You will need to handle starting new games / restarting gmod yourself. (It will remember where it is across reboots)
  6. Use abs restore to put everything back where it was at the end
--[[
Addon Binary Searcher
Copyright 2018 Lex Robinson
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--]]
print("Addon binary search script active")
local print_ = print
local function print(...)
print_("ABS:", ...)
end
local function f(s, ...)
return s:format(...)
end
local function printf(...)
return print(f(...))
end
local function json_encode(value)
if type(value) ~= "table" then
value = { _a="a", value }
end
return util.TableToJSON(value)
end
local function json_decode(str)
value = util.JSONToTable(str)
if value and value._a == "a" then
return value[1]
end
return value
end
local function db_setup()
sql.Query("CREATE TABLE IF NOT EXISTS addon_bs (key TEXT PRIMARY KEY, value BLOB)")
end
local function db_get(key, default)
local v = sql.QueryValue(f("SELECT value FROM addon_bs WHERE key=%s", sql.SQLStr(key)))
if v == false then
error(sql.LastError())
end
if v then
return json_decode(v)
else
return default
end
end
local function db_delete(key)
key = sql.SQLStr(key)
local r = sql.Query(f("DELETE FROM addon_bs WHERE key=%s", key))
if r == false then
error(sql.LastError())
end
end
local function db_set(key, value)
if value == nil then
db_delete(key)
end
key = sql.SQLStr(key)
value = sql.SQLStr(json_encode(value))
local r = sql.Query(f("INSERT OR REPLACE INTO addon_bs (key, value) VALUES (%s, %s)", key, value))
if r == false then
error(sql.LastError())
end
end
local function get_enabled_addons(not_this_one)
local ids = {}
for _, addon in pairs(engine.GetAddons()) do
if addon.mounted then
-- Sanity check
if addon.wsid == "0" then
ErrorNoHalt(f("Unexpected non workshop gma %q from %q found!", addon.title, addon.file))
elseif addon.wsid ~= not_this_one then
table.insert(ids, addon.wsid)
end
end
end
return ids
end
local ADDON_NAMES = {}
local function split(arr)
local n = math.floor(#arr/2)
return {unpack(arr, 1, n)}, {unpack(arr, n + 1)}
end
local function abs_test(ids)
local test, rest = split(ids)
printf("Test narrowed to %d addons", #test)
if #test < 10 then
for _, id in pairs(test) do
print("", ADDON_NAMES[id] or f("??? %s", id))
end
end
db_set("test", { test = test, rest = rest })
local gold = db_get("golden boy")
if not gold then
error("everything broke, start again from the beginning")
end
steamworks.SetShouldMountAddon(gold, true)
for _, id in pairs(get_enabled_addons(gold)) do
steamworks.SetShouldMountAddon(id, false)
end
for _, id in pairs(test) do
steamworks.SetShouldMountAddon(id, true)
end
steamworks.ApplyAddons()
print("Problem still happening? Use 'abs yes' or 'abs no'.")
end
local function abs_test_result(which)
local test = db_get("test")
if not test then
print("No test in progress?")
return
end
local ids = test[which]
if #ids > 1 then
abs_test(ids)
else
local id = ids[1]
printf("Your asshole is: %q (%s)", ADDON_NAMES[id], id)
steamworks.ViewFile(id)
end
end
local function abs_yes()
abs_test_result("test")
end
local function abs_no()
abs_test_result("rest")
end
local function abs_start(id)
db_setup()
if not id then
print("Please specify your target addon. EG 'abs start 107821465'")
return
end
for _, addon in pairs(engine.GetAddons()) do
ADDON_NAMES[addon.wsid] = addon.title
end
local name = ADDON_NAMES[id]
if not name then
printf("Unknown addon %s?", id)
return
end
printf("Trying to find out who is conflicting with %q...", name)
db_set("golden boy", id)
local ids = get_enabled_addons(id)
printf("Testing against the currently enabled %d addons.", #ids)
db_set("starting state", ids)
abs_test(ids)
end
local function abs_restore()
local starters = db_get("starting state")
if not starters then
print("No state to restore to?")
return
end
print(("Restoring %s addons"):format(#starters))
for _, id in pairs(starters) do
steamworks.SetShouldMountAddon(id, true)
end
steamworks.ApplyAddons()
db_delete("starting state")
db_delete("test")
end
local ACTIONS = {
start = abs_start,
restore = abs_restore,
yes = abs_yes,
no = abs_no,
}
concommand.Add("abs", function(_, _, args, full)
local wat = args[1]
local func = ACTIONS[wat]
if not func then
if wat ~= "help" then
print("Unknown command '" .. wat .."'")
end
print("Available commands:")
print("", "help")
for key in pairs(ACTIONS) do
print("", key)
end
return
end
func(unpack(args, 2))
end)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment