Эмулятор ps4 для pc windows 10 отзывы

Симулятор эмулятора.

Поспорить в интернете — святое дело. В одном таком споре о (не)схожести характеристик PS4 и видеокарты Radeon HD7850 (даже не спрашивайте), мой собеседник скинул ссылку на видео с God of War 3 Remastered, запущенной на PC средней конфигурации.

Автор ролика установил высокие настройки через некий эмулятор PS4, PCSX4, выбрал образ и в одно нажатие запустил игру. Частота кадров была близка к идеальной как в катсценах, так и в игре. Я был ошеломлен. Неизвестная программа выдаёт такие результаты?!

Разработчики эмулятора PS3 (RPCS3) много лет доводят программу до ума, стараясь добиться стабильной работы некоторой части игр и лишь совсем недавно смогли добиться работоспособности «тяжелых проектов» уровня God Of War и Last of Us. Вокруг эмулятора есть большое сообщество (зачеркнуто)пиратов(зачеркнуто) пользователей , заинтересованных в сохранении библиотеки игр консоли для потомков (аррр). Каждый новый отчёт разработчиков программы становится инфоповодом для игровых ресурсов.

Заподозрив подвох, я стал искать информацию. У эмулятора был сайт, с 30 тысячами лайков от пользователей Facebook. Также, свой канал на Youtube с парой видео. На каждом из них было несколько десятков тысяч просмотров, чуть более тысячи лайков, минимум «пальцев вниз» и не было отрицательных отзывов. Авторы видео рассказали в деталях подробности настройки программы, запустили Uncharted 4, а также Infamous: Second Son в стабильных 60 кадрах в секунду.

Интересные подробности нашлись в характеристиках эмулятора. Авторы отправляли ссылку только на e-mail, якобы, из-за защиты от ботов. Для запуска игры, пользователю понадобился бы Blue-Ray дисковод и подключение к какому-то интернет-сервису. Далее, эмулятор скачивал данные игры на жёсткий диск компьютера.

Всё это выглядело весьма приятно и удобно для рядового пользователя. Если бы не одно «но» — полное отсутствие новостей и гайдов об этом эмуляторе. Лишь нескольких видео от небольших каналов на Youtube. Вторая ссылка в гугле вела на Reddit, где и подтвердились сомнения в работоспособности программы.

Судя по тому, что я нашёл на просторах Youtube и Reddit, сложилась следующая картина — авторы «эмулятора» потратились на дизайн и поддержку сайта, рекламу эмулятора на варезных сайтах, занимаются чисткой комментариев на подконтрольном канале и даже устраивают набеги на видео с разоблачением их «проекта», закидывая тысячью дизлайков.

Эмулировать текущее поколение консолей, кроме Nintendo Switch, пока что — невозможно. Донесите эту мысль до знакомых казуалов, пока они не словили какой-нибудь майнер. Консоли сейчас стоят меньше, чем игровые компьютеры, распродажи игр происходят постоянно, а б/у-рынок весьма развит. При желании поиграть в какую-то игру с консоли, всегда можно арендовать приставку с играми поблизости или взять у друга на время, в обмен на что-нибудь вкусное. Например, пиво. Или подождите до выхода нового поколения приставок, там как раз и новый эмулятор выйдет. Наверное.

Старые версии эмулятора:

Спойлер

shadPS4 — это ранний эмулятор PS4 для Windows и Linux, написанный на C++.

Shadps4 может загружать некоторые модули в режиме lle, некоторые необходимы, некоторые имеют замены HLE. Следующие модули fw поддерживаются и должны быть помещены в папку user/sys_modules. Протестированные модули fw из fw 11.00

  • libSceNgs2.sprx
  • libSceLibcInternal.sprx
  • libSceDiscMap.sprx
  • libSceRtc.sprx
  • libSceJpegEnc.sprx
  • libSceJson2.sprx

Вышеуказанные модули должны быть извлечены из вашего ps4. Мы не предоставляем никакой информации или поддержки о том, как это сделать.

Использование эмулятора:

Для установки файлов PKG (игры и обновлений) вам понадобится приложение Qt (с пользовательским интерфейсом). Вам нужно будет перейти в «Файл», затем в «Установить пакеты (PKG)», откроется окно, в котором вам нужно будет выбрать файлы. Вы можете установить несколько файлов PKG одновременно. После завершения игра должна появиться в приложении.

Настройте эмулятор

Вы можете настроить эмулятор в папке «user» (создается после первого запуска приложения), а затем в файле «config.toml». Здесь вы найдете множество параметров для установки True или False.

Отображение клавиатуры

Спойлер

Кнопка контроллера Клавиатура

ЛЕВАЯ ОСЬ ВВЕРХ W

ЛЕВАЯ ОСЬ ВНИЗ С

ЛЕВАЯ ОСЬ ВЛЕВО A

ЛЕВАЯ ОСЬ ВПРАВО D

ПРАВАЯ ОСЬ ВВЕРХ I

ПРАВАЯ ОСЬ ВНИЗ K

ПРАВАЯ ОСЬ ВЛЕВО J

ПРАВАЯ ОСЬ ВПРАВО L

TRIANGLE Numpad 8

Круг Numpad 6

CROSS Numpad 2

Квадрат Numpad 4

PAD UP UP

PAD DOWN DOWN

PAD LEFT LEFT

ПЭД ВПРАВО ВПРАВО

ОПЦИИ ВОЗВРАТА

СЕНСОРНАЯ ПАНЕЛЬ ПРОСТРАНСТВО

L1 Q

R1 U

L2 E

R2 O

L3 X

R3 M

Установка:

  1. Скачайте файл и распакуйте его. Для распаковки архива можете воспользоваться одним из архиваторов у нас на сайте.
  2. После загрузки подходящей для вас версии (Windows или Linux) необходимо разархивировать файл и затем запустить его. Обратите внимание, что для каждой платформы есть две версии: версия Qt с пользовательским интерфейсом и версия без (SDL Builds).

Автор: shadps4-emu

Другие эмуляторы:

В последнее время Sony активно штурмует ПК-рынок и выпускает на народную платформу свои лучшие игры, однако эксклюзивов на ее консолях все еще хватает. Особенно на PS4. Взять ту же Bloodborne, например.

Если нет денег на консоль или не хочется ждать полноценный порт, то можно воспользоваться эмулятором. Правда, есть несколько оговорок.

Как выбрать правильный эмулятор PS4

