forked from CastroFidel/PortWINE
		
	major changes
This commit is contained in:
		| @@ -5,17 +5,22 @@ | ||||
| import fcntl | ||||
| import array | ||||
| import filecmp | ||||
| import fnmatch | ||||
| import json | ||||
| import os | ||||
| import shutil | ||||
| import errno | ||||
| import stat | ||||
| import subprocess | ||||
| import sys | ||||
| import tarfile | ||||
|  | ||||
| from filelock import FileLock | ||||
|  | ||||
| CURRENT_PREFIX_VERSION="5.0.9" | ||||
| #To enable debug logging, copy "user_settings.sample.py" to "user_settings.py" | ||||
| #and edit it if needed. | ||||
|  | ||||
| CURRENT_PREFIX_VERSION="5.13-1" | ||||
|  | ||||
| PFX="Proton: " | ||||
| ld_path_var = "LD_LIBRARY_PATH" | ||||
| @@ -27,14 +32,23 @@ def log(msg): | ||||
|     sys.stderr.write(PFX + msg + os.linesep) | ||||
|     sys.stderr.flush() | ||||
|  | ||||
| def file_is_wine_fake_dll(path): | ||||
| def log(msg): | ||||
|     sys.stderr.write(PFX + msg + os.linesep) | ||||
|     sys.stderr.flush() | ||||
|  | ||||
| def file_is_wine_builtin_dll(path): | ||||
|     if os.path.islink(path): | ||||
|         contents = os.readlink(path) | ||||
|         if os.path.dirname(contents).endswith(('/lib/wine', '/lib/wine/fakedlls', '/lib64/wine', '/lib64/wine/fakedlls')): | ||||
|             # This may be a broken link to a dll in a removed Proton install | ||||
|             return True | ||||
|     if not os.path.exists(path): | ||||
|         return False | ||||
|     try: | ||||
|         sfile = open(path, "rb") | ||||
|         sfile.seek(0x40) | ||||
|         tag = sfile.read(20) | ||||
|         return tag == b"Wine placeholder DLL" | ||||
|         return tag.startswith((b"Wine placeholder DLL", b"Wine builtin DLL")) | ||||
|     except IOError: | ||||
|         return False | ||||
|  | ||||
| @@ -45,15 +59,23 @@ def makedirs(path): | ||||
|         #already exists | ||||
|         pass | ||||
|  | ||||
| def try_copy(src, dst): | ||||
| def try_copy(src, dst, add_write_perm=True): | ||||
|     try: | ||||
|         if os.path.isdir(dst): | ||||
|             dstfile = dst + "/" + os.path.basename(src) | ||||
|             if os.path.lexists(dstfile): | ||||
|                 os.remove(dstfile) | ||||
|         elif os.path.lexists(dst): | ||||
|             os.remove(dst) | ||||
|         else: | ||||
|             dstfile = dst | ||||
|             if os.path.lexists(dst): | ||||
|                 os.remove(dst) | ||||
|  | ||||
|         shutil.copy(src, dst) | ||||
|  | ||||
|         if add_write_perm: | ||||
|             new_mode = os.lstat(dstfile).st_mode | stat.S_IWUSR | stat.S_IWGRP | ||||
|             os.chmod(dstfile, new_mode) | ||||
|  | ||||
|     except PermissionError as e: | ||||
|         if e.errno == errno.EPERM: | ||||
|             #be forgiving about permissions errors; if it's a real problem, things will explode later anyway | ||||
| @@ -61,11 +83,28 @@ def try_copy(src, dst): | ||||
|         else: | ||||
|             raise | ||||
|  | ||||
| def real_copy(src, dst): | ||||
|     if os.path.islink(src): | ||||
|         os.symlink(os.readlink(src), dst) | ||||
|     else: | ||||
|         try_copy(src, dst) | ||||
| def try_copyfile(src, dst): | ||||
|     try: | ||||
|         if os.path.isdir(dst): | ||||
|             dstfile = dst + "/" + os.path.basename(src) | ||||
|             if os.path.lexists(dstfile): | ||||
|                 os.remove(dstfile) | ||||
|         elif os.path.lexists(dst): | ||||
|             os.remove(dst) | ||||
|         shutil.copyfile(src, dst) | ||||
|     except PermissionError as e: | ||||
|         if e.errno == errno.EPERM: | ||||
|             #be forgiving about permissions errors; if it's a real problem, things will explode later anyway | ||||
|             log('Error while copying to \"' + dst + '\": ' + e.strerror) | ||||
|         else: | ||||
|             raise | ||||
|  | ||||
| def getmtimestr(*path_fragments): | ||||
|     path = os.path.join(*path_fragments) | ||||
|     try: | ||||
|         return str(os.path.getmtime(path)) | ||||
|     except IOError: | ||||
|         return "0" | ||||
|  | ||||
| EXT2_IOC_GETFLAGS = 0x80086601 | ||||
| EXT2_IOC_SETFLAGS = 0x40086602 | ||||
| @@ -151,24 +190,19 @@ class CompatData: | ||||
|         os.remove(self.tracked_files_file) | ||||
|         os.remove(self.version_file) | ||||
|  | ||||
|     def upgrade_pfx(self, old_ver): | ||||
|         #if old_ver == CURRENT_PREFIX_VERSION: | ||||
|         #    return | ||||
|      | ||||
|         #replace broken .NET installations with wine-mono support | ||||
|         if os.path.exists(self.prefix_dir + "/drive_c/windows/Microsoft.NET/NETFXRepair.exe") and \ | ||||
|                 file_is_wine_fake_dll(self.prefix_dir + "/drive_c/windows/system32/mscoree.dll"): | ||||
|             log("Broken .NET installation detected, switching to wine-mono.") | ||||
|             #deleting this directory allows wine-mono to work | ||||
|             shutil.rmtree(self.prefix_dir + "/drive_c/windows/Microsoft.NET") | ||||
|              | ||||
|         #fix mono and gecko | ||||
|         if os.path.exists(self.prefix_dir + "/drive_c/windows/mono"): | ||||
|             shutil.rmtree(self.prefix_dir + "/drive_c/windows/mono") | ||||
|         if os.path.exists(self.prefix_dir + "/drive_c/windows/system32/gecko"): | ||||
|             shutil.rmtree(self.prefix_dir + "/drive_c/windows/system32/gecko") | ||||
|         if os.path.exists(self.prefix_dir + "/drive_c/windows/syswow64/gecko"): | ||||
|             shutil.rmtree(self.prefix_dir + "/drive_c/windows/syswow64/gecko") | ||||
|     def pfx_copy(self, src, dst, dll_copy=False): | ||||
|         if os.path.islink(src): | ||||
|             contents = os.readlink(src) | ||||
|             if os.path.dirname(contents).endswith(('/lib/wine', '/lib/wine/fakedlls', '/lib64/wine', '/lib64/wine/fakedlls')): | ||||
|                 # wine builtin dll | ||||
|                 # make the destination an absolute symlink | ||||
|                 contents = os.path.normpath(os.path.join(os.path.dirname(src), contents)) | ||||
|             if dll_copy: | ||||
|                 try_copyfile(src, dst) | ||||
|             else: | ||||
|                 os.symlink(contents, dst) | ||||
|         else: | ||||
|             try_copyfile(src, dst) | ||||
|  | ||||
|     def copy_pfx(self): | ||||
|         with open(self.tracked_files_file, "w") as tracked_files: | ||||
| @@ -184,14 +218,48 @@ class CompatData: | ||||
|                     src_file = os.path.join(src_dir, dir_) | ||||
|                     dst_file = os.path.join(dst_dir, dir_) | ||||
|                     if os.path.islink(src_file) and not os.path.exists(dst_file): | ||||
|                         real_copy(src_file, dst_file) | ||||
|                         self.pfx_copy(src_file, dst_file) | ||||
|                 for file_ in files: | ||||
|                     src_file = os.path.join(src_dir, file_) | ||||
|                     dst_file = os.path.join(dst_dir, file_) | ||||
|                     if not os.path.exists(dst_file): | ||||
|                         real_copy(src_file, dst_file) | ||||
|                         self.pfx_copy(src_file, dst_file) | ||||
|                         tracked_files.write(rel_dir + file_ + "\n") | ||||
|  | ||||
|     def update_builtin_libs(self, dll_copy_patterns): | ||||
|         dll_copy_patterns = dll_copy_patterns.split(',') | ||||
|         prev_tracked_files = set() | ||||
|         with open(self.tracked_files_file, "r") as tracked_files: | ||||
|             for line in tracked_files: | ||||
|                 prev_tracked_files.add(line.strip()) | ||||
|         with open(self.tracked_files_file, "a") as tracked_files: | ||||
|             for src_dir, dirs, files in os.walk(g_proton.default_pfx_dir): | ||||
|                 rel_dir = src_dir.replace(g_proton.default_pfx_dir, "", 1).lstrip('/') | ||||
|                 if len(rel_dir) > 0: | ||||
|                     rel_dir = rel_dir + "/" | ||||
|                 dst_dir = src_dir.replace(g_proton.default_pfx_dir, self.prefix_dir, 1) | ||||
|                 if not os.path.exists(dst_dir): | ||||
|                     os.makedirs(dst_dir) | ||||
|                     tracked_files.write(rel_dir + "\n") | ||||
|                 for file_ in files: | ||||
|                     src_file = os.path.join(src_dir, file_) | ||||
|                     dst_file = os.path.join(dst_dir, file_) | ||||
|                     if not file_is_wine_builtin_dll(src_file): | ||||
|                         # Not a builtin library | ||||
|                         continue | ||||
|                     if file_is_wine_builtin_dll(dst_file): | ||||
|                         os.unlink(dst_file) | ||||
|                     elif os.path.lexists(dst_file): | ||||
|                         # builtin library was replaced | ||||
|                         continue | ||||
|                     else: | ||||
|                         os.makedirs(dst_dir, exist_ok=True) | ||||
|                     dll_copy = any(fnmatch.fnmatch(file_, pattern) for pattern in dll_copy_patterns) | ||||
|                     self.pfx_copy(src_file, dst_file, dll_copy) | ||||
|                     tracked_name = rel_dir + file_ | ||||
|                     if tracked_name not in prev_tracked_files: | ||||
|                         tracked_files.write(tracked_name + "\n") | ||||
|  | ||||
|     def create_fonts_symlinks(self): | ||||
|         fontsmap = [ | ||||
|             ( "LiberationSans-Regular.ttf", "arial.ttf" ), | ||||
| @@ -215,12 +283,6 @@ class CompatData: | ||||
|  | ||||
|     def setup_prefix(self): | ||||
|         with self.prefix_lock: | ||||
|             if os.path.exists(self.version_file): | ||||
|                 with open(self.version_file, "r") as f: | ||||
|                     self.upgrade_pfx(f.readline().strip()) | ||||
|             else: | ||||
|                 self.upgrade_pfx(None) | ||||
|  | ||||
|             if not os.path.exists(self.prefix_dir): | ||||
|                 makedirs(self.prefix_dir + "/drive_c") | ||||
|                 set_dir_casefold_bit(self.prefix_dir + "/drive_c") | ||||
| @@ -228,6 +290,34 @@ class CompatData: | ||||
|             if not os.path.exists(self.prefix_dir + "/user.reg"): | ||||
|                 self.copy_pfx() | ||||
|  | ||||
|             builtin_dll_copy = os.environ.get("PROTON_DLL_COPY", | ||||
|                     #dxsetup redist | ||||
|                     "d3dcompiler_*.dll," + | ||||
|                     "d3dcsx*.dll," + | ||||
|                     "d3dx*.dll," + | ||||
|                     "x3daudio*.dll," + | ||||
|                     "xactengine*.dll," + | ||||
|                     "xapofx*.dll," + | ||||
|                     "xaudio*.dll," + | ||||
|                     "xinput*.dll," + | ||||
|  | ||||
|                     #vcruntime redist | ||||
|                     "atl1*.dll," + | ||||
|                     "concrt1*.dll," + | ||||
|                     "msvcp1*.dll," + | ||||
|                     "msvcr1*.dll," + | ||||
|                     "vcamp1*.dll," + | ||||
|                     "vcomp1*.dll," + | ||||
|                     "vccorlib1*.dll," + | ||||
|                     "vcruntime1*.dll," + | ||||
|  | ||||
|                     #some games balk at ntdll symlink(?) | ||||
|                     "ntdll.dll," + | ||||
|  | ||||
|                     #some games require official vulkan loader | ||||
|                     "vulkan-1.dll" | ||||
|                     ) | ||||
|  | ||||
|             with open(self.version_file, "w") as f: | ||||
|                 f.write(CURRENT_PREFIX_VERSION + "\n") | ||||
|  | ||||
| @@ -278,7 +368,12 @@ class Session: | ||||
|         self.log_file = None | ||||
|         self.env = dict(os.environ) | ||||
|         self.dlloverrides = { | ||||
|                 "steam.exe": "n"  | ||||
|                 "steam.exe": "n",  | ||||
|                 "steam": "n",  | ||||
|                 "steam_api": "n", | ||||
|                 "steam_api64": "n", | ||||
|                 "steamclient": "n", | ||||
|                 "steamclient64": "n" | ||||
|         } | ||||
|  | ||||
|         self.compat_config = set() | ||||
| @@ -323,6 +418,12 @@ class Session: | ||||
|         self.env["GST_PLUGIN_SYSTEM_PATH_1_0"] = g_proton.lib64_dir + "gstreamer-1.0" + ":" + g_proton.lib_dir + "gstreamer-1.0" | ||||
|         self.env["WINE_GST_REGISTRY_DIR"] = g_compatdata.path("/tmp/gstreamer-1.0/") | ||||
|  | ||||
|         if "PW_COMPAT_MEDIA_PATH" in os.environ: | ||||
|             self.env["MEDIACONV_AUDIO_DUMP_FILE"] = os.environ["PW_COMPAT_MEDIA_PATH"] + "/tmp/audio.foz" | ||||
|             self.env["MEDIACONV_AUDIO_TRANSCODED_FILE"] = os.environ["PW_COMPAT_MEDIA_PATH"] + "/tmp/transcoded_audio.foz" | ||||
|             self.env["MEDIACONV_VIDEO_DUMP_FILE"] = os.environ["PW_COMPAT_MEDIA_PATH"] + "/tmp/video.foz" | ||||
|             self.env["MEDIACONV_VIDEO_TRANSCODED_FILE"] = os.environ["PW_COMPAT_MEDIA_PATH"] + "/tmp/transcoded_video.foz" | ||||
|  | ||||
|         if "PATH" in os.environ: | ||||
|             self.env["PATH"] = g_proton.bin_dir + ":" + os.environ["PATH"] | ||||
|         else: | ||||
| @@ -359,6 +460,7 @@ class Session: | ||||
|  | ||||
|         if not self.check_environment("PW_USE_WINED3D", "wined3d"): | ||||
|             self.check_environment("PW_USE_WINED3D11", "wined3d") | ||||
|         self.check_environment("PW_NO_D3D12", "nod3d12") | ||||
|         self.check_environment("PW_NO_D3D11", "nod3d11") | ||||
|         self.check_environment("PW_NO_D3D10", "nod3d10") | ||||
|         self.check_environment("PW_NO_D3D9",  "nod3d9") | ||||
| @@ -401,6 +503,9 @@ class Session: | ||||
|              | ||||
|         g_compatdata.setup_prefix() | ||||
|  | ||||
|         if "nod3d12" in self.compat_config: | ||||
|             self.dlloverrides["d3d12"] = "" | ||||
|  | ||||
|         if "nod3d11" in self.compat_config: | ||||
|             self.dlloverrides["d3d11"] = "" | ||||
|             if "dxgi" in self.dlloverrides: | ||||
|   | ||||
| @@ -2,18 +2,16 @@ | ||||
| # Author: PortWINE-Linux.ru | ||||
| . "$(dirname $(readlink -f "$0"))/runlib" | ||||
| ######################################################################## | ||||
| PORTPROTON_NAME=$(zenity --entry --text "${sc_name}") | ||||
| if [ $? -eq 1 ];then exit 1; fi | ||||
| PORTPROTON_EXE=$(zenity --file-selection --file-filter=""*.exe" "*.bat"" \ | ||||
| --title="${sc_path}" --filename="${PORT_WINE_PATH}/data/pfx/drive_c/") | ||||
| if [ $? -eq 1 ];then exit 1; fi | ||||
| PORTPROTON_NAME="$(basename "${PORTPROTON_EXE}" | sed s/".exe"/""/g )" | ||||
| PORTPROTON_PATH="$( cd "$( dirname "${PORTPROTON_EXE}" )" >/dev/null 2>&1 && pwd )"  | ||||
| if [ -x "`which wrestool 2>/dev/null`" ]; then | ||||
|     wrestool -x --output="${PORT_WINE_PATH}/data/img/" -t14 "${PORTPROTON_EXE}" | ||||
|     wrestool -x --output="${PORTPROTON_PATH}/" -t14 "${PORTPROTON_EXE}" | ||||
|     cp "$(ls -S -1 "${PORTPROTON_EXE}"*".ico"  | head -n 1)" "${PORTPROTON_EXE}.ico" | ||||
|     cp -f "${PORTPROTON_EXE}.ico" "${PORT_WINE_PATH}/data/img/${PORTPROTON_NAME}.ico" | ||||
| fi | ||||
| PORTPROTON_IMG=$(zenity --file-selection --file-filter=""*.png" "*.ico"" \ | ||||
| --title="${sc_img}" --filename="${PORT_WINE_PATH}/data/img/") | ||||
| if [ $? -eq 1 ];then exit 1; fi | ||||
| PORTPROTON_CMD="" | ||||
| #PORTPROTON_CMD=$(zenity --entry --text "${sc_cmd}") | ||||
| #if [ $? -eq 1 ];then exit 1; fi | ||||
| @@ -54,7 +52,6 @@ start_settings=`zenity --title  "${ss_title}" --text "${ss_text}" --list --radio | ||||
|             fi ;; | ||||
|     esac   | ||||
| ######################################################################## | ||||
| cp -f "${PORTPROTON_IMG}" "${PORT_WINE_PATH}/data/img/${PORTPROTON_NAME}.png" | ||||
| name_desktop="${PORTPROTON_NAME}"  | ||||
| echo "[Desktop Entry]" > "${PORT_WINE_PATH}/${name_desktop}.desktop" | ||||
| echo "Name=${PORTPROTON_NAME}" >> "${PORT_WINE_PATH}/${name_desktop}.desktop"  | ||||
| @@ -64,7 +61,10 @@ echo "Type=Application" >> "${PORT_WINE_PATH}/${name_desktop}.desktop" | ||||
| echo "Categories=Game" >> "${PORT_WINE_PATH}/${name_desktop}.desktop" | ||||
| echo "StartupNotify=true" >> "${PORT_WINE_PATH}/${name_desktop}.desktop" | ||||
| echo "Path="${PORT_SCRIPTS_PATH}/"" >> "${PORT_WINE_PATH}/${name_desktop}.desktop" | ||||
| echo "Icon="${PORTPROTON_IMG}"" >> "${PORT_WINE_PATH}/${name_desktop}.desktop" | ||||
| chmod +x "${PORT_WINE_PATH}/${name_desktop}.desktop" | ||||
| echo "Icon="${PORT_WINE_PATH}/data/img/${PORTPROTON_NAME}.ico"" >> "${PORT_WINE_PATH}/${name_desktop}.desktop" | ||||
| chmod u+x "${PORT_WINE_PATH}/${name_desktop}.desktop" | ||||
| ######################################################################## | ||||
| `zenity --info --title "Успешно." --text "Ярлык создан в корневом каталоге порта." --no-wrap ` > /dev/null 2>&1   | ||||
| `zenity --question --title "${inst_set}." --text "${ss_done}" --no-wrap ` > /dev/null 2>&1   | ||||
| if [ $? -eq "0" ]; then | ||||
| 	cp -f "${PORT_WINE_PATH}/${name_desktop}.desktop" /home/${USER}/.local/share/applications/  | ||||
| fi | ||||
|   | ||||
| @@ -16,7 +16,8 @@ then | ||||
| 	export ss_default_1="Cтандартный запуск" | ||||
| 	export ss_default_2="Старый запуск, с вопросами и логами" | ||||
| 	export ss_ogl_2="Использовать OpenGL и VKD3D. (DX9-DX11 в OpenGL и DX12 в vulkan)" | ||||
| 	export ss_ogl_3="Использовать DXVK и D9VK. (DX9-DX11 в Vulkan и выключить DX12)"  | ||||
| 	export ss_ogl_3="Использовать DXVK и D9VK. (DX9-DX11 в Vulkan и выключить DX12)" | ||||
| 	export ss_done="Ярлык создан в корневом каталоге порта.\nДобавить его в МЕНЮ -> ИГРЫ?"  | ||||
|  | ||||
| 	export inst_succ="Установка завершена успешно!" | ||||
|  | ||||
| @@ -90,6 +91,7 @@ then | ||||
| 	export ss_default_2="Old startup, with questions and logs" | ||||
| 	export ss_ogl_2="Use OpenGL and VKD3D. (DX9-DX11 to OpenGL and DX12 to vulkan)" | ||||
| 	export ss_ogl_3="Use DXVK and D9VK. (DX9-DX11 to Vulkan)"  | ||||
| 	export ss_done="The shortcut was created in the ${portname} directory.\nAdd it to MENU -> GAMES?"  | ||||
|  | ||||
| 	export inst_succ="The installation was successful." | ||||
|  | ||||
|   | ||||
| @@ -50,6 +50,7 @@ export WINEPREFIX="${PORT_WINE_PATH}/data/pfx" | ||||
| export PATH="${WINEDIR}/bin:${PATH}" | ||||
| export WINESTART="C:\\windows\\command\\start.exe" | ||||
| export STEAM_COMPAT_DATA_PATH="${PORT_WINE_PATH}/data/" | ||||
| export PW_COMPAT_MEDIA_PATH="${PW_COMPAT_MEDIA_PATH}" | ||||
| ######################################################################## | ||||
| export urlg="http://portwine-linux.ru/donate" | ||||
| ######################################################################## | ||||
| @@ -131,17 +132,9 @@ echo "DXVK_HUD=${DXVK_HUD}" | ||||
|  | ||||
| if [ "${var_dxvk_on}" != "off" ]; then | ||||
|     export PW_USE_WINED3D=0 | ||||
|     export PW_NO_D3D9=0 | ||||
|     export PW_NO_D3D10=0 | ||||
|     export PW_NO_D3D11=0 | ||||
|     export PW_OLD_GL_STRING=0 | ||||
|     echo "Use DXVK and D9VK (DX9-DX11 to Vulkan)"  | ||||
| else | ||||
|     export PW_USE_WINED3D=1 | ||||
|     export PW_NO_D3D9=0 | ||||
|     export PW_NO_D3D10=0 | ||||
|     export PW_NO_D3D11=0 | ||||
|     export PW_OLD_GL_STRING=0 | ||||
|     echo "Use OpenGL and VKD3D (DX9-DX11 to OpenGL and DX12 to vulkan)"  | ||||
| fi | ||||
|  | ||||
|   | ||||
| @@ -115,7 +115,7 @@ fi | ||||
| if [ -f "${PORT_WINE_PATH}/restart.desktop" ]; then | ||||
| 	rm "${PORT_WINE_PATH}/restart.desktop" | ||||
| fi | ||||
|  | ||||
| START_PORTWINE | ||||
| ADD_IN_POST_INSTALL | ||||
|  | ||||
| xdg-open "http://portwine-linux.ru/portwine-faq/" > /dev/null 2>&1 & exit 0   | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| # License GPL | ||||
| # Author: Tergoev M.A. | ||||
| ######################################################################## | ||||
| export portname= | ||||
| export portname=PortProton | ||||
| export gamename= | ||||
| export gamedir= | ||||
| if [ ! -z "${gamedir}" ]; then | ||||
| @@ -16,12 +16,16 @@ export WINEDLLOVERRIDES="winemenubuilder.exe=d" | ||||
| export STAGING_SHARED_MEMORY=1 | ||||
| export PW_LOG=0     # 1-ENABLE_DEBUG_MODE_FOR_TERMINAL  | ||||
| export PW_NO_VR=1 | ||||
| export PW_NO_D3D9=0 | ||||
| export PW_NO_D3D10=0 | ||||
| export PW_NO_D3D11=0 | ||||
| export PW_NO_D3D12=0 | ||||
| export PW_NO_FSYNC=0 | ||||
| export PW_NO_ESYNC=1 | ||||
| export PW_FILELOCK=1 | ||||
| export PW_DXVK_ASYNC=0 | ||||
| export PW_DXGI_NATIVE=0 | ||||
| export PW_USE_SECCOMP=0 | ||||
| export PW_OLD_GL_STRING=0 | ||||
| export PW_NO_WINEMFPLAY=1 | ||||
| export PW_NVAPI_DISABLE=1 | ||||
| export PW_NO_WRITE_WATCH=1 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user