mirror of
https://github.com/RPCS3/rpcs3.git
synced 2024-11-25 12:12:50 +01:00
- Implemented MTCRF instruction.
- Implemented sys_spu_thread_set_argument syscall. - Improved Debugger.
This commit is contained in:
parent
6d7d3acb43
commit
fb57bb9c4e
@ -11,7 +11,6 @@ PPCThread::PPCThread(PPCThreadType type)
|
||||
: ThreadBase(true, "PPCThread")
|
||||
, m_type(type)
|
||||
, DisAsmFrame(NULL)
|
||||
, m_arg(0)
|
||||
, m_dec(NULL)
|
||||
, stack_size(0)
|
||||
, stack_addr(0)
|
||||
@ -43,6 +42,7 @@ void PPCThread::Reset()
|
||||
|
||||
m_sync_wait = 0;
|
||||
m_wait_thread_id = -1;
|
||||
memset(m_args, 0, sizeof(u64) * 4);
|
||||
|
||||
SetPc(0);
|
||||
cycle = 0;
|
||||
@ -88,8 +88,6 @@ void PPCThread::CloseStack()
|
||||
void PPCThread::SetId(const u32 id)
|
||||
{
|
||||
m_id = id;
|
||||
ID& thread = Emu.GetIdManager().GetIDData(m_id);
|
||||
thread.m_name = GetName();
|
||||
}
|
||||
|
||||
void PPCThread::SetName(const wxString& name)
|
||||
@ -119,10 +117,20 @@ bool PPCThread::Sync()
|
||||
|
||||
int PPCThread::ThreadStatus()
|
||||
{
|
||||
if(Emu.IsStopped()) return PPCThread_Stopped;
|
||||
if(TestDestroy()) return PPCThread_Break;
|
||||
if(Emu.IsStopped())
|
||||
{
|
||||
return PPCThread_Stopped;
|
||||
}
|
||||
|
||||
if(TestDestroy())
|
||||
{
|
||||
return PPCThread_Break;
|
||||
}
|
||||
|
||||
if(Emu.IsPaused() || Sync())
|
||||
{
|
||||
return PPCThread_Sleeping;
|
||||
}
|
||||
|
||||
return PPCThread_Running;
|
||||
}
|
||||
@ -186,7 +194,7 @@ void PPCThread::SetError(const u32 error)
|
||||
wxArrayString PPCThread::ErrorToString(const u32 error)
|
||||
{
|
||||
wxArrayString earr;
|
||||
earr.Clear();
|
||||
|
||||
if(error == 0) return earr;
|
||||
|
||||
earr.Add("Unknown error");
|
||||
|
@ -27,12 +27,13 @@ protected:
|
||||
wxWindow* DisAsmFrame;
|
||||
u32 m_id;
|
||||
PPCThreadType m_type;
|
||||
u64 m_arg;
|
||||
u64 m_args[4];
|
||||
u64 m_prio;
|
||||
bool m_joinable;
|
||||
bool m_joining;
|
||||
Array<u64> argv_addr;
|
||||
u64 m_offset;
|
||||
u32 m_exit_status;
|
||||
|
||||
public:
|
||||
u64 stack_size;
|
||||
@ -47,14 +48,17 @@ public:
|
||||
virtual u64 GetStackAddr() const { return stack_addr; }
|
||||
virtual u64 GetStackSize() const { return stack_size; }
|
||||
virtual u64 GetFreeStackSize() const=0;
|
||||
void SetArg(const u64 arg) { m_arg = arg; }
|
||||
void SetArg(const uint pos, const u64 arg) { assert(pos < 4); m_args[pos] = arg; }
|
||||
|
||||
|
||||
void SetId(const u32 id);
|
||||
void SetName(const wxString& name);
|
||||
void SetPrio(const u64 prio) { m_prio = prio; }
|
||||
void SetOffset(const u64 offset) { m_offset = offset; }
|
||||
u64 GetOffset() { return m_offset; }
|
||||
void SetExitStatus(const u32 status) { m_exit_status = status; }
|
||||
|
||||
u64 GetOffset() const { return m_offset; }
|
||||
u32 GetExitStatus() const { return m_exit_status; }
|
||||
u64 GetPrio() const { return m_prio; }
|
||||
wxString GetName() const { return m_name; }
|
||||
wxString GetFName() const
|
||||
|
@ -274,9 +274,9 @@ private:
|
||||
{
|
||||
DisAsm_V2("vlogefp", vd, vb);
|
||||
}
|
||||
void VMADDFP(u32 vd, u32 va, u32 vb, u32 vc)
|
||||
void VMADDFP(u32 vd, u32 va, u32 vc, u32 vb)
|
||||
{
|
||||
DisAsm_V4("vmaddfp", vd, va, vb, vc);
|
||||
DisAsm_V4("vmaddfp", vd, va, vc, vb);
|
||||
}
|
||||
void VMAXFP(u32 vd, u32 va, u32 vb)
|
||||
{
|
||||
@ -1166,9 +1166,16 @@ private:
|
||||
{
|
||||
DisAsm_R3_OE_RC("adde", rd, ra, rb, oe, rc);
|
||||
}
|
||||
void MTOCRF(u32 crm, u32 rs)
|
||||
void MTOCRF(u32 l, u32 crm, u32 rs)
|
||||
{
|
||||
DisAsm_INT1_R1("mtocrf", crm, rs);
|
||||
if(l)
|
||||
{
|
||||
DisAsm_INT1_R1("mtocrf", crm, rs);
|
||||
}
|
||||
else
|
||||
{
|
||||
DisAsm_INT1_R1("mtcrf", crm, rs);
|
||||
}
|
||||
}
|
||||
void STDX(u32 rs, u32 ra, u32 rb)
|
||||
{
|
||||
@ -1284,10 +1291,6 @@ private:
|
||||
{
|
||||
DisAsm_V1_R2("lvxl", vd, ra, rb);
|
||||
}
|
||||
void ABS(u32 rd, u32 ra, u32 oe, bool rc)
|
||||
{
|
||||
DisAsm_R2_OE_RC("abs", rd, ra, oe, rc);
|
||||
}
|
||||
void MFTB(u32 rd, u32 spr)
|
||||
{
|
||||
const u32 n = (spr >> 5) | ((spr & 0x1f) << 5);
|
||||
|
@ -125,8 +125,10 @@ namespace PPU_instr
|
||||
*/
|
||||
static CodeField<30> AA;
|
||||
|
||||
static CodeFieldSignedOffset<6, 29, 2> LI(FIELD_BRANCH);
|
||||
|
||||
//
|
||||
static CodeFieldSigned<6, 31> LL(FIELD_BRANCH);
|
||||
static CodeFieldSignedOffset<6, 29, 2> LL(FIELD_BRANCH);
|
||||
/*
|
||||
Link bit.
|
||||
0 Does not update the link register (LR).
|
||||
@ -177,36 +179,7 @@ namespace PPU_instr
|
||||
static CodeFieldSigned<16, 31> D;
|
||||
|
||||
//
|
||||
struct : public CodeFieldSigned<16, 31>
|
||||
{
|
||||
static __forceinline u32 decode(u32 data)
|
||||
{
|
||||
int res = sign<size>((data & mask) >> shift);
|
||||
if(res < 0) return res - 1;
|
||||
return res;
|
||||
}
|
||||
|
||||
static __forceinline void encode(u32& data, u32 value)
|
||||
{
|
||||
if((s32)value < 0)
|
||||
{
|
||||
value++;
|
||||
}
|
||||
|
||||
data &= ~mask;
|
||||
data |= (value << shift) & mask;
|
||||
}
|
||||
|
||||
virtual u32 operator ()(u32 data) const
|
||||
{
|
||||
return decode(data);
|
||||
}
|
||||
|
||||
virtual void operator ()(u32& data, u32 value) const
|
||||
{
|
||||
return encode(data, value);
|
||||
}
|
||||
} static DS;
|
||||
static CodeFieldSignedOffset<16, 29, 2> DS;
|
||||
|
||||
//This immediate field is used to specify a 16-bit signed integer
|
||||
static CodeFieldSigned<16, 31> simm16;
|
||||
@ -269,7 +242,7 @@ namespace PPU_instr
|
||||
bind_instr(main_list, ADDIS, RD, RA, simm16);
|
||||
bind_instr(main_list, BC, BO, BI, BD, AA, LK);
|
||||
bind_instr(main_list, SC, SYS);
|
||||
bind_instr(main_list, B, LL, AA, LK);
|
||||
bind_instr(main_list, B, LI, AA, LK);
|
||||
bind_instr(main_list, RLWIMI, RA, RS, SH, MB, ME, RC);
|
||||
bind_instr(main_list, RLWINM, RA, RS, SH, MB, ME, RC);
|
||||
bind_instr(main_list, RLWNM, RA, RS, RB, MB, ME, RC);
|
||||
@ -302,7 +275,7 @@ namespace PPU_instr
|
||||
bind_instr(main_list, STFD, FRS, RA, D);
|
||||
bind_instr(main_list, STFDU, FRS, RA, D);
|
||||
|
||||
bind_instr(g04_list, VMADDFP, VD, VA, VB, VC);
|
||||
bind_instr(g04_list, VMADDFP, VD, VA, VC, VB);
|
||||
bind_instr(g04_list, VMHADDSHS, VD, VA, VB, VC);
|
||||
bind_instr(g04_list, VMHRADDSHS, VD, VA, VB, VC);
|
||||
bind_instr(g04_list, VMLADDUHM, VD, VA, VB, VC);
|
||||
@ -515,7 +488,7 @@ namespace PPU_instr
|
||||
/*0x087*/bind_instr(g1f_list, STVEBX, VS, RA, RB);
|
||||
/*0x088*/bind_instr(g1f_list, SUBFE, RD, RA, RB, OE, RC);
|
||||
/*0x08a*/bind_instr(g1f_list, ADDE, RD, RA, RB, OE, RC);
|
||||
/*0x090*/bind_instr(g1f_list, MTOCRF, CRM, RS);
|
||||
/*0x090*/bind_instr(g1f_list, MTOCRF, L_11, CRM, RS);
|
||||
/*0x095*/bind_instr(g1f_list, STDX, RS, RA, RB);
|
||||
/*0x096*/bind_instr(g1f_list, STWCX_, RS, RA, RB);
|
||||
/*0x097*/bind_instr(g1f_list, STWX, RS, RA, RB);
|
||||
@ -541,7 +514,6 @@ namespace PPU_instr
|
||||
/*0x156*/bind_instr(g1f_list, DST, RA, RB, STRM, L_6);
|
||||
/*0x157*/bind_instr(g1f_list, LHAX, RD, RA, RB);
|
||||
/*0x167*/bind_instr(g1f_list, LVXL, VD, RA, RB);
|
||||
/*0x168*/bind_instr(g1f_list, ABS, RD, RA, OE, RC);
|
||||
/*0x173*/bind_instr(g1f_list, MFTB, RD, SPR);
|
||||
/*0x176*/bind_instr(g1f_list, DSTST, RA, RB, STRM, L_6);
|
||||
/*0x177*/bind_instr(g1f_list, LHAUX, RD, RA, RB);
|
||||
@ -554,7 +526,7 @@ namespace PPU_instr
|
||||
/*0x1d3*/bind_instr(g1f_list, MTSPR, SPR, RS);
|
||||
/*0x1d6*///DCBI
|
||||
/*0x1dc*/bind_instr(g1f_list, NAND, RA, RS, RB, RC);
|
||||
/*0x1e7*/bind_instr(g1f_list, STVXL, RS, RA, RB);
|
||||
/*0x1e7*/bind_instr(g1f_list, STVXL, VS, RA, RB);
|
||||
/*0x1e9*/bind_instr(g1f_list, DIVD, RD, RA, RB, OE, RC);
|
||||
/*0x1eb*/bind_instr(g1f_list, DIVW, RD, RA, RB, OE, RC);
|
||||
/*0x207*/bind_instr(g1f_list, LVLX, VD, RA, RB);
|
||||
@ -568,9 +540,9 @@ namespace PPU_instr
|
||||
/*0x257*/bind_instr(g1f_list, LFDX, FRD, RA, RB);
|
||||
/*0x277*/bind_instr(g1f_list, LFDUX, FRD, RA, RB);
|
||||
/*0x287*/bind_instr(g1f_list, STVLX, VS, RA, RB);
|
||||
/*0x297*/bind_instr(g1f_list, STFSX, RS, RA, RB);
|
||||
/*0x297*/bind_instr(g1f_list, STFSX, FRS, RA, RB);
|
||||
/*0x2a7*/bind_instr(g1f_list, STVRX, VS, RA, RB);
|
||||
/*0x2d7*/bind_instr(g1f_list, STFDX, RS, RA, RB);
|
||||
/*0x2d7*/bind_instr(g1f_list, STFDX, FRS, RA, RB);
|
||||
/*0x307*/bind_instr(g1f_list, LVLXL, VD, RA, RB);
|
||||
/*0x316*/bind_instr(g1f_list, LHBRX, RD, RA, RB);
|
||||
/*0x318*/bind_instr(g1f_list, SRAW, RA, RS, RB, RC);
|
||||
@ -588,7 +560,7 @@ namespace PPU_instr
|
||||
/*0x3d6*///ICBI
|
||||
/*0x3f6*/bind_instr(g1f_list, DCBZ, RA, RB);
|
||||
|
||||
bind_instr(g3a_list, LD, RD, RA, D);
|
||||
bind_instr(g3a_list, LD, RD, RA, DS);
|
||||
bind_instr(g3a_list, LDU, RD, RA, DS);
|
||||
|
||||
bind_instr(g3b_list, FDIVS, FRD, FRA, FRB, RC);
|
||||
@ -602,7 +574,7 @@ namespace PPU_instr
|
||||
bind_instr(g3b_list, FNMSUBS, FRD, FRA, FRC, FRB, RC);
|
||||
bind_instr(g3b_list, FNMADDS, FRD, FRA, FRC, FRB, RC);
|
||||
|
||||
bind_instr(g3e_list, STD, RS, RA, D);
|
||||
bind_instr(g3e_list, STD, RS, RA, DS);
|
||||
bind_instr(g3e_list, STDU, RS, RA, DS);
|
||||
|
||||
bind_instr(g3f_list, FSEL, FRD, FRA, FRC, FRB, RC);
|
||||
|
@ -97,18 +97,12 @@ private:
|
||||
const u8 bo1 = (bo & 0x08) ? 1 : 0;
|
||||
const u8 bo2 = (bo & 0x04) ? 1 : 0;
|
||||
const u8 bo3 = (bo & 0x02) ? 1 : 0;
|
||||
const u8 bo4 = (bo & 0x01) ? 1 : 0;
|
||||
|
||||
if(!bo2) --CPU.CTR;
|
||||
|
||||
const u8 ctr_ok = bo2 | ((CPU.CTR != 0) ^ bo3);
|
||||
const u8 cond_ok = bo0 | (CPU.IsCR(bi) ^ (~bo1 & 0x1));
|
||||
|
||||
//if(bo1) CPU.SetCR(bi, bo4 ? 1 : 0);
|
||||
//if(bo1) return !bo4;
|
||||
|
||||
//ConLog.Write("bo0: 0x%x, bo1: 0x%x, bo2: 0x%x, bo3: 0x%x", bo0, bo1, bo2, bo3);
|
||||
|
||||
return ctr_ok && cond_ok;
|
||||
}
|
||||
|
||||
@ -805,7 +799,7 @@ private:
|
||||
CPU.VPR[vd]._f[w] = log(CPU.VPR[vb]._f[w]) / log(2.0f);
|
||||
}
|
||||
}
|
||||
void VMADDFP(u32 vd, u32 va, u32 vb, u32 vc)
|
||||
void VMADDFP(u32 vd, u32 va, u32 vc, u32 vb)
|
||||
{
|
||||
for (uint w = 0; w < 4; w++)
|
||||
{
|
||||
@ -2549,24 +2543,38 @@ private:
|
||||
if(rc) CPU.UpdateCR0<s64>(CPU.GPR[rd]);
|
||||
if(oe) UNK("addeo");
|
||||
}
|
||||
void MTOCRF(u32 crm, u32 rs)
|
||||
void MTOCRF(u32 l, u32 crm, u32 rs)
|
||||
{
|
||||
u32 n = 0, count = 0;
|
||||
for(u32 i=0; i<8; ++i)
|
||||
if(l)
|
||||
{
|
||||
if(crm & (1 << i))
|
||||
u32 n = 0, count = 0;
|
||||
for(u32 i=0; i<8; ++i)
|
||||
{
|
||||
n = i;
|
||||
count++;
|
||||
if(crm & (1 << i))
|
||||
{
|
||||
n = i;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if(count == 1)
|
||||
{
|
||||
//CR[4*n : 4*n+3] = RS[32+4*n : 32+4*n+3];
|
||||
CPU.SetCR(n, (CPU.GPR[rs] >> (4*n)) & 0xf);
|
||||
}
|
||||
else
|
||||
CPU.CR.CR = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
for(u32 i=0; i<8; ++i)
|
||||
{
|
||||
if(crm & (1 << i))
|
||||
{
|
||||
CPU.SetCR(i, CPU.GPR[rs] & (0xf << i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(count == 1)
|
||||
{
|
||||
//CR[4*n : 4*n+3] = RS[32+4*n : 32+4*n+3];
|
||||
CPU.SetCR(n, (CPU.GPR[rs] >> (4*n)) & 0xf);
|
||||
}
|
||||
else CPU.CR.CR = 0;
|
||||
}
|
||||
void STDX(u32 rs, u32 ra, u32 rb)
|
||||
{
|
||||
@ -2584,6 +2592,7 @@ private:
|
||||
{
|
||||
Memory.Write32(addr, CPU.GPR[rs]);
|
||||
CPU.SetCR_EQ(0, true);
|
||||
CPU.reserve = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -2732,12 +2741,6 @@ private:
|
||||
{
|
||||
CPU.VPR[vd]._u128 = Memory.Read128((ra ? CPU.GPR[ra] + CPU.GPR[rb] : CPU.GPR[rb]) & ~0xfULL);
|
||||
}
|
||||
void ABS(u32 rd, u32 ra, u32 oe, bool rc)
|
||||
{
|
||||
CPU.GPR[rd] = abs((s64)CPU.GPR[ra]);
|
||||
if(oe) UNK("abso");
|
||||
if(rc) CPU.UpdateCR0<s64>(CPU.GPR[rd]);
|
||||
}
|
||||
void MFTB(u32 rd, u32 spr)
|
||||
{
|
||||
const u32 n = (spr >> 5) | ((spr & 0x1f) << 5);
|
||||
@ -3175,8 +3178,41 @@ private:
|
||||
}
|
||||
void FDIVS(u32 frd, u32 fra, u32 frb, bool rc)
|
||||
{
|
||||
if(CPU.FPR[frb] == 0.0) CPU.SetFPSCRException(FPSCR_ZX);
|
||||
CPU.FPR[frd] = static_cast<float>(CPU.FPR[fra] / CPU.FPR[frb]);
|
||||
if(FPRdouble::IsNaN(CPU.FPR[fra]))
|
||||
{
|
||||
CPU.FPR[frd] = CPU.FPR[fra];
|
||||
}
|
||||
else if(FPRdouble::IsNaN(CPU.FPR[frb]))
|
||||
{
|
||||
CPU.FPR[frd] = CPU.FPR[frb];
|
||||
}
|
||||
else
|
||||
{
|
||||
if(CPU.FPR[frb] == 0.0)
|
||||
{
|
||||
if(CPU.FPR[fra] == 0.0)
|
||||
{
|
||||
CPU.FPSCR.VXZDZ = true;
|
||||
CPU.FPR[frd] = FPR_NAN;
|
||||
}
|
||||
else
|
||||
{
|
||||
CPU.FPR[frd] = (float)(CPU.FPR[fra] / CPU.FPR[frb]);
|
||||
}
|
||||
|
||||
CPU.FPSCR.ZX = true;
|
||||
}
|
||||
else if(FPRdouble::IsINF(CPU.FPR[fra]) && FPRdouble::IsINF(CPU.FPR[frb]))
|
||||
{
|
||||
CPU.FPSCR.VXIDI = true;
|
||||
CPU.FPR[frd] = FPR_NAN;
|
||||
}
|
||||
else
|
||||
{
|
||||
CPU.FPR[frd] = (float)(CPU.FPR[fra] / CPU.FPR[frb]);
|
||||
}
|
||||
}
|
||||
|
||||
CPU.FPSCR.FPRF = PPCdouble(CPU.FPR[frd]).GetType();
|
||||
if(rc) UNK("fdivs.");//CPU.UpdateCR1(CPU.FPR[frd]);
|
||||
}
|
||||
@ -3194,13 +3230,17 @@ private:
|
||||
}
|
||||
void FSQRTS(u32 frd, u32 frb, bool rc)
|
||||
{
|
||||
CPU.FPR[frd] = static_cast<float>(sqrt((float)CPU.FPR[frb]));
|
||||
CPU.FPR[frd] = static_cast<float>(sqrt(CPU.FPR[frb]));
|
||||
CPU.FPSCR.FPRF = PPCdouble(CPU.FPR[frd]).GetType();
|
||||
if(rc) UNK("fsqrts.");//CPU.UpdateCR1(CPU.FPR[frd]);
|
||||
}
|
||||
void FRES(u32 frd, u32 frb, bool rc)
|
||||
{
|
||||
if(CPU.FPR[frb] == 0.0) CPU.SetFPSCRException(FPSCR_ZX);
|
||||
CPU.FPR[frd] = static_cast<float>(1.0f/CPU.FPR[frb]);
|
||||
CPU.FPSCR.FPRF = PPCdouble(CPU.FPR[frd]).GetType();
|
||||
CPU.FPSCR.FI = 0;
|
||||
CPU.FPSCR.FR = 0;
|
||||
if(rc) UNK("fres.");//CPU.UpdateCR1(CPU.FPR[frd]);
|
||||
}
|
||||
void FMULS(u32 frd, u32 fra, u32 frc, bool rc)
|
||||
|
@ -318,7 +318,6 @@ namespace PPU_opcodes
|
||||
DST = 0x156, //Data Stream Touch
|
||||
LHAX = 0x157,
|
||||
LVXL = 0x167, //Load Vector Indexed Last
|
||||
ABS = 0x168,
|
||||
MFTB = 0x173,
|
||||
DSTST = 0x176, //Data Stream Touch for Store
|
||||
LHAUX = 0x177,
|
||||
@ -436,8 +435,8 @@ public:
|
||||
|
||||
static u64 branchTarget(const u64 pc, const u64 imm)
|
||||
{
|
||||
return pc + (imm & ~0x3);
|
||||
}
|
||||
return pc + (imm & ~0x3ULL);
|
||||
}
|
||||
|
||||
virtual void NULL_OP() = 0;
|
||||
virtual void NOP() = 0;
|
||||
@ -498,7 +497,7 @@ public:
|
||||
virtual void VCTUXS(u32 vd, u32 uimm5, u32 vb) = 0;
|
||||
virtual void VEXPTEFP(u32 vd, u32 vb) = 0;
|
||||
virtual void VLOGEFP(u32 vd, u32 vb) = 0;
|
||||
virtual void VMADDFP(u32 vd, u32 va, u32 vb, u32 vc) = 0;
|
||||
virtual void VMADDFP(u32 vd, u32 va, u32 vc, u32 vb) = 0;
|
||||
virtual void VMAXFP(u32 vd, u32 va, u32 vb) = 0;
|
||||
virtual void VMAXSB(u32 vd, u32 va, u32 vb) = 0;
|
||||
virtual void VMAXSH(u32 vd, u32 va, u32 vb) = 0;
|
||||
@ -675,7 +674,7 @@ public:
|
||||
virtual void STVEBX(u32 vs, u32 ra, u32 rb) = 0;
|
||||
virtual void SUBFE(u32 rd, u32 ra, u32 rb, u32 oe, bool rc) = 0;
|
||||
virtual void ADDE(u32 rd, u32 ra, u32 rb, u32 oe, bool rc) = 0;
|
||||
virtual void MTOCRF(u32 crm, u32 rs) = 0;
|
||||
virtual void MTOCRF(u32 l, u32 crm, u32 rs) = 0;
|
||||
virtual void STDX(u32 rs, u32 ra, u32 rb) = 0;
|
||||
virtual void STWCX_(u32 rs, u32 ra, u32 rb) = 0;
|
||||
virtual void STWX(u32 rs, u32 ra, u32 rb) = 0;
|
||||
@ -701,7 +700,6 @@ public:
|
||||
virtual void DST(u32 ra, u32 rb, u32 strm, u32 t) = 0;
|
||||
virtual void LHAX(u32 rd, u32 ra, u32 rb) = 0;
|
||||
virtual void LVXL(u32 vd, u32 ra, u32 rb) = 0;
|
||||
virtual void ABS(u32 rd, u32 ra, u32 oe, bool rc) = 0;
|
||||
virtual void MFTB(u32 rd, u32 spr) = 0;
|
||||
virtual void DSTST(u32 ra, u32 rb, u32 strm, u32 t) = 0;
|
||||
virtual void LHAUX(u32 rd, u32 ra, u32 rb) = 0;
|
||||
|
@ -68,19 +68,6 @@ void PPUThread::InitRegs()
|
||||
|
||||
SetPc(pc);
|
||||
|
||||
u64 argc = m_arg;
|
||||
u64 argv = 0;
|
||||
|
||||
if(argv_addr.GetCount())
|
||||
{
|
||||
argc = argv_addr.GetCount();
|
||||
stack_point -= 0xc + 4 * argc;
|
||||
argv = stack_point;
|
||||
|
||||
mem64_t argv_list(argv);
|
||||
for(int i=0; i<argc; ++i) argv_list += argv_addr[i];
|
||||
}
|
||||
|
||||
const s32 thread_num = Emu.GetCPU().GetThreadNumById(!IsSPU(), GetId());
|
||||
|
||||
if(thread_num < 0)
|
||||
@ -106,22 +93,41 @@ void PPUThread::InitRegs()
|
||||
GPR[1] = stack_point;
|
||||
GPR[2] = rtoc;
|
||||
|
||||
if(argc)
|
||||
for(int i=4; i<32; ++i)
|
||||
{
|
||||
if(i != 6)
|
||||
GPR[i] = (i+1) * 0x10000;
|
||||
}
|
||||
|
||||
if(argv_addr.GetCount())
|
||||
{
|
||||
u64 argc = argv_addr.GetCount();
|
||||
stack_point -= 0xc + 4 * argc;
|
||||
u64 argv = stack_point;
|
||||
|
||||
mem64_t argv_list(argv);
|
||||
for(int i=0; i<argc; ++i) argv_list += argv_addr[i];
|
||||
|
||||
GPR[3] = argc;
|
||||
GPR[4] = argv;
|
||||
GPR[5] = argv ? argv + 0xc + 4 * argc : 0; //unk
|
||||
}
|
||||
else
|
||||
{
|
||||
GPR[3] = m_arg;
|
||||
GPR[3] = m_args[0];
|
||||
GPR[4] = m_args[1];
|
||||
GPR[5] = m_args[2];
|
||||
GPR[6] = m_args[3];
|
||||
}
|
||||
|
||||
u32 prx_mem = Memory.PRXMem.Alloc(0x10000);
|
||||
Memory.Write64(prx_mem, 0xDEADBEEFABADCAFE);
|
||||
|
||||
GPR[0] = pc;
|
||||
GPR[8] = entry;
|
||||
GPR[11] = 0x80;
|
||||
GPR[12] = Emu.GetMallocPageSize();
|
||||
GPR[13] = Memory.PRXMem.Alloc(0x10000) + 0x7060 - 8;
|
||||
GPR[13] = prx_mem + 0x7060;
|
||||
GPR[28] = GPR[4];
|
||||
GPR[29] = GPR[3];
|
||||
GPR[31] = GPR[5];
|
||||
@ -172,15 +178,30 @@ bool dump_enable = false;
|
||||
|
||||
void PPUThread::DoCode(const s32 code)
|
||||
{
|
||||
static bool is_last_enabled = false;
|
||||
|
||||
if(dump_enable)
|
||||
{
|
||||
static wxFile f("dump.txt", wxFile::write);
|
||||
static PPU_DisAsm disasm(*this, DumpMode);
|
||||
static PPU_Decoder decoder(disasm);
|
||||
|
||||
if(!is_last_enabled)
|
||||
{
|
||||
f.Write(RegsToString() + "\n");
|
||||
}
|
||||
|
||||
disasm.dump_pc = PC;
|
||||
decoder.Decode(code);
|
||||
f.Write(disasm.last_opcode);
|
||||
|
||||
is_last_enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
is_last_enabled = false;
|
||||
}
|
||||
|
||||
|
||||
if(++cycle > 220)
|
||||
{
|
||||
@ -193,7 +214,7 @@ void PPUThread::DoCode(const s32 code)
|
||||
|
||||
bool FPRdouble::IsINF(PPCdouble d)
|
||||
{
|
||||
return wxFinite(d) ? 1 : 0;
|
||||
return d.GetType() == FPR_INF;
|
||||
}
|
||||
|
||||
bool FPRdouble::IsNaN(PPCdouble d)
|
||||
|
@ -18,6 +18,18 @@ enum
|
||||
CR_SO = 0x1,
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
PPU_THREAD_STATUS_IDLE = (1 << 0),
|
||||
PPU_THREAD_STATUS_RUNNABLE = (1 << 1),
|
||||
PPU_THREAD_STATUS_ONPROC = (1 << 2),
|
||||
PPU_THREAD_STATUS_SLEEP = (1 << 3),
|
||||
PPU_THREAD_STATUS_STOP = (1 << 4),
|
||||
PPU_THREAD_STATUS_ZOMBIE = (1 << 5),
|
||||
PPU_THREAD_STATUS_DELETED = (1 << 6),
|
||||
PPU_THREAD_STATUS_UNKNOWN = (1 << 7),
|
||||
};
|
||||
|
||||
enum FPSCR_EXP
|
||||
{
|
||||
FPSCR_FX = 0x80000000,
|
||||
@ -302,6 +314,9 @@ enum FPRType
|
||||
FPR_ND = 0x18,
|
||||
};
|
||||
|
||||
static const u64 FPR_NAN_I = 0x7FF8000000000000ULL;
|
||||
static const double& FPR_NAN = (double&)FPR_NAN_I;
|
||||
|
||||
struct PPCdouble
|
||||
{
|
||||
union
|
||||
@ -707,7 +722,7 @@ public:
|
||||
void SetFPSCR_FI(const u32 val)
|
||||
{
|
||||
if(val) SetFPSCRException(FPSCR_XX);
|
||||
FPSCR.FI = val;
|
||||
FPSCR.FI = val;
|
||||
}
|
||||
|
||||
virtual wxString RegsToString()
|
||||
|
@ -30,7 +30,11 @@ void SPUThread::DoReset()
|
||||
|
||||
void SPUThread::InitRegs()
|
||||
{
|
||||
GPR[1]._u64[0] = stack_point;
|
||||
//GPR[1]._u64[0] = stack_point;
|
||||
GPR[3]._u64[1] = m_args[0];
|
||||
GPR[4]._u64[1] = m_args[1];
|
||||
GPR[5]._u64[1] = m_args[2];
|
||||
GPR[6]._u64[1] = m_args[3];
|
||||
}
|
||||
|
||||
u64 SPUThread::GetFreeStackSize() const
|
||||
|
@ -461,7 +461,7 @@ void GLGSRender::InitVertexData()
|
||||
void GLGSRender::DoCmd(const u32 fcmd, const u32 cmd, mem32_t& args, const u32 count)
|
||||
{
|
||||
#if CMD_DEBUG
|
||||
wxString debug = getMethodName(cmd);
|
||||
wxString debug = GetMethodName(cmd);
|
||||
debug += "(";
|
||||
for(u32 i=0; i<count; ++i) debug += (i ? ", " : "") + wxString::Format("0x%x", args[i]);
|
||||
debug += ")";
|
||||
@ -757,7 +757,7 @@ void GLGSRender::DoCmd(const u32 fcmd, const u32 cmd, mem32_t& args, const u32 c
|
||||
m_indexed_array.m_data.InsertRoomEnd(4);
|
||||
index = Memory.Read32(m_indexed_array.m_addr + i * 4);
|
||||
*(u32*)&m_indexed_array.m_data[pos] = index;
|
||||
ConLog.Warning("index 4: %d", *(u32*)&m_indexed_array.m_data[pos]);
|
||||
//ConLog.Warning("index 4: %d", *(u32*)&m_indexed_array.m_data[pos]);
|
||||
}
|
||||
break;
|
||||
|
||||
@ -766,8 +766,8 @@ void GLGSRender::DoCmd(const u32 fcmd, const u32 cmd, mem32_t& args, const u32 c
|
||||
int pos = m_indexed_array.m_data.GetCount();
|
||||
m_indexed_array.m_data.InsertRoomEnd(2);
|
||||
index = Memory.Read16(m_indexed_array.m_addr + i * 2);
|
||||
ConLog.Warning("index 2: %d", index);
|
||||
*(u32*)&m_indexed_array.m_data[pos] = index;
|
||||
//ConLog.Warning("index 2: %d", index);
|
||||
*(u16*)&m_indexed_array.m_data[pos] = index;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -1294,13 +1294,13 @@ void GLGSRender::ExecCMD()
|
||||
case 0:
|
||||
glDrawElements(m_draw_mode, m_indexed_array.m_count, GL_UNSIGNED_INT, nullptr);
|
||||
checkForGlError("glDrawElements #4");
|
||||
for(uint i=0; i<m_indexed_array.m_count; i+=4) log.Write(wxString::Format("index 4: %d\n", (u32&)m_indexed_array.m_data[i]));
|
||||
//for(uint i=0; i<m_indexed_array.m_count; i+=4) log.Write(wxString::Format("index 4: %d\n", (u32&)m_indexed_array.m_data[i]));
|
||||
break;
|
||||
|
||||
case 1:
|
||||
glDrawElements(m_draw_mode, m_indexed_array.m_count, GL_UNSIGNED_SHORT, nullptr);
|
||||
checkForGlError("glDrawElements #2");
|
||||
for(uint i=0; i<m_indexed_array.m_count; i+=2) log.Write(wxString::Format("index 2: %d\n", (u16&)m_indexed_array.m_data[i]));
|
||||
//for(uint i=0; i<m_indexed_array.m_count; i+=2) log.Write(wxString::Format("index 2: %d\n", (u16&)m_indexed_array.m_data[i]));
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -1310,7 +1310,7 @@ void GLGSRender::ExecCMD()
|
||||
|
||||
DisableVertexData();
|
||||
m_indexed_array.Reset();
|
||||
Emu.Pause();
|
||||
//Emu.Pause();
|
||||
}
|
||||
|
||||
if(m_draw_array_count)
|
||||
|
@ -39,7 +39,7 @@ static func_caller* sc_table[1024] =
|
||||
null_func, bind_func(sys_spu_image_open), null_func, null_func, null_func, //159
|
||||
bind_func(sys_raw_spu_create), null_func, null_func, null_func, null_func, //164
|
||||
null_func, null_func, null_func, null_func, bind_func(sys_spu_initialize), //169
|
||||
bind_func(sys_spu_thread_group_create), null_func, bind_func(sys_spu_thread_initialize), bind_func(sys_spu_thread_group_start), null_func, //174
|
||||
bind_func(sys_spu_thread_group_create), bind_func(sys_spu_thread_set_argument), bind_func(sys_spu_thread_initialize), bind_func(sys_spu_thread_group_start), null_func, //174
|
||||
null_func, null_func, null_func, null_func, null_func, //179
|
||||
null_func, bind_func(sys_spu_thread_write_ls), bind_func(sys_spu_thread_read_ls), null_func, null_func, //184
|
||||
null_func, null_func, null_func, null_func, null_func, //189
|
||||
@ -260,6 +260,7 @@ void SysCalls::DoSyscall(u32 code)
|
||||
|
||||
case 999:
|
||||
dump_enable = !dump_enable;
|
||||
Emu.Pause();
|
||||
ConLog.Warning("Dump %s", dump_enable ? "enabled" : "disabled");
|
||||
return;
|
||||
|
||||
|
@ -251,6 +251,7 @@ extern int sys_heap_malloc(const u32 heap_addr, const u32 size);
|
||||
//sys_spu
|
||||
extern int sys_spu_image_open(u32 img_addr, u32 path_addr);
|
||||
extern int sys_spu_thread_initialize(u32 thread_addr, u32 group, u32 spu_num, u32 img_addr, u32 attr_addr, u32 arg_addr);
|
||||
extern int sys_spu_thread_set_argument(u32 id, u32 arg_addr);
|
||||
extern int sys_spu_thread_group_start(u32 id);
|
||||
extern int sys_spu_thread_group_create(u64 id_addr, u32 num, int prio, u64 attr_addr);
|
||||
extern int sys_spu_thread_create(u64 thread_id_addr, u64 entry_addr, u64 arg, int prio, u32 stacksize, u64 flags, u64 threadname_addr);
|
||||
|
@ -7,6 +7,14 @@ extern Module cellGcmSys;
|
||||
CellGcmConfig current_config;
|
||||
CellGcmContextData current_context;
|
||||
gcmInfo gcm_info;
|
||||
struct gcm_offset
|
||||
{
|
||||
u16 ea;
|
||||
u16 offset;
|
||||
};
|
||||
|
||||
u32 map_offset_addr = 0;
|
||||
u32 map_offset_pos = 0;
|
||||
|
||||
int cellGcmMapMainMemory(u32 address, u32 size, u32 offset_addr)
|
||||
{
|
||||
@ -24,6 +32,8 @@ int cellGcmInit(u32 context_addr, u32 cmdSize, u32 ioSize, u32 ioAddress)
|
||||
const u32 local_size = 0xf900000; //TODO
|
||||
const u32 local_addr = Memory.RSXFBMem.GetStartAddr();
|
||||
|
||||
map_offset_addr = 0;
|
||||
map_offset_pos = 0;
|
||||
current_config.ioSize = re32(ioSize);
|
||||
current_config.ioAddress = re32(ioAddress);
|
||||
current_config.localSize = re32(local_size);
|
||||
@ -91,10 +101,19 @@ int cellGcmAddressToOffset(u32 address, u32 offset_addr)
|
||||
cellGcmSys.Log("cellGcmAddressToOffset(address=0x%x,offset_addr=0x%x)", address, offset_addr);
|
||||
if(!Memory.IsGoodAddr(offset_addr, sizeof(u32))) return CELL_EFAULT;
|
||||
|
||||
Memory.Write32(offset_addr,
|
||||
Memory.RSXFBMem.IsInMyRange(address)
|
||||
? address - Memory.RSXFBMem.GetStartAddr()
|
||||
: address - re(current_context.begin));
|
||||
if(!map_offset_addr)
|
||||
{
|
||||
map_offset_addr = Memory.Alloc(4*50, 4);
|
||||
}
|
||||
|
||||
u32 sa = Memory.RSXFBMem.IsInMyRange(address) ? Memory.RSXFBMem.GetStartAddr() : re(current_context.begin);
|
||||
u16 ea = (sa + address - sa) >> 16;
|
||||
u32 offset = address - sa;
|
||||
//Memory.Write16(map_offset_addr + map_offset_pos + 0, ea);
|
||||
//Memory.Write16(map_offset_addr + map_offset_pos + 2, offset);
|
||||
//map_offset_pos += 4;
|
||||
|
||||
Memory.Write32(offset_addr, offset);
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
@ -129,7 +129,7 @@ int sys_ppu_thread_create(u32 thread_id_addr, u32 entry, u32 arg, int prio, u32
|
||||
|
||||
Memory.Write32(thread_id_addr, new_thread.GetId());
|
||||
new_thread.SetEntry(entry);
|
||||
new_thread.SetArg(arg);
|
||||
new_thread.SetArg(0, arg);
|
||||
new_thread.SetPrio(prio);
|
||||
new_thread.stack_size = stacksize;
|
||||
//new_thread.flags = flags;
|
||||
|
@ -36,14 +36,16 @@ struct sys_spu_image
|
||||
int nsegs;
|
||||
};
|
||||
|
||||
static const u32 g_spu_group_thr_count = 255;
|
||||
|
||||
struct SpuGroupInfo
|
||||
{
|
||||
PPCThread* threads[10];
|
||||
PPCThread* threads[g_spu_group_thr_count];
|
||||
sys_spu_thread_group_attribute& attr;
|
||||
|
||||
SpuGroupInfo(sys_spu_thread_group_attribute& attr) : attr(attr)
|
||||
{
|
||||
memset(threads, 0, sizeof(PPCThread*) * 10);
|
||||
memset(threads, 0, sizeof(PPCThread*) * g_spu_group_thr_count);
|
||||
}
|
||||
};
|
||||
|
||||
@ -74,7 +76,7 @@ int sys_spu_image_open(u32 img_addr, u32 path_addr)
|
||||
|
||||
if(!stream || !stream->IsOpened())
|
||||
{
|
||||
sc_spu.Error("'%s' not found!", path);
|
||||
sc_spu.Error("sys_spu_image_open error: '%s' not found!", path);
|
||||
delete stream;
|
||||
|
||||
return CELL_ENOENT;
|
||||
@ -83,7 +85,7 @@ int sys_spu_image_open(u32 img_addr, u32 path_addr)
|
||||
u32 entry = LoadSpuImage(*stream);
|
||||
delete stream;
|
||||
|
||||
sys_spu_image& ret = (sys_spu_image&)Memory[img_addr];
|
||||
auto& ret = (sys_spu_image&)Memory[img_addr];
|
||||
re(ret.type, 1);
|
||||
re(ret.entry_point, entry);
|
||||
re(ret.segs_addr, 0x0);
|
||||
@ -103,6 +105,8 @@ int sys_spu_thread_initialize(u32 thread_addr, u32 group, u32 spu_num, u32 img_a
|
||||
return CELL_ESRCH;
|
||||
}
|
||||
|
||||
SpuGroupInfo& group_info = *(SpuGroupInfo*)Emu.GetIdManager().GetIDData(group).m_data;
|
||||
|
||||
if(
|
||||
!Memory.IsGoodAddr(img_addr, sizeof(sys_spu_image)) ||
|
||||
!Memory.IsGoodAddr(attr_addr, sizeof(sys_spu_thread_attribute)) ||
|
||||
@ -111,15 +115,25 @@ int sys_spu_thread_initialize(u32 thread_addr, u32 group, u32 spu_num, u32 img_a
|
||||
return CELL_EFAULT;
|
||||
}
|
||||
|
||||
sys_spu_image& img = (sys_spu_image&)Memory[img_addr];
|
||||
sys_spu_thread_attribute& attr = (sys_spu_thread_attribute&)Memory[attr_addr];
|
||||
sys_spu_thread_argument& arg = (sys_spu_thread_argument&)Memory[arg_addr];
|
||||
auto& img = (sys_spu_image&)Memory[img_addr];
|
||||
auto& attr = (sys_spu_thread_attribute&)Memory[attr_addr];
|
||||
auto& arg = (sys_spu_thread_argument&)Memory[arg_addr];
|
||||
|
||||
if(!Memory.IsGoodAddr(re(attr.name_addr), re(attr.name_len)))
|
||||
{
|
||||
return CELL_EFAULT;
|
||||
}
|
||||
|
||||
if(spu_num >= g_spu_group_thr_count)
|
||||
{
|
||||
return CELL_EINVAL;
|
||||
}
|
||||
|
||||
if(group_info.threads[spu_num])
|
||||
{
|
||||
return CELL_EBUSY;
|
||||
}
|
||||
|
||||
u32 entry = re(img.entry_point);
|
||||
wxString name = Memory.ReadString(re(attr.name_addr), re(attr.name_len));
|
||||
u64 a1 = re(arg.arg1);
|
||||
@ -140,21 +154,43 @@ int sys_spu_thread_initialize(u32 thread_addr, u32 group, u32 spu_num, u32 img_a
|
||||
new_thread.SetOffset(g_spu_offset);
|
||||
new_thread.SetEntry(entry - g_spu_offset);
|
||||
new_thread.SetName(name);
|
||||
SPU_GPR_hdr* GPR = ((SPUThread&)new_thread).GPR;
|
||||
new_thread.Run();
|
||||
new_thread.Pause();
|
||||
GPR[3]._u64[1] = a1;
|
||||
GPR[4]._u64[1] = a2;
|
||||
GPR[5]._u64[1] = a3;
|
||||
GPR[6]._u64[1] = a4;
|
||||
new_thread.SetArg(0, a1);
|
||||
new_thread.SetArg(1, a2);
|
||||
new_thread.SetArg(2, a3);
|
||||
new_thread.SetArg(3, a4);
|
||||
|
||||
ID& id = Emu.GetIdManager().GetIDData(group);
|
||||
SpuGroupInfo& group_info = *(SpuGroupInfo*)id.m_data;
|
||||
group_info.threads[spu_num] = &new_thread;
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
||||
//166
|
||||
int sys_spu_thread_set_argument(u32 id, u32 arg_addr)
|
||||
{
|
||||
sc_spu.Warning("sys_spu_thread_set_argument(id=0x%x, arg_addr=0x%x)", id, arg_addr);
|
||||
PPCThread* thr = Emu.GetCPU().GetThread(id);
|
||||
|
||||
if(!thr || !thr->IsSPU())
|
||||
{
|
||||
return CELL_ESRCH;
|
||||
}
|
||||
|
||||
if(!Memory.IsGoodAddr(arg_addr, sizeof(sys_spu_thread_argument)))
|
||||
{
|
||||
return CELL_EFAULT;
|
||||
}
|
||||
|
||||
auto& arg = (sys_spu_thread_argument&)Memory[arg_addr];
|
||||
thr->SetArg(0, re(arg.arg1));
|
||||
thr->SetArg(1, re(arg.arg2));
|
||||
thr->SetArg(2, re(arg.arg3));
|
||||
thr->SetArg(3, re(arg.arg4));
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
||||
//173
|
||||
int sys_spu_thread_group_start(u32 id)
|
||||
{
|
||||
@ -169,7 +205,7 @@ int sys_spu_thread_group_start(u32 id)
|
||||
SpuGroupInfo& group_info = *(SpuGroupInfo*)id_data.m_data;
|
||||
|
||||
Emu.Pause();
|
||||
for(int i=0; i<10; i++)
|
||||
for(int i=0; i<g_spu_group_thr_count; i++)
|
||||
{
|
||||
if(group_info.threads[i])
|
||||
{
|
||||
@ -211,6 +247,21 @@ int sys_spu_thread_create(u64 thread_id_addr, u64 entry_addr, u64 arg,
|
||||
return CELL_OK;
|
||||
}
|
||||
|
||||
int sys_spu_thread_connect_event(u32 id, u32 eq, u32 et, u8 spup)
|
||||
{
|
||||
if(!Emu.GetIdManager().CheckID(id))
|
||||
{
|
||||
return CELL_ESRCH;
|
||||
}
|
||||
|
||||
if(spup > 63)
|
||||
{
|
||||
return CELL_EINVAL;
|
||||
}
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
||||
//160
|
||||
int sys_raw_spu_create(u32 id_addr, u32 attr_addr)
|
||||
{
|
||||
@ -329,7 +380,7 @@ int sys_spu_thread_group_connect_event_all_threads(u32 id, u32 eq, u64 req, u32
|
||||
SpuGroupInfo* group = (SpuGroupInfo*)Emu.GetIdManager().GetIDData(id).m_data;
|
||||
EventQueue* equeue = (EventQueue*)Emu.GetIdManager().GetIDData(eq).m_data;
|
||||
|
||||
for(int i=0; i<10; ++i)
|
||||
for(int i=0; i<g_spu_group_thr_count; ++i)
|
||||
{
|
||||
if(group->threads[i])
|
||||
{
|
||||
|
@ -85,8 +85,6 @@ void Emulator::Load()
|
||||
Memory.Init();
|
||||
GetInfo().Reset();
|
||||
|
||||
Memory.Write64(Memory.PRXMem.Alloc(8), 0xDEADBEEFABADCAFE);
|
||||
|
||||
bool is_error;
|
||||
vfsLocalFile f(m_path);
|
||||
Loader l(f);
|
||||
@ -127,29 +125,27 @@ void Emulator::Load()
|
||||
else
|
||||
{
|
||||
thread.SetEntry(l.GetEntry());
|
||||
Memory.StackMem.Alloc(0x1000);
|
||||
thread.InitStack();
|
||||
thread.AddArgv(m_path);
|
||||
//thread.AddArgv("-emu");
|
||||
|
||||
m_rsx_callback = Memory.MainMem.Alloc(4 * 4) + 4;
|
||||
Memory.Write32(m_rsx_callback - 4, m_rsx_callback);
|
||||
|
||||
mem32_t callback_data(m_rsx_callback);
|
||||
callback_data += ADDI(11, 0, 0x3ff);
|
||||
callback_data += SC(2);
|
||||
callback_data += BCLR(0x10 | 0x04, 0, 0, 0);
|
||||
|
||||
m_ppu_thr_exit = Memory.MainMem.Alloc(4 * 3);
|
||||
|
||||
mem32_t ppu_thr_exit_data(m_ppu_thr_exit);
|
||||
ppu_thr_exit_data += ADDI(11, 0, 41);
|
||||
ppu_thr_exit_data += SC(2);
|
||||
ppu_thr_exit_data += BCLR(0x10 | 0x04, 0, 0, 0);
|
||||
}
|
||||
|
||||
thread.SetArg(thread.GetId());
|
||||
Memory.StackMem.Alloc(0x1000);
|
||||
thread.InitStack();
|
||||
thread.AddArgv(m_path);
|
||||
//thread.AddArgv("-emu");
|
||||
|
||||
m_rsx_callback = Memory.MainMem.Alloc(4 * 4) + 4;
|
||||
Memory.Write32(m_rsx_callback - 4, m_rsx_callback);
|
||||
|
||||
mem32_t callback_data(m_rsx_callback);
|
||||
callback_data += ADDI(11, 0, 0x3ff);
|
||||
callback_data += SC(2);
|
||||
callback_data += BCLR(0x10 | 0x04, 0, 0, 0);
|
||||
|
||||
m_ppu_thr_exit = Memory.MainMem.Alloc(4 * 3);
|
||||
|
||||
mem32_t ppu_thr_exit_data(m_ppu_thr_exit);
|
||||
ppu_thr_exit_data += ADDI(11, 0, 41);
|
||||
ppu_thr_exit_data += SC(2);
|
||||
ppu_thr_exit_data += BCLR(0x10 | 0x04, 0, 0, 0);
|
||||
|
||||
thread.Run();
|
||||
|
||||
wxCriticalSectionLocker lock(m_cs_status);
|
||||
|
@ -99,16 +99,9 @@ DebuggerPanel::DebuggerPanel(wxWindow* parent) : wxPanel(parent, wxID_ANY, wxDef
|
||||
{
|
||||
m_aui_mgr.SetManagedWindow(this);
|
||||
|
||||
m_nb = new wxAuiNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
|
||||
wxAUI_NB_TOP | wxAUI_NB_TAB_SPLIT |
|
||||
wxAUI_NB_TAB_EXTERNAL_MOVE | wxAUI_NB_SCROLL_BUTTONS |
|
||||
wxAUI_NB_WINDOWLIST_BUTTON | wxAUI_NB_TAB_MOVE | wxNO_BORDER);
|
||||
|
||||
m_aui_mgr.AddPane(new DbgEmuPanel(this), wxAuiPaneInfo().Top());
|
||||
m_aui_mgr.AddPane(m_nb, wxAuiPaneInfo().Center().CaptionVisible(false).CloseButton().MaximizeButton());
|
||||
m_aui_mgr.AddPane(new InterpreterDisAsmFrame(this), wxAuiPaneInfo().Center().CaptionVisible(false).CloseButton().MaximizeButton());
|
||||
m_aui_mgr.Update();
|
||||
|
||||
m_app_connector.Connect(wxEVT_DBG_COMMAND, wxCommandEventHandler(DebuggerPanel::HandleCommand), (wxObject*)0, this);
|
||||
}
|
||||
|
||||
DebuggerPanel::~DebuggerPanel()
|
||||
@ -119,30 +112,3 @@ DebuggerPanel::~DebuggerPanel()
|
||||
void DebuggerPanel::UpdateUI()
|
||||
{
|
||||
}
|
||||
|
||||
void DebuggerPanel::HandleCommand(wxCommandEvent& event)
|
||||
{
|
||||
PPCThread* thr = (PPCThread*)event.GetClientData();
|
||||
|
||||
switch(event.GetId())
|
||||
{
|
||||
case DID_CREATE_THREAD:
|
||||
m_nb->AddPage(new InterpreterDisAsmFrame(m_nb, thr), thr->GetFName());
|
||||
break;
|
||||
|
||||
case DID_REMOVE_THREAD:
|
||||
for(uint i=0; i<m_nb->GetPageCount(); ++i)
|
||||
{
|
||||
InterpreterDisAsmFrame* page = (InterpreterDisAsmFrame*)m_nb->GetPage(i);
|
||||
|
||||
if(page->CPU.GetId() == thr->GetId())
|
||||
{
|
||||
m_nb->DeletePage(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
event.Skip();
|
||||
}
|
@ -5,13 +5,10 @@
|
||||
class DebuggerPanel : public wxPanel
|
||||
{
|
||||
wxAuiManager m_aui_mgr;
|
||||
wxAuiNotebook* m_nb;
|
||||
AppConnector m_app_connector;
|
||||
|
||||
public:
|
||||
DebuggerPanel(wxWindow* parent);
|
||||
~DebuggerPanel();
|
||||
|
||||
void UpdateUI();
|
||||
void HandleCommand(wxCommandEvent& event);
|
||||
};
|
@ -3,48 +3,39 @@
|
||||
|
||||
//static const int show_lines = 30;
|
||||
|
||||
u32 InterpreterDisAsmFrame::CentrePc(const u32 pc) const
|
||||
u64 InterpreterDisAsmFrame::CentrePc(const u64 pc) const
|
||||
{
|
||||
return pc - ((m_item_count / 2) * 4);
|
||||
}
|
||||
|
||||
InterpreterDisAsmFrame::InterpreterDisAsmFrame(wxWindow* parent, PPCThread* cpu)
|
||||
InterpreterDisAsmFrame::InterpreterDisAsmFrame(wxWindow* parent)
|
||||
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(500, 700), wxTAB_TRAVERSAL)
|
||||
, ThreadBase(false, "DisAsmFrame Thread")
|
||||
, CPU(*cpu)
|
||||
, PC(0)
|
||||
, CPU(nullptr)
|
||||
, m_item_count(30)
|
||||
, decoder(nullptr)
|
||||
, disasm(nullptr)
|
||||
{
|
||||
if(CPU.IsSPU())
|
||||
{
|
||||
SPU_DisAsm& dis_asm = *new SPU_DisAsm(CPU, InterpreterMode);
|
||||
decoder = new SPU_Decoder(dis_asm);
|
||||
disasm = &dis_asm;
|
||||
}
|
||||
else
|
||||
{
|
||||
PPU_DisAsm& dis_asm = *new PPU_DisAsm(CPU, InterpreterMode);
|
||||
decoder = new PPU_Decoder(dis_asm);
|
||||
disasm = &dis_asm;
|
||||
}
|
||||
|
||||
wxBoxSizer& s_p_main = *new wxBoxSizer(wxVERTICAL);
|
||||
wxBoxSizer& s_b_main = *new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
m_list = new wxListView(this);
|
||||
m_choice_units = new wxChoice(this, wxID_ANY);
|
||||
|
||||
wxButton& b_go_to_addr = *new wxButton(this, wxID_ANY, "Go To Address");
|
||||
wxButton& b_go_to_pc = *new wxButton(this, wxID_ANY, "Go To PC");
|
||||
|
||||
m_btn_step = new wxButton(this, wxID_ANY, "Step");
|
||||
m_btn_run = new wxButton(this, wxID_ANY, "Run");
|
||||
m_btn_pause = new wxButton(this, wxID_ANY, "Pause");
|
||||
m_btn_step = new wxButton(this, wxID_ANY, "Step");
|
||||
m_btn_run = new wxButton(this, wxID_ANY, "Run");
|
||||
m_btn_pause = new wxButton(this, wxID_ANY, "Pause");
|
||||
|
||||
s_b_main.Add(&b_go_to_addr, wxSizerFlags().Border(wxALL, 5));
|
||||
s_b_main.Add(&b_go_to_pc, wxSizerFlags().Border(wxALL, 5));
|
||||
s_b_main.Add(m_btn_step, wxSizerFlags().Border(wxALL, 5));
|
||||
s_b_main.Add(m_btn_run, wxSizerFlags().Border(wxALL, 5));
|
||||
s_b_main.Add(m_btn_pause, wxSizerFlags().Border(wxALL, 5));
|
||||
s_b_main.Add(&b_go_to_addr, wxSizerFlags().Border(wxALL, 5));
|
||||
s_b_main.Add(&b_go_to_pc, wxSizerFlags().Border(wxALL, 5));
|
||||
s_b_main.Add(m_btn_step, wxSizerFlags().Border(wxALL, 5));
|
||||
s_b_main.Add(m_btn_run, wxSizerFlags().Border(wxALL, 5));
|
||||
s_b_main.Add(m_btn_pause, wxSizerFlags().Border(wxALL, 5));
|
||||
s_b_main.Add(m_choice_units, wxSizerFlags().Border(wxALL, 5));
|
||||
|
||||
m_regs = new wxTextCtrl(this, wxID_ANY, wxEmptyString,
|
||||
wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE|wxTE_DONTWRAP|wxNO_BORDER|wxTE_RICH2);
|
||||
@ -77,12 +68,14 @@ InterpreterDisAsmFrame::InterpreterDisAsmFrame(wxWindow* parent, PPCThread* cpu)
|
||||
Connect(m_btn_run->GetId(), wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(InterpreterDisAsmFrame::DoRun));
|
||||
Connect(m_btn_pause->GetId(), wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(InterpreterDisAsmFrame::DoPause));
|
||||
Connect(m_list->GetId(), wxEVT_COMMAND_LIST_ITEM_ACTIVATED, wxListEventHandler(InterpreterDisAsmFrame::DClick));
|
||||
Connect(m_choice_units->GetId(),wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler(InterpreterDisAsmFrame::OnSelectUnit));
|
||||
Connect(wxEVT_SIZE, wxSizeEventHandler(InterpreterDisAsmFrame::OnResize));
|
||||
m_app_connector.Connect(m_list->GetId(), wxEVT_MOUSEWHEEL, wxMouseEventHandler(InterpreterDisAsmFrame::MouseWheel), (wxObject*)0, this);
|
||||
m_app_connector.Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(InterpreterDisAsmFrame::OnKeyDown), (wxObject*)0, this);
|
||||
|
||||
m_app_connector.Connect(wxEVT_DBG_COMMAND, wxCommandEventHandler(InterpreterDisAsmFrame::HandleCommand), (wxObject*)0, this);
|
||||
WriteRegs();
|
||||
|
||||
ShowAddr(CentrePc(PC));
|
||||
UpdateUnitList();
|
||||
}
|
||||
|
||||
InterpreterDisAsmFrame::~InterpreterDisAsmFrame()
|
||||
@ -90,6 +83,48 @@ InterpreterDisAsmFrame::~InterpreterDisAsmFrame()
|
||||
ThreadBase::Stop();
|
||||
}
|
||||
|
||||
void InterpreterDisAsmFrame::UpdateUnitList()
|
||||
{
|
||||
m_choice_units->Freeze();
|
||||
m_choice_units->Clear();
|
||||
auto& thrs = Emu.GetCPU().GetThreads();
|
||||
|
||||
for(uint i=0; i<thrs.GetCount(); ++i)
|
||||
{
|
||||
m_choice_units->Append(thrs[i].GetFName(), &thrs[i]);
|
||||
}
|
||||
|
||||
m_choice_units->Thaw();
|
||||
}
|
||||
|
||||
void InterpreterDisAsmFrame::OnSelectUnit(wxCommandEvent& event)
|
||||
{
|
||||
CPU = (PPCThread*)event.GetClientData();
|
||||
|
||||
delete decoder;
|
||||
//delete disasm;
|
||||
decoder = nullptr;
|
||||
disasm = nullptr;
|
||||
|
||||
if(CPU)
|
||||
{
|
||||
if(CPU->IsSPU())
|
||||
{
|
||||
SPU_DisAsm& dis_asm = *new SPU_DisAsm(*CPU, InterpreterMode);
|
||||
decoder = new SPU_Decoder(dis_asm);
|
||||
disasm = &dis_asm;
|
||||
}
|
||||
else
|
||||
{
|
||||
PPU_DisAsm& dis_asm = *new PPU_DisAsm(*CPU, InterpreterMode);
|
||||
decoder = new PPU_Decoder(dis_asm);
|
||||
disasm = &dis_asm;
|
||||
}
|
||||
}
|
||||
|
||||
DoUpdate();
|
||||
}
|
||||
|
||||
void InterpreterDisAsmFrame::OnKeyDown(wxKeyEvent& event)
|
||||
{
|
||||
if(wxGetActiveWindow() != wxGetTopLevelParent(this))
|
||||
@ -164,48 +199,59 @@ void InterpreterDisAsmFrame::ShowAddr(const u64 addr)
|
||||
{
|
||||
PC = addr;
|
||||
m_list->Freeze();
|
||||
disasm->offset = CPU.GetOffset();
|
||||
for(uint i=0; i<m_item_count; ++i, PC += 4)
|
||||
|
||||
if(!CPU)
|
||||
{
|
||||
if(!Memory.IsGoodAddr(CPU.GetOffset() + PC, 4))
|
||||
for(uint i=0; i<m_item_count; ++i, PC += 4)
|
||||
{
|
||||
m_list->SetItem(i, 0, wxString::Format("[%08llx] illegal address", PC));
|
||||
continue;
|
||||
}
|
||||
|
||||
disasm->dump_pc = PC;
|
||||
decoder->Decode(Memory.Read32(CPU.GetOffset() + PC));
|
||||
|
||||
if(IsBreakPoint(PC))
|
||||
}
|
||||
else
|
||||
{
|
||||
disasm->offset = CPU->GetOffset();
|
||||
for(uint i=0; i<m_item_count; ++i, PC += 4)
|
||||
{
|
||||
m_list->SetItem(i, 0, ">>> " + disasm->last_opcode);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_list->SetItem(i, 0, " " + disasm->last_opcode);
|
||||
}
|
||||
|
||||
wxColour colour;
|
||||
|
||||
if((!CPU.IsRunned() || !Emu.IsRunned()) && PC == CPU.PC)
|
||||
{
|
||||
colour = wxColour("Green");
|
||||
}
|
||||
else
|
||||
{
|
||||
colour = wxColour("White");
|
||||
|
||||
for(u32 i=0; i<Emu.GetMarkedPoints().GetCount(); ++i)
|
||||
if(!Memory.IsGoodAddr(CPU->GetOffset() + PC, 4))
|
||||
{
|
||||
if(Emu.GetMarkedPoints()[i] == PC)
|
||||
m_list->SetItem(i, 0, wxString::Format("[%08llx] illegal address", PC));
|
||||
continue;
|
||||
}
|
||||
|
||||
disasm->dump_pc = PC;
|
||||
decoder->Decode(Memory.Read32(CPU->GetOffset() + PC));
|
||||
|
||||
if(IsBreakPoint(PC))
|
||||
{
|
||||
m_list->SetItem(i, 0, ">>> " + disasm->last_opcode);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_list->SetItem(i, 0, " " + disasm->last_opcode);
|
||||
}
|
||||
|
||||
wxColour colour;
|
||||
|
||||
if((!CPU->IsRunned() || !Emu.IsRunned()) && PC == CPU->PC)
|
||||
{
|
||||
colour = wxColour("Green");
|
||||
}
|
||||
else
|
||||
{
|
||||
colour = wxColour("White");
|
||||
|
||||
for(u32 i=0; i<Emu.GetMarkedPoints().GetCount(); ++i)
|
||||
{
|
||||
colour = wxColour("Wheat");
|
||||
break;
|
||||
if(Emu.GetMarkedPoints()[i] == PC)
|
||||
{
|
||||
colour = wxColour("Wheat");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_list->SetItemBackgroundColour( i, colour );
|
||||
m_list->SetItemBackgroundColour( i, colour );
|
||||
}
|
||||
}
|
||||
|
||||
while(remove_markedPC.GetCount())
|
||||
@ -234,7 +280,7 @@ void InterpreterDisAsmFrame::WriteRegs()
|
||||
{
|
||||
m_regs->Freeze();
|
||||
m_regs->Clear();
|
||||
m_regs->WriteText(CPU.RegsToString());
|
||||
if(CPU) m_regs->WriteText(CPU->RegsToString());
|
||||
m_regs->Thaw();
|
||||
}
|
||||
|
||||
@ -253,7 +299,7 @@ void InterpreterDisAsmFrame::HandleCommand(wxCommandEvent& event)
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if(thr->GetId() == CPU.GetId())
|
||||
else if(CPU && thr->GetId() == CPU->GetId())
|
||||
{
|
||||
switch(event.GetId())
|
||||
{
|
||||
@ -266,7 +312,6 @@ void InterpreterDisAsmFrame::HandleCommand(wxCommandEvent& event)
|
||||
DoUpdate();
|
||||
break;
|
||||
|
||||
|
||||
case DID_START_THREAD:
|
||||
case DID_EXEC_THREAD:
|
||||
case DID_RESUME_THREAD:
|
||||
@ -285,6 +330,36 @@ void InterpreterDisAsmFrame::HandleCommand(wxCommandEvent& event)
|
||||
m_btn_run->Disable();
|
||||
m_btn_step->Disable();
|
||||
m_btn_pause->Disable();
|
||||
|
||||
if(event.GetId() == DID_REMOVE_THREAD)
|
||||
{
|
||||
m_choice_units->SetSelection(-1);
|
||||
wxCommandEvent event;
|
||||
event.SetInt(-1);
|
||||
//event.SetClientData(nullptr);
|
||||
OnSelectUnit(event);
|
||||
UpdateUnitList();
|
||||
}
|
||||
|
||||
DoUpdate();
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch(event.GetId())
|
||||
{
|
||||
case DID_CREATE_THREAD:
|
||||
UpdateUnitList();
|
||||
if(m_choice_units->GetSelection() == -1)
|
||||
{
|
||||
m_choice_units->SetSelection(0);
|
||||
wxCommandEvent event;
|
||||
event.SetInt(0);
|
||||
event.SetClientData(&Emu.GetCPU().GetThreads()[0]);
|
||||
OnSelectUnit(event);
|
||||
DoUpdate();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -313,11 +388,11 @@ void InterpreterDisAsmFrame::Show_Val(wxCommandEvent& WXUNUSED(event))
|
||||
|
||||
diag->SetSizerAndFit( s_panel );
|
||||
|
||||
p_pc->SetLabel(wxString::Format("%llx", CPU.PC));
|
||||
if(CPU) p_pc->SetLabel(wxString::Format("%llx", CPU->PC));
|
||||
|
||||
if(diag->ShowModal() == wxID_OK)
|
||||
{
|
||||
u64 pc = CPU.PC;
|
||||
u64 pc = CPU ? CPU->PC : 0x0;
|
||||
sscanf(p_pc->GetLabel(), "%llx", &pc);
|
||||
remove_markedPC.AddCpy(Emu.GetMarkedPoints().AddCpy(pc));
|
||||
ShowAddr(CentrePc(pc));
|
||||
@ -326,17 +401,19 @@ void InterpreterDisAsmFrame::Show_Val(wxCommandEvent& WXUNUSED(event))
|
||||
|
||||
void InterpreterDisAsmFrame::Show_PC(wxCommandEvent& WXUNUSED(event))
|
||||
{
|
||||
ShowAddr(CentrePc(CPU.PC));
|
||||
if(CPU) ShowAddr(CentrePc(CPU->PC));
|
||||
}
|
||||
|
||||
extern bool dump_enable;
|
||||
void InterpreterDisAsmFrame::DoRun(wxCommandEvent& WXUNUSED(event))
|
||||
{
|
||||
if(CPU.IsPaused()) CPU.Resume();
|
||||
if(!CPU) return;
|
||||
|
||||
if(CPU->IsPaused()) CPU->Resume();
|
||||
|
||||
if(!Emu.IsPaused())
|
||||
{
|
||||
CPU.Exec();
|
||||
CPU->Exec();
|
||||
}
|
||||
|
||||
//ThreadBase::Start();
|
||||
@ -344,7 +421,7 @@ void InterpreterDisAsmFrame::DoRun(wxCommandEvent& WXUNUSED(event))
|
||||
|
||||
void InterpreterDisAsmFrame::DoPause(wxCommandEvent& WXUNUSED(event))
|
||||
{
|
||||
CPU.Pause();
|
||||
if(CPU) CPU->Pause();
|
||||
}
|
||||
|
||||
void InterpreterDisAsmFrame::DoStep(wxCommandEvent& WXUNUSED(event))
|
||||
@ -418,7 +495,8 @@ bool InterpreterDisAsmFrame::RemoveBreakPoint(u64 pc)
|
||||
|
||||
void InterpreterDisAsmFrame::Task()
|
||||
{
|
||||
wxGetApp().SendDbgCommand(DID_RESUME_THREAD, &CPU);
|
||||
if(!CPU) return;
|
||||
wxGetApp().SendDbgCommand(DID_RESUME_THREAD, CPU);
|
||||
|
||||
bool dump_status = dump_enable;
|
||||
|
||||
@ -428,9 +506,9 @@ void InterpreterDisAsmFrame::Task()
|
||||
{
|
||||
do
|
||||
{
|
||||
CPU.ExecOnce();
|
||||
CPU->ExecOnce();
|
||||
}
|
||||
while(CPU.IsRunned() && Emu.IsRunned() && !TestDestroy() && !IsBreakPoint(CPU.PC) && dump_status == dump_enable);
|
||||
while(CPU->IsRunned() && Emu.IsRunned() && !TestDestroy() && !IsBreakPoint(CPU->PC) && dump_status == dump_enable);
|
||||
}
|
||||
catch(const wxString& e)
|
||||
{
|
||||
@ -443,5 +521,5 @@ void InterpreterDisAsmFrame::Task()
|
||||
|
||||
//CPU.FreeTls();
|
||||
|
||||
wxGetApp().SendDbgCommand(DID_PAUSE_THREAD, &CPU);
|
||||
wxGetApp().SendDbgCommand(DID_PAUSE_THREAD, CPU);
|
||||
}
|
@ -20,16 +20,19 @@ class InterpreterDisAsmFrame
|
||||
wxButton* m_btn_pause;
|
||||
AppConnector m_app_connector;
|
||||
u32 m_item_count;
|
||||
wxChoice* m_choice_units;
|
||||
|
||||
public:
|
||||
PPCThread& CPU;
|
||||
PPCThread* CPU;
|
||||
|
||||
public:
|
||||
InterpreterDisAsmFrame(wxWindow* parent, PPCThread* cpu);
|
||||
InterpreterDisAsmFrame(wxWindow* parent);
|
||||
~InterpreterDisAsmFrame();
|
||||
|
||||
u32 CentrePc(const u32 pc) const;
|
||||
void UpdateUnitList();
|
||||
|
||||
u64 CentrePc(const u64 pc) const;
|
||||
void OnSelectUnit(wxCommandEvent& event);
|
||||
void OnKeyDown(wxKeyEvent& event);
|
||||
void OnResize(wxSizeEvent& event);
|
||||
void DoUpdate();
|
||||
|
@ -30,13 +30,16 @@ bool SELFLoader::LoadData(u64 offset)
|
||||
if( !l.LoadEhdrInfo(self_hdr.se_elfoff) ||
|
||||
!l.LoadPhdrInfo(self_hdr.se_phdroff) ||
|
||||
!l.LoadShdrInfo(self_hdr.se_shdroff) ||
|
||||
!l.LoadData(offset) )
|
||||
!l.LoadData(self_hdr.se_appinfooff) )
|
||||
{
|
||||
ConLog.Error("Broken SELF file.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
machine = l.GetMachine();
|
||||
entry = l.GetEntry();
|
||||
|
||||
return true;
|
||||
|
||||
ConLog.Error("Boot SELF not supported yet!");
|
||||
|
Loading…
Reference in New Issue
Block a user