--- deliantra/server/utils/cfutil.in 2009/10/28 04:10:46 1.74 +++ deliantra/server/utils/cfutil.in 2010/10/06 01:23:11 1.97 @@ -1,6 +1,28 @@ #!@PERL@ -use strict; +# +# This file is part of Deliantra, the Roguelike Realtime MMORPG. +# +# Copyright (©) 2007,2008,2009 Marc Alexander Lehmann / Robin Redeker / the Deliantra team +# +# Deliantra is free software: you can redistribute it and/or modify it under +# the terms of the Affero GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# and the GNU General Public License along with this program. If not, see +# . +# +# The authors can be reached via e-mail to +# + +use common::sense; my $prefix = "@prefix@"; my $exec_prefix = "@exec_prefix@"; @@ -23,6 +45,7 @@ use YAML::XS (); use JSON::XS (); use IO::AIO (); +use Digest::MD5 (); use Coro 5.12; use Coro::AIO; @@ -53,6 +76,7 @@ my $FORCE; my $TMPDIR = "/tmp/cfutil$$~"; my $TMPFILE = "aaaa0"; +my @COMMIT; our %COLOR = ( black => 0, @@ -75,6 +99,28 @@ my $s_INT = EV::signal INT => sub { exit 1 }; my $s_TERM = EV::signal TERM => sub { exit 1 }; +our %hash; + +# here we could try to avoid collisions and reduce chksum size further +sub make_hash($\$\$;$) { + my ($id, $dataref, $hashref, $clen) = @_; + + my $hash = substr +(Digest::MD5::md5 $$dataref), 0, $clen || 4; + + if (exists $hash{$hash}) { + # hash collision, but some files are simply identical + if (${$hash{$hash}[1]} ne $$dataref) { + warn "hash collision $hash{$hash}[0] vs. $id\n"; + exit 1; + } else { + print "$hash{$hash}[0] and $id are identical (which is fine).\n" if $VERBOSE >= 3; + } + } + $hash{$hash} = [$id, $dataref, $hashref]; + + $$hashref = $hash; +} + mkdir $TMPDIR, 0700 or die "$TMPDIR: $!"; @@ -112,6 +158,32 @@ print "maps installed successfully.\n"; } +our @WALL_SUFFIX = qw(⬤ ╹ ╺ ┗ ╻ ┃ ┏ ┣ ╸ ┛ ━ ┻ ┓ ┫ ┳ ╋); + +# used to create crude text glyphs for text-based clients +sub autoglyph { + my ($stem, $face) = @_; + + if ($stem =~ /^wall\/|Nimwall/) { + return $WALL_SUFFIX[hex $1] + if $stem =~ /(_[0-9A-F]).x11/; + + "+" + + } elsif ($stem =~ /^monster.*\/(.)/) { + $1 + + } elsif ($stem =~ /^floor\/|^ground\/|Nimfloor/) { + "." + + } elsif ($stem =~ /^player\//) { + "\@" + + } else { + substr $stem, 0, 1 + } +} + { our %ANIMINFO; our %FACEINFO; @@ -130,9 +202,10 @@ our @c_png; - sub commit_png($$$) { - my ($name, $data, $T) = @_; + sub commit_png($$$$) { + my ($stem, $name, $data, $T) = @_; + $FACEINFO{$name}{"stem"} = substr $stem, 1 + length $PATH; $FACEINFO{$name}{"data$T"} = $data; } @@ -360,11 +433,11 @@ die "$path: unable to read tile +$x+$y, aborting.\n"; } IO::AIO::aio_unlink $file unless $CACHE; - commit_png $x|$y ? "$face+$x+$y" : $face, $tile, $T; + commit_png $stem, $x|$y ? "$face+$x+$y" : $face, $tile, $T; } } else { # use as-is (either small, use smooth) - commit_png $face, $png, $T; + commit_png $stem, $face, $png, $T; } aio_unlink $path if $delete; @@ -385,10 +458,11 @@ $ARC{$m->{_name}} = $m; } - $o->{editor_folder} = $dir; + $o->{editor_folder} ||= "\x00$dir"; # horrible kludge my $visibility = delete $o->{visibility}; my $magicmap = delete $o->{magicmap}; + my $glyph = delete $o->{glyph}; # find upper left corner :/ # omg, this is sooo broken @@ -408,6 +482,7 @@ $visibility = delete $o->{visibility} if exists $o->{visibility}; $magicmap = delete $o->{magicmap} if exists $o->{magicmap}; + $glyph = delete $o->{glyph} if exists $o->{glyph}; my $anim = delete $o->{anim}; @@ -448,6 +523,7 @@ $info->{visibility} = $visibility if defined $visibility; $info->{magicmap} = $magicmap if defined $magicmap; + $info->{glyph} = $glyph if defined $glyph; } if (my $smooth = delete $o->{smoothface}) { @@ -526,12 +602,17 @@ "pd" => "Public Domain", "gpl" => "GNU General Public License, version 3.0 or any later", "cc/by/2.0" => "Licensed under Creative Commons Attribution 2.0 http://creativecommons.org/licenses/by/2.0/", + "cc/by/2.1" => "Licensed under Creative Commons Attribution 2.1 http://creativecommons.org/licenses/by/2.1/", "cc/by/2.5" => "Licensed under Creative Commons Attribution 2.5 http://creativecommons.org/licenses/by/2.5/", "cc/by/3.0" => "Licensed under Creative Commons Attribution 3.0 http://creativecommons.org/licenses/by/3.0/", })->{$meta->{license}} || warn "$dir/$file: license tag '$meta->{license}' not found."; } + if (!exists $meta->{author} && $meta->{source} =~ m%^http://www.jamendo.com/en/artist/(.*)$%) { + ($meta->{author} = $1) =~ s/_/ /g; + } + $file =~ s/\.res$//; $file =~ s/\.(ogg|wav|jpg|png)$//; @@ -567,7 +648,7 @@ my ($dirs, $nondirs) = @_; find_files "$path/$_" - for grep $_ !~ /^(?:CVS|dev)$/, @$dirs; + for grep $_ !~ /^(?:CVS|dev|\..*)$/, @$dirs; my $dir = $path; substr $dir, 0, 1 + length $PATH, ""; @@ -679,8 +760,9 @@ print "scanning done, processing results...\n" if $VERBOSE; { # remove path prefix from editor_folder - substr $_->{editor_folder}, 0, 1 + length $path, "" - for values %ARC; + $_->{editor_folder} =~ /^\x00/ + and substr $_->{editor_folder}, 0, 2 + length $path, "" + for values %ARC; print "resolving inheritance tree...\n" if $VERBOSE; # resolve inherit @@ -689,7 +771,7 @@ my $loop; for my $o (values %ARC) { - if (my $other = $o->{inherit}) { + for my $other (split /,/, $o->{inherit}) { if (my $s = $ARC{$other}) { if ($s->{inherit}) { $loop = $s; @@ -721,14 +803,14 @@ print "generating plurals...\n" if $VERBOSE; generate_plurals; - print "writing archetypes...\n" if $VERBOSE; + printf "writing %d archetypes...\n", scalar @ARC if $VERBOSE; open my $fh, ">:utf8", "$DATADIR/archetypes~" or die "$DATADIR/archetypes~: $!"; print $fh Deliantra::archlist_to_string [sort { $a->{_name} cmp $b->{_name} } @ARC]; } { - print "writing treasures...\n" if $VERBOSE; + printf "writing treasures (%d octets)...\n", length $TRS if $VERBOSE; open my $fh, ">:utf8", "$DATADIR/treasures~" or die "$DATADIR/treasures~: $!"; print $fh $TRS; @@ -740,6 +822,9 @@ length $v->{data32} or warn "$k: face has no png32. this will not work (shoddy gcfclient will crash of course).\n"; length $v->{data64} or warn "$k: face has no png64. this will not work very well.\n"; + make_hash $k, $v->{data32}, $v->{hash32}; + make_hash $k, $v->{data64}, $v->{hash64}; + #length $v->{data32} <= 10000 or warn "$k: face32 larger than 10000 bytes, will not work with crossfire client.\n"; #length $v->{data64} <= 10000 or warn "$k: face64 larger than 10000 bytes.\n"; @@ -748,24 +833,66 @@ $v->{magicmap} = $COLOR{$magicmap}; } + my $stem = delete $v->{stem}; + $v->{glyph} //= autoglyph $stem, $v; + utf8::encode $v->{glyph}; + $v->{glyph} = (chr $v->{magicmap}) . $v->{glyph}; + delete $v->{arc}; } - print "writing facedata...\n" if $VERBOSE; + print "processing resources...\n" if $VERBOSE; + my $enc = JSON::XS->new->utf8->canonical->relaxed; + while (my ($k, $v) = each %RESOURCE) { + + if ($v->{meta} && $v->{meta}{datadir}) { + delete $RESOURCE{$k}; + + $k =~ s/^res\/// or die "$k: datadir files must be in res/"; + + printf "writing $k (%d octets)...\n", length $v->{data} if $VERBOSE; + open my $fh, ">:raw", "$DATADIR/$k~" + or die "$DATADIR/$k~: $!"; + syswrite $fh, $v->{data}; + push @COMMIT, $k; + + } else { + if ($v->{type} & 1) { + # prepend meta info + + my $meta = $enc->encode ({ + name => $k, + %{ $v->{meta} || {} }, + }); + + $v->{data} = pack "(w/a*)*", $meta, $v->{data}; + } + + make_hash $k, $v->{data}, $v->{hash}, 6; # 6 for the benefit of existing clients + } + } + + printf "writing facedata (%d faces, %d anims, %d resources)...\n", + scalar keys %FACEINFO, + scalar keys %ANIMINFO, + scalar keys %RESOURCE + if $VERBOSE; + open my $fh, ">:perlio", "$DATADIR/facedata~" or die "$DATADIR/facedata~: $!"; - print $fh freeze { + print $fh nfreeze { version => 2, faceinfo => \%FACEINFO, animinfo => \%ANIMINFO, resource => \%RESOURCE, }; + } print "committing files...\n" if $VERBOSE; - for (qw(archetypes facedata treasures)) { + for (qw(archetypes facedata treasures), @COMMIT) { chmod 0644, "$DATADIR/$_~"; rename "$DATADIR/$_~", "$DATADIR/$_" or die "$DATADIR/$_: $!";