FPPS4/gui/game_run.pas

672 lines
12 KiB
Plaintext

unit game_run;
{$mode ObjFPC}{$H+}
interface
uses
windows,
Classes,
SysUtils,
CharStream,
Dialogs,
kern_thr,
md_sleep,
md_pipe,
host_ipc,
host_ipc_interface,
md_host_ipc,
game_info,
game_mount;
type
TGameRunConfig=record
hOutput:THandle;
hError :THandle;
FConfInfo:TConfigInfo;
FGameItem:TGameItem;
FhasParamSfo:Integer;
end;
TGameProcessSimple=class(TGameProcess)
Ftd:p_kthread;
procedure suspend; override;
procedure resume; override;
Destructor Destroy; override;
end;
function run_item(const cfg:TGameRunConfig):TGameProcess;
implementation
uses
errno,
sys_sysinit,
kern_exec,
sys_crt, //<- init writeln redirect
sys_tty,
md_exception, //<- install custom
sys_event,
kern_proc,
md_systm,
md_systm_fork,
md_game_process,
kern_jit,
kern_jit_ctx,
dev_dce,
display_soft,
time,
pm4_me,
vDevice,
//internal libs
ps4_libSceDiscMap,
ps4_libSceSystemService,
ps4_libSceUserService,
ps4_libSceAppContent,
ps4_libSceIpmi,
ps4_libSceMbus,
ps4_libSceDialogs,
ps4_libSceAvSetting,
ps4_libSceNpCommon,
ps4_libSceNpManager,
ps4_libSceNpTrophy,
ps4_libSceNpScoreRanking,
ps4_libSceNpUtility,
ps4_libSceNpTus,
ps4_libSceNpGameIntent,
ps4_libSceNpWebApi,
ps4_libSceNpWebApi2,
ps4_libSceNpSns,
ps4_libSceNpMatching2,
ps4_libSceNpSignaling,
ps4_libSceNpParty,
ps4_libSceRemoteplay,
ps4_libSceScreenShot,
ps4_libSceSaveData,
ps4_libSceAudioOut,
ps4_libSceAudioIn,
ps4_libSceNetCtl,
ps4_libSceGameLiveStreaming,
ps4_libSceVideoRecording,
ps4_libSceIme,
ps4_libSceMove,
ps4_libSceSharePlay,
ps4_libSceShareUtility,
ps4_libScePlayGo,
ps4_libSceAjm,
ps4_libSceCompanionUtil,
ps4_libSceAutoMounterClient,
//internal libs
kern_rtld,
kern_budget,
kern_authinfo,
sys_bootparam,
subr_backtrace;
//
procedure TGameProcessSimple.suspend;
begin
thread_suspend_all(nil);
end;
procedure TGameProcessSimple.resume;
begin
thread_resume_all(nil);
end;
Destructor TGameProcessSimple.Destroy;
begin
if (Ftd<>nil) then
begin
thread_dec_ref(Ftd);
Ftd:=nil;
end;
inherited;
end;
procedure re_init_tty; register;
var
i:Integer;
begin
For i:=0 to High(std_tty) do
begin
//std_tty[i].t_rd_handle:=GetStdHandle(STD_INPUT_HANDLE);
//std_tty[i].t_wr_handle:=t_wr_handle;
//std_tty[i].t_update :=@WakeMainThread;
end;
For i:=0 to High(deci_tty) do
begin
//deci_tty[i].t_rd_handle:=GetStdHandle(STD_INPUT_HANDLE);
//deci_tty[i].t_wr_handle:=t_wr_handle;
//deci_tty[i].t_update :=@WakeMainThread;
end;
//debug_tty.t_wr_handle:=t_wr_handle;
//debug_tty.t_update :=@WakeMainThread;
end;
procedure load_config(ConfInfo:TConfigInfo);
begin
sys_bootparam.set_neo_mode(ConfInfo.BootParamInfo.Neo);
sys_bootparam.p_halt_on_exit :=ConfInfo.BootParamInfo.halt_on_exit;
sys_bootparam.p_print_guest_syscall:=ConfInfo.BootParamInfo.print_guest_syscall;
sys_bootparam.p_print_pmap :=ConfInfo.BootParamInfo.print_pmap;
sys_bootparam.p_print_jit_preload :=ConfInfo.BootParamInfo.print_jit_preload;
sys_bootparam.p_print_gpu_ops :=ConfInfo.BootParamInfo.print_gpu_ops;
sys_bootparam.p_print_gpu_hint :=ConfInfo.BootParamInfo.print_gpu_hint;
//
kern_jit.print_asm :=ConfInfo.JITInfo.print_asm;
kern_jit.debug_info:=ConfInfo.JITInfo.debug_info;
kern_jit_ctx.jit_relative_analize:=ConfInfo.JITInfo.relative_analize;
kern_jit_ctx.jit_memory_guard :=ConfInfo.JITInfo.memory_guard;
//
time.strict_ps4_freq :=ConfInfo.MiscInfo.strict_ps4_freq;
pm4_me.use_renderdoc_capture:=ConfInfo.MiscInfo.renderdoc_capture;
//
vDevice.VulkanDeviceGuid:=Default(TGUID);
TryStringToGUID(ConfInfo.VulkanInfo.device,vDevice.VulkanDeviceGuid);
vDevice.VulkanAppFlags:=t_vulkan_app_flags(ConfInfo.VulkanInfo.app_flags);
//
ps4_libSceSystemService.FSystemName :=ConfInfo.PS4SystemService.SystemName;
ps4_libSceSystemService.FLanguage :=ConfInfo.PS4SystemService.Language;
ps4_libSceSystemService.FDateFormat :=ConfInfo.PS4SystemService.DateFormat;
ps4_libSceSystemService.FTimeFormat :=ConfInfo.PS4SystemService.TimeFormat;
ps4_libSceSystemService.FButtonAssign:=ConfInfo.PS4SystemService.ButtonAssign;
end;
procedure free_params(argv:PPChar);
var
curr:PPChar;
begin
if (argv=nil) then Exit;
curr:=argv;
while (curr^<>nil) do
begin
FreeMem(curr^);
Inc(curr);
end;
FreeMem(argv);
end;
function parse_params(const params:RawByteString;var argv:PPChar):Integer;
var
curr:PChar;
last:PChar;
barg:PChar;
blen:Integer;
argc:Integer;
state:char;
procedure concat_arg(delta:Integer);
var
i:Integer;
begin
if (curr<>last) then
begin
i:=(curr-last);
ReAllocMem(barg,blen+i+1); //zero truncate
Move(last^,barg[blen],i);
blen:=blen+i;
barg[blen]:=#0;
end;
last:=curr+delta;
end;
procedure next_arg;
begin
if (barg<>nil) then
begin
ReAllocMem(argv,SizeOf(Pointer)*(argc+1+1)); //zero truncate
argv[argc]:=barg;
Inc(argc);
argv[argc]:=nil; //truncate
barg:=nil; //reset
blen:=0; //reset
end;
end;
begin
Result:=1;
//init
argc:=0;
argv:=AllocMem(SizeOf(Pointer)*2);
argv[0]:=nil; //truncate
curr:=@params[1];
last:=curr;
barg:=nil;
blen:=0;
state:=#0;
if (curr<>nil) then
while (curr^<>#0) do
begin
case state of
' ':
if (curr^<>' ') then
begin
last:=curr; //update pos
state:=#0;
end;
'\':
begin
last:=curr; //update pos
state:=#0;
//skip
Inc(curr);
Continue;
end;
else;
end;
case curr^ of
' ':
begin
if (state=#0) then
begin
concat_arg(1);
next_arg;
state:=' ';
end;
end;
'''',
'"':
begin
if (state=#0) then
begin
concat_arg(1);
state:=curr^;
end else
if (state=curr^) then
begin
concat_arg(1);
state:=#0;
end;
end;
'\':
begin
concat_arg(1);
state:='\';
end;
else;
end;
Inc(curr);
end;
if (state<>' ') then
begin
concat_arg(0);
next_arg;
end;
Result:=argc;
end;
function get_errno_str(err:Integer):RawByteString;
begin
case err of
EPERM :Result:='Operation not permitted';
ENOENT :Result:='No such file or directory';
EACCES :Result:='Permission denied';
EEXIST :Result:='Directory exists';
ENOTDIR:Result:='Not a directory';
else
Result:=IntToStr(err);
end;
end;
procedure prepare(GameStartupInfo:TGameStartupInfo); SysV_ABI_CDecl;
var
err:Integer;
argv:PPChar;
i,argc:Integer;
Item:TGameItem;
begin
//re_init_tty;
//init_tty:=@re_init_tty;
load_config(GameStartupInfo.FConfInfo);
//init all
sys_init;
if (p_host_ipc<>nil) then
begin
THostIpcConnect(p_host_ipc).thread_new;
end;
//p_cpuid :=CPUID_NEO_MODE;
//p_base_ps4_mode:=0;
//p_neomode :=1;
dev_dce.dce_interface:=display_soft.TDisplayHandleSoft;
Item:=GameStartupInfo.FGameItem;
g_appinfo.mmap_flags:=1; //is_big_app ???
g_appinfo.CUSANAME :=Item.FGameInfo.TitleId;
g_appinfo.hasParamSfo:=GameStartupInfo.hasParamSfo;
//g_appinfo.debug_level:=1;
g_appinfo.titleWorkaround.version:=69;
//budget init
p_proc.p_budget_ptype:=PTYPE_BIG_APP;
p_proc.p_vm_container:=1;
kern_app_state_change(as_start);
kern_app_state_change(as_begin_game_app_mount);
kern_reserve_2mb_page(0,M2MB_DEFAULT);
///
Writeln('Name :',Item.FGameInfo.Name );
Writeln('TitleId :',Item.FGameInfo.TitleId );
Writeln('Version :',Item.FGameInfo.Version );
Writeln('AppVer :',Item.FGameInfo.AppVer );
Writeln('Exec :',Item.FGameInfo.Exec );
Writeln('game :',Item.FMountList.game );
Writeln('firmware:',Item.FMountList.firmware );
Writeln('LocalDir:',GameStartupInfo.LocalDir );
InitMount(GameStartupInfo);
///argv
argv:=nil;
argc:=parse_params(Item.FGameInfo.Exec,argv);
Writeln('main_thread:',HexStr(curkthread));
//
FreeAndNil(GameStartupInfo);
//
Writeln('main_execve->');
For i:=0 to argc-1 do
begin
Writeln(' argv[',i,']:',argv[i]);
end;
Flush(stdout);
if (argv[0]=nil) then
begin
err:=ENOENT;
end else
begin
err:=main_execve(argv[0],argv,nil);
end;
if (err=0) then
begin
//free data
free_params(argv);
//jump to code
main_switch_context;
end else
if (err<>0) then
begin
print_error_td('[execve error]'+#13#10+
' cmd:"'+argv[0]+'"'#13#10+
' err:'+get_errno_str(err)
,False);
end;
//
end;
{
function NtTerminateProcessTrap(ProcessHandle:THANDLE;ExitStatus:DWORD):DWORD; MS_ABI_Default;
begin
Result:=0;
Writeln(stderr,'NtTerminateProcess:0x',HexStr(ExitStatus,8));
print_backtrace(StdErr,Get_pc_addr,get_frame,0);
print_backtrace_td(StdErr);
asm
mov ProcessHandle,%R10
mov ExitStatus ,%EDX
mov $0x2c ,%EAX
syscall
end;
end;
type
t_jmp_rop=packed record
cmd:WORD; //FF 25
ofs:DWORD; //00 00 00 00
adr:QWORD;
end;
Procedure CreateNtTerminateTrap;
var
rop:t_jmp_rop;
adr:Pointer;
num:PTRUINT;
R:Boolean;
begin
rop.cmd:=$25FF;
rop.ofs:=0;
rop.adr:=QWORD(@NtTerminateProcessTrap);
adr:=GetProcAddress(GetModuleHandle('ntdll.dll'),'NtTerminateProcess');
num:=0;
R:=WriteProcessMemory(GetCurrentProcess,adr,@rop,SizeOf(rop),num);
Writeln('CreateNtTerminateTrap:0x',HexStr(adr),' ',R,' ',num);
end;
}
procedure fork_process(data:Pointer;size:QWORD); SysV_ABI_CDecl;
var
td:p_kthread;
r:Integer;
pipefd:THandle;
parent:THandle;
kipc:THostIpcPipeKERN;
mem:TPCharStream;
GameStartupInfo:TGameStartupInfo;
begin
//while not IsDebuggerPresent do sleep(100);
mem:=TPCharStream.Create(data,size);
GameStartupInfo:=TGameStartupInfo.Create(True);
GameStartupInfo.Deserialize(mem);
mem.Free;
//free shared
md_fork_unshare;
parent:=md_pidfd_open(md_getppid);
pipefd:=GameStartupInfo.Pipe;
pipefd:=md_pidfd_getfd(parent,pipefd);
kipc:=THostIpcPipeKERN.Create;
kipc.set_pipe(pipefd);
p_host_ipc :=kipc;
p_host_handler:=THostIpcHandler.Create;
p_host_ipc .FHandler:=p_host_handler;
//CreateNtTerminateTrap;
td:=nil;
r:=kthread_add(@prepare,GameStartupInfo,@td,0,'[main]');
Assert(r=0);
msleep_td(0);
end;
function run_item(const cfg:TGameRunConfig):TGameProcess;
label
_error;
var
r:Integer;
kern2mgui:array[0..1] of THandle;
fork_info:t_fork_proc;
kev:t_kevent;
p_mgui_ipc:THostIpcPipeMGUI;
s_kern_ipc:THostIpcSimpleKERN;
s_mgui_ipc:THostIpcSimpleMGUI;
GameStartupInfo:TGameStartupInfo;
mem:TMemoryStream;
begin
Result:=nil;
r:=0;
GameStartupInfo:=TGameStartupInfo.Create(False);
GameStartupInfo.FConfInfo:=cfg.FConfInfo;
GameStartupInfo.FGameItem:=cfg.FGameItem;
GameStartupInfo.LocalDir :=GetAppConfigDir(False);
GameStartupInfo.hasParamSfo:=cfg.FhasParamSfo;
SetStdHandle(STD_OUTPUT_HANDLE,cfg.hOutput);
SetStdHandle(STD_ERROR_HANDLE ,cfg.hError );
fork_info:=Default(t_fork_proc);
if cfg.FConfInfo.MiscInfo.fork_proc then
begin
Result:=TGameProcessPipe.Create;
Result.g_fork:=True;
with TGameProcessPipe(Result) do
begin
r:=md_pipe2(@kern2mgui,MD_PIPE_ASYNC0 or MD_PIPE_ASYNC1);
if (r<>0) then goto _error;
p_mgui_ipc:=THostIpcPipeMGUI.Create;
p_mgui_ipc.set_pipe(kern2mgui[0]);
g_ipc:=p_mgui_ipc;
FChildpip:=kern2mgui[1];
end;
//
mem:=TMemoryStream.Create;
GameStartupInfo.FPipe:=kern2mgui[1];
GameStartupInfo.Serialize(mem);
FreeAndNil(GameStartupInfo);
fork_info.hInput :=GetStdHandle(STD_INPUT_HANDLE);
fork_info.hOutput:=cfg.hOutput;
fork_info.hError :=cfg.hError;
fork_info.proc:=@fork_process;
fork_info.data:=mem.Memory;
fork_info.size:=mem.Size;
r:=md_fork_process(fork_info);
mem.Free;
end else
begin
Result:=TGameProcessSimple.Create;
Result.g_fork:=False;
with TGameProcessSimple(Result) do
begin
s_kern_ipc:=THostIpcSimpleKERN.Create;
s_mgui_ipc:=THostIpcSimpleMGUI.Create;
s_kern_ipc.FDest:=s_mgui_ipc;
s_mgui_ipc.FDest:=s_kern_ipc;
g_ipc:=s_mgui_ipc;
p_host_ipc :=s_kern_ipc;
p_host_handler:=THostIpcHandler.Create;
p_host_ipc .FHandler:=p_host_handler;
Ftd:=nil;
r:=kthread_add(@prepare,GameStartupInfo,@Ftd,0,'[main]');
fork_info.fork_pid:=GetProcessID;
end;
end;
if (r<>0) then
begin
_error:
ShowMessage('error run process code=0x'+HexStr(r,8));
FreeAndNil(Result);
Exit;
end;
Result.g_proc :=fork_info.hProcess;
Result.g_p_pid:=fork_info.fork_pid;
Result.g_ipc.thread_new;
kev.ident :=fork_info.fork_pid;
kev.filter:=EVFILT_PROC;
kev.flags :=EV_ADD;
kev.fflags:=NOTE_EXIT or NOTE_EXEC;
kev.data :=0;
kev.udata :=nil;
Result.g_ipc.kevent(@kev,1);
end;
end.