На сайте Emulation General Wiki масса эмуляторов PS4. Однако почти все они не пригодны для работы с ними из-за ряда причин. И сейчас есть лишь один проект, способный на большее, чем просто показать системное меню — fpPS4.

Однако эмулятор до сих пор несовершенен, и может запустить только простые и не очень требовательные игры. Если хотите поиграть в ту же Bloodborne, то придется подождать пару лет. Cейчас эмулятор годится разве что для забавы, не более.

Системные требования fpPS4

Минимальные системные требования:

  • ОС: Windows 7 и выше;

  • Процессор: 4-ядерный;

  • ОЗУ: 8 ГБ и больше;

  • Видеокарта: с поддержкой Vulcan 1.3 и выше;

  • Свободное место: 5 ГБ.

Рекомендуемые системные требования:

  • ОС: Windows 10 и выше;

  • Процессор: 8-ядерный;

  • ОЗУ: 12 ГБ и больше;

  • Видеокарта: с поддержкой Vulcan 1.3 и выше;

  • Свободное место: 5 ГБ.

Также перед установкой и запуском эмулятора обновите DirectX и драйвер видеокарты до последних версий.

Редакция CQ.ru не поддерживает пиратство, информация публикуется исключительно в ознакомительных целях.

Настройка fpPS4

  • Скачиваем эмулятор и лончер;

  • Скачиваем утилиты и набор для работы с образами игр;

  • Распаковываем каждый архив в отдельные папки и затем закидываем в одну большую;

  • В папке Launcher создаем три подпапки с названиями Emu, Lib и Games;

  • Заходим в папку Games и создаем подпапку под ту или иную игру. Название можно задать любое;

  • Переносим файлы эмулятора в папку Emu;

  • Переносим файлы из папки libSceUtils.zip в Lib;

  • Запускаем PkgEditor с правами администратора (ПКМ), выбираем образ игры в формате PKG;

  • Выбираем Export, указав ранее созданную папку с игрой;

  • Теперь открываем папку Launcher и запускаем одноименный файл. Если все сделано правильно, то игра (в нашем случае это Sonic Mania) появится в списке. Нажимаем на кнопку «Run fpPS4» и наслаждаемся.

Несколько важных моментов:

  • Эмулятор еще очень сырой, поэтому даже в таких играх могут быть тормоза, графические баги, вылеты и так далее;

  • Настройки эмулятора (меню Settings) лучше не трогать. Исключение: управление. Играть с клавиатуры в заточенные под геймпад игры точно не надо. Поэтому рекомендуем подключить какой-нибудь контроллер;

  • Включая эмулятор, вы нарушаете закон об авторском праве.

Недавно авторы эмулятора RPCS3 анонсировали наследника, который будет воспроизводить игры с PS4. Но проект под названием RPCSX пока что находится в зачаточном состоянии, поэтому рассказать о нем нечего.

Заглавное фото: Sony

На чтение7 мин

Опубликовано

Обновлено

Владельцы ПК на базе Windows 10 мечтают о возможности играть в игры, доступные только на консоли PlayStation 4. С появлением эмулятора PCSX4, эта мечта может стать реальностью. PCSX4 — это эмулятор, разработанный специально для запуска игр PS4 на компьютере с операционной системой Windows 10. В этой статье рассмотрим детальное руководство и инструкцию по использованию этого эмулятора.

PCSX4 позволяет пользователям запускать игры PS4 без необходимости иметь физическую консоль. Он делает это с помощью поддержки игровых образов (ROM-файлов) игр PS4, которые можно загружать на компьютер. Таким образом, пользователи могут наслаждаться играми PS4 на своем персональном компьютере с Windows 10, используя клавиатуру и мышь или подключая геймпад.

Однако, стоит отметить, что PCSX4 находится в стадии разработки и не все игры PS4 полностью совместимы с этим эмулятором. Есть вероятность некоторых ошибок и сбоев в работе при запуске некоторых игр. Разработчики работают над постоянными обновлениями эмулятора, чтобы улучшить его производительность и совместимость.

Для использования эмулятора PCSX4 на компьютере с Windows 10 необходимо соблюдать определенные системные требования. Компьютер должен иметь достаточно мощное железо для эмуляции игр PS4. Рекомендуется иметь процессор Intel Core i5 или AMD Ryzen 5, видеокарту Nvidia GeForce GTX 970 или AMD Radeon RX 590, а также минимум 8 ГБ оперативной памяти. Важно также убедиться, что Windows 10 обновлен до последней версии и установлены все необходимые драйверы.

В этой статье мы рассмотрим процесс установки и настройки эмулятора PCSX4 на ПК с Windows 10, а также методы загрузки игровых образов и запуска игр PS4. Мы также рассмотрим основные функции и возможности PCSX4, чтобы вы могли полностью насладиться игровым процессом игр PS4 на своем компьютере.

Эмулятор PS4 для PC Windows 10 — PCSX4: руководство по использованию

Установка:

1. Скачайте установочный файл PCSX4 с официального сайта разработчика.

2. Запустите установочный файл и следуйте инструкциям на экране для завершения установки.

3. После установки откройте программу PCSX4.

Запуск и использование:

1. Подключите джойстик или геймпад к компьютеру, если необходимо.

2. В программе PCSX4 выберите игру, которую вы хотите запустить.

3. Проверьте настройки эмулятора, включая разрешение экрана и качество графики.

4. Нажмите на кнопку «Запустить» или просто дважды щелкните на игре в списке.

5. После запуска игры вы сможете управлять ею с помощью клавиатуры и/или подключенного геймпада.

Важные советы:

— Убедитесь, что ваш компьютер соответствует минимальным требованиям системы для работы PCSX4 и запуска игр PS4.

— Обновляйте эмулятор и драйверы вашей видеокарты для лучшей совместимости и производительности.

— Некоторые игры могут запускаться и работать более плавно, чем другие, в зависимости от их сложности и оптимизации.

С помощью эмулятора PCSX4 вы можете насладиться играми PS4 на своем компьютере под управлением Windows 10. Просто следуйте инструкциям по установке и запуску, и наслаждайтесь игровым процессом без необходимости покупки самой консоли!

Установка PCSX4 на ПК с операционной системой Windows 10

  1. Скачайте установочный файл PCSX4 с официального сайта разработчика.
  2. После загрузки установочного файла запустите его и следуйте инструкциям мастера установки.
  3. Выполните все необходимые действия, чтобы завершить установку PCSX4 на вашем ПК.
  4. После установки откройте эмулятор и пройдите процесс настройки. Укажите путь к игровым ISO-файлам PS4, которые вы хотите запустить на PCSX4.
  5. Загрузите выбранный ISO-файл в эмулятор и наслаждайтесь игрой на своем ПК.

