Merge pull request #480 from thatsIch/uuid

Fixes #473, catches outdated player names and checks if all entries are UUIDs before converted and thus prevents the server crashing.
This commit is contained in:
thatsIch 2014-11-30 14:21:38 +01:00
commit 78bf0ec608
6 changed files with 423 additions and 140 deletions

View file

@ -1,3 +1,23 @@
/*
* This file is part of Applied Energistics 2.
* Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved.
*
* Applied Energistics 2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Applied Energistics 2 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Applied Energistics 2. If not, see <http://www.gnu.org/licenses/lgpl>.
*/
repositories {
maven {
@ -41,4 +61,6 @@ dependencies {
// add hacked buildcraft jar to compile time (for facades)
compile fileTree(dir: 'libs', include: '*.jar')
testCompile "junit:junit:4.11"
}

View file

@ -0,0 +1,79 @@
/*
* This file is part of Applied Energistics 2.
* Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved.
*
* Applied Energistics 2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Applied Energistics 2 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Applied Energistics 2. If not, see <http://www.gnu.org/licenses/lgpl>.
*/
package appeng.core;
import java.util.Map;
import java.util.UUID;
import net.minecraftforge.common.config.ConfigCategory;
import cpw.mods.fml.relauncher.FMLRelaunchLog;
import com.google.common.base.Optional;
/**
* Wrapper class for the player mappings.
* Will grant access to a pre initialized player map
* based on the "players" category in the settings.cfg
*/
public class PlayerMappings
{
/**
* View of player mappings, is not immutable,
* since it needs to be edited upon runtime,
* cause new players can join
*/
private final Map<Integer, UUID> mappings;
public PlayerMappings( ConfigCategory category, FMLRelaunchLog log )
{
final PlayerMappingsInitializer init = new PlayerMappingsInitializer( category, log );
this.mappings = init.getPlayerMappings();
}
/**
* Tries to retrieve the UUID of a player.
* Might not be stored inside of the map.
* Should not happen though.
*
* @param id ID of the to be searched player
*
* @return maybe the UUID of the searched player
*/
public Optional<UUID> get( int id )
{
final UUID maybe = this.mappings.get( id );
return Optional.fromNullable( maybe );
}
/**
* Put in new players when they join the server
*
* @param id id of new player
* @param uuid UUID of new player
*/
public void put( int id, UUID uuid )
{
this.mappings.put( id, uuid );
}
}

View file

@ -0,0 +1,94 @@
/*
* This file is part of Applied Energistics 2.
* Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved.
*
* Applied Energistics 2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Applied Energistics 2 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Applied Energistics 2. If not, see <http://www.gnu.org/licenses/lgpl>.
*/
package appeng.core;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import net.minecraftforge.common.config.ConfigCategory;
import net.minecraftforge.common.config.Property;
import cpw.mods.fml.relauncher.FMLRelaunchLog;
import appeng.util.UUIDMatcher;
/**
* Initializes a map of ID to UUID from the player list in the settings.cfg
*/
public class PlayerMappingsInitializer
{
/**
* Internal immutable mapping
*/
private final Map<Integer, UUID> playerMappings;
/**
* Creates the initializer for the player mappings.
* The map will be filled upon construction
* and will only be filled with valid entries.
* If an invalid entry is found, an warning is printed,
* mostly due to migration problems from 1.7.2 to 1.7.10
* where the UUIDs were introduced.
*
* @param playerList the category for the player list, generally extracted using the "players" tag
* @param log the logger used to warn the server or user of faulty entries
*/
public PlayerMappingsInitializer( ConfigCategory playerList, FMLRelaunchLog log )
{
// Matcher for UUIDs
final UUIDMatcher matcher = new UUIDMatcher();
// Initial capacity for mappings
final int capacity = playerList.size();
// Mappings for the IDs is a regular HashMap
this.playerMappings = new HashMap<Integer, UUID>( capacity );
// Iterates through every pair of UUID to ID
for ( Map.Entry<String, Property> entry : playerList.getValues().entrySet() )
{
final String maybeUUID = entry.getKey();
final int id = entry.getValue().getInt();
if ( matcher.isUUID( maybeUUID ) )
{
final UUID UUIDString = UUID.fromString( maybeUUID );
this.playerMappings.put( id, UUIDString );
}
else
{
AELog.warning( "The configuration for players contained an outdated entry instead an expected UUID " + maybeUUID + " for the player " + id + ". Please clean this up." );
}
}
}
/**
* Getter
*
* @return Immutable map of the players mappings of their ID to their UUID
*/
public Map<Integer, UUID> getPlayerMappings()
{
return this.playerMappings;
}
}

View file

@ -26,15 +26,11 @@ import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
import java.util.UUID;
import java.util.WeakHashMap;
import com.mojang.authlib.GameProfile;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.nbt.CompressedStreamTools;
@ -45,6 +41,9 @@ import net.minecraftforge.common.config.ConfigCategory;
import net.minecraftforge.common.config.Configuration;
import net.minecraftforge.common.config.Property;
import com.google.common.base.Optional;
import com.mojang.authlib.GameProfile;
import appeng.api.util.WorldCoord;
import appeng.core.sync.network.NetworkHandler;
import appeng.core.sync.packets.PacketNewStorageDimension;
@ -57,17 +56,17 @@ import appeng.services.CompassService;
public class WorldSettings extends Configuration
{
private static final String SPAWNDATA_FOLDER = "spawndata";
private static final String COMPASS_Folder = "compass";
private static WorldSettings instance;
private final List<Integer> storageCellDims = new ArrayList<Integer>();
private final File aeFolder;
private final CompassService compass;
private long lastGridStorage = 0;
private int lastPlayer = 0;
private final PlayerMappings mappings;
private final WeakHashMap<GridStorageSearch, WeakReference<GridStorageSearch>> loadedStorage = new WeakHashMap<GridStorageSearch, WeakReference<GridStorageSearch>>();
private long lastGridStorage;
private int lastPlayer;
public WorldSettings( File aeFolder )
{
@ -91,6 +90,69 @@ public class WorldSettings extends Configuration
lastGridStorage = 0;
lastPlayer = 0;
}
final ConfigCategory playerList = this.getCategory( "players" );
this.mappings = new PlayerMappings( playerList, AELog.instance );
}
public static WorldSettings getInstance()
{
if ( instance == null )
{
File world = DimensionManager.getCurrentSaveRootDirectory();
File aeBaseFolder = new File( world.getPath() + File.separatorChar + "AE2" );
if ( !aeBaseFolder.isDirectory() && !aeBaseFolder.mkdir() )
{
throw new RuntimeException( "Failed to create " + aeBaseFolder.getAbsolutePath() );
}
File compass = new File( aeBaseFolder, COMPASS_Folder );
if ( !compass.isDirectory() && !compass.mkdir() )
{
throw new RuntimeException( "Failed to create " + compass.getAbsolutePath() );
}
File spawnData = new File( aeBaseFolder, SPAWNDATA_FOLDER );
if ( !spawnData.isDirectory() && !spawnData.mkdir() )
{
throw new RuntimeException( "Failed to create " + spawnData.getAbsolutePath() );
}
instance = new WorldSettings( aeBaseFolder );
}
return instance;
}
public Collection<NBTTagCompound> getNearByMeteorites( int dim, int chunkX, int chunkZ )
{
LinkedList<NBTTagCompound> ll = new LinkedList<NBTTagCompound>();
synchronized ( WorldSettings.class )
{
for ( int x = -1; x <= 1; x++ )
{
for ( int z = -1; z <= 1; z++ )
{
int cx = x + ( chunkX >> 4 );
int cz = z + ( chunkZ >> 4 );
NBTTagCompound data = loadSpawnData( dim, cx << 4, cz << 4 );
if ( data != null )
{
// edit.
int size = data.getInteger( "num" );
for ( int s = 0; s < size; s++ )
ll.add( data.getCompoundTag( "" + s ) );
}
}
}
}
return ll;
}
NBTTagCompound loadSpawnData( int dim, int chunkX, int chunkZ )
@ -138,6 +200,28 @@ public class WorldSettings extends Configuration
return data;
}
public boolean hasGenerated( int dim, int chunkX, int chunkZ )
{
synchronized ( WorldSettings.class )
{
NBTTagCompound data = loadSpawnData( dim, chunkX, chunkZ );
return data.getBoolean( chunkX + "," + chunkZ );
}
}
public void setGenerated( int dim, int chunkX, int chunkZ )
{
synchronized ( WorldSettings.class )
{
NBTTagCompound data = loadSpawnData( dim, chunkX, chunkZ );
// edit.
data.setBoolean( chunkX + "," + chunkZ, true );
writeSpawnData( dim, chunkX, chunkZ, data );
}
}
void writeSpawnData( int dim, int chunkX, int chunkZ, NBTTagCompound data )
{
if ( !Thread.holdsLock( WorldSettings.class ) )
@ -171,57 +255,6 @@ public class WorldSettings extends Configuration
}
}
public Collection<NBTTagCompound> getNearByMeteorites( int dim, int chunkX, int chunkZ )
{
LinkedList<NBTTagCompound> ll = new LinkedList<NBTTagCompound>();
synchronized ( WorldSettings.class )
{
for ( int x = -1; x <= 1; x++ )
{
for ( int z = -1; z <= 1; z++ )
{
int cx = x + ( chunkX >> 4 );
int cz = z + ( chunkZ >> 4 );
NBTTagCompound data = loadSpawnData( dim, cx << 4, cz << 4 );
if ( data != null )
{
// edit.
int size = data.getInteger( "num" );
for ( int s = 0; s < size; s++ )
ll.add( data.getCompoundTag( "" + s ) );
}
}
}
}
return ll;
}
public boolean hasGenerated( int dim, int chunkX, int chunkZ )
{
synchronized ( WorldSettings.class )
{
NBTTagCompound data = loadSpawnData( dim, chunkX, chunkZ );
return data.getBoolean( chunkX + "," + chunkZ );
}
}
public void setGenerated( int dim, int chunkX, int chunkZ )
{
synchronized ( WorldSettings.class )
{
NBTTagCompound data = loadSpawnData( dim, chunkX, chunkZ );
// edit.
data.setBoolean( chunkX + "," + chunkZ, true );
writeSpawnData( dim, chunkX, chunkZ, data );
}
}
public boolean addNearByMeteorites( int dim, int chunkX, int chunkZ, NBTTagCompound newData )
{
synchronized ( WorldSettings.class )
@ -252,8 +285,24 @@ public class WorldSettings extends Configuration
instance = null;
}
final List<Integer> storageCellDims = new ArrayList<Integer>();
HashMap<Integer, UUID> idToUUID;
@Override
public void save()
{
// populate new data
for ( GridStorageSearch gs : loadedStorage.keySet() )
{
GridStorage thisStorage = gs.gridStorage.get();
if ( thisStorage != null && thisStorage.getGrid() != null && !thisStorage.getGrid().isEmpty() )
{
String value = thisStorage.getValue();
get( "gridstorage", "" + thisStorage.getID(), value ).set( value );
}
}
// save to files
if ( hasChanged() )
super.save();
}
public void addStorageCellDim( int newDim )
{
@ -276,37 +325,6 @@ public class WorldSettings extends Configuration
return compass;
}
public static WorldSettings getInstance()
{
if ( instance == null )
{
File world = DimensionManager.getCurrentSaveRootDirectory();
File aeBaseFolder = new File( world.getPath() + File.separatorChar + "AE2" );
if ( !aeBaseFolder.isDirectory() && !aeBaseFolder.mkdir() )
{
throw new RuntimeException( "Failed to create " + aeBaseFolder.getAbsolutePath() );
}
File compass = new File( aeBaseFolder, COMPASS_Folder );
if ( !compass.isDirectory() && !compass.mkdir() )
{
throw new RuntimeException( "Failed to create " + compass.getAbsolutePath() );
}
File spawnData = new File( aeBaseFolder, SPAWNDATA_FOLDER );
if ( !spawnData.isDirectory() && !spawnData.mkdir() )
{
throw new RuntimeException( "Failed to create " + spawnData.getAbsolutePath() );
}
instance = new WorldSettings( aeBaseFolder );
}
return instance;
}
public void sendToPlayer( NetworkManager manager, EntityPlayerMP player )
{
if ( manager != null )
@ -328,8 +346,6 @@ public class WorldSettings extends Configuration
save();
}
private final WeakHashMap<GridStorageSearch, WeakReference<GridStorageSearch>> loadedStorage = new WeakHashMap<GridStorageSearch, WeakReference<GridStorageSearch>>();
public WorldCoord getStoredSize( int dim )
{
int x = get( "StorageCell" + dim, "scaleX", 0 ).getInt();
@ -348,8 +364,9 @@ public class WorldSettings extends Configuration
/**
* lazy loading, can load any id, even ones that don't exist anymore.
*
*
* @param storageID ID of grid storage
*
* @return corresponding grid storage
*/
public GridStorage getGridStorage( long storageID )
@ -381,30 +398,6 @@ public class WorldSettings extends Configuration
return newStorage;
}
public void destroyGridStorage( long id )
{
this.getCategory( "gridstorage" ).remove( "" + id );
}
@Override
public void save()
{
// populate new data
for ( GridStorageSearch gs : loadedStorage.keySet() )
{
GridStorage thisStorage = gs.gridStorage.get();
if ( thisStorage != null && thisStorage.getGrid() != null && !thisStorage.getGrid().isEmpty() )
{
String value = thisStorage.getValue();
get( "gridstorage", "" + thisStorage.getID(), value ).set( value );
}
}
// save to files
if ( hasChanged() )
super.save();
}
private long nextGridStorage()
{
long r = lastGridStorage++;
@ -412,11 +405,9 @@ public class WorldSettings extends Configuration
return r;
}
private long nextPlayer()
public void destroyGridStorage( long id )
{
long r = lastPlayer++;
get( "Counters", "lastPlayer", lastPlayer ).set( lastPlayer );
return r;
this.getCategory( "gridstorage" ).remove( "" + id );
}
public int getNextOrderedValue( String name )
@ -442,36 +433,29 @@ public class WorldSettings extends Configuration
else
{
playerList.put( uuid, prop = new Property( uuid, "" + nextPlayer(), Property.Type.INTEGER ) );
getUUIDMap().put( prop.getInt(), profile.getId() ); // add to reverse map
this.mappings.put( prop.getInt(), profile.getId() ); // add to reverse map
save();
return prop.getInt();
}
}
public HashMap<Integer, UUID> getUUIDMap()
private long nextPlayer()
{
if ( idToUUID == null )
{
idToUUID = new HashMap<Integer, UUID>();
ConfigCategory playerList = this.getCategory( "players" );
for ( Entry<String, Property> b : playerList.getValues().entrySet() )
idToUUID.put( b.getValue().getInt(), UUID.fromString( b.getKey() ) );
}
return idToUUID;
long r = lastPlayer++;
get( "Counters", "lastPlayer", lastPlayer ).set( lastPlayer );
return r;
}
public EntityPlayer getPlayerFromID( int playerID )
{
UUID id = getUUIDMap().get( playerID );
Optional<UUID> maybe = this.mappings.get( playerID );
if ( id != null )
if ( maybe.isPresent() )
{
final UUID uuid = maybe.get();
for ( EntityPlayer player : CommonHelper.proxy.getPlayers() )
{
if ( player.getUniqueID().equals( id ) )
if ( player.getUniqueID().equals( uuid ) )
return player;
}
}

View file

@ -0,0 +1,43 @@
/*
* This file is part of Applied Energistics 2.
* Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved.
*
* Applied Energistics 2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Applied Energistics 2 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Applied Energistics 2. If not, see <http://www.gnu.org/licenses/lgpl>.
*/
package appeng.util;
/**
* Regex wrapper for UUIDs to not rely on try catch
*/
public final class UUIDMatcher
{
/**
* String which is the regular expression for UUIDs
*/
private static final String UUID_REGEX = "[0-9a-fA-F]{8}(?:-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}";
/**
* Checks if a potential UUID is an UUID by applying a regular expression on it.
*
* @param potential to be checked potential UUID
*
* @return true, if the potential UUID is indeed an UUID
*/
public boolean isUUID( String potential )
{
return potential.matches( UUID_REGEX );
}
}

View file

@ -0,0 +1,61 @@
/*
* This file is part of Applied Energistics 2.
* Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved.
*
* Applied Energistics 2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Applied Energistics 2 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Applied Energistics 2. If not, see <http://www.gnu.org/licenses/lgpl>.
*/
package appeng.util;
import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Tests for {@link UUIDMatcher}
*/
public class UUIDMatcherTest
{
private static final String isUUID = "03ba29a1-d6bd-32ba-90b2-375e4d65abc9";
private static final String noUUID = "no";
private static final String invalidUUID = "g3ba29a1-d6bd-32ba-90b2-375e4d65abc9";
private final UUIDMatcher matcher;
public UUIDMatcherTest()
{
this.matcher = new UUIDMatcher();
}
@Test
public void testUUID_shouldPass()
{
assertTrue( this.matcher.isUUID( isUUID ) );
}
@Test
public void testNoUUD_shouldPass()
{
assertFalse( this.matcher.isUUID( noUUID ) );
}
@Test
public void testInvalidUUID_shouldPass()
{
assertFalse( this.matcher.isUUID( invalidUUID ) );
}
}