Skip to content

Commit

Permalink
feat: Basically the entire program
Browse files Browse the repository at this point in the history
  • Loading branch information
JordanViknar committed Nov 24, 2023
1 parent 6aeb988 commit 6de6247
Show file tree
Hide file tree
Showing 25 changed files with 2,286 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/subprojects/blueprint-compiler
88 changes: 88 additions & 0 deletions config_modules/configManager.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
-- Internal Modules
local gameConfigFolder = require("extra_modules.programMetadata").folders.gamesConfig
local fsUtils = require("general_modules.fsUtils")
local logSystem = require("general_modules.logSystem")

-- External Modules
local json = require("dkjson")

local configManager = {}

--[[
Name : function configManager.createGameConfig(gameId)
Description : Creates a config file for a game.
Arg 1 : gameId (string) : The game's Steam ID.
Return : The game's config.
]]
function configManager.createGameConfig(gameId)
-- Grab the default template
local gameConfig = require("config_modules.defaultConfigTemplate")
gameConfig.steamGameId = gameId

-- Write the new table to the file
local path = string.format("%s/%s.json", gameConfigFolder, gameId)
local file = assert(io.open(path, "w"), "Couldn't open " .. path .. " !")
file:write(json.encode(gameConfig, { indent = false }))
file:close()

return gameConfig
end

--[[
Name : configManager.getGameConfig(gameId)
Description : Retrieves a game's config.
Arg 1 : gameId (string) : The game's Steam ID.
]]
function configManager.getGameConfig(gameId)
-- Check if the config exists
local path = string.format("%s/%s.json", gameConfigFolder, gameId)
if not fsUtils.exists(path) then
local warningMsg = "Config for game " .. gameId .. " at " .. path .. " doesn't exist. Creating it..."
logSystem.log("warning", warningMsg)
return configManager.createGameConfig(gameId)
end

-- Read the file
local file = assert(io.open(path, "r"), "Couldn't open " .. path .. " !")
local fileContent = file:read("*a")
file:close()

return json.decode(fileContent)
end

--[[
Name : function configManager.modifyGameConfig(gameId, dataToChange)
Description : Modifies a game's config.
Arg 1 : gameId (string) : The game's Steam ID.
Arg 2 : dataToChange (string) : The data to change. Example : "steamGameId"
Arg 3 : value (any) : The value to set.
Return : The game's config.
]]
function configManager.modifyGameConfig(gameId, dataToChange, value)
-- Grab the original config data
local gameConfig = configManager.getGameConfig(gameId)