PCSX4 также поддерживает использование геймпадов и других контроллеров, поэтому вы можете настроить свой контроллер для игры с помощью эмулятора.

Установка PCSX4 на ПК с операционной системой Windows 10 не займет много времени и позволит вам наслаждаться играми от PS4 прямо на вашем компьютере. Пользуйтесь эмулятором с удовольствием!

Настройка эмулятора PS4 PCSX4 на PC Windows 10

Для начала использования эмулятора PS4 PCSX4 на компьютере с операционной системой Windows 10 необходимо выполнить следующие шаги:

  1. Скачайте установочный файл эмулятора PCSX4 с официального сайта разработчика.
  2. Запустите установочный файл и следуйте указанным инструкциям для завершения установки эмулятора.
  3. После установки эмулятора PCSX4 откройте его и выберите опцию «Настройки».
  4. В меню настроек выберите раздел «Видео», чтобы настроить параметры графики.
  5. В разделе «Видео» вы можете выбрать разрешение экрана, настройки антиалиасинга и другие параметры графики.
  6. После настройки параметров графики вы можете перейти в раздел «Контроллеры» для настройки параметров управления.
  7. В разделе «Контроллеры» вы можете выбрать тип контроллера, назначить кнопки и настроить другие параметры управления.
  8. После завершения настройки параметров графики и управления вы можете закрыть меню настроек.
  9. Теперь вы можете запустить игру, выбрав ее файл в меню эмулятора PCSX4.

При использовании эмулятора PCSX4 для игры в игры PS4 на компьютере с ОС Windows 10 необходимо учесть, что некоторые игры могут работать неправильно или иметь ограничения. Рекомендуется проверить совместимость выбранной игры с эмулятором и прочитать дополнительные инструкции по запуску игр на официальном сайте PCSX4.

Преимущества Недостатки
Возможность играть в игры PS4 на компьютере Ограниченная поддержка игр
Параметры настройки графики и управления Возможные проблемы совместимости
Расширенные функции эмуляции Требование высокой производительности компьютера

В целом, эмулятор PS4 PCSX4 предоставляет возможность играть в игры PS4 на компьютере с ОС Windows 10, однако рекомендуется использовать его с учетом вышеперечисленных преимуществ и недостатков.

Загрузка и запуск игр на эмуляторе PS4 PCSX4 для PC Windows 10

Играть в игры для PlayStation 4 на компьютере с операционной системой Windows 10 теперь возможно благодаря эмулятору PCSX4. В этом руководстве мы расскажем вам, как загрузить и запустить игры на эмуляторе PS4 PCSX4.

Прежде всего, убедитесь, что у вас установлены все необходимые компоненты: PCSX4 эмулятор, BIOS файлы для PS4, а также образы игр, которые хотите запустить. Все эти файлы можно найти в Сети.

После установки эмулятора PCSX4 и BIOS файлов, откройте программу и выберите пункт меню «Файл» > «Загрузить игру». Найдите образ игры на вашем компьютере и выберите его.

После выбора игры эмулятор автоматически начнет ее запуск. Во время загрузки может потребоваться некоторое время, в зависимости от производительности вашего компьютера и размера файла игры.

Когда игра загружена, вы можете настроить управление, используя клавиатуру или геймпад. Обратите внимание, что некоторые игры могут иметь особые требования к настройкам графики или звука, которые могут быть доступны в настройках эмулятора PCSX4.

Теперь вы готовы играть в игры для PlayStation 4 на вашем компьютере с помощью эмулятора PCSX4. Наслаждайтесь игровым процессом и возвращайтесь в будущем, чтобы попробовать новые игры и обновления для эмулятора.

Особенности работы эмулятора PS4 PCSX4 для PC Windows 10

  1. Высокое качество графики: Эмулятор PCSX4 поддерживает высокое разрешение и качество графики, что позволяет игравщам наслаждаться играми для PS4 с превосходной визуальной отдачей.
  2. Поддержка контроллеров: Эмулятор PCSX4 полностью совместим с контроллерами PlayStation 4, что позволяет пользователям наслаждаться играми с помощью оригинальных контроллеров PS4, обеспечивая максимальный комфорт и качество управления.
  3. Отличная производительность: Эмулятор PCSX4 обеспечивает высокую производительность при запуске игр для PS4 на компьютере. Он оптимизирован для работы на Windows 10 и обеспечивает плавный и стабильный геймплей даже на слабых системах.
  4. Поддержка широкого спектра игр: PCSX4 поддерживает большое количество игр для PlayStation 4, включая самые популярные и новые релизы. Пользователи могут выбирать из различных жанров игр и наслаждаться большим разнообразием контента.
  5. Обновления и улучшения: Разработчики эмулятора PCSX4 постоянно выпускают обновления, которые улучшают функциональность и производительность эмулятора. Пользователям предоставляется возможность скачать последнюю версию эмулятора и насладиться новыми возможностями и улучшениями.

В целом, эмулятор PS4 PCSX4 для PC Windows 10 представляет собой мощный инструмент, позволяющий пользователям опытным образом запускать и играть в игры PlayStation 4 на компьютере. Он обладает высоким качеством графики, поддержкой контроллеров, отличной производительностью и значительным количеством поддерживаемых игр. Пользователи могут регулярно обновлять эмулятор и наслаждаться новыми возможностями, которые предоставляются.

Часто задаваемые вопросы об эмуляторе PS4 PCSX4 для PC Windows 10

В этом разделе мы ответим на некоторые из самых часто задаваемых вопросов об эмуляторе PS4 PCSX4 для PC Windows 10. Если у вас есть другие вопросы, не указанные здесь, не стесняйтесь задавать их в комментариях.

