Skip to content

Commit

Permalink
chore: initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
ark120202 committed Jan 17, 2019
0 parents commit ddbe550
Show file tree
Hide file tree
Showing 12 changed files with 1,001 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
/lib
7 changes: 7 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
language: node_js
node_js: '10'
cache: yarn

script:
- yarn build
- yarn lint
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# find-steam-app

> Find location of an installed Steam app
## Usage

```js
import {
findSteam,
findSteamAppById,
findSteamAppByName,
findSteamAppManifest,
findSteamLibraries,
} from 'find-steam-app';

await findSteamAppById(570);
// => '/path/to/steam/steamapps/common/dota 2 beta'

await findSteamAppByName('dota 2 beta');
// => '/path/to/steam/steamapps/common/dota 2 beta'

await findSteam();
// => '/path/to/steam'

await findSteamLibraries();
// => ['/path/to/steam/steamapps', '/path/to/library/steamapps']

await findSteamAppManifest(570);
// => { appid: 570, Universe: 1, name: 'Dota 2', ... }
```

For more information about manifest, see [manifest.ts](src/manifest.ts)
49 changes: 49 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"name": "find-steam-app",
"version": "1.0.0",
"description": "Find location of an installed Steam app",
"keywords": [
"app",
"appid",
"search",
"steam",
"valve"
],
"repository": "https://github.com/ark120202/find-steam-app",
"license": "MIT",
"author": "ark120202",
"files": [
"lib"
],
"main": "lib/index.js",
"types": "lib/index.d.ts",
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"lint": "tslint -p .",
"prepublishOnly": "yarn build"
},
"prettier": {
"printWidth": 100,
"singleQuote": true,
"trailingComma": "all"
},
"dependencies": {
"execa": "^1.0.0",
"fs-extra": "^7.0.1",
"p-filter": "^1.0.0",
"tslib": "^1.9.3",
"vdf-extra": "^2.2.2"
},
"devDependencies": {
"@ark120202/tslint-config": "^2.1.1",
"@ark120202/typescript-config": "^1.4.2",
"@types/execa": "^0.9.0",
"@types/fs-extra": "^5.0.4",
"prettier": "^1.15.3",
"tslint": "^5.12.1",
"tslint-plugin-prettier": "^2.0.1",
"typescript": "^3.2.2",
"typescript-tslint-plugin": "^0.2.1"
}
}
67 changes: 67 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import fs from 'fs-extra';
import pFilter from 'p-filter';
import path from 'path';
import { loadSteamLibraries } from './libraries';
import { AppManifest, hasManifest, readManifest } from './manifest';
import { findSteam } from './steam';

export { AppManifest, findSteam };

export class SteamNotFoundError extends Error {
public constructor() {
super('Steam installation directory not found');
this.name = 'SteamNotFoundError';
}
}

/**
* Searches for all local Steam libraries.
*
* @returns Array of paths to library folders.
*/
export async function findSteamLibraries() {
const steam = await findSteam();
if (steam == null) throw new SteamNotFoundError();

return loadSteamLibraries(steam);
}

/**
* Searches for app in local Steam libraries.
*
* @returns Information about installed application.
*/
export async function findSteamAppManifest(appId: number) {
const libs = await findSteamLibraries();
const [library] = await pFilter(libs, lib => hasManifest(lib, appId));
if (library == null) return;

return readManifest(library, appId);
}

/**
* Searches for app in local Steam libraries.
*
* @returns Path to installed app.
*/
export async function findSteamAppByName(name: string) {
const libs = await findSteamLibraries();
const [library] = await pFilter(libs, lib => fs.pathExists(path.join(lib, 'common', name)));
if (library == null) return;

return path.join(library, 'common', name);
}

/**
* Searches for app in local Steam libraries.
*
* @returns Path to installed app.
*/
export async function findSteamAppById(appId: number) {
const libs = await findSteamLibraries();
const [library] = await pFilter(libs, lib => hasManifest(lib, appId));
if (library == null) return;

const manifest = await readManifest(library, appId);
return path.join(library, 'common', manifest.installdir);
}
22 changes: 22 additions & 0 deletions src/libraries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import fs from 'fs-extra';
import path from 'path';
import vdf from 'vdf-extra';

interface LibraryFolders {
TimeNextStatsReport: string;
ContentStatsID: string;
[id: string]: string;
}

