mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-04-21 12:05:23 +00:00
Implemented vm::var.
MemoryAllocator replaced with vm::var
This commit is contained in:
parent
6cb083be1a
commit
cd33be1491
16 changed files with 763 additions and 149 deletions
|
@ -1113,6 +1113,7 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
/*
|
||||
template<typename T>
|
||||
class MemoryAllocator
|
||||
{
|
||||
|
@ -1222,6 +1223,7 @@ public:
|
|||
return (NT*)(m_ptr + offset);
|
||||
}
|
||||
};
|
||||
*/
|
||||
|
||||
typedef mem_t<u8, u32> mem8_t;
|
||||
typedef mem_t<u16, u32> mem16_t;
|
||||
|
@ -1245,3 +1247,5 @@ typedef mem_list_ptr_t<u16, u32> mem16_ptr_t;
|
|||
typedef mem_list_ptr_t<u32, u32> mem32_ptr_t;
|
||||
typedef mem_list_ptr_t<u64, u32> mem64_ptr_t;
|
||||
|
||||
#include "vm.h"
|
||||
|
||||
|
|
48
rpcs3/Emu/Memory/vm.h
Normal file
48
rpcs3/Emu/Memory/vm.h
Normal file
|
@ -0,0 +1,48 @@
|
|||
#pragma once
|
||||
|
||||
namespace vm
|
||||
{
|
||||
//TODO
|
||||
bool check_addr(u32 addr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool map(u32 addr, u32 size, u32 flags)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool unmap(u32 addr, u32 size = 0, u32 flags = 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
u32 alloc(u32 size)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void unalloc(u32 addr)
|
||||
{
|
||||
}
|
||||
|
||||
u32 read32(u32 addr)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool read32(u32 addr, u32& value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool write32(u32 addr, u32 value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#include "vm_ptr.h"
|
||||
#include "vm_ref.h"
|
||||
#include "vm_var.h"
|
6
rpcs3/Emu/Memory/vm_ptr.h
Normal file
6
rpcs3/Emu/Memory/vm_ptr.h
Normal file
|
@ -0,0 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
namespace vm
|
||||
{
|
||||
//TODO
|
||||
}
|
6
rpcs3/Emu/Memory/vm_ref.h
Normal file
6
rpcs3/Emu/Memory/vm_ref.h
Normal file
|
@ -0,0 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
namespace vm
|
||||
{
|
||||
//TODO
|
||||
}
|
513
rpcs3/Emu/Memory/vm_var.h
Normal file
513
rpcs3/Emu/Memory/vm_var.h
Normal file
|
@ -0,0 +1,513 @@
|
|||
#pragma once
|
||||
|
||||
namespace vm
|
||||
{
|
||||
template<typename T>
|
||||
class var
|
||||
{
|
||||
u32 m_addr;
|
||||
u32 m_size;
|
||||
u32 m_align;
|
||||
T* m_ptr;
|
||||
|
||||
public:
|
||||
var(u32 size = sizeof(T), u32 align = sizeof(T))
|
||||
: m_size(size)
|
||||
, m_align(align)
|
||||
{
|
||||
alloc();
|
||||
}
|
||||
|
||||
var(const var& r)
|
||||
: m_size(r.m_size)
|
||||
, m_align(r.m_align)
|
||||
{
|
||||
alloc();
|
||||
*m_ptr = *r.m_ptr;
|
||||
}
|
||||
|
||||
~var()
|
||||
{
|
||||
dealloc();
|
||||
}
|
||||
|
||||
void alloc()
|
||||
{
|
||||
m_addr = Memory.Alloc(size(), m_align);
|
||||
m_ptr = Memory.IsGoodAddr(m_addr, size()) ? (T*)&Memory[m_addr] : nullptr;
|
||||
}
|
||||
|
||||
void dealloc()
|
||||
{
|
||||
if (check())
|
||||
{
|
||||
Memory.Free(m_addr);
|
||||
m_addr = 0;
|
||||
m_ptr = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
if (check())
|
||||
{
|
||||
Memory.Free(m_addr);
|
||||
m_addr = 0;
|
||||
m_ptr = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static var make(u32 addr, u32 size = sizeof(T), u32 align = sizeof(T))
|
||||
{
|
||||
var res;
|
||||
|
||||
res.m_addr = addr;
|
||||
res.m_size = size;
|
||||
res.m_align = align;
|
||||
res.m_ptr = Memory.IsGoodAddr(m_addr, sizeof(T)) ? (T*)&Memory[m_addr] : nullptr;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
T* operator -> ()
|
||||
{
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
const T* operator -> () const
|
||||
{
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
T* get_ptr()
|
||||
{
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
const T* get_ptr() const
|
||||
{
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
T& value()
|
||||
{
|
||||
return *m_ptr;
|
||||
}
|
||||
|
||||
const T& value() const
|
||||
{
|
||||
return *m_ptr;
|
||||
}
|
||||
|
||||
u32 addr() const
|
||||
{
|
||||
return m_addr;
|
||||
}
|
||||
|
||||
u32 size() const
|
||||
{
|
||||
return m_size;
|
||||
}
|
||||
|
||||
bool check() const
|
||||
{
|
||||
return m_ptr != nullptr;
|
||||
}
|
||||
|
||||
/*
|
||||
operator const ref<T>() const
|
||||
{
|
||||
return addr();
|
||||
}
|
||||
*/
|
||||
|
||||
operator T&()
|
||||
{
|
||||
return *m_ptr;
|
||||
}
|
||||
|
||||
operator const T&() const
|
||||
{
|
||||
return *m_ptr;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class var<T[]>
|
||||
{
|
||||
u32 m_addr;
|
||||
u32 m_count;
|
||||
u32 m_size;
|
||||
u32 m_align;
|
||||
T* m_ptr;
|
||||
|
||||
public:
|
||||
var(u32 count, u32 size = sizeof(T), u32 align = sizeof(T))
|
||||
: m_count(count)
|
||||
, m_size(size)
|
||||
, m_align(align)
|
||||
{
|
||||
alloc();
|
||||
}
|
||||
|
||||
~var()
|
||||
{
|
||||
dealloc();
|
||||
}
|
||||
|
||||
var(const var& r)
|
||||
: m_size(r.m_size)
|
||||
, m_align(r.m_align)
|
||||
{
|
||||
alloc();
|
||||
memcpy(m_ptr, r.m_ptr, size());
|
||||
}
|
||||
|
||||
void alloc()
|
||||
{
|
||||
m_addr = Memory.Alloc(size(), m_align);
|
||||
m_ptr = Memory.IsGoodAddr(m_addr, size()) ? (T*)&Memory[m_addr] : nullptr;
|
||||
}
|
||||
|
||||
void dealloc()
|
||||
{
|
||||
if (check())
|
||||
{
|
||||
Memory.Free(m_addr);
|
||||
m_addr = 0;
|
||||
m_ptr = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static var make(u32 addr, u32 count, u32 size = sizeof(T), u32 align = sizeof(T))
|
||||
{
|
||||
var res;
|
||||
|
||||
res.m_addr = addr;
|
||||
res.m_count = count;
|
||||
res.m_size = size;
|
||||
res.m_align = align;
|
||||
res.m_ptr = Memory.IsGoodAddr(m_addr, size()) ? (T*)&Memory[m_addr] : nullptr;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
T* begin()
|
||||
{
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
const T* begin() const
|
||||
{
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
T* end()
|
||||
{
|
||||
return m_ptr + count();
|
||||
}
|
||||
|
||||
const T* end() const
|
||||
{
|
||||
return m_ptr + count();
|
||||
}
|
||||
|
||||
T* operator -> ()
|
||||
{
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
T& get()
|
||||
{
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
const T& get() const
|
||||
{
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
const T* operator -> () const
|
||||
{
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
uint addr() const
|
||||
{
|
||||
return m_addr;
|
||||
}
|
||||
|
||||
uint size() const
|
||||
{
|
||||
return m_count * m_size;
|
||||
}
|
||||
|
||||
uint count() const
|
||||
{
|
||||
return m_count;
|
||||
}
|
||||
|
||||
bool check() const
|
||||
{
|
||||
return Memory.IsGoodAddr(m_addr, size());
|
||||
}
|
||||
|
||||
template<typename T1>
|
||||
operator const T1() const
|
||||
{
|
||||
return T1(*m_ptr);
|
||||
}
|
||||
|
||||
template<typename T1>
|
||||
operator T1()
|
||||
{
|
||||
return T1(*m_ptr);
|
||||
}
|
||||
|
||||
operator const T&() const
|
||||
{
|
||||
return *m_ptr;
|
||||
}
|
||||
|
||||
operator T&()
|
||||
{
|
||||
return *m_ptr;
|
||||
}
|
||||
|
||||
operator const T*() const
|
||||
{
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
operator T*()
|
||||
{
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
T& operator [](int index)
|
||||
{
|
||||
return *(T*)((u8*)m_ptr + (m_size < m_align ? m_align : m_size) * index);
|
||||
}
|
||||
|
||||
const T& operator [](int index) const
|
||||
{
|
||||
return *(T*)((u8*)m_ptr + (m_size < m_align ? m_align : m_size) * index);
|
||||
}
|
||||
|
||||
T& at(uint index)
|
||||
{
|
||||
if (index >= count())
|
||||
throw std::out_of_range();
|
||||
|
||||
return *(m_ptr + index);
|
||||
}
|
||||
|
||||
const T& at(uint index) const
|
||||
{
|
||||
if (index >= count())
|
||||
throw std::out_of_range();
|
||||
|
||||
return *(m_ptr + index);
|
||||
}
|
||||
|
||||
T* ptr()
|
||||
{
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
const T* ptr() const
|
||||
{
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
/*
|
||||
operator const ptr<T>() const
|
||||
{
|
||||
return addr();
|
||||
}
|
||||
*/
|
||||
template<typename NT>
|
||||
NT* To(uint offset = 0)
|
||||
{
|
||||
return (NT*)(m_ptr + offset);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, int _count>
|
||||
class var<T[_count]>
|
||||
{
|
||||
u32 m_addr;
|
||||
u32 m_size;
|
||||
u32 m_align;
|
||||
T* m_ptr;
|
||||
|
||||
public:
|
||||
var(u32 size = sizeof(T), u32 align = sizeof(T))
|
||||
: m_size(size)
|
||||
, m_align(align)
|
||||
{
|
||||
alloc();
|
||||
}
|
||||
|
||||
~var()
|
||||
{
|
||||
dealloc();
|
||||
}
|
||||
|
||||
var(const var& r)
|
||||
: m_size(r.m_size)
|
||||
, m_align(r.m_align)
|
||||
{
|
||||
alloc();
|
||||
memcpy(m_ptr, r.m_ptr, size());
|
||||
}
|
||||
|
||||
void alloc()
|
||||
{
|
||||
m_addr = Memory.Alloc(size(), m_align);
|
||||
m_ptr = Memory.IsGoodAddr(m_addr, size()) ? (T*)&Memory[m_addr] : nullptr;
|
||||
}
|
||||
|
||||
void dealloc()
|
||||
{
|
||||
if (check())
|
||||
{
|
||||
Memory.Free(m_addr);
|
||||
m_addr = 0;
|
||||
m_ptr = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
T* operator -> ()
|
||||
{
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
T* begin()
|
||||
{
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
const T* begin() const
|
||||
{
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
T* end()
|
||||
{
|
||||
return m_ptr + count();
|
||||
}
|
||||
|
||||
const T* end() const
|
||||
{
|
||||
return m_ptr + count();
|
||||
}
|
||||
|
||||
T& get()
|
||||
{
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
const T& get() const
|
||||
{
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
const T* operator -> () const
|
||||
{
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
uint addr() const
|
||||
{
|
||||
return m_addr;
|
||||
}
|
||||
|
||||
__forceinline uint count() const
|
||||
{
|
||||
return _count;
|
||||
}
|
||||
|
||||
uint size() const
|
||||
{
|
||||
return _count * m_size;
|
||||
}
|
||||
|
||||
bool check() const
|
||||
{
|
||||
return m_ptr != nullptr;
|
||||
}
|
||||
|
||||
template<typename T1>
|
||||
operator const T1() const
|
||||
{
|
||||
return T1(*m_ptr);
|
||||
}
|
||||
|
||||
template<typename T1>
|
||||
operator T1()
|
||||
{
|
||||
return T1(*m_ptr);
|
||||
}
|
||||
|
||||
operator const T&() const
|
||||
{
|
||||
return *m_ptr;
|
||||
}
|
||||
|
||||
operator T&()
|
||||
{
|
||||
return *m_ptr;
|
||||
}
|
||||
|
||||
operator const T*() const
|
||||
{
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
operator T*()
|
||||
{
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
T& operator [](uint index)
|
||||
{
|
||||
assert(index < count());
|
||||
return *(T*)((u8*)m_ptr + (m_size < m_align ? m_align : m_size) * index);
|
||||
}
|
||||
|
||||
const T& operator [](uint index) const
|
||||
{
|
||||
assert(index < count());
|
||||
return return *(T*)((u8*)m_ptr + (m_size < m_align ? m_align : m_size) * index);
|
||||
}
|
||||
|
||||
T& at(uint index)
|
||||
{
|
||||
if (index >= count())
|
||||
throw std::out_of_range();
|
||||
|
||||
return *(m_ptr + index);
|
||||
}
|
||||
|
||||
const T& at(uint index) const
|
||||
{
|
||||
if (index >= count())
|
||||
throw std::out_of_range();
|
||||
|
||||
return *(m_ptr + index);
|
||||
}
|
||||
|
||||
/*
|
||||
operator const ptr<T>() const
|
||||
{
|
||||
return addr();
|
||||
}
|
||||
*/
|
||||
template<typename NT>
|
||||
NT* To(uint offset = 0)
|
||||
{
|
||||
return (NT*)(m_ptr + offset);
|
||||
}
|
||||
};
|
||||
}
|
|
@ -269,8 +269,9 @@ int cellFontInit(mem_ptr_t<CellFontConfig> config)
|
|||
{
|
||||
cellFont->Log("cellFontInit(config=0x%x)", config.GetAddr());
|
||||
|
||||
MemoryAllocator<u64> revisionFlags = 0;
|
||||
cellFontGetRevisionFlags(revisionFlags.GetAddr());
|
||||
vm::var<u64> revisionFlags;
|
||||
revisionFlags.value() = 0;
|
||||
cellFontGetRevisionFlags(revisionFlags.addr());
|
||||
return cellFontInitializeWithRevision(revisionFlags, config.GetAddr());
|
||||
}
|
||||
|
||||
|
|
|
@ -241,11 +241,11 @@ int cellGameDataCheckCreate2(u32 version, const mem_list_ptr_t<u8> dirName, u32
|
|||
}
|
||||
|
||||
// TODO: use memory container
|
||||
MemoryAllocator<CellGameDataCBResult> cbResult;
|
||||
MemoryAllocator<CellGameDataStatGet> cbGet;
|
||||
MemoryAllocator<CellGameDataStatSet> cbSet;
|
||||
vm::var<CellGameDataCBResult> cbResult;
|
||||
vm::var<CellGameDataStatGet> cbGet;
|
||||
vm::var<CellGameDataStatSet> cbSet;
|
||||
|
||||
memset(cbGet.GetPtr(), 0, sizeof(CellGameDataStatGet));
|
||||
cbGet.value() = {};
|
||||
|
||||
// TODO: Use the free space of the computer's HDD where RPCS3 is being run.
|
||||
cbGet->hddFreeSizeKB = 40000000; //40 GB
|
||||
|
@ -270,7 +270,7 @@ int cellGameDataCheckCreate2(u32 version, const mem_list_ptr_t<u8> dirName, u32
|
|||
strcpy_trunc(cbGet->getParam.title, psf.GetString("TITLE"));
|
||||
// TODO: write lang titles
|
||||
|
||||
funcStat(cbResult.GetAddr(), cbGet.GetAddr(), cbSet.GetAddr());
|
||||
funcStat(cbResult.addr(), cbGet.addr(), cbSet.addr());
|
||||
|
||||
if (cbSet->setParam.GetAddr())
|
||||
{
|
||||
|
|
|
@ -42,14 +42,14 @@ int cellGifDecOpen(u32 mainHandle, mem32_t subHandle, const mem_ptr_t<CellGifDec
|
|||
|
||||
case se32(CELL_GIFDEC_FILE):
|
||||
// Get file descriptor
|
||||
MemoryAllocator<be_t<u32>> fd;
|
||||
int ret = cellFsOpen(src->fileName, 0, fd.GetAddr(), 0, 0);
|
||||
vm::var<be_t<u32>> fd;
|
||||
int ret = cellFsOpen(src->fileName, 0, fd.addr(), 0, 0);
|
||||
current_subHandle->fd = fd->ToLE();
|
||||
if (ret != CELL_OK) return CELL_GIFDEC_ERROR_OPEN_FILE;
|
||||
|
||||
// Get size of file
|
||||
MemoryAllocator<CellFsStat> sb; // Alloc a CellFsStat struct
|
||||
ret = cellFsFstat(current_subHandle->fd, sb.GetAddr());
|
||||
vm::var<CellFsStat> sb; // Alloc a CellFsStat struct
|
||||
ret = cellFsFstat(current_subHandle->fd, sb.addr());
|
||||
if (ret != CELL_OK) return ret;
|
||||
current_subHandle->fileSize = sb->st_size; // Get CellFsStat.st_size
|
||||
break;
|
||||
|
@ -75,21 +75,21 @@ int cellGifDecReadHeader(u32 mainHandle, u32 subHandle, mem_ptr_t<CellGifDecInfo
|
|||
CellGifDecInfo& current_info = subHandle_data->info;
|
||||
|
||||
//Write the header to buffer
|
||||
MemoryAllocator<u8> buffer(13); // Alloc buffer for GIF header
|
||||
MemoryAllocator<be_t<u64>> pos, nread;
|
||||
vm::var<u8[13]> buffer; // Alloc buffer for GIF header
|
||||
vm::var<be_t<u64>> pos, nread;
|
||||
|
||||
switch(subHandle_data->src.srcSelect.ToBE())
|
||||
{
|
||||
case se32(CELL_GIFDEC_BUFFER):
|
||||
if (!Memory.Copy(buffer.GetAddr(), subHandle_data->src.streamPtr.ToLE(), buffer.GetSize())) {
|
||||
if (!Memory.Copy(buffer.addr(), subHandle_data->src.streamPtr.ToLE(), buffer.size())) {
|
||||
cellGifDec->Error("cellGifDecReadHeader() failed ()");
|
||||
return CELL_EFAULT;
|
||||
}
|
||||
break;
|
||||
|
||||
case se32(CELL_GIFDEC_FILE):
|
||||
cellFsLseek(fd, 0, CELL_SEEK_SET, pos.GetAddr());
|
||||
cellFsRead(fd, buffer.GetAddr(), buffer.GetSize(), nread);
|
||||
cellFsLseek(fd, 0, CELL_SEEK_SET, pos.addr());
|
||||
cellFsRead(fd, buffer.addr(), buffer.size(), nread.addr());
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -160,21 +160,21 @@ int cellGifDecDecodeData(u32 mainHandle, u32 subHandle, mem8_ptr_t data, const m
|
|||
const CellGifDecOutParam& current_outParam = subHandle_data->outParam;
|
||||
|
||||
//Copy the GIF file to a buffer
|
||||
MemoryAllocator<unsigned char> gif(fileSize);
|
||||
MemoryAllocator<u64> pos, nread;
|
||||
vm::var<unsigned char[]> gif(fileSize);
|
||||
vm::var<u64> pos, nread;
|
||||
|
||||
switch(subHandle_data->src.srcSelect.ToBE())
|
||||
{
|
||||
case se32(CELL_GIFDEC_BUFFER):
|
||||
if (!Memory.Copy(gif.GetAddr(), subHandle_data->src.streamPtr.ToLE(), gif.GetSize())) {
|
||||
if (!Memory.Copy(gif.addr(), subHandle_data->src.streamPtr.ToLE(), gif.size())) {
|
||||
cellGifDec->Error("cellGifDecDecodeData() failed (I)");
|
||||
return CELL_EFAULT;
|
||||
}
|
||||
break;
|
||||
|
||||
case se32(CELL_GIFDEC_FILE):
|
||||
cellFsLseek(fd, 0, CELL_SEEK_SET, pos.GetAddr());
|
||||
cellFsRead(fd, gif.GetAddr(), gif.GetSize(), nread);
|
||||
cellFsLseek(fd, 0, CELL_SEEK_SET, pos.addr());
|
||||
cellFsRead(fd, gif.addr(), gif.size(), nread.addr());
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -182,7 +182,7 @@ int cellGifDecDecodeData(u32 mainHandle, u32 subHandle, mem8_ptr_t data, const m
|
|||
int width, height, actual_components;
|
||||
auto image = std::unique_ptr<unsigned char,decltype(&::free)>
|
||||
(
|
||||
stbi_load_from_memory(gif.GetPtr(), fileSize, &width, &height, &actual_components, 4),
|
||||
stbi_load_from_memory(gif.ptr(), fileSize, &width, &height, &actual_components, 4),
|
||||
&::free
|
||||
);
|
||||
|
||||
|
|
|
@ -49,14 +49,14 @@ int cellJpgDecOpen(u32 mainHandle, mem32_t subHandle, mem_ptr_t<CellJpgDecSrc> s
|
|||
|
||||
case se32(CELL_JPGDEC_FILE):
|
||||
// Get file descriptor
|
||||
MemoryAllocator<be_t<u32>> fd;
|
||||
int ret = cellFsOpen(src->fileName, 0, fd.GetAddr(), 0, 0);
|
||||
vm::var<be_t<u32>> fd;
|
||||
int ret = cellFsOpen(src->fileName, 0, fd.addr(), 0, 0);
|
||||
current_subHandle->fd = fd->ToLE();
|
||||
if (ret != CELL_OK) return CELL_JPGDEC_ERROR_OPEN_FILE;
|
||||
|
||||
// Get size of file
|
||||
MemoryAllocator<CellFsStat> sb; // Alloc a CellFsStat struct
|
||||
ret = cellFsFstat(current_subHandle->fd, sb.GetAddr());
|
||||
vm::var<CellFsStat> sb; // Alloc a CellFsStat struct
|
||||
ret = cellFsFstat(current_subHandle->fd, sb.addr());
|
||||
if (ret != CELL_OK) return ret;
|
||||
current_subHandle->fileSize = sb->st_size; // Get CellFsStat.st_size
|
||||
break;
|
||||
|
@ -96,21 +96,21 @@ int cellJpgDecReadHeader(u32 mainHandle, u32 subHandle, mem_ptr_t<CellJpgDecInfo
|
|||
CellJpgDecInfo& current_info = subHandle_data->info;
|
||||
|
||||
//Write the header to buffer
|
||||
MemoryAllocator<u8> buffer(fileSize);
|
||||
MemoryAllocator<be_t<u64>> pos, nread;
|
||||
vm::var<u8[]> buffer(fileSize);
|
||||
vm::var<be_t<u64>> pos, nread;
|
||||
|
||||
switch(subHandle_data->src.srcSelect.ToBE())
|
||||
{
|
||||
case se32(CELL_JPGDEC_BUFFER):
|
||||
if (!Memory.Copy(buffer.GetAddr(), subHandle_data->src.streamPtr.ToLE(), buffer.GetSize())) {
|
||||
if (!Memory.Copy(buffer.addr(), subHandle_data->src.streamPtr.ToLE(), buffer.size())) {
|
||||
cellJpgDec->Error("cellJpgDecReadHeader() failed ()");
|
||||
return CELL_EFAULT;
|
||||
}
|
||||
break;
|
||||
|
||||
case se32(CELL_JPGDEC_FILE):
|
||||
cellFsLseek(fd, 0, CELL_SEEK_SET, pos.GetAddr());
|
||||
cellFsRead(fd, buffer.GetAddr(), buffer.GetSize(), nread);
|
||||
cellFsLseek(fd, 0, CELL_SEEK_SET, pos.addr());
|
||||
cellFsRead(fd, buffer.addr(), buffer.size(), nread.addr());
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -168,21 +168,21 @@ int cellJpgDecDecodeData(u32 mainHandle, u32 subHandle, mem8_ptr_t data, const m
|
|||
const CellJpgDecOutParam& current_outParam = subHandle_data->outParam;
|
||||
|
||||
//Copy the JPG file to a buffer
|
||||
MemoryAllocator<unsigned char> jpg(fileSize);
|
||||
MemoryAllocator<u64> pos, nread;
|
||||
vm::var<unsigned char[]> jpg(fileSize);
|
||||
vm::var<u64> pos, nread;
|
||||
|
||||
switch(subHandle_data->src.srcSelect.ToBE())
|
||||
{
|
||||
case se32(CELL_JPGDEC_BUFFER):
|
||||
if (!Memory.Copy(jpg.GetAddr(), subHandle_data->src.streamPtr.ToLE(), jpg.GetSize())) {
|
||||
if (!Memory.Copy(jpg.addr(), subHandle_data->src.streamPtr.ToLE(), jpg.size())) {
|
||||
cellJpgDec->Error("cellJpgDecDecodeData() failed (I)");
|
||||
return CELL_EFAULT;
|
||||
}
|
||||
break;
|
||||
|
||||
case se32(CELL_JPGDEC_FILE):
|
||||
cellFsLseek(fd, 0, CELL_SEEK_SET, pos.GetAddr());
|
||||
cellFsRead(fd, jpg.GetAddr(), jpg.GetSize(), nread);
|
||||
cellFsLseek(fd, 0, CELL_SEEK_SET, pos.addr());
|
||||
cellFsRead(fd, jpg.addr(), jpg.size(), nread.addr());
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -190,7 +190,7 @@ int cellJpgDecDecodeData(u32 mainHandle, u32 subHandle, mem8_ptr_t data, const m
|
|||
int width, height, actual_components;
|
||||
auto image = std::unique_ptr<unsigned char,decltype(&::free)>
|
||||
(
|
||||
stbi_load_from_memory(jpg.GetPtr(), fileSize, &width, &height, &actual_components, 4),
|
||||
stbi_load_from_memory(jpg.ptr(), fileSize, &width, &height, &actual_components, 4),
|
||||
&::free
|
||||
);
|
||||
|
||||
|
|
|
@ -13,9 +13,10 @@ Module *cellPngDec = nullptr;
|
|||
|
||||
static std::map<u32, CellPngDecMainHandle *> cellPngDecMap;
|
||||
|
||||
CellPngDecMainHandle *getCellPngDecCtx(u32 mainHandle) {
|
||||
CellPngDecMainHandle *getCellPngDecCtx(u32 mainHandle)
|
||||
{
|
||||
if (cellPngDecMap.find(mainHandle) == cellPngDecMap.end())
|
||||
return NULL;
|
||||
return nullptr;
|
||||
|
||||
return cellPngDecMap[mainHandle];
|
||||
}
|
||||
|
@ -71,14 +72,14 @@ int cellPngDecOpen(u32 mainHandle, mem32_t subHandle, mem_ptr_t<CellPngDecSrc> s
|
|||
|
||||
case se32(CELL_PNGDEC_FILE):
|
||||
// Get file descriptor
|
||||
MemoryAllocator<be_t<u32>> fd;
|
||||
int ret = cellFsOpen(src->fileName_addr, 0, fd.GetAddr(), 0, 0);
|
||||
vm::var<be_t<u32>> fd;
|
||||
int ret = cellFsOpen(src->fileName_addr, 0, fd.addr(), 0, 0);
|
||||
current_subHandle->fd = fd->ToLE();
|
||||
if(ret != CELL_OK) return CELL_PNGDEC_ERROR_OPEN_FILE;
|
||||
|
||||
// Get size of file
|
||||
MemoryAllocator<CellFsStat> sb; // Alloc a CellFsStat struct
|
||||
ret = cellFsFstat(current_subHandle->fd, sb.GetAddr());
|
||||
vm::var<CellFsStat> sb; // Alloc a CellFsStat struct
|
||||
ret = cellFsFstat(current_subHandle->fd, sb.addr());
|
||||
if(ret != CELL_OK) return ret;
|
||||
current_subHandle->fileSize = sb->st_size; // Get CellFsStat.st_size
|
||||
break;
|
||||
|
@ -97,12 +98,12 @@ int cellPngDecExtOpen(u32 mainHandle, mem32_t subHandle, mem_ptr_t<CellPngDecSrc
|
|||
|
||||
cellPngDec->Warning("*** cbCtrlStrm->cbCtrlStrmFunc_addr=0x%x", cbCtrlStrm->cbCtrlStrmFunc.GetAddr());
|
||||
|
||||
MemoryAllocator<CellPngDecStrmInfo> streamInfo;
|
||||
MemoryAllocator<CellPngDecStrmParam> streamParam;
|
||||
vm::var<CellPngDecStrmInfo> streamInfo;
|
||||
vm::var<CellPngDecStrmParam> streamParam;
|
||||
|
||||
int res = cellPngDecOpen(mainHandle, subHandle, src, openInfo);
|
||||
|
||||
if (!res) cbCtrlStrm->cbCtrlStrmFunc(streamInfo.GetAddr(), streamParam.GetAddr(), cbCtrlStrm->cbCtrlStrmArg);
|
||||
if (!res) cbCtrlStrm->cbCtrlStrmFunc(streamInfo.addr(), streamParam.addr(), cbCtrlStrm->cbCtrlStrmArg);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
@ -139,32 +140,33 @@ int cellPngDecReadHeader(u32 mainHandle, u32 subHandle, mem_ptr_t<CellPngDecInfo
|
|||
if(fileSize < 29) return CELL_PNGDEC_ERROR_HEADER; // Error: The file is smaller than the length of a PNG header
|
||||
|
||||
//Write the header to buffer
|
||||
MemoryAllocator<be_t<u32>> buffer(34); // Alloc buffer for PNG header
|
||||
MemoryAllocator<be_t<u64>> pos, nread;
|
||||
vm::var<u8[34]> buffer; // Alloc buffer for PNG header
|
||||
auto buffer_32 = buffer.To<be_t<u32>>();
|
||||
vm::var<be_t<u64>> pos, nread;
|
||||
|
||||
switch(subHandle_data->src.srcSelect.ToBE())
|
||||
{
|
||||
case se32(CELL_PNGDEC_BUFFER):
|
||||
if (!Memory.Copy(buffer.GetAddr(), subHandle_data->src.streamPtr.ToLE(), buffer.GetSize()))
|
||||
if (!Memory.Copy(buffer.addr(), subHandle_data->src.streamPtr.ToLE(), buffer.size()))
|
||||
{
|
||||
cellPngDec->Error("cellPngDecReadHeader() failed ()");
|
||||
return CELL_EFAULT;
|
||||
}
|
||||
break;
|
||||
case se32(CELL_PNGDEC_FILE):
|
||||
cellFsLseek(fd, 0, CELL_SEEK_SET, pos.GetAddr());
|
||||
cellFsRead(fd, buffer.GetAddr(), buffer.GetSize(), nread.GetAddr());
|
||||
cellFsLseek(fd, 0, CELL_SEEK_SET, pos.addr());
|
||||
cellFsRead(fd, buffer.addr(), buffer.size(), nread.addr());
|
||||
break;
|
||||
}
|
||||
|
||||
if (buffer[0] != 0x89504E47 ||
|
||||
buffer[1] != 0x0D0A1A0A || // Error: The first 8 bytes are not a valid PNG signature
|
||||
buffer[3] != 0x49484452) // Error: The PNG file does not start with an IHDR chunk
|
||||
if (buffer_32[0].ToBE() != se32(0x89504E47) ||
|
||||
buffer_32[1].ToBE() != se32(0x0D0A1A0A) || // Error: The first 8 bytes are not a valid PNG signature
|
||||
buffer_32[3].ToBE() != se32(0x49484452)) // Error: The PNG file does not start with an IHDR chunk
|
||||
{
|
||||
return CELL_PNGDEC_ERROR_HEADER;
|
||||
}
|
||||
|
||||
switch (buffer.To<u8>()[25])
|
||||
switch (buffer[25])
|
||||
{
|
||||
case 0: current_info.colorSpace = CELL_PNGDEC_GRAYSCALE; current_info.numComponents = 1; break;
|
||||
case 2: current_info.colorSpace = CELL_PNGDEC_RGB; current_info.numComponents = 3; break;
|
||||
|
@ -174,10 +176,10 @@ int cellPngDecReadHeader(u32 mainHandle, u32 subHandle, mem_ptr_t<CellPngDecInfo
|
|||
default: return CELL_PNGDEC_ERROR_HEADER; // Not supported color type
|
||||
}
|
||||
|
||||
current_info.imageWidth = buffer[4];
|
||||
current_info.imageHeight = buffer[5];
|
||||
current_info.bitDepth = buffer.To<u8>()[24];
|
||||
current_info.interlaceMethod = buffer.To<u8>()[28];
|
||||
current_info.imageWidth = buffer_32[4];
|
||||
current_info.imageHeight = buffer_32[5];
|
||||
current_info.bitDepth = buffer[24];
|
||||
current_info.interlaceMethod = buffer[28];
|
||||
current_info.chunkInformation = 0; // Unimplemented
|
||||
|
||||
*info = current_info;
|
||||
|
@ -208,21 +210,21 @@ int cellPngDecDecodeData(u32 mainHandle, u32 subHandle, mem8_ptr_t data, const m
|
|||
const CellPngDecOutParam& current_outParam = subHandle_data->outParam;
|
||||
|
||||
//Copy the PNG file to a buffer
|
||||
MemoryAllocator<unsigned char> png(fileSize);
|
||||
MemoryAllocator<u64> pos, nread;
|
||||
vm::var<unsigned char[]> png(fileSize);
|
||||
vm::var<u64> pos, nread;
|
||||
|
||||
switch(subHandle_data->src.srcSelect.ToBE())
|
||||
{
|
||||
case se32(CELL_PNGDEC_BUFFER):
|
||||
if (!Memory.Copy(png.GetAddr(), subHandle_data->src.streamPtr.ToLE(), png.GetSize())) {
|
||||
if (!Memory.Copy(png.addr(), subHandle_data->src.streamPtr.ToLE(), png.size())) {
|
||||
cellPngDec->Error("cellPngDecDecodeData() failed (I)");
|
||||
return CELL_EFAULT;
|
||||
}
|
||||
break;
|
||||
|
||||
case se32(CELL_PNGDEC_FILE):
|
||||
cellFsLseek(fd, 0, CELL_SEEK_SET, pos.GetAddr());
|
||||
cellFsRead(fd, png.GetAddr(), png.GetSize(), nread.GetAddr());
|
||||
cellFsLseek(fd, 0, CELL_SEEK_SET, pos.addr());
|
||||
cellFsRead(fd, png.addr(), png.size(), nread.addr());
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -230,7 +232,7 @@ int cellPngDecDecodeData(u32 mainHandle, u32 subHandle, mem8_ptr_t data, const m
|
|||
int width, height, actual_components;
|
||||
auto image = std::unique_ptr<unsigned char,decltype(&::free)>
|
||||
(
|
||||
stbi_load_from_memory(png.GetPtr(), fileSize, &width, &height, &actual_components, 4),
|
||||
stbi_load_from_memory(png.ptr(), fileSize, &width, &height, &actual_components, 4),
|
||||
&::free
|
||||
);
|
||||
if (!image) return CELL_PNGDEC_ERROR_STREAM_FORMAT;
|
||||
|
|
|
@ -839,13 +839,13 @@ int cellRescSetDisplayMode(u32 displayMode)
|
|||
else m_pCFragmentShader = m_pCFragmentShaderArray[RESC_SHADER_DEFAULT_BILINEAR];
|
||||
}*/
|
||||
|
||||
MemoryAllocator<CellVideoOutConfiguration> videocfg;
|
||||
vm::var<CellVideoOutConfiguration> videocfg;
|
||||
videocfg->resolutionId = RescBufferMode2SysutilResolutionId(s_rescInternalInstance->m_dstMode);
|
||||
videocfg->format = RescDstFormat2SysutilFormat(s_rescInternalInstance->m_pRescDsts->format );
|
||||
videocfg->aspect = CELL_VIDEO_OUT_ASPECT_AUTO;
|
||||
videocfg->pitch = s_rescInternalInstance->m_dstPitch;
|
||||
|
||||
cellVideoOutConfigure(CELL_VIDEO_OUT_PRIMARY, videocfg.GetAddr(), 0, 0);
|
||||
cellVideoOutConfigure(CELL_VIDEO_OUT_PRIMARY, videocfg.addr(), 0, 0);
|
||||
|
||||
if (IsPalInterpolate())
|
||||
{
|
||||
|
@ -1141,8 +1141,8 @@ int cellRescSetBufferAddress(mem32_t colorBuffers, mem32_t vertexArray, mem32_t
|
|||
s_rescInternalInstance->m_vertexArrayEA = vertexArray.GetAddr();
|
||||
s_rescInternalInstance->m_fragmentUcodeEA = fragmentShader.GetAddr();
|
||||
|
||||
MemoryAllocator<be_t<u32>> dstOffset;
|
||||
cellGcmAddressToOffset(s_rescInternalInstance->m_colorBuffersEA, dstOffset.GetAddr());
|
||||
vm::var<be_t<u32>> dstOffset;
|
||||
cellGcmAddressToOffset(s_rescInternalInstance->m_colorBuffersEA, dstOffset.addr());
|
||||
|
||||
for (int i=0; i<GetNumColorBuffers(); i++)
|
||||
{
|
||||
|
|
|
@ -702,10 +702,10 @@ int cellHddGameCheck(u32 version, u32 dirName_addr, u32 errDialog, mem_func_ptr_
|
|||
if (dirName.size() != 9)
|
||||
return CELL_HDDGAME_ERROR_PARAM;
|
||||
|
||||
MemoryAllocator<CellHddGameSystemFileParam> param;
|
||||
MemoryAllocator<CellHddGameCBResult> result;
|
||||
MemoryAllocator<CellHddGameStatGet> get;
|
||||
MemoryAllocator<CellHddGameStatSet> set;
|
||||
vm::var<CellHddGameSystemFileParam> param;
|
||||
vm::var<CellHddGameCBResult> result;
|
||||
vm::var<CellHddGameStatGet> get;
|
||||
vm::var<CellHddGameStatSet> set;
|
||||
|
||||
get->hddFreeSizeKB = 40 * 1024 * 1024; // 40 GB, TODO: Use the free space of the computer's HDD where RPCS3 is being run.
|
||||
get->isNewData = CELL_HDDGAME_ISNEWDATA_EXIST;
|
||||
|
@ -751,7 +751,7 @@ int cellHddGameCheck(u32 version, u32 dirName_addr, u32 errDialog, mem_func_ptr_
|
|||
|
||||
// TODO ?
|
||||
|
||||
funcStat(result.GetAddr(), get.GetAddr(), set.GetAddr());
|
||||
funcStat(result.addr(), get.addr(), set.addr());
|
||||
if (result->result != CELL_HDDGAME_CBRESULT_OK &&
|
||||
result->result != CELL_HDDGAME_CBRESULT_OK_CANCEL)
|
||||
return CELL_HDDGAME_ERROR_CBRESULT;
|
||||
|
|
|
@ -217,8 +217,8 @@ void getSaveDataStat(SaveDataEntry entry, mem_ptr_t<CellSaveDataStatGet> statGet
|
|||
|
||||
s32 modifySaveDataFiles(mem_func_ptr_t<CellSaveDataFileCallback>& funcFile, mem_ptr_t<CellSaveDataCBResult> result, const std::string& saveDataDir)
|
||||
{
|
||||
MemoryAllocator<CellSaveDataFileGet> fileGet;
|
||||
MemoryAllocator<CellSaveDataFileSet> fileSet;
|
||||
vm::var<CellSaveDataFileGet> fileGet;
|
||||
vm::var<CellSaveDataFileSet> fileSet;
|
||||
|
||||
if (!Emu.GetVFS().ExistsDir(saveDataDir))
|
||||
Emu.GetVFS().CreateDir(saveDataDir);
|
||||
|
@ -226,7 +226,7 @@ s32 modifySaveDataFiles(mem_func_ptr_t<CellSaveDataFileCallback>& funcFile, mem_
|
|||
fileGet->excSize = 0;
|
||||
while (true)
|
||||
{
|
||||
funcFile(result.GetAddr(), fileGet.GetAddr(), fileSet.GetAddr());
|
||||
funcFile(result.GetAddr(), fileGet.addr(), fileSet.addr());
|
||||
if (result->result < 0) {
|
||||
LOG_ERROR(HLE, "modifySaveDataFiles: CellSaveDataFileCallback failed."); // TODO: Once we verify that the entire SysCall is working, delete this debug error message.
|
||||
return CELL_SAVEDATA_ERROR_CBRESULT;
|
||||
|
@ -298,11 +298,11 @@ int cellSaveDataListSave2(u32 version, mem_ptr_t<CellSaveDataSetList> setList, m
|
|||
if (!setList.IsGood() || !setBuf.IsGood() || !funcList.IsGood() || !funcStat.IsGood() || !funcFile.IsGood())
|
||||
return CELL_SAVEDATA_ERROR_PARAM;
|
||||
|
||||
MemoryAllocator<CellSaveDataCBResult> result;
|
||||
MemoryAllocator<CellSaveDataListGet> listGet;
|
||||
MemoryAllocator<CellSaveDataListSet> listSet;
|
||||
MemoryAllocator<CellSaveDataStatGet> statGet;
|
||||
MemoryAllocator<CellSaveDataStatSet> statSet;
|
||||
vm::var<CellSaveDataCBResult> result;
|
||||
vm::var<CellSaveDataListGet> listGet;
|
||||
vm::var<CellSaveDataListSet> listSet;
|
||||
vm::var<CellSaveDataStatGet> statGet;
|
||||
vm::var<CellSaveDataStatSet> statSet;
|
||||
|
||||
std::string saveBaseDir = "/dev_hdd0/home/00000001/savedata/"; // TODO: Get the path of the current user
|
||||
vfsDir dir(saveBaseDir);
|
||||
|
@ -330,13 +330,15 @@ int cellSaveDataListSave2(u32 version, mem_ptr_t<CellSaveDataSetList> setList, m
|
|||
std::sort(saveEntries.begin(), saveEntries.end(), sortSaveDataEntry(setList->sortType, setList->sortOrder));
|
||||
listGet->dirList.SetAddr(setBuf->buf_addr);
|
||||
CellSaveDataDirList* dirList = (CellSaveDataDirList*)Memory.VirtualToRealAddr(listGet->dirList.GetAddr());
|
||||
for (u32 i=0; i<saveEntries.size(); i++) {
|
||||
for (u32 i=0; i<saveEntries.size(); i++)
|
||||
{
|
||||
memcpy(dirList[i].dirName, saveEntries[i].dirName.c_str(), CELL_SAVEDATA_DIRNAME_SIZE);
|
||||
memcpy(dirList[i].listParam, saveEntries[i].listParam.c_str(), CELL_SAVEDATA_SYSP_LPARAM_SIZE);
|
||||
}
|
||||
|
||||
funcList(result.GetAddr(), listGet.GetAddr(), listSet.GetAddr());
|
||||
if (result->result < 0) {
|
||||
funcList(result.addr(), listGet.addr(), listSet.addr());
|
||||
if (result->result < 0)
|
||||
{
|
||||
LOG_ERROR(HLE, "cellSaveDataListSave2: CellSaveDataListCallback failed."); // TODO: Once we verify that the entire SysCall is working, delete this debug error message.
|
||||
return CELL_SAVEDATA_ERROR_CBRESULT;
|
||||
}
|
||||
|
@ -346,7 +348,8 @@ int cellSaveDataListSave2(u32 version, mem_ptr_t<CellSaveDataSetList> setList, m
|
|||
setSaveDataList(saveEntries, (u32)listSet->fixedList.GetAddr(), listSet->fixedListNum);
|
||||
if (listSet->newData.IsGood())
|
||||
addNewSaveDataEntry(saveEntries, (u32)listSet->newData.GetAddr());
|
||||
if (saveEntries.size() == 0) {
|
||||
if (saveEntries.size() == 0)
|
||||
{
|
||||
LOG_WARNING(HLE, "cellSaveDataListSave2: No save entries found!"); // TODO: Find a better way to handle this error
|
||||
return CELL_SAVEDATA_RET_OK;
|
||||
}
|
||||
|
@ -354,10 +357,10 @@ int cellSaveDataListSave2(u32 version, mem_ptr_t<CellSaveDataSetList> setList, m
|
|||
u32 focusIndex = focusSaveDataEntry(saveEntries, listSet->focusPosition);
|
||||
// TODO: Display the dialog here
|
||||
u32 selectedIndex = focusIndex; // TODO: Until the dialog is implemented, select always the focused entry
|
||||
getSaveDataStat(saveEntries[selectedIndex], statGet.GetAddr());
|
||||
getSaveDataStat(saveEntries[selectedIndex], statGet.addr());
|
||||
result->userdata_addr = userdata_addr;
|
||||
|
||||
funcStat(result.GetAddr(), statGet.GetAddr(), statSet.GetAddr());
|
||||
funcStat(result.addr(), statGet.addr(), statSet.addr());
|
||||
Memory.Free(statGet->fileList.GetAddr());
|
||||
if (result->result < 0) {
|
||||
LOG_ERROR(HLE, "cellSaveDataListLoad2: CellSaveDataStatCallback failed."); // TODO: Once we verify that the entire SysCall is working, delete this debug error message.
|
||||
|
@ -368,7 +371,7 @@ int cellSaveDataListSave2(u32 version, mem_ptr_t<CellSaveDataSetList> setList, m
|
|||
*/
|
||||
|
||||
// Enter the loop where the save files are read/created/deleted.
|
||||
s32 ret = modifySaveDataFiles(funcFile, result.GetAddr(), saveBaseDir + (char*)statGet->dir.dirName);
|
||||
s32 ret = modifySaveDataFiles(funcFile, result.addr(), saveBaseDir + (char*)statGet->dir.dirName);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -383,11 +386,11 @@ int cellSaveDataListLoad2(u32 version, mem_ptr_t<CellSaveDataSetList> setList, m
|
|||
if (!setList.IsGood() || !setBuf.IsGood() || !funcList.IsGood() || !funcStat.IsGood() || !funcFile.IsGood())
|
||||
return CELL_SAVEDATA_ERROR_PARAM;
|
||||
|
||||
MemoryAllocator<CellSaveDataCBResult> result;
|
||||
MemoryAllocator<CellSaveDataListGet> listGet;
|
||||
MemoryAllocator<CellSaveDataListSet> listSet;
|
||||
MemoryAllocator<CellSaveDataStatGet> statGet;
|
||||
MemoryAllocator<CellSaveDataStatSet> statSet;
|
||||
vm::var<CellSaveDataCBResult> result;
|
||||
vm::var<CellSaveDataListGet> listGet;
|
||||
vm::var<CellSaveDataListSet> listSet;
|
||||
vm::var<CellSaveDataStatGet> statGet;
|
||||
vm::var<CellSaveDataStatSet> statSet;
|
||||
|
||||
std::string saveBaseDir = "/dev_hdd0/home/00000001/savedata/"; // TODO: Get the path of the current user
|
||||
vfsDir dir(saveBaseDir);
|
||||
|
@ -415,13 +418,15 @@ int cellSaveDataListLoad2(u32 version, mem_ptr_t<CellSaveDataSetList> setList, m
|
|||
std::sort(saveEntries.begin(), saveEntries.end(), sortSaveDataEntry(setList->sortType, setList->sortOrder));
|
||||
listGet->dirList.SetAddr(setBuf->buf_addr);
|
||||
CellSaveDataDirList* dirList = (CellSaveDataDirList*)Memory.VirtualToRealAddr(listGet->dirList.GetAddr());
|
||||
for (u32 i=0; i<saveEntries.size(); i++) {
|
||||
for (u32 i=0; i<saveEntries.size(); i++)
|
||||
{
|
||||
memcpy(dirList[i].dirName, saveEntries[i].dirName.c_str(), CELL_SAVEDATA_DIRNAME_SIZE);
|
||||
memcpy(dirList[i].listParam, saveEntries[i].listParam.c_str(), CELL_SAVEDATA_SYSP_LPARAM_SIZE);
|
||||
}
|
||||
|
||||
funcList(result.GetAddr(), listGet.GetAddr(), listSet.GetAddr());
|
||||
if (result->result < 0) {
|
||||
funcList(result.addr(), listGet.addr(), listSet.addr());
|
||||
if (result->result < 0)
|
||||
{
|
||||
LOG_ERROR(HLE, "cellSaveDataListLoad2: CellSaveDataListCallback failed."); // TODO: Once we verify that the entire SysCall is working, delete this debug error message.
|
||||
return CELL_SAVEDATA_ERROR_CBRESULT;
|
||||
}
|
||||
|
@ -431,7 +436,8 @@ int cellSaveDataListLoad2(u32 version, mem_ptr_t<CellSaveDataSetList> setList, m
|
|||
setSaveDataList(saveEntries, (u32)listSet->fixedList.GetAddr(), listSet->fixedListNum);
|
||||
if (listSet->newData.IsGood())
|
||||
addNewSaveDataEntry(saveEntries, (u32)listSet->newData.GetAddr());
|
||||
if (saveEntries.size() == 0) {
|
||||
if (saveEntries.size() == 0)
|
||||
{
|
||||
LOG_WARNING(HLE, "cellSaveDataListLoad2: No save entries found!"); // TODO: Find a better way to handle this error
|
||||
return CELL_SAVEDATA_RET_OK;
|
||||
}
|
||||
|
@ -439,12 +445,13 @@ int cellSaveDataListLoad2(u32 version, mem_ptr_t<CellSaveDataSetList> setList, m
|
|||
u32 focusIndex = focusSaveDataEntry(saveEntries, listSet->focusPosition);
|
||||
// TODO: Display the dialog here
|
||||
u32 selectedIndex = focusIndex; // TODO: Until the dialog is implemented, select always the focused entry
|
||||
getSaveDataStat(saveEntries[selectedIndex], statGet.GetAddr());
|
||||
getSaveDataStat(saveEntries[selectedIndex], statGet.addr());
|
||||
result->userdata_addr = userdata_addr;
|
||||
|
||||
funcStat(result.GetAddr(), statGet.GetAddr(), statSet.GetAddr());
|
||||
funcStat(result.addr(), statGet.addr(), statSet.addr());
|
||||
Memory.Free(statGet->fileList.GetAddr());
|
||||
if (result->result < 0) {
|
||||
if (result->result < 0)
|
||||
{
|
||||
LOG_ERROR(HLE, "cellSaveDataListLoad2: CellSaveDataStatCallback failed."); // TODO: Once we verify that the entire SysCall is working, delete this debug error message.
|
||||
return CELL_SAVEDATA_ERROR_CBRESULT;
|
||||
}
|
||||
|
@ -453,7 +460,7 @@ int cellSaveDataListLoad2(u32 version, mem_ptr_t<CellSaveDataSetList> setList, m
|
|||
*/
|
||||
|
||||
// Enter the loop where the save files are read/created/deleted.
|
||||
s32 ret = modifySaveDataFiles(funcFile, result.GetAddr(), saveBaseDir + (char*)statGet->dir.dirName);
|
||||
s32 ret = modifySaveDataFiles(funcFile, result.addr(), saveBaseDir + (char*)statGet->dir.dirName);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -468,11 +475,11 @@ int cellSaveDataFixedSave2(u32 version, mem_ptr_t<CellSaveDataSetList> setList,
|
|||
if (!setList.IsGood() || !setBuf.IsGood() || !funcFixed.IsGood() || !funcStat.IsGood() || !funcFile.IsGood())
|
||||
return CELL_SAVEDATA_ERROR_PARAM;
|
||||
|
||||
MemoryAllocator<CellSaveDataCBResult> result;
|
||||
MemoryAllocator<CellSaveDataListGet> listGet;
|
||||
MemoryAllocator<CellSaveDataFixedSet> fixedSet;
|
||||
MemoryAllocator<CellSaveDataStatGet> statGet;
|
||||
MemoryAllocator<CellSaveDataStatSet> statSet;
|
||||
vm::var<CellSaveDataCBResult> result;
|
||||
vm::var<CellSaveDataListGet> listGet;
|
||||
vm::var<CellSaveDataFixedSet> fixedSet;
|
||||
vm::var<CellSaveDataStatGet> statGet;
|
||||
vm::var<CellSaveDataStatSet> statSet;
|
||||
|
||||
std::string saveBaseDir = "/dev_hdd0/home/00000001/savedata/"; // TODO: Get the path of the current user
|
||||
vfsDir dir(saveBaseDir);
|
||||
|
@ -500,23 +507,26 @@ int cellSaveDataFixedSave2(u32 version, mem_ptr_t<CellSaveDataSetList> setList,
|
|||
std::sort(saveEntries.begin(), saveEntries.end(), sortSaveDataEntry(setList->sortType, setList->sortOrder));
|
||||
listGet->dirList.SetAddr(setBuf->buf_addr);
|
||||
CellSaveDataDirList* dirList = (CellSaveDataDirList*)Memory.VirtualToRealAddr(listGet->dirList.GetAddr());
|
||||
for (u32 i = 0; i<saveEntries.size(); i++) {
|
||||
for (u32 i = 0; i<saveEntries.size(); i++)
|
||||
{
|
||||
memcpy(dirList[i].dirName, saveEntries[i].dirName.c_str(), CELL_SAVEDATA_DIRNAME_SIZE);
|
||||
memcpy(dirList[i].listParam, saveEntries[i].listParam.c_str(), CELL_SAVEDATA_SYSP_LPARAM_SIZE);
|
||||
}
|
||||
funcFixed(result.GetAddr(), listGet.GetAddr(), fixedSet.GetAddr());
|
||||
if (result->result < 0) {
|
||||
funcFixed(result.addr(), listGet.addr(), fixedSet.addr());
|
||||
if (result->result < 0)
|
||||
{
|
||||
LOG_ERROR(HLE, "cellSaveDataFixedSave2: CellSaveDataFixedCallback failed."); // TODO: Once we verify that the entire SysCall is working, delete this debug error message.
|
||||
return CELL_SAVEDATA_ERROR_CBRESULT;
|
||||
}
|
||||
setSaveDataFixed(saveEntries, fixedSet.GetAddr());
|
||||
getSaveDataStat(saveEntries[0], statGet.GetAddr()); // There should be only one element in this list
|
||||
setSaveDataFixed(saveEntries, fixedSet.addr());
|
||||
getSaveDataStat(saveEntries[0], statGet.addr()); // There should be only one element in this list
|
||||
// TODO: Display the Yes|No dialog here
|
||||
result->userdata_addr = userdata_addr;
|
||||
|
||||
funcStat(result.GetAddr(), statGet.GetAddr(), statSet.GetAddr());
|
||||
funcStat(result.addr(), statGet.addr(), statSet.addr());
|
||||
Memory.Free(statGet->fileList.GetAddr());
|
||||
if (result->result < 0) {
|
||||
if (result->result < 0)
|
||||
{
|
||||
LOG_ERROR(HLE, "cellSaveDataFixedSave2: CellSaveDataStatCallback failed."); // TODO: Once we verify that the entire SysCall is working, delete this debug error message.
|
||||
return CELL_SAVEDATA_ERROR_CBRESULT;
|
||||
}
|
||||
|
@ -525,7 +535,7 @@ int cellSaveDataFixedSave2(u32 version, mem_ptr_t<CellSaveDataSetList> setList,
|
|||
*/
|
||||
|
||||
// Enter the loop where the save files are read/created/deleted.
|
||||
s32 ret = modifySaveDataFiles(funcFile, result.GetAddr(), saveBaseDir + (char*)statGet->dir.dirName);
|
||||
s32 ret = modifySaveDataFiles(funcFile, result.addr(), saveBaseDir + (char*)statGet->dir.dirName);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -540,11 +550,11 @@ int cellSaveDataFixedLoad2(u32 version, mem_ptr_t<CellSaveDataSetList> setList,
|
|||
if (!setList.IsGood() || !setBuf.IsGood() || !funcFixed.IsGood() || !funcStat.IsGood() || !funcFile.IsGood())
|
||||
return CELL_SAVEDATA_ERROR_PARAM;
|
||||
|
||||
MemoryAllocator<CellSaveDataCBResult> result;
|
||||
MemoryAllocator<CellSaveDataListGet> listGet;
|
||||
MemoryAllocator<CellSaveDataFixedSet> fixedSet;
|
||||
MemoryAllocator<CellSaveDataStatGet> statGet;
|
||||
MemoryAllocator<CellSaveDataStatSet> statSet;
|
||||
vm::var<CellSaveDataCBResult> result;
|
||||
vm::var<CellSaveDataListGet> listGet;
|
||||
vm::var<CellSaveDataFixedSet> fixedSet;
|
||||
vm::var<CellSaveDataStatGet> statGet;
|
||||
vm::var<CellSaveDataStatSet> statSet;
|
||||
|
||||
std::string saveBaseDir = "/dev_hdd0/home/00000001/savedata/"; // TODO: Get the path of the current user
|
||||
vfsDir dir(saveBaseDir);
|
||||
|
@ -572,23 +582,26 @@ int cellSaveDataFixedLoad2(u32 version, mem_ptr_t<CellSaveDataSetList> setList,
|
|||
std::sort(saveEntries.begin(), saveEntries.end(), sortSaveDataEntry(setList->sortType, setList->sortOrder));
|
||||
listGet->dirList.SetAddr(setBuf->buf_addr);
|
||||
CellSaveDataDirList* dirList = (CellSaveDataDirList*)Memory.VirtualToRealAddr(listGet->dirList.GetAddr());
|
||||
for (u32 i = 0; i<saveEntries.size(); i++) {
|
||||
for (u32 i = 0; i<saveEntries.size(); i++)
|
||||
{
|
||||
memcpy(dirList[i].dirName, saveEntries[i].dirName.c_str(), CELL_SAVEDATA_DIRNAME_SIZE);
|
||||
memcpy(dirList[i].listParam, saveEntries[i].listParam.c_str(), CELL_SAVEDATA_SYSP_LPARAM_SIZE);
|
||||
}
|
||||
funcFixed(result.GetAddr(), listGet.GetAddr(), fixedSet.GetAddr());
|
||||
if (result->result < 0) {
|
||||
funcFixed(result.addr(), listGet.addr(), fixedSet.addr());
|
||||
if (result->result < 0)
|
||||
{
|
||||
LOG_ERROR(HLE, "cellSaveDataFixedLoad2: CellSaveDataFixedCallback failed."); // TODO: Once we verify that the entire SysCall is working, delete this debug error message.
|
||||
return CELL_SAVEDATA_ERROR_CBRESULT;
|
||||
}
|
||||
setSaveDataFixed(saveEntries, fixedSet.GetAddr());
|
||||
getSaveDataStat(saveEntries[0], statGet.GetAddr()); // There should be only one element in this list
|
||||
setSaveDataFixed(saveEntries, fixedSet.addr());
|
||||
getSaveDataStat(saveEntries[0], statGet.addr()); // There should be only one element in this list
|
||||
// TODO: Display the Yes|No dialog here
|
||||
result->userdata_addr = userdata_addr;
|
||||
|
||||
funcStat(result.GetAddr(), statGet.GetAddr(), statSet.GetAddr());
|
||||
funcStat(result.addr(), statGet.addr(), statSet.addr());
|
||||
Memory.Free(statGet->fileList.GetAddr());
|
||||
if (result->result < 0) {
|
||||
if (result->result < 0)
|
||||
{
|
||||
LOG_ERROR(HLE, "cellSaveDataFixedLoad2: CellSaveDataStatCallback failed."); // TODO: Once we verify that the entire SysCall is working, delete this debug error message.
|
||||
return CELL_SAVEDATA_ERROR_CBRESULT;
|
||||
}
|
||||
|
@ -597,7 +610,7 @@ int cellSaveDataFixedLoad2(u32 version, mem_ptr_t<CellSaveDataSetList> setList,
|
|||
*/
|
||||
|
||||
// Enter the loop where the save files are read/created/deleted.
|
||||
s32 ret = modifySaveDataFiles(funcFile, result.GetAddr(), saveBaseDir + (char*)statGet->dir.dirName);
|
||||
s32 ret = modifySaveDataFiles(funcFile, result.addr(), saveBaseDir + (char*)statGet->dir.dirName);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -612,9 +625,9 @@ int cellSaveDataAutoSave2(u32 version, u32 dirName_addr, u32 errDialog, mem_ptr_
|
|||
if (!setBuf.IsGood() || !funcStat.IsGood() || !funcFile.IsGood())
|
||||
return CELL_SAVEDATA_ERROR_PARAM;
|
||||
|
||||
MemoryAllocator<CellSaveDataCBResult> result;
|
||||
MemoryAllocator<CellSaveDataStatGet> statGet;
|
||||
MemoryAllocator<CellSaveDataStatSet> statSet;
|
||||
vm::var<CellSaveDataCBResult> result;
|
||||
vm::var<CellSaveDataStatGet> statGet;
|
||||
vm::var<CellSaveDataStatSet> statSet;
|
||||
|
||||
std::string saveBaseDir = "/dev_hdd0/home/00000001/savedata/"; // TODO: Get the path of the current user
|
||||
vfsDir dir(saveBaseDir);
|
||||
|
@ -625,13 +638,15 @@ int cellSaveDataAutoSave2(u32 version, u32 dirName_addr, u32 errDialog, mem_ptr_
|
|||
std::vector<SaveDataEntry> saveEntries;
|
||||
for (const DirEntryInfo* entry = dir.Read(); entry; entry = dir.Read())
|
||||
{
|
||||
if (entry->flags & DirEntry_TypeDir && entry->name == dirName) {
|
||||
if (entry->flags & DirEntry_TypeDir && entry->name == dirName)
|
||||
{
|
||||
addSaveDataEntry(saveEntries, saveBaseDir+dirName);
|
||||
}
|
||||
}
|
||||
|
||||
// The target entry does not exist
|
||||
if (saveEntries.size() == 0) {
|
||||
if (saveEntries.size() == 0)
|
||||
{
|
||||
SaveDataEntry entry;
|
||||
entry.dirName = dirName;
|
||||
entry.sizeKB = 0;
|
||||
|
@ -639,12 +654,13 @@ int cellSaveDataAutoSave2(u32 version, u32 dirName_addr, u32 errDialog, mem_ptr_
|
|||
saveEntries.push_back(entry);
|
||||
}
|
||||
|
||||
getSaveDataStat(saveEntries[0], statGet.GetAddr()); // There should be only one element in this list
|
||||
getSaveDataStat(saveEntries[0], statGet.addr()); // There should be only one element in this list
|
||||
result->userdata_addr = userdata_addr;
|
||||
funcStat(result.GetAddr(), statGet.GetAddr(), statSet.GetAddr());
|
||||
funcStat(result.addr(), statGet.addr(), statSet.addr());
|
||||
|
||||
Memory.Free(statGet->fileList.GetAddr());
|
||||
if (result->result < 0) {
|
||||
if (result->result < 0)
|
||||
{
|
||||
LOG_ERROR(HLE, "cellSaveDataAutoSave2: CellSaveDataStatCallback failed."); // TODO: Once we verify that the entire SysCall is working, delete this debug error message.
|
||||
return CELL_SAVEDATA_ERROR_CBRESULT;
|
||||
}
|
||||
|
@ -653,7 +669,7 @@ int cellSaveDataAutoSave2(u32 version, u32 dirName_addr, u32 errDialog, mem_ptr_
|
|||
*/
|
||||
|
||||
// Enter the loop where the save files are read/created/deleted.
|
||||
s32 ret = modifySaveDataFiles(funcFile, result.GetAddr(), saveBaseDir + (char*)statGet->dir.dirName);
|
||||
s32 ret = modifySaveDataFiles(funcFile, result.addr(), saveBaseDir + (char*)statGet->dir.dirName);
|
||||
|
||||
return CELL_SAVEDATA_RET_OK;
|
||||
}
|
||||
|
@ -668,9 +684,9 @@ int cellSaveDataAutoLoad2(u32 version, u32 dirName_addr, u32 errDialog, mem_ptr_
|
|||
if (!setBuf.IsGood() || !funcStat.IsGood() || !funcFile.IsGood())
|
||||
return CELL_SAVEDATA_ERROR_PARAM;
|
||||
|
||||
MemoryAllocator<CellSaveDataCBResult> result;
|
||||
MemoryAllocator<CellSaveDataStatGet> statGet;
|
||||
MemoryAllocator<CellSaveDataStatSet> statSet;
|
||||
vm::var<CellSaveDataCBResult> result;
|
||||
vm::var<CellSaveDataStatGet> statGet;
|
||||
vm::var<CellSaveDataStatSet> statSet;
|
||||
|
||||
std::string saveBaseDir = "/dev_hdd0/home/00000001/savedata/"; // TODO: Get the path of the current user
|
||||
vfsDir dir(saveBaseDir);
|
||||
|
@ -681,7 +697,8 @@ int cellSaveDataAutoLoad2(u32 version, u32 dirName_addr, u32 errDialog, mem_ptr_
|
|||
std::vector<SaveDataEntry> saveEntries;
|
||||
for (const DirEntryInfo* entry = dir.Read(); entry; entry = dir.Read())
|
||||
{
|
||||
if (entry->flags & DirEntry_TypeDir && entry->name == dirName) {
|
||||
if (entry->flags & DirEntry_TypeDir && entry->name == dirName)
|
||||
{
|
||||
addSaveDataEntry(saveEntries, saveBaseDir+dirName);
|
||||
}
|
||||
}
|
||||
|
@ -692,12 +709,13 @@ int cellSaveDataAutoLoad2(u32 version, u32 dirName_addr, u32 errDialog, mem_ptr_
|
|||
return CELL_OK; // TODO: Can anyone check the actual behaviour of a PS3 when saves are not found?
|
||||
}
|
||||
|
||||
getSaveDataStat(saveEntries[0], statGet.GetAddr()); // There should be only one element in this list
|
||||
getSaveDataStat(saveEntries[0], statGet.addr()); // There should be only one element in this list
|
||||
result->userdata_addr = userdata_addr;
|
||||
funcStat(result.GetAddr(), statGet.GetAddr(), statSet.GetAddr());
|
||||
funcStat(result.addr(), statGet.addr(), statSet.addr());
|
||||
|
||||
Memory.Free(statGet->fileList.GetAddr());
|
||||
if (result->result < 0) {
|
||||
if (result->result < 0)
|
||||
{
|
||||
LOG_ERROR(HLE, "cellSaveDataAutoLoad2: CellSaveDataStatCallback failed."); // TODO: Once we verify that the entire SysCall is working, delete this debug error message.
|
||||
return CELL_SAVEDATA_ERROR_CBRESULT;
|
||||
}
|
||||
|
@ -706,7 +724,7 @@ int cellSaveDataAutoLoad2(u32 version, u32 dirName_addr, u32 errDialog, mem_ptr_
|
|||
*/
|
||||
|
||||
// Enter the loop where the save files are read/created/deleted.
|
||||
s32 ret = modifySaveDataFiles(funcFile, result.GetAddr(), saveBaseDir + (char*)statGet->dir.dirName);
|
||||
s32 ret = modifySaveDataFiles(funcFile, result.addr(), saveBaseDir + (char*)statGet->dir.dirName);
|
||||
|
||||
return CELL_SAVEDATA_RET_OK;
|
||||
}
|
||||
|
|
|
@ -246,14 +246,14 @@ int cellFsReadWithOffset(u32 fd, u64 offset, u32 buf_addr, u64 buffer_size, mem6
|
|||
fd, offset, buf_addr, buffer_size, nread.GetAddr());
|
||||
|
||||
int ret;
|
||||
MemoryAllocator<be_t<u64>> oldPos, newPos;
|
||||
ret = cellFsLseek(fd, 0, CELL_SEEK_CUR, oldPos.GetAddr()); // Save the current position
|
||||
vm::var<be_t<u64>> oldPos, newPos;
|
||||
ret = cellFsLseek(fd, 0, CELL_SEEK_CUR, oldPos.addr()); // Save the current position
|
||||
if (ret) return ret;
|
||||
ret = cellFsLseek(fd, offset, CELL_SEEK_SET, newPos.GetAddr()); // Move to the specified offset
|
||||
ret = cellFsLseek(fd, offset, CELL_SEEK_SET, newPos.addr()); // Move to the specified offset
|
||||
if (ret) return ret;
|
||||
ret = cellFsRead(fd, buf_addr, buffer_size, nread.GetAddr()); // Read the file
|
||||
if (ret) return ret;
|
||||
ret = cellFsLseek(fd, Memory.Read64(oldPos.GetAddr()), CELL_SEEK_SET, newPos.GetAddr()); // Return to the old position
|
||||
ret = cellFsLseek(fd, oldPos.value(), CELL_SEEK_SET, newPos.addr()); // Return to the old position
|
||||
if (ret) return ret;
|
||||
|
||||
return CELL_OK;
|
||||
|
|
|
@ -313,6 +313,10 @@
|
|||
<ClInclude Include="Emu\Memory\DynamicMemoryBlockBase.h" />
|
||||
<ClInclude Include="Emu\Memory\Memory.h" />
|
||||
<ClInclude Include="Emu\Memory\MemoryBlock.h" />
|
||||
<ClInclude Include="Emu\Memory\vm.h" />
|
||||
<ClInclude Include="Emu\Memory\vm_ptr.h" />
|
||||
<ClInclude Include="Emu\Memory\vm_ref.h" />
|
||||
<ClInclude Include="Emu\Memory\vm_var.h" />
|
||||
<ClInclude Include="Emu\SysCalls\Callback.h" />
|
||||
<ClInclude Include="Emu\SysCalls\ErrorCodes.h" />
|
||||
<ClInclude Include="Emu\SysCalls\LogBase.h" />
|
||||
|
|
|
@ -1096,5 +1096,17 @@
|
|||
<ClInclude Include="Emu\SysCalls\LogBase.h">
|
||||
<Filter>Emu\SysCalls</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Emu\Memory\vm.h">
|
||||
<Filter>Emu\Memory</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Emu\Memory\vm_ptr.h">
|
||||
<Filter>Emu\Memory</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Emu\Memory\vm_ref.h">
|
||||
<Filter>Emu\Memory</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Emu\Memory\vm_var.h">
|
||||
<Filter>Emu\Memory</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
Loading…
Add table
Reference in a new issue