Changeset View
Changeset View
Standalone View
Standalone View
source/tools/entity/checkrefs.pl
use strict; | use strict; | ||||
use warnings; | use warnings; | ||||
use Data::Dumper; | use Data::Dumper; | ||||
use File::Find; | use File::Find; | ||||
use XML::Simple; | use XML::Simple; | ||||
use JSON; | use JSON; | ||||
use Getopt::Long qw(GetOptions); | |||||
use lib "."; | |||||
use Entity; | use Entity; | ||||
use constant CHECK_MAPS_XML => 0; | GetOptions ( | ||||
use constant ROOT_ACTORS => 1; | '--check-map-xml' => \(my $checkMapXml = 0), | ||||
'--check-unused' => \(my $checkUnused = 0), | |||||
'--validate-templates' => \(my $validateTemplates = 0), | |||||
'--root-actors' => \(my $rootActors = 0), | |||||
'--mod-to-check=s' => \(my $modToCheck = "public") | |||||
); | |||||
my @files; | my @files; | ||||
my @roots; | my @roots; | ||||
my @deps; | my @deps; | ||||
my $vfsroot = '../../../binaries/data/mods'; | my $vfsroot = '../../../binaries/data/mods'; | ||||
my $mods = get_mod_dependencies_string($modToCheck); | |||||
my $mod_list_string = $modToCheck; | |||||
if ($mods ne "") | |||||
{ | |||||
$mod_list_string = $mod_list_string."|$mods"; | |||||
} | |||||
$mod_list_string = $mod_list_string."|mod"; | |||||
print("Checking $modToCheck\'s integrity. \n"); | |||||
print("The following mod(s) will be loaded: $mod_list_string. \n"); | |||||
my @mods_list = split(/\|/, "$mod_list_string"); | |||||
sub vfs_to_physical | sub get_mod_dependencies | ||||
{ | { | ||||
my ($vfspath) = @_; | my ($mod) = @_; | ||||
my $fn = "$vfsroot/public/$vfspath"; | my $modjson = parse_json_file_full_path("$vfsroot/$mod/mod.json"); | ||||
if (not -e $fn) | my $modjsondeps = $modjson->{'dependencies'}; | ||||
for my $dep (@{$modjsondeps}) | |||||
{ | |||||
# 0ad's folder isn't named like the mod. | |||||
if(index($dep, "0ad") != -1) | |||||
{ | { | ||||
$fn = "$vfsroot/mod/$vfspath"; | $dep = "public"; | ||||
} | } | ||||
return $fn; | } | ||||
return $modjsondeps; | |||||
} | |||||
sub get_mod_dependencies_string | |||||
{ | |||||
my ($mod) = @_; | |||||
return join( '|',@{get_mod_dependencies($mod)}); | |||||
} | |||||
sub vfs_to_physical | |||||
{ | |||||
my ($vfsPath) = @_; | |||||
my $fn = vfs_to_relative_to_mods($vfsPath); | |||||
return "$vfsroot/$fn"; | |||||
} | } | ||||
sub vfs_to_relative_to_mods | sub vfs_to_relative_to_mods | ||||
{ | { | ||||
my ($vfspath) = @_; | my ($vfsPath) = @_; | ||||
my $fn = "public/$vfspath"; | |||||
for my $dep (@mods_list) | |||||
{ | |||||
my $fn = "$dep/$vfsPath"; | |||||
if (-e "$vfsroot/$fn") | |||||
{ | |||||
return $fn; | return $fn; | ||||
} | } | ||||
} | |||||
} | |||||
sub find_files | sub find_files | ||||
{ | { | ||||
my ($vfspath, $extn) = @_; | my ($vfsPath, $extn) = @_; | ||||
my @files; | my @files; | ||||
my $find_process = sub { | my $find_process = sub { | ||||
return $File::Find::prune = 1 if $_ eq '.svn'; | return $File::Find::prune = 1 if $_ eq '.svn'; | ||||
my $n = $File::Find::name; | my $n = $File::Find::name; | ||||
return if /~$/; | return if /~$/; | ||||
return unless -f $_; | return unless -f $_; | ||||
return unless /\.($extn)$/; | return unless /\.($extn)$/; | ||||
$n =~ s~\Q$vfsroot\E/(public|mod)/~~; | $n =~ s~\Q$vfsroot\E/($mod_list_string)/~~; | ||||
push @files, $n; | push @files, $n; | ||||
}; | }; | ||||
find({ wanted => $find_process }, "$vfsroot/public/$vfspath"); | |||||
find({ wanted => $find_process }, "$vfsroot/mod/$vfspath") if -d "$vfsroot/mod/$vfspath"; | for my $dep (@mods_list) | ||||
{ | |||||
find({ wanted => $find_process },"$vfsroot/$dep/$vfsPath") if -d "$vfsroot/$dep/$vfsPath"; | |||||
} | |||||
return @files; | return @files; | ||||
} | } | ||||
sub parse_json_file | sub parse_json_file_full_path | ||||
{ | { | ||||
my ($vfspath) = @_; | my ($vfspath) = @_; | ||||
open my $fh, vfs_to_physical($vfspath) or die "Failed to open '$vfspath': $!"; | open my $fh, $vfspath or die "Failed to open '$vfspath': $!"; | ||||
# decode_json expects a UTF-8 string and doesn't handle BOMs, so we strip those | # decode_json expects a UTF-8 string and doesn't handle BOMs, so we strip those | ||||
# (see http://trac.wildfiregames.com/ticket/1556) | # (see http://trac.wildfiregames.com/ticket/1556) | ||||
return decode_json(do { local $/; my $file = <$fh>; $file =~ s/^\xEF\xBB\xBF//; $file }); | return decode_json(do { local $/; my $file = <$fh>; $file =~ s/^\xEF\xBB\xBF//; $file }); | ||||
} | } | ||||
sub parse_json_file | |||||
{ | |||||
my ($vfspath) = @_; | |||||
return parse_json_file_full_path(vfs_to_physical($vfspath)) | |||||
} | |||||
sub add_entities | sub add_entities | ||||
{ | { | ||||
print "Loading entities...\n"; | print "Loading entities...\n"; | ||||
my @entfiles = find_files('simulation/templates', 'xml'); | my @entfiles = find_files('simulation/templates', 'xml'); | ||||
s~^simulation/templates/(.*)\.xml$~$1~ for @entfiles; | s~^simulation/templates/(.*)\.xml$~$1~ for @entfiles; | ||||
for my $f (sort @entfiles) | for my $f (sort @entfiles) | ||||
{ | { | ||||
my $path = "simulation/templates/$f.xml"; | my $path = "simulation/templates/$f.xml"; | ||||
push @files, $path; | push @files, $path; | ||||
my $ent = Entity::load_inherited($f); | my $ent = Entity::load_inherited($f, "$mod_list_string"); | ||||
push @deps, [ $path, "simulation/templates/" . $ent->{Entity}{'@parent'}{' content'} . ".xml" ] if $ent->{Entity}{'@parent'}; | push @deps, [ $path, "simulation/templates/" . $ent->{Entity}{'@parent'}{' content'} . ".xml" ] if $ent->{Entity}{'@parent'}; | ||||
if ($f !~ /^template_/) | if ($f !~ /^template_/) | ||||
{ | { | ||||
push @roots, $path; | push @roots, $path; | ||||
if ($ent->{Entity}{VisualActor}) | if ($ent->{Entity}{VisualActor}) | ||||
{ | { | ||||
push @deps, [ $path, "art/actors/" . $ent->{Entity}{VisualActor}{Actor}{' content'} ] if $ent->{Entity}{VisualActor}{Actor}; | push @deps, [ $path, "art/actors/" . $ent->{Entity}{VisualActor}{Actor}{' content'} ] if $ent->{Entity}{VisualActor}{Actor}; | ||||
Show All 12 Lines | for my $f (sort @entfiles) | ||||
$soundPath =~ s/{gender}/$gender/g; | $soundPath =~ s/{gender}/$gender/g; | ||||
$soundPath =~ s/{lang}/$lang/g; | $soundPath =~ s/{lang}/$lang/g; | ||||
push @deps, [ $path, "audio/" . $soundPath ]; | push @deps, [ $path, "audio/" . $soundPath ]; | ||||
} | } | ||||
} | } | ||||
if ($ent->{Entity}{Identity}) | if ($ent->{Entity}{Identity}) | ||||
{ | { | ||||
push @deps, [ $path, "art/textures/ui/session/portraits/" . $ent->{Entity}{Identity}{Icon}{' content'} ] if $ent->{Entity}{Identity}{Icon}; | push @deps, [ $path, "art/textures/ui/session/portraits/" . $ent->{Entity}{Identity}{Icon}{' content'} ] if $ent->{Entity}{Identity}{Icon} and $ent->{Entity}{Identity}{Icon}{' content'} ne ''; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
sub push_variant_dependencies | |||||
{ | |||||
my ($variant, $f) = @_; | |||||
push @deps, [ $f, "art/variants/$variant->{file}" ] if $variant->{file}; | |||||
push @deps, [ $f, "art/meshes/$variant->{mesh}" ] if $variant->{mesh}; | |||||
push @deps, [ $f, "art/particles/$variant->{particles}{file}" ] if $variant->{particles}{file}; | |||||
for my $tex (@{$variant->{textures}{texture}}) | |||||
{ | |||||
push @deps, [ $f, "art/textures/skins/$tex->{file}" ] if $tex->{file}; | |||||
} | |||||
for my $prop (@{$variant->{props}{prop}}) | |||||
{ | |||||
push @deps, [ $f, "art/actors/$prop->{actor}" ] if $prop->{actor}; | |||||
} | |||||
for my $anim (@{$variant->{animations}{animation}}) | |||||
{ | |||||
push @deps, [ $f, "art/animation/$anim->{file}" ] if $anim->{file}; | |||||
} | |||||
} | |||||
sub add_actors | sub add_actors | ||||
{ | { | ||||
print "Loading actors...\n"; | print "Loading actors...\n"; | ||||
my @actorfiles = find_files('art/actors', 'xml'); | my @actorfiles = find_files('art/actors', 'xml'); | ||||
for my $f (sort @actorfiles) | for my $f (sort @actorfiles) | ||||
{ | { | ||||
push @files, $f; | push @files, $f; | ||||
push @roots, $f if ROOT_ACTORS; | push @roots, $f if $rootActors; | ||||
my $actor = XMLin(vfs_to_physical($f), ForceArray => [qw(group variant texture prop animation)], KeyAttr => []) or die "Failed to parse '$f': $!"; | my $actor = XMLin(vfs_to_physical($f), ForceArray => [qw(group variant texture prop animation)], KeyAttr => []) or die "Failed to parse '$f': $!"; | ||||
for my $group (@{$actor->{group}}) | for my $group (@{$actor->{group}}) | ||||
{ | { | ||||
for my $variant (@{$group->{variant}}) | for my $variant (@{$group->{variant}}) | ||||
{ | { | ||||
push @deps, [ $f, "art/meshes/$variant->{mesh}" ] if $variant->{mesh}; | push_variant_dependencies($variant, $f); | ||||
push @deps, [ $f, "art/particles/$variant->{particles}{file}" ] if $variant->{particles}{file}; | |||||
for my $tex (@{$variant->{textures}{texture}}) | |||||
{ | |||||
push @deps, [ $f, "art/textures/skins/$tex->{file}" ] if $tex->{file}; | |||||
} | } | ||||
for my $prop (@{$variant->{props}{prop}}) | |||||
{ | |||||
push @deps, [ $f, "art/actors/$prop->{actor}" ] if $prop->{actor}; | |||||
} | |||||
for my $anim (@{$variant->{animations}{animation}}) | |||||
{ | |||||
push @deps, [ $f, "art/animation/$anim->{file}" ] if $anim->{file}; | |||||
} | } | ||||
push @deps, [ $f, "art/materials/$actor->{material}" ] if $actor->{material}; | |||||
} | } | ||||
} | } | ||||
push @deps, [ $f, "art/materials/$actor->{material}" ] if $actor->{material}; | |||||
sub add_variants | |||||
{ | |||||
print "Loading variants...\n"; | |||||
my @variantfiles = find_files('art/variants', 'xml'); | |||||
for my $f (sort @variantfiles) | |||||
{ | |||||
push @files, $f; | |||||
push @roots, $f if $rootActors; | |||||
my $variant = XMLin(vfs_to_physical($f), ForceArray => [qw(texture prop animation)], KeyAttr => []) or die "Failed to parse '$f': $!"; | |||||
push_variant_dependencies($variant, $f); | |||||
} | } | ||||
} | } | ||||
sub add_art | sub add_art | ||||
{ | { | ||||
print "Loading art files...\n"; | print "Loading art files...\n"; | ||||
push @files, find_files('art/textures/particles', 'dds|png|jpg|tga'); | push @files, find_files('art/textures/particles', 'dds|png|jpg|tga'); | ||||
push @files, find_files('art/textures/terrain', 'dds|png|jpg|tga'); | push @files, find_files('art/textures/terrain', 'dds|png|jpg|tga'); | ||||
push @files, find_files('art/textures/skins', 'dds|png|jpg|tga'); | push @files, find_files('art/textures/skins', 'dds|png|jpg|tga'); | ||||
push @files, find_files('art/meshes', 'pmd|dae'); | push @files, find_files('art/meshes', 'pmd|dae'); | ||||
push @files, find_files('art/animation', 'psa|dae'); | push @files, find_files('art/animation', 'psa|dae'); | ||||
} | } | ||||
sub add_materials | sub add_materials | ||||
{ | { | ||||
elexis: L180-L193 is copypasta, better put it into a function, so maintenance and extension is half as… | |||||
print "Loading materials...\n"; | print "Loading materials...\n"; | ||||
my @materialfiles = find_files('art/materials', 'xml'); | my @materialfiles = find_files('art/materials', 'xml'); | ||||
for my $f (sort @materialfiles) | for my $f (sort @materialfiles) | ||||
{ | { | ||||
push @files, $f; | push @files, $f; | ||||
my $material = XMLin(vfs_to_physical($f), ForceArray => [qw(alternative)], KeyAttr => []); | my $material = XMLin(vfs_to_physical($f), ForceArray => [qw(alternative)], KeyAttr => []); | ||||
for my $alternative (@{$material->{alternative}}) | for my $alternative (@{$material->{alternative}}) | ||||
Show All 18 Lines | |||||
sub add_maps_xml | sub add_maps_xml | ||||
{ | { | ||||
print "Loading maps XML...\n"; | print "Loading maps XML...\n"; | ||||
my @mapfiles = find_files('maps/scenarios', 'xml'); | my @mapfiles = find_files('maps/scenarios', 'xml'); | ||||
push @mapfiles, find_files('maps/skirmishes', 'xml'); | push @mapfiles, find_files('maps/skirmishes', 'xml'); | ||||
for my $f (sort @mapfiles) | for my $f (sort @mapfiles) | ||||
{ | { | ||||
print " $f\n"; | |||||
push @files, $f; | push @files, $f; | ||||
push @roots, $f; | push @roots, $f; | ||||
my $map = XMLin(vfs_to_physical($f), ForceArray => [qw(Entity)], KeyAttr => []) or die "Failed to parse '$f': $!"; | my $map = XMLin(vfs_to_physical($f), ForceArray => [qw(Entity)], KeyAttr => []) or die "Failed to parse '$f': $!"; | ||||
my %used; | my %used; | ||||
for my $entity (@{$map->{Entities}{Entity}}) | for my $entity (@{$map->{Entities}{Entity}}) | ||||
{ | { | ||||
$used{$entity->{Template}} = 1; | $used{$entity->{Template}} = 1; | ||||
▲ Show 20 Lines • Show All 222 Lines • ▼ Show 20 Lines | |||||
{ | { | ||||
print "Loading random maps...\n"; | print "Loading random maps...\n"; | ||||
push @files, find_files('maps/random', 'js'); | push @files, find_files('maps/random', 'js'); | ||||
my @rmsdefs = find_files('maps/random', 'json'); | my @rmsdefs = find_files('maps/random', 'json'); | ||||
for my $f (sort @rmsdefs) | for my $f (sort @rmsdefs) | ||||
{ | { | ||||
if ($f =~ /^maps\/random\/rmbiome/) | |||||
{ | |||||
next; | |||||
} | |||||
push @files, $f; | push @files, $f; | ||||
push @roots, $f; | push @roots, $f; | ||||
my $rms = parse_json_file($f); | my $rms = parse_json_file($f); | ||||
if($rms->{settings}{Script}) | |||||
{ | |||||
push @deps, [ $f, "maps/random/" . $rms->{settings}{Script} ]; | push @deps, [ $f, "maps/random/" . $rms->{settings}{Script} ]; | ||||
} | |||||
# Map previews | # Map previews | ||||
push @deps, [ $f, "art/textures/ui/session/icons/mappreview/" . $rms->{settings}{Preview} ] if $rms->{settings}{Preview}; | push @deps, [ $f, "art/textures/ui/session/icons/mappreview/" . $rms->{settings}{Preview} ] if $rms->{settings}{Preview}; | ||||
} | } | ||||
} | } | ||||
sub add_techs | sub add_techs | ||||
{ | { | ||||
▲ Show 20 Lines • Show All 48 Lines • ▼ Show 20 Lines | sub check_deps | ||||
for my $d (@deps) | for my $d (@deps) | ||||
{ | { | ||||
push @{$revdeps{$d->[1]}}, $d->[0]; | push @{$revdeps{$d->[1]}}, $d->[0]; | ||||
} | } | ||||
for my $f (sort keys %revdeps) | for my $f (sort keys %revdeps) | ||||
{ | { | ||||
next if exists $files{$f}; | next if exists $files{$f}; | ||||
warn "Missing file '$f' referenced by: " . (join ', ', map "'$_'", map vfs_to_relative_to_mods($_), sort @{$revdeps{$f}}) . "\n"; | warn "Missing file '$f' referenced by: " . (join ', ', map "'$_'", map vfs_to_relative_to_mods($_), sort @{$revdeps{$f}}) . "\n"; | ||||
if (exists $lcfiles{lc $f}) | if (exists $lcfiles{lc $f}) | ||||
{ | { | ||||
warn "### Case-insensitive match (found '$lcfiles{lc $f}')\n"; | warn "### Case-insensitive match (found '$lcfiles{lc $f}')\n"; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
sub check_unused | sub check_unused | ||||
{ | { | ||||
my ($modName) = @_; | |||||
my %reachable; | my %reachable; | ||||
@reachable{@roots} = (); | @reachable{@roots} = (); | ||||
my %deps; | my %deps; | ||||
for my $d (@deps) | for my $d (@deps) | ||||
{ | { | ||||
push @{$deps{$d->[0]}}, $d->[1]; | push @{$deps{$d->[0]}}, $d->[1]; | ||||
} | } | ||||
while (1) | while (1) | ||||
{ | { | ||||
my @newreachable; | my @newreachable; | ||||
for my $r (keys %reachable) | for my $r (keys %reachable) | ||||
{ | { | ||||
push @newreachable, grep { not exists $reachable{$_} } @{$deps{$r}}; | push @newreachable, grep { not exists $reachable{$_} } @{$deps{$r}}; | ||||
} | } | ||||
last if @newreachable == 0; | last if @newreachable == 0; | ||||
@reachable{@newreachable} = (); | @reachable{@newreachable} = (); | ||||
} | } | ||||
my $terrains = "$modName/art/terrains/"; | |||||
my $random = "$modName/maps/random/"; | |||||
my $materials = "$modName/art/materials/"; | |||||
for my $f (sort @files) | for my $f (sort @files) | ||||
{ | { | ||||
next if exists $reachable{$f}; | my $fullPath = vfs_to_relative_to_mods($f); | ||||
warn "Unused file '" . vfs_to_relative_to_mods($f) . "'\n"; | next if exists $reachable{$f} || ( | ||||
index($fullPath, $terrains) != -1 || | |||||
index($fullPath, $random) != -1 || | |||||
index($fullPath, $materials) != -1); | |||||
warn "Unused file '" . $fullPath . "'\n"; | |||||
} | } | ||||
} | } | ||||
add_maps_xml() if CHECK_MAPS_XML; | add_maps_xml() if $checkMapXml; | ||||
add_maps_pmp(); | add_maps_pmp(); | ||||
add_entities(); | add_entities(); | ||||
add_actors(); | add_actors(); | ||||
add_variants(); | |||||
add_art(); | add_art(); | ||||
add_materials(); | add_materials(); | ||||
add_particles(); | add_particles(); | ||||
add_soundgroups(); | add_soundgroups(); | ||||
add_audio(); | add_audio(); | ||||
add_gui_xml(); | add_gui_xml(); | ||||
add_gui_data(); | add_gui_data(); | ||||
add_civs(); | add_civs(); | ||||
add_rms(); | add_rms(); | ||||
add_techs(); | add_techs(); | ||||
add_terrains(); | add_terrains(); | ||||
# TODO: add non-skin textures, and all the references to them | |||||
print "\n"; | print "\n"; | ||||
check_deps(); | check_deps(); | ||||
print "\n"; | print "\n"; | ||||
check_unused(); | check_unused($modToCheck) if $checkUnused; | ||||
print "\n"; | |||||
system("perl ../xmlvalidator/validate.pl") if $validateTemplates; |
Wildfire Games · Phabricator
L180-L193 is copypasta, better put it into a function, so maintenance and extension is half as expensive