forked from eden-emu/eden
		
	hle: Implement ConvertSessionToDomain, various cleanups.
This commit is contained in:
		
							parent
							
								
									5e11c12766
								
							
						
					
					
						commit
						be299c7636
					
				
					 10 changed files with 82 additions and 33 deletions
				
			
		|  | @ -144,6 +144,16 @@ struct DataPayloadHeader { | ||||||
| }; | }; | ||||||
| static_assert(sizeof(DataPayloadHeader) == 8, "DataPayloadRequest size is incorrect"); | static_assert(sizeof(DataPayloadHeader) == 8, "DataPayloadRequest size is incorrect"); | ||||||
| 
 | 
 | ||||||
|  | struct DomainMessageHeader { | ||||||
|  |     union { | ||||||
|  |         BitField<0, 8, u32_le> command; | ||||||
|  |         BitField<16, 16, u32_le> size; | ||||||
|  |     }; | ||||||
|  |     u32_le object_id; | ||||||
|  |     INSERT_PADDING_WORDS(2); | ||||||
|  | }; | ||||||
|  | static_assert(sizeof(DomainMessageHeader) == 16, "DomainMessageHeader size is incorrect"); | ||||||
|  | 
 | ||||||
| enum DescriptorType : u32 { | enum DescriptorType : u32 { | ||||||
|     // Buffer related desciptors types (mask : 0x0F)
 |     // Buffer related desciptors types (mask : 0x0F)
 | ||||||
|     StaticBuffer = 0x02, |     StaticBuffer = 0x02, | ||||||
|  |  | ||||||
|  | @ -80,7 +80,7 @@ public: | ||||||
|         AlignWithPadding(); |         AlignWithPadding(); | ||||||
| 
 | 
 | ||||||
|         IPC::DataPayloadHeader data_payload_header{}; |         IPC::DataPayloadHeader data_payload_header{}; | ||||||
|         data_payload_header.magic = 0x4f434653; |         data_payload_header.magic = Common::MakeMagic('S', 'F', 'C', 'O'); | ||||||
|         PushRaw(data_payload_header); |         PushRaw(data_payload_header); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ | ||||||
| 
 | 
 | ||||||
| #include <boost/range/algorithm_ext/erase.hpp> | #include <boost/range/algorithm_ext/erase.hpp> | ||||||
| #include "common/assert.h" | #include "common/assert.h" | ||||||
|  | #include "common/common_funcs.h" | ||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
| #include "core/hle/ipc_helpers.h" | #include "core/hle/ipc_helpers.h" | ||||||
| #include "core/hle/kernel/handle_table.h" | #include "core/hle/kernel/handle_table.h" | ||||||
|  | @ -45,10 +46,15 @@ void HLERequestContext::ClearIncomingObjects() { | ||||||
|     request_handles.clear(); |     request_handles.clear(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf) { | void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) { | ||||||
|     IPC::RequestParser rp(src_cmdbuf); |     IPC::RequestParser rp(src_cmdbuf); | ||||||
|     command_header = std::make_unique<IPC::CommandHeader>(rp.PopRaw<IPC::CommandHeader>()); |     command_header = std::make_unique<IPC::CommandHeader>(rp.PopRaw<IPC::CommandHeader>()); | ||||||
| 
 | 
 | ||||||
|  |     if (command_header->type == IPC::CommandType::Close) { | ||||||
|  |         // Close does not populate the rest of the IPC header
 | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     // If handle descriptor is present, add size of it
 |     // If handle descriptor is present, add size of it
 | ||||||
|     if (command_header->enable_handle_descriptor) { |     if (command_header->enable_handle_descriptor) { | ||||||
|         handle_descriptor_header = |         handle_descriptor_header = | ||||||
|  | @ -80,9 +86,18 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf) { | ||||||
|         UNIMPLEMENTED(); |         UNIMPLEMENTED(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     if (incoming && Session()->IsDomain()) { | ||||||
|  |         domain_message_header = std::make_unique<IPC::DomainMessageHeader>(rp.PopRaw<IPC::DomainMessageHeader>()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     data_payload_header = |     data_payload_header = | ||||||
|         std::make_unique<IPC::DataPayloadHeader>(rp.PopRaw<IPC::DataPayloadHeader>()); |         std::make_unique<IPC::DataPayloadHeader>(rp.PopRaw<IPC::DataPayloadHeader>()); | ||||||
|     ASSERT(data_payload_header->magic == 0x49434653 || data_payload_header->magic == 0x4F434653); | 
 | ||||||
|  |     if (incoming) { | ||||||
|  |         ASSERT(data_payload_header->magic == Common::MakeMagic('S', 'F', 'C', 'I')); | ||||||
|  |     } else { | ||||||
|  |         ASSERT(data_payload_header->magic == Common::MakeMagic('S', 'F', 'C', 'O')); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     data_payload_offset = rp.GetCurrentOffset(); |     data_payload_offset = rp.GetCurrentOffset(); | ||||||
|     command = rp.Pop<u32_le>(); |     command = rp.Pop<u32_le>(); | ||||||
|  | @ -91,7 +106,7 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf) { | ||||||
| ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(u32_le* src_cmdbuf, | ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(u32_le* src_cmdbuf, | ||||||
|                                                                 Process& src_process, |                                                                 Process& src_process, | ||||||
|                                                                 HandleTable& src_table) { |                                                                 HandleTable& src_table) { | ||||||
|     ParseCommandBuffer(src_cmdbuf); |     ParseCommandBuffer(src_cmdbuf, true); | ||||||
|     size_t untranslated_size = data_payload_offset + command_header->data_size; |     size_t untranslated_size = data_payload_offset + command_header->data_size; | ||||||
|     std::copy_n(src_cmdbuf, untranslated_size, cmd_buf.begin()); |     std::copy_n(src_cmdbuf, untranslated_size, cmd_buf.begin()); | ||||||
| 
 | 
 | ||||||
|  | @ -106,7 +121,7 @@ ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(u32_le* src_cmdb | ||||||
| 
 | 
 | ||||||
| ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, Process& dst_process, | ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, Process& dst_process, | ||||||
|                                                            HandleTable& dst_table) { |                                                            HandleTable& dst_table) { | ||||||
|     ParseCommandBuffer(&cmd_buf[0]); |     ParseCommandBuffer(&cmd_buf[0], false); | ||||||
|     size_t untranslated_size = data_payload_offset + command_header->data_size; |     size_t untranslated_size = data_payload_offset + command_header->data_size; | ||||||
|     std::copy_n(cmd_buf.begin(), untranslated_size, dst_cmdbuf); |     std::copy_n(cmd_buf.begin(), untranslated_size, dst_cmdbuf); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -119,7 +119,7 @@ public: | ||||||
|      */ |      */ | ||||||
|     void ClearIncomingObjects(); |     void ClearIncomingObjects(); | ||||||
| 
 | 
 | ||||||
|     void ParseCommandBuffer(u32_le* src_cmdbuf); |     void ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming); | ||||||
| 
 | 
 | ||||||
|     /// Populates this context with data from the requesting process/thread.
 |     /// Populates this context with data from the requesting process/thread.
 | ||||||
|     ResultCode PopulateFromIncomingCommandBuffer(u32_le* src_cmdbuf, Process& src_process, |     ResultCode PopulateFromIncomingCommandBuffer(u32_le* src_cmdbuf, Process& src_process, | ||||||
|  | @ -149,6 +149,7 @@ private: | ||||||
|     std::unique_ptr<IPC::CommandHeader> command_header; |     std::unique_ptr<IPC::CommandHeader> command_header; | ||||||
|     std::unique_ptr<IPC::HandleDescriptorHeader> handle_descriptor_header; |     std::unique_ptr<IPC::HandleDescriptorHeader> handle_descriptor_header; | ||||||
|     std::unique_ptr<IPC::DataPayloadHeader> data_payload_header; |     std::unique_ptr<IPC::DataPayloadHeader> data_payload_header; | ||||||
|  |     std::unique_ptr<IPC::DomainMessageHeader> domain_message_header; | ||||||
| 
 | 
 | ||||||
|     unsigned data_payload_offset{}; |     unsigned data_payload_offset{}; | ||||||
|     u32_le command{}; |     u32_le command{}; | ||||||
|  |  | ||||||
|  | @ -91,6 +91,14 @@ public: | ||||||
|     /// TODO(Subv): Find a better name for this.
 |     /// TODO(Subv): Find a better name for this.
 | ||||||
|     SharedPtr<Thread> currently_handling; |     SharedPtr<Thread> currently_handling; | ||||||
| 
 | 
 | ||||||
|  |     void ConvertToDomain() { | ||||||
|  |         is_domain = true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     bool IsDomain() const { | ||||||
|  |         return is_domain; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
| private: | private: | ||||||
|     ServerSession(); |     ServerSession(); | ||||||
|     ~ServerSession() override; |     ~ServerSession() override; | ||||||
|  | @ -102,6 +110,8 @@ private: | ||||||
|      * @return The created server session |      * @return The created server session | ||||||
|      */ |      */ | ||||||
|     static ResultVal<SharedPtr<ServerSession>> Create(std::string name = "Unknown"); |     static ResultVal<SharedPtr<ServerSession>> Create(std::string name = "Unknown"); | ||||||
|  | 
 | ||||||
|  |     bool is_domain{}; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|  |  | ||||||
|  | @ -18,7 +18,7 @@ void InstallInterfaces(SM::ServiceManager& service_manager) { | ||||||
|  *  Inputs: |  *  Inputs: | ||||||
|  *      0: 0x00000000 |  *      0: 0x00000000 | ||||||
|  *  Outputs: |  *  Outputs: | ||||||
|  *      1: ResultCode |  *      0: ResultCode | ||||||
|  */ |  */ | ||||||
| void LM::Initialize(Kernel::HLERequestContext& ctx) { | void LM::Initialize(Kernel::HLERequestContext& ctx) { | ||||||
|     IPC::RequestBuilder rb{ctx, 1}; |     IPC::RequestBuilder rb{ctx, 1}; | ||||||
|  | @ -29,10 +29,6 @@ void LM::Initialize(Kernel::HLERequestContext& ctx) { | ||||||
| LM::LM() : ServiceFramework("lm") { | LM::LM() : ServiceFramework("lm") { | ||||||
|     static const FunctionInfo functions[] = { |     static const FunctionInfo functions[] = { | ||||||
|         {0x00000000, &LM::Initialize, "Initialize"}, |         {0x00000000, &LM::Initialize, "Initialize"}, | ||||||
|         {0x00000001, nullptr, "Unknown2"}, |  | ||||||
|         {0x00000002, nullptr, "Unknown3"}, |  | ||||||
|         {0x00000003, nullptr, "Unknown4"}, |  | ||||||
|         {0x00000004, nullptr, "Unknown5"}, |  | ||||||
|     }; |     }; | ||||||
|     RegisterHandlers(functions); |     RegisterHandlers(functions); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -10,11 +10,11 @@ | ||||||
| #include "core/hle/ipc.h" | #include "core/hle/ipc.h" | ||||||
| #include "core/hle/ipc_helpers.h" | #include "core/hle/ipc_helpers.h" | ||||||
| #include "core/hle/kernel/client_port.h" | #include "core/hle/kernel/client_port.h" | ||||||
|  | #include "core/hle/kernel/handle_table.h" | ||||||
| #include "core/hle/kernel/process.h" | #include "core/hle/kernel/process.h" | ||||||
| #include "core/hle/kernel/server_port.h" | #include "core/hle/kernel/server_port.h" | ||||||
| #include "core/hle/kernel/server_session.h" | #include "core/hle/kernel/server_session.h" | ||||||
| #include "core/hle/kernel/thread.h" | #include "core/hle/kernel/thread.h" | ||||||
| #include "core/hle/kernel/handle_table.h" |  | ||||||
| #include "core/hle/service/am/am.h" | #include "core/hle/service/am/am.h" | ||||||
| #include "core/hle/service/apm/apm.h" | #include "core/hle/service/apm/apm.h" | ||||||
| #include "core/hle/service/dsp_dsp.h" | #include "core/hle/service/dsp_dsp.h" | ||||||
|  | @ -82,7 +82,8 @@ void ServiceFrameworkBase::RegisterHandlersBase(const FunctionInfoBase* function | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void ServiceFrameworkBase::ReportUnimplementedFunction(Kernel::HLERequestContext& ctx, const FunctionInfoBase* info) { | void ServiceFrameworkBase::ReportUnimplementedFunction(Kernel::HLERequestContext& ctx, | ||||||
|  |                                                        const FunctionInfoBase* info) { | ||||||
|     auto cmd_buf = ctx.CommandBuffer(); |     auto cmd_buf = ctx.CommandBuffer(); | ||||||
|     std::string function_name = info == nullptr ? fmt::format("{:#08x}", cmd_buf[0]) : info->name; |     std::string function_name = info == nullptr ? fmt::format("{:#08x}", cmd_buf[0]) : info->name; | ||||||
| 
 | 
 | ||||||
|  | @ -96,7 +97,7 @@ void ServiceFrameworkBase::ReportUnimplementedFunction(Kernel::HLERequestContext | ||||||
| 
 | 
 | ||||||
|     LOG_ERROR(Service, "unknown / unimplemented %s", w.c_str()); |     LOG_ERROR(Service, "unknown / unimplemented %s", w.c_str()); | ||||||
|     // TODO(bunnei): Hack - ignore error
 |     // TODO(bunnei): Hack - ignore error
 | ||||||
|     IPC::RequestBuilder rb{ ctx, 1 }; |     IPC::RequestBuilder rb{ctx, 1}; | ||||||
|     rb.Push(RESULT_SUCCESS); |     rb.Push(RESULT_SUCCESS); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -107,13 +108,14 @@ void ServiceFrameworkBase::InvokeRequest(Kernel::HLERequestContext& ctx) { | ||||||
|         return ReportUnimplementedFunction(ctx, info); |         return ReportUnimplementedFunction(ctx, info); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     LOG_TRACE(Service, "%s", |     LOG_TRACE( | ||||||
|  |         Service, "%s", | ||||||
|         MakeFunctionString(info->name, GetServiceName().c_str(), ctx.CommandBuffer()).c_str()); |         MakeFunctionString(info->name, GetServiceName().c_str(), ctx.CommandBuffer()).c_str()); | ||||||
|     handler_invoker(this, info->handler_callback, ctx); |     handler_invoker(this, info->handler_callback, ctx); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void ServiceFrameworkBase::HandleSyncRequest(SharedPtr<ServerSession> server_session) { | void ServiceFrameworkBase::HandleSyncRequest(SharedPtr<ServerSession> server_session) { | ||||||
|     u32* cmd_buf = (u32*)Memory::GetPointer(Kernel::GetCurrentThread()->GetTLSAddress());; |     u32* cmd_buf = (u32*)Memory::GetPointer(Kernel::GetCurrentThread()->GetTLSAddress()); | ||||||
| 
 | 
 | ||||||
|     // TODO(yuriks): The kernel should be the one handling this as part of translation after
 |     // TODO(yuriks): The kernel should be the one handling this as part of translation after
 | ||||||
|     // everything else is migrated
 |     // everything else is migrated
 | ||||||
|  | @ -122,19 +124,16 @@ void ServiceFrameworkBase::HandleSyncRequest(SharedPtr<ServerSession> server_ses | ||||||
|                                               Kernel::g_handle_table); |                                               Kernel::g_handle_table); | ||||||
| 
 | 
 | ||||||
|     switch (context.GetCommandType()) { |     switch (context.GetCommandType()) { | ||||||
|     case IPC::CommandType::Close: |     case IPC::CommandType::Close: { | ||||||
|     { |  | ||||||
|         IPC::RequestBuilder rb{context, 1}; |         IPC::RequestBuilder rb{context, 1}; | ||||||
|         rb.Push(RESULT_SUCCESS); |         rb.Push(RESULT_SUCCESS); | ||||||
|         break; |         break; | ||||||
|     } |     } | ||||||
|     case IPC::CommandType::Control: |     case IPC::CommandType::Control: { | ||||||
|     { |  | ||||||
|         SM::g_service_manager->InvokeControlRequest(context); |         SM::g_service_manager->InvokeControlRequest(context); | ||||||
|         break; |         break; | ||||||
|     } |     } | ||||||
|     case IPC::CommandType::Request: |     case IPC::CommandType::Request: { | ||||||
|     { |  | ||||||
|         InvokeRequest(context); |         InvokeRequest(context); | ||||||
|         break; |         break; | ||||||
|     } |     } | ||||||
|  | @ -176,4 +175,4 @@ void Shutdown() { | ||||||
|     g_kernel_named_ports.clear(); |     g_kernel_named_ports.clear(); | ||||||
|     LOG_DEBUG(Service, "shutdown OK"); |     LOG_DEBUG(Service, "shutdown OK"); | ||||||
| } | } | ||||||
| } | } // namespace Service
 | ||||||
|  |  | ||||||
|  | @ -9,25 +9,43 @@ | ||||||
| namespace Service { | namespace Service { | ||||||
| namespace SM { | namespace SM { | ||||||
| 
 | 
 | ||||||
|  | /**
 | ||||||
|  |  * Controller::ConvertSessionToDomain service function | ||||||
|  |  *  Inputs: | ||||||
|  |  *      0: 0x00000000 | ||||||
|  |  *  Outputs: | ||||||
|  |  *      0: ResultCode | ||||||
|  |  *      2: Handle of domain | ||||||
|  |  */ | ||||||
|  | void Controller::ConvertSessionToDomain(Kernel::HLERequestContext& ctx) { | ||||||
|  |     ctx.Session()->ConvertToDomain(); | ||||||
|  |     IPC::RequestBuilder rb{ctx, 3}; | ||||||
|  |     rb.Push(RESULT_SUCCESS); | ||||||
|  |     rb.Skip(1, true); | ||||||
|  |     Kernel::Handle handle = Kernel::g_handle_table.Create(ctx.Session()).Unwrap(); | ||||||
|  |     rb.Push(handle); | ||||||
|  |     LOG_DEBUG(Service, "called, handle=0x%08x", handle); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /**
 | /**
 | ||||||
|  * Controller::QueryPointerBufferSize service function |  * Controller::QueryPointerBufferSize service function | ||||||
|  *  Inputs: |  *  Inputs: | ||||||
|  *      0: 0x00000003 |  *      0: 0x00000003 | ||||||
|  *  Outputs: |  *  Outputs: | ||||||
|  *      1: ResultCode |  *      0: ResultCode | ||||||
|  *      3: Size of memory |  *      2: Size of memory | ||||||
|  */ |  */ | ||||||
| void Controller::QueryPointerBufferSize(Kernel::HLERequestContext& ctx) { | void Controller::QueryPointerBufferSize(Kernel::HLERequestContext& ctx) { | ||||||
|     IPC::RequestBuilder rb{ctx, 2}; |     IPC::RequestBuilder rb{ctx, 3}; | ||||||
|     rb.Push(RESULT_SUCCESS); |     rb.Push(RESULT_SUCCESS); | ||||||
|     rb.Push(0x0U); |     rb.Skip(1, true); | ||||||
|     rb.Push(0x500U); |     rb.Push<u32>(0x500); | ||||||
|     LOG_WARNING(Service, "(STUBBED) called"); |     LOG_WARNING(Service, "(STUBBED) called"); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| Controller::Controller() : ServiceFramework("IpcController") { | Controller::Controller() : ServiceFramework("IpcController") { | ||||||
|     static const FunctionInfo functions[] = { |     static const FunctionInfo functions[] = { | ||||||
|         {0x00000000, nullptr, "ConvertSessionToDomain"}, |         {0x00000000, &Controller::ConvertSessionToDomain, "ConvertSessionToDomain"}, | ||||||
|         {0x00000001, nullptr, "ConvertDomainToSession"}, |         {0x00000001, nullptr, "ConvertDomainToSession"}, | ||||||
|         {0x00000002, nullptr, "DuplicateSession"}, |         {0x00000002, nullptr, "DuplicateSession"}, | ||||||
|         {0x00000003, &Controller::QueryPointerBufferSize, "QueryPointerBufferSize"}, |         {0x00000003, &Controller::QueryPointerBufferSize, "QueryPointerBufferSize"}, | ||||||
|  |  | ||||||
|  | @ -15,6 +15,7 @@ public: | ||||||
|     ~Controller(); |     ~Controller(); | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|  |     void ConvertSessionToDomain(Kernel::HLERequestContext& ctx); | ||||||
|     void QueryPointerBufferSize(Kernel::HLERequestContext& ctx); |     void QueryPointerBufferSize(Kernel::HLERequestContext& ctx); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -80,7 +80,7 @@ std::shared_ptr<ServiceManager> g_service_manager; | ||||||
|  *  Inputs: |  *  Inputs: | ||||||
|  *      0: 0x00000000 |  *      0: 0x00000000 | ||||||
|  *  Outputs: |  *  Outputs: | ||||||
|  *      1: ResultCode |  *      0: ResultCode | ||||||
|  */ |  */ | ||||||
| void SM::Initialize(Kernel::HLERequestContext& ctx) { | void SM::Initialize(Kernel::HLERequestContext& ctx) { | ||||||
|     IPC::RequestBuilder rb{ctx, 1}; |     IPC::RequestBuilder rb{ctx, 1}; | ||||||
|  | @ -89,15 +89,14 @@ void SM::Initialize(Kernel::HLERequestContext& ctx) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|  * SM::GetServiceHandle service function |  * SM::GetService service function | ||||||
|  *  Inputs: |  *  Inputs: | ||||||
|  *      0: 0x00000001 |  *      0: 0x00000001 | ||||||
|  *      1: Unknown |  *      1: Unknown | ||||||
|  *      2: Unknown |  *      2: Unknown | ||||||
|  *      3-4: 8-byte UTF-8 service name |  *      3-4: 8-byte UTF-8 service name | ||||||
|  *  Outputs: |  *  Outputs: | ||||||
|  *      1: ResultCode |  *      0: ResultCode | ||||||
|  *      3: Service handle |  | ||||||
|  */ |  */ | ||||||
| void SM::GetService(Kernel::HLERequestContext& ctx) { | void SM::GetService(Kernel::HLERequestContext& ctx) { | ||||||
|     IPC::RequestParser rp{ctx}; |     IPC::RequestParser rp{ctx}; | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 bunnei
						bunnei