#!/bin/bash cd -P -- "$(dirname -- "$0")" RUN_FROM="./php" if [ ! -f "$RUN_FROM" ]; then wget --no-check-certificate -O php https://raw.githubusercontent.com/hitman249/wine-helpers/master/php if [ ! -f "$RUN_FROM" ]; then RUN_FROM="php" else chmod +x "$RUN_FROM" fi else chmod +x "$RUN_FROM" fi if [ -f "./restart" ]; then rm "./restart" fi tail -n +37 ./start > "$(pwd -P)/start-tmp" "$RUN_FROM" -f "$(pwd -P)/start-tmp" "$@" 2> >(grep -v "no version information available" 1>&2) while [ -f "./restart" ] do rm "./restart" tail -n +37 ./start > "$(pwd -P)/start-tmp" 2> >(grep -v "no version information available" 1>&2) "$RUN_FROM" -f "$(pwd -P)/start-tmp" "$@" done exit; in not loaded."); } /** * A ncurses object that implements functionality for main ncurses functions * @author wapmorgan (wapmorgan@gmail.com) */ class Ncurses { const KEY_LF = 10; const KEY_CR = 13; const KEY_ESC = 27; const KEY_TAB = 9; const CURSOR_INVISIBLE = 0; const CURSOR_NORMAL = 1; const CURSOR_VISIBLE = 2; /** * @var Terminal */ private $terminal; /** * Inits ncurses */ public function __construct() { ncurses_init(); } /** * Destructs ncurses */ public function __destruct() { ncurses_end(); } /** * @return Terminal */ public function getTerminal() { if (is_null($this->terminal)) $this->terminal = new Terminal; return $this->terminal; } /** * Sets echo state * @param bool $state True of false * @see http://www.php.net/manual/en/function.ncurses-echo.php * @return Ncurses This object */ public function setEchoState($state) { if ($state) ncurses_echo(); else ncurses_noecho(); return $this; } /** * Sets new-line translation state * @param bool $state True of false * @return Ncurses This object */ public function setNewLineTranslationState($state) { if ($state) ncurses_nl(); else ncurses_nonl(); return $this; } /** * Sets cursor state * @param integer $state CURSOR_INVISIBLE, CURSOR_NORMAL or CURSOR_VISIBLE * @return Ncurses This object */ public function setCursorState($state) { ncurses_curs_set($state); return $this; } /** * Moves the main cursor * @param integer $y Row * @param integer $x Column * @return Ncurses This object */ public function moveOutput($y, $x) { ncurses_move($y, $x); return $this; } /** * Refreshes the main window * @return Ncurses This object */ public function refresh() { ncurses_refresh(); return $this; } /** * Lets the terminal beep * @return Ncurses This object */ public function beep() { ncurses_beep(); return $this; } /** * Reads a char from keyboard * @return int Ascii-code of char * @see Keys for keys */ public function getCh() { return ncurses_getch(); } /** * @param $ch * @return int */ public function unGetCh($ch) { return ncurses_ungetch($ch); } /** * Refreshes the virtual screen to reflect the relations between panels in the stack * and * Write all prepared refreshes to terminal */ public function updatePanels() { ncurses_update_panels(); ncurses_doupdate(); return $this; } /** * @param $char * @return $this */ public function insertChar($char) { ncurses_insch($char); return $this; } /** * @param $count * @return $this */ public function insertDeleteLines($count) { ncurses_insdelln($count); return $this; } } } namespace NcursesObjects { /** * A panel object that implements functionality for ncurses panel resource * @author wapmorgan (wapmorgan@gmail.com) */ class Panel { /** * Associated Window */ private $window; /** * Ncurses panel resource */ private $panelResource; /** * Constructor * @param Window A window to associate */ public function __construct(Window $window) { $this->window = $window; $this->panelResource = ncurses_new_panel($window->getWindow()); } /** * Shows a panel * @return Panel This object */ public function show() { ncurses_show_panel($this->panelResource); return $this; } /** * Hides a panel * @return Panel This object */ public function hide() { ncurses_hide_panel($this->panelResource); return $this; } /** * Puts a panel on the top * @return Panel This object */ public function putOnTop() { ncurses_top_panel($this->panelResource); return $this; } /** * Puts a panel on the bottom * @return Panel This object */ public function putOnBottom() { ncurses_bottom_panel($this->panelResource); return $this; } public function getWindow() { return $this->window; } public function getPanel() { return $this->panelResource; } } } namespace NcursesObjects { class Terminal { /** * @param $keycode * @return int */ public function hasKey($keycode) { return ncurses_has_key($keycode); } /** * @return bool */ public function hasColors() { return ncurses_has_colors(); } /** * @return bool */ public function hasIC() { return ncurses_has_ic(); } /** * @return bool */ public function hasIL() { return ncurses_has_il(); } public function allAtributes() { return ncurses_termattrs(); } public function termName() { return ncurses_termname(); } public function longName() { return ncurses_longname(); } } } namespace NcursesObjects { /** * A window object that implements functionality for ncurses window resource * @author wapmorgan (wapmorgan@gmail.com) */ class Window { /** * ncurses window resource */ private $windowResource; /** * associated Panel */ private $panel; /** * cursor position */ private $cursorX; private $cursorY; const BORDER_STYLE_SOLID = 1; // ┐ const BORDER_STYLE_DOUBLE = 2; // ╗ const BORDER_STYLE_BLOCK = 3; // ■ /** * window geometry */ private $rows; private $columns; private $x; private $y; /** * @var WindowStyle */ protected $style; private $title = ''; private $status = ''; private $frame; /** * Create a window * * @param int $columns * @param int $rows * @param int $x * @param int $y */ public function __construct($columns = 0, $rows = 0, $x = 0, $y = 0) { $this->x = $x; $this->y = $y; $this->windowResource = ncurses_newwin($rows, $columns, $y, $x); if ($rows == 0 || $columns == 0) $this->getSize($columns, $rows); $this->columns = $columns; $this->rows = $rows; $this->frame = [ 'draw' => [], 'border' => null, ]; } /** * Destructs a window and associated panel */ public function __destruct() { if ($this->panel !== null) $this->panel = null; ncurses_delwin($this->windowResource); } /** * Returns a ncurses window resource * @return resource */ public function getWindow() { return $this->windowResource; } /** * Gets window size * @param int $columns Will be filled with window columns * @param int $rows Will be filled with window rows * @return array An array with elements 'columns' and 'rows' */ public function getSize(&$columns = null, &$rows = null) { ncurses_getmaxyx($this->windowResource, $rows, $columns); return array('columns' => $columns, 'rows' => $rows); } public function getWidth() { $this->getSize($width, $height); return $width; } public function getHeight() { $this->getSize($width, $height); return $height; } public function getCursorXY(&$x = null, &$y = null) { ncurses_getyx($this->windowResource, $y, $x); return array('x' => $x, 'y' => $y); } public function getX() { return $this->x; } public function getY() { return $this->y; } /** * Draws a border around this window * @return Window This object */ public function border($left = 0, $right = 0, $top = 0, $bottom = 0, $tl_corner = 0, $tr_corner = 0, $bl_corner = 0, $br_corner = 0) { ncurses_wborder($this->windowResource, $left, $right, $top, $bottom, $tl_corner, $tr_corner, $bl_corner, $br_corner); $this->frame['border'] = true; return $this; } /** * Draws a border with non-usual style * @param integer $style Can be BORDER_STYLE_SOLID, BORDER_STYLE_DOUBLE or BORDER_STYLE_BLOCK * @return Window This object */ public function borderStyle($style) { if ($style == self::BORDER_STYLE_SOLID) { $this->border(); } elseif($style == self::BORDER_STYLE_DOUBLE) { $this->border(226, 186, 205, 205, 201, 187, 200, 188); // $this->border(ord('║'), ord('║'), ord('═'), ord('═'), ord('╔'), ord('╗'), ord('╚'), ord('╝')); } return $this; } /** * Refreshes (redraws) a window * @return Window This object */ public function refresh() { ncurses_wrefresh($this->windowResource); return $this; } /** * Draws a window title * @param string $title Title * @return Window This object */ public function title($title) { $this->moveCursor(1, 0); $this->title = " {$title} "; $this->drawStringHere($this->title, NCURSES_A_REVERSE); return $this; } /** * Draws a window status (a line at the bottom) * @param string $status Status * @return Window This object */ public function status($status) { $this->moveCursor(1, $this->rows -1 ); $this->status = " {$status} "; $this->drawStringHere($this->status, NCURSES_A_REVERSE); return $this; } public function getTitle() { return $this->title; } public function getStatus() { return $this->status; } /** * Erases a window * @return Window This object */ public function erase() { $this->frame = [ 'draw' => [], 'border' => null, ]; ncurses_werase($this->windowResource); return $this; } /** * Moves a cursor * @param integer $x Cursor column * @param integer $y Cursor row * @return Window This object */ public function moveCursor($x, $y) { $this->cursorX = $x; $this->cursorY = $y; ncurses_wmove($this->windowResource, $y, $x); return $this; } /** * Draws a string at current position * @param string $string String * @param integer $attributes Ncurses attributes (eg. NCURSES_A_REVERSE) * @see http://pubs.opengroup.org/onlinepubs/007908799/xcurses/curses.h.html for available attributes (WA_LOW => NCURSES_A_LOW) * @return Window This object */ public function drawStringHere($string, $attributes = 0) { ncurses_wattron($this->windowResource, $attributes); ncurses_waddstr($this->windowResource, $string); ncurses_wattroff($this->windowResource, $attributes); $this->frame['draw'][] = ['string' => $string, 'attributes' => $attributes, 'x' => $this->cursorX, 'y' => $this->cursorY]; return $this; } /** * Makes a panel associated with this window * @return $this */ public function makePanel() { $this->panel = new Panel($this); return $this; } /** * Returns a Panel associated with this window * @return Panel or null */ public function getPanel() { return $this->panel; } public function reload() { $frame = $this->frame; $this->erase(); if ($frame['border']) { $this->border(); } foreach ($frame['draw'] as $item) { $this->moveCursor($item['x'], $item['y'])->drawStringHere($item['string'], $item['attributes']); } $this->refresh(); } /** * Makes a window that will be in center of the screen * @param Window $parentWindow A window that will be major for new window * @param integer $columns New window columns count * @param integer $row New window rows count * @return Window A new window that centered of parent window */ static public function createCenteredOf(Window $parentWindow, $columns, $rows) { $parentWindow->getSize($max_columns, $max_rows); return new Window($columns, $rows, ($max_columns / 2 - $columns / 2), ($max_rows / 2 - $rows / 2)); } } } namespace NcursesObjects { class WindowStyle { /** * @var string */ const BORDER_STYLE_SOLID = 'solid'; /** * @var string */ const BORDER_STYLE_DOUBLE = 'double'; /** * @var string */ const BORDER_STYLE_BLOCK = 'block'; /** * @var int */ protected $borderWidth = 1; /** * @var string */ protected $borderStyle = 'solid'; /** * @param $style */ public function setBorderStyle($style) { $enum = array(self::BORDER_STYLE_SOLID, self::BORDER_STYLE_DOUBLE, self::BORDER_STYLE_BLOCK); if (in_array($style, $enum)) $this->borderStyle = $style; } /** * @return string */ public function getBorderStyle() { return $this->borderStyle; } /** * @param $width */ public function setBorderWidth($width) { $width = intval($width, 10); if ($width > 1) { $this->borderWidth = $width; } } /** * @return int */ public function getBorderWidth() { return $this->borderWidth; } } } namespace Rakit\Curl { class Curl { /** * @var curl session $curl */ protected $curl; /** * @var string $url for url target */ protected $url; /** * @var array $params for store query params or post fields */ protected $params = array(); /** * @var array $cookies for store cookie data */ protected $cookies = array(); /** * @var array $options for store curl options */ protected $options = array(); /** * @var array $files for store file post fields */ protected $files = array(); /** * @var array $headers for store header settings */ protected $headers = array(); /** * @var int $limit_redirect_count */ protected $limit_redirect_count = 0; /** * @var array $redirect_urls */ protected $redirect_urls = array(); /** * @var string $cookie_jar for storing session cookies while request */ protected $cookie_jar = null; /** * @var bool $closed */ protected $closed = FALSE; /** * @var mixed $error_message */ protected $error_message = null; /** * @var mixed $errno */ protected $errno = null; /** * @var Response $response for store response object after curl_exec() */ public $response = null; /** * Curl constructor. * @param $url * @param array $params * @throws \ErrorException */ public function __construct($url, array $params = array()) { // check curl extension if ( ! extension_loaded('curl')) { throw new \ErrorException('cURL library need PHP cURL extension'); } $this->curl = curl_init(); $this->url = $url; $this->params = $params; $this->timeout(10); $this->option(CURLOPT_RETURNTRANSFER, 1); $this->option(CURLOPT_HEADER, TRUE); $this->option(CURLOPT_SSL_VERIFYPEER, FALSE); } /** * set timeout * * @param int $time request time limit * @return self */ public function timeout($time) { $this->option(CURLOPT_CONNECTTIMEOUT, $time); return $this; } /** * set http authentication * * @param string username * @param string password * @param string type curl authentication type * * @return self */ public function auth($username, $password, $type = 'basic') { $auth_type = constant('CURLAUTH_' . strtoupper($type)); $this->option(CURLOPT_HTTPAUTH, $auth_type); $this->option(CURLOPT_USERPWD, $username . ':' . $password); return $this; } /** * set useragent * * @param string $user_agent * @return self */ public function useragent($user_agent) { $this->option(CURLOPT_USERAGENT, $user_agent); return $this; } /** * set referer * * @param string $referer * @return self */ public function referer($referer) { $this->option(CURLOPT_REFERER, $referer); return $this; } /** * set an option * * @param string $option option key * @param mixed $value option value * @return self */ public function option($option, $value) { $this->options[$option] = $value; return $this; } /** * check if option has been setted * * @param string $option option name * @return bool */ public function hasOption($option) { return array_key_exists($option, $this->options); } /** * set proxy * * @param string $url proxy url * @param int $port proxy port * @return self */ public function proxy($url, $port = 80) { $this->option(CURLOPT_HTTPPROXYTUNNEL, true); $this->option(CURLOPT_PROXY, $url . ':' . $port); return $this; } /** * set request header * * @param string $key header key * @param string $value header value * @return self */ public function header($key, $value = null) { $this->headers[] = $value === null ? $key : $key.': '.$value; return $this; } /** * getting curl session */ public function getCurl() { return $this->curl; } /** * set request data(params) * * @param string $key * @param string $value * @return self */ public function param($key, $value) { $this->params[$key] = $value; return $this; } /** * set request cookie * * @param string $key * @param string $value * @return self */ public function cookie($key, $value) { $this->cookies[$key] = $value; return $this; } /** * add a file to upload * * @param string $key post file key * @param string $filepath * @param string $mimetype * @param string $filename posted filename * @return self */ public function addFile($key, $filepath, $mimetype='', $filename='') { $postfield = "@$filepath;filename=" . ($filename ?: basename($filepath)) . ($mimetype ? ";type=$mimetype" : ''); $this->files[$key] = $postfield; return $this; } /** * auto redirect if reseponse is redirecting (status: 3xx) * * @param int $count maximum redirecting */ public function autoRedirect($count) { if(!is_int($count)) { throw new \InvalidArgumentException("Limit redirect must be integer"); } $this->limit_redirect_count = $count; } /** * storing session cookie with CURLOPT_COOKIEJAR and CURLOPT_COOKIEFILE * * @param string $file file to store cookies */ public function storeSession($file) { $this->option(CURLOPT_COOKIEJAR, $file); $this->option(CURLOPT_COOKIEFILE, $file); $this->cookie_jar = $file; } /** * set request as ajax(XMLHttpRequest) */ public function ajax() { $this->header("X-Requested-With: XMLHttpRequest"); return $this; } /** * execute get request * * @param array $data * @return \Rakit\Curl\Response * @throws \ErrorException */ public function get(array $data = array()) { $params = array_merge($this->params, $data); $params = !empty($params)? '?' . http_build_query($params) : ''; $url = $this->url.$params; $this->option(CURLOPT_URL, $url); $this->option(CURLOPT_HTTPGET, TRUE); return $this->execute(); } /** * execute post request * * @param array $data * @return \Rakit\Curl\Response * @throws \ErrorException */ public function post(array $data = array()) { $params = array_merge($this->params, $this->files, $data); $this->option(CURLOPT_URL, $this->url); $this->option(CURLOPT_POST, TRUE); $this->option(CURLOPT_POSTFIELDS, $params); return $this->execute(); } /** * execute post request * * @param string $data * @return \Rakit\Curl\Response * @throws \ErrorException */ public function postRaw($data) { $this->option(CURLOPT_URL, $this->url); $this->option(CURLOPT_POST, TRUE); $this->option(CURLOPT_POSTFIELDS, $data); return $this->execute(); } /** * execute put request * * @param array $data * @return \Rakit\Curl\Response * @throws \ErrorException */ public function put(array $data = array()) { $params = array_merge($this->params, $data); $params = !empty($this->params)? '?' . http_build_query($this->params) : ''; $url = $this->url.$params; $this->option(CURLOPT_URL, $url); $this->option(CURLOPT_CUSTOMREQUEST, 'PUT'); return $this->execute(); } /** * execute patch request * * @param array $data * @return \Rakit\Curl\Response * @throws \ErrorException */ public function patch(array $data = array()) { $params = array_merge($this->params, $this->files, $data); $this->option(CURLOPT_URL, $this->url); $this->option(CURLOPT_CUSTOMREQUEST, 'PATCH'); $this->option(CURLOPT_POSTFIELDS, $params); return $this->execute(); } /** * execute delete request * * @param array $data * @return \Rakit\Curl\Response * @throws \ErrorException */ public function delete(array $data = array()) { $params = array_merge($this->params, $data); $params = !empty($params)? '?' . http_build_query($params) : ''; $url = $this->url.$params; $this->option(CURLOPT_URL, $url); $this->option(CURLOPT_CUSTOMREQUEST, 'DELETE'); return $this->execute(); } /** * getting redirect urls * @return array redirect urls */ public function getRedirectUrls() { return $this->redirect_urls; } /** * getting last url (may be last redirected url or defined url) * @return string url */ public function getFinalUrl() { if(count($this->redirect_urls) > 0) { return $this->redirect_urls[count($this->redirect_urls)-1]; } return $this->url; } /** * execute curl * * @return \Rakit\Curl\Response * @throws \ErrorException * @throws \Exception */ protected function execute() { if(TRUE === $this->closed) { throw new \Exception("Cannot execute curl session twice, create a new one!"); } if(!empty($this->cookies)) { $this->option(CURLOPT_COOKIE, http_build_query($this->cookies, '', '; ')); } $this->option(CURLOPT_HTTPHEADER, $this->headers); curl_setopt_array($this->curl, $this->options); $response = curl_exec($this->curl); $info = curl_getinfo($this->curl); $this->errno = $error = curl_errno($this->curl); $this->error_message = curl_error($this->curl); $this->response = new Response($info, $response, $this->errno, $this->error_message); $this->close(); if($this->limit_redirect_count > 0) { $count_redirect = 0; while($this->response->isRedirect()) { $this->redirect(); $count_redirect++; if($count_redirect >= $this->limit_redirect_count) { break; } } } return $this->response; } /** * redirect from response 3xx * @throws \ErrorException */ protected function redirect() { $redirect_url = $this->response->getHeader("location"); $curl = new static($redirect_url); if($this->cookie_jar) { $curl->storeSession($this->cookie_jar); } $this->response = $curl->get(); $this->redirect_urls[] = $redirect_url; } /** * getting error number after execute * @return int error number */ public function getErrno() { return $this->errno; } /** * alias for getErrno * @return int error number */ public function getError() { return $this->getErrno(); } /** * getting error message after execute * @return string error message */ public function getErrorMessage() { return $this->error_message; } /** * closing curl */ protected function close() { curl_close($this->curl); $this->closed = TRUE; } /** * simple get request * @throws \ErrorException */ public static function doGet($url, array $data = array()) { return static::make($url, $data)->get(); } /** * simple post request * @throws \ErrorException */ public static function doPost($url, array $data = array()) { return static::make($url, $data)->post(); } /** * simple put request * @throws \ErrorException */ public static function doPut($url, array $data = array()) { return static::make($url, $data)->put(); } /** * simple patch request * @throws \ErrorException */ public static function doPatch($url, array $data = array()) { return static::make($url, $data)->patch(); } /** * simple delete request * @throws \ErrorException */ public static function doDelete($url, array $data = array()) { return static::make($url, $data)->delete(); } /** * make a curl request object * @throws \ErrorException */ public static function make($url, array $data = array()) { return new static($url, $data); } } } namespace Rakit\Curl { class Response { protected $info; protected $body; protected $error; protected $error_message; protected $header_string; protected $parsed_headers = array(); protected $cookie = array(); public function __construct(array $info, $response, $errno, $error_message) { $this->errno = $errno; $this->error_message = $error_message; $this->body = substr($response, $info['header_size']); $this->header_string = substr($response, 0, $info['header_size']); $this->info = $info; $this->parsed_headers = $this->parseHeaderString($this->header_string); } public function isNotFound() { return $this->getStatus() == 404; } public function isRedirect() { $status = $this->getStatus(); if(substr($status, 0, 1) == '3') return TRUE; } public function error() { return $this->errno != 0; } public function getErrno() { return $this->errno; } public function getError() { return $this->getErrno(); } public function getErrorMessage() { return $this->error_message; } public function getHeader($key, $default = null) { $key = strtolower($key); return isset($this->parsed_headers[$key]) ? $this->parsed_headers[$key] : $default; } public function getHeaders() { return $this->parsed_headers; } public function getCookie() { return $this->cookie; } public function length() { return strlen($this->getbody()) + strlen($this->getHeaderString()); } public function getStatus() { return $this->getInfo('http_code', 0); } public function getContentType() { return $this->getInfo('content_type', FALSE); } public function isHtml() { return $this->getContentType() === 'text/html'; } public function getInfo($key, $default = null) { return isset($this->info[$key])? $this->info[$key] : $default; } public function getAllInfo() { return $this->info; } public function getBody() { return $this->body; } public function getHeaderString() { return $this->header_string; } public function toArray() { $data = array( 'headers' => $this->getHeaders(), 'cookie' => $this->getCookie(), 'body' => $this->getBody() ); return array_merge($this->info, $data); } public function __toString() { return (string) $this->getBody(); } protected function parseHeaderString($header_string) { $exp = explode("\n", $header_string); $headers = array(); foreach($exp as $header) { $header = trim($header); if(preg_match('/^HTTP\/(?[^ ]+)/', $header, $match)) { $headers['http_version'] = $match['v']; $this->info['http_version'] = $match['v']; } elseif('' == $header) { continue; } else { list($key, $value) = explode(':', $header, 2); $key = strtolower($key); $headers[$key] = trim($value); if($key === 'set-cookie') { $this->parseCookie($value); } } } return $headers; } protected function parseCookie($cookie_string) { $exp = explode(';', $cookie_string); $cookie['value'] = array_shift($exp); foreach($exp as $i => $data) { $_parse = explode('=', $data, 2); $key = $_parse[0]; $value = isset($_parse[1])? $_parse[1] : ""; $cookie[trim(strtolower($key))] = trim($value); } $this->cookie = $cookie; } } } namespace { class Text { public static function isUtf16($text) { preg_match_all('/\x00/', $text, $count); if (count($count[0]) / strlen($text) > 0.4) { return true; } return false; } public static function normalize($text) { if (self::isUtf16($text)) { $text = mb_convert_encoding($text, 'UTF-8', 'UTF-16'); } elseif (md5(@iconv('Windows-1251', 'Windows-1251', $text)) !== md5(@iconv('UTF-8', 'UTF-8', $text))) { $text = mb_convert_encoding($text, 'UTF-8', 'Windows-1251'); } return $text; } /** * @param string $haystack * @param array|string $needle * * @return bool */ public static function startsWith($haystack, $needle) { if (is_array($needle)) { foreach ($needle as $str) { if (strpos($haystack, $str) === 0) { return true; } } return false; } return (string)$needle === "" || strpos($haystack, (string)$needle) === 0; } /** * @param string $haystack * @param array|string $needle * * @return bool */ public static function endsWith($haystack, $needle) { if (is_array($needle)) { foreach ($needle as $str) { if (substr($haystack, -strlen($str)) === $str) { return true; } } return false; } return (string)$needle === "" || substr($haystack, (string)-strlen($needle)) === $needle; } public static function quoteArgs($args) { return implode(' ', array_map(function ($a) {return "\"{$a}\"";}, (array)$args)); } } if (!function_exists('app')) { /** * @param null|Start $type * @return ControllerGUI|Start */ function app($type = null) { static $gui; static $start; if (null === $gui && $type === 'gui') { $gui = new ControllerGUI(); } if (null === $start && $type && $type instanceof Start) { $start = $type; } if ($type === 'start') { return $start; } return $gui; } } if (!function_exists('debug_string_backtrace')) { function debug_string_backtrace($text = null, $config = null) { /** @var Config $config */ $config = null === $config ? app('start')->getConfig() : $config; if (is_string($config)) { $file = $config; } else { $file = $config->getLogsDir() . '/debug.log'; } static $init; if (null === $init) { $init = true; if (file_exists($file)) { @unlink($file); } } if (null === $text) { ob_start(); static $time; if (null === $time) { $time = microtime(true); print "Time: 0\n\n"; } else { print 'Time: ' . (microtime(true) - $time) . "\n\n"; } debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); $trace = ob_get_contents(); ob_end_clean(); // Remove first item from backtrace as it's this function which // is redundant. $trace = preg_replace ('/^#0\s+' . __FUNCTION__ . "[^\n]*\n/", '', $trace, 1); // Renumber backtrace items. $trace = preg_replace ('/^#(\d+)/me', '\'#\' . ($1 - 1)', $trace); $trace = implode("\n", array_map(function ($n) {list($a) = explode('called at', $n); return trim($a);}, explode("\n", $trace))); } else { $trace = $text; } file_put_contents($file, "{$trace}\n\n", FILE_APPEND); return "{$trace}\n\n"; } } /* * * startsWith('http://example.com', 'http://') -> true * startsWith('http://example.com', 'ttp://') -> false * */ if (!function_exists('startsWith')) { /** * @param string $haystack * @param array|string $needle * * @return bool */ function startsWith($haystack, $needle) { if (is_array($needle)) { foreach ($needle as $str) { if (strpos($haystack, $str) === 0) { return true; } } return false; } return (string)$needle === "" || strpos($haystack, (string)$needle) === 0; } } /** * endWith('http://example.com', 'om') -> true * endWith('http://example.com', '.co') -> false */ if (!function_exists('endsWith')) { /** * @param string $haystack * @param array|string $needle * * @return bool */ function endsWith($haystack, $needle) { if (is_array($needle)) { foreach ($needle as $str) { if (substr($haystack, -strlen($str)) === $str) { return true; } } return false; } return (string)$needle === "" || substr($haystack, (string)-strlen($needle)) === $needle; } } class FileINI { private $file; /** * FileINI constructor. * @param $path */ public function __construct($path) { $this->file = $path; } public function get() { return file_exists($this->file) ? parse_ini_file($this->file, true) : []; } public function write($array = []) { $file = $this->file; if (!is_string($file)) { throw new \InvalidArgumentException('Function argument 1 must be a string.'); } if (!is_array($array)) { throw new \InvalidArgumentException('Function argument 2 must be an array.'); } $data = array(); foreach ($array as $key => $val) { if (is_array($val)) { $data[] = "[$key]"; foreach ($val as $skey => $sval) { if (is_array($sval)) { foreach ($sval as $_skey => $_sval) { if (is_numeric($_skey)) { $data[] = $skey . '[] = ' . (is_numeric($_sval) ? $_sval : (ctype_upper($_sval) ? $_sval : '"' . $_sval . '"')); } else { $data[] = $skey . '[' . $_skey . '] = ' . (is_numeric($_sval) ? $_sval : (ctype_upper($_sval) ? $_sval : '"' . $_sval . '"')); } } } else { $data[] = $skey . ' = ' . (is_numeric($sval) ? $sval : (ctype_upper($sval) ? $sval : '"' . $sval . '"')); } } } else { $data[] = $key . ' = ' . (is_numeric($val) ? $val : (ctype_upper($val) ? $val : '"' . $val . '"')); } $data[] = null; } $fp = fopen($file, 'w'); $retries = 0; $max_retries = 100; if (!$fp) { return false; } do { if ($retries > 0) { usleep(rand(1, 5000)); } $retries += 1; } while (!flock($fp, LOCK_EX) && $retries <= $max_retries); if ($retries == $max_retries) { return false; } fwrite($fp, implode(PHP_EOL, $data) . PHP_EOL); flock($fp, LOCK_UN); fclose($fp); return true; } } class Logs { private $table = false; private $length = 80; public function insertLogFile($text, $path) { @file_put_contents($path, "{$text}\n", FILE_APPEND); } public function log($text='', $symbols = [], $lenght = 0, $return = false) { if (!$symbols && $this->table === true) { $symbols = 'line'; $lenght = $this->length; $items = explode("\n", $text); if (count($items) > 1) { foreach ($items as $item) { $this->log($item); } return; } } if ($symbols === 'head') { $symbols = ['start' => '╔', 'space' => '═', 'end' => '╗']; } elseif ($symbols === 'line') { $symbols = ['start' => '║ ', 'space' => ' ', 'end' => ' ║']; } elseif ($symbols === 'footer') { $symbols = ['start' => '╚', 'space' => '═', 'end' => '╝']; } elseif ($symbols === 'hr') { $symbols = ['start' => '╟', 'space' => '─', 'end' => '╢']; } $symbols = [ 'start' => isset($symbols['start']) ? $symbols['start'] : '', 'space' => isset($symbols['space']) ? $symbols['space'] : ' ', 'end' => isset($symbols['end']) ? $symbols['end'] : '', ]; if ($lenght > 0) { $text = "{$symbols['start']}{$text}"; $len = mb_strlen($text); $compare = $lenght - $len; if ($compare > 0) { $len2 = $compare - (1 + mb_strlen($symbols['end'])); if ($len2 > 0) { $end = []; foreach (range(1, $len2) as $i) { $end[] = $symbols['space']; } $end[] = $symbols['end']; $end = implode('', $end); $text = "{$text}{$end}"; } } else { $text = "{$text}{$symbols['space']}{$symbols['end']}"; } } if ($return) { return $text; } print "{$text}\n"; } public function logStart() { if ($this->table === false) { $this->log('', 'head', $this->length); } $this->table = true; } public function logStop() { if ($this->table === true) { $this->log('', 'footer', $this->length); } $this->table = false; } } class Buffer { private $size = 150; private $buffer; private $changes; /** * Buffer constructor. */ public function __construct() { $this->buffer = []; $this->changes = []; } public function setSize($size) { $this->size = $size; } public function add($text) { if (count($this->buffer) <= $this->size) { $this->buffer[] = $text; } else { array_shift($this->buffer); $this->buffer[] = $text; } $this->doChange(); } private function doChange() { foreach ($this->changes as $change) { if ($change) { $change($this->buffer); } } } public function onChangeEvent($callable) { $this->changes[] = $callable; } public function offChangeEvent($callable) { $this->changes = array_filter($this->changes, function ($item) use (&$callable) {return $item !== $callable;}); } public function clear() { $this->buffer = []; } } class History { private $history; /** * History constructor. */ public function __construct() { $this->history = []; } public function add(&$object) { $this->history[] = $object; } public function back() { array_pop($this->history); return $this->current(); } public function current() { return end($this->history); } public function count() { return count($this->history); } } class FileSystem { private $command; private $config; /** * FileSystem constructor. * @param Config $config * @param Command $command */ public function __construct(Config $config, Command $command) { $this->command = $command; $this->config = $config; } public static function getDirectorySize($path) { $bytes = 0; $path = realpath($path); if ($path !== false && $path != '' && file_exists($path)) { foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS)) as $object) { $bytes += $object->getSize(); } } return $bytes; } public function relativePath($absPath, $path = null) { return trim(str_replace($path === null ? $this->config->getRootDir() : $path, '', $absPath), " \t\n\r\0\x0B/"); } public function rm($dir) { if (!file_exists($dir)) { return false; } if (!is_dir($dir)) { unlink($dir); return true; } foreach (scandir($dir, SCANDIR_SORT_NONE) as $object) { if ($object !== '.' && $object !== '..') { if (is_dir("{$dir}/{$object}") && !is_link("{$dir}/{$object}")) { $this->rm("{$dir}/{$object}"); } else { unlink("{$dir}/{$object}"); } } } if (file_exists($dir)) { rmdir($dir); } return true; } public function cp($in, $out, $overwrite = false) { if (file_exists($in) && !is_dir($in)) { copy($in, $out); return true; } if (!file_exists($in) || !is_dir($in)) { return false; } $mode = 0775; if (false === $overwrite || ($overwrite && !file_exists($out))) { if (!mkdir($out, $mode, true) && !is_dir($out)) { throw new \RuntimeException(sprintf('Directory "%s" was not created', $out)); } } foreach ( $iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($in, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST) as $item ) { if ($item->isDir()) { $pathOut = $out . DIRECTORY_SEPARATOR . $iterator->getSubPathName(); if ($overwrite && file_exists($pathOut)) { continue; } if (!mkdir($concurrentDirectory = $pathOut, $mode) && !is_dir($concurrentDirectory)) { throw new \RuntimeException(sprintf('Directory "%s" was not created', $concurrentDirectory)); } } else { $pathOut = $out . DIRECTORY_SEPARATOR . $iterator->getSubPathName(); $fileName = basename((string)$item); if ($overwrite && file_exists("{$pathOut}/{$fileName}")) { unlink("{$pathOut}/{$fileName}"); } copy($item, $pathOut); } } return true; } public function mv($in, $out) { if (file_exists($in) && !is_dir($in)) { rename($in, $out); return true; } if (!is_dir($in) || file_exists($out)) { return false; } $mode = 0775; if (!mkdir($out, $mode, true) && !is_dir($out)) { throw new \RuntimeException(sprintf('Directory "%s" was not created', $out)); } foreach (new DirectoryIterator($in) as $iterator) { if ($iterator->isFile() || $iterator->isLink()) { rename($iterator->isLink() ? $iterator->getPathName() : $iterator->getRealPath(), "{$out}/" . $iterator->getFilename()); } else if (!$iterator->isDot() && $iterator->isDir()) { $this->mv($iterator->getRealPath(), "{$out}/{$iterator}"); if (file_exists($iterator->getRealPath())) { if (is_dir($iterator->getRealPath())) { rmdir($in); } else { unlink($iterator->getRealPath()); } } } } if (file_exists($in)) { if (is_dir($in)) { rmdir($in); } else { unlink($in); } } return true; } public function mkdirs($dirs) { foreach ($dirs as $dir) { if (!file_exists($dir)) { if (!mkdir($dir, 0775, true) && !is_dir($dir)) { throw new \RuntimeException(sprintf('Directory "%s" was not created', $dir)); } } } } public function link($in, $out) { return $this->command->run("ln -sfr \"{$in}\" \"{$out}\""); } public function unpackXz($inFile, $outDir, $type = 'xf', $glob = '', $archiver = 'tar') { if (!app('start')->getSystem()->isXz()) { return false; } if (!file_exists($inFile) || is_dir($inFile)) { return false; } if (file_exists($outDir)) { $this->rm($outDir); } $dir = dirname($inFile); $rnd = mt_rand(10000, 99999); $tmpDir = "{$dir}/tmp_{$rnd}"; $this->mkdirs([$tmpDir]); if (!file_exists($tmpDir)) { return false; } $fileName = basename($inFile); $mvFile = "{$tmpDir}/{$fileName}"; $this->mv($inFile, $mvFile); $this->command->run("cd \"{$tmpDir}\" && {$archiver} {$type} \"./{$fileName}\""); $this->rm($mvFile); $find = glob("{$tmpDir}/{$glob}*"); $path = $tmpDir; if (count($find) === 1) { $path = reset($find); } $this->mv($path, $outDir); if (file_exists($tmpDir)) { $this->rm($tmpDir); } return true; } public function unpackGz($inFile, $outDir) { return $this->unpackXz($inFile, $outDir, '-xzf'); } public function unpackPol($inFile, $outDir) { return $this->unpackXz($inFile, $outDir, '-xjf', 'wineversion/'); } public function unpackRar($inFile, $outDir) { return $this->unpackXz($inFile, $outDir, 'x', '', 'unrar'); } public function unpackZip($inFile, $outDir) { return $this->unpackXz($inFile, $outDir, '', '', 'unzip'); } public function unpack($inFile, $outDir) { if (Text::endsWith($inFile, '.tar.xz')) { return $this->unpackXz($inFile, $outDir); } if (Text::endsWith($inFile, '.tar.gz')) { return $this->unpackGz($inFile, $outDir); } if (Text::endsWith($inFile, '.pol')) { return $this->unpackPol($inFile, $outDir); } if (Text::endsWith($inFile, ['.exe', '.rar'])) { return $this->unpackRar($inFile, $outDir); } if (Text::endsWith($inFile, '.zip')) { return $this->unpackZip($inFile, $outDir); } return false; } public function pack($folder) { $folder = rtrim($folder, '\\/'); if (!file_exists($folder) || file_exists("{$folder}.tar.gz")) { return false; } $this->command->run("cd \"{$folder}\" && tar -zcf \"{$folder}.tar.gz\" -C \"{$folder}\" ."); return file_exists("{$folder}.tar.gz"); } public function download($url, $path) { try { ini_set('memory_limit', '-1'); $request = new \Rakit\Curl\Curl($url); $request->autoRedirect(5); $response = $request->get(); } catch (ErrorException $e) { return ''; } $fileName = basename($url); $pathFile = "{$path}/{$fileName}"; file_put_contents($pathFile, $response->getBody()); unset($request, $response); return $pathFile; } } class Command { private $config; /** * Command constructor. * @param Config $config */ public function __construct(Config $config) { $this->config = $config; } /** * @param string $cmd * @param string|null $saveLog * @param bool $outputConsole * @return bool|string */ public function run($cmd, $saveLog = null, $outputConsole = false) { if (null !== $saveLog && file_exists($saveLog)) { @unlink($saveLog); } $cmd = $this->cast($cmd); if ($outputConsole) { system($cmd); return ''; } $descriptorspec = array( 0 => array('pipe', 'r'), // stdin is a pipe that the child will read from 1 => array('pipe', 'w'), // stdout is a pipe that the child will write to 2 => array('pipe', 'w') // stderr is a file to write to ); $pipes = array(); $process = proc_open($cmd, $descriptorspec, $pipes); $output = ""; if (!is_resource($process)) return false; #close child's input imidiately fclose($pipes[0]); stream_set_blocking($pipes[1], false); stream_set_blocking($pipes[2], false); $todo = array($pipes[1], $pipes[2]); while (true) { $read = array(); if (!feof($pipes[1])) $read[] = $pipes[1]; if (!feof($pipes[2])) $read[] = $pipes[2]; if (!$read) break; $ready = @stream_select($read, $write = NULL, $ex = NULL, 2); if ($ready === false) { break; #should never happen - something died } foreach ($read as $r) { $s = fread($r, 1024); if ($saveLog) { @file_put_contents($saveLog, $s, FILE_APPEND); } $output .= $s; } } fclose($pipes[1]); fclose($pipes[2]); #$code = proc_close($process); return $output; } public function cast($cmd) { $this->createLibsDirectory(); $dir = $this->config->getRootDir(); $additionalWineLibs = "{$dir}/libs/i386:{$dir}/libs/x86-64"; $exported = [ 'export WINE' => $this->config->wine('WINE'), 'export WINE64' => $this->config->wine('WINE64'), 'export WINEPREFIX' => $this->config->wine('WINEPREFIX'), 'export WINESERVER' => $this->config->wine('WINESERVER'), 'export WINEARCH' => $this->config->wine('WINEARCH'), 'export WINEDEBUG' => $this->config->wine('WINEDEBUG'), 'export WINEDLLOVERRIDES' => $this->config->wine('WINEDLLOVERRIDES'), 'export LD_LIBRARY_PATH' => "\$LD_LIBRARY_PATH:{$additionalWineLibs}", 'export DXVK_CONFIG_FILE' => $this->config->getDxvkConfigFile(), 'export PROTON_LOG' => $this->config->getLogsDir() . '/proton.log', 'export XDG_CACHE_HOME' => $this->config->getCacheDir(), ]; if ($locale = $this->getLocale()) { $exported['export LC_ALL'] = $locale; } if (!$this->config->isEsync()) { $exported['export PROTON_NO_ESYNC'] = 'noesync'; } if ($this->config->isDxvk() || $this->config->isD9vk()) { $cache = $this->config->getDxvkCacheDir(); $logs = $this->config->getDxvkLogsDir(); $exported['export DXVK_STATE_CACHE_PATH'] = $cache; $exported['export DXVK_LOG_PATH'] = $logs; if (strpos($exported['export WINEDLLOVERRIDES'], 'nvapi') === false) { $overrides = explode(';', $exported['export WINEDLLOVERRIDES']); $overrides[] = 'nvapi64,nvapi='; $overrides[] = 'd3d9=n'; $exported['export WINEDLLOVERRIDES'] = implode(';', $overrides); } } if ($this->config->get('export')) { foreach ((array)$this->config->get('export') as $key => $value) { $exported["export {$key}"] = $value; } } $prefix = []; foreach ($exported as $key => $value) { $prefix[] = "{$key}=\"{$value}\";"; } $prefix = implode(' ', $prefix); $cmd = "{$prefix} cd \"{$dir}\" && {$cmd}"; return $cmd; } public function squashfuse($folder) { (new Update($this->config, $this))->downloadSquashfuse(); $squashfuse = $this->config->getRootDir() . '/squashfuse'; return $this->run(Text::quoteArgs($squashfuse) . ' ' . Text::quoteArgs("{$folder}.squashfs") . ' ' . Text::quoteArgs($folder)); } public function zipfuse($folder) { (new Update($this->config, $this))->downloadFusezip(); $zipfuse = $this->config->getRootDir() . '/fuse-zip'; return $this->run(Text::quoteArgs($zipfuse) . ' ' . Text::quoteArgs("{$folder}.zip") . ' ' . Text::quoteArgs($folder)); } public function umount($folder) { return $this->run('fusermount -u ' . Text::quoteArgs($folder)); } public function getLocale() { static $locale; if (null === $locale) { $cmdValue = getenv('LC_ALL'); if ($cmdValue) { $locale = $cmdValue; } else { exec('locale', $out, $return); $locales = is_array($out) ? $out : explode("\n", $out); $counts = []; foreach ($locales as $loc) { list($field, $value) = explode('=', $loc); if (!$loc || !$value) { continue; } $value = trim($value, " \t\n\r\0\x0B\"'"); if (!isset($counts[$value])) { $counts[$value] = 0; } else { $counts[$value] += 1; } } asort($counts); end($counts); $locale = key($counts); } } return $locale; } public function createLibsDirectory() { $libs = $this->config->getRootDir() . '/libs'; if (!file_exists($libs)) { if (!mkdir($libs, 0775, true) && !is_dir($libs)) { throw new \RuntimeException(sprintf('Directory "%s" was not created', $libs)); } if (!mkdir("{$libs}/i386", 0775, true) && !is_dir("{$libs}/i386")) { throw new \RuntimeException(sprintf('Directory "%s" was not created', "{$libs}/i386")); } if (!mkdir("{$libs}/x86-64", 0775, true) && !is_dir("{$libs}/x86-64")) { throw new \RuntimeException(sprintf('Directory "%s" was not created', "{$libs}/x86-64")); } file_put_contents("{$libs}/readme.txt",'В папки i386, x86-64 можно ложить специфичные библиотеки для wine.'); } } } class Network { private static $isConnected; private $command; private $config; /** * Network constructor. * @param Config $config * @param Command $command */ public function __construct(Config $config, Command $command) { $this->command = $command; $this->config = $config; } public static function isConnected() { if (self::$isConnected !== null) { return self::$isConnected; } $connected = @fsockopen('8.8.8.8', 53, $errno,$errstr, 5); if ($connected) { self::$isConnected = true; fclose($connected); } else { self::$isConnected = false; } return self::$isConnected; } public function get($url) { return self::isConnected() ? file_get_contents($url, false, stream_context_create($this->config->getContextOptions())) : ''; } public function getJSON($url) { if ($result = $this->get($url)) { return json_decode($result, true); } return []; } public function getRepo($url) { return $this->get($this->config->getRepositoryUrl() . $url); } } class System { private $command; private $config; /** * System constructor. * @param Config $config * @param Command $command */ public function __construct(Config $config, Command $command) { $this->command = $command; $this->config = $config; } public function getUserName($reset = false) { static $userName = null; if (true === $reset) { $userName = null; } if ($userName === null) { $libwine = $this->config->getWineDir() . '/lib/libwine.so'; $libwine = glob("{$libwine}*"); $libwine = reset($libwine); if ($libwine && file_exists($libwine) && (bool)trim($this->command->run('grep -i "proton" ' . Text::quoteArgs($libwine)))) { $userName = 'steamuser'; return $userName; } $userName = trim($this->command->run('id -u -n')); } return $userName; } public function getHostname() { static $hostname = null; if ($hostname === null) { $hostname = trim($this->command->run('hostname')); } return $hostname; } public function getDesktopPath() { $isXdg = (bool)trim($this->command->run('command -v xdg-user-dir')); if ($isXdg) { return trim($this->command->run('xdg-user-dir DESKTOP')); } return ''; } public function getCPU() { static $result; if (null === $result) { $cpuinfo = explode("\n", trim($this->command->run('cat /proc/cpuinfo'))); foreach ($cpuinfo as $line) { if (strpos($line, 'model name') !== false) { $line = explode(':', $line); $result = trim(end($line)); return $result; } } $result = ''; } return $result; } public function getGPU() { static $result; if (null === $result) { $gpuinfo = explode("\n", trim($this->command->run('glxinfo'))); foreach ($gpuinfo as $line) { if (strpos($line, 'Device') !== false) { $line = explode(':', $line); $result = trim(end($line)); break; } } if (!$result) { $result = trim($this->command->run('lspci | grep VGA | cut -d ":" -f3')); } } return $result; } public function getRAM() { static $result; if (null === $result) { $meminfo = explode("\n", trim($this->command->run('cat /proc/meminfo'))); foreach ($meminfo as $line) { if (strpos($line, 'MemTotal') !== false) { $line = explode(':', $line); $line = array_filter(explode(' ', end($line))); $line = trim(reset($line)); $line = round($line / 1024); $result = $line; return $result; } } $result = ''; } return $result; } public function getFreeRAM() { $meminfo = explode("\n", trim($this->command->run('cat /proc/meminfo'))); foreach ($meminfo as $line) { if (strpos($line, 'MemAvailable') !== false) { $line = explode(':', $line); $line = array_filter(explode(' ', end($line))); $line = trim(reset($line)); $line = round($line / 1024); return $line; } } return ''; } public function getLinuxVersion() { static $result; if (null === $result) { $result = trim($this->command->run('uname -mrs')); } return $result; } public function getDistrName() { static $result; if (null === $result) { $release = explode("\n", trim($this->command->run('cat /etc/*-release'))); $name = null; $version = null; foreach ($release as $line) { if ($name === null && strpos($line, 'DISTRIB_ID=') !== false) { $line = explode('=', $line); $name = trim(end($line)); continue; } if ($version === null && strpos($line, 'DISTRIB_RELEASE=') !== false) { $line = explode('=', $line); $version = trim(end($line)); continue; } } if ($name === null || $version === null) { foreach ($release as $line) { if ($name === null && strpos($line, 'NAME=') !== false) { $line = explode('=', $line); $name = trim(end($line)); continue; } if ($version === null && strpos($line, 'VERSION=') !== false) { $line = explode('=', $line); $version = trim(end($line)); continue; } } } $result = trim("{$name} {$version}"); } return $result; } public function getMesaVersion() { static $result; if (null === $result) { $mesa = explode("\n", trim($this->command->run('glxinfo | grep "Mesa"'))); $version = null; foreach ($mesa as $line) { if ($version === null && strpos($line, 'OpenGL version string') !== false) { $line = explode('Mesa', $line); $line = trim(end($line)); $line = explode(' ', $line); $version = trim(reset($line)); break; } } $result = $version ?: ''; } return $result; } public function getGlibcVersion() { static $result; if (null === $result) { $isGetConf = (bool)trim($this->command->run('command -v getconf')); if ($isGetConf) { $text = explode("\n", trim($this->command->run('getconf GNU_LIBC_VERSION'))); preg_match_all('/([0-9]{1,}.[0-9]{1,})/m', trim(reset($text)), $matches, PREG_SET_ORDER, 0); if ($matches && $matches[0] && $matches[0][0]) { $result = $matches[0][0]; } } if (!$result) { $text = explode("\n", trim($this->command->run('ldd --version'))); preg_match_all('/([0-9]{1,}.[0-9]{1,})/m', trim(reset($text)), $matches, PREG_SET_ORDER, 0); if ($matches && $matches[0] && $matches[0][0]) { $result = $matches[0][0]; } } } return $result; } public function getXrandrVersion() { static $result; if (null === $result) { $result = trim($this->command->run("xrandr --version")); } return $result; } public function getTypeGPU() { static $result; if (null === $result) { $isGlxinfo = $this->command->run("command -v glxinfo"); if ($isGlxinfo) { $type = trim($this->command->run('glxinfo | grep -E "(ATI|AMD)"')); if ($type) { $result = 'amd'; return $result; } $type = trim($this->command->run('glxinfo | grep "NVIDIA"')); if ($type) { $result = 'nvidia'; return $result; } $type = trim($this->command->run('glxinfo | grep "Intel"')); if ($type) { $result = 'intel'; return $result; } } return null; } return $result; } public function getUlimitHard() { return (int)trim($this->command->run('ulimit -Hn')); } public function getUlimitSoft() { return (int)trim($this->command->run('ulimit -Sn')); } public function lock() { $lock = $this->config->getRootDir() . '/run.pid'; if (file_exists($lock)) { $pid = trim(file_get_contents($lock)); if ($pid) { $processExists = $this->command->run("ps -p {$pid} -o comm="); if ($processExists) { return false; } } } file_put_contents($lock, posix_getpid()); register_shutdown_function(function () use ($lock) { if (file_exists($lock)) { @unlink($lock); } }); return true; } public function checkPhp() { return extension_loaded('ncurses'); } public function getFont() { $fonts = array_map('trim', explode("\n", trim($this->command->run('xlsfonts')))); $find = ['-misc-fixed-bold-r-normal--0-0-100-100-c-0-iso8859-1', '9x15bold', '10x20', 'lucidasanstypewriter-bold-18', 'lucidasans-bold-18']; foreach ($find as $font) { if (in_array($font, $fonts, true)) { return $font; } } foreach ($fonts as $font) { $font = trim($font); if (strpos($font, ' ') === false && strpos($font,'&') === false && strpos($font,'.') === false && strpos($font, 'bold') !== false && ( strpos($font, '100') !== false || strpos($font, '110') !== false || strpos($font, '120') !== false || strpos($font, '130') !== false || strpos($font, '140') !== false ) ) { return $font; } } return ''; } public function isCyrillic() { static $result; if (null === $result) { $result = (bool)trim($this->command->run('locale | grep LANG=ru')); } return $result; } public function isTar() { static $result; if (null === $result) { $result = (bool)trim($this->command->run("command -v tar")); } return $result; } public function isXz() { static $result; if (null === $result) { $result = (bool)trim($this->command->run("command -v xz")); } return $this->isTar() && $result; } public function getArch() { static $arch; if (null === $arch) { if ((bool)trim($this->command->run('command -v arch'))) { if (trim($this->command->run('arch')) === 'x86_64') { $arch = 64; } else { $arch = 32; } } elseif ((bool)trim($this->command->run('command -v getconf'))) { if (trim($this->command->run('getconf LONG_BIT')) === '64') { $arch = 64; } else { $arch = 32; } } } return $arch; } public function getXorgVersion() { static $xorg; if (null === $xorg) { if ((bool)trim($this->command->run('command -v xdpyinfo'))) { $result = trim($this->command->run('xdpyinfo | grep -i "X.Org version"')); $result = array_map('trim', explode(':', $result)); $result = end($result); if ($result) { $xorg = $result; } } if (null === $xorg) { $path = '/var/log/Xorg.0.log'; if (file_exists($path)) { $result = trim($this->command->run("cat {$path} | grep \"X.Org X Server\"")); $result = array_map('trim', explode(' ', $result)); $result = end($result); if ($result) { $xorg = $result; } } } } return $xorg; } public function getVmMaxMapCount() { static $vmMaxMapCount; if (null === $vmMaxMapCount) { if ((bool)trim($this->command->run('command -v sysctl'))) { list($key, $value) = array_map('trim', explode('=', trim($this->command->run('sysctl vm.max_map_count')))); $vmMaxMapCount = (int)$value; } } return $vmMaxMapCount; } public function getCpuFreq() { $cpu = trim($this->command->run('cat /proc/cpuinfo')); $result = []; $cpuId = null; $name = null; foreach (explode("\n", $cpu) as $line) { if (stripos($line, 'processor') !== false) { $exLine = explode(':', $line); if (trim($exLine[0]) === 'processor') { $cpuId = (int)trim(end($exLine)); } } if (stripos($line, 'model name') !== false) { $exLine = explode(':', $line); if (trim($exLine[0]) === 'model name') { $name = trim(end($exLine = explode(':', $line))); } } if (stripos($line, 'cpu MHz') !== false) { $exLine = explode(':', $line); if (trim($exLine[0]) === 'cpu MHz') { $result[] = [ 'id' => $cpuId, 'name' => $name, 'freq' => trim(end($exLine = explode(':', $line))), 'mode' => file_exists("/sys/devices/system/cpu/cpu{$cpuId}/cpufreq/scaling_governor") ? trim($this->command->run("cat /sys/devices/system/cpu/cpu{$cpuId}/cpufreq/scaling_governor")) : '', ]; } } } return $result; } } class Mount { private $command; private $config; private $console; private $folder; private $extension; private $mounted = false; /** * Mount constructor. * @param Config $config * @param Command $command * @param Console $console * @param string $folder */ public function __construct(Config $config, Command $command, Console $console, $folder) { $this->config = $config; $this->command = $command; $this->folder = $folder; $this->console = $console; $this->mount(); if ($this->getFolder() === $config->getWineDir()) { $config->updateWine(); } if ($this->isMounted()) { register_shutdown_function(function () { if ($this->isMounted()) $this->umount(); }); } } /** * @return bool */ public function isMounted() { return $this->mounted; } /** * @return string */ public function getFolder() { return $this->folder; } /** * @return string|null */ public function getExtension() { return $this->extension; } /** * @return bool */ public function mount() { if (!$this->console->lock()) { return false; } $folder = $this->folder; $this->umount(); if ($this->mounted === false && file_exists("{$folder}.squashfs") && !file_exists($folder)) { if (!mkdir($folder, 0775) && !is_dir($folder)) { throw new \RuntimeException(sprintf('Directory "%s" was not created', $folder)); } $this->extension = 'squashfs'; $this->mounted = true; $this->command->squashfuse($folder); } if ($this->mounted === false && file_exists("{$folder}.zip") && !file_exists($folder)) { if (!mkdir($folder, 0775) && !is_dir($folder)) { throw new \RuntimeException(sprintf('Directory "%s" was not created', $folder)); } $this->extension = 'zip'; $this->mounted = true; $this->command->zipfuse($folder); } return $this->mounted; } /** * @return bool */ public function umount() { if (!$this->console->lock()) { return false; } foreach (range(0, 5) as $i) { if (!file_exists($this->folder)) { $this->mounted = false; $this->extension = null; break; } $this->command->umount($this->folder); if (file_exists($this->folder) && (file_exists("{$this->folder}.squashfs") || file_exists("{$this->folder}.zip"))) { @rmdir($this->folder); } if (file_exists($this->folder) && (file_exists("{$this->folder}.squashfs") || file_exists("{$this->folder}.zip"))) { sleep(1); } else { $this->extension = null; $this->mounted = false; } } return $this->mounted; } } class Event { const EVENT_BEFORE_START_GAME = 'before-start'; const EVENT_AFTER_START_GAME = 'after-start'; const EVENT_AFTER_CREATE_PREFIX = 'after-create-prefix'; private $command; private $config; protected $events; /** * Event constructor. * @param Config $config * @param Command $command */ public function __construct(Config $config, Command $command) { $this->command = $command; $this->config = $config; if (null === $this->events) { $this->events = []; } } public function on($event, $callbackOrNamespaceStaticFunc) { if (!isset($this->events[$event])) { $this->events[$event] = []; } $this->events[$event][] = $callbackOrNamespaceStaticFunc; } public function fireEvent($event) { if (empty($this->events[$event])) { return; } foreach ($this->events[$event] as $item) { $item(); } } public function createPrefix($type = 'after_create_prefix') { if (!file_exists($this->config->wine('WINEPREFIX'))) { return []; } if (file_exists($this->config->getHooksDir())) { $result = []; if ($this->config->get('hooks', $type)) { foreach ((array)$this->config->get('hooks', $type) as $hookCmd) { $hookCmd = trim($hookCmd); if (!$hookCmd) { continue; } $trimHook = trim($hookCmd, '&'); if (file_exists($this->config->getHooksDir() . "/{$trimHook}")) { $result[] = "Run {$trimHook}"; $result[] = $this->command->run('cd "' . $this->config->getHooksDir() . '"; chmod +x ' . $trimHook . "; ./{$hookCmd}"); } } } } if ('after_create_prefix' === $type) { $this->fireEvent(self::EVENT_AFTER_CREATE_PREFIX); } elseif ('before_run_game' === $type) { $this->fireEvent(self::EVENT_BEFORE_START_GAME); } elseif ('after_exit_game' === $type) { $this->fireEvent(self::EVENT_AFTER_START_GAME); } return $result; } public function beforeRun() { $this->createPrefix('before_run_game'); } public function afterExit() { $this->createPrefix('after_exit_game'); } public function gpu() { $gpu = (new System($this->config, $this->command))->getTypeGPU(); if (!file_exists($this->config->wine('WINEPREFIX')) || !file_exists($this->config->getHooksGpuDir()) || !$gpu) { return []; } $result = []; if ($this->config->get('hooks', "gpu_{$gpu}")) { $hooks = (array)$this->config->get('hooks', "gpu_{$gpu}"); foreach ($hooks as $hook) { $hookCmd = trim($hook); if (!$hookCmd) { continue; } $trimHook = trim($hookCmd, '&'); if (file_exists($this->config->getHooksDir() . "/{$trimHook}")) { $result[] = "Run {$trimHook}"; $result[] = $this->command->run('cd "' . $this->config->getHooksDir() . '"; chmod +x ' . Text::quoteArgs($trimHook) . "; ./{$hookCmd}", true); } } } return $result; } } class Config { private $repo; private $context; private $gameInfoDir; private $rootDir; private $dataDir; private $dataFile; private $additionalDir; private $dllsDir; private $dlls64Dir; private $hooksDir; private $regsDir; private $cacheDir; private $logsDir; private $libsDir; private $libs64Dir; private $configPath; private $config; private $wine; private $dxvkConfFile; private $hooksGpuDir; private $symlinksDir; private $dataSymlinksDir; private $patchApplyDir; private $patchAutoDir; public function __construct($path = null) { $this->repo = 'https://raw.githubusercontent.com/hitman249/wine-helpers/master'; $this->context = [ 'ssl' => ['verify_peer' => false, 'verify_peer_name' => false], 'http' => ['header' => ['User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)']], ]; $this->rootDir = __DIR__; $this->gameInfoDir = "{$this->rootDir}/game_info"; $this->dataDir = "{$this->rootDir}/game_info/data"; $this->dataSymlinksDir = "{$this->rootDir}/game_info/data/_symlinks"; $this->dataFile = "{$this->rootDir}/game_info/data.squashfs"; $this->additionalDir = "{$this->rootDir}/game_info/additional"; $this->symlinksDir = "{$this->rootDir}/game_info/additional/symlinks"; $this->dllsDir = "{$this->rootDir}/game_info/dlls"; $this->dlls64Dir = "{$this->rootDir}/game_info/dlls64"; $this->hooksDir = "{$this->rootDir}/game_info/hooks"; $this->hooksGpuDir = "{$this->rootDir}/game_info/hooks/gpu"; $this->regsDir = "{$this->rootDir}/game_info/regs"; $this->cacheDir = "{$this->rootDir}/game_info/cache"; $this->logsDir = "{$this->rootDir}/game_info/logs"; $this->patchApplyDir = "{$this->rootDir}/game_info/patches/apply"; $this->patchAutoDir = "{$this->rootDir}/game_info/patches/auto"; $this->dxvkConfFile = "{$this->rootDir}/game_info/dxvk.conf"; $this->libsDir = "{$this->rootDir}/libs/i386"; $this->libs64Dir = "{$this->rootDir}/libs/x86-64"; $this->wineDir = "{$this->rootDir}/wine"; $this->wineFile = "{$this->rootDir}/wine.squashfs"; if (null !== $path) { $this->load($path); } $this->wine = [ 'WINEDEBUG' => '-all', 'WINEARCH' => 'win32', 'WINEDLLOVERRIDES' => '', // 'winemenubuilder.exe=d;nvapi,nvapi64,mscoree,mshtml=' 'WINEPREFIX' => "{$this->rootDir}/prefix", 'DRIVE_C' => "{$this->rootDir}/prefix/drive_c", 'WINE' => "{$this->rootDir}/wine/bin/wine", 'WINE64' => "{$this->rootDir}/wine/bin/wine64", 'REGEDIT' => "{$this->rootDir}/wine/bin/wine\" \"regedit", 'REGEDIT64' => "{$this->rootDir}/wine/bin/wine64\" \"regedit", 'WINEBOOT' => "{$this->rootDir}/wine/bin/wine\" \"wineboot", 'WINEFILE' => "{$this->rootDir}/wine/bin/wine\" \"winefile", 'WINECFG' => "{$this->rootDir}/wine/bin/wine\" \"winecfg", 'WINETASKMGR' => "{$this->rootDir}/wine/bin/wine\" \"taskmgr", 'WINEUNINSTALLER' => "{$this->rootDir}/wine/bin/wine\" \"uninstaller", 'WINEPROGRAM' => "{$this->rootDir}/wine/bin/wine\" \"progman", 'WINESERVER' => "{$this->rootDir}/wine/bin/wineserver", ]; } public function updateWine() { if ((new Wine($this, new Command($this)))->isUsedSystemWine()) { $this->wine['WINE'] = 'wine'; $this->wine['WINE64'] = 'wine64'; $this->wine['REGEDIT'] = 'wine" "regedit'; $this->wine['REGEDIT64'] = 'wine64" "regedit'; $this->wine['WINETASKMGR'] = 'wine" "taskmgr'; $this->wine['WINEUNINSTALLER'] = 'wine" "uninstaller'; $this->wine['WINEPROGRAM'] = 'wine" "progman'; $this->wine['WINEBOOT'] = 'wineboot'; $this->wine['WINEFILE'] = 'winefile'; $this->wine['WINECFG'] = 'winecfg'; $this->wine['WINESERVER'] = 'wineserver'; } else { $this->wine['WINE'] = "{$this->rootDir}/wine/bin/wine"; $this->wine['WINE64'] = "{$this->rootDir}/wine/bin/wine64"; $this->wine['REGEDIT'] = "{$this->rootDir}/wine/bin/wine\" \"regedit"; $this->wine['REGEDIT64'] = "{$this->rootDir}/wine/bin/wine64\" \"regedit"; $this->wine['WINETASKMGR'] = "{$this->rootDir}/wine/bin/wine\" \"taskmgr"; $this->wine['WINEUNINSTALLER'] = "{$this->rootDir}/wine/bin/wine\" \"uninstaller"; $this->wine['WINEPROGRAM'] = "{$this->rootDir}/wine/bin/wine\" \"progman"; $this->wine['WINEBOOT'] = "{$this->rootDir}/wine/bin/wine\" \"wineboot"; $this->wine['WINEFILE'] = "{$this->rootDir}/wine/bin/wine\" \"winefile"; $this->wine['WINECFG'] = "{$this->rootDir}/wine/bin/wine\" \"winecfg"; $this->wine['WINESERVER'] = "{$this->rootDir}/wine/bin/wineserver"; } } public function wine($field) { $value = $this->get('wine', $field); if ($value === null) { $value = $this->wine[$field] ?: null; } return $value; } public function get($section, $field = null) { $this->load(); if ($field !== null && empty($this->config[$section])) { return null; } return null === $field ? $this->config[$section] : $this->config[$section][$field]; } /** * @return array */ public function getConfig() { $this->load(); return $this->config; } /** * @return bool */ public function save() { $update = new Update($this, new Command($this)); return $update->updateConfig($this); } public function set($section, $field, $value) { $this->load(); $this->config[$section][$field] = $value; } public function getBool($section, $field) { return (bool)$this->get($section, $field); } public function getInt($section, $field) { $result = $this->get($section, $field); if (is_array($result)) { return array_map('intval', $result); } return (int)$result; } public function getFloat($section, $field) { $result = $this->get($section, $field); if (is_array($result)) { return array_map('floatval', $result); } return (float)$result; } public function getGamePath() { return str_replace('\\', '/', trim($this->get('game', 'path'), '\\/')); } public function getPrefixGameFolder() { return $this->wine('DRIVE_C') . '/' . $this->getGamePath(); } public function getPrefixFolder() { return $this->wine('WINEPREFIX'); } public function isWineArch64() { return $this->wine('WINEARCH') === 'win64'; } public function isWineArch32() { return !$this->isWineArch64(); } public function getWineSystem32Folder() { if ($this->isWineArch64()) { return $this->wine('DRIVE_C') . '/windows/syswow64'; } return $this->wine('DRIVE_C') . '/windows/system32'; } public function getWineSyswow64Folder() { if ($this->isWineArch64()) { return $this->wine('DRIVE_C') . '/windows/system32'; } return $this->wine('DRIVE_C') . '/windows/syswow64'; } public function getGameAdditionalPath() { return str_replace('\\', '/', trim($this->get('game', 'additional_path'), '\\/')); } public function getGameExe() { return $this->get('game', 'exe'); } public function getGameArgs() { return $this->get('game', 'cmd'); } public function getGameTitle() { return $this->get('game', 'name'); } public function getGameVersion() { return $this->get('game', 'version'); } private function load($path = null) { if (null === $this->config) { if (!$path) { $path = $this->getConfigFile(); } else { $this->configPath = $path; } if (file_exists($this->configPath)) { $this->config = (new FileINI($path))->get(); } else { $this->config = parse_ini_string($this->getDefaultConfig(), true); @file_put_contents($path, $this->getDefaultConfig()); } } } public function reload() { if (file_exists($this->configPath)) { $this->config = (new FileINI($this->configPath))->get(); } } public function getPrefixDosdeviceDir() { return $this->getPrefixFolder() . '/dosdevices'; } public function getPrefixDriveC() { return $this->wine('DRIVE_C'); } /** * @return string */ public function getConfigFile() { if (null === $this->configPath) { $configs = $this->findConfigsPath(); if ($configs) { $this->configPath = reset($configs); } else { $this->configPath = $this->getGameInfoDir() . '/game_info.ini'; } } return $this->configPath; } public function findConfigsPath() { $configs = glob("{$this->gameInfoDir}/*.ini"); natsort($configs); return $configs; } public function getContextOptions($field = null) { if ('User-Agent' === $field) { return str_replace('User-Agent: ', '', $this->context['http']['header']); } return $this->context; } public function getRepositoryUrl() { return $this->repo; } public function getGameInfoDir() { return $this->gameInfoDir; } public function getRootDir() { return $this->rootDir; } public function getDataDir() { return $this->dataDir; } public function getDataFile() { return $this->dataFile; } public function getWineDir() { return $this->wineDir; } public function getWineFile() { return $this->wineFile; } public function getAdditionalDir() { return $this->additionalDir; } public function getSymlinksDir() { return $this->symlinksDir; } public function getDllsDir() { return $this->dllsDir; } public function getDlls64Dir() { return $this->dlls64Dir; } public function getHooksDir() { return $this->hooksDir; } public function getHooksGpuDir() { return $this->hooksGpuDir; } public function getRegistryDir() { return $this->regsDir; } public function getRegistryFiles() { if (!file_exists($this->getRegistryDir())) { return []; } $regs = glob($this->getRegistryDir() . '/*.reg'); natsort($regs); return $regs; } public function getCacheDir() { if (!file_exists($this->cacheDir) && !mkdir($this->cacheDir, 0775, true) && !is_dir($this->cacheDir)) { throw new \RuntimeException(sprintf('Directory "%s" was not created', $this->cacheDir)); } return $this->cacheDir; } public function getPatchApplyDir() { return $this->patchApplyDir; } public function getPatchAutoDir() { return $this->patchAutoDir; } public function getLogsDir() { if (!file_exists($this->logsDir) && !mkdir($this->logsDir, 0775, true) && !is_dir($this->logsDir)) { throw new \RuntimeException(sprintf('Directory "%s" was not created', $this->logsDir)); } return $this->logsDir; } public function getLibsDir() { return $this->libsDir; } public function getLibs64Dir() { return $this->libs64Dir; } public function getDxvkConfFile() { return $this->dxvkConfFile; } public function getDefaultConfig() { return '[game] path = "Games" additional_path = "The Super Game/bin" exe = "Game.exe" cmd = "-language=russian" name = "The Super Game: Deluxe Edition" version = "1.0.0" [wine] WINEARCH = "win32" WINEDLLOVERRIDES = "" WINEDEBUG = "-all" [export] ; ; Export additional variables ; Examples: ; ; DXVK_HUD=fps ; DXVK_HUD=1 ; DXVK_HUD=fps,devinfo,memory ; DXVK_HUD=fps,devinfo,frametimes,memory ; DXVK_HUD=fps,devinfo,frametimes,submissions,drawcalls,pipelines,memory ; GALLIUM_HUD=simple,fps ; WINEESYNC=1 ; PBA_DISABLE=1 ; MESA_GLTHREAD=true ; __GL_THREADED_OPTIMIZATIONS=1 ; ; If the game wheezing sound, you can try ; PULSE_LATENCY_MSEC=60 ; SDL_AUDIODRIVER=directsound WINEESYNC=1 PBA_DISABLE=1 [script] ; ; Automatic patch generation MODE. ; ; See folder ./game_info/patches/auto ; ; To apply, move the patches to the folder ./game_info/patches/apply ; ; When enabled, patches do not apply (only creates)! ; generation_patches_mode = 0 ; ; Autoupdate this script the latest version. ; https://github.com/hitman249/wine-helpers ; autoupdate = 1 ; ; Download the latest DXVK. ; https://github.com/doitsujin/dxvk ; Dxvk versions: dxvk101, dxvk100, dxvk96, dxvk95 other. Empty from latest. ; dxvk = 0 dxvk_version = "" dxvk_autoupdate = 1 ; ; Required for determining display manner FPS. ; dxvk_d3d10 = 0 ; ; Download the latest D9VK. ; https://github.com/Joshua-Ashton/d9vk ; D9vk versions: d9vk010, d9vk011 other. Empty from latest. ; d9vk = 0 d9vk_version = "" d9vk_autoupdate = 1 ; ; Download the latest dumbxinputemu. ; https://github.com/kozec/dumbxinputemu ; dumbxinputemu = 0 dumbxinputemu_autoupdate = 1 ; ; Download the latest FAudio. ; https://github.com/FNA-XNA/FAudio ; faudio = 0 faudio_autoupdate = 1 ; ; winetricks_to_install = "d3dx9 xact" ; winetricks_to_install = "" ; ; Windows version (win10, win7, winxp, win2k). ; winver = "win7" ; ; CSMT (Commandstream multithreading) for better graphic performance. ; csmt = 1 ; ; Not use /home/user directory. ; sandbox = 1 ; ; Set sound driver to PulseAudio or ALSA. ; pulse = 1 ; ; Auto fixed resolution, brightness, gamma for all monitors. ; fixres = 1 [fixes] ; ; Fix focus ; focus = 0 ; ; No crash dialog ; Values: 0(default), 1 ; nocrashdialog = 0 ; ; CheckFloatConstants ; Values: 0(default), 1 ; cfc = 0 ; ; DirectDrawRenderer ; Values: ""(default), "gdi", "opengl" ; ddr = "" ; ; Use GLSL shaders (1) or ARB shaders (0) (faster, but sometimes breaks) ; Values: 0, 1(default) ; glsl = 1 ; ; OffscreenRenderingMode ; Values: ""(default), "fbo", "backbuffer" ; orm = "" [window] enable = 0 ; ; resolution = "auto" ; resolution = "800x600" ; resolution = "auto" [dlls] ; ; Additional dlls folder logic ; Example: dll[name_file.dll] = "nooverride" ; ; Variables: ; "builtin" - Builtin ; "native" - External (default) ; "builtin,native" - Builtin, External ; "native,builtin" - External, Builtin ; "nooverride" - Do not register ; "register" - Register via regsvr32 ; ; dll[d3d11.dll] = "nooverride" ; dll[l3codecx.ax] = "register" [hooks] ; ; Hooks ; after_create_prefix - commands are executed after prefix creation ; before_run_game - commands are executed before the game start ; after_exit_game - commands are executed after the game exit ; ; after_create_prefix[] = "create.sh" ; before_run_game[] = "before.sh" ; after_exit_game[] = "after.sh" ; after_exit_game[] = "after2.sh" ; gpu_amd[] = "gpu/amd.sh" ; gpu_nvidia[] = "gpu/nvidia.sh" ; gpu_intel[] = "gpu/intel.sh" [replaces] ; ; When creating a prefix, it searches for and replaces tags in the specified files. ; Path relative to the position of the ./start file ; Performed BEFORE registering * .reg files ; ; {WIDTH} - default monitor width in pixels (number) ; {HEIGHT} - default monitor height in pixels (number) ; {USER} - username ; {DOSDEVICES} - Full path to "/.../prefix/dosdevice" ; {DRIVE_C} - Full path to "/.../prefix/drive_c" ; {PREFIX} - Full path to "/.../prefix" ; {ROOT_DIR} - Full path to game folder ; {HOSTNAME} - See command: hostname ; ; file[] = "game_info/data/example.conf"'; } public function getDefaultDxvkConfig() { return "# Create the VkSurface on the first call to IDXGISwapChain::Present, # rather than when creating the swap chain. Some games that start # rendering with a different graphics API may require this option, # or otherwise the window may stay black. # # Supported values: True, False # dxgi.deferSurfaceCreation = False # d3d9.deferSurfaceCreation = False # Enforce a stricter maximum frame latency. Overrides the application # setting specified by calling IDXGIDevice::SetMaximumFrameLatency. # Setting this to 0 will have no effect. # # Supported values : 0 - 16 # dxgi.maxFrameLatency = 0 # d3d9.maxFrameLatency = 0 # Override PCI vendor and device IDs reported to the application. Can # cause the app to adjust behaviour depending on the selected values. # # Supported values: Any four-digit hex number. # dxgi.customDeviceId = 0000 # dxgi.customVendorId = 0000 # d3d9.customDeviceId = 0000 # d3d9.customVendorId = 0000 # Report Nvidia GPUs as AMD GPUs by default. This is enabled by default # to work around issues with NVAPI, but may cause issues in some games. # # Supported values: True, False # dxgi.nvapiHack = True # Override maximum amount of device memory and shared system memory # reported to the application. This may fix texture streaming issues # in games that do not support cards with large amounts of VRAM. # # Supported values: Any number in Megabytes. # dxgi.maxDeviceMemory = 0 # dxgi.maxSharedMemory = 0 # Override back buffer count for the Vulkan swap chain. # Setting this to 0 or less will have no effect. # # Supported values: Any number greater than or equal to 2. # dxgi.numBackBuffers = 0 # Overrides synchronization interval (Vsync) for presentation. # Setting this to 0 disables vertical synchronization entirely. # A positive value 'n' will enable Vsync and repeat the same # image n times, and a negative value will have no effect. # # Supported values: Any non-negative number # dxgi.syncInterval = -1 # d3d9.presentInterval = -1 # Toggles asynchronous present. # # Off-loads presentation to the queue submission thread in # order to reduce stalling on the main rendering thread and # improve performance. # # Supported values: # - Auto: Enable on certain drivers # - True / False: Always enable / disable # dxgi.asyncPresent = Auto # d3d9.asyncPresent = Auto # Enables or dsables d3d10 support. # # Supported values: True, False # d3d10.enable = True # Handle D3D11_MAP_FLAG_DO_NOT_WAIT correctly when D3D11DeviceContext::Map() # is called. Enabling this can potentially improve performance, but breaks # games which do not expect Map() to return an error despite using the flag. # # Supported values: True, False # d3d11.allowMapFlagNoWait = False # Performs range check on dynamically indexed constant buffers in shaders. # This may be needed to work around a certain type of game bug, but may # also introduce incorrect behaviour. # # Supported values: True, False # d3d11.constantBufferRangeCheck = False # Assume single-use mode for command lists created on deferred contexts. # This may need to be disabled for some applications to avoid rendering # issues, which may come at a significant performance cost. # # Supported values: True, False # d3d11.dcSingleUseMode = True # Override the maximum feature level that a D3D11 device can be created # with. Setting this to a higher value may allow some applications to run # that would otherwise fail to create a D3D11 device. # # Supported values: 9_1, 9_2, 9_3, 10_0, 10_1, 11_0, 11_1 # d3d11.maxFeatureLevel = 11_1 # Overrides the maximum allowed tessellation factor. This can be used to # improve performance in titles which overuse tessellation. # # Supported values: Any number between 8 and 64 # d3d11.maxTessFactor = 0 # Enables relaxed pipeline barriers around UAV writes. # # This may improve performance in some games, but may also introduce # rendering issues. Please don't report bugs with the option enabled. # # Supported values: True, False # d3d11.relaxedBarriers = False # Overrides anisotropic filtering for all samplers. Set this to a positive # value to enable AF for all samplers in the game, or to 0 in order to # disable AF entirely. Negative values will have no effect. # # Supported values: Any number between 0 and 16 # d3d11.samplerAnisotropy = -1 # d3d9.samplerAnisotropy = -1 # Enables SM4-compliant division-by-zero behaviour. Enabling may reduce # performance and / or cause issues in games that expect the default # behaviour of Windows drivers, which also is not SM4-compliant. # # Supported values: True, False # d3d11.strictDivision = False # Clears workgroup memory in compute shaders to zero. Some games don't do # this and rely on undefined behaviour. Enabling may reduce performance. # # Supported values: True, False # d3d11.zeroWorkgroupMemory = False # Enables the dedicated transfer queue if available # # If enabled, resource uploads will be performed on the # transfer queue rather than the graphics queue. This # may improve texture streaming performance. # # Supported values: True, False # dxvk.enableTransferQueue = True # Sets number of pipeline compiler threads. # # Supported values: # - 0 to automatically determine the number of threads to use # - any positive number to enforce the thread count # dxvk.numCompilerThreads = 0 # Toggles raw SSBO usage. # # Uses storage buffers to implement raw and structured buffer # views. Enabled by default on hardware which has a storage # buffer offset alignment requirement of 4 Bytes (e.g. AMD). # Enabling this may improve performance, but is not safe on # hardware with higher alignment requirements. # # Supported values: # - Auto: Don't change the default # - True, False: Always enable / disable # dxvk.useRawSsbo = Auto # Toggles early discard. # # Uses subgroup operations to determine whether it is safe to # discard fragments before the end of a fragment shader. This # is enabled by default on all drivers except RADV and Nvidia. # Enabling this may improve or degrade performance depending # on the game and hardware, or cause other issues. # # Supported values: # - Auto: Don't change the default # - True, False: Always enable / disable # dxvk.useEarlyDiscard = Auto # Reported shader model # # The shader model to state that we support in the device # capabilities that the applicatation queries. # # Supported values: # - 1: Shader Model 1 # - 2: Shader Model 2 # - 3: Shader Model 3 # d3d9.shaderModel = 3 # Evict Managed on Unlock # # Decides whether we should evict managed resources from # system memory when they are unlocked entirely. # # Supported values: # - True, False: Always enable / disable # d3d9.evictManagedOnUnlock = False # DPI Awareness # # Decides whether we should call SetProcessDPIAware on device # creation. Helps avoid upscaling blur in modern Windows on # Hi-DPI screens/devices. # # Supported values: # - True, False: Always enable / disable # d3d9.dpiAware = True # Strict Constant Copies # # Decides whether we should always copy defined constants to # the UBO when relative addresssing is used, or only when the # relative addressing starts a defined constant. # # Supported values: # - True, False: Always enable / disable # d3d9.strictConstantCopies = False # Strict Pow # # Decides whether we have an opSelect for handling pow(0,0) = 0 # otherwise it becomes undefined. # # Supported values: # - True, False: Always enable / disable # d3d9.strictPow = True # Lenient Clear # # Decides whether or not we fastpath clear anyway if we are close enough to # clearing a full render target. # # Supported values: # - True, False: Always enable / disable # d3d9.lenientClear = False # Max available memory # # Changes the max initial value used in tracking and GetAvailableTextureMem # # Supported values: # - Any uint32_t # d3d9.maxAvailableMemory = 4294967295"; } public function isScriptAutoupdate() { return $this->getBool('script', 'autoupdate'); } public function isDxvkAutoupdate() { return $this->getBool('script', 'dxvk_autoupdate'); } public function isDxvk() { return $this->getBool('script', 'dxvk'); } public function isD9vkAutoupdate() { return $this->getBool('script', 'd9vk_autoupdate'); } public function isD9vk() { return $this->getBool('script', 'd9vk'); } public function isSandbox() { return $this->getBool('script', 'sandbox'); } public function isPulse() { return $this->getBool('script', 'pulse'); } public function isFixres() { return $this->getBool('script', 'fixres'); } public function isCsmt() { return $this->getBool('script', 'csmt'); } public function isPBA() { return $this->get('export', 'PBA_DISABLE') !== null && !$this->getBool('export', 'PBA_DISABLE'); } public function isEsync() { return $this->getBool('export', 'WINEESYNC'); } public function isGenerationPatchesMode() { return $this->getBool('script', 'generation_patches_mode'); } public function getDxvkConfigFile() { $file = $this->dxvkConfFile; $driveC = $this->wine('DRIVE_C') . '/dxvk.conf'; static $run = false; if ($run) { return $driveC; } if (file_exists($file) && !file_exists($driveC)) { $run = true; (new Command($this))->run("ln -sfr \"{$file}\" \"{$driveC}\""); $run = false; } return $driveC; } public function getDxvkCacheDir() { $dir = $this->getCacheDir(); $driveC = $this->wine('DRIVE_C') . '/cache'; static $run = false; if ($run) { return $driveC; } if (!file_exists($dir)) { if (!mkdir($dir, 0775) && !is_dir($dir)) { throw new \RuntimeException(sprintf('Directory "%s" was not created', $dir)); } } if (!file_exists($driveC)) { $run = true; (new Command($this))->run("ln -sfr \"{$dir}\" \"{$driveC}\""); $run = false; } return $driveC; } public function getDxvkLogsDir() { $dir = $this->getLogsDir(); $driveC = $this->wine('DRIVE_C') . '/logs'; static $run = false; if ($run) { return $driveC; } if (!file_exists($dir)) { if (!mkdir($dir, 0775) && !is_dir($dir)) { throw new \RuntimeException(sprintf('Directory "%s" was not created', $dir)); } } if (!file_exists($driveC)) { $run = true; (new Command($this))->run("ln -sfr \"{$dir}\" \"{$driveC}\""); $run = false; } return $driveC; } public function getWindowsVersion() { return $this->get('script' , 'winver'); } public function getWineArch() { return $this->wine('WINEARCH'); } public function getDataSymlinksDir() { return $this->dataSymlinksDir; } public function versionPrefix() { $version = $this->wine('WINEPREFIX') . '/version'; if (file_exists($version)) { return trim(file_get_contents($version)); } return ''; } } class Wine { private $command; private $config; private $version; private $missingLibs; /** * Wine constructor. * @param Config $config * @param Command $command */ public function __construct(Config $config, Command $command) { $this->command = $command; $this->config = $config; } public function boot() { $this->command->run(Text::quoteArgs($this->config->wine('WINEBOOT')) . ' && ' . Text::quoteArgs($this->config->wine('WINESERVER')) . ' -w'); } public function down() { $this->command->run(Text::quoteArgs($this->config->wine('WINESERVER')) . ' -k'); } public function run($args) { $cmd = Text::quoteArgs($args); $result = $this->command->run(Text::quoteArgs($this->config->wine('WINE')) . " {$cmd}"); if ($this->config->wine('WINEARCH') === 'win64') { $result .= $this->command->run(Text::quoteArgs($this->config->wine('WINE64')) . " {$cmd}"); } return $result; } public function fm($args) { $config = clone $this->config; $config->set('wine', 'WINEDEBUG', ''); $cmd = Text::quoteArgs($args); $logFile = $this->config->getLogsDir() . '/filemanager.log'; return app('start')->getPatch()->create(function () use ($config, $cmd, $logFile) { return (new Command($config))->run(Text::quoteArgs($this->config->wine('WINEFILE')) . " {$cmd}", $logFile); }); } public function cfg($args) { $cmd = Text::quoteArgs($args); return $this->command->run(Text::quoteArgs($this->config->wine('WINECFG')) . " {$cmd}"); } public function reg($args) { $cmd = Text::quoteArgs($args); $result = $this->command->run(Text::quoteArgs($this->config->wine('REGEDIT')) . " {$cmd}"); if ($this->config->wine('WINEARCH') === 'win64') { $result .= $this->command->run(Text::quoteArgs($this->config->wine('REGEDIT64')) . " {$cmd}"); } return $result; } public function regsvr32($args) { $this->run(array_merge(['regsvr32'], $args)); } public function winetricks($args, $output = false) { (new Update($this->config, $this->command))->downloadWinetricks(); if ($args && file_exists($this->config->getRootDir() . '/winetricks')) { $config = clone $this->config; $config->set('wine', 'WINEDEBUG', ''); $cmd = Text::quoteArgs($args); $title = implode('-', $args); $title = mb_strlen($title) > 50 ? mb_substr($title, 0, 48) . '..' : $title; $logFile = $this->config->getLogsDir() . "/winetricks-{$title}.log"; return app('start')->getPatch()->create(function () use (&$config, $cmd, $logFile, $output) { return (new Command($config))->run(Text::quoteArgs($this->config->getRootDir() . '/winetricks') . " {$cmd}", $logFile, $output); }); } return ''; } public function checkSystemWine() { return (bool)trim($this->command->run('command -v "wine"')); } public function checkWine() { return (bool)trim($this->command->run('command -v ' . Text::quoteArgs($this->config->wine('WINE')))); } public function version() { if (null === $this->version) { $this->version = trim($this->command->run(Text::quoteArgs($this->config->wine('WINE')) . ' --version')); } return $this->version; } public function isUsedSystemWine() { return !file_exists($this->config->getRootDir() . '/wine/bin/wine') || version_compare((new System($this->config, $this->command))->getGlibcVersion(), '2.23', '<'); } public function getMissingLibs() { if (null === $this->missingLibs) { $help = $this->command->run(Text::quoteArgs($this->config->wine('WINE')) . ' --help'); if (strpos($help, '--check-libs') === false) { $this->missingLibs = []; return $this->missingLibs; } $this->missingLibs = $this->command->run(Text::quoteArgs($this->config->wine('WINE')) . ' --check-libs'); $this->missingLibs = array_filter( array_map('trim', explode("\n", $this->missingLibs)), function ($line) { if (!$line) { return false; } list($left, $right) = array_map( function ($s) {return trim($s, " \t\n\r\0\x0B.");}, explode(':', $line) ); return strpos($right, '.') === false; } ); } return $this->missingLibs; } } class Update { private $version = '0.85'; private $command; private $config; private $network; private $system; /** * Update constructor. * @param Config $config * @param Command $command */ public function __construct(Config $config, Command $command) { $this->command = $command; $this->config = $config; $this->network = new Network($config, $command); $this->system = new System($config, $command); } public function version() { return $this->version; } public function getUrl() { return 'https://github.com/hitman249/wine-helpers'; } public function init() { /** * Autoupdate script */ if ($this->autoupdate()) { $this->restart(); } /** * Autoupdate dxvk */ (new DXVK($this->config, $this->command, $this->network))->update(); /** * Autoupdate d9vk */ (new D9VK($this->config, $this->command, $this->network, app('start')->getFileSystem(), app('start')->getWine()))->update(); /** * README.md */ $this->updateReadme(); $restart = $this->config->getRootDir() . '/restart'; if (file_exists($restart)) { unlink($restart); } } public function updateReadme($create = false, $update = false) { if ($create === false) { if (!file_exists($this->config->getRootDir() . '/README.md')) { return false; } $path = $this->config->wine('DRIVE_C') . '/readme'; if (!file_exists($path)) { if ($update === true) { file_put_contents($path, ' '); return false; } if ($update === false) { return false; } } if ($update === true) { return false; } if (file_exists($path)) { unlink($path); } else { return false; } } if (Network::isConnected()) { if (file_exists($this->config->wine('DRIVE_C'))) { $readme = $this->network->get($this->config->getRepositoryUrl() . '/README.md'); if ($readme) { /** * README.md */ file_put_contents($this->config->getRootDir() . '/README.md', $readme); return true; } } } return false; } /** * @param Config|null $config * @return bool */ public function updateConfig($config = null) { if (null === $config) { $config = $this->config; } if (!file_exists($config->getConfigFile())) { return false; } $result = []; $current = $config->getConfig(); $currentText = file_get_contents($config->getConfigFile()); $defaultText = $config->getDefaultConfig(); $defaultTextArray = explode("\n", $defaultText); $section = null; $space = null; foreach ($defaultTextArray as $line) { $line = trim($line); if (!$line) { $result[] = ''; continue; } if (Text::startsWith($line, '[') && Text::endsWith($line, ']')) { if ($section !== null) { if ($space === null) { $space = true; $result[] = ''; } if ($current[$section]) { foreach ($current[$section]?:[] as $key => $value) { if (is_array($value)) { foreach ($value?:[] as $k => $v) { $v = is_numeric($v) && $v !== null && $v !== '' ? $v : "\"{$v}\""; if (is_numeric($k)) { $result[] = "{$key}[] = {$v}"; } else { $result[] = "{$key}[{$k}] = {$v}"; } } } else { $value = is_numeric($value) && $value !== null && $value !== '' ? $value : "\"{$value}\""; $result[] = "{$key} = {$value}"; } } $result[] = ''; } } $space = null; $result[] = $line; $section = trim(str_replace(['[',']'], '', $line)); continue; } if (Text::startsWith($line, ';')) { $result[] = $line; continue; } if ($section !== null) { list($key, $value) = array_map(function ($n) { return trim($n, " \t\n\r\0\x0B\"'");}, explode('=', $line)); if (Text::endsWith($key, ']')) { } else { if (!isset($current[$section][$key])) { $result[] = $line; } else { $value = $current[$section][$key]; $value = is_numeric($value) && $value !== null && $value !== '' ? $value : "\"{$value}\""; $result[] = "{$key} = {$value}"; unset($current[$section][$key]); } } } } if ($section !== null) { $result[] = ''; foreach ($current[$section]?:[] as $key => $value) { if (is_array($value)) { foreach ($value?:[] as $k => $v) { $v = is_numeric($v) && $v !== null && $v !== '' ? $v : "\"{$v}\""; if (is_numeric($k)) { $result[] = "{$key}[] = {$v}"; } else { $result[] = "{$key}[{$k}] = {$v}"; } } } else { $value = is_numeric($value) && $value !== null && $value !== '' ? $value : "\"{$value}\""; $result[] = "{$key} = {$value}"; } } $result[] = ''; } $newConfig = implode("\n", $result); if (md5($currentText) !== md5($newConfig)) { file_put_contents($config->getConfigFile(), $newConfig); app('start')->getConfig()->reload(); return true; } return false; } public function versionRemote() { static $version; if (null === $version) { $version = trim($this->network->get($this->config->getRepositoryUrl() . '/RELEASE')); } return $version; } public function autoupdate() { if ($this->config->isScriptAutoupdate() && Network::isConnected()) { if ($this->versionRemote() !== $this->version()) { return $this->update(); } } return false; } public function update() { $newStart = $this->network->get($this->config->getRepositoryUrl() . '/start'); if ($newStart) { file_put_contents($this->config->getRootDir() . '/start', $newStart); $this->command->run('chmod +x ' . Text::quoteArgs($this->config->getRootDir() . '/start')); /** * README.md */ $this->updateReadme(false, true); return true; } return false; } public function downloadWinetricks() { $filePath = $this->config->getRootDir() . '/winetricks'; $isDelete = false; if (file_exists($filePath)) { $createAt = filectime($filePath); $currentAt = time(); if (($currentAt - $createAt) > 86400) { $isDelete = true; } } if ($isDelete || !file_exists($filePath)) { $winetricks = $this->network ->get('https://raw.githubusercontent.com/Winetricks/winetricks/master/src/winetricks'); if ($winetricks) { file_put_contents($filePath, $winetricks); $this->command->run("chmod +x \"{$filePath}\""); return true; } } return false; } public function downloadSquashfuse() { $filePath = $this->config->getRootDir() . '/squashfuse'; if (!file_exists($filePath)) { $squashfuse = $this->network->getRepo('/squashfuse'); if ($squashfuse) { file_put_contents($filePath, $squashfuse); $this->command->run("chmod +x \"{$filePath}\""); return true; } } return false; } public function downloadFusezip() { $filePath = $this->config->getRootDir() . '/fuse-zip'; if (!file_exists($filePath)) { $fusezip = $this->network->getRepo('/fuse-zip'); if ($fusezip) { file_put_contents($filePath, $fusezip); $this->command->run("chmod +x \"{$filePath}\""); return true; } } return false; } public function downloadHwprobe() { $filePath = $this->config->getRootDir() . '/hw-probe'; if (!file_exists($filePath)) { $hwprobe = $this->network->getRepo('/hw-probe'); if ($hwprobe) { file_put_contents($filePath, $hwprobe); $this->command->run("chmod +x \"{$filePath}\""); return true; } } return false; } public function downloadOsd() { $filePath = $this->config->getRootDir() . '/osd'; if (!file_exists($filePath)) { $osd = $this->network->getRepo('/osd'); if ($osd) { file_put_contents($filePath, $osd); $this->command->run("chmod +x \"{$filePath}\""); return true; } } return false; } public function updatePhp() { if ($this->system->checkPhp()) { return false; } $filePath = $this->config->getRootDir() . '/php'; $php = $this->network->getRepo('/php'); if ($php) { if (file_exists($filePath)) { @unlink($filePath); } file_put_contents($filePath, $php); $this->command->run("chmod +x \"{$filePath}\""); return true; } return false; } public function restart() { $restart = $this->config->getRootDir() . '/restart'; if (!file_exists($restart)) { file_put_contents($restart, ' '); exit(0); } } } class Monitor { private $config; private $command; private $xrandr; private $monitors; private $jsonPath; /** * Monitor constructor. */ public function __construct(Config $config, Command $command) { $this->command = $command; $this->config = $config; $this->jsonPath = $this->config->getRootDir() . '/resolutions.json'; } public function isXrandr() { if (null === $this->xrandr) { $this->xrandr = (bool)trim($this->command->run("command -v xrandr")); } return $this->xrandr; } public function resolutions($reload = false) { if (!$this->isXrandr()) { return []; } if (false === $reload && null !== $this->monitors) { return $this->monitors; } $head = '/^(.*) connected( | primary )([0-9]{3,4}x[0-9]{3,4}).*\n*/m'; $dump = $this->command->run('xrandr --verbose'); $array = explode("\n", $dump); $monitors = []; preg_match_all($head, $dump, $matches); foreach ($matches[0] as $i => $_line) { $monitors[$matches[1][$i]] = [ 'output' => $matches[1][$i], 'resolution' => $matches[3][$i], ]; $inner = false; foreach ($array as $line) { if (!$line || !$_line) { continue; } if ($inner === false && strpos($_line, $line) !== false) { $inner = true; $monitors[$matches[1][$i]]['default'] = strpos($line, 'primary') !== false; } elseif ($inner) { if (strpos($line, 'connected') !== false || strpos($line, 'disconnected') !== false) { $inner = false; } else { if (isset($monitors[$matches[1][$i]]['brightness'], $monitors[$matches[1][$i]]['gamma'])) { $inner = false; break; } if (strpos($line, 'Brightness:') !== false) { $value = trim(str_replace('Brightness:', '', $line)); $monitors[$matches[1][$i]]['brightness'] = $value; } if (strpos($line, 'Gamma:') !== false) { $value = trim(str_replace('Gamma:', '', $line)); $monitors[$matches[1][$i]]['gamma'] = $value; } } } } } $this->monitors = $monitors; return $this->monitors; } public function getDefaultMonitor() { $monitors = $this->resolutions(); foreach ($monitors as $monitor) { if ($monitor['default']) { return $monitor; } } return []; } public function resolutionsSave() { file_put_contents($this->jsonPath, json_encode($this->resolutions(true), JSON_PRETTY_PRINT)); } public function resolutionLoad() { if (file_exists($this->jsonPath)) { return json_decode(file_get_contents($this->jsonPath), true); } return null; } public function resolutionsRestore() { if (!$this->isXrandr()) { return []; } $result = []; $monitors = $this->resolutions(true); foreach ($this->resolutionLoad()?:[] as $output => $params) { if ($monitors[$output]) { if ($params['gamma'] !== $monitors[$output]['gamma']) { $this->command->run(Text::quoteArgs($this->config->wine('WINESERVER')) . " -w && xrandr --output {$output} --gamma {$params['gamma']}"); $result[] = "Revert gamma, output {$output}, gamma {$monitors[$output]['gamma']} > {$params['gamma']}."; } if ($params['brightness'] !== $monitors[$output]['brightness']) { $this->command->run(Text::quoteArgs($this->config->wine('WINESERVER')) . " -w && xrandr --output {$output} --brightness {$params['brightness']}"); $result[] = "Revert brightness, output {$output}, brightness {$monitors[$output]['brightness']} > {$params['brightness']}."; } if ($params['resolution'] !== $monitors[$output]['resolution']) { $this->command->run(Text::quoteArgs($this->config->wine('WINESERVER')) . " -w && xrandr --output {$output} --mode {$params['resolution']}"); $result[] = "Revert resolution, output {$output}, resolution {$monitors[$output]['resolution']} > {$params['resolution']}."; } } } if (file_exists($this->jsonPath)) { @unlink($this->jsonPath); } return $result; } } class WinePrefix { private $command; private $config; private $wine; private $monitor; private $system; private $fs; private $update; private $event; private $replaces; private $network; private $log; private $buffer; private $created = false; /** * WinePrefix constructor. * @param Config $config * @param Command $command * @param Event $event */ public function __construct(Config $config, Command $command, Event $event) { $this->command = $command; $this->config = $config; $this->event = $event; $this->wine = new Wine($this->config, $this->command); $this->monitor = new Monitor($this->config, $this->command); $this->system = new System($this->config, $this->command); $this->fs = new FileSystem($this->config, $this->command); $this->update = new Update($this->config, $this->command); $this->replaces = new Replaces($this->config, $this->command, $this->fs, $this->system, $this->monitor); $this->network = new Network($this->config, $this->command); } public function log($text) { $logPath = $this->config->getLogsDir() . '/prefix.log'; if (null === $this->log) { $this->log = app('start')->getLog(); } if (null === $this->buffer) { $this->buffer = app('start')->getBuffer(); $this->buffer->clear(); if (file_exists($logPath)) { @unlink($logPath); } } $this->log->insertLogFile($text, $logPath); $this->buffer->add($text); } public function create() { if (file_exists($this->config->getRootDir() . '/wine/bin')) { $this->command->run('chmod +x -R ' . Text::quoteArgs($this->config->getRootDir() . '/wine/bin/')); } if (!file_exists($this->config->wine('WINEPREFIX'))) { app('gui'); $this->created = true; (new CheckDependencies($this->config, $this->command, $this->system))->check(); app()->showPrefix(); $this->log('Create folder "' . $this->config->wine('WINEPREFIX') . '"'); app()->getCurrentScene()->setProgress(10); $this->log('Initialize ' . $this->wine->version() . ' prefix.'); $this->wine->boot(); @file_put_contents($this->config->wine('WINEPREFIX') . '/version', $this->wine->version()); app()->getCurrentScene()->setProgress(20); /** * Apply replace {WIDTH}, {HEIGHT}, {USER} from files */ foreach ($this->updateReplaces() as $replace) { $this->log($replace); } app()->getCurrentScene()->setProgress(25); /** * Sandbox the prefix; Borrowed from winetricks scripts */ if ($this->updateSandbox(true)) { $this->log('Set sandbox.'); } app()->getCurrentScene()->setProgress(30); /** * Create symlink to game directory */ $this->createGameDirectory(); app()->getCurrentScene()->setProgress(33); /** * Apply reg files */ $regs = array_merge( app('start')->getPatch()->getRegistryFiles(), $this->config->getRegistryFiles() ); app('start')->getRegistry()->apply($regs, function ($text) {$this->log($text);}); app()->getCurrentScene()->setProgress(35); /** * Apply patches */ if (app('start')->getPatch()->apply()) { $this->log('Apply patches'); } app()->getCurrentScene()->setProgress(37); /** * Update dumbxinputemu */ if ($this->config->getBool('script', 'dumbxinputemu')) { app('start')->getPatch()->create(function () { (new Dumbxinputemu($this->config, $this->command, $this->fs, $this->wine))->update(function ($text) {$this->log($text);}); }); } app()->getCurrentScene()->setProgress(40); /** * Update FAudio */ if ($this->config->getBool('script', 'faudio')) { app('start')->getPatch()->create(function () { (new FAudio($this->config, $this->command, $this->fs, $this->wine))->update(function ($text) {$this->log($text);}); }); } app()->getCurrentScene()->setProgress(45); /** * Apply fixes */ (new Fixes($this->config, $this->command, $this->fs, $this->wine))->update(function ($text) {$this->log($text);}); app()->getCurrentScene()->setProgress(50); if ($winetricksInstall = $this->config->get('script', 'winetricks_to_install')) { $this->log("Winetricks install \"{$winetricksInstall}\"."); $this->wine->winetricks(array_filter(explode(' ', $winetricksInstall))); } /** * Copy required dlls and override them */ $this->updateDlls(); app()->getCurrentScene()->setProgress(55); /** * Enable or disable CSMT */ $this->updateCsmt(); app()->getCurrentScene()->setProgress(70); /** * Set sound driver to PulseAudio; Borrowed from winetricks */ $this->updatePulse(); app()->getCurrentScene()->setProgress(75); /** * Set windows version; Borrowed from winetricks */ $this->updateWinVersion(); app()->getCurrentScene()->setProgress(85); /** * Install latest dxvk (d3d10.dll, d3d10_1.dll, d3d11.dll, dxgi.dll) */ if ($this->config->isDxvk()) { app('start')->getPatch()->create(function () { (new DXVK($this->config, $this->command, $this->network))->update(function ($text) {$this->log($text);}); }); } app()->getCurrentScene()->setProgress(87); /** * Install latest d9vk (d3d9.dll) */ if ($this->config->isD9vk()) { app('start')->getPatch()->create(function () { (new D9VK($this->config, $this->command, $this->network, app('start')->getFileSystem(), app('start')->getWine())) ->update(function ($text) {$this->log($text);}); }); } app()->getCurrentScene()->setProgress(90); /** * Fired hooks */ $this->event->createPrefix(); app()->getCurrentScene()->setProgress(95); $this->event->gpu(); app()->getCurrentScene()->setProgress(100); $this->log('Success!'); } $this->init(); } public function init() { if (!$this->wine->checkWine()) { (new Logs())->log('There is no Wine available in your system!'); exit(0); } /** * Update configs */ $this->update->updateConfig(); (new DXVK($this->config, $this->command, $this->network))->updateDxvkConfig(); /** * Create symlink to game directory */ $this->createGameDirectory(); /** * Enable or disable CSMT */ $this->updateCsmt(); /** * Update dumbxinputemu */ (new Dumbxinputemu($this->config, $this->command, $this->fs, $this->wine))->update(function ($text) {$this->log($text);}); /** * Update FAudio */ (new FAudio($this->config, $this->command, $this->fs, $this->wine))->update(function ($text) {$this->log($text);}); /** * Apply fixes */ (new Fixes($this->config, $this->command, $this->fs, $this->wine))->update(function ($text) {$this->log($text);}); /** * Copy required dlls and override them */ $this->updateDlls(); /** * Set sound driver to PulseAudio; Borrowed from winetricks */ $this->updatePulse(); /** * Set windows version; Borrowed from winetricks */ $this->updateWinVersion(); } public function updateReplaces() { if (!file_exists($this->config->wine('WINEPREFIX'))) { return []; } if (!$this->config->get('replaces', 'file')) { return []; } $result = []; $userName = $this->replaces->getUserName(); $width = $this->replaces->getWidth(); $height = $this->replaces->getHeight(); foreach ((array)$this->config->get('replaces', 'file') as $file) { $file = trim($file, " \t\n\r\0\x0B/"); if (file_exists($this->config->getRootDir() . "/{$file}")) { $this->replaces->replaceByFile($this->config->getRootDir() . "/{$file}", true); $result[] = "Replace {WIDTH}x{HEIGHT} -> {$width}x{$height}, {USER} -> \"{$userName}\" ... from file \"{$file}\""; } } return $result; } public function updateDlls() { if (!file_exists($this->config->wine('WINEPREFIX'))) { return []; } /** * Copy required dlls and override them */ $dlls = []; $isDll32 = file_exists($this->config->getDllsDir()) && file_exists($this->config->getWineSystem32Folder()); $isDll64 = file_exists($this->config->getDlls64Dir()) && file_exists($this->config->getWineSyswow64Folder()); $isChange = false; $result = []; if ($isDll32) { $files = glob($this->config->getDllsDir(). '/*.dll'); if ($this->config->get('dlls', 'dll')) { foreach ($this->config->get('dlls', 'dll') as $dll => $rule) { $path = $this->config->getDllsDir() . "/{$dll}"; if (file_exists($path) && !in_array($path, $files, true)) { $files[] = $path; } } } foreach ($files as $filePath) { $fileName = basename($filePath); $to = $this->config->wine('DRIVE_C') . "/windows/system32/{$fileName}"; if (file_exists($to)) { if (md5_file($filePath) === md5_file($to)) { continue; } else { unlink($to); } } $isChange = true; $dlls[$fileName] = 'native'; $dll32 = $this->fs->relativePath($this->config->getDllsDir()); $this->command->run('ln -sfr ' . Text::quoteArgs("{$dll32}/{$fileName}") . ' ' . Text::quoteArgs($this->config->wine('DRIVE_C') . '/windows/system32/')); $result[] = "Add system32/{$fileName}"; $this->log("Add system32/{$fileName}"); } } if ($isDll64) { $files = glob($this->config->getDlls64Dir() . '/*.dll'); if ($this->config->get('dlls', 'dll')) { foreach ($this->config->get('dlls', 'dll') as $dll => $rule) { $path = $this->config->getDlls64Dir() . "/{$dll}"; if (file_exists($path) && !in_array($path, $files, true)) { $files[] = $path; } } } foreach ($files as $filePath) { $fileName = basename($filePath); $to = $this->config->wine('DRIVE_C') . "/windows/syswow64/{$fileName}"; if (file_exists($to)) { if (md5_file($filePath) === md5_file($to)) { continue; } else { unlink($to); } } $isChange = true; $dlls[$fileName] = 'native'; $dll64 = $this->fs->relativePath($this->config->getDlls64Dir()); $this->command->run('ln -sfr ' . Text::quoteArgs("{$dll64}/{$fileName}") . ' ' . Text::quoteArgs($this->config->wine('DRIVE_C') . '/windows/syswow64/')); $result[] = "Add system64/{$fileName}"; $this->log("Add system64/{$fileName}"); } } if ($isChange) { $dlls = array_filter($dlls); if ($dlls) { // $this->runExternal("\"{$this->wineConfig['WINE']}\" reg delete \"HKEY_CURRENT_USER\\Software\\Wine\\DllOverrides\" /f"); $configDlls = $this->config->get('dlls', 'dll'); foreach ($dlls as $dll => $typeOverride) { if (!empty($configDlls) && !empty($configDlls[$dll])) { if ($configDlls[$dll] === 'nooverride') { $result[] = "Register skip {$dll}"; $this->log("Register skip {$dll}"); continue; } if ($configDlls[$dll] === 'register') { $this->wine->regsvr32([$dll]); $result[] = "Register regsvr32 {$dll}"; $this->log("Register regsvr32 {$dll}"); continue; } $typeOverride = $configDlls[$dll]; } $this->wine->run(['reg', 'add', 'HKEY_CURRENT_USER\\Software\\Wine\\DllOverrides', '/v', $dll, '/d', $typeOverride, '/f']); $result[] = "Register {$dll}"; $this->log("Register {$dll}"); } $result[] = "Update dll overrides."; $this->log("Update dll overrides."); } } return $result; } public function updateCsmt() { if (!file_exists($this->config->wine('WINEPREFIX'))) { return false; } $reg = [ "Windows Registry Editor Version 5.00\n", "[HKEY_CURRENT_USER\Software\Wine\Direct3D]\n", ]; $file = $this->config->wine('DRIVE_C') . '/csmt.reg'; if ($this->config->isCsmt() && !file_exists($file)) { $reg[] = "\"csmt\"=-\n"; file_put_contents($file, implode("\n", $reg)); $this->wine->reg([$file]); $this->log('CSMT enable.'); return true; } elseif (!$this->config->isCsmt() && file_exists($file)) { $reg[] = "\"csmt\"=dword:0\n"; file_put_contents($file, implode("\n", $reg)); $this->wine->reg([$file]); unlink($file); $this->log('CSMT disable.'); return false; } return $this->config->isCsmt(); } public function updatePulse() { if (!file_exists($this->config->wine('WINEPREFIX'))) { return false; } $reg = [ "Windows Registry Editor Version 5.00\n", "[HKEY_CURRENT_USER\Software\Wine\Drivers]\n", ]; $isInstallPulseAudio = (bool)trim($this->command->run("command -v pulseaudio")); if ($isInstallPulseAudio === false && $this->config->isPulse()) { $this->config->set('script', 'pulse', 0); } $filePulsa = $this->config->wine('DRIVE_C') . '/usepulse.reg'; $fileAlsa = $this->config->wine('DRIVE_C') . '/usealsa.reg'; if ($this->config->isPulse() && !file_exists($filePulsa)) { $reg[] = "\"Audio\"=\"pulse\"\n"; file_put_contents($filePulsa, implode("\n", $reg)); $this->wine->reg([$filePulsa]); if (file_exists($fileAlsa)) { unlink($fileAlsa); } $this->log('Set sound driver to PulseAudio.'); return true; } elseif (!$this->config->isPulse() && !file_exists($fileAlsa)) { $reg[] = "\"Audio\"=\"alsa\"\n"; file_put_contents($fileAlsa, implode("\n", $reg)); $this->wine->reg([$fileAlsa]); if (file_exists($filePulsa)) { unlink($filePulsa); } $this->log('Set sound driver to Alsa.'); return false; } return $this->config->isPulse(); } public function updateWinVersion() { if (!file_exists($this->config->wine('WINEPREFIX'))) { return false; } $lastwin = $this->config->wine('DRIVE_C') . '/lastwin'; if (file_exists($lastwin)) { $winver = trim(file_get_contents($lastwin)); if ($winver === $this->config->getWindowsVersion()) { return false; } } $default = []; $defaultWinver = 'win7'; $reg = [ "Windows Registry Editor Version 5.00\n", ]; switch ($this->config->getWindowsVersion()) { case 'win2k'; $defaultWinver = 'win2k'; $default = [ 'HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion' => [ 'CSDVersion' => 'Service Pack 4', 'CurrentBuildNumber' => '2195', 'CurrentVersion' => '5.0', ], 'HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Windows' => [ 'CSDVersion' => 'dword:00000400', ], ]; break; case 'winxp'; $defaultWinver = 'winxp'; $default = [ 'HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion' => [ 'CSDVersion' => 'Service Pack 3', 'CurrentBuildNumber' => '2600', 'CurrentVersion' => '5.1', ], 'HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Windows' => [ 'CSDVersion' => 'dword:00000300', ], ]; break; case 'win10'; $this->wine->run(['reg', 'add', 'HKLM\\System\\CurrentControlSet\\Control\\ProductOptions', '/v', 'ProductType', '/d', 'WinNT', '/f']); $defaultWinver = 'win10'; $default = [ 'HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion' => [ 'CSDVersion' => '', 'CurrentBuildNumber' => '10240', 'CurrentVersion' => '10.0', ], 'HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Windows' => [ 'CSDVersion' => 'dword:00000300', ], ]; break; case 'win7': default: $this->wine->run(['reg', 'add', 'HKLM\\System\\CurrentControlSet\\Control\\ProductOptions', '/v', 'ProductType', '/d', 'WinNT', '/f']); $defaultWinver = 'win7'; $default = [ 'HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion' => [ 'CSDVersion' => 'Service Pack 1', 'CurrentBuildNumber' => '7601', 'CurrentVersion' => '6.1', ], 'HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Windows' => [ 'CSDVersion' => 'dword:00000100', ], ]; } foreach ($default as $path => $values) { $reg[] = "\n[{$path}]\n"; foreach ($values as $key => $value) { $reg[] = "\"{$key}\"=\"{$value}\"\n"; } } file_put_contents($lastwin, $defaultWinver); file_put_contents($this->config->wine('DRIVE_C') . '/setwinver.reg', implode('', $reg)); $this->wine->reg([$this->config->wine('DRIVE_C') . '/setwinver.reg']); $this->log("Set Windows {$defaultWinver} version."); return true; } public function updateSandbox($print = false) { $update = false; if ($this->config->isSandbox()) { $z = $this->config->wine('WINEPREFIX') . '/dosdevices/z:'; if (file_exists($z)) { unlink($z); } foreach (glob($this->config->wine('DRIVE_C') . '/users/' . $this->system->getUserName() . '/*') as $filePath) { if (is_link($filePath)) { $update = true; unlink($filePath); if (!mkdir($filePath, 0775, true) && !is_dir($filePath)) { throw new \RuntimeException(sprintf('Directory "%s" was not created', $filePath)); } } } $this->wine->reg(['/d', 'HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Explorer\Desktop\Namespace\{9D20AAE8-0625-44B0-9CA7-71889C2254D9}']); if ($update) { file_put_contents($this->config->wine('WINEPREFIX') . '/.update-timestamp', 'disable'); } } /** * Create symlinks to additional folders */ if (file_exists($this->config->getAdditionalDir()) && file_exists($this->config->getAdditionalDir() . '/path.txt')) { $folders = array_filter(array_map('trim', explode("\n", file_get_contents($this->config->getAdditionalDir() . '/path.txt')))); if ($folders) { $adds = glob($this->config->getAdditionalDir() . '/dir_*/'); $isCyrillic = $this->system->isCyrillic(); $folderCount = count($folders); if (count($adds) >= $folderCount) { foreach ($adds as $i => $path) { if ($i >= $folderCount) { break; } $add = str_replace('--REPLACE_WITH_USERNAME--', $this->system->getUserName(), trim($folders[$i], " \t\n\r\0\x0B/")); if (!$isCyrillic) { $add = str_replace('Мои документы', 'My Documents', $add); } $gameInfoAddDir = Text::quoteArgs($this->fs->relativePath($path)); $dirAdd = Text::quoteArgs($this->config->wine('DRIVE_C') . "/{$add}"); $this->command->run("mkdir -p {$dirAdd} && rm -r {$dirAdd} && ln -sfr {$gameInfoAddDir} {$dirAdd}"); if ($print) { $this->log('Create symlink ' . $gameInfoAddDir . ' > ' . Text::quoteArgs($this->fs->relativePath($this->config->wine('DRIVE_C') . "/{$add}"))); } } } } } return $update; } public function createGameDirectory() { /** * Create symlink to game directory */ if (!file_exists($this->config->getPrefixGameFolder()) && file_exists($this->config->wine('WINEPREFIX'))) { $data = $this->fs->relativePath($this->config->getDataDir()); $game = $this->config->getPrefixGameFolder(); $this->command->run("mkdir -p \"{$game}\" && rm -r \"{$game}\" && ln -sfr \"{$data}\" \"{$game}\""); $gameFolder = trim(str_replace($this->config->wine('DRIVE_C'), '', $this->config->getPrefixGameFolder()), " \t\n\r\0\x0B/"); $this->log("Create game folder \"{$data}\" > " . Text::quoteArgs($this->fs->relativePath($this->config->getPrefixGameFolder())) . '.'); return $gameFolder; } return ''; } public function isCreated() { return $this->created; } /** * @param Wine $wine */ public function setWine($wine) { $this->wine = $wine; } } class GameInfo { private $command; private $config; private $log; private $buffer; private $created = false; /** * GameInfo constructor. * @param Config $config * @param Command $command */ public function __construct(Config $config, Command $command) { if (posix_geteuid() === 0) { (new Logs)->log('Do not run this script as root!'); exit(0); } $this->command = $command; $this->config = $config; } public function log($text) { $logPath = $this->config->getLogsDir() . '/game_info.log'; if (null === $this->log) { $this->log = app('start')->getLog(); } if (null === $this->buffer) { $this->buffer = app('start')->getBuffer(); $this->buffer->clear(); if (file_exists($logPath)) { @unlink($logPath); } } $this->log->insertLogFile($text, $logPath); $this->buffer->add($text); } public function create() { if ($this->isEmptyGameFolder()) { app('gui'); $this->created = true; app()->showGameInfo(); $folders = [ $this->config->getLogsDir(), $this->config->getCacheDir(), $this->config->getGameInfoDir(), $this->config->getAdditionalDir(), $this->config->getDataDir(), $this->config->getDllsDir(), $this->config->getDlls64Dir(), $this->config->getHooksDir(), $this->config->getHooksGpuDir(), $this->config->getRegistryDir(), $this->config->getPatchApplyDir(), ]; foreach ($folders as $path) { if (!file_exists($path) && !mkdir($path, 0775, true) && !is_dir($path)) { throw new \RuntimeException(sprintf('Directory "%s" was not created', $path)); } $this->log("Create folder \"{$path}\""); } $readme = 'readme.txt'; /** * game_info/readme.txt */ file_put_contents( $this->config->getGameInfoDir() . "/{$readme}", "Эта директория необходима для работы скрипта. Описание директорий/файлов: game_info.ini - информация об игре (обязательный файл) data - каталог с игрой (обязательная директория) dlls - дополнительные dll файлы (необязательная директория) dlls64 - дополнительные dll файлы (необязательная директория) additional - специфичные для игры настройки (необязательная директория) hooks - скрипты которые выполняются в зависимости от каких либо событий (необязательная директория) regs - файлы реестра windows (необязательная директория)" ); $this->log('Create file "' . $this->config->getGameInfoDir() . "/{$readme}" . '"'); /** * game_info/game_info.ini */ file_put_contents($this->config->getConfigFile(), $this->config->getDefaultConfig()); $this->log('Create file "' . $this->config->getConfigFile() . '"'); /** * game_info/data/readme.txt */ file_put_contents( $this->config->getDataDir() . "/{$readme}", "Здесь должна находиться игра." ); $this->log('Create file "' . $this->config->getDataDir() . "/{$readme}" . '"'); /** * game_info/dlls/readme.txt */ file_put_contents( $this->config->getDllsDir() . "/{$readme}", "В эту директорию нужно класть необходимые игре DLL файлы. Если таких нет директорию можно удалить." ); $this->log('Create file "' . $this->config->getDllsDir() . "/{$readme}" . '"'); /** * game_info/dlls64/readme.txt */ file_put_contents( $this->config->getDlls64Dir() . "/{$readme}", "В эту директорию нужно класть необходимые игре DLL файлы. Если таких нет директорию можно удалить." ); $this->log('Create file "' . $this->config->getDlls64Dir() . "/{$readme}" . '"'); /** * game_info/regs/readme.txt */ file_put_contents( $this->config->getRegistryDir() . "/{$readme}", "Здесь должны находиться .reg файлы." ); $this->log('Create file "' . $this->config->getRegistryDir() . "/{$readme}" . '"'); /** * game_info/additional/readme.txt */ file_put_contents( $this->config->getAdditionalDir() . "/{$readme}", "Специфичные для игры настройки. Класть в директории dir_1, dir_2, dir_3 и т.д. Путь для копирования (относительно drive_c) нужно указывать в файле path.txt. Первая строчка для dir_1, вторая - для dir_2 и т.д. Всю директорию additional можно удалить, если к игре не нужно заранее применять настройки. --REPLACE_WITH_USERNAME-- в файле path.txt заменяется на имя пользователя автоматически." ); $this->log('Create file "' . $this->config->getAdditionalDir() . "/{$readme}" . '"'); /** * game_info/additional/path.txt */ file_put_contents( $this->config->getAdditionalDir() . '/path.txt', "users/--REPLACE_WITH_USERNAME--/Мои документы users/--REPLACE_WITH_USERNAME--/Documents" ); $this->log('Create file "' . $this->config->getAdditionalDir() . '/path.txt' . '"'); if (!mkdir($this->config->getAdditionalDir() . '/dir_1', 0775, true) && !is_dir($path)) { throw new \RuntimeException(sprintf('Directory "%s" was not created', $path)); } if (!mkdir($this->config->getAdditionalDir() . '/dir_2', 0775, true) && !is_dir($path)) { throw new \RuntimeException(sprintf('Directory "%s" was not created', $path)); } $this->log('Create folder "' . $this->config->getAdditionalDir() . '/dir_1' . '"'); $this->log('Create folder "' . $this->config->getAdditionalDir() . '/dir_2' . '"'); /** * game_info/additional/dir_1/readme.txt */ file_put_contents( $this->config->getAdditionalDir() . "/dir_1/{$readme}", "Здесь должно находиться содержимое директории dir_1." ); $this->log('Create file "' . $this->config->getAdditionalDir() . "/dir_1/{$readme}" . '"'); app()->getCurrentScene()->setProgress(5); /** * README.md */ if ((new Update($this->config, $this->command))->updateReadme(true)) { $this->log('Create file "' . $this->config->getRootDir() . '/README.md' . '"'); } /** * game_info/hooks/after.sh */ file_put_contents( $this->config->getHooksDir() . '/after.sh', '#' ."!/bin/sh\necho \"After!\"" ); $this->log('Create file "' . $this->config->getHooksDir() . '/after.sh' . '"'); /** * game_info/hooks/before.sh */ file_put_contents( $this->config->getHooksDir() . '/before.sh', '#' ."!/bin/sh\necho \"Before!\"" ); $this->log('Create file "' . $this->config->getHooksDir() . '/before.sh' . '"'); /** * game_info/hooks/create.sh */ file_put_contents( $this->config->getHooksDir() . '/create.sh', '#' ."!/bin/sh\necho \"Create prefix!\"\ncd ../../\n./start unlock\n./start winetricks wmp9" ); $this->log('Create file "' . $this->config->getHooksDir() . '/create.sh' . '"'); if (!file_exists($this->config->getHooksGpuDir())) { if (!mkdir($this->config->getHooksGpuDir(), 0775, true) && !is_dir($this->config->getHooksGpuDir())) { throw new \RuntimeException(sprintf('Directory "%s" was not created', $this->config->getHooksGpuDir())); } } $this->log('Create folder "' . $this->config->getHooksGpuDir() . '"'); /** * game_info/hooks/gpu/amd.sh */ file_put_contents( $this->config->getHooksGpuDir() . '/amd.sh', '#' ."!/bin/sh\necho \"AMD GPU hook!\"" ); $this->log('Create file "' . $this->config->getHooksGpuDir() . '/amd.sh' . '"'); /** * game_info/hooks/gpu/nvidia.sh */ file_put_contents( $this->config->getHooksGpuDir() . '/nvidia.sh', '#' ."!/bin/sh\necho \"NVIDIA GPU hook!\"" ); $this->log('Create file "' . $this->config->getHooksGpuDir() . '/nvidia.sh' . '"'); /** * game_info/hooks/gpu/intel.sh */ file_put_contents( $this->config->getHooksGpuDir() . '/intel.sh', '#' ."!/bin/sh\necho \"Intel GPU hook!\"" ); $this->log('Create file "' . $this->config->getHooksGpuDir() . '/intel.sh' . '"'); app()->getCurrentScene()->setProgress(10); $wineDownloader = new WineDownloader($this->config, $this->command, app('start')->getFileSystem(), app('start')->getPack()); if ($wineDownloader->isWineEmpty()) { $wineDownloader->wizard(); } } } private function isEmptyGameFolder() { if (!file_exists($this->config->getGameInfoDir())) { return true; } $skip = [$this->config->getLogsDir(), $this->config->getCacheDir(), $this->config->getConfigFile()]; foreach (glob($this->config->getGameInfoDir() . '/*') as $path) { if (!in_array($path, $skip, true)) { return false; } } return true; } public function isCreated() { return $this->created; } } class CheckDependencies { private $command; private $config; private $log; private $buffer; private $system; /** * Event constructor. * @param Config $config * @param Command $command * @param System $system */ public function __construct(Config $config, Command $command, System $system) { $this->command = $command; $this->config = $config; $this->system = $system; } public function log($text) { $logPath = $this->config->getLogsDir() . '/dependencies.log'; if (null === $this->log) { $this->log = app('start')->getLog(); } if (null === $this->buffer) { $this->buffer = app('start')->getBuffer(); $this->buffer->clear(); if (file_exists($logPath)) { @unlink($logPath); } } $this->log->insertLogFile($text, $logPath); $this->buffer->add($text); } private function formattedItem($item, $value) { $length = 25; $lengthItem = mb_strlen($item); $lenDot = $length - $lengthItem; return '- ' . $item . ' ' . str_repeat('.', $lenDot > 0 ? $lenDot : 0) . ' ' . $value; } public function check() { app()->showCheckDependencies(); $system = app('start')->getSystem(); $update = app('start')->getUpdate(); $driver = app('start')->getDriver()->getVersion(); $items = [ 'Script version: ' . $update->version(), 'RAM: ' . $system->getRAM() . ' Mb', 'CPU: ' . $system->getCPU(), 'GPU: ' . $system->getGPU(), 'Distr: ' . $system->getDistrName(), 'Arch: ' . $system->getArch(), 'Linux: ' . $system->getLinuxVersion(), 'GPU Driver: ' . implode(', ', array_filter($driver)), 'Glibc: ' . $system->getGlibcVersion(), 'X.Org version: ' . $system->getXorgVersion(), 'vm.max_map_count: ' . $system->getVmMaxMapCount(), 'ulimit soft: ' . $system->getUlimitSoft(), 'ulimit hard: ' . $system->getUlimitHard(), ]; $this->log('System info.'); $this->log(''); foreach ($items as $item) { $this->log($item); } $this->log(''); $this->log(''); $this->log('Check dependencies.'); $this->log(''); $isOk = true; $install = []; $apps = [ 'wine' => false, 'zenity' => false, 'xrandr' => false, 'pulseaudio' => false, 'glxinfo' => false, 'grep' => false, 'tar' => false, 'wget' => false, 'ldconfig' => false, 'mksquashfs' => false, 'ldd' => false, 'ps' => false, 'lspci' => false, 'fusermount' => false, 'mount' => false, 'tee' => false, 'sed' => false, 'xlsfonts' => false, 'id' => false, 'cabextract' => false, 'p7zip' => false, 'unrar' => false, 'unzip' => false, 'zip' => false, 'binutils' => false, 'ffmpeg' => false, 'xz' => false, 'diff' => false, 'patch' => false, 'hostname' => false, 'locale' => false, 'modinfo' => false, 'lsmod' => false, 'winbindd' => false, 'fc-list' => false, ]; $appsPackage = [ 'wine' => 'wine', 'zenity' => 'zenity', 'xrandr' => 'x11-xserver-utils', 'pulseaudio' => 'pulseaudio', 'glxinfo' => 'mesa-utils', 'grep' => 'grep', 'tar' => 'tar', 'wget' => 'wget', 'ldconfig' => 'libc-bin', 'mksquashfs' => 'squashfs-tools', 'ldd' => 'libc-bin', 'ps' => 'procps', 'lspci' => 'pciutils', 'fusermount' => 'fuse', 'mount' => 'mount', 'tee' => 'coreutils', 'sed' => 'sed', 'xlsfonts' => 'x11-utils', 'id' => 'coreutils', 'cabextract' => 'cabextract', 'p7zip' => 'p7zip', 'unrar' => 'unrar', 'unzip' => 'unzip', 'zip' => 'zip', 'binutils' => 'binutils', 'ffmpeg' => 'ffmpeg', 'xz' => 'xz-utils', 'diff' => 'diffutils', 'patch' => 'patch', 'hostname' => 'hostname', 'locale' => 'libc-bin', 'modinfo' => 'kmod', 'lsmod' => 'kmod', 'winbindd' => 'winbind', 'fc-list' => 'fontconfig', ]; $libs = [ 'libvulkan1' => [ 'name' => 'libvulkan1', 'status' => true, 'find' => 'libvulkan.so.1', ], 'libfuse2' => [ 'name' => 'libfuse2', 'status' => true, 'find' => 'libfuse.so.2', ], 'libopenal1' => [ 'name' => 'libopenal', 'status' => true, 'find' => 'libopenal.so.1', ], 'libxinerama1' => [ 'name' => 'libxinerama1', 'status' => true, 'find' => 'libXinerama.so.1', ], 'libsdl2-2.0-0' => [ 'name' => 'libsdl2-2.0-0', 'status' => true, 'find' => 'libSDL2-2.0.so.0', ], 'libudev1' => [ 'name' => 'libudev1', 'status' => true, 'find' => 'libudev.so.1', ], 'libasound2' => [ 'name' => 'libasound2', 'status' => true, 'find' => 'libasound.so.2', ], 'libsm6' => [ 'name' => 'libsm6', 'status' => true, 'find' => 'libSM.so.6', ], 'libgl1' => [ 'name' => 'libgl1', 'status' => true, 'find' => 'libGL.so.1', ], 'libgif7' => [ 'name' => 'libgif7', 'status' => true, 'find' => 'libgif.so.7', ], 'libncurses5' => [ 'name' => 'libncurses5', 'status' => true, 'find' => 'libncurses.so.5', ], 'libncursesw5' => [ 'name' => 'libncursesw5', 'status' => true, 'find' => 'libncursesw.so.5', ], 'libncurses6' => [ 'name' => 'libncurses6', 'status' => true, 'find' => 'libncurses.so.6', ], 'libncursesw6' => [ 'name' => 'libncursesw6', 'status' => true, 'find' => 'libncursesw.so.6', ], 'libfreetype6' => [ 'name' => 'libfreetype', 'status' => true, 'find' => 'libfreetype.so.6', ], 'libfontconfig1' => [ 'name' => 'libfontconfig1', 'status' => true, 'find' => 'libfontconfig.so.1', ], 'libmpg123-0' => [ 'name' => 'libmpg123-0', 'status' => true, 'find' => 'libmpg123.so.0', ], 'libxcomposite1' => [ 'name' => 'libxcomposite1', 'status' => true, 'find' => 'libXcomposite.so.1', ], 'libgnutls30' => [ 'name' => 'libgnutls30', 'status' => true, 'find' => 'libgnutls.so.30', ], 'libgnutls-deb0-28' => [ 'name' => 'libgnutls-deb0-28', 'status' => true, 'find' => 'libgnutls-deb0.so.28', ], 'libjpeg62' => [ 'name' => 'libjpeg62', 'status' => true, 'find' => 'libjpeg.so.62', ], 'libjpeg8' => [ 'name' => 'libjpeg8', 'status' => true, 'find' => 'libjpeg.so.8', ], 'libxslt1.1' => [ 'name' => 'libxslt1.1', 'status' => true, 'find' => 'libxslt.so.1', ], 'libxrandr2' => [ 'name' => 'libxrandr2', 'status' => true, 'find' => 'libXrandr.so.2', ], 'libpng16-16' => [ 'name' => 'libpng16-16', 'status' => true, 'find' => 'libpng16.so.16', ], 'libpng12-0' => [ 'name' => 'libpng12-0', 'status' => true, 'find' => 'libpng12.so', ], 'libtiff5' => [ 'name' => 'libtiff5', 'status' => true, 'find' => 'libtiff.so.5', ], 'libxcb1' => [ 'name' => 'libxcb1', 'status' => true, 'find' => 'libxcb.so.1', ], 'libtheora0' => [ 'name' => 'libtheora0', 'status' => true, 'find' => 'libtheora.so.0', ], 'libvorbis0a' => [ 'name' => 'libvorbis0a', 'status' => true, 'find' => 'libvorbis.so.0', ], 'zlib1g' => [ 'name' => 'zlib1g', 'status' => true, 'find' => 'libz.so.1', ], 'samba-libs' => [ 'name' => 'samba-libs', 'status' => true, 'find' => 'libnetapi.so.0', ], 'libsane1' => [ 'name' => 'libsane1', 'status' => true, 'find' => 'libsane.so.1', ], 'libcapi20-3' => [ 'name' => 'libcapi20-3', 'status' => true, 'find' => 'libcapi20.so.3', ], 'libcups2' => [ 'name' => 'libcups2', 'status' => true, 'find' => 'libcups.so.2', ], 'libgsm1' => [ 'name' => 'libgsm1', 'status' => true, 'find' => 'libgsm.so.1', ], 'libodbc1' => [ 'name' => 'libodbc1', 'status' => true, 'find' => 'libodbc.so.2', ], 'libosmesa6' => [ 'name' => 'libosmesa6', 'status' => true, 'find' => 'libOSMesa.so.8', ], 'libpcap0.8' => [ 'name' => 'libpcap0.8', 'status' => true, 'find' => 'libpcap.so.0.8', ], 'libv4l-0' => [ 'name' => 'libv4l-0', 'status' => true, 'find' => 'libv4l1.so.0', ], 'libdbus-1-3' => [ 'name' => 'libdbus-1-3', 'status' => true, 'find' => 'libdbus-1.so.3', ], 'libglib2.0-0' => [ 'name' => 'libglib2.0-0', 'status' => true, 'find' => 'libgobject-2.0.so.0', ], 'libgtk-3-0' => [ 'name' => 'libgtk-3-0', 'status' => true, 'find' => 'libgtk-3.so.0', ], 'libgstreamer1.0-0' => [ 'name' => [ 'gstreamer1.0-plugins-base', 'gstreamer1.0-plugins-good', 'libgstreamer1.0-0' ], 'status' => true, 'find' => 'libgstreamer-1.0.so.0', ], ]; $fonts = [ 'fonts-unfonts-extra' => [ 'name' => 'fonts-unfonts-extra', 'status' => true, 'find' => 'UnJamoBatang.ttf', ], 'fonts-unfonts-core' => [ 'name' => 'fonts-unfonts-core', 'status' => true, 'find' => 'UnBatang.ttf', ], 'fonts-wqy-microhei' => [ 'name' => [ 'fonts-wqy-microhei', 'ttf-wqy-microhei' ], 'status' => true, 'find' => 'wqy-microhei.ttc', ], 'fonts-horai-umefont' => [ 'name' => 'fonts-horai-umefont', 'status' => true, 'find' => 'horai-umefont', ], 'ttf-mscorefonts-installer' => [ 'name' => 'ttf-mscorefonts-installer', 'status' => true, 'find' => 'Georgia.ttf', ], ]; ksort($apps); $percent = 100 / (count($apps) + count($libs)); $progress = 0; foreach ($apps as $app => $_) { if ($app === 'binutils') { $app = 'ld'; } else if ($app === 'p7zip') { $app = '7z'; } $is = trim($this->command->run("command -v {$app}")); if ($app === 'ld') { $app = 'binutils'; } else if ($app === '7z') { $app = 'p7zip'; } if ($is) { $apps[$app] = true; $this->log($this->formattedItem($app, 'ok')); } else { $apps[$app] = false; $this->log($this->formattedItem($app, 'fail')); if (!in_array($appsPackage[$app], $install, true)) { $install[] = $appsPackage[$app]; } } $progress += $percent; app()->getCurrentScene()->setProgress($progress); } if (trim($this->command->run('command -v fc-list'))) { foreach ($fonts as $key => $font) { $findFont = (bool)trim($this->command->run("fc-list | grep '{$font['find']}'")); if (!$findFont) { $fonts[$key]['status'] = false; foreach ((array)$font['name'] as $pkg) { $install[] = $pkg; } } $this->log(''); $this->log(''); $findLibs = implode(', ', (array)$font['name']); $this->log($this->formattedItem("Find font \"{$findLibs}\"", $fonts[$key]['status'] ? 'ok' : 'fail')); } } if ($apps['ldconfig']) { foreach ($libs as $key => $lib) { $result = [ 'x86-64' => null, 'i386' => null, ]; $finds = array_filter(array_map('trim', explode("\n", trim($this->command->run("ldconfig -p | grep '{$lib['find']}'"))))); foreach (glob($this->config->getLibsDir() . "/{$lib['find']}*") as $_lib) { $_name = basename($_lib); $finds[] = "{$_name} (libc6) => {$_lib}"; } foreach (glob($this->config->getLibs64Dir() . "/{$lib['find']}*") as $_lib) { $_name = basename($_lib); $finds[] = "{$_name} (libc6,x86-64) => {$_lib}"; } foreach ($finds as $find) { list($_fullName, $_path) = array_map('trim', explode('=>', $find)); list($_name, $_arch) = array_map(function ($n) {return trim($n, " \t\n\r\0\x0B()");}, explode(' (', $find)); $_arch = stripos($_arch,'x86-64') !== false ? 'x86-64' : 'i386'; if (null === $result[$_arch]) { $result[$_arch] = [ 'name' => $_name, 'path' => $_path, ]; } elseif (strlen($_name) > strlen($result[$_arch]['name']) || (strlen($_name) === strlen($result[$_arch]['name']) && strlen($_path) > strlen($result[$_arch]['path']))) { $result[$_arch] = [ 'name' => $_name, 'path' => $_path, ]; } } if ($this->system->getArch() === 64) { $libs[$key]['status'] = (bool)($result['x86-64'] && $result['i386']); } else { $libs[$key]['status'] = (bool)$result['i386']; } if (false === $libs[$key]['status']) { foreach ((array)$lib['name'] as $findLib) { if (!$result['i386']) { $install[] = "{$findLib}:i386"; } if ($this->system->getArch() === 64) { if (!$result['x86-64']) { $install[] = $findLib; } } } } $this->log(''); $this->log(''); $findLibs = implode(', ', (array)$lib['name']); $this->log($this->formattedItem("Find lib \"{$findLibs}\"", $libs[$key]['status'] ? 'ok' : 'fail')); $this->log(''); if ($this->system->getArch() === 64) { $this->log("(x86-64) \"{$result['x86-64']['path']}\""); } $this->log("(i386) \"{$result['i386']['path']}\""); $progress += $percent; app()->getCurrentScene()->setProgress($progress); } } else { $message = 'Failed to check due to missing ldconfig'; $this->log(''); $this->log("libvulkan, libfuse, libopenal, libXinerama, SDL2, libasound2\n{$message}."); $progress += ($percent * count($libs)); app()->getCurrentScene()->setProgress($progress); } if ($install) { $this->log(''); $this->log(''); $this->log('[Recommended] install (not all required):'); $this->log('sudo dpkg --add-architecture i386 && sudo apt-get update'); $this->log('sudo apt-get install ' . implode(' ', $install)); } if (false === $apps['wine']) { $isOk = false; $this->log(''); $this->log('Require install wine.'); $this->log(''); $this->log("Ubuntu: sudo dpkg --add-architecture i386 wget -nc --no-check-certificate https://dl.winehq.org/wine-builds/Release.key sudo apt-key add Release.key sudo apt-add-repository https://dl.winehq.org/wine-builds/ubuntu/ sudo apt-get update sudo apt-get install binutils cabextract p7zip-full unrar unzip wget wine zenity Debian: dpkg --add-architecture i386 && apt-get update apt-get install wine32 wine binutils unzip cabextract p7zip-full unrar-free wget zenity"); } if (false === $apps['zenity']) { if (count($this->config->findConfigsPath()) > 1) { $isOk = false; } $this->log(''); $this->log('Require install zenity.'); $this->log("sudo apt-get install zenity"); } if (false === $apps['xrandr']) { $isOk = false; $this->log(''); $this->log('Require install xrandr.'); $this->log("sudo apt-get install x11-xserver-utils"); } if ($apps['ldconfig'] && !$libs['libvulkan1']['status'] && ($this->config->isDxvk() || $this->config->isD9vk())) { $isOk = false; $this->log(''); $this->log('Require install libvulkan1.'); $this->log('https://github.com/lutris/lutris/wiki/How-to:-DXVK#installing-vulkan'); } if ($apps['ldconfig'] && !$libs['libfuse2']['status']) { if (file_exists($this->config->getRootDir() . '/wine.squashfs') || file_exists($this->config->getRootDir() . '/game_info/data.squashfs')) { $isOk = false; } $this->log(''); $this->log('Require install libfuse2.'); $this->log('sudo dpkg --add-architecture i386'); $this->log("sudo apt-get install libfuse2 libfuse2:i386"); } if ($this->config->isDxvk() || $this->config->isD9vk()) { $driver = app('start')->getDriver()->getVersion(); list($mesa) = isset($driver['mesa']) ? explode('-', $driver['mesa']) : ''; if ($mesa) { $mesa = version_compare($mesa, '18.3', '<'); } if ('nvidia' === $driver['vendor']) { $text = "\nPlease install NVIDIA driver 415.22 or newer."; if ('nvidia' !== $driver['driver']) { $this->log($text); } elseif ('nvidia' === $driver['driver'] && version_compare($driver['version'], '415.22', '<')) { $this->log($text); } } elseif ('amd' === $driver['vendor']) { $text = 'Please install AMD driver: '; if ('amdgpu-pro' === $driver['driver'] && version_compare($driver['version'], '18.50', '<')) { $this->log("\n{$text} AMDGPU PRO 18.50 or newer."); } elseif ('amdgpu' === $driver['driver'] && $mesa) { $this->log("\n{$text} RADV, Mesa 18.3 or newer (recommended)."); } } elseif ('intel' === $driver['vendor'] && $mesa) { $this->log("\nPlease install Mesa 18.3 or newer."); } } if ($this->config->isEsync() || $this->config->isDxvk() || $this->config->isD9vk()) { $currentUlimit = $this->system->getUlimitSoft(); $recommendedUlimit = 200000; if ($recommendedUlimit > $currentUlimit) { $this->log(''); $this->log("Error. Current ulimit: {$currentUlimit}, Required min ulimit: {$recommendedUlimit}"); $this->log(''); $this->log('Add to "/etc/security/limits.conf" file and reboot system:'); $this->log("* soft nofile {$recommendedUlimit}"); $this->log("* hard nofile {$recommendedUlimit}"); } } if ($this->system->getVmMaxMapCount() < 200000) { $this->log(''); $this->log('Please set vm.max_map_count=262144'); $this->log('Current: ' . $this->system->getVmMaxMapCount()); $this->log('Run as root:'); $this->log('sudo sh -c "echo \'vm.max_map_count=262144\' >> /etc/sysctl.conf"'); } if ($this->config->isGenerationPatchesMode()) { if (!$apps['diff']) { $isOk = false; $this->log(''); $this->log('Install "diff" or disable "generation_patches_mode = 0" in config.'); } if (!$apps['patch']) { $isOk = false; $this->log(''); $this->log('Install "patch" or disable "generation_patches_mode = 0" in config.'); } } if (false === $isOk) { $this->log(''); $this->log("Press any key to exit."); ncurses_getch(); exit(0); } } } class Icon { private $config; private $command; private $system; private $user; private $home; private $folders; private $local; private $title; /** * Icon constructor. * @param Config $config * @param Command $command * @param System $system */ public function __construct(Config $config, Command $command, System $system) { $this->command = $command; $this->config = $config; $this->system = $system; $this->user = $this->system->getUserName(); $this->home = getenv("HOME") ?: "/home/{$this->user}"; $this->folders = [ "{$this->home}/Рабочий стол/Games", "{$this->home}/Рабочий стол/games", "{$this->home}/Рабочий стол/Игры", "{$this->home}/Рабочий стол/игры", "{$this->home}/Рабочий стол", "{$this->home}/Desktop/Игры", "{$this->home}/Desktop/игры", "{$this->home}/Desktop/Games", "{$this->home}/Desktop/games", "{$this->home}/Desktop", ]; $desktop = $this->system->getDesktopPath(); if ($desktop) { $this->folders = array_unique(array_merge( [ "{$desktop}/Games", "{$desktop}/games", "{$desktop}/Игры", "{$desktop}/игры", $desktop, ], $this->folders )); } $this->local = "{$this->home}/.local/share/applications"; $this->title = $this->config->getGameTitle(); if (!file_exists($this->local) && file_exists($this->home)) { if (!mkdir($this->local, 0775, true) && !is_dir($this->local)) { throw new \RuntimeException(sprintf('Directory "%s" was not created', $this->local)); } } } public function create($png, $isAddMenu = true) { $icon = $this->getTemplate($png); $result = []; if ($isAddMenu) { file_put_contents("{$this->local}/{$this->title}.desktop", $icon); $result[] = "{$this->local}/{$this->title}.desktop"; } if ($desktop = $this->findDir()) { $fileName = "{$desktop}/{$this->title}"; if (file_exists($fileName)) { file_put_contents($fileName, $icon); $result[] = $fileName; } else { file_put_contents("{$fileName}.desktop", $icon); $result[] = "{$fileName}.desktop"; } } return $result; } public function remove() { $icons = $this->findExistIcons(); if (!$icons) { return []; } foreach ($icons as $icon) { @unlink($icon); } return $icons; } /** * @return array */ public function findExistIcons() { $result = []; foreach (array_merge([$this->local], $this->folders) as $item) { $v1 = "{$item}/{$this->title}"; $v2 = "{$v1}.desktop"; if (file_exists($v1) && !is_dir($v1)) { $result[] = $v1; } elseif (file_exists($v2) && !is_dir($v2)) { $result[] = $v2; } } return $result; } /** * @return string */ public function findDir() { foreach ($this->folders as $folder) { if (file_exists($folder) && is_dir($folder)) { return $folder; } } return ''; } /** * @return array */ public function findPng() { $rootDir = $this->config->getRootDir(); $icons = []; $icons[] = glob("{$rootDir}/*.png"); $icons[] = glob("{$rootDir}/game_info/*.png"); return array_filter(call_user_func_array('array_merge', $icons)); } /** * @param string $png * @return string */ public function getTemplate($png) { $rootDir = $this->config->getRootDir(); return "[Desktop Entry] Version=1.0 Exec={$rootDir}/start Path={$rootDir} Icon={$png} Name={$this->title} Terminal=false TerminalOptions= Type=Application Categories=Game"; } } class Pack { private $command; private $config; private $fs; /** * Pack constructor. * @param Config $config * @param Command $command * @param FileSystem $fs */ public function __construct(Config $config, Command $command, FileSystem $fs) { $this->config = $config; $this->command = $command; $this->fs = $fs; } public function pack($folder) { $mount = $this->getMount($folder); if (false === $mount || $mount->isMounted() || !file_exists($folder)) { return false; } if ($this->isMksquashfs()) { if (file_exists("{$folder}.squashfs")) { @unlink("{$folder}.squashfs"); } if ($folder === $this->config->getWineDir() && file_exists("{$folder}/bin")) { $this->command->run('chmod +x -R ' . Text::quoteArgs("{$folder}/bin")); } $folderName = basename($folder); $cmd = "mksquashfs \"{$folder}\" \"{$folder}.squashfs\" -b 1048576 -comp gzip -Xcompression-level 9"; if ('wine' === $folderName) { $cmd = "mksquashfs \"{$folder}\" \"{$folder}.squashfs\" -b 1048576 -comp xz -Xdict-size 100%"; } $this->command->run($cmd); return true; } return false; } public function unpack($folder) { $mount = $this->getMount($folder); if (false === $mount || !$mount->isMounted() || !file_exists($folder)) { return false; } if (file_exists("{$folder}_tmp")) { $this->fs->rm("{$folder}_tmp"); } $this->fs->cp($folder, "{$folder}_tmp"); $mount->umount(); if ($mount->isMounted() || file_exists($folder)) { register_shutdown_function(function () use ($folder) { if (!file_exists($folder) && file_exists(file_exists("{$folder}_tmp"))) { $this->fs->mv("{$folder}_tmp", $folder); } }); } else { $this->fs->mv("{$folder}_tmp", $folder); } return true; } public function isMksquashfs() { static $result; if (null === $result) { $result = (bool)trim($this->command->run('command -v mksquashfs')); } return $result; } public function getMount($folder) { $mountes = app('start')->getMountes(); $findMount = null; foreach ($mountes as $mount) { /** @var Mount $mount */ if ($mount->getFolder() === $folder) { $findMount = $mount; break; } } if (!$findMount) { return false; } return $findMount; } public function getMountes() { $result = []; $mountes = app('start')->getMountes(); foreach ($mountes as $mount) { /** @var Mount $mount */ if ($mount->isMounted()) { $result[] = $mount->getFolder() . '.' .$mount->getExtension(); } } return $result; } } class Symlink { private $command; private $config; private $fs; private $extensions; /** * Symlink constructor. * @param Config $config * @param Command $command * @param FileSystem $fs */ public function __construct(Config $config, Command $command, FileSystem $fs) { $this->config = $config; $this->command = $command; $this->fs = $fs; $this->extensions = ['cfg', 'conf', 'ini', 'inf', 'log', 'sav', 'save', 'config', 'con', 'profile', 'ltx']; } /** * @return array */ public function getExtensions() { return $this->extensions; } public function replace($path) { $path = trim($path, " \t\n\r\0\x0B/\\"); if (!$path || !file_exists($this->config->getDataDir() . "/{$path}")) { return false; } $symlinks = $this->config->getSymlinksDir(); $_symlinks = $this->config->getDataSymlinksDir(); $data = $this->config->getDataDir(); $this->fs->mkdirs([$symlinks, $_symlinks]); $this->fs->mv("{$data}/{$path}","{$_symlinks}/{$path}"); $this->cloneDir($this->fs->relativePath("{$_symlinks}/{$path}", $data)); $this->fs->link("{$symlinks}/{$path}", "{$data}/{$path}"); return true; } public function cloneDir($path) { $path = trim($path, " \t\n\r\0\x0B/\\"); if (!$path || !file_exists($this->config->getDataDir() . "/{$path}")) { return false; } $symlinks = $this->config->getSymlinksDir(); $_symlinks = $this->config->getDataSymlinksDir(); $data = $this->config->getDataDir(); $in = "{$data}/{$path}"; $out = "{$symlinks}/" . $this->fs->relativePath($in, $_symlinks); $this->fs->mkdirs([$out]); foreach (glob("{$in}/*") as $_path) { if (is_dir($_path)) { $this->cloneDir($this->fs->relativePath($_path, $data)); } else { $basename = pathinfo($_path); $_out = "{$symlinks}/" . $this->fs->relativePath($_path, $_symlinks); if (in_array(strtolower($basename['extension']), $this->getExtensions(), true)) { $this->fs->cp($_path, $_out); } else { $this->fs->link($_path, $_out); } } } return true; } public function getDirs() { $result = []; foreach (glob($this->config->getDataDir() . '/*') as $path) { $name = basename($path); if ('_symlinks' !== $name && is_dir($path) && !is_link($path)) { $result[] = $name; } } return $result; } } class Build { private $command; private $config; private $system; private $fs; /** * Build constructor. * @param Config $config * @param Command $command * @param System $system * @param FileSystem $fs */ public function __construct(Config $config, Command $command, System $system, FileSystem $fs) { $this->config = $config; $this->command = $command; $this->system = $system; $this->fs = $fs; } public function checkSupportReset() { $root = $this->config->getRootDir(); return !(!file_exists($this->config->getDataFile()) || !file_exists("{$root}/static.tar.gz") || !file_exists("{$root}/extract.sh")); } public function reset() { if (!$this->checkSupportReset()) { return false; } foreach (app('start')->getMountes() as $mount) { /** @var Mount $mount */ $mount->umount(); } $root = $this->config->getRootDir(); $gameInfo = $this->config->getGameInfoDir(); foreach (glob("{$gameInfo}/*") as $item) { $name = basename($item); if ($name !== 'data.squashfs') { $this->command->run("\\rm -rf \"{$item}\""); } } foreach (glob("{$root}/*") as $item) { $name = basename($item); if (!in_array($name, ['game_info', 'extract.sh', 'static.tar.gz'], true)) { $this->command->run("\\rm -rf \"{$item}\""); } } if (file_exists("{$root}/libs")) { $this->command->run("\\rm -rf \"{$root}/libs\""); } return true; } public function build($isPrefix = false) { $root = $this->config->getRootDir(); $gameDir = basename($root); $userName = $this->system->getUserName(); $gameInfo = $this->config->getGameInfoDir(); $gameData = $this->config->getDataDir(); if (file_exists("{$root}/build")) { $this->command->run("\\rm -rf \"{$root}/build\""); } if (!mkdir("{$root}/build/{$gameDir}/static/game_info", 0775, true) && !is_dir("{$root}/build/{$gameDir}/static/game_info")) { throw new \RuntimeException(sprintf('Directory "%s" was not created', "{$root}/build/{$gameDir}/static/game_info")); } if (!mkdir("{$root}/build/{$gameDir}/game_info", 0775, true) && !is_dir("{$root}/build/{$gameDir}/game_info")) { throw new \RuntimeException(sprintf('Directory "%s" was not created', "{$root}/build/{$gameDir}/game_info")); } foreach (glob("{$root}/*.png") as $path) { $this->command->run("\\cp -a --link \"{$path}\" \"{$root}/build/{$gameDir}/static/\""); } if (file_exists("{$root}/wine.squashfs")) { $this->command->run("\\cp -a --link \"{$root}/wine.squashfs\" \"{$root}/build/{$gameDir}/static/\""); } elseif (file_exists("{$root}/wine")) { $this->command->run("\\cp -ra --link \"{$root}/wine\" \"{$root}/build/{$gameDir}/static/wine\""); } if (file_exists("{$root}/libs")) { $this->command->run("\\cp -ra --link \"{$root}/libs\" \"{$root}/build/{$gameDir}/static/libs\""); } if (file_exists("{$root}/README.md")) { $this->command->run("\\cp -a --link \"{$root}/README.md\" \"{$root}/build/{$gameDir}/static/\""); } if (file_exists("{$root}/php")) { $this->command->run("\\cp -a --link \"{$root}/php\" \"{$root}/build/{$gameDir}/static/\""); } if (file_exists("{$root}/squashfuse")) { $this->command->run("\\cp -a --link \"{$root}/squashfuse\" \"{$root}/build/{$gameDir}/static/\""); } if (file_exists("{$root}/fuse-zip")) { $this->command->run("\\cp -a --link \"{$root}/fuse-zip\" \"{$root}/build/{$gameDir}/static/\""); } if (file_exists("{$root}/start")) { $this->command->run("\\cp -a --link \"{$root}/start\" \"{$root}/build/{$gameDir}/static/\""); } if ($isPrefix && file_exists("{$root}/prefix")) { $this->command->run("\\cp -ra --link \"{$root}/prefix\" \"{$root}/build/{$gameDir}/static/prefix\""); if ($userName) { $userFolder = "{$root}/build/{$gameDir}/static/prefix/drive_c/users/{$userName}"; if (file_exists($userFolder)) { $this->command->run("\\rm -rf \"{$userFolder}\""); } } } $skip = [ 'data', 'dataold', 'data_old', 'data.old', 'databak', 'data_bak', 'data.bak', 'test', 'testold', 'test_old', 'test.old', 'testbak', 'test_bak', 'test.bak', 'test1', 'test2', 'test3', 'test4', 'data1', 'data2', 'data3', 'data4', 'data.squashfs', 'data1.squashfs', 'data2.squashfs', 'data.zip', 'data1.zip', 'data2.zip', 'logs', 'cache', 'tmp', 'tmp1', 'tmp2', 'temp', 'temp1', 'temp2', 'prev', 'prev1', 'prev2', 'old', 'old1', 'old2', 'bak', 'bak1', 'bak2', 'backup', 'backup1', 'backup2', 'original', 'original1', 'original2', ]; foreach (glob("{$gameInfo}/*") as $path) { $file = basename($path); if (in_array($file, $skip, true)) { continue; } if (is_dir($path)) { $this->command->run("\\cp -ra --link \"{$path}\" \"{$root}/build/{$gameDir}/static/game_info/{$file}\""); } else { $this->command->run("\\cp -a --link \"{$path}\" \"{$root}/build/{$gameDir}/static/game_info/\""); } } $cache = "{$root}/build/{$gameDir}/static/game_info/cache"; if (!file_exists($cache)) { $this->fs->mkdirs([$cache]); } foreach (glob("{$gameInfo}/cache/*.dxvk-cache") as $file) { $fileName = basename($file); $this->fs->cp($file, "{$cache}/{$fileName}"); } if (file_exists("{$gameInfo}/data.squashfs")) { $this->command->run("\\cp -a --link \"{$gameInfo}/data.squashfs\" \"{$root}/build/{$gameDir}/game_info/\""); } elseif (file_exists("{$gameInfo}/data.zip")) { $this->command->run("\\cp -a --link \"{$gameInfo}/data.zip\" \"{$root}/build/{$gameDir}/game_info/\""); } elseif (file_exists($gameData)) { $this->command->run("\\cp -rav --link \"{$gameData}\" \"{$root}/build/{$gameDir}/game_info/data\""); } $this->command->run("\\tar -cvzf \"{$root}/build/{$gameDir}/static.tar.gz\" -C \"{$root}/build/{$gameDir}/static\" ."); $this->command->run("\\rm -rf \"{$root}/build/{$gameDir}/static/\""); /** * build/extract.sh */ file_put_contents("{$root}/build/{$gameDir}/extract.sh", "#!/bin/sh cd -P -- \"$(dirname -- \"$0\")\" tar -xvf ./static.tar.gz chmod +x ./start" ); $this->command->run("chmod +x \"{$root}/build/{$gameDir}/extract.sh\""); } } class Task { private $command; private $config; private $logfile; private $cmd; private $monitor; private $event; private $fs; private $fps; private $fpsCmd; private $system; private $update; /** * Task constructor. * @param Config $config * @param Wine $wine */ public function __construct(Config $config) { $this->config = clone $config; $this->command = new Command($this->config); $this->wine = new Wine($this->config, $this->command); $this->monitor = new Monitor($this->config, $this->command); $this->event = app('start')->getEvent(); $this->fs = new FileSystem($this->config, $this->command); $this->system = new System($this->config, $this->command); $this->update = new Update($this->config, $this->command); } public function logName($prefix) { $this->logfile = "{$prefix}.log"; return $this; } public function debug() { $this->config->set('wine', 'WINEDEBUG', ''); return $this; } public function fps() { $this->fps = true; $root = $this->config->getRootDir(); if (!($this->config->isDxvk() || $this->config->isD9vk()) || $this->config->getBool('script', 'dxvk_d3d10')) { $mesa = $this->system->getMesaVersion(); if ($mesa) { $this->config->set('export', 'GALLIUM_HUD', 'simple,fps'); } else { $this->update->downloadOsd(); $font = $this->system->getFont(); $font = $font ? "--font=\"{$font}\"" : ''; $add = [ ' 2>&1', 'tee /dev/stderr', 'sed -u -n -e \'/trace/ s/.*approx //p\'', "\"{$root}/osd\" --lines=1 {$font} --color=yellow", ]; $this->fpsCmd = implode(' | ', $add); $this->config->set('export', 'WINEDEBUG', '-all,fps'); } } elseif (!$this->config->get('export', 'DXVK_HUD')) { $this->config->set('export', 'DXVK_HUD', 'fps,devinfo,memory'); } return $this; } public function cmd($cmd) { $this->cmd = $cmd; return $this; } private function desktop() { if ($this->config->getBool('window', 'enable')) { $resolution = $this->config->get('window', 'resolution'); if ($resolution === 'auto') { if ($monitor = $this->monitor->getDefaultMonitor()) { $resolution = $monitor['resolution']; } } $title = str_replace([' ', ','], ['_', ''], $this->config->getGameTitle()); return "explorer \"/desktop={$title},{$resolution}\""; } return ''; } public function game() { $driveC = $this->config->wine('DRIVE_C'); $gamePath = $this->config->getGamePath(); $additional = $this->config->getGameAdditionalPath(); $fullPath = implode('/', array_filter([$driveC, $gamePath, $additional])); $wine = $this->config->wine('WINE'); $desktop = $this->desktop(); $fileName = $this->config->getGameExe(); $arguments = str_replace("'", '"', $this->config->getGameArgs()); $this->cmd = "cd \"{$fullPath}\" && \"{$wine}\" {$desktop} \"{$fileName}\" {$arguments}"; return $this; } /** * @param callable|null $callback */ public function run($callback = null) { app('start')->getPlugins()->setConfig($this->config); $this->beforeRun(); if ($callback) { $callback(); } else { $logging = null; if ($this->logfile) { $logging = $this->config->getLogsDir() . "/{$this->logfile}"; } if ($this->cmd) { $this->command->run("{$this->cmd} {$this->fpsCmd}", $logging); } } $this->afterExit(); } private function beforeRun() { if ($this->system->checkPhp() && app('start')->getConsole()->isTerminal()) { app('gui'); $scene = app()->getCurrentScene(); $popup = $scene->addWidget(new PopupInfoWidget($scene->getWindow())); $popup ->setTitle('Running') ->setText([ 'Application is running...', $this->fpsCmd ? '' : 'See log ./' . $this->fs->relativePath($this->config->getLogsDir() . "/{$this->logfile}"), ]) ->setActive(true) ->show(); } $this->monitor->resolutionsSave(); $this->event->beforeRun(); } private function afterExit() { $this->event->afterExit(); $this->monitor->resolutionsRestore(); if ($this->system->checkPhp()) { app()->close(); } else { exit(0); } } } class Console { private $config; private $command; private $system; private $arguments; private $log; private $gui = false; /** * Console constructor. * @param Config $config * @param Command $command * @param System $system * @param Logs $log */ public function __construct(Config $config, Command $command, System $system, Logs $log) { $this->config = $config; $this->command = $command; $this->system = $system; $this->log = $log; global $argv; $this->arguments = array_splice($argv, 1); } public function isTerminal() { return (bool)getenv('TERM'); } public function isGui() { return trim(reset($this->arguments)) === 'gui' || $this->gui; } public function isHelp() { return in_array(trim(reset($this->arguments)), ['help', '-help', '--help', '-h']); } public function isWinetricks() { return trim(reset($this->arguments)) === 'winetricks'; } public function isKill() { return trim(reset($this->arguments)) === 'kill'; } public function isWine() { return trim(reset($this->arguments)) === 'wine' || $this->isWine64(); } public function isWine64() { return 'wine64' === trim(reset($this->arguments)); } public function wine($args, $wine = 'WINE') { $config = clone $this->config; $config->set('wine', 'WINEDEBUG', ''); $cmd = Text::quoteArgs($args); (new Command($config))->run(Text::quoteArgs($config->wine($wine)) . " {$cmd}", null, true); } public function lock() { static $lock; if (null === $lock) { $mount = $this->system->lock(); $force = ($this->isKill() || $this->isWinetricks() || $this->isHelp() || $this->isWine()); $lock = $mount; if (!$mount && !$force) { $this->log->logStart(); $this->log->log('Application is already running.'); $this->log->logStop(); exit(0); } } return $lock; } public function init() { if (!$this->arguments) { (new Monitor($this->config, $this->command))->resolutionsRestore(); /** @var Config $config */ $config = app('start')->getConfig(); $configs = $config->findConfigsPath(); $starts = []; foreach ($configs as $i => $path) { if ($config->getConfigFile() === $path && count($configs) === 1) { $starts[] = ['name' => $config->getGameTitle(), 'config' => $config]; } else { $cfg = new Config($path); $starts[] = ['name' => $cfg->getGameTitle(), 'config' => $cfg]; } } $title = 'Run'; if ($this->system->isCyrillic()) { $title = 'Запустить'; } $item = null; if (count($starts) > 1) { $item = (new Dialog()) ->columns(['name' => $title]) ->items($starts) ->size(400, 300) ->get(); } elseif ($starts) { $item = reset($starts); } if (!$item) { exit(0); } /** @var Config $config */ $config = $item['config']; $task = new Task($config); $task ->logName($config->getGameTitle()) ->game() ->run(); } if ($this->isKill()) { (new Wine($this->config, $this->command))->down(); exit(0); } if ($this->isWine()) { $this->wine(array_splice($this->arguments, 1), $this->isWine64() ? 'WINE64' : 'WINE'); exit(0); } if ($this->isWinetricks()) { $args = array_splice($this->arguments, 1); $this->log->log('Processing...'); $this->log->log('See logs: ' . './game_info/logs/winetricks-' . implode('-', $args) . '.log'); (new Wine($this->config, $this->command))->winetricks($args); exit(0); } if (!$this->isGui() && ($this->isHelp() || $this->arguments)) { $help = [ 'Help:', './start - Run game.', './start gui - Graphical user interface.', './start kill - Kill this instance Wine.', './start winetricks - Winetricks install d3dx9 (./start winetricks d3dx9).', './start wine - Get Wine Instance.', './start help', ]; $this->log->log(implode("\n", $help)); exit(0); } (new Monitor($this->config, $this->command))->resolutionsRestore(); } } class Dialog { private $type; private $title; private $width; private $height; private $items; private $text; private $columns; /** * Dialog constructor. */ public function __construct() { $this->type = '--info'; $this->width = '--width=400'; $this->height = ''; $this->title = '--title=""'; $this->items = []; } public function typeInfo() { $this->type = '--info'; return $this; } public function typeWarning() { $this->type = '--warning'; return $this; } public function typeList() { $this->type = '--list'; return $this; } public function typeQuestion() { $this->type = '--question'; return $this; } public function title($title) { $this->title = "--title=\"{$title}\"";; return $this; } public function text($text) { $this->text = "--text=\"{$text}\""; return $this; } public function size($width = null, $height = null) { $this->width = $width ? "--width={$width}" : ''; $this->height = $height ? "--height={$height}" : ''; return $this; } public function columns($columns) { $this->columns = $columns; return $this; } private function getColumns() { if ($this->columns) { $result = []; foreach ($this->columns as $id => $name) { $result[] = "--column=\"{$name}\""; } return '--hide-column=1 --column="" ' . implode(' ', $result); } return ''; } public function items($items) { $this->typeList(); $this->items = $items; return $this; } private function getItems() { if ($this->items) { $result = []; foreach ($this->items as $i => $item) { $result[] = "\"{$i}\""; foreach ($this->columns as $id => $column) { $result[] = "\"{$item[$id]}\""; } } return implode(' ', $result); } return ''; } public function get() { $zenity = 'LD_LIBRARY_PATH="" zenity'; $columns = $this->getColumns(); $items = $this->getItems(); $cmd = "{$zenity} {$this->type} {$this->title} {$this->text} {$this->width} {$this->height} {$columns} {$items}"; $returnVar = null; $output = null; $result = trim(exec("{$cmd} 2> /dev/null", $returnVar, $output)); if ('--question' === $this->type) { return (bool)$returnVar; } if ($result === '') { return null; } if ($result === '') { return null; } if ('--list' === $this->type && $this->items) { return $this->items[(int)$result]; } return $result; } } class YandexDisk { private $url; private $data; private $cookie; private $headers; private $userAgent = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/71.0.3578.80 Chrome/71.0.3578.80 Safari/537.36'; private $parent; private $currentPath; /** * YandexDisk constructor. * @param string $url */ public function __construct($url, $parent = null) { $this->url = $url; $this->parent = $parent; try { $request = new \Rakit\Curl\Curl($this->url); $request->header('User-Agent', $this->userAgent); $response = $request->get(); } catch (ErrorException $e) { try { sleep(1); $response = $request->get(); } catch (ErrorException $e) { try { sleep(3); $response = $request->get(); } catch (ErrorException $e) { return; } } } if ($request && !$response->error()) { $html = $response->getBody(); $this->cookie = $response->getCookie(); $this->headers = $response->getHeaders(); preg_match_all('/(\