It's simply a loader that will do all of the things my
release does. It's just a practical application of what my Silkroad Development Series guides show, except with specific Silkroad patches. Make sure to keep a 1.201 TSRO client so you can refer back to it in the future for updates as I won't be updating this. You can also use the other guides to add in your own custom console commands and output. Likewise, if you are a developer, you can integrate in my new
and have a nice dev tool for TSRO.Just as a reminder, the Loader's configuration file is stored in: "%appdata%\edxLabs\edxTsroMod\edxTsroMod.ini". So if you change the paths of the DLL and it no longer works, you need to clear the path to the DLL inside that file or just delete it and rechoose your locations.
Included is the source and a precompiled binary in the attached file. I have tested it out for a while on TSRO while playing a new character and it seems to work just fine.

Here's the relevant source code files for easy reference if you don't want to downlod anything. You can grab Common.h/Common.cpp from
as well.DLL.cpp
Code:
// This project based on my Silkroad development series guides:
// http://www.elitepvpers.com/forum/sro-guides-templates/271405-guide-silkroad-development-series.html
#include <windows.h>
#include "../common/common.h"
// Global instance handle to this DLL
HMODULE gInstance = NULL;
// Function prototype
void UserOnInject();
// Main DLL entry point
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ulReason, LPVOID lpReserved)
{
UNREFERENCED_PARAMETER(lpReserved);
if(ulReason == DLL_PROCESS_ATTACH)
{
gInstance = hModule;
// Do not notify this DLL of thread based events
DisableThreadLibraryCalls(hModule);
}
return TRUE;
}
// This is the main function that is called when the DLL is injected into the process
extern "C" __declspec(dllexport) void OnInject(DWORD address, LPDWORD bytes)
{
// Restore the original bytes at the OEP
DWORD wrote = 0;
WriteProcessMemory(GetCurrentProcess(), UlongToPtr(address), bytes, 6, &wrote);
// Call our user function to keep this function clean
UserOnInject();
}
//-------------------------------------------------------------------------
// Function we use to obtain the global settings object (needs updating each patch)
FARPROC GetGlobalSettingsFunc = (FARPROC)0x4631D0;
DWORD codecave_EnglishPatch_ReturnAddress = 0;
__declspec(naked) void codecave_EnglishPatch()
{
__asm pop codecave_EnglishPatch_ReturnAddress
__asm pushad
__asm call GetGlobalSettingsFunc
__asm MOV DWORD PTR DS:[EAX + 0x160], 2 // Taiwan
__asm popad
__asm CMP DWORD PTR DS:[0xD3DF4C], 0 // original code, needs updating each client patch
__asm push codecave_EnglishPatch_ReturnAddress
__asm ret
}
//-------------------------------------------------------------------------
// Function we use to obtain the global settings object (needs updating each patch)
FARPROC AppendBytesFunc = (FARPROC)0x413D80;
LPBYTE pMac;
void Multiclient()
{
*((LPDWORD)(pMac + 2)) = GetTickCount();
}
DWORD codecave_Multiclient_ReturnAddress = 0;
__declspec(naked) void codecave_Multiclient()
{
__asm pop codecave_Multiclient_ReturnAddress
__asm mov pMac, eax
__asm pushad
Multiclient();
__asm popad
__asm call AppendBytesFunc // Original code
__asm push codecave_Multiclient_ReturnAddress
__asm ret
}
//-------------------------------------------------------------------------
// The function where we place all our logic
void UserOnInject()
{
// Create a debugging console
//edx::CreateConsole("SilkroadFramework Debugging Console");
//-------------------------------------------------------------------------
// Mutex for the launcher, no patches required to start Silkroad now
CreateMutexA(0, 0, "Silkroad Online Launcher");
CreateMutexA(0, 0, "Ready");
//-------------------------------------------------------------------------
// Mutex patch - multi-client 1, easy, above launcher check
//006D5DE0 |. /75 47 JNZ SHORT [edx]sro.006D5E29
//006D5DE0 /EB 47 JMP SHORT [edx]sro.006D5E29
BYTE patch0[] = {0xEB, 0x47};
edx::WriteBytes(0x6D5DE0, patch0, 2);
// MAC patch - multi-client 2, more tricky, above
// 004363C1 |> \BF 1074C900 MOV EDI,sro_clie.00C97410 ; ASCII "DownloadServer"
// and below:
// 0043629A |> \BF 0474C900 MOV EDI,sro_clie.00C97404 ; ASCII "AgentServer"
/*0043636A |. 6A 06 PUSH 6 <-- size of bytes
0043636C |. 8D4424 48 LEA EAX,DWORD PTR SS:[ESP+48]
00436370 |. 50 PUSH EAX <- buffer to write
00436371 |. 8BCF MOV ECX,EDI
00436373 |. E8 08DAFDFF CALL <sro_clie.AppendBytes> <- function we want to call (we codecave on it)
*/
edx::CreateCodeCave(0x436373, 5, codecave_Multiclient);
// Bind patch - multi-client 3
/*006E7987 |. E8 D4192C00 CALL sro_clie.009A9360 ; <-- NOP
006E798C |. 85C0 TEST EAX,EAX ; <-- NOP
006E798E |. 75 6B JNZ SHORT sro_clie.006E79FB ; <-- Force JMP
006E7990 |. 53 PUSH EBX ; /Style
006E7991 |. 68 C061C500 PUSH sro_clie.00C561C0 ; |Title = "Silkroad"
006E7996 |. 68 A061C500 PUSH sro_clie.00C561A0 ; |Text = "½ÇÅ©·Îµå°¡ ÀÌ¹Ì ½ÇÇà Áß ÀÔ´Ï´Ù."
006E799B |. 53 PUSH EBX ; |hOwner
006E799C |. FF15 B433C100 CALL NEAR DWORD PTR DS:[<&USER32.Message>; \MessageBoxA*/
BYTE patch5[] = {0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0xEB, 0x6B};
edx::WriteBytes(0x6E7987, patch5, 9);
//-------------------------------------------------------------------------
// English patch - force English - see my post here:
// http://www.elitepvpers.com/forum/sro-guides-templates/277221-guide-how-patch-any-client-into-english-client.html
//006EA5B9 . /75 16 JNZ SHORT [edx]sro.006EA5D1
BYTE patch2[] = {0x90, 0x90};
edx::WriteBytes(0x6EA5B9, patch2, 2);
// English patch - Stop non-English
//006EA539 . /75 1D JNZ SHORT [edx]sro.006EA558
BYTE patch3[] = {0xEB, 0x1D};
edx::WriteBytes(0x6EA539, patch3, 2);
// English patch - Fake foreign flag
//006D5EC1 |. 833D 4CDFD300 00 CMP DWORD PTR DS:[D3DF4C],0
edx::CreateCodeCave(0x6D5EC1, 7, codecave_EnglishPatch);
//-------------------------------------------------------------------------
// Zoom hack - see my post here:
// http://www.elitepvpers.com/forum/sro-ask-experts/298072-help-infinite-zoom.html#post2731209
//0063B206 . /7A 08 JPE SHORT sro_clie.0063B210
BYTE patch4[] = {0xEB, 0x08};
edx::WriteBytes(0x63B206, patch4, 2);
//-------------------------------------------------------------------------
// Swear filter - just find all 3 UIIT_MSG_CHATWND_MESSAGE_FILTER and patch JEs before them
// Skip the one that is a different layout.
//004C712C . /74 43 JE SHORT sro_clie.004C7171
//004FF166 . /74 44 JE SHORT sro_clie.004FF1AC
//0066DD4B |. /74 43 JE SHORT sro_clie.0066DD90
BYTE patch6[] = {0xEB};
edx::WriteBytes(0x4C712C, patch6, 1);
edx::WriteBytes(0x4FF166, patch6, 1);
edx::WriteBytes(0x66DD4B, patch6, 1);
//-------------------------------------------------------------------------
// Nude patch - try binary search: 8B 84 EE 1C 01 00 00 3B 44 24 14
// NOP the check.
//008CA74B |. /75 1A JNZ SHORT sro_clie.008CA767
BYTE patch7[] = {0x90, 0x90};
edx::WriteBytes(0x8CA74B, patch7, 2);
}
Code:
#include <windows.h>
#include <string>
#include <sstream>
#include "../common/common.h"
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
// Let's get rid of any extra warnings we don't need
UNREFERENCED_PARAMETER(hInstance);
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
UNREFERENCED_PARAMETER(nCmdShow);
STARTUPINFOA si = {0};
PROCESS_INFORMATION pi = {0};
std::string pathToClient;
std::string pathToDll;
edx::ConfigFile cf;
bool bExists = false;
std::stringstream ini;
// Try to load the configuration file
ini << edx::GetWriteableDirectory("edxTsroMod") << "edxTsroMod.ini";
cf.Open(ini.str(), false, bExists);
if(bExists)
{
pathToClient = cf.Read("client");
pathToDll = cf.Read("dll");
}
if(bExists == false || pathToClient.empty() || pathToDll.empty())
{
// Choose the injection DLL if required
if(pathToDll.empty())
{
edx::FileChooser fc;
fc.AddFilter("Dynamic-link library", "*.dll");
fc.SetInitialDirectory(edx::GetAbsoluteDirectoryPath().c_str());
fc.SetDialogTitle("Please choose the DLL to inject...");
if(fc.ShowChooseFile(true) == false)
{
MessageBoxA(0, "The DLL selection process was canceled. The program will now exit.", "Fatal Error", MB_ICONERROR);
return 0;
}
pathToDll = fc.GetSelectedFilePath();
cf.Write("dll", pathToDll);
}
// Choose the game client if required
if(pathToClient.empty())
{
edx::FileChooser fc;
fc.SetDefaultFileName("sro_client.exe");
fc.AddFilter("Executable Files", "*.exe");
fc.SetDialogTitle("Please choose your \"sro_client.exe\" executable...");
if(fc.ShowChooseFile(true) == false)
{
MessageBoxA(0, "The client selection process was canceled. The program will now exit.", "Fatal Error", MB_ICONERROR);
return 0;
}
std::string title = fc.GetSelectedFileTitle();
if(title.find("sro_client") == std::string::npos)
{
std::stringstream ss;
ss << "You selected the file \"" << fc.GetSelectedFileName() << "\". Are you sure this is your \"sro_client.exe\" file?\n\nIf this file is not your \"sro_client.exe\" file, please choose No to exit the loader and try again. Otherwise please choose Yes to continue.";
int result = MessageBoxA(0, ss.str().c_str(), "Unexpected file name detected", MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2);
if(result == IDNO)
{
return 0;
}
}
pathToClient = fc.GetSelectedFilePath();
cf.Write("client", pathToClient);
}
// We have to restart the application now since the GetOpenFileName function messes up the working directories.
// Unfortunately on Vista/Win7, the old method of storing the previous working directory and reseting it
// doesn't seem to work anymore.
MessageBoxA(0, "The directory changes have been saved. Please relaunch the program now.", "Application restart required", MB_ICONINFORMATION);
return 0;
}
// Since we want to use a few functions
WSADATA wsaData = {0};
WSAStartup(MAKEWORD(2, 2), &wsaData);
bool bConnected = false;
// Figure out which login server we should use. This is what Silkroad.exe pretty much does
// in figuring out which last parameter to send to the client.
std::stringstream args;
args << "0 /12 0 ";
for(int x = 1; x <= 3; ++x)
{
std::stringstream ss;
if(x == 1 || x == 2)
{
ss << "gw" << x << ".sro.com.tw";
}
else
{
ss << "login.sro.com.tw";
}
if(edx::CanGetHostFromAddress(ss.str()))
{
SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
hostent * host = edx::GetHost(ss.str().c_str());
if(host)
{
sockaddr_in svr = {0};
svr.sin_addr.s_addr = *((unsigned long*)host->h_addr);
svr.sin_family = AF_INET;
svr.sin_port = htons(15779);
if(connect(s, (struct sockaddr*)&svr, sizeof(svr)) == 0)
{
bConnected = true;
args << (x - 1);
break;
}
}
closesocket(s);
}
}
if(bConnected == false)
{
WSACleanup();
MessageBoxA(0, "The address to the Silkroad servers could not be resolved. There are two causes for this error:\n\n1. The Silkroad servers are actually down and you will have to wait until they are up again.\n\n2.Your computer cannot obtain the address of the Silkroad servers. Please visit http://www.joymax.com/silkroad first and try launching the Loader again.\n\nIf that fails, try running Silkroad.exe and see if you get the Start button. If you get a Start button, try running the Loader again until it works. If you do not get a Start button, then there is an issue on your system preventing you from obtaining the addresses.", "Fatal Error", MB_ICONERROR);
return 0;
}
WSACleanup();
// Launch the client in a suspended state so we can patch it
std::string workingDir = pathToClient.substr(0, 1 + pathToClient.find_last_of("\\/"));
bool result = edx::CreateSuspendedProcess(pathToClient, args.str(), si, pi);
if(result == false)
{
MessageBoxA(0, "Could not start \"sro_client.exe\".", "Fatal Error", MB_ICONERROR);
return 0;
}
// Inject the DLL so we can have some fun
result = (FALSE != edx::InjectDLL(pi.hProcess, pathToDll.c_str(), "OnInject", static_cast<DWORD>(edx::GetEntryPoint(pathToClient.c_str())), false));
if(result == false)
{
TerminateThread(pi.hThread, 0);
MessageBoxA(0, "Could not inject into the Silkroad client process.", "Fatal Error", MB_ICONERROR);
return 0;
}
// Finally resume the client.
ResumeThread(pi.hThread);
ResumeThread(pi.hProcess);
// All done!
return 0;
}
Enjoy!






