vSRO patch client module version without SMC

10/26/2023 12:28 thehaifisch#1
Hello,

I would like to know if its possible to add an update patch without using SMC. The reason I need this because I want to automate it.

I see when a patch is added a new record is inserted into _ModuleVersion and also each file included in the patch is represented in _ModuleVersionFile. I have a problem where nToBePacked = 1. I have no idea how to "pack" the file.

Thanks in advance
10/26/2023 12:39 Athena'#2
If you're willing to upload a new client with every update it is surely do able otherwise no as the client from players are not going to update.
10/26/2023 12:48 thehaifisch#3
Quote:
Originally Posted by Athena' View Post
If you're willing to upload a new client with every update it is surely do able otherwise no as the client from players are not going to update.
I would like to replace the client patch functionality of SMC with something else that can be automated. But of course I want clients to update automatically.
10/26/2023 15:29 DaxterSoul#4
It's definitely possible but not in the way you think, at least from what I understood from your post.

Just adding rows to _ModuleVersion and _ModuleVersionFile is also not enough as neither DownloadServer nor GatewayServer have received information about this.

nToBePacked just means the files are supposed to go into a PackFile (.pk2)
You might be confusing this with the required compression used by SMC which is zLib in vSRO and lzma in some other clients.

You can either write your own launcher that downloads from a cdn based on a "version.txt" file, thus never modifying the actual silkroad version but importing the files into pk2.

Or you can write a clientless tool that acts like SMC. Meaning its communicating via GlobalManager to automate the patching.

Step 0. Fix your SecurityApi

There are some oversights in @[Only registered and activated users can see links. Click Here To Register...] 's [Only registered and activated users can see links. Click Here To Register...]. They only happen in backend communication, that's why they went unnoticed.

0x1001 is supposed to be ignored from CRC and Sequence.
Code:
// Original
byte sb1 = GenerateCountByte(true);

// Fixed
byte sb1 = opcode == 0x1001 ? (byte)0 : GenerateCountByte(true);

// Original
byte sb2 = GenerateCheckByte(writer.GetBytes()); 

// Fixed
byte sb2 = opcode == 0x1001 ? (byte)0 : GenerateCheckByte(writer.GetBytes());

// Original
byte expected_count = GenerateCountByte(true); 
 
// Fixed
byte expected_count = packet_opcode == 0x1001 ? (byte)0 : GenerateCountByte(true);

// Original
byte expected_crc = GenerateCheckByte(buffer.Buffer); 

// Fixed
byte expected_crc = packet_opcode == 0x1001 ? (byte)0 : GenerateCheckByte(buffer.Data);
The FormatPacket is out of order thus messing with CRC and Sequence. I'm not sure if this one is 100% needed, but just in case.

Code:
// Original
ushort parts = 0;

PacketWriter final = new PacketWriter();
PacketWriter final_data = new PacketWriter();

byte[] input_data = packet.GetBytes();
PacketReader input_reader = new PacketReader(input_data);

TransferBuffer workspace = new TransferBuffer(4089, 0, input_data.Length);

while (workspace.Size > 0)
{
    PacketWriter part_data = new PacketWriter();

    int cur_size = workspace.Size > 4089 ? 4089 : workspace.Size; // Max buffer size is 4kb for the client

    part_data.Write((byte)0); // Data flag

    part_data.Write(input_data, workspace.Offset, cur_size);

    workspace.Offset += cur_size;
    workspace.Size -= cur_size; // Update the size

    final_data.Write(FormatPacket(0x600D, part_data.GetBytes(), false));

    ++parts; // Track how many parts there are
}

// Write the final header packet to the front of the packet
PacketWriter final_header = new PacketWriter();
final_header.Write((byte)1); // Header flag
final_header.Write((short)parts);
final_header.Write(packet.Opcode);
final.Write(FormatPacket(0x600D, final_header.GetBytes(), false));
Code:
// Fixed
ushort parts = 0;

PacketWriter final = new PacketWriter();
PacketWriter final_data = new PacketWriter();

byte[] input_data = packet.GetBytes();

PacketWriter header = new PacketWriter();
header.Write((byte)1); //Header flag
header.Write((ushort)((input_data.Length / 4089) + (input_data.Length % 4089 != 0 ? 1 : 0)));
header.Write(packet.Opcode);
final.Write(this.FormatPacket(0x600D, header.GetBytes(), false));

TransferBuffer workspace = new TransferBuffer(4089, 0, input_data.Length);

while (workspace.Length > 0)
{
    PacketWriter part_data = new PacketWriter();

    int cur_size = workspace.Length > 4089 ? 4089 : workspace.Length; // Max buffer size is 4kb for the client

    part_data.Write((byte)0); // Data flag

    part_data.Write(input_data, workspace.Offset, cur_size);

    workspace.Offset += cur_size;
    workspace.Length -= cur_size; // Update the size

    final_data.Write(this.FormatPacket(0x600D, part_data.GetBytes(), false));

    ++parts; // Track how many parts there are
}
Step 1.
Login as an SMC that has the required SecurityDescriptions

0x7001 - SM_LOGIN_REQ
Code:
2	ushort	Username.Length
*	string	Username
2	ushort	PasswordHash.Length
*	string	PasswordHash //MD5
2	ushort	SSNumber2Hash.Length
*	string	SSNumber2Hash //MD5, can be empty
4	uint	ProtocolVersion //24
0xB001 - SM_LOGIN_ACK
Code:
1	byte	result
if(result == 1)
{
	1	byte	curentSecurityGroup
	1	byte	currentDivision
    
    // SecurityDescriptionData (optional)
    // See https://github.com/DummkopfOfHachtenduden/SilkroadDoc/wiki/0xA003-FRAMEWORK_MSG_CERTIFICATION_ACK.md

}
else if(result == 0x02)
{
	1	byte	errorCode
	//01 = cannot connect to division manager (wrong version)
	//02 = cannot connect to division manager (invalid user info)
	//03 = cannot connect to division manager (server internal error)
	//04 = cannot connect to division manager (access denied)
}
Step 2.
Request the architecture info (optional)

0x7002 - SM_ARCHITECTURE_REQ
Code:
// Empty
0xB002 - SM_ARCHITECTURE_ACK
Code:
See https://github.com/DummkopfOfHachtenduden/SilkroadDoc/wiki/0xA003-FRAMEWORK_MSG_CERTIFICATION_ACK.md
Step 3.
Request module version info

0x7003 - SM_MODULE_VERSION_REQ
Code:
// Empty
0xB003 - SM_MODULE_VERSION_ACK
Code:
1	byte	result

while(true)
{
	1	byte	entryFlag
	if(entryFlag == 0)
		break;

	4	uint	nID
	1	byte	nDivisionID
	1	byte	nContentID
	1	byte	nModuleID
	4	uint	nVersion
	64	string	szVersion
	256	string	szDesc
	1	byte	nValid //Always 1 as GlobalManager culls all obsolete versions
}
Step 4.
Prepare patch on the front servers [GatewayServer(s) and DownloadServer(s)]
GlobalManager will give you the latest version info so you can compare your local files against it.

0x7104 - SM_MODULE_PATCH_PREPARE_REQ
Code:
1   byte    ModuleID        // SR_Client or ServiceManager
1   byte    ContentID
0xB104 - SM_MODULE_PATCH_PREPARE_ACK
Code:
1   byte    result
if(result == 0x01)
{
    //ModuleVersion@HEAD
    while(true)
    {
        1   byte    entryFlag
        if(entryFlag == 0)
            break;    
        
        4   uint    nID
        1   byte    nDivisionID
        1   byte    nContentID
        1   byte    nModuleID
        4   uint    nVersion
        64	string	szVersion
        256	string	szDesc
        1	byte	nValid // always 1 as GlobalManager culls all obsolete versions
    }

    while(true)
    {
        1   byte    entryFlag
        if(entryFlag == 0)
            break;    
        
        4   uint    nID
        4   uint    nVersion
        1   byte    nDivisionID
        1   byte    nContentID
        1   byte    nModuleID
        256 string  szFilename
        256 string  szPath
        4   uint    nFileSize
        1   byte    nFileType
        4   uint    nFileTypeVersion //1001 part of JMXV*****
        1   byte    nToBePacked
        2   ushort  timeModified.Year
        2   ushort  timeModified.Month
        2   ushort  timeModified.Day
        2   ushort  timeModified.Hour
        2   ushort  timeModified.Minute
        2   ushort  timeModified.Second
        4   uint    timeModified.Microsecond
        1   byte    nValid
    }
}
else if(result == 0x02)
{
    1   byte    errorCode
    //01 = "cannot prepare patch : another user patch in progress"
    //02 = "cannot prepare patch : 5:DownloadServer is offline"
    //03 = "cannot prepare patch : DB error"
    //?? = "cannot prepare patch : front server prepare patch fail"    
}
Step 5.
Compress your workspace with zLib. C#s DeflateStream only works for decompression.
The zLib from the [Only registered and activated users can see links. Click Here To Register...] works fine

Step 6.
Make an upload request to GlobalManager.

0x7100 - SM_MODULE_PATCH_UPLOAD_REQ (Massive)
Code:
4	uint	nID
1	byte	nDivisionID
1	byte	nContentID
1	byte	nModuleID
1	uint	nVersion
64	string	szVersion
256	string	szDesc
1	byte	nValid // always 1 as GlobalManager culls all obsolete versions

2   ushort  fileCount
foreach(file)
{
    4   uint    nID
    4   uint    nVersion
    1   byte    nDivisionID
    1   byte    nContentID
    1   byte    nModuleID
    256 string  szFilename
    256 string  szPath
    4   uint    nFileSize
    1   byte    nFileType
    4   uint    nFileTypeVersion //1001 part of JMXV*****
    1   byte    nToBePacked
    2   ushort  timeModified.Year
    2   ushort  timeModified.Month
    2   ushort  timeModified.Day
    2   ushort  timeModified.Hour
    2   ushort  timeModified.Minute
    2   ushort  timeModified.Second
    4   uint    timeModified.Microsecond
    1   byte    nValid
}
0xB100 - SM_MODULE_PATCH_UPLOAD_ACK
Code:
1   byte    result
Step 7.

GlobalManager will request the the files that are supposed to be uploaded via 0x6004 - FRAMEWORK_FILE_TRANSFER_REQ
Code:
4   uint    file.ID
4   uint    fileOffset  // Used to continue interupted downloads from last chunk. (can be ignored)
You're now supposed to send 0x1001 - NETENGINE_FILE_DATA
Code:
*   byte[]  data    // up to 4090 bytes of raw file data
until the file is complete then you send 0xA004 - FRAMEWORK_FILE_TRANSFER_ACK
Code:
1   byte    result
if(result == 0x02)
{
	1	byte	errorCode
}
GlobalManager will also send 0x3101 - SM_MODULE_PATCH_NOTIFY_PROGRESS telling you what's going on. (optional)
Code:
1   byte    notifyFlag                          //1 = Relay, 2 = Recording database
if(notifyFlag & 1)
{
    1   byte    state   // 1  = Start, 2 = Progress
    if(state == 2)
    {
        1   byte    fileCount
        1   byte    fileCurrent
    }
}
if(notifyFlag & 2)
{
    1   byte    state   // 1  = Start, 2 = Progress
    if(state == 2)
    {
        1   byte    progress // 0-100
    }
}
Repeat Step 7. until GlobalManager sends 0x3102 - SM_MODULE_PATCH_NOTIFY_COMPLETE
Code:
1   byte    result  
if(result == 2)
{
    1   byte    errorCode
    // 1 = patch failed: relay server offline
    // 2 = patch failed: fail to tranfer patch data to relay server
    // 3 = patch failed: service operating
    // 4 = patch failed: recording db failed
}
Step 8. Since you're automating stuff make sure you put the front servers back into service

0x7008 - SM_CONTROL_SERVICE_START_REQ
Code:
1   byte    ServiceMode     // 1 = Start, 2 = Stop
2   ushort  ServerBodyID
Your users can now download the latest patches via Silkroad.exe as usual.

If you have questions just ask.
10/27/2023 10:31 thehaifisch#5
Quote:
Originally Posted by DaxterSoul View Post
Just using the thanks button would not be sufficient. This is pure gold. Thanks a lot! :handsdown:
10/29/2023 01:31 #HB#6
Quote:
Originally Posted by DaxterSoul View Post
...
Had some of these parsed years ago, but most were incomplete.

Thanks a bunch dude.