mirror of
https://github.com/BillyOutlast/bloodborne_pc.git
synced 2026-10-12 02:25:51 +00:00
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:
@@ -0,0 +1,4 @@
|
||||
out/
|
||||
__pycache__/
|
||||
user/
|
||||
*.mp4
|
||||
@@ -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` и не нужен
|
||||
для распространения исходников инструмента.
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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 '')
|
||||
@@ -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)
|
||||
@@ -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
@@ -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
|
||||
@@ -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.
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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 ⦥
|
||||
}
|
||||
|
||||
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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Executable
+25
@@ -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
|
||||
@@ -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
|
||||
@@ -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>;
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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**>(¤t), 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
Executable
+53
@@ -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
|
||||
Executable
+33
@@ -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
|
||||
@@ -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"
|
||||
@@ -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>
|
||||
@@ -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]{};
|
||||
};
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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, ¶ms);
|
||||
}
|
||||
|
||||
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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
Executable
+61
@@ -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
|
||||
@@ -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
Reference in New Issue
Block a user