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

外挂编程

开发平台:

Windows_Unix

  1. #########################################################################
  2. #  OpenKore - Settings
  3. #  Copyright (c) 2007 OpenKore Developers
  4. #
  5. #  This software is open source, licensed under the GNU General Public
  6. #  License, version 2.
  7. #  Basically, this means that you're allowed to modify and distribute
  8. #  this software. However, if you distribute modified versions, you MUST
  9. #  also distribute the source code.
  10. #  See http://www.gnu.org/licenses/gpl.html for the full license.
  11. #
  12. #  $Revision: 6520 $
  13. #  $Id: Settings.pm 6520 2008-09-10 03:14:41Z malevolyn $
  14. #
  15. #########################################################################
  16. ##
  17. # MODULE DESCRIPTION: Settings and configuration files management.
  18. #
  19. # Core OpenKore settings, such as OpenKore's program name and version number,
  20. # are stored by this module.
  21. #
  22. # OpenKore uses two kinds of data files:
  23. # `l
  24. # - Control files. These configuration files define character-specific
  25. #   behavior and can be changed at any time.
  26. # - Table files. These files contain character-independent, but server-specific
  27. #   information that OpenKore needs. These files are read-mostly (don't change
  28. #   often).
  29. # `l`
  30. # This module is also responsible for data file management. It allows one to:
  31. # `l
  32. # - Register control or table files by name.
  33. # - Locate control or table files from multiple possible locations.
  34. # - (Re)load control or table files based on some search criteria.
  35. # `l`
  36. # Most of the functions for parsing configuration files are located in
  37. # FileParsers.pm, while the variables which contain the configuration data are
  38. # in Globals.pm.
  39. #
  40. # Finally, the Settings module provides support functions for commandline
  41. # argument parsing.
  42. package Settings;
  43. use strict;
  44. use lib 'src/deps';
  45. use lib 'src';
  46. use Exporter;
  47. use base qw(Exporter);
  48. use Carp::Assert;
  49. use UNIVERSAL qw(isa);
  50. use FindBin qw($RealBin);
  51. use Getopt::Long;
  52. use File::Spec;
  53. use Translation qw(T TF);
  54. use Utils::ObjectList;
  55. use Utils::Exceptions;
  56. use enum qw(CONTROL_FILE_TYPE TABLE_FILE_TYPE);
  57. ###################################
  58. ### CATEGORY: Constants
  59. ###################################
  60. ##
  61. # String $Settings::NAME
  62. #
  63. # The name of this program, usually "OpenKore".
  64. ##
  65. # String $Settings::VERSION
  66. #
  67. # The version number of this program.
  68. # Translation Comment: Strings for the name and version of the application
  69. our $NAME = 'OpenKore';
  70. our $VERSION = '2.0.7';
  71. # Translation Comment: Version String
  72. our $SVN = T(" (Release Candidate) ");
  73. our $WEBSITE = 'http://www.openkore.com/';
  74. # Translation Comment: Version String
  75. our $versionText = "*** $NAME ${VERSION}${SVN} - " . T("Custom Ragnarok Online client") . " ***n***   $WEBSITE   ***n";
  76. our $welcomeText = TF("Welcome to %s.", $NAME);
  77. # Data file folders.
  78. our @controlFolders;
  79. our @tablesFolders;
  80. our @pluginsFolders;
  81. # The registered data files.
  82. our $files;
  83. # System configuration.
  84. our %sys;
  85. our $fields_folder;
  86. our $logs_folder;
  87. our $config_file;
  88. our $mon_control_file;
  89. our $items_control_file;
  90. our $shop_file;
  91. our $recvpackets_name;
  92. our $chat_log_file;
  93. our $storage_log_file;
  94. our $shop_log_file;
  95. our $sys_file;
  96. our $monster_log_file;
  97. our $item_log_file;
  98. our $interface;
  99. our $lockdown;
  100. our $no_connect;
  101. my $pathDelimiter = ($^O eq 'MSWin32') ? ';' : ':';
  102. sub MODINIT {
  103. $files = new ObjectList();
  104. }
  105. use Modules 'register';
  106. our @EXPORT_OK = qw(%sys);
  107. ###################################
  108. ### CATEGORY: Public functions
  109. ###################################
  110. ##
  111. # int Settings::parseArguments()
  112. # Returns: 1 on success, 0 if a 'usage' text should be displayed.
  113. #
  114. # Parse commandline arguments. Various variables within the Settings
  115. # module will be filled with values.
  116. #
  117. # This function will also attempt to create necessary folders. If
  118. # one of the folders cannot be created, then an IOException is thrown,
  119. # although the variables are already filled.
  120. #
  121. # If the arguments are not correct, then an ArgumentException is thrown.
  122. sub parseArguments {
  123. my %options;
  124. undef $fields_folder;
  125. undef $logs_folder;
  126. undef $config_file;
  127. undef $mon_control_file;
  128. undef $items_control_file;
  129. undef $shop_file;
  130. undef $chat_log_file;
  131. undef $storage_log_file;
  132. undef $sys_file;
  133. undef $interface;
  134. undef $lockdown;
  135. local $SIG{__WARN__} = sub {
  136. ArgumentException->throw($_[0]);
  137. };
  138. GetOptions(
  139. 'control=s',          $options{control},
  140. 'tables=s',           $options{tables},
  141. 'plugins=s',          $options{plugins},
  142. 'fields=s',           $fields_folder,
  143. 'logs=s',             $logs_folder,
  144. 'config=s',           $config_file,
  145. 'mon_control=s',      $mon_control_file,
  146. 'items_control=s',    $items_control_file,
  147. 'shop=s',             $shop_file,
  148. 'chat-log=s',         $chat_log_file,
  149. 'storage-log=s',      $storage_log_file,
  150. 'sys=s',              $sys_file,
  151. 'interface=s',        $interface,
  152. 'lockdown',           $lockdown,
  153. 'help',               $options{help},
  154. 'no-connect',         $no_connect
  155. );
  156. if ($options{control}) {
  157. setControlFolders(split($pathDelimiter, $options{control}));
  158. } else {
  159. setControlFolders("control");
  160. }
  161. if ($options{tables}) {
  162. setTablesFolders(split($pathDelimiter, $options{tables}));
  163. } else {
  164. setTablesFolders("tables");
  165. }
  166. if ($options{plugins}) {
  167. setPluginsFolders(split($pathDelimiter, $options{plugins}));
  168. } else {
  169. setPluginsFolders("plugins");
  170. }
  171. $fields_folder = "fields" if (!defined $fields_folder);
  172. $logs_folder = "logs" if (!defined $logs_folder);
  173. $chat_log_file = File::Spec->catfile($logs_folder, "chat.txt");
  174. $storage_log_file = File::Spec->catfile($logs_folder, "storage.txt");
  175. $shop_log_file = File::Spec->catfile($logs_folder, "shop_log.txt");
  176. $monster_log_file = File::Spec->catfile($logs_folder, "monster_log.txt");
  177. $item_log_file = File::Spec->catfile($logs_folder, "item_log.txt");
  178. if (!defined $interface) {
  179. if ($ENV{OPENKORE_DEFAULT_INTERFACE} && $ENV{OPENKORE_DEFAULT_INTERFACE} ne "") {
  180. $interface = $ENV{OPENKORE_DEFAULT_INTERFACE};
  181. } else {
  182. $interface = "Console"
  183. }
  184. }
  185. return 0 if ($options{help});
  186. if (! -d $logs_folder) {
  187. if (!mkdir($logs_folder)) {
  188. IOException->throw("Unable to create folder $logs_folder ($!)");
  189. }
  190. }
  191. return 1;
  192. }
  193. ##
  194. # String Settings::getUsageText()
  195. #
  196. # Return the usage text that should be displayed.
  197. sub getUsageText {
  198. my $text = qq{
  199. Usage: openkore.exe [options...]
  200. General path options:
  201. --control=PATHS           Specify folders in which to look for control files.
  202. --tables=PATHS            Specify folders in which to look for table files.
  203. --plugins=PATH            Specify folders in which to look for plugins.
  204. For the above options, you can specify multiple paths, delimited by '$pathDelimiter'.
  205. --fields=PATH             Specify the folder in which to look for field files.
  206. --logs=PATH               Save log files in the specified folder.
  207. Control files lookup options:
  208. --config=FILENAME         Which config.txt to use.
  209. --mon_control=FILENAME    Which mon_control.txt to use.
  210. --items_control=FILENAME  Which items_control.txt to use.
  211. --shop=FILENAME           Which shop.txt to use.
  212. --chat-log=FILENAME       Which chat log file to use.
  213. --storage-log=FILENAME    Which storage log file to use.
  214. --sys=FILENAME            Which sys.txt to use.
  215. Other options:
  216. --interface=NAME          Which interface to use at startup.
  217. --lockdown                Disable potentially insecure features.
  218. --help                    Displays this help message.
  219. Developer options:
  220. --no-connect              Do not connect to any servers.
  221. };
  222. $text =~ s/^n//s;
  223. $text =~ s/^tt?//gm;
  224. return $text;
  225. }
  226. ##
  227. # void Settings::setControlFolders(Array<String> folders)
  228. #
  229. # Set the folders in which to look for control files.
  230. sub setControlFolders {
  231. @controlFolders = @_;
  232. }
  233. sub getControlFolders {
  234. return @controlFolders;
  235. }
  236. ##
  237. # void Settings::setTablesFolders(Array<String> folders)
  238. #
  239. # Set the folders in which to look for table files.
  240. sub setTablesFolders {
  241. @tablesFolders = @_;
  242. }
  243. sub getTablesFolders {
  244. return @tablesFolders;
  245. }
  246. ##
  247. # void Settings::setPluginsFolders(Array<String> folders)
  248. #
  249. # Set the folders in which to look for plugins.
  250. sub setPluginsFolders {
  251. @pluginsFolders = @_;
  252. }
  253. ##
  254. # Array<String> Settings::getPluginsFolders()
  255. #
  256. # Get the folders in which to look for plugins.
  257. sub getPluginsFolders {
  258. return @pluginsFolders;
  259. }
  260. ##
  261. # Settings::addControlFile(String name, options...)
  262. # Returns: A handle for this data file, which can be used by Settings::removeFile() or Settings::loadByHandle().
  263. #
  264. # Register a control file. This file will be eligable for (re)loading
  265. # when one of the load() functions is called.
  266. #
  267. # The following options are allowed:
  268. # `l
  269. # - loader (required): must be either a reference to a function, or
  270. #       be an array in which the first element is a function reference.
  271. #       This function will be used to load this control file. In case
  272. #       of an array, all but the first element of that array will be passed
  273. #       to the load function as additional parameters.
  274. # - autoSearch (boolean): whether the full filename of this control file
  275. #       should be looked up by looking into one of the folders specified by
  276. #       Settings::setControlFolders(). If disabled, it will be assumed that
  277. #       $name is a correct absolute or relative path. The default is enabled.
  278. # `l`
  279. sub addControlFile {
  280. my $name = shift;
  281. return _addFile($name, CONTROL_FILE_TYPE, @_);
  282. }
  283. ##
  284. # Settings::addTableFile(String name, options...)
  285. #
  286. # This is like Settings::addControlFile(), but for table files.
  287. sub addTableFile {
  288. my $name = shift;
  289. return _addFile($name, TABLE_FILE_TYPE, @_);
  290. }
  291. ##
  292. # void Settings::removeFile(handle)
  293. #
  294. # Unregister a file that was registered by Settings::addControlFile()
  295. # or Settings::addTableFile().
  296. sub removeFile {
  297. my ($handle) = @_;
  298. $files->remove($files->get($handle));
  299. }
  300. ##
  301. # void loadByHandle(handle, [Function progressHandler])
  302. # handle: A handle, as returned by Settings::addControlFile() or
  303. #         Settings::addTableFile().
  304. # progressHandler: A function which will be called when the filename
  305. #                  resolved.
  306. #
  307. # Load or reload a data file as specified by the given data file handle.
  308. # Throws FileNotFoundException if the file cannot be found in any of the
  309. # search locations.
  310. # Note that the data file loader function may throw additional exceptions.
  311. #
  312. # The progress handler function, if specified, will be called when the
  313. # full filename of this data file has been resolved (that is, it has been
  314. # found in one of the search locations), but before the file is actually
  315. # loaded. It is useful for displaying progress reports.
  316. #
  317. # The progress handler function is called with two arguments: the filename,
  318. # and the data file's type (which can be Settings::CONTROL_FILE_TYPE or
  319. # Settings::TABLE_FILE_TYPE).
  320. # For example:
  321. # <pre class="example">
  322. # Settings::loadByHandle($handle, sub {
  323. #     my ($filename, $type) = @_;
  324. #     print "$_[0] is about to be loaded!n";
  325. #     if ($type == Settings::CONTROL_FILE_TYPE) {
  326. #         print "And it's a control file.n";
  327. #     } else {
  328. #         print "And it's a table file.n";
  329. #     }
  330. # });
  331. # </pre>
  332. sub loadByHandle {
  333. my ($handle, $progressHandler) = @_;
  334. assert(defined $handle) if DEBUG;
  335. my $object = $files->get($handle);
  336. assert(defined $object) if DEBUG;
  337. my $filename;
  338. if ($object->{autoSearch}) {
  339. if ($object->{type} == CONTROL_FILE_TYPE) {
  340. $filename = _findFileFromFolders($object->{name}, @controlFolders);
  341. } else {
  342. $filename = _findFileFromFolders($object->{name}, @tablesFolders);
  343. }
  344. } else {
  345. $filename = $object->{name};
  346. }
  347. if (!defined($filename) || ! -f $filename) {
  348. $filename = $object->{name} if (!defined $filename);
  349. if ($object->{type} == CONTROL_FILE_TYPE) {
  350. FileNotFoundException->throw(
  351. message => TF("Cannot load control file %s", $filename),
  352. filename => $filename);
  353. } else {
  354. FileNotFoundException->throw(
  355. message => TF("Cannot load table file %s", $filename),
  356. filename => $filename);
  357. }
  358. } elsif ($progressHandler) {
  359. $progressHandler->($filename, $object->{type});
  360. }
  361. if (ref($object->{loader}) eq 'ARRAY') {
  362. my @array = @{$object->{loader}};
  363. my $loader = shift @array;
  364. $loader->($filename, @array);
  365. } else {
  366. $object->{loader}->($filename);
  367. }
  368. }
  369. ##
  370. # void Settings::loadAll(regexp, [Function progressHandler])
  371. #
  372. # (Re)loads all registered data files whose name matches the given regular expression.
  373. # This method follows the same contract as
  374. # Settings::loadByHandle(), so see that method for parameter descriptions
  375. # and exceptions.
  376. sub loadByRegexp {
  377. my ($regexp, $progressHandler) = @_;
  378. my @result;
  379. foreach my $object (@{$files->getItems()}) {
  380. if ($object->{name} =~ /$regexp/) {
  381. loadByHandle($object->{index}, $progressHandler);
  382. }
  383. }
  384. }
  385. ##
  386. # void Settings::loadAll([Function progressHandler])
  387. #
  388. # (Re)loads all registered data files. This method follows the same contract as
  389. # Settings::loadByHandle(), so see that method for parameter descriptions
  390. # and exceptions.
  391. sub loadAll {
  392. my ($progressHandler) = @_;
  393. foreach my $object (@{$files->getItems()}) {
  394. loadByHandle($object->{index}, $progressHandler);
  395. }
  396. }
  397. ##
  398. # int Settings::getSVNRevision()
  399. #
  400. # Return OpenKore's SVN revision number, or undef if that information cannot be retrieved.
  401. sub getSVNRevision {
  402. my $f;
  403. if (open($f, "<", "$RealBin/.svn/entries")) {
  404. my $revision;
  405. eval {
  406. die unless <$f> =~ /^d+$/; # We only support the non-XML format
  407. die unless <$f> eq "n"; # Empty string for current directory.
  408. die unless <$f> eq "dirn"; # We expect a directory entry.
  409. $revision = <$f>;
  410. $revision =~ s/[rn]//g;
  411. undef $revision unless $revision =~ /^d+$/;
  412. };
  413. close($f);
  414. return $revision;
  415. } else {
  416. return;
  417. }
  418. }
  419. sub loadSysConfig {
  420. _processSysConfig(0);
  421. }
  422. sub writeSysConfig {
  423. _processSysConfig(1);
  424. }
  425. ##########################################
  426. ### CATEGORY: Data file lookup functions
  427. ##########################################
  428. ##
  429. # String Settings::getControlFilename(String name)
  430. # name: A valid base file name.
  431. # Returns: A valid filename, or undef if not found.
  432. # Ensures: if defined($result): -f $result
  433. #
  434. # Get a control file by its name. This file will be looked up
  435. # in all possible locations, as specified by earlier calls
  436. # to Settings::setControlFolders().
  437. sub getControlFilename {
  438. return _findFileFromFolders($_[0], @controlFolders);
  439. }
  440. ##
  441. # String Settings::getTableFilename(String name)
  442. # name: A valid base file name.
  443. # Ensures: if defined($result): -f $result
  444. #
  445. # Get a table file by its name. This file will be looked up
  446. # in all possible locations, as specified by earlier calls
  447. # to Settings::setTabblesFolders().
  448. sub getTableFilename {
  449. return _findFileFromFolders($_[0], @tablesFolders);
  450. }
  451. sub getConfigFilename {
  452. if (defined $config_file) {
  453. return $config_file;
  454. } else {
  455. return getControlFilename("config.txt");
  456. }
  457. }
  458. sub setConfigFilename {
  459. my ($new_filename) = @_;
  460. my $current_filename = getConfigFilename();
  461. foreach my $object (@{$files->getItems()}) {
  462. if ($object->{name} eq $current_filename) {
  463. $object->{name} = $new_filename;
  464. last;
  465. }
  466. }
  467. $config_file = $new_filename;
  468. }
  469. sub getMonControlFilename {
  470. if (defined $mon_control_file) {
  471. return $mon_control_file;
  472. } else {
  473. return getControlFilename("mon_control.txt");
  474. }
  475. }
  476. sub getItemsControlFilename {
  477. if (defined $items_control_file) {
  478. return $items_control_file;
  479. } else {
  480. return getControlFilename("items_control.txt");
  481. }
  482. }
  483. sub getShopFilename {
  484. if (defined $shop_file) {
  485. return $shop_file;
  486. } else {
  487. return getControlFilename("shop.txt");
  488. }
  489. }
  490. sub getSysFilename {
  491. if (defined $sys_file) {
  492. return $sys_file;
  493. } else {
  494. return getControlFilename("sys.txt");
  495. }
  496. }
  497. sub getRecvPacketsFilename {
  498. return getTableFilename($recvpackets_name || "recvpackets.txt");
  499. }
  500. sub setRecvPacketsName {
  501. my ($new_name) = @_;
  502. if ($recvpackets_name ne $new_name) {
  503. my $current_filename = getRecvPacketsFilename();
  504. foreach my $object (@{$files->getItems()}) {
  505. if ($object->{name} eq $current_filename) {
  506. $object->{name} = getTableFilename($new_name || "recvpackets.txt");
  507. last;
  508. }
  509. }
  510. $recvpackets_name = $new_name;
  511. return 1;
  512. } else {
  513. return undef;
  514. }
  515. }
  516. ##########################
  517. # Private methods
  518. ##########################
  519. sub _assertNameIsBasename {
  520. my (undef, undef, $file) = File::Spec->splitpath($_[0]);
  521. if ($file ne $_[0]) {
  522. ArgumentException->throw("Name must be a valid file base name.");
  523. }
  524. }
  525. sub _findFileFromFolders {
  526. my ($name, $folders) = @_;
  527. _assertNameIsBasename($name);
  528. foreach my $dir (@{$folders}) {
  529. my $filename = File::Spec->catfile($dir, $name);
  530. if (-f $filename) {
  531. return $filename;
  532. }
  533. }
  534. return undef;
  535. }
  536. sub _addFile {
  537. my $name = shift;
  538. my $type = shift;
  539. my %options = @_;
  540. if (!$options{loader}) {
  541. ArgumentException->throw("The 'loader' option must be specified.");
  542. }
  543. my $object = {
  544. type => $type,
  545. name => $name,
  546. mustExist  => exists($options{mustExist}) ? $options{mustExist} : 1,
  547. autoSearch => exists($options{autoSearch}) ? $options{autoSearch} : 1,
  548. loader     => $options{loader}
  549. };
  550. my $index = $files->add(bless($object, 'Settings::Handle'));
  551. $object->{index} = $index;
  552. return $index;
  553. }
  554. sub _processSysConfig {
  555. my ($writeMode) = @_;
  556. my ($f, @lines, %keysNotWritten);
  557. my $sysFile = getSysFilename();
  558. return if (!$sysFile || !open($f, "<:utf8", $sysFile));
  559. if ($writeMode) {
  560. foreach my $key (keys %sys) {
  561. $keysNotWritten{$key} = 1;
  562. }
  563. }
  564. while (!eof($f)) {
  565. my ($line, $key, $val);
  566. $line = <$f>;
  567. $line =~ s/[rn]//g;
  568. if ($line eq '' || $line =~ /^#/) {
  569. if ($writeMode) {
  570. push @lines, $line;
  571. } else {
  572. next;
  573. }
  574. }
  575. ($key, $val) = split / /, $line, 2;
  576. if ($writeMode) {
  577. if (exists $sys{$key}) {
  578. push @lines, "$key $sys{$key}";
  579. delete $keysNotWritten{$key};
  580. }
  581. } else {
  582. $sys{$key} = $val;
  583. }
  584. }
  585. close $f;
  586. if ($writeMode && open($f, ">:utf8", $sysFile)) {
  587. foreach my $line (@lines) {
  588. print $f "$linen";
  589. }
  590. foreach my $key (keys %keysNotWritten) {
  591. print $f "$key $sys{$key}n";
  592. }
  593. close $f;
  594. }
  595. }
  596. 1;