Вопрос Ответ
Могу ли я запустить игры PS4 на PC с помощью эмулятора PCSX4? Нет, эмулятор PCSX4 находится в разработке и не поддерживает полную совместимость с играми PS4. Некоторые игры могут работать, но они могут иметь неполадки и проблемы со совместимостью.
Какую версию Windows 10 необходимо иметь для использования эмулятора PCSX4? Для использования эмулятора PCSX4 необходима 64-разрядная версия Windows 10. Эмулятор не будет работать на 32-разрядной версии операционной системы.
Требует ли эмулятор PCSX4 высокой производительности компьютера? Да, эмулятор PCSX4 требует достаточно мощного компьютера для запуска игр PS4 на приемлемом уровне производительности. Рекомендуется иметь современный процессор и видеокарту для достижения плавной игры.
Могу ли я использовать контроллер PS4 с эмулятором PCSX4? Да, вы можете использовать контроллер PS4 соединенный с компьютером через USB или Bluetooth для игры с эмулятором PCSX4.
Будет ли эмулятор PCSX4 поддерживать будущие обновления PS4? Точной информации о будущей поддержке эмулятора PCSX4 нет. Текущая версия эмулятора может не совместима с будущими обновлениями PS4.

Это были ответы на некоторые из самых часто задаваемых вопросов об эмуляторе PS4 PCSX4 для PC Windows 10. Если у вас остались дополнительные вопросы, не стесняйтесь обратиться к официальной документации эмулятора или задать вопрос в комментариях.

Время на прочтение15 мин

Количество просмотров10K

Эмулятор – это приложение, способное имитировать запуск программы, предназначенной для одной платформы, на другой. Примером эмулятора является GPCS4, предназначенный для запуска игр для PS4 на PC. Недавно состоялся первый релиз GPCS4, и мы решили проверить этот проект. Давайте посмотрим, какие ошибки удалось найти PVS-Studio в исходном коде этого эмулятора.

О проекте

GPCS4 – это эмулятор PlayStation 4, написанный на C и C++.

Изначально целью автора проекта было исследовать архитектуру PS4. Однако проект быстро развивался, и уже в начале 2020 года разработчикам GPCS4 удалось запустить на своём эмуляторе игру We are Doomed. Это был первый успешный стабильный запуск на PC игры для PS4. Впрочем, сам игровой процесс пока что был далёк от идеала: игра тормозила, картинка дёргалась. Тем не менее, основной разработчик проекта полон энтузиазма и продолжает совершенствовать эмулятор.

В конце апреля 2022 года состоялся первый релиз GPCS4. Я скачал и проверил версию 0.1.0 проекта. К слову, на момент публикации статьи уже вышел релиз 0.2.0 – проект развивается очень быстро. Давайте перейдём к ошибкам и недочётам, которые удалось найти анализатору PVS-Studio в первом релизе проекта GPCS4.

Потерянный break

V796 [CWE-484] It is possible that ‘break’ statement is missing in switch statement. AudioOut.cpp 137

static AudioProperties getAudioProperties(uint32_t param)
{
  uint32_t format       = param & 0x000000ff;
  AudioProperties props = {};

  switch (format)
  {
    // ....
    case SCE_AUDIO_OUT_PARAM_FORMAT_S16_8CH_STD:
    {
      props.nChannels   = 8;
      props.bytesPerSample  = 2;
      props.audioFormat = RTAUDIO_FORMAT_SINT16;
      break;
    }
    case SCE_AUDIO_OUT_PARAM_FORMAT_FLOAT_MONO:
    {
      props.nChannels   = 1;
      props.bytesPerSample  = 4;
      props.audioFormat = RTAUDIO_FORMAT_FLOAT32;         // <=
    }
    case SCE_AUDIO_OUT_PARAM_FORMAT_FLOAT_STEREO:
    {
      props.nChannels   = 2;
      props.bytesPerSample  = 4;
      props.audioFormat = RTAUDIO_FORMAT_FLOAT32;
      break;
    }
  }
  return props;
}

В данном фрагменте кода для случая SCE_AUDIO_OUT_PARAM_FORMAT_FLOAT_MONO потерян break. Из-за этого количество каналов будет выставлено неправильно.

Проверка указателя после использования

V595 The ‘m_moduleData’ pointer was utilized before it was verified against nullptr. Check lines: 49, 53. ELFMapper.cpp 49

struct NativeModule { /*....*/ };

class ELFMapper
{
  // ....
  NativeModule *m_moduleData;
};

bool ELFMapper::validateHeader()
{
  bool retVal      = false;
  auto &fileMemory = m_moduleData->m_fileMemory;
  do
  {
    if (m_moduleData == nullptr)
    {
      LOG_ERR("file has not been loaded");
      break;
    }
    // ....
  } while (false);
  
  return retVal;
}

В приведённом фрагменте указатель m_moduleData сначала разыменовывается, а затем в цикле do-while сравнивается с nullptr.

Внимательные читатели могут возразить: «Может быть, на вход функции всегда приходит валидный указатель? А потом в цикле do-while этот указатель модифицируется и может стать нулевым? Тогда и ошибки тут нет». В данном случае это не так. Во-первых, из-за условия while (false) выполнится ровно одна итерация цикла. Во-вторых, указатель m_moduleData не модифицируется.

Другое возражение может заключаться в том, что взятие ссылки – это безопасно. Ведь использоваться эта ссылка будет, только если указатель валидный. Но нет, такой код содержит неопределённое поведение. Это ошибка. Скорее всего, нужно сделать проверку указателя до его первого разыменования:

bool ELFMapper::validateHeader()
{
  bool retVal      = false;
  
  do
  {
    if (m_moduleData == nullptr)
    {
      LOG_ERR("file has not been loaded");
      break;
    }

    auto &fileMemory = m_moduleData->m_fileMemory;
    // ....
  } while (false);

  return retVal;
}

Повторное присвоение

V519 [CWE-563] The ‘* memoryType’ variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 54, 55. sce_kernel_memory.cpp 55

int PS4API sceKernelGetDirectMemoryType(sce_off_t start, int *memoryType, 
    sce_off_t *regionStartOut, sce_off_t *regionEndOut)
{
  LOG_SCE_DUMMY_IMPL();
  *memoryType = SCE_KERNEL_WB_GARLIC;
  *memoryType = SCE_KERNEL_WC_GARLIC;
  return SCE_OK;
}

Как можно догадаться по названию LOG_SCE_DUMMY_IMPL, реализация метода sceKernelGetDirectMemoryType ещё будет меняться. И всё же, два присваивания по одному и тому же адресу memoryType выглядят странно. Возможно, так получилось в результате неудачного слияния кода.

Переполнение буфера

V512 [CWE-119] A call of the ‘memset’ function will lead to overflow of the buffer ‘param->reserved’. sce_gnm_draw.cpp 420

V531 [CWE-131] It is odd that a sizeof() operator is multiplied by sizeof(). sce_gnm_draw.cpp 420

