diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..857e939 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,16 @@ +on: + push: + pull_request: + workflow_dispatch: + +jobs: + lint: + runs-on: ubuntu-latest + env: + GITHUB_ACTIONS: true + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: latest + - run: node scripts/lint.mjs \ No newline at end of file diff --git a/.github/workflows/mdbook.yml b/.github/workflows/mdbook.yml index a358368..81ec1e3 100644 --- a/.github/workflows/mdbook.yml +++ b/.github/workflows/mdbook.yml @@ -20,13 +20,14 @@ jobs: env: MDBOOK_VERSION: 0.4.37 CARGO_TERM_COLOR: always + GITHUB_ACTIONS: true steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: latest - name: Generate summary - run: node scripts/generate-summary.mjs + run: node scripts/generate.mjs - uses: Swatinem/rust-cache@v2 - name: Install mdBook run: | diff --git a/README.md b/README.md index 8d0597d..029490c 100644 --- a/README.md +++ b/README.md @@ -71,11 +71,12 @@ Documentation for Whisky. ``` Screenshot 2024-04-16 at 10 06 11 PM -3. Run the `generate-summary` script with `./scripts/generate-summary.mjs` to update the `SUMMARY.md` and `game-support/README.md` files. +3. Run the `generate` script with `./scripts/generate.mjs` to update `SUMMARY.md`. This will also make the game appear in the sidebar of the book. 4. Create a pull request detailing the changes you made. Ensure that it's consise, yet readable and coherent. - You will need to create a fork of `whisky-book` and push your changes there before creating a PR. Once you've done that, then you can submit a PR to merge your fork with `main`. -5. Sit back, wait for PR reviews, and make changes as necessary. +5. Run `./scripts/lint.mjs` to ensure that your changes are properly formatted. +6. Sit back, wait for PR reviews, and make changes as necessary. Have any questions about this process or anything Whisky-related? Stop by the [Discord](https://discord.gg/CsqAfs9CnM) and ask us a question! We're more than happy to help. diff --git a/scripts/.prettierrc.yaml b/scripts/.prettierrc.yaml new file mode 100644 index 0000000..8f5e8ff --- /dev/null +++ b/scripts/.prettierrc.yaml @@ -0,0 +1,7 @@ +singleQuote: true +semi: true +trailingComma: none +arrowParens: avoid +endOfLine: lf +tabWidth: 4 +printWidth: 80 \ No newline at end of file diff --git a/scripts/core.mjs b/scripts/core.mjs new file mode 100644 index 0000000..24a5555 --- /dev/null +++ b/scripts/core.mjs @@ -0,0 +1,274 @@ +/** + * @fileoverview Core shared functions between linting and generation. + */ +import { readdir } from 'node:fs/promises'; +import { getDirName, logging } from './utils.mjs'; +import { resolve } from 'node:path'; +import { request } from 'node:https'; + +/** + * Core directory paths. + * @property {string} rootDir + * @property {string} srcDir + * @property {string} gameSupportDir + * @property {string} summaryFile + * @property {string} gamesJsonFile + * @readonly + */ +export const CORE_PATHS = { + rootDir: resolve(getDirName(), '..'), + srcDir: resolve(getDirName(), '..', 'src'), + gameSupportDir: resolve(getDirName(), '..', 'src', 'game-support'), + summaryFile: resolve(getDirName(), '..', 'src', 'SUMMARY.md'), + gamesJsonFile: resolve(getDirName(), '..', 'src', 'games.json') +}; + +export const SCRIPT_GENERATE_START = ''; +export const SCRIPT_GENERATE_END = ''; + +/** + * Gets the start and end sections of a file. + * @param {string} content + * @returns {[[number, number], null] | [null, 'not-found' | 'invalid-position']} + */ +export const sectionsGetStartAndEnd = content => { + // The start and end sections both need to be present + const startMatch = content.indexOf(SCRIPT_GENERATE_START); + const endMatch = content.indexOf(SCRIPT_GENERATE_END); + if (startMatch === -1 || endMatch === -1) { + logging.debug('Failed to find start or end section in file.'); + return [null, 'not-found']; + } + + // The end section must come after the start section + if (startMatch > endMatch) { + logging.debug('End section comes before start section in file.'); + return [null, 'invalid-position']; + } + + // Get the start and end sections + return [[startMatch, endMatch], null]; +}; + +export const TITLES_REGEX = /^# (.+)/; + +/** + * Gets the title of a file. + * @param {string} content + * @returns {[string, null] | [null, 'not-found']} + */ +export const getTitle = content => { + // Match the title + const titleMatch = content.match(TITLES_REGEX); + if (!titleMatch || titleMatch.length < 2) { + logging.debug('Failed to find title in file.'); + return [null, 'not-found']; + } + + return [titleMatch[1], null]; +}; + +export const SCRIPT_ALIASES_REGEX = //; + +/** + * Parse aliases from a file. + * @param {string} content + * @returns {[string[], null] | [null, 'not-found' | 'bad-json' | 'bad-json-format']} + */ +export const parseAliases = content => { + // Match the aliases section + const aliasesMatch = content.match(SCRIPT_ALIASES_REGEX); + if (!aliasesMatch || aliasesMatch.length < 2) { + logging.debug('Failed to find aliases section in file.'); + return [null, 'not-found']; + } + + // Parse the aliases + let [aliasesParsed, aliasesError] = (() => { + try { + return [JSON.parse(aliasesMatch[1]), null]; + } catch (error) { + logging.debug('Failed to parse aliases section in file: %o', error); + return [null, error]; + } + })(); + if (aliasesError) { + return [null, 'bad-json']; + } + if ( + !aliasesParsed || + !Array.isArray(aliasesParsed) || + !aliasesParsed.every(alias => typeof alias === 'string') + ) { + logging.debug( + 'Failed to parse aliases section in file: not an array of strings.' + ); + return [null, 'bad-json-format']; + } + + return [aliasesParsed, null]; +}; + +export const REVIEW_METADATA_REGEX = + /{{#template \.\.\/templates\/rating.md status=(Platinum|Gold|Silver|Bronze|Garbage) installs=(Yes|No) opens=(Yes|No)}}/; + +/** + * @typedef {'Platinum' | 'Gold' | 'Silver' | 'Bronze' | 'Garbage'} RatingStatus + */ + +/** + * Parse rating information from a file. + * @param {string} content + * @returns {[{ + * status: RatingStatus, + * installs: 'Yes' | 'No', + * opens: 'Yes' | 'No', + * }, null] | [null, 'not-found'] + */ +export const parseReviewMetadata = content => { + // Match the rating section + const ratingMatch = content.match(REVIEW_METADATA_REGEX); + if (!ratingMatch || ratingMatch.length < 4) { + logging.debug('Failed to find rating section in file.'); + return [null, 'not-found']; + } + + const status = ratingMatch[1]; + const installs = ratingMatch[2]; + const opens = ratingMatch[3]; + + return [ + { + status, + installs, + opens + }, + null + ]; +}; + +export const GAMES_EMBEDS_METADATA = { + steam: /{{#template ..\/templates\/steam.md id=(\d+)}}/ +}; + +/** + * @typedef {{ + * type: 'steam', + * id: number, + * }} GameEmbed + */ + +/** + * Get game embeds from a file. + * @param {string} content + * @returns {[[GameEmbed, number] | null, 'not-found' | 'multiple-found']} + * + */ +export const parseGameEmbeds = content => { + // Match the game embeds section + /** + * @type {{ + * location: number, + * embed: GameEmbed + * }[]} + */ + const embeds = []; + for (const [type, regex] of Object.entries(GAMES_EMBEDS_METADATA)) { + const match = content.match(regex); + if (match && match.length > 1) { + embeds.push({ + location: match.index, + embed: { + type, + id: parseInt(match[1]) + } + }); + } + } + + if (embeds.length === 0) { + logging.debug('Failed to find game embeds section in file.'); + return [null, 'not-found']; + } + if (embeds.length > 1) { + logging.debug('Found multiple game embeds section in file.'); + return [null, 'multiple-found']; + } + + return [[embeds[0].embed, embeds[0].location], null]; +}; + +/** + * Use webservers to check that a GameEmbed is valid. + * @param {GameEmbed} embed + * @returns {Promise<[boolean, null] | [null, 'invalid-embed' | 'web-request-failed']>} + */ +export const checkGameEmbed = async embed => { + if (embed.type === 'steam') { + const steamUrl = + 'https://store.steampowered.com/app/' + + encodeURIComponent(embed.id); + const url = new URL(steamUrl); + /** + * @type {import('http').IncomingMessage} + */ + const [response, responseError] = await new Promise(resolve => { + request( + { + hostname: url.hostname, + port: 443, + path: url.pathname, + method: 'GET', + headers: { + 'User-Agent': 'WhiskyBookBot/1.0' + } + }, + resolve + ).end(); + }) + .then(response => [response, null]) + .catch(error => [null, error]); + if (responseError) { + logging.debug('Failed to request Steam URL: %o', responseError); + return [null, 'web-request-failed']; + } + + return [response.statusCode === 200, null]; + } + + return [false, 'invalid-embed']; +}; + +const FILES_SKIP = ['README.md', 'template.md']; + +/** + * Gets all markdown files in the game-support directory. + * @returns {Promise<[string[], null] | [null, 'failed-to-read-dir']>} + */ +export const getMarkdownFiles = async () => { + const [gameSupportDirFiles, gameSupportDirFilesError] = await readdir( + CORE_PATHS.gameSupportDir, + { withFileTypes: true } + ) + .then(files => [files, null]) + .catch(error => [null, error]); + if (gameSupportDirFilesError) { + logging.error( + 'Failed to read game-support directory: %o', + gameSupportDirFilesError + ); + return [null, 'failed-to-read-dir']; + } + + return [ + gameSupportDirFiles + .filter( + file => + file.isFile() && + file.name.endsWith('.md') && + !FILES_SKIP.includes(file.name) + ) + .map(file => resolve(CORE_PATHS.gameSupportDir, file.name)), + null + ]; +}; diff --git a/scripts/generate-summary.mjs b/scripts/generate-summary.mjs deleted file mode 100755 index d4ffd98..0000000 --- a/scripts/generate-summary.mjs +++ /dev/null @@ -1,282 +0,0 @@ -#!/usr/bin/env node -/** - * Generates a SUMMARY.md file for rust book. - */ -import { readdir, readFile, writeFile } from 'node:fs/promises'; -import { resolve, dirname, extname } from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { format } from 'node:util'; -import { execSync } from 'node:child_process'; - -import { markdownEscape, logging, removeDuplicates } from './utils.mjs'; - -const SCRIPT_GENERATE_START = ''; -const SCRIPT_GENERATE_END = ''; -// After the aliases start, we will find a json array of aliases (match everything until the closing comment) -const SCRIPT_ALIASES_REGEX = //s; -const TEMPLATE_METADATA = /{{#template \.\.\/templates\/rating.md status=(Gold|Silver|Bronze|Garbage) installs=(Yes|No) opens=(Yes|No)}}/; -const GAME_SUPPORT_DIR = 'game-support'; -const INDEX_FILE = 'README.md'; -const GAMES_OUT_FILE = 'games.json'; -const FILES_SKIP = ['README.md', 'template.md']; - -/** - * Gets the start and end sections of a file. - * @param {string} content - * @returns {[string | null, string | null]} - */ -const getStartAndEndSections = (content) => { - if ( - content.split(SCRIPT_GENERATE_START).length !== 2 || - content.split(SCRIPT_GENERATE_END).length !== 2 - ) { - return [null, null]; - } - - const [start, replaceAndEnd] = content.split(SCRIPT_GENERATE_START); - const [_, end] = replaceAndEnd.split(SCRIPT_GENERATE_END); - return [start, end]; -} - -/** - * Parse aliases from a file. - * @param {string} content - * @returns {[string[], null] | [null, 'not-found' | 'bad-json' | 'bad-json-format']} - */ -const parseAliases = (content) => { - // Match the aliases section - const aliasesMatch = content.match(SCRIPT_ALIASES_REGEX); - if (!aliasesMatch || aliasesMatch.length < 2) { - return [null, 'not-found']; - } - - // Parse the aliases - let [aliasesParsed, aliasesError] = (() => { - try { - return [JSON.parse(aliasesMatch[1]), null]; - } catch (error) { - return [null, error]; - } - })(); - if (aliasesError) { - return [null, 'bad-json']; - } - if (!aliasesParsed || !Array.isArray(aliasesParsed) || !aliasesParsed.every((alias) => typeof alias === 'string')) { - return [null, 'bad-json-format']; - } - - return [aliasesParsed, null]; -} - -/** - * Parse rating information from a file. - * @param {string} content - * @returns {[{ - * status: 'Gold' | 'Silver' | 'Bronze' | 'Garbage', - * installs: 'Yes' | 'No', - * opens: 'Yes' | 'No', - * }, null] | [null, 'not-found'] - */ -const parseRating = (content) => { - // Match the rating section - const ratingMatch = content.match(TEMPLATE_METADATA); - if (!ratingMatch || ratingMatch.length < 4) { - return [null, 'not-found']; - } - - const status = ratingMatch[1]; - const installs = ratingMatch[2]; - const opens = ratingMatch[3]; - - return [{ - status, - installs, - opens, - }, null]; -} - -/** - * Get last updated date from a file. - * MUST BE IN GIT SOURCE TREE - * @param {string} path - * @returns {[Date, null] | [null, 'git-error']} - */ -const getLastUpdated = (path) => { - try { - const lastUpdated = new Date(execSync(`git log -1 --format=%cd -- ${path}`).toString().trim()); - - if (isNaN(lastUpdated.getTime())) { - throw new Error('Invalid date'); - } - - return [lastUpdated, null]; - } catch (error) { - logging.warning('Failed to get last updated for file %s: %o', path, error); - return [null, 'git-error']; - } -} - - -/** - * Main function. - * @returns {Promise} - */ -const main = async () => { - logging.info('Generating SUMMARY.md...'); - // Get local file paths - const dirName = resolve(dirname(fileURLToPath(import.meta.url)), '../', 'src'); - const summaryFilePath = resolve(dirName, 'SUMMARY.md'); - const indexFilePath = resolve(dirName, 'game-support', INDEX_FILE); - const gameSupportDirPath = resolve(dirName, GAME_SUPPORT_DIR); - const gamesOutFilePath = resolve(dirName, GAMES_OUT_FILE); - - // Read the SUMMARY.md file - const [summaryFileContent, summaryFileReadError] = await readFile(summaryFilePath, 'utf-8') - .then((data) => [data, null]) - .catch((error) => [null, error]); - if (summaryFileReadError) { - logging.error('Failed to read SUMMARY.md: %o', summaryFileReadError); - return 1; - } - const [summaryFileStart, summaryFileEnd] = getStartAndEndSections(summaryFileContent); - if (!summaryFileStart === null || !summaryFileEnd === null) { - logging.error('Failed to find start and end sections in SUMMARY.md.'); - return 1; - } - - // Get all files in the game-support directory - const [gameSupportDirFiles, gameSupportDirFilesError] = await readdir(gameSupportDirPath, { withFileTypes: true }) - .then((files) => [files, null]) - .catch((error) => [null, error]); - if (gameSupportDirFilesError) { - logging.error('Failed to read game-support directory: %o', gameSupportDirFilesError); - return 1; - } - - // Filter out directories and non-markdown files - const markdownFiles = gameSupportDirFiles - .filter((file) => file.isFile() && file.name.endsWith('.md') && !FILES_SKIP.includes(file.name)); - - // For all, generate a markdown link - /** - * @type {Array<{ - * name: string, - * path: string, - * title: string, - * lastUpdated: Date, - * aliases: string[], - * rating: { - * status: 'Gold' | 'Silver' | 'Bronze' | 'Garbage', - * installs: 'Yes' | 'No', - * opens: 'Yes' | 'No', - * } - * }>} - */ - const links = []; - for (const file of markdownFiles) { - // Read the file - const filePath = resolve(gameSupportDirPath, file.name); - const [fileContent, fileReadError] = await readFile(filePath, 'utf-8') - .then((data) => [data, null]) - .catch((error) => [null, error]); - if (fileReadError) { - logging.error('Failed to read file %s: %o', filePath, fileReadError); - return 1; - } - - // Get the title - const titleMatch = fileContent.match(/^# (.+)$/m); - if (!titleMatch || titleMatch.length < 2) { - logging.warning('Failed to find title in file %s. "%s" will be skipped.', filePath, file.name); - continue; - } - - // Add the link - const title = titleMatch[1]; - - // Look for aliases - const [aliasesParsed, aliasesError] = parseAliases(fileContent); - - if (aliasesError && aliasesError !== 'not-found') { - logging.warning('Failed to parse aliases in file %s: %s', filePath, aliasesError); - continue; - } - - const aliases = removeDuplicates([...(aliasesParsed ?? []), title].map((alias) => alias.toLowerCase())); - - // Look for rating - const [ratingParsed, ratingError] = parseRating(fileContent); - - if (ratingError) { - logging.warning('Failed to parse rating in file %s: %s', filePath, ratingError); - continue; - } - - // Look for last updated - const [lastUpdated, lastUpdatedError] = getLastUpdated(filePath); - if (lastUpdatedError) { - logging.warning('Failed to get last updated in file %s: %s', filePath, lastUpdatedError); - continue; - } - - links.push({ - name: file.name, - title, - lastUpdated, - aliases, - rating: ratingParsed, - }); - } - - // Write the new SUMMARY.md file - const newSummaryFileContent = [ - summaryFileStart, - SCRIPT_GENERATE_START, - '\n', - links.map((link) => { - return ` - [${markdownEscape(link.title)}](./${GAME_SUPPORT_DIR}/${encodeURIComponent(link.name)})`; - }).join('\n'), - '\n', - SCRIPT_GENERATE_END, - summaryFileEnd, - ].join(''); - const [_a, writeError] = await writeFile(summaryFilePath, newSummaryFileContent) - .then(() => [null, null]) - .catch((error) => [null, error]); - if (writeError) { - logging.error('Failed to write SUMMARY.md: %o', writeError); - return 1; - } - logging.info('SUMMARY.md generated successfully.'); - - // Write the games.json file - const gamesOutFileContent = JSON.stringify(links.map((link) => { - // Strip the extension - const name = link.name; - const ext = extname(name); - const base = name.slice(0, -ext.length); - return { - url: `/${GAME_SUPPORT_DIR}/${encodeURIComponent(base + '.html')}`, - title: link.title, - aliases: link.aliases, - lastUpdated: link.lastUpdated.toISOString(), - rating: { - status: link.rating.status, - installs: link.rating.installs, - opens: link.rating.opens - }, - }; - }), null, 2); - const [_c, writeGamesError] = await writeFile(gamesOutFilePath, gamesOutFileContent) - .then(() => [null, null]) - .catch((error) => [null, error]); - if (writeGamesError) { - logging.error('Failed to write games.json: %o', writeGamesError); - return 1; - } - logging.info('games.json generated successfully.'); - - return 0; -}; - -main().then((code) => process.exit(code)); \ No newline at end of file diff --git a/scripts/generate.mjs b/scripts/generate.mjs new file mode 100755 index 0000000..1f4cf31 --- /dev/null +++ b/scripts/generate.mjs @@ -0,0 +1,289 @@ +#!/usr/bin/env node +/** + * @fileoverview Generates the SUMMARY.md and games.json files. + */ + +import { readFile, writeFile } from 'node:fs/promises'; +import { extname, basename } from 'node:path'; + +import { + markdownEscape, + logging, + removeDuplicates, + getLastUpdated +} from './utils.mjs'; +import { + CORE_PATHS, + sectionsGetStartAndEnd, + getMarkdownFiles, + getTitle, + parseAliases, + parseReviewMetadata, + parseGameEmbeds, + SCRIPT_GENERATE_START, + SCRIPT_GENERATE_END +} from './core.mjs'; + +/** + * @typedef {import('./core.mjs').GameEmbed} GameEmbed + * @typedef {import('./core.mjs').RatingStatus} RatingStatus + */ + +/** + * @typedef {{ + * title: string, + * lastUpdated: Date, + * aliases: string[], + * rating: { + * status: RatingStatus, + * installs: boolean, + * opens: boolean, + * }, + * embed: GameEmbed | null, + * }} GameMetadata + */ + +/** + * Generate GameMetadata for every game. + * @returns {Promise<[{ + * path: string, + * game: GameMetadata, + * }[], null] | [null, 'folder-read-error']>} + */ +export const generateGameMetadata = async () => { + const [markdownFiles, markdownFilesError] = await getMarkdownFiles(); + if (markdownFilesError) { + logging.error('Failed to get markdown files: %s', markdownFilesError); + return [null, 'folder-read-error']; + } + + /** + * @type {{ + * path: string, + * game: GameMetadata, + * }[]} + */ + const links = []; + for (const file of markdownFiles) { + // Read the file + /** + * @type {[string, null] | [null, Error]} + */ + const [fileContent, fileContentError] = await readFile(file, 'utf-8') + .then(data => [data, null]) + .catch(error => [null, error]); + if (fileContentError) { + logging.error( + 'Failed to read file %s: %o', + filePath, + fileContentError + ); + continue; + } + + // Get the title + const [title, titleError] = getTitle(fileContent); + if (titleError) { + logging.warning( + 'Failed to get title in file %s: %s', + filePath, + titleError + ); + continue; + } + + // Look for aliases + const [aliasesParsed, aliasesError] = parseAliases(fileContent); + if (aliasesError && aliasesError !== 'not-found') { + logging.error( + 'Failed to parse aliases in file %s: %s', + filePath, + aliasesError + ); + continue; + } + + const aliases = removeDuplicates( + [...(aliasesParsed ?? []), title].map(alias => alias.toLowerCase()) + ); + + // Look for rating + const [ratingParsed, ratingError] = parseReviewMetadata(fileContent); + if (ratingError) { + logging.error( + 'Failed to parse rating in file %s: %s', + filePath, + ratingError + ); + continue; + } + + // Look for embed + const [embedData, embedError] = parseGameEmbeds(fileContent); + if (embedError && embedError !== 'not-found') { + logging.error( + 'Failed to parse embed in file %s: %s', + filePath, + embedError + ); + continue; + } + const embed = embedData && embedData.length > 0 ? embedData[0] : []; + + // Look for last updated + const [lastUpdated, lastUpdatedError] = getLastUpdated(file); + if (lastUpdatedError) { + logging.error( + 'Failed to get last updated in file %s: %s', + file, + lastUpdatedError + ); + continue; + } + + links.push({ + path: file, + game: { + title, + lastUpdated, + aliases, + rating: { + status: ratingParsed.status, + installs: ratingParsed.installs === 'Yes', + opens: ratingParsed.opens === 'Yes' + }, + embed: embedError ? null : embed + } + }); + } + + return [links, null]; +}; + +/** + * Generate the sidebar links. + * @param {{ + * path: string, + * game: GameMetadata + * }[]} links + * @returns {string} + */ +export const generateLinks = links => { + return '\n' + links + .map(link => { + return ` - [${markdownEscape(link.game.title)}](/game-support/${encodeURIComponent(basename(link.path))})`; + }) + .join('\n') + '\n'; +} + +/** + * Generate the games.json file. + * @param {{ + * path: string, + * game: GameMetadata + * }[]} links + * @returns {string} + */ +export const generateGamesJson = links => { + return JSON.stringify( + links.map(link => { + // Strip the extension + const name = basename(link.path); + const ext = extname(name); + const base = name.slice(0, -ext.length); + return { + url: `/game-support/${encodeURIComponent(base + '.html')}`, + title: link.game.title, + aliases: link.game.aliases, + lastUpdated: link.game.lastUpdated.toISOString(), + rating: { + status: link.game.rating.status, + installs: link.game.rating.installs, + opens: link.game.rating.opens + }, + embed: link.game.embed + }; + }), + null, + 2 + ); +} + +/** + * Main function. + * @returns {Promise} + */ +const main = async () => { + logging.info('Generating SUMMARY.md and games.json...'); + const [gameData, gameDataError] = await generateGameMetadata(); + if (gameDataError) { + return 1; + } + + + // Get the SUMMARY.md file + /** + * @type {[string, null] | [null, Error]} + */ + const [summaryFile, summaryFileError] = await readFile( + CORE_PATHS.summaryFile, + 'utf-8' + ) + .then(data => [data, null]) + .catch(error => [null, error]); + if (summaryFileError) { + logging.error('Failed to read SUMMARY.md: %o', summaryFileError); + return 1; + } + + // Get the start and end of the SUMMARY.md file + const [summarySections, summarySectionsError] = sectionsGetStartAndEnd( + summaryFile + ); + if (summarySectionsError) { + logging.error('Failed to find start and end sections in SUMMARY.md.'); + return 1; + } + const [start, end] = summarySections; + + // Write the new SUMMARY.md file + const newSummaryFileContent = [ + summaryFile.slice(0, start), + SCRIPT_GENERATE_START, + generateLinks(gameData), + SCRIPT_GENERATE_END, + summaryFile.slice(end + SCRIPT_GENERATE_END.length) + ].join(''); + /** + * @type {[string, null] | [null, Error]} + */ + const [_1, writeError] = await writeFile( + CORE_PATHS.summaryFile, + newSummaryFileContent + ) + .then(() => [null, null]) + .catch(error => [null, error]); + if (writeError) { + logging.error('Failed to write SUMMARY.md: %o', writeError); + return 1; + } + + logging.info('SUMMARY.md generated successfully.'); + + // Write the games.json file + const [_3, writeGamesError] = await writeFile( + CORE_PATHS.gamesJsonFile, + generateGamesJson(gameData) + ) + .then(() => [null, null]) + .catch(error => [null, error]); + if (writeGamesError) { + logging.error('Failed to write games.json: %o', writeGamesError); + return 1; + } + logging.info('games.json generated successfully.'); + + return 0; +}; + +main().then(code => process.exit(code)); diff --git a/scripts/lint.mjs b/scripts/lint.mjs new file mode 100755 index 0000000..bdddfb5 --- /dev/null +++ b/scripts/lint.mjs @@ -0,0 +1,342 @@ +#!/usr/bin/env node + +/** + * @fileoverview Provides linting for the project. + */ + +import { readFile } from 'node:fs/promises'; + +import { logging } from './utils.mjs'; +import { + CORE_PATHS, + checkGameEmbed, + sectionsGetStartAndEnd, + getTitle, + getMarkdownFiles, + parseAliases, + parseReviewMetadata, + parseGameEmbeds, + SCRIPT_ALIASES_REGEX, + SCRIPT_GENERATE_START, + REVIEW_METADATA_REGEX +} from './core.mjs'; +import { generateGameMetadata, generateLinks } from './generate.mjs'; + +const isGithubAnotationsMode = process.env['GITHUB_ACTIONS'] === 'true'; + +/** + * Remove path prefix to the root directory. + * @param {string} path The path to remove the root directory from. + * @returns {string} + */ +const removeRootDir = path => { + return path.replace(CORE_PATHS.rootDir + '/', ''); +}; + +/** + * Logs a lint violation. + * @param {'warning' | 'error'} type The type of violation. + * @param {string | null} file The file path. + * @param {number | null} line The line number. + * @param {number | null} column The column number. + * @param {string} message The violation message. + */ +const logLintViolation = (type = 'error', file, line, column, message) => { + if (isGithubAnotationsMode) { + const args = []; + file !== null && args.push(`file=${removeRootDir(file)}`); + line !== null && args.push(`line=${line}`); + column !== null && args.push(`col=${column}`); + console.log('::%s %s::%s', type, args.join(','), message); + } else { + if (type === 'warning') { + logging.warning('[%s%s] %s', file, line ? `:${line}` : '', message); + } else { + logging.error('[%s%s] %s', file, line ? `:${line}` : '', message); + } + } +}; + +/** + * Check game embeds for linting issues. + * @param {GameEmbed} + */ + +/** + * Checks a markdown file for linting issues. + * @param {string} file The file path. + */ +const lintMarkdownFile = async file => { + // Read the file + /** + * @type {[string, null] | [null, Error]} + */ + const [content, error] = await readFile(file, 'utf-8') + .then(data => [data, null]) + .catch(error => [null, error]); + if (error) { + logging.error('Failed to read file: %o', error); + return; + } + if (content.includes('\r\n')) { + logLintViolation( + 'warning', + file, + null, + null, + 'File contains CRLF line endings, should be LF.' + ); + } + + // Create an array of indexes for each line + const lineIndexes = [0]; + for (let i = 0; i < content.length; i++) { + if (content[i] === '\n') { + lineIndexes.push(i + 1); + } + } + + // Check the title + const [_0, titleError] = getTitle(content); + if (titleError) { + const error = { + 'not-found': 'Failed to find title in file.' + }; + logLintViolation('error', file, 1, 1, error[titleError]); + } + + /** + * Find the line and column number from the index. + * @param {number} index The index to find the line and column number for. + * @returns {[number, number]} + */ + const findLineAndColumn = index => { + const line = lineIndexes.filter(i => i <= index).length; + const column = index - lineIndexes[line - 1]; + return [line, column + 1]; + }; + + // Check the line of the aliases (should be on second line) + let reviewMetadataExpectedLine = 3; + const aliasesMatch = SCRIPT_ALIASES_REGEX.exec(content); + if (aliasesMatch !== null) { + const [line, column] = findLineAndColumn(aliasesMatch.index); + if (column !== 1) { + logLintViolation( + 'warning', + file, + line, + column, + 'Aliases should be on the first column.' + ); + } + if (line !== 2) { + logLintViolation( + 'warning', + file, + line, + column, + 'Aliases should be on the second line of the file.' + ); + } + + // Update the expected line for the review metadata to be two lines after the aliases + const [aliasesLine, _] = findLineAndColumn( + aliasesMatch.index + aliasesMatch[0].length + ); + + reviewMetadataExpectedLine = aliasesLine + 2; + } + + // Check the aliases + const [_1, aliasesError] = parseAliases(content); + if (aliasesError) { + const error = { + 'not-found': 'Failed to find aliases in file.', + 'bad-json': 'Failed to parse aliases JSON in file.', + 'bad-json-format': + 'Aliases JSON in file is not an array of strings.' + }; + logLintViolation( + aliasesError === 'not-found' ? 'warning' : 'error', + file, + 1, + 1, + error[aliasesError] + ); + } + + // Check the review metadata + const reviewMetadataMatch = REVIEW_METADATA_REGEX.exec(content); + if (reviewMetadataMatch.length > 0) { + const [line, column] = findLineAndColumn(reviewMetadataMatch.index); + if (column !== 1) { + logLintViolation( + 'warning', + file, + line, + column, + 'Review metadata should be on the first column.' + ); + } + if (line !== reviewMetadataExpectedLine) { + logLintViolation( + 'warning', + file, + line, + column, + 'Review metadata should be on the line after the aliases (line ' + + reviewMetadataExpectedLine + + ').' + ); + } else { + // Make sure the line before the review metadata is empty + const previousLineIndex = lineIndexes[line - 2]; + if ( + content + .slice(previousLineIndex, lineIndexes[line - 1]) + .trim() !== '' + ) { + logLintViolation( + 'warning', + file, + line - 1, + 1, + 'Line before review metadata should be empty.' + ); + } + } + } + + // Check the review metadata + const [_2, reviewMetadataError] = parseReviewMetadata(content); + if (reviewMetadataError) { + const error = { + 'not-found': + 'Failed to find review metadata in file. (Might be because it is the wrong format.)' + }; + logLintViolation('error', file, 1, 1, error[reviewMetadataError]); + } + + // Game embeds should be on the last line + const [gameEmbedsMatch, gameEmbedsMatchError] = parseGameEmbeds(content); + if (gameEmbedsMatchError && gameEmbedsMatchError !== 'not-found') { + logLintViolation( + 'warning', + file, + null, + null, + 'Multiple game embeds found in file.' + ); + } else if (gameEmbedsMatch !== null) { + const [embed, position] = gameEmbedsMatch; + const [line, column] = findLineAndColumn(position); + if (column !== 1) { + logLintViolation( + 'warning', + file, + line, + column, + 'Game embeds should be on the first column.' + ); + } + if (line !== lineIndexes.length - 1) { + logLintViolation( + 'warning', + file, + line, + column, + 'Game embeds should be on the second last line of the file. (Line ' + + (lineIndexes.length - 1) + + ')' + ); + } + + // Check the game embeds is not invalid + const [isValid, gameEmbedsError] = await checkGameEmbed(embed); + if (!isValid) { + const error = { + 'invalid-embed': 'Invalid game embed found in file.', + 'web-request-failed': 'Failed to fetch game embed data.' + }; + logLintViolation( + 'error', + file, + line, + column, + error[gameEmbedsError] + ); + } + } +}; + +/** + * Main function. + * @returns {Promise} + */ +const main = async () => { + logging.info('Linting files...'); + + // First lint the SUMMARY.md file + /** + * @type {[string, null] | [null, Error]} + */ + const [summaryFile, summaryFileError] = await readFile( + CORE_PATHS.summaryFile, + 'utf-8' + ) + .then(data => [data, null]) + .catch(error => [null, error]); + if (summaryFileError) { + logging.error('Failed to read SUMMARY.md: %o', summaryFileError); + return 1; + } + + // Games metadata + const [gameData, gameDataError] = await generateGameMetadata(); + + // Check if the SUMMARY.md file contains the correct sections + const [summarySections, summarySectionsError] = + sectionsGetStartAndEnd(summaryFile); + if (summarySectionsError) { + const error = { + 'not-found': 'Failed to find start and end sections in SUMMARY.md.', + 'invalid-position': + 'End section comes before start section in SUMMARY.md.' + }; + logLintViolation( + 'error', + CORE_PATHS.summaryFile, + null, + null, + error[summarySectionsError] + ); + } else if (!gameDataError) { + const [start, end] = summarySections; + // Slice at these indexes to get the content between the sections + const contentBetweenSections = summaryFile.slice(start + SCRIPT_GENERATE_START.length, end); + // Check if the content between the sections is up to date + const expectedContent = generateLinks(gameData); + if (contentBetweenSections !== expectedContent) { + logLintViolation( + 'error', + CORE_PATHS.summaryFile, + null, + null, + 'SUMMARY.md content is out of date.' + ); + } + } + + // Lint all markdown files in the game-support directory + const [markdownFiles, markdownFilesError] = await getMarkdownFiles(); + if (markdownFilesError) { + return 1; + } + + await Promise.all(markdownFiles.map(file => lintMarkdownFile(file))); + + return 0; +}; + +main().then(code => process.exit(code)); diff --git a/scripts/utils.mjs b/scripts/utils.mjs index a9d8f5f..a3b1aa8 100644 --- a/scripts/utils.mjs +++ b/scripts/utils.mjs @@ -1,3 +1,19 @@ +/** + * @fileoverview Utility functions. + */ +import { format } from 'node:util'; +import { dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { execSync } from 'node:child_process'; + +/** + * Returns the directory of the current module. (utils.mjs) + * @returns {string} + */ +export const getDirName = () => { + return dirname(fileURLToPath(import.meta.url)); +}; + /** * Escapes a string for use in Markdown. * @type {Array<[RegExp, string]>} @@ -13,19 +29,19 @@ const markdownEscapeItems = [ [//g, '>'], // Close angle brackets [/_/g, '\\_'], // Underscores - [/`/g, '\\`'], // Backticks + [/`/g, '\\`'] // Backticks ]; /** * Escapes a string for use in Markdown. * @param {string} str */ -export const markdownEscape = (str) => { +export const markdownEscape = str => { for (const [regex, replacement] of markdownEscapeItems) { str = str.replace(regex, replacement); } return str; -} +}; /** * Fancy colors for console output. @@ -37,7 +53,7 @@ const colors = { warning: '\x1b[33m', error: '\x1b[31m', bold: '\x1b[1m', - reset: '\x1b[0m', + reset: '\x1b[0m' }; /** @@ -55,9 +71,18 @@ export const fancyLog = (type, message, ...args) => { colors.reset, colors[type], format(message, ...args), - colors.reset, + colors.reset ); -} +}; + +const LOG_LEVELS = ['debug', 'info', 'warning', 'error', 'none']; + +/** + * Logging levels. + */ +const loggingLevel = LOG_LEVELS.includes(process.env['LOG']) + ? LOG_LEVELS.indexOf(process.env['LOG']) + : 1; /** * Logging functions. @@ -72,33 +97,63 @@ export const logging = { * @param {string} message * @param {...any} args */ - debug: (message, ...args) => fancyLog('debug', message, ...args), + debug: (message, ...args) => + loggingLevel <= 0 && fancyLog('debug', message, ...args), /** * Logs an info message. * @param {string} message * @param {...any} args */ - info: (message, ...args) => fancyLog('info', message, ...args), + info: (message, ...args) => + loggingLevel <= 1 && fancyLog('info', message, ...args), /** * Logs a warning message. * @param {string} message * @param {...any} args */ - warning: (message, ...args) => fancyLog('warning', message, ...args), + warning: (message, ...args) => + loggingLevel <= 2 && fancyLog('warning', message, ...args), /** * Logs an error message. * @param {string} message * @param {...any} args */ - error: (message, ...args) => fancyLog('error', message, ...args), + error: (message, ...args) => + loggingLevel <= 3 && fancyLog('error', message, ...args) }; - /** * Remove duplicates from an array. * @param {T[]} arr * @returns {T[]} */ -export const removeDuplicates = (arr) => { +export const removeDuplicates = arr => { return [...new Set(arr)]; -} \ No newline at end of file +}; + +/** + * Get last updated date from a file. + * MUST BE IN GIT SOURCE TREE + * @param {string} path + * @returns {[Date, null] | [null, 'git-error']} + */ +export const getLastUpdated = path => { + try { + const lastUpdated = new Date( + execSync(`git log -1 --format=%cd -- ${path}`).toString().trim() + ); + + if (isNaN(lastUpdated.getTime())) { + throw new Error('Invalid date'); + } + + return [lastUpdated, null]; + } catch (error) { + logging.warning( + 'Failed to get last updated for file %s: %o', + path, + error + ); + return [null, 'git-error']; + } +}; diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 4e49ffa..a1ba4c8 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -8,95 +8,95 @@ - [Whisky x Heroic](./whisky-x-heroic.md) - [Debugging](./debugging.md) - [Game Support](./game-support/README.md) - - - [Assassin's Creed: Director's Cut Edition](./game-support/ac-directors-cut.md) - - [Among Us](./game-support/among-us.md) - - [Armored Core VI: Fires of Rubicon](./game-support/armored-core-6.md) - - [Battle Brothers](./game-support/battle-brothers.md) - - [Betrayer](./game-support/betrayer.md) - - [Blasphemous 2](./game-support/blasphemous-2.md) - - [Blitzkrieg 2 Anthology](./game-support/blitzkrieg-2-anthology.md) - - [Buckshot Roulette](./game-support/buckshot-roulette.md) - - [Call of Cthulhu](./game-support/call-of-cthulhu.md) - - [Cities: Skylines 2](./game-support/cities-skylines-2.md) - - [Call of Juarez: Gunslinger](./game-support/coj-gunslinger.md) - - [Content Warning](./game-support/content-warning.md) - - [Contraband Police](./game-support/contraband-police.md) - - [Control](./game-support/control.md) - - [Counter-Strike 2](./game-support/counter-strike-2.md) - - [Cyberpunk 2077](./game-support/cyberpunk-2077.md) - - [Dagon: by H. P. Lovecraft](./game-support/dagon.md) - - [Dark Souls III](./game-support/dark-souls-3.md) - - [Dark Souls: Remastered](./game-support/dark-souls-remastered.md) - - [Dead Space \(2023\)](./game-support/dead-space-2023.md) - - [Deadlink](./game-support/deadlink.md) - - [Deep Rock Galactic](./game-support/deep-rock-galactic.md) - - [Diablo IV - Battle.net Version](./game-support/diablo-4-battle-net.md) - - [Diablo IV - Steam Version](./game-support/diablo-4-steam.md) - - [Dishonored 2](./game-support/dishonored-2.md) - - [Dishonored](./game-support/dishonored.md) - - [Dorfromantik](./game-support/dorfromantik.md) - - [Dying Light 2](./game-support/dying-light-2.md) - - [Elden Ring](./game-support/elden-ring.md) - - [Elite Dangerous](./game-support/elite-dangerous.md) - - [F1 Manager 2023](./game-support/f1m23.md) - - [Fallout 3: Game of the Year Edition](./game-support/fallout-3-goty.md) - - [Fallout 4](./game-support/fallout-4.md) - - [Fear and Hunger](./game-support/fear-and-hunger.md) - - [FlatOut](./game-support/flatout.md) - - [Forgive Me Father 2](./game-support/forgive-me-father-2.md) - - [Fortnite](./game-support/fortnite.md) - - [Friends vs Friends](./game-support/friends-vs-friends.md) - - [Grand Theft Auto V](./game-support/gta-5.md) - - [Guild Wars 2](./game-support/gw2.md) - - [Geometry Wars 3: Dimensions Evolved](./game-support/gw3-dimensions-evolved.md) - - [Half-Life 1](./game-support/half-life-1.md) - - [Half-Life 2](./game-support/half-life-2.md) - - [Hellblade: Senua's Sacrifice](./game-support/hellblade.md) - - [Hitman: Contracts](./game-support/hitman-3-c.md) - - [Hearts of Iron III](./game-support/hoi-3.md) - - [Horizon Zero Dawn](./game-support/horizon-zero-dawn.md) - - [JoJo's Bizarre Adventure: All-Star Battle R](./game-support/jjba-asbr.md) - - [Kingdom Come: Deliverance](./game-support/kcd.md) - - [Kenshi](./game-support/kenshi.md) - - [Kingsway](./game-support/kingsway.md) - - [LEGO Star Wars III - The Clone Wars](./game-support/lego-sw-iii-clone-wars.md) - - [LEGO Star Wars: The Skywalker Saga](./game-support/lego-sw-skywalker-saga.md) - - [Lethal Company](./game-support/lethal-company.md) - - [Manor Lords](./game-support/manor-lords.md) - - [Mount & Blade: With Fire & Sword](./game-support/mb-wfas.md) - - [Metro 2033 Redux](./game-support/metro-2033-rx.md) - - [Metro: Last Light Redux](./game-support/metro-ll-rx.md) - - [Metal Gear Solid V: The Phantom Pain](./game-support/mgs-5.md) - - [Monster Hunter World: Iceborne](./game-support/monster-hunter-world-iceborne.md) - - [Neon White](./game-support/neon-white.md) - - [Overwatch 2](./game-support/overwatch-2.md) - - [Persona 3 Reload](./game-support/p3r.md) - - [Persona 4 Golden](./game-support/p4g.md) - - [Palworld](./game-support/palworld.md) - - [People Playground](./game-support/people-playground.md) - - [Phasmophobia](./game-support/phasmophobia.md) - - [Prey \(2017\)](./game-support/prey-2017.md) - - [Quake II](./game-support/quake2.md) - - [r2modman](./game-support/r2modman.md) - - [Rain World](./game-support/rain-world.md) - - [Risk of Rain 2](./game-support/risk-of-rain-2.md) - - [Risk of Rain Returns](./game-support/risk-of-rain-returns.md) - - [Ruiner](./game-support/ruiner.md) - - [Satisfactory](./game-support/satisfactory.md) - - [Sekiro: Shadows Die Twice](./game-support/sekiro.md) - - [Skyrim SE](./game-support/skyrim-se.md) - - [Stardew Valley](./game-support/stardew-valley.md) - - [Stronghold Crusader HD](./game-support/stronghold-crusader-hd.md) - - [Star Wars Jedi: Fallen Order](./game-support/sw-fallen-order.md) - - [Star Wars: Squadrons](./game-support/sw-squadrons.md) - - [Tom Clancy's Rainbow Six Siege \(Steam\)](./game-support/tcr6s.md) - - [The Stanley Parable: Ultra Deluxe](./game-support/tsp-ud.md) - - [Turbo Overkill](./game-support/turbo-overkill.md) - - [Ultrakill](./game-support/ultrakill.md) - - [Undertale](./game-support/undertale.md) - - [The Vanishing of Ethan Carter](./game-support/vanishing-of-ethan-carter.md) - - [Warframe](./game-support/warframe.md) - - [The Witcher 3: Wild Hunt](./game-support/witcher3.md) - - [The Wolf Among Us](./game-support/wolf-among-us.md) + + - [Assassin's Creed: Director's Cut Edition](/game-support/ac-directors-cut.md) + - [Among Us](/game-support/among-us.md) + - [Armored Core VI: Fires of Rubicon](/game-support/armored-core-6.md) + - [Battle Brothers](/game-support/battle-brothers.md) + - [Betrayer](/game-support/betrayer.md) + - [Blasphemous 2](/game-support/blasphemous-2.md) + - [Blitzkrieg 2 Anthology](/game-support/blitzkrieg-2-anthology.md) + - [Buckshot Roulette](/game-support/buckshot-roulette.md) + - [Call of Cthulhu](/game-support/call-of-cthulhu.md) + - [Cities: Skylines 2](/game-support/cities-skylines-2.md) + - [Call of Juarez: Gunslinger](/game-support/coj-gunslinger.md) + - [Content Warning](/game-support/content-warning.md) + - [Contraband Police](/game-support/contraband-police.md) + - [Control](/game-support/control.md) + - [Counter-Strike 2](/game-support/counter-strike-2.md) + - [Cyberpunk 2077](/game-support/cyberpunk-2077.md) + - [Dagon: by H. P. Lovecraft](/game-support/dagon.md) + - [Dark Souls III](/game-support/dark-souls-3.md) + - [Dark Souls: Remastered](/game-support/dark-souls-remastered.md) + - [Dead Space \(2023\)](/game-support/dead-space-2023.md) + - [Deadlink](/game-support/deadlink.md) + - [Deep Rock Galactic](/game-support/deep-rock-galactic.md) + - [Diablo IV \(Battle.net\)](/game-support/diablo-4-battle-net.md) + - [Diablo IV \(Steam\)](/game-support/diablo-4-steam.md) + - [Dishonored 2](/game-support/dishonored-2.md) + - [Dishonored](/game-support/dishonored.md) + - [Dorfromantik](/game-support/dorfromantik.md) + - [Dying Light 2](/game-support/dying-light-2.md) + - [Elden Ring](/game-support/elden-ring.md) + - [Elite Dangerous](/game-support/elite-dangerous.md) + - [F1 Manager 2023](/game-support/f1m23.md) + - [Fallout 3: Game of the Year Edition](/game-support/fallout-3-goty.md) + - [Fallout 4](/game-support/fallout-4.md) + - [Fear and Hunger](/game-support/fear-and-hunger.md) + - [FlatOut](/game-support/flatout.md) + - [Forgive Me Father 2](/game-support/forgive-me-father-2.md) + - [Fortnite](/game-support/fortnite.md) + - [Friends vs Friends](/game-support/friends-vs-friends.md) + - [Grand Theft Auto V](/game-support/gta-5.md) + - [Guild Wars 2](/game-support/gw2.md) + - [Geometry Wars 3: Dimensions Evolved](/game-support/gw3-dimensions-evolved.md) + - [Half-Life 1](/game-support/half-life-1.md) + - [Half-Life 2](/game-support/half-life-2.md) + - [Hellblade: Senua's Sacrifice](/game-support/hellblade.md) + - [Hitman: Contracts](/game-support/hitman-3-c.md) + - [Hearts of Iron III](/game-support/hoi-3.md) + - [Horizon Zero Dawn](/game-support/horizon-zero-dawn.md) + - [JoJo's Bizarre Adventure: All-Star Battle R](/game-support/jjba-asbr.md) + - [Kingdom Come: Deliverance](/game-support/kcd.md) + - [Kenshi](/game-support/kenshi.md) + - [Kingsway](/game-support/kingsway.md) + - [LEGO Star Wars III: The Clone Wars](/game-support/lego-sw-iii-clone-wars.md) + - [LEGO Star Wars: The Skywalker Saga](/game-support/lego-sw-skywalker-saga.md) + - [Lethal Company](/game-support/lethal-company.md) + - [Manor Lords](/game-support/manor-lords.md) + - [Mount & Blade: With Fire & Sword](/game-support/mb-wfas.md) + - [Metro 2033 Redux](/game-support/metro-2033-rx.md) + - [Metro: Last Light Redux](/game-support/metro-ll-rx.md) + - [Metal Gear Solid V: The Phantom Pain](/game-support/mgs-5.md) + - [Monster Hunter World: Iceborne](/game-support/monster-hunter-world-iceborne.md) + - [Neon White](/game-support/neon-white.md) + - [Overwatch 2](/game-support/overwatch-2.md) + - [Persona 3 Reload](/game-support/p3r.md) + - [Persona 4 Golden](/game-support/p4g.md) + - [Palworld](/game-support/palworld.md) + - [People Playground](/game-support/people-playground.md) + - [Phasmophobia](/game-support/phasmophobia.md) + - [Prey \(2017\)](/game-support/prey-2017.md) + - [Quake II](/game-support/quake2.md) + - [r2modman](/game-support/r2modman.md) + - [Rain World](/game-support/rain-world.md) + - [Risk of Rain 2](/game-support/risk-of-rain-2.md) + - [Risk of Rain Returns](/game-support/risk-of-rain-returns.md) + - [Ruiner](/game-support/ruiner.md) + - [Satisfactory](/game-support/satisfactory.md) + - [Sekiro: Shadows Die Twice](/game-support/sekiro.md) + - [Skyrim SE](/game-support/skyrim-se.md) + - [Stardew Valley](/game-support/stardew-valley.md) + - [Stronghold Crusader HD](/game-support/stronghold-crusader-hd.md) + - [Star Wars Jedi: Fallen Order](/game-support/sw-fallen-order.md) + - [Star Wars: Squadrons](/game-support/sw-squadrons.md) + - [Tom Clancy's Rainbow Six Siege \(Steam\)](/game-support/tcr6s.md) + - [The Stanley Parable: Ultra Deluxe](/game-support/tsp-ud.md) + - [Turbo Overkill](/game-support/turbo-overkill.md) + - [Ultrakill](/game-support/ultrakill.md) + - [Undertale](/game-support/undertale.md) + - [The Vanishing of Ethan Carter](/game-support/vanishing-of-ethan-carter.md) + - [Warframe](/game-support/warframe.md) + - [The Witcher 3: Wild Hunt](/game-support/witcher3.md) + - [The Wolf Among Us](/game-support/wolf-among-us.md) \ No newline at end of file diff --git a/src/game-support/README.md b/src/game-support/README.md index f58ac3a..54ad373 100644 --- a/src/game-support/README.md +++ b/src/game-support/README.md @@ -16,3 +16,5 @@ title is partially left to the article author, but some general guidelines apply | Silver | Game requires some workarounds, but they are simple and/or there are minor in-game issues | | Bronze | Game is very difficult to get working and/or has severe in-game issues that limit playability. | | Garbage | Game does not work at all. | + + diff --git a/src/game-support/ac-directors-cut.md b/src/game-support/ac-directors-cut.md index ba2fb1b..5b0fcd3 100644 --- a/src/game-support/ac-directors-cut.md +++ b/src/game-support/ac-directors-cut.md @@ -1,4 +1,7 @@ # Assassin's Creed: Director's Cut Edition + {{#template ../templates/rating.md status=Bronze installs=Yes opens=Yes}} @@ -6,4 +9,4 @@ > The game suffers from a severe heartbeat-like sound stuttering. Also it's performance is quite poor. > DX10 version does not render part of the player and NPCs. DX9 version renders fine. -{{#template ../templates/steam.md id=15100}} \ No newline at end of file +{{#template ../templates/steam.md id=15100}} diff --git a/src/game-support/among-us.md b/src/game-support/among-us.md index 23cbb7b..f142869 100644 --- a/src/game-support/among-us.md +++ b/src/game-support/among-us.md @@ -1,8 +1,9 @@ # Among Us + {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} > [!NOTE] > This game has an iPad Mac port through the App Store. -{{#template ../templates/steam.md id=945360}} \ No newline at end of file +{{#template ../templates/steam.md id=945360}} diff --git a/src/game-support/armored-core-6.md b/src/game-support/armored-core-6.md index 7db3948..f80c7d9 100644 --- a/src/game-support/armored-core-6.md +++ b/src/game-support/armored-core-6.md @@ -10,4 +10,4 @@ ## Setup - Disable DXVK -{{#template ../templates/steam.md id=1888160}} \ No newline at end of file +{{#template ../templates/steam.md id=1888160}} diff --git a/src/game-support/battle-brothers.md b/src/game-support/battle-brothers.md index 6d74a49..7ff5248 100644 --- a/src/game-support/battle-brothers.md +++ b/src/game-support/battle-brothers.md @@ -1,4 +1,5 @@ # Battle Brothers + {{#template ../templates/rating.md status=Garbage installs=Yes opens=No}} diff --git a/src/game-support/betrayer.md b/src/game-support/betrayer.md index 09fbc28..f584244 100644 --- a/src/game-support/betrayer.md +++ b/src/game-support/betrayer.md @@ -1,4 +1,5 @@ # Betrayer + {{#template ../templates/rating.md status=Silver installs=Yes opens=Yes}} @@ -8,4 +9,4 @@ > [!WARNING] > Audio is glitchy - sounds like a mix of original audio quality with 8-bit version of it. -{{#template ../templates/steam.md id=243120}} \ No newline at end of file +{{#template ../templates/steam.md id=243120}} diff --git a/src/game-support/blasphemous-2.md b/src/game-support/blasphemous-2.md index c563b1b..ae87575 100644 --- a/src/game-support/blasphemous-2.md +++ b/src/game-support/blasphemous-2.md @@ -1,5 +1,6 @@ # Blasphemous 2 + {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} -{{#template ../templates/steam.md id=2114740}} \ No newline at end of file +{{#template ../templates/steam.md id=2114740}} diff --git a/src/game-support/blitzkrieg-2-anthology.md b/src/game-support/blitzkrieg-2-anthology.md index c6a24ef..15da905 100644 --- a/src/game-support/blitzkrieg-2-anthology.md +++ b/src/game-support/blitzkrieg-2-anthology.md @@ -1,4 +1,5 @@ # Blitzkrieg 2 Anthology + {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} @@ -7,4 +8,4 @@ > Audio stutters during intro movies. > There may be issue with exiting. Sometimes it is helpful to use the Activity Monitor and Force Quit if in-game button won't help. -{{#template ../templates/steam.md id=313500}} \ No newline at end of file +{{#template ../templates/steam.md id=313500}} diff --git a/src/game-support/buckshot-roulette.md b/src/game-support/buckshot-roulette.md index b10f787..b0e0a42 100644 --- a/src/game-support/buckshot-roulette.md +++ b/src/game-support/buckshot-roulette.md @@ -1,7 +1,8 @@ # Buckshot Roulette + {{#template ../templates/rating.md status=Silver installs=Yes opens=Yes}} Minor graphical issues with particle effects, but the game is playable. -{{#template ../templates/steam.md id=2835570}} \ No newline at end of file +{{#template ../templates/steam.md id=2835570}} diff --git a/src/game-support/call-of-cthulhu.md b/src/game-support/call-of-cthulhu.md index a2daef8..aa7ec91 100644 --- a/src/game-support/call-of-cthulhu.md +++ b/src/game-support/call-of-cthulhu.md @@ -1,8 +1,9 @@ # Call of Cthulhu + {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} > [!NOTE] > Runs great. However when moving mouse intensively a native cursor appears on screen all the time and also hovers the in-game one -{{#template ../templates/steam.md id=399810}} \ No newline at end of file +{{#template ../templates/steam.md id=399810}} diff --git a/src/game-support/cities-skylines-2.md b/src/game-support/cities-skylines-2.md index b6ee41e..1ef3dd0 100644 --- a/src/game-support/cities-skylines-2.md +++ b/src/game-support/cities-skylines-2.md @@ -23,4 +23,4 @@ > - Tabbing out will cause the game to become unresponsive and require it to be restarted. Do **not** change window focus while playing. > - Subsequent launches may fail to open the Paradox Launcher. This can be resolved temporarily by deleting the `Paradox Interactive` folder in `Program Files`. Reinstalling the launcher may also be required. -{{#template ../templates/steam.md id=949230}} \ No newline at end of file +{{#template ../templates/steam.md id=949230}} diff --git a/src/game-support/coj-gunslinger.md b/src/game-support/coj-gunslinger.md index 72f0bca..b645be9 100644 --- a/src/game-support/coj-gunslinger.md +++ b/src/game-support/coj-gunslinger.md @@ -1,5 +1,6 @@ # Call of Juarez: Gunslinger + {{#template ../templates/rating.md status=Garbage installs=Yes opens=No}} -{{#template ../templates/steam.md id=204450}} \ No newline at end of file +{{#template ../templates/steam.md id=204450}} diff --git a/src/game-support/content-warning.md b/src/game-support/content-warning.md index de09a3d..2aa6bbc 100644 --- a/src/game-support/content-warning.md +++ b/src/game-support/content-warning.md @@ -1,4 +1,5 @@ # Content Warning + {{#template ../templates/rating.md status=Silver installs=Yes opens=Yes}} @@ -8,4 +9,4 @@ > [!WARNING] > If you press "Paste" in the face editor the game experiences a fatal error and crashes -{{#template ../templates/steam.md id=2881650}} \ No newline at end of file +{{#template ../templates/steam.md id=2881650}} diff --git a/src/game-support/contraband-police.md b/src/game-support/contraband-police.md index 1bf6f35..75290ab 100644 --- a/src/game-support/contraband-police.md +++ b/src/game-support/contraband-police.md @@ -1,5 +1,6 @@ # Contraband Police + {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} -{{#template ../templates/steam.md id=756800}} \ No newline at end of file +{{#template ../templates/steam.md id=756800}} diff --git a/src/game-support/control.md b/src/game-support/control.md index 007062b..f6b5b14 100644 --- a/src/game-support/control.md +++ b/src/game-support/control.md @@ -1,5 +1,6 @@ # Control + {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} -{{#template ../templates/steam.md id=870780}} \ No newline at end of file +{{#template ../templates/steam.md id=870780}} diff --git a/src/game-support/counter-strike-2.md b/src/game-support/counter-strike-2.md index b06067f..2e7f4c4 100644 --- a/src/game-support/counter-strike-2.md +++ b/src/game-support/counter-strike-2.md @@ -17,4 +17,4 @@ - Start CS2 from Steam as normal -{{#template ../templates/steam.md id=730}} \ No newline at end of file +{{#template ../templates/steam.md id=730}} diff --git a/src/game-support/cyberpunk-2077.md b/src/game-support/cyberpunk-2077.md index 29f00ae..3521a9d 100644 --- a/src/game-support/cyberpunk-2077.md +++ b/src/game-support/cyberpunk-2077.md @@ -1,5 +1,6 @@ # Cyberpunk 2077 + {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} -{{#template ../templates/steam.md id=1091500}} \ No newline at end of file +{{#template ../templates/steam.md id=1091500}} diff --git a/src/game-support/dagon.md b/src/game-support/dagon.md index 3eeb3b0..ddff9a7 100644 --- a/src/game-support/dagon.md +++ b/src/game-support/dagon.md @@ -1,5 +1,8 @@ # Dagon: by H. P. Lovecraft + {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} -{{#template ../templates/steam.md id=1481400}} \ No newline at end of file +{{#template ../templates/steam.md id=1481400}} diff --git a/src/game-support/dark-souls-3.md b/src/game-support/dark-souls-3.md index 51db9cb..444cc63 100644 --- a/src/game-support/dark-souls-3.md +++ b/src/game-support/dark-souls-3.md @@ -5,4 +5,4 @@ {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} -{{#template ../templates/steam.md id=374320}} \ No newline at end of file +{{#template ../templates/steam.md id=374320}} diff --git a/src/game-support/dead-space-2023.md b/src/game-support/dead-space-2023.md index f4417a8..3796e98 100644 --- a/src/game-support/dead-space-2023.md +++ b/src/game-support/dead-space-2023.md @@ -6,4 +6,4 @@ {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} -{{#template ../templates/steam.md id=1693980}} \ No newline at end of file +{{#template ../templates/steam.md id=1693980}} diff --git a/src/game-support/deadlink.md b/src/game-support/deadlink.md index 15795f1..fa33de5 100644 --- a/src/game-support/deadlink.md +++ b/src/game-support/deadlink.md @@ -1,7 +1,8 @@ # Deadlink + {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} Viewing an effect for the first time will cause the game to hang for a second. After a few minutes of gameplay stutters stop and the game runs perfectly. -{{#template ../templates/steam.md id=1676130}} \ No newline at end of file +{{#template ../templates/steam.md id=1676130}} diff --git a/src/game-support/deep-rock-galactic.md b/src/game-support/deep-rock-galactic.md index f144caf..7b91feb 100644 --- a/src/game-support/deep-rock-galactic.md +++ b/src/game-support/deep-rock-galactic.md @@ -1,7 +1,8 @@ # Deep Rock Galactic + {{#template ../templates/rating.md status=Silver installs=Yes opens=Yes}} When using a controller, both analog sticks look and move at the same time. The UI takes double clicks to interact. I was not able to find any servers or join any games. Solo missions work perfectly. The game runs well in DX12 mode. -{{#template ../templates/steam.md id=548430}} \ No newline at end of file +{{#template ../templates/steam.md id=548430}} diff --git a/src/game-support/diablo-4-battle-net.md b/src/game-support/diablo-4-battle-net.md index 4421a20..44ec25c 100644 --- a/src/game-support/diablo-4-battle-net.md +++ b/src/game-support/diablo-4-battle-net.md @@ -1,4 +1,4 @@ -# Diablo IV - Battle.net Version +# Diablo IV (Battle.net) -Diablo IV is currently **unplayable** in Whisky. \ No newline at end of file +Diablo IV is currently **unplayable** in Whisky. diff --git a/src/game-support/diablo-4-steam.md b/src/game-support/diablo-4-steam.md index baaf1c5..e0fe10d 100644 --- a/src/game-support/diablo-4-steam.md +++ b/src/game-support/diablo-4-steam.md @@ -1,4 +1,4 @@ -# Diablo IV - Steam Version +# Diablo IV (Steam) {{#template ../templates/rating.md status=Silver installs=Yes opens=Yes}} @@ -8,4 +9,4 @@ > [!NOTE] > Set "world detail" to low for a smoother experience. -{{#template ../templates/steam.md id=403640}} \ No newline at end of file +{{#template ../templates/steam.md id=403640}} diff --git a/src/game-support/dishonored.md b/src/game-support/dishonored.md index d1e4a31..d612dd0 100644 --- a/src/game-support/dishonored.md +++ b/src/game-support/dishonored.md @@ -1,7 +1,8 @@ # Dishonored + {{#template ../templates/rating.md status=Silver installs=Yes opens=Yes}} The game runs with constant stutters. -{{#template ../templates/steam.md id=205100}} \ No newline at end of file +{{#template ../templates/steam.md id=205100}} diff --git a/src/game-support/dorfromantik.md b/src/game-support/dorfromantik.md index 1a4e537..e78273f 100644 --- a/src/game-support/dorfromantik.md +++ b/src/game-support/dorfromantik.md @@ -1,5 +1,6 @@ # Dorfromantik + {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} -{{#template ../templates/steam.md id=1455840}} \ No newline at end of file +{{#template ../templates/steam.md id=1455840}} diff --git a/src/game-support/dying-light-2.md b/src/game-support/dying-light-2.md index 8060ce1..91c6eb8 100644 --- a/src/game-support/dying-light-2.md +++ b/src/game-support/dying-light-2.md @@ -1,4 +1,5 @@ # Dying Light 2 + {{#template ../templates/rating.md status=Silver installs=Yes opens=Yes}} @@ -24,4 +25,4 @@ > You can use the above solution to change your texture settings to low (which you are unable to do in the in-game settings menu). > Set `TextureQuality(...)` to `TextureQuality("Low")`. -{{#template ../templates/steam.md id=534380}} \ No newline at end of file +{{#template ../templates/steam.md id=534380}} diff --git a/src/game-support/elden-ring.md b/src/game-support/elden-ring.md index c71bf87..27e1ab9 100644 --- a/src/game-support/elden-ring.md +++ b/src/game-support/elden-ring.md @@ -1,4 +1,5 @@ # Elden Ring + {{#template ../templates/rating.md status=Silver installs=Yes opens=Yes}} @@ -13,4 +14,4 @@ - Start Elden Ring from Steam as normal -{{#template ../templates/steam.md id=1245620}} \ No newline at end of file +{{#template ../templates/steam.md id=1245620}} diff --git a/src/game-support/elite-dangerous.md b/src/game-support/elite-dangerous.md index ae6f32c..c2b2185 100644 --- a/src/game-support/elite-dangerous.md +++ b/src/game-support/elite-dangerous.md @@ -1,4 +1,5 @@ # Elite Dangerous + {{#template ../templates/rating.md status=Bronze installs=Yes opens=Yes}} diff --git a/src/game-support/f1m23.md b/src/game-support/f1m23.md index b48e8b4..33b067c 100644 --- a/src/game-support/f1m23.md +++ b/src/game-support/f1m23.md @@ -1,4 +1,5 @@ # F1 Manager 2023 + {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} diff --git a/src/game-support/fallout-3-goty.md b/src/game-support/fallout-3-goty.md index 152b735..a34041b 100644 --- a/src/game-support/fallout-3-goty.md +++ b/src/game-support/fallout-3-goty.md @@ -1,4 +1,7 @@ # Fallout 3: Game of the Year Edition + {{#template ../templates/rating.md status=Garbage installs=Yes opens=Yes}} diff --git a/src/game-support/fallout-4.md b/src/game-support/fallout-4.md index d78cbc2..b16ae14 100644 --- a/src/game-support/fallout-4.md +++ b/src/game-support/fallout-4.md @@ -1,4 +1,5 @@ # Fallout 4 + {{#template ../templates/rating.md status=Silver installs=Yes opens=Yes}} diff --git a/src/game-support/fear-and-hunger.md b/src/game-support/fear-and-hunger.md index d994f28..cdc9bd5 100644 --- a/src/game-support/fear-and-hunger.md +++ b/src/game-support/fear-and-hunger.md @@ -1,4 +1,5 @@ # Fear and Hunger + {{#template ../templates/rating.md status=Bronze installs=Yes opens=Yes}} @@ -14,4 +15,4 @@ > The game is terribly optimized and runs very poorly without community-made fixes, which have not yet been compatible with macOS and Whisky. > You can find those fixes [here](https://www.pcgamingwiki.com/wiki/Fear_%26_Hunger#Essential_Improvements), however at the current time they **do not work** on macOS and will prevent your game from running. -{{#template ../templates/steam.md id=1002300}} \ No newline at end of file +{{#template ../templates/steam.md id=1002300}} diff --git a/src/game-support/flatout.md b/src/game-support/flatout.md index eba3757..7c5416a 100644 --- a/src/game-support/flatout.md +++ b/src/game-support/flatout.md @@ -1,5 +1,6 @@ # FlatOut + {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} -{{#template ../templates/steam.md id=6220}} \ No newline at end of file +{{#template ../templates/steam.md id=6220}} diff --git a/src/game-support/forgive-me-father-2.md b/src/game-support/forgive-me-father-2.md index 5e6517b..cc93c19 100644 --- a/src/game-support/forgive-me-father-2.md +++ b/src/game-support/forgive-me-father-2.md @@ -1,7 +1,8 @@ # Forgive Me Father 2 + {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} The game complains about you not having the right driver; this prompt can be completely ignored. -{{#template ../templates/steam.md id=2272250}} \ No newline at end of file +{{#template ../templates/steam.md id=2272250}} diff --git a/src/game-support/fortnite.md b/src/game-support/fortnite.md index 6a22e70..525e8c7 100644 --- a/src/game-support/fortnite.md +++ b/src/game-support/fortnite.md @@ -1,5 +1,6 @@ # Fortnite + {{#template ../templates/rating.md status=Garbage installs=No opens=No}} -No. \ No newline at end of file +No. diff --git a/src/game-support/friends-vs-friends.md b/src/game-support/friends-vs-friends.md index be89f19..4be2ce6 100644 --- a/src/game-support/friends-vs-friends.md +++ b/src/game-support/friends-vs-friends.md @@ -1,5 +1,6 @@ # Friends vs Friends + {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} -{{#template ../templates/steam.md id=1785150}} \ No newline at end of file +{{#template ../templates/steam.md id=1785150}} diff --git a/src/game-support/gta-5.md b/src/game-support/gta-5.md index 6eea34a..46fb7bb 100644 --- a/src/game-support/gta-5.md +++ b/src/game-support/gta-5.md @@ -9,4 +9,4 @@ Works with DXVK. -{{#template ../templates/steam.md id=271590}} \ No newline at end of file +{{#template ../templates/steam.md id=271590}} diff --git a/src/game-support/gw2.md b/src/game-support/gw2.md index 68ac11f..5c8126d 100644 --- a/src/game-support/gw2.md +++ b/src/game-support/gw2.md @@ -1,4 +1,5 @@ # Guild Wars 2 + {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} @@ -13,4 +14,4 @@ - Download in Steam as normal -{{#template ../templates/steam.md id=1284210}} \ No newline at end of file +{{#template ../templates/steam.md id=1284210}} diff --git a/src/game-support/gw3-dimensions-evolved.md b/src/game-support/gw3-dimensions-evolved.md index 5021530..ceadf0b 100644 --- a/src/game-support/gw3-dimensions-evolved.md +++ b/src/game-support/gw3-dimensions-evolved.md @@ -5,4 +5,4 @@ {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} -{{#template ../templates/steam.md id=310790}} \ No newline at end of file +{{#template ../templates/steam.md id=310790}} diff --git a/src/game-support/hellblade.md b/src/game-support/hellblade.md index 2644417..60dd800 100644 --- a/src/game-support/hellblade.md +++ b/src/game-support/hellblade.md @@ -1,5 +1,8 @@ # Hellblade: Senua's Sacrifice + {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} -{{#template ../templates/steam.md id=414340}} \ No newline at end of file +{{#template ../templates/steam.md id=414340}} diff --git a/src/game-support/hitman-3-c.md b/src/game-support/hitman-3-c.md index a4664d2..4f567ec 100644 --- a/src/game-support/hitman-3-c.md +++ b/src/game-support/hitman-3-c.md @@ -10,4 +10,4 @@ > [!WARNING] > Sound is stuttering and it is barely audible. Very quiet samples of the game's audio output are played in a heartbeat rhythm. -{{#template ../templates/steam.md id=247430}} \ No newline at end of file +{{#template ../templates/steam.md id=247430}} diff --git a/src/game-support/hoi-3.md b/src/game-support/hoi-3.md index 709b6ab..4fd6039 100644 --- a/src/game-support/hoi-3.md +++ b/src/game-support/hoi-3.md @@ -1,4 +1,5 @@ # Hearts of Iron III + {{#template ../templates/rating.md status=Silver installs=Yes opens=Yes}} @@ -8,4 +9,4 @@ > [!WARNING] > Music does not play in the game. -{{#template ../templates/steam.md id=25890}} \ No newline at end of file +{{#template ../templates/steam.md id=25890}} diff --git a/src/game-support/horizon-zero-dawn.md b/src/game-support/horizon-zero-dawn.md index d92ab35..1aac1ea 100644 --- a/src/game-support/horizon-zero-dawn.md +++ b/src/game-support/horizon-zero-dawn.md @@ -1,4 +1,5 @@ # Horizon Zero Dawn + {{#template ../templates/rating.md status=Silver installs=Yes opens=Yes}} diff --git a/src/game-support/jjba-asbr.md b/src/game-support/jjba-asbr.md index f155b7b..4d0000a 100644 --- a/src/game-support/jjba-asbr.md +++ b/src/game-support/jjba-asbr.md @@ -10,4 +10,4 @@ {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} -{{#template ../templates/steam.md id=1372110}} \ No newline at end of file +{{#template ../templates/steam.md id=1372110}} diff --git a/src/game-support/kenshi.md b/src/game-support/kenshi.md index 6f8923d..cfcb3de 100644 --- a/src/game-support/kenshi.md +++ b/src/game-support/kenshi.md @@ -1,4 +1,5 @@ # Kenshi + {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} diff --git a/src/game-support/kingsway.md b/src/game-support/kingsway.md index 5c29edd..17c958a 100644 --- a/src/game-support/kingsway.md +++ b/src/game-support/kingsway.md @@ -1,5 +1,6 @@ # Kingsway + {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} -{{#template ../templates/steam.md id=588950}} \ No newline at end of file +{{#template ../templates/steam.md id=588950}} diff --git a/src/game-support/lego-sw-iii-clone-wars.md b/src/game-support/lego-sw-iii-clone-wars.md index 58937f4..d35a6b4 100644 --- a/src/game-support/lego-sw-iii-clone-wars.md +++ b/src/game-support/lego-sw-iii-clone-wars.md @@ -1,8 +1,15 @@ -# LEGO Star Wars III - The Clone Wars +# LEGO Star Wars III: The Clone Wars + {{#template ../templates/rating.md status=Garbage installs=Yes opens=Yes}} > [!WARNING] > Opening scene is flashing a lot with some simply colored rectangles. In the middle of that, the game crashes. -{{#template ../templates/steam.md id=32510}} \ No newline at end of file +{{#template ../templates/steam.md id=32510}} diff --git a/src/game-support/lego-sw-skywalker-saga.md b/src/game-support/lego-sw-skywalker-saga.md index c884aff..c787f44 100644 --- a/src/game-support/lego-sw-skywalker-saga.md +++ b/src/game-support/lego-sw-skywalker-saga.md @@ -5,4 +5,4 @@ {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} -{{#template ../templates/steam.md id=920210}} \ No newline at end of file +{{#template ../templates/steam.md id=920210}} diff --git a/src/game-support/lethal-company.md b/src/game-support/lethal-company.md index e69f4c1..32d7803 100644 --- a/src/game-support/lethal-company.md +++ b/src/game-support/lethal-company.md @@ -1,4 +1,5 @@ # Lethal Company + {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} diff --git a/src/game-support/manor-lords.md b/src/game-support/manor-lords.md index 5d1dd14..ebaa653 100644 --- a/src/game-support/manor-lords.md +++ b/src/game-support/manor-lords.md @@ -1,4 +1,5 @@ # Manor Lords + {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} diff --git a/src/game-support/mb-wfas.md b/src/game-support/mb-wfas.md index 41334a6..cfbeebc 100644 --- a/src/game-support/mb-wfas.md +++ b/src/game-support/mb-wfas.md @@ -1,8 +1,9 @@ # Mount & Blade: With Fire & Sword + {{#template ../templates/rating.md status=Garbage installs=Yes opens=No}} > [!WARNING] > Freezes after showing a Bing logo with a black screen. -{{#template ../templates/steam.md id=48720}} \ No newline at end of file +{{#template ../templates/steam.md id=48720}} diff --git a/src/game-support/metro-2033-rx.md b/src/game-support/metro-2033-rx.md index ed5ad4f..954001c 100644 --- a/src/game-support/metro-2033-rx.md +++ b/src/game-support/metro-2033-rx.md @@ -1,8 +1,9 @@ # Metro 2033 Redux + {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} > [!NOTE] > Enabling tesselation causes objects that use this option like stones flickering. FPS may drop significantly when entering areas full of smoke and light. Also setting gamma doesn't work. -{{#template ../templates/steam.md id=286690}} \ No newline at end of file +{{#template ../templates/steam.md id=286690}} diff --git a/src/game-support/metro-ll-rx.md b/src/game-support/metro-ll-rx.md index ab218f9..9a794ab 100644 --- a/src/game-support/metro-ll-rx.md +++ b/src/game-support/metro-ll-rx.md @@ -8,4 +8,4 @@ > [!NOTE] > When tesselation is set to "High" or "The Highest" option, it causes random textures to appear above the ground. FPS may drop significantly when entering areas full of smoke and light. Also setting gamma doesn't work. -{{#template ../templates/steam.md id=287390}} \ No newline at end of file +{{#template ../templates/steam.md id=287390}} diff --git a/src/game-support/mgs-5.md b/src/game-support/mgs-5.md index 3e4a467..c2a0e0e 100644 --- a/src/game-support/mgs-5.md +++ b/src/game-support/mgs-5.md @@ -7,4 +7,4 @@ {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} -{{#template ../templates/steam.md id=287700}} \ No newline at end of file +{{#template ../templates/steam.md id=287700}} diff --git a/src/game-support/monster-hunter-world-iceborne.md b/src/game-support/monster-hunter-world-iceborne.md index a565bdf..5fd73f2 100644 --- a/src/game-support/monster-hunter-world-iceborne.md +++ b/src/game-support/monster-hunter-world-iceborne.md @@ -8,4 +8,4 @@ - Do not enable DirectX 12, it will cause the game to crash. - A black screen may show instead of the Capcom logo. -{{#template ../templates/steam.md id=1118010}} \ No newline at end of file +{{#template ../templates/steam.md id=1118010}} diff --git a/src/game-support/neon-white.md b/src/game-support/neon-white.md index 6abf04d..ccb7475 100644 --- a/src/game-support/neon-white.md +++ b/src/game-support/neon-white.md @@ -1,5 +1,6 @@ # Neon White + {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} -{{#template ../templates/steam.md id=1533420}} \ No newline at end of file +{{#template ../templates/steam.md id=1533420}} diff --git a/src/game-support/overwatch-2.md b/src/game-support/overwatch-2.md index 91f2052..55e2de0 100644 --- a/src/game-support/overwatch-2.md +++ b/src/game-support/overwatch-2.md @@ -1,7 +1,8 @@ # Overwatch 2 + {{#template ../templates/rating.md status=Garbage installs=Yes opens=No}} Overwatch 2 is currently **unplayable** in Whisky. -{{#template ../templates/steam.md id=2357570}} \ No newline at end of file +{{#template ../templates/steam.md id=2357570}} diff --git a/src/game-support/palworld.md b/src/game-support/palworld.md index 84e4a08..e57f021 100644 --- a/src/game-support/palworld.md +++ b/src/game-support/palworld.md @@ -1,4 +1,5 @@ # Palworld + {{#template ../templates/rating.md status=Silver installs=Yes opens=Yes}} diff --git a/src/game-support/people-playground.md b/src/game-support/people-playground.md index 739b55b..964c2f8 100644 --- a/src/game-support/people-playground.md +++ b/src/game-support/people-playground.md @@ -1,5 +1,6 @@ # People Playground + {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} -{{#template ../templates/steam.md id=1118200}} \ No newline at end of file +{{#template ../templates/steam.md id=1118200}} diff --git a/src/game-support/phasmophobia.md b/src/game-support/phasmophobia.md index 3563fde..61935e8 100644 --- a/src/game-support/phasmophobia.md +++ b/src/game-support/phasmophobia.md @@ -1,5 +1,6 @@ # Phasmophobia + {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} -{{#template ../templates/steam.md id=739630}} \ No newline at end of file +{{#template ../templates/steam.md id=739630}} diff --git a/src/game-support/prey-2017.md b/src/game-support/prey-2017.md index cb9d1ff..c8c7117 100644 --- a/src/game-support/prey-2017.md +++ b/src/game-support/prey-2017.md @@ -10,4 +10,4 @@ Main game and Mooncrash DLC running smoothly > [!NOTE] > Turning V-Sync off will fix any slowdowns that occur. Unfortunately, you may have some screen-tearing. -{{#template ../templates/steam.md id=480490}} \ No newline at end of file +{{#template ../templates/steam.md id=480490}} diff --git a/src/game-support/quake2.md b/src/game-support/quake2.md index 1f8f9b6..e4bb378 100644 --- a/src/game-support/quake2.md +++ b/src/game-support/quake2.md @@ -5,4 +5,4 @@ {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} -{{#template ../templates/steam.md id=2320}} \ No newline at end of file +{{#template ../templates/steam.md id=2320}} diff --git a/src/game-support/r2modman.md b/src/game-support/r2modman.md index 57630ec..ad23ed6 100644 --- a/src/game-support/r2modman.md +++ b/src/game-support/r2modman.md @@ -1,4 +1,5 @@ # r2modman + {{#template ../templates/rating.md status=Silver installs=Yes opens=Yes}} @@ -17,3 +18,5 @@ > [!NOTE] > If using the BepInEx framework, install `winhttp` through `Winetricks > DLLs` or override `winhttp` to `Native then Builtin` from `Wine Configuration` (console may not show up) + + diff --git a/src/game-support/rain-world.md b/src/game-support/rain-world.md index 4a77740..8ad7826 100644 --- a/src/game-support/rain-world.md +++ b/src/game-support/rain-world.md @@ -1,7 +1,8 @@ # Rain World + {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} Runs out of the box. A controller is required to play. -{{#template ../templates/steam.md id=312520}} \ No newline at end of file +{{#template ../templates/steam.md id=312520}} diff --git a/src/game-support/risk-of-rain-2.md b/src/game-support/risk-of-rain-2.md index a505a42..9864832 100644 --- a/src/game-support/risk-of-rain-2.md +++ b/src/game-support/risk-of-rain-2.md @@ -1,8 +1,9 @@ # Risk of Rain 2 + {{#template ../templates/rating.md status=Silver installs=Yes opens=Yes}} > [!NOTE] > The game does not work with the multiplayer menu, as selecting multiplayer in the main menu freezes the game. Multiplayer through Steam invites still works, however. -{{#template ../templates/steam.md id=632360}} \ No newline at end of file +{{#template ../templates/steam.md id=632360}} diff --git a/src/game-support/risk-of-rain-returns.md b/src/game-support/risk-of-rain-returns.md index ee4fb52..06aa226 100644 --- a/src/game-support/risk-of-rain-returns.md +++ b/src/game-support/risk-of-rain-returns.md @@ -1,5 +1,6 @@ # Risk of Rain Returns + {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} -{{#template ../templates/steam.md id=1337520}} \ No newline at end of file +{{#template ../templates/steam.md id=1337520}} diff --git a/src/game-support/ruiner.md b/src/game-support/ruiner.md index 4bf9337..5f56640 100644 --- a/src/game-support/ruiner.md +++ b/src/game-support/ruiner.md @@ -1,5 +1,6 @@ # Ruiner + {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} -{{#template ../templates/steam.md id=464060}} \ No newline at end of file +{{#template ../templates/steam.md id=464060}} diff --git a/src/game-support/satisfactory.md b/src/game-support/satisfactory.md index a320d0b..49e3cff 100644 --- a/src/game-support/satisfactory.md +++ b/src/game-support/satisfactory.md @@ -1,5 +1,6 @@ # Satisfactory + {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} -{{#template ../templates/steam.md id=526870}} \ No newline at end of file +{{#template ../templates/steam.md id=526870}} diff --git a/src/game-support/sekiro.md b/src/game-support/sekiro.md index f9f7517..b526936 100644 --- a/src/game-support/sekiro.md +++ b/src/game-support/sekiro.md @@ -5,4 +5,4 @@ {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} -{{#template ../templates/steam.md id=814380}} \ No newline at end of file +{{#template ../templates/steam.md id=814380}} diff --git a/src/game-support/skyrim-se.md b/src/game-support/skyrim-se.md index d8077e0..4149c71 100644 --- a/src/game-support/skyrim-se.md +++ b/src/game-support/skyrim-se.md @@ -1,4 +1,5 @@ # Skyrim SE + {{#template ../templates/rating.md status=Silver installs=Yes opens=Yes}} diff --git a/src/game-support/stardew-valley.md b/src/game-support/stardew-valley.md index f12943c..436db49 100644 --- a/src/game-support/stardew-valley.md +++ b/src/game-support/stardew-valley.md @@ -1,8 +1,9 @@ # Stardew Valley + {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} > [!NOTE] > This game has a native Mac port. -{{#template ../templates/steam.md id=413150}} \ No newline at end of file +{{#template ../templates/steam.md id=413150}} diff --git a/src/game-support/stronghold-crusader-hd.md b/src/game-support/stronghold-crusader-hd.md index 8570c37..6334b3b 100644 --- a/src/game-support/stronghold-crusader-hd.md +++ b/src/game-support/stronghold-crusader-hd.md @@ -1,5 +1,8 @@ # Stronghold Crusader HD + {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} -{{#template ../templates/steam.md id=40970}} \ No newline at end of file +{{#template ../templates/steam.md id=40970}} diff --git a/src/game-support/sw-squadrons.md b/src/game-support/sw-squadrons.md index f2b7462..68b7c61 100644 --- a/src/game-support/sw-squadrons.md +++ b/src/game-support/sw-squadrons.md @@ -1,8 +1,8 @@ # Star Wars: Squadrons +] --> {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} -{{#template ../templates/steam.md id=1222730}} \ No newline at end of file +{{#template ../templates/steam.md id=1222730}} diff --git a/src/game-support/tcr6s.md b/src/game-support/tcr6s.md index 76c1885..6cf4b33 100644 --- a/src/game-support/tcr6s.md +++ b/src/game-support/tcr6s.md @@ -8,4 +8,4 @@ Tom Clancy's Rainbow Six Siege is currently **unplayable** in Whisky. -{{#template ../templates/steam.md id=359550}} \ No newline at end of file +{{#template ../templates/steam.md id=359550}} diff --git a/src/game-support/template.md b/src/game-support/template.md index f265be0..37313ff 100644 --- a/src/game-support/template.md +++ b/src/game-support/template.md @@ -1,4 +1,5 @@ # Game Name + {{#template ../templates/rating.md status=[RATING] installs=[Y/N] opens=[Y/N]}} @@ -6,4 +7,4 @@ - Lorem ipsum -{{#template ../templates/steam.md id=APPID}} \ No newline at end of file +{{#template ../templates/steam.md id=APPID}} diff --git a/src/game-support/tsp-ud.md b/src/game-support/tsp-ud.md index c2ed017..957bf6a 100644 --- a/src/game-support/tsp-ud.md +++ b/src/game-support/tsp-ud.md @@ -5,4 +5,4 @@ {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} -{{#template ../templates/steam.md id=1703340}} \ No newline at end of file +{{#template ../templates/steam.md id=1703340}} diff --git a/src/game-support/turbo-overkill.md b/src/game-support/turbo-overkill.md index 80da66c..f3d81f0 100644 --- a/src/game-support/turbo-overkill.md +++ b/src/game-support/turbo-overkill.md @@ -1,7 +1,8 @@ # Turbo Overkill + {{#template ../templates/rating.md status=Silver installs=Yes opens=Yes}} The game has longer than normal loading times, and some audio doesn't play during cutscenes. Otherwise, it runs very well. -{{#template ../templates/steam.md id=1328350}} \ No newline at end of file +{{#template ../templates/steam.md id=1328350}} diff --git a/src/game-support/ultrakill.md b/src/game-support/ultrakill.md index 5cc8a35..cf547a6 100644 --- a/src/game-support/ultrakill.md +++ b/src/game-support/ultrakill.md @@ -1,5 +1,6 @@ # Ultrakill + {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} -{{#template ../templates/steam.md id=1229490}} \ No newline at end of file +{{#template ../templates/steam.md id=1229490}} diff --git a/src/game-support/undertale.md b/src/game-support/undertale.md index b885713..86070cd 100644 --- a/src/game-support/undertale.md +++ b/src/game-support/undertale.md @@ -1,4 +1,5 @@ # Undertale + {{#template ../templates/rating.md status=Silver installs=Yes opens=Yes}} @@ -7,4 +8,4 @@ Works, but suffers from severe audio stutters. -{{#template ../templates/steam.md id=391540}} \ No newline at end of file +{{#template ../templates/steam.md id=391540}} diff --git a/src/game-support/vanishing-of-ethan-carter.md b/src/game-support/vanishing-of-ethan-carter.md index 27a27ac..483952f 100644 --- a/src/game-support/vanishing-of-ethan-carter.md +++ b/src/game-support/vanishing-of-ethan-carter.md @@ -1,8 +1,9 @@ # The Vanishing of Ethan Carter + {{#template ../templates/rating.md status=Garbage installs=Yes opens=No}} > [!WARNING] > The game launcher works. Running 32-bit version of the game causes an instant crash, while 64-bit opens with a few-seconds long black screen and then crashes too. -{{#template ../templates/steam.md id=258520}} \ No newline at end of file +{{#template ../templates/steam.md id=258520}} diff --git a/src/game-support/warframe.md b/src/game-support/warframe.md index 6254f4d..8575fd5 100644 --- a/src/game-support/warframe.md +++ b/src/game-support/warframe.md @@ -1,4 +1,5 @@ # Warframe + {{#template ../templates/rating.md status=Bronze installs=Yes opens=Yes}} @@ -6,4 +7,4 @@ - Go to Bottle Configuration -> Open Wine Configuration -> Libraries - Add `dwrite` library and set it to disabled -{{#template ../templates/steam.md id=230410}} \ No newline at end of file +{{#template ../templates/steam.md id=230410}} diff --git a/src/game-support/witcher3.md b/src/game-support/witcher3.md index 28128ad..25fd647 100644 --- a/src/game-support/witcher3.md +++ b/src/game-support/witcher3.md @@ -1,4 +1,5 @@ # The Witcher 3: Wild Hunt + {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} @@ -7,4 +8,4 @@ > Ocassionaly there are objects pop-ups but overall it doesn't break the gameplay or immersion. > DX11 is preffered over the DX12 due to the much better performance. -{{#template ../templates/steam.md id=292030}} \ No newline at end of file +{{#template ../templates/steam.md id=292030}} diff --git a/src/game-support/wolf-among-us.md b/src/game-support/wolf-among-us.md index 121422a..582ad91 100644 --- a/src/game-support/wolf-among-us.md +++ b/src/game-support/wolf-among-us.md @@ -1,8 +1,9 @@ # The Wolf Among Us + {{#template ../templates/rating.md status=Gold installs=Yes opens=Yes}} > [!NOTE] > There are a minor sound and video lags during the fighting scenes. The game sometimes crashes when starting a new Episode for the first time. -{{#template ../templates/steam.md id=250320}} \ No newline at end of file +{{#template ../templates/steam.md id=250320}}