-- Parse dataToChange
local keys = {}
for substring in dataToChange:gmatch("[^.]+") do
keys[#keys + 1] = substring
end

-- Modify the table
local pointer = gameConfig
for i = 1, #keys - 1 do
pointer = pointer[keys[i]] or {}
end
pointer[keys[#keys]] = value

-- Write the new table
local path = string.format("%s/%s.json", gameConfigFolder, gameId)
local file = assert(io.open(path, "w"), "Couldn't open " .. path .. " !")

file:write(json.encode(gameConfig))
file:close()

return gameConfig
end

return configManager
10 changes: 10 additions & 0 deletions config_modules/defaultConfigTemplate.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
return
{
["config_version"] = 1;
["steamGameId"] = nil;
["utilities"] = {
["gamemode"] = {
["enabled"] = false;
}
}
}
47 changes: 47 additions & 0 deletions docs/STEAM_PROBLEMS_LIST.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
For *"fun"*, I have compiled a list of every single problem I consider shouldn't have happened that I've encountered anyways, going through Steam's configuration files.

I put them in order, from ***most annoying*** to *least annoying*.

This is meant to warn people to not create code that may cross these issues.

___

# Problems

## Linux games don't get removed from *compat.vdf* when going back from their Windows version.

### Conclusion

We have no way from Steam's configuration files to know if a game that ever had a Windows version installed now has its Linux version installed.

I looked everywhere I could for an alternative configuration file (or a file that could be useful through another method), but came empty-handed.

### Workaround

We check for Linux executables or shell scripts in every Windows game's root directory.

We previously used in a loop

```lua
os.execute("file "..location.."/"..file.." | grep -e 'Linux' -e 'shell' &> /dev/null")
```

to detect those.

However, ironically, this method led us into another problem later on, which required us to scrap the os.execute approach.

## *libraryfolders.vdf* doesn't get immediately updated upon game installation/uninstallation.

### Conclusion

This problem effectively makes it completely unreliable (and thus useless) to gather the game IDs stored inside it.

### Workaround

As a workaround, we look in the library folders for *appmanifest_ID.acf* files, and use them to guess the installed game IDs and gather the data.

## Steam can get stuck trying to start a game when using *os.execute* many times.

### Workaround

Use os.execute as little as possible if launching a game.
15 changes: 15 additions & 0 deletions extra_modules/programMetadata.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
return {
name="SimpleSteamTinker",
executable="sst",
version="indev",
developer="JordanViknar",
url = "https://github.com/JordanViknar/SimpleSteamTinker",
installdir = os.getenv("SST_SCRIPT_PATH"),
folders = {
config = os.getenv("HOME").."/.config/SimpleSteamTinker",
gamesConfig = os.getenv("HOME").."/.config/SimpleSteamTinker/games",

storage = os.getenv("HOME").."/.local/share/SimpleSteamTinker",
cache = os.getenv("HOME").."/.cache/SimpleSteamTinker"
}
}
179 changes: 179 additions & 0 deletions general_modules/fsUtils.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
-- External Modules
local lfs = require("lfs")

-- Internal Modules
local logSystem = require("general_modules.logSystem")

-- Module
local fsUtils = {}

--[[
Name : function fsUtils.exists(path)
Description : Checks if a file or directory exists.
Arg 1 : path (string) : The path to check.
Return : true if the file or directory exists, false otherwise.
]]
function fsUtils.exists(path)
local attributes, err = lfs.attributes(path)
if attributes then
return true
else
return false, err
end
end

--[[
Name : function fsUtils.createDirectory(path)
Description : Creates a directory.
Arg 1 : path (string) : The path to create.
Return : true if the directory was created, false otherwise.
]]
function fsUtils.createDirectory(path)
local success, err = lfs.mkdir(path)
if success then
return true
else
return false, err
end
end

--[[
Name : function fsUtils.createOrUseDirectory(path)
Description : Creates a directory if it doesn't exist, or uses it if it does.
Arg 1 : path (string) : The path to create or use.
Return : true if the directory was created or used, false otherwise.
]]
function fsUtils.createOrUseDirectory(path)
if not fsUtils.exists(path) then
logSystem.log("debug", "Directory "..path.." not found. Creating it...")
return fsUtils.createDirectory(path)
else
return true
end
end

--[[
Name : function fsUtils.getSize(path)
Description : Gets the size of a file or directory.
Arg 1 : path (string) : The path to get the size of.
Return : The size of the file or directory.
]]
function fsUtils.getSize(path)
local totalSize = 0
local stack = { path } -- Initialize a stack with the initial path

while #stack > 0 do
local currentPath = table.remove(stack) -- Get the last path from the stack

local attributes = lfs.attributes(currentPath)
if attributes then
if attributes.mode == "file" then
totalSize = totalSize + attributes.size -- Add file size to total size
elseif attributes.mode == "directory" then
for file in lfs.dir(currentPath) do
if file ~= "." and file ~= ".." then
local filePath = currentPath .. "/" .. file
table.insert(stack, filePath) -- Add subdirectories to the stack
end
end
end
end
end

return totalSize
end

--[[
Name : function fsUtils.sizeToUnit(size)
Description : Converts a size in bytes to a human-readable size.
Arg 1 : size (number) : The size to convert.
Return : The human-readable size.
]]
function fsUtils.sizeToUnit(size)
local unit = "B"
if size > 1024 then
size = size / 1024
unit = "KB"
end
if size > 1024 then
size = size / 1024
unit = "MB"
end
if size > 1024 then
size = size / 1024
unit = "GB"
end
if size > 1024 then
size = size / 1024
unit = "TB"
end
return string.format("%.2f", size).." "..unit
end

--[[
Name : function fsUtils.directoryContainsLinuxData(location)
Description : Checks if a directory contains Linux data.
Arg 1 : location (string) : The directory to check.
Return : true if the directory contains Linux data, false otherwise.
Note : Used to for game platform detection to work around Steam not cleaning up the compat.vdf file when going back from the Windows version of a game to the Linux version.
Note 2 : The reason we don't use os.execute(file) is because Steam can get stuck while starting a game for whatever reason.
]]
-- Alternative to using file command
local function checkFileForKeywords(location, file)
local filepath = location .. "/" .. file

local fileHandle = io.open(filepath, "r")
if not fileHandle then
return false
end

local foundKeywords = false

for line in fileHandle:lines() do
if line:find("Linux") or line:find("shell") then
foundKeywords = true
break
end
end

fileHandle:close()

return foundKeywords
end

function fsUtils.directoryContainsLinuxData(location)
for file in lfs.dir(location) do
if (file:find(".sh") or not file:find(".")) and (file ~= "." or "..") then
local containsKeywords = checkFileForKeywords(location, file)
if containsKeywords then
return true
end
end
end

return false
end

--[[
Name : function fsUtils.getFilenamePatternInDirectory(directory, pattern)
Description : Returns a table containing every filename matching a pattern in a directory.
Arg 1 : directory (string) : The directory to search in.
Arg 2 : pattern (string) : The pattern to search for.
Return : A table containing every filename matching the pattern.
Note : Used to for game detection to work around Steam not updating the libraryfolders.vdf file immediately.
]]
function fsUtils.getFilenamePatternInDirectory(directory, pattern)
local result = {}

for file in lfs.dir(directory) do
if file:match(pattern) then
table.insert(result, file)
end
end

return result
end

return fsUtils
Loading

0 comments on commit 6de6247

Please sign in to comment.