struct GnmCmdPSShader
{
  uint32_t              opcode;
  gcn::PsStageRegisters psRegs;
  uint32_t              reserved[27];
};

int PS4API sceGnmSetPsShader350(uint32_t* cmdBuffer, uint32_t numDwords, 
                                const gcn::PsStageRegisters *psRegs)
{
  // ....
  memset(param->reserved, 0, sizeof(param->reserved) * sizeof(uint32_t)); 
  return SCE_OK;
}

Иногда бывает, что на одну и ту же строчку кода указывают сразу несколько диагностик PVS-Studio. Так получилось и в этом примере. В данном фрагменте кода в функцию memset передаётся неправильное значение третьим аргументом. Выражение sizeof(param->reserved) уже вернёт размер массива param->reserved. Умножение на sizeof(uint32_t) увеличит это значение в 4 раза, и значение получится неправильным. Поэтому в результате вызова memset произойдёт выход за границу массива param->reserved. Нужно убрать лишнее умножение:

int PS4API sceGnmSetPsShader350( /*....*/ )
{
  // ....
  memset(param->reserved, 0, sizeof(param->reserved));
  return SCE_OK;
}

Всего анализатор обнаружил 20 подобных переполнений, приведу ещё один пример:

V512 [CWE-119] A call of the ‘memset’ function will lead to overflow of the buffer ‘initParam->reserved’. sce_gnm_dispatch.cpp 16

uint32_t PS4API sceGnmDispatchInitDefaultHardwareState(uint32_t* cmdBuffer,
                                                       uint32_t numDwords)
{
  // ....
  memset(initParam->reserved, 0,
         sizeof(initParam->reserved) * sizeof(uint32_t));
  return initCmdSize;
}

В этом фрагменте кода происходит выход за границу массива initParam->reserved.

Учимся считать до семи, или ещё одно переполнение буфера

V557 [CWE-787] Array overrun is possible. The ‘dynamicStateCount ++’ index is pointing beyond array bound. VltGraphics.cpp 157

VkPipeline VltGraphicsPipeline::createPipeline(/* .... */) const
{
  // ....
  std::array<VkDynamicState, 6> dynamicStates;
  uint32_t                      dynamicStateCount = 0;
  dynamicStates[dynamicStateCount++] = VK_DYNAMIC_STATE_VIEWPORT;
  dynamicStates[dynamicStateCount++] = VK_DYNAMIC_STATE_SCISSOR;
  if (state.useDynamicDepthBias())
    dynamicStates[dynamicStateCount++] = VK_DYNAMIC_STATE_DEPTH_BIAS;
  if (state.useDynamicDepthBounds())
  {
    dynamicStates[dynamicStateCount++] = VK_DYNAMIC_STATE_DEPTH_BOUNDS;
    dynamicStates[dynamicStateCount++] =
                             VK_DYNAMIC_STATE_DEPTH_BOUNDS_TEST_ENABLE;
  }
  if (state.useDynamicBlendConstants())
    dynamicStates[dynamicStateCount++] = VK_DYNAMIC_STATE_BLEND_CONSTANTS;
  if (state.useDynamicStencilRef())
    dynamicStates[dynamicStateCount++] = VK_DYNAMIC_STATE_STENCIL_REFERENCE;
  // ....
}

Анализатор предупреждает, что может произойти переполнение массива dynamicStates. В данном фрагменте кода есть 4 проверки:

  • if (state.useDynamicDepthBias())

  • if (state.useDynamicDepthBounds())

  • if (state.useDynamicBlendConstants())

  • if (state.useDynamicStencilRef())

Каждая из них – это проверка одного из независимых флагов. Например, проверка if (state.useDynamicDepthBias()):

bool useDynamicDepthBias() const
{
  return rs.depthBiasEnable();
}

VkBool32 depthBiasEnable() const
{
  return VkBool32(m_depthBiasEnable);
}

Получается, все эти 4 проверки могут быть истинными одновременно. Тогда выполнится 7 строк вида ‘dynamicStates[dynamicStateCount++] = ….’. На седьмой такой строке произойдёт обращение к dynamicStates[6]. Это выход за границу массива.

Для исправления нужно выделить память на 7 элементов:

VkPipeline VltGraphicsPipeline::createPipeline(/* .... */) const
{
  // ....
  std::array<VkDynamicState, 7> dynamicStates; // <=
  uint32_t                      dynamicStateCount = 0;
  dynamicStates[dynamicStateCount++] = VK_DYNAMIC_STATE_VIEWPORT;
  dynamicStates[dynamicStateCount++] = VK_DYNAMIC_STATE_SCISSOR;
  if (state.useDynamicDepthBias())
    dynamicStates[dynamicStateCount++] = VK_DYNAMIC_STATE_DEPTH_BIAS;
  if (state.useDynamicDepthBounds())
  {
    dynamicStates[dynamicStateCount++] = VK_DYNAMIC_STATE_DEPTH_BOUNDS;
    dynamicStates[dynamicStateCount++] =
                             VK_DYNAMIC_STATE_DEPTH_BOUNDS_TEST_ENABLE;
  }
  if (state.useDynamicBlendConstants())
    dynamicStates[dynamicStateCount++] = VK_DYNAMIC_STATE_BLEND_CONSTANTS;
  if (state.useDynamicStencilRef())
    dynamicStates[dynamicStateCount++] = VK_DYNAMIC_STATE_STENCIL_REFERENCE;
  // ....
}

Неправильное использование флага

V547 [CWE-570] Expression ‘nOldFlag & VMPF_NOACCESS’ is always false. PlatMemory.cpp 22

#define PAGE_NOACCESS           0x01
#define PAGE_READONLY           0x02
#define PAGE_READWRITE          0x04
#define PAGE_EXECUTE            0x10
#define PAGE_EXECUTE_READ       0x20
#define PAGE_EXECUTE_READWRITE  0x40

enum VM_PROTECT_FLAG
{
  VMPF_NOACCESS  = 0x00000000,
  VMPF_CPU_READ  = 0x00000001,
  VMPF_CPU_WRITE = 0x00000002,
  VMPF_CPU_EXEC  = 0x00000004,
  VMPF_CPU_RW    = VMPF_CPU_READ | VMPF_CPU_WRITE,
  VMPF_CPU_RWX   = VMPF_CPU_READ | VMPF_CPU_WRITE | VMPF_CPU_EXEC,
};

