XKore.pm
上传用户:market2
上传日期:2018-11-18
资源大小:18786k
文件大小:13k
源码类别:

外挂编程

开发平台:

Windows_Unix

  1. #########################################################################
  2. #  OpenKore - X-Kore
  3. #
  4. #  This software is open source, licensed under the GNU General Public
  5. #  License, version 2.
  6. #  Basically, this means that you're allowed to modify and distribute
  7. #  this software. However, if you distribute modified versions, you MUST
  8. #  also distribute the source code.
  9. #  See http://www.gnu.org/licenses/gpl.html for the full license.
  10. #
  11. #  $Revision: 6371 $
  12. #  $Id: XKore.pm 6371 2008-05-25 18:36:19Z darkfate_ $
  13. #
  14. #########################################################################
  15. package Network::XKore;
  16. use strict;
  17. use base qw(Exporter);
  18. use Exporter;
  19. use IO::Socket::INET;
  20. use Time::HiRes qw(time usleep);
  21. use Win32;
  22. use Exception::Class ('Network::XKore::CannotStart');
  23. use Modules 'register';
  24. use Globals;
  25. use Log qw(message error);
  26. use Utils::Win32;
  27. use Network;
  28. use Network::Send ();
  29. use Utils qw(dataWaiting timeOut);
  30. use Translation;
  31. ##
  32. # Network::XKore->new()
  33. #
  34. # Initialize X-Kore mode. Throws Network::XKore::CannotStart on error.
  35. sub new {
  36. my $class = shift;
  37. my $port = 2350;
  38. my $self = bless {}, $class;
  39. undef $@;
  40. $self->{server} = new IO::Socket::INET->new(
  41. Listen => 5,
  42. LocalAddr => 'localhost',
  43. LocalPort => $port,
  44. Proto => 'tcp');
  45. if (!$self->{server}) {
  46. Network::XKore::CannotStart->throw(error => TF("Unable to start the X-Kore server.n" . 
  47. "You can only run one X-Kore session at the same time.n" . 
  48. "And make sure no other servers are running on port %s.n", $port));
  49. }
  50. $self->{incomingPackets} = "";
  51. $self->{serverPackets} = "";
  52. $self->{clientPackets} = "";
  53. $masterServer = $masterServers{$config{master}};
  54. if ($config{serverType} != $masterServer->{serverType}) {
  55. Misc::configModify('serverType', $masterServer->{serverType});
  56. }
  57. if (Settings::setRecvPacketsName($masterServer->{recvpackets})) {
  58. my (undef, undef, $basename) = File::Spec->splitpath(Settings::getRecvPacketsFilename());
  59. Settings::loadByRegexp(quotemeta $basename, sub {
  60. my ($filename) = @_;
  61. message TF("Loading %s...n", $filename);
  62. });
  63. }
  64. $packetParser = Network::Receive->create($masterServer->{serverType});
  65. $messageSender = Network::Send->create($self, $masterServer->{serverType});
  66. Plugins::addHook("Network::Receive/willMangle", &willMangle);
  67. Plugins::addHook("Network::Receive/mangle", &mangle);
  68. message T("X-Kore mode intialized.n"), "startup";
  69. return $self;
  70. }
  71. sub version {
  72. return 1;
  73. }
  74. sub DESTROY {
  75. my $self = shift;
  76. close($self->{client});
  77. }
  78. ######################
  79. ## Server Functions ##
  80. ######################
  81. sub serverAlive {
  82. return $_[0]->{client} && $_[0]->{client}->connected;
  83. }
  84. sub serverConnect {
  85. return undef;
  86. }
  87. sub serverPeerHost {
  88. return undef;
  89. }
  90. sub serverPeerPort {
  91. return undef;
  92. }
  93. sub serverRecv {
  94. my $self = shift;
  95. $self->recv();
  96. return undef unless length($self->{serverPackets});
  97. my $packets = $self->{serverPackets};
  98. $self->{serverPackets} = "";
  99. return $packets;
  100. }
  101. sub serverSend {
  102. my $self = shift;
  103. my $msg = shift;
  104. Plugins::callHook("Network::serverSend/pre", { msg => $msg });
  105. $self->{client}->send("S".pack("v", length($msg)).$msg) if ($self->serverAlive);
  106. }
  107. sub serverDisconnect {
  108. return undef;
  109. }
  110. sub serverAddress {
  111. return undef;
  112. }
  113. sub getState {
  114. return $conState;
  115. }
  116. sub setState {
  117. my ($self, $state) = @_;
  118. if ($conState != $state) {
  119. $conState = $state;
  120. Plugins::callHook('Network::stateChanged');
  121. }
  122. }
  123. ######################
  124. ## Client Functions ##
  125. ######################
  126. ##
  127. # $net->clientAlive()
  128. # Returns: a boolean.
  129. #
  130. # Check whether the connection with the client is still alive.
  131. sub clientAlive {
  132. return $_[0]->serverAlive();
  133. }
  134. ##
  135. # $net->clientConnect
  136. #
  137. # Not used with XKore mode 1
  138. sub clientConnect {
  139. return undef;
  140. }
  141. ##
  142. # $net->clientPeerHost
  143. #
  144. sub clientPeerHost {
  145. return $_[0]->{client}->peerhost if ($_[0]->clientAlive);
  146. return undef;
  147. }
  148. ##
  149. # $net->clientPeerPort
  150. #
  151. sub clientPeerPort {
  152. return $_[0]->{client}->peerport if ($_[0]->clientAlive);
  153. return undef;
  154. }
  155. ##
  156. # $net->clientRecv()
  157. # Returns: the message sent from the client (towards the server), or undef if there are no pending messages.
  158. sub clientRecv {
  159. my $self = shift;
  160. $self->recv();
  161. return undef unless length($self->{clientPackets});
  162. my $packets = $self->{clientPackets};
  163. $self->{clientPackets} = "";
  164. return $packets;
  165. }
  166. ##
  167. # $net->clientSend(msg)
  168. # msg: A scalar to be sent to the RO client
  169. #
  170. sub clientSend {
  171. my $self = shift;
  172. my $msg = shift;
  173. my $switch = uc(unpack("H2", substr($msg, 1, 1))) . uc(unpack("H2", substr($msg, 0, 1)));
  174. if ($switch eq "02AE") {
  175. $msg = "";
  176. }
  177. $self->{client}->send("R".pack("v", length($msg)).$msg) if ($self->clientAlive);
  178. }
  179. sub clientDisconnect {
  180. return undef;
  181. }
  182. #######################
  183. ## Utility Functions ##
  184. #######################
  185. ##
  186. # $net->injectSync()
  187. #
  188. # Send a keep-alive packet to the injected DLL.
  189. sub injectSync {
  190. my $self = shift;
  191. $self->{client}->send("K" . pack("v", 0)) if ($self->serverAlive);
  192. }
  193. ##
  194. # $net->checkConnection()
  195. #
  196. # Handles any connection issues. Based on the current situation, this function may
  197. # re-connect to the RO server, disconnect, do nothing, etc.
  198. #
  199. # This function is meant to be run in the Kore main loop.
  200. sub checkConnection {
  201. my $self = shift;
  202. return if ($self->serverAlive);
  203. # (Re-)initialize X-Kore if necessary
  204. $self->setState(Network::NOT_CONNECTED);
  205. my $printed;
  206. my $pid;
  207. # Wait until the RO client has started
  208. while (!($pid = Utils::Win32::GetProcByName($config{XKore_exeName}))) {
  209. message TF("Please start the Ragnarok Online client (%s)n", $config{XKore_exeName}), "startup" unless $printed;
  210. $printed = 1;
  211. $interface->iterate;
  212. if (defined(my $input = $interface->getInput(0))) {
  213. if ($input eq "quit") {
  214. $quit = 1;
  215. last;
  216. } else {
  217. message T("Error: You cannot type anything except 'quit' right now.n");
  218. }
  219. }
  220. usleep 20000;
  221. last if $quit;
  222. }
  223. return if $quit;
  224. # Inject DLL
  225. message T("Ragnarok Online client foundn"), "startup";
  226. sleep 1 if $printed;
  227. if (!$self->inject($pid)) {
  228. # Failed to inject
  229. $interface->errorDialog($@);
  230. exit 1;
  231. }
  232. # Patch client
  233. $self->hackClient($pid) if ($config{XKore_bypassBotDetection});
  234. # Wait until the RO client has connected to us
  235. $self->waitForClient;
  236. message T("You can login with the Ragnarok Online client now.n"), "startup";
  237. $timeout{'injectSync'}{'time'} = time;
  238. }
  239. ##
  240. # $net->inject(pid)
  241. # pid: a process ID.
  242. # Returns: 1 on success, 0 on failure.
  243. #
  244. # Inject NetRedirect.dll into an external process. On failure, $@ is set.
  245. #
  246. # This function is meant to be used internally only.
  247. sub inject {
  248. my ($self, $pid) = @_;
  249. my $cwd = Win32::GetCwd();
  250. my $dll;
  251. undef $@;
  252. foreach my $file ("$cwd\src\auto\XSTools\NetRedirect.dll", "$cwd\src\auto\XSTools\win32\NetRedirect.dll",
  253. "$cwd\NetRedirect.dll", "$cwd\Inject.dll") {
  254. if (-f $file) {
  255. $dll = $file;
  256. last;
  257. }
  258. }
  259. if (!$dll) {
  260. $@ = T("Cannot find NetRedirect.dll. Please check your installation.");
  261. return 0;
  262. }
  263. if (Utils::Win32::InjectDLL($pid, $dll)) {
  264. return 1;
  265. } else {
  266. $@ = T("Unable to inject NetRedirect.dll");
  267. return undef;
  268. }
  269. }
  270. ##
  271. # $net->waitForClient()
  272. # Returns: the socket which connects X-Kore to the client.
  273. #
  274. # Wait until the client has connected the X-Kore server.
  275. #
  276. # This function is meant to be used internally only.
  277. sub waitForClient {
  278. my $self = shift;
  279. message T("Waiting for the Ragnarok Online client to connect to X-Kore..."), "startup";
  280. $self->{client} = $self->{server}->accept;
  281. # Translation Comment: Waiting for the Ragnarok Online client to connect to X-Kore...
  282. message " " . T("readyn"), "startup";
  283. return $self->{client};
  284. }
  285. ##
  286. # $net->recv()
  287. # Returns: Nothing
  288. #
  289. # Receive packets from the client. Then sort them into server-bound or client-bound;
  290. #
  291. # This is meant to be used internally only.
  292. sub recv {
  293. my $self = shift;
  294. my $msg;
  295. return undef unless dataWaiting($self->{client});
  296. undef $@;
  297. eval {
  298. $self->{client}->recv($msg, 32 * 1024);
  299. };
  300. if (!defined $msg || length($msg) == 0 || $@) {
  301. delete $self->{client};
  302. return undef;
  303. }
  304. $self->{incomingPackets} .= $msg;
  305. while ($self->{incomingPackets} ne "") {
  306. last if (!length($self->{incomingPackets}));
  307. my $type = substr($self->{incomingPackets}, 0, 1);
  308. my $len = unpack("v",substr($self->{incomingPackets}, 1, 2));
  309. last if ($len > length($self->{incomingPackets}));
  310. $msg = substr($self->{incomingPackets}, 3, $len);
  311. $self->{incomingPackets} = (length($self->{incomingPackets}) - $len - 3)?
  312. substr($self->{incomingPackets}, $len + 3, length($self->{incomingPackets}) - $len - 3)
  313. : "";
  314. if ($type eq "R") {
  315. # Client-bound (or "from server") packets
  316. $self->{serverPackets} .= $msg;
  317. } elsif ($type eq "S") {
  318. # Server-bound (or "to server") packets
  319. $self->{clientPackets} .= $msg;
  320. } elsif ($type eq "K") {
  321. # Keep-alive... useless.
  322. }
  323. }
  324. # Check if we need to send our sync
  325. if (timeOut($timeout{'injectSync'})) {
  326. $self->injectSync;
  327. $timeout{'injectSync'}{'time'} = time;
  328. }
  329. return 1;
  330. }
  331. sub willMangle {
  332. my (undef, $args) = @_;
  333.     
  334. $args->{return} = 0;
  335. # if ($args->{messageID} eq '02AE') {
  336. # $args->{return} = 1;
  337. # }
  338. }
  339. sub mangle {
  340. my (undef, $args) = @_;
  341. my $message_args = $args->{messageArgs};
  342. $args->{return} = 0;
  343. # if ($message_args->{switch} eq '02AE') {
  344. # $args->{return} = 2;
  345. # }
  346. }
  347. ##
  348. # $net->hackClient(pid)
  349. # pid: Process ID of a running (and official) Ragnarok Online client
  350. #
  351. # Hacks the client (non-nProtect GameGuard version) to remove bot detection.
  352. # If the code is in the RO Client, it should find it fairly quick and patch, but
  353. # if not it will spend a bit of time scanning through Ragnarok's memory. Perhaps
  354. # there should be a config option to disable/enable this?
  355. #
  356. # Code Note: $original is a regexp match, and since 0x7C is '|', its escaped.
  357. sub hackClient {
  358. my $self = shift;
  359. my $pid = shift;
  360. my $handle;
  361. my $pageSize = Utils::Win32::SystemInfo_PageSize();
  362. my $minAddr = Utils::Win32::SystemInfo_MinAppAddress();
  363. my $maxAddr = Utils::Win32::SystemInfo_MaxAppAddress();
  364. my $patchFind = pack('C*', 0x66, 0xA3) . '....' # mov word ptr [xxxx], ax
  365. . pack('C*', 0xA0) . '....' # mov al, byte ptr [xxxx]
  366. . pack('C*', 0x3C, 0x0A, # cmp al, 0A
  367. 0x66, 0x89, 0x0D) . '....'; # mov word ptr [xxxx], cx
  368. my $original = '\' . pack('C*', 0x7C, 0x6D); # jl 6D
  369. # (to be replaced by)
  370. my $patched = pack('C*', 0xEB, 0x6D); # jmp 6D
  371. my $patchFind2 = pack('C*', 0xA1) . '....' # mov eax, dword ptr [xxxx]
  372. . pack('C*', 0x8D, 0x4D, 0xF4, # lea ecx, dword ptr [ebp+var_0C]
  373. 0x51); # push ecx
  374. $original = $patchFind . $original . $patchFind2;
  375. message T("Patching client to remove bot detection:n"), "startup";
  376. # Open Ragnarok's process
  377. my $hnd = Utils::Win32::OpenProcess(0x638, $pid);
  378. # Loop through Ragnarok's memory
  379. my ($nextUpdate, $updateChar, $patchCount) = (0, '.', 0);
  380. for (my $i = $minAddr; $i < $maxAddr; $i += $pageSize) {
  381. # Status update...
  382. my $percent = int($i / ($maxAddr - $minAddr) * 100);
  383. if ($percent >= $nextUpdate) {
  384. if ($nextUpdate % 25 == 0) {
  385. if ($updateChar eq '.') {
  386. message $percent . '%';
  387. } else {
  388. message $updateChar . $percent . '%' . $updateChar;
  389. }
  390. } else {
  391. message $updateChar;
  392. }
  393. $updateChar = '.';
  394. $nextUpdate += 5;
  395. }
  396. # Ensure we can read/write the memory
  397. my $oldprot = Utils::Win32::VirtualProtectEx($hnd, $i, $pageSize, 0x40);
  398. if ($oldprot) {
  399. # Read the page
  400. my $data = Utils::Win32::ReadProcessMemory($hnd, $i, $pageSize);
  401. # Is the patched code in there?
  402. if ($data =~ m/($original)/) {
  403. # It is!
  404. my $matched = $1;
  405. # Generate the new code, based on the old.
  406. $patched = substr($matched, 0, length($patchFind)) . $patched;
  407. $patched = $patched . substr($matched, length($patchFind) + 2, length($patchFind2));
  408. # Patch the data
  409. $data =~ s/$original/$patched/;
  410. # Write the new code
  411. if (Utils::Win32::WriteProcessMemory($hnd, $i, $data)) {
  412. $updateChar = '*';
  413. $patchCount++;
  414. } else {
  415. $updateChar = '!';
  416. }
  417. }
  418. # Undo the protection change
  419. Utils::Win32::VirtualProtectEx($hnd, $i, $pageSize, $oldprot);
  420. }
  421. }
  422. message "n";
  423. # Close Ragnarok's process
  424. Utils::Win32::CloseProcess($hnd);
  425. message TF("Client modified in %d places.n", $patchCount), "startup";
  426. }
  427. #
  428. # XKore::redirect([enabled])
  429. # enabled: Whether you want to redirect (some) console messages to the RO client.
  430. #
  431. # Enable or disable console message redirection. Or, if $enabled is not given,
  432. # returns whether message redirection is currently enabled.
  433. #sub redirect {
  434. # my $arg = shift;
  435. # if ($arg) {
  436. # $redirect = $arg;
  437. # } else {
  438. # return $redirect;
  439. # }
  440. #}
  441. #sub redirectMessages {
  442. # my ($type, $domain, $level, $globalVerbosity, $message, $user_data) = @_;
  443. #
  444. # return if ($type eq "debug" || $level > 0 || $conState != 5 || !$redirect);
  445. # return if ($domain =~ /^(connection|startup|pm|publicchat|guildchat|selfchat|emotion|drop|inventory|deal)$/);
  446. # return if ($domain =~ /^(attack|skill|list|info|partychat|npc)/);
  447. #
  448. # $message =~ s/n*$//s;
  449. # $message =~ s/n/\n/g;
  450. # main::sendMessage($messageSender, "k", $message);
  451. #}
  452. return 1;