Native Bloodborne loader with HLE runtime, shadPS4 video core and performance work

Includes FPS unlock patches, AudioOut pacing fix, rwlock memory table,
threaded Vulkan command recording and front-end caches.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
deadinside
2026-09-25 10:20:27 +03:00
co-authored by Claude Opus 5.5
commit 6849d33cf1
596 changed files with 240354 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
out/
__pycache__/
user/
*.mp4
+428
View File
@@ -0,0 +1,428 @@
# Bloodborne: первый эксперимент нативного запуска
## Частота кадров, звук, диагностика (2026-09-25)
- **FPS**: `BB_FPS=uncap` (по умолчанию) применяет патч «Uncap FPS++»
(Lance McDonald, Kyo) из `patches/Bloodborne.xml`: интервал флипа 1 и
шаги симуляции от реального дельтатайма кадра. Vblank при этом следует за
частотой монитора (`BB_VBLANK_HZ=0`). `BB_FPS=60`/`90` — патчи с
фиксированным шагом, `BB_FPS=30` — без патчей. Дополнительные патчи из XML:
`BB_PATCHES="Skip Intro;Disable Motion Blur (perf increase)"`.
`patches.py` компилирует их в `out/patches.bin`; адреса в XML — это
виртуальные адреса PS4 (база eboot 0x400000), загрузчик пишет их по
смещению `адрес-0x400000` после релокаций (`--patches`).
- **Звук**: `sceAudioOutOutput` теперь возвращает управление строго раз в
период буфера (как железо). Раньше вызовы шли пачками раз в квант PipeWire
(21 мс), поток вывода FMOD обгонял микшер и проигрывал незаписанные половины
512-кадровых блоков — щелчок каждые 10.7 мс.
- **Производительность потока GPU** (`shadPS4:GpuCommandProcessor` — узкое
место: ~97% одного ядра). Замер в одной сцене: 26.5 → 47 FPS.
- `runtime_memory.c`: таблица регионов под rwlock вместо рекурсивного
мьютекса (18% времени поток GPU ждал его), плюс потоковый кеш регионов,
проверяемый по счётчику поколений таблицы, — запросы видеоядра на каждый
draw идут без блокировки.
- `vk_scheduler.cpp`: `PopPendingOperations` на каждом draw делал ioctl
опроса timeline-семафора; теперь только при непустой очереди и не чаще
раза в 250 мкс.
- `fetch_shader.cpp`: разбор fetch-шейдера кешируется по адресу кода с
проверкой копии кода.
- `region_manager.h`: без изменённых страниц в диапазоне защита не
пересчитывается.
Профилирование без perf: загрузчик по SIGUSR2 печатает RIP, `rdi` (адрес
мьютекса при ожидании futex) и цепочку кадров потока; GPU-библиотека
собирается с `-fno-omit-frame-pointer`.
- **Диагностика**: `BB_FRAME_STATS=1` — FPS, худший кадр и время компиляции
шейдеров/пайплайнов раз в 5 с; `BB_AUDIO_STATS=1` — ритм и заполнение
очереди звука; `BB_AUDIO_DUMP=путь` — сырой PCM каждого порта.
## Текущее состояние (2026-09-25)
Оригинальный x86-64 код `eboot.bin` выполняется напрямую на Linux и проходит
глобальные конструкторы, инициализацию памяти и запуск собственной системы
потоков игры: создано 13 гостевых потоков (`FD4JobWorker:0`, `EzWorkPool_*` ×9,
`MOMainThread`, логгер). Игра выделяет ~4.8 ГиБ direct- и ~337 МиБ
flexible-памяти. Текущая остановка — `sceAjmInitialize` (аудиокодек ATRAC9),
caller `0xd18a6a`.
Изменения этого этапа:
- `runtime_memory.c` переписан: единый sparse memfd на весь direct-пул
(5056 МиБ, как `sceKernelGetDirectMemorySize` на PS4), таблица регионов
с разбиением, flexible memory (448 МиБ), `ReserveVirtualRange`, `Mprotect`,
`QueryMemoryProtection`, `VirtualQuery`, `GetDirectMemoryType`, `BatchMap(2)`,
частичные unmap/release, `MAP_FIXED`/`NO_OVERWRITE`, GPU-биты защиты.
- **Все видимые гостю адреса размещаются ниже 1 ТиБ**: образ, стеки потоков,
главный стек, трамплины (`runtime_low_map`), гостевая память
(`0x10_0000_0000..0xfc_0000_0000`), кучи host-объектов (non-PIE + `mallopt`).
Игра упаковывает указатели в 40 бит: например, lock по адресу `0x20368c0`
хранит владельца как `scePthreadSelf() & 0xffffffffff` и сравнивает с полным
значением. С хэндлами `0x7f…` это давало вечную взаимоблокировку.
- Watchdog (`--timeout`) печатает RIP и цепочку кадров всех потоков
(смещения относительно образа и `bb-probe` для `addr2line`).
`BB_TRACE_SEMA=1` печатает блокирующие ожидания семафоров.
`got_import.py <адрес GOT>` называет импорт за PLT-заглушкой.
- Сеть в режиме «кабель отключён»: HTTP-шаблоны/соединения/запросы создаются,
отправка возвращает сетевую ошибку; NpWebApi/Matching2/Signaling/Score —
«не в PSN»; голосовой чат — порты без звука.
Следующие подсистемы по порядку появления: Ajm (ATRAC9 через ffmpeg),
Fios2, SaveData, Rtc, Pad, AudioOut, AvPlayer, VideoOut и GnmDriver.
## Предыдущее описание
**Оригинальный x86-64 код `eboot.bin` выполняется непосредственно процессором
Linux и проходит начальный участок конструкторов.** Теперь он возвращается из
`_init_env`, регистрирует три обработчика `atexit` и 279 C++-деструкторов,
проходит 1416 guard инициализации, создаёт 1422 mutex, один rwlock и 17 семафоров,
выделяет/отображает 116 МиБ для игрового аллокатора.
Оригинальная `libc.prx` загружается вместе с eboot, её инициализатор возвращает 0,
конструктор `std::ios_base::Init` пройден. Следующая контролируемая остановка —
`sceSysmoduleLoadModule(0xb4)` (`g8cM39EUZ6o#M#N`, caller `0x2026149`).
Отдельный тест Vulkan выполняет команду заполнения буфера и проверяет
возвращённые 4096 байт. shadPS4 в этом эксперименте не запускается.
**Это ещё не рекомпиляция всей игры и не играбельный порт.** Нет окна игры,
меню, отрисовки игровых ресурсов или работающего игрового цикла. Vulkan пока
не соединён с графическими командами игры. Контролируемая остановка на очередном
неподдерживаемом импорте — измеримая граница прототипа; запуск игры не завершён.
## Запуск в текущем окружении
Из `game_files`:
```bash
bash native_probe/run.sh --software
```
Скрипт находит локальные Python, GCC и Vulkan SDK, подготавливает образ,
собирает загрузчик и запускает его. На этой машине инструменты найдены
в `/nix/store`; ничего скачивать или устанавливать не понадобилось.
`--software` выбирает Lavapipe и отключает неявные Vulkan layers для теста.
В проверенном окружении `/dev/dri` отсутствует, поэтому реальный GPU не проверен.
Ожидаемый результат:
```text
Vulkan: llvmpipe (LLVM 21.1.8, 256 bits); command submission + 4096-byte readback PASS
Mapped 91973152 bytes, 4 segments; applied 237083 relocations
Starting native libc.prx at image offset 0x56e0000; fallback exports=202
Runtime: application heap API registered
Native libc module initializer returned 0
Entering original x86-64 code at guest offset 0xa0
Runtime: _init_env returned (verified libc implementation: RET)
Runtime: direct allocation offset=0x0, size=121634816 bytes
Runtime: mapped direct memory, size=121634816, alignment=0x200000
Runtime: semaphore id=1, attr=2, initial=0, max=1
Runtime: semaphore id=2, attr=2, initial=0, max=1
Runtime: semaphore id=3, attr=2, initial=0, max=1
Runtime: semaphore id=4, attr=2, initial=0, max=1
Runtime: semaphore id=5, attr=2, initial=0, max=1
Runtime: semaphore id=6, attr=2, initial=0, max=1
Runtime: semaphore id=7, attr=2, initial=0, max=1
Runtime: semaphore id=8, attr=2, initial=0, max=1
Runtime: semaphore id=9, attr=2, initial=0, max=1
Runtime: semaphore id=10, attr=2, initial=0, max=1
Runtime: semaphore id=11, attr=2, initial=0, max=1
Runtime: semaphore id=12, attr=2, initial=0, max=1
Runtime: semaphore id=13, attr=2, initial=0, max=1
Runtime: semaphore id=14, attr=2, initial=0, max=1
Runtime: semaphore id=15, attr=2, initial=0, max=1
Runtime: semaphore id=16, attr=2, initial=0, max=1
Runtime: semaphore id=17, attr=2, initial=0, max=1
STOP: first unsupported PS4 import: g8cM39EUZ6o#M#N (index 249)
API: sceSysmoduleLoadModule
Caller return offset: 0x2026149; first argument: 0xb4
Runtime: _init_env=1, atexit=3, __cxa_atexit=279, registered handlers=282
Runtime: static guards acquired=1416, released=1416
Runtime: mutexes created=1422, locks=3208, unlocks=3208
Runtime: rwlocks created=1, reads=21, writes=1, unlocks=22
Runtime: semaphores created=17, deleted=0, acquired=0, signals=0, timeouts=0
Runtime: direct memory allocations=1, maps=1, live=121634816, budget=536870912
Runtime: memory operations=9884
Original guest entry instructions executed; game initialization is incomplete.
```
**Код завершения 20 означает ожидаемую остановку на отсутствующей реализации
PS4 API.** Он намеренно отличается от 0. Ошибки загрузки возвращают 1;
явно неподдерживаемые режимы runtime — 21, срабатывание stack protector — 22.
Linux-сигналы — 128 + номер сигнала. На выполнение гостевого кода в Linux
установлен таймер 10 секунд.
На системе с доступным GPU можно попробовать `bash native_probe/run.sh`.
Это использует обычный выбор устройства Vulkan; аппаратный результат здесь
не проверен. Для повторного запуска уже собранных файлов:
```bash
native_probe/out/bb-probe native_probe/out/boot-libc.bin --cpu-only
native_probe/out/bb-probe --vulkan-only
```
Базовый образ без нативной libc сохраняется в `out/boot.bin` для сравнения
с прежней остановкой на `std::ios_base::Init`.
Из каталога `native_probe` команда остаётся прежней: `bash run.sh --software`.
Для воспроизведения самой ранней остановки на `_init_env` используйте
`out/bb-probe out/boot.bin --cpu-only --strict-imports` из этого каталога.
Последняя команда использует обычные настройки Vulkan. Для программного
драйвера задайте `VK_DRIVER_FILES` на его JSON и
`VK_LOADER_LAYERS_DISABLE='~implicit~'`, как делает `run.sh --software`.
## Что найдено в файлах
| Параметр | Результат |
|---|---|
| Игра | Bloodborne, CUSA03173 |
| Версия из `param.sfo` | APP_VER 01.09, VERSION 01.00 |
| Контейнер | SELF, 8 записей сегментов |
| CPU | x86-64, ELF64 little-endian, PS4/FreeBSD ABI |
| Тип ELF | ET_SCE_DYNEXEC (`0xfe10`) |
| Точка входа | `0xa0` относительно базы загрузки |
| Загружаемые сегменты | 2: код/данные только для чтения и изменяемые данные/BSS |
| Образ в памяти | 91 042 036 байт |
| Импорты | 686 уникальных импортированных символов |
| Зависимости DT_NEEDED | 42 модуля |
| Перемещения адресов | 231 478 RELATIVE, 2 838 ABS64, 23 GLOB_DAT, 661 JUMP_SLOT |
| Ресурсы `dvdroot_ps4` | 31 322 968 005 байт |
Среди зависимостей: libc, libkernel, GnmDriver, VideoOut, Pad, Fios2,
AudioOut, SaveData, Sysmodule, сетевые и диалоговые API. В `sce_module`
имеются только семь файлов; наличие файлов модулей не заменяет поддержку их ABI.
Используемые сегменты SELF доступны открытым текстом. Извлечение не выполняет
расшифровку. Неподдерживаемые сжатые/зашифрованные сегменты отклоняются.
Один незагружаемый сегмент метаданных (`program header 9`, тип `0x6fffff01`)
не представлен в доступных SELF-сегментах. В диагностическом `eboot.elf` его
диапазон заполнен нулями: **это не побайтово полное восстановление ELF**.
Все сегменты, нужные этому эксперименту, проверены на наличие.
Подробности, SHA-256 исходного eboot, список зависимостей, NID, сегментов
и количество ресурсов по каталогам находятся в `out/analysis.json`.
Имена нескольких импортов проверены вычислением NID из предполагаемых имён;
результаты записаны в `import_name_hints`. Для первого вызова `_init_env`
получен NID `bzQExy189ZI`, следующий вызов соответствует `atexit`.
## Как устроен прототип
`prepare.py` читает SELF и SFO, проверяет границы, восстанавливает доступные
сегменты, разбирает PS4 dynamic/symbol/RELA-таблицы и формирует `out/boot.bin`.
Исходные файлы открываются только для чтения. Все результаты — в `out/`.
Дополнительно проверяется фактический экспорт `_init_env` в `sce_module/libc.prx`:
NID `bzQExy189ZI`, адрес `0x5fef0`, размер 1, байт `c3` (RET).
SHA-256 libc и доказательство записываются в `analysis.json/libc_evidence`.
Первоначальное предположение о сложной инициализации внутри `_init_env`
для этого дампа не подтвердилось. Возврат соответствует реальному коду функции.
Формат `BBPROBE2` содержит флаг этой проверки; без флага runtime отключён.
Старые образы `BBPROBE1` читаются с отключённым runtime.
`link_libc.py` создаёт отдельный `out/boot-libc.bin` формата `BBPROBE3`:
добавляет два сегмента оригинальной libc, её перемещения адресов и 202
резервные привязки экспортов. Сопоставляет NID вместе с именами и версиями
библиотеки/модуля: локальные суффиксы libc `#C#A` и eboot `#q#q` различаются.
Реализованные host-контракты имеют приоритет. Остальные совпавшие импорты
ссылаются на настоящий код/данные libc, включая объекты и таблицы C++.
Исходные файлы игры не изменяются; сведения о привязках и SHA-256 libc
записываются в `out/libc-link.json`.
Перед входом в eboot выполняется `DT_INIT` libc. Поддержан TLS этой библиотеки:
модуль 2, шаблон 544 байта, блок 1184 байта на host-поток, обнулённый остаток;
14 перемещений `DTPMOD64` записывают номер модуля, а не указатель на образ.
`__tls_get_addr` проверяет модуль и границы. Полный TLS всех модулей PS4,
его освобождение при завершении гостевых потоков и прямой доступ через FS
не реализованы. Для текущего одного гостевого потока блок живёт до выхода.
`sceKernelGetProcParam` возвращает исходный перемещённый procparam eboot;
`_sceKernelRtldSetApplicationHeapAPI` сохраняет таблицу callbacks аллокатора.
`runtime_thread.c` реализует self и запросы init/get/getaffinity/destroy
атрибутов текущего гостевого потока. Для этого прототипа существует один
виртуальный CPU (маска 1); это не маска host CPU и не настройка его affinity.
Создание гостевых потоков и изменение атрибутов остаются неподдержанными.
`probe.c` выделяет память, копирует сегменты и применяет перемещения адресов.
Неизвестные функции направляются в исполняемые перехватчики, печатающие NID
и завершающие процесс. Неподдерживаемые импортированные данные направляются в недоступные
страницы: их использование вызывает ошибку, а не возвращает фиктивные данные.
После загрузки устанавливаются права страниц по ELF, код получает права RX.
Затем выполняется прямой вызов оригинальной точки входа по SysV ABI.
Подготовки аргументов достаточно для наблюдаемого участка; полноценный стек
запуска ядра PS4, исключения и общий механизм загрузки зависимых модулей
не реализованы; пока специально подключена только libc.
`runtime.c` реализует проверенный возврат из `_init_env`, реестр `atexit`/
`__cxa_atexit` с обратным порядком и фильтрацией DSO, `__cxa_finalize`,
`exit`, guard acquire/release/abort, случайный process-local stack canary,
аварийный обработчик повреждения стека, четыре функции работы с памятью и `strlen`.
Сопоставление выполняется по полному NID с library/module suffix именно
этого eboot, без автоматического «вернуть 0» для неизвестных импортов.
Реестр завершения и guards рассчитаны на текущий один гостевой поток;
рекурсивная/конкурентная инициализация guard останавливает выполнение с кодом 21.
`runtime_mutex.c` связывает PS4 mutex/атрибуты с Linux pthread, преобразует
типы и коды ошибок, поддерживает рекурсивные и статически созданные mutex.
Приоритетные протоколы, PS4-планировщик и гостевые потоки не реализованы.
Opaque handles предназначены для реализованных API; двоичный layout
внутренних PS4 mutex не воспроизводится. Наблюдаемый участок libc работает
через эти API; прямой доступ к внутренностям mutex потребует отдельной реализации.
POSIX-импорты mutex из `libScePosix` используют тот же backend через
отдельные wrappers: возвращают положительные PS4 errno, а не отрицательные
коды `scePthread`. Двухаргументный `pthread_mutex_init` не читает отсутствующий
третий аргумент. Linux errno не передаётся гостю напрямую.
`runtime_sema.c` реализует kernel create/delete, wait/poll, signal/cancel.
Handles — уникальные 32-битные ID; удалённые ID не переиспользуются.
Проверены счётчики и переполнение, таймаут в микросекундах с монотонными
часами, резервирование токенов до пробуждения, отмена и удаление с ожидающими
host-потоками. Объект освобождается после возврата всех ожидающих вызовов.
В режиме FIFO обслуживаются подходящие запросы в порядке очереди; запрос
меньшего размера может пройти ожидающий запрос, для которого токенов ещё нет.
Это соответствует использованной справочной модели shadPS4; сопоставление
на реальной PS4 пока не выполнено.
Атрибуты 0/2 допускают создание и неблокирующие операции, но блокирующее
ожидание явно останавливает прототип (21): приоритетный PS4-планировщик ещё
отсутствует. Ненулевые option parameters также не реализованы. В текущем
запуске игра создала 17 семафоров с attr=2, initial=0, max=1, но пока не
выполняла wait/signal; эти операции проверены отдельными C-тестами.
`runtime_time.c` поддерживает POSIX `gettimeofday`: время берётся у host,
поля копируются в явные PS4 timeval/timezone. Проверены границы полей и
диапазон микросекунд. Неожиданная ошибка host API останавливает прототип:
полный POSIX errno/TLS bridge ещё не реализован.
`runtime_rwlock.c` поддерживает init/destroy, read/write, try-read/try-write,
timed-read/timed-write и unlock поверх Linux pthread. Реестр синхронизирует
инициализацию статических handles, отслеживает владельцев, читателей и
ожидающие вызовы: уничтожение занятой блокировки возвращает EBUSY,
чужой unlock — EPERM, таймаут — Orbis ETIMEDOUT. Ожидание не удерживает
mutex реестра. Проверена одновременная работа нескольких host-потоков,
но создание гостевых потоков пока отсутствует. Поддержаны только атрибуты
по умолчанию; нестандартные атрибуты останавливают прототип с кодом 21.
`runtime_memory.c` даёт **ограниченный CPU-пул 512 МиБ для исследования**.
Это выбранный бюджет прототипа, не объём RAM PS4. Выделение возвращает
виртуальный физический offset; Linux memfd обеспечивает реальное общее
хранилище для нескольких отображений и выравнивание 16 КиБ/2 МиБ.
Реализованы allocate/map, полное unmap и полное release. Поддержаны типы
памяти 0/3 для CPU, permissions read/write, flags=0, автоматический адрес;
иной режим останавливается с диагностикой. Частичные release/unmap пока
возвращают EINVAL. GPU coherency, резервирование фиксированных адресов,
полное адресное пространство PS4 и подключение этого пула к Vulkan отсутствуют.
`vulkan_smoke.c` создаёт instance/device/queue, отправляет `vkCmdFillBuffer`,
ставит барьер для чтения CPU, ожидает завершения и сравнивает все слова буфера.
Это проверка будущего графического backend, не переводчик GNM.
## Сборка на других системах
Linux x86-64: Python 3, GCC/Clang, Vulkan headers и loader. Обычно достаточно:
```bash
python3 native_probe/prepare.py CUSA03173
python3 native_probe/link_libc.py CUSA03173
bash native_probe/build.sh
```
Скрипт поддерживает `CC`, `VULKAN_INCLUDE`, `VULKAN_LIB`;
при наличии `pkg-config vulkan` использует его настройки.
Для Windows в исходнике есть ветка `VirtualAlloc`/`VirtualProtect` и явный
SysV ABI. Предполагается **MinGW-w64 GCC/Clang x86-64**, не MSVC.
Эта ветка **не собрана и не проверена** в текущем Linux-окружении.
Пример ручной сборки в окружении MinGW с установленным Vulkan SDK:
```text
python native_probe/prepare.py CUSA03173
gcc -std=c11 -O2 -Wall -Wextra native_probe/probe.c native_probe/runtime.c native_probe/runtime_mutex.c native_probe/runtime_thread.c native_probe/runtime_rwlock.c native_probe/runtime_sema.c native_probe/runtime_time.c native_probe/runtime_memory.c native_probe/vulkan_smoke.c -I<SDK>/Include -L<SDK>/Lib -lvulkan-1 -o native_probe/out/bb-probe.exe
native_probe/out/bb-probe.exe native_probe/out/boot.bin
```
Пути SDK заменяются фактическими; бинарник Linux непереносим на Windows.
Диагностика сигналов и таймер в текущем исходнике реализованы только для Linux.
Новые mutex/rwlock/semaphore/time/direct-memory backends также реализованы только для Linux;
Windows дойдёт до более раннего неподдерживаемого импорта.
## Проверки
```bash
bash native_probe/build.sh --test
python3 -m unittest discover -s native_probe -v
```
Пройдено 35 Python-тестов, включая запуск набора C-проверок runtime:
границы SELF/образа, проверка инструкции `_init_env`, прямое исполнение
x86-64, перемещения адресов, режим strict, отключение неподтверждённого runtime,
диагностика сбоев. C-проверки охватывают обратный порядок деструкторов,
DSO и повторную финализацию, регистрацию при финализации и рост реестра,
guard acquire/abort/release, ошибки и рекурсивность mutex, выравнивание,
непересечение физических выделений, shared aliasing и повторное выделение
очищенной памяти. Stack protector и рекурсивный guard проверены в subprocess.
Дополнительно проверены жизненный цикл rwlock, повторные read-захваты,
чужой unlock, занятое уничтожение, несколько одновременных читателей,
ожидающий writer, преобразование таймаутов и `strlen` с NUL внутри строки.
Добавлены проверки нативного инициализатора/экспорта, приоритета host API,
отключения libc в strict-режиме, прав исполнения и границ новых metadata,
совпадения идентичности при разных локальных ID, отказа при другой версии
библиотеки и обработки TLS-релокаций. C-тест проверяет отдельные TLS-блоки
в host-потоках, шаблон/BSS, запросы атрибутов и регистрацию heap API.
Отдельный `sema-test` проверяет 32-битную запись handle без порчи соседнего
поля, устаревшие ID, переполнение, таймаут, очередь из нескольких host-потоков,
резервирование токенов, отмену/удаление с ожидающими и явный отказ от
приоритетного блокирующего ожидания. Проверены положительные ошибки POSIX
mutex, рекурсивный захват и выходные структуры `gettimeofday`.
Linux-сборка выполнена с `-Wall -Wextra -Werror`.
Актуальный интеграционный запуск настоящего eboot записан в `out/run.log`;
результат старого режима — в `out/strict-run.log`.
## Следующие этапы настоящего порта
1. Реализовать загрузку/реестр системных модулей и необходимые контракты
AppContent. Игра вызвала `sceSysmoduleLoadModule(0xb4)` из `0x2026149`;
по справочной таблице этот ID означает `libSceAppContent`. Такого файла
среди семи локальных `sce_module/*.prx` нет. Потребуется реализация
используемых API и состояния модуля; простой возврат успеха не добавлен.
2. Продолжить конструкторы, дополнить память/синхронизацию, затем реализовать
TLS остальных модулей, запуск потоков и загрузку необходимых модулей.
3. Дойти до `main`, добавить файловую систему (`/app0` → каталог ресурсов)
и фиксировать каждый следующий неподдерживаемый контракт.
4. Перевести GNM/PM4-команды, форматы ресурсов и AMD GCN-шейдеры в Vulkan/SPIR-V;
затем связать VideoOut с окном и swapchain. Наличие Vulkan само по себе
не делает графический код PS4 совместимым.
5. Для статической рекомпиляции восстанавливать функции и контрольный поток,
таблицы виртуальных вызовов, исключения и ABI; проверять поведение сравнением
с исходным машинным кодом. На x86-64 прямое исполнение с совместимым runtime
уже позволяет исследовать запуск без предварительного перевода всего CPU-кода.
Приоритет следующего изменения — Sysmodule/AppContent и следующие конструкторы.
## Справочные источники
Формат SELF и правила соотнесения сегментов сверены с
[заголовками загрузчика shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/loader/elf.h)
и [загрузкой сегментов](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/loader/elf.cpp).
PS4 dynamic-таблицы и вычисление NID сверены с
[module.cpp](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/module.cpp),
контекст запуска — с [linker.cpp](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/linker.cpp).
Сигнатуры mutex и значения типов сверены с
[mutex.cpp](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/kernel/threads/mutex.cpp)
и [pthread.h](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/kernel/threads/pthread.h),
rwlock — с [rwlock.cpp](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/kernel/threads/rwlock.cpp),
сигнатуры direct memory — с
[memory.cpp](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/kernel/memory.cpp).
Семафоры сверены с
[semaphore.cpp](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/kernel/threads/semaphore.cpp),
структуры времени — с
[time.h](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/kernel/time.h).
Идентификатор AppContent — с
[sysmodule_table.h](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/sysmodule/sysmodule_table.h).
Новые контракты регистрации heap API и procparam сверены с
[linker.h](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/linker.h)
и [process.cpp](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/kernel/process.cpp),
атрибуты потоков — с
[pthread_attr.cpp](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/kernel/threads/pthread_attr.cpp).
Тело `_init_env` и устройство guards проверены дизассемблированием локальной libc.
Реализация прототипа написана отдельно; зависимости на исполняемый shadPS4 нет.
Каталог `out/` содержит производные данные игры, имеет `.gitignore` и не нужен
для распространения исходников инструмента.
+49
View File
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
set -euo pipefail
cd -- "$(dirname -- "$0")"
mkdir -p out
if [[ -z ${CC:-} ]]; then
CC=$(command -v cc || command -v gcc || true)
if [[ -z $CC ]]; then
for candidate in /nix/store/*-gcc-wrapper-*/bin/gcc; do
if [[ -x $candidate ]]; then CC=$candidate; break; fi
done
fi
fi
if [[ -z ${CC:-} ]]; then echo 'Install GCC/Clang or set CC.' >&2; exit 1; fi
# Dependencies come from pkg-config (Vulkan loader/headers, SDL3). On NixOS the
# environment is provided by shell.nix; re-enter it automatically if needed.
if ! { command -v pkg-config >/dev/null && pkg-config --exists vulkan sdl3 && command -v cmake >/dev/null && command -v ninja >/dev/null; }; then
if [[ -z ${BB_IN_NIX_SHELL:-} ]] && command -v nix-shell >/dev/null; then
exec env BB_IN_NIX_SHELL=1 nix-shell shell.nix --run "bash build.sh $*"
fi
echo 'Need pkg-config with vulkan and sdl3, cmake and ninja (see shell.nix).' >&2; exit 1
fi
read -r -a includes <<< "$(pkg-config --cflags vulkan sdl3)"
read -r -a libraries <<< "$(pkg-config --libs vulkan sdl3)"
# GPU library (shadPS4 video core + drivers), built by CMake into out/gpu/libbbgpu.so.
cmake -S gpu -B out/gpu -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo >/dev/null
# A failed GPU build must stop here: an older libbbgpu.so would otherwise be used silently.
if ! ninja -C out/gpu bbgpu > out/gpu-build.log 2>&1; then
grep -v '^\[' out/gpu-build.log | tail -40 >&2
echo 'GPU library build failed (full log: out/gpu-build.log)' >&2; exit 1
fi
gpu=(-Lout/gpu -lbbgpu -Wl,-rpath,"$PWD/out/gpu" -rdynamic)
runtime=(runtime*.c)
# Third-party decoders: compiled once, without this project's -Werror policy.
atrac9=(third_party/LibAtrac9/C/src/*.c)
if [[ ! -f out/libatrac9.a || -n $(find third_party/LibAtrac9/C/src -newer out/libatrac9.a -name '*.c') ]]; then
rm -rf out/atrac9 && mkdir -p out/atrac9
for source in "${atrac9[@]}"; do "$CC" -std=c99 -O2 -g -w -c "$source" -o "out/atrac9/$(basename "${source%.c}").o"; done
ar rcs out/libatrac9.a out/atrac9/*.o
fi
"$CC" -std=c11 -O2 -g -Wall -Wextra -Werror -pthread -no-pie "${includes[@]}" probe.c "${runtime[@]}" vulkan_smoke.c out/libatrac9.a -lm "${gpu[@]}" "${libraries[@]}" -o out/bb-probe
echo "Built $PWD/out/bb-probe"
if [[ ${1:-} == --test ]]; then
"$CC" -std=c11 -O2 -g -Wall -Wextra -Werror -pthread test_runtime.c "${runtime[@]}" out/libatrac9.a -lm "${gpu[@]}" "${libraries[@]}" -o out/runtime-test
out/runtime-test
"$CC" -std=c11 -O2 -g -Wall -Wextra -Werror -pthread test_sema.c "${runtime[@]}" out/libatrac9.a -lm "${gpu[@]}" "${libraries[@]}" -o out/sema-test
out/sema-test
"$CC" -std=c11 -O2 -g -Wall -Wextra -Werror test_content.c runtime_content.c -o out/content-test
out/content-test
fi
+26
View File
@@ -0,0 +1,26 @@
"""Prepare explicit offline AppContent metadata; SKU is a probe setting, not a license check."""
import argparse
import json
import struct
from pathlib import Path
from prepare import sfo
def prepare(game, out, sku='full'):
values=sfo((game/'sce_sys/param.sfo').read_bytes())
params=[values.get(f'USER_DEFINED_PARAM_{i}',0) for i in range(1,5)]
if any(type(v) is not int or not 0<=v<=0xffffffff for v in params):
raise ValueError('invalid user-defined parameter')
profile=dict(title_id=values.get('TITLE_ID'),sku=sku,sku_source='explicit probe setting',
user_params=params, mounted_addons=[],boot_attr=0)
out.mkdir(parents=True,exist_ok=True)
(out/'content.bin').write_bytes(struct.pack('<8s5I',b'BBCONT01',{'full':3,'trial':1}[sku],*params))
(out/'content-profile.json').write_text(json.dumps(profile,indent=2)+'\n')
print(f'AppContent profile: SKU={sku} (probe setting), user params={params}, mounted add-ons=0')
if __name__=='__main__':
p=argparse.ArgumentParser(description=__doc__)
p.add_argument('game',type=Path)
p.add_argument('--out',type=Path,default=Path(__file__).parent/'out')
p.add_argument('--sku',choices=['full','trial'],default='full')
a=p.parse_args();prepare(a.game,a.out,a.sku)
+18
View File
@@ -0,0 +1,18 @@
"""Diagnostic: name the import bound to a GOT slot (guest offsets) in out/boot.bin."""
import re, struct, sys
from pathlib import Path
here = Path(__file__).parent
data = (here / 'out/boot.bin').read_bytes()
size, entry, ns, nr, ni, caps = struct.unpack_from('<QQQQQQ', data, 8)
pos = 56 + ns * 24
names = [data[pos + i * 128:pos + (i + 1) * 128].split(b'\0')[0].decode() for i in range(ni)]
pos += ni * 128
symbols = dict(re.findall(r'\{"([^"]+)","([^"]+)"\}', (here / 'import_names.inc').read_text()))
slots = {}
for i in range(nr):
target, kind, value, addend = struct.unpack_from('<QQqq', data, pos + i * 32)
if kind:
slots[target] = names[value]
for arg in sys.argv[1:]:
nid = slots.get(int(arg, 16))
print(arg, nid, symbols.get(nid) if nid else '')
+86
View File
@@ -0,0 +1,86 @@
# bbport GPU library: shadPS4 video core + shader recompiler + Gnm/VideoOut
# drivers, adapted to the native_probe runtime through shims in shim/.
cmake_minimum_required(VERSION 3.24)
project(bbgpu LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
if (NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE RelWithDebInfo)
endif()
# Frame pointers keep the loader's thread dumps (watchdog, SIGUSR2 sampling) walkable.
add_compile_options(-fno-omit-frame-pointer)
find_package(Vulkan REQUIRED)
find_package(fmt REQUIRED)
find_package(Boost REQUIRED)
find_package(magic_enum REQUIRED)
find_package(tsl-robin-map REQUIRED)
find_package(VulkanMemoryAllocator CONFIG REQUIRED)
find_package(PkgConfig REQUIRED)
pkg_check_modules(XXHASH REQUIRED IMPORTED_TARGET libxxhash)
pkg_check_modules(SDL3 REQUIRED IMPORTED_TARGET sdl3)
pkg_check_modules(X11 REQUIRED IMPORTED_TARGET x11)
find_package(miniz REQUIRED)
find_package(Zydis REQUIRED)
find_program(GLSLANG_COMPILER NAMES glslang glslangValidator REQUIRED)
set(SIRIT_USE_SYSTEM_SPIRV_HEADERS OFF CACHE BOOL "" FORCE)
add_subdirectory(third_party/sirit EXCLUDE_FROM_ALL)
# Host shaders -> headers (same scheme as shadPS4's host_shaders/CMakeLists.txt).
set(SHADER_SRC ${CMAKE_CURRENT_SOURCE_DIR}/shadps4/video_core/host_shaders)
set(SHADER_INCLUDE ${CMAKE_CURRENT_BINARY_DIR}/include)
set(SHADER_DIR ${SHADER_INCLUDE}/video_core/host_shaders)
set(SHADER_HEADERS "")
function(add_host_shader FILENAME SHADER_NAME)
set(OUT ${SHADER_DIR}/${SHADER_NAME}.h)
string(TOUPPER ${SHADER_NAME} SYMBOL)
set(DEFINES ${ARGN})
list(TRANSFORM DEFINES PREPEND "-D")
add_custom_command(OUTPUT ${OUT}
COMMAND ${CMAKE_COMMAND} -E make_directory ${SHADER_DIR}
COMMAND ${GLSLANG_COMPILER} --target-env vulkan1.3 -o ${OUT} --vn ${SYMBOL} ${DEFINES} ${SHADER_SRC}/${FILENAME}
COMMAND ${CMAKE_COMMAND} "-DHEADER_FILE=${OUT}" -P ${SHADER_SRC}/AddShaderHeaderInclude.cmake
DEPENDS ${SHADER_SRC}/${FILENAME} VERBATIM)
set(SHADER_HEADERS ${SHADER_HEADERS} ${OUT} PARENT_SCOPE)
endfunction()
add_host_shader(color_to_ms_depth.frag color_to_ms_depth_frag)
add_host_shader(ms_image_blit.frag ms_image_blit_frag)
add_host_shader(ms_image_blit.frag ms_image_blit_msaa_frag SRC_MSAA=1)
add_host_shader(fault_buffer_process.comp fault_buffer_process_comp)
add_host_shader(fs_tri.vert fs_tri_vert)
add_host_shader(fsr.comp fsr_easu_comp SAMPLE_EASU=1)
add_host_shader(fsr.comp fsr_rcas_comp SAMPLE_RCAS=1)
add_host_shader(post_process.frag post_process_frag)
foreach(BPP IN ITEMS 8 16 32 64 96 128)
add_host_shader(tiling.comp tiling_micro_${BPP}_comp BITS_PER_PIXEL=${BPP} IS_MACRO_TILED=0)
add_host_shader(tiling.comp tiling_macro_${BPP}_comp BITS_PER_PIXEL=${BPP} IS_MACRO_TILED=1)
endforeach()
add_custom_target(host_shaders DEPENDS ${SHADER_HEADERS})
file(GLOB_RECURSE VIDEO_CORE CONFIGURE_DEPENDS shadps4/video_core/*.cpp)
file(GLOB_RECURSE RECOMPILER CONFIGURE_DEPENDS shadps4/shader_recompiler/*.cpp)
list(FILTER VIDEO_CORE EXCLUDE REGEX "renderdoc\\.cpp$")
set(COMMON
shadps4/common/decoder.cpp shadps4/common/error.cpp
shadps4/common/io_file.cpp shadps4/common/native_clock.cpp shadps4/common/number_utils.cpp
shadps4/common/path_util.cpp shadps4/common/rdtsc.cpp shadps4/common/spin_lock.cpp shadps4/common/recursive_lock.cpp
shadps4/common/string_util.cpp shadps4/common/thread.cpp shadps4/common/signal_context.cpp
shim/common/logging/log.cpp)
file(GLOB AVPLAYER CONFIGURE_DEPENDS shadps4/core/libraries/avplayer/*.cpp)
pkg_check_modules(FFMPEG REQUIRED IMPORTED_TARGET libavformat libavcodec libavutil libswscale libswresample)
file(GLOB SHIM_SOURCES CONFIGURE_DEPENDS shim/*.cpp shim/*/*.cpp)
add_library(bbgpu SHARED ${VIDEO_CORE} ${RECOMPILER} ${COMMON} ${SHIM_SOURCES}
shadps4/core/libraries/gnmdriver/gnmdriver.cpp
shadps4/core/libraries/videoout/driver.cpp shadps4/core/libraries/videoout/video_out.cpp
shadps4/core/libraries/kernel/equeue.cpp ${AVPLAYER}
shadps4/core/libraries/videodec/video_utils.cpp)
add_dependencies(bbgpu host_shaders)
target_include_directories(bbgpu PRIVATE shim shadps4 ${SHADER_INCLUDE} third_party/gcn/include third_party/half/include)
target_compile_definitions(bbgpu PRIVATE BOOST_ASIO_STANDALONE NDEBUG)
target_compile_options(bbgpu PRIVATE -Wno-deprecated-declarations -Wno-missing-field-initializers)
# Runtime symbols (runtime_memory_*, runtime_process_*) resolve against bb-probe.
target_link_options(bbgpu PRIVATE -Wl,--allow-shlib-undefined)
target_link_libraries(bbgpu PUBLIC Vulkan::Headers fmt::fmt Boost::headers magic_enum::magic_enum
tsl::robin_map GPUOpen::VulkanMemoryAllocator PkgConfig::XXHASH PkgConfig::SDL3 PkgConfig::X11 PkgConfig::FFMPEG miniz::miniz Zydis::Zydis sirit)
+5
View File
@@ -0,0 +1,5 @@
Vendored from https://github.com/shadps4-emu/shadPS4 commit b4e7ae776071ceb1dd35004577a8719481e1d258 (GPL-2.0-or-later).
Directories: src/video_core, src/shader_recompiler, src/common, src/core/libraries/{gnmdriver,videoout}, externals/gcn, externals/sirit, externals/half.
Also: src/core/libraries/kernel/{orbis_error,posix_error,time,process,equeue}.h, equeue.cpp, src/core/libraries/error_codes.h, src/core/platform.h, src/core/libraries/system/{userservice,systemservice}.h.
Also: src/core/libraries/avplayer (without the handle streamer), src/core/libraries/videodec/video_utils.{h,cpp}.
Local changes are marked with "bbport:" comments.
+34
View File
@@ -0,0 +1,34 @@
/* C interface of the GPU library (gpu/): shadPS4's Liverpool/Vulkan video core,
* GnmDriver, VideoOut and kernel event queues, adapted to the native loader. */
#ifndef BBGPU_H
#define BBGPU_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
const char *title; /* window title */
const char *serial; /* CUSA id, names the pipeline cache */
const char *user_dir; /* pipeline cache/logs directory */
uint32_t sdk_version; /* from the eboot's procparam */
uint32_t psf_attributes; /* param.sfo ATTRIBUTE */
int32_t width, height; /* initial window size */
} BbGpuConfig;
/* Registers kernel event queues (needed with or without graphics). */
void bbgpu_register_kernel(void);
/* Creates window, Vulkan device, presenter and GPU command processor. */
int bbgpu_init(const BbGpuConfig *config);
/* Function for an imported NID ("NID#lib#mod"), or 0 when the GPU library does not provide it. */
uintptr_t bbgpu_resolve(const char *scoped_nid);
/* Called first by the loader's SIGSEGV handler: 1 when a GPU page-tracking fault was handled. */
int bbgpu_handle_fault(void *ucontext, void *address);
/* Keyboard text entry through the game window (IME dialog). begin returns 0 when
* no window exists; poll returns 0 typing, 1 confirmed, 2 cancelled (UTF-8 text). */
int bbgpu_text_input_begin(const char *initial_utf8, const char *prompt_utf8);
int bbgpu_text_input_poll(char *out_utf8, uint64_t size);
/* Number of symbols registered by the vendored libraries (diagnostics). */
unsigned bbgpu_symbol_count(void);
#ifdef __cplusplus
}
#endif
#endif
+339
View File
@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
+37
View File
@@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#if __unix__
#include <pthread.h>
#endif
namespace Common {
#ifdef PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP
class AdaptiveMutex {
public:
AdaptiveMutex() = default;
~AdaptiveMutex() {
pthread_mutex_destroy(&mutex);
}
AdaptiveMutex(const AdaptiveMutex&) = delete;
AdaptiveMutex& operator=(const AdaptiveMutex&) = delete;
void lock() {
pthread_mutex_lock(&mutex);
}
void unlock() {
pthread_mutex_unlock(&mutex);
}
[[nodiscard]] bool try_lock() {
return pthread_mutex_trylock(&mutex) == 0;
}
private:
pthread_mutex_t mutex = PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP;
};
#endif // PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP
} // namespace Common
File diff suppressed because it is too large Load Diff
+59
View File
@@ -0,0 +1,59 @@
// SPDX-FileCopyrightText: 2014 Jannik Vogel <[email protected]>
// SPDX-License-Identifier: CC0-1.0
#pragma once
#include <cstddef>
#include <type_traits>
namespace Common {
template <typename T>
[[nodiscard]] constexpr T AlignUp(T value, std::size_t size) {
static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
auto mod{static_cast<T>(value % size)};
value -= mod;
return static_cast<T>(mod == T{0} ? value : value + size);
}
template <typename T>
[[nodiscard]] constexpr T AlignDown(T value, std::size_t size) {
static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
return static_cast<T>(value - value % size);
}
template <typename T>
[[nodiscard]] constexpr T AlignUpPow2(T value, T size) {
return static_cast<T>(value + (size - 1) & ~(size - 1));
}
template <typename T>
[[nodiscard]] constexpr T AlignDownPow2(T value, T size) {
return static_cast<T>(value & ~(size - 1));
}
template <typename T>
requires std::is_integral_v<T>
[[nodiscard]] constexpr bool IsAligned(T value, std::size_t alignment) {
return (value & (alignment - 1)) == 0;
}
template <typename T>
requires std::is_integral_v<T>
[[nodiscard]] constexpr bool Is16KBAligned(T value) {
return (value & 0x3FFF) == 0;
}
template <typename T>
requires std::is_integral_v<T>
[[nodiscard]] constexpr bool Is64KBAligned(T value) {
return (value & 0xFFFF) == 0;
}
template <typename T>
requires std::is_integral_v<T>
[[nodiscard]] constexpr bool Is2MBAligned(T value) {
return (value & 0x1FFFFF) == 0;
}
} // namespace Common
+10
View File
@@ -0,0 +1,10 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#if defined(__x86_64__) || defined(_M_X64)
#define ARCH_X86_64 1
#elif defined(__aarch64__) || defined(_M_ARM64)
#define ARCH_ARM64 1
#endif
+31
View File
@@ -0,0 +1,31 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/arch.h"
#include "common/assert.h"
#include "core/signals.h"
#include "emulator.h"
#if defined(ARCH_X86_64)
#define Crash() __asm__ __volatile__("int $3")
#elif defined(ARCH_ARM64)
#define Crash() __asm__ __volatile__("brk 0")
#else
#error "Missing Crash() implementation for target CPU architecture."
#endif
void assert_fail_impl() {
Core::Signals::Instance()->RemoveHandlers();
Common::Singleton<Core::Emulator>::Instance()->Shutdown();
Crash();
}
[[noreturn]] void unreachable_impl() {
assert_fail_impl();
throw std::runtime_error("Unreachable code");
}
void assert_fail_debug_msg(const char* msg) {
LOG_CRITICAL(Debug, "Assertion Failed!\n{}", msg);
assert_fail_impl();
}
+85
View File
@@ -0,0 +1,85 @@
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/logging/log.h"
// Sometimes we want to try to continue even after hitting an assert.
// However touching this file yields a global recompilation as this header is included almost
// everywhere. So let's just move the handling of the failed assert to a single cpp file.
void assert_fail_impl();
[[noreturn]] void unreachable_impl();
#ifdef _MSC_VER
#define SHAD_NO_INLINE __declspec(noinline)
#else
#define SHAD_NO_INLINE __attribute__((noinline))
#endif
#define ASSERT(_a_) \
do { \
if (!(_a_)) [[unlikely]] { \
LOG_CRITICAL(Debug, "Assertion Failed!"); \
assert_fail_impl(); \
} \
} while (false)
#define ASSERT_MSG(_a_, ...) \
do { \
if (!(_a_)) [[unlikely]] { \
LOG_CRITICAL(Debug, "Assertion Failed!\n" __VA_ARGS__); \
assert_fail_impl(); \
} \
} while (false)
#define UNREACHABLE() \
do { \
LOG_CRITICAL(Debug, "Unreachable code!"); \
unreachable_impl(); \
} while (0)
#define UNREACHABLE_MSG(...) \
do { \
LOG_CRITICAL(Debug, "Unreachable code!\n" __VA_ARGS__); \
unreachable_impl(); \
} while (0)
#ifdef _DEBUG
#define DEBUG_ASSERT(_a_) ASSERT(_a_)
#define DEBUG_ASSERT_MSG(_a_, ...) ASSERT_MSG(_a_, __VA_ARGS__)
#else // not debug
#define DEBUG_ASSERT(_a_) \
do { \
} while (0)
#define DEBUG_ASSERT_MSG(_a_, _desc_, ...) \
do { \
} while (0)
#endif
#define UNIMPLEMENTED() ASSERT_MSG(false, "Unimplemented code!")
#define UNIMPLEMENTED_MSG(...) ASSERT_MSG(false, __VA_ARGS__)
#define UNIMPLEMENTED_IF(cond) ASSERT_MSG(!(cond), "Unimplemented code!")
#define UNIMPLEMENTED_IF_MSG(cond, ...) ASSERT_MSG(!(cond), __VA_ARGS__)
// If the assert is ignored, execute _b_
#define ASSERT_OR_EXECUTE(_a_, _b_) \
do { \
ASSERT(_a_); \
if (!(_a_)) [[unlikely]] { \
_b_ \
} \
} while (0)
// If the assert is ignored, execute _b_
#define ASSERT_OR_EXECUTE_MSG(_a_, _b_, ...) \
do { \
ASSERT_MSG(_a_, __VA_ARGS__); \
if (!(_a_)) [[unlikely]] { \
_b_ \
} \
} while (0)
+432
View File
@@ -0,0 +1,432 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <cstddef>
#include "common/types.h"
#ifdef __AVX2__
#define BIT_ARRAY_USE_AVX
#include <immintrin.h>
#endif
namespace Common {
template <size_t N>
class BitArray {
static_assert(N % 64 == 0, "BitArray size must be a multiple of 64 bits.");
static constexpr size_t BITS_PER_WORD = 64;
static constexpr size_t WORD_COUNT = N / BITS_PER_WORD;
static constexpr size_t WORDS_PER_AVX = 4;
static constexpr size_t AVX_WORD_COUNT = WORD_COUNT / WORDS_PER_AVX;
public:
using Range = std::pair<size_t, size_t>;
class Iterator {
public:
explicit Iterator(const BitArray& bit_array_, u64 start) : bit_array(bit_array_) {
range = bit_array.FirstRangeFrom(start);
}
Iterator& operator++() {
range = bit_array.FirstRangeFrom(range.second);
return *this;
}
bool operator==(const Iterator& other) const {
return range == other.range;
}
bool operator!=(const Iterator& other) const {
return !(*this == other);
}
const Range& operator*() const {
return range;
}
const Range* operator->() const {
return &range;
}
private:
const BitArray& bit_array;
Range range;
};
using const_iterator = Iterator;
using iterator_category = std::forward_iterator_tag;
using value_type = Range;
using difference_type = std::ptrdiff_t;
using pointer = const Range*;
using reference = const Range&;
BitArray() = default;
BitArray(const BitArray& other) = default;
BitArray& operator=(const BitArray& other) = default;
BitArray(BitArray&& other) noexcept = default;
BitArray& operator=(BitArray&& other) noexcept = default;
~BitArray() = default;
BitArray(const BitArray& other, size_t start, size_t end) {
if (start >= end || end > N) {
return;
}
const size_t first_word = start / BITS_PER_WORD;
const size_t last_word = (end - 1) / BITS_PER_WORD;
const size_t start_bit = start % BITS_PER_WORD;
const size_t end_bit = (end - 1) % BITS_PER_WORD;
const u64 start_mask = ~((1ULL << start_bit) - 1);
const u64 end_mask = end_bit == BITS_PER_WORD - 1 ? ~0ULL : (1ULL << (end_bit + 1)) - 1;
if (first_word == last_word) {
data[first_word] = other.data[first_word] & (start_mask & end_mask);
} else {
data[first_word] = other.data[first_word] & start_mask;
size_t i = first_word + 1;
#ifdef BIT_ARRAY_USE_AVX
for (; i + WORDS_PER_AVX <= last_word; i += WORDS_PER_AVX) {
const __m256i current =
_mm256_loadu_si256(reinterpret_cast<const __m256i*>(&other.data[i]));
_mm256_storeu_si256(reinterpret_cast<__m256i*>(&data[i]), current);
}
#endif
for (; i < last_word; ++i) {
data[i] = other.data[i];
}
data[last_word] = other.data[last_word] & end_mask;
}
}
BitArray(const BitArray& other, const Range& range)
: BitArray(other, range.first, range.second) {}
const_iterator begin() const {
return Iterator(*this, 0);
}
const_iterator end() const {
return Iterator(*this, N);
}
inline constexpr void Set(size_t idx) {
data[idx / BITS_PER_WORD] |= (1ULL << (idx % BITS_PER_WORD));
}
inline constexpr void Unset(size_t idx) {
data[idx / BITS_PER_WORD] &= ~(1ULL << (idx % BITS_PER_WORD));
}
inline constexpr bool Get(size_t idx) const {
return (data[idx / BITS_PER_WORD] & (1ULL << (idx % BITS_PER_WORD))) != 0;
}
inline void SetRange(size_t start, size_t end) {
if (start >= end || end > N) {
return;
}
const size_t first_word = start / BITS_PER_WORD;
const size_t last_word = (end - 1) / BITS_PER_WORD;
const size_t start_bit = start % BITS_PER_WORD;
const size_t end_bit = (end - 1) % BITS_PER_WORD;
const u64 start_mask = ~((1ULL << start_bit) - 1);
const u64 end_mask = end_bit == BITS_PER_WORD - 1 ? ~0ULL : (1ULL << (end_bit + 1)) - 1;
if (first_word == last_word) {
data[first_word] |= start_mask & end_mask;
} else {
data[first_word] |= start_mask;
size_t i = first_word + 1;
#ifdef BIT_ARRAY_USE_AVX
const __m256i value = _mm256_set1_epi64x(-1);
for (; i + WORDS_PER_AVX <= last_word; i += WORDS_PER_AVX) {
_mm256_storeu_si256(reinterpret_cast<__m256i*>(&data[i]), value);
}
#endif
for (; i < last_word; ++i) {
data[i] = ~0ULL;
}
data[last_word] |= end_mask;
}
}
inline void UnsetRange(size_t start, size_t end) {
if (start >= end || end > N) {
return;
}
size_t first_word = start / BITS_PER_WORD;
const size_t last_word = (end - 1) / BITS_PER_WORD;
const size_t start_bit = start % BITS_PER_WORD;
const size_t end_bit = (end - 1) % BITS_PER_WORD;
const u64 start_mask = (1ULL << start_bit) - 1;
const u64 end_mask = end_bit == BITS_PER_WORD - 1 ? 0ULL : ~((1ULL << (end_bit + 1)) - 1);
if (first_word == last_word) {
data[first_word] &= start_mask | end_mask;
} else {
data[first_word] &= start_mask;
size_t i = first_word + 1;
#ifdef BIT_ARRAY_USE_AVX
const __m256i value = _mm256_setzero_si256();
for (; i + WORDS_PER_AVX <= last_word; i += WORDS_PER_AVX) {
_mm256_storeu_si256(reinterpret_cast<__m256i*>(&data[i]), value);
}
#endif
for (; i < last_word; ++i) {
data[i] = 0ULL;
}
data[last_word] &= end_mask;
}
}
inline constexpr void SetRange(const Range& range) {
SetRange(range.first, range.second);
}
inline constexpr void UnsetRange(const Range& range) {
UnsetRange(range.first, range.second);
}
inline constexpr void Clear() {
data.fill(0);
}
inline constexpr void Fill() {
data.fill(~0ULL);
}
inline constexpr bool None() const {
u64 result = 0;
for (const auto& word : data) {
result |= word;
}
return result == 0;
}
inline constexpr bool Any() const {
return !None();
}
inline constexpr bool AnyInRange(size_t start, size_t end) const {
const size_t first_word = start / BITS_PER_WORD;
const size_t last_word = (end - 1) / BITS_PER_WORD;
const size_t start_bit = start % BITS_PER_WORD;
const size_t end_bit = (end - 1) % BITS_PER_WORD;
const u64 start_mask = ~((1ULL << start_bit) - 1);
const u64 end_mask = end_bit == BITS_PER_WORD - 1 ? ~0ULL : (1ULL << (end_bit + 1)) - 1;
if (first_word == last_word) {
return data[first_word] & (start_mask & end_mask);
} else {
if (data[first_word] & start_mask) {
return true;
}
size_t i = first_word + 1;
for (; i < last_word; ++i) {
if (data[i]) {
return true;
}
}
if (data[last_word] & end_mask) {
return true;
}
}
return false;
}
Range FirstRangeFrom(size_t start) const {
if (start >= N) {
return {N, N};
}
const auto find_end_bit = [&](size_t word) {
#ifdef BIT_ARRAY_USE_AVX
const __m256i all_one = _mm256_set1_epi64x(-1);
for (; word + WORDS_PER_AVX <= WORD_COUNT; word += WORDS_PER_AVX) {
const __m256i current =
_mm256_loadu_si256(reinterpret_cast<const __m256i*>(&data[word]));
const __m256i cmp = _mm256_cmpeq_epi64(current, all_one);
if (_mm256_movemask_epi8(cmp) != 0xFFFFFFFF) {
break;
}
}
#endif
for (; word < WORD_COUNT; ++word) {
if (data[word] != ~0ULL) {
return (word * BITS_PER_WORD) + std::countr_one(data[word]);
}
}
return N;
};
const auto word_bits = [&](size_t index, u64 word) {
const int empty_bits = std::countr_zero(word);
const int ones_count = std::countr_one(word >> empty_bits);
const size_t start_bit = index * BITS_PER_WORD + empty_bits;
if (ones_count + empty_bits < BITS_PER_WORD) {
return Range{start_bit, start_bit + ones_count};
}
return Range{start_bit, find_end_bit(index + 1)};
};
const size_t start_word = start / BITS_PER_WORD;
const size_t start_bit = start % BITS_PER_WORD;
const u64 masked_first = data[start_word] & (~((1ULL << start_bit) - 1));
if (masked_first) {
return word_bits(start_word, masked_first);
}
size_t word = start_word + 1;
#ifdef BIT_ARRAY_USE_AVX
for (; word + WORDS_PER_AVX <= WORD_COUNT; word += WORDS_PER_AVX) {
const __m256i current =
_mm256_loadu_si256(reinterpret_cast<const __m256i*>(&data[word]));
if (!_mm256_testz_si256(current, current)) {
break;
}
}
#endif
for (; word < WORD_COUNT; ++word) {
if (data[word] != 0) {
return word_bits(word, data[word]);
}
}
return {N, N};
}
inline constexpr Range FirstRange() const {
return FirstRangeFrom(0);
}
Range LastRangeFrom(size_t end) const {
if (end == 0) {
return {0, 0};
}
if (end > N) {
end = N;
}
const auto find_start_bit = [&](size_t word) {
#ifdef BIT_ARRAY_USE_AVX
const __m256i all_zero = _mm256_setzero_si256();
for (; word >= WORDS_PER_AVX; word -= WORDS_PER_AVX) {
const __m256i current = _mm256_loadu_si256(
reinterpret_cast<const __m256i*>(&data[word - WORDS_PER_AVX]));
const __m256i cmp = _mm256_cmpeq_epi64(current, all_zero);
if (_mm256_movemask_epi8(cmp) != 0xFFFFFFFF) {
break;
}
}
#endif
for (; word > 0; --word) {
if (data[word - 1] != ~0ULL) {
return word * BITS_PER_WORD - std::countl_one(data[word - 1]);
}
}
return size_t(0);
};
const auto word_bits = [&](size_t index, u64 word) {
const int empty_bits = std::countl_zero(word);
const int ones_count = std::countl_one(word << empty_bits);
const size_t end_bit = index * BITS_PER_WORD - empty_bits;
if (empty_bits + ones_count < BITS_PER_WORD) {
return Range{end_bit - ones_count, end_bit};
}
return Range{find_start_bit(index - 1), end_bit};
};
const size_t end_word = ((end - 1) / BITS_PER_WORD) + 1;
const size_t end_bit = (end - 1) % BITS_PER_WORD;
u64 masked_last = data[end_word - 1];
if (end_bit < BITS_PER_WORD - 1) {
masked_last &= (1ULL << (end_bit + 1)) - 1;
}
if (masked_last) {
return word_bits(end_word, masked_last);
}
size_t word = end_word - 1;
#ifdef BIT_ARRAY_USE_AVX
for (; word >= WORDS_PER_AVX; word -= WORDS_PER_AVX) {
const __m256i current =
_mm256_loadu_si256(reinterpret_cast<const __m256i*>(&data[word - WORDS_PER_AVX]));
if (!_mm256_testz_si256(current, current)) {
break;
}
}
#endif
for (; word > 0; --word) {
if (data[word - 1] != 0) {
return word_bits(word, data[word - 1]);
}
}
return {0, 0};
}
inline constexpr Range LastRange() const {
return LastRangeFrom(N);
}
inline constexpr size_t Size() const {
return N;
}
inline constexpr BitArray& operator|=(const BitArray& other) {
for (size_t i = 0; i < WORD_COUNT; ++i) {
data[i] |= other.data[i];
}
return *this;
}
inline constexpr BitArray& operator&=(const BitArray& other) {
for (size_t i = 0; i < WORD_COUNT; ++i) {
data[i] &= other.data[i];
}
return *this;
}
inline constexpr BitArray& operator^=(const BitArray& other) {
for (size_t i = 0; i < WORD_COUNT; ++i) {
data[i] ^= other.data[i];
}
return *this;
}
inline constexpr BitArray operator|(const BitArray& other) const {
BitArray result = *this;
result |= other;
return result;
}
inline constexpr BitArray operator&(const BitArray& other) const {
BitArray result = *this;
result &= other;
return result;
}
inline constexpr BitArray operator^(const BitArray& other) const {
BitArray result = *this;
result ^= other;
return result;
}
inline constexpr BitArray operator~() const {
BitArray result = *this;
for (size_t i = 0; i < WORD_COUNT; ++i) {
result.data[i] = ~result.data[i];
}
return result;
}
inline constexpr bool operator==(const BitArray& other) const {
u64 result = 0;
for (size_t i = 0; i < WORD_COUNT; ++i) {
result |= data[i] ^ other.data[i];
}
return result == 0;
}
inline constexpr bool operator!=(const BitArray& other) const {
return !(*this == other);
}
private:
std::array<u64, WORD_COUNT> data{};
};
} // namespace Common
+167
View File
@@ -0,0 +1,167 @@
// SPDX-FileCopyrightText: 2014 Tony Wasserka
// SPDX-FileCopyrightText: 2014 Dolphin Emulator Project
// SPDX-License-Identifier: BSD-3-Clause AND GPL-2.0-or-later
#pragma once
#include <cstddef>
#include <limits>
#include <type_traits>
/*
* Abstract bitfield class
*
* Allows endianness-independent access to individual bitfields within some raw
* integer value. The assembly generated by this class is identical to the
* usage of raw bitfields, so it's a perfectly fine replacement.
*
* For BitField<X,Y,Z>, X is the distance of the bitfield to the LSB of the
* raw value, Y is the length in bits of the bitfield. Z is an integer type
* which determines the sign of the bitfield. Z must have the same size as the
* raw integer.
*
*
* General usage:
*
* Create a new union with the raw integer value as a member.
* Then for each bitfield you want to expose, add a BitField member
* in the union. The template parameters are the bit offset and the number
* of desired bits.
*
* Changes in the bitfield members will then get reflected in the raw integer
* value and vice-versa.
*
*
* Sample usage:
*
* union SomeRegister
* {
* u32 hex;
*
* BitField<0,7,u32> first_seven_bits; // unsigned
* BitField<7,8,u32> next_eight_bits; // unsigned
* BitField<3,15,s32> some_signed_fields; // signed
* };
*
* This is equivalent to the little-endian specific code:
*
* union SomeRegister
* {
* u32 hex;
*
* struct
* {
* u32 first_seven_bits : 7;
* u32 next_eight_bits : 8;
* };
* struct
* {
* u32 : 3; // padding
* s32 some_signed_fields : 15;
* };
* };
*
*
* Caveats:
*
* 1)
* BitField provides automatic casting from and to the storage type where
* appropriate. However, when using non-typesafe functions like printf, an
* explicit cast must be performed on the BitField object to make sure it gets
* passed correctly, e.g.:
* printf("Value: %d", (s32)some_register.some_signed_fields);
*
* 2)
* Not really a caveat, but potentially irritating: This class is used in some
* packed structures that do not guarantee proper alignment. Therefore we have
* to use #pragma pack here not to pack the members of the class, but instead
* to break GCC's assumption that the members of the class are aligned on
* sizeof(StorageType).
*/
#pragma pack(1)
template <std::size_t Position, std::size_t Bits, typename T>
struct BitField {
using Type = T;
// UnderlyingType is T for non-enum types and the underlying type of T if
// T is an enumeration. Note that T is wrapped within an enable_if in the
// former case to workaround compile errors which arise when using
// std::underlying_type<T>::type directly.
using UnderlyingType = typename std::conditional_t<std::is_enum_v<T>, std::underlying_type<T>,
std::enable_if<true, T>>::type;
// We store the value as the unsigned type to avoid undefined behaviour on value shifting
using StorageType = std::make_unsigned_t<UnderlyingType>;
/// Constants to allow limited introspection of fields if needed
static constexpr std::size_t position = Position;
static constexpr std::size_t bits = Bits;
static constexpr StorageType mask = (((StorageType)~0) >> (8 * sizeof(T) - bits)) << position;
/**
* Formats a value by masking and shifting it according to the field parameters. A value
* containing several bitfields can be assembled by formatting each of their values and ORing
* the results together.
*/
[[nodiscard]] static constexpr StorageType FormatValue(const T& value) {
return (static_cast<StorageType>(value) << position) & mask;
}
/**
* Extracts a value from the passed storage. In most situations prefer use the member functions
* (such as Value() or operator T), but this can be used to extract a value from a bitfield
* union in a constexpr context.
*/
[[nodiscard]] static constexpr T ExtractValue(const StorageType& storage) {
if constexpr (std::numeric_limits<UnderlyingType>::is_signed) {
std::size_t shift = 8 * sizeof(T) - bits;
return static_cast<T>(static_cast<UnderlyingType>(storage << (shift - position)) >>
shift);
} else {
return static_cast<T>((storage & mask) >> position);
}
}
// This constructor and assignment operator might be considered ambiguous:
// Would they initialize the storage or just the bitfield?
// Hence, delete them. Use the Assign method to set bitfield values!
BitField(T val) = delete;
BitField& operator=(T val) = delete;
constexpr BitField() noexcept = default;
constexpr BitField(const BitField&) noexcept = default;
constexpr BitField& operator=(const BitField&) noexcept = default;
constexpr BitField(BitField&&) noexcept = default;
constexpr BitField& operator=(BitField&&) noexcept = default;
[[nodiscard]] constexpr operator T() const {
return Value();
}
constexpr void Assign(const T& value) {
storage = (static_cast<StorageType>(storage) & ~mask) | FormatValue(value);
}
[[nodiscard]] constexpr T Value() const {
return ExtractValue(storage);
}
[[nodiscard]] constexpr explicit operator bool() const {
return Value() != 0;
}
private:
StorageType storage;
static_assert(bits + position <= 8 * sizeof(T), "Bitfield out of range");
// And, you know, just in case people specify something stupid like bits=position=0x80000000
static_assert(position < 8 * sizeof(T), "Invalid position");
static_assert(bits <= 8 * sizeof(T), "Invalid number of bits");
static_assert(bits > 0, "Invalid number of bits");
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable in a BitField");
};
#pragma pack()
@@ -0,0 +1,248 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <atomic>
#include <condition_variable>
#include <cstddef>
#include <memory>
#include <mutex>
#include "common/polyfill_thread.h"
namespace Common {
namespace detail {
constexpr std::size_t DefaultCapacity = 0x1000;
} // namespace detail
template <typename T, std::size_t Capacity = detail::DefaultCapacity>
class SPSCQueue {
static_assert((Capacity & (Capacity - 1)) == 0, "Capacity must be a power of two.");
public:
template <typename... Args>
bool TryEmplace(Args&&... args) {
return Emplace<PushMode::Try>(std::forward<Args>(args)...);
}
template <typename... Args>
void EmplaceWait(Args&&... args) {
Emplace<PushMode::Wait>(std::forward<Args>(args)...);
}
bool TryPop(T& t) {
return Pop<PopMode::Try>(t);
}
void PopWait(T& t) {
Pop<PopMode::Wait>(t);
}
void PopWait(T& t, std::stop_token stop_token) {
Pop<PopMode::WaitWithStopToken>(t, stop_token);
}
T PopWait() {
T t;
Pop<PopMode::Wait>(t);
return t;
}
T PopWait(std::stop_token stop_token) {
T t;
Pop<PopMode::WaitWithStopToken>(t, stop_token);
return t;
}
private:
enum class PushMode {
Try,
Wait,
Count,
};
enum class PopMode {
Try,
Wait,
WaitWithStopToken,
Count,
};
template <PushMode Mode, typename... Args>
bool Emplace(Args&&... args) {
const std::size_t write_index = m_write_index.load(std::memory_order::relaxed);
if constexpr (Mode == PushMode::Try) {
// Check if we have free slots to write to.
if ((write_index - m_read_index.load(std::memory_order::acquire)) == Capacity) {
return false;
}
} else if constexpr (Mode == PushMode::Wait) {
// Wait until we have free slots to write to.
std::unique_lock lock{producer_cv_mutex};
producer_cv.wait(lock, [this, write_index] {
return (write_index - m_read_index.load(std::memory_order::acquire)) < Capacity;
});
} else {
static_assert(Mode < PushMode::Count, "Invalid PushMode.");
}
// Determine the position to write to.
const std::size_t pos = write_index % Capacity;
// Emplace into the queue.
new (std::addressof(m_data[pos])) T(std::forward<Args>(args)...);
// Increment the write index.
++m_write_index;
// Notify the consumer that we have pushed into the queue.
std::scoped_lock lock{consumer_cv_mutex};
consumer_cv.notify_one();
return true;
}
template <PopMode Mode>
bool Pop(T& t, [[maybe_unused]] std::stop_token stop_token = {}) {
const std::size_t read_index = m_read_index.load(std::memory_order::relaxed);
if constexpr (Mode == PopMode::Try) {
// Check if the queue is empty.
if (read_index == m_write_index.load(std::memory_order::acquire)) {
return false;
}
} else if constexpr (Mode == PopMode::Wait) {
// Wait until the queue is not empty.
std::unique_lock lock{consumer_cv_mutex};
consumer_cv.wait(lock, [this, read_index] {
return read_index != m_write_index.load(std::memory_order::acquire);
});
} else if constexpr (Mode == PopMode::WaitWithStopToken) {
// Wait until the queue is not empty.
std::unique_lock lock{consumer_cv_mutex};
Common::CondvarWait(consumer_cv, lock, stop_token, [this, read_index] {
return read_index != m_write_index.load(std::memory_order::acquire);
});
if (stop_token.stop_requested()) {
return false;
}
} else {
static_assert(Mode < PopMode::Count, "Invalid PopMode.");
}
// Determine the position to read from.
const std::size_t pos = read_index % Capacity;
// Pop the data off the queue, moving it.
t = std::move(m_data[pos]);
// Increment the read index.
++m_read_index;
// Notify the producer that we have popped off the queue.
std::scoped_lock lock{producer_cv_mutex};
producer_cv.notify_one();
return true;
}
alignas(128) std::atomic_size_t m_read_index{0};
alignas(128) std::atomic_size_t m_write_index{0};
std::array<T, Capacity> m_data;
std::condition_variable_any producer_cv;
std::mutex producer_cv_mutex;
std::condition_variable_any consumer_cv;
std::mutex consumer_cv_mutex;
};
template <typename T, std::size_t Capacity = detail::DefaultCapacity>
class MPSCQueue {
public:
template <typename... Args>
bool TryEmplace(Args&&... args) {
std::scoped_lock lock{write_mutex};
return spsc_queue.TryEmplace(std::forward<Args>(args)...);
}
template <typename... Args>
void EmplaceWait(Args&&... args) {
std::scoped_lock lock{write_mutex};
spsc_queue.EmplaceWait(std::forward<Args>(args)...);
}
bool TryPop(T& t) {
return spsc_queue.TryPop(t);
}
void PopWait(T& t) {
spsc_queue.PopWait(t);
}
void PopWait(T& t, std::stop_token stop_token) {
spsc_queue.PopWait(t, stop_token);
}
T PopWait() {
return spsc_queue.PopWait();
}
T PopWait(std::stop_token stop_token) {
return spsc_queue.PopWait(stop_token);
}
private:
SPSCQueue<T, Capacity> spsc_queue;
std::mutex write_mutex;
};
template <typename T, std::size_t Capacity = detail::DefaultCapacity>
class MPMCQueue {
public:
template <typename... Args>
bool TryEmplace(Args&&... args) {
std::scoped_lock lock{write_mutex};
return spsc_queue.TryEmplace(std::forward<Args>(args)...);
}
template <typename... Args>
void EmplaceWait(Args&&... args) {
std::scoped_lock lock{write_mutex};
spsc_queue.EmplaceWait(std::forward<Args>(args)...);
}
bool TryPop(T& t) {
std::scoped_lock lock{read_mutex};
return spsc_queue.TryPop(t);
}
void PopWait(T& t) {
std::scoped_lock lock{read_mutex};
spsc_queue.PopWait(t);
}
void PopWait(T& t, std::stop_token stop_token) {
std::scoped_lock lock{read_mutex};
spsc_queue.PopWait(t, stop_token);
}
T PopWait() {
std::scoped_lock lock{read_mutex};
return spsc_queue.PopWait();
}
T PopWait(std::stop_token stop_token) {
std::scoped_lock lock{read_mutex};
return spsc_queue.PopWait(stop_token);
}
private:
SPSCQueue<T, Capacity> spsc_queue;
std::mutex write_mutex;
std::mutex read_mutex;
};
} // namespace Common
+31
View File
@@ -0,0 +1,31 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <concepts>
#include <iterator>
#include <type_traits>
namespace Common {
// Check if type satisfies the ContiguousContainer named requirement.
template <typename T>
concept IsContiguousContainer = std::contiguous_iterator<typename T::iterator>;
template <typename Derived, typename Base>
concept DerivedFrom = std::derived_from<Derived, Base>;
// TODO: Replace with std::convertible_to when libc++ implements it.
template <typename From, typename To>
concept ConvertibleTo = std::is_convertible_v<From, To>;
// No equivalents in the stdlib
template <typename T>
concept IsArithmetic = std::is_arithmetic_v<T>;
template <typename T>
concept IsIntegral = std::is_integral_v<T>;
} // namespace Common
+173
View File
@@ -0,0 +1,173 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <algorithm>
#include <ranges>
#include <string_view>
#include "assert.h"
namespace Common {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wtautological-undefined-compare"
/**
* @brief A null-terminated string with a fixed maximum length
* This class is not meant to be used as a general-purpose string class
* It is meant to be used as `char[N]` where memory layout is fixed
* @tparam N Maximum length of the string
* @tparam T Type of character
*/
template <size_t N, typename T = char>
class CString {
T data[N]{};
public:
class Iterator;
CString() = default;
template <size_t M>
explicit CString(const CString<M>& other)
requires(M <= N)
{
if (this == nullptr) {
return;
}
std::ranges::copy(other.begin(), other.end(), data);
}
void FromString(const std::basic_string_view<T>& str) {
if (this == nullptr) {
return;
}
size_t p = str.copy(data, N - 1);
data[p] = '\0';
}
void Zero() {
if (this == nullptr) {
return;
}
std::ranges::fill(data, 0);
}
explicit(false) operator std::basic_string_view<T>() const {
if (this == nullptr) {
return {};
}
return std::basic_string_view<T>{data};
}
explicit operator std::basic_string<T>() const {
if (this == nullptr) {
return {};
}
return std::basic_string<T>{data};
}
std::basic_string<T> to_string() const {
if (this == nullptr) {
return {};
}
return std::basic_string<T>{data};
}
std::basic_string_view<T> to_view() const {
if (this == nullptr) {
return {};
}
return std::basic_string_view<T>{data};
}
T* begin() {
if (this == nullptr) {
return nullptr;
}
return data;
}
const T* begin() const {
if (this == nullptr) {
return nullptr;
}
return data;
}
T* end() {
if (this == nullptr) {
return nullptr;
}
return data + N;
}
const T* end() const {
if (this == nullptr) {
return nullptr;
}
return data + N;
}
constexpr std::size_t capacity() const {
return N;
}
std::size_t size() const {
return std::char_traits<T>::length(data);
}
T& operator[](size_t idx) {
return data[idx];
}
const T& operator[](size_t idx) const {
return data[idx];
}
class Iterator {
T* ptr;
T* end;
public:
using difference_type = std::ptrdiff_t;
using value_type = T;
using pointer = T*;
using reference = T&;
using iterator_category = std::random_access_iterator_tag;
Iterator() = default;
explicit Iterator(T* ptr) : ptr(ptr), end(ptr + N) {}
Iterator& operator++() {
++ptr;
return *this;
}
Iterator operator++(int) {
Iterator tmp = *this;
++ptr;
return tmp;
}
operator T*() {
ASSERT_MSG(ptr >= end, "CString iterator out of bounds");
return ptr;
}
};
};
static_assert(sizeof(CString<13>) == sizeof(char[13])); // Ensure size still matches a simple array
static_assert(std::weakly_incrementable<CString<13>::Iterator>);
template <size_t N>
using CWString = CString<N, wchar_t>;
template <size_t N>
using CU16String = CString<N, char16_t>;
#pragma clang diagnostic pop
} // namespace Common
+73
View File
@@ -0,0 +1,73 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#ifdef _MSC_VER
#define BREAKPOINT __debugbreak
#elif defined(__GNUC__)
#define BREAKPOINT __builtin_trap
#else
#error What the fuck is this compiler
#endif
#include <tracy/Tracy.hpp>
static inline bool IsProfilerConnected() {
#if TRACY_ENABLE
return tracy::GetProfiler().IsConnected();
#else
return false;
#endif
}
#define TRACY_GPU_ENABLED 0
#define CUSTOM_LOCK(type, varname) \
tracy::LockableCtx varname { \
[]() -> const tracy::SourceLocationData* { \
static constexpr tracy::SourceLocationData srcloc{nullptr, #type " " #varname, \
TracyFile, TracyLine, 0}; \
return &srcloc; \
}() \
}
#define TRACK_ALLOC(ptr, size, pool) TracyAllocN(std::bit_cast<void*>(ptr), (size), (pool))
#define TRACK_FREE(ptr, pool) TracyFreeN(std::bit_cast<void*>(ptr), (pool))
enum MarkersPalette : int {
EmulatorMarkerColor = 0x264653,
RendererMarkerColor = 0x2a9d8f,
HleMarkerColor = 0xe9c46a,
GpuMarkerColor = 0xf4a261,
Reserved1 = 0xe76f51,
};
#define EMULATOR_TRACE ZoneScopedC(EmulatorMarkerColor)
#define RENDERER_TRACE ZoneScopedC(RendererMarkerColor)
#define HLE_TRACE ZoneScopedC(HleMarkerColor)
#define TRACE_HINT(str) ZoneText(str.data(), str.size())
#define TRACE_WARN(msg) \
[](const auto& msg) { TracyMessageC(msg.c_str(), msg.size(), tracy::Color::DarkOrange); }(msg);
#define TRACE_ERROR(msg) \
[](const auto& msg) { TracyMessageC(msg.c_str(), msg.size(), tracy::Color::Red); }(msg)
#define TRACE_CRIT(msg) \
[](const auto& msg) { TracyMessageC(msg.c_str(), msg.size(), tracy::Color::HotPink); }(msg)
#define GPU_SCOPE_LOCATION(name, color) \
tracy::SourceLocationData{name, TracyFunction, TracyFile, (uint32_t)TracyLine, color};
#define MUTEX_LOCATION(name) \
tracy::SourceLocationData{nullptr, name, TracyFile, (uint32_t)TracyLine, 0};
#define FRAME_END FrameMark
#ifdef TRACY_FIBERS
#define FIBER_ENTER(name) TracyFiberEnter(name)
#define FIBER_EXIT TracyFiberLeave
#else
#define FIBER_ENTER(name)
#define FIBER_EXIT
#endif
+30
View File
@@ -0,0 +1,30 @@
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <fmt/format.h>
#include "common/decoder.h"
namespace Common {
DecoderImpl::DecoderImpl() {
ZydisDecoderInit(&m_decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_STACK_WIDTH_64);
ZydisFormatterInit(&m_formatter, ZYDIS_FORMATTER_STYLE_INTEL);
}
DecoderImpl::~DecoderImpl() = default;
std::string DecoderImpl::disassembleInst(ZydisDecodedInstruction& inst,
ZydisDecodedOperand* operands, u64 address) {
const int bufLen = 256;
char szBuffer[bufLen];
ZydisFormatterFormatInstruction(&m_formatter, &inst, operands, inst.operand_count_visible,
szBuffer, sizeof(szBuffer), address, ZYAN_NULL);
return szBuffer;
}
ZyanStatus DecoderImpl::decodeInstruction(ZydisDecodedInstruction& inst,
ZydisDecodedOperand* operands, void* data, u64 size) {
return ZydisDecoderDecodeFull(&m_decoder, data, size, &inst, operands);
}
} // namespace Common
+29
View File
@@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <Zydis/Zydis.h>
#include "common/singleton.h"
#include "common/types.h"
namespace Common {
class DecoderImpl {
public:
DecoderImpl();
~DecoderImpl();
std::string disassembleInst(ZydisDecodedInstruction& inst, ZydisDecodedOperand* operands,
u64 address);
ZyanStatus decodeInstruction(ZydisDecodedInstruction& inst, ZydisDecodedOperand* operands,
void* data, u64 size = 15);
private:
ZydisDecoder m_decoder;
ZydisFormatter m_formatter;
};
using Decoder = Common::Singleton<DecoderImpl>;
} // namespace Common
@@ -0,0 +1,57 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include <ctime>
#include "discord_rpc_handler.h"
namespace DiscordRPCHandler {
void RPC::init() {
DiscordEventHandlers handlers{};
Discord_Initialize("1139939140494971051", &handlers, 1, nullptr);
startTimestamp = time(nullptr);
rpcEnabled = true;
}
void RPC::setStatusIdling() {
DiscordRichPresence rpc{};
rpc.largeImageKey = "https://cdn.jsdelivr.net/gh/shadps4-emu/shadPS4@main/.github/shadps4.png";
rpc.largeImageText = "shadPS4 is a PS4 emulator";
rpc.startTimestamp = startTimestamp;
rpc.details = "Idle";
status = RPCStatus::Idling;
Discord_UpdatePresence(&rpc);
}
void RPC::setStatusPlaying(const std::string& game_name, const std::string& game_id) {
DiscordRichPresence rpc{};
rpc.details = "Playing";
rpc.state = game_name.c_str();
std::string largeImageUrl =
"https://store.playstation.com/store/api/chihiro/00_09_000/titlecontainer/US/en/999/" +
game_id + "_00/image";
rpc.largeImageKey = largeImageUrl.c_str();
rpc.largeImageText = game_name.c_str();
rpc.startTimestamp = startTimestamp;
status = RPCStatus::Playing;
Discord_UpdatePresence(&rpc);
}
void RPC::shutdown() {
if (rpcEnabled) {
rpcEnabled = false;
Discord_ClearPresence();
Discord_Shutdown();
}
}
bool RPC::getRPCEnabled() {
return rpcEnabled;
}
} // namespace DiscordRPCHandler
+30
View File
@@ -0,0 +1,30 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <cstdint>
#include <string>
#include <discord_rpc.h>
namespace DiscordRPCHandler {
enum class RPCStatus {
Idling,
Playing,
};
class RPC {
std::uint64_t startTimestamp;
bool rpcEnabled = false;
RPCStatus status;
public:
void init();
void setStatusIdling();
void setStatusPlaying(const std::string& game_name, const std::string& game_id);
void shutdown();
bool getRPCEnabled();
};
} // namespace DiscordRPCHandler
+25
View File
@@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <cstddef>
#include <type_traits>
namespace Common {
/// Ceiled integer division.
template <typename N, typename D>
requires std::is_integral_v<N> && std::is_unsigned_v<D>
[[nodiscard]] constexpr N DivCeil(N number, D divisor) {
return static_cast<N>((static_cast<D>(number) + divisor - 1) / divisor);
}
/// Ceiled integer division with logarithmic divisor in base 2
template <typename N, typename D>
requires std::is_integral_v<N> && std::is_unsigned_v<D>
[[nodiscard]] constexpr N DivCeilLog2(N value, D alignment_log2) {
return static_cast<N>((static_cast<D>(value) + (D(1) << alignment_log2) - 1) >> alignment_log2);
}
} // namespace Common
+162
View File
@@ -0,0 +1,162 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <filesystem>
#include <string>
#include <string_view>
#include <vector>
#include <map>
#include "assert.h"
#include "bit_field.h"
#include "singleton.h"
#include "types.h"
namespace Core {
class Emulator;
}
namespace Common {
union PSFAttributes {
/// Supports initial user's logout
BitField<0, 1, u32> support_initial_user_logout;
/// Enter button for the common dialog is cross.
BitField<1, 1, u32> enter_button_cross;
/// Warning dialog for PS Move is displayed in the options menu.
BitField<2, 1, u32> ps_move_warning;
/// Supports stereoscopic 3D.
BitField<3, 1, u32> support_stereoscopic_3d;
/// Suspends when PS button is pressed.
BitField<4, 1, u32> ps_button_suspend;
/// Enter button for the common dialog is assigned by the system software.
BitField<5, 1, u32> enter_button_system;
/// Overrides share menu behavior.
BitField<6, 1, u32> override_share_menu;
/// Suspends when PS button is pressed and special output resolution is set.
BitField<8, 1, u32> special_res_ps_button_suspend;
/// Enable HDCP.
BitField<9, 1, u32> enable_hdcp;
/// Disable HDCP for non-game.
BitField<10, 1, u32> disable_hdcp_non_game;
/// Supports PS VR.
BitField<14, 1, u32> support_ps_vr;
/// CPU mode (6 CPU)
BitField<15, 1, u32> six_cpu_mode;
/// CPU mode (7 CPU)
BitField<16, 1, u32> seven_cpu_mode;
/// Supports PS4 Pro (Neo) mode.
BitField<23, 1, u32> support_neo_mode;
/// Requires PS VR.
BitField<26, 1, u32> require_ps_vr;
/// Supports HDR.
BitField<29, 1, u32> support_hdr;
/// Display location.
BitField<31, 1, u32> display_location;
u32 raw{};
};
static_assert(sizeof(PSFAttributes) == 4);
class ElfInfo {
friend class Core::Emulator;
bool initialized = false;
std::string game_serial{};
std::string title{};
std::string app_ver{};
u32 firmware_ver = 0;
u32 raw_firmware_ver = 0;
u32 sdk_ver = 0;
PSFAttributes psf_attributes{};
std::vector<u8> splash_data{};
std::filesystem::path game_folder{};
std::vector<std::string> npCommIds{};
std::map<s32, std::string> trophy_index_map{};
public:
static constexpr u32 FW_100 = 0x1000000;
static constexpr u32 FW_150 = 0x1500000;
static constexpr u32 FW_160 = 0x1600000;
static constexpr u32 FW_170 = 0x1700000;
static constexpr u32 FW_200 = 0x2000000;
static constexpr u32 FW_250 = 0x2500000;
static constexpr u32 FW_300 = 0x3000000;
static constexpr u32 FW_350 = 0x3500000;
static constexpr u32 FW_400 = 0x4000000;
static constexpr u32 FW_450 = 0x4500000;
static constexpr u32 FW_500 = 0x5000000;
static constexpr u32 FW_550 = 0x5500000;
static constexpr u32 FW_600 = 0x6000000;
static constexpr u32 FW_700 = 0x7000000;
static constexpr u32 FW_750 = 0x7500000;
static constexpr u32 FW_800 = 0x8000000;
static constexpr u32 FW_900 = 0x9000000;
static constexpr u32 FW_1000 = 0x10000000;
static constexpr u32 FW_1150 = 0x11500000;
static ElfInfo& Instance() {
return *Singleton<ElfInfo>::Instance();
}
[[nodiscard]] std::string_view GameSerial() const {
ASSERT(initialized);
return Instance().game_serial;
}
[[nodiscard]] std::string_view Title() const {
ASSERT(initialized);
return title;
}
[[nodiscard]] std::string_view AppVer() const {
ASSERT(initialized);
return app_ver;
}
[[nodiscard]] u32 FirmwareVer() const {
ASSERT(initialized);
return firmware_ver;
}
[[nodiscard]] u32 RawFirmwareVer() const {
ASSERT(initialized);
return raw_firmware_ver;
}
[[nodiscard]] u32 CompiledSdkVer() const {
ASSERT(initialized);
return sdk_ver;
}
[[nodiscard]] const PSFAttributes& GetPSFAttributes() const {
ASSERT(initialized);
return psf_attributes;
}
[[nodiscard]] const std::vector<u8>& GetSplashData() const {
return splash_data;
}
[[nodiscard]] const std::filesystem::path& GetGameFolder() const {
return game_folder;
}
[[nodiscard]] const std::map<s32, std::string>& GetTrophyIndexMap() const {
return trophy_index_map;
}
[[nodiscard]] const std::vector<std::string>& GetNpCommIds() const {
return npCommIds;
}
void SetNpCommIds(std::vector<std::string> ids) {
npCommIds = std::move(ids);
}
};
} // namespace Common
+242
View File
@@ -0,0 +1,242 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
/**
* (c) 2014-2016 Alexandro Sanchez Bach. All rights reserved.
* Released under GPL v2 license. Read LICENSE for more details.
* Some modifications for using with shadps4 by georgemoralis
*/
#pragma once
#include <bit>
#include <concepts>
#include "common/types.h"
namespace Common {
/**
* Native endianness
*/
template <typename T>
using NativeEndian = T;
template <std::integral T>
class SwappedEndian {
public:
const T& Raw() const {
return data;
}
T Swap() const {
return std::byteswap(data);
}
void FromRaw(const T& value) {
data = value;
}
void FromSwap(const T& value) {
data = std::byteswap(value);
}
operator const T() const {
return Swap();
}
template <typename T1>
explicit operator const SwappedEndian<T1>() const {
SwappedEndian<T1> res;
if (sizeof(T1) < sizeof(T)) {
res.FromRaw(Raw() >> ((sizeof(T) - sizeof(T1)) * 8));
} else if (sizeof(T1) > sizeof(T)) {
res.FromSwap(Swap());
} else {
res.FromRaw(Raw());
}
return res;
}
SwappedEndian<T>& operator=(const T& right) {
FromSwap(right);
return *this;
}
SwappedEndian<T>& operator=(const SwappedEndian<T>& right) = default;
template <typename T1>
SwappedEndian<T>& operator+=(T1 right) {
return *this = T(*this) + right;
}
template <typename T1>
SwappedEndian<T>& operator-=(T1 right) {
return *this = T(*this) - right;
}
template <typename T1>
SwappedEndian<T>& operator*=(T1 right) {
return *this = T(*this) * right;
}
template <typename T1>
SwappedEndian<T>& operator/=(T1 right) {
return *this = T(*this) / right;
}
template <typename T1>
SwappedEndian<T>& operator%=(T1 right) {
return *this = T(*this) % right;
}
template <typename T1>
SwappedEndian<T>& operator&=(T1 right) {
return *this = T(*this) & right;
}
template <typename T1>
SwappedEndian<T>& operator|=(T1 right) {
return *this = T(*this) | right;
}
template <typename T1>
SwappedEndian<T>& operator^=(T1 right) {
return *this = T(*this) ^ right;
}
template <typename T1>
SwappedEndian<T>& operator<<=(T1 right) {
return *this = T(*this) << right;
}
template <typename T1>
SwappedEndian<T>& operator>>=(T1 right) {
return *this = T(*this) >> right;
}
template <typename T1>
SwappedEndian<T>& operator+=(const SwappedEndian<T1>& right) {
return *this = Swap() + right.Swap();
}
template <typename T1>
SwappedEndian<T>& operator-=(const SwappedEndian<T1>& right) {
return *this = Swap() - right.Swap();
}
template <typename T1>
SwappedEndian<T>& operator*=(const SwappedEndian<T1>& right) {
return *this = Swap() * right.Swap();
}
template <typename T1>
SwappedEndian<T>& operator/=(const SwappedEndian<T1>& right) {
return *this = Swap() / right.Swap();
}
template <typename T1>
SwappedEndian<T>& operator%=(const SwappedEndian<T1>& right) {
return *this = Swap() % right.Swap();
}
template <typename T1>
SwappedEndian<T>& operator&=(const SwappedEndian<T1>& right) {
return *this = Raw() & right.Raw();
}
template <typename T1>
SwappedEndian<T>& operator|=(const SwappedEndian<T1>& right) {
return *this = Raw() | right.Raw();
}
template <typename T1>
SwappedEndian<T>& operator^=(const SwappedEndian<T1>& right) {
return *this = Raw() ^ right.Raw();
}
template <typename T1>
SwappedEndian<T> operator&(const SwappedEndian<T1>& right) const {
return SwappedEndian<T>{Raw() & right.Raw()};
}
template <typename T1>
SwappedEndian<T> operator|(const SwappedEndian<T1>& right) const {
return SwappedEndian<T>{Raw() | right.Raw()};
}
template <typename T1>
SwappedEndian<T> operator^(const SwappedEndian<T1>& right) const {
return SwappedEndian<T>{Raw() ^ right.Raw()};
}
template <typename T1>
bool operator==(T1 right) const {
return (T1)Swap() == right;
}
template <typename T1>
bool operator!=(T1 right) const {
return !(*this == right);
}
template <typename T1>
bool operator>(T1 right) const {
return (T1)Swap() > right;
}
template <typename T1>
bool operator<(T1 right) const {
return (T1)Swap() < right;
}
template <typename T1>
bool operator>=(T1 right) const {
return (T1)Swap() >= right;
}
template <typename T1>
bool operator<=(T1 right) const {
return (T1)Swap() <= right;
}
template <typename T1>
bool operator==(const SwappedEndian<T1>& right) const {
return Raw() == right.Raw();
}
template <typename T1>
bool operator!=(const SwappedEndian<T1>& right) const {
return !(*this == right);
}
template <typename T1>
bool operator>(const SwappedEndian<T1>& right) const {
return (T1)Swap() > right.Swap();
}
template <typename T1>
bool operator<(const SwappedEndian<T1>& right) const {
return (T1)Swap() < right.Swap();
}
template <typename T1>
bool operator>=(const SwappedEndian<T1>& right) const {
return (T1)Swap() >= right.Swap();
}
template <typename T1>
bool operator<=(const SwappedEndian<T1>& right) const {
return (T1)Swap() <= right.Swap();
}
SwappedEndian<T> operator++(int) {
SwappedEndian<T> res = *this;
*this += 1;
return res;
}
SwappedEndian<T> operator--(int) {
SwappedEndian<T> res = *this;
*this -= 1;
return res;
}
SwappedEndian<T>& operator++() {
*this += 1;
return *this;
}
SwappedEndian<T>& operator--() {
*this -= 1;
return *this;
}
private:
T data;
};
template <typename T>
using LittleEndian = std::conditional_t<std::endian::native == std::endian::little, NativeEndian<T>,
SwappedEndian<T>>;
template <typename T>
using BigEndian =
std::conditional_t<std::endian::native == std::endian::big, NativeEndian<T>, SwappedEndian<T>>;
} // namespace Common
using u16_be = Common::BigEndian<u16>;
using u32_be = Common::BigEndian<u32>;
using u64_be = Common::BigEndian<u64>;
using u16_le = Common::LittleEndian<u16>;
using u32_le = Common::LittleEndian<u32>;
using u64_le = Common::LittleEndian<u64>;
+161
View File
@@ -0,0 +1,161 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <type_traits>
#include "common/types.h"
#define DECLARE_ENUM_FLAG_OPERATORS(type) \
[[nodiscard]] constexpr type operator|(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) | static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator&(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) & static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator^(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) ^ static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator<<(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) << static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator>>(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) >> static_cast<T>(b)); \
} \
constexpr type& operator|=(type& a, type b) noexcept { \
a = a | b; \
return a; \
} \
constexpr type& operator&=(type& a, type b) noexcept { \
a = a & b; \
return a; \
} \
constexpr type& operator^=(type& a, type b) noexcept { \
a = a ^ b; \
return a; \
} \
constexpr type& operator<<=(type& a, type b) noexcept { \
a = a << b; \
return a; \
} \
constexpr type& operator>>=(type& a, type b) noexcept { \
a = a >> b; \
return a; \
} \
[[nodiscard]] constexpr type operator~(type key) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(~static_cast<T>(key)); \
} \
[[nodiscard]] constexpr bool True(type key) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<T>(key) != 0; \
} \
[[nodiscard]] constexpr bool False(type key) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<T>(key) == 0; \
}
namespace Common {
template <typename T>
class Flags {
public:
using IntType = std::underlying_type_t<T>;
Flags() {}
Flags(IntType t) : m_bits(t) {}
template <typename... Tx>
Flags(T f, Tx... fx) {
this->set(f, fx...);
}
template <typename... Tx>
void set(Tx... fx) {
m_bits |= bits(fx...);
}
void set(Flags flags) {
m_bits |= flags.m_bits;
}
template <typename... Tx>
void clr(Tx... fx) {
m_bits &= ~bits(fx...);
}
void clr(Flags flags) {
m_bits &= ~flags.m_bits;
}
template <typename... Tx>
bool any(Tx... fx) const {
return (m_bits & bits(fx...)) != 0;
}
template <typename... Tx>
bool all(Tx... fx) const {
const IntType mask = bits(fx...);
return (m_bits & mask) == mask;
}
bool test(T f) const {
return this->any(f);
}
bool isClear() const {
return m_bits == 0;
}
void clrAll() {
m_bits = 0;
}
u32 raw() const {
return m_bits;
}
Flags operator&(const Flags& other) const {
return Flags(m_bits & other.m_bits);
}
Flags operator|(const Flags& other) const {
return Flags(m_bits | other.m_bits);
}
Flags operator^(const Flags& other) const {
return Flags(m_bits ^ other.m_bits);
}
bool operator==(const Flags& other) const {
return m_bits == other.m_bits;
}
bool operator!=(const Flags& other) const {
return m_bits != other.m_bits;
}
private:
IntType m_bits = 0;
static IntType bit(T f) {
return IntType(1) << static_cast<IntType>(f);
}
template <typename... Tx>
static IntType bits(T f, Tx... fx) {
return bit(f) | bits(fx...);
}
static IntType bits() {
return 0;
}
};
} // namespace Common
+57
View File
@@ -0,0 +1,57 @@
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstddef>
#ifdef _WIN32
#include <windows.h>
#else
#include <cerrno>
#include <cstring>
#endif
#include "common/error.h"
namespace Common {
std::string NativeErrorToString(int e) {
#ifdef _WIN32
LPSTR err_str;
DWORD res = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, e, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
reinterpret_cast<LPSTR>(&err_str), 1, nullptr);
if (!res) {
return "(FormatMessageA failed to format error)";
}
std::string ret(err_str);
LocalFree(err_str);
return ret;
#else
char err_str[255];
#if defined(__GLIBC__) && (_GNU_SOURCE || (_POSIX_C_SOURCE < 200112L && _XOPEN_SOURCE < 600)) || \
defined(ANDROID)
// Thread safe (GNU-specific)
const char* str = strerror_r(e, err_str, sizeof(err_str));
return std::string(str);
#else
// Thread safe (XSI-compliant)
int second_err = strerror_r(e, err_str, sizeof(err_str));
if (second_err != 0) {
return "(strerror_r failed to format error)";
}
return std::string(err_str);
#endif // GLIBC etc.
#endif // _WIN32
}
std::string GetLastErrorMsg() {
#ifdef _WIN32
return NativeErrorToString(GetLastError());
#else
return NativeErrorToString(errno);
#endif
}
} // namespace Common
+21
View File
@@ -0,0 +1,21 @@
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
namespace Common {
// Generic function to get last error message.
// Call directly after the command or use the error num.
// This function might change the error code.
// Defined in error.cpp.
[[nodiscard]] std::string GetLastErrorMsg();
// Like GetLastErrorMsg(), but passing an explicit error code.
// Defined in error.cpp.
[[nodiscard]] std::string NativeErrorToString(int e);
} // namespace Common
+35
View File
@@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
/**
* @brief A template class that encapsulates a fixed, compile-time constant value.
*
* @tparam T The type of the value.
* @tparam Value The fixed value of type T.
*
* This class provides a way to encapsulate a value that is constant and known at compile-time.
* The value is stored as a private member and cannot be changed. Any attempt to assign a new
* value to an object of this class will reset it to the fixed value.
*/
template <typename T, T Value>
class FixedValue {
T m_value{Value};
public:
constexpr FixedValue() = default;
constexpr explicit(false) operator T() const {
return m_value;
}
FixedValue& operator=(const T&) {
m_value = Value;
return *this;
}
FixedValue& operator=(T&&) noexcept {
m_value = {Value};
return *this;
}
};
+34
View File
@@ -0,0 +1,34 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <tuple>
namespace Common {
template <class Func>
struct FuncTraits {};
template <class ReturnType_, class... Args>
struct FuncTraits<ReturnType_ (*)(Args...)> {
using ReturnType = ReturnType_;
static constexpr size_t NUM_ARGS = sizeof...(Args);
template <size_t I>
using ArgType = std::tuple_element_t<I, std::tuple<Args...>>;
};
template <typename Func>
struct LambdaTraits : LambdaTraits<decltype(&std::remove_reference_t<Func>::operator())> {};
template <typename ReturnType, typename LambdaType, typename... Args>
struct LambdaTraits<ReturnType (LambdaType::*)(Args...) const> {
template <size_t I>
using ArgType = std::tuple_element_t<I, std::tuple<Args...>>;
static constexpr size_t NUM_ARGS{sizeof...(Args)};
};
} // namespace Common
+14
View File
@@ -0,0 +1,14 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
[[nodiscard]] inline u64 HashCombine(const u64 seed, const u64 hash) {
return seed ^ (hash + 0x9e3779b9 + (seed << 12) + (seed >> 4));
}
[[nodiscard]] inline u32 HashCombine(const u32 seed, const u32 hash) {
return seed ^ (hash + 0x9e3779b9 + (seed << 6) + (seed >> 2));
}
+22
View File
@@ -0,0 +1,22 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <atomic>
namespace Common {
template <typename CounterType>
class IncrementalIdProvider {
public:
IncrementalIdProvider() : counter(0) {}
~IncrementalIdProvider() = default;
CounterType Next() {
return counter.fetch_add(1, std::memory_order_relaxed) + 1;
}
private:
std::atomic<CounterType> counter;
};
} // namespace Common
+205
View File
@@ -0,0 +1,205 @@
// SPDX-FileCopyrightText: 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <algorithm>
#include <concepts>
#include <iterator>
#include <vector>
#include "common/types.h"
struct Interval {
u64 start;
u64 end;
constexpr bool CanMergeWith(const Interval&) const noexcept {
return true;
}
constexpr Interval SubRange(u64 a, u64 b) const noexcept {
return {a, b};
}
};
template <class IV>
concept IsInterval = std::derived_from<IV, Interval> && std::is_trivially_copyable_v<IV> &&
requires(const IV& a, const IV& b, u64 x) {
{ a.CanMergeWith(b) } -> std::same_as<bool>;
{ a.SubRange(x, x) } -> std::same_as<IV>;
};
template <class IV = Interval>
requires IsInterval<IV>
class IntervalList {
public:
using List = std::vector<IV>;
using const_iterator = typename List::const_iterator;
using iterator = typename List::iterator;
const_iterator begin() const {
return intervals.begin();
}
const_iterator end() const {
return intervals.end();
}
void Clear() {
intervals.clear();
}
bool Empty() const {
return intervals.empty();
}
std::size_t Size() const {
return intervals.size();
}
void Reserve(std::size_t n) {
intervals.reserve(n);
}
/// Adds a range to the interval set.
void Add(IV value) {
if (value.start >= value.end) [[unlikely]] {
return;
}
IV out[3];
std::size_t m = 0;
auto [first, last] = OverlapRun(value.start, value.end);
if (first != last) {
if (first->start < value.start) {
out[m++] = first->SubRange(first->start, value.start);
}
out[m++] = value;
auto prev = std::prev(last);
if (prev->end > value.end) {
out[m++] = prev->SubRange(value.end, prev->end);
}
} else {
out[m++] = value;
}
const auto at = std::distance(intervals.begin(), first);
Splice(first, last, out, m);
Coalesce(at, m);
}
/// Returns immutable iterator to an interval that contains provided address.
const_iterator Find(u64 addr) const {
auto it = std::ranges::upper_bound(intervals, addr, {}, &IV::start);
if (it == intervals.begin()) {
return intervals.end();
}
--it;
return (it->start <= addr && addr < it->end) ? it : intervals.end();
}
/// Returns true if address if contained inside an interval.
bool Contains(u64 addr) const {
return Find(addr) != intervals.end();
}
/// Returns true if range is fully contained in the interval set.
bool Contains(u64 start, u64 end) const {
if (start >= end) [[unlikely]] {
return false;
}
auto it = std::ranges::upper_bound(intervals, start, {}, &IV::end);
u64 cur = start;
while (it != intervals.end() && it->start <= cur && cur < end) {
cur = it->end;
++it;
}
return cur >= end;
}
/// Returns true if range partially overlaps an interval.
bool Overlaps(u64 start, u64 end) const {
if (start >= end) [[unlikely]] {
return false;
}
auto it = std::ranges::upper_bound(intervals, start, {}, &IV::end);
return it != intervals.end() && it->start < end;
}
/// Calls 'func' for each gap in provided range.
void ForEachGap(u64 start, u64 end, auto&& func) const {
if (start >= end) [[unlikely]] {
return;
}
auto it = std::ranges::upper_bound(intervals, start, {}, &IV::end);
u64 cur = start;
for (; it != intervals.end() && it->start < end; ++it) {
if (it->start > cur) {
func(cur, it->start);
}
cur = std::max(cur, it->end);
}
if (cur < end) {
func(cur, end);
}
}
/// Calls 'func' for each interval that partially overlaps provided range.
void ForEachInRange(u64 start, u64 end, auto&& func) const {
if (start >= end) [[unlikely]] {
return;
}
auto it = std::ranges::upper_bound(intervals, start, {}, &IV::end);
if (it == intervals.end()) {
return;
}
while (it != intervals.end() && it->start < end) {
func(*it);
++it;
}
}
protected:
/// Returns first interval that overlaps range and first interval after range.
std::pair<iterator, iterator> OverlapRun(u64 start, u64 end) noexcept {
auto first = std::ranges::upper_bound(intervals, start, {}, &IV::end);
auto last = first;
while (last != intervals.end() && last->start <= end) {
++last;
}
return {first, last};
}
/// Inserts intervals from first until last
void Splice(iterator first, iterator last, const IV* src, std::size_t m) noexcept {
if (first == last) {
intervals.insert(first, src, src + m);
return;
}
const auto slots = std::distance(first, last);
if (m <= slots) {
std::copy(src, src + m, first);
if (m < slots) {
intervals.erase(first + m, last);
}
} else {
std::copy(src, src + slots, first);
intervals.insert(last, src + slots, src + m);
}
}
/// Merges compatible adjacent intervals.
void Coalesce(std::size_t start, std::size_t count) {
auto stop = std::min(start + count + 1, intervals.size());
for (auto i = start ? start - 1 : start; i + 1 < stop;) {
IV& a = intervals[i];
IV& b = intervals[i + 1];
if (a.end == b.start && a.CanMergeWith(b)) {
a.end = b.end;
intervals.erase(intervals.begin() + i + 1);
--stop;
} else {
++i;
}
}
}
List intervals;
};
+423
View File
@@ -0,0 +1,423 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <vector>
#include "common/alignment.h"
#include "common/assert.h"
#include "common/error.h"
#include "common/io_file.h"
#include "common/logging/log.h"
#include "common/path_util.h"
#ifdef _WIN32
#include "common/ntapi.h"
#include <io.h>
#include <share.h>
#include <windows.h>
#else
#include <unistd.h>
#endif
#ifdef _MSC_VER
#define fileno _fileno
#define fseeko _fseeki64
#define ftello _ftelli64
#endif
namespace Common::FS {
namespace fs = std::filesystem;
namespace {
#ifdef _WIN32
[[nodiscard]] constexpr const wchar_t* AccessModeToWStr(FileAccessMode mode, FileType type) {
switch (type) {
case FileType::BinaryFile:
switch (mode) {
case FileAccessMode::Read:
return L"rb";
case FileAccessMode::Append:
return L"ab";
case FileAccessMode::Write:
case FileAccessMode::ReadWrite:
return L"r+b";
case FileAccessMode::ReadAppend:
return L"a+b";
case FileAccessMode::Create:
return L"wb";
}
break;
case FileType::TextFile:
switch (mode) {
case FileAccessMode::Read:
return L"r";
case FileAccessMode::Append:
return L"a";
case FileAccessMode::Write:
case FileAccessMode::ReadWrite:
return L"r+";
case FileAccessMode::ReadAppend:
return L"a+";
case FileAccessMode::Create:
return L"w";
}
break;
}
return L"";
}
[[nodiscard]] constexpr int ToWindowsFileShareFlag(FileShareFlag flag) {
switch (flag) {
case FileShareFlag::ShareNone:
default:
return _SH_DENYRW;
case FileShareFlag::ShareReadOnly:
return _SH_DENYWR;
case FileShareFlag::ShareWriteOnly:
return _SH_DENYRD;
case FileShareFlag::ShareReadWrite:
return _SH_DENYNO;
}
}
#else
[[nodiscard]] constexpr const char* AccessModeToStr(FileAccessMode mode, FileType type) {
switch (type) {
case FileType::BinaryFile:
switch (mode) {
case FileAccessMode::Read:
return "rb";
case FileAccessMode::Append:
return "ab";
case FileAccessMode::Write:
case FileAccessMode::ReadWrite:
return "r+b";
case FileAccessMode::ReadAppend:
return "a+b";
case FileAccessMode::Create:
return "wb";
}
break;
case FileType::TextFile:
switch (mode) {
case FileAccessMode::Read:
return "r";
case FileAccessMode::Append:
return "a";
case FileAccessMode::Write:
case FileAccessMode::ReadWrite:
return "r+";
case FileAccessMode::ReadAppend:
return "a+";
case FileAccessMode::Create:
return "w";
}
break;
}
return "";
}
#endif
[[nodiscard]] constexpr int ToSeekOrigin(SeekOrigin origin) {
switch (origin) {
case SeekOrigin::SetOrigin:
return SEEK_SET;
case SeekOrigin::CurrentPosition:
return SEEK_CUR;
case SeekOrigin::End:
return SEEK_END;
default:
UNREACHABLE_MSG("Impossible SeekOrigin {}", static_cast<u32>(origin));
}
}
} // Anonymous namespace
IOFile::IOFile() = default;
IOFile::IOFile(const std::string& path, FileAccessMode mode, FileType type, FileShareFlag flag) {
Open(path, mode, type, flag);
}
IOFile::IOFile(std::string_view path, FileAccessMode mode, FileType type, FileShareFlag flag) {
Open(path, mode, type, flag);
}
IOFile::IOFile(const fs::path& path, FileAccessMode mode, FileType type, FileShareFlag flag) {
Open(path, mode, type, flag);
}
IOFile::~IOFile() {
Close();
}
IOFile::IOFile(IOFile&& other) noexcept {
std::swap(file_path, other.file_path);
std::swap(file_access_mode, other.file_access_mode);
std::swap(file_type, other.file_type);
std::swap(file, other.file);
}
IOFile& IOFile::operator=(IOFile&& other) noexcept {
std::swap(file_path, other.file_path);
std::swap(file_access_mode, other.file_access_mode);
std::swap(file_type, other.file_type);
std::swap(file, other.file);
return *this;
}
int IOFile::Open(const fs::path& path, FileAccessMode mode, FileType type, FileShareFlag flag) {
Close();
file_path = path;
file_access_mode = mode;
file_type = type;
errno = 0;
int result = 0;
#ifdef _WIN32
if (flag != FileShareFlag::ShareNone) {
file = _wfsopen(path.c_str(), AccessModeToWStr(mode, type), ToWindowsFileShareFlag(flag));
result = errno;
} else {
result = _wfopen_s(&file, path.c_str(), AccessModeToWStr(mode, type));
}
#else
file = std::fopen(path.c_str(), AccessModeToStr(mode, type));
result = errno;
#endif
if (!IsOpen()) {
const auto ec = std::error_code{result, std::generic_category()};
LOG_ERROR(Common_Filesystem, "Failed to open the file at path={}, error_message={}",
PathToUTF8String(file_path), ec.message());
}
return result;
}
void IOFile::Close() {
if (!IsOpen()) {
return;
}
errno = 0;
const auto close_result = std::fclose(file) == 0;
if (!close_result) {
const auto ec = std::error_code{errno, std::generic_category()};
LOG_ERROR(Common_Filesystem, "Failed to close the file at path={}, ec_message={}",
PathToUTF8String(file_path), ec.message());
}
file = nullptr;
#ifdef _WIN64
if (file_mapping && file_access_mode == FileAccessMode::ReadWrite) {
CloseHandle(std::bit_cast<HANDLE>(file_mapping));
}
#endif
}
void IOFile::Unlink() {
if (!IsOpen()) {
return;
}
// Mark the file for deletion
// TODO: Also remove the file path?
#ifdef _WIN64
FILE_DISPOSITION_INFORMATION disposition;
IO_STATUS_BLOCK iosb;
const int fd = fileno(file);
HANDLE hfile = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
disposition.DeleteFile = TRUE;
NtSetInformationFile(hfile, &iosb, &disposition, sizeof(disposition),
FileDispositionInformation);
#else
if (unlink(file_path.c_str()) != 0) {
const auto ec = std::error_code{errno, std::generic_category()};
LOG_ERROR(Common_Filesystem, "Failed to unlink the file at path={}, ec_message={}",
PathToUTF8String(file_path), ec.message());
}
#endif
}
uintptr_t IOFile::GetFileMapping() {
if (file_mapping) {
return file_mapping;
}
#ifdef _WIN64
const int fd = fileno(file);
HANDLE hfile = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
HANDLE mapping = nullptr;
if (file_access_mode == FileAccessMode::ReadWrite) {
mapping = CreateFileMapping2(hfile, NULL, FILE_MAP_WRITE, PAGE_READWRITE, SEC_COMMIT, 0,
NULL, NULL, 0);
} else {
mapping = hfile;
}
file_mapping = std::bit_cast<uintptr_t>(mapping);
ASSERT_MSG(file_mapping, "{}", Common::GetLastErrorMsg());
return file_mapping;
#else
file_mapping = fileno(file);
return file_mapping;
#endif
}
std::string IOFile::ReadString(size_t length) const {
std::vector<char> string_buffer(length);
const auto chars_read = ReadSpan<char>(string_buffer);
const auto string_size = chars_read != length ? chars_read : length;
return std::string{string_buffer.data(), string_size};
}
bool IOFile::Flush() const {
if (!IsOpen()) {
return false;
}
errno = 0;
#ifdef _WIN32
const auto flush_result = std::fflush(file) == 0;
#else
const auto flush_result = std::fflush(file) == 0;
#endif
if (!flush_result) {
const auto ec = std::error_code{errno, std::generic_category()};
LOG_ERROR(Common_Filesystem, "Failed to flush the file at path={}, ec_message={}",
PathToUTF8String(file_path), ec.message());
}
return flush_result;
}
bool IOFile::Commit() const {
if (!IsOpen()) {
return false;
}
errno = 0;
#ifdef _WIN32
const auto commit_result = std::fflush(file) == 0 && _commit(fileno(file)) == 0;
#else
const auto commit_result = std::fflush(file) == 0 && fsync(fileno(file)) == 0;
#endif
if (!commit_result) {
const auto ec = std::error_code{errno, std::generic_category()};
LOG_ERROR(Common_Filesystem, "Failed to commit the file at path={}, ec_message={}",
PathToUTF8String(file_path), ec.message());
}
return commit_result;
}
bool IOFile::SetSize(u64 size) const {
if (!IsOpen()) {
return false;
}
errno = 0;
#ifdef _WIN32
const auto set_size_result = _chsize_s(fileno(file), static_cast<s64>(size)) == 0;
#else
const auto set_size_result = ftruncate(fileno(file), static_cast<s64>(size)) == 0;
#endif
if (!set_size_result) {
const auto ec = std::error_code{errno, std::generic_category()};
LOG_ERROR(Common_Filesystem, "Failed to resize the file at path={}, size={}, ec_message={}",
PathToUTF8String(file_path), size, ec.message());
}
return set_size_result;
}
u64 IOFile::GetSize() const {
if (!IsOpen()) {
return 0;
}
// Flush any unwritten buffered data into the file prior to retrieving the file size.
std::fflush(file);
std::error_code ec;
const auto file_size = fs::file_size(file_path, ec);
if (ec) {
LOG_ERROR(Common_Filesystem, "Failed to retrieve the file size of path={}, ec_message={}",
PathToUTF8String(file_path), ec.message());
return 0;
}
return file_size;
}
bool IOFile::Seek(s64 offset, SeekOrigin origin) const {
if (!IsOpen()) {
return false;
}
errno = 0;
const auto seek_result = fseeko(file, offset, ToSeekOrigin(origin)) == 0;
if (!seek_result) {
const auto ec = std::error_code{errno, std::generic_category()};
LOG_ERROR(Common_Filesystem,
"Failed to seek the file at path={}, offset={}, origin={}, ec_message={}",
PathToUTF8String(file_path), offset, static_cast<u32>(origin), ec.message());
}
return seek_result;
}
s64 IOFile::Tell() const {
if (!IsOpen()) {
return 0;
}
errno = 0;
return ftello(file);
}
u64 GetDirectorySize(const std::filesystem::path& path) {
if (!fs::exists(path)) {
return 0;
}
u64 total = 0;
for (const auto& entry : fs::recursive_directory_iterator(path)) {
if (fs::is_regular_file(entry.path())) {
total += fs::file_size(entry.path());
}
}
return total;
}
} // namespace Common::FS
+252
View File
@@ -0,0 +1,252 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <cstdio>
#include <filesystem>
#include <span>
#include <type_traits>
#include "common/assert.h"
#include "common/concepts.h"
#include "common/types.h"
#include "enum.h"
namespace Common::FS {
enum class FileAccessMode {
/**
* If the file at path exists, it opens the file for reading.
* If the file at path does not exist, it fails to open the file.
*/
Read = 1 << 0,
/**
* If the file at path exists, it opens the file for writing.
* If the file at path does not exist, it fails to open the file.
*/
Write = 1 << 1,
/**
* If the file at path exists, it opens the file for reading and writing.
* If the file at path does not exist, it fails to open the file.
*/
ReadWrite = Read | Write,
/**
* If the file at path exists, it opens the file for appending.
* If the file at path does not exist, it creates and opens a new empty file for appending.
*/
Append = 1 << 2,
/**
* If the file at path exists, it opens the file for both reading and appending.
* If the file at path does not exist, it creates and opens a new empty file for both
* reading and appending.
*/
ReadAppend = Read | Append,
/**
* If the file at path exists, the existing contents of the file are erased.
* The empty file is then opened for writing.
* If the file at path does not exist, it creates and opens a new empty file for writing.
*/
Create = 1 << 3,
};
DECLARE_ENUM_FLAG_OPERATORS(FileAccessMode);
enum class FileType {
BinaryFile,
TextFile,
};
enum class FileShareFlag {
ShareNone, // Provides exclusive access to the file.
ShareReadOnly, // Provides read only shared access to the file.
ShareWriteOnly, // Provides write only shared access to the file.
ShareReadWrite, // Provides read and write shared access to the file.
};
enum class SeekOrigin : u32 {
SetOrigin, // Seeks from the start of the file.
CurrentPosition, // Seeks from the current file pointer position.
End, // Seeks from the end of the file.
};
class IOFile final {
public:
IOFile();
explicit IOFile(const std::string& path, FileAccessMode mode,
FileType type = FileType::BinaryFile,
FileShareFlag flag = FileShareFlag::ShareReadOnly);
explicit IOFile(std::string_view path, FileAccessMode mode,
FileType type = FileType::BinaryFile,
FileShareFlag flag = FileShareFlag::ShareReadOnly);
explicit IOFile(const std::filesystem::path& path, FileAccessMode mode,
FileType type = FileType::BinaryFile,
FileShareFlag flag = FileShareFlag::ShareReadOnly);
~IOFile();
IOFile(const IOFile&) = delete;
IOFile& operator=(const IOFile&) = delete;
IOFile(IOFile&& other) noexcept;
IOFile& operator=(IOFile&& other) noexcept;
std::filesystem::path GetPath() const {
return file_path;
}
FileAccessMode GetAccessMode() const {
return file_access_mode;
}
FileType GetType() const {
return file_type;
}
bool IsOpen() const {
return file != nullptr;
}
bool IsWriteOnly() const {
return file_access_mode == FileAccessMode::Append ||
file_access_mode == FileAccessMode::Write;
}
uintptr_t GetFileMapping();
int Open(const std::filesystem::path& path, FileAccessMode mode,
FileType type = FileType::BinaryFile,
FileShareFlag flag = FileShareFlag::ShareReadOnly);
void Close();
void Unlink();
bool Flush() const;
bool Commit() const;
bool SetSize(u64 size) const;
u64 GetSize() const;
bool Seek(s64 offset, SeekOrigin origin = SeekOrigin::SetOrigin) const;
s64 Tell() const;
template <typename T>
size_t Read(T& data) const {
if constexpr (IsContiguousContainer<T>) {
using ContiguousType = typename T::value_type;
static_assert(std::is_trivially_copyable_v<ContiguousType>,
"Data type must be trivially copyable.");
return ReadSpan<ContiguousType>(data);
} else {
return ReadObject(data) ? 1 : 0;
}
}
template <typename T>
size_t Write(const T& data) const {
if constexpr (IsContiguousContainer<T>) {
using ContiguousType = typename T::value_type;
static_assert(std::is_trivially_copyable_v<ContiguousType>,
"Data type must be trivially copyable.");
return WriteSpan<ContiguousType>(data);
} else {
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
return WriteObject(data) ? 1 : 0;
}
}
template <typename T>
size_t ReadSpan(std::span<T> data) const {
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
if (!IsOpen()) {
return 0;
}
return ReadRaw<T>(data.data(), data.size());
}
template <typename T>
size_t ReadRaw(void* data, size_t size) const {
u64 read = std::fread(data, sizeof(T), size, file);
ASSERT_MSG(std::ferror(file) == 0, "Failed to read file, error = {}", std::strerror(errno));
return read;
}
template <typename T>
size_t WriteSpan(std::span<const T> data) const {
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
if (!IsOpen()) {
return 0;
}
u64 written = std::fwrite(data.data(), sizeof(T), data.size(), file);
ASSERT_MSG(std::ferror(file) == 0, "Failed to write to file, error = {}",
std::strerror(errno));
std::fflush(file);
return written;
}
template <typename T>
bool ReadObject(T& object) const {
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
static_assert(!std::is_pointer_v<T>, "T must not be a pointer to an object.");
if (!IsOpen()) {
return false;
}
bool success = std::fread(&object, sizeof(T), 1, file) == 1;
ASSERT_MSG(std::ferror(file) == 0, "Failed to read file, error = {}", std::strerror(errno));
return success;
}
template <typename T>
size_t WriteRaw(const void* data, size_t size) const {
u64 bytes = std::fwrite(data, sizeof(T), size, file);
ASSERT_MSG(std::ferror(file) == 0, "Failed to write to file, error = {}",
std::strerror(errno));
std::fflush(file);
return bytes;
}
template <typename T>
bool WriteObject(const T& object) const {
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
static_assert(!std::is_pointer_v<T>, "T must not be a pointer to an object.");
if (!IsOpen()) {
return false;
}
u64 bytes = std::fwrite(&object, sizeof(T), 1, file) == 1;
ASSERT_MSG(std::ferror(file) == 0, "Failed to read file, error = {}", std::strerror(errno));
std::fflush(file);
return bytes;
}
std::string ReadString(size_t length) const;
size_t WriteString(std::span<const char> string) const {
return WriteSpan(string);
}
static size_t WriteBytes(const std::filesystem::path path, const auto& data) {
IOFile out(path, FileAccessMode::Create);
return out.Write(data);
}
std::FILE* file = nullptr;
private:
std::filesystem::path file_path;
FileAccessMode file_access_mode{};
FileType file_type{};
uintptr_t file_mapping = 0;
};
u64 GetDirectorySize(const std::filesystem::path& path);
} // namespace Common::FS
+202
View File
@@ -0,0 +1,202 @@
// SPDX-FileCopyrightText: Copyright 2025-2026 shadLauncher4 Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <fstream>
#include <iomanip>
#include <iostream>
#include <stdexcept>
#include <toml.hpp>
#include "common/logging/formatter.h"
#include "common/logging/log.h"
#include "key_manager.h"
#include "path_util.h"
std::shared_ptr<KeyManager> KeyManager::s_instance = nullptr;
std::mutex KeyManager::s_mutex;
// ------------------- Constructor & Singleton -------------------
KeyManager::KeyManager() {
SetDefaultKeys();
}
KeyManager::~KeyManager() {
SaveToFile();
}
std::shared_ptr<KeyManager> KeyManager::GetInstance() {
std::lock_guard<std::mutex> lock(s_mutex);
if (!s_instance)
s_instance = std::make_shared<KeyManager>();
return s_instance;
}
void KeyManager::SetInstance(std::shared_ptr<KeyManager> instance) {
std::lock_guard<std::mutex> lock(s_mutex);
s_instance = instance;
}
// ------------------- Load / Save -------------------
bool KeyManager::TransferTrophyKey() {
try {
auto path = Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.toml";
if (!std::filesystem::exists(path)) {
return false;
}
// Parse the old config
std::ifstream ifs;
ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit);
ifs.open(path, std::ios_base::binary);
toml::value og_data =
toml::parse(ifs, std::string{fmt::UTF(path.filename().u8string()).data});
// Retrieve old trophy key and store it.
if (og_data.contains("Keys")) {
const auto& keys = og_data.at("Keys").as_table();
auto it = keys.find("TrophyKey");
if (it == keys.end()) {
return false;
}
std::string old_key = toml::get<std::string>(it->second);
if (!old_key.empty()) {
m_keys.TrophyKeySet.ReleaseTrophyKey = KeyManager::HexStringToBytes(old_key);
return true;
}
}
} catch (std::exception& ex) {
fmt::print("Got exception trying to load config file. Exception: {}\n", ex.what());
}
return false;
}
bool KeyManager::LoadFromFile() {
try {
const auto userDir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
const auto keysPath = userDir / "keys.json";
if (!std::filesystem::exists(keysPath)) {
SetDefaultKeys();
TransferTrophyKey();
SaveToFile();
return true;
}
std::ifstream file(keysPath);
if (!file.is_open()) {
LOG_ERROR(KeyManager, "Could not open key file: {}", keysPath.string());
return false;
}
json j;
file >> j;
SetDefaultKeys(); // start from defaults
if (j.contains("TrophyKeySet")) {
j.at("TrophyKeySet").get_to(m_keys.TrophyKeySet);
}
if (m_keys.TrophyKeySet.ReleaseTrophyKey.empty()) {
// No key, try to transfer it from the old configs
if (TransferTrophyKey()) {
// If we did transfer something, make sure to save it.
SaveToFile();
}
}
return true;
} catch (const std::exception& e) {
LOG_ERROR(KeyManager, "Error loading keys, using defaults: {}", e.what());
SetDefaultKeys();
return false;
}
}
bool KeyManager::SaveToFile() {
try {
const auto userDir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
const auto keysPath = userDir / "keys.json";
json j;
KeysToJson(j);
std::ofstream file(keysPath);
if (!file.is_open()) {
LOG_ERROR(KeyManager, "Could not open key file for writing: {}", keysPath.string());
return false;
}
file << std::setw(4) << j;
file.flush();
if (file.fail()) {
LOG_ERROR(KeyManager, "Failed to write keys to: {}", keysPath.string());
return false;
}
return true;
} catch (const std::exception& e) {
LOG_ERROR(KeyManager, "Error saving keys: {}", e.what());
return false;
}
}
// ------------------- JSON conversion -------------------
void KeyManager::KeysToJson(json& j) const {
j = m_keys;
}
void KeyManager::JsonToKeys(const json& j) {
json current = m_keys; // serialize current defaults
current.update(j); // merge only fields present in file
m_keys = current.get<AllKeys>(); // deserialize back
}
// ------------------- Defaults / Checks -------------------
void KeyManager::SetDefaultKeys() {
m_keys = AllKeys{};
}
bool KeyManager::HasKeys() const {
return !m_keys.TrophyKeySet.ReleaseTrophyKey.empty();
}
// ------------------- Hex conversion -------------------
std::vector<u8> KeyManager::HexStringToBytes(const std::string& hexStr) {
std::vector<u8> bytes;
if (hexStr.empty())
return bytes;
if (hexStr.size() % 2 != 0)
throw std::runtime_error("Invalid hex string length");
bytes.reserve(hexStr.size() / 2);
auto hexCharToInt = [](char c) -> u8 {
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
throw std::runtime_error("Invalid hex character");
};
for (size_t i = 0; i < hexStr.size(); i += 2) {
u8 high = hexCharToInt(hexStr[i]);
u8 low = hexCharToInt(hexStr[i + 1]);
bytes.push_back((high << 4) | low);
}
return bytes;
}
std::string KeyManager::BytesToHexString(const std::vector<u8>& bytes) {
static const char hexDigits[] = "0123456789ABCDEF";
std::string hexStr;
hexStr.reserve(bytes.size() * 2);
for (u8 b : bytes) {
hexStr.push_back(hexDigits[(b >> 4) & 0xF]);
hexStr.push_back(hexDigits[b & 0xF]);
}
return hexStr;
}
+80
View File
@@ -0,0 +1,80 @@
// SPDX-FileCopyrightText: Copyright 2025-2026 shadLauncher4 Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <cstdint>
#include <filesystem>
#include <memory>
#include <mutex>
#include <string>
#include <vector>
#include "common/types.h"
#include "nlohmann/json.hpp"
using json = nlohmann::json;
class KeyManager {
public:
// ------------------- Nested keysets -------------------
struct TrophyKeySet {
std::vector<u8> ReleaseTrophyKey;
};
struct AllKeys {
KeyManager::TrophyKeySet TrophyKeySet;
};
// ------------------- Construction -------------------
KeyManager();
~KeyManager();
// ------------------- Singleton -------------------
static std::shared_ptr<KeyManager> GetInstance();
static void SetInstance(std::shared_ptr<KeyManager> instance);
// ------------------- File operations -------------------
bool TransferTrophyKey();
bool LoadFromFile();
bool SaveToFile();
// ------------------- Key operations -------------------
void SetDefaultKeys();
bool HasKeys() const;
// ------------------- Getters / Setters -------------------
const AllKeys& GetAllKeys() const {
return m_keys;
}
void SetAllKeys(const AllKeys& keys) {
m_keys = keys;
}
static std::vector<u8> HexStringToBytes(const std::string& hexStr);
static std::string BytesToHexString(const std::vector<u8>& bytes);
private:
void KeysToJson(json& j) const;
void JsonToKeys(const json& j);
AllKeys m_keys{};
static std::shared_ptr<KeyManager> s_instance;
static std::mutex s_mutex;
};
// ------------------- NLOHMANN macros -------------------
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(KeyManager::TrophyKeySet, ReleaseTrophyKey)
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(KeyManager::AllKeys, TrophyKeySet)
namespace nlohmann {
template <>
struct adl_serializer<std::vector<u8>> {
static void to_json(json& j, const std::vector<u8>& vec) {
j = KeyManager::BytesToHexString(vec);
}
static void from_json(const json& j, std::vector<u8>& vec) {
vec = KeyManager::HexStringToBytes(j.get<std::string>());
}
};
} // namespace nlohmann
+120
View File
@@ -0,0 +1,120 @@
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
namespace Common::Log::Class {
// clang-format off
/// Listing all log classes, if you add here, dont forget ALL_LOGGERS
constexpr auto Common = "Common"; ///< Library routines
constexpr auto Common_Filesystem = "Common.Filesystem"; ///< Filesystem interface library
constexpr auto Common_Memory = "Common.Memory"; ///< Memory mapping and management functions
constexpr auto Config = "Config"; ///< Emulator configuration (including commandline)
constexpr auto Core = "Core"; ///< LLE emulation core
constexpr auto Core_Devices = "Core.Devices"; ///< Devices emulation
constexpr auto Core_Linker = "Core.Linker"; ///< The module linker
constexpr auto Debug = "Debug"; ///< Debugging tools
constexpr auto Frontend = "Frontend"; ///< Emulator UI
constexpr auto IPC = "IPC"; ///< IPC
constexpr auto ImGui = "ImGui"; ///< ImGui
constexpr auto Input = "Input"; ///< Input emulation
constexpr auto Kernel = "Kernel"; ///< The HLE implementation of the PS4 kernel.
constexpr auto Kernel_Event = "Kernel.Event"; ///< The event management implementation of the kernel.
constexpr auto Kernel_Fs = "Kernel.Fs"; ///< The filesystem implementation of the kernel.
constexpr auto Kernel_Pthread = "Kernel.Pthread"; ///< The pthread implementation of the kernel.
constexpr auto Kernel_Sce = "Kernel.Sce"; ///< The Sony-specific interfaces provided by the kernel.
constexpr auto Kernel_Vmm = "Kernel.Vmm"; ///< The virtual memory implementation of the kernel.
constexpr auto KeyManager = "KeyManager"; ///< Key management system
constexpr auto Lib = "Lib"; ///< HLE implementation of system library. Each major library should have its own subclass.
constexpr auto Lib_Ajm = "Lib.Ajm"; ///< The LibSceAjm implementation.
constexpr auto Lib_AppContent = "Lib.AppContent"; ///< The LibSceAppContent implementation.
constexpr auto Lib_Audio3d = "Lib.Audio3d"; ///< The LibSceAudio3d implementation.
constexpr auto Lib_AudioIn = "Lib.AudioIn"; ///< The LibSceAudioIn implementation.
constexpr auto Lib_AudioOut = "Lib.AudioOut"; ///< The LibSceAudioOut implementation.
constexpr auto Lib_AvPlayer = "Lib.AvPlayer"; ///< The LibSceAvPlayer implementation.
constexpr auto Lib_Camera = "Lib.Camera"; ///< The LibSceCamera implementation.
constexpr auto Lib_CommonDlg = "Lib.CommonDlg"; ///< The LibSceCommonDialog implementation.
constexpr auto Lib_CompanionHttpd = "Lib.CompanionHttpd"; ///< The LibSceCompanionHttpd implementation.
constexpr auto Lib_CompanionUtil = "Lib.CompanionUtil"; ///< The LibSceCompanionUtil implementation.
constexpr auto Lib_ContentExport = "Lib.ContentExport"; ///< The LibSceContentExport implementation.
constexpr auto Lib_DiscMap = "Lib.DiscMap"; ///< The LibSceDiscMap implementation.
constexpr auto Lib_ErrorDialog = "Lib.ErrorDialog"; ///< The LibSceErrorDialog implementation.
constexpr auto Lib_Fiber = "Lib.Fiber"; ///< The LibSceFiber implementation.
constexpr auto Lib_Font = "Lib.Font"; ///< The libSceFont implementation.
constexpr auto Lib_FontFt = "Lib.FontFt"; ///< The libSceFontFt implementation.
constexpr auto Lib_GameLiveStreaming = "Lib.GameLiveStreaming"; ///< The LibSceGameLiveStreaming implementation
constexpr auto Lib_GnmDriver = "Lib.GnmDriver"; ///< The LibSceGnmDriver implementation.
constexpr auto Lib_Hmd = "Lib.Hmd"; ///< The LibSceHmd implementation.
constexpr auto Lib_HmdSetupDialog = "Lib.HmdSetupDialog"; ///< The LibSceHmdSetupDialog implementation.
constexpr auto Lib_Http = "Lib.Http"; ///< The LibSceHttp implementation.
constexpr auto Lib_Http2 = "Lib.Http2"; ///< The LibSceHttp2 implementation.
constexpr auto Lib_Ime = "Lib.Ime"; ///< The LibSceIme implementation
constexpr auto Lib_ImeDialog = "Lib.ImeDialog"; ///< The LibSceImeDialog implementation.
constexpr auto Lib_InvitationDialog="Lib.InvitationDialog"; ///< The LibSceInvitationDialog implementation.
constexpr auto Lib_Jpeg = "Lib.Jpeg"; ///< The LibSceJpeg implementation.
constexpr auto Lib_Kernel = "Lib.Kernel"; ///< The LibKernel implementation.
constexpr auto Lib_LibcInternal = "Lib.LibcInternal"; ///< The LibSceLibcInternal implementation.
constexpr auto Lib_Mouse = "Lib.Mouse"; ///< The LibSceMouse implementation
constexpr auto Lib_Move = "Lib.Move"; ///< The LibSceMove implementation.
constexpr auto Lib_MsgDlg = "Lib.MsgDlg"; ///< The LibSceMsgDialog implementation.
constexpr auto Lib_Net = "Lib.Net"; ///< The LibSceNet implementation.
constexpr auto Lib_NetCtl = "Lib.NetCtl"; ///< The LibSceNetCtl implementation.
constexpr auto Lib_Ngs2 = "Lib.Ngs2"; ///< The LibSceNgs2 implementation.
constexpr auto Lib_NpAuth = "Lib.NpAuth"; ///< The LibSceNpAuth implementation
constexpr auto Lib_NpCommerce = "Lib.NpCommerce"; ///< The LibSceNpCommerce implementation
constexpr auto Lib_NpCommon = "Lib.NpCommon"; ///< The LibSceNpCommon implementation
constexpr auto Lib_NpManager = "Lib.NpManager"; ///< The LibSceNpManager implementation
constexpr auto Lib_NpMatching2 = "Lib.NpMatching2"; ///< The LibSceNpMatching2 implementation
constexpr auto Lib_NpSignaling = "Lib.NpSignaling"; ///< The LibSceNpSignaling implementation
constexpr auto Lib_NpPartner = "Lib.NpPartner"; ///< The LibSceNpPartner implementation
constexpr auto Lib_NpParty = "Lib.NpParty"; ///< The LibSceNpParty implementation
constexpr auto Lib_NpProfileDialog = "Lib.NpProfileDialog"; ///< The LibSceNpProfileDialog implementation
constexpr auto Lib_NpScore = "Lib.NpScore"; ///< The LibSceNpScore implementation
constexpr auto Lib_NpSnsFacebookDialog = "Lib.NpSnsFacebookDialog"; ///< The LibSceNpSnsFacebookDialog implementation
constexpr auto Lib_NpTrophy = "Lib.NpTrophy"; ///< The LibSceNpTrophy implementation
constexpr auto Lib_NpTus = "Lib.NpTus"; ///< The LibSceNpTus implementation
constexpr auto Lib_NpUtility = "Lib.NpUtility"; ///< The LibSceNpUtility implementation
constexpr auto Lib_NpWebApi = "Lib.NpWebApi"; ///< The LibSceWebApi implementation
constexpr auto Lib_NpWebApi2 = "Lib.NpWebApi2"; ///< The LibSceWebApi2 implementation
constexpr auto Lib_Pad = "Lib.Pad"; ///< The LibScePad implementation.
constexpr auto Lib_PlayGo = "Lib.PlayGo"; ///< The LibScePlayGo implementation.
constexpr auto Lib_PlayGoDialog = "Lib.PlayGoDialog"; ///< The LibScePlayGoDialog implementation.
constexpr auto Lib_Png = "Lib.Png"; ///< The LibScePng implementation.
constexpr auto Lib_Random = "Lib.Random"; ///< The LibSceRandom implementation.
constexpr auto Lib_RazorCpu = "Lib.RazorCpu"; ///< The LibRazorCpu implementation.
constexpr auto Lib_Remoteplay = "Lib.Remoteplay"; ///< The LibSceRemotePlay implementation
constexpr auto Lib_Rtc = "Lib.Rtc"; ///< The LibSceRtc implementation.
constexpr auto Lib_Rudp = "Lib.Rudp"; ///< The LibSceRudp implementation.
constexpr auto Lib_SaveData = "Lib.SaveData"; ///< The LibSceSaveData implementation.
constexpr auto Lib_SaveDataDialog = "Lib.SaveDataDialog"; ///< The LibSceSaveDataDialog implementation.
constexpr auto Lib_Screenshot = "Lib.Screenshot"; ///< The LibSceScreenshot implementation
constexpr auto Lib_SharePlay = "Lib.SharePlay"; ///< The LibSceSharePlay implemenation
constexpr auto Lib_SigninDialog = "Lib.SigninDialog"; ///< The LibSigninDialog implementation.
constexpr auto Lib_Ssl = "Lib.Ssl"; ///< The LibSceSsl implementation.
constexpr auto Lib_Ssl2 = "Lib.Ssl2"; ///< The LibSceSsl2 implementation.
constexpr auto Lib_SysModule = "Lib.SysModule"; ///< The LibSceSysModule implementation
constexpr auto Lib_SystemGesture = "Lib.SystemGesture"; ///< The LibSceSystemGesture implementation.
constexpr auto Lib_SystemService = "Lib.SystemService"; ///< The LibSceSystemService implementation.
constexpr auto Lib_Usbd = "Lib.Usbd"; ///< The LibSceUsbd implementation.
constexpr auto Lib_UserService = "Lib.UserService"; ///< The LibSceUserService implementation.
constexpr auto Lib_Vdec2 = "Lib.Vdec2"; ///< The LibSceVideodec2 implementation.
constexpr auto Lib_Vdecsw = "Lib.Vdecsw"; ///< The LibSceVdecsw implementation.
constexpr auto Lib_VideoOut = "Lib.VideoOut"; ///< The LibSceVideoOut implementation.
constexpr auto Lib_Videodec = "Lib.Videodec"; ///< The LibSceVideodec implementation.
constexpr auto Lib_VideoRecording = "Lib.VideoRecording"; ///< The LibSceVideoRecording implementation.
constexpr auto Lib_Voice = "Lib.Voice"; ///< The LibSceVoice implementation.
constexpr auto Lib_VrTracker = "Lib.VrTracker"; ///< The LibSceVrTracker implementation.
constexpr auto Lib_WebBrowserDialog = "Lib.WebBrowserDialog"; ///< The LibSceWebBrowserDialog implementation
constexpr auto Lib_Zlib = "Lib.Zlib"; ///< The LibSceZlib implementation.
constexpr auto Loader = "Loader"; ///< ROM loader
constexpr auto Log = "Log"; ///< Messages about the log system itself
constexpr auto NpHandler = "NpHandler"; ///< NpHandler shadNet manager
constexpr auto Render = "Render"; ///< Video Core
constexpr auto Render_Recompiler = "Render.Recompiler"; ///< Shader recompiler
constexpr auto Render_Vulkan = "Render.Vulkan"; ///< Vulkan backend
constexpr auto ShadNet = "ShadNet"; ///< shadNet binary protocol client
constexpr auto Tty = "Tty"; ///< Debug output from emu
// clang-format on
} // namespace Common::Log::Class
+42
View File
@@ -0,0 +1,42 @@
// SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <type_traits>
#include <fmt/format.h>
// Adapted from https://github.com/fmtlib/fmt/issues/2704
// a generic formatter for enum classes
#if FMT_VERSION >= 80100
template <typename T>
struct fmt::formatter<T, std::enable_if_t<std::is_enum_v<T>, char>>
: formatter<std::underlying_type_t<T>> {
template <typename FormatContext>
auto format(const T& value, FormatContext& ctx) -> decltype(ctx.out()) {
return fmt::formatter<std::underlying_type_t<T>>::format(
static_cast<std::underlying_type_t<T>>(value), ctx);
}
};
#endif
namespace fmt {
template <typename T = std::string_view>
struct UTF {
T data;
explicit UTF(const std::u8string_view view) {
data = view.empty() ? T{} : T{(const char*)&view.front(), (const char*)&view.back() + 1};
}
explicit UTF(const std::u8string& str) : UTF(std::u8string_view{str}) {}
};
} // namespace fmt
template <>
struct fmt::formatter<fmt::UTF<std::string_view>, char> : formatter<std::string_view> {
template <typename FormatContext>
auto format(const UTF<std::string_view>& wrapper, FormatContext& ctx) const {
return formatter<std::string_view>::format(wrapper.data, ctx);
}
};
+302
View File
@@ -0,0 +1,302 @@
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstdlib>
#include <iostream>
#include <string>
#include <fmt/std.h>
#include <spdlog/sinks/async_sink.h>
#include <spdlog/sinks/dup_filter_sink.h>
#ifdef _WIN32
#include <Windows.h>
#endif
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/logging/log_file_sink.h"
#include "common/types.h"
#include "core/emulator_settings.h"
namespace Common::Log {
bool g_should_append = false;
static std::shared_ptr<spdlog_stdout> g_console_sink;
static std::shared_ptr<LogFileSink> g_shad_file_sink;
std::unordered_map<std::string_view, std::shared_ptr<spdlog::logger>> ALL_LOGGERS{
{Class::Common, nullptr},
{Class::Common_Filesystem, nullptr},
{Class::Common_Memory, nullptr},
{Class::Config, nullptr},
{Class::Core, nullptr},
{Class::Core_Devices, nullptr},
{Class::Core_Linker, nullptr},
{Class::Debug, nullptr},
{Class::Frontend, nullptr},
{Class::IPC, nullptr},
{Class::ImGui, nullptr},
{Class::Input, nullptr},
{Class::Kernel, nullptr},
{Class::Kernel_Event, nullptr},
{Class::Kernel_Fs, nullptr},
{Class::Kernel_Pthread, nullptr},
{Class::Kernel_Sce, nullptr},
{Class::Kernel_Vmm, nullptr},
{Class::KeyManager, nullptr},
{Class::Lib, nullptr},
{Class::Lib_Ajm, nullptr},
{Class::Lib_AppContent, nullptr},
{Class::Lib_Audio3d, nullptr},
{Class::Lib_AudioIn, nullptr},
{Class::Lib_AudioOut, nullptr},
{Class::Lib_AvPlayer, nullptr},
{Class::Lib_Camera, nullptr},
{Class::Lib_CommonDlg, nullptr},
{Class::Lib_CompanionHttpd, nullptr},
{Class::Lib_CompanionUtil, nullptr},
{Class::Lib_ContentExport, nullptr},
{Class::Lib_DiscMap, nullptr},
{Class::Lib_ErrorDialog, nullptr},
{Class::Lib_Fiber, nullptr},
{Class::Lib_Font, nullptr},
{Class::Lib_FontFt, nullptr},
{Class::Lib_GameLiveStreaming, nullptr},
{Class::Lib_GnmDriver, nullptr},
{Class::Lib_Hmd, nullptr},
{Class::Lib_HmdSetupDialog, nullptr},
{Class::Lib_Http, nullptr},
{Class::Lib_Http2, nullptr},
{Class::Lib_Ime, nullptr},
{Class::Lib_ImeDialog, nullptr},
{Class::Lib_InvitationDialog, nullptr},
{Class::Lib_Jpeg, nullptr},
{Class::Lib_Kernel, nullptr},
{Class::Lib_LibcInternal, nullptr},
{Class::Lib_Mouse, nullptr},
{Class::Lib_Move, nullptr},
{Class::Lib_MsgDlg, nullptr},
{Class::Lib_Net, nullptr},
{Class::Lib_NetCtl, nullptr},
{Class::Lib_Ngs2, nullptr},
{Class::Lib_NpAuth, nullptr},
{Class::Lib_NpCommerce, nullptr},
{Class::Lib_NpCommon, nullptr},
{Class::Lib_NpManager, nullptr},
{Class::Lib_NpMatching2, nullptr},
{Class::Lib_NpSignaling, nullptr},
{Class::Lib_NpPartner, nullptr},
{Class::Lib_NpParty, nullptr},
{Class::Lib_NpProfileDialog, nullptr},
{Class::Lib_NpScore, nullptr},
{Class::Lib_NpSnsFacebookDialog, nullptr},
{Class::Lib_NpTrophy, nullptr},
{Class::Lib_NpTus, nullptr},
{Class::Lib_NpUtility, nullptr},
{Class::Lib_NpWebApi, nullptr},
{Class::Lib_NpWebApi2, nullptr},
{Class::Lib_Pad, nullptr},
{Class::Lib_PlayGo, nullptr},
{Class::Lib_PlayGoDialog, nullptr},
{Class::Lib_Png, nullptr},
{Class::Lib_Random, nullptr},
{Class::Lib_RazorCpu, nullptr},
{Class::Lib_Remoteplay, nullptr},
{Class::Lib_Rtc, nullptr},
{Class::Lib_Rudp, nullptr},
{Class::Lib_SaveData, nullptr},
{Class::Lib_SaveDataDialog, nullptr},
{Class::Lib_Screenshot, nullptr},
{Class::Lib_SharePlay, nullptr},
{Class::Lib_SigninDialog, nullptr},
{Class::Lib_Ssl, nullptr},
{Class::Lib_Ssl2, nullptr},
{Class::Lib_SysModule, nullptr},
{Class::Lib_SystemGesture, nullptr},
{Class::Lib_SystemService, nullptr},
{Class::Lib_Usbd, nullptr},
{Class::Lib_UserService, nullptr},
{Class::Lib_Vdec2, nullptr},
{Class::Lib_Vdecsw, nullptr},
{Class::Lib_VideoOut, nullptr},
{Class::Lib_Videodec, nullptr},
{Class::Lib_VideoRecording, nullptr},
{Class::Lib_Voice, nullptr},
{Class::Lib_VrTracker, nullptr},
{Class::Lib_WebBrowserDialog, nullptr},
{Class::Lib_Zlib, nullptr},
{Class::Loader, nullptr},
{Class::Log, nullptr},
{Class::NpHandler, nullptr},
{Class::Render, nullptr},
{Class::Render_Recompiler, nullptr},
{Class::Render_Vulkan, nullptr},
{Class::ShadNet, nullptr},
{Class::Tty, nullptr},
};
template <typename T>
static auto UpdateColorLevels(T sink) {
#ifdef _WIN32
using LogColor = std::uint16_t;
const auto Grey = FOREGROUND_INTENSITY;
const auto Cyan = FOREGROUND_GREEN | FOREGROUND_BLUE;
const auto Bright_gray = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
const auto Bright_yellow = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY;
const auto Bright_red = FOREGROUND_RED | FOREGROUND_INTENSITY;
const auto Bright_magenta = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY;
#else
using LogColor = std::string_view;
#define ESC "\x1b"
const auto Grey = ESC "[1;30m";
const auto Cyan = ESC "[0;36m";
const auto Bright_gray = ESC "[0;37m";
const auto Bright_yellow = ESC "[1;33m";
const auto Bright_red = ESC "[1;31m";
const auto Bright_magenta = ESC "[1;35m";
#undef ESC
#endif
const std::unordered_map<spdlog::level, LogColor> colors{
{spdlog::level::trace, Grey}, {spdlog::level::debug, Cyan},
{spdlog::level::info, Bright_gray}, {spdlog::level::warn, Bright_yellow},
{spdlog::level::err, Bright_red}, {spdlog::level::critical, Bright_magenta}};
for (const auto& [level, color] : colors) {
sink->set_color(level, color);
}
return sink;
}
void Setup(std::string_view shadps4_filename) {
static std::once_flag already_registered;
std::call_once(already_registered, []() {
std::atexit(Shutdown);
std::at_quick_exit(Flush);
});
for (auto& [name, logger] : ALL_LOGGERS) {
logger = std::make_shared<spdlog::logger>(std::string(name));
logger->set_level(spdlog::level::trace);
}
// Setup console
#ifdef _WIN32
if (EmulatorSettings.GetLogType() == "wincolor") {
g_console_sink = std::make_shared<spdlog::sinks::wincolor_stdout_sink_mt>();
} else {
g_console_sink = std::make_shared<spdlog::sinks::msvc_sink_mt>();
}
#else
g_console_sink = UpdateColorLevels(std::make_shared<spdlog_stdout>(spdlog::color_mode::always));
#endif
g_console_sink->set_pattern("%^%v%$");
// Setup file
g_shad_file_sink = std::make_shared<LogFileSink>(
(GetUserPath(Common::FS::PathType::LogDir) / shadps4_filename).string(), false,
EmulatorSettings.GetLogSizeLimit());
g_shad_file_sink->set_pattern("%^%v%$");
UpdateSinks();
}
void Switch(std::string_view game_filename) {
UpdateSinks();
UpdateLogLevels(EmulatorSettings.GetLogFilter());
UpdateLogFlushLevel(EmulatorSettings.GetLogFlushLevel());
g_shad_file_sink->_size_limit = EmulatorSettings.GetLogSizeLimit();
g_shad_file_sink->session_file_helper_.open(
(GetUserPath(Common::FS::PathType::LogDir) / game_filename).string(), !g_should_append);
}
void Shutdown() {
for (auto& logger : ALL_LOGGERS | std::views::values) {
logger.reset();
}
g_shad_file_sink.reset();
g_console_sink.reset();
}
void Flush() {
if (g_shad_file_sink != nullptr) {
g_shad_file_sink->flush();
}
if (g_console_sink != nullptr) {
g_console_sink->flush();
}
}
void UpdateSinks() {
std::initializer_list<spdlog::sink_ptr> sinks{g_console_sink, g_shad_file_sink};
std::initializer_list<spdlog::sink_ptr> async_sink{std::make_shared<spdlog::sinks::async_sink>(
spdlog::sinks::async_sink::config{.sinks = sinks})};
std::initializer_list<spdlog::sink_ptr> dup_filter{
std::make_shared<spdlog::sinks::dup_filter_sink_mt>(
std::chrono::milliseconds(EmulatorSettings.GetLogMaxSkipDuration()),
EmulatorSettings.IsLogSync() ? sinks : async_sink)};
for (auto& [name, logger] : ALL_LOGGERS) {
logger->sinks() = EmulatorSettings.IsLogSkipDuplicate()
? dup_filter
: (EmulatorSettings.IsLogSync() ? sinks : async_sink);
}
}
void UpdateLogLevels(std::string_view log_filter) {
spdlog::level default_log_level = spdlog::level::info;
std::unordered_map<std::string, spdlog::level> log_level_per_class;
if (EmulatorSettings.IsLogEnable()) {
for (const auto class_level : std::views::split(log_filter, ' ')) {
const auto class_level_pair =
std::views::split(class_level, ':') | std::ranges::to<std::vector<std::string>>();
if (class_level_pair.size() != 2) {
LOG_ERROR(Config, "bad log filter provided");
continue;
}
if (class_level_pair.front()[0] == '*') {
default_log_level = spdlog::level_from_str(class_level_pair.back() |
std::ranges::to<std::string>());
} else {
log_level_per_class[class_level_pair.front() | std::ranges::to<std::string>()] =
spdlog::level_from_str(class_level_pair.back() |
std::ranges::to<std::string>());
}
}
}
for (auto& [name, logger] : ALL_LOGGERS) {
if (EmulatorSettings.IsLogEnable()) {
const auto level_it = log_level_per_class.find(std::string(name));
logger->set_level(level_it != log_level_per_class.end() ? level_it->second
: default_log_level);
} else {
logger->set_level(spdlog::level::off);
}
}
}
void UpdateLogFlushLevel(std::string_view log_flush_level) {
if (!log_flush_level.empty()) {
for (auto& [name, logger] : ALL_LOGGERS) {
logger->flush_on(spdlog::level_from_str(log_flush_level.data()));
}
}
}
} // namespace Common::Log
+82
View File
@@ -0,0 +1,82 @@
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <iostream>
#include <unordered_map>
#include <vector>
#include <spdlog/details/fmt_helper.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#ifdef _WIN32
#include <spdlog/sinks/msvc_sink.h>
#include <spdlog/sinks/wincolor_sink.h>
using spdlog_stdout = spdlog::sinks::sink;
#else
using spdlog_stdout = spdlog::sinks::stdout_color_sink_mt;
#endif
#include <spdlog/spdlog.h>
#include "common/logging/classes.h"
#include "common/path_util.h"
#include "common/thread.h"
namespace Common::Log {
extern bool g_should_append;
extern std::unordered_map<std::string_view, std::shared_ptr<spdlog::logger>> ALL_LOGGERS;
void Setup(std::string_view shadps4_filename);
void Switch(std::string_view game_filename);
void Shutdown();
void Flush();
void Terminate();
void UpdateSinks();
void UpdateLogLevels(std::string_view log_filter);
void UpdateLogFlushLevel(std::string_view log_flush_level);
static constexpr std::array level_string_views{"Trace", "Debug", "Info", "Warning",
"Error", "Critical", "Off"};
[[nodiscard]] static constexpr std::string_view to_string_view(spdlog::level lvl) noexcept {
return level_string_views.at(level_to_number(lvl));
}
} // namespace Common::Log
// Define the fmt lib macros
#define LOG_GENERIC(log_class, log_level, format, ...) \
do { \
if (auto logger = Common::Log::ALL_LOGGERS[log_class]; \
logger && logger->should_log(log_level)) { \
logger->log(log_level, "[{}] <{}> ({}) {}:{} {}: " format, log_class, \
Common::Log::to_string_view(log_level), Common::GetCurrentThreadName(), \
spdlog::source_loc::basename(__FILE__), __LINE__, \
std::string_view(__func__) == "operator()" ? "lambda" : __func__, \
##__VA_ARGS__); \
} \
} while (false)
#ifdef NDEBUG
#define LOG_TRACE(log_class, ...) (void(0))
#else
#define LOG_TRACE(log_class, ...) \
LOG_GENERIC(Common::Log::Class::log_class, spdlog::level::trace, __VA_ARGS__)
#endif
#define LOG_DEBUG(log_class, ...) \
LOG_GENERIC(Common::Log::Class::log_class, spdlog::level::debug, __VA_ARGS__)
#define LOG_INFO(log_class, ...) \
LOG_GENERIC(Common::Log::Class::log_class, spdlog::level::info, __VA_ARGS__)
#define LOG_WARNING(log_class, ...) \
LOG_GENERIC(Common::Log::Class::log_class, spdlog::level::warn, __VA_ARGS__)
#define LOG_ERROR(log_class, ...) \
LOG_GENERIC(Common::Log::Class::log_class, spdlog::level::err, __VA_ARGS__)
#define LOG_CRITICAL(log_class, ...) \
LOG_GENERIC(Common::Log::Class::log_class, spdlog::level::critical, __VA_ARGS__)
@@ -0,0 +1,53 @@
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <string_view>
#include <spdlog/common.h>
#include <spdlog/details/log_msg.h>
#include <spdlog/formatter.h>
#include <spdlog/sinks/base_sink.h>
namespace Common::Log {
static constexpr unsigned long long UNLIMITED_SIZE = 0;
/*
* Trivial file sink with two files as target : main and session
*/
class LogFileSink final : public spdlog::sinks::base_sink<std::mutex> {
public:
explicit LogFileSink(const spdlog::filename_t& main_filename, bool truncate = false,
unsigned long long size_limit = UNLIMITED_SIZE)
: _size_limit{size_limit} {
main_file_helper_.open(main_filename, truncate);
}
spdlog::details::file_helper main_file_helper_;
spdlog::details::file_helper session_file_helper_;
unsigned long long _size_limit;
protected:
void sink_it_(const spdlog::details::log_msg& msg) override {
spdlog::memory_buf_t formatted;
this->formatter_->format(msg, formatted);
if (session_file_helper_.filename().empty()) {
// always log in the main file, it shouldnt be too big
main_file_helper_.write(formatted);
} else if (_current_size < _size_limit || msg.log_level == spdlog::level::critical) {
session_file_helper_.write(formatted);
_current_size += formatted.size();
}
}
void flush_() override {
main_file_helper_.flush();
session_file_helper_.flush();
}
unsigned long long _current_size = 0;
};
} // namespace Common::Log
+135
View File
@@ -0,0 +1,135 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <deque>
#include <type_traits>
#include "common/types.h"
namespace Common {
template <typename ObjectType, typename TickType>
class LeastRecentlyUsedCache {
struct Item {
ObjectType obj;
TickType tick;
Item* next{};
Item* prev{};
};
public:
LeastRecentlyUsedCache() : first_item{}, last_item{} {}
~LeastRecentlyUsedCache() = default;
size_t Insert(ObjectType obj, TickType tick) {
const auto new_id = Build();
auto& item = item_pool[new_id];
item.obj = obj;
item.tick = tick;
Attach(item);
return new_id;
}
void Touch(size_t id, TickType tick) {
auto& item = item_pool[id];
if (item.tick >= tick) {
return;
}
item.tick = tick;
if (&item == last_item) {
return;
}
Detach(item);
Attach(item);
}
void Free(size_t id) {
auto& item = item_pool[id];
Detach(item);
item.prev = nullptr;
item.next = nullptr;
free_items.push_back(id);
}
template <typename Func>
void ForEachItemBelow(TickType tick, Func&& func) {
static constexpr bool RETURNS_BOOL =
std::is_same_v<std::invoke_result<Func, ObjectType>, bool>;
Item* iterator = first_item;
while (iterator) {
if (static_cast<s64>(tick) - static_cast<s64>(iterator->tick) < 0) {
return;
}
Item* next = iterator->next;
if constexpr (RETURNS_BOOL) {
if (func(iterator->obj)) {
return;
}
} else {
func(iterator->obj);
}
iterator = next;
}
}
private:
size_t Build() {
if (free_items.empty()) {
const size_t item_id = item_pool.size();
auto& item = item_pool.emplace_back();
item.next = nullptr;
item.prev = nullptr;
return item_id;
}
const size_t item_id = free_items.front();
free_items.pop_front();
auto& item = item_pool[item_id];
item.next = nullptr;
item.prev = nullptr;
return item_id;
}
void Attach(Item& item) {
if (!first_item) {
first_item = &item;
}
if (!last_item) {
last_item = &item;
} else {
item.prev = last_item;
last_item->next = &item;
item.next = nullptr;
last_item = &item;
}
}
void Detach(Item& item) {
if (item.prev) {
item.prev->next = item.next;
}
if (item.next) {
item.next->prev = item.prev;
}
if (&item == first_item) {
first_item = item.next;
if (first_item) {
first_item->prev = nullptr;
}
}
if (&item == last_item) {
last_item = item.prev;
if (last_item) {
last_item->next = nullptr;
}
}
}
std::deque<Item> item_pool;
std::deque<size_t> free_items;
Item* first_item{};
Item* last_item{};
};
} // namespace Common
+460
View File
@@ -0,0 +1,460 @@
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <codecvt>
#include <fstream>
#include <sstream>
#include <string>
#include <nlohmann/json.hpp>
#include <pugixml.hpp>
#include "common/elf_info.h"
#include "common/logging/log.h"
#include "common/path_util.h"
#include "core/emulator_state.h"
#include "core/file_format/psf.h"
#include "memory_patcher.h"
namespace MemoryPatcher {
EXPORT uintptr_t g_eboot_address;
uint64_t g_eboot_image_size;
std::string g_game_serial;
std::string patch_file;
bool patches_applied = false;
std::vector<patchInfo> pending_patches;
std::string toHex(u64 value, size_t byteSize) {
std::stringstream ss;
ss << std::hex << std::setfill('0') << std::setw(byteSize * 2) << value;
return ss.str();
}
static bool isHexSym(const std::string& s) {
return (s.size() >= 2 && (s[0] == '$' || s[0] == '#'));
}
static bool isHex0x(const std::string& s) {
return (s.size() >= 3 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X'));
}
// possible prefixes == (s == `0x` || s == `#` || s == `$`) will be interpet as hex value, else is
// decimal
static int convertNumBase(const std::string& s) {
return (isHex0x(s) || isHexSym(s)) ? 16 : 10;
}
std::string convertValueToHex(const std::string type, const std::string valueStr) {
std::string result;
if (type == "byte") {
const u32 value = std::stoul(valueStr, nullptr, convertNumBase(valueStr));
result = toHex(value, 1);
} else if (type == "bytes16") {
const u32 value = std::stoul(valueStr, nullptr, convertNumBase(valueStr));
result = toHex(value, 2);
} else if (type == "bytes32") {
const u32 value = std::stoul(valueStr, nullptr, convertNumBase(valueStr));
result = toHex(value, 4);
} else if (type == "bytes64") {
const u64 value = std::stoull(valueStr, nullptr, convertNumBase(valueStr));
result = toHex(value, 8);
} else if (type == "float32") {
union {
float f;
uint32_t i;
} floatUnion;
floatUnion.f = std::stof(valueStr);
result = toHex(std::byteswap(floatUnion.i), sizeof(floatUnion.i));
} else if (type == "float64") {
union {
double d;
uint64_t i;
} doubleUnion;
doubleUnion.d = std::stod(valueStr);
result = toHex(std::byteswap(doubleUnion.i), sizeof(doubleUnion.i));
} else if (type == "utf8") {
std::vector<unsigned char> byteArray =
std::vector<unsigned char>(valueStr.begin(), valueStr.end());
byteArray.push_back('\0');
std::stringstream ss;
for (unsigned char c : byteArray) {
ss << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(c);
}
result = ss.str();
} else if (type == "utf16") {
std::wstring wide_str(valueStr.size(), L'\0');
std::mbstowcs(&wide_str[0], valueStr.c_str(), valueStr.size());
wide_str.resize(std::wcslen(wide_str.c_str()));
std::u16string valueStringU16;
for (wchar_t wc : wide_str) {
if (wc <= 0xFFFF) {
valueStringU16.push_back(static_cast<char16_t>(wc));
} else {
wc -= 0x10000;
valueStringU16.push_back(static_cast<char16_t>(0xD800 | (wc >> 10)));
valueStringU16.push_back(static_cast<char16_t>(0xDC00 | (wc & 0x3FF)));
}
}
std::vector<unsigned char> byteArray;
// convert to little endian
for (char16_t ch : valueStringU16) {
unsigned char low_byte = static_cast<unsigned char>(ch & 0x00FF);
unsigned char high_byte = static_cast<unsigned char>((ch >> 8) & 0x00FF);
byteArray.push_back(low_byte);
byteArray.push_back(high_byte);
}
byteArray.push_back('\0');
byteArray.push_back('\0');
std::stringstream ss;
for (unsigned char ch : byteArray) {
ss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(ch);
}
result = ss.str();
} else if (type == "bytes") {
result = valueStr;
} else if (type == "mask" || type == "mask_jump32") {
result = valueStr;
} else {
LOG_INFO(Loader, "Error applying Patch, unknown type: {}", type);
}
return result;
}
void ApplyPendingPatches();
void ApplyPatchesFromXML(std::filesystem::path path) {
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file(path.c_str());
auto* param_sfo = Common::Singleton<PSF>::Instance();
auto app_version = param_sfo->GetString("APP_VER").value_or("Unknown version");
if (result) {
auto patchXML = doc.child("Patch");
for (pugi::xml_node_iterator it = patchXML.children().begin();
it != patchXML.children().end(); ++it) {
if (std::string(it->name()) == "Metadata") {
if (std::string(it->attribute("isEnabled").value()) == "true") {
std::string currentPatchName = it->attribute("Name").value();
std::string metadataAppVer = it->attribute("AppVer").value();
bool versionMatches = metadataAppVer == app_version;
auto patchList = it->first_child();
for (pugi::xml_node_iterator patchLineIt = patchList.children().begin();
patchLineIt != patchList.children().end(); ++patchLineIt) {
std::string type = patchLineIt->attribute("Type").value();
if (!versionMatches && type != "mask" && type != "mask_jump32")
continue;
std::string address = patchLineIt->attribute("Address").value();
std::string patchValue = patchLineIt->attribute("Value").value();
std::string maskOffsetStr = patchLineIt->attribute("Offset").value();
std::string targetStr = "";
std::string sizeStr = "";
if (type == "mask_jump32") {
targetStr = patchLineIt->attribute("Target").value();
sizeStr = patchLineIt->attribute("Size").value();
} else {
try {
patchValue = convertValueToHex(type, patchValue);
} catch (std::exception& e) {
ASSERT_MSG(false,
"Failed to parse patch value \"{}\" for \"{}\" in "
"patch \"{}\", error: \"{}\"\n"
"If the patch was working on earlier versions, then it "
"was using a format that shadPS4 handled incorrectly, "
"and the patch should instead be fixed.",
patchValue, address, currentPatchName, e.what());
}
}
bool littleEndian = false;
if (type == "bytes16" || type == "bytes32" || type == "bytes64") {
littleEndian = true;
}
MemoryPatcher::PatchMask patchMask = MemoryPatcher::PatchMask::None;
int maskOffsetValue = 0;
if (type == "mask")
patchMask = MemoryPatcher::PatchMask::Mask;
if (type == "mask_jump32")
patchMask = MemoryPatcher::PatchMask::Mask_Jump32;
if ((type == "mask" || type == "mask_jump32") && !maskOffsetStr.empty()) {
maskOffsetValue = std::stoi(maskOffsetStr, 0, 10);
}
const patchInfo patch = {
.gameSerial = "*",
.modNameStr = currentPatchName,
.offsetStr = address,
.valueStr = patchValue,
.targetStr = targetStr,
.sizeStr = sizeStr,
.littleEndian = littleEndian,
.patchMask = patchMask,
.maskOffset = maskOffsetValue,
};
MemoryPatcher::PatchMemory(patch);
}
}
}
}
} else {
LOG_ERROR(Loader, "Could not parse patch XML: {}", result.description());
}
}
void OnGameLoaded() {
std::filesystem::path patch_dir = Common::FS::GetUserPath(Common::FS::PathType::PatchesDir);
if (!patch_file.empty()) {
auto file_path = (patch_dir / patch_file).native();
if (std::filesystem::exists(patch_file)) {
ApplyPatchesFromXML(patch_file);
} else {
ApplyPatchesFromXML(file_path);
}
} else if (EmulatorState::GetInstance()->IsAutoPatchesLoadEnabled()) {
for (auto const& repo : std::filesystem::directory_iterator(patch_dir)) {
if (!repo.is_directory()) {
continue;
}
const auto files_json_path = repo.path() / "files.json";
if (!std::filesystem::is_regular_file(files_json_path)) {
LOG_ERROR(Loader, "The folder '{}' does not contain the files.json file.",
repo.path().filename().string());
continue;
}
std::ifstream json_file{files_json_path};
nlohmann::json available_patches;
if (!json_file.is_open()) {
LOG_ERROR(Loader, "Could not open patch index: {}", files_json_path.string());
continue;
}
try {
available_patches = nlohmann::json::parse(json_file);
} catch (const nlohmann::json::parse_error& e) {
LOG_ERROR(Loader, "Could not parse patch index {}: {}", files_json_path.string(),
e.what());
continue;
}
std::filesystem::path game_patch_file;
for (auto const& [filename, serials] : available_patches.items()) {
if (std::find(serials.begin(), serials.end(), g_game_serial) != serials.end()) {
game_patch_file = repo.path() / filename;
break;
}
}
if (std::filesystem::exists(game_patch_file)) {
ApplyPatchesFromXML(game_patch_file);
}
}
}
ApplyPendingPatches();
}
void AddPatchToQueue(const patchInfo& patchToAdd) {
if (patches_applied) {
PatchMemory(patchToAdd);
return;
}
pending_patches.push_back(patchToAdd);
}
void ApplyPendingPatches() {
patches_applied = true;
for (size_t i = 0; i < pending_patches.size(); ++i) {
const patchInfo& currentPatch = pending_patches[i];
if (currentPatch.gameSerial != "*" && currentPatch.gameSerial != g_game_serial)
continue;
PatchMemory(currentPatch);
}
pending_patches.clear();
}
void PatchMemory(const patchInfo& patch) {
// Send a request to modify the process memory.
void* cheatAddress = nullptr;
if (patch.patchMask == PatchMask::None) {
if (patch.isOffset) {
cheatAddress =
reinterpret_cast<void*>(g_eboot_address + std::stoi(patch.offsetStr, 0, 16));
} else {
cheatAddress = reinterpret_cast<void*>(g_eboot_address +
(std::stoi(patch.offsetStr, 0, 16) - 0x400000));
}
}
if (patch.patchMask == PatchMask::Mask) {
cheatAddress = reinterpret_cast<void*>(PatternScan(patch.offsetStr) + patch.maskOffset);
}
if (patch.patchMask == PatchMask::Mask_Jump32) {
int jumpSize = std::stoi(patch.sizeStr);
constexpr int MAX_PATTERN_LENGTH = 256;
if (jumpSize < 5) {
LOG_ERROR(Loader, "Jump size must be at least 5 bytes");
return;
}
if (jumpSize > MAX_PATTERN_LENGTH) {
LOG_ERROR(Loader, "Jump size must be no more than {} bytes.", MAX_PATTERN_LENGTH);
return;
}
// Find the base address using "Address"
uintptr_t baseAddress = PatternScan(patch.offsetStr);
if (baseAddress == 0) {
LOG_ERROR(Loader, "PatternScan failed for mask_jump32 with pattern: {}",
patch.offsetStr);
return;
}
uintptr_t patchAddress = baseAddress + patch.maskOffset;
// Fills the original region (jumpSize bytes) with NOPs
std::vector<u8> nopBytes(jumpSize, 0x90);
std::memcpy(reinterpret_cast<void*>(patchAddress), nopBytes.data(), nopBytes.size());
// Use "Target" to locate the start of the code cave
uintptr_t jump_target = PatternScan(patch.targetStr);
if (jump_target == 0) {
LOG_ERROR(Loader, "PatternScan failed to Target with pattern: {}", patch.targetStr);
return;
}
// Converts the Value attribute to a byte array (payload)
std::vector<u8> payload;
for (size_t i = 0; i < patch.valueStr.length(); i += 2) {
std::string tempStr = patch.valueStr.substr(i, 2);
const char* byteStr = tempStr.c_str();
char* endPtr;
unsigned int byteVal = std::strtoul(byteStr, &endPtr, 16);
if (endPtr != byteStr + 2) {
LOG_ERROR(Loader, "Invalid byte in Value: {}", patch.valueStr.substr(i, 2));
return;
}
payload.push_back(static_cast<u8>(byteVal));
}
// Calculates the end of the code cave (where the return jump will be inserted)
uintptr_t code_cave_end = jump_target + payload.size();
// Write the payload to the code cave, from jump_target
std::memcpy(reinterpret_cast<void*>(jump_target), payload.data(), payload.size());
// Inserts the initial jump in the original region to divert to the code cave
u8 jumpInstruction[5];
jumpInstruction[0] = 0xE9;
s32 relJump = static_cast<s32>(jump_target - patchAddress - 5);
std::memcpy(&jumpInstruction[1], &relJump, sizeof(relJump));
std::memcpy(reinterpret_cast<void*>(patchAddress), jumpInstruction,
sizeof(jumpInstruction));
// Inserts jump back at the end of the code cave to resume execution after patching
u8 jumpBack[5];
jumpBack[0] = 0xE9;
// Calculates the relative offset to return to the instruction immediately following the
// overwritten region
s32 target_return = static_cast<s32>((patchAddress + jumpSize) - (code_cave_end + 5));
std::memcpy(&jumpBack[1], &target_return, sizeof(target_return));
std::memcpy(reinterpret_cast<void*>(code_cave_end), jumpBack, sizeof(jumpBack));
LOG_INFO(Loader,
"Applied Patch mask_jump32: {}, PatchAddress: {:#x}, JumpTarget: {:#x}, "
"CodeCaveEnd: {:#x}, JumpSize: {}",
patch.modNameStr, patchAddress, jump_target, code_cave_end, jumpSize);
return;
}
if (cheatAddress == nullptr) {
LOG_ERROR(Loader, "Failed to get address for patch {}", patch.modNameStr);
return;
}
std::vector<unsigned char> bytePatch;
for (size_t i = 0; i < patch.valueStr.length(); i += 2) {
unsigned char byte = static_cast<unsigned char>(
std::strtol(patch.valueStr.substr(i, 2).c_str(), nullptr, 16));
bytePatch.push_back(byte);
}
if (patch.littleEndian) {
std::reverse(bytePatch.begin(), bytePatch.end());
}
std::memcpy(cheatAddress, bytePatch.data(), bytePatch.size());
LOG_INFO(Loader, "Applied patch: {}, Offset: {:#x}, Value: {}", patch.modNameStr,
(uintptr_t)cheatAddress, patch.valueStr);
}
static std::vector<int32_t> PatternToByte(const std::string& pattern) {
std::vector<int32_t> bytes;
const char* start = pattern.data();
const char* end = start + pattern.size();
for (const char* current = start; current < end; ++current) {
if (*current == '?') {
++current;
if (*current == '?')
++current;
bytes.push_back(-1);
} else {
bytes.push_back(strtoul(current, const_cast<char**>(&current), 16));
}
}
return bytes;
}
uintptr_t PatternScan(const std::string& signature) {
std::vector<int32_t> patternBytes = PatternToByte(signature);
const auto scanBytes = static_cast<uint8_t*>((void*)g_eboot_address);
const int32_t* sigPtr = patternBytes.data();
const size_t sigSize = patternBytes.size();
uint32_t foundResults = 0;
for (uint32_t i = 0; i < g_eboot_image_size - sigSize; ++i) {
bool found = true;
for (uint32_t j = 0; j < sigSize; ++j) {
if (scanBytes[i + j] != sigPtr[j] && sigPtr[j] != -1) {
found = false;
break;
}
}
if (found) {
foundResults++;
return reinterpret_cast<uintptr_t>(&scanBytes[i]);
}
}
return 0;
}
} // namespace MemoryPatcher
+51
View File
@@ -0,0 +1,51 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <cstring>
#include <string>
#include <vector>
#if defined(WIN32)
#define EXPORT __declspec(dllexport)
#else
#define EXPORT __attribute__((visibility("default")))
#endif
namespace MemoryPatcher {
extern EXPORT uintptr_t g_eboot_address;
extern uint64_t g_eboot_image_size;
extern std::string g_game_serial;
extern std::string patch_file;
enum PatchMask : uint8_t {
None,
Mask,
Mask_Jump32,
};
struct patchInfo {
std::string gameSerial;
std::string modNameStr;
std::string offsetStr;
std::string valueStr;
std::string targetStr;
std::string sizeStr;
bool isOffset;
bool littleEndian;
PatchMask patchMask;
int maskOffset;
};
std::string convertValueToHex(const std::string type, const std::string valueStr);
void OnGameLoaded();
void AddPatchToQueue(const patchInfo& patchToAdd);
void PatchMemory(const patchInfo& patch);
static std::vector<int32_t> PatternToByte(const std::string& pattern);
uintptr_t PatternScan(const std::string& signature);
} // namespace MemoryPatcher
+32
View File
@@ -0,0 +1,32 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/native_clock.h"
#include "common/rdtsc.h"
#include "common/uint128.h"
namespace Common {
NativeClock::NativeClock()
: rdtsc_frequency{EstimateRDTSCFrequency()},
ns_rdtsc_factor{GetFixedPoint64Factor(std::nano::den, rdtsc_frequency)},
us_rdtsc_factor{GetFixedPoint64Factor(std::micro::den, rdtsc_frequency)},
ms_rdtsc_factor{GetFixedPoint64Factor(std::milli::den, rdtsc_frequency)} {}
u64 NativeClock::GetTimeNS(u64 base_ptc /*= 0*/) const {
return MultiplyHigh(GetUptime() - base_ptc, ns_rdtsc_factor);
}
u64 NativeClock::GetTimeUS(u64 base_ptc /*= 0*/) const {
return MultiplyHigh(GetUptime() - base_ptc, us_rdtsc_factor);
}
u64 NativeClock::GetTimeMS(u64 base_ptc /*= 0*/) const {
return MultiplyHigh(GetUptime() - base_ptc, ms_rdtsc_factor);
}
u64 NativeClock::GetUptime() const {
return FencedRDTSC();
}
} // namespace Common
+31
View File
@@ -0,0 +1,31 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <chrono>
#include "common/types.h"
namespace Common {
class NativeClock final {
public:
explicit NativeClock();
u64 GetTscFrequency() const {
return rdtsc_frequency;
}
u64 GetTimeNS(u64 base_ptc = 0) const;
u64 GetTimeUS(u64 base_ptc = 0) const;
u64 GetTimeMS(u64 base_ptc = 0) const;
u64 GetUptime() const;
private:
u64 rdtsc_frequency;
u64 ns_rdtsc_factor;
u64 us_rdtsc_factor;
u64 ms_rdtsc_factor;
};
} // namespace Common
+30
View File
@@ -0,0 +1,30 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#ifdef _WIN32
#include "ntapi.h"
NtClose_t NtClose = nullptr;
NtSetInformationFile_t NtSetInformationFile = nullptr;
NtCreateThread_t NtCreateThread = nullptr;
NtTerminateThread_t NtTerminateThread = nullptr;
NtQueueApcThreadEx_t NtQueueApcThreadEx = nullptr;
namespace Common::NtApi {
void Initialize() {
HMODULE nt_handle = GetModuleHandleA("ntdll.dll");
// http://stackoverflow.com/a/31411628/4725495
NtClose = (NtClose_t)GetProcAddress(nt_handle, "NtClose");
NtSetInformationFile =
(NtSetInformationFile_t)GetProcAddress(nt_handle, "NtSetInformationFile");
NtCreateThread = (NtCreateThread_t)GetProcAddress(nt_handle, "NtCreateThread");
NtTerminateThread = (NtTerminateThread_t)GetProcAddress(nt_handle, "NtTerminateThread");
NtQueueApcThreadEx = (NtQueueApcThreadEx_t)GetProcAddress(nt_handle, "NtQueueApcThreadEx");
}
} // namespace Common::NtApi
#endif
+554
View File
@@ -0,0 +1,554 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#ifdef _WIN32
#include <windows.h>
#include "common/types.h"
typedef enum _FILE_INFORMATION_CLASS {
FileDirectoryInformation = 1,
FileFullDirectoryInformation = 2,
FileBothDirectoryInformation = 3,
FileBasicInformation = 4,
FileStandardInformation = 5,
FileInternalInformation = 6,
FileEaInformation = 7,
FileAccessInformation = 8,
FileNameInformation = 9,
FileRenameInformation = 10,
FileLinkInformation = 11,
FileNamesInformation = 12,
FileDispositionInformation = 13,
FilePositionInformation = 14,
FileFullEaInformation = 15,
FileModeInformation = 16,
FileAlignmentInformation = 17,
FileAllInformation = 18,
FileAllocationInformation = 19,
FileEndOfFileInformation = 20,
FileAlternateNameInformation = 21,
FileStreamInformation = 22,
FilePipeInformation = 23,
FilePipeLocalInformation = 24,
FilePipeRemoteInformation = 25,
FileMailslotQueryInformation = 26,
FileMailslotSetInformation = 27,
FileCompressionInformation = 28,
FileObjectIdInformation = 29,
FileCompletionInformation = 30,
FileMoveClusterInformation = 31,
FileQuotaInformation = 32,
FileReparsePointInformation = 33,
FileNetworkOpenInformation = 34,
FileAttributeTagInformation = 35,
FileTrackingInformation = 36,
FileIdBothDirectoryInformation = 37,
FileIdFullDirectoryInformation = 38,
FileValidDataLengthInformation = 39,
FileShortNameInformation = 40,
FileIoCompletionNotificationInformation = 41,
FileIoStatusBlockRangeInformation = 42,
FileIoPriorityHintInformation = 43,
FileSfioReserveInformation = 44,
FileSfioVolumeInformation = 45,
FileHardLinkInformation = 46,
FileProcessIdsUsingFileInformation = 47,
FileNormalizedNameInformation = 48,
FileNetworkPhysicalNameInformation = 49,
FileIdGlobalTxDirectoryInformation = 50,
FileIsRemoteDeviceInformation = 51,
FileUnusedInformation = 52,
FileNumaNodeInformation = 53,
FileStandardLinkInformation = 54,
FileRemoteProtocolInformation = 55,
FileRenameInformationBypassAccessCheck = 56,
FileLinkInformationBypassAccessCheck = 57,
FileVolumeNameInformation = 58,
FileIdInformation = 59,
FileIdExtdDirectoryInformation = 60,
FileReplaceCompletionInformation = 61,
FileHardLinkFullIdInformation = 62,
FileIdExtdBothDirectoryInformation = 63,
FileDispositionInformationEx = 64,
FileRenameInformationEx = 65,
FileRenameInformationExBypassAccessCheck = 66,
FileDesiredStorageClassInformation = 67,
FileStatInformation = 68,
FileMemoryPartitionInformation = 69,
FileStatLxInformation = 70,
FileCaseSensitiveInformation = 71,
FileLinkInformationEx = 72,
FileLinkInformationExBypassAccessCheck = 73,
FileStorageReserveIdInformation = 74,
FileCaseSensitiveInformationForceAccessCheck = 75,
FileKnownFolderInformation = 76,
FileStatBasicInformation = 77,
FileId64ExtdDirectoryInformation = 78,
FileId64ExtdBothDirectoryInformation = 79,
FileIdAllExtdDirectoryInformation = 80,
FileIdAllExtdBothDirectoryInformation = 81,
FileStreamReservationInformation,
FileMupProviderInfo,
FileMaximumInformation
} FILE_INFORMATION_CLASS,
*PFILE_INFORMATION_CLASS;
typedef struct _IO_STATUS_BLOCK {
union {
u32 Status;
PVOID Pointer;
};
ULONG_PTR Information;
} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
typedef struct _FILE_DISPOSITION_INFORMATION {
BOOLEAN DeleteFile;
} FILE_DISPOSITION_INFORMATION, *PFILE_DISPOSITION_INFORMATION;
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWCH Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
typedef const UNICODE_STRING* PCUNICODE_STRING;
typedef struct _OBJECT_ATTRIBUTES {
ULONG Length;
HANDLE RootDirectory;
PCUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor; // PSECURITY_DESCRIPTOR;
PVOID SecurityQualityOfService; // PSECURITY_QUALITY_OF_SERVICE
} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;
typedef const OBJECT_ATTRIBUTES* PCOBJECT_ATTRIBUTES;
typedef struct _CLIENT_ID {
HANDLE UniqueProcess;
HANDLE UniqueThread;
} CLIENT_ID, *PCLIENT_ID;
typedef struct _INITIAL_TEB {
struct {
PVOID OldStackBase;
PVOID OldStackLimit;
} OldInitialTeb;
PVOID StackBase;
PVOID StackLimit;
PVOID StackAllocationBase;
} INITIAL_TEB, *PINITIAL_TEB;
typedef struct _PEB_LDR_DATA {
ULONG Length;
BOOLEAN Initialized;
PVOID SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
PVOID EntryInProgress;
BOOLEAN ShutdownInProgress;
HANDLE ShutdownThreadId;
} PEB_LDR_DATA, *PPEB_LDR_DATA;
typedef struct _CURDIR {
UNICODE_STRING DosPath;
PVOID Handle;
} CURDIR, *PCURDIR;
typedef struct RTL_DRIVE_LETTER_CURDIR {
USHORT Flags;
USHORT Length;
ULONG TimeStamp;
UNICODE_STRING DosPath;
} RTL_DRIVE_LETTER_CURDIR, *PRTL_DRIVE_LETTER_CURDIR;
typedef struct _RTL_USER_PROCESS_PARAMETERS {
ULONG AllocationSize;
ULONG Size;
ULONG Flags;
ULONG DebugFlags;
HANDLE ConsoleHandle;
ULONG ConsoleFlags;
HANDLE hStdInput;
HANDLE hStdOutput;
HANDLE hStdError;
CURDIR CurrentDirectory;
UNICODE_STRING DllPath;
UNICODE_STRING ImagePathName;
UNICODE_STRING CommandLine;
PWSTR Environment;
ULONG dwX;
ULONG dwY;
ULONG dwXSize;
ULONG dwYSize;
ULONG dwXCountChars;
ULONG dwYCountChars;
ULONG dwFillAttribute;
ULONG dwFlags;
ULONG wShowWindow;
UNICODE_STRING WindowTitle;
UNICODE_STRING Desktop;
UNICODE_STRING ShellInfo;
UNICODE_STRING RuntimeInfo;
RTL_DRIVE_LETTER_CURDIR DLCurrentDirectory[0x20];
ULONG_PTR EnvironmentSize;
ULONG_PTR EnvironmentVersion;
PVOID PackageDependencyData;
ULONG ProcessGroupId;
ULONG LoaderThreads;
} RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;
typedef struct tagRTL_BITMAP {
ULONG SizeOfBitMap;
PULONG Buffer;
} RTL_BITMAP, *PRTL_BITMAP;
typedef struct {
UINT next;
UINT id;
ULONGLONG addr;
ULONGLONG size;
UINT args[4];
} CROSS_PROCESS_WORK_ENTRY;
typedef union {
struct {
UINT first;
UINT counter;
};
volatile LONGLONG hdr;
} CROSS_PROCESS_WORK_HDR;
typedef struct {
CROSS_PROCESS_WORK_HDR free_list;
CROSS_PROCESS_WORK_HDR work_list;
ULONGLONG unknown[4];
CROSS_PROCESS_WORK_ENTRY entries[1];
} CROSS_PROCESS_WORK_LIST;
typedef struct _CHPEV2_PROCESS_INFO {
ULONG Wow64ExecuteFlags; /* 000 */
USHORT NativeMachineType; /* 004 */
USHORT EmulatedMachineType; /* 006 */
HANDLE SectionHandle; /* 008 */
CROSS_PROCESS_WORK_LIST* CrossProcessWorkList; /* 010 */
void* unknown; /* 018 */
} CHPEV2_PROCESS_INFO, *PCHPEV2_PROCESS_INFO;
typedef u64(__stdcall* KERNEL_CALLBACK_PROC)(void*, ULONG);
typedef struct _PEB { /* win32/win64 */
BOOLEAN InheritedAddressSpace; /* 000/000 */
BOOLEAN ReadImageFileExecOptions; /* 001/001 */
BOOLEAN BeingDebugged; /* 002/002 */
UCHAR ImageUsedLargePages : 1; /* 003/003 */
UCHAR IsProtectedProcess : 1;
UCHAR IsImageDynamicallyRelocated : 1;
UCHAR SkipPatchingUser32Forwarders : 1;
UCHAR IsPackagedProcess : 1;
UCHAR IsAppContainer : 1;
UCHAR IsProtectedProcessLight : 1;
UCHAR IsLongPathAwareProcess : 1;
HANDLE Mutant; /* 004/008 */
HMODULE ImageBaseAddress; /* 008/010 */
PPEB_LDR_DATA LdrData; /* 00c/018 */
RTL_USER_PROCESS_PARAMETERS* ProcessParameters; /* 010/020 */
PVOID SubSystemData; /* 014/028 */
HANDLE ProcessHeap; /* 018/030 */
PRTL_CRITICAL_SECTION FastPebLock; /* 01c/038 */
PVOID AtlThunkSListPtr; /* 020/040 */
PVOID IFEOKey; /* 024/048 */
ULONG ProcessInJob : 1; /* 028/050 */
ULONG ProcessInitializing : 1;
ULONG ProcessUsingVEH : 1;
ULONG ProcessUsingVCH : 1;
ULONG ProcessUsingFTH : 1;
ULONG ProcessPreviouslyThrottled : 1;
ULONG ProcessCurrentlyThrottled : 1;
ULONG ProcessImagesHotPatched : 1;
ULONG ReservedBits0 : 24;
KERNEL_CALLBACK_PROC* KernelCallbackTable; /* 02c/058 */
ULONG Reserved; /* 030/060 */
ULONG AtlThunkSListPtr32; /* 034/064 */
PVOID ApiSetMap; /* 038/068 */
ULONG TlsExpansionCounter; /* 03c/070 */
PRTL_BITMAP TlsBitmap; /* 040/078 */
ULONG TlsBitmapBits[2]; /* 044/080 */
PVOID ReadOnlySharedMemoryBase; /* 04c/088 */
PVOID SharedData; /* 050/090 */
PVOID* ReadOnlyStaticServerData; /* 054/098 */
PVOID AnsiCodePageData; /* 058/0a0 */
PVOID OemCodePageData; /* 05c/0a8 */
PVOID UnicodeCaseTableData; /* 060/0b0 */
ULONG NumberOfProcessors; /* 064/0b8 */
ULONG NtGlobalFlag; /* 068/0bc */
LARGE_INTEGER CriticalSectionTimeout; /* 070/0c0 */
SIZE_T HeapSegmentReserve; /* 078/0c8 */
SIZE_T HeapSegmentCommit; /* 07c/0d0 */
SIZE_T HeapDeCommitTotalFreeThreshold; /* 080/0d8 */
SIZE_T HeapDeCommitFreeBlockThreshold; /* 084/0e0 */
ULONG NumberOfHeaps; /* 088/0e8 */
ULONG MaximumNumberOfHeaps; /* 08c/0ec */
PVOID* ProcessHeaps; /* 090/0f0 */
PVOID GdiSharedHandleTable; /* 094/0f8 */
PVOID ProcessStarterHelper; /* 098/100 */
PVOID GdiDCAttributeList; /* 09c/108 */
PVOID LoaderLock; /* 0a0/110 */
ULONG OSMajorVersion; /* 0a4/118 */
ULONG OSMinorVersion; /* 0a8/11c */
ULONG OSBuildNumber; /* 0ac/120 */
ULONG OSPlatformId; /* 0b0/124 */
ULONG ImageSubSystem; /* 0b4/128 */
ULONG ImageSubSystemMajorVersion; /* 0b8/12c */
ULONG ImageSubSystemMinorVersion; /* 0bc/130 */
KAFFINITY ActiveProcessAffinityMask; /* 0c0/138 */
#ifdef _WIN64
ULONG GdiHandleBuffer[60]; /* /140 */
#else
ULONG GdiHandleBuffer[34]; /* 0c4/ */
#endif
PVOID PostProcessInitRoutine; /* 14c/230 */
PRTL_BITMAP TlsExpansionBitmap; /* 150/238 */
ULONG TlsExpansionBitmapBits[32]; /* 154/240 */
ULONG SessionId; /* 1d4/2c0 */
ULARGE_INTEGER AppCompatFlags; /* 1d8/2c8 */
ULARGE_INTEGER AppCompatFlagsUser; /* 1e0/2d0 */
PVOID ShimData; /* 1e8/2d8 */
PVOID AppCompatInfo; /* 1ec/2e0 */
UNICODE_STRING CSDVersion; /* 1f0/2e8 */
PVOID ActivationContextData; /* 1f8/2f8 */
PVOID ProcessAssemblyStorageMap; /* 1fc/300 */
PVOID SystemDefaultActivationData; /* 200/308 */
PVOID SystemAssemblyStorageMap; /* 204/310 */
SIZE_T MinimumStackCommit; /* 208/318 */
PVOID* FlsCallback; /* 20c/320 */
LIST_ENTRY FlsListHead; /* 210/328 */
union {
PRTL_BITMAP FlsBitmap; /* 218/338 */
#ifdef _WIN64
CHPEV2_PROCESS_INFO* ChpeV2ProcessInfo; /* /338 */
#endif
};
ULONG FlsBitmapBits[4]; /* 21c/340 */
ULONG FlsHighIndex; /* 22c/350 */
PVOID WerRegistrationData; /* 230/358 */
PVOID WerShipAssertPtr; /* 234/360 */
PVOID EcCodeBitMap; /* 238/368 */
PVOID pImageHeaderHash; /* 23c/370 */
ULONG HeapTracingEnabled : 1; /* 240/378 */
ULONG CritSecTracingEnabled : 1;
ULONG LibLoaderTracingEnabled : 1;
ULONG SpareTracingBits : 29;
ULONGLONG CsrServerReadOnlySharedMemoryBase; /* 248/380 */
ULONG TppWorkerpListLock; /* 250/388 */
LIST_ENTRY TppWorkerpList; /* 254/390 */
PVOID WaitOnAddressHashTable[0x80]; /* 25c/3a0 */
PVOID TelemetryCoverageHeader; /* 45c/7a0 */
ULONG CloudFileFlags; /* 460/7a8 */
ULONG CloudFileDiagFlags; /* 464/7ac */
CHAR PlaceholderCompatibilityMode; /* 468/7b0 */
CHAR PlaceholderCompatibilityModeReserved[7]; /* 469/7b1 */
PVOID LeapSecondData; /* 470/7b8 */
ULONG LeapSecondFlags; /* 474/7c0 */
ULONG NtGlobalFlag2; /* 478/7c4 */
} PEB, *PPEB;
typedef struct _RTL_ACTIVATION_CONTEXT_STACK_FRAME {
struct _RTL_ACTIVATION_CONTEXT_STACK_FRAME* Previous;
struct _ACTIVATION_CONTEXT* ActivationContext;
ULONG Flags;
} RTL_ACTIVATION_CONTEXT_STACK_FRAME, *PRTL_ACTIVATION_CONTEXT_STACK_FRAME;
typedef struct _ACTIVATION_CONTEXT_STACK {
RTL_ACTIVATION_CONTEXT_STACK_FRAME* ActiveFrame;
LIST_ENTRY FrameListCache;
ULONG Flags;
ULONG NextCookieSequenceNumber;
ULONG_PTR StackId;
} ACTIVATION_CONTEXT_STACK, *PACTIVATION_CONTEXT_STACK;
typedef struct _GDI_TEB_BATCH {
ULONG Offset;
HANDLE HDC;
ULONG Buffer[0x136];
} GDI_TEB_BATCH;
typedef struct _TEB_ACTIVE_FRAME_CONTEXT {
ULONG Flags;
const char* FrameName;
} TEB_ACTIVE_FRAME_CONTEXT, *PTEB_ACTIVE_FRAME_CONTEXT;
typedef struct _TEB_ACTIVE_FRAME {
ULONG Flags;
struct _TEB_ACTIVE_FRAME* Previous;
TEB_ACTIVE_FRAME_CONTEXT* Context;
} TEB_ACTIVE_FRAME, *PTEB_ACTIVE_FRAME;
typedef struct _TEB { /* win32/win64 */
NT_TIB Tib; /* 000/0000 */
PVOID EnvironmentPointer; /* 01c/0038 */
CLIENT_ID ClientId; /* 020/0040 */
PVOID ActiveRpcHandle; /* 028/0050 */
PVOID ThreadLocalStoragePointer; /* 02c/0058 */
PPEB Peb; /* 030/0060 */
ULONG LastErrorValue; /* 034/0068 */
ULONG CountOfOwnedCriticalSections; /* 038/006c */
PVOID CsrClientThread; /* 03c/0070 */
PVOID Win32ThreadInfo; /* 040/0078 */
ULONG User32Reserved[26]; /* 044/0080 */
ULONG UserReserved[5]; /* 0ac/00e8 */
PVOID WOW32Reserved; /* 0c0/0100 */
ULONG CurrentLocale; /* 0c4/0108 */
ULONG FpSoftwareStatusRegister; /* 0c8/010c */
PVOID ReservedForDebuggerInstrumentation[16]; /* 0cc/0110 */
#ifdef _WIN64
PVOID SystemReserved1[30]; /* /0190 */
#else
PVOID SystemReserved1[26]; /* 10c/ */
#endif
char PlaceholderCompatibilityMode; /* 174/0280 */
BOOLEAN PlaceholderHydrationAlwaysExplicit; /* 175/0281 */
char PlaceholderReserved[10]; /* 176/0282 */
DWORD ProxiedProcessId; /* 180/028c */
ACTIVATION_CONTEXT_STACK ActivationContextStack; /* 184/0290 */
UCHAR WorkingOnBehalfOfTicket[8]; /* 19c/02b8 */
LONG ExceptionCode; /* 1a4/02c0 */
ACTIVATION_CONTEXT_STACK* ActivationContextStackPointer; /* 1a8/02c8 */
ULONG_PTR InstrumentationCallbackSp; /* 1ac/02d0 */
ULONG_PTR InstrumentationCallbackPreviousPc; /* 1b0/02d8 */
ULONG_PTR InstrumentationCallbackPreviousSp; /* 1b4/02e0 */
#ifdef _WIN64
ULONG TxFsContext; /* /02e8 */
BOOLEAN InstrumentationCallbackDisabled; /* /02ec */
BOOLEAN UnalignedLoadStoreExceptions; /* /02ed */
#else
BOOLEAN InstrumentationCallbackDisabled; /* 1b8/ */
BYTE SpareBytes1[23]; /* 1b9/ */
ULONG TxFsContext; /* 1d0/ */
#endif
GDI_TEB_BATCH GdiTebBatch; /* 1d4/02f0 */
CLIENT_ID RealClientId; /* 6b4/07d8 */
HANDLE GdiCachedProcessHandle; /* 6bc/07e8 */
ULONG GdiClientPID; /* 6c0/07f0 */
ULONG GdiClientTID; /* 6c4/07f4 */
PVOID GdiThreadLocaleInfo; /* 6c8/07f8 */
ULONG_PTR Win32ClientInfo[62]; /* 6cc/0800 */
PVOID glDispatchTable[233]; /* 7c4/09f0 */
PVOID glReserved1[29]; /* b68/1138 */
PVOID glReserved2; /* bdc/1220 */
PVOID glSectionInfo; /* be0/1228 */
PVOID glSection; /* be4/1230 */
PVOID glTable; /* be8/1238 */
PVOID glCurrentRC; /* bec/1240 */
PVOID glContext; /* bf0/1248 */
ULONG LastStatusValue; /* bf4/1250 */
UNICODE_STRING StaticUnicodeString; /* bf8/1258 */
WCHAR StaticUnicodeBuffer[261]; /* c00/1268 */
PVOID DeallocationStack; /* e0c/1478 */
PVOID TlsSlots[64]; /* e10/1480 */
LIST_ENTRY TlsLinks; /* f10/1680 */
PVOID Vdm; /* f18/1690 */
PVOID ReservedForNtRpc; /* f1c/1698 */
PVOID DbgSsReserved[2]; /* f20/16a0 */
ULONG HardErrorMode; /* f28/16b0 */
#ifdef _WIN64
PVOID Instrumentation[11]; /* /16b8 */
#else
PVOID Instrumentation[9]; /* f2c/ */
#endif
GUID ActivityId; /* f50/1710 */
PVOID SubProcessTag; /* f60/1720 */
PVOID PerflibData; /* f64/1728 */
PVOID EtwTraceData; /* f68/1730 */
PVOID WinSockData; /* f6c/1738 */
ULONG GdiBatchCount; /* f70/1740 */
ULONG IdealProcessorValue; /* f74/1744 */
ULONG GuaranteedStackBytes; /* f78/1748 */
PVOID ReservedForPerf; /* f7c/1750 */
PVOID ReservedForOle; /* f80/1758 */
ULONG WaitingOnLoaderLock; /* f84/1760 */
PVOID SavedPriorityState; /* f88/1768 */
ULONG_PTR ReservedForCodeCoverage; /* f8c/1770 */
PVOID ThreadPoolData; /* f90/1778 */
PVOID* TlsExpansionSlots; /* f94/1780 */
#ifdef _WIN64
union {
PVOID DeallocationBStore; /* /1788 */
PVOID* ChpeV2CpuAreaInfo; /* /1788 */
} DUMMYUNIONNAME;
PVOID BStoreLimit; /* /1790 */
#endif
ULONG MuiGeneration; /* f98/1798 */
ULONG IsImpersonating; /* f9c/179c */
PVOID NlsCache; /* fa0/17a0 */
PVOID ShimData; /* fa4/17a8 */
ULONG HeapVirtualAffinity; /* fa8/17b0 */
PVOID CurrentTransactionHandle; /* fac/17b8 */
TEB_ACTIVE_FRAME* ActiveFrame; /* fb0/17c0 */
PVOID* FlsSlots; /* fb4/17c8 */
PVOID PreferredLanguages; /* fb8/17d0 */
PVOID UserPrefLanguages; /* fbc/17d8 */
PVOID MergedPrefLanguages; /* fc0/17e0 */
ULONG MuiImpersonation; /* fc4/17e8 */
USHORT CrossTebFlags; /* fc8/17ec */
USHORT SameTebFlags; /* fca/17ee */
PVOID TxnScopeEnterCallback; /* fcc/17f0 */
PVOID TxnScopeExitCallback; /* fd0/17f8 */
PVOID TxnScopeContext; /* fd4/1800 */
ULONG LockCount; /* fd8/1808 */
LONG WowTebOffset; /* fdc/180c */
PVOID ResourceRetValue; /* fe0/1810 */
PVOID ReservedForWdf; /* fe4/1818 */
ULONGLONG ReservedForCrt; /* fe8/1820 */
GUID EffectiveContainerId; /* ff0/1828 */
} TEB, *PTEB;
static_assert(offsetof(TEB, DeallocationStack) ==
0x1478); /* The only member we care about at the moment */
typedef enum _QUEUE_USER_APC_FLAGS {
QueueUserApcFlagsNone,
QueueUserApcFlagsSpecialUserApc,
QueueUserApcFlagsMaxValue
} QUEUE_USER_APC_FLAGS;
typedef union _USER_APC_OPTION {
ULONG_PTR UserApcFlags;
HANDLE MemoryReserveHandle;
} USER_APC_OPTION, *PUSER_APC_OPTION;
using PPS_APC_ROUTINE = void (*)(PVOID ApcArgument1, PVOID ApcArgument2, PVOID ApcArgument3,
PCONTEXT Context);
typedef u64(__stdcall* NtClose_t)(HANDLE Handle);
typedef u64(__stdcall* NtSetInformationFile_t)(HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock,
PVOID FileInformation, ULONG Length,
FILE_INFORMATION_CLASS FileInformationClass);
typedef u64(__stdcall* NtCreateThread_t)(PHANDLE ThreadHandle, ACCESS_MASK DesiredAccess,
PCOBJECT_ATTRIBUTES ObjectAttributes, HANDLE ProcessHandle,
PCLIENT_ID ClientId, PCONTEXT ThreadContext,
PINITIAL_TEB InitialTeb, BOOLEAN CreateSuspended);
typedef u64(__stdcall* NtTerminateThread_t)(HANDLE ThreadHandle, u64 ExitStatus);
typedef u64(__stdcall* NtQueueApcThreadEx_t)(HANDLE ThreadHandle,
USER_APC_OPTION UserApcReserveHandle,
PPS_APC_ROUTINE ApcRoutine, PVOID ApcArgument1,
PVOID ApcArgument2, PVOID ApcArgument3);
extern NtClose_t NtClose;
extern NtSetInformationFile_t NtSetInformationFile;
extern NtCreateThread_t NtCreateThread;
extern NtTerminateThread_t NtTerminateThread;
extern NtQueueApcThreadEx_t NtQueueApcThreadEx;
namespace Common::NtApi {
void Initialize();
}
#endif
+155
View File
@@ -0,0 +1,155 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <half.hpp>
#include "common/number_utils.h"
constexpr u32 UF11_EXPONENT_SHIFT = 6;
constexpr u32 UF10_EXPONENT_SHIFT = 5;
constexpr u32 RGB9E5_MANTISSA_BITS = 9;
constexpr u32 RGB9E5_EXP_BIAS = 1;
constexpr u32 F32_INFINITY = 0x7f800000;
namespace NumberUtils {
float Uf11ToF32(u16 val) {
union {
float f;
u32 ui;
} f32;
int exponent = (val & 0x07c0) >> UF11_EXPONENT_SHIFT;
int mantissa = (val & 0x003f);
f32.f = 0.0;
if (exponent == 0) {
if (mantissa != 0) {
const float scale = 1.0 / (1 << 20);
f32.f = scale * mantissa;
}
} else if (exponent == 31) {
f32.ui = F32_INFINITY | mantissa;
} else {
float scale, decimal;
exponent -= 15;
if (exponent < 0) {
scale = 1.0f / (1 << -exponent);
} else {
scale = (float)(1 << exponent);
}
decimal = 1.0f + (float)mantissa / 64;
f32.f = scale * decimal;
}
return f32.f;
}
float Uf10ToF32(u16 val) {
union {
float f;
u32 ui;
} f32;
int exponent = (val & 0x03e0) >> UF10_EXPONENT_SHIFT;
int mantissa = (val & 0x001f);
f32.f = 0.0;
if (exponent == 0) {
if (mantissa != 0) {
const float scale = 1.0 / (1 << 19);
f32.f = scale * mantissa;
}
} else if (exponent == 31) {
f32.ui = F32_INFINITY | mantissa;
} else {
float scale, decimal;
exponent -= 15;
if (exponent < 0) {
scale = 1.0f / (1 << -exponent);
} else {
scale = (float)(1 << exponent);
}
decimal = 1.0f + (float)mantissa / 32;
f32.f = scale * decimal;
}
return f32.f;
}
float Uf16ToF32(u16 val) {
return half_float::half_cast<float>(reinterpret_cast<half_float::half&>(val));
}
float U2ToUnorm(u8 val) {
static constexpr auto c = 1.0f / 3.0f;
return float(val * c);
}
float S2ToSnorm(s8 val) {
static constexpr auto c = 1.0f / 1.0f;
return float(val * c);
}
float U4ToUnorm(u8 val) {
static constexpr auto c = 1.0f / 15.0f;
return float(val * c);
}
float S4ToSnorm(s8 val) {
static constexpr auto c = 1.0f / 7.0f;
return float(val * c);
}
float U5ToUnorm(u8 val) {
static constexpr auto c = 1.0f / 31.0f;
return float(val * c);
}
float S5ToSnorm(s8 val) {
static constexpr auto c = 1.0f / 15.0f;
return float(val * c);
}
float U6ToUnorm(u8 val) {
static constexpr auto c = 1.0f / 63.0f;
return float(val * c);
}
float S6ToSnorm(s8 val) {
static constexpr auto c = 1.0f / 31.0f;
return float(val * c);
}
float U8ToUnorm(u8 val) {
static constexpr auto c = 1.0f / 255.0f;
return float(val * c);
}
float S8ToSnorm(s8 val) {
static constexpr auto c = 1.0f / 127.0f;
return float(val * c);
}
float U10ToUnorm(u16 val) {
static constexpr auto c = 1.0f / 1023.0f;
return float(val * c);
}
float S10ToSnorm(s16 val) {
static constexpr auto c = 1.0f / 511.0f;
return float(val * c);
}
float U16ToUnorm(u16 val) {
static constexpr auto c = 1.0f / 65535.0f;
return float(val * c);
}
float S16ToSnorm(s16 val) {
static constexpr auto c = 1.0f / 32767.0f;
return float(val * c);
}
} // namespace NumberUtils
+28
View File
@@ -0,0 +1,28 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
namespace NumberUtils {
float Uf11ToF32(u16 val);
float Uf10ToF32(u16 val);
float Uf16ToF32(u16 val);
float U2ToUnorm(u8 val);
float S2ToSnorm(s8 val);
float U4ToUnorm(u8 val);
float S4ToSnorm(s8 val);
float U5ToUnorm(u8 val);
float S5ToSnorm(s8 val);
float U6ToUnorm(u8 val);
float S6ToSnorm(s8 val);
float U8ToUnorm(u8 val);
float S8ToSnorm(s8 val);
float U10ToUnorm(u16 val);
float S10ToSnorm(s16 val);
float U16ToUnorm(u16 val);
float S16ToSnorm(s16 val);
} // namespace NumberUtils
+109
View File
@@ -0,0 +1,109 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <type_traits>
#include <utility>
#include <vector>
namespace Common {
template <typename T>
requires std::is_destructible_v<T>
class ObjectPool {
public:
explicit ObjectPool(size_t chunk_size = 1024) : new_chunk_size{chunk_size} {
node = &chunks.emplace_back(new_chunk_size);
}
template <typename... Args>
requires std::is_constructible_v<T, Args...>
[[nodiscard]] T* Create(Args&&... args) {
return std::construct_at(Memory(), std::forward<Args>(args)...);
}
void ReleaseContents() {
if (chunks.empty()) {
return;
}
Chunk& root{chunks.front()};
if (root.used_objects == root.num_objects) {
// Root chunk has been filled, squash allocations into it
const size_t total_objects{root.num_objects + new_chunk_size * (chunks.size() - 1)};
chunks.clear();
chunks.emplace_back(total_objects);
} else {
root.Release();
chunks.resize(1);
}
chunks.shrink_to_fit();
node = &chunks.front();
}
private:
struct NonTrivialDummy {
NonTrivialDummy() noexcept {}
};
union Storage {
Storage() noexcept {}
~Storage() noexcept {}
NonTrivialDummy dummy{};
T object;
};
struct Chunk {
explicit Chunk() = default;
explicit Chunk(size_t size)
: num_objects{size}, storage{std::make_unique<Storage[]>(size)} {}
Chunk& operator=(Chunk&& rhs) noexcept {
Release();
used_objects = std::exchange(rhs.used_objects, 0);
num_objects = std::exchange(rhs.num_objects, 0);
storage = std::move(rhs.storage);
return *this;
}
Chunk(Chunk&& rhs) noexcept
: used_objects{std::exchange(rhs.used_objects, 0)},
num_objects{std::exchange(rhs.num_objects, 0)}, storage{std::move(rhs.storage)} {}
~Chunk() {
Release();
}
void Release() {
for (size_t i = 0; i < used_objects; i++) {
storage[i].object.~T();
}
used_objects = 0;
}
size_t used_objects{};
size_t num_objects{};
std::unique_ptr<Storage[]> storage;
};
[[nodiscard]] T* Memory() {
Chunk* const chunk{FreeChunk()};
return &chunk->storage[chunk->used_objects++].object;
}
[[nodiscard]] Chunk* FreeChunk() {
if (node->used_objects != node->num_objects) {
return node;
}
node = &chunks.emplace_back(new_chunk_size);
return node;
}
Chunk* node{};
std::vector<Chunk> chunks;
size_t new_chunk_size{};
};
} // namespace Common
+250
View File
@@ -0,0 +1,250 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <fstream>
#include <unordered_map>
#include "common/logging/log.h"
#include "common/path_util.h"
#include "common/scope_exit.h"
#include "common/types.h"
#ifdef __APPLE__
#include <CoreFoundation/CFBundle.h>
#include <dlfcn.h>
#include <sys/param.h>
#endif
#ifndef MAX_PATH
#ifdef _WIN32
// This is the maximum number of UTF-16 code units permissible in Windows file paths
#define MAX_PATH 260
#include <Shlobj.h>
#include <windows.h>
#else
// This is the maximum number of UTF-8 code units permissible in all other OSes' file paths
#define MAX_PATH 1024
#endif
#endif
namespace Common::FS {
namespace fs = std::filesystem;
#ifdef __APPLE__
using IsTranslocatedURLFunc = Boolean (*)(CFURLRef path, bool* isTranslocated,
CFErrorRef* __nullable error);
using CreateOriginalPathForURLFunc = CFURLRef __nullable (*)(CFURLRef translocatedPath,
CFErrorRef* __nullable error);
static CFURLRef UntranslocateBundlePath(const CFURLRef bundle_path) {
if (void* security_handle =
dlopen("/System/Library/Frameworks/Security.framework/Security", RTLD_LAZY)) {
SCOPE_EXIT {
dlclose(security_handle);
};
const auto IsTranslocatedURL = reinterpret_cast<IsTranslocatedURLFunc>(
dlsym(security_handle, "SecTranslocateIsTranslocatedURL"));
const auto CreateOriginalPathForURL = reinterpret_cast<CreateOriginalPathForURLFunc>(
dlsym(security_handle, "SecTranslocateCreateOriginalPathForURL"));
bool is_translocated = false;
if (IsTranslocatedURL && CreateOriginalPathForURL &&
IsTranslocatedURL(bundle_path, &is_translocated, nullptr) && is_translocated) {
return CreateOriginalPathForURL(bundle_path, nullptr);
}
}
return nullptr;
}
static std::optional<std::filesystem::path> GetBundleParentDirectory() {
if (CFBundleRef bundle_ref = CFBundleGetMainBundle()) {
if (CFURLRef bundle_url_ref = CFBundleCopyBundleURL(bundle_ref)) {
SCOPE_EXIT {
CFRelease(bundle_url_ref);
};
CFURLRef untranslocated_url_ref = UntranslocateBundlePath(bundle_url_ref);
SCOPE_EXIT {
if (untranslocated_url_ref) {
CFRelease(untranslocated_url_ref);
}
};
char app_bundle_path[MAXPATHLEN];
if (CFURLGetFileSystemRepresentation(
untranslocated_url_ref ? untranslocated_url_ref : bundle_url_ref, true,
reinterpret_cast<u8*>(app_bundle_path), sizeof(app_bundle_path))) {
std::filesystem::path bundle_path{app_bundle_path};
return bundle_path.parent_path();
}
}
}
return std::nullopt;
}
#endif
static auto UserPaths = [] {
// bbport: the port keeps GPU caches where BB_GPU_USER_DIR points.
const char* port_dir = std::getenv("BB_GPU_USER_DIR");
auto user_dir = port_dir ? std::filesystem::path(port_dir) : std::filesystem::current_path() / PORTABLE_DIR;
if (!std::filesystem::exists(user_dir)) {
// If it doesn't exist, use the standard path for the platform instead.
// NOTE: On Windows we currently just create the portable directory instead.
#ifdef __APPLE__
user_dir =
std::filesystem::path(getenv("HOME")) / "Library" / "Application Support" / "shadPS4";
#elif defined(__linux__)
const char* xdg_data_home = getenv("XDG_DATA_HOME");
if (xdg_data_home != nullptr && strlen(xdg_data_home) > 0) {
user_dir = std::filesystem::path(xdg_data_home) / "shadPS4";
} else {
user_dir = std::filesystem::path(getenv("HOME")) / ".local" / "share" / "shadPS4";
}
#elif _WIN32
TCHAR appdata[MAX_PATH] = {0};
SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, appdata);
user_dir = std::filesystem::path(appdata) / "shadPS4";
#endif
}
std::unordered_map<PathType, fs::path> paths;
const auto create_path = [&](PathType shad_path, const fs::path& new_path) {
std::filesystem::create_directory(new_path);
paths.insert_or_assign(shad_path, new_path);
};
create_path(PathType::UserDir, user_dir);
create_path(PathType::LogDir, user_dir / LOG_DIR);
create_path(PathType::ScreenshotsDir, user_dir / SCREENSHOTS_DIR);
create_path(PathType::ShaderDir, user_dir / SHADER_DIR);
create_path(PathType::GameDataDir, user_dir / GAMEDATA_DIR);
create_path(PathType::TempDataDir, user_dir / TEMPDATA_DIR);
create_path(PathType::SysModuleDir, user_dir / SYSMODULES_DIR);
create_path(PathType::DownloadDir, user_dir / DOWNLOAD_DIR);
create_path(PathType::CapturesDir, user_dir / CAPTURES_DIR);
create_path(PathType::CheatsDir, user_dir / CHEATS_DIR);
create_path(PathType::PatchesDir, user_dir / PATCHES_DIR);
create_path(PathType::MetaDataDir, user_dir / METADATA_DIR);
create_path(PathType::CustomTrophy, user_dir / CUSTOM_TROPHY);
create_path(PathType::CustomConfigs, user_dir / CUSTOM_CONFIGS);
create_path(PathType::CacheDir, user_dir / CACHE_DIR);
create_path(PathType::FontsDir, user_dir / FONTS_DIR);
create_path(PathType::TrophyDir, user_dir / TROPHY_DIR);
create_path(PathType::HomeDir, user_dir / HOME_DIR);
create_path(PathType::CustomModulesDir, user_dir / CUSTOM_MODULES_DIR);
std::ofstream notice_file(user_dir / CUSTOM_TROPHY / "Notice.txt");
if (notice_file.is_open()) {
notice_file
<< "++++++++++++++++++++++++++++++++\n+ Custom Trophy Images / Sound "
"+\n++++++++++++++++++++++++++++++++\n\nYou can add custom images to the "
"trophies.\n*We recommend a square resolution image, for example 200x200, 500x500, "
"the same size as the height and width.\nIn this folder ('user\\custom_trophy'), "
"add the files with the following "
"names:\n\nbronze.png\nsilver.png\ngold.png\nplatinum.png\n\nYou can add a custom "
"sound for trophy notifications.\n*By default, no audio is played unless it is in "
"this folder and you are using the QT version.\nIn this folder "
"('user\\custom_trophy'), add the files with the following names:\n\ntrophy.mp3";
notice_file.close();
}
return paths;
}();
bool ValidatePath(const fs::path& path) {
if (path.empty()) {
LOG_ERROR(Common_Filesystem, "Input path is empty, path={}", PathToUTF8String(path));
return false;
}
#ifdef _WIN32
if (path.u16string().size() >= MAX_PATH) {
LOG_ERROR(Common_Filesystem, "Input path is too long, path={}", PathToUTF8String(path));
return false;
}
#else
if (path.u8string().size() >= MAX_PATH) {
LOG_ERROR(Common_Filesystem, "Input path is too long, path={}", PathToUTF8String(path));
return false;
}
#endif
return true;
}
std::string PathToUTF8String(const std::filesystem::path& path) {
const auto u8_string = path.u8string();
return std::string{u8_string.begin(), u8_string.end()};
}
const fs::path& GetUserPath(PathType shad_path) {
return UserPaths.at(shad_path);
}
std::string GetUserPathString(PathType shad_path) {
return PathToUTF8String(GetUserPath(shad_path));
}
void SetUserPath(PathType shad_path, const fs::path& new_path) {
if (!std::filesystem::is_directory(new_path)) {
LOG_ERROR(Common_Filesystem, "Filesystem object at new_path={} is not a directory",
PathToUTF8String(new_path));
return;
}
UserPaths.insert_or_assign(shad_path, new_path);
}
std::optional<fs::path> FindGameByID(const fs::path& dir, const std::string& game_id,
int max_depth) {
if (max_depth < 0) {
return std::nullopt;
}
const auto boot_path_for = [](const fs::path& root) -> std::optional<fs::path> {
std::error_code ec;
if (fs::is_directory(root, ec) && !ec) {
if (!fs::exists(root / "sce_sys" / "param.sfo")) {
return std::nullopt;
}
if (auto eboot_path = root / "eboot.bin"; fs::exists(eboot_path)) {
return eboot_path;
}
return std::nullopt;
}
// bbport: packed (ZArchive) game images are not supported by the port.
return std::nullopt;
};
// Check if this is the game we're looking for
if (dir.filename() == game_id) {
if (auto found = boot_path_for(dir)) {
return found;
}
}
if (auto found = boot_path_for(dir / game_id)) {
return found;
}
if (auto found = boot_path_for(dir / (game_id + ".zar"))) {
return found;
}
// Recursively search subdirectories
std::error_code ec;
for (const auto& entry : fs::directory_iterator(dir, ec)) {
std::error_code entry_ec;
if (!entry.is_directory(entry_ec) || entry_ec) {
continue;
}
if (auto found = FindGameByID(entry.path(), game_id, max_depth - 1)) {
return found;
}
}
return std::nullopt;
}
} // namespace Common::FS
+123
View File
@@ -0,0 +1,123 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <filesystem>
#include <optional>
#include <vector>
namespace Common::FS {
enum class PathType {
UserDir, // Where shadPS4 stores its data.
LogDir, // Where log files are stored.
ScreenshotsDir, // Where screenshots are stored.
ShaderDir, // Where shaders are stored.
TempDataDir, // Where game temp data is stored.
GameDataDir, // Where game data is stored.
SysModuleDir, // Where system modules are stored.
DownloadDir, // Where downloads/temp files are stored.
CapturesDir, // Where rdoc captures are stored.
CheatsDir, // Where cheats are stored.
PatchesDir, // Where patches are stored.
MetaDataDir, // Where game metadata (e.g. trophies and menu backgrounds) is stored.
CustomTrophy, // Where custom files for trophies are stored.
CustomConfigs, // Where custom files for different games are stored.
CacheDir, // Where pipeline and shader cache is stored.
FontsDir, // Where dumped system fonts are stored.
TrophyDir, // Where general trophy metadata is stored.
HomeDir, // PS4 home directory
CustomModulesDir // Where custom modules are stored.
};
constexpr auto PORTABLE_DIR = "user";
// Sub-directories contained within a user data directory
constexpr auto LOG_DIR = "log";
constexpr auto SCREENSHOTS_DIR = "screenshots";
constexpr auto SHADER_DIR = "shader";
constexpr auto GAMEDATA_DIR = "data";
constexpr auto TEMPDATA_DIR = "temp";
constexpr auto SYSMODULES_DIR = "sys_modules";
constexpr auto DOWNLOAD_DIR = "download";
constexpr auto CAPTURES_DIR = "captures";
constexpr auto CHEATS_DIR = "cheats";
constexpr auto PATCHES_DIR = "patches";
constexpr auto METADATA_DIR = "game_data";
constexpr auto CUSTOM_TROPHY = "custom_trophy";
constexpr auto CUSTOM_CONFIGS = "custom_configs";
constexpr auto CACHE_DIR = "cache";
constexpr auto FONTS_DIR = "fonts";
constexpr auto TROPHY_DIR = "trophy";
constexpr auto HOME_DIR = "home";
constexpr auto CUSTOM_MODULES_DIR = "custom_modules";
// Filenames
constexpr auto LOG_FILE = "shad_log.txt";
/**
* Validates a given path.
*
* A given path is valid if it meets these conditions:
* - The path is not empty
* - The path is not too long
*
* @param path Filesystem path
*
* @returns True if the path is valid, false otherwise.
*/
[[nodiscard]] bool ValidatePath(const std::filesystem::path& path);
/**
* Converts a filesystem path to a UTF-8 encoded std::string.
*
* @param path Filesystem path
*
* @returns UTF-8 encoded std::string.
*/
[[nodiscard]] std::string PathToUTF8String(const std::filesystem::path& path);
/**
* Gets the filesystem path associated with the PathType enum.
*
* @param user_path PathType enum
*
* @returns The filesystem path associated with the PathType enum.
*/
[[nodiscard]] const std::filesystem::path& GetUserPath(PathType user_path);
/**
* Gets the filesystem path associated with the PathType enum as a UTF-8 encoded std::string.
*
* @param user_path PathType enum
*
* @returns The filesystem path associated with the PathType enum as a UTF-8 encoded std::string.
*/
[[nodiscard]] std::string GetUserPathString(PathType user_path);
/**
* Sets a new filesystem path associated with the PathType enum.
* If the filesystem object at new_path is not a directory, this function will not do anything.
*
* @param user_path PathType enum
* @param new_path New filesystem path
*/
void SetUserPath(PathType user_path, const std::filesystem::path& new_path);
/**
* Recursively searches for an installed game by its ID.
* Limits search depth to prevent excessive filesystem traversal.
*
* @param dir Base directory to start the search from
* @param game_id The game ID to search for
* @param max_depth Maximum directory depth to search
*
* @returns A path to eboot.bin for an unpacked game, or the ".zar" archive itself for a
* packed one. std::nullopt if the game isn't found.
*/
[[nodiscard]] std::optional<std::filesystem::path> FindGameByID(const std::filesystem::path& dir,
const std::string& game_id,
int max_depth);
} // namespace Common::FS
+377
View File
@@ -0,0 +1,377 @@
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
//
// TODO: remove this file when jthread is supported by all compilation targets
//
#pragma once
#include <version>
#ifdef __cpp_lib_jthread
#include <chrono>
#include <condition_variable>
#include <stop_token>
#include <thread>
#include <utility>
namespace Common {
template <typename Condvar, typename Lock, typename Pred>
void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred&& pred) {
cv.wait(lk, token, std::forward<Pred>(pred));
}
template <typename Rep, typename Period>
bool StoppableTimedWait(std::stop_token token, const std::chrono::duration<Rep, Period>& rel_time) {
std::condition_variable_any cv;
std::mutex m;
// Perform the timed wait.
std::unique_lock lk{m};
return !cv.wait_for(lk, token, rel_time, [&] { return token.stop_requested(); });
}
} // namespace Common
#else
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <functional>
#include <map>
#include <memory>
#include <mutex>
#include <optional>
#include <thread>
#include <type_traits>
#include <utility>
namespace std {
namespace polyfill {
using stop_state_callback = size_t;
class stop_state {
public:
stop_state() = default;
~stop_state() = default;
bool request_stop() {
unique_lock lk{m_lock};
if (m_stop_requested) {
// Already set, nothing to do.
return false;
}
// Mark stop requested.
m_stop_requested = true;
while (!m_callbacks.empty()) {
// Get an iterator to the first element.
const auto it = m_callbacks.begin();
// Move the callback function out of the map.
function<void()> f;
swap(it->second, f);
// Erase the now-empty map element.
m_callbacks.erase(it);
// Run the callback.
if (f) {
f();
}
}
return true;
}
bool stop_requested() const {
unique_lock lk{m_lock};
return m_stop_requested;
}
stop_state_callback insert_callback(function<void()> f) {
unique_lock lk{m_lock};
if (m_stop_requested) {
// Stop already requested. Don't insert anything,
// just run the callback synchronously.
if (f) {
f();
}
return 0;
}
// Insert the callback.
stop_state_callback ret = ++m_next_callback;
m_callbacks.emplace(ret, std::move(f));
return ret;
}
void remove_callback(stop_state_callback cb) {
unique_lock lk{m_lock};
m_callbacks.erase(cb);
}
private:
mutable recursive_mutex m_lock;
map<stop_state_callback, function<void()>> m_callbacks;
stop_state_callback m_next_callback{0};
bool m_stop_requested{false};
};
} // namespace polyfill
class stop_token;
class stop_source;
struct nostopstate_t {
explicit nostopstate_t() = default;
};
inline constexpr nostopstate_t nostopstate{};
template <class Callback>
class stop_callback;
class stop_token {
public:
stop_token() noexcept = default;
stop_token(const stop_token&) noexcept = default;
stop_token(stop_token&&) noexcept = default;
stop_token& operator=(const stop_token&) noexcept = default;
stop_token& operator=(stop_token&&) noexcept = default;
~stop_token() = default;
void swap(stop_token& other) noexcept {
m_stop_state.swap(other.m_stop_state);
}
[[nodiscard]] bool stop_requested() const noexcept {
return m_stop_state && m_stop_state->stop_requested();
}
[[nodiscard]] bool stop_possible() const noexcept {
return m_stop_state != nullptr;
}
private:
friend class stop_source;
template <typename Callback>
friend class stop_callback;
stop_token(shared_ptr<polyfill::stop_state> stop_state) : m_stop_state(std::move(stop_state)) {}
private:
shared_ptr<polyfill::stop_state> m_stop_state;
};
class stop_source {
public:
stop_source() : m_stop_state(make_shared<polyfill::stop_state>()) {}
explicit stop_source(nostopstate_t) noexcept {}
stop_source(const stop_source&) noexcept = default;
stop_source(stop_source&&) noexcept = default;
stop_source& operator=(const stop_source&) noexcept = default;
stop_source& operator=(stop_source&&) noexcept = default;
~stop_source() = default;
void swap(stop_source& other) noexcept {
m_stop_state.swap(other.m_stop_state);
}
[[nodiscard]] stop_token get_token() const noexcept {
return stop_token(m_stop_state);
}
[[nodiscard]] bool stop_possible() const noexcept {
return m_stop_state != nullptr;
}
[[nodiscard]] bool stop_requested() const noexcept {
return m_stop_state && m_stop_state->stop_requested();
}
bool request_stop() noexcept {
return m_stop_state && m_stop_state->request_stop();
}
private:
friend class jthread;
explicit stop_source(shared_ptr<polyfill::stop_state> stop_state)
: m_stop_state(std::move(stop_state)) {}
private:
shared_ptr<polyfill::stop_state> m_stop_state;
};
template <typename Callback>
class stop_callback {
static_assert(is_nothrow_destructible_v<Callback>);
static_assert(is_invocable_v<Callback>);
public:
using callback_type = Callback;
template <typename C>
requires constructible_from<Callback, C>
explicit stop_callback(const stop_token& st,
C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>)
: m_stop_state(st.m_stop_state) {
if (m_stop_state) {
m_callback = m_stop_state->insert_callback(std::move(cb));
}
}
template <typename C>
requires constructible_from<Callback, C>
explicit stop_callback(stop_token&& st,
C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>)
: m_stop_state(std::move(st.m_stop_state)) {
if (m_stop_state) {
m_callback = m_stop_state->insert_callback(std::move(cb));
}
}
~stop_callback() {
if (m_stop_state && m_callback) {
m_stop_state->remove_callback(m_callback);
}
}
stop_callback(const stop_callback&) = delete;
stop_callback(stop_callback&&) = delete;
stop_callback& operator=(const stop_callback&) = delete;
stop_callback& operator=(stop_callback&&) = delete;
private:
shared_ptr<polyfill::stop_state> m_stop_state;
polyfill::stop_state_callback m_callback;
};
template <typename Callback>
stop_callback(stop_token, Callback) -> stop_callback<Callback>;
class jthread {
public:
using id = thread::id;
using native_handle_type = thread::native_handle_type;
jthread() noexcept = default;
template <typename F, typename... Args,
typename = enable_if_t<!is_same_v<remove_cvref_t<F>, jthread>>>
explicit jthread(F&& f, Args&&... args)
: m_stop_state(make_shared<polyfill::stop_state>()),
m_thread(make_thread(std::forward<F>(f), std::forward<Args>(args)...)) {}
~jthread() {
if (joinable()) {
request_stop();
join();
}
}
jthread(const jthread&) = delete;
jthread(jthread&&) noexcept = default;
jthread& operator=(const jthread&) = delete;
jthread& operator=(jthread&& other) noexcept {
m_thread.swap(other.m_thread);
m_stop_state.swap(other.m_stop_state);
return *this;
}
void swap(jthread& other) noexcept {
m_thread.swap(other.m_thread);
m_stop_state.swap(other.m_stop_state);
}
[[nodiscard]] bool joinable() const noexcept {
return m_thread.joinable();
}
void join() {
m_thread.join();
}
void detach() {
m_thread.detach();
m_stop_state.reset();
}
[[nodiscard]] id get_id() const noexcept {
return m_thread.get_id();
}
[[nodiscard]] native_handle_type native_handle() {
return m_thread.native_handle();
}
[[nodiscard]] stop_source get_stop_source() noexcept {
return stop_source(m_stop_state);
}
[[nodiscard]] stop_token get_stop_token() const noexcept {
return stop_source(m_stop_state).get_token();
}
bool request_stop() noexcept {
return get_stop_source().request_stop();
}
[[nodiscard]] static unsigned int hardware_concurrency() noexcept {
return thread::hardware_concurrency();
}
private:
template <typename F, typename... Args>
thread make_thread(F&& f, Args&&... args) {
if constexpr (is_invocable_v<decay_t<F>, stop_token, decay_t<Args>...>) {
return thread(std::forward<F>(f), get_stop_token(), std::forward<Args>(args)...);
} else {
return thread(std::forward<F>(f), std::forward<Args>(args)...);
}
}
shared_ptr<polyfill::stop_state> m_stop_state;
thread m_thread;
};
} // namespace std
namespace Common {
template <typename Condvar, typename Lock, typename Pred>
void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred pred) {
if (token.stop_requested()) {
return;
}
std::stop_callback callback(token, [&] {
{
std::scoped_lock lk2{*lk.mutex()};
}
cv.notify_all();
});
cv.wait(lk, [&] { return pred() || token.stop_requested(); });
}
template <typename Rep, typename Period>
bool StoppableTimedWait(std::stop_token token, const std::chrono::duration<Rep, Period>& rel_time) {
if (token.stop_requested()) {
return false;
}
bool stop_requested = false;
std::condition_variable cv;
std::mutex m;
std::stop_callback cb(token, [&] {
// Wake up the waiting thread.
{
std::scoped_lock lk{m};
stop_requested = true;
}
cv.notify_one();
});
// Perform the timed wait.
std::unique_lock lk{m};
return !cv.wait_for(lk, rel_time, [&] { return stop_requested; });
}
} // namespace Common
#endif
+101
View File
@@ -0,0 +1,101 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <iterator>
#include <mutex>
namespace Common {
// From boost thread locking
template <typename Iterator>
struct RangeLockGuard {
Iterator begin;
Iterator end;
RangeLockGuard(Iterator begin_, Iterator end_) : begin(begin_), end(end_) {
LockRange(begin, end);
}
void release() {
begin = end;
}
~RangeLockGuard() {
for (; begin != end; ++begin) {
begin->unlock();
}
}
};
template <typename Iterator>
Iterator TryLockRange(Iterator begin, Iterator end) {
using LockType = typename std::iterator_traits<Iterator>::value_type;
if (begin == end) {
return end;
}
std::unique_lock<LockType> guard(*begin, std::try_to_lock);
if (!guard.owns_lock()) {
return begin;
}
Iterator failed = TryLockRange(++begin, end);
if (failed == end) {
guard.release();
}
return failed;
}
template <typename Iterator>
void LockRange(Iterator begin, Iterator end) {
using LockType = typename std::iterator_traits<Iterator>::value_type;
if (begin == end) {
return;
}
bool start_with_begin = true;
Iterator second = begin;
++second;
Iterator next = second;
while (true) {
std::unique_lock<LockType> begin_lock(*begin, std::defer_lock);
if (start_with_begin) {
begin_lock.lock();
const Iterator failed_lock = TryLockRange(next, end);
if (failed_lock == end) {
begin_lock.release();
return;
}
start_with_begin = false;
next = failed_lock;
} else {
RangeLockGuard<Iterator> guard(next, end);
if (begin_lock.try_lock()) {
const Iterator failed_lock = TryLockRange(second, next);
if (failed_lock == next) {
begin_lock.release();
guard.release();
return;
}
start_with_begin = false;
next = failed_lock;
} else {
start_with_begin = true;
next = second;
}
}
}
}
} // namespace Common
+60
View File
@@ -0,0 +1,60 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <thread>
#include "common/rdtsc.h"
#include "common/uint128.h"
#ifdef _WIN64
#include <windows.h>
#endif
namespace Common {
static constexpr size_t SecondToNanoseconds = 1000000000ULL;
template <u64 Nearest>
static u64 RoundToNearest(u64 value) {
const auto mod = value % Nearest;
return mod >= (Nearest / 2) ? (value - mod + Nearest) : (value - mod);
}
static u64 GetTimeNs() {
#ifdef _WIN64
// GetSystemTimePreciseAsFileTime returns the file time in 100ns units.
static constexpr u64 Multiplier = 100;
// Convert Windows epoch to Unix epoch.
static constexpr u64 WindowsEpochToUnixEpoch = 0x19DB1DED53E8000LL;
FILETIME filetime;
GetSystemTimePreciseAsFileTime(&filetime);
return Multiplier * ((static_cast<u64>(filetime.dwHighDateTime) << 32) +
static_cast<u64>(filetime.dwLowDateTime) - WindowsEpochToUnixEpoch);
#elif defined(__APPLE__)
return clock_gettime_nsec_np(CLOCK_REALTIME);
#else
timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
return ts.tv_sec * SecondToNanoseconds + ts.tv_nsec;
#endif
}
u64 EstimateRDTSCFrequency() {
// Discard the first result measuring the rdtsc.
FencedRDTSC();
std::this_thread::sleep_for(std::chrono::milliseconds{1});
FencedRDTSC();
// Get the current time.
const auto start_time = GetTimeNs();
const u64 tsc_start = FencedRDTSC();
// Wait for 100 milliseconds.
std::this_thread::sleep_for(std::chrono::milliseconds{100});
const auto end_time = GetTimeNs();
const u64 tsc_end = FencedRDTSC();
// Calculate differences.
const u64 tsc_diff = tsc_end - tsc_start;
const u64 tsc_freq = MultiplyAndDivide64(tsc_diff, 1000000000ULL, end_time - start_time);
return RoundToNearest<100'000>(tsc_freq);
}
} // namespace Common
+54
View File
@@ -0,0 +1,54 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/arch.h"
#ifdef _MSC_VER
#include <intrin.h>
#endif
#include "common/types.h"
namespace Common {
#ifdef _MSC_VER
__forceinline static u64 FencedRDTSC() {
#ifdef ARCH_X86_64
_mm_lfence();
_ReadWriteBarrier();
const u64 result = __rdtsc();
_mm_lfence();
_ReadWriteBarrier();
return result;
#else
#error "Missing FencedRDTSC() implementation for target CPU architecture."
#endif
}
#else
static inline u64 FencedRDTSC() {
#ifdef ARCH_X86_64
u64 eax;
u64 edx;
asm volatile("lfence\n\t"
"rdtsc\n\t"
"lfence\n\t"
: "=a"(eax), "=d"(edx));
return (edx << 32) | eax;
#elif defined(ARCH_ARM64)
u64 ret;
asm volatile("isb\n\t"
"mrs %0, cntvct_el0\n\t"
"isb\n\t"
: "=r"(ret)::"memory");
return ret;
#else
#error "Missing FencedRDTSC() implementation for target CPU architecture."
#endif
}
#endif
u64 EstimateRDTSCFrequency();
} // namespace Common
+37
View File
@@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <unordered_map>
#include "common/assert.h"
#include "common/recursive_lock.h"
namespace Common::Detail {
struct RecursiveLockState {
RecursiveLockType type;
int count;
};
thread_local std::unordered_map<void*, RecursiveLockState> g_recursive_locks;
bool IncrementRecursiveLock(void* mutex, RecursiveLockType type) {
auto& state = g_recursive_locks[mutex];
if (state.count == 0) {
ASSERT(state.type == RecursiveLockType::None);
state.type = type;
}
ASSERT(state.type == type);
return state.count++ == 0;
}
bool DecrementRecursiveLock(void* mutex, RecursiveLockType type) {
auto& state = g_recursive_locks[mutex];
ASSERT(state.type == type && state.count > 0);
if (--state.count == 0) {
g_recursive_locks.erase(mutex);
return true;
}
return false;
}
} // namespace Common::Detail
+67
View File
@@ -0,0 +1,67 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <mutex>
#include <optional>
#include <shared_mutex>
namespace Common {
namespace Detail {
enum class RecursiveLockType { None, Shared, Exclusive };
bool IncrementRecursiveLock(void* mutex, RecursiveLockType type);
bool DecrementRecursiveLock(void* mutex, RecursiveLockType type);
} // namespace Detail
template <typename MutexType>
class RecursiveScopedLock {
public:
explicit RecursiveScopedLock(MutexType& mutex) : m_mutex(mutex), m_locked(false) {
if (Detail::IncrementRecursiveLock(&m_mutex, Detail::RecursiveLockType::Exclusive)) {
m_locked = true;
m_lock.emplace(m_mutex);
}
}
~RecursiveScopedLock() {
Detail::DecrementRecursiveLock(&m_mutex, Detail::RecursiveLockType::Exclusive);
if (m_locked) {
m_lock.reset();
}
}
private:
MutexType& m_mutex;
std::optional<std::unique_lock<MutexType>> m_lock;
bool m_locked = false;
};
template <typename MutexType>
class RecursiveSharedLock {
public:
explicit RecursiveSharedLock(MutexType& mutex) : m_mutex(mutex), m_locked(false) {
if (Detail::IncrementRecursiveLock(&m_mutex, Detail::RecursiveLockType::Shared)) {
m_locked = true;
m_lock.emplace(m_mutex);
}
}
~RecursiveSharedLock() {
Detail::DecrementRecursiveLock(&m_mutex, Detail::RecursiveLockType::Shared);
if (m_locked) {
m_lock.reset();
}
}
private:
MutexType& m_mutex;
std::optional<std::shared_lock<MutexType>> m_lock;
bool m_locked = false;
};
} // namespace Common
+53
View File
@@ -0,0 +1,53 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <optional>
#include <utility>
#include <vector>
#include "common/types.h"
template <class T>
class RingBufferQueue {
public:
RingBufferQueue(u64 size) : m_storage(size) {}
void Push(T item) {
const u64 index = (m_begin + m_size) % m_storage.size();
m_storage[index] = std::move(item);
if (m_size < m_storage.size()) {
m_size += 1;
} else {
m_begin = (m_begin + 1) % m_storage.size();
}
}
std::optional<T> Pop() {
if (m_size == 0) {
return {};
}
const u64 index = m_begin;
m_begin = (m_begin + 1) % m_storage.size();
m_size -= 1;
return std::move(m_storage[index]);
}
std::optional<T> Peek() {
if (m_size == 0) {
return {};
}
return m_storage[m_begin];
}
void Clear() {
m_begin = 0;
m_size = 0;
}
private:
u64 m_begin = 0;
u64 m_size = 0;
std::vector<T> m_storage;
};
+42
View File
@@ -0,0 +1,42 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <string>
#include "common/scm_rev.h"
namespace Common {
constexpr char g_version[] = "@APP_VERSION@";
constexpr bool g_is_release = @APP_IS_RELEASE@;
constexpr char g_scm_rev[] = "@GIT_REV@";
constexpr char g_scm_branch[] = "@GIT_BRANCH@";
constexpr char g_scm_desc[] = "@GIT_DESC@";
constexpr char g_scm_remote_name[] = "@GIT_REMOTE_NAME@";
constexpr char g_scm_remote_url[] = "@GIT_REMOTE_URL@";
constexpr char g_scm_date[] = "@BUILD_DATE@";
const std::string GetRemoteNameFromLink() {
std::string remote_url(Common::g_scm_remote_url);
std::string remote_host;
try {
if (remote_url.starts_with("http")) {
if (*remote_url.rbegin() == '/') {
remote_url.pop_back();
}
remote_host = remote_url.substr(19, remote_url.rfind('/') - 19);
} else if (remote_url.starts_with("git@")) {
auto after_comma_pos = remote_url.find(':') + 1, slash_pos = remote_url.find('/');
remote_host = remote_url.substr(after_comma_pos, slash_pos - after_comma_pos);
} else {
remote_host = "unknown";
}
} catch (...) {
remote_host = "unknown";
}
return remote_host;
}
} // namespace
+22
View File
@@ -0,0 +1,22 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
namespace Common {
extern const char g_version[];
extern const bool g_is_release;
extern const char g_scm_rev[];
extern const char g_scm_branch[];
extern const char g_scm_desc[];
extern const char g_scm_remote_name[];
extern const char g_scm_remote_url[];
extern const char g_scm_date[];
const std::string GetRemoteNameFromLink();
} // namespace Common
+79
View File
@@ -0,0 +1,79 @@
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <utility>
namespace detail {
template <class F>
class ScopeGuard {
private:
F f;
bool active;
public:
constexpr ScopeGuard(F f_) : f(std::move(f_)), active(true) {}
constexpr ~ScopeGuard() {
if (active) {
f();
}
}
constexpr void Cancel() {
active = false;
}
constexpr ScopeGuard(ScopeGuard&& rhs) : f(std::move(rhs.f)), active(rhs.active) {
rhs.Cancel();
}
ScopeGuard& operator=(ScopeGuard&& rhs) = delete;
};
template <class F>
constexpr ScopeGuard<F> MakeScopeGuard(F f) {
return ScopeGuard<F>(std::move(f));
}
enum class ScopeGuardOnExit {};
template <typename F>
constexpr ScopeGuard<F> operator+(ScopeGuardOnExit, F&& f) {
return ScopeGuard<F>(std::forward<F>(f));
}
} // namespace detail
#define CONCATENATE_IMPL(s1, s2) s1##s2
#define CONCATENATE(s1, s2) CONCATENATE_IMPL(s1, s2)
#ifdef __COUNTER__
#define ANONYMOUS_VARIABLE(pref) CONCATENATE(pref, __COUNTER__)
#else
#define ANONYMOUS_VARIABLE(pref) CONCATENATE(pref, __LINE__)
#endif
/**
* This macro is similar to SCOPE_EXIT, except the object is caller managed. This is intended to be
* used when the caller might want to cancel the ScopeExit.
*/
#define SCOPE_GUARD detail::ScopeGuardOnExit() + [&]()
/**
* This macro allows you to conveniently specify a block of code that will run on scope exit. Handy
* for doing ad-hoc clean-up tasks in a function with multiple returns.
*
* Example usage:
* \code
* const int saved_val = g_foo;
* g_foo = 55;
* SCOPE_EXIT{ g_foo = saved_val; };
*
* if (Bar()) {
* return 0;
* } else {
* return 20;
* }
* \endcode
*/
#define SCOPE_EXIT auto ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_) = SCOPE_GUARD
+143
View File
@@ -0,0 +1,143 @@
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/assert.h"
#include "common/types.h"
#include <cstddef>
#include <vector>
namespace Serialization {
template <typename T>
concept Container = requires(T t) {
typename T::iterator;
{ t.begin() } -> std::same_as<typename T::iterator>;
{ t.end() } -> std::same_as<typename T::iterator>;
{ t.size() } -> std::convertible_to<std::size_t>;
};
struct Archive {
void Alloc(size_t size) {
container.resize(size);
}
void Grow(size_t size) {
container.resize(container.size() + size);
}
void Merge(const Archive& ar) {
container.insert(container.end(), ar.container.cbegin(), ar.container.cend());
offset = container.size();
}
[[nodiscard]] size_t SizeBytes() const {
return container.size();
}
u8* CurrPtr() {
return container.data() + offset;
}
void Advance(size_t size) {
ASSERT_MSG(offset + size <= container.size(),
"Invalid or corrupted deserialization container/shader cache");
offset += size;
}
std::vector<u8>&& TakeOff() {
offset = 0;
return std::move(container);
}
[[nodiscard]] bool IsEoS() const {
return offset >= container.size();
}
Archive() = default;
explicit Archive(std::vector<u8>&& v) : container{v} {}
private:
u32 offset{};
std::vector<u8> container{};
friend struct Writer;
friend struct Reader;
};
struct Writer {
template <typename T>
void Write(const T* ptr, size_t size) {
if (ar.offset + size >= ar.container.size()) {
ar.Grow(size);
}
std::memcpy(ar.CurrPtr(), reinterpret_cast<const void*>(ptr), size);
ar.Advance(size);
}
template <typename T>
requires(!Container<T>)
void Write(const T& value) {
const auto size = sizeof(value);
Write(&value, size);
}
void Write(const auto& v) {
Write(v.size());
for (const auto& elem : v) {
Write(elem);
}
}
void Write(const std::string& s) {
Write(s.size());
Write(s.c_str(), s.size());
}
Writer() = delete;
explicit Writer(Archive& ar_) : ar{ar_} {}
Archive& ar;
};
struct Reader {
template <typename T>
void Read(T* ptr, size_t size) {
ASSERT_MSG(ar.offset + size <= ar.container.size(),
"Invalid or corrupted deserialization container/shader cache");
std::memcpy(reinterpret_cast<void*>(ptr), ar.CurrPtr(), size);
ar.Advance(size);
}
template <typename T>
requires(!Container<T>)
void Read(T& value) {
const auto size = sizeof(value);
Read(&value, size);
}
void Read(auto& v) {
size_t num_elements{};
Read(num_elements);
for (int i = 0; i < num_elements; ++i) {
v.emplace_back();
Read(v.back());
}
}
void Read(std::string& s) {
size_t length{};
Read(length);
s.resize(length);
Read(s.data(), length);
}
Reader() = delete;
explicit Reader(Archive& ar_) : ar{ar_} {}
Archive& ar;
};
} // namespace Serialization
+180
View File
@@ -0,0 +1,180 @@
// SPDX-FileCopyrightText: 2012 SAURAV MOHAPATRA <[email protected]>
// SPDX-License-Identifier: MIT
#pragma once
#include <cstdint>
#include <cstdlib>
#include <cstring>
namespace sha1 {
class SHA1 {
public:
typedef uint32_t digest32_t[5];
typedef uint8_t digest8_t[20];
inline static uint32_t LeftRotate(uint32_t value, size_t count) {
return (value << count) ^ (value >> (32 - count));
}
SHA1() {
reset();
}
virtual ~SHA1() {}
SHA1(const SHA1& s) {
*this = s;
}
const SHA1& operator=(const SHA1& s) {
memcpy(m_digest, s.m_digest, 5 * sizeof(uint32_t));
memcpy(m_block, s.m_block, 64);
m_blockByteIndex = s.m_blockByteIndex;
m_byteCount = s.m_byteCount;
return *this;
}
SHA1& reset() {
m_digest[0] = 0x67452301;
m_digest[1] = 0xEFCDAB89;
m_digest[2] = 0x98BADCFE;
m_digest[3] = 0x10325476;
m_digest[4] = 0xC3D2E1F0;
m_blockByteIndex = 0;
m_byteCount = 0;
return *this;
}
SHA1& processByte(uint8_t octet) {
this->m_block[this->m_blockByteIndex++] = octet;
++this->m_byteCount;
if (m_blockByteIndex == 64) {
this->m_blockByteIndex = 0;
processBlock();
}
return *this;
}
SHA1& processBlock(const void* const start, const void* const end) {
const uint8_t* begin = static_cast<const uint8_t*>(start);
const uint8_t* finish = static_cast<const uint8_t*>(end);
while (begin != finish) {
processByte(*begin);
begin++;
}
return *this;
}
SHA1& processBytes(const void* const data, size_t len) {
const uint8_t* block = static_cast<const uint8_t*>(data);
processBlock(block, block + len);
return *this;
}
const uint32_t* getDigest(digest32_t digest) {
size_t bitCount = this->m_byteCount * 8;
processByte(0x80);
if (this->m_blockByteIndex > 56) {
while (m_blockByteIndex != 0) {
processByte(0);
}
while (m_blockByteIndex < 56) {
processByte(0);
}
} else {
while (m_blockByteIndex < 56) {
processByte(0);
}
}
processByte(0);
processByte(0);
processByte(0);
processByte(0);
processByte(static_cast<unsigned char>((bitCount >> 24) & 0xFF));
processByte(static_cast<unsigned char>((bitCount >> 16) & 0xFF));
processByte(static_cast<unsigned char>((bitCount >> 8) & 0xFF));
processByte(static_cast<unsigned char>((bitCount) & 0xFF));
memcpy(digest, m_digest, 5 * sizeof(uint32_t));
return digest;
}
const uint8_t* getDigestBytes(digest8_t digest) {
digest32_t d32;
getDigest(d32);
size_t di = 0;
digest[di++] = ((d32[0] >> 24) & 0xFF);
digest[di++] = ((d32[0] >> 16) & 0xFF);
digest[di++] = ((d32[0] >> 8) & 0xFF);
digest[di++] = ((d32[0]) & 0xFF);
digest[di++] = ((d32[1] >> 24) & 0xFF);
digest[di++] = ((d32[1] >> 16) & 0xFF);
digest[di++] = ((d32[1] >> 8) & 0xFF);
digest[di++] = ((d32[1]) & 0xFF);
digest[di++] = ((d32[2] >> 24) & 0xFF);
digest[di++] = ((d32[2] >> 16) & 0xFF);
digest[di++] = ((d32[2] >> 8) & 0xFF);
digest[di++] = ((d32[2]) & 0xFF);
digest[di++] = ((d32[3] >> 24) & 0xFF);
digest[di++] = ((d32[3] >> 16) & 0xFF);
digest[di++] = ((d32[3] >> 8) & 0xFF);
digest[di++] = ((d32[3]) & 0xFF);
digest[di++] = ((d32[4] >> 24) & 0xFF);
digest[di++] = ((d32[4] >> 16) & 0xFF);
digest[di++] = ((d32[4] >> 8) & 0xFF);
digest[di++] = ((d32[4]) & 0xFF);
return digest;
}
protected:
void processBlock() {
uint32_t w[80];
for (size_t i = 0; i < 16; i++) {
w[i] = (m_block[i * 4 + 0] << 24);
w[i] |= (m_block[i * 4 + 1] << 16);
w[i] |= (m_block[i * 4 + 2] << 8);
w[i] |= (m_block[i * 4 + 3]);
}
for (size_t i = 16; i < 80; i++) {
w[i] = LeftRotate((w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]), 1);
}
uint32_t a = m_digest[0];
uint32_t b = m_digest[1];
uint32_t c = m_digest[2];
uint32_t d = m_digest[3];
uint32_t e = m_digest[4];
for (std::size_t i = 0; i < 80; ++i) {
uint32_t f = 0;
uint32_t k = 0;
if (i < 20) {
f = (b & c) | (~b & d);
k = 0x5A827999;
} else if (i < 40) {
f = b ^ c ^ d;
k = 0x6ED9EBA1;
} else if (i < 60) {
f = (b & c) | (b & d) | (c & d);
k = 0x8F1BBCDC;
} else {
f = b ^ c ^ d;
k = 0xCA62C1D6;
}
uint32_t temp = LeftRotate(a, 5) + f + e + k + w[i];
e = d;
d = c;
c = LeftRotate(b, 30);
b = a;
a = temp;
}
m_digest[0] += a;
m_digest[1] += b;
m_digest[2] += c;
m_digest[3] += d;
m_digest[4] += e;
}
private:
digest32_t m_digest;
uint8_t m_block[64];
size_t m_blockByteIndex;
size_t m_byteCount;
};
} // namespace sha1
+85
View File
@@ -0,0 +1,85 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <chrono>
#include <condition_variable>
#include <mutex>
namespace Common {
// Like std::shared_mutex, but reader has priority over writer.
class SharedFirstMutex {
public:
void lock() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [this]() { return !writer_active && readers == 0; });
writer_active = true;
}
bool try_lock() {
std::lock_guard<std::mutex> lock(mtx);
if (writer_active || readers > 0) {
return false;
}
writer_active = true;
return true;
}
template <typename Clock, typename Duration>
bool try_lock_until(const std::chrono::time_point<Clock, Duration>& abs_time) {
std::unique_lock<std::mutex> lock(mtx);
if (!cv.wait_until(lock, abs_time, [this]() { return !writer_active && readers == 0; })) {
return false;
}
writer_active = true;
return true;
}
void unlock() {
std::lock_guard<std::mutex> lock(mtx);
writer_active = false;
cv.notify_all();
}
void lock_shared() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [this]() { return !writer_active; });
++readers;
}
bool try_lock_shared() {
std::lock_guard<std::mutex> lock(mtx);
if (writer_active) {
return false;
}
++readers;
return true;
}
template <typename Clock, typename Duration>
bool try_lock_shared_until(const std::chrono::time_point<Clock, Duration>& abs_time) {
std::unique_lock<std::mutex> lock(mtx);
if (!cv.wait_until(lock, abs_time, [this]() { return !writer_active; })) {
return false;
}
++readers;
return true;
}
void unlock_shared() {
std::lock_guard<std::mutex> lock(mtx);
if (--readers == 0) {
cv.notify_all();
}
}
private:
std::mutex mtx;
std::condition_variable cv;
int readers = 0;
bool writer_active = false;
};
} // namespace Common
+50
View File
@@ -0,0 +1,50 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/arch.h"
#include "common/signal_context.h"
#ifdef _WIN32
#include <windows.h>
#elif defined(__FreeBSD__)
#include <machine/npx.h>
#include <sys/ucontext.h>
#else
#include <sys/ucontext.h>
#endif
namespace Common {
void* GetRip(void* ctx) {
#if defined(_WIN32)
return (void*)((EXCEPTION_POINTERS*)ctx)->ContextRecord->Rip;
#elif defined(__APPLE__) && defined(ARCH_X86_64)
return (void*)((ucontext_t*)ctx)->uc_mcontext->__ss.__rip;
#elif defined(__APPLE__) && defined(ARCH_ARM64)
return (void*)((ucontext_t*)ctx)->uc_mcontext->__ss.__pc;
#elif defined(__FreeBSD__)
return (void*)((ucontext_t*)ctx)->uc_mcontext.mc_rip;
#elif defined(ARCH_X86_64)
return (void*)((ucontext_t*)ctx)->uc_mcontext.gregs[REG_RIP];
#else
#error "Unsupported architecture"
#endif
}
bool IsWriteError(void* ctx) {
#if defined(_WIN32)
return ((EXCEPTION_POINTERS*)ctx)->ExceptionRecord->ExceptionInformation[0] == 1;
#elif defined(__APPLE__) && defined(ARCH_X86_64)
return ((ucontext_t*)ctx)->uc_mcontext->__es.__err & 0x2;
#elif defined(__APPLE__) && defined(ARCH_ARM64)
return ((ucontext_t*)ctx)->uc_mcontext->__es.__esr & 0x40;
#elif defined(__FreeBSD__) && defined(ARCH_X86_64)
return ((ucontext_t*)ctx)->uc_mcontext.mc_err & 0x2;
#elif defined(ARCH_X86_64)
return ((ucontext_t*)ctx)->uc_mcontext.gregs[REG_ERR] & 0x2;
#else
#error "Unsupported architecture"
#endif
}
} // namespace Common
+18
View File
@@ -0,0 +1,18 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
namespace Common {
void* GetXmmPointer(void* ctx, u8 index);
void* GetRip(void* ctx);
void IncrementRip(void* ctx, u64 length);
bool IsWriteError(void* ctx);
} // namespace Common
+28
View File
@@ -0,0 +1,28 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
namespace Common {
template <class T>
class Singleton {
public:
static T* Instance() {
if (!m_instance) {
m_instance = std::make_unique<T>();
}
return m_instance.get();
}
protected:
Singleton();
~Singleton();
private:
static inline std::unique_ptr<T> m_instance{};
};
} // namespace Common
+165
View File
@@ -0,0 +1,165 @@
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <atomic>
#include <memory>
#include "common/assert.h"
#include "common/spin_lock.h"
namespace Common {
class SlabHeapImpl {
public:
struct Node {
Node* next{};
};
public:
constexpr SlabHeapImpl() = default;
void Initialize() {
ASSERT(m_head == nullptr);
}
Node* GetHead() const {
return m_head;
}
void* Allocate() {
m_lock.lock();
Node* ret = m_head;
if (ret != nullptr) {
m_head = ret->next;
}
m_lock.unlock();
return ret;
}
void Free(void* obj) {
m_lock.lock();
Node* node = static_cast<Node*>(obj);
node->next = m_head;
m_head = node;
m_lock.unlock();
}
private:
std::atomic<Node*> m_head{};
Common::SpinLock m_lock;
};
class SlabHeapBase : protected SlabHeapImpl {
private:
size_t m_obj_size{};
uintptr_t m_peak{};
uintptr_t m_start{};
uintptr_t m_end{};
public:
constexpr SlabHeapBase() = default;
bool Contains(uintptr_t address) const {
return m_start <= address && address < m_end;
}
void Initialize(size_t obj_size, void* memory, size_t memory_size) {
// Ensure we don't initialize a slab using null memory.
ASSERT(memory != nullptr);
// Set our object size.
m_obj_size = obj_size;
// Initialize the base allocator.
SlabHeapImpl::Initialize();
// Set our tracking variables.
const size_t num_obj = (memory_size / obj_size);
m_start = reinterpret_cast<uintptr_t>(memory);
m_end = m_start + num_obj * obj_size;
m_peak = m_start;
// Free the objects.
u8* cur = reinterpret_cast<u8*>(m_end);
for (size_t i = 0; i < num_obj; i++) {
cur -= obj_size;
SlabHeapImpl::Free(cur);
}
}
size_t GetSlabHeapSize() const {
return (m_end - m_start) / this->GetObjectSize();
}
size_t GetObjectSize() const {
return m_obj_size;
}
void* Allocate() {
void* obj = SlabHeapImpl::Allocate();
return obj;
}
void Free(void* obj) {
// Don't allow freeing an object that wasn't allocated from this heap.
const bool contained = this->Contains(reinterpret_cast<uintptr_t>(obj));
ASSERT(contained);
SlabHeapImpl::Free(obj);
}
size_t GetObjectIndex(const void* obj) const {
return (reinterpret_cast<uintptr_t>(obj) - m_start) / this->GetObjectSize();
}
size_t GetPeakIndex() const {
return this->GetObjectIndex(reinterpret_cast<const void*>(m_peak));
}
uintptr_t GetSlabHeapAddress() const {
return m_start;
}
size_t GetNumRemaining() const {
// Only calculate the number of remaining objects under debug configuration.
return 0;
}
};
template <typename T>
class SlabHeap final : public SlabHeapBase {
private:
using BaseHeap = SlabHeapBase;
public:
constexpr SlabHeap() = default;
void Initialize(void* memory, size_t memory_size) {
BaseHeap::Initialize(sizeof(T), memory, memory_size);
}
T* Allocate() {
T* obj = static_cast<T*>(BaseHeap::Allocate());
if (obj != nullptr) [[likely]] {
std::construct_at(obj);
}
return obj;
}
void Free(T* obj) {
std::destroy_at(obj);
BaseHeap::Free(obj);
}
size_t GetObjectIndex(const T* obj) const {
return BaseHeap::GetObjectIndex(obj);
}
};
} // namespace Common
+63
View File
@@ -0,0 +1,63 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// #include <boost/icl/interval_set.hpp>
#include <limits>
#include <optional>
#include <shared_mutex>
#include <unordered_map>
#include <memory>
#include <numeric>
namespace Common {
template <class IndexType, class ResourceType,
IndexType MaxIndex = std::numeric_limits<IndexType>::max(), IndexType MinIndex = 0>
class SlotArray {
public:
SlotArray() {
std::iota(m_free_indices.begin(), m_free_indices.end(), MinIndex);
}
template <class... Types>
std::optional<IndexType> Create(Types&&... args) {
if (!HasFreeSlots()) {
return std::nullopt;
}
const auto index = m_free_indices[m_curr_cursor];
m_resources[index - MinIndex] = ResourceType(std::forward<Types>(args)...);
m_curr_cursor += 1;
return index;
}
bool Destroy(IndexType index) {
if (!m_resources[index - MinIndex].has_value()) {
return false;
}
m_curr_cursor -= 1;
m_free_indices[m_curr_cursor] = index;
m_resources[index - MinIndex] = std::nullopt;
return true;
}
ResourceType* Get(IndexType index) {
auto& resource = m_resources[index - MinIndex];
if (!resource.has_value()) {
return nullptr;
}
return &resource.value();
}
bool HasFreeSlots() {
return m_curr_cursor < m_free_indices.size();
}
private:
size_t m_curr_cursor = 0;
std::array<IndexType, MaxIndex - MinIndex> m_free_indices;
std::array<std::optional<ResourceType>, MaxIndex - MinIndex> m_resources;
};
} // namespace Common
+273
View File
@@ -0,0 +1,273 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <numeric>
#include <utility>
#include <vector>
#include "common/assert.h"
#include "common/types.h"
namespace Common {
struct SlotId {
static constexpr u32 INVALID_INDEX = std::numeric_limits<u32>::max();
SlotId() noexcept = default;
constexpr SlotId(u32 index) noexcept : index(index) {}
constexpr auto operator<=>(const SlotId&) const noexcept = default;
constexpr explicit operator bool() const noexcept {
return index != INVALID_INDEX;
}
u32 index = INVALID_INDEX;
};
template <class T>
class SlotVector {
constexpr static std::size_t InitialCapacity = 2048;
public:
template <typename ValueType, typename Pointer, typename Reference>
class Iterator {
public:
using iterator_category = std::forward_iterator_tag;
using value_type = ValueType;
using difference_type = std::ptrdiff_t;
using pointer = Pointer;
using reference = Reference;
Iterator(SlotVector& vector_, SlotId index_) : vector(vector_), slot(index_) {
AdvanceToValid();
}
reference operator*() const {
return vector[slot];
}
pointer operator->() const {
return &vector[slot];
}
Iterator& operator++() {
++slot.index;
AdvanceToValid();
return *this;
}
Iterator operator++(int) {
Iterator temp = *this;
++(*this);
return temp;
}
bool operator==(const Iterator& other) const {
return slot == other.slot;
}
bool operator!=(const Iterator& other) const {
return !(*this == other);
}
private:
void AdvanceToValid() {
while (slot < vector.values_capacity && !vector.ReadStorageBit(slot.index)) {
++slot.index;
}
}
SlotVector& vector;
SlotId slot;
};
using iterator = Iterator<T, T*, T&>;
using const_iterator = Iterator<const T, const T*, const T&>;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
SlotVector() {
Reserve(InitialCapacity);
}
~SlotVector() noexcept {
std::size_t index = 0;
for (u64 bits : stored_bitset) {
for (std::size_t bit = 0; bits; ++bit, bits >>= 1) {
if ((bits & 1) != 0) {
values[index + bit].object.~T();
}
}
index += 64;
}
delete[] values;
}
[[nodiscard]] T& operator[](SlotId id) noexcept {
ValidateIndex(id);
return values[id.index].object;
}
[[nodiscard]] const T& operator[](SlotId id) const noexcept {
ValidateIndex(id);
return values[id.index].object;
}
bool is_allocated(SlotId id) const {
return ReadStorageBit(id.index);
}
template <typename... Args>
SlotId insert(Args&&... args) noexcept {
const u32 index = FreeValueIndex();
new (&values[index].object) T(std::forward<Args>(args)...);
SetStorageBit(index);
return SlotId{index};
}
void erase(SlotId id) noexcept {
values[id.index].object.~T();
free_list.push_back(id.index);
ResetStorageBit(id.index);
}
std::size_t size() const noexcept {
return values_capacity - free_list.size();
}
iterator begin() noexcept {
return iterator(*this, 0);
}
const_iterator begin() const noexcept {
return const_iterator(*this, 0);
}
const_iterator cbegin() const noexcept {
return begin();
}
iterator end() noexcept {
return iterator(*this, values_capacity);
}
const_iterator end() const noexcept {
return const_iterator(*this, values_capacity);
}
const_iterator cend() const noexcept {
return end();
}
reverse_iterator rbegin() noexcept {
return reverse_iterator(end());
}
const_reverse_iterator rbegin() const noexcept {
return const_reverse_iterator(end());
}
const_reverse_iterator crbegin() const noexcept {
return rbegin();
}
reverse_iterator rend() noexcept {
return reverse_iterator(begin());
}
const_reverse_iterator rend() const noexcept {
return const_reverse_iterator(begin());
}
const_reverse_iterator crend() const noexcept {
return rend();
}
private:
struct NonTrivialDummy {
NonTrivialDummy() noexcept {}
};
union Entry {
Entry() noexcept : dummy{} {}
~Entry() noexcept {}
NonTrivialDummy dummy;
T object;
};
void SetStorageBit(u32 index) noexcept {
stored_bitset[index / 64] |= u64(1) << (index % 64);
}
void ResetStorageBit(u32 index) noexcept {
stored_bitset[index / 64] &= ~(u64(1) << (index % 64));
}
bool ReadStorageBit(u32 index) const noexcept {
return ((stored_bitset[index / 64] >> (index % 64)) & 1) != 0;
}
void ValidateIndex([[maybe_unused]] SlotId id) const noexcept {
DEBUG_ASSERT(id);
DEBUG_ASSERT(id.index / 64 < stored_bitset.size());
DEBUG_ASSERT(((stored_bitset[id.index / 64] >> (id.index % 64)) & 1) != 0);
}
[[nodiscard]] u32 FreeValueIndex() noexcept {
if (free_list.empty()) {
Reserve(values_capacity ? (values_capacity << 1) : 1);
}
const u32 free_index = free_list.back();
free_list.pop_back();
return free_index;
}
void Reserve(std::size_t new_capacity) noexcept {
Entry* const new_values = new Entry[new_capacity];
std::size_t index = 0;
for (u64 bits : stored_bitset) {
for (std::size_t bit = 0; bits; ++bit, bits >>= 1) {
const std::size_t i = index + bit;
if ((bits & 1) == 0) {
continue;
}
T& old_value = values[i].object;
new (&new_values[i].object) T(std::move(old_value));
old_value.~T();
}
index += 64;
}
stored_bitset.resize((new_capacity + 63) / 64);
const std::size_t old_free_size = free_list.size();
free_list.resize(old_free_size + (new_capacity - values_capacity));
const std::size_t new_free_size = free_list.size();
std::iota(free_list.rbegin(), free_list.rbegin() + new_free_size - old_free_size,
static_cast<u32>(values_capacity));
delete[] values;
values = new_values;
values_capacity = new_capacity;
}
Entry* values = nullptr;
std::size_t values_capacity = 0;
std::vector<u64> stored_bitset;
std::vector<u32> free_list;
};
} // namespace Common
template <>
struct std::hash<Common::SlotId> {
std::size_t operator()(const Common::SlotId& id) const noexcept {
return std::hash<u32>{}(id.index);
}
};
+53
View File
@@ -0,0 +1,53 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/spin_lock.h"
#if _MSC_VER
#include <intrin.h>
#if _M_AMD64
#define __x86_64__ 1
#endif
#if _M_ARM64
#define __aarch64__ 1
#endif
#else
#if __x86_64__
#include <xmmintrin.h>
#endif
#endif
namespace {
void ThreadPause() {
#if __x86_64__
_mm_pause();
#elif __aarch64__ && _MSC_VER
__yield();
#elif __aarch64__
asm("yield");
#endif
}
} // Anonymous namespace
namespace Common {
void SpinLock::lock() {
while (lck.test_and_set(std::memory_order_acquire)) {
ThreadPause();
}
}
void SpinLock::unlock() {
lck.clear(std::memory_order_release);
}
bool SpinLock::try_lock() {
if (lck.test_and_set(std::memory_order_acquire)) {
return false;
}
return true;
}
} // namespace Common
+33
View File
@@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <atomic>
namespace Common {
/**
* SpinLock class
* a lock similar to mutex that forces a thread to spin wait instead calling the
* supervisor. Should be used on short sequences of code.
*/
class SpinLock {
public:
SpinLock() = default;
SpinLock(const SpinLock&) = delete;
SpinLock& operator=(const SpinLock&) = delete;
SpinLock(SpinLock&&) = delete;
SpinLock& operator=(SpinLock&&) = delete;
void lock();
void unlock();
[[nodiscard]] bool try_lock();
private:
std::atomic_flag lck = ATOMIC_FLAG_INIT;
};
} // namespace Common
+8
View File
@@ -0,0 +1,8 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#define STBI_ONLY_PNG
#define STBI_NO_STDIO
#include "common/stb.h"
+7
View File
@@ -0,0 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <stb_image.h>
#include <stb_image_write.h>
+15
View File
@@ -0,0 +1,15 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
template <size_t N, typename C = char>
struct StringLiteral {
static constexpr size_t len = N;
constexpr StringLiteral(const C (&str)[N]) {
std::copy_n(str, N, value);
}
C value[N]{};
};
+86
View File
@@ -0,0 +1,86 @@
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <sstream>
#include <string>
#include "common/string_util.h"
#include "common/types.h"
#ifdef _WIN32
#include <windows.h>
#endif
namespace Common {
std::string ToLower(std::string_view input) {
std::string str;
str.resize(input.size());
std::ranges::transform(input, str.begin(), tolower);
return str;
}
void ToLowerInPlace(std::string& str) {
std::ranges::transform(str, str.begin(), tolower);
}
std::vector<std::string> SplitString(const std::string& str, char delimiter) {
std::istringstream iss(str);
std::vector<std::string> output(1);
while (std::getline(iss, *output.rbegin(), delimiter)) {
output.emplace_back();
}
output.pop_back();
return output;
}
std::string_view U8stringToString(std::u8string_view u8str) {
return std::string_view{reinterpret_cast<const char*>(u8str.data()), u8str.size()};
}
#ifdef _WIN32
static std::wstring CPToUTF16(u32 code_page, std::string_view input) {
const auto size =
MultiByteToWideChar(code_page, 0, input.data(), static_cast<int>(input.size()), nullptr, 0);
if (size == 0) {
return {};
}
std::wstring output(size, L'\0');
if (size != MultiByteToWideChar(code_page, 0, input.data(), static_cast<int>(input.size()),
&output[0], static_cast<int>(output.size()))) {
output.clear();
}
return output;
}
std::string UTF16ToUTF8(std::wstring_view input) {
const auto size = WideCharToMultiByte(CP_UTF8, 0, input.data(), static_cast<int>(input.size()),
nullptr, 0, nullptr, nullptr);
if (size == 0) {
return {};
}
std::string output(size, '\0');
if (size != WideCharToMultiByte(CP_UTF8, 0, input.data(), static_cast<int>(input.size()),
&output[0], static_cast<int>(output.size()), nullptr,
nullptr)) {
output.clear();
}
return output;
}
std::wstring UTF8ToUTF16W(std::string_view input) {
return CPToUTF16(CP_UTF8, input);
}
#endif
} // namespace Common
+26
View File
@@ -0,0 +1,26 @@
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
#include <vector>
namespace Common {
/// Make a string lowercase
[[nodiscard]] std::string ToLower(std::string_view str);
void ToLowerInPlace(std::string& str);
std::vector<std::string> SplitString(const std::string& str, char delimiter);
std::string_view U8stringToString(std::u8string_view u8str);
#ifdef _WIN32
[[nodiscard]] std::string UTF16ToUTF8(std::wstring_view input);
[[nodiscard]] std::wstring UTF8ToUTF16W(std::string_view str);
#endif
} // namespace Common
+17
View File
@@ -0,0 +1,17 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
// support header file for libav
// The av_err2str macro in libavutil/error.h does not play nice with C++
#ifdef av_err2str
#undef av_err2str
#include <string>
av_always_inline std::string av_err2string(int errnum) {
char errbuf[AV_ERROR_MAX_STRING_SIZE];
return av_make_error_string(errbuf, AV_ERROR_MAX_STRING_SIZE, errnum);
}
#define av_err2str(err) av_err2string(err).c_str()
#endif // av_err2str
+263
View File
@@ -0,0 +1,263 @@
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <ctime>
#include <string>
#include <thread>
#include "common/error.h"
#include "common/logging/log.h"
#include "common/thread.h"
#include "ntapi.h"
#ifdef __APPLE__
#include <mach/mach.h>
#include <mach/mach_time.h>
#include <pthread.h>
#elif defined(_WIN32)
#include <windows.h>
#include "common/string_util.h"
#else
#if defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__)
#include <pthread_np.h>
#else
#include <pthread.h>
#endif
#include <sched.h>
#endif
#ifndef _WIN32
#include <unistd.h>
#endif
#ifdef __FreeBSD__
#define cpu_set_t cpuset_t
#endif
namespace Common {
#ifdef __APPLE__
void SetCurrentThreadRealtime(const std::chrono::nanoseconds period_ns) {
// CPU time to grant.
const std::chrono::nanoseconds computation_ns = period_ns / 2;
// Determine the timebase for converting time to ticks.
struct mach_timebase_info timebase{};
mach_timebase_info(&timebase);
const auto ticks_per_ns =
static_cast<double>(timebase.denom) / static_cast<double>(timebase.numer);
const auto period_ticks =
static_cast<u32>(static_cast<double>(period_ns.count()) * ticks_per_ns);
const auto computation_ticks =
static_cast<u32>(static_cast<double>(computation_ns.count()) * ticks_per_ns);
thread_time_constraint_policy policy = {
.period = period_ticks,
.computation = computation_ticks,
// Should not matter since preemptible is false, but needs to be >= computation regardless.
.constraint = computation_ticks,
.preemptible = false,
};
int ret = thread_policy_set(
pthread_mach_thread_np(pthread_self()), THREAD_TIME_CONSTRAINT_POLICY,
reinterpret_cast<thread_policy_t>(&policy), THREAD_TIME_CONSTRAINT_POLICY_COUNT);
if (ret != KERN_SUCCESS) {
LOG_ERROR(Common, "Could not set thread to real-time with period {} ns: {}",
period_ns.count(), ret);
}
}
#else
void SetCurrentThreadRealtime(const std::chrono::nanoseconds period_ns) {
// Not implemented
}
#endif
#ifdef _WIN32
void SetCurrentThreadPriority(ThreadPriority new_priority) {
auto handle = GetCurrentThread();
int windows_priority = 0;
switch (new_priority) {
case ThreadPriority::Low:
windows_priority = THREAD_PRIORITY_BELOW_NORMAL;
break;
case ThreadPriority::Normal:
windows_priority = THREAD_PRIORITY_NORMAL;
break;
case ThreadPriority::High:
windows_priority = THREAD_PRIORITY_ABOVE_NORMAL;
break;
case ThreadPriority::VeryHigh:
windows_priority = THREAD_PRIORITY_HIGHEST;
break;
case ThreadPriority::Critical:
windows_priority = THREAD_PRIORITY_TIME_CRITICAL;
break;
default:
windows_priority = THREAD_PRIORITY_NORMAL;
break;
}
SetThreadPriority(handle, windows_priority);
}
bool AccurateSleep(const std::chrono::nanoseconds duration, std::chrono::nanoseconds* remaining,
const bool interruptible) {
const auto begin_sleep = std::chrono::high_resolution_clock::now();
LARGE_INTEGER interval{
.QuadPart = -1 * (duration.count() / 100u),
};
HANDLE timer = ::CreateWaitableTimer(NULL, TRUE, NULL);
SetWaitableTimer(timer, &interval, 0, NULL, NULL, 0);
const auto ret = WaitForSingleObjectEx(timer, INFINITE, interruptible);
::CloseHandle(timer);
if (remaining) {
const auto end_sleep = std::chrono::high_resolution_clock::now();
const auto sleep_time = end_sleep - begin_sleep;
*remaining = duration > sleep_time ? duration - sleep_time : std::chrono::nanoseconds(0);
}
return ret == WAIT_OBJECT_0;
}
#else
void SetCurrentThreadPriority(ThreadPriority new_priority) {
pthread_t this_thread = pthread_self();
const auto scheduling_type = SCHED_OTHER;
s32 max_prio = sched_get_priority_max(scheduling_type);
s32 min_prio = sched_get_priority_min(scheduling_type);
u32 level = std::max(static_cast<u32>(new_priority) + 1, 4U);
struct sched_param params;
if (max_prio > min_prio) {
params.sched_priority = min_prio + ((max_prio - min_prio) * level) / 4;
} else {
params.sched_priority = min_prio - ((min_prio - max_prio) * level) / 4;
}
pthread_setschedparam(this_thread, scheduling_type, &params);
}
bool AccurateSleep(const std::chrono::nanoseconds duration, std::chrono::nanoseconds* remaining,
const bool interruptible) {
timespec request = {
.tv_sec = duration.count() / 1'000'000'000,
.tv_nsec = duration.count() % 1'000'000'000,
};
timespec remain;
int ret;
while ((ret = nanosleep(&request, &remain)) < 0 && errno == EINTR) {
if (interruptible) {
break;
}
request = remain;
}
if (remaining) {
*remaining = std::chrono::nanoseconds(remain.tv_sec * 1'000'000'000 + remain.tv_nsec);
}
return ret == 0 || errno != EINTR;
}
#endif
#ifdef _WIN32
// Sets the debugger-visible name of the current thread.
void SetCurrentThreadName(const char* name) {
// bbport: guest thread names are kept by the C runtime.
SetThreadDescription(GetCurrentThread(), UTF8ToUTF16W(name).data());
}
void SetThreadName(void* thread, const char* name) {
SetThreadDescription(thread, UTF8ToUTF16W(name).data());
}
#else // !_WIN32, so must be POSIX threads
// MinGW with the POSIX threading model does not support pthread_setname_np
#if !defined(_WIN32) || defined(_MSC_VER)
void SetCurrentThreadName(const char* name) {
// bbport: guest thread names are kept by the C runtime.
#ifdef __APPLE__
pthread_setname_np(name);
#elif defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__)
pthread_set_name_np(pthread_self(), name);
#elif defined(__NetBSD__)
pthread_setname_np(pthread_self(), "%s", (void*)name);
#elif defined(__linux__)
// Linux limits thread names to 15 characters and will outright reject any
// attempt to set a longer name with ERANGE.
std::string truncated(name, std::min(strlen(name), static_cast<std::size_t>(15)));
if (int e = pthread_setname_np(pthread_self(), truncated.c_str())) {
errno = e;
LOG_ERROR(Common, "Failed to set thread name to '{}': {}", truncated, GetLastErrorMsg());
}
#else
pthread_setname_np(pthread_self(), name);
#endif
}
void SetThreadName(void* thread, const char* name) {
// TODO
}
#endif
#if defined(_WIN32)
void SetCurrentThreadName(const char* name) {
// bbport: guest thread names are kept by the C runtime.
// Do Nothing on MinGW
}
void SetThreadName(void* thread, const char* name) {
// Do Nothing on MinGW
}
#endif
#endif
AccurateTimer::AccurateTimer(std::chrono::nanoseconds target_interval)
: target_interval(target_interval) {}
void AccurateTimer::Start() {
const auto begin_sleep = std::chrono::high_resolution_clock::now();
if (total_wait.count() > 0) {
AccurateSleep(total_wait, nullptr, false);
}
start_time = std::chrono::high_resolution_clock::now();
total_wait -= std::chrono::duration_cast<std::chrono::nanoseconds>(start_time - begin_sleep);
}
void AccurateTimer::End() {
auto now = std::chrono::high_resolution_clock::now();
total_wait +=
target_interval - std::chrono::duration_cast<std::chrono::nanoseconds>(now - start_time);
}
std::string GetCurrentThreadName() {
#ifdef _WIN32
PWSTR name{};
if (FAILED(GetThreadDescription(GetCurrentThread(), &name)) || name == nullptr) {
return "<unknown name>";
}
const auto result = Common::UTF16ToUTF8(name);
LocalFree(name);
return result;
#else
char name[256];
if (pthread_getname_np(pthread_self(), name, sizeof(name)) != 0) {
return "<unknown name>";
}
return std::string{name};
#endif
}
} // namespace Common
+52
View File
@@ -0,0 +1,52 @@
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <chrono>
#include "common/types.h"
namespace Common {
enum class ThreadPriority : u32 {
Low = 0,
Normal = 1,
High = 2,
VeryHigh = 3,
Critical = 4,
};
void SetCurrentThreadRealtime(std::chrono::nanoseconds period_ns);
void SetCurrentThreadPriority(ThreadPriority new_priority);
void SetCurrentThreadName(const char* name);
void SetThreadName(void* thread, const char* name);
bool AccurateSleep(std::chrono::nanoseconds duration, std::chrono::nanoseconds* remaining,
bool interruptible);
class AccurateTimer {
std::chrono::nanoseconds target_interval{};
std::chrono::nanoseconds total_wait{};
std::chrono::high_resolution_clock::time_point start_time;
public:
explicit AccurateTimer(std::chrono::nanoseconds target_interval);
void Start();
void End();
std::chrono::nanoseconds GetTotalWait() const {
return total_wait;
}
};
std::string GetCurrentThreadName();
} // namespace Common
+39
View File
@@ -0,0 +1,39 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <cstdint>
using s8 = std::int8_t;
using s16 = std::int16_t;
using s32 = std::int32_t;
using s64 = std::int64_t;
using u8 = std::uint8_t;
using u16 = std::uint16_t;
using u32 = std::uint32_t;
using u64 = std::uint64_t;
using f32 = float;
using f64 = double;
using u128 = std::array<std::uint64_t, 2>;
static_assert(sizeof(u128) == 16, "u128 must be 128 bits wide");
using VAddr = uintptr_t;
using PAddr = uintptr_t;
#define PS4_SYSV_ABI __attribute__((sysv_abi))
// UDLs for memory size values
constexpr unsigned long long operator""_KB(unsigned long long x) {
return 1024ULL * x;
}
constexpr unsigned long long operator""_MB(unsigned long long x) {
return 1024_KB * x;
}
constexpr unsigned long long operator""_GB(unsigned long long x) {
return 1024_MB * x;
}
+115
View File
@@ -0,0 +1,115 @@
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <utility>
#ifdef _MSC_VER
#ifndef __clang__
#define HAS_INTRINSICS
#include <intrin.h>
#pragma intrinsic(__umulh)
#pragma intrinsic(_umul128)
#pragma intrinsic(_udiv128)
#else
#endif
#else
#include <cstring>
#endif
#include "common/types.h"
namespace Common {
// This function multiplies 2 u64 values and divides it by a u64 value.
[[nodiscard]] static inline u64 MultiplyAndDivide64(u64 a, u64 b, u64 d) {
#ifdef HAS_INTRINSICS
u128 r{};
r[0] = _umul128(a, b, &r[1]);
u64 remainder;
return _udiv128(r[1], r[0], d, &remainder);
#else
const u64 diva = a / d;
const u64 moda = a % d;
const u64 divb = b / d;
const u64 modb = b % d;
return diva * b + moda * divb + moda * modb / d;
#endif
}
// This function multiplies 2 u64 values and produces a u128 value;
[[nodiscard]] static inline u128 Multiply64Into128(u64 a, u64 b) {
u128 result;
#ifdef HAS_INTRINSICS
result[0] = _umul128(a, b, &result[1]);
#else
unsigned __int128 tmp = a;
tmp *= b;
std::memcpy(&result, &tmp, sizeof(u128));
#endif
return result;
}
[[nodiscard]] static inline u64 GetFixedPoint64Factor(u64 numerator, u64 divisor) {
#ifdef __SIZEOF_INT128__
const auto base = static_cast<unsigned __int128>(numerator) << 64ULL;
return static_cast<u64>(base / divisor);
#elif defined(_M_X64) || defined(_M_ARM64)
std::array<u64, 2> r = {0, numerator};
u64 remainder;
return _udiv128(r[1], r[0], divisor, &remainder);
#else
// This one is bit more inaccurate.
return MultiplyAndDivide64(std::numeric_limits<u64>::max(), numerator, divisor);
#endif
}
[[nodiscard]] static inline u64 MultiplyHigh(u64 a, u64 b) {
#ifdef __SIZEOF_INT128__
return (static_cast<unsigned __int128>(a) * static_cast<unsigned __int128>(b)) >> 64;
#elif defined(_M_X64) || defined(_M_ARM64)
return __umulh(a, b); // MSVC
#else
// Generic fallback
const u64 a_lo = u32(a);
const u64 a_hi = a >> 32;
const u64 b_lo = u32(b);
const u64 b_hi = b >> 32;
const u64 a_x_b_hi = a_hi * b_hi;
const u64 a_x_b_mid = a_hi * b_lo;
const u64 b_x_a_mid = b_hi * a_lo;
const u64 a_x_b_lo = a_lo * b_lo;
const u64 carry_bit = (static_cast<u64>(static_cast<u32>(a_x_b_mid)) +
static_cast<u64>(static_cast<u32>(b_x_a_mid)) + (a_x_b_lo >> 32)) >>
32;
const u64 multhi = a_x_b_hi + (a_x_b_mid >> 32) + (b_x_a_mid >> 32) + carry_bit;
return multhi;
#endif
}
// This function divides a u128 by a u32 value and produces two u64 values:
// the result of division and the remainder
[[nodiscard]] static inline std::pair<u64, u64> Divide128On32(const u128& dividend, u32 divisor) {
u64 remainder = dividend[0] % divisor;
u64 accum = dividend[0] / divisor;
if (dividend[1] == 0)
return {accum, remainder};
// We ignore dividend[1] / divisor as that overflows
const u64 first_segment = (dividend[1] % divisor) << 32;
accum += (first_segment / divisor) << 32;
const u64 second_segment = (first_segment % divisor) << 32;
accum += (second_segment / divisor);
remainder += second_segment % divisor;
if (remainder >= divisor) {
accum++;
remainder -= divisor;
}
return {accum, remainder};
}
} // namespace Common
+61
View File
@@ -0,0 +1,61 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <utility>
namespace Common {
/// General purpose function wrapper similar to std::function.
/// Unlike std::function, the captured values don't have to be copyable.
/// This class can be moved but not copied.
template <typename ResultType, typename... Args>
class UniqueFunction {
class CallableBase {
public:
virtual ~CallableBase() = default;
virtual ResultType operator()(Args&&...) = 0;
};
template <typename Functor>
class Callable final : public CallableBase {
public:
Callable(Functor&& functor_) : functor{std::move(functor_)} {}
~Callable() override = default;
ResultType operator()(Args&&... args) override {
return functor(std::forward<Args>(args)...);
}
private:
Functor functor;
};
public:
UniqueFunction() = default;
template <typename Functor>
UniqueFunction(Functor&& functor)
: callable{std::make_unique<Callable<Functor>>(std::move(functor))} {}
UniqueFunction& operator=(UniqueFunction&& rhs) noexcept = default;
UniqueFunction(UniqueFunction&& rhs) noexcept = default;
UniqueFunction& operator=(const UniqueFunction&) = delete;
UniqueFunction(const UniqueFunction&) = delete;
ResultType operator()(Args&&... args) const {
return (*callable)(std::forward<Args>(args)...);
}
explicit operator bool() const noexcept {
return static_cast<bool>(callable);
}
private:
std::unique_ptr<CallableBase> callable;
};
} // namespace Common
+118
View File
@@ -0,0 +1,118 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/arch.h"
#include "common/types.h"
#ifdef ARCH_X86_64
#include <xmmintrin.h>
#else
// FIXME
typedef __int128 __m128;
#endif
#define VA_ARGS \
uint64_t rdi, uint64_t rsi, uint64_t rdx, uint64_t rcx, uint64_t r8, uint64_t r9, \
uint64_t overflow_arg_area, __m128 xmm0, __m128 xmm1, __m128 xmm2, __m128 xmm3, \
__m128 xmm4, __m128 xmm5, __m128 xmm6, __m128 xmm7
#define VA_CTX(ctx) \
alignas(16)::Common::VaCtx ctx{}; \
(ctx).reg_save_area.gp[0] = rdi; \
(ctx).reg_save_area.gp[1] = rsi; \
(ctx).reg_save_area.gp[2] = rdx; \
(ctx).reg_save_area.gp[3] = rcx; \
(ctx).reg_save_area.gp[4] = r8; \
(ctx).reg_save_area.gp[5] = r9; \
(ctx).reg_save_area.fp[0] = xmm0; \
(ctx).reg_save_area.fp[1] = xmm1; \
(ctx).reg_save_area.fp[2] = xmm2; \
(ctx).reg_save_area.fp[3] = xmm3; \
(ctx).reg_save_area.fp[4] = xmm4; \
(ctx).reg_save_area.fp[5] = xmm5; \
(ctx).reg_save_area.fp[6] = xmm6; \
(ctx).reg_save_area.fp[7] = xmm7; \
(ctx).va_list.reg_save_area = &(ctx).reg_save_area; \
(ctx).va_list.gp_offset = offsetof(::Common::VaRegSave, gp); \
(ctx).va_list.fp_offset = offsetof(::Common::VaRegSave, fp); \
(ctx).va_list.overflow_arg_area = &overflow_arg_area;
namespace Common {
// https://stackoverflow.com/questions/4958384/what-is-the-format-of-the-x86-64-va-list-structure
struct VaList {
u32 gp_offset;
u32 fp_offset;
void* overflow_arg_area;
void* reg_save_area;
};
struct VaRegSave {
u64 gp[6];
__m128 fp[8];
};
struct VaCtx {
VaRegSave reg_save_area;
VaList va_list;
};
template <class T, uint32_t Size>
T vaArgRegSaveAreaGp(VaList* l) {
auto* addr = reinterpret_cast<T*>(static_cast<u8*>(l->reg_save_area) + l->gp_offset);
l->gp_offset += Size;
return *addr;
}
template <class T, u64 Align, u64 Size>
T vaArgOverflowArgArea(VaList* l) {
auto ptr = ((reinterpret_cast<u64>(l->overflow_arg_area) + (Align - 1)) & ~(Align - 1));
auto* addr = reinterpret_cast<T*>(ptr);
l->overflow_arg_area = reinterpret_cast<void*>(ptr + Size);
return *addr;
}
template <class T, uint32_t Size>
T vaArgRegSaveAreaFp(VaList* l) {
auto* addr = reinterpret_cast<T*>(static_cast<u8*>(l->reg_save_area) + l->fp_offset);
l->fp_offset += Size;
return *addr;
}
inline int vaArgInteger(VaList* l) {
if (l->gp_offset <= 40) {
return vaArgRegSaveAreaGp<int, 8>(l);
}
return vaArgOverflowArgArea<int, 1, 8>(l);
}
inline long long vaArgLongLong(VaList* l) {
if (l->gp_offset <= 40) {
return vaArgRegSaveAreaGp<long long, 8>(l);
}
return vaArgOverflowArgArea<long long, 1, 8>(l);
}
inline long vaArgLong(VaList* l) {
if (l->gp_offset <= 40) {
return vaArgRegSaveAreaGp<long, 8>(l);
}
return vaArgOverflowArgArea<long, 1, 8>(l);
}
inline double vaArgDouble(VaList* l) {
if (l->fp_offset <= 160) {
return vaArgRegSaveAreaFp<double, 16>(l);
}
return vaArgOverflowArgArea<double, 1, 8>(l);
}
template <class T>
T* vaArgPtr(VaList* l) {
if (l->gp_offset <= 40) {
return vaArgRegSaveAreaGp<T*, 8>(l);
}
return vaArgOverflowArgArea<T*, 1, 8>(l);
}
} // namespace Common
@@ -0,0 +1,283 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/libraries/avplayer/avplayer.h"
#include "core/libraries/avplayer/avplayer_error.h"
#include "core/libraries/avplayer/avplayer_impl.h"
#include "core/libraries/libs.h"
#include <string_view>
namespace Libraries::AvPlayer {
s32 PS4_SYSV_ABI sceAvPlayerAddSource(AvPlayerHandle handle, const char* filename) {
LOG_TRACE(Lib_AvPlayer, "filename = {}", filename);
if (handle == nullptr) {
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
}
return handle->AddSource(filename);
}
s32 PS4_SYSV_ABI sceAvPlayerAddSourceEx(AvPlayerHandle handle, AvPlayerUriType uri_type,
AvPlayerSourceDetails* source_details) {
LOG_TRACE(Lib_AvPlayer, "called");
if (handle == nullptr || uri_type != AvPlayerUriType::Source || source_details == nullptr) {
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
}
const auto path = std::string_view(source_details->uri.name, source_details->uri.length);
return handle->AddSourceEx(path, source_details->source_type);
}
int PS4_SYSV_ABI sceAvPlayerChangeStream() {
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceAvPlayerClose(AvPlayerHandle handle) {
LOG_TRACE(Lib_AvPlayer, "called");
if (handle == nullptr) {
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
}
delete handle;
return ORBIS_OK;
}
u64 PS4_SYSV_ABI sceAvPlayerCurrentTime(AvPlayerHandle handle) {
LOG_TRACE(Lib_AvPlayer, "called");
if (handle == nullptr) {
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
}
return handle->CurrentTime();
}
s32 PS4_SYSV_ABI sceAvPlayerDisableStream(AvPlayerHandle handle, u32 stream_id) {
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
if (handle == nullptr) {
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
}
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceAvPlayerEnableStream(AvPlayerHandle handle, u32 stream_id) {
LOG_TRACE(Lib_AvPlayer, "stream_id = {}", stream_id);
if (handle == nullptr) {
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
}
return handle->EnableStream(stream_id);
}
bool PS4_SYSV_ABI sceAvPlayerGetAudioData(AvPlayerHandle handle, AvPlayerFrameInfo* p_info) {
LOG_TRACE(Lib_AvPlayer, "called");
if (handle == nullptr || p_info == nullptr) {
return false;
}
return handle->GetAudioData(*p_info);
}
s32 PS4_SYSV_ABI sceAvPlayerGetStreamInfo(AvPlayerHandle handle, u32 stream_id,
AvPlayerStreamInfo* p_info) {
LOG_TRACE(Lib_AvPlayer, "stream_id = {}", stream_id);
if (handle == nullptr || p_info == nullptr) {
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
}
return handle->GetStreamInfo(stream_id, *p_info);
}
bool PS4_SYSV_ABI sceAvPlayerGetVideoData(AvPlayerHandle handle, AvPlayerFrameInfo* video_info) {
LOG_TRACE(Lib_AvPlayer, "called");
if (handle == nullptr || video_info == nullptr) {
return false;
}
return handle->GetVideoData(*video_info);
}
bool PS4_SYSV_ABI sceAvPlayerGetVideoDataEx(AvPlayerHandle handle,
AvPlayerFrameInfoEx* video_info) {
LOG_TRACE(Lib_AvPlayer, "called");
if (handle == nullptr || video_info == nullptr) {
return false;
}
return handle->GetVideoData(*video_info);
}
AvPlayerHandle PS4_SYSV_ABI sceAvPlayerInit(AvPlayerInitData* data) {
LOG_TRACE(Lib_AvPlayer, "called");
if (data == nullptr) {
return nullptr;
}
if (data->memory_replacement.allocate == nullptr ||
data->memory_replacement.allocate_texture == nullptr ||
data->memory_replacement.deallocate == nullptr ||
data->memory_replacement.deallocate_texture == nullptr) {
LOG_ERROR(Lib_AvPlayer, "All allocators are required for AvPlayer Initialisation.");
return nullptr;
}
return new AvPlayer(*data);
}
s32 PS4_SYSV_ABI sceAvPlayerInitEx(const AvPlayerInitDataEx* p_data, AvPlayerHandle* p_player) {
LOG_TRACE(Lib_AvPlayer, "called");
if (p_data == nullptr || p_player == nullptr) {
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
}
if (p_data->memory_replacement.allocate == nullptr ||
p_data->memory_replacement.allocate_texture == nullptr ||
p_data->memory_replacement.deallocate == nullptr ||
p_data->memory_replacement.deallocate_texture == nullptr) {
LOG_ERROR(Lib_AvPlayer, "All allocators are required for AvPlayer Initialisation.");
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
}
AvPlayerInitData data = {};
data.memory_replacement = p_data->memory_replacement;
data.file_replacement = p_data->file_replacement;
data.event_replacement = p_data->event_replacement;
data.default_language = p_data->default_language;
data.num_output_video_framebuffers = p_data->num_output_video_framebuffers;
data.auto_start = p_data->auto_start;
*p_player = new AvPlayer(data);
return ORBIS_OK;
}
bool PS4_SYSV_ABI sceAvPlayerIsActive(AvPlayerHandle handle) {
LOG_TRACE(Lib_AvPlayer, "called");
if (handle == nullptr) {
return false;
}
return handle->IsActive();
}
s32 PS4_SYSV_ABI sceAvPlayerJumpToTime(AvPlayerHandle handle, uint64_t time) {
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called, time (msec) = {}", time);
if (handle == nullptr) {
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
}
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceAvPlayerPause(AvPlayerHandle handle) {
LOG_TRACE(Lib_AvPlayer, "called");
if (handle == nullptr) {
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
}
return handle->Pause();
}
s32 PS4_SYSV_ABI sceAvPlayerPostInit(AvPlayerHandle handle, AvPlayerPostInitData* data) {
LOG_TRACE(Lib_AvPlayer, "called");
if (handle == nullptr || data == nullptr) {
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
}
return handle->PostInit(*data);
}
s32 PS4_SYSV_ABI sceAvPlayerPrintf(const char* format, ...) {
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceAvPlayerResume(AvPlayerHandle handle) {
LOG_TRACE(Lib_AvPlayer, "called");
if (handle == nullptr) {
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
}
return handle->Resume();
}
s32 PS4_SYSV_ABI sceAvPlayerSetAvSyncMode(AvPlayerHandle handle, AvPlayerAvSyncMode sync_mode) {
LOG_TRACE(Lib_AvPlayer, "called");
if (handle == nullptr) {
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
}
return handle->SetAvSyncMode(sync_mode);
}
s32 PS4_SYSV_ABI sceAvPlayerSetLogCallback(AvPlayerLogCallback log_cb, void* user_data) {
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceAvPlayerSetLooping(AvPlayerHandle handle, bool loop_flag) {
LOG_TRACE(Lib_AvPlayer, "called, looping = {}", loop_flag);
if (handle == nullptr) {
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
}
if (!handle->SetLooping(loop_flag)) {
return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED;
}
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceAvPlayerSetTrickSpeed(AvPlayerHandle handle, s32 trick_speed) {
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called speed = {}", trick_speed);
if (handle == nullptr) {
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
}
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceAvPlayerStart(AvPlayerHandle handle) {
LOG_TRACE(Lib_AvPlayer, "called");
if (handle == nullptr) {
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
}
return handle->Start();
}
s32 PS4_SYSV_ABI sceAvPlayerStop(AvPlayerHandle handle) {
LOG_TRACE(Lib_AvPlayer, "called");
if (handle == nullptr) {
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
}
return handle->Stop();
}
s32 PS4_SYSV_ABI sceAvPlayerStreamCount(AvPlayerHandle handle) {
LOG_TRACE(Lib_AvPlayer, "called");
if (handle == nullptr) {
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
}
return handle->GetStreamCount();
}
s32 PS4_SYSV_ABI sceAvPlayerVprintf(const char* format, va_list args) {
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
return ORBIS_OK;
}
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("KMcEa+rHsIo", "libSceAvPlayer", 1, "libSceAvPlayer", sceAvPlayerAddSource);
LIB_FUNCTION("x8uvuFOPZhU", "libSceAvPlayer", 1, "libSceAvPlayer", sceAvPlayerAddSourceEx);
LIB_FUNCTION("buMCiJftcfw", "libSceAvPlayer", 1, "libSceAvPlayer", sceAvPlayerChangeStream);
LIB_FUNCTION("NkJwDzKmIlw", "libSceAvPlayer", 1, "libSceAvPlayer", sceAvPlayerClose);
LIB_FUNCTION("wwM99gjFf1Y", "libSceAvPlayer", 1, "libSceAvPlayer", sceAvPlayerCurrentTime);
LIB_FUNCTION("BOVKAzRmuTQ", "libSceAvPlayer", 1, "libSceAvPlayer", sceAvPlayerDisableStream);
LIB_FUNCTION("ODJK2sn9w4A", "libSceAvPlayer", 1, "libSceAvPlayer", sceAvPlayerEnableStream);
LIB_FUNCTION("Wnp1OVcrZgk", "libSceAvPlayer", 1, "libSceAvPlayer", sceAvPlayerGetAudioData);
LIB_FUNCTION("d8FcbzfAdQw", "libSceAvPlayer", 1, "libSceAvPlayer", sceAvPlayerGetStreamInfo);
LIB_FUNCTION("o3+RWnHViSg", "libSceAvPlayer", 1, "libSceAvPlayer", sceAvPlayerGetVideoData);
LIB_FUNCTION("JdksQu8pNdQ", "libSceAvPlayer", 1, "libSceAvPlayer", sceAvPlayerGetVideoDataEx);
LIB_FUNCTION("aS66RI0gGgo", "libSceAvPlayer", 1, "libSceAvPlayer", sceAvPlayerInit);
LIB_FUNCTION("o9eWRkSL+M4", "libSceAvPlayer", 1, "libSceAvPlayer", sceAvPlayerInitEx);
LIB_FUNCTION("UbQoYawOsfY", "libSceAvPlayer", 1, "libSceAvPlayer", sceAvPlayerIsActive);
LIB_FUNCTION("XC9wM+xULz8", "libSceAvPlayer", 1, "libSceAvPlayer", sceAvPlayerJumpToTime);
LIB_FUNCTION("9y5v+fGN4Wk", "libSceAvPlayer", 1, "libSceAvPlayer", sceAvPlayerPause);
LIB_FUNCTION("HD1YKVU26-M", "libSceAvPlayer", 1, "libSceAvPlayer", sceAvPlayerPostInit);
// LIB_FUNCTION("agig-iDRrTE", "libSceAvPlayer", 1, "libSceAvPlayer", sceAvPlayerPrintf);
LIB_FUNCTION("w5moABNwnRY", "libSceAvPlayer", 1, "libSceAvPlayer", sceAvPlayerResume);
LIB_FUNCTION("k-q+xOxdc3E", "libSceAvPlayer", 1, "libSceAvPlayer", sceAvPlayerSetAvSyncMode);
LIB_FUNCTION("eBTreZ84JFY", "libSceAvPlayer", 1, "libSceAvPlayer", sceAvPlayerSetLogCallback);
LIB_FUNCTION("OVths0xGfho", "libSceAvPlayer", 1, "libSceAvPlayer", sceAvPlayerSetLooping);
LIB_FUNCTION("av8Z++94rs0", "libSceAvPlayer", 1, "libSceAvPlayer", sceAvPlayerSetTrickSpeed);
LIB_FUNCTION("ET4Gr-Uu07s", "libSceAvPlayer", 1, "libSceAvPlayer", sceAvPlayerStart);
LIB_FUNCTION("ZC17w3vB5Lo", "libSceAvPlayer", 1, "libSceAvPlayer", sceAvPlayerStop);
LIB_FUNCTION("hdTyRzCXQeQ", "libSceAvPlayer", 1, "libSceAvPlayer", sceAvPlayerStreamCount);
LIB_FUNCTION("yN7Jhuv8g24", "libSceAvPlayer", 1, "libSceAvPlayer", sceAvPlayerVprintf);
};
} // namespace Libraries::AvPlayer
@@ -0,0 +1,295 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
#include <cstdarg> // va_list
#include <cstddef> // size_t
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::AvPlayer {
class AvPlayer;
using AvPlayerHandle = AvPlayer*;
enum class AvPlayerUriType : u32 {
Source = 0,
};
struct AvPlayerUri {
const char* name;
u32 length;
};
enum class AvPlayerSourceType {
Unknown = 0,
FileMp4 = 1,
Hls = 8,
};
enum class AvPlayerStreamType : u32 {
Video,
Audio,
TimedText,
Unknown,
};
struct AvPlayerSourceDetails {
AvPlayerUri uri;
u8 reserved1[64];
AvPlayerSourceType source_type;
u8 reserved2[44];
};
struct AvPlayerAudio {
u16 channel_count;
u8 reserved1[2];
u32 sample_rate;
u32 size;
char language_code[4];
};
struct AvPlayerVideo {
u32 width;
u32 height;
f32 aspect_ratio;
char language_code[4];
};
struct AvPlayerTextPosition {
u16 top;
u16 left;
u16 bottom;
u16 right;
};
struct AvPlayerTimedText {
char language_code[4];
u16 text_size;
u16 font_size;
AvPlayerTextPosition position;
};
union AvPlayerStreamDetails {
u8 reserved[16];
AvPlayerAudio audio;
AvPlayerVideo video;
AvPlayerTimedText subs;
};
struct AvPlayerFrameInfo {
u8* p_data;
u8 reserved[4];
u64 timestamp;
AvPlayerStreamDetails details;
};
struct AvPlayerStreamInfo {
AvPlayerStreamType type;
u8 reserved[4];
AvPlayerStreamDetails details;
u64 duration;
u64 start_time;
};
struct AvPlayerAudioEx {
u16 channel_count;
u8 reserved[2];
u32 sample_rate;
u32 size;
u8 language_code[4];
u8 reserved1[64];
};
struct AvPlayerVideoEx {
u32 width;
u32 height;
f32 aspect_ratio;
u8 language_code[4];
u32 framerate;
u32 crop_left_offset;
u32 crop_right_offset;
u32 crop_top_offset;
u32 crop_bottom_offset;
u32 pitch;
u8 luma_bit_depth;
u8 chroma_bit_depth;
bool video_full_range_flag;
u8 reserved1[37];
};
struct AvPlayerTimedTextEx {
u8 language_code[4];
u8 reserved[12];
u8 reserved1[64];
};
union AvPlayerStreamDetailsEx {
AvPlayerAudioEx audio;
AvPlayerVideoEx video;
AvPlayerTimedTextEx subs;
u8 reserved1[80];
};
struct AvPlayerFrameInfoEx {
void* p_data;
u8 reserved[4];
u64 timestamp;
AvPlayerStreamDetailsEx details;
};
using AvPlayerAllocate = void* PS4_SYSV_ABI (*)(void* p, u32 align, u32 size);
using AvPlayerDeallocate = void PS4_SYSV_ABI (*)(void* p, void* mem);
using AvPlayerAllocateTexture = void* PS4_SYSV_ABI (*)(void* p, u32 align, u32 size);
using AvPlayerDeallocateTexture = void PS4_SYSV_ABI (*)(void* p, void* mem);
struct AvPlayerMemAllocator {
void* object_ptr;
AvPlayerAllocate allocate;
AvPlayerDeallocate deallocate;
AvPlayerAllocateTexture allocate_texture;
AvPlayerDeallocateTexture deallocate_texture;
};
using AvPlayerOpenFile = s32 PS4_SYSV_ABI (*)(void* p, const char* name);
using AvPlayerCloseFile = s32 PS4_SYSV_ABI (*)(void* p);
using AvPlayerReadOffsetFile = s32 PS4_SYSV_ABI (*)(void* p, u8* buf, u64 pos, u32 len);
using AvPlayerSizeFile = u64 PS4_SYSV_ABI (*)(void* p);
struct AvPlayerFileReplacement {
void* object_ptr;
AvPlayerOpenFile open;
AvPlayerCloseFile close;
AvPlayerReadOffsetFile read_offset;
AvPlayerSizeFile size;
};
enum class AvPlayerEvents {
StateStop = 0x01,
StateReady = 0x02,
StatePlay = 0x03,
StatePause = 0x04,
StateBuffering = 0x05,
TimedTextDelivery = 0x10,
WarningId = 0x20,
Encryption = 0x30,
DrmError = 0x40,
};
using AvPlayerEventCallback = void PS4_SYSV_ABI (*)(void* p, AvPlayerEvents event, s32 src_id,
void* data);
struct AvPlayerEventReplacement {
void* object_ptr;
AvPlayerEventCallback event_callback;
};
enum class AvPlayerDebuglevels {
None,
Info,
Warnings,
All,
};
struct AvPlayerInitData {
AvPlayerMemAllocator memory_replacement;
AvPlayerFileReplacement file_replacement;
AvPlayerEventReplacement event_replacement;
AvPlayerDebuglevels debug_level;
u32 base_priority;
s32 num_output_video_framebuffers;
bool auto_start;
u8 reserved[3];
const char* default_language;
};
struct AvPlayerInitDataEx {
size_t this_size;
AvPlayerMemAllocator memory_replacement;
AvPlayerFileReplacement file_replacement;
AvPlayerEventReplacement event_replacement;
const char* default_language;
AvPlayerDebuglevels debug_level;
u32 audio_decoder_priority;
u32 audio_decoder_affinity;
u32 video_decoder_priority;
u32 video_decoder_affinity;
u32 demuxer_priority;
u32 demuxer_affinity;
u32 controller_priority;
u32 controller_affinity;
u32 http_streaming_priority;
u32 http_streaming_affinity;
u32 file_streaming_priority;
u32 file_streaming_affinity;
s32 num_output_video_framebuffers;
bool auto_start;
u8 reserved[3];
};
enum class AvPlayerVideoDecoderType {
Default = 0,
Reserved1,
Software,
Software2,
};
enum class AvPlayerAudioDecoderType {
Default = 0,
Reserved1,
Reserved2,
};
struct AvPlayerDecoderInit {
union {
AvPlayerVideoDecoderType video_type;
AvPlayerAudioDecoderType audio_type;
u8 reserved[4];
} decoder_type;
union {
struct {
s32 cpu_affinity_mask;
s32 cpu_thread_priority;
u8 decode_pipeline_depth;
u8 compute_pipe_id;
u8 compute_queue_id;
u8 enable_interlaced;
u8 reserved[16];
} avc_sw2;
struct {
u8 audio_channel_order;
u8 reserved[27];
} aac;
u8 reserved[28];
} decoder_params;
};
struct AvPlayerHTTPCtx {
u32 http_context_id;
u32 ssl_context_id;
};
struct AvPlayerPostInitData {
u32 demux_video_buffer_size;
AvPlayerDecoderInit video_decoder_init;
AvPlayerDecoderInit audio_decoder_init;
AvPlayerHTTPCtx http_context;
u8 reserved[56];
};
enum class AvPlayerAvSyncMode {
Default = 0,
None,
};
using AvPlayerLogCallback = int PS4_SYSV_ABI (*)(void* p, const char* format, va_list args);
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::AvPlayer
@@ -0,0 +1,60 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm> // std::equal
#include <cctype> // std::tolower
#include "core/libraries/avplayer/avplayer_common.h"
namespace Libraries::AvPlayer {
static bool iequals(std::string_view l, std::string_view r) {
return std::ranges::equal(l, r, [](u8 a, u8 b) { return std::tolower(a) == std::tolower(b); });
}
AvPlayerSourceType GetSourceType(std::string_view path) {
if (path.empty()) {
return AvPlayerSourceType::Unknown;
}
std::string_view name = path;
if (path.find("://") != std::string_view::npos) {
// This path is a URI. Strip HTTP parameters from it.
// schema://server.domain/path/file.ext/and/beyond?param=value#paragraph ->
// -> schema://server.domain/path/to/file.ext/and/beyond
name = path.substr(0, path.find_first_of("?#"));
if (name.empty()) {
return AvPlayerSourceType::Unknown;
}
}
// schema://server.domain/path/to/file.ext/and/beyond -> .ext/and/beyond
// Find extension dot
auto dot_pos = name.rfind('.');
if (dot_pos == std::string_view::npos) {
return AvPlayerSourceType::Unknown;
}
// Extract extension (".ext/anything" or ".ext")
auto ext = name.substr(dot_pos);
if (ext.empty()) {
return AvPlayerSourceType::Unknown;
}
// .ext/and/beyond -> .ext
ext = ext.substr(0, ext.find('/'));
if (iequals(ext, ".mp4") || iequals(ext, ".m4v") || iequals(ext, ".m3d") ||
iequals(ext, ".m4a") || iequals(ext, ".mov")) {
return AvPlayerSourceType::FileMp4;
}
if (iequals(ext, ".m3u8")) {
return AvPlayerSourceType::Hls;
}
return AvPlayerSourceType::Unknown;
}
} // namespace Libraries::AvPlayer
@@ -0,0 +1,94 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <mutex>
#include <optional>
#include <string_view>
#include <utility>
#include <queue>
#include "core/libraries/avplayer/avplayer.h"
#define AVPLAYER_IS_ERROR(x) ((x) < 0)
namespace Libraries::AvPlayer {
enum class AvState {
Unknown,
Initial,
AddingSource,
Ready,
Play,
Stop,
EndOfFile,
Pause,
C0x08,
Jump,
TrickMode,
C0x0B,
Buffering,
Starting,
Error,
};
enum class AvEventType {
ChangeFlowState = 21,
WarningId = 22,
RevertState = 30,
AddSource = 40,
Error = 255,
};
union AvPlayerEventData {
u32 num_frames; // 20
AvState state; // AvEventType::ChangeFlowState
s32 error; // AvEventType::WarningId
u32 attempt; // AvEventType::AddSource
};
struct AvPlayerEvent {
AvEventType event;
AvPlayerEventData payload;
};
template <class T>
class AvPlayerQueue {
public:
size_t Size() {
return m_queue.size();
}
void Push(T&& value) {
std::lock_guard guard(m_mutex);
m_queue.emplace(std::forward<T>(value));
}
T& Front() {
return m_queue.front();
}
std::optional<T> Pop() {
if (Size() == 0) {
return std::nullopt;
}
std::lock_guard guard(m_mutex);
auto result = std::move(m_queue.front());
m_queue.pop();
return result;
}
void Clear() {
std::lock_guard guard(m_mutex);
m_queue = {};
}
private:
std::mutex m_mutex{};
std::queue<T> m_queue{};
};
AvPlayerSourceType GetSourceType(std::string_view path);
} // namespace Libraries::AvPlayer
@@ -0,0 +1,20 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string_view>
struct AVIOContext;
namespace Libraries::AvPlayer {
class IDataStreamer {
public:
virtual ~IDataStreamer() = default;
virtual bool Init(std::string_view path) = 0;
virtual void Reset() = 0;
virtual AVIOContext* GetContext() = 0;
};
} // namespace Libraries::AvPlayer
@@ -0,0 +1,19 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/libraries/error_codes.h"
// AvPlayer library
constexpr int ORBIS_AVPLAYER_ERROR_INVALID_PARAMS = 0x806A0001;
constexpr int ORBIS_AVPLAYER_ERROR_OPERATION_FAILED = 0x806A0002;
constexpr int ORBIS_AVPLAYER_ERROR_NO_MEMORY = 0x806A0003;
constexpr int ORBIS_AVPLAYER_ERROR_NOT_SUPPORTED = 0x806A0004;
constexpr int ORBIS_AVPLAYER_ERROR_WAR_FILE_NONINTERLEAVED = 0x806A00A0;
constexpr int ORBIS_AVPLAYER_ERROR_WAR_LOOPING_BACK = 0x806A00A1;
constexpr int ORBIS_AVPLAYER_ERROR_WAR_JUMP_COMPLETE = 0x806A00A3;
constexpr int ORBIS_AVPLAYER_ERROR_INFO_MARLIN_ENCRY = 0x806A00B0;
constexpr int ORBIS_AVPLAYER_ERROR_INFO_PLAYREADY_ENCRY = 0x806A00B4;
constexpr int ORBIS_AVPLAYER_ERROR_INFO_AES_ENCRY = 0x806A00B5;
constexpr int ORBIS_AVPLAYER_ERROR_INFO_OTHER_ENCRY = 0x806A00BF;
@@ -0,0 +1,90 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm> // std::max, std::min
#include "core/libraries/avplayer/avplayer_file_streamer.h"
extern "C" {
#include <libavformat/avio.h>
#include <libavutil/error.h>
#include <libavutil/mem.h>
}
constexpr u32 AVPLAYER_AVIO_BUFFER_SIZE = 4096;
namespace Libraries::AvPlayer {
AvPlayerFileStreamer::AvPlayerFileStreamer(const AvPlayerFileReplacement& file_replacement)
: m_file_replacement(file_replacement) {}
AvPlayerFileStreamer::~AvPlayerFileStreamer() {
if (m_avio_context != nullptr) {
avio_context_free(&m_avio_context);
}
if (m_file_replacement.close != nullptr && m_fd >= 0) {
const auto close = m_file_replacement.close;
const auto ptr = m_file_replacement.object_ptr;
close(ptr);
}
}
bool AvPlayerFileStreamer::Init(std::string_view path) {
const auto ptr = m_file_replacement.object_ptr;
m_fd = m_file_replacement.open(ptr, path.data());
if (m_fd < 0) {
return false;
}
m_file_size = m_file_replacement.size(ptr);
// avio_buffer is deallocated in `avio_context_free`
const auto avio_buffer = reinterpret_cast<u8*>(av_malloc(AVPLAYER_AVIO_BUFFER_SIZE));
m_avio_context =
avio_alloc_context(avio_buffer, AVPLAYER_AVIO_BUFFER_SIZE, 0, this,
&AvPlayerFileStreamer::ReadPacket, nullptr, &AvPlayerFileStreamer::Seek);
return true;
}
void AvPlayerFileStreamer::Reset() {
m_position = 0;
}
s32 AvPlayerFileStreamer::ReadPacket(void* opaque, u8* buffer, s32 size) {
const auto self = reinterpret_cast<AvPlayerFileStreamer*>(opaque);
if (self->m_position >= self->m_file_size) {
return AVERROR_EOF;
}
if (self->m_position + size > self->m_file_size) {
size = self->m_file_size - self->m_position;
}
const auto read_offset = self->m_file_replacement.read_offset;
const auto ptr = self->m_file_replacement.object_ptr;
const auto bytes_read = read_offset(ptr, buffer, self->m_position, size);
if (bytes_read == 0 && size != 0) {
return AVERROR_EOF;
}
self->m_position += bytes_read;
return bytes_read;
}
s64 AvPlayerFileStreamer::Seek(void* opaque, s64 offset, int whence) {
const auto self = reinterpret_cast<AvPlayerFileStreamer*>(opaque);
if (whence & AVSEEK_SIZE) {
return self->m_file_size;
}
if (whence == SEEK_CUR) {
self->m_position =
std::min(u64(std::max(s64(0), s64(self->m_position) + offset)), self->m_file_size);
return self->m_position;
} else if (whence == SEEK_SET) {
self->m_position = std::min(u64(std::max(s64(0), offset)), self->m_file_size);
return self->m_position;
} else if (whence == SEEK_END) {
self->m_position =
std::min(u64(std::max(s64(0), s64(self->m_file_size) + offset)), self->m_file_size);
return self->m_position;
}
return -1;
}
} // namespace Libraries::AvPlayer
@@ -0,0 +1,38 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string_view>
#include "core/libraries/avplayer/avplayer.h"
#include "core/libraries/avplayer/avplayer_data_streamer.h"
struct AVIOContext;
namespace Libraries::AvPlayer {
class AvPlayerFileStreamer : public IDataStreamer {
public:
AvPlayerFileStreamer(const AvPlayerFileReplacement& file_replacement);
~AvPlayerFileStreamer();
bool Init(std::string_view path) override;
void Reset() override;
AVIOContext* GetContext() override {
return m_avio_context;
}
private:
static s32 ReadPacket(void* opaque, u8* buffer, s32 size);
static s64 Seek(void* opaque, s64 buffer, int whence);
AvPlayerFileReplacement m_file_replacement;
int m_fd = -1;
u64 m_position{};
u64 m_file_size{};
AVIOContext* m_avio_context{};
};
} // namespace Libraries::AvPlayer
@@ -0,0 +1,227 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/libraries/avplayer/avplayer_common.h"
#include "core/libraries/avplayer/avplayer_error.h"
#include "core/libraries/avplayer/avplayer_impl.h"
namespace Libraries::AvPlayer {
void* PS4_SYSV_ABI AvPlayer::Allocate(void* handle, u32 alignment, u32 size) {
const auto* const self = reinterpret_cast<AvPlayer*>(handle);
const auto allocate = self->m_init_data_original.memory_replacement.allocate;
const auto ptr = self->m_init_data_original.memory_replacement.object_ptr;
return allocate(ptr, alignment, size);
}
void PS4_SYSV_ABI AvPlayer::Deallocate(void* handle, void* memory) {
const auto* const self = reinterpret_cast<AvPlayer*>(handle);
const auto deallocate = self->m_init_data_original.memory_replacement.deallocate;
const auto ptr = self->m_init_data_original.memory_replacement.object_ptr;
return deallocate(ptr, memory);
}
void* PS4_SYSV_ABI AvPlayer::AllocateTexture(void* handle, u32 alignment, u32 size) {
const auto* const self = reinterpret_cast<AvPlayer*>(handle);
const auto allocate = self->m_init_data_original.memory_replacement.allocate_texture;
const auto ptr = self->m_init_data_original.memory_replacement.object_ptr;
return allocate(ptr, alignment, size);
}
void PS4_SYSV_ABI AvPlayer::DeallocateTexture(void* handle, void* memory) {
const auto* const self = reinterpret_cast<AvPlayer*>(handle);
const auto deallocate = self->m_init_data_original.memory_replacement.deallocate_texture;
const auto ptr = self->m_init_data_original.memory_replacement.object_ptr;
return deallocate(ptr, memory);
}
int PS4_SYSV_ABI AvPlayer::OpenFile(void* handle, const char* filename) {
auto const self = reinterpret_cast<AvPlayer*>(handle);
std::lock_guard guard(self->m_file_io_mutex);
const auto open = self->m_init_data_original.file_replacement.open;
const auto ptr = self->m_init_data_original.file_replacement.object_ptr;
return open(ptr, filename);
}
int PS4_SYSV_ABI AvPlayer::CloseFile(void* handle) {
auto const self = reinterpret_cast<AvPlayer*>(handle);
std::lock_guard guard(self->m_file_io_mutex);
const auto close = self->m_init_data_original.file_replacement.close;
const auto ptr = self->m_init_data_original.file_replacement.object_ptr;
return close(ptr);
}
int PS4_SYSV_ABI AvPlayer::ReadOffsetFile(void* handle, u8* buffer, u64 position, u32 length) {
auto const self = reinterpret_cast<AvPlayer*>(handle);
std::lock_guard guard(self->m_file_io_mutex);
const auto read_offset = self->m_init_data_original.file_replacement.read_offset;
const auto ptr = self->m_init_data_original.file_replacement.object_ptr;
return read_offset(ptr, buffer, position, length);
}
u64 PS4_SYSV_ABI AvPlayer::SizeFile(void* handle) {
auto const self = reinterpret_cast<AvPlayer*>(handle);
std::lock_guard guard(self->m_file_io_mutex);
const auto size = self->m_init_data_original.file_replacement.size;
const auto ptr = self->m_init_data_original.file_replacement.object_ptr;
return size(ptr);
}
AvPlayerInitData AvPlayer::StubInitData(const AvPlayerInitData& data) {
AvPlayerInitData result = data;
result.memory_replacement.object_ptr = this;
result.memory_replacement.allocate = &AvPlayer::Allocate;
result.memory_replacement.deallocate = &AvPlayer::Deallocate;
result.memory_replacement.allocate_texture = &AvPlayer::AllocateTexture;
result.memory_replacement.deallocate_texture = &AvPlayer::DeallocateTexture;
if (data.file_replacement.open == nullptr || data.file_replacement.close == nullptr ||
data.file_replacement.read_offset == nullptr || data.file_replacement.size == nullptr) {
result.file_replacement = {};
} else {
result.file_replacement.object_ptr = this;
result.file_replacement.open = &AvPlayer::OpenFile;
result.file_replacement.close = &AvPlayer::CloseFile;
result.file_replacement.read_offset = &AvPlayer::ReadOffsetFile;
result.file_replacement.size = &AvPlayer::SizeFile;
}
return result;
}
AvPlayer::AvPlayer(const AvPlayerInitData& data)
: m_init_data(StubInitData(data)), m_init_data_original(data),
m_state(std::make_unique<AvPlayerState>(m_init_data)) {}
s32 AvPlayer::PostInit(const AvPlayerPostInitData& data) {
m_state->PostInit(data);
return ORBIS_OK;
}
s32 AvPlayer::AddSource(std::string_view path) {
return AddSourceEx(path, AvPlayerSourceType::Unknown);
}
s32 AvPlayer::AddSourceEx(std::string_view path, AvPlayerSourceType source_type) {
if (source_type == AvPlayerSourceType::Unknown) {
source_type = GetSourceType(path);
}
if (source_type == AvPlayerSourceType::Hls) {
LOG_ERROR(Lib_AvPlayer, "HTTP Live Streaming is not implemented");
return ORBIS_AVPLAYER_ERROR_NOT_SUPPORTED;
}
if (!m_state->AddSource(path, GetSourceType(path))) {
return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED;
}
return ORBIS_OK;
}
s32 AvPlayer::GetStreamCount() {
if (m_state == nullptr) {
return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED;
}
const auto res = m_state->GetStreamCount();
if (AVPLAYER_IS_ERROR(res)) {
return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED;
}
return res;
}
s32 AvPlayer::GetStreamInfo(u32 stream_index, AvPlayerStreamInfo& info) {
if (!m_state->GetStreamInfo(stream_index, info)) {
return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED;
}
return ORBIS_OK;
}
s32 AvPlayer::EnableStream(u32 stream_index) {
if (m_state == nullptr) {
return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED;
}
if (!m_state->EnableStream(stream_index)) {
return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED;
}
return ORBIS_OK;
}
s32 AvPlayer::Start() {
if (m_state == nullptr || !m_state->Start()) {
return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED;
}
return ORBIS_OK;
}
s32 AvPlayer::Pause() {
if (m_state == nullptr || !m_state->Pause()) {
return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED;
}
return ORBIS_OK;
}
s32 AvPlayer::Resume() {
if (m_state == nullptr || !m_state->Resume()) {
return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED;
}
return ORBIS_OK;
}
s32 AvPlayer::SetAvSyncMode(AvPlayerAvSyncMode sync_mode) {
if (m_state == nullptr) {
return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED;
}
m_state->SetAvSyncMode(sync_mode);
return ORBIS_OK;
}
bool AvPlayer::GetVideoData(AvPlayerFrameInfo& video_info) {
if (m_state == nullptr) {
return false;
}
return m_state->GetVideoData(video_info);
}
bool AvPlayer::GetVideoData(AvPlayerFrameInfoEx& video_info) {
if (m_state == nullptr) {
return false;
}
return m_state->GetVideoData(video_info);
}
bool AvPlayer::GetAudioData(AvPlayerFrameInfo& audio_info) {
if (m_state == nullptr) {
return false;
}
return m_state->GetAudioData(audio_info);
}
bool AvPlayer::IsActive() {
if (m_state == nullptr) {
return false;
}
return m_state->IsActive();
}
u64 AvPlayer::CurrentTime() {
if (m_state == nullptr) {
return 0;
}
return m_state->CurrentTime();
}
s32 AvPlayer::Stop() {
if (m_state == nullptr || !m_state->Stop()) {
return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED;
}
return ORBIS_OK;
}
bool AvPlayer::SetLooping(bool is_looping) {
if (m_state == nullptr) {
return false;
}
return m_state->SetLooping(is_looping);
}
} // namespace Libraries::AvPlayer
@@ -0,0 +1,59 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/libraries/avplayer/avplayer.h"
#include "core/libraries/avplayer/avplayer_state.h"
#include <memory>
#include <mutex>
#include <string_view>
namespace Libraries::AvPlayer {
class AvPlayer {
public:
AvPlayer(const AvPlayerInitData& data);
s32 PostInit(const AvPlayerPostInitData& data);
s32 AddSource(std::string_view filename);
s32 AddSourceEx(std::string_view path, AvPlayerSourceType source_type);
s32 GetStreamCount();
s32 GetStreamInfo(u32 stream_index, AvPlayerStreamInfo& info);
s32 EnableStream(u32 stream_index);
s32 Start();
s32 Pause();
s32 Resume();
s32 SetAvSyncMode(AvPlayerAvSyncMode sync_mode);
bool GetAudioData(AvPlayerFrameInfo& audio_info);
bool GetVideoData(AvPlayerFrameInfo& video_info);
bool GetVideoData(AvPlayerFrameInfoEx& video_info);
bool IsActive();
u64 CurrentTime();
s32 Stop();
bool SetLooping(bool is_looping);
private:
// Memory Replacement
static void* PS4_SYSV_ABI Allocate(void* handle, u32 alignment, u32 size);
static void PS4_SYSV_ABI Deallocate(void* handle, void* memory);
static void* PS4_SYSV_ABI AllocateTexture(void* handle, u32 alignment, u32 size);
static void PS4_SYSV_ABI DeallocateTexture(void* handle, void* memory);
// File Replacement
static int PS4_SYSV_ABI OpenFile(void* handle, const char* filename);
static int PS4_SYSV_ABI CloseFile(void* handle);
static int PS4_SYSV_ABI ReadOffsetFile(void* handle, u8* buffer, u64 position, u32 length);
static u64 PS4_SYSV_ABI SizeFile(void* handle);
AvPlayerInitData StubInitData(const AvPlayerInitData& data);
AvPlayerInitData m_init_data{};
AvPlayerInitData m_init_data_original{};
std::mutex m_file_io_mutex{};
std::unique_ptr<AvPlayerState> m_state{};
};
} // namespace Libraries::AvPlayer

Some files were not shown because too many files have changed in this diff Show More