@lampdotdev/lamp 0.0.0-stage → 0.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +357 -2
- package/Resources/Devices/roku.json +410 -0
- package/Resources/Devices/xbox-one.json +367 -0
- package/Resources/GameKit/NOTICE.md +63 -0
- package/Resources/GameKit/apple/LampPage.swift +1646 -0
- package/Resources/GameKit/apple/LampPlayer.swift +46 -0
- package/Resources/GameKit/apple/LampWebApp.swift +63 -0
- package/Resources/GameKit/apple/LampWebGame.swift +87 -0
- package/Resources/GameKit/apple/Native.swift +12 -0
- package/Resources/GameKit/build.sh +55 -0
- package/Resources/GameKit/lamp_game.h +116 -0
- package/Resources/GameKit/lamp_host.h +19 -0
- package/Resources/GameKit/lamp_kit.cpp +61 -0
- package/Resources/GameKit/lamp_sound.cpp +455 -0
- package/Resources/GameKit/lamp_xbox.cpp +374 -0
- package/Resources/GameKit/libraries/HandmadeMath.h +3940 -0
- package/Resources/GameKit/libraries/cgltf.h +7240 -0
- package/Resources/GameKit/libraries/dr_mp3.h +5430 -0
- package/Resources/GameKit/libraries/dr_wav.h +9209 -0
- package/Resources/GameKit/libraries/sokol_debugtext.h +4754 -0
- package/Resources/GameKit/libraries/sokol_gfx.h +29558 -0
- package/Resources/GameKit/libraries/sokol_gl.h +4538 -0
- package/Resources/GameKit/libraries/sokol_log.h +335 -0
- package/Resources/GameKit/libraries/sokol_shape.h +1531 -0
- package/Resources/GameKit/libraries/stb_image.h +7988 -0
- package/Resources/GameKit/libraries/stb_vorbis.c +5584 -0
- package/Resources/GameKit/scaffold/Game/game.cpp +142 -0
- package/Resources/GameKit/scaffold/Game/scene.glsl +49 -0
- package/Resources/GameKit/scaffold-web/Web/game.js +31 -0
- package/Resources/GameKit/scaffold-web/Web/index.html +13 -0
- package/Resources/GameKit/scaffold-web/Web/making.js +33 -0
- package/Resources/GameKit/scaffold-web/Web/world.js +33 -0
- package/Resources/GameKit/web/lamp-game.js +3036 -0
- package/Resources/GameKit/web/lamp-phaser.js +128 -0
- package/Resources/WebHost/App.exe +0 -0
- package/Resources/WebHost/Microsoft.Web.WebView2.Core.dll +0 -0
- package/Resources/WebHost/WebView2-LICENSE.txt +27 -0
- package/Resources/WebHost/lamp_controllers.h +1506 -0
- package/Resources/WebHost/lamp_web_host.cpp +1173 -0
- package/Resources/WebLibraries/earlier.txt +318 -0
- package/Resources/WebLibraries/phaser/LICENSE.md +20 -0
- package/Resources/WebLibraries/phaser/NOTICE.md +2 -0
- package/Resources/WebLibraries/phaser/phaser.esm.min.js +1 -0
- package/Resources/WebLibraries/qrcode/LICENSE +21 -0
- package/Resources/WebLibraries/qrcode/NOTICE.md +4 -0
- package/Resources/WebLibraries/qrcode/qrcode.js +2297 -0
- package/Resources/WebLibraries/rapier/LICENSE +201 -0
- package/Resources/WebLibraries/rapier/NOTICE.md +3 -0
- package/Resources/WebLibraries/rapier/rapier.js +2 -0
- package/Resources/WebLibraries/three/LICENSE +21 -0
- package/Resources/WebLibraries/three/NOTICE.md +4 -0
- package/Resources/WebLibraries/three/addons/animation/AnimationClipCreator.js +168 -0
- package/Resources/WebLibraries/three/addons/animation/CCDIKSolver.js +607 -0
- package/Resources/WebLibraries/three/addons/controls/ArcballControls.js +3543 -0
- package/Resources/WebLibraries/three/addons/controls/DragControls.js +452 -0
- package/Resources/WebLibraries/three/addons/controls/FirstPersonControls.js +512 -0
- package/Resources/WebLibraries/three/addons/controls/FlyControls.js +384 -0
- package/Resources/WebLibraries/three/addons/controls/MapControls.js +116 -0
- package/Resources/WebLibraries/three/addons/controls/OrbitControls.js +1985 -0
- package/Resources/WebLibraries/three/addons/controls/PointerLockControls.js +264 -0
- package/Resources/WebLibraries/three/addons/controls/TrackballControls.js +1106 -0
- package/Resources/WebLibraries/three/addons/controls/TransformControls.js +2003 -0
- package/Resources/WebLibraries/three/addons/curves/CurveExtras.js +694 -0
- package/Resources/WebLibraries/three/addons/curves/NURBSCurve.js +169 -0
- package/Resources/WebLibraries/three/addons/curves/NURBSSurface.js +98 -0
- package/Resources/WebLibraries/three/addons/curves/NURBSUtils.js +532 -0
- package/Resources/WebLibraries/three/addons/curves/NURBSVolume.js +82 -0
- package/Resources/WebLibraries/three/addons/effects/AnaglyphEffect.js +274 -0
- package/Resources/WebLibraries/three/addons/effects/AsciiEffect.js +323 -0
- package/Resources/WebLibraries/three/addons/effects/OutlineEffect.js +489 -0
- package/Resources/WebLibraries/three/addons/effects/ParallaxBarrierEffect.js +155 -0
- package/Resources/WebLibraries/three/addons/effects/StereoEffect.js +91 -0
- package/Resources/WebLibraries/three/addons/environments/ColorEnvironment.js +59 -0
- package/Resources/WebLibraries/three/addons/environments/DebugEnvironment.js +102 -0
- package/Resources/WebLibraries/three/addons/environments/RoomEnvironment.js +185 -0
- package/Resources/WebLibraries/three/addons/geometries/BoxLineGeometry.js +92 -0
- package/Resources/WebLibraries/three/addons/geometries/ConvexGeometry.js +72 -0
- package/Resources/WebLibraries/three/addons/geometries/DecalGeometry.js +420 -0
- package/Resources/WebLibraries/three/addons/geometries/LoftGeometry.js +355 -0
- package/Resources/WebLibraries/three/addons/geometries/ParametricFunctions.js +100 -0
- package/Resources/WebLibraries/three/addons/geometries/ParametricGeometry.js +172 -0
- package/Resources/WebLibraries/three/addons/geometries/RoundedBoxGeometry.js +216 -0
- package/Resources/WebLibraries/three/addons/geometries/TeapotGeometry.js +689 -0
- package/Resources/WebLibraries/three/addons/geometries/TextGeometry.js +102 -0
- package/Resources/WebLibraries/three/addons/helpers/AnimationPathHelper.js +302 -0
- package/Resources/WebLibraries/three/addons/helpers/LightProbeGridHelper.js +166 -0
- package/Resources/WebLibraries/three/addons/helpers/LightProbeGridHelperWebGL.js +221 -0
- package/Resources/WebLibraries/three/addons/helpers/LightProbeHelper.js +166 -0
- package/Resources/WebLibraries/three/addons/helpers/LightProbeHelperGPU.js +102 -0
- package/Resources/WebLibraries/three/addons/helpers/OctreeHelper.js +109 -0
- package/Resources/WebLibraries/three/addons/helpers/PositionalAudioHelper.js +169 -0
- package/Resources/WebLibraries/three/addons/helpers/RapierHelper.js +59 -0
- package/Resources/WebLibraries/three/addons/helpers/RectAreaLightHelper.js +118 -0
- package/Resources/WebLibraries/three/addons/helpers/TextureHelper.js +265 -0
- package/Resources/WebLibraries/three/addons/helpers/TextureHelperGPU.js +214 -0
- package/Resources/WebLibraries/three/addons/helpers/VertexNormalsHelper.js +155 -0
- package/Resources/WebLibraries/three/addons/helpers/VertexTangentsHelper.js +133 -0
- package/Resources/WebLibraries/three/addons/helpers/ViewHelper.js +529 -0
- package/Resources/WebLibraries/three/addons/interactive/HTMLMesh.js +617 -0
- package/Resources/WebLibraries/three/addons/interactive/InteractiveGroup.js +224 -0
- package/Resources/WebLibraries/three/addons/interactive/SelectionBox.js +324 -0
- package/Resources/WebLibraries/three/addons/interactive/SelectionHelper.js +150 -0
- package/Resources/WebLibraries/three/addons/libs/fflate.module.js +2699 -0
- package/Resources/WebLibraries/three/addons/libs/lil-gui.module.min.js +8 -0
- package/Resources/WebLibraries/three/addons/libs/meshopt_decoder.module.js +196 -0
- package/Resources/WebLibraries/three/addons/libs/potpack.module.js +125 -0
- package/Resources/WebLibraries/three/addons/libs/stats.module.js +167 -0
- package/Resources/WebLibraries/three/addons/libs/tween.module.js +876 -0
- package/Resources/WebLibraries/three/addons/lighting/ClusteredLighting.js +153 -0
- package/Resources/WebLibraries/three/addons/lighting/DynamicLighting.js +82 -0
- package/Resources/WebLibraries/three/addons/lighting/LightMapper.js +295 -0
- package/Resources/WebLibraries/three/addons/lighting/LightProbeGrid.js +923 -0
- package/Resources/WebLibraries/three/addons/lighting/LightProbeGridUtils.js +107 -0
- package/Resources/WebLibraries/three/addons/lighting/LightProbeGridWebGL.js +846 -0
- package/Resources/WebLibraries/three/addons/lighting/vxgi/VXGIConeTracer.js +243 -0
- package/Resources/WebLibraries/three/addons/lighting/vxgi/VXGIIrradiance.js +43 -0
- package/Resources/WebLibraries/three/addons/lighting/vxgi/VXGINode.js +617 -0
- package/Resources/WebLibraries/three/addons/lighting/vxgi/VXGISceneCollector.js +793 -0
- package/Resources/WebLibraries/three/addons/lighting/vxgi/VXGIVolume.js +1440 -0
- package/Resources/WebLibraries/three/addons/lights/LightProbeGenerator.js +337 -0
- package/Resources/WebLibraries/three/addons/lights/RectAreaLightTexturesLib.js +127 -0
- package/Resources/WebLibraries/three/addons/lights/RectAreaLightUniformsLib.js +40 -0
- package/Resources/WebLibraries/three/addons/lights/SunLight.js +100 -0
- package/Resources/WebLibraries/three/addons/lights/SunLightNode.js +56 -0
- package/Resources/WebLibraries/three/addons/lights/SunLightShadow.js +319 -0
- package/Resources/WebLibraries/three/addons/lights/SunShadowNode.js +312 -0
- package/Resources/WebLibraries/three/addons/lines/Line2.js +56 -0
- package/Resources/WebLibraries/three/addons/lines/LineGeometry.js +157 -0
- package/Resources/WebLibraries/three/addons/lines/LineMaterial.js +742 -0
- package/Resources/WebLibraries/three/addons/lines/LineSegments2.js +434 -0
- package/Resources/WebLibraries/three/addons/lines/LineSegmentsGeometry.js +298 -0
- package/Resources/WebLibraries/three/addons/lines/Wireframe.js +108 -0
- package/Resources/WebLibraries/three/addons/lines/WireframeGeometry2.js +49 -0
- package/Resources/WebLibraries/three/addons/lines/webgpu/Line2.js +46 -0
- package/Resources/WebLibraries/three/addons/lines/webgpu/LineSegments2.js +419 -0
- package/Resources/WebLibraries/three/addons/lines/webgpu/Wireframe.js +86 -0
- package/Resources/WebLibraries/three/addons/loaders/EXRLoader.js +3411 -0
- package/Resources/WebLibraries/three/addons/loaders/FBXLoader.js +4660 -0
- package/Resources/WebLibraries/three/addons/loaders/FontLoader.js +262 -0
- package/Resources/WebLibraries/three/addons/loaders/GLTFLoader.js +5006 -0
- package/Resources/WebLibraries/three/addons/loaders/HDRLoader.js +463 -0
- package/Resources/WebLibraries/three/addons/loaders/MTLLoader.js +601 -0
- package/Resources/WebLibraries/three/addons/loaders/OBJLoader.js +961 -0
- package/Resources/WebLibraries/three/addons/loaders/PLYLoader.js +992 -0
- package/Resources/WebLibraries/three/addons/loaders/RGBELoader.js +18 -0
- package/Resources/WebLibraries/three/addons/loaders/STLLoader.js +423 -0
- package/Resources/WebLibraries/three/addons/loaders/SVGLoader.js +3297 -0
- package/Resources/WebLibraries/three/addons/loaders/TGALoader.js +539 -0
- package/Resources/WebLibraries/three/addons/loaders/UltraHDRLoader.js +757 -0
- package/Resources/WebLibraries/three/addons/math/Capsule.js +159 -0
- package/Resources/WebLibraries/three/addons/math/ColorConverter.js +62 -0
- package/Resources/WebLibraries/three/addons/math/ColorSpaces.js +147 -0
- package/Resources/WebLibraries/three/addons/math/ConvexHull.js +1695 -0
- package/Resources/WebLibraries/three/addons/math/ImprovedNoise.js +88 -0
- package/Resources/WebLibraries/three/addons/math/Lut.js +317 -0
- package/Resources/WebLibraries/three/addons/math/MeshSurfaceSampler.js +315 -0
- package/Resources/WebLibraries/three/addons/math/OBB.js +535 -0
- package/Resources/WebLibraries/three/addons/math/Octree.js +825 -0
- package/Resources/WebLibraries/three/addons/math/SimplexNoise.js +470 -0
- package/Resources/WebLibraries/three/addons/misc/ConvexObjectBreaker.js +539 -0
- package/Resources/WebLibraries/three/addons/misc/GPUComputationRenderer.js +508 -0
- package/Resources/WebLibraries/three/addons/misc/Gyroscope.js +78 -0
- package/Resources/WebLibraries/three/addons/misc/MorphAnimMesh.js +119 -0
- package/Resources/WebLibraries/three/addons/misc/MorphBlendMesh.js +425 -0
- package/Resources/WebLibraries/three/addons/misc/ProgressiveLightMap.js +370 -0
- package/Resources/WebLibraries/three/addons/misc/ProgressiveLightMapGPU.js +322 -0
- package/Resources/WebLibraries/three/addons/misc/RollerCoaster.js +661 -0
- package/Resources/WebLibraries/three/addons/misc/Sculptor.js +1679 -0
- package/Resources/WebLibraries/three/addons/misc/SculptorMesh.js +1504 -0
- package/Resources/WebLibraries/three/addons/misc/SculptorTools.js +1180 -0
- package/Resources/WebLibraries/three/addons/misc/SculptorUtils.js +345 -0
- package/Resources/WebLibraries/three/addons/misc/TileCreasedNormalsPlugin.js +270 -0
- package/Resources/WebLibraries/three/addons/misc/TubePainter.js +611 -0
- package/Resources/WebLibraries/three/addons/misc/Volume.js +520 -0
- package/Resources/WebLibraries/three/addons/misc/VolumeSlice.js +275 -0
- package/Resources/WebLibraries/three/addons/modifiers/CurveModifier.js +374 -0
- package/Resources/WebLibraries/three/addons/modifiers/CurveModifierGPU.js +256 -0
- package/Resources/WebLibraries/three/addons/modifiers/EdgeSplitModifier.js +299 -0
- package/Resources/WebLibraries/three/addons/modifiers/TessellateModifier.js +340 -0
- package/Resources/WebLibraries/three/addons/objects/GroundedSkybox.js +69 -0
- package/Resources/WebLibraries/three/addons/objects/Lensflare.js +489 -0
- package/Resources/WebLibraries/three/addons/objects/LensflareMesh.js +376 -0
- package/Resources/WebLibraries/three/addons/objects/MarchingCubes.js +1261 -0
- package/Resources/WebLibraries/three/addons/objects/Reflector.js +390 -0
- package/Resources/WebLibraries/three/addons/objects/ReflectorForSSRPass.js +393 -0
- package/Resources/WebLibraries/three/addons/objects/Refractor.js +389 -0
- package/Resources/WebLibraries/three/addons/objects/ShadowMesh.js +130 -0
- package/Resources/WebLibraries/three/addons/objects/Sky.js +342 -0
- package/Resources/WebLibraries/three/addons/objects/SkyMesh.js +403 -0
- package/Resources/WebLibraries/three/addons/objects/Water.js +374 -0
- package/Resources/WebLibraries/three/addons/objects/Water2.js +403 -0
- package/Resources/WebLibraries/three/addons/objects/Water2Mesh.js +199 -0
- package/Resources/WebLibraries/three/addons/objects/WaterMesh.js +194 -0
- package/Resources/WebLibraries/three/addons/postprocessing/AfterimagePass.js +187 -0
- package/Resources/WebLibraries/three/addons/postprocessing/BloomPass.js +274 -0
- package/Resources/WebLibraries/three/addons/postprocessing/BokehPass.js +218 -0
- package/Resources/WebLibraries/three/addons/postprocessing/ClearPass.js +97 -0
- package/Resources/WebLibraries/three/addons/postprocessing/CubeTexturePass.js +146 -0
- package/Resources/WebLibraries/three/addons/postprocessing/DotScreenPass.js +114 -0
- package/Resources/WebLibraries/three/addons/postprocessing/EffectComposer.js +365 -0
- package/Resources/WebLibraries/three/addons/postprocessing/FXAAPass.js +40 -0
- package/Resources/WebLibraries/three/addons/postprocessing/FilmPass.js +113 -0
- package/Resources/WebLibraries/three/addons/postprocessing/GTAOPass.js +735 -0
- package/Resources/WebLibraries/three/addons/postprocessing/GlitchPass.js +177 -0
- package/Resources/WebLibraries/three/addons/postprocessing/HalftonePass.js +134 -0
- package/Resources/WebLibraries/three/addons/postprocessing/LUTPass.js +138 -0
- package/Resources/WebLibraries/three/addons/postprocessing/MaskPass.js +195 -0
- package/Resources/WebLibraries/three/addons/postprocessing/OutlinePass.js +776 -0
- package/Resources/WebLibraries/three/addons/postprocessing/OutputPass.js +148 -0
- package/Resources/WebLibraries/three/addons/postprocessing/Pass.js +191 -0
- package/Resources/WebLibraries/three/addons/postprocessing/RenderPass.js +193 -0
- package/Resources/WebLibraries/three/addons/postprocessing/RenderPixelatedPass.js +314 -0
- package/Resources/WebLibraries/three/addons/postprocessing/RenderTransitionPass.js +267 -0
- package/Resources/WebLibraries/three/addons/postprocessing/SAOPass.js +407 -0
- package/Resources/WebLibraries/three/addons/postprocessing/SMAAPass.js +230 -0
- package/Resources/WebLibraries/three/addons/postprocessing/SSAARenderPass.js +313 -0
- package/Resources/WebLibraries/three/addons/postprocessing/SSAOPass.js +527 -0
- package/Resources/WebLibraries/three/addons/postprocessing/SSRPass.js +858 -0
- package/Resources/WebLibraries/three/addons/postprocessing/SavePass.js +132 -0
- package/Resources/WebLibraries/three/addons/postprocessing/ShaderPass.js +135 -0
- package/Resources/WebLibraries/three/addons/postprocessing/TAARenderPass.js +243 -0
- package/Resources/WebLibraries/three/addons/postprocessing/TexturePass.js +131 -0
- package/Resources/WebLibraries/three/addons/postprocessing/UnrealBloomPass.js +541 -0
- package/Resources/WebLibraries/three/addons/shaders/ACESFilmicToneMappingShader.js +96 -0
- package/Resources/WebLibraries/three/addons/shaders/AfterimageShader.js +63 -0
- package/Resources/WebLibraries/three/addons/shaders/BasicShader.js +36 -0
- package/Resources/WebLibraries/three/addons/shaders/BleachBypassShader.js +68 -0
- package/Resources/WebLibraries/three/addons/shaders/BlendShader.js +56 -0
- package/Resources/WebLibraries/three/addons/shaders/BokehShader.js +151 -0
- package/Resources/WebLibraries/three/addons/shaders/BokehShader2.js +404 -0
- package/Resources/WebLibraries/three/addons/shaders/BrightnessContrastShader.js +62 -0
- package/Resources/WebLibraries/three/addons/shaders/ColorCorrectionShader.js +59 -0
- package/Resources/WebLibraries/three/addons/shaders/ColorifyShader.js +57 -0
- package/Resources/WebLibraries/three/addons/shaders/ConvolutionShader.js +74 -0
- package/Resources/WebLibraries/three/addons/shaders/CopyShader.js +52 -0
- package/Resources/WebLibraries/three/addons/shaders/DOFMipMapShader.js +63 -0
- package/Resources/WebLibraries/three/addons/shaders/DepthLimitedBlurShader.js +183 -0
- package/Resources/WebLibraries/three/addons/shaders/DigitalGlitch.js +104 -0
- package/Resources/WebLibraries/three/addons/shaders/DotScreenShader.js +75 -0
- package/Resources/WebLibraries/three/addons/shaders/ExposureShader.js +51 -0
- package/Resources/WebLibraries/three/addons/shaders/FXAAShader.js +298 -0
- package/Resources/WebLibraries/three/addons/shaders/FilmShader.js +73 -0
- package/Resources/WebLibraries/three/addons/shaders/FocusShader.js +94 -0
- package/Resources/WebLibraries/three/addons/shaders/FreiChenShader.js +103 -0
- package/Resources/WebLibraries/three/addons/shaders/GTAOShader.js +437 -0
- package/Resources/WebLibraries/three/addons/shaders/GammaCorrectionShader.js +52 -0
- package/Resources/WebLibraries/three/addons/shaders/HalftoneShader.js +332 -0
- package/Resources/WebLibraries/three/addons/shaders/HorizontalBlurShader.js +68 -0
- package/Resources/WebLibraries/three/addons/shaders/HorizontalTiltShiftShader.js +70 -0
- package/Resources/WebLibraries/three/addons/shaders/HueSaturationShader.js +74 -0
- package/Resources/WebLibraries/three/addons/shaders/KaleidoShader.js +65 -0
- package/Resources/WebLibraries/three/addons/shaders/LuminosityHighPassShader.js +68 -0
- package/Resources/WebLibraries/three/addons/shaders/LuminosityShader.js +54 -0
- package/Resources/WebLibraries/three/addons/shaders/MirrorShader.js +62 -0
- package/Resources/WebLibraries/three/addons/shaders/NormalMapShader.js +60 -0
- package/Resources/WebLibraries/three/addons/shaders/OutputShader.js +103 -0
- package/Resources/WebLibraries/three/addons/shaders/PoissonDenoiseShader.js +230 -0
- package/Resources/WebLibraries/three/addons/shaders/RGBShiftShader.js +61 -0
- package/Resources/WebLibraries/three/addons/shaders/SAOShader.js +201 -0
- package/Resources/WebLibraries/three/addons/shaders/SMAAShader.js +489 -0
- package/Resources/WebLibraries/three/addons/shaders/SSAOShader.js +331 -0
- package/Resources/WebLibraries/three/addons/shaders/SSRShader.js +396 -0
- package/Resources/WebLibraries/three/addons/shaders/SepiaShader.js +57 -0
- package/Resources/WebLibraries/three/addons/shaders/SobelOperatorShader.js +98 -0
- package/Resources/WebLibraries/three/addons/shaders/SubsurfaceScatteringShader.js +95 -0
- package/Resources/WebLibraries/three/addons/shaders/TechnicolorShader.js +51 -0
- package/Resources/WebLibraries/three/addons/shaders/ToonShader.js +349 -0
- package/Resources/WebLibraries/three/addons/shaders/TriangleBlurShader.js +79 -0
- package/Resources/WebLibraries/three/addons/shaders/UnpackDepthRGBAShader.js +60 -0
- package/Resources/WebLibraries/three/addons/shaders/VelocityShader.js +137 -0
- package/Resources/WebLibraries/three/addons/shaders/VerticalBlurShader.js +66 -0
- package/Resources/WebLibraries/three/addons/shaders/VerticalTiltShiftShader.js +70 -0
- package/Resources/WebLibraries/three/addons/shaders/VignetteShader.js +56 -0
- package/Resources/WebLibraries/three/addons/shaders/VolumeShader.js +285 -0
- package/Resources/WebLibraries/three/addons/shaders/WaterRefractionShader.js +106 -0
- package/Resources/WebLibraries/three/addons/textures/FlakesTexture.js +54 -0
- package/Resources/WebLibraries/three/addons/tsl/WebGLNodesHandler.js +607 -0
- package/Resources/WebLibraries/three/addons/tsl/display/AfterImageNode.js +256 -0
- package/Resources/WebLibraries/three/addons/tsl/display/AnaglyphPassNode.js +550 -0
- package/Resources/WebLibraries/three/addons/tsl/display/BilateralBlurNode.js +377 -0
- package/Resources/WebLibraries/three/addons/tsl/display/BleachBypass.js +33 -0
- package/Resources/WebLibraries/three/addons/tsl/display/BloomNode.js +599 -0
- package/Resources/WebLibraries/three/addons/tsl/display/CRT.js +150 -0
- package/Resources/WebLibraries/three/addons/tsl/display/ChromaticAberrationNode.js +163 -0
- package/Resources/WebLibraries/three/addons/tsl/display/DenoiseNode.js +354 -0
- package/Resources/WebLibraries/three/addons/tsl/display/DepthOfFieldNode.js +572 -0
- package/Resources/WebLibraries/three/addons/tsl/display/DotScreenNode.js +104 -0
- package/Resources/WebLibraries/three/addons/tsl/display/FSR1Node.js +481 -0
- package/Resources/WebLibraries/three/addons/tsl/display/FXAANode.js +357 -0
- package/Resources/WebLibraries/three/addons/tsl/display/FilmNode.js +101 -0
- package/Resources/WebLibraries/three/addons/tsl/display/GTAONode.js +727 -0
- package/Resources/WebLibraries/three/addons/tsl/display/GaussianBlurNode.js +412 -0
- package/Resources/WebLibraries/three/addons/tsl/display/GodraysNode.js +604 -0
- package/Resources/WebLibraries/three/addons/tsl/display/ImportanceSampledEnvironment.js +564 -0
- package/Resources/WebLibraries/three/addons/tsl/display/LensflareNode.js +282 -0
- package/Resources/WebLibraries/three/addons/tsl/display/Lut3DNode.js +109 -0
- package/Resources/WebLibraries/three/addons/tsl/display/MotionBlur.js +33 -0
- package/Resources/WebLibraries/three/addons/tsl/display/OITPassNode.js +341 -0
- package/Resources/WebLibraries/three/addons/tsl/display/OutlineNode.js +916 -0
- package/Resources/WebLibraries/three/addons/tsl/display/ParallaxBarrierPassNode.js +90 -0
- package/Resources/WebLibraries/three/addons/tsl/display/PixelationPassNode.js +227 -0
- package/Resources/WebLibraries/three/addons/tsl/display/RGBShiftNode.js +96 -0
- package/Resources/WebLibraries/three/addons/tsl/display/RecurrentDenoiseNode.js +812 -0
- package/Resources/WebLibraries/three/addons/tsl/display/RetroPassNode.js +263 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SMAANode.js +729 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SSAAPassNode.js +344 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SSAONode.js +422 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SSGINode.js +698 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SSRNode.js +1430 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SSSNode.js +479 -0
- package/Resources/WebLibraries/three/addons/tsl/display/Sepia.js +24 -0
- package/Resources/WebLibraries/three/addons/tsl/display/Shape.js +29 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SharpenNode.js +284 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SobelOperatorNode.js +168 -0
- package/Resources/WebLibraries/three/addons/tsl/display/StereoCompositePassNode.js +190 -0
- package/Resources/WebLibraries/three/addons/tsl/display/StereoPassNode.js +117 -0
- package/Resources/WebLibraries/three/addons/tsl/display/TAAUNode.js +700 -0
- package/Resources/WebLibraries/three/addons/tsl/display/TRAANode.js +609 -0
- package/Resources/WebLibraries/three/addons/tsl/display/TemporalReprojectNode.js +1024 -0
- package/Resources/WebLibraries/three/addons/tsl/display/TransitionNode.js +141 -0
- package/Resources/WebLibraries/three/addons/tsl/display/boxBlur.js +65 -0
- package/Resources/WebLibraries/three/addons/tsl/display/depthAwareBlend.js +80 -0
- package/Resources/WebLibraries/three/addons/tsl/display/depthAwareBlur.js +63 -0
- package/Resources/WebLibraries/three/addons/tsl/display/hashBlur.js +55 -0
- package/Resources/WebLibraries/three/addons/tsl/display/lensDirt.js +30 -0
- package/Resources/WebLibraries/three/addons/tsl/display/radialBlur.js +68 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/ClusteredLightsNode.js +994 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/DynamicLightsNode.js +201 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/LightProbeGridNode.js +183 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/data/AmbientLightDataNode.js +67 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/data/DirectionalLightDataNode.js +117 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/data/HemisphereLightDataNode.js +105 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/data/PointLightDataNode.js +140 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/data/SpotLightDataNode.js +167 -0
- package/Resources/WebLibraries/three/addons/tsl/math/Bayer.js +84 -0
- package/Resources/WebLibraries/three/addons/tsl/math/curlNoise.js +107 -0
- package/Resources/WebLibraries/three/addons/tsl/math/voronoiNoise.js +112 -0
- package/Resources/WebLibraries/three/addons/tsl/shadows/TileShadowNode.js +542 -0
- package/Resources/WebLibraries/three/addons/tsl/shadows/TileShadowNodeHelper.js +212 -0
- package/Resources/WebLibraries/three/addons/tsl/utils/GroundedSkybox.js +62 -0
- package/Resources/WebLibraries/three/addons/tsl/utils/RNoise.js +51 -0
- package/Resources/WebLibraries/three/addons/tsl/utils/Raymarching.js +70 -0
- package/Resources/WebLibraries/three/addons/tsl/utils/SoftParticles.js +53 -0
- package/Resources/WebLibraries/three/addons/tsl/utils/SpecularHelpers.js +302 -0
- package/Resources/WebLibraries/three/addons/tsl/utils/TAAUtils.js +184 -0
- package/Resources/WebLibraries/three/addons/utils/BufferGeometryUtils.js +1523 -0
- package/Resources/WebLibraries/three/addons/utils/CameraUtils.js +82 -0
- package/Resources/WebLibraries/three/addons/utils/ColorUtils.js +76 -0
- package/Resources/WebLibraries/three/addons/utils/GaussianSplatUtils.js +229 -0
- package/Resources/WebLibraries/three/addons/utils/GeometryCompressionUtils.js +547 -0
- package/Resources/WebLibraries/three/addons/utils/GeometryUtils.js +226 -0
- package/Resources/WebLibraries/three/addons/utils/LDrawUtils.js +211 -0
- package/Resources/WebLibraries/three/addons/utils/SceneOptimizer.js +459 -0
- package/Resources/WebLibraries/three/addons/utils/SceneUtils.js +363 -0
- package/Resources/WebLibraries/three/addons/utils/ShadowMapViewer.js +244 -0
- package/Resources/WebLibraries/three/addons/utils/ShadowMapViewerGPU.js +233 -0
- package/Resources/WebLibraries/three/addons/utils/SkeletonUtils.js +496 -0
- package/Resources/WebLibraries/three/addons/utils/SortUtils.js +175 -0
- package/Resources/WebLibraries/three/addons/utils/UVsDebug.js +173 -0
- package/Resources/WebLibraries/three/addons/utils/WebGLTextureUtils.js +115 -0
- package/Resources/WebLibraries/three/addons/utils/WebGPUTextureUtils.js +81 -0
- package/Resources/WebLibraries/three/addons/utils/WorkerPool.js +167 -0
- package/Resources/WebLibraries/three/addons/utils/unwrapUVs.js +865 -0
- package/Resources/WebLibraries/three/three.core.js +61162 -0
- package/Resources/WebLibraries/three/three.module.js +19537 -0
- package/Resources/WebLibraries/three/three.tsl.js +41903 -0
- package/Resources/WebLibraries/three/three.webgpu.js +53714 -0
- package/Resources/skills/2d-game/SKILL.md +305 -0
- package/Resources/skills/3d-game/SKILL.md +274 -0
- package/Resources/skills/accounts/SKILL.md +41 -0
- package/Resources/skills/android-design/SKILL.md +159 -0
- package/Resources/skills/app-ai/SKILL.md +154 -0
- package/Resources/skills/app-intents/SKILL.md +31 -0
- package/Resources/skills/app-kinds/SKILL.md +122 -0
- package/Resources/skills/apple-watch/SKILL.md +43 -0
- package/Resources/skills/assets/SKILL.md +183 -0
- package/Resources/skills/building-blocks/SKILL.md +97 -0
- package/Resources/skills/device-control/SKILL.md +56 -0
- package/Resources/skills/device-integrations/SKILL.md +278 -0
- package/Resources/skills/finder-files/SKILL.md +19 -0
- package/Resources/skills/game/SKILL.md +414 -0
- package/Resources/skills/game-3d/SKILL.md +12 -0
- package/Resources/skills/game-art/SKILL.md +11 -0
- package/Resources/skills/game-feel/SKILL.md +13 -0
- package/Resources/skills/game-levels/SKILL.md +12 -0
- package/Resources/skills/game-live/SKILL.md +12 -0
- package/Resources/skills/game-physics/SKILL.md +13 -0
- package/Resources/skills/game-playtest/SKILL.md +26 -0
- package/Resources/skills/game-ui/SKILL.md +278 -0
- package/Resources/skills/github-projects/SKILL.md +59 -0
- package/Resources/skills/icloud-sync/SKILL.md +30 -0
- package/Resources/skills/iphone-hardware/SKILL.md +57 -0
- package/Resources/skills/iphone-widgets/SKILL.md +53 -0
- package/Resources/skills/lg-developer-mode/SKILL.md +152 -0
- package/Resources/skills/mac-widgets/SKILL.md +60 -0
- package/Resources/skills/maps/SKILL.md +8 -0
- package/Resources/skills/menu-bar/SKILL.md +36 -0
- package/Resources/skills/more-apps/SKILL.md +53 -0
- package/Resources/skills/phone-controllers/SKILL.md +79 -0
- package/Resources/skills/phone-pages/SKILL.md +173 -0
- package/Resources/skills/roku-developer-mode/SKILL.md +106 -0
- package/Resources/skills/scene-3d/SKILL.md +27 -0
- package/Resources/skills/scripting/SKILL.md +17 -0
- package/Resources/skills/share-extension/SKILL.md +22 -0
- package/Resources/skills/tv-design/SKILL.md +72 -0
- package/Resources/skills/web-app/SKILL.md +136 -0
- package/Resources/skills/web-game/SKILL.md +95 -0
- package/Resources/skills/web-or-native/SKILL.md +103 -0
- package/Resources/skills/wish-links/SKILL.md +156 -0
- package/THIRD_PARTY_NOTICES.md +170 -0
- package/cli/android.mjs +941 -0
- package/cli/browser.mjs +268 -0
- package/cli/calls.mjs +390 -0
- package/cli/dev.mjs +773 -0
- package/cli/devices.mjs +984 -0
- package/cli/lamp.mjs +1016 -0
- package/cli/lampapp.mjs +332 -0
- package/cli/lamps/android-host/LampPageGateway.kt +345 -0
- package/cli/lamps/android-host/app/build.gradle.kts +40 -0
- package/cli/lamps/android-host/build.gradle.kts +4 -0
- package/cli/lamps/android-host/game/AndroidManifest.xml +33 -0
- package/cli/lamps/android-host/game/MainActivity.kt +173 -0
- package/cli/lamps/android-host/gradle/libs.versions.toml +39 -0
- package/cli/lamps/android-host/gradle.properties +8 -0
- package/cli/lamps/android-host/page/AndroidManifest.xml +35 -0
- package/cli/lamps/android-host/page/MainActivity.kt +193 -0
- package/cli/lamps/android-host/settings.gradle.kts +30 -0
- package/cli/lamps/android-host/tools.json +43 -0
- package/cli/lamps/lamp-controllers.js +2 -0
- package/cli/lamps/lamp-for-games.js +6863 -0
- package/cli/lamps/lamp.js +7023 -0
- package/cli/lamps/mac-starter/.lamp/meta.json +1 -0
- package/cli/lamps/mac-starter/APP.md +12 -0
- package/cli/lamps/mac-starter/Package.swift +8 -0
- package/cli/lamps/mac-starter/Sources/App.swift +11 -0
- package/cli/lamps/mac-starter/Sources/ContentView.swift +17 -0
- package/cli/lamps/mac-starter/Sources/LampSupport.swift +18097 -0
- package/cli/lamps/mac-starter/build.sh +336 -0
- package/cli/lamps/mac-starter/gitignore +3 -0
- package/cli/lamps/mac-starter/lamp-starter.json +6 -0
- package/cli/lamps/web-starter/APP.md +8 -0
- package/cli/lamps/web-starter/app.js +1 -0
- package/cli/lamps/web-starter/index.html +17 -0
- package/cli/lamps/web-starter/lamp-starter.json +5 -0
- package/cli/lamps/web-starter/lamp.js +7023 -0
- package/cli/lamps/web-starter/style.css +25 -0
- package/cli/link.mjs +138 -0
- package/cli/mac.mjs +621 -0
- package/cli/mdns.mjs +205 -0
- package/cli/msix.mjs +413 -0
- package/cli/network.mjs +209 -0
- package/cli/outside.mjs +140 -0
- package/cli/page.js +165 -0
- package/cli/place.mjs +298 -0
- package/cli/png.mjs +184 -0
- package/cli/share.mjs +202 -0
- package/cli/skill/SKILL.md +255 -0
- package/cli/skills.mjs +71 -0
- package/cli/state.mjs +173 -0
- package/cli/update.mjs +57 -0
- package/package.json +20 -4
- package/site/lib/quota.js +59 -0
- package/site/lib/shared-wishes.js +996 -0
package/cli/mdns.mjs
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
// Bonjour without a Bonjour library: mDNS questions asked from a port of lamp's own, which each device answers there and
|
|
2
|
+
// only there (a one-shot query, RFC 6762 6.7), so nothing listens on mDNS's own port, which is this computer's own
|
|
3
|
+
// responder's. Enough of the format to find a type's services, and every service on the network with its host, its
|
|
4
|
+
// address and the few words of its record that say what the device is.
|
|
5
|
+
|
|
6
|
+
import dgram from "node:dgram";
|
|
7
|
+
|
|
8
|
+
/** A name as records are kept by: its labels in small letters, a dot inside one escaped, so a service called "Mr. Lee's TV" stays one label. */
|
|
9
|
+
const keyOf = (labels) => labels.map((label) => label.toLowerCase().replaceAll("\\", "\\\\").replaceAll(".", "\\.")).join(".");
|
|
10
|
+
|
|
11
|
+
/** An mDNS name in a packet at `at`, its pointers followed, and where it ends there; null when it can't be read. */
|
|
12
|
+
function nameAt(packet, at) {
|
|
13
|
+
const labels = [];
|
|
14
|
+
let end = null;
|
|
15
|
+
for (let jumps = 0; ; ) {
|
|
16
|
+
if (at >= packet.length) return null;
|
|
17
|
+
const length = packet[at];
|
|
18
|
+
if (length === 0) return { labels, end: end ?? at + 1 };
|
|
19
|
+
if ((length & 0xc0) === 0xc0) {
|
|
20
|
+
if (at + 1 >= packet.length || ++jumps > 32) return null;
|
|
21
|
+
end ??= at + 2;
|
|
22
|
+
at = ((length & 0x3f) << 8) | packet[at + 1];
|
|
23
|
+
} else {
|
|
24
|
+
if (length > 63 || at + 1 + length > packet.length) return null;
|
|
25
|
+
labels.push(packet.toString("utf8", at + 1, at + 1 + length));
|
|
26
|
+
at += 1 + length;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* A TXT record's entries: each a length and "key=value", or a bare key, which is an empty value. The first value for a
|
|
33
|
+
* key wins, as the record format says.
|
|
34
|
+
*/
|
|
35
|
+
export function entriesOf(data) {
|
|
36
|
+
const entries = {};
|
|
37
|
+
for (let at = 0; at < data.length; ) {
|
|
38
|
+
const length = data[at];
|
|
39
|
+
at += 1;
|
|
40
|
+
if (at + length > data.length) break;
|
|
41
|
+
if (length === 0) continue;
|
|
42
|
+
const entry = data.subarray(at, at + length), equals = entry.indexOf(0x3d);
|
|
43
|
+
at += length;
|
|
44
|
+
const key = entry.toString("utf8", 0, equals < 0 ? entry.length : equals);
|
|
45
|
+
if (key && !(key in entries)) entries[key] = equals < 0 ? "" : entry.toString("utf8", equals + 1);
|
|
46
|
+
}
|
|
47
|
+
return entries;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Every record of an mDNS answer: its name (as `keyOf` keeps it, and its labels), and for a pointer, a service, an address or a TXT record what it says. */
|
|
51
|
+
export function recordsOf(packet) {
|
|
52
|
+
if (packet.length < 12) return [];
|
|
53
|
+
const count = (index) => packet.readUInt16BE(4 + index * 2), records = [];
|
|
54
|
+
let at = 12;
|
|
55
|
+
for (let question = 0; question < count(0); question++) {
|
|
56
|
+
const name = nameAt(packet, at);
|
|
57
|
+
if (!name) return [];
|
|
58
|
+
at = name.end + 4;
|
|
59
|
+
}
|
|
60
|
+
for (let left = count(1) + count(2) + count(3); left > 0; left--) {
|
|
61
|
+
const name = nameAt(packet, at);
|
|
62
|
+
if (!name || name.end + 10 > packet.length) break;
|
|
63
|
+
const type = packet.readUInt16BE(name.end), length = packet.readUInt16BE(name.end + 8), data = name.end + 10;
|
|
64
|
+
if (data + length > packet.length) break;
|
|
65
|
+
const record = { name: keyOf(name.labels), labels: name.labels, type };
|
|
66
|
+
if (type === 12) record.target = nameAt(packet, data)?.labels;
|
|
67
|
+
else if (type === 33 && length >= 6) { record.port = packet.readUInt16BE(data + 4); record.target = nameAt(packet, data + 6)?.labels; }
|
|
68
|
+
else if (type === 1 && length === 4) record.address = [...packet.subarray(data, data + 4)].join(".");
|
|
69
|
+
else if (type === 16) record.txt = entriesOf(packet.subarray(data, data + length));
|
|
70
|
+
records.push(record);
|
|
71
|
+
at = data + length;
|
|
72
|
+
}
|
|
73
|
+
return records;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The services of a type an mDNS answer offers, each by its name, at the address of the host it names when the
|
|
78
|
+
* answer gives one, else at the address the answer came from.
|
|
79
|
+
*/
|
|
80
|
+
export function servicesIn(packet, type, from) {
|
|
81
|
+
const wanted = keyOf(`${type}.local`.split(".")), records = recordsOf(packet), found = [];
|
|
82
|
+
for (const offered of records) {
|
|
83
|
+
if (offered.type !== 12 || offered.name !== wanted || !offered.target?.length) continue;
|
|
84
|
+
const instance = keyOf(offered.target);
|
|
85
|
+
const host = records.find((record) => record.type === 33 && record.name === instance)?.target;
|
|
86
|
+
const address = (host && records.find((record) => record.type === 1 && record.name === keyOf(host))?.address) || from;
|
|
87
|
+
found.push({ name: offered.target[0], address });
|
|
88
|
+
}
|
|
89
|
+
return found;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** A question for a name's records of one type, asking for the answer to come back here. */
|
|
93
|
+
function question(labels, type) {
|
|
94
|
+
const header = Buffer.alloc(12);
|
|
95
|
+
header.writeUInt16BE(1 + Math.floor(Math.random() * 0xfffe), 0);
|
|
96
|
+
header.writeUInt16BE(1, 4);
|
|
97
|
+
const name = labels.map((label) => { const bytes = Buffer.from(label); return Buffer.concat([Buffer.from([bytes.length]), bytes]); });
|
|
98
|
+
return Buffer.concat([header, ...name, Buffer.from([0, (type >> 8) & 0xff, type & 0xff, 0x80, 1])]);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const TYPE = /^_[A-Za-z0-9-]{1,40}\._(tcp|udp)$/;
|
|
102
|
+
const sleep = (ms) => new Promise((done) => setTimeout(done, ms));
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* A socket that asks questions and keeps every record any answer gives, each with the address it came from; `close`
|
|
106
|
+
* when done. Nothing is sent but the questions.
|
|
107
|
+
*/
|
|
108
|
+
async function asking() {
|
|
109
|
+
const socket = dgram.createSocket({ type: "udp4", reuseAddr: true }), records = [];
|
|
110
|
+
socket.on("error", () => {});
|
|
111
|
+
socket.on("message", (packet, from) => { for (const record of recordsOf(packet)) records.push({ ...record, from: from.address }); });
|
|
112
|
+
await new Promise((done) => socket.bind(0, done));
|
|
113
|
+
try { socket.setMulticastTTL(1); } catch {}
|
|
114
|
+
return {
|
|
115
|
+
records,
|
|
116
|
+
ask(labels, type) { socket.send(question(labels, type), 5353, "224.0.0.251", () => {}); },
|
|
117
|
+
close() { try { socket.close(); } catch {} },
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** The devices offering a Bonjour service of this type ("_airplay._tcp") on the home network, by name and address, asked twice. */
|
|
122
|
+
export async function bonjour(type, seconds = 3) {
|
|
123
|
+
if (!/^_[a-z0-9-]{1,40}\._(tcp|udp)$/.test(type)) return [];
|
|
124
|
+
const mdns = await asking(), labels = [...type.split("."), "local"], wanted = keyOf(labels);
|
|
125
|
+
try {
|
|
126
|
+
mdns.ask(labels, 12);
|
|
127
|
+
await sleep((seconds * 1000) / 2);
|
|
128
|
+
mdns.ask(labels, 12);
|
|
129
|
+
await sleep((seconds * 1000) / 2);
|
|
130
|
+
} finally { mdns.close(); }
|
|
131
|
+
const found = new Map();
|
|
132
|
+
for (const offered of mdns.records) {
|
|
133
|
+
if (offered.type !== 12 || offered.name !== wanted || !offered.target?.length) continue;
|
|
134
|
+
const host = mdns.records.find((record) => record.type === 33 && record.name === keyOf(offered.target))?.target;
|
|
135
|
+
const address = (host && mdns.records.find((record) => record.type === 1 && record.name === keyOf(host))?.address) || offered.from;
|
|
136
|
+
found.set(`${offered.target[0]} ${address}`, { name: offered.target[0], address });
|
|
137
|
+
}
|
|
138
|
+
return [...found.values()];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Browsed whether or not the network names them: a device that's asleep often misses that one answer, and these are
|
|
142
|
+
// the services that say most about what a quiet device is (a Cast device, a printer, a gadget's web page).
|
|
143
|
+
const ALWAYS = ["_googlecast._tcp", "_androidtvremote2._tcp", "_ipp._tcp", "_printer._tcp", "_http._tcp", "_esphomelib._tcp"];
|
|
144
|
+
|
|
145
|
+
// The words of a service's record kept: those that say what the device is, and nothing that says more of it or its person.
|
|
146
|
+
const MODEL_KEYS = new Set(["md", "model", "am", "ty", "usb_MFG", "usb_MDL", "product", "manufacturer", "fn"]);
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Every Bonjour service on the network: the types it offers (`_services._dns-sd._udp`), the services of each, each
|
|
150
|
+
* one's host and record, and each host's addresses, a round of questions at a time, each asked twice in its round.
|
|
151
|
+
* Each service comes with its type, its name, its host ("Kitchen-Speaker.local."), the words of its record that say
|
|
152
|
+
* what the device is, and its addresses.
|
|
153
|
+
*/
|
|
154
|
+
export async function everything({ seconds = 3 } = {}) {
|
|
155
|
+
const mdns = await asking(), round = (seconds * 1000) / 3;
|
|
156
|
+
const twice = async (asked) => { for (const [labels, type] of asked) mdns.ask(labels, type); await sleep(round / 2); for (const [labels, type] of asked) mdns.ask(labels, type); await sleep(round / 2); };
|
|
157
|
+
const targets = (name, type = 12) => mdns.records.filter((record) => record.type === type && record.name === name && record.target?.length).map((record) => record.target);
|
|
158
|
+
try {
|
|
159
|
+
const meta = ["_services", "_dns-sd", "_udp", "local"];
|
|
160
|
+
await twice([[meta, 12]]);
|
|
161
|
+
// The network's answers name types as "_airplay._tcp.local".
|
|
162
|
+
const named = targets(keyOf(meta)).map((labels) => labels.slice(0, 2).join("."));
|
|
163
|
+
const types = [...new Set([...named, ...ALWAYS])].filter((type) => TYPE.test(type)).slice(0, 60);
|
|
164
|
+
await twice(types.map((type) => [[...type.split("."), "local"], 12]));
|
|
165
|
+
const services = [];
|
|
166
|
+
for (const type of types) {
|
|
167
|
+
const seen = new Set();
|
|
168
|
+
for (const labels of targets(keyOf([...type.split("."), "local"]))) {
|
|
169
|
+
if (seen.has(keyOf(labels)) || seen.size >= 40) continue;
|
|
170
|
+
seen.add(keyOf(labels));
|
|
171
|
+
services.push({ type, labels });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Each service's host and record, where the answers so far didn't give them.
|
|
175
|
+
const has = (labels, type) => mdns.records.some((record) => record.type === type && record.name === keyOf(labels));
|
|
176
|
+
await twice(services.flatMap(({ labels }) => [...(has(labels, 33) ? [] : [[labels, 33]]), ...(has(labels, 16) ? [] : [[labels, 16]])]));
|
|
177
|
+
// Each host's address, asked once for all its services: a device with many answered some and not others.
|
|
178
|
+
const hostOf = (labels) => targets(keyOf(labels), 33)[0];
|
|
179
|
+
const hosts = new Map();
|
|
180
|
+
for (const { labels } of services) { const host = hostOf(labels); if (host) hosts.set(keyOf(host), host); }
|
|
181
|
+
await twice([...hosts.values()].filter((host) => !has(host, 1)).map((host) => [host, 1]));
|
|
182
|
+
const found = [];
|
|
183
|
+
for (const { type, labels } of services) {
|
|
184
|
+
const host = hostOf(labels);
|
|
185
|
+
if (!host) continue;
|
|
186
|
+
const addresses = [...new Set(mdns.records.filter((record) => record.type === 1 && record.name === keyOf(host)).map((record) => record.address))];
|
|
187
|
+
if (!addresses.length) {
|
|
188
|
+
// A host whose address wasn't said is the one that said where its service is.
|
|
189
|
+
const from = mdns.records.find((record) => record.type === 33 && record.name === keyOf(labels))?.from;
|
|
190
|
+
if (from) addresses.push(from);
|
|
191
|
+
}
|
|
192
|
+
if (!addresses.length) continue;
|
|
193
|
+
const txt = {};
|
|
194
|
+
for (const [key, value] of Object.entries(mdns.records.find((record) => record.type === 16 && record.name === keyOf(labels))?.txt ?? {})) {
|
|
195
|
+
// A Cast device's "md" is its model, and its "fn" the name the person gave it; AirPlay's "md" is the kinds of
|
|
196
|
+
// metadata it takes.
|
|
197
|
+
if (!MODEL_KEYS.has(key) || ((key === "md" || key === "fn") && type !== "_googlecast._tcp")) continue;
|
|
198
|
+
const text = value.trim();
|
|
199
|
+
if (text && [...text].length <= 80 && !/[\u0000-\u001f\u007f-\u009f]/.test(text)) txt[key] = text;
|
|
200
|
+
}
|
|
201
|
+
found.push({ service: { type, name: labels[0], host: `${host.join(".")}.`, txt }, addresses });
|
|
202
|
+
}
|
|
203
|
+
return found;
|
|
204
|
+
} finally { mdns.close(); }
|
|
205
|
+
}
|
package/cli/msix.mjs
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
// Windows app packages (MSIX), made and signed by lamp itself: an Xbox in Developer Mode takes them through its Device
|
|
2
|
+
// Portal. This is Sources/Lamp/MSIX.swift in Node, and makes the same package from the same files (a test holds the
|
|
3
|
+
// two together, cli/tests/fixtures/msix): the manifest lamp writes, so the package's identity and publisher are Lamp's,
|
|
4
|
+
// the block map and content types, the files laid out as Zip64 (Windows opens nothing else), and an Authenticode
|
|
5
|
+
// signature with a key and a certificate of lamp's own, which Developer Mode accepts once the certificate is installed
|
|
6
|
+
// beside the package.
|
|
7
|
+
|
|
8
|
+
import { createHash, createPrivateKey, createPublicKey, generateKeyPairSync, randomBytes, sign as signWith } from "node:crypto";
|
|
9
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { deflateRawSync } from "node:zlib";
|
|
12
|
+
import { Problem } from "./state.mjs";
|
|
13
|
+
|
|
14
|
+
/** Lamp's publisher name; a package's manifest must name exactly its certificate's subject. */
|
|
15
|
+
export const PUBLISHER = "CN=Lamp";
|
|
16
|
+
|
|
17
|
+
const sha256 = (bytes) => createHash("sha256").update(bytes).digest();
|
|
18
|
+
|
|
19
|
+
/** Windows' short id for a publisher: the first 8 bytes of SHA-256 of its name in UTF-16LE, in Crockford's base 32. */
|
|
20
|
+
export function publisherID(publisher) {
|
|
21
|
+
const bits = sha256(Buffer.from(publisher, "utf16le")).readBigUInt64BE(0), alphabet = "0123456789abcdefghjkmnpqrstvwxyz";
|
|
22
|
+
let id = "";
|
|
23
|
+
// 64 bits as 13 digits of 5 bits: the last digit takes the lowest 4 bits shifted up one.
|
|
24
|
+
for (let index = 0; index < 13; index++) {
|
|
25
|
+
const shift = 64n - 5n * BigInt(index + 1);
|
|
26
|
+
id += alphabet[Number((shift >= 0n ? bits >> shift : bits << -shift) & 31n)];
|
|
27
|
+
}
|
|
28
|
+
return id;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** An identity name for an app: Windows takes letters, digits, dots and dashes, 3 to 50 of them. */
|
|
32
|
+
export function identityName(slug) {
|
|
33
|
+
const plain = [...slug].filter((c) => /[A-Za-z0-9-]/.test(c)).join("");
|
|
34
|
+
return "Lamp." + (plain || "app").slice(0, 40);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* A version newer than `previous`, so each build replaces the last (Windows refuses a changed package under a version
|
|
39
|
+
* it has): minutes since 2026 in two of the four parts, or the previous one's next revision when two builds fall in
|
|
40
|
+
* the same minute.
|
|
41
|
+
*/
|
|
42
|
+
export function versionAfter(previous, date = new Date()) {
|
|
43
|
+
const minutes = Math.max(0, Math.floor((date.getTime() / 1000 - 1_767_225_600) / 60));
|
|
44
|
+
const byTime = [1, minutes >> 16, minutes & 0xffff, 0];
|
|
45
|
+
const next = String(previous ?? "").split(".").map(Number);
|
|
46
|
+
const earlier = () => { for (let i = 0; i < 4; i++) if (next[i] !== byTime[i]) return next[i] < byTime[i]; return false; };
|
|
47
|
+
if (next.length !== 4 || next.some((n) => !Number.isInteger(n)) || earlier()) return byTime.join(".");
|
|
48
|
+
for (let index = 3; index >= 0; index--) {
|
|
49
|
+
if (next[index] < 0xffff) { next[index] += 1; break; }
|
|
50
|
+
next[index] = 0;
|
|
51
|
+
}
|
|
52
|
+
return next.join(".");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** The names Windows gives a package with this identity ({ name, publisher, version, architecture }): what it's opened and removed by. */
|
|
56
|
+
export function namesOf(identity) {
|
|
57
|
+
const family = `${identity.name}_${publisherID(identity.publisher)}`;
|
|
58
|
+
return {
|
|
59
|
+
fullName: `${identity.name}_${identity.version}_${identity.architecture ?? "neutral"}__${publisherID(identity.publisher)}`,
|
|
60
|
+
familyName: family,
|
|
61
|
+
appUserModelID: `${family}!App`,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// MARK: Making a package
|
|
66
|
+
|
|
67
|
+
// Lamp's web host for the Xbox (Resources/WebHost): the program a web app's package starts, and WebView2's own
|
|
68
|
+
// library beside it, whose classes the package registers so Windows finds them (`WebHost` in Lamp's app).
|
|
69
|
+
export const WEB_HOST = {
|
|
70
|
+
program: "App.exe",
|
|
71
|
+
library: "Microsoft.Web.WebView2.Core.dll",
|
|
72
|
+
folder: "Web",
|
|
73
|
+
classes: [
|
|
74
|
+
"Microsoft.Web.WebView2.Core.CoreWebView2Environment",
|
|
75
|
+
"Microsoft.Web.WebView2.Core.CoreWebView2EnvironmentOptions",
|
|
76
|
+
"Microsoft.Web.WebView2.Core.CoreWebView2ControllerWindowReference",
|
|
77
|
+
"Microsoft.Web.WebView2.Core.CoreWebView2CustomSchemeRegistration",
|
|
78
|
+
],
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const RESERVED = new Set(["appxmanifest.xml", "appxblockmap.xml", "[content_types].xml", "appxsignature.p7x"]);
|
|
82
|
+
export const LOGOS = [["StoreLogo", 50, 50], ["Square150x150Logo", 150, 150], ["Square44x44Logo", 44, 44], ["Wide310x150Logo", 310, 150], ["SplashScreen", 620, 300]];
|
|
83
|
+
|
|
84
|
+
const escaped = (text) => text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The manifest of a package whose app starts at `entry`: `{ page }` for a web app in the system's own host,
|
|
88
|
+
* `{ program }` for a native one, or "webHost" for a web app in Lamp's host.
|
|
89
|
+
*/
|
|
90
|
+
export function manifest(identity, displayName, entry) {
|
|
91
|
+
const name = escaped([...displayName].slice(0, 80).join(""));
|
|
92
|
+
let application, minimum, capabilities;
|
|
93
|
+
const network = '<Capabilities><Capability Name="internetClient"/><Capability Name="privateNetworkClientServer"/></Capabilities>';
|
|
94
|
+
if (entry === "webHost") {
|
|
95
|
+
// WebView2's classes come from the copy of its library in the package, which Windows finds only by these
|
|
96
|
+
// registrations; the browser itself is the console's.
|
|
97
|
+
application = `Executable="${WEB_HOST.program}" EntryPoint="App.App"`;
|
|
98
|
+
minimum = "10.0.19041.0";
|
|
99
|
+
const classes = WEB_HOST.classes.map((id) => `<ActivatableClass ActivatableClassId="${id}" ThreadingModel="both"/>`).join("");
|
|
100
|
+
capabilities = network + `<Extensions><Extension Category="windows.activatableClass.inProcessServer"><InProcessServer><Path>${WEB_HOST.library}</Path>${classes}</InProcessServer></Extension></Extensions>`;
|
|
101
|
+
} else if (entry.page) {
|
|
102
|
+
application = `StartPage="${escaped(entry.page)}"`;
|
|
103
|
+
minimum = "10.0.10240.0";
|
|
104
|
+
capabilities = network;
|
|
105
|
+
} else {
|
|
106
|
+
application = `Executable="${escaped(entry.program.replaceAll("/", "\\"))}" EntryPoint="App.App"`;
|
|
107
|
+
minimum = "10.0.17763.0";
|
|
108
|
+
capabilities = "";
|
|
109
|
+
}
|
|
110
|
+
return `<?xml version="1.0" encoding="utf-8"?>
|
|
111
|
+
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" IgnorableNamespaces="uap">
|
|
112
|
+
<Identity Name="${identity.name}" Publisher="${escaped(identity.publisher)}" Version="${identity.version}" ProcessorArchitecture="${identity.architecture ?? "neutral"}"/>
|
|
113
|
+
<Properties><DisplayName>${name}</DisplayName><PublisherDisplayName>Lamp</PublisherDisplayName><Logo>LampLogos\\StoreLogo.png</Logo></Properties>
|
|
114
|
+
<Dependencies><TargetDeviceFamily Name="Windows.Universal" MinVersion="${minimum}" MaxVersionTested="10.0.22621.0"/></Dependencies>
|
|
115
|
+
<Resources><Resource Language="en-us"/></Resources>
|
|
116
|
+
<Applications>
|
|
117
|
+
<Application Id="App" ${application}>
|
|
118
|
+
<uap:VisualElements DisplayName="${name}" Description="${name}" BackgroundColor="#1b1b3a" Square150x150Logo="LampLogos\\Square150x150Logo.png" Square44x44Logo="LampLogos\\Square44x44Logo.png">
|
|
119
|
+
<uap:DefaultTile Wide310x150Logo="LampLogos\\Wide310x150Logo.png"/>
|
|
120
|
+
<uap:SplashScreen Image="LampLogos\\SplashScreen.png"/>
|
|
121
|
+
</uap:VisualElements>
|
|
122
|
+
</Application>
|
|
123
|
+
</Applications>
|
|
124
|
+
${capabilities}
|
|
125
|
+
</Package>
|
|
126
|
+
`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* A signed package of the app's files (`[path, bytes]`), started at `entry`, with `logo(width, height)` drawing each
|
|
131
|
+
* of the pictures Windows shows it by.
|
|
132
|
+
*/
|
|
133
|
+
export function packageOf({ payload, displayName, identity, entry, logo, signer }) {
|
|
134
|
+
const start = entry === "webHost" ? WEB_HOST.program : entry.page ?? entry.program;
|
|
135
|
+
if (!payload.some(([path]) => path === start)) throw new Problem(`the package has no ${start} for the app to start at`);
|
|
136
|
+
const files = payload.filter(([path]) => !RESERVED.has(path.toLowerCase()) && !path.toLowerCase().startsWith("lamplogos/"));
|
|
137
|
+
for (const [name, width, height] of LOGOS) files.push([`LampLogos/${name}.png`, logo(width, height)]);
|
|
138
|
+
files.push(["AppxManifest.xml", Buffer.from(manifest(identity, displayName, entry))]);
|
|
139
|
+
return assemble(files, signer);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// MARK: The container
|
|
143
|
+
|
|
144
|
+
const CRC = Array.from({ length: 256 }, (_, n) => { let c = n; for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1; return c >>> 0; });
|
|
145
|
+
function crc32(bytes) {
|
|
146
|
+
let c = 0xffffffff;
|
|
147
|
+
for (let i = 0; i < bytes.length; i++) c = CRC[(c ^ bytes[i]) & 0xff] ^ (c >>> 8);
|
|
148
|
+
return (c ^ 0xffffffff) >>> 0;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Little-endian fields appended one after another. */
|
|
152
|
+
class Writer {
|
|
153
|
+
parts = [];
|
|
154
|
+
length = 0;
|
|
155
|
+
put(bytes) { this.parts.push(bytes); this.length += bytes.length; return this; }
|
|
156
|
+
u16(...values) { for (const v of values) { const b = Buffer.alloc(2); b.writeUInt16LE(v & 0xffff); this.put(b); } return this; }
|
|
157
|
+
u32(...values) { for (const v of values) { const b = Buffer.alloc(4); b.writeUInt32LE(v >>> 0); this.put(b); } return this; }
|
|
158
|
+
u64(...values) { for (const v of values) { const b = Buffer.alloc(8); b.writeBigUInt64LE(BigInt(v)); this.put(b); } return this; }
|
|
159
|
+
get data() { return Buffer.concat(this.parts); }
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Local records and the central directory (with its Zip64 end records), each Zip64 throughout. */
|
|
163
|
+
function layout(entries) {
|
|
164
|
+
const records = new Writer(), central = new Writer();
|
|
165
|
+
for (const [path, data] of entries) {
|
|
166
|
+
const name = Buffer.from(path, "utf8"), crc = crc32(data), offset = records.length, size = data.length;
|
|
167
|
+
// The signature is the one plain, deflated record, as Windows' own packager writes it: it opens nothing else.
|
|
168
|
+
if (path === "AppxSignature.p7x") {
|
|
169
|
+
const deflated = deflateRawSync(data);
|
|
170
|
+
records.u32(0x04034b50).u16(20, 0, 8, 0, 0x21).u32(crc, deflated.length, size).u16(name.length, 0).put(name).put(deflated);
|
|
171
|
+
central.u32(0x02014b50).u16(45, 20, 0, 8, 0, 0x21).u32(crc, deflated.length, size).u16(name.length, 0, 0, 0, 0).u32(0, offset).put(name);
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
// Local header: Zip64, stored, sizes in the extra field.
|
|
175
|
+
records.u32(0x04034b50).u16(45, 0, 0, 0, 0x21).u32(crc, 0xffffffff, 0xffffffff).u16(name.length, 20).put(name);
|
|
176
|
+
records.u16(1, 16).u64(size, size).put(data);
|
|
177
|
+
// Its central entry, with the offset in the extra field too.
|
|
178
|
+
central.u32(0x02014b50).u16(45, 45, 0, 0, 0, 0x21).u32(crc, 0xffffffff, 0xffffffff).u16(name.length, 28, 0, 0, 0).u32(0, 0xffffffff).put(name);
|
|
179
|
+
central.u16(1, 24).u64(size, size, offset);
|
|
180
|
+
}
|
|
181
|
+
const end = new Writer();
|
|
182
|
+
end.u32(0x06064b50).u64(44).u16(45, 45).u32(0, 0).u64(entries.length, entries.length, central.length, records.length);
|
|
183
|
+
end.u32(0x07064b50, 0).u64(records.length + central.length).u32(1);
|
|
184
|
+
// The disk numbers are written as zeros: the signature is checked over them as zeros.
|
|
185
|
+
end.u32(0x06054b50).u16(0, 0, 0xffff, 0xffff).u32(0xffffffff, 0xffffffff).u16(0);
|
|
186
|
+
return { records: records.data, directory: Buffer.concat([central.data, end.data]) };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const lfhSize = (name) => 30 + Buffer.byteLength(name, "utf8") + 20;
|
|
190
|
+
|
|
191
|
+
function blockMap(entries) {
|
|
192
|
+
const files = entries.map(([path, data]) => {
|
|
193
|
+
let blocks = "";
|
|
194
|
+
for (let start = 0; start < data.length; start += 65536) blocks += `<Block Hash="${sha256(data.subarray(start, start + 65536)).toString("base64")}"/>`;
|
|
195
|
+
return `<File Name="${path.replaceAll("/", "\\").replaceAll("&", "&")}" Size="${data.length}" LfhSize="${lfhSize(path)}">${blocks}</File>`;
|
|
196
|
+
}).join("");
|
|
197
|
+
return Buffer.from('<?xml version="1.0" encoding="UTF-8"?><BlockMap xmlns="http://schemas.microsoft.com/appx/2010/blockmap" '
|
|
198
|
+
+ 'HashMethod="http://www.w3.org/2001/04/xmlenc#sha256">' + files + "</BlockMap>");
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const KNOWN_TYPES = {
|
|
202
|
+
exe: "application/x-msdownload", dll: "application/x-msdownload", png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg", gif: "image/gif",
|
|
203
|
+
svg: "image/svg+xml", html: "text/html", htm: "text/html", js: "application/javascript", css: "text/css", json: "application/json",
|
|
204
|
+
txt: "text/plain", woff: "font/woff", woff2: "font/woff2", mp3: "audio/mpeg", mp4: "video/mp4", wav: "audio/wav",
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// A part's name as its content type names it: what a path may hold as it is, the rest by its bytes.
|
|
208
|
+
const partName = (path) => [...Buffer.from(path, "utf8")]
|
|
209
|
+
.map((byte) => (/[A-Za-z0-9!$&'()*+,\-./:=@_~]/.test(String.fromCharCode(byte)) && byte < 128 ? String.fromCharCode(byte) : "%" + byte.toString(16).toUpperCase().padStart(2, "0")))
|
|
210
|
+
.join("");
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Every part needs a content type: by its extension, or, for a file with none Windows can take (a LICENSE), one of
|
|
214
|
+
* its own. Windows refuses a package with an untyped part, saying its block map is invalid.
|
|
215
|
+
*/
|
|
216
|
+
export function contentTypes(paths) {
|
|
217
|
+
const extensions = new Set(), untyped = [];
|
|
218
|
+
for (const path of paths) {
|
|
219
|
+
if (path === "AppxManifest.xml") continue;
|
|
220
|
+
const parts = path.split("/").filter(Boolean).pop()?.split(".") ?? [""], last = parts[parts.length - 1];
|
|
221
|
+
if (parts.length > 1 && last && /^[A-Za-z0-9]+$/.test(last)) extensions.add(last.toLowerCase());
|
|
222
|
+
else untyped.push(path);
|
|
223
|
+
}
|
|
224
|
+
const defaults = [...extensions].sort().map((ext) => `<Default Extension="${ext}" ContentType="${KNOWN_TYPES[ext] ?? "application/octet-stream"}"/>`).join("");
|
|
225
|
+
const overrides = untyped.sort().map((path) => `<Override PartName="/${partName(path).replaceAll("&", "&")}" ContentType="application/octet-stream"/>`).join("");
|
|
226
|
+
return Buffer.from('<?xml version="1.0" encoding="UTF-8"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">'
|
|
227
|
+
+ defaults + overrides
|
|
228
|
+
+ '<Override PartName="/AppxManifest.xml" ContentType="application/vnd.ms-appx.manifest+xml"/>'
|
|
229
|
+
+ '<Override PartName="/AppxBlockMap.xml" ContentType="application/vnd.ms-appx.blockmap+xml"/>'
|
|
230
|
+
+ '<Override PartName="/AppxSignature.p7x" ContentType="application/vnd.ms-appx.signature"/></Types>');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* The files laid out as an MSIX: each stored (so the block map's hashes are of what's in the zip), the block map and
|
|
235
|
+
* content types after them, then the signature, then a Zip64 central directory.
|
|
236
|
+
*/
|
|
237
|
+
export function assemble(files, signer) {
|
|
238
|
+
const entries = files.map(([path, data]) => [path, data]);
|
|
239
|
+
entries.push(["AppxBlockMap.xml", blockMap(entries)]);
|
|
240
|
+
entries.push(["[Content_Types].xml", contentTypes(files.map(([path]) => path))]);
|
|
241
|
+
// What's signed is the package without its signature: its records, its directory and the two files' contents.
|
|
242
|
+
const unsigned = layout(entries);
|
|
243
|
+
const blob = Buffer.concat([
|
|
244
|
+
Buffer.from("APPX"),
|
|
245
|
+
Buffer.from("AXPC"), sha256(unsigned.records),
|
|
246
|
+
Buffer.from("AXCD"), sha256(unsigned.directory),
|
|
247
|
+
Buffer.from("AXCT"), sha256(entries[entries.length - 1][1]),
|
|
248
|
+
Buffer.from("AXBM"), sha256(entries[entries.length - 2][1]),
|
|
249
|
+
]);
|
|
250
|
+
entries.push(["AppxSignature.p7x", Buffer.concat([Buffer.from("PKCX"), sign(signer, blob)])]);
|
|
251
|
+
const signed = layout(entries);
|
|
252
|
+
return Buffer.concat([signed.records, signed.directory]);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// MARK: Signing
|
|
256
|
+
|
|
257
|
+
const der = {
|
|
258
|
+
tlv(tag, content) {
|
|
259
|
+
const n = content.length, head = [tag];
|
|
260
|
+
if (n < 0x80) head.push(n);
|
|
261
|
+
else {
|
|
262
|
+
const bytes = [];
|
|
263
|
+
for (let v = n; v > 0; v = Math.floor(v / 256)) bytes.unshift(v & 0xff);
|
|
264
|
+
head.push(0x80 | bytes.length, ...bytes);
|
|
265
|
+
}
|
|
266
|
+
return Buffer.concat([Buffer.from(head), content]);
|
|
267
|
+
},
|
|
268
|
+
sequence: (parts) => der.tlv(0x30, Buffer.concat(parts)),
|
|
269
|
+
set: (parts) => der.tlv(0x31, Buffer.concat(parts)),
|
|
270
|
+
null: Buffer.from([0x05, 0x00]),
|
|
271
|
+
integer(value) {
|
|
272
|
+
const bytes = [];
|
|
273
|
+
let v = value;
|
|
274
|
+
do { bytes.unshift(v & 0xff); v = Math.floor(v / 256); } while (v > 0);
|
|
275
|
+
if (bytes[0] & 0x80) bytes.unshift(0);
|
|
276
|
+
return der.tlv(0x02, Buffer.from(bytes));
|
|
277
|
+
},
|
|
278
|
+
/** A dotted object identifier. */
|
|
279
|
+
oid(dotted) {
|
|
280
|
+
const parts = dotted.split(".").map(Number), bytes = [parts[0] * 40 + parts[1]];
|
|
281
|
+
for (const part of parts.slice(2)) {
|
|
282
|
+
const chunk = [part & 0x7f];
|
|
283
|
+
for (let v = Math.floor(part / 128); v > 0; v = Math.floor(v / 128)) chunk.unshift((v & 0x7f) | 0x80);
|
|
284
|
+
bytes.push(...chunk);
|
|
285
|
+
}
|
|
286
|
+
return der.tlv(0x06, Buffer.from(bytes));
|
|
287
|
+
},
|
|
288
|
+
/** An element's contents without its tag and length. */
|
|
289
|
+
contents(element) {
|
|
290
|
+
if (element.length < 2) return Buffer.alloc(0);
|
|
291
|
+
return element.subarray(Math.min(element[1] & 0x80 ? 2 + (element[1] & 0x7f) : 2, element.length));
|
|
292
|
+
},
|
|
293
|
+
/** The elements inside one. */
|
|
294
|
+
children(element) {
|
|
295
|
+
const header = (at) => (at + 1 < element.length ? (element[at + 1] & 0x80 ? 2 + (element[at + 1] & 0x7f) : 2) : 0);
|
|
296
|
+
const length = (at) => {
|
|
297
|
+
const first = element[at + 1];
|
|
298
|
+
if (!(first & 0x80)) return first;
|
|
299
|
+
let n = 0;
|
|
300
|
+
for (let i = 0; i < (first & 0x7f); i++) n = n * 256 + (element[at + 2 + i] ?? 0);
|
|
301
|
+
return n;
|
|
302
|
+
};
|
|
303
|
+
const out = [];
|
|
304
|
+
for (let at = header(0); at < element.length;) {
|
|
305
|
+
const head = header(at), size = length(at);
|
|
306
|
+
if (!head || at + head + size > element.length) break;
|
|
307
|
+
out.push(element.subarray(at, at + head + size));
|
|
308
|
+
at += head + size;
|
|
309
|
+
}
|
|
310
|
+
return out;
|
|
311
|
+
},
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const OID = {
|
|
315
|
+
rsa: "1.2.840.113549.1.1.1", sha256WithRSA: "1.2.840.113549.1.1.11", sha256: "2.16.840.1.101.3.4.2.1", commonName: "2.5.4.3",
|
|
316
|
+
signedData: "1.2.840.113549.1.7.2", contentType: "1.2.840.113549.1.9.3", messageDigest: "1.2.840.113549.1.9.4",
|
|
317
|
+
indirectData: "1.3.6.1.4.1.311.2.1.4", sipInfo: "1.3.6.1.4.1.311.2.1.30", statementType: "1.3.6.1.4.1.311.2.1.11",
|
|
318
|
+
individualCodeSigning: "1.3.6.1.4.1.311.2.1.21", opusInfo: "1.3.6.1.4.1.311.2.1.12",
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
// The APPX signature's SIP GUID, as Windows lays it out in an SpcSipInfo.
|
|
322
|
+
const APPX_SIP = Buffer.from([0x4b, 0xdf, 0xc5, 0x0a, 0x07, 0xce, 0xe2, 0x4d, 0xb7, 0x6e, 0x23, 0xc8, 0x39, 0xa0, 0x9f, 0xd1]);
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Signs the APPX digest blob as Authenticode does: the blob is the "digest" in an SpcIndirectDataContent, whose own
|
|
326
|
+
* SHA-256 (of its contents, without its outer header) is the signed message digest.
|
|
327
|
+
*/
|
|
328
|
+
function sign(signer, blob) {
|
|
329
|
+
const zero = der.integer(0);
|
|
330
|
+
const sipInfo = der.sequence([der.integer(0x01010000), der.tlv(0x04, APPX_SIP), zero, zero, zero, zero, zero]);
|
|
331
|
+
const digestAlgorithm = der.sequence([der.oid(OID.sha256), der.null]);
|
|
332
|
+
const indirect = der.sequence([
|
|
333
|
+
der.sequence([der.oid(OID.sipInfo), sipInfo]),
|
|
334
|
+
der.sequence([digestAlgorithm, der.tlv(0x04, blob)]),
|
|
335
|
+
]);
|
|
336
|
+
const attributes = [
|
|
337
|
+
der.sequence([der.oid(OID.contentType), der.set([der.oid(OID.indirectData)])]),
|
|
338
|
+
der.sequence([der.oid(OID.messageDigest), der.set([der.tlv(0x04, sha256(der.contents(indirect)))])]),
|
|
339
|
+
der.sequence([der.oid(OID.statementType), der.set([der.sequence([der.oid(OID.individualCodeSigning)])])]),
|
|
340
|
+
der.sequence([der.oid(OID.opusInfo), der.set([der.sequence([])])]),
|
|
341
|
+
].sort(Buffer.compare);
|
|
342
|
+
const signature = signWith("sha256", der.set(attributes), signer.key);
|
|
343
|
+
const signerInfo = der.sequence([
|
|
344
|
+
der.integer(1), der.sequence([signer.issuer, der.tlv(0x02, signer.serial)]), digestAlgorithm,
|
|
345
|
+
der.tlv(0xa0, Buffer.concat(attributes)),
|
|
346
|
+
der.sequence([der.oid(OID.rsa), der.null]), der.tlv(0x04, signature),
|
|
347
|
+
]);
|
|
348
|
+
const signedData = der.sequence([
|
|
349
|
+
der.integer(1), der.set([digestAlgorithm]),
|
|
350
|
+
der.sequence([der.oid(OID.indirectData), der.tlv(0xa0, indirect)]),
|
|
351
|
+
der.tlv(0xa0, signer.certificate),
|
|
352
|
+
der.set([signerInfo]),
|
|
353
|
+
]);
|
|
354
|
+
return der.sequence([der.oid(OID.signedData), der.tlv(0xa0, signedData)]);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/** A certificate for Lamp's publisher name, signed by its own key, for code signing only. */
|
|
358
|
+
function selfSigned(key) {
|
|
359
|
+
const pkcs1 = createPublicKey(key).export({ type: "pkcs1", format: "der" });
|
|
360
|
+
const name = der.sequence([der.set([der.sequence([der.oid(OID.commonName), der.tlv(0x0c, Buffer.from("Lamp"))])])]);
|
|
361
|
+
const spki = der.sequence([der.sequence([der.oid(OID.rsa), der.null]), der.tlv(0x03, Buffer.concat([Buffer.from([0]), pkcs1]))]);
|
|
362
|
+
const serial = randomBytes(16);
|
|
363
|
+
serial[0] = (serial[0] & 0x7f) | 0x01;
|
|
364
|
+
const time = (date) => {
|
|
365
|
+
const text = date.toISOString().replace(/[-:T]/g, "").slice(0, 14) + "Z";
|
|
366
|
+
return date.getUTCFullYear() < 2050 ? der.tlv(0x17, Buffer.from(text.slice(2))) : der.tlv(0x18, Buffer.from(text));
|
|
367
|
+
};
|
|
368
|
+
const from = new Date(Date.now() - 86_400_000);
|
|
369
|
+
const validity = der.sequence([time(from), time(new Date(from.getTime() + 20 * 365 * 86_400_000))]);
|
|
370
|
+
const critical = der.tlv(0x01, Buffer.from([0xff]));
|
|
371
|
+
const extensions = der.sequence([
|
|
372
|
+
der.sequence([der.oid("2.5.29.19"), critical, der.tlv(0x04, der.sequence([]))]),
|
|
373
|
+
der.sequence([der.oid("2.5.29.15"), critical, der.tlv(0x04, der.tlv(0x03, Buffer.from([0x07, 0x80])))]),
|
|
374
|
+
der.sequence([der.oid("2.5.29.37"), der.tlv(0x04, der.sequence([der.oid("1.3.6.1.5.5.7.3.3")]))]),
|
|
375
|
+
der.sequence([der.oid("2.5.29.14"), der.tlv(0x04, der.tlv(0x04, createHash("sha1").update(pkcs1).digest()))]),
|
|
376
|
+
]);
|
|
377
|
+
const algorithm = der.sequence([der.oid(OID.sha256WithRSA), der.null]);
|
|
378
|
+
const tbs = der.sequence([der.tlv(0xa0, der.integer(2)), der.tlv(0x02, serial), algorithm, name, validity, name, spki, der.tlv(0xa3, extensions)]);
|
|
379
|
+
return der.sequence([tbs, algorithm, der.tlv(0x03, Buffer.concat([Buffer.from([0]), signWith("sha256", tbs, key)]))]);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/** A signer from a key (PKCS #1, as it's kept) and its certificate: the issuer and serial a signature names are read back out of the certificate. */
|
|
383
|
+
export function signerFrom(keyBytes, certificate) {
|
|
384
|
+
const parts = der.children(der.children(certificate)[0] ?? Buffer.alloc(0));
|
|
385
|
+
if (parts.length < 4) throw new Problem("lamp's certificate for app packages can't be read");
|
|
386
|
+
const versioned = parts[0][0] === 0xa0;
|
|
387
|
+
return {
|
|
388
|
+
key: createPrivateKey({ key: keyBytes, format: "der", type: "pkcs1" }),
|
|
389
|
+
certificate,
|
|
390
|
+
serial: der.contents(parts[versioned ? 1 : 0]),
|
|
391
|
+
issuer: parts[versioned ? 3 : 2],
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/** A new key (PKCS #1) and a certificate of its own for it. */
|
|
396
|
+
export function newSigner() {
|
|
397
|
+
const { privateKey } = generateKeyPairSync("rsa", { modulusLength: 2048 });
|
|
398
|
+
return { key: privateKey.export({ type: "pkcs1", format: "der" }), certificate: selfSigned(privateKey) };
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/** lamp's key and its own certificate for packages, made the first time and kept in `folder`, which only the person reads. */
|
|
402
|
+
export function keptSigner(folder) {
|
|
403
|
+
const keyFile = join(folder, "key.der"), certificateFile = join(folder, "certificate.der");
|
|
404
|
+
if (existsSync(keyFile) && existsSync(certificateFile)) {
|
|
405
|
+
try { return signerFrom(readFileSync(keyFile), readFileSync(certificateFile)); } catch { /* made again below */ }
|
|
406
|
+
}
|
|
407
|
+
const made = newSigner();
|
|
408
|
+
mkdirSync(folder, { recursive: true, mode: 0o700 });
|
|
409
|
+
writeFileSync(keyFile, made.key, { mode: 0o600 });
|
|
410
|
+
chmodSync(keyFile, 0o600);
|
|
411
|
+
writeFileSync(certificateFile, made.certificate);
|
|
412
|
+
return signerFrom(made.key, made.certificate);
|
|
413
|
+
}
|