TMA services: Difference between revisions
No edit summary |
mNo edit summary |
||
| (One intermediate revision by the same user not shown) | |||
| Line 306: | Line 306: | ||
Takes no input, returns an s32 "Priority". | Takes no input, returns an s32 "Priority". | ||
= Protocol (gen1) = | |||
== PacketHeader == | |||
{| class="wikitable" border="1" | |||
|- | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x4 || [[#ServiceId]] | |||
|- | |||
| 0x4 || 0x4 || Task ID | |||
|- | |||
| 0x8 || 0x2 || [[#TaskType]] | |||
|- | |||
| 0xA || 0x1 || Initiate | |||
|- | |||
| 0xB || 0x1 || Padding | |||
|- | |||
| 0xC || 0x4 || Packet data size | |||
|- | |||
| 0x10 || 0x10 || Padding | |||
|} | |||
All packets sent and received by tma begin with this header, the data following the header is specific to each task type. | |||
Including the header, a packet can be up to 0xE020 bytes large | |||
Initiate will be set to 1 on the first packet associated with an task/operation and cleared on any/all subsequent packets. | |||
== ServiceId == | |||
{| class="wikitable" border="1" | |||
! Service ID || Service Name | |||
|- | |||
| 0x378D2DB4 || BenchmarkReportService | |||
|- | |||
| 0xBA2C1174 || BenchmarkService | |||
|- | |||
| 0xC191DAC9 || EnvService | |||
|- | |||
| 0x5FAE4D7E || HostIOService | |||
|- | |||
| 0xB04489F2 || HostDirectoryIOService | |||
|- | |||
| 0xB644D830 || HTCSService | |||
|- | |||
| 0xDDDE6636 || DebugService | |||
|- | |||
| 0x0955E925 || ControllerService | |||
|} | |||
This designates which tma "service" produced or will process a packet. These services are hosted over the host<->console interface, they are unrelated to hipc services. | |||
The ServiceID values are derived from the Service Names strings using this hash: | |||
<syntaxhighlight> | |||
uint tmipc::HashString(char *param_1) | |||
{ | |||
uint uVar1; | |||
long lVar2; | |||
uint uVar3; | |||
uVar1 = (uint)(byte)*param_1; | |||
uVar3 = (uint)(byte)*param_1; | |||
if (uVar3 == 0) { | |||
lVar2 = 0; | |||
} | |||
else { | |||
lVar2 = 0; | |||
do { | |||
uVar1 = uVar1 * 0xf4243 ^ uVar3; | |||
uVar3 = (uint)(byte)param_1[lVar2 + 1]; | |||
lVar2 = lVar2 + 1; | |||
} while (uVar3 != 0); | |||
} | |||
if (uVar1 != (uint)lVar2) { | |||
return (uint)lVar2 ^ uVar1; | |||
} | |||
assertion_failure(); | |||
} | |||
</syntaxhighlight> | |||
== TaskType == | |||
{| class="wikitable" border="1" | |||
! Type || Name | |||
|- | |||
| 0x01 || [[#OpenFile_Task|OpenFile]] | |||
|- | |||
| 0x02 || [[#GetFileSize_Task|GetFileSize]] | |||
|- | |||
| 0x03 || [[#SetFileSize_Task|SetFileSize]] | |||
|- | |||
| 0x05 || [[#FileExists_Task|FileExists]] | |||
|- | |||
| 0x06 || [[#ReadFile_Task|ReadFile]] | |||
|- | |||
| 0x07 || [[#WriteFile_Task|WriteFile]] | |||
|- | |||
| 0x08 || [[#FlushFile_Task|FlushFile]] | |||
|- | |||
| 0x09 || [[#SetPriorityForFile_Task|SetPriorityForFile]] | |||
|- | |||
| 0x0A || [[#GetPriorityForFile_Task|GetPriorityForFile]] | |||
|- | |||
| 0x0B || [[#CloseFile_Task|CloseFile]] | |||
|- | |||
| 0x0C || [[#CreateFile_Task|CreateFile]] | |||
|- | |||
| 0x0D || [[#DeleteFile_Task|DeleteFile]] | |||
|- | |||
| 0x0E || [[#RenameFile_Task|RenameFile]] | |||
|- | |||
| 0x0F || [[#GetIOType_Task|GetIOType]] | |||
|- | |||
| 0x10 || [[#DirectoryExists_Task|DirectoryExists]] | |||
|- | |||
| 0x11 || [[#OpenDirectory_Task|OpenDirectory]] | |||
|- | |||
| 0x12 || [[#GetDirectoryEntryCount_Task|GetDirectoryEntryCount]] | |||
|- | |||
| 0x13 || [[#ReadDirectory_Task|ReadDirectory]] | |||
|- | |||
| 0x14 || [[#CloseDirectory_Task|ReadDirectory]] | |||
|- | |||
| 0x15 || [[#SetPriorityForDirectory_Task|SetPriorityForDirectory]] | |||
|- | |||
| 0x16 || [[#GetPriorityForDirectory_Task|GetPriorityForDirectory]] | |||
|- | |||
| 0x17 || [[#CreateDirectory_Task|CreateDirectory]] | |||
|- | |||
| 0x18 || [[#DeleteDirectory_Task|DeleteDirectory]] | |||
|- | |||
| 0x19 || [[#RenameDirectory_Task|RenameDirectory]] | |||
|- | |||
| 0x1A || Socket | |||
|- | |||
| 0x1B || Close | |||
|- | |||
| 0x1C || Connect | |||
|- | |||
| 0x1D || Bind | |||
|- | |||
| 0x1E || Listen | |||
|- | |||
| 0x1F || Accept | |||
|- | |||
| 0x20 || Recv | |||
|- | |||
| 0x21 || Send | |||
|- | |||
| 0x22 || Shutdown | |||
|- | |||
| 0x23 || Fcntl | |||
|- | |||
| 0x26 || Debug (Debugger) | |||
|- | |||
| 0x29 || [[#GetVar_Task|GetVar (GetEnvironmentVariable)]] | |||
|- | |||
| 0x2B || [[#GetFileTimeStamp_Task|GetFileTimeStamp]] | |||
|} | |||
Each task is associated with a specific [[#ServiceId|Service]], there seems to be no overlap in task type values between services. | |||
== EnvService == | |||
Creates tasks for requesting environment variables from host, it's used by [[#GetEnvironmentVariable]] and [[#GetEnvironmentVariableLength]]. | |||
=== GetVar Task === | |||
This is used by [[#GetEnvironmentVariable]] and [[#GetEnvironmentVariableLength]]. | |||
Console request: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || variable || Environment variable name | |||
|} | |||
Host response: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x4 || Result | |||
|- | |||
| 0x4 || variable || Null terminated environment variable string | |||
|} | |||
Up to <buffer size> bytes of the environment variable string are copied to a provided buffer and the length of string is counted. | |||
[[#GetEnvironmentVariableLength]] provides no buffer so the string is discarded completely, only the size is returned. | |||
[[#GetEnvironmentVariable]] provides its caller supplied hipc buffer. | |||
== HostIOService == | |||
Creates tasks for host file operations, it's used by all [[#file_io]] File commands and [[#GetIOType]]. | |||
=== OpenFile Task === | |||
This is used by [[#OpenFile]]. | |||
Console request: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || File descriptor? (always -1) | |||
|- | |||
| 0x8 || variable || File path | |||
|- | |||
| variable || 0x4 || Open mode | |||
|} | |||
Host response: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || File descriptor | |||
|- | |||
| 0x8 || 0x4 || Result | |||
|- | |||
| 0xC || 0x8 || Unused | |||
|} | |||
=== GetFileSize Task === | |||
This is used by [[#GetFileSize]]. | |||
Console request: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || File descriptor | |||
|} | |||
Host response: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || File descriptor | |||
|- | |||
| 0x8 || 0x4 || Result | |||
|- | |||
| 0xC || 0x8 || File size | |||
|} | |||
=== SetFileSize Task === | |||
This is used by [[#SetFileSize]]. | |||
Console request: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || File descriptor | |||
|- | |||
| 0x8 || 0x8 || File size | |||
|} | |||
Host response: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || File descriptor | |||
|- | |||
| 0x8 || 0x4 || Result | |||
|- | |||
| 0xC || 0x8 || Unused | |||
|} | |||
=== FileExists Task === | |||
This is used by [[#FileExists]]. | |||
Console request: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || File descriptor? (always -1) | |||
|- | |||
| 0x8 || variable || File path | |||
|} | |||
Host response: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || File descriptor? (unused) | |||
|- | |||
| 0x8 || 0x4 || Result | |||
|- | |||
| 0xC || 0x8 || File exists (cast to a bool) | |||
|} | |||
=== ReadFile Task === | |||
This is used by [[#ReadFile]]. | |||
Console request: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || File descriptor | |||
|- | |||
| 0x8 || 0x8 || Read offset | |||
|- | |||
| 0x10 || 0x8 || Read size | |||
|} | |||
Host response: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x4 || Sent size | |||
|- | |||
| 0x4 || 0x4 || Result | |||
|- | |||
| 0x8 || variable || <sent size> bytes | |||
|} | |||
tma will receive responses until <read size> bytes have been received, result>0 is received, or an empty packet (<sent size>=0) is received. | |||
=== WriteFile Task === | |||
This is used by [[#WriteFile]]. | |||
Console request: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || File descriptor | |||
|- | |||
| 0x8 || 0x8 || Read offset | |||
|- | |||
| 0x10 || 0x4 || Write size (of current packet) | |||
|- | |||
| 0x14 || 0x1 || End | |||
|} | |||
Host response: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x4 || Total written size? (unused) | |||
|- | |||
| 0x4 || 0x4 || Result | |||
|} | |||
tma sends write requests containing up to 0xdfdf bytes of data until until all data has been sent. The last request will have End=1, prior requests will have End=0. | |||
=== FlushFile Task === | |||
This is used by [[#FlushFile]]. | |||
Console request: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || File descriptor | |||
|} | |||
Host response: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || File descriptor | |||
|- | |||
| 0x8 || 0x4 || Result | |||
|- | |||
| 0xC || 0x8 || Unused | |||
|} | |||
=== SetPriorityForFile Task === | |||
This is used by [[#SetPriorityForFile]]. | |||
Console request: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || File descriptor | |||
|- | |||
| 0x8 || 0x4 || Priority | |||
|} | |||
Host response: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || File descriptor | |||
|- | |||
| 0x8 || 0x4 || Result | |||
|- | |||
| 0xC || 0x8 || Unused | |||
|} | |||
=== GetPriorityForFile Task === | |||
This is used by [[#GetPriorityForFile]]. | |||
Console request: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || File descriptor | |||
|} | |||
Host response: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || File descriptor | |||
|- | |||
| 0x8 || 0x4 || Result | |||
|- | |||
| 0xC || 0x8 || Priority (cast to a u32/s32) | |||
|} | |||
=== CloseFile Task === | |||
This is sent when an [[#IFileAccessor]] session is closed. The result value is discarded. | |||
Console request: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || File descriptor | |||
|} | |||
Host response: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || File descriptor | |||
|- | |||
| 0x8 || 0x4 || Result | |||
|- | |||
| 0xC || 0x8 || Unused | |||
|} | |||
=== CreateFile Task === | |||
This is used by [[#CreateFile]]. | |||
Console request: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || File descriptor? (always -1) | |||
|- | |||
| 0x8 || variable || File path | |||
|- | |||
| variable || 0x8 || File size | |||
|} | |||
Host response: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || File descriptor? (unused) | |||
|- | |||
| 0x8 || 0x4 || Result | |||
|- | |||
| 0xC || 0x8 || Unused | |||
|} | |||
=== DeleteFile Task === | |||
This is used by [[#DeleteFile]]. | |||
Console request: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || File descriptor? (always -1) | |||
|- | |||
| 0x8 || variable || File path | |||
|} | |||
Host response: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || File descriptor? (unused) | |||
|- | |||
| 0x8 || 0x4 || Result | |||
|- | |||
| 0xC || 0x8 || Unused | |||
|} | |||
=== RenameFile Task === | |||
This is used by [[#RenameFile]]. | |||
Console request: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || File descriptor? (always -1) | |||
|- | |||
| 0x8 || variable || Source file path | |||
|- | |||
| variable || variable || Dest file path | |||
|} | |||
Host response: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || File descriptor? (unused) | |||
|- | |||
| 0x8 || 0x4 || Result | |||
|- | |||
| 0xC || 0x8 || Unused | |||
|} | |||
=== GetIOType Task === | |||
This is used by [[#GetIOType]]. | |||
Console request: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || File descriptor? (always -1) | |||
|- | |||
| 0x8 || variable || File path | |||
|} | |||
Host response: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || File descriptor? (unused) | |||
|- | |||
| 0x8 || 0x4 || Result | |||
|- | |||
| 0xC || 0x8 || Entry type (cast to a u32/[[Filesystem_services#DirectoryEntryType|DirectoryEntryType]]) | |||
|} | |||
=== GetFileTimeStamp Task === | |||
This is used by [[#GetFileTimeStamp]]. | |||
Console request: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || File descriptor? (always -1) | |||
|- | |||
| 0x8 || variable || File path | |||
|} | |||
Host response: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || File descriptor? (unused) | |||
|- | |||
| 0x8 || 0x4 || Result | |||
|- | |||
| 0xC || 0x8 || CreatedTime | |||
|- | |||
| 0x14 || 0x8 || AccessTime | |||
|- | |||
| 0x2C || 0x8 || ModifiedTime | |||
|} | |||
== HostDirectoryIOService == | |||
Creates tasks for host directory operations, it's used by all [[#file_io]] Directory commands. | |||
=== DirectoryExists Task === | |||
This is used by [[#DirectoryExists]]. | |||
Console request: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || Directory descriptor? (always -1) | |||
|- | |||
| 0x8 || variable || Directory path | |||
|} | |||
Host response: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || Directory descriptor? (unused) | |||
|- | |||
| 0x8 || 0x4 || Result | |||
|- | |||
| 0xC || 0x8 || Directory exists (cast to a bool) | |||
|} | |||
=== OpenDirectory Task === | |||
This is used by [[#OpenDirectory]]. | |||
Console request: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || Directory descriptor? (always -1) | |||
|- | |||
| 0x8 || variable || Directory path | |||
|- | |||
| variable || 0x8 || Tag | |||
|- | |||
| variable || 0x4 || Open mode | |||
|} | |||
Host response: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || Directory descriptor | |||
|- | |||
| 0x8 || 0x4 || Result | |||
|- | |||
| 0xC || 0x8 || Unused | |||
|- | |||
| 0x24 || 0x8 || Tag (must be the same as Tag in request) | |||
|} | |||
=== GetDirectoryEntryCount Task === | |||
This is used by [[#GetDirectoryEntryCount]]. | |||
Console request: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || Directory descriptor | |||
|} | |||
Host response: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || Directory descriptor | |||
|- | |||
| 0x8 || 0x4 || Result | |||
|- | |||
| 0xC || 0x8 || Entry count | |||
|} | |||
=== ReadDirectory Task === | |||
This is used by [[#ReadDirectory]]. | |||
Console request: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || Directory descriptor | |||
|- | |||
| 0x8 || 0x4 || Max out entry count | |||
|} | |||
Host response: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x4 || Result | |||
|- | |||
| 0x8 || 0x1 || Entry type | |||
|- | |||
| 0x9 || variable || Entry path (up to FS_MAX_PATH chars) | |||
|- | |||
| variable || 0x8 || Entry size | |||
|} | |||
tma receives response packets until result>0 or it's received <max out entry count> entries. Entries with an empty path are ignored but still count toward <max out count>. | |||
Response packets are converted to a [[Filesystem_services#DirectoryEntry]]. The path and size are copied directly and type is flipped (0->1 (File), 1->0 (Directory)) | |||
=== CloseDirectory Task === | |||
This is used when an [[#IDirectoryAccessor]] session is closed. The result value is discarded. | |||
Console request: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || Directory descriptor | |||
|} | |||
Host response: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || Directory descriptor | |||
|- | |||
| 0x8 || 0x4 || Result | |||
|- | |||
| 0xC || 0x8 || Unused | |||
|} | |||
=== SetPriorityForDirectory Task === | |||
This is used by [[#SetPriorityForDirectory]]. | |||
Console request: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || Directory descriptor | |||
|- | |||
| 0x8 || 0x8 || Priority | |||
|} | |||
Host response: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || Directory descriptor | |||
|- | |||
| 0x8 || 0x4 || Result | |||
|- | |||
| 0xC || 0x8 || Unused | |||
|} | |||
=== GetPriorityForDirectory Task === | |||
This is used by [[#GetPriorityForDirectory]]. | |||
Console request: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || Directory descriptor | |||
|} | |||
Host response: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || Directory descriptor | |||
|- | |||
| 0x8 || 0x4 || Result | |||
|- | |||
| 0xC || 0x8 || Priority | |||
|} | |||
=== CreateDirectory Task === | |||
This is used by [[#CreateDirectory]]. | |||
Console request: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || Directory descriptor? (always -1) | |||
|- | |||
| 0x8 || variable || Directory path | |||
|} | |||
Host response: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || Directory descriptor | |||
|- | |||
| 0x8 || 0x4 || Result | |||
|- | |||
| 0xC || 0x8 || Unused | |||
|} | |||
=== DeleteDirectory Task === | |||
This is used by [[#DeleteDirectory]]. | |||
Console request: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || Directory descriptor? (always -1) | |||
|- | |||
| 0x8 || variable || Directory path | |||
|- | |||
| variable || 0x1 || Is recursive (0=not, 1=recursive) | |||
|} | |||
Host response: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || Directory descriptor? (unused) | |||
|- | |||
| 0x8 || 0x4 || Result | |||
|- | |||
| 0xC || 0x8 || Unused | |||
|} | |||
=== RenameDirectory Task === | |||
This is used by [[#RenameDirectory]]. | |||
Console request: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || Directory descriptor? (always -1) | |||
|- | |||
| 0x8 || variable || Src path | |||
|- | |||
| variable || variable || Dst path | |||
|} | |||
Host response: | |||
{| class="wikitable" border="1" | |||
! Offset || Size || Description | |||
|- | |||
| 0x0 || 0x8 || Directory descriptor? (unused) | |||
|- | |||
| 0x8 || 0x4 || Result | |||
|- | |||
| 0xC || 0x8 || Unused | |||
|} | |||
== HTCSService == | |||
Creates tasks for htcs operations, it's used by all [[#htcs]] commands. | |||
== DebugService == | |||
Process debugger. | |||
[[Category:Services]] | [[Category:Services]] | ||