├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── manifest_generator.c ├── manifest_generator.h ├── parser.c ├── parser.h ├── steam_injector.c ├── steamcurses.c └── steamcurses.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Libraries 12 | *.lib 13 | *.a 14 | *.la 15 | *.lo 16 | 17 | # Shared objects (inc. Windows DLLs) 18 | *.dll 19 | *.so 20 | *.so.* 21 | *.dylib 22 | 23 | # Executables 24 | *.exe 25 | *.out 26 | *.app 27 | *.i*86 28 | *.x86_64 29 | *.hex 30 | 31 | *.swp 32 | *.ycm* 33 | # The Actual Executable 34 | steamcurses 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 ReedMullanix 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: injector final 2 | 3 | install: injector final 4 | install -m 0755 steamcurses /usr/bin 5 | install -m 0755 steam_injector.so /usr/lib32 6 | 7 | injector: steam_injector.c 8 | gcc -m32 -shared -fPIC steam_injector.c -o steam_injector.so -ldl -lX11 9 | 10 | debug: parser.c parser.h steamcurses.c steamcurses.h manifest_generator.c manifest_generator.h 11 | gcc -g -Wall -Wextra -std=c11 -o steamcurses steamcurses.c parser.c manifest_generator.c -lmenu -lncurses 12 | 13 | final: parser.c parser.h steamcurses.c steamcurses.h manifest_generator.c manifest_generator.h 14 | gcc -std=c11 -s -o steamcurses steamcurses.c parser.c manifest_generator.c -lmenu -lncurses 15 | 16 | 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SteamCurses 2 | A basic NCurses client for Steam on Linux 3 | 4 | **This version will be replaced by https://github.com/TOTBWF/SteamCurses/tree/rust-rewrite** 5 | 6 | 7 | ### Features: 8 | - Supports both wine and native games at the same time 9 | 10 | ### Building: 11 | **Dependencies:** 12 | * Steam 13 | * ncurses-dev package 14 | 15 | 16 | For Ubuntu flavors: 17 | * ```sudo apt-get install steam``` 18 | 19 | * ```sudo apt-get install libncurses5-dev``` 20 | 21 | Arch: 22 | * ```sudo pacman -S ncurses``` 23 | 24 | * ```sudo pacman -S steam``` 25 | 26 | * It can also be found under the AUR as steamcurses-git 27 | 28 | Simply run ```make install``` to build and install. 29 | 30 | ### Usage: 31 | ``` 32 | ./steamcurses -u [options] 33 | -u --username: your Steam username 34 | -p --steam_path: the path to your steamapps directory 35 | -w --wine_steam_path: the path to your wine steamapps directory 36 | -m --update_manifests: update the generated manifests 37 | -h --help: print help message 38 | ``` 39 | -------------------------------------------------------------------------------- /manifest_generator.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "parser.h" 9 | #include "steamcurses.h" 10 | 11 | void init_generator(char* path) { 12 | // Check to see if the manifest path exists 13 | struct stat st; 14 | if(stat(path, &st) == -1) { 15 | // Create the dir if it doesnt exits 16 | mkdir(path, 0755); 17 | } 18 | } 19 | 20 | void generate_manifests(char* path, game_t** games, int size) { 21 | fprintf(g_logfile, "Begining Manifest Generation...\n"); 22 | // Make sure the path actually exists 23 | init_generator(path); 24 | 25 | for(int i = 0; i < size; i++) { 26 | printf("Generating Manifest %d out of %d\n", i + 1, size); 27 | // Fetch the appid 28 | char* appid = fetch_value(games[i]->key_value_pairs, "appid", games[i]->size); 29 | // Get the game install dir 30 | char* installdir = fetch_value(games[i]->key_value_pairs, "installdir", games[i]->size); 31 | // Get the games steam path 32 | char* steam_path = games[i]->steam_path; 33 | int is_wine = games[i]->is_wine; 34 | // Create the full path 35 | char* full_path; 36 | asprintf(&full_path, "%s%s.manifest", path, appid); 37 | 38 | fprintf(g_logfile, "Checking file %s...\n", full_path); 39 | // Check to see if the file exists 40 | if(access(full_path, F_OK) == -1) { 41 | fprintf(g_logfile, "Creating file %s...\n", full_path); 42 | 43 | /* 44 | KLUDGE ALERT!!! 45 | We need to get the path to the games executable 46 | the only way we can do this is to use and abuse steamcmd 47 | The cmd app_info_print dumps all configs for a given appid 48 | We are going to pipe that into a manifest file , then parse it to get the details 49 | The full command to do so is as follows (command will be passed to shell) 50 | steamcmd +app_info_print +quit 1> 2> 51 | */ 52 | 53 | char* cmd; 54 | asprintf(&cmd, "steamcmd +app_info_print %s +quit 1>%s 2>%s", appid, full_path, full_path); 55 | fprintf(g_logfile, "Generated steamcmd: %s\n", cmd); 56 | system(cmd); 57 | free(cmd); 58 | } 59 | // Parse the generated file 60 | int size = 0; 61 | kvp_t** gen_manifest = parse_manifest(full_path, &size); 62 | // Find the executable path 63 | // The heirarchy for the executable paths is as follows: 64 | // "" "config" "launch" "" "executable" 65 | // We also need to find launch options, which are located under the arguments key at 66 | // "" "config" "launch" "" "arguments" 67 | // To find the right executable, we have to look for the right oslist value (linux for linux, windows for wine) 68 | // That is located here: 69 | // "" "config" "launch" " "config" "oslist" 70 | // The procedure to get the executable path is then: 71 | // 1) get a listing of all possible options in kvp_t** form 72 | // 2) iterate through and recursivley search for the matching oslist value 73 | // 3) use the executable value to get part of the path 74 | // 4) append that to the game directory, found in the normal appmanifest files under the installdir key 75 | // 5) append that to the steam_path 76 | // At this point you are probably thinking, WTF, why??? 77 | // Me too.... me too... 78 | 79 | // First get a listing of all options in kvp_t form 80 | kvp_t* match_appids = fetch_match(gen_manifest, appid, size); 81 | kvp_t* match_config = fetch_match(match_appids->children, "config", match_appids->num_children); 82 | if(match_config == NULL) { 83 | printf("Parsing Error, %s could not be found in %s\n", "config", full_path); 84 | } 85 | // There should only be one 86 | kvp_t* match_launch = fetch_match(match_config->children, "launch", match_config->num_children); 87 | 88 | char* executable = NULL; 89 | char* launch_options = NULL; 90 | 91 | // Iterate through and search for a valid launch option 92 | for(int j = 0; j < match_launch->num_children; j++) { 93 | kvp_t* curr_child = match_launch->children[j]; 94 | // Recursively search for the oslist value for each child 95 | char* os_list = fetch_value(curr_child->children, "oslist", curr_child->num_children); 96 | if((is_wine && strcmp(os_list, "windows") == 0) || ((!is_wine) && strcmp(os_list, "linux") == 0)) { 97 | // we found the correct listing, so use the index to search for the executable path and the launch options 98 | executable = fetch_value(curr_child->children, "executable", curr_child->num_children); 99 | launch_options = fetch_value(curr_child->children, "arguments", curr_child->num_children); 100 | } 101 | } 102 | 103 | // Build the full executable path 104 | char* full_exec_path; 105 | asprintf(&full_exec_path, "%scommon/%s/%s %s", steam_path, installdir, executable, launch_options); 106 | fprintf(g_logfile, "Full Exec Path:%s\n", full_exec_path); 107 | games[i]->exec_path = full_exec_path; 108 | 109 | // Free all of the kvps that we created 110 | for(int j = 0; j < size; j++) { 111 | free_kvp(gen_manifest[j]); 112 | } 113 | free(gen_manifest); 114 | free(full_path); 115 | } 116 | } 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /manifest_generator.h: -------------------------------------------------------------------------------- 1 | // Creates full manifests for all given games using steamcmd, and fills in the game executable path 2 | void generate_manifests(char* path, game_t** games, int size); 3 | -------------------------------------------------------------------------------- /parser.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "parser.h" 7 | #include "steamcurses.h" 8 | 9 | // Sorts a given list of games alphabetacally 10 | void sort_games(game_t** games, int size) { 11 | for(int i = 1; i < size; i++) { 12 | for(int j = 1; j < size; j++) { 13 | // Do the first level of search, and select the first result 14 | // We dont need to use the output size so just toss it 15 | char* name_curr = fetch_value(games[j - 1]->key_value_pairs, "name", games[j - 1]->size); 16 | char* name_next = fetch_value(games[j]->key_value_pairs, "name", games[j]->size); 17 | // Find the names in the new kvp 18 | if(strcmp(name_curr, name_next) > 0) { 19 | game_t* tmp = games[j]; 20 | games[j] = games[j - 1]; 21 | games[j - 1] = tmp; 22 | } 23 | } 24 | } 25 | } 26 | 27 | // Fetches the first Key Value Pair that matches the key from an array of Key Value Pairs, NOT RECURSIVE 28 | kvp_t* fetch_match(kvp_t** pairs, char* key, int size) { 29 | int index = 0; 30 | 31 | while(index < size) { 32 | if(strcmp(pairs[index]->key, key) == 0) { 33 | return pairs[index]; 34 | } 35 | index++; 36 | } 37 | return NULL; 38 | } 39 | 40 | // Fetches a list of Key Value pairs that match the key, NOT RECURSIVE 41 | kvp_t** fetch_matching(kvp_t** pairs, char* key, int size, int* output_size) { 42 | int capacity = 5; 43 | kvp_t** matches = (kvp_t**)malloc(capacity*sizeof(kvp_t**)); 44 | int index = 0; 45 | 46 | while(index < size) { 47 | if(strcmp(pairs[index]->key, key) == 0) { 48 | matches[*output_size] = pairs[index]; 49 | *output_size += 1; 50 | 51 | // Check to see if the list needs to be resized 52 | if(*output_size >= capacity) { 53 | capacity *= 2; 54 | matches = (kvp_t**)realloc(matches, capacity*sizeof(kvp_t*)); 55 | } 56 | } 57 | index++; 58 | } 59 | return matches; 60 | } 61 | 62 | // Fetches a list of Values from a list of Key Value Pairs that match the key, RECURSIVE 63 | char** fetch_values(kvp_t** pairs, char* key, int size, int* output_size) { 64 | int capacity = 5; 65 | char** matches = (char**)malloc(capacity*sizeof(char*)); 66 | int index = 0; 67 | 68 | while(index < size) { 69 | // If there is no value, then this is a parent 70 | if(pairs[index]->value == NULL) { 71 | // Recursively search 72 | int s = 0; 73 | char** out = fetch_values(pairs[index]->children, key, pairs[index]->num_children, &s); 74 | // Increase capacity and copy any matches 75 | capacity += s; 76 | matches = (char**)realloc(matches, capacity*sizeof(char*)); 77 | memcpy(matches[*output_size], out, s); 78 | *output_size += s; 79 | } else { 80 | if(strcmp(pairs[index]->key, key) == 0) { 81 | matches[*output_size] = pairs[index]->value; 82 | *output_size += 1; 83 | 84 | // Check to see if the list needs to be resized 85 | if(*output_size >= capacity) { 86 | capacity *= 2; 87 | matches = (char**)realloc(matches, capacity*sizeof(char*)); 88 | } 89 | } 90 | } 91 | index++; 92 | } 93 | return matches; 94 | } 95 | 96 | // Fetches the first occurance of a key from an array of Key Value Pairs, RECURSIVE 97 | char* fetch_value(kvp_t** pairs, char* key, int size) { 98 | int index = 0; 99 | while(index < size) { 100 | // If there is no value, then this is a parent 101 | if(pairs[index]->value == NULL) { 102 | // Recursively search 103 | char* out = fetch_value(pairs[index]->children, key, pairs[index]->num_children); 104 | if(out != NULL) { 105 | return out; 106 | } 107 | } else { 108 | if(strcmp(pairs[index]->key, key) == 0) { 109 | return pairs[index]->value; 110 | } 111 | } 112 | index++; 113 | } 114 | return NULL; 115 | } 116 | 117 | 118 | kvp_t* parse_line(char* line) { 119 | kvp_t* key_pair = (kvp_t*)malloc(sizeof(kvp_t)); 120 | key_pair->key = NULL; 121 | key_pair->value = NULL; 122 | int index = 0; 123 | int open_quote = 0; 124 | int closed_quote = 0; 125 | int quote_num = 0; 126 | 127 | while(line[index] != '\0') { 128 | if(line[index] == '\"') { 129 | if(++quote_num % 2 == 0) { 130 | closed_quote = index; 131 | if(quote_num == 2) { 132 | char* key = (char*)malloc(sizeof(char)*(closed_quote - open_quote + 1)); 133 | strncpy(key, &line[open_quote], closed_quote - open_quote); 134 | key[closed_quote - open_quote] = '\0'; 135 | key_pair->key = key; 136 | } else if(quote_num == 4) { 137 | char* val = (char*)malloc(sizeof(char)*(closed_quote - open_quote + 1)); 138 | strncpy(val, &line[open_quote], closed_quote - open_quote); 139 | val[closed_quote - open_quote] = '\0'; 140 | key_pair->value = val; 141 | } 142 | } else { 143 | open_quote = index + 1; 144 | } 145 | } 146 | index++; 147 | } 148 | // If there arent 4 quotes (1 key and 1 value), return null 149 | if(key_pair->key == NULL || key_pair->value == NULL) { 150 | key_pair = NULL; 151 | } 152 | return key_pair; 153 | } 154 | 155 | kvp_t** parse_lines(FILE* f, int* size, int* capacity) { 156 | // Allocate arrau of kvps 157 | kvp_t** pairs = (kvp_t**) malloc(*capacity*sizeof(kvp_t*)); 158 | 159 | char buf[1024]; 160 | while(fgets(buf, sizeof(buf), f)) { 161 | // Start iterating through the line 162 | kvp_t* key_pair = (kvp_t*)malloc(sizeof(kvp_t)); 163 | key_pair->key = NULL; 164 | key_pair->value = NULL; 165 | key_pair->children = NULL; 166 | key_pair->num_children = 0; 167 | key_pair->capacity_children = 0; 168 | 169 | int index = 0; 170 | int open_quote = 0; 171 | int closed_quote = 0; 172 | int quote_num = 0; 173 | 174 | // Iterate through the line 175 | while(buf[index]) { 176 | if(buf[index] == '}') { 177 | // Check to see if next char is EOL 178 | if(buf[index + 1] == '\n') { 179 | // End parent object, so break out of recursion 180 | // Free the generated line 181 | free(key_pair); 182 | return pairs; 183 | } 184 | } else if(buf[index] == '\"') { 185 | if(++quote_num % 2 == 0) { 186 | closed_quote = index; 187 | if(quote_num == 2) { 188 | char* key = (char*)malloc(sizeof(char)*(closed_quote - open_quote + 1)); 189 | strncpy(key, &buf[open_quote], closed_quote - open_quote); 190 | key[closed_quote - open_quote] = '\0'; 191 | key_pair->key = key; 192 | } else if(quote_num == 4) { 193 | char* val = (char*)malloc(sizeof(char)*(closed_quote - open_quote + 1)); 194 | strncpy(val, &buf[open_quote], closed_quote - open_quote); 195 | val[closed_quote - open_quote] = '\0'; 196 | key_pair->value = val; 197 | } 198 | } else { 199 | open_quote = index + 1; 200 | } 201 | } 202 | index++; 203 | } 204 | 205 | // Line contains important info 206 | if(key_pair->key != NULL) { 207 | 208 | // Add the new kvp to the array 209 | pairs[*size] = key_pair; 210 | *size += 1; 211 | 212 | // Check to see if the array needs to be resized 213 | if(*size >= *capacity) { 214 | *capacity *= 2; 215 | pairs = (kvp_t**) realloc(pairs, *capacity * sizeof(kvp_t)); 216 | } 217 | 218 | // If there is no value, then the new kvp is a parent 219 | if(key_pair->value == NULL) { 220 | // allocate space for the new parent 221 | // Recurse into the new parent 222 | fprintf(g_logfile, "PARSER: Recursing into Key %s\n", key_pair->key); 223 | key_pair->capacity_children = 10; 224 | key_pair->children = parse_lines(f, &(key_pair->num_children), &(key_pair->capacity_children)); 225 | fprintf(g_logfile, "PARSER: Recursing out of Key %s\n", key_pair->key); 226 | } else { 227 | fprintf(g_logfile, "PARSER: Adding Key %s with value %s\n", key_pair->key, key_pair->value); 228 | } 229 | } else { 230 | // We wont use this line, so free the kvp 231 | free(key_pair); 232 | } 233 | // Clear the buffer 234 | memset(buf, '\0', sizeof(buf)); 235 | } 236 | // We should never hit this point 237 | return pairs; 238 | } 239 | 240 | 241 | kvp_t** parse_manifest(char* path, int* size) { 242 | 243 | FILE* f = fopen(path, "r"); 244 | if(f == NULL) { 245 | printf("Error opening manifest file %s\n", path); 246 | exit(1); 247 | } 248 | int capacity = 100; 249 | kvp_t** key_pairs = parse_lines(f, size, &capacity); 250 | fclose(f); 251 | return key_pairs; 252 | } 253 | 254 | void parse_manifests(int* size, int* capacity, game_t*** games, char* steam_path, int is_wine) { 255 | // Set up directory streams and whatnot 256 | DIR* d; 257 | struct dirent* dir; 258 | d = opendir(steam_path); 259 | 260 | // Get all files in the steam path dir 261 | if(d) { 262 | while((dir = readdir(d)) != NULL) { 263 | // If the file is a manifest, prepare to parse 264 | if(strstr(dir->d_name, "appmanifest") != NULL) { 265 | 266 | // Create and fill out game struct 267 | game_t* g = (game_t*) malloc(sizeof(game_t)); 268 | g->steam_path = steam_path; 269 | g->is_wine = is_wine; 270 | g->size = 0; 271 | 272 | // Construct the full path to the file 273 | char* full_path; 274 | asprintf(&full_path, "%s%s", steam_path, dir->d_name); 275 | g->key_value_pairs = parse_manifest(full_path, &(g->size)); 276 | (*games)[*size] = g; 277 | *size += 1; 278 | 279 | // Free the memory used by the string 280 | free(full_path); 281 | full_path = NULL; 282 | 283 | //Resize array as needed 284 | if(*size >= *capacity) { 285 | *games = (game_t**) realloc(*games, *capacity*2*sizeof(game_t*)); 286 | *capacity *= 2; 287 | } 288 | } 289 | } 290 | closedir(d); 291 | } else { 292 | printf("Could not open steam directory %s\n", steam_path); 293 | } 294 | } 295 | 296 | 297 | void free_kvp(kvp_t* pair) { 298 | if(pair->num_children > 0) { 299 | for(int i = 0; i < pair->num_children; i++) { 300 | free_kvp(pair->children[i]); 301 | pair->children[i] = NULL; 302 | } 303 | free(pair->children); 304 | free(pair->key); 305 | } else { 306 | free(pair->key); 307 | free(pair->value); 308 | } 309 | // Null out all pointers 310 | pair->key = NULL; 311 | pair->value = NULL; 312 | pair->children = NULL; 313 | pair->num_children = 0; 314 | pair->capacity_children = 0; 315 | free(pair); 316 | } 317 | -------------------------------------------------------------------------------- /parser.h: -------------------------------------------------------------------------------- 1 | // Key Value Pairs 2 | typedef struct kvp_t { 3 | char* key; 4 | char* value; 5 | struct kvp_t** children; 6 | int num_children; 7 | int capacity_children; 8 | } kvp_t; 9 | 10 | typedef struct game { 11 | kvp_t** key_value_pairs; 12 | int size; 13 | char* exec_path; 14 | char* steam_path; 15 | int is_wine; 16 | } game_t; 17 | 18 | 19 | // Fetches the value of the first instance of a KVP with a matching key out of an array of KVPs, RECURSIVE 20 | char* fetch_value(kvp_t** pairs, char* key, int size); 21 | // Fetches all values associated with the given key out of an array of KVPs, RECURSIVE 22 | char** fetch_values(kvp_t** pairs, char* key, int size, int* output_size); 23 | // Fetches the kvp associated with the first instance of a matching key out of an array of KVPs, NOT RECURSIVE 24 | kvp_t* fetch_match(kvp_t** pairs, char* key, int size); 25 | // Fetches all kvp associated with a matching key out of an array of KVPs, NOT RECURSIVE 26 | kvp_t** fetch_matching(kvp_t** pairs, char* key, int size, int* output_size); 27 | // Parses a single manifest and turns it into an array of KVPs 28 | kvp_t** parse_manifest(char* path, int* size); 29 | // Parses all manifests in a given path and turns them into an array of game structs 30 | void parse_manifests(int* size, int* capacity, game_t*** games, char* steam_path, int is_wine); 31 | // Alphabetacally sorts an array of games 32 | void sort_games(game_t** games, int size); 33 | // Frees a kvp and all of its children 34 | void free_kvp(kvp_t* pair); 35 | -------------------------------------------------------------------------------- /steam_injector.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | int XMapWindow(Display* display, Window w) { 9 | printf("Window Mapping Blocked\n"); 10 | return 0; 11 | } 12 | 13 | -------------------------------------------------------------------------------- /steamcurses.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "parser.h" 13 | #include "steamcurses.h" 14 | #include "manifest_generator.h" 15 | 16 | void launch_game_cmd(char* cmd) { 17 | // Fork off the game on a new thread 18 | pid_t pid; 19 | if((pid = fork()) < 0) { 20 | perror("Forking Error!"); 21 | exit(1); 22 | } 23 | if(pid == 0) { 24 | // Pipe stderr -> stdout -> logfile 25 | dup2(fileno(g_logfile), fileno(stdout)); 26 | dup2(fileno(stdout), fileno(stderr)); 27 | system(cmd); 28 | exit(0); 29 | } 30 | } 31 | 32 | void launch_native_game(char* appid) { 33 | char* cmd; 34 | asprintf(&cmd, "steam -applaunch %s", appid); 35 | launch_game_cmd(cmd); 36 | free(cmd); 37 | } 38 | 39 | void launch_wine_game(char* appid, char* username, char* password) { 40 | char* cmd; 41 | asprintf(&cmd, "wine $WINEPREFIX/drive_c/Program\\ Files/Steam/Steam.exe -silent -login %s %s -applaunch %s", g_username, g_password, appid); 42 | launch_game_cmd(cmd); 43 | free(cmd); 44 | } 45 | 46 | void print_title(WINDOW* win, char* title) { 47 | int length = strlen(title); 48 | int x, y; 49 | getmaxyx(win, y, x); 50 | mvwprintw(win, 0, x/2 - length/2, title); 51 | refresh(); 52 | } 53 | 54 | void print_help() { 55 | printf("Usage: ./steamcurses [options]\n"); 56 | printf(" -c --config_path: use a given config for launch\n"); 57 | printf(" -m --update_manifests: update the generated manifest files\n"); 58 | printf(" -h --help: print this help message and exit\n"); 59 | printf("Config Options:\n"); 60 | printf(" username: the steam username to use. REQUIRED!\n"); 61 | printf(" steam_path: the path to the native steamapps directory.\n"); 62 | printf(" wine_steam_path: the path to the wine steamapps directory.\n"); 63 | } 64 | 65 | void print_menu(WINDOW* win, MENU* menu, game_t** games) { 66 | int c; 67 | // Only call getch once 68 | while((c = wgetch(win)) != 'q') { 69 | switch(c) { 70 | case KEY_DOWN: { 71 | menu_driver(menu, REQ_DOWN_ITEM); 72 | break; 73 | } 74 | case KEY_UP: { 75 | menu_driver(menu, REQ_UP_ITEM); 76 | break; 77 | } 78 | case 10: { 79 | game_t* curr_game = games[menu->curitem->index]; 80 | char* appid = fetch_value(curr_game->key_value_pairs, "appid", curr_game->size); 81 | if(curr_game->is_wine) { 82 | launch_wine_game(appid, g_username, g_password); 83 | } else { 84 | launch_native_game(appid); 85 | } 86 | break; 87 | } 88 | } 89 | } 90 | } 91 | 92 | WINDOW* init_ncurses() { 93 | // NCurses stuff 94 | initscr(); 95 | cbreak(); 96 | noecho(); 97 | keypad(stdscr, TRUE); 98 | 99 | // Get the max size of the window, and init windows 100 | WINDOW* win = newwin(LINES - 3, COLS - 1, 0, 0); 101 | // q 102 | // Give the menu window focus 103 | keypad(win, TRUE); 104 | return win; 105 | } 106 | 107 | 108 | ITEM** init_items(game_t** games, int size) { 109 | // Populate the array of items 110 | ITEM **items = (ITEM**)calloc(size + 1, sizeof(ITEM*)); 111 | for (int i = 0; i < size; i++) { 112 | char* name = fetch_value(games[i]->key_value_pairs, "name", games[i]->size); 113 | if(!games[i]->is_wine) { 114 | items[i] = new_item(name, NULL); 115 | } else { 116 | items[i] = new_item(name, "(Wine)"); 117 | } 118 | items[i]->index = i; 119 | } 120 | items[size] = (ITEM*) NULL; 121 | return items; 122 | } 123 | 124 | MENU* init_menu(ITEM** items, WINDOW* win) { 125 | MENU* menu = new_menu((ITEM**)items); 126 | set_menu_format(menu, LINES - 4, 0); 127 | set_menu_win(menu, win); 128 | set_menu_sub(menu, derwin(win, LINES - 4, COLS - 2, 1, 0)); 129 | 130 | // Make things pretty 131 | mvprintw(LINES - 1, 0, "q to exit"); 132 | char title[] = "SteamCurses"; 133 | print_title(win, title); 134 | refresh(); 135 | 136 | // Post the menu 137 | post_menu(menu); 138 | wrefresh(win); 139 | return menu; 140 | } 141 | 142 | void init(char** home, char** steamcurses_dir) { 143 | // The current home dir, used to generate default locations 144 | *home = getenv("HOME"); 145 | if(home == NULL) { 146 | printf("Error! $HOME is not valid or null\n"); 147 | exit(1); 148 | } 149 | 150 | // Set the directory that we use to store files 151 | asprintf(steamcurses_dir, "%s/.steamcurses/", *home); 152 | 153 | // Make sure that directory exists, and if it doesnt, create it 154 | struct stat st; 155 | if(stat(*steamcurses_dir, &st) == -1) { 156 | // Create the dir if it doesnt exits 157 | mkdir(*steamcurses_dir, 0755); 158 | } 159 | 160 | // Set the log path, and init logging 161 | char* log_path; 162 | asprintf(&log_path, "%s/steamcurses.log", *steamcurses_dir); 163 | g_logfile = fopen(log_path, "a"); 164 | free(log_path); 165 | } 166 | 167 | int main(int argc, char* argv[]) { 168 | char* steam_path = NULL; 169 | char* wine_steam_path = NULL; 170 | char* steamcurses_dir = NULL; 171 | char* config_path = NULL; 172 | char* home = NULL; 173 | g_username = NULL; 174 | g_password = NULL; 175 | int update_manifests = 0; 176 | FILE* config_file = NULL; 177 | 178 | // Perform basic init 179 | init(&home, &steamcurses_dir); 180 | 181 | // Deal with user input 182 | for(int i = 1; i < argc; i++) { 183 | if(strcmp(argv[i], "-m") == 0 || strcmp(argv[i], "--update_manifests") == 0) { 184 | update_manifests = 1; 185 | } else if(strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "--config_path") == 0) { 186 | config_path = argv[++i]; 187 | } else { 188 | print_help(); 189 | exit(0); 190 | } 191 | } 192 | 193 | // If we arent provided a config path, then look in the default location 194 | if(config_path == NULL) { 195 | asprintf(&config_path, "%s/steamcurses.conf", steamcurses_dir); 196 | } 197 | 198 | // Check to see if the config file actually exists 199 | if(access(config_path, F_OK) == -1) { 200 | // If the file does not exist, create the file and fill out fields 201 | fprintf(g_logfile, "Creating config file %s...\n", config_path); 202 | config_file = fopen(config_path, "a+"); 203 | // Construct the opening skeleton of the file 204 | fprintf(config_file, "\"config\"\n{\n"); 205 | char buf[1024]; 206 | printf(config_path, "Config File created at path %s\n"); 207 | printf("Enter your steam username: "); 208 | scanf("%s", buf); 209 | fprintf(config_file, " \"username\" \"%s\"\n", buf); 210 | // Fill out the rest of the file 211 | fprintf(config_file, "}\n"); 212 | // Let the user know about the other options 213 | printf("You can use this config file to specify the path to your steamapp directorys\n"); 214 | printf("Just use the steam_path and wine_steam_path options!\n"); 215 | } else { 216 | // The file already exists, so just open it for reading 217 | config_file = fopen(config_path, "r"); 218 | } 219 | 220 | // Read the values from the config 221 | int config_size = 0; 222 | kvp_t** config_kvp = parse_manifest(config_path, &config_size); 223 | g_username = fetch_value(config_kvp, "username", config_size); 224 | steam_path = fetch_value(config_kvp, "steam_path", config_size); 225 | wine_steam_path = fetch_value(config_kvp, "wine_steam_path", config_size); 226 | 227 | // If we don't get provided input, make an educated guess or throw errors 228 | if(steam_path == NULL) { 229 | asprintf(&steam_path, "%s/.steam/steam/steamapps/", home); 230 | } 231 | 232 | if(g_username == NULL) { 233 | printf("Error, no username provided!\n"); 234 | print_help(); 235 | exit(1); 236 | } 237 | 238 | g_password = (char*) getpass("Password: "); 239 | 240 | pid_t pid; 241 | 242 | // Fork off the steam process 243 | if((pid=fork()) < 0) { 244 | perror("Forking Error!"); 245 | exit(1); 246 | } 247 | 248 | if(pid == 0) { 249 | // Figure out path to the executable, used for loading the injector 250 | 251 | char* bin_path; 252 | asprintf(&bin_path, "%s/.steam/bin", home); 253 | fprintf(g_logfile, "%s\n", bin_path); 254 | 255 | // Pipe stderr -> stdout -> logfile 256 | dup2(fileno(g_logfile), fileno(stdout)); 257 | dup2(fileno(stdout), fileno(stderr)); 258 | // Set up launch command 259 | char* cmd; 260 | asprintf(&cmd, "LD_PRELOAD=/usr/lib32/steam_injector.so LD_LIBRARY_PATH=%s %s/steam -silent -login %s %s", bin_path, bin_path, g_username, g_password); 261 | system(cmd); 262 | exit(0); 263 | } else { 264 | // Parent Process 265 | // Parse the manifests to get a list of installed games 266 | int size = 0; 267 | int capacity = 100; 268 | game_t** games = (game_t**) malloc(capacity * sizeof(game_t*)); 269 | parse_manifests(&size, &capacity, &games, steam_path, 0); 270 | if(wine_steam_path) { 271 | parse_manifests(&size, &capacity, &games, wine_steam_path, 1); 272 | } 273 | sort_games(games, size); 274 | // We use these generated manifests to view interesting info about the games 275 | if(update_manifests) { 276 | // Create a folder specifically for the generated manifests 277 | char* manifest_path; 278 | asprintf(&manifest_path, "%s/manifests/", steamcurses_dir); 279 | printf("Generating manifests, this can take a while if you have a lot of games...\n"); 280 | generate_manifests(manifest_path, games, size); 281 | printf("Manifest generation complete\n"); 282 | free(manifest_path); 283 | } 284 | 285 | WINDOW* win; 286 | ITEM** my_items; 287 | MENU* my_menu; 288 | 289 | // Set up ncurses 290 | win = init_ncurses(); 291 | 292 | // Populate the array of items 293 | my_items = init_items(games, size); 294 | 295 | // Set up the menu 296 | my_menu = init_menu(my_items, win); 297 | 298 | // Display the menu 299 | print_menu(win, my_menu, games); 300 | 301 | // Perform clean up 302 | unpost_menu(my_menu); 303 | free_menu(my_menu); 304 | for(int i = 0; i < size; i++) { 305 | for(int j = 0; j < games[i]->size; j++) { 306 | free_kvp(games[i]->key_value_pairs[j]); 307 | } 308 | free(games[i]->key_value_pairs); 309 | free(games[i]); 310 | free_item(my_items[i]); 311 | } 312 | free(config_path); 313 | free_kvp(*config_kvp); 314 | free(config_kvp); 315 | fclose(g_logfile); 316 | // Free Strings that were allocated 317 | free(steam_path); 318 | if(wine_steam_path != NULL) { 319 | free(wine_steam_path); 320 | } 321 | 322 | free(games); 323 | free(my_items); 324 | free(steamcurses_dir); 325 | games = NULL; 326 | my_items = NULL; 327 | steam_path = NULL; 328 | endwin(); 329 | system("steam -shutdown 1>/dev/null 2>/dev/null"); 330 | return 0; 331 | } 332 | } 333 | 334 | -------------------------------------------------------------------------------- /steamcurses.h: -------------------------------------------------------------------------------- 1 | FILE* g_logfile; 2 | char* g_username; 3 | char* g_password; 4 | --------------------------------------------------------------------------------