export async function loadSteamLibraries(steam: string) {
const mainSteamApps = path.join(steam, 'steamapps');
const libraryFoldersPath = path.join(mainSteamApps, 'libraryfolders.vdf');
const libraryFoldersContent = await fs.readFile(libraryFoldersPath, 'utf8');
const libraryFoldersData = vdf.parse<LibraryFolders>(libraryFoldersContent);

const libraries = Object.entries(libraryFoldersData)
.filter(([id]) => !isNaN(Number(id)))
.map(([, libPath]) => path.join(libPath, 'steamapps'));

return [mainSteamApps, ...libraries];
}
45 changes: 45 additions & 0 deletions src/manifest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import fs from 'fs-extra';
import path from 'path';
import vdf from 'vdf-extra';

export interface AppManifest {
appid: number;
Universe: number;
name: string;
StateFlags: number;
installdir: string;
LastUpdated: number;
UpdateResult: 0 | 1;
SizeOnDisk: number;
buildid: number;
LastOwner: string;
BytesToDownload: number;
BytesDownloaded: number;
AutoUpdateBehavior: 0 | 1;
AllowOtherDownloadsWhileRunning: 0 | 1;
ScheduledAutoUpdate: number;
UserConfig: {
language: string;
DisabledDLC?: string;
optionaldlc?: string;
};
InstalledDepots: Record<string, { manifest: string; dlcappid?: number }>;
MountedDepots: Record<string, string>;
InstallScripts?: Record<string, string>;
ShaderDepot?: {
ManifestID: string;
DepotSize: number;
};
}

export async function hasManifest(library: string, appid: number) {
return fs.pathExists(path.join(library, `appmanifest_${appid}.acf`));
}

export async function readManifest(library: string, appid: number) {
const manifestPath = path.join(library, `appmanifest_${appid}.acf`);
const manifestContent = await fs.readFile(manifestPath, 'utf8');
const manifestData = vdf.parse<AppManifest>(manifestContent);

return manifestData;
}
53 changes: 53 additions & 0 deletions src/steam.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import execa from 'execa';
import fs from 'fs-extra';
import path from 'path';

const pathIfExists = async (name: string) => ((await fs.pathExists(name)) ? name : undefined);
const getRegExePath = () =>
process.platform === 'win32' && process.env.windir != null
? path.join(process.env.windir, 'System32', 'reg.exe')
: 'REG';

const REG_TREE_PATH = 'HKCU\\Software\\Valve\\Steam';
const REG_KEY_NOT_FOUND = 'The system was unable to find the specified registry key or value';
async function windows() {
let programFiles = process.env['ProgramFiles(x86)'];
if (programFiles == null) programFiles = process.env.ProgramFiles;
if (programFiles != null && (await fs.pathExists(`${programFiles}/Steam/Steam.exe`))) {
return `${programFiles}/Steam`;
}

try {
const output = await execa.stdout(
getRegExePath(),
['QUERY', REG_TREE_PATH, '/v', 'SteamPath'],
{ cwd: undefined },
);
const matches = output.match(/SteamPath\s+[A-Z_]+\s+(.+)/);
if (!matches || !matches[1]) throw new Error(`Unexpected output:\n${output.trim()}`);

return pathIfExists(matches[1]);
} catch (err) {
if (!err.message.includes(REG_KEY_NOT_FOUND)) {
throw err;
}
}
}

/**
* Searches for Steam.
*
* @returns Location of Steam. `undefined` if Steam wasn't found.
*/
export async function findSteam() {
switch (process.platform) {
case 'win32':
return windows();
case 'linux':
return pathIfExists(`${process.env.HOME}/.local/share/Steam`);
case 'darwin':
return pathIfExists(`${process.env.HOME}/Library/Application Support/Steam`);
default:
throw new Error(`Steam finding isn't implemented for ${process.platform}`);
}
}
8 changes: 8 additions & 0 deletions src/types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
declare module 'p-filter' {
function pFilter<T>(
iterable: Iterable<T | Promise<T>>,
filterer: (element: T, index: number) => boolean | Promise<boolean>,
): Promise<T[]>;

export = pFilter;
}
7 changes: 7 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "@ark120202/typescript-config/node10.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib"
}
}
3 changes: 3 additions & 0 deletions tslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": ["@ark120202/tslint-config", "@ark120202/tslint-config/prettier"]
}
Loading

0 comments on commit ddbe550

Please sign in to comment.