inline uint32_t GetProtectFlag(VM_PROTECT_FLAG nOldFlag)
{
  uint32_t nNewFlag = 0;
  do
  {
    if (nOldFlag & VMPF_NOACCESS)
    {
      nNewFlag = PAGE_NOACCESS;
      break;
    }

    if (nOldFlag & VMPF_CPU_READ)
    {
      nNewFlag = PAGE_READONLY;
    }

    if (nOldFlag & VMPF_CPU_WRITE)
    {
      nNewFlag = PAGE_READWRITE;
    }

    if (nOldFlag & VMPF_CPU_EXEC)
    {
      nNewFlag = PAGE_EXECUTE_READWRITE;
    }

  } while (false);
  return nNewFlag;
}

Функция GetProtectFlag конвертирует флаг с правами доступа к файлу из одного формата в другой. Однако делает это некорректно. Программист не учёл, что значение VMPF_NOACCESS равно нулю. Из-за этого условие if (nOldFlag & VMPF_NOACCESS) всегда ложное и функция никогда не вернёт значение PAGE_NOACCESS.

Кроме того, функция GetProtectFlag неправильно конвертирует не только флаг VMPF_NOACCESS, но и другие. Например, флаг VMPF_CPU_EXEC будет сконвертирован во флаг PAGE_EXECUTE_READWRITE.

Когда я думал, как это исправить, первой мыслью было написать что-то в таком роде:

inline uint32_t GetProtectFlag(VM_PROTECT_FLAG nOldFlag)
{
  uint32_t nNewFlag = PAGE_NOACCESS;
  if (nOldFlag & VMPF_CPU_READ)
  {
    nNewFlag |= PAGE_READ;
  }

  if (nOldFlag & VMPF_CPU_WRITE)
  {
    nNewFlag |= PAGE_WRITE;
  }

  if (nOldFlag & VMPF_CPU_EXEC)
  {
    nNewFlag |= PAGE_EXECUTE;
  }

  return nNewFlag;
}

Однако в данном случае такой подход не работает. Дело в том, что PAGE_NOACCESS, PAGE_READONLY и остальные – это Windows-флаги со своей спецификой. Например, среди них нет флага PAGE_WRITE. Считается, что если есть права на запись, то как минимум есть права еще и на чтение. По тем же причинам нет флага PAGE_EXECUTE_WRITE.

Кроме того, побитовое «ИЛИ» двух Windows-флагов не даёт в результате флаг, соответствующий сумме прав: PAGE_READONLY | PAGE_EXECUTE != PAGE_EXECUTE_READ. Поэтому нужно перебирать все возможные комбинации флагов:

inline uint32_t GetProtectFlag(VM_PROTECT_FLAG nOldFlag)
{
  switch (nOldFlag)
  {
    case VMPF_NOACCESS:
      return PAGE_NOACCESS;
    case VMPF_CPU_READ:
      return PAGE_READONLY;
    case VMPF_CPU_WRITE: // same as ReadWrite
    case VMPF_CPU_RW:
      return PAGE_READWRITE;
    case VMPF_CPU_EXEC:
      return PAGE_EXECUTE;
    case VMPF_CPU_READ | VMPF_CPU_EXEC:
      return PAGE_EXECUTE_READ:
    case VMPF_CPU_WRITE | VMPF_CPU_EXEC: // same as ExecuteReadWrite
    case VMPF_CPU_RWX:
      return PAGE_EXECUTE_READWRITE;
    default:
      LOG("unknown PS4 flag");
      return PAGE_NOACCESS;
  }
}

Лишняя проверка

V547 [CWE-571] Expression ‘retAddress’ is always true. Memory.cpp 373

void* MemoryAllocator::allocateInternal(void* addrIn, size_t len,
                                        size_t alignment, int prot)
{
  // ....
  while (searchAddr < SCE_KERNEL_APP_MAP_AREA_END_ADDR)
    {
      // ....
      void* retAddress = VMAllocate(reinterpret_cast<void*>(regionAddress), len,
                                    plat::VMAT_RESERVE_COMMIT, uprot);
      if (!retAddress)
      {
        searchAddr = reinterpret_cast<size_t>(mi.pRegionStart) + mi.nRegionSize;
        continue;
      }
      // ....
      if (retAddress)
      {
        // unlikely
        plat::VMFree(retAddress);
      }
    // ....
    }
  // ....
}

В этом фрагменте кода указатель retAddress проверяется дважды. Сначала делается проверка if (!retAddress). Если указатель нулевой, то выполнение перейдёт к следующей итерации цикла while. Иначе указатель retAddress ненулевой. Поэтому вторая проверка if (retAddress) всегда истинная, и её можно убрать.

Ещё одно всегда истинное условие

V547 [CWE-571] Expression ‘pipeConfig == kPipeConfigP16’ is always true. GnmDepthRenderTarget.h 170

uint8_t getZReadTileSwizzleMask(void) const
    {
      // From IDA
      auto pipeConfig = getPipeConfig();
      auto zfmt       = getZFormat();
      auto tileMode   = getTileMode();
      if (pipeConfig != kPipeConfigP16 ||     // <=
        zfmt == kZFormatInvalid ||
        !GpuAddress::isMacroTiled(tileMode))
      {
        return 0;
      }

      auto     dataFormat          = DataFormat::build(zfmt);
      auto     totalBitsPerElement = dataFormat.getTotalBitsPerElement();
      uint32_t numFragments          = 1 << getNumFragments();
      uint32_t shift               = 0;
      NumBanks numBanks            = {};
      if (pipeConfig == kPipeConfigP16)      // <=
      {
        GpuAddress::getAltNumBanks(&numBanks, tileMode,
                                   totalBitsPerElement, numFragments);
        shift = 4;
      }
      else
      {
        GpuAddress::getNumBanks(&numBanks, tileMode,
                                totalBitsPerElement, numFragments);
        shift = 3;
      }

      return (this->m_regs[2] & (((1 << (numBanks + 1)) - 1) << shift)) >> 4;
    }

В данном фрагменте кода анализатор нашёл всегда истинное условие if (pipeConfig == kPipeConfigP16). Давайте разберёмся, почему это так.

Если вызов функции getPipeConfig вернул значение, не равное kPipeConfigP16, то первое условие будет верным и выполнение программы не достигнет проверки if (pipeConfig == kPipeConfigP16).

Получается, что вторая проверка этой переменной либо не выполняется, либо всегда истинна. Но не стоит спешить и убирать её. Возможно, первое условие было добавлено временно и будет убрано в дальнейшем.

