From 97211c9285fb98384166fc8134504151270a1617 Mon Sep 17 00:00:00 2001 From: Jens Ayton Date: Wed, 25 Jul 2007 16:43:29 +0000 Subject: [PATCH] * Fixed backing up of log (OS X only). * Only release plist parsing error messages under OS X (this is a documented requirement that doesn't follow normal usage, and isn't the right thing under GNUstep.) * Work on shipdata.plist and model verifier stages. * equipment_price_factor fixes: exploit when low equipment_price_factor doesn't affect refund prices, minimum equipment_price_factor of 0.5. * Made fuel unsigned. * Implemented property list schema verifier to simplify implementation of plist checkers. git-svn-id: http://svn.berlios.de/svnroot/repos/oolite-linux/trunk@1104 127b21dd-08f5-0310-b4b7-95ae10353056 --- GNUmakefile | 2 +- Resources/Config/shipdata.plist | 176 --- Resources/Config/verifyOXP.plist | 468 +++++- src/Cocoa/OOLogOutputHandler.m | 9 +- src/Core/Entities/Entity.m | 2 +- src/Core/Entities/PlayerEntity.m | 33 +- .../Entities/PlayerEntityLegacyScriptEngine.m | 12 +- src/Core/Entities/ShipEntity.h | 6 +- src/Core/Entities/ShipEntity.m | 16 +- src/Core/Entities/StationEntity.m | 5 +- src/Core/OOPListParsing.m | 10 +- src/Core/OOTypes.h | 2 + .../OOCheckShipDataPListVerifierStage.h | 2 + .../OOCheckShipDataPListVerifierStage.m | 64 + .../OXPVerifier/OOFileScannerVerifierStage.m | 9 +- src/Core/OXPVerifier/OOModelVerifierStage.h | 21 +- src/Core/OXPVerifier/OOModelVerifierStage.m | 85 +- src/Core/OXPVerifier/OOOXPVerifier.m | 7 +- src/Core/OXPVerifier/OOPListSchemaVerifier.h | 143 ++ src/Core/OXPVerifier/OOPListSchemaVerifier.m | 1357 +++++++++++++++++ src/Core/OXPVerifier/OOTextureVerifierStage.h | 9 +- src/Core/OXPVerifier/OOTextureVerifierStage.m | 11 + .../OXPVerifier/plist verifier design.txt | 42 + 23 files changed, 2217 insertions(+), 274 deletions(-) create mode 100644 src/Core/OXPVerifier/OOPListSchemaVerifier.h create mode 100644 src/Core/OXPVerifier/OOPListSchemaVerifier.m create mode 100644 src/Core/OXPVerifier/plist verifier design.txt diff --git a/GNUmakefile b/GNUmakefile index 67f38068..72cecd62 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -26,7 +26,7 @@ endif OBJC_PROGRAM_NAME = oolite oolite_C_FILES = legacy_random.c strlcpy.c -oolite_OBJC_FILES = Comparison.m AI.m DustEntity.m Entity.m GameController.m GuiDisplayGen.m HeadUpDisplay.m main.m MyOpenGLView.m OpenGLSprite.m ParticleEntity.m PlanetEntity.m PlayerEntityLegacyScriptEngine.m PlayerEntityContracts.m PlayerEntityControls.m PlayerEntityLoadSave.m PlayerEntitySound.m PlayerEntity.m ResourceManager.m RingEntity.m ShipEntityAI.m ShipEntity.m SkyEntity.m StationEntity.m Universe.m OOSound.m SDLMusic.m NSFileManagerOOExtensions.m JoystickHandler.m PlayerEntityStickMapper.m OOBasicSoundReferencePoint.m OOBasicSoundSource.m OOCharacter.m OOTrumble.m WormholeEntity.m NSScannerOOExtensions.m OOXMLExtensions.m NSMutableDictionaryOOExtensions.m Geometry.m Octree.m CollisionRegion.m OOColor.m OOLogging.m OOCacheManager.m OOCache.m OOStringParsing.m OOCollectionExtractors.m OOVector.m OOMatrix.m OOQuaternion.m OOVoxel.m OOTriangle.m OOPListParsing.m OOFastArithmetic.m OOTextureScaling.m OOConstToString.m OOScript.m OOJSScript.m OOJavaScriptEngine.m OOPListScript.m NSStringOOExtensions.m PlayerEntityScriptMethods.m OOWeakReference.m OOJSEntity.m EntityOOJavaScriptExtensions.m OOJSQuaternion.m OOMaterial.m OOShaderMaterial.m OOShaderProgram.m OOShaderUniform.m OOTexture.m OOTextureLoader.m OOPNGTextureLoader.m OOOpenGLExtensionManager.m OOBasicMaterial.m OOSingleTextureMaterial.m OOCPUInfo.m OOSelfDrawingEntity.m OOEntityWithDrawable.m OODrawable.m OOJSVector.m OOMesh.m OOOpenGL.m OOGraphicsResetManager.m OOProbabilisticTextureManager.m OODebugGLDrawing.m OOShaderUniformMethodType.m OOAsyncQueue.m TextureStore.m OOOXPVerifier.m OOOXPVerifierStage.m OOFileScannerVerifierStage.m OOCheckRequiresPListVerifierStage.m OOCheckDemoShipsPListVerifierStage.m OOCheckEquipmentPListVerifierStage.m OOTextureVerifierStage.m OOModelVerifierStage.m OOCheckShipDataPListVerifierStage.m +oolite_OBJC_FILES = Comparison.m AI.m DustEntity.m Entity.m GameController.m GuiDisplayGen.m HeadUpDisplay.m main.m MyOpenGLView.m OpenGLSprite.m ParticleEntity.m PlanetEntity.m PlayerEntityLegacyScriptEngine.m PlayerEntityContracts.m PlayerEntityControls.m PlayerEntityLoadSave.m PlayerEntitySound.m PlayerEntity.m ResourceManager.m RingEntity.m ShipEntityAI.m ShipEntity.m SkyEntity.m StationEntity.m Universe.m OOSound.m SDLMusic.m NSFileManagerOOExtensions.m JoystickHandler.m PlayerEntityStickMapper.m OOBasicSoundReferencePoint.m OOBasicSoundSource.m OOCharacter.m OOTrumble.m WormholeEntity.m NSScannerOOExtensions.m OOXMLExtensions.m NSMutableDictionaryOOExtensions.m Geometry.m Octree.m CollisionRegion.m OOColor.m OOLogging.m OOCacheManager.m OOCache.m OOStringParsing.m OOCollectionExtractors.m OOVector.m OOMatrix.m OOQuaternion.m OOVoxel.m OOTriangle.m OOPListParsing.m OOFastArithmetic.m OOTextureScaling.m OOConstToString.m OOScript.m OOJSScript.m OOJavaScriptEngine.m OOPListScript.m NSStringOOExtensions.m PlayerEntityScriptMethods.m OOWeakReference.m OOJSEntity.m EntityOOJavaScriptExtensions.m OOJSQuaternion.m OOMaterial.m OOShaderMaterial.m OOShaderProgram.m OOShaderUniform.m OOTexture.m OOTextureLoader.m OOPNGTextureLoader.m OOOpenGLExtensionManager.m OOBasicMaterial.m OOSingleTextureMaterial.m OOCPUInfo.m OOSelfDrawingEntity.m OOEntityWithDrawable.m OODrawable.m OOJSVector.m OOMesh.m OOOpenGL.m OOGraphicsResetManager.m OOProbabilisticTextureManager.m OODebugGLDrawing.m OOShaderUniformMethodType.m OOAsyncQueue.m TextureStore.m OOOXPVerifier.m OOOXPVerifierStage.m OOFileScannerVerifierStage.m OOCheckRequiresPListVerifierStage.m OOCheckDemoShipsPListVerifierStage.m OOCheckEquipmentPListVerifierStage.m OOTextureVerifierStage.m OOModelVerifierStage.m OOCheckShipDataPListVerifierStage.m OOPListSchemaVerifier.m include $(GNUSTEP_MAKEFILES)/objc.make include GNUmakefile.postamble diff --git a/Resources/Config/shipdata.plist b/Resources/Config/shipdata.plist index 160b4867..57007109 100644 --- a/Resources/Config/shipdata.plist +++ b/Resources/Config/shipdata.plist @@ -2335,8 +2335,6 @@ 0.5 max_flight_speed 50 - max_missiles - 0 missile_launch_position 0.0 0.0 4.5 missiles @@ -2355,14 +2353,6 @@ thrust 5 - view_position_aft - 0.0 0.0 -1.67 - view_position_forward - 0.0 0.0 4.18 - view_position_port - -1.31 0.76 0.34 - view_position_starboard - 1.31 0.76 0.34 weapon_position_aft 0.0 0.0 -1.67 weapon_position_forward @@ -2418,8 +2408,6 @@ 3.6 max_flight_speed 300 - max_missiles - 2 missile_launch_position 0.0 -10.5 16.5 missiles @@ -2617,8 +2605,6 @@ 3 max_flight_speed 300 - max_missiles - 1 missile_launch_position 0.0 -2.0 10.0 missiles @@ -2631,16 +2617,6 @@ pirate oolite-gecko thrust 45 - view_position_aft - 0.0 5.0 -20.0 - view_position_forward - 0.0 1.9375 5.0 - view_position_port - -11.85 2.825 -3.5 - view_position_starboard - 11.85 2.825 -3.5 - weapon_position_aft - 0.0 -5.0 -20.0 weapon_position_forward 0.0 0.0417 16.6667 weapon_position_port @@ -2702,8 +2678,6 @@ 8 max_flight_speed 0.0 - max_missiles - 0 missiles 0 model @@ -2782,8 +2756,6 @@ 2.75 max_flight_speed 300 - max_missiles - 0 missile_launch_position 0.0 -3.5 16.5 missiles @@ -2796,14 +2768,6 @@ pirate hunter oolite-krait thrust 35 - view_position_aft - 0.0 5.0 -20.0 - view_position_forward - 0.0 3.0 16.0 - view_position_port - -11.25 2.5 6.625 - view_position_starboard - 11.25 2.5 6.625 weapon_position_aft 0.0 -5.0 -20.0 weapon_position_forward @@ -2849,8 +2813,6 @@ 2.1 max_flight_speed 320 - max_missiles - 0 missile_launch_position 0.0 -3.5 3.5 missiles @@ -2863,14 +2825,6 @@ pirate oolite-mamba thrust 32 - view_position_aft - 0.0 4.5 -27.5 - view_position_forward - 0.0 2.4 5.5 - view_position_port - -12.125 0.0 0.0 - view_position_starboard - 12.125 0.0 0.0 weapon_position_aft 0.0 -4.5 -27.5 weapon_position_forward @@ -2912,8 +2866,6 @@ 2.1 max_flight_speed 320 - max_missiles - 0 missile_launch_position 0.0 -3.5 3.5 missiles @@ -2926,14 +2878,6 @@ escort oolite-mamba-escort thrust 32 - view_position_aft - 0.0 4.5 -27.5 - view_position_forward - 0.0 2.4 5.5 - view_position_port - -12.125 0.0 0.0 - view_position_starboard - 12.125 0.0 0.0 weapon_position_aft 0.0 -4.5 -27.5 weapon_position_forward @@ -2971,8 +2915,6 @@ 10 max_flight_speed 750 - max_missiles - 0 missiles 0 model @@ -3028,8 +2970,6 @@ 2.5 max_flight_speed 300 - max_missiles - 2 missile_launch_position 0.0 -7.75 0.5 missiles @@ -3225,8 +3165,6 @@ 2.5 max_flight_speed 300 - max_missiles - 2 missile_launch_position 0.0 -7.75 0.5 missiles @@ -3426,8 +3364,6 @@ 2 max_flight_speed 200 - max_missiles - 2 missile_launch_position 0.0 -20.5 -18.0 missiles @@ -3493,8 +3429,6 @@ 2 max_flight_speed 200 - max_missiles - 2 missile_launch_position 0.0 -20.5 -18.0 missiles @@ -3694,8 +3628,6 @@ 2 max_flight_speed 200 - max_missiles - 2 missile_launch_position 0.0 -20.5 -18.0 missiles @@ -3778,8 +3710,6 @@ 8 max_flight_speed 0.0 - max_missiles - 0 max_scavengers 1 missiles @@ -3878,8 +3808,6 @@ 2 max_flight_speed 80 - max_missiles - 0 missile_launch_position 0.0 -7.25 15.0 missiles @@ -3892,14 +3820,6 @@ shuttle oolite-shuttle thrust 16 - view_position_aft - 0.0 15.0 -49.5 - view_position_forward - 0.0 2.25 14.5 - view_position_port - -3.0 1.5 13.5 - view_position_starboard - 3.0 1.5 13.5 weapon_position_aft 0.0 0.0 -17.5 weapon_position_forward @@ -3941,8 +3861,6 @@ 2.8 max_flight_speed 370 - max_missiles - 0 missile_launch_position 0.0 -3.0 6.0 missiles @@ -3955,14 +3873,6 @@ pirate hunter oolite-sidewinder thrust 37 - view_position_aft - 0.0 7.0 -17.5 - view_position_forward - 0.0 2.5 5.83 - view_position_port - -16.67 2.5 -5.83 - view_position_starboard - 16.67 2.5 -5.83 weapon_position_aft 0.0 -6.5 -17.5 weapon_position_forward @@ -4004,8 +3914,6 @@ 2.8 max_flight_speed 370 - max_missiles - 0 missile_launch_position 0.0 -3.0 6.0 missiles @@ -4018,14 +3926,6 @@ escort hunter oolite-sidewinder-escort thrust 37 - view_position_aft - 0.0 7.0 -17.5 - view_position_forward - 0.0 2.5 5.83 - view_position_port - -16.67 2.5 -5.83 - view_position_starboard - 16.67 2.5 -5.83 weapon_position_aft 0.0 -6.5 -17.5 weapon_position_forward @@ -4193,8 +4093,6 @@ 2 max_flight_speed 500 - max_missiles - 5 missile_role thargon missile_launch_position @@ -4245,8 +4143,6 @@ 2 max_flight_speed 100 - max_missiles - 0 missile_launch_position 0.0 -5.5 0.5 missiles @@ -4259,14 +4155,6 @@ shuttle oolite-transporter thrust 20 - view_position_aft - 0.0 2.5 -17.5 - view_position_forward - 0.0 0.17 13.67 - view_position_port - -5.83 0.17 9.83 - view_position_starboard - 5.83 0.17 9.83 weapon_position_aft 0.0 -1.0 -17.5 weapon_position_forward @@ -4311,8 +4199,6 @@ 2 max_flight_speed 100 - max_missiles - 0 missile_launch_position 0.0 -5.5 0.5 missiles @@ -4325,14 +4211,6 @@ miner oolite-transporter-miner thrust 20 - view_position_aft - 0.0 2.5 -17.5 - view_position_forward - 0.0 0.17 13.67 - view_position_port - -5.83 0.17 9.83 - view_position_starboard - 5.83 0.17 9.83 weapon_position_aft 0.0 -1.0 -17.5 weapon_position_forward @@ -4377,8 +4255,6 @@ 2.8 max_flight_speed 320 - max_missiles - 1 missile_launch_position 0.0 -8.5 -11.0 missiles @@ -4408,14 +4284,6 @@ thrust 32 - view_position_aft - 0.0 6.0 -27.5 - view_position_forward - 0.0 6.0 6.875 - view_position_port - -6.25 4.0 0.0 - view_position_starboard - 6.25 4.0 0.0 weapon_position_aft 0.0 0.0 -27.5 weapon_position_forward @@ -4465,8 +4333,6 @@ 4.2 max_flight_speed 520 - max_missiles - 3 missile_launch_position 0.0 -10.5 -9.0 missiles @@ -4496,14 +4362,6 @@ thrust 50 - view_position_aft - 0.0 8.0 -32.0 - view_position_forward - 0.0 6.0 12.8 - view_position_port - -10.5 3.0 8.9 - view_position_starboard - 10.5 3.0 8.9 weapon_position_aft 0.0 -8.0 -32.0 weapon_position_forward @@ -4548,8 +4406,6 @@ 2.8 max_flight_speed 360 - max_missiles - 1 missile_launch_position 0.0 -8.5 -11.0 missiles @@ -4562,14 +4418,6 @@ wingman oolite-viper-pursuit thrust 36 - view_position_aft - 0.0 6.0 -27.5 - view_position_forward - 0.0 6.0 6.875 - view_position_port - -6.25 4.0 0.0 - view_position_starboard - 6.25 4.0 0.0 weapon_position_aft 0.0 0.0 -27.5 weapon_position_forward @@ -4609,8 +4457,6 @@ 2 max_flight_speed 110 - max_missiles - 0 missile_launch_position 0.0 -6.5 13.0 missiles @@ -4623,14 +4469,6 @@ shuttle oolite-worm thrust 12 - view_position_aft - 0.0 4.8 -17.5 - view_position_forward - 0.0 -1.2 11.5 - view_position_port - -10.25 0.0 -2.5 - view_position_starboard - 10.25 0.0 -2.5 weapon_position_aft 0.0 -4.8 -17.5 weapon_position_forward @@ -4672,8 +4510,6 @@ 2 max_flight_speed 100 - max_missiles - 0 missile_launch_position 0.0 -6.5 13.0 missiles @@ -4686,14 +4522,6 @@ miner oolite-worm-miner thrust 12 - view_position_aft - 0.0 4.8 -17.5 - view_position_forward - 0.0 -1.2 11.5 - view_position_port - -10.25 0.0 -2.5 - view_position_starboard - 10.25 0.0 -2.5 weapon_position_aft 0.0 -4.8 -17.5 weapon_position_forward @@ -4732,8 +4560,6 @@ 10 max_flight_speed 750 - max_missiles - 0 missiles 0 model @@ -4829,8 +4655,6 @@ Question Mark roles oo-unknown-ship oolite-unknown-ship - scanClass - CLASS_NEUTRAL smooth materials diff --git a/Resources/Config/verifyOXP.plist b/Resources/Config/verifyOXP.plist index 485cf79f..dac6732c 100644 --- a/Resources/Config/verifyOXP.plist +++ b/Resources/Config/verifyOXP.plist @@ -81,7 +81,7 @@ skipDirectories = ( - // Directories to silently skip (version control systems make these) + // Directories to silently skip (version control systems make these). ".svn", "CVS" ); @@ -239,38 +239,438 @@ "custom_views", "max_missiles" ); - validWeaponTypes = - ( - "WEAPON_NONE", - "WEAPON_PLASMA_CANNON", - "WEAPON_PULSE_LASER", - "WEAPON_BEAM_LASER", - "WEAPON_MINING_LASER", - "WEAPON_MILITARY_LASER", - "WEAPON_THARGOID_LASER" - ); - validScanClasses = - ( - "CLASS_NOT_SET", - "CLASS_BUOY", - "CLASS_CARGO", - "CLASS_MILITARY", - "CLASS_MISSILE", - "CLASS_POLICE", - "CLASS_ROCK", - "CLASS_STATION", - "CLASS_THARGOID" - ); - validCargoTypes = - ( - "CARGO_NOT_CARGO", - "CARGO_SLAVES", - "CARGO_ALLOY", - "CARGO_MINERALS", - "CARGO_THARGOID", - "CARGO_RANDOM", - "CARGO_SCRIPTED_ITEM", - "CARGO_CHARACTER" - ); + + entrySchema = + { + type = "dictionary"; + $definitions = + { + // "Special" types referred to above. + $weaponType = + { + type = "enumeration"; + values = + ( + "WEAPON_NONE", + "WEAPON_PLASMA_CANNON", + "WEAPON_PULSE_LASER", + "WEAPON_BEAM_LASER", + "WEAPON_MINING_LASER", + "WEAPON_MILITARY_LASER", + "WEAPON_THARGOID_LASER" + ); + }; + $materialDict = + { + type = "dictionary"; + schema = + { + ambient = "$colorSpecifier"; + diffuse = "$colorSpecifier"; + diffuse_map = "$textureSpecifier"; + emission = "$colorSpecifier"; + shininess = + { + type = "positiveInteger"; + max = "128"; + }; + specular = "$colorSpecifier"; + fragment_shader = "$shaderFileName"; + textures = + { + type = "array"; + valueType = "$textureSpecifier"; + }; + uniforms = + { + type = "dictionary"; + valueType = + { + type = "oneOf"; + options = + ( + "string", + { + type = "dictionary"; + schema = + { + asMatrix = "boolean"; + binding = "string"; + clamped = "boolean"; + normalized = "boolean"; + type = + { + type = "enumeration"; + values = + ( + "binding", + "float", + "real", + "int", + "integer", + "texture" + ); + }; + value = "float"; + }; + } + ); + }; + }; + vertex_shader = "$shaderFileName"; + }; + }; + $textureSpecifier = + { + type = "oneOf"; + options = + ( + "string", + { + type = "dictionary"; + schema = + { + anisotropy = "positiveFloat"; + mag_filter = + { + type = "enumeration"; + values = + ( + "nearest", + "linear" + ); + }; + min_filter = + { + type = "enumeration"; + values = + ( + "nearest", + "linear", + "mipmap", + "default" + ); + }; + name = "string"; + no_shrink = "boolean"; + repeat_s = "boolean"; + repeat_t = "boolean"; + texture_LOD_bias = "float"; + }; + } + ); + }; + $scanClass = + { + type = "enumeration"; + values = + ( + "CLASS_NOT_SET", + "CLASS_BUOY", + "CLASS_CARGO", + "CLASS_MILITARY", + "CLASS_MISSILE", + "CLASS_POLICE", + "CLASS_ROCK", + "CLASS_STATION", + "CLASS_THARGOID" + ); + }; + $cargoType = + { + type = "enumeration"; + values = + ( + "CARGO_NOT_CARGO", + "CARGO_SLAVES", + "CARGO_ALLOY", + "CARGO_MINERALS", + "CARGO_THARGOID", + "CARGO_RANDOM", + "CARGO_SCRIPTED_ITEM", + "CARGO_CHARACTER" + ); + }; + $customViewSpec = + { + type = "dictionary"; + schema = + { + view_description = "string"; + view_position = "vector"; + view_orientation = "quaternion"; + weapon_facing = + { + type = "enumeration"; + filter = "lowerCase"; + values = + ( + "forward", + "aft", + "port", + "starboard" + ); + }; + }; + }; + + // Types handled in code. + $modelName = + { + type = "delegatedType"; + baseType = + { + type = "string"; + filter = "lowerCase"; + requiredSuffix = ".dat"; + }; + key = "modelName"; + }; + $colorSpecifier = + { + type = "delegatedType"; + baseType = + { + type = "oneOf"; + options = + ( + "array", + "dictionary", + "string" + ); + }; + key = "colorSpecifier"; + }; + $textureFileName = + { + type = "delegatedType"; + baseType = + { + type = "string"; + filter = "lowerCase"; + requiredSuffix = ".png"; + }; + key = "textureFileName"; + }; + $aiFileName = + { + type = "delegatedType"; + baseType = + { + type = "string"; + filter = "lowerCase"; + requiredSuffix = ".plist"; + }; + key = "aiFileName"; + }; + $shaderFileName = + { + type = "delegatedType"; + baseType = + { + type = "string"; + filter = "lowerCase"; + requiredSuffix = + ( + ".vertex", + ".vert", + ".fragment", + ".frag" + ); + }; + key = "shaderFileName"; + }; + $exhaustSpecifier = + { + type = "delegatedType"; + baseType = "string"; + key = "exhaustSpecifier"; + }; + $subEntitySpecifier = + { + type = "delegatedType"; + baseType = "string"; + key = "subEntitySpecifier"; + }; + $scriptActions = + { + type = "delegatedType"; + baseType = "array"; + key = "scriptActions"; + }; + $characterKey = + { + type = "delegatedType"; + baseType = "string"; + key = "characterKey"; + }; + $shipRole = + { + type = "delegatedType"; + baseType = "string"; + key = "shipRole"; + }; + $shipKey = + { + type = "delegatedType"; + baseType = "string"; + key = "shipKey"; + }; + $roles = + { + type = "delegatedType"; + baseType = "string"; + key = "roles"; + }; + $scriptCondition = + { + type = "delegatedType"; + baseType = "string"; + key = "scriptCondition"; + }; + $scriptCouplet = + { + type = "delegatedType"; + baseType = "dictionary"; + key = "scriptCouplet"; + }; + $portDimensions = + { + type = "delegatedType"; + baseType = "string"; + key = "portDimensions"; + }; + $extraEquipmentDictionary = + { + type = "delegatedType"; + baseType = "dictionary"; + key = "extraEquipmentDictionary"; + }; + $hudFileName = + { + type = "delegatedType"; + baseType = "string"; + key = "hudFileName"; + }; + $cargoCarried = + { + type = "delegatedType"; + baseType = "string"; + key = "cargoCarried"; + }; + }; + schema = + { + like_ship = "$shipRole"; + max_flight_speed = "positiveFloat"; + max_flight_roll = "positiveFloat"; + max_flight_pitch = "positiveFloat"; + max_flight_yaw = "positiveFloat"; + thrust = "positiveFloat"; + accuracy = "float"; + max_energy = "positiveFloat"; + energy_recharge_rate = "positiveFloat"; + forward_weapon_type = "$weaponType"; + aft_weapon_type = "$weaponType"; + weapon_energy = "positiveFloat"; + scanner_range = "positiveFloat"; + missiles = "positiveInteger"; + has_ecm = "fuzzyBoolean"; + has_scoop = "fuzzyBoolean"; + has_escape_pod = "positiveInteger"; + has_energy_bomb = "fuzzyBoolean"; + has_fuel_injection = "fuzzyBoolean"; + has_cloaking_device = "fuzzyBoolean"; + has_military_jammer = "fuzzyBoolean"; + has_military_scanner_filter = "fuzzyBoolean"; + fragment_chance = "fuzzyBoolean"; + has_shield_booster = "fuzzyBoolean"; + has_shield_enhancer = "fuzzyBoolean"; + fuel = "positiveInteger"; + bounty = "positiveInteger"; + ai_type = "$aiFileName"; + max_cargo = "positiveInteger"; + likely_cargo = "positiveInteger"; + extra_cargo = "positiveInteger"; + cargo_carried = "$cargoCarried"; + cargo_type = "$cargoType"; + model = "$modelName"; + materials = "$materialDict"; + shaders = "$materialDict"; + smooth = "boolean"; + density = "positiveFloat"; + name = "string"; + roles = "$roles"; + exhaust = + { + type = "array"; + valueType = "$exhaustSpecifier"; + }; + is_hulk = "boolean"; + subentities = + { + type = "array"; + valueType = "$subEntitySpecifier"; + }; + frangible = "boolean"; + laser_color = "$colorSpecifier"; + scanClass = "$scanClass"; + launch_actions = "$scriptActions"; + script_actions = "$scriptActions"; + death_actions = "$scriptActions"; + setup_actions = "$scriptActions"; + escorts = "positiveInteger"; + beacon = "string"; + rotational_velocity = "quaternion"; + track_contacts = "boolean"; + weapon_position_forward = "vector"; + weapon_position_aft = "vector"; + weapon_position_port = "vector"; + weapon_position_starboard = "vector"; + scoop_position = "vector"; + heat_insulation = "positiveFloat"; + pilot = "$characterKey"; + unpiloted = "fuzzyBoolean"; + escort-role = "$shipRole"; + escort-ship = "$shipKey"; + missile_launch_position = "vector"; + missile_role = "$shipRole"; + escape_pod_model = "$modelName"; + aft_eject_position = "vector"; + auto_ai = "boolean"; + rotating = "boolean"; + defense_ship = "$shipKey"; + defense_ship_role = "$shipRole"; + hasShipyard = + { + type = "oneOf"; + options = + ( + "$scriptCondition", + "boolean" + ); + }; + conditions = "$scriptCouplet"; + port_radius = "positiveFloat"; + port_dimensions = "$portDimensions"; + equivalent_tech_level = "integer"; + max_scavengers = "positiveInteger"; + max_defense_ships = "positiveInteger"; + max_police = "positiveInteger"; + equipment_price_factor = "positiveFloat"; + extra_equipment = "$extraEquipmentDictionary"; + hud = "$hudFileName"; + view_position_forward = "vector"; + view_position_aft = "vector"; + view_position_port = "vector"; + view_position_starboard = "vector"; + custom_views = + { + type = "array"; + valueType = "$customViewSpec"; + }; + max_missiles = "positiveInteger"; + }; + }; }; } diff --git a/src/Cocoa/OOLogOutputHandler.m b/src/Cocoa/OOLogOutputHandler.m index e5c2e3a1..ae55105b 100644 --- a/src/Cocoa/OOLogOutputHandler.m +++ b/src/Cocoa/OOLogOutputHandler.m @@ -223,7 +223,6 @@ enum - (id)init { BOOL OK = YES; - NSString *basePath = nil; NSString *logPath = nil; NSString *oldPath = nil; NSFileManager *fmgr = nil; @@ -231,11 +230,11 @@ enum // We'll need these for a couple of things. fmgr = [NSFileManager defaultManager]; - basePath = GetLogBasePath(); + logPath = OOLogHandlerGetLogPath(); // If there is an existing file, move it to Previous.log. if ([fmgr fileExistsAtPath:logPath]) { - oldPath = [basePath stringByAppendingPathComponent:@"Previous.log"]; + oldPath = [GetLogBasePath() stringByAppendingPathComponent:@"Previous.log"]; [fmgr removeFileAtPath:oldPath handler:nil]; if (![fmgr movePath:logPath toPath:oldPath handler:nil]) { @@ -350,10 +349,10 @@ enum sysModel = GetSysCtlString("hw.model"); Gestalt(gestaltPhysicalRAMSizeInMegabytes, (long *)&sysPhysMem); - preamble = [NSString stringWithFormat:@"Opening log for %@ version %@ [" CPU_TYPE_STRING "] at %@.\n" + preamble = [NSString stringWithFormat:@"Opening log for %@ version %@ [" CPU_TYPE_STRING "] under Mac OS X %@ at %@.\n" "Machine type: %@, %u MiB memory, CPU: %@.\n" "Note that the contents of the log file can be adjusted by editing logcontrol.plist.\n", - GetAppName(), versionString, [NSDate date], + GetAppName(), versionString, [[NSProcessInfo processInfo] operatingSystemVersionString], [NSDate date], sysModel, sysPhysMem, GetCPUDescription()]; [self asyncLogMessage:preamble]; } diff --git a/src/Core/Entities/Entity.m b/src/Core/Entities/Entity.m index 586b40a5..15e89b2c 100644 --- a/src/Core/Entities/Entity.m +++ b/src/Core/Entities/Entity.m @@ -1017,7 +1017,7 @@ static NSString * const kOOLogEntityUpdateError = @"entity.linkedList.update. - (void)subEntityReallyDied:(ShipEntity *)sub { - OOLog(@"entity.bug", @"%s called for non-ship entity %@ by %@", __FUNCTION__, self, sub); + OOLog(@"entity.bug", @"%s called for non-ship entity %p by %p", __FUNCTION__, self, sub); } diff --git a/src/Core/Entities/PlayerEntity.m b/src/Core/Entities/PlayerEntity.m index 40e32bbe..ecc93cba 100644 --- a/src/Core/Entities/PlayerEntity.m +++ b/src/Core/Entities/PlayerEntity.m @@ -4989,11 +4989,11 @@ static int last_outfitting_index; - (BOOL) tryBuyingItem:(int) index { // note this doesn't check the availability by tech-level - NSArray *equipdata = [UNIVERSE equipmentdata]; - int price_per_unit = [[[equipdata objectAtIndex:index] objectAtIndex:EQUIPMENT_PRICE_INDEX] intValue]; - NSString *eq_key = [[equipdata objectAtIndex:index] objectAtIndex:EQUIPMENT_KEY_INDEX]; - NSString *eq_key_damaged = [NSString stringWithFormat:@"%@_DAMAGED", eq_key]; - double price = ([eq_key isEqual:@"EQ_FUEL"]) ? ((PLAYER_MAX_FUEL - fuel) * price_per_unit) : (price_per_unit) ; + NSArray *equipdata = [UNIVERSE equipmentdata]; + OOCreditsQuantity price_per_unit = [[[equipdata objectAtIndex:index] objectAtIndex:EQUIPMENT_PRICE_INDEX] intValue]; + NSString *eq_key = [[equipdata objectAtIndex:index] objectAtIndex:EQUIPMENT_KEY_INDEX]; + NSString *eq_key_damaged = [NSString stringWithFormat:@"%@_DAMAGED", eq_key]; + double price = ([eq_key isEqual:@"EQ_FUEL"]) ? ((PLAYER_MAX_FUEL - fuel) * price_per_unit) : (price_per_unit) ; double price_factor = 1.0; OOCargoQuantity cargo_space = max_cargo - current_cargo; @@ -5066,30 +5066,39 @@ static int last_outfitting_index; credits -= price; - // refund here for current_weapon; + // refund here for current_weapon + /* BUG: equipment_price_factor does not affect trade-ins. This means + that an equipment_price_factor less than one can be exploited. + Analysis: price factor simply not being applied here. + Fix: trivial. + Acknowledgment: bug and fix both reported by Cmdr James on forum. + -- Ahruman 20070724 + */ + OOCreditsQuantity tradeIn = 0; switch (current_weapon) { case WEAPON_PLASMA_CANNON : - credits += [UNIVERSE getPriceForWeaponSystemWithKey:@"EQ_WEAPON_TWIN_PLASMA_CANNON"]; + tradeIn = [UNIVERSE getPriceForWeaponSystemWithKey:@"EQ_WEAPON_TWIN_PLASMA_CANNON"]; break; case WEAPON_PULSE_LASER : - credits += [UNIVERSE getPriceForWeaponSystemWithKey:@"EQ_WEAPON_PULSE_LASER"]; + tradeIn = [UNIVERSE getPriceForWeaponSystemWithKey:@"EQ_WEAPON_PULSE_LASER"]; break; case WEAPON_BEAM_LASER : - credits += [UNIVERSE getPriceForWeaponSystemWithKey:@"EQ_WEAPON_BEAM_LASER"]; + tradeIn = [UNIVERSE getPriceForWeaponSystemWithKey:@"EQ_WEAPON_BEAM_LASER"]; break; case WEAPON_MINING_LASER : - credits += [UNIVERSE getPriceForWeaponSystemWithKey:@"EQ_WEAPON_MINING_LASER"]; + tradeIn = [UNIVERSE getPriceForWeaponSystemWithKey:@"EQ_WEAPON_MINING_LASER"]; break; case WEAPON_MILITARY_LASER : - credits += [UNIVERSE getPriceForWeaponSystemWithKey:@"EQ_WEAPON_MILITARY_LASER"]; + tradeIn = [UNIVERSE getPriceForWeaponSystemWithKey:@"EQ_WEAPON_MILITARY_LASER"]; break; case WEAPON_THARGOID_LASER : - credits += [UNIVERSE getPriceForWeaponSystemWithKey:@"EQ_WEAPON_THARGOID_LASER"]; + tradeIn = [UNIVERSE getPriceForWeaponSystemWithKey:@"EQ_WEAPON_THARGOID_LASER"]; break; case WEAPON_NONE : break; } + if (price_factor < 1.0f) credits += tradeIn * price_factor; [self setGuiToEquipShipScreen:-1:-1]; return YES; diff --git a/src/Core/Entities/PlayerEntityLegacyScriptEngine.m b/src/Core/Entities/PlayerEntityLegacyScriptEngine.m index e9f31d54..430cf016 100644 --- a/src/Core/Entities/PlayerEntityLegacyScriptEngine.m +++ b/src/Core/Entities/PlayerEntityLegacyScriptEngine.m @@ -1194,11 +1194,13 @@ static int scriptRandomSeed = -1; // ensure proper random function - (void) awardFuel:(NSString *)valueString // add to fuel up to 7.0 LY { - fuel += 10 * [valueString floatValue]; - if (fuel > PLAYER_MAX_FUEL) - fuel = PLAYER_MAX_FUEL; - if (fuel < 0) - fuel = 0; + int delta = 10 * [valueString floatValue]; + if (delta < 0 && fuel < (unsigned)-delta) fuel = 0; + else + { + fuel += delta; + if (fuel > PLAYER_MAX_FUEL) fuel = PLAYER_MAX_FUEL; + } } diff --git a/src/Core/Entities/ShipEntity.h b/src/Core/Entities/ShipEntity.h index 15c38734..489a27f8 100644 --- a/src/Core/Entities/ShipEntity.h +++ b/src/Core/Entities/ShipEntity.h @@ -166,7 +166,7 @@ MA 02110-1301, USA. canFragment: 1, // Can it break into wreckage? suppressExplosion: 1; // Avoid exploding on death (script hook) - int fuel; // witch-space fuel + OOFuelQuantity fuel; // witch-space fuel GLfloat fuel_accumulator; OOCargoQuantity likely_cargo; // likely amount of cargo (for merchantmen, this is what is spilled as loot) @@ -404,8 +404,8 @@ MA 02110-1301, USA. - (void) setAI:(AI *) ai; - (AI *) getAI; -- (int) fuel; -- (void) setFuel:(int) amount; +- (OOFuelQuantity) fuel; +- (void) setFuel:(OOFuelQuantity) amount; - (void) setRoll:(double) amount; - (void) setPitch:(double) amount; diff --git a/src/Core/Entities/ShipEntity.m b/src/Core/Entities/ShipEntity.m index 38492e2d..4a0d41df 100644 --- a/src/Core/Entities/ShipEntity.m +++ b/src/Core/Entities/ShipEntity.m @@ -200,18 +200,18 @@ static NSString * const kOOLogEntityBehaviourChanged = @"entity.behaviour.change // Moved here from above upgrade loading so that ships start with full energy banks. -- Ahruman energy = maxEnergy; - fuel = [shipDict intForKey:@"fuel"]; // Does it make sense that this defaults to 0? Should it not be 70? -- Ahruman + fuel = [shipDict unsignedShortForKey:@"fuel"]; // Does it make sense that this defaults to 0? Should it not be 70? -- Ahruman fuel_accumulator = 1.0; - bounty = [shipDict intForKey:@"bounty"]; + bounty = [shipDict unsignedIntForKey:@"bounty"]; [shipAI autorelease]; shipAI = [[AI alloc] init]; [shipAI setStateMachine:[shipDict stringForKey:@"ai_type" defaultValue:@"nullAI.plist"]]; - max_cargo = [shipDict intForKey:@"max_cargo"]; - likely_cargo = [shipDict intForKey:@"likely_cargo"]; - extra_cargo = [shipDict intForKey:@"extra_cargo" defaultValue:15]; + max_cargo = [shipDict unsignedIntForKey:@"max_cargo"]; + likely_cargo = [shipDict unsignedIntForKey:@"likely_cargo"]; + extra_cargo = [shipDict unsignedIntForKey:@"extra_cargo" defaultValue:15]; NSString *cargoString = [shipDict stringForKey:@"cargo_carried"]; if (cargoString != nil) @@ -3002,17 +3002,15 @@ static GLfloat mascem_color2[4] = { 0.4, 0.1, 0.4, 1.0}; // purple } -- (int) fuel +- (OOFuelQuantity) fuel { return fuel; } -- (void) setFuel:(int) amount +- (void) setFuel:(OOFuelQuantity) amount { fuel = amount; - if (fuel < 0) - fuel = 0; if (fuel > PLAYER_MAX_FUEL) fuel = PLAYER_MAX_FUEL; } diff --git a/src/Core/Entities/StationEntity.m b/src/Core/Entities/StationEntity.m index 98006dc6..35cdf33f 100644 --- a/src/Core/Entities/StationEntity.m +++ b/src/Core/Entities/StationEntity.m @@ -771,8 +771,9 @@ static NSDictionary* instructions(int station_id, Vector coords, float speed, fl max_scavengers = [dict unsignedIntForKey:@"max_scavengers" defaultValue:3]; max_defense_ships = [dict unsignedIntForKey:@"max_defense_ships" defaultValue:3]; max_police = [dict unsignedIntForKey:@"max_police" defaultValue:STATION_MAX_POLICE]; - equipment_price_factor = [dict nonNegativeDoubleForKey:@"equipment_price_factor" defaultValue:1.0]; - + equipment_price_factor = [dict nonNegativeFloatForKey:@"equipment_price_factor" defaultValue:1.0]; + equipment_price_factor = MAX(equipment_price_factor, 0.5f); + if ([self isRotatingStation]) { docked_shuttles = ranrot_rand() & 3; // 0..3; diff --git a/src/Core/OOPListParsing.m b/src/Core/OOPListParsing.m index 8c341218..d1f605c1 100644 --- a/src/Core/OOPListParsing.m +++ b/src/Core/OOPListParsing.m @@ -90,8 +90,14 @@ id OOPropertyListFromData(NSData *data, NSString *whereFrom) result = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListImmutable format:NULL errorDescription:&error]; if (result == nil) // Foundation parser failed - { - [error autorelease]; // Note: it is a documented design wart that this string needs releasing. + { +#if OOLITE_MAC_OS_X + /* Documented wart: this is caller responsibility in Cocoa (but not GNUstep?) + TODO: verify that it is correct to not do this in GNUstep. + In the mean time, not doing it in GNUstep seems to be avoiding a crash. + */ + [error autorelease]; +#endif // Ensure we can say something sensible... if (error == nil) error = @""; if (whereFrom == nil) whereFrom = @""; diff --git a/src/Core/OOTypes.h b/src/Core/OOTypes.h index 385f869c..9c266570 100644 --- a/src/Core/OOTypes.h +++ b/src/Core/OOTypes.h @@ -273,6 +273,8 @@ typedef enum typedef uint32_t OOCargoQuantity; typedef int32_t OOCargoQuantityDelta; +typedef uint16_t OOFuelQuantity; + typedef uint32_t OOCreditsQuantity; diff --git a/src/Core/OXPVerifier/OOCheckShipDataPListVerifierStage.h b/src/Core/OXPVerifier/OOCheckShipDataPListVerifierStage.h index 32b8f766..174ed6b7 100644 --- a/src/Core/OXPVerifier/OOCheckShipDataPListVerifierStage.h +++ b/src/Core/OXPVerifier/OOCheckShipDataPListVerifierStage.h @@ -26,6 +26,7 @@ MA 02110-1301, USA. */ #import "OOTextureVerifierStage.h" +#import "OOPListSchemaVerifier.h" #if OO_OXP_VERIFIER_ENABLED @@ -37,6 +38,7 @@ MA 02110-1301, USA. *_stationKeys, *_playerKeys, *_allKeys; + OOPListSchemaVerifier *_schemaVerifier; // Info about ship currently being checked. None of these are retained! NSString *_name; diff --git a/src/Core/OXPVerifier/OOCheckShipDataPListVerifierStage.m b/src/Core/OXPVerifier/OOCheckShipDataPListVerifierStage.m index 15d9115e..7618a9e2 100644 --- a/src/Core/OXPVerifier/OOCheckShipDataPListVerifierStage.m +++ b/src/Core/OXPVerifier/OOCheckShipDataPListVerifierStage.m @@ -42,9 +42,12 @@ static NSString * const kStageName = @"Checking shipdata.plist"; - (void)verifyShipInfo:(NSDictionary *)info withName:(NSString *)name; - (void)message:(NSString *)format, ...; +- (void)verboseMessage:(NSString *)format, ...; - (void)getRoles; - (void)checkKeys; +- (void)checkSchema; +- (void)checkModel; - (NSSet *)rolesFromString:(NSString *)string; @@ -129,6 +132,9 @@ static NSString * const kStageName = @"Checking shipdata.plist"; [mergeSet unionSet:_stationKeys]; _allKeys = mergeSet; + _schemaVerifier = [OOPListSchemaVerifier verifierWithSchema:[settings dictionaryForKey:@"entrySchema"]]; + [_schemaVerifier setDelegate:self]; + shipList = [[_shipdataPList allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]; for (shipEnum = [shipList objectEnumerator]; (shipKey = [shipEnum nextObject]); ) { @@ -168,6 +174,8 @@ static NSString * const kStageName = @"Checking shipdata.plist"; [self getRoles]; [self checkKeys]; + [self checkSchema]; + [self checkModel]; OOLogPopIndent(); if (!_havePrintedMessage) @@ -198,6 +206,25 @@ static NSString * const kStageName = @"Checking shipdata.plist"; } +- (void)verboseMessage:(NSString *)format, ... +{ + va_list args; + + if (!OOLogWillDisplayMessagesInClass(@"verifyOXP.verbose.shipData")) return; + + if (!_havePrintedMessage) + { + OOLog(@"verifyOXP.shipData.firstMessage", @"Ship \"%@\":", _name); + OOLogIndent(); + _havePrintedMessage = YES; + } + + va_start(args, format); + OOLogWithFunctionFileAndLineAndArguments(@"verifyOXP.verbose.shipData", NULL, NULL, 0, format, args); + va_end(args); +} + + - (void)getRoles { NSString *rolesString = nil; @@ -244,6 +271,30 @@ static NSString * const kStageName = @"Checking shipdata.plist"; } +- (void)checkSchema +{ + [_schemaVerifier validatePropertyList:_info named:_name]; +} + + +- (void)checkModel +{ + id model = nil, + materials = nil, + shaders = nil; + + model = [_info stringForKey:@"model"]; + materials = [_info dictionaryForKey:@"materials"]; + shaders = [_info dictionaryForKey:@"shaders"]; + + [[[self verifier] modelVerifierStage] modelNamed:model + usedForEntry:_name + inFile:@"shipdata.plist" + withMaterials:materials + andShaders:shaders]; +} + + // Convert a roles string to a set of role names, discarding probabilities. - (NSSet *)rolesFromString:(NSString *)string { @@ -274,6 +325,19 @@ static NSString * const kStageName = @"Checking shipdata.plist"; return result; } + +- (BOOL)verifier:(OOPListSchemaVerifier *)verifier withPropertyList:(id)rootPList named:(NSString *)name testProperty:(id)subPList atPath:(NSArray *)keyPath againstType:(NSString *)typeKey +{ + [self verboseMessage:@"- Skipping verification for type %@ at %@.%@.", typeKey, _name, [OOPListSchemaVerifier descriptionForKeyPath:keyPath]]; + return YES; +} + +- (BOOL)verifier:(OOPListSchemaVerifier *)verifier withPropertyList:(id)rootPList named:(NSString *)name failedForProperty:(id)subPList atPath:(NSArray *)keyPath expectedType:(NSDictionary *)localSchema +{ + [self message:@"ERROR: wrong type (%@) in shipdata.plist at %@.%@.", [subPList class], _name, [OOPListSchemaVerifier descriptionForKeyPath:keyPath]]; + return YES; +} + @end #endif diff --git a/src/Core/OXPVerifier/OOFileScannerVerifierStage.m b/src/Core/OXPVerifier/OOFileScannerVerifierStage.m index e0291e57..d9dc728e 100644 --- a/src/Core/OXPVerifier/OOFileScannerVerifierStage.m +++ b/src/Core/OXPVerifier/OOFileScannerVerifierStage.m @@ -280,7 +280,14 @@ static BOOL CheckNameConflict(NSString *lcName, NSDictionary *directoryCases, NS mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&errorString]; - [errorString autorelease]; // Documented wart: this is caller responsibility + +#if OOLITE_MAC_OS_X + /* Documented wart: this is caller responsibility in Cocoa (but not GNUstep?) + TODO: verify that it is correct to not do this in GNUstep. + In the mean time, not doing it in GNUstep seems to be avoiding a crash. + */ + [errorString autorelease]; +#endif if (plist != nil) { diff --git a/src/Core/OXPVerifier/OOModelVerifierStage.h b/src/Core/OXPVerifier/OOModelVerifierStage.h index ecd826de..85f86a89 100644 --- a/src/Core/OXPVerifier/OOModelVerifierStage.h +++ b/src/Core/OXPVerifier/OOModelVerifierStage.h @@ -32,18 +32,25 @@ MA 02110-1301, USA. @interface OOModelVerifierStage: OOTextureHandlingStage { - NSMutableSet *_usedModels; + NSMutableSet *_modelsToCheck; } // Returns name to be used in -dependents by other stages; also registers stage. + (NSString *)nameForReverseDependencyForVerifier:(OOOXPVerifier *)verifier; -/* These can be called by other stages *before* the model stage runs. - The context specifies where the model is used; something like - "fooShip.dat" or "shipdata.plist materials dictionary for ship \"foo\"". - It should make sense with "Model \"foo\" referenced in " in front of it. -*/ -- (void) modelNamed:(NSString *)name usedInContext:(NSString *)context; +// This can be called by other stages *before* the model stage runs. +- (void) modelNamed:(NSString *)name + usedForEntry:(NSString *)entryName + inFile:(NSString *)fileName + withMaterials:(NSDictionary *)materials + andShaders:(NSDictionary *)shaders; + +@end + + +@interface OOOXPVerifier(OOModelVerifierStage) + +- (OOModelVerifierStage *)modelVerifierStage; @end diff --git a/src/Core/OXPVerifier/OOModelVerifierStage.m b/src/Core/OXPVerifier/OOModelVerifierStage.m index 474a6c2c..19b9b853 100644 --- a/src/Core/OXPVerifier/OOModelVerifierStage.m +++ b/src/Core/OXPVerifier/OOModelVerifierStage.m @@ -31,10 +31,15 @@ MA 02110-1301, USA. static NSString * const kStageName = @"Testing models"; +static id NSNULL = nil; + @interface OOModelVerifierStage (OOPrivate) -- (void)checkModelNamed:(NSString *)name inFolder:(NSString *)folder; +- (void)checkModel:(NSString *)name + context:(NSString *)context + materials:(NSDictionary *)materials + shaders:(NSDictionary *)shaders; @end @@ -46,7 +51,8 @@ static NSString * const kStageName = @"Testing models"; self = [super init]; if (self != nil) { - _usedModels = [[NSMutableSet alloc] init]; + NSNULL = [[NSNull null] retain]; + _modelsToCheck = [[NSMutableSet alloc] init]; } return self; } @@ -54,7 +60,7 @@ static NSString * const kStageName = @"Testing models"; - (void)dealloc { - [_usedModels release]; + [_modelsToCheck release]; [super dealloc]; } @@ -82,35 +88,60 @@ static NSString * const kStageName = @"Testing models"; - (BOOL)shouldRun { - return [_usedModels count] != 0; + return [_modelsToCheck count] != 0; } - (void)run { NSEnumerator *nameEnum = nil; - NSString *name = nil; + NSDictionary *info = nil; NSAutoreleasePool *pool = nil; + NSString *name = nil, + *context = nil; + NSDictionary *materials = nil, + *shaders = nil; OOLog(@"verifyOXP.models.unimplemented", @"TODO: implement model verifier."); - for (nameEnum = [_usedModels objectEnumerator]; (name = [nameEnum nextObject]); ) + for (nameEnum = [_modelsToCheck objectEnumerator]; (info = [nameEnum nextObject]); ) { pool = [[NSAutoreleasePool alloc] init]; - [self checkModelNamed:name inFolder:@"Models"]; + + name = [info objectForKey:@"name"]; + context = [info objectForKey:@"context"]; + if (context == NSNULL) context = nil; + materials = [info objectForKey:@"materials"]; + if (materials == NSNULL) materials = nil; + shaders = [info objectForKey:@"shaders"]; + if (shaders == NSNULL) shaders = nil; + + [self checkModel:name + context:context + materials:materials + shaders:shaders]; + [pool release]; } - [_usedModels release]; - _usedModels = nil; + [_modelsToCheck release]; + _modelsToCheck = nil; } -- (void) modelNamed:(NSString *)name usedInContext:(NSString *)context +- (void) modelNamed:(NSString *)name + usedForEntry:(NSString *)entryName + inFile:(NSString *)fileName + withMaterials:(NSDictionary *)materials + andShaders:(NSDictionary *)shaders { OOFileScannerVerifierStage *fileScanner = nil; + NSDictionary *info = nil; + NSString *context = nil; - if ([_usedModels member:name] != nil) return; - [_usedModels addObject:name]; + if (name == nil) return; + + if (entryName != nil) context = [NSString stringWithFormat:@"entry \"%@\" of %@", entryName, fileName]; + else context = fileName; fileScanner = [[self verifier] fileScannerStage]; if (![fileScanner fileExists:name @@ -120,6 +151,19 @@ static NSString * const kStageName = @"Testing models"; { OOLog(@"verifyOXP.model.notFound", @"WARNING: model \"%@\" referenced in %@ could not be found in %@ or in Oolite.", name, context, [[self verifier] oxpDisplayName]); } + + if (context == nil) context = NSNULL; + if (materials == nil) materials = NSNULL; + if (shaders == nil) shaders = NSNULL; + + info = [NSDictionary dictionaryWithObjectsAndKeys: + name, @"name", + context, @"context", + materials, @"materials", + shaders, @"shaders", + nil]; + + [_modelsToCheck addObject:info]; } @end @@ -127,11 +171,26 @@ static NSString * const kStageName = @"Testing models"; @implementation OOModelVerifierStage (OOPrivate) -- (void)checkModelNamed:(NSString *)name inFolder:(NSString *)folder + +- (void)checkModel:(NSString *)name + context:(NSString *)context + materials:(NSDictionary *)materials + shaders:(NSDictionary *)shaders { + OOLog(@"verifyOXP.verbose.model.unimp", @"- Pretending to verify model %@ referenced in %@.", name, context); // FIXME: this should check DAT files. } @end + +@implementation OOOXPVerifier(OOModelVerifierStage) + +- (OOModelVerifierStage *)modelVerifierStage +{ + return [self stageWithName:kStageName]; +} + +@end + #endif diff --git a/src/Core/OXPVerifier/OOOXPVerifier.m b/src/Core/OXPVerifier/OOOXPVerifier.m index c11b790c..6f060219 100644 --- a/src/Core/OXPVerifier/OOOXPVerifier.m +++ b/src/Core/OXPVerifier/OOOXPVerifier.m @@ -527,7 +527,7 @@ static void OpenLogFile(NSString *name); } else { - OOLog(@"verifyOXP.verbose.skipStage", @"- Skipping stage: %@", stageName); + OOLog(@"verifyOXP.verbose.skipStage", @"- Skipping stage: %@.", stageName); [stageToRun noteSkipped]; } @@ -713,7 +713,10 @@ static void OpenLogFile(NSString *name) { // Open log file in appropriate application. - [[NSWorkspace sharedWorkspace] openFile:OOLogHandlerGetLogPath()]; + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"oxp-verifier-open-log" defaultValue:YES]) + { + [[NSWorkspace sharedWorkspace] openFile:OOLogHandlerGetLogPath()]; + } } #endif // OOLITE_HAVE_APPKIT diff --git a/src/Core/OXPVerifier/OOPListSchemaVerifier.h b/src/Core/OXPVerifier/OOPListSchemaVerifier.h new file mode 100644 index 00000000..1cbac57f --- /dev/null +++ b/src/Core/OXPVerifier/OOPListSchemaVerifier.h @@ -0,0 +1,143 @@ +/* + + OOPListSchemaVerifier.h + + Utility class to verify the structure of a property list based on a schema + (which is itself a property list). + + + Oolite + Copyright (C) 2004-2007 Giles C Williams and contributors + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + 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 GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02110-1301, USA. + + + This file may also be distributed under the MIT/X11 license: + + Copyright (C) 2007 Jens Ayton + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + */ + +#import "OOOxpVerifier.h" + +#if OO_OXP_VERIFIER_ENABLED + +#import + + +@interface OOPListSchemaVerifier: NSObject +{ + NSDictionary *_schema; + id _delegate; + uint32_t _badDelegateWarning: 1; +} + ++ (id)verifierWithSchema:(id)schema; +- (id)initWithSchema:(id)schema; + +- (void)setDelegate:(id)delegate; +- (id)delegate; + +- (BOOL)validatePropertyList:(id)plist named:(NSString *)name; + +/* Convert a key path (such as provided to the delegate method + -verifier:withPropertyList:failedForProperty:atPath:expectedType:) to a + human-readable string. Strings are separated by dots and numbers are give + brackets. For instance, the key path ( "adder-player", "custom_views", 0, + "view_description" ) is transfomed to + "adder-player.custom_views[0].view_description". +*/ ++ (NSString *)descriptionForKeyPath:(NSArray *)keyPath; + +@end + + +@interface NSObject (OOPListSchemaVerifierDelegate) + +// Handle "delegated types". Return YES for valid, NO for invalid. +- (BOOL)verifier:(OOPListSchemaVerifier *)verifier withPropertyList:(id)rootPList named:(NSString *)name testProperty:(id)subPList atPath:(NSArray *)keyPath againstType:(NSString *)typeKey; + +/* Method notifying of validation failure. + Return YES to continue validating, NO to stop. +*/ +- (BOOL)verifier:(OOPListSchemaVerifier *)verifier withPropertyList:(id)rootPList named:(NSString *)name failedForProperty:(id)subPList atPath:(NSArray *)keyPath expectedType:(NSDictionary *)localSchema; + +@end + + +// NSError domain and codes used to report schema verifier errors. +extern NSString * const kOOPListSchemaVerifierErrorDomain; + +extern NSString * const kPListKeyPathErrorKey; +extern NSString * const kSchemaKeyPathErrorKey; + +extern NSString * const kMissingRequiredKeysErrorKey; +extern NSString * const kUnknownTypeErrorKey; + + +// All plist verifier errors have a short error description in their -localizedDescription. Generally this is something that would be more suitable to -localizedFailureReason, but we need Mac OS X 10.3 compatibility. + +typedef enum +{ + kPListErrorNone, + + // Validation errors -- property list doesn't match schema. + kPListErrorTypeMismatch, // Basic type mismatch -- array instead of number, for instance. + + kPListErrorMinimumConstraintNotMet, // minimum/minCount/minLength constraint violated + kPListErrorNumberIsNegative, // Negative number in positiveInteger/positiveFloat + + kPListErrorStringPrefixMissing, // String does not match requiredPrefix rule. + kPListErrorStringSuffixMissing, // String does not match requiredSuffix rule. + kPListErrorStringSubstringMissing, // String does not match requiredSuffix rule. + + kPListErrorDictionaryUnknownKey, // Unknown key for dictionary with allowOthers = NO. + kPListErrorDictionaryMissingRequiredKeys, // requiredKeys rule is not fulfilled. The missing keys are listed in kMissingRequiredKeysErrorKey. + + kPListErrorEnumerationBadValue, // Enumeration type contains string that isn't in permitted set. + + kPListDelegatedTypeError, // Delegate's verification method failed. If it returned an error, this will be in NSUnderlyingErrorKey. + + // Schema errors -- schema is broken. + kPListErrorSchemaMacroRecursion, // Macro reference recursion limit hit (currently, recursion limit is 32). This can only happen on init. + + kPListErrorSchemaTypeMismatch, // Bad type in schema. + kPListErrorSchemaUndefinedMacro, // Reference to undefined macro. + kPListErrorSchemaNoType, // No type specified in type specifier. + kPListErrorSchemaUnkownType, // Unknown type specified in type specifier. kUnknownTypeErrorKey is set. + kPListErrorSchemaNoOneOfOptions, // OneOf clause has no options array. + kPListErrorSchemaNoEnumerationValues // Enumeration clause has no values array. +} OOPListSchemaVerifierErrorCode; + +#endif // OO_OXP_VERIFIER_ENABLED diff --git a/src/Core/OXPVerifier/OOPListSchemaVerifier.m b/src/Core/OXPVerifier/OOPListSchemaVerifier.m new file mode 100644 index 00000000..2553d94b --- /dev/null +++ b/src/Core/OXPVerifier/OOPListSchemaVerifier.m @@ -0,0 +1,1357 @@ +/* + + OOPListSchemaVerifier.m + + + Oolite + Copyright (C) 2004-2007 Giles C Williams and contributors + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + 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 GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02110-1301, USA. + + + This file may also be distributed under the MIT/X11 license: + + Copyright (C) 2007 Jens Ayton + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + */ + +#import "OOPListSchemaVerifier.h" + +#if OO_OXP_VERIFIER_ENABLED + +#import "OOLogging.h" +#import "OOCollectionExtractors.h" +#import "OOFunctionAttributes.h" +#import "OOMaths.h" +#import + + +NSString * const kOOPListSchemaVerifierErrorDomain = @"org.aegidian.oolite.OOPListSchemaVerifier.ErrorDomain"; + +NSString * const kPListKeyPathErrorKey = @"org.aegidian.oolite.OOPListSchemaVerifier plist key path"; +NSString * const kSchemaKeyPathErrorKey = @"org.aegidian.oolite.OOPListSchemaVerifier schema key path"; + +NSString * const kMissingRequiredKeysErrorKey = @"org.aegidian.oolite.OOPListSchemaVerifier missing required keys"; +NSString * const kUnknownTypeErrorKey = @"org.aegidian.oolite.OOPListSchemaVerifier unknown type"; + + +typedef enum +{ + kTypeUnknown, + kTypeString, + kTypeArray, + kTypeDictionary, + kTypeInteger, + kTypePositiveInteger, + kTypeFloat, + kTypePositiveFloat, + kTypeOneOf, + kTypeEnumeration, + kTypeBoolean, + kTypeFuzzyBoolean, + kTypeVector, + kTypeQuaternion, + kTypeDelegatedType +} SchemaType; + + +typedef struct BackLinkChain BackLinkChain; +struct BackLinkChain +{ + BackLinkChain *link; + id element; +}; + +OOINLINE BackLinkChain BackLink(BackLinkChain *link, id element) +{ + BackLinkChain result = { link, element }; + return result; +} + +OOINLINE BackLinkChain BackLinkIndex(BackLinkChain *link, unsigned index) +{ + BackLinkChain result = { link, [NSNumber numberWithInt:index] }; + return result; +} + +OOINLINE BackLinkChain BackLinkRoot(void) +{ + BackLinkChain result = { NULL, NULL }; + return result; +} + + +static SchemaType StringToSchemaType(NSString *string); +static SchemaType ResolveSchemaType(NSDictionary **typeSpec, BackLinkChain keyPath); +static NSString *ApplyStringFilter(NSString *string, id filterSpec); +static BOOL ApplyStringTest(NSString *string, id test, SEL testSelector, NSString *testDescription); + +static NSError *Error(OOPListSchemaVerifierErrorCode errorCode, NSString *format, ...); +static NSError *ErrorWithProperty(OOPListSchemaVerifierErrorCode errorCode, NSString *propKey, id propValue, NSString *format, ...); +static NSError *ErrorWithDictionary(OOPListSchemaVerifierErrorCode errorCode, NSDictionary *dict, NSString *format, ...); +static NSError *ErrorWithDictionaryAndArguments(OOPListSchemaVerifierErrorCode errorCode, NSDictionary *dict, NSString *format, va_list arguments); + + +@interface OOPListSchemaVerifier (OOPrivate) + +// Call delegate methods. +- (BOOL)delegateVerifierWithPropertyList:(id)rootPList named:(NSString *)name testProperty:(id)subPList atPath:(NSArray *)keyPath againstType:(NSString *)typeKey; +- (BOOL)delegateVerifierWithPropertyList:(id)rootPList named:(NSString *)name failedForProperty:(id)subPList atPath:(NSArray *)keyPath expectedType:(NSDictionary *)localSchema; + ++ (NSDictionary *)normalizedSchema:(id)schema; + ++ (NSDictionary *)normalizedSubSchema:(NSDictionary *)subSchema + atPath:(BackLinkChain)keyPath + withDefinitions:(NSDictionary *)definitions + changed:(BOOL *)outChanged; + ++ (NSDictionary *)normalizedSchemaType:(id)element + atPath:(BackLinkChain)keyPath + withDefinitions:(NSDictionary *)definitions + changed:(BOOL *)outChanged; + ++ (NSArray *)normalizedArrayOfSchemaTypes:(NSArray *)types + atPath:(BackLinkChain)keyPath + withDefinitions:(NSDictionary *)definitions + changed:(BOOL *)outChanged; + ++ (NSArray *)keyPathToArray:(BackLinkChain)keyPath; ++ (NSString *)keyPathToString:(BackLinkChain)keyPath; + +- (BOOL)validatePList:(id)rootPList + named:(NSString *)name + subProperty:(id)subProperty + againstSchemaType:(NSDictionary *)subSchema + atPath:(BackLinkChain)keyPath + tentative:(BOOL)tentative + stop:(BOOL *)outStop; + +@end + + +#define VALIDATE_PROTO(T) static BOOL Validate_##T(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop) +VALIDATE_PROTO(String); +VALIDATE_PROTO(Array); +VALIDATE_PROTO(Dictionary); +VALIDATE_PROTO(Integer); +VALIDATE_PROTO(PositiveInteger); +VALIDATE_PROTO(Float); +VALIDATE_PROTO(PositiveFloat); +VALIDATE_PROTO(OneOf); +VALIDATE_PROTO(Enumeration); +VALIDATE_PROTO(Boolean); +VALIDATE_PROTO(FuzzyBoolean); +VALIDATE_PROTO(Vector); +VALIDATE_PROTO(Quaternion); +VALIDATE_PROTO(DelegatedType); + + +@implementation OOPListSchemaVerifier + ++ (id)verifierWithSchema:(id)schema +{ + return [[[self alloc] initWithSchema:schema] autorelease]; +} + + +- (id)initWithSchema:(id)schema +{ + self = [super init]; + if (self != nil) + { + _schema = [[OOPListSchemaVerifier normalizedSchema:schema] retain]; + if (_schema == nil) + { + [self release]; + self = nil; + } + } + + return self; +} + + +- (void)dealloc +{ + [_schema release]; + + [super dealloc]; +} + + +- (void)setDelegate:(id)delegate +{ + if (_delegate != delegate) + { + _delegate = delegate; + _badDelegateWarning = NO; + } +} + + +- (id)delegate +{ + return _delegate; +} + + +- (BOOL)validatePropertyList:(id)plist named:(NSString *)name +{ + NSAutoreleasePool *pool = nil; + BOOL OK; + BOOL stop = NO; + + pool = [[NSAutoreleasePool alloc] init]; + + OK = [self validatePList:plist + named:name + subProperty:plist + againstSchemaType:_schema + atPath:BackLinkRoot() + tentative:NO + stop:&stop]; + + [pool release]; + + return OK; +} + + ++ (NSString *)descriptionForKeyPath:(NSArray *)keyPath +{ + NSMutableString *result = nil; + NSEnumerator *componentEnum = nil; + id component = nil; + BOOL first = YES; + + result = [NSMutableString string]; + + for (componentEnum = [keyPath objectEnumerator]; (component = [componentEnum nextObject]); ) + { + if ([component isKindOfClass:[NSNumber class]]) + { + [result appendFormat:@"[%@]", component]; + } + else if ([component isKindOfClass:[NSString class]]) + { + if (!first) [result appendString:@"."]; + [result appendString:component]; + } + else return nil; + first = NO; + } + + if (first) + { + // Empty path + result = @"root"; + } + + return result; +} + +@end + + +@implementation OOPListSchemaVerifier (OOPrivate) + +- (BOOL)delegateVerifierWithPropertyList:(id)rootPList named:(NSString *)name testProperty:(id)subPList atPath:(NSArray *)keyPath againstType:(NSString *)typeKey +{ + BOOL result; + + if ([_delegate respondsToSelector:@selector(verifier:withPropertyList:named:testProperty:atPath:againstType:)]) + { + NS_DURING + result = [_delegate verifier:self + withPropertyList:rootPList + named:name + testProperty:subPList + atPath:keyPath + againstType:typeKey]; + NS_HANDLER + OOLog(@"plistVerifier.delegateException", @"Property list schema verifier: delegate threw exception (%@) in -verifier:withPropertyList:named:testProperty:atPath:againstType: for type \"%@\" at %@ in %@ -- treating as failure.", [localException name], typeKey, [OOPListSchemaVerifier descriptionForKeyPath:keyPath], name); + result = NO; + NS_ENDHANDLER + } + else + { + if (!_badDelegateWarning) + { + OOLog(@"plistVerifier.badDelegate", @"Property list schema verifier: delegate does not handle delegated types."); + _badDelegateWarning = YES; + } + result = NO; + } + return result; +} + + +- (BOOL)delegateVerifierWithPropertyList:(id)rootPList named:(NSString *)name failedForProperty:(id)subPList atPath:(NSArray *)keyPath expectedType:(NSDictionary *)localSchema +{ + BOOL result; + + if ([_delegate respondsToSelector:@selector(verifier:withPropertyList:named:failedForProperty:atPath:expectedType:)]) + { + NS_DURING + result = [_delegate verifier:self + withPropertyList:rootPList + named:name + failedForProperty:subPList + atPath:keyPath + expectedType:localSchema]; + NS_HANDLER + OOLog(@"plistVerifier.delegateException", @"Property list schema verifier: delegate threw exception (%@) in -verifier:withPropertyList:named:failedForProperty:atPath:expectedType: at %@ in %@ -- stopping.", [localException name], [OOPListSchemaVerifier descriptionForKeyPath:keyPath], name); + result = NO; + NS_ENDHANDLER + } + else + { + OOLog(@"plistVerifier.failed", @"Verification of property list \"%@\" failed: wrong type at %@.", name, [[self class] descriptionForKeyPath:keyPath]); + result = NO; + } + return result; +} + + ++ (NSDictionary *)normalizedSchema:(id)schema +{ + enum { kRecursionLimit = 32 }; + NSDictionary *definitions = nil; + NSAutoreleasePool *pool = nil; + unsigned i; + BOOL OK = NO, changed; + + if (schema == nil) return nil; + + pool = [[NSAutoreleasePool alloc] init]; + + if ([schema isKindOfClass:[NSString class]]) schema = [NSDictionary dictionaryWithObject:schema forKey:@"type"]; + + definitions = [schema dictionaryForKey:@"$definitions"]; + + // Repeatedly normalize until all definition references are resolved. + for (i = 0; i != kRecursionLimit; ++i) + { + changed = NO; + schema = [self normalizedSchemaType:schema atPath:BackLinkRoot() withDefinitions:definitions changed:&changed]; + if (schema != nil && !changed) + { + OK = YES; + break; + } + if (schema == nil) break; + } + + if (!OK) + { + if (schema != nil) + { + schema = nil; + OOLog(@"plistVerifier.badSchema", @"Property list schema verifier: bad schema -- hit definition resolution recursion limit %u.", i); + } + } + else if ([[NSUserDefaults standardUserDefaults] boolForKey:@"plist-schema-verifier-dump-schema"]) + { + NSString *errorDesc = nil; + NSData *data = [NSPropertyListSerialization dataFromPropertyList:schema format:NSPropertyListXMLFormat_v1_0 errorDescription:&errorDesc]; + if (data != nil) [data writeToFile:@"schema.plist" atomically:YES]; + else OOLog(@"plistVerifier.temp", @"Failed to convert schema to plist: %@", errorDesc); + } + + [schema retain]; + [pool release]; + return [schema autorelease]; +} + + + ++ (NSDictionary *)normalizedSubSchema:(NSDictionary *)subSchema + atPath:(BackLinkChain)keyPath + withDefinitions:(NSDictionary *)definitions + changed:(BOOL *)outChanged +{ + NSMutableDictionary *result = nil; + BOOL OK = YES, thisChanged, changed = NO; + NSEnumerator *keyEnum = nil; + NSString *key = nil; + id schemaType = nil; + id newType = nil; + + if (![subSchema isKindOfClass:[NSDictionary class]]) + { + OOLog(@"plistVerifier.badSchema", @"Property list schema verifier: bad schema -- expected dictionary, got %@ at path: %@.", [subSchema class], [self keyPathToString:keyPath]); + return nil; + } + + // Iterate over each key-type pair, normalizing types. + result = [[NSMutableDictionary alloc] initWithCapacity:[subSchema count]]; + for (keyEnum = [subSchema keyEnumerator]; (key = [keyEnum nextObject]); ) + { + schemaType = [subSchema objectForKey:key]; + thisChanged = NO; + newType = [self normalizedSchemaType:schemaType + atPath:BackLink(&keyPath, key) + withDefinitions:definitions + changed:&thisChanged]; + + if (newType == nil) + { + OK = NO; + break; + } + if (thisChanged) + { + changed = YES; + [result setObject:newType forKey:key]; + } + else + { + [result setObject:schemaType forKey:key]; + } + } + + + if (OK) + { + if (changed) + { + if (outChanged != NULL) *outChanged = YES; + return [result autorelease]; + } + else + { + [result release]; + return subSchema; + } + } + else + { + [result release]; + return nil; + } +} + + ++ (NSDictionary *)normalizedSchemaType:(id)schemaType + atPath:(BackLinkChain)keyPath + withDefinitions:(NSDictionary *)definitions + changed:(BOOL *)outChanged +{ + BOOL OK = YES, changed = NO, subChanged = NO; + id type = nil; + id newType = nil; + NSMutableDictionary *dict = nil; + id sub = nil; + static NSSet *simpleTypes = nil; + + if ([schemaType isKindOfClass:[NSString class]]) + { + // Short-circuit dictionary references here to avoid {type = {type = "foo"}} + if ([schemaType hasPrefix:@"$"]) + { + newType = [definitions objectForKey:schemaType]; + if (newType == nil) + { + OOLog(@"plistVerifier.badSchema", @"Property list schema verifier: bad schema -- undefined reference %@ at path %@.", schemaType, [self keyPathToString:keyPath]); + } + if (outChanged != NULL) *outChanged = YES; + return newType; + } + dict = schemaType = [[NSMutableDictionary alloc] initWithObjectsAndKeys:schemaType, @"type", nil]; + changed = YES; + } + else if ([schemaType isKindOfClass:[NSDictionary class]]) + { + dict = [schemaType mutableCopy]; + [dict removeObjectForKey:@"$definitions"]; + } + else + { + OOLog(@"plistVerifier.badSchema", @"Property list schema verifier: bad schema -- expected string or dictionary, got %@ at path: %@.", [schemaType class], [self keyPathToString:keyPath]); + OK = NO; + } + + // Normalize type + if (OK) + { + type = [schemaType objectForKey:@"type"]; + if (type == nil) + { + OOLog(@"plistVerifier.badSchema", @"Property list schema verifier: bad schema -- no type specified at path: %@.", [self keyPathToString:keyPath]); + OK = NO; + } + } + + if (OK && [type isKindOfClass:[NSString class]] && [type hasPrefix:@"$"]) + { + newType = [definitions objectForKey:type]; + if (newType == nil) + { + OOLog(@"plistVerifier.badSchema", @"Property list schema verifier: bad schema -- undefined reference %@ at path %@.", type, [self keyPathToString:keyPath]); + OK = NO; + } + type = newType; + changed = YES; + } + + if (OK && [type isKindOfClass:[NSDictionary class]]) + { + type = [self normalizedSchemaType:type atPath:BackLink(&keyPath, @"type") withDefinitions:definitions changed:&changed]; + OK = type != nil; + } + + if (OK && changed) + { + [dict setObject:type forKey:@"type"]; + } + + // Normalize settings for various types. + if (OK && [type isKindOfClass:[NSString class]]) + { + if ([type isEqualToString:@"delegatedType"]) + { + sub = [dict objectForKey:@"baseType"]; + if (sub != nil) + { + sub = [self normalizedSchemaType:sub atPath:BackLink(&keyPath, @"baseType") withDefinitions:definitions changed:&subChanged]; + OK = sub != nil; + if (subChanged) + { + changed = YES; + [dict setObject:sub forKey:@"baseType"]; + } + } + } + else if ([type isEqualToString:@"dictionary"]) + { + sub = [dict objectForKey:@"schema"]; + if (sub != nil) + { + sub = [self normalizedSubSchema:sub atPath:BackLink(&keyPath, @"schema") withDefinitions:definitions changed:&subChanged]; + OK = sub != nil; + if (subChanged) + { + changed = YES; + [dict setObject:sub forKey:@"schema"]; + } + } + sub = [dict objectForKey:@"valueType"]; + if (sub != nil) + { + sub = [self normalizedSchemaType:sub atPath:BackLink(&keyPath, @"valueType") withDefinitions:definitions changed:&subChanged]; + OK = sub != nil; + if (subChanged) + { + changed = YES; + [dict setObject:sub forKey:@"valueType"]; + } + } + } + else if ([type isEqualToString:@"array"]) + { + sub = [dict objectForKey:@"valueType"]; + if (sub != nil) + { + sub = [self normalizedSchemaType:sub atPath:BackLink(&keyPath, @"valueType") withDefinitions:definitions changed:&subChanged]; + OK = sub != nil; + if (subChanged) + { + changed = YES; + [dict setObject:sub forKey:@"valueType"]; + } + } + } + else if ([type isEqualToString:@"oneOf"]) + { + sub = [dict objectForKey:@"options"]; + if (sub != nil) + { + sub = [self normalizedArrayOfSchemaTypes:sub atPath:BackLink(&keyPath, @"options") withDefinitions:definitions changed:&subChanged]; + OK = sub != nil; + if (subChanged) + { + changed = YES; + [dict setObject:sub forKey:@"options"]; + } + } + } + else + { + if (simpleTypes == nil) + { + // Types which are known and for which no special verification is needed. + simpleTypes = [[NSSet setWithObjects:@"string", @"integer", @"positiveInteger", @"float", @"positiveFloat", @"boolean", @"fuzzyBoolean", @"vector", @"quaternion", @"enumeration", nil] retain]; + } + if ([simpleTypes member:type] == nil && ![type hasPrefix:@"$"]) + { + OOLog(@"plistVerifier.badSchema", @"Property list schema verifier: bad schema -- unknown type \"%@\" at path %@.", type, [self keyPathToString:keyPath]); + OK = NO; + } + } + } + + if (OK) + { + if (changed) + { + if (outChanged != NULL) *outChanged = YES; + return [dict autorelease]; + } + else + { + [dict release]; + return schemaType; + } + } + else + { + [dict release]; + return nil; + } +} + + ++ (NSArray *)normalizedArrayOfSchemaTypes:(NSArray *)types + atPath:(BackLinkChain)keyPath + withDefinitions:(NSDictionary *)definitions + changed:(BOOL *)outChanged +{ + NSMutableArray *result = nil; + BOOL OK = YES, thisChanged, changed = NO; + NSEnumerator *schemaTypeEnum = nil; + id schemaType = nil; + id newType = nil; + unsigned i = 0; + + if (![types isKindOfClass:[NSArray class]]) + { + OOLog(@"plistVerifier.badSchema", @"Property list schema verifier: bad schema -- expected array, got %@ at path: %@.", [types class], [self keyPathToString:keyPath]); + return nil; + } + + // Iterate over each type, normalizing. + result = [[NSMutableArray alloc] initWithCapacity:[types count]]; + for (schemaTypeEnum = [types objectEnumerator]; (schemaType = [schemaTypeEnum nextObject]); ) + { + thisChanged = NO; + newType = [self normalizedSchemaType:schemaType + atPath:BackLinkIndex(&keyPath, i) + withDefinitions:definitions + changed:&thisChanged]; + + if (newType == nil) + { + OK = NO; + break; + } + if (thisChanged) + { + changed = YES; + [result addObject:newType]; + } + else + { + [result addObject:schemaType]; + } + } + + if (OK) + { + if (changed) + { + if (outChanged != NULL) *outChanged = YES; + return [result autorelease]; + } + else + { + [result release]; + return types; + } + } + else + { + [result release]; + return nil; + } + +} + + ++ (NSArray *)keyPathToArray:(BackLinkChain)keyPath +{ + NSMutableArray *result = nil; + BackLinkChain *curr = NULL; + + result = [NSMutableArray array]; + for (curr = &keyPath; curr != NULL; curr = curr->link) + { + if (curr->element != nil) [result insertObject:curr->element atIndex:0]; + } + + return result; +} + + ++ (NSString *)keyPathToString:(BackLinkChain)keyPath +{ + return [self descriptionForKeyPath:[self keyPathToArray:keyPath]]; +} + + +- (BOOL)validatePList:(id)rootPList + named:(NSString *)name + subProperty:(id)subProperty + againstSchemaType:(NSDictionary *)subSchema + atPath:(BackLinkChain)keyPath + tentative:(BOOL)tentative + stop:(BOOL *)outStop +{ + SchemaType type; + BOOL OK; + + assert(outStop != nil); + + type = ResolveSchemaType(&subSchema, keyPath); + + #define VALIDATE_CASE(T) case kType##T: OK = Validate_##T(self, subProperty, subSchema, rootPList, name, keyPath, tentative, outStop); break; + + switch (type) + { + VALIDATE_CASE(String); + VALIDATE_CASE(Array); + VALIDATE_CASE(Dictionary); + VALIDATE_CASE(Integer); + VALIDATE_CASE(PositiveInteger); + VALIDATE_CASE(Float); + VALIDATE_CASE(PositiveFloat); + VALIDATE_CASE(OneOf); + VALIDATE_CASE(Enumeration); + VALIDATE_CASE(Boolean); + VALIDATE_CASE(FuzzyBoolean); + VALIDATE_CASE(Vector); + VALIDATE_CASE(Quaternion); + VALIDATE_CASE(DelegatedType); + + case kTypeUnknown: + // ResolveSchemaType() should have provided an error. + *outStop = YES; + return NO; + } + + if (!OK && !tentative && type != kTypeArray && type != kTypeDictionary) + { + [self delegateVerifierWithPropertyList:rootPList + named:name + failedForProperty:subProperty + atPath:[OOPListSchemaVerifier keyPathToArray:keyPath] + expectedType:subSchema]; + } + return OK; +} + +@end + + +static SchemaType StringToSchemaType(NSString *string) +{ + static NSDictionary *typeMap = nil; + SchemaType result; + + if (typeMap == nil) + { + typeMap = + [[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithUnsignedInt:kTypeString], @"string", + [NSNumber numberWithUnsignedInt:kTypeArray], @"array", + [NSNumber numberWithUnsignedInt:kTypeDictionary], @"dictionary", + [NSNumber numberWithUnsignedInt:kTypeInteger], @"integer", + [NSNumber numberWithUnsignedInt:kTypePositiveInteger], @"positiveInteger", + [NSNumber numberWithUnsignedInt:kTypeFloat], @"float", + [NSNumber numberWithUnsignedInt:kTypePositiveFloat], @"positiveFloat", + [NSNumber numberWithUnsignedInt:kTypeOneOf], @"oneOf", + [NSNumber numberWithUnsignedInt:kTypeEnumeration], @"enumeration", + [NSNumber numberWithUnsignedInt:kTypeBoolean], @"boolean", + [NSNumber numberWithUnsignedInt:kTypeFuzzyBoolean], @"fuzzyBoolean", + [NSNumber numberWithUnsignedInt:kTypeVector], @"vector", + [NSNumber numberWithUnsignedInt:kTypeQuaternion], @"quaternion", + [NSNumber numberWithUnsignedInt:kTypeDelegatedType], @"delegatedType", + nil + ] retain]; + } + + result = [[typeMap objectForKey:string] unsignedIntValue]; + if (result == kTypeUnknown) + { + if ([string hasPrefix:@"$"]) + { + OOLog(@"plistVerifier.badSchema", @"Property list schema verifier: bad schema -- unresolved type definition reference \"%@\".", string); + } + else + { + OOLog(@"plistVerifier.badSchema", @"Property list schema verifier: bad schema -- unknown type \"%@\".", string); + } + } + + return result; +} + + +static SchemaType ResolveSchemaType(NSDictionary **typeSpec, BackLinkChain keyPath) +{ + id typeVal = nil; + + for (;;) + { + typeVal = [*typeSpec objectForKey:@"type"]; + if (![typeVal isKindOfClass:[NSDictionary class]]) break; + *typeSpec = typeVal; + } + + if ([typeVal isKindOfClass:[NSString class]]) return StringToSchemaType(typeVal); + + OOLog(@"plistVerifier.badSchema", @"Property list schema verifier: bad schema -- no type for path %@.", [OOPListSchemaVerifier keyPathToString:keyPath]); + return kTypeUnknown; +} + + +static NSString *ApplyStringFilter(NSString *string, id filterSpec) +{ + NSEnumerator *filterEnum = nil; + id filter = nil; + + if (filterSpec == nil) return string; + + if ([filterSpec isKindOfClass:[NSString class]]) + { + filterSpec = [NSArray arrayWithObject:filterSpec]; + } + if ([filterSpec isKindOfClass:[NSArray class]]) + { + for (filterEnum = [filterSpec objectEnumerator]; (filter = [filterEnum nextObject]); ) + { + if ([filter isKindOfClass:[NSString class]]) + { + if ([filter isEqual:@"lowerCase"]) string = [string lowercaseString]; + if ([filter isEqual:@"upperCase"]) string = [string uppercaseString]; + if ([filter hasPrefix:@"truncFront:"]) + { + string = [string substringToIndex:[[filter substringFromIndex:11] intValue]]; + } + if ([filter hasPrefix:@"truncBack:"]) + { + string = [string substringToIndex:[[filter substringFromIndex:10] intValue]]; + } + } + else + { + OOLog(@"plistVerifier.badSchema", @"Property list schema verifier: unknown string filter \"%@\".", filter); + } + } + } + else + { + OOLog(@"plistVerifier.badSchema", @"Property list schema verifier: unknown string filter \"%@\".", filterSpec); + } + + return string; +} + + +static BOOL ApplyStringTest(NSString *string, id test, SEL testSelector, NSString *testDescription) +{ + BOOL (*testIMP)(id, SEL, NSString *); + NSEnumerator *testEnum = nil; + id subTest = nil; + + if (test == nil) return YES; + + testIMP = (BOOL(*)(id, SEL, NSString *))[string methodForSelector:testSelector]; + if (testIMP == NULL) return NO; + + if ([test isKindOfClass:[NSString class]]) + { + test = [NSArray arrayWithObject:test]; + } + + if ([test isKindOfClass:[NSArray class]]) + { + for (testEnum = [test objectEnumerator]; (subTest = [testEnum nextObject]); ) + { + if ([subTest isKindOfClass:[NSString class]]) + { + if (testIMP(string, testSelector, subTest)) return YES; + } + else + { + OOLog(@"plistVerifier.badSchema", @"Property list schema verifier: bad schema -- %@ requirement \"%@\" is not a %@.", testDescription, subTest, @"string"); + return NO; + } + } + } + else + { + OOLog(@"plistVerifier.badSchema", @"Property list schema verifier: bad schema -- %@ requirement \"%@\" is not a %@.", testDescription, test, @"string or array"); + } + return NO; +} + + +static BOOL Validate_String(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop) +{ + NSString *filteredString = nil; + id testValue = nil; + unsigned length; + unsigned lengthConstraint; + + if (![value isKindOfClass:[NSString class]]) return NO; + + filteredString = ApplyStringFilter(value, [params objectForKey:@"filter"]); + if (filteredString == nil) return NO; + + testValue = [params objectForKey:@"requiredPrefix"]; + if (testValue != nil) + { + if (!ApplyStringTest(filteredString, testValue, @selector(hasPrefix:), @"prefix")) return NO; + } + + testValue = [params objectForKey:@"requiredSuffix"]; + if (testValue != nil) + { + if (!ApplyStringTest(filteredString, testValue, @selector(hasSuffix:), @"suffix")) return NO; + } + + testValue = [params objectForKey:@"requiredSubString"]; + if (testValue != nil) + { + if (!ApplyStringTest(filteredString, testValue, @selector(ooPListVerifierHasSubString:), @"substring") + ) return NO; + } + + length = [filteredString length]; + lengthConstraint = [params unsignedIntForKey:@"minLength"]; + if (length < lengthConstraint) return NO; + + lengthConstraint = [params unsignedIntForKey:@"maxLength" defaultValue:UINT_MAX]; + if (lengthConstraint < length) return NO; + + return YES; +} + + +static BOOL Validate_Array(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop) +{ + NSDictionary *valueType = nil; + BOOL OK = YES, stop = NO; + unsigned i, count; + id subProperty = nil; + NSAutoreleasePool *pool = nil; + unsigned constraint; + + if (![value isKindOfClass:[NSArray class]]) return NO; + + count = [value count]; + constraint = [params unsignedIntForKey:@"minCount" defaultValue:0]; + if (count < constraint) return NO; + constraint = [params unsignedIntForKey:@"maxCount" defaultValue:UINT_MAX]; + if (constraint < count) return NO; + + valueType = [params objectForKey:@"valueType"]; + if (valueType != nil) + { + for (i = 0; i != count; ++i) + { + pool = [[NSAutoreleasePool alloc] init]; + + subProperty = [value objectAtIndex:i]; + + if (![verifier validatePList:rootPList + named:name + subProperty:subProperty + againstSchemaType:valueType + atPath:BackLinkIndex(&keyPath, i) + tentative:tentative stop:&stop]) + { + OK = NO; + } + + [pool release]; + ++i; + } + } + + *outStop = stop; + return OK; +} + + +static BOOL Validate_Dictionary(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop) +{ + NSDictionary *schema = nil, + *valueType = nil, + *typeSpec = nil; + NSEnumerator *keyEnum = nil; + NSString *key = nil; + id subProperty = nil; + BOOL OK = YES, stop = NO; + BOOL allowOthers; + NSAutoreleasePool *pool = nil; + NSMutableSet *requiredKeys = nil; + NSArray *requiredKeyList = nil; + unsigned count, constraint; + + if (![value isKindOfClass:[NSDictionary class]]) return NO; + + count = [value count]; + constraint = [params unsignedIntForKey:@"minCount" defaultValue:0]; + if (count < constraint) return NO; + constraint = [params unsignedIntForKey:@"maxCount" defaultValue:UINT_MAX]; + if (constraint < count) return NO; + + schema = [params objectForKey:@"schema"]; + valueType = [params objectForKey:@"valueType"]; + allowOthers = [params boolForKey:@"allowOthers" defaultValue:YES]; + requiredKeyList = [params arrayForKey:@"requiredKeys"]; + + if (schema == nil && valueType == nil && requiredKeyList == nil && allowOthers) return YES; + + if (requiredKeyList != nil) + { + requiredKeys = [NSMutableSet setWithArray:requiredKeyList]; + } + + for (keyEnum = [value keyEnumerator]; (key = [keyEnum nextObject]) && !stop; ) + { + pool = [[NSAutoreleasePool alloc] init]; + + subProperty = [value objectForKey:key]; + typeSpec = [schema objectForKey:key]; + if (typeSpec == nil) typeSpec = valueType; + + if (typeSpec != nil) + { + if (![verifier validatePList:rootPList + named:name + subProperty:subProperty + againstSchemaType:typeSpec + atPath:BackLink(&keyPath, key) + tentative:tentative stop:&stop]) + { + OK = NO; + } + } + else if (!allowOthers && ![requiredKeys member:key]) OK = NO; + + [requiredKeys removeObject:key]; + + [pool release]; + } + + if ([requiredKeys count] != 0) + { + OK = NO; + } + + *outStop = stop; + return OK; +} + + +static BOOL Validate_Integer(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop) +{ + long long numericValue; + long long constraint; + + numericValue = OOLongLongFromObject(value, 0); + + // Check basic parseability. If there's inequality here, the default value is being returned. + if (numericValue != OOLongLongFromObject(value, 1)) return NO; + + // Check constraints. + constraint = [params longLongForKey:@"minimum" defaultValue:LLONG_MIN]; + if (numericValue < constraint) return NO; + + constraint = [params longLongForKey:@"maximum" defaultValue:LLONG_MAX]; + if (constraint < numericValue) return NO; + + return YES; +} + + +static BOOL Validate_PositiveInteger(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop) +{ + unsigned long long numericValue; + unsigned long long constraint; + + numericValue = OOUnsignedLongLongFromObject(value, 0); + + // Check basic parseability. If there's inequality here, the default value is being returned. + if (numericValue != OOUnsignedLongLongFromObject(value, 1)) return NO; + + // Check constraints. + constraint = [params unsignedLongLongForKey:@"minimum"]; + if (numericValue < constraint) return NO; + + constraint = [params unsignedLongLongForKey:@"maximum" defaultValue:ULLONG_MAX]; + if (constraint < numericValue) return NO; + + return YES; +} + + +static BOOL Validate_Float(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop) +{ + double numericValue; + double constraint; + + numericValue = OODoubleFromObject(value, 0); + + // Check basic parseability. If there's inequality here, the default value is being returned. + if (numericValue != OODoubleFromObject(value, 1)) return NO; + + // Check constraints. + constraint = [params doubleForKey:@"minimum" defaultValue:-INFINITY]; + if (numericValue < constraint) return NO; + + constraint = [params doubleForKey:@"maximum" defaultValue:INFINITY]; + if (constraint < numericValue) return NO; + + return YES; +} + + +static BOOL Validate_PositiveFloat(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop) +{ + double numericValue; + double constraint; + + numericValue = OODoubleFromObject(value, 0); + + // Check basic parseability. If there's inequality here, the default value is being returned. + if (numericValue != OODoubleFromObject(value, 1)) return NO; + + if (numericValue < 0) return NO; + + // Check constraints. + constraint = [params doubleForKey:@"minimum" defaultValue:0]; + if (numericValue < constraint) return NO; + + constraint = [params doubleForKey:@"maximum" defaultValue:INFINITY]; + if (constraint < numericValue) return NO; + + return YES; +} + + +static BOOL Validate_OneOf(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop) +{ + NSArray *options = nil; + BOOL OK = NO, stop = NO; + NSEnumerator *optionEnum = nil; + id option = nil; + NSAutoreleasePool *pool = nil; + + options = [params arrayForKey:@"options"]; + if (options == nil) + { + OOLog(@"plistVerifier.badSchema", @"Property list schema verifier: bad schema encountered while verifying %@ -- no options specified for oneOf clause at path %@.", name, [OOPListSchemaVerifier keyPathToString:keyPath]); + *outStop = YES; + return NO; + } + + for (optionEnum = [options objectEnumerator]; (option = [optionEnum nextObject]) ;) + { + pool = [[NSAutoreleasePool alloc] init]; + + if ([verifier validatePList:rootPList + named:name + subProperty:value + againstSchemaType:option + atPath:keyPath + tentative:YES + stop:&stop]) + { + OK = YES; + } + + [pool release]; + if (OK) break; + } + + // Ignore stop in tentatives. + return OK; +} + + +static BOOL Validate_Enumeration(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop) +{ + NSArray *values = nil; + NSString *filteredString = nil; + + values = [params arrayForKey:@"values"]; + if (values == nil) + { + OOLog(@"plistVerifier.badSchema", @"Property list schema verifier: bad schema encountered while verifying %@ -- no values specified for enumeration at path %@.", name, [OOPListSchemaVerifier keyPathToString:keyPath]); + *outStop = YES; + return NO; + } + + filteredString = ApplyStringFilter(value, [params objectForKey:@"filter"]); + if (filteredString == nil) return NO; + + return [values containsObject:filteredString]; +} + + +static BOOL Validate_Boolean(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop) +{ + // Check basic parseability. If there's inequality here, the default value is being returned. + return OOBooleanFromObject(value, 0) == OOBooleanFromObject(value, 1); +} + + +static BOOL Validate_FuzzyBoolean(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop) +{ + // Check basic parseability. If there's inequality here, the default value is being returned. + return OOFuzzyBooleanFromObject(value, 0) == OOFuzzyBooleanFromObject(value, 1); +} + + +static BOOL Validate_Vector(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop) +{ + // Check basic parseability. If there's inequality here, the default value is being returned. + return vector_equal(OOVectorFromObject(value, kZeroVector), OOVectorFromObject(value, kBasisXVector)); +} + + +static BOOL Validate_Quaternion(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop) +{ + // Check basic parseability. If there's inequality here, the default value is being returned. + return quaternion_equal(OOQuaternionFromObject(value, kZeroQuaternion), OOQuaternionFromObject(value, kIdentityQuaternion)); +} + + +static BOOL Validate_DelegatedType(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop) +{ + NSDictionary *baseType = nil; + NSString *key = nil; + BOOL stop = NO; + + baseType = [params objectForKey:@"baseType"]; + if (baseType != nil) + { + if (![verifier validatePList:rootPList + named:name + subProperty:value + againstSchemaType:baseType + atPath:keyPath + tentative:NO + stop:&stop]) + { + *outStop = stop; + return NO; + } + } + + key = [params objectForKey:@"key"]; + return [verifier delegateVerifierWithPropertyList:rootPList + named:name + testProperty:value + atPath:[OOPListSchemaVerifier keyPathToArray:keyPath] + againstType:key]; +} + + +@implementation NSString (OOPListSchemaVerifierHelpers) + +- (BOOL)ooPListVerifierHasSubString:(NSString *)string +{ + return [self rangeOfString:string].location != NSNotFound; +} + +@end + + +static NSError *Error(OOPListSchemaVerifierErrorCode errorCode, NSString *format, ...) +{ + va_list args; + + va_start(args, format); + ErrorWithDictionaryAndArguments(errorCode, nil, format, args); + va_end(args); +} + + +static NSError *ErrorWithProperty(OOPListSchemaVerifierErrorCode errorCode, NSString *propKey, id propValue, NSString *format, ...) +{ + va_list args; + NSDictionary *dict = nil; + + dict = [NSDictionary dictionaryWithObject:propValue forKey:propKey]; + va_start(args, format); + ErrorWithDictionaryAndArguments(errorCode, dict, format, args); + va_end(args); +} + + +static NSError *ErrorWithDictionary(OOPListSchemaVerifierErrorCode errorCode, NSDictionary *dict, NSString *format, ...) +{ + va_list args; + NSDictionary *dict = nil; + + va_start(args, format); + ErrorWithDictionaryAndArguments(errorCode, dict, format, args); + va_end(args); +} + + +static NSError *ErrorWithDictionaryAndArguments(OOPListSchemaVerifierErrorCode errorCode, NSDictionary *dict, NSString *format, va_list arguments) +{ + NSString *message = nil; + NSError *result = nil; + id userInfo = nil; + + message = [[NSString alloc] initWithFormat:format arguments:arguments]; + if (dict == nil) userInfo = [NSDictionary dictionaryWithObject:message forKey:NSLocalizedDescriptionKey]; + else + { + userInfo = [dict mutableCopy]; + [dict setObject:userInfo forKey:NSLocalizedDescriptionKey]; + [userInfo autorelease]; + } + [message release]; + + return [NSError errorWithDomain:kOOPListSchemaVerifierErrorDomain code:errorCode userInfo:dict]; +} + +#endif // OO_OXP_VERIFIER_ENABLED diff --git a/src/Core/OXPVerifier/OOTextureVerifierStage.h b/src/Core/OXPVerifier/OOTextureVerifierStage.h index 8a4b1141..7dd780ff 100644 --- a/src/Core/OXPVerifier/OOTextureVerifierStage.h +++ b/src/Core/OXPVerifier/OOTextureVerifierStage.h @@ -38,7 +38,7 @@ MA 02110-1301, USA. // Returns name to be used in -dependents by other stages; also registers stage. + (NSString *)nameForReverseDependencyForVerifier:(OOOXPVerifier *)verifier; -/* These can be called by other stages *before* the texture stage runs. +/* This can be called by other stages *before* the texture stage runs. The context specifies where the texture is used; something like "fooShip.dat" or "shipdata.plist materials dictionary for ship \"foo\"". It should make sense with "Texture \"foo\" referenced in " in front of it. @@ -53,4 +53,11 @@ MA 02110-1301, USA. @end + +@interface OOOXPVerifier(OOTextureVerifierStage) + +- (OOTextureVerifierStage *)textureVerifierStage; + +@end + #endif diff --git a/src/Core/OXPVerifier/OOTextureVerifierStage.m b/src/Core/OXPVerifier/OOTextureVerifierStage.m index e9c84ceb..a8936fd5 100644 --- a/src/Core/OXPVerifier/OOTextureVerifierStage.m +++ b/src/Core/OXPVerifier/OOTextureVerifierStage.m @@ -119,6 +119,7 @@ static NSString * const kStageName = @"Testing textures and images"; { OOFileScannerVerifierStage *fileScanner = nil; + if (name == nil) return; if ([_usedTextures member:name] != nil) return; [_usedTextures addObject:name]; @@ -198,4 +199,14 @@ static NSString * const kStageName = @"Testing textures and images"; @end + +@implementation OOOXPVerifier(OOTextureVerifierStage) + +- (OOTextureVerifierStage *)textureVerifierStage +{ + return [self stageWithName:kStageName]; +} + +@end + #endif diff --git a/src/Core/OXPVerifier/plist verifier design.txt b/src/Core/OXPVerifier/plist verifier design.txt new file mode 100644 index 00000000..1960ac68 --- /dev/null +++ b/src/Core/OXPVerifier/plist verifier design.txt @@ -0,0 +1,42 @@ +String filters: +lowerCase +upperCase +truncFront:# +truncBack:# +Can be chained using array. + +Strings: +filters applied first. +requiredPrefix (may be array) +requiredSuffix (may be array) +requiredSubString (may be array) +minLength +maxLength + +Numerical types: +minimum +maximum + +Enumerations: +filters applied before test. +values + +Dictionary: +minCount +maxCount +valueType +schema +allowOthers (default:YES) +requiredKeys + +Array: +minCount +maxCount +valueType + +oneOf: +options + +delegatedType: +baseType +key \ No newline at end of file