mirror of
https://github.com/EmiiBytee/np2kai-ps4.git
synced 2026-10-06 10:00:13 +02:00
454 lines
25 KiB
Python
Executable File
454 lines
25 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""comprobaciones antes de compilar.
|
|
|
|
esto no compila nada, solo lee los fuentes y mira que sigan estando las cosas
|
|
que ya funcionaban. lo hice porque me cargue el mando y el audio arreglando el
|
|
teclado y no me entere hasta tenerlo instalado en la consola.
|
|
|
|
si una comprobacion se queja, o has roto algo o has movido codigo y hay que
|
|
actualizarla aqui.
|
|
"""
|
|
from pathlib import Path
|
|
import re
|
|
import sys
|
|
|
|
root = Path(__file__).resolve().parents[1]
|
|
main = (root / 'SDL2/main.cpp').read_text()
|
|
backend = (root / 'SDL2/ps4_keyboard.cpp').read_text()
|
|
backend_header = (root / 'SDL2/ps4_keyboard.h').read_text()
|
|
core = (root / 'third_party/NP2kai/sdl/kbtrans.c').read_text()
|
|
makefile = (root / 'Makefile').read_text()
|
|
metadata_validator = (root / 'tools/validate-ps4-package-metadata.py').read_text()
|
|
|
|
errors = []
|
|
|
|
|
|
def require(condition, message):
|
|
if not condition:
|
|
errors.append(message)
|
|
|
|
|
|
# --- Control selector: 104 assignments over the full 101-key PC-98 table -----
|
|
start = main.index('static const ControlChoice CONTROL_CHOICES[]')
|
|
end = main.index('static constexpr unsigned CONTROL_CHOICE_COUNT', start)
|
|
block = main[start:end]
|
|
choices = re.findall(
|
|
r'\{\{ControlBindingType::(None|Keyboard|MouseLeft|MouseRight),\s*'
|
|
r'(RETROK_[A-Za-z0-9_]+)\},\s*"([^"]+)"',
|
|
block,
|
|
)
|
|
require(len(choices) == 104, f'expected 104 choices, found {len(choices)}')
|
|
config_names = [item[2] for item in choices]
|
|
require(len(config_names) == len(set(config_names)), 'duplicate config names')
|
|
legacy = {
|
|
'none', 'key_z', 'key_x', 'space', 'control', 'shift',
|
|
'backspace', 'escape', 'return', 'mouse_left', 'mouse_right',
|
|
}
|
|
require(legacy.issubset(config_names), 'one or more legacy binding names are missing')
|
|
|
|
core101 = core[
|
|
core.index('static const LRKCNV lrcnv101[]'):
|
|
core.index('/* 106 keyboard key table */')
|
|
]
|
|
core_keys = set(re.findall(r'\{(RETROK_[A-Za-z0-9_]+),', core101))
|
|
choice_keys = {key for kind, key, _ in choices if kind == 'Keyboard'}
|
|
unsupported = sorted(choice_keys - core_keys)
|
|
require(not unsupported, 'selector keys missing from PC-98 table: ' + ', '.join(unsupported))
|
|
require(len(choice_keys) == len(core_keys) == 101,
|
|
f'expected complete 101-key coverage, selector={len(choice_keys)} core={len(core_keys)}')
|
|
|
|
# --- Existing functionality must remain intact ------------------------------
|
|
require('static bool runDefaultControlsMenu()' in main, 'global controls editor missing')
|
|
require('runGameControlsMenu(game)' in main, 'per-game controls editor missing')
|
|
require('default_binding_%s=%s\\n' in main, 'custom default bindings are not persistent')
|
|
require('static bool presentGameDetailsOverlay(' not in main,
|
|
'la ficha del juego ahora vive en el panel de la biblioteca')
|
|
require('gameHasOwnProfile' in main, 'el panel debe decir si el juego tiene perfil propio')
|
|
require('enum class InputLayout' in main and
|
|
'setActiveInputLayout(InputLayout::Keyboard)' in main and
|
|
'setActiveInputLayout(InputLayout::Controller)' in main,
|
|
'automatic input-layout switching missing')
|
|
require('SDL_KEYDOWN' in main and 'SDL_KEYUP' in main and 'SDL_TEXTINPUT' in main,
|
|
'SDL keyboard event bridge/fallback missing')
|
|
|
|
# --- 1.0 root-cause fix: libSceKeyboard, not the IME ----------------------
|
|
require('#include "ps4_keyboard.h"' in main, 'native backend header is not included')
|
|
require('__attribute__((weak))' not in main, 'stale weak liborbisKeyboard declarations remain')
|
|
for stale in ('orbisKeyboardInit', 'orbisKeyboardOpen', 'orbisKeyboardFinish',
|
|
'sceImeKeyboardOpen', 'PS4_SYSMODULE_LIBIME',
|
|
'sceSysmoduleLoadModuleInternal'):
|
|
require(stale not in main, f'v0.37 IME leftover still referenced in main.cpp: {stale}')
|
|
|
|
for symbol in ('sceKeyboardInit', 'sceKeyboardOpen', 'sceKeyboardClose',
|
|
'sceKeyboardReadState', 'sceKeyboardGetKey2Char'):
|
|
require(symbol in backend, f'native backend does not call {symbol}')
|
|
require('PS4_SYSMODULE_KEYBOARD = 0x0106' in backend,
|
|
'keyboard sysmodule id is missing')
|
|
require('ps4KeyboardPoll' in backend_header and 'ps4KeyboardOpenDevice' in backend_header,
|
|
'native backend public API incomplete')
|
|
|
|
require('static void processNativeKeyboardEvents()' in main,
|
|
'native keyboard poll is missing from the frontend')
|
|
require('processNativeKeyboardEvents();' in main and
|
|
'if (!g_externalKeyboardEventSubsystemReady) return;' in main,
|
|
'native polling must run before the SDL-only early return')
|
|
require('static unsigned hidUsageToRetroKey(' in main,
|
|
'HID usage to libretro keycode table missing')
|
|
require('static void applyExternalKeyEvent(' in main,
|
|
'shared key handler missing: native and SDL paths must converge')
|
|
require('if (!g_nativeKeyboardOpened) tryOpenNativeKeyboardBackend();' in main,
|
|
'hot-plug retry path missing')
|
|
require('NATIVE_KEYBOARD_RETRY_MS = 2000' in main,
|
|
'native keyboard retry interval missing')
|
|
require('Keyboard backend: libSceKeyboard opened=' in main,
|
|
'persistent latest.log keyboard diagnostics missing')
|
|
require('externalKeyboardStatusText()' in main and
|
|
'sceKeyboardOpen no encuentra el dispositivo' in main,
|
|
'visible native keyboard status missing')
|
|
|
|
# The frontend must not lose a held key when the device disappears.
|
|
require('handle released' in backend,
|
|
'unplug handling does not release held keys')
|
|
|
|
# --- Build wiring: no network, no libSceIme, no external repositories -------
|
|
require('-lSceKeyboard' in makefile, 'libSceKeyboard is not linked')
|
|
require('-lSceIme' not in makefile, 'libSceIme is still linked')
|
|
require('NATIVE_KEYBOARD_LIB' not in makefile.replace('clean-native-keyboard', ''),
|
|
'the liborbisKeyboard archive is still wired into the build')
|
|
require('--whole-archive $(CORE_LIB) --no-whole-archive' in makefile,
|
|
'core force-link line was altered')
|
|
require(not (root / 'tools/prepare-native-keyboard.sh').exists(),
|
|
'prepare-native-keyboard.sh still present: the build would clone repositories')
|
|
for token in ('liborbis', 'ps4sdk', 'firmware505', 'git clone'):
|
|
require(token not in makefile, f'build still references removed dependency: {token}')
|
|
|
|
# --- 1.0 regression guard: one shared user service -----------------------
|
|
# v0.38 broke the pad and the audio because the keyboard backend called
|
|
# sceUserServiceInitialize first, so the pad's own call returned
|
|
# ALREADY_INITIALIZED and it aborted, leaving the user id invalid.
|
|
require('static bool ensureUserServiceReady()' in main,
|
|
'shared user-service helper missing')
|
|
require('ORBIS_USER_SERVICE_ALREADY_INITIALIZED' in main,
|
|
'ALREADY_INITIALIZED is not treated as success')
|
|
require(main.count('sceUserServiceInitialize(') == 1,
|
|
'sceUserServiceInitialize must be called from exactly one place')
|
|
require('sceUserServiceInitialize' not in backend,
|
|
'the keyboard backend must not own the user service')
|
|
require('sceUserServiceGetInitialUser' not in backend,
|
|
'the keyboard backend must not resolve the user itself')
|
|
require('bool ps4KeyboardOpenDevice(int userId)' in backend_header,
|
|
'the keyboard open call must receive the user id from the frontend')
|
|
require('ps4KeyboardOpenDevice(g_padUserId)' in main,
|
|
'the frontend does not pass the shared user id to the keyboard')
|
|
require('if (!ensureUserServiceReady()) {' in main,
|
|
'initializePadInput no longer goes through the shared helper')
|
|
require('g_userServiceReady && !ps4KeyboardIsOpen()' in main,
|
|
'the user service can be terminated while the keyboard is open')
|
|
|
|
# --- 1.0 feature: guided first-run setup ---------------------------------
|
|
require('static bool runFirstRunSetup()' in main, 'first-run setup screen missing')
|
|
require('static bool firstRunSetupNeeded()' in main, 'first-run detection missing')
|
|
require('static unsigned collectSetupRequirements(' in main,
|
|
'first-run requirement scan missing')
|
|
require('SETUP_COMPLETED_MARKER' in main, 'first-run marker missing')
|
|
require('if (g_running && firstRunSetupNeeded()) {' in main,
|
|
'first-run setup is not wired into startup')
|
|
require('contentDirectoryHasFiles' in main, 'games directory is not scanned')
|
|
|
|
# --- 1.0: la interfaz se compone en ram y se anima sola -------------------
|
|
# antes cada pantalla pintaba pixel a pixel en la memoria de video y solo
|
|
# repintaba al pulsar un boton, asi que no habia forma de animar nada
|
|
require('uiFrameBegin()' in main and 'uiFrameSubmit()' in main,
|
|
'falta la capa de composicion de la interfaz')
|
|
require(main.count('uiFrameBegin()') >= 12,
|
|
'alguna pantalla sigue pintando fuera del lienzo de ram')
|
|
_screens = main[main.index('static bool presentSettingsScreen'):]
|
|
require('g_displayBuffers[g_activeDisplayBuffer])' not in _screens,
|
|
'alguna pantalla sigue escribiendo directa en la memoria de video')
|
|
require('static void renderBackgroundInto' in main and
|
|
'g_uiBackgroundCacheReady' in main,
|
|
'el fondo ya no se cachea')
|
|
require('static float uiApproach(' in main and 'UI_TAU_FOCUS' in main,
|
|
'falta el sistema de animacion')
|
|
require('g_uiDeltaSeconds' in main,
|
|
'las animaciones no van por tiempo real')
|
|
require('static void uiDrawMovingFocus(' in main,
|
|
'el recuadro del foco no se desliza')
|
|
require('static void drawUiIcon(' in main and 'enum class UiIcon' in main,
|
|
'faltan los iconos')
|
|
# ningun bucle de menu puede volver a bloquear la entrada con delays
|
|
for pattern in ('SDL_Delay(12);', 'if (dirty)'):
|
|
require(pattern not in main, f'vuelve a haber animacion bloqueante: {pattern}')
|
|
|
|
# --- 1.0: la de la pantalla negra ----------------------------------------
|
|
# uiFrameSubmit se llamaba a si misma. lo hizo un reemplazo automatico que
|
|
# encontro el mismo patron dentro de la funcion que acababa de escribir, y
|
|
# compila igual porque una funcion recursiva es legal. muerte por pila llena
|
|
# antes de pintar el primer frame.
|
|
import re as _re
|
|
_begin = main.count('uiFrameBegin();')
|
|
_submit = main.count('return uiFrameSubmit();')
|
|
require(_begin == _submit,
|
|
f'cada pantalla necesita su pareja: {_begin} uiFrameBegin y {_submit} uiFrameSubmit')
|
|
_body = main[main.index('static bool uiFrameSubmit()') + len('static bool uiFrameSubmit()'):]
|
|
_body = _body[:_body.index('\n}\n')]
|
|
require('submitActiveDisplayBuffer()' in _body,
|
|
'uiFrameSubmit no lanza el flip')
|
|
require('uiFrameSubmit()' not in _body,
|
|
'uiFrameSubmit se llama a si misma otra vez')
|
|
# ninguna funcion puede acabar llamandose a si misma sin condicion
|
|
for _m in _re.finditer(r'^static [^\n(]*?\b(\w+)\(', main, _re.M):
|
|
_name = _m.group(1)
|
|
_i = main.find('{', _m.end())
|
|
if _i < 0 or _i - _m.end() > 400: continue
|
|
_d = 0; _e = -1
|
|
for _j in range(_i, len(main)):
|
|
if main[_j] == '{': _d += 1
|
|
elif main[_j] == '}':
|
|
_d -= 1
|
|
if _d == 0: _e = _j; break
|
|
if _e < 0: continue
|
|
require(not _re.search(r'\breturn ' + _name + r'\(\);', main[_i:_e]),
|
|
f'{_name} se llama a si misma como ultima instruccion')
|
|
|
|
# si el lienzo no cabe en memoria hay que seguir pintando, no quedarse en negro
|
|
require('g_uiDirectMode' in main, 'falta el modo directo de emergencia')
|
|
|
|
# --- 1.0: medicion por tramos --------------------------------------------
|
|
# el tiempo de presentacion era un solo numero que juntaba conversion, volcado
|
|
# y espera de vsync, asi que no se podia saber que frenaba
|
|
for _bucket in ('g_msWaitBuffer', 'g_msConvert', 'g_msSubmit', 'g_msSlack'):
|
|
require(_bucket in main, f'falta el cronometro {_bucket}')
|
|
require('g_lateFrames' in main, 'no se cuentan los frames que llegan tarde')
|
|
require('writePerfSample' in main, 'no se escribe el historico de rendimiento')
|
|
require('logs/perf.log' in main, 'falta el archivo perf.log')
|
|
require('static bool runDiagnosticsScreen()' in main, 'falta la pantalla de diagnostico')
|
|
require('g_tickSlack += nextDeadline - now;' in main,
|
|
'el hueco libre no se mide, y es el dato que dice si hay margen')
|
|
|
|
# --- 1.0: picos y iconos -------------------------------------------------
|
|
require('g_msWorstRun' in main, 'no se guarda el peor frame del segundo')
|
|
require('g_audioCutsPerSecond' in main, 'los cortes de audio deben ser por segundo, no el total')
|
|
require('const int inset = size * 12 / 100;' in main,
|
|
'los iconos deben dibujarse sobre la caja comun')
|
|
require('cortes/s' in main and 'cuts/s' in main,
|
|
'la cola de audio tiene que estar traducida')
|
|
|
|
# --- 1.0: paleta unica ---------------------------------------------------
|
|
# antes habia 119 colores escritos a pelo por todo el archivo y no se podia
|
|
# cambiar el aspecto sin ir uno por uno
|
|
require('struct UiPalette' in main and 'static UiPalette g_ui' in main,
|
|
'falta la paleta')
|
|
require('enum class UiTheme' in main, 'faltan los temas')
|
|
require('g_uiTheme = UiTheme::Grey;' in main, 'el tema por defecto debe ser el gris')
|
|
require('ui_theme=%s' in main, 'el tema no se guarda en el ini')
|
|
_pantallas = main[main.index('static void renderBackgroundInto'):]
|
|
import re as _re2
|
|
_sueltos = _re2.findall(r'encodeDisplayColor\(\s*\d+,\s*\d+,\s*\d+\s*\)', _pantallas)
|
|
require(len(_sueltos) <= 6,
|
|
f'quedan {len(_sueltos)} colores a pelo en las pantallas, deberian salir de la paleta')
|
|
require('blendDisplayColor(*pixel, sheen' in main,
|
|
'la banda de luz debe mezclarse sobre el fondo, con el destino primero')
|
|
# el texto personal del autor no se toca
|
|
require('cabezoneria' in main, 'el texto de Sobre este proyecto no debe borrarse')
|
|
|
|
# --- 1.0: maquetacion comun ----------------------------------------------
|
|
# antes cada pantalla ponia sus coordenadas a mano y todas salian iguales de
|
|
# sosas: titulo arriba, lista centrada y media pantalla vacia a la derecha
|
|
require('namespace ui {' in main and 'constexpr int PANEL_X' in main,
|
|
'falta la rejilla comun')
|
|
for _pieza in ('uiHeader', 'uiRow', 'uiPanel', 'uiFocusRow', 'uiPanelTitle',
|
|
'uiPanelField', 'uiScrollBar', 'uiRoundedRect'):
|
|
require('static ' in main and _pieza + '(' in main, f'falta la pieza {_pieza}')
|
|
# ninguna pantalla puede volver a maquetar a mano, salvo la pausa
|
|
_sinPausa = main.replace(main[main.index('static bool presentPauseScreen'):
|
|
main.index('static bool presentPauseScreen') + 4000], '')
|
|
require(_sinPausa.count('970, FooterButton') == 0,
|
|
'alguna pantalla sigue con el pie en la coordenada vieja')
|
|
require('settingsRowHelp' in main, 'el panel de ajustes necesita sus descripciones')
|
|
# el texto del autor sigue intacto
|
|
require('cabezoneria' in main, 'el texto de Sobre este proyecto no debe borrarse')
|
|
require('Emii_in_Gensokyo' in main, 'falta el contacto del autor')
|
|
|
|
# --- 1.0: la del fondo roto ----------------------------------------------
|
|
# blendDisplayColor(destino, origen, alpha) lleva el destino PRIMERO. yo pasaba
|
|
# el color como destino, asi que en vez de teñir el fondo lo sustituia entero:
|
|
# banda blanca gigante y todo lo demas casi negro
|
|
import re as _re3
|
|
for _m in _re3.finditer(r'blendDisplayColor\(([^,]+),', main):
|
|
_primero = _m.group(1).strip()
|
|
require(_primero.startswith('*pixel') or _primero.startswith('destination') or
|
|
_primero.startswith('row[px]') or _primero.startswith('std::uint32_t'),
|
|
f'blendDisplayColor con el destino mal: primer argumento {_primero}')
|
|
|
|
# dos tipografias, generadas con el script del repo
|
|
require('enum class UiFace' in main, 'falta la separacion de tipografias')
|
|
require('UI_GLYPHS_DISPLAY' in main and 'UI_FONT_ALPHA_DISPLAY' in main,
|
|
'falta la tipografia de titulos')
|
|
require((root / 'tools/build-ui-font.py').exists(),
|
|
'el generador de la fuente debe ir en el repo, o no se puede rehacer')
|
|
require('UiFace::Display' in main, 'los titulos deben usar la cara de display')
|
|
|
|
# --- 1.0: respuesta al pulsar e iconos nuevos ----------------------------
|
|
require('uiTriggerPress' in main, 'falta la respuesta al pulsar')
|
|
require(main.count('uiTriggerPress();') >= 5,
|
|
'la pulsacion tiene que dispararse en todos los menus, no solo en uno')
|
|
require('g_uiPress' in main, 'falta el estado del golpecito')
|
|
# nada de borrar pintando con color 0: eso pinta negro
|
|
require('0x00000000u)' not in main, 'hay un rectangulo de borrado con color 0')
|
|
# los iconos son otros
|
|
require('UI_ICON_ALPHA' in main and 'drawIconBitmap' in main,
|
|
'los iconos deben salir del atlas de Tabler')
|
|
require('playstation-x' in main, 'los botones del pie deben usar los iconos nuevos')
|
|
require((root / 'THIRD-PARTY.md').exists(),
|
|
'Tabler es MIT: hace falta el aviso de licencia en el repo')
|
|
require('Tabler' in (root / 'THIRD-PARTY.md').read_text(),
|
|
'falta la atribucion de Tabler')
|
|
require((root / 'tools/build-ui-icons.py').exists(),
|
|
'el conversor de iconos debe ir en el repo')
|
|
|
|
# --- 1.0: estados de guardado --------------------------------------------
|
|
for _f in ('retro_serialize_size', 'retro_serialize', 'retro_unserialize'):
|
|
require(_f in main, f'falta el enlace con {_f}')
|
|
require('saveStateToSlot' in main and 'loadStateFromSlot' in main,
|
|
'falta el gestor de estados')
|
|
require('static bool runSaveStateMenu' in main, 'falta la pantalla de ranuras')
|
|
require('STATE_MAGIC' in main, 'los archivos de estado deben llevar cabecera')
|
|
# retro_serialize_size hace una grabacion entera por dentro: no puede llamarse
|
|
# mas que en el momento de guardar
|
|
require(main.count('= retro_serialize_size();') == 1,
|
|
'retro_serialize_size solo puede llamarse al guardar: cuesta una grabacion entera')
|
|
# escritura atomica: primero al temporal y luego rename
|
|
require('std::rename(temporary, path)' in main,
|
|
'el estado debe escribirse a un temporal y renombrarse')
|
|
require('g_stateJustLoaded' in main,
|
|
'al cargar un estado hay que volver al juego, no al menu')
|
|
require('Estados de guardado' in main, 'falta la entrada en el menu de pausa')
|
|
|
|
# --- 1.0: recarga, cambio de disco y la fila perdida ---------------------
|
|
_core = (root / 'third_party/NP2kai/sdl/libretro/libretro.c').read_text(errors='replace')
|
|
require('NP2KAI_PS4_RELOAD_FIX' in _core,
|
|
'falta el arreglo de recarga en el core: cerrar un juego y abrir otro cuelga')
|
|
require('firstcall = 1;' in _core,
|
|
'retro_unload_game debe dejar el core listo para la siguiente carga')
|
|
require((root / 'tools/np2kai-reload-fix.patch').exists(),
|
|
'el parche del core debe ir en el repo: NP2kai es un submodulo')
|
|
require((root / 'tools/apply-np2kai-patches.sh').exists(),
|
|
'falta el aplicador compartido de parches del core')
|
|
require('apply-np2kai-patches.sh' in (root / 'build-ps4-core.sh').read_text(),
|
|
'el script del core debe aplicar los parches en un clon limpio')
|
|
|
|
# la fila del menu de pausa se perdia por un 6 escrito a mano
|
|
require('PAUSE_ROW_COUNT' in main, 'el numero de filas de la pausa debe salir de una constante')
|
|
require('row < PAUSE_ROW_COUNT' in main, 'el bucle de la pausa no puede llevar el numero a mano')
|
|
_labels = main[main.index('static const char* pauseRowLabel'):]
|
|
_labels = _labels[:_labels.index('return interfaceIsEnglish()')]
|
|
require(_labels.count('",') + _labels.count('"}') == 14,
|
|
'las etiquetas de la pausa no cuadran con las 7 filas')
|
|
require('Cerrar aplicación' not in main, 'la opcion de cerrar la aplicacion debia quitarse')
|
|
|
|
require('runDiskSwapMenu' in main, 'falta el cambio de disco')
|
|
require('GameSessionResult::SwapDisk' in main, 'el cambio de disco no llega al bucle principal')
|
|
|
|
# --- release: nada residual ------------------------------------------------
|
|
import glob as _glob
|
|
import re as _re5
|
|
_prohibidos = []
|
|
# los .log son artefactos normales de compilacion y estan en .gitignore, asi
|
|
# que no se comprueban aqui: se comprobaba que existieran las reglas
|
|
for _pat in ('*.orig', '*.rej', '*.bak', 'VALIDATION-*.txt',
|
|
'assets/system/np2kai/*.rom', 'assets/system/np2kai/*.ROM',
|
|
'assets/system/np2kai/font.bmp', 'assets/games/*'):
|
|
_prohibidos += _glob.glob(str(root / _pat))
|
|
require(not _prohibidos,
|
|
'quedan archivos residuales: ' + ', '.join(_prohibidos[:4]))
|
|
require(not (root / 'backups').exists(), 'la carpeta backups no debe existir')
|
|
_ignore = (root / '.gitignore').read_text()
|
|
for _regla in ('*.log', '*.orig', 'build/', 'assets/system/np2kai/*', '*.rom'):
|
|
require(_regla in _ignore, f'.gitignore no cubre {_regla}')
|
|
require(not (root / 'patches').exists(), 'la carpeta patches no debe existir')
|
|
require(len(_glob.glob(str(root / 'CHANGELOG-v*.md'))) == 0,
|
|
'los changelogs deben estar consolidados en CHANGELOG.md')
|
|
require((root / 'CHANGELOG.md').exists(), 'falta CHANGELOG.md')
|
|
require((root / 'LICENSE').exists(), 'falta el archivo LICENSE en la raiz')
|
|
require((root / 'build-and-deploy.sh').exists(),
|
|
'falta el script de compilacion portable')
|
|
# nada de rutas de una maquina concreta
|
|
import re as _re4
|
|
for _f in ('build-and-deploy.sh', 'build-ps4-core.sh', 'build-libretro-common-ps4.sh',
|
|
'upload-pkg-goldhen-ftp.sh', 'Makefile'):
|
|
_txt = (root / _f).read_text(errors='replace')
|
|
require('/home/' not in _txt and '/Users/' not in _txt,
|
|
f'{_f} lleva una ruta personal escrita a mano')
|
|
|
|
# creditos completos y visibles desde la aplicacion
|
|
require('InfoDocument::Credits' in main, 'falta la pantalla de creditos')
|
|
for _quien in ('AZO234', 'Neko Project II', 'fmgen', 'SoftFloat', 'OpenOrbis',
|
|
'GoldHEN', 'SDL2', 'Tabler', 'Carlito', 'Poppins'):
|
|
require(_quien in main, f'los creditos no mencionan {_quien}')
|
|
require('Créditos' in main, 'falta la fila de creditos en Ayuda')
|
|
|
|
# nada de desahogos personales en los comentarios
|
|
for _raro in ('me comi', 'menos mal', 'culpa mia', 'ni idea de si'):
|
|
require(_raro not in main, f'comentario demasiado personal: {_raro}')
|
|
|
|
# --- las etiquetas de ajustes tienen que cuadrar en los dos idiomas ---------
|
|
# se me colo una fila solo en el array español: en ingles todo se desplazaba y
|
|
# la ultima fila leia fuera del array, mostrando memoria de al lado como texto
|
|
_paginas = dict(_re5.findall(r'case SettingsPage::(\w+): return (\d+);', main))
|
|
for _pagina, _clave in (('Root','root'), ('Display','display'), ('Audio','audio'),
|
|
('Controls','controls'), ('Emulation','emulation'),
|
|
('Interface','interface'), ('Developer','developer'),
|
|
('Support','support')):
|
|
_esperadas = int(_paginas.get(_pagina, -1))
|
|
for _idioma in ('Es', 'En'):
|
|
_m = _re5.search(r'static const char\* ' + _clave + _idioma + r'\[\] = \{(.*?)\};',
|
|
main, _re5.S)
|
|
require(_m is not None, f'falta el array {_clave}{_idioma}')
|
|
_n = len(_re5.findall(r'"[^"]*"', _m.group(1)))
|
|
require(_n == _esperadas,
|
|
f'{_clave}{_idioma} tiene {_n} etiquetas y la pagina declara {_esperadas}')
|
|
|
|
# --- Versioning and package safety ------------------------------------------
|
|
require('config_version=36\\n' in main, 'el formato de perfiles por juego debe seguir en 36')
|
|
require('config_version=37\\n' in main, 'frontend.ini debe declarar la version 37')
|
|
require('if (loadedConfigVersion < 37)' in main, 'falta la migracion a la version 37')
|
|
require('static bool g_uiAnimationsEnabled = true;' in main,
|
|
'las animaciones deben venir encendidas de serie')
|
|
require('RELEASE_VERSION := 1.0' in makefile, 'source release version is not 1.0')
|
|
require('VERSION := 01.00' in makefile, 'SFO version is not 01.00')
|
|
require('validate-ps4-package-metadata.py' in makefile, 'package metadata preflight missing')
|
|
require('[0-9]{2}\\.[0-9]{2}' in metadata_validator, 'metadata validator does not enforce XX.YY')
|
|
require('ORBIS_PAD_BUTTON_TOUCH_PAD' in main, 'fixed touchpad shortcut missing')
|
|
require('ORBIS_PAD_BUTTON_L3' in main and 'ORBIS_PAD_BUTTON_R3' in main,
|
|
'fixed controller pause chord missing')
|
|
require('keycode == RETROK_F12' in main, 'fixed keyboard pause key missing')
|
|
|
|
if errors:
|
|
print('NP2kai PS4 1.0 validation: FAILED')
|
|
for error in errors:
|
|
print(f'- {error}')
|
|
sys.exit(1)
|
|
|
|
print('NP2kai PS4 1.0 validation: PASS')
|
|
print(f'- Selectable assignments: {len(choices)}')
|
|
print(f'- PC-98 keyboard keys covered: {len(choice_keys)}/{len(core_keys)}')
|
|
print('- Keyboard backend: libSceKeyboard (sysmodule 0x0106, sceKeyboardOpen/ReadState)')
|
|
print('- No libSceIme, no liborbisKeyboard, no build-time downloads')
|
|
print('- Hot-plug: device open retries every 2 seconds; unplug releases held keys')
|
|
print('- Native and SDL key paths share applyExternalKeyEvent()')
|
|
print('- Unified keyboard/controller UI and automatic layout: preserved')
|
|
print('- Game details overlay and global/per-game controls: preserved')
|
|
print('- Interfaz: composicion en ram, fondo cacheado, repintado continuo')
|
|
print('- Animaciones por tiempo real, sin bloquear la entrada')
|
|
print('- uiFrameBegin/uiFrameSubmit emparejados, sin recursion')
|
|
print('- Modo directo de emergencia si no hay memoria para el lienzo')
|
|
print('- Medicion separada: run / espera / conversion / flip / hueco')
|
|
print('- Historico por segundo en logs/perf.log, etiquetado por juego')
|
|
print('- Rejilla comun: lista a la izquierda, panel de detalle a la derecha')
|
|
print('- Dos tipografias: Carlito de cuerpo, Poppins Medium de titulos')
|
|
print('- Paleta unica con cuatro temas, gris por defecto')
|
|
print('- Estados de guardado: 4 ranuras por juego, escritura atomica')
|
|
print('- Recarga del core arreglada; cambio de disco cerrando y abriendo')
|
|
print('- Config format: 36; SFO version: 01.00')
|