Ошибка копипасты

V517 [CWE-570] The use of ‘if (A) {…} else if (A) {…}’ pattern was detected. There is a probability of logical error presence. Check lines: 469, 475. GnmGpuAddress.cpp 469

int32_t sce::GpuAddress::adjustTileMode(/* .... */)
{
switch(microTileMode)
{
  case Gnm::kMicroTileModeThin:
    if      (newArrayMode == Gnm::kArrayMode3dTiledThick)
      *outTileMode = Gnm::kTileModeThick_3dThick;
    else if      (newArrayMode == Gnm::kArrayMode2dTiledThick)
      *outTileMode = Gnm::kTileModeThick_2dThick;
    else if (newArrayMode == Gnm::kArrayMode1dTiledThick)
      *outTileMode = Gnm::kTileModeThick_1dThick;
    else if (newArrayMode == Gnm::kArrayMode3dTiledThin)
      *outTileMode = Gnm::kTileModeThin_3dThin; // ....
    else if (newArrayMode == Gnm::kArrayMode3dTiledThinPrt)
      *outTileMode = Gnm::kTileModeThin_3dThinPrt; // ....
    else if (newArrayMode == Gnm::kArrayMode2dTiledThin)                  // <=
      *outTileMode = Gnm::kTileModeThin_2dThin; // ....
    else if (newArrayMode == Gnm::kArrayMode2dTiledThinPrt)
      *outTileMode = Gnm::kTileModeThin_2dThinPrt; // ....
    else if (newArrayMode == Gnm::kArrayModeTiledThinPrt)
      *outTileMode = Gnm::kTileModeThin_ThinPrt; // ....
    else if (newArrayMode == Gnm::kArrayMode2dTiledThin)                  // <=
      *outTileMode = Gnm::kTileModeThin_2dThin;
    else if (newArrayMode == Gnm::kArrayMode1dTiledThin)
      *outTileMode = Gnm::kTileModeThin_1dThin;
    else
      break;
    return kStatusSuccess;
  // ....
}
}

Не обошлось и без ошибок копипасты. В данном фрагменте кода дважды написана одна и та же проверка newArrayMode == Gnm::kArrayMode2dTiledThin.

Сложно сказать, как именно нужно это поправить. Скорее всего, вторая проверка должна быть несколько другой. А может быть, она лишняя, и её можно убрать.

Почему лучше избегать сложных выражений?

V732 [CWE-480] Unary minus operator does not modify a bool type value. Consider using the ‘!’ operator. GnmRenderTarget.h 237

typedef enum RenderTargetChannelType
{
  kRenderTargetChannelTypeUNorm            = 0x00000000,
  kRenderTargetChannelTypeSNorm            = 0x00000001,
  kRenderTargetChannelTypeUInt             = 0x00000004,
  kRenderTargetChannelTypeSInt             = 0x00000005,
  kRenderTargetChannelTypeSrgb             = 0x00000006,
  kRenderTargetChannelTypeFloat            = 0x00000007,
} RenderTargetChannelType;

void setDataFormat(DataFormat format)
{
  // ....
  int v3;
  RenderTargetChannelType  type;  // [rsp+4h] [rbp-3Ch]
  __int64                  v9;  // [rsp+10h] [rbp-30h]
  bool typeConvertable = format.getRenderTargetChannelType(&type);
  v2 = type | kRenderTargetChannelTypeSNorm;
  v3  = (uint8_t) - (type < 7) & (uint8_t)(0x43u >> type) & 1; // <=
  // ....
}

Похоже, что программист ожидал следующего поведения при вычислении выражения:

  • пусть переменная type меньше 7;

  • тогда выражение type < 7 равно true;

  • затем к true применяется унарный минус, получается -1;

  • значение -1 приводится к unsigned char, получается 0b1111’1111.

Однако на самом деле происходит следующее:

  • пусть переменная type меньше 7;

  • тогда выражение type < 7 равно true;

  • затем к true применяется унарный минус, получается 1;

  • значение 1 приводится к unsigned char, получается 0b0000’0001.

Впрочем, следующая операция & 1 приводит к одному и тому же результату. По этой счастливой случайности, код целиком работает так, как ожидает программист. Тем не менее, стоит поправить этот код. Попробуем понять, какое значение присваивается переменной v3 в зависимости от значения type.

Первый случай: переменная type больше или равна 7.

  • Тогда выражение type < 7 равно false.

  • К false применяется унарный минус, получается false.

  • False приводится к unsigned char, получается 0b0000’0000.

  • Побитовое «И» с 0 всегда даёт 0, поэтому в результате получаем 0.

Второй случай: переменная type меньше 7.

  • Как уже выяснили раньше, выражение (uint8_t) — (type < 7) равно 1.

  • В данном случае есть смысл вычислять выражение 0x43u >> type.

  • Для удобства запишем бинарное представление числа 0x43 = 0b0100’0011.

  • Нас интересует только младший бит, потому что к результату выражения 0x43u >> type применится побитовое «И» с 1.

  • Если type равен 0, 1 или 6, то младший бит будет равен 1, и результат всего выражения будет 1. Во всех других случаях получится 0.

Итого, если type равен 0, 1 или 6, то в переменную v3 будет записано значение 1, а во всех остальных случаях – значение 0. Стоит заменить сложное выражение на более простое и понятное (type == 0) || (type == 1) || (type == 6). Предлагаю следующий код:

typedef enum RenderTargetChannelType
    {
      kRenderTargetChannelTypeUNorm            = 0x00000000,
      kRenderTargetChannelTypeSNorm            = 0x00000001,
      kRenderTargetChannelTypeUInt             = 0x00000004,
      kRenderTargetChannelTypeSInt             = 0x00000005,
      kRenderTargetChannelTypeSrgb             = 0x00000006,
      kRenderTargetChannelTypeFloat            = 0x00000007,
    } RenderTargetChannelType;

void setDataFormat(DataFormat format)
{
  // ....
  int v3;
  RenderTargetChannelType  type;  // [rsp+4h] [rbp-3Ch]
  __int64                  v9;  // [rsp+10h] [rbp-30h]
  bool typeConvertable = format.getRenderTargetChannelType(&type);
  v2                   = type | kRenderTargetChannelTypeSNorm;
  v3                   = (type == kRenderTargetChannelTypeUNorm)
                      || (type == kRenderTargetChannelTypeSNorm)
                      || (type == kRenderTargetChannelTypeSrgb);
  // ....
}

Здесь я также заменил числа 0, 1 и 6 на соответствующие именованные значения перечисления и записал подвыражения в табличном виде.

Краевой случай в операторе перемещения

V794 The assignment operator should be protected from the case of ‘this == &other’. VltShader.cpp 39

VltShaderConstData& VltShaderConstData::operator=(VltShaderConstData&& other)
{
  delete[] m_data;
  this->m_size = other.m_size;
  this->m_data = other.m_data;
  other.m_size = 0;
  other.m_data = nullptr;
  return *this;
}

Если при вызове такого оператора окажется, что ‘this == &other’, то все поля текущего объекта будут очищены и данные потеряются. Такое поведение некорректно, нужно добавить проверку. Исправленный код:

VltShaderConstData& VltShaderConstData::operator=(VltShaderConstData&& other)
{
  if (this == std::addressof(other))
  {
    return *this;
  }

  delete[] m_data;
  this->m_size = other.m_size;
  this->m_data = other.m_data;
  other.m_size = 0;
  other.m_data = nullptr;
  return *this;
}

Повторное присвоение как повод для рефакторинга

V1048 [CWE-1164] The ‘retVal’ variable was assigned the same value. Module.cpp 129

bool NativeModule::getSymbolInfo( /* .... */) const
{
  bool retVal = false;
  do
  {
    uint32_t modId = 0, libId = 0;
    if (modName == nullptr || libName == nullptr || nid == nullptr)
      {
        break;
      }
      if (!isEncodedSymbol(encSymbol))
      {
        *modName = "";
        *libName = "";
        *nid     = 0;
        retVal   = true;
        break;
      }
      retVal = decodeSymbol(encSymbol, &modId, &libId, nid);
      if (!retVal)
      {
        LOG_ERR("fail to decode encoded symbol");
        break;
      }
      retVal = getModNameFromId(modId, mods, modName);
      if (!retVal)
      {
        LOG_ERR("fail to get module name for symbol: %s in %s",
        encSymbol.c_str(), fileName.c_str());
        break;
      }
      retVal = getLibNameFromId(libId, libs, libName);
      if (!retVal)
      {
        LOG_ERR("fail to get library name");
        break;
      }
      retVal = true;                                                      // <=
    } while (false);
  return retVal;
}

В данном фрагменте кода есть повторное присваивание значения true в переменную retVal. Давайте разберемся, почему так происходит. Для этого рассмотрим все возможные модификации переменной retVal до присваивания, указанного анализатором.

  • Переменная retVal инициализируется значением false.

  • Если вызов функции isEncodedSymbol вернул false, то переменной retVal присваивается значение true и прерывается цикл do-while.

  • Переменной retVal присваивается результат вызова функции decodeSymbol. После этого если retVal == false, то цикл do-while прерывается.

  • То же самое происходит и с двумя вызовами функции getModNameFromId. Если любой из вызовов вернёт false, то цикл do-while прерывается.

Заметим, что если цикл do-while был досрочно прерван, то и указанное анализатором присваивание не выполнится. Это значит, подозрительное присваивание retVal = true будет выполнено, только если все рассмотренные выше вызовы функций вернули true. Поэтому переменная retVal уже равна true, и присваивание не имеет смысла.

А зачем вообще использовать конструкцию ‘do … while(false)‘? Дело в том, что такая конструкция позволяет сделать ранний выход из функции с одним return. К функциям с одним return, в свою очередь, с большей вероятностью будет применена NRVO – named return value optimization. Эта оптимизация компилятора позволяет избежать лишнего копирования или перемещения возвращаемого объекта, конструируя его сразу на месте вызова функции. В данном случае функция возвращает легковесный тип bool, поэтому выигрыш от NRVO будет незначительным. Кроме того, современные компиляторы умеют применять NRVO и к функциям с несколькими return, если во всех return возвращается один и тот же объект.

Метод getSymbolInfo не содержит ошибки и работает так, как задумал программист. Однако стоит провести рефакторинг метода getSymbolInfo и убрать цикл do-while вместе с переменной retVal. Предлагаю следующий код:

bool NativeModule::getSymbolInfo( /* .... */) const
{
  uint32_t modId = 0, libId = 0;
  if (modName == nullptr || libName == nullptr || nid == nullptr)
  {
    return false;
  }

  if (!isEncodedSymbol(encSymbol))
  {
    *modName = "";
    *libName = "";
    *nid     = 0;
    return true;
  }

  if (!decodeSymbol(encSymbol, &modId, &libId, nid))
  {
    LOG_ERR("fail to decode encoded symbol");
    return false;
  }

  if (!getModNameFromId(modId, mods, modName))
  {
    LOG_ERR("fail to get module name for symbol: %s in %s",
    encSymbol.c_str(), fileName.c_str());
    return false;
  }

  if (!getLibNameFromId(libId, libs, libName))
  {
    LOG_ERR("fail to get library name");
    return false;
  }

  return true;
}

Здесь я сделал следующее:

  • убрал цикл do-while и лишнюю переменную retVal;

  • каждую проверку переменной retVal заменил на проверку результата вызова соответствующей функции;

  • каждый break цикла do-while заменил на возврат нужного значения true / false. Какое именно значение вернуть, знаем из анализа переменной retVal, который провели ранее.

На мой взгляд, такой код проще читать и поддерживать.

Заключение

Конечно, это не все ошибки и недочёты, которые мы нашли в GPCS4. Некоторые случаи было довольно сложно описать, поэтому я не включил их в статью.

Мы желаем разработчикам GPCS4 успехов в дальнейшем развитии эмулятора и рекомендуем проверить текущую версию проекта анализатором PVS-Studio. Для этого достаточно скачать дистрибутив анализатора и запросить бесплатную лицензию для Open Source проектов. Если вас заинтересовал статический анализ в целом и PVS-Studio в частности – самое время попробовать. Вы можете проверить GPCS4 вслед за нами, а можете проверить свой собственный проект:) Спасибо за внимание!

Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку Checking the GPCS4 emulator: will we ever be able to play «Bloodborne» on PC?

Понравилась статья? Поделить с друзьями:
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
  • Ru windows 7 professional with sp1 x86 dvd u 677084 iso
  • Desktop clock plus 7 для windows 10
  • Настройка windows server 2022 rdp без домена
  • Rocket chat сервер для windows
  • Как сделать снимок экрана на ноутбуке леново windows 10