diff --git a/Game/BakerHouse.fbx b/Game/Assets/Models/BakerHouse.fbx similarity index 100% rename from Game/BakerHouse.fbx rename to Game/Assets/Models/BakerHouse.fbx diff --git a/Game/Assets/Models/Clock.fbx b/Game/Assets/Models/Clock.fbx new file mode 100644 index 000000000..955bf5f5a Binary files /dev/null and b/Game/Assets/Models/Clock.fbx differ diff --git a/Game/Assets/Models/Dollhouse.fbx b/Game/Assets/Models/Dollhouse.fbx new file mode 100644 index 000000000..b1de5b717 Binary files /dev/null and b/Game/Assets/Models/Dollhouse.fbx differ diff --git a/Game/Assets/Models/Drawers.fbx b/Game/Assets/Models/Drawers.fbx new file mode 100644 index 000000000..62e29104a Binary files /dev/null and b/Game/Assets/Models/Drawers.fbx differ diff --git a/Game/Assets/Models/Firetruck.fbx b/Game/Assets/Models/Firetruck.fbx new file mode 100644 index 000000000..1673e072a Binary files /dev/null and b/Game/Assets/Models/Firetruck.fbx differ diff --git a/Game/Assets/Models/Floor.fbx b/Game/Assets/Models/Floor.fbx new file mode 100644 index 000000000..fd2b5b34d Binary files /dev/null and b/Game/Assets/Models/Floor.fbx differ diff --git a/Game/Assets/Models/Hearse.FBX b/Game/Assets/Models/Hearse.FBX new file mode 100644 index 000000000..35033d1ed Binary files /dev/null and b/Game/Assets/Models/Hearse.FBX differ diff --git a/Game/Assets/Models/Player.fbx b/Game/Assets/Models/Player.fbx new file mode 100644 index 000000000..e1947f28d Binary files /dev/null and b/Game/Assets/Models/Player.fbx differ diff --git a/Game/Assets/Models/Robot.FBX b/Game/Assets/Models/Robot.FBX new file mode 100644 index 000000000..c4905c7c6 Binary files /dev/null and b/Game/Assets/Models/Robot.FBX differ diff --git a/Game/Assets/Models/SpinningTop.fbx b/Game/Assets/Models/SpinningTop.fbx new file mode 100644 index 000000000..36e26abf1 Binary files /dev/null and b/Game/Assets/Models/SpinningTop.fbx differ diff --git a/Game/Assets/Models/Wall.FBX b/Game/Assets/Models/Wall.FBX new file mode 100644 index 000000000..0d1aa2083 Binary files /dev/null and b/Game/Assets/Models/Wall.FBX differ diff --git a/Game/WodenWacth.fbx b/Game/Assets/Models/WodenWacth.fbx similarity index 100% rename from Game/WodenWacth.fbx rename to Game/Assets/Models/WodenWacth.fbx diff --git a/Game/Assets/Models/Zombunny.fbx b/Game/Assets/Models/Zombunny.fbx new file mode 100644 index 000000000..5e3f86662 Binary files /dev/null and b/Game/Assets/Models/Zombunny.fbx differ diff --git a/Game/megalodon.FBX b/Game/Assets/Models/megalodon.FBX similarity index 100% rename from Game/megalodon.FBX rename to Game/Assets/Models/megalodon.FBX diff --git a/Game/Assets/Scenes/lights_delivery.scene b/Game/Assets/Scenes/lights_delivery.scene new file mode 100644 index 000000000..91d6ec045 --- /dev/null +++ b/Game/Assets/Scenes/lights_delivery.scene @@ -0,0 +1,1610 @@ +{ + "GORoot": { + "Uid": 7865191887523500989, + "GOName": "Root", + "Active": true, + "ParentId": 0, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + 0.0, + 0.0, + 0.0 + ], + "Rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "Scale": [ + 1.0, + 1.0, + 1.0 + ] + } + ], + "GOChildrens": [ + { + "Uid": 13510238329507608277, + "GOName": "Floor", + "Active": true, + "ParentId": 7865191887523500989, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + 25.0, + 1.0, + -25.0 + ], + "Rotation": [ + -9.099628641706659e-7, + 1.0, + -9.099628641706659e-7, + -4.371224093802084e-8 + ], + "Scale": [ + 0.9999993443489075, + 1.0, + 0.9999993443489075 + ] + } + ], + "GOChildrens": [ + { + "Uid": 13813034102765608926, + "GOName": "Base", + "Active": true, + "ParentId": 13510238329507608277, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + 0.0, + 0.0, + 0.0 + ], + "Rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "Scale": [ + 1.0, + 1.0, + 1.0 + ] + }, + { + "ComponentID": 0, + "ComponentType": 2 + }, + { + "ComponentID": 0, + "ComponentType": 3, + "HasDiffuse": true, + "HasSpecular": true, + "DiffusePath": "Assets\\Textures\\PlanksDiffuse.png", + "SpecularPath": "Assets\\Textures\\PlanksSpecular.tif", + "Shininess": 2.0, + "DiffuseColor": [ + 1.0, + 1.0, + 1.0, + 1.0 + ], + "SpecularColor": [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + } + ], + "GOChildrens": null + }, + { + "Uid": 2243169657355825613, + "GOName": "Sides", + "Active": true, + "ParentId": 13510238329507608277, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + 0.0, + 0.0, + 0.0 + ], + "Rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "Scale": [ + 1.0, + 1.0, + 1.0 + ] + }, + { + "ComponentID": 0, + "ComponentType": 2 + }, + { + "ComponentID": 0, + "ComponentType": 3, + "HasDiffuse": true, + "HasSpecular": true, + "DiffusePath": "Assets\\Textures\\PlanksDiffuse.png", + "SpecularPath": "Assets\\Textures\\PlanksSpecular.tif", + "Shininess": 2.0, + "DiffuseColor": [ + 1.0, + 1.0, + 1.0, + 1.0 + ], + "SpecularColor": [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + } + ], + "GOChildrens": null + }, + { + "Uid": 15589345809168240669, + "GOName": "Planks", + "Active": true, + "ParentId": 13510238329507608277, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + 0.0, + 0.0, + 0.0 + ], + "Rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "Scale": [ + 1.0, + 1.0, + 1.0 + ] + }, + { + "ComponentID": 0, + "ComponentType": 2 + }, + { + "ComponentID": 0, + "ComponentType": 3, + "HasDiffuse": true, + "HasSpecular": true, + "DiffusePath": "Assets\\Textures\\PlanksDiffuse.png", + "SpecularPath": "Assets\\Textures\\PlanksSpecular.tif", + "Shininess": 2.0, + "DiffuseColor": [ + 1.0, + 1.0, + 1.0, + 1.0 + ], + "SpecularColor": [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + } + ], + "GOChildrens": null + } + ] + }, + { + "Uid": 5777577542275192995, + "GOName": "Wall", + "Active": true, + "ParentId": 7865191887523500989, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + 25.0, + 0.0, + -25.0 + ], + "Rotation": [ + 9.099709359361441e-7, + 1.0, + -0.00018306175479665399, + -4.354480864776633e-8 + ], + "Scale": [ + 1.0000048875808716, + 0.9999999403953552, + 1.0 + ] + } + ], + "GOChildrens": [ + { + "Uid": 10235216291708969123, + "GOName": "Wall", + "Active": true, + "ParentId": 5777577542275192995, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + 0.0, + 0.0, + 0.0 + ], + "Rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "Scale": [ + 1.0, + 1.0, + 1.0 + ] + }, + { + "ComponentID": 0, + "ComponentType": 2 + }, + { + "ComponentID": 0, + "ComponentType": 3, + "HasDiffuse": true, + "HasSpecular": true, + "DiffusePath": "Assets\\Textures\\WallDiffuse.png", + "SpecularPath": "Assets\\Textures\\WallSpecular.tif", + "Shininess": 2.0, + "DiffuseColor": [ + 1.0, + 1.0, + 1.0, + 1.0 + ], + "SpecularColor": [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + } + ], + "GOChildrens": null + }, + { + "Uid": 4213928734425214934, + "GOName": "Stars", + "Active": true, + "ParentId": 5777577542275192995, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + 0.0, + 0.0, + 0.0 + ], + "Rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "Scale": [ + 1.0, + 1.0, + 1.0 + ] + }, + { + "ComponentID": 0, + "ComponentType": 2 + }, + { + "ComponentID": 0, + "ComponentType": 3, + "HasDiffuse": false, + "HasSpecular": false, + "DiffusePath": "Assets\\Textures\\StarsDiffuse.png", + "SpecularPath": "Assets\\Textures\\StarsSpecular.tif", + "Shininess": 2.0, + "DiffuseColor": [ + 0.8352941870689392, + 1.0, + 0.5176470875740051, + 1.0 + ], + "SpecularColor": [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + } + ], + "GOChildrens": null + } + ] + }, + { + "Uid": 4044411750347881611, + "GOName": "Drawers", + "Active": true, + "ParentId": 7865191887523500989, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + 0.0, + 1.0, + -18.149871826171876 + ], + "Rotation": [ + -4.371138828673793e-8, + 1.0, + -4.371138828673793e-8, + -1.910685465164705e-15 + ], + "Scale": [ + 0.9999997615814209, + 1.0, + 0.9999997615814209 + ] + } + ], + "GOChildrens": [ + { + "Uid": 12686487505404020089, + "GOName": "Drawers", + "Active": true, + "ParentId": 4044411750347881611, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + 0.0, + 0.0, + 0.0 + ], + "Rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "Scale": [ + 1.0, + 1.0, + 1.0 + ] + }, + { + "ComponentID": 0, + "ComponentType": 2 + }, + { + "ComponentID": 0, + "ComponentType": 3, + "HasDiffuse": true, + "HasSpecular": true, + "DiffusePath": "Assets\\Textures\\DrawersDiffuse.png", + "SpecularPath": "Assets\\Textures\\DrawersSpecular.tif", + "Shininess": 7.999998092651367, + "DiffuseColor": [ + 1.0, + 1.0, + 1.0, + 1.0 + ], + "SpecularColor": [ + 0.26999998092651369, + 0.26999998092651369, + 0.26999998092651369, + 1.0 + ] + } + ], + "GOChildrens": null + } + ] + }, + { + "Uid": 17631367046667315039, + "GOName": "Player", + "Active": true, + "ParentId": 7865191887523500989, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + 0.0, + 1.0, + 0.0 + ], + "Rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "Scale": [ + 2.130000114440918, + 2.130000114440918, + 2.130000114440918 + ] + } + ], + "GOChildrens": [ + { + "Uid": 4343651034254014147, + "GOName": "Gun", + "Active": true, + "ParentId": 17631367046667315039, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + 0.0, + 0.0, + 0.0 + ], + "Rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "Scale": [ + 1.0, + 1.0, + 1.0 + ] + }, + { + "ComponentID": 0, + "ComponentType": 2 + }, + { + "ComponentID": 0, + "ComponentType": 3, + "HasDiffuse": true, + "HasSpecular": true, + "DiffusePath": "Assets\\Textures\\GunDiffuse.png", + "SpecularPath": "Assets\\Textures\\GunSpecular.tif", + "Shininess": 2.0, + "DiffuseColor": [ + 1.0, + 1.0, + 1.0, + 1.0 + ], + "SpecularColor": [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + } + ], + "GOChildrens": null + }, + { + "Uid": 4928332077875624079, + "GOName": "Player", + "Active": true, + "ParentId": 17631367046667315039, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + 0.0, + 0.0, + 0.0 + ], + "Rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "Scale": [ + 1.0, + 1.0, + 1.0 + ] + }, + { + "ComponentID": 0, + "ComponentType": 2 + }, + { + "ComponentID": 0, + "ComponentType": 3, + "HasDiffuse": true, + "HasSpecular": true, + "DiffusePath": "Assets\\Textures\\PlayerDiffuse.png", + "SpecularPath": "Assets\\Textures\\PlayerSpecular.tif", + "Shininess": 2.0, + "DiffuseColor": [ + 1.0, + 1.0, + 1.0, + 1.0 + ], + "SpecularColor": [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + } + ], + "GOChildrens": null + }, + { + "Uid": 17868595259755893693, + "GOName": "Camera", + "Active": true, + "ParentId": 17631367046667315039, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + 0.0, + 1.3587090969085694, + 0.0 + ], + "Rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "Scale": [ + 1.0, + 1.0, + 1.0 + ] + }, + { + "ComponentID": 0, + "ComponentType": 4, + "Frustrum": { + "NearDistance": 0.10000000149011612, + "FarDistance": 30.0, + "Fov": 30.0, + "Pos": [ + 0.0, + 3.8940505981445314, + 0.0 + ], + "Front": [ + 0.0, + 0.0, + 2.130000114440918 + ], + "Up": [ + 0.0, + 2.130000114440918, + 0.0 + ] + } + } + ], + "GOChildrens": null + } + ] + }, + { + "Uid": 14261037417874424292, + "GOName": "Dollhouse", + "Active": true, + "ParentId": 7865191887523500989, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + 20.186649322509767, + 1.0, + -12.893146514892579 + ], + "Rotation": [ + 0.0, + 0.9590730667114258, + 0.0, + 0.2831587493419647 + ], + "Scale": [ + 1.000001072883606, + 1.0, + 1.000001072883606 + ] + } + ], + "GOChildrens": [ + { + "Uid": 5542913171551279356, + "GOName": "Dollhouse", + "Active": true, + "ParentId": 14261037417874424292, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + 0.0, + 0.0, + 0.0 + ], + "Rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "Scale": [ + 1.0, + 1.0000929832458497, + 1.0000929832458497 + ] + }, + { + "ComponentID": 0, + "ComponentType": 2 + }, + { + "ComponentID": 0, + "ComponentType": 3, + "HasDiffuse": true, + "HasSpecular": true, + "DiffusePath": "Assets\\Textures\\DollhouseDiffuse.png", + "SpecularPath": "Assets\\Textures\\DollhouseSpecular.tif", + "Shininess": 3.999999523162842, + "DiffuseColor": [ + 0.5882353186607361, + 0.5882353186607361, + 0.5882353186607361, + 1.0 + ], + "SpecularColor": [ + 0.17999997735023499, + 0.17999997735023499, + 0.17999997735023499, + 1.0 + ] + } + ], + "GOChildrens": null + } + ] + }, + { + "Uid": 1787667446689130583, + "GOName": "Robot", + "Active": true, + "ParentId": 7865191887523500989, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + 20.813589096069337, + 1.0, + 0.0 + ], + "Rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "Scale": [ + 1.0, + 1.0, + 1.0 + ] + } + ], + "GOChildrens": [ + { + "Uid": 14765660926887550727, + "GOName": "Robot", + "Active": true, + "ParentId": 1787667446689130583, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + 0.0, + 0.0, + 0.0 + ], + "Rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "Scale": [ + 1.0, + 1.0, + 1.0 + ] + }, + { + "ComponentID": 0, + "ComponentType": 2 + }, + { + "ComponentID": 0, + "ComponentType": 3, + "HasDiffuse": true, + "HasSpecular": true, + "DiffusePath": "Assets\\Textures\\RobotDiffuse.png", + "SpecularPath": "Assets\\Textures\\RobotSpecular.tif", + "Shininess": 2.0, + "DiffuseColor": [ + 0.5882353186607361, + 0.5882353186607361, + 0.5882353186607361, + 1.0 + ], + "SpecularColor": [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + } + ], + "GOChildrens": null + } + ] + }, + { + "Uid": 2600371658760113841, + "GOName": "Zombunny", + "Active": true, + "ParentId": 7865191887523500989, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + 22.61165428161621, + 1.0, + -7.191998481750488 + ], + "Rotation": [ + 0.0, + -0.48521727323532107, + 0.0, + 0.8743936419487 + ], + "Scale": [ + 2.6100010871887209, + 2.6100001335144045, + 2.6100010871887209 + ] + } + ], + "GOChildrens": [ + { + "Uid": 11545460801033758116, + "GOName": "Zombunny", + "Active": true, + "ParentId": 2600371658760113841, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + 0.0, + 0.0, + 0.0 + ], + "Rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "Scale": [ + 1.0, + 1.0, + 1.0 + ] + }, + { + "ComponentID": 0, + "ComponentType": 2 + }, + { + "ComponentID": 0, + "ComponentType": 3, + "HasDiffuse": true, + "HasSpecular": true, + "DiffusePath": "Assets\\Textures\\ZombunnyDiffuse.png", + "SpecularPath": "Assets\\Textures\\ZombunnySpecular.tif", + "Shininess": 7.999998092651367, + "DiffuseColor": [ + 0.22352942824363709, + 0.22352942824363709, + 0.22352942824363709, + 1.0 + ], + "SpecularColor": [ + 0.08546578139066696, + 0.08546578139066696, + 0.08546578139066696, + 1.0 + ] + } + ], + "GOChildrens": null + }, + { + "Uid": 17319656394498909293, + "GOName": "Zombunny", + "Active": true, + "ParentId": 2600371658760113841, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + 0.0, + 0.0, + 0.0 + ], + "Rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "Scale": [ + 1.0, + 1.0, + 1.0 + ] + }, + { + "ComponentID": 0, + "ComponentType": 2 + }, + { + "ComponentID": 0, + "ComponentType": 3, + "HasDiffuse": true, + "HasSpecular": true, + "DiffusePath": "Assets\\Textures\\ZombunnyDiffuse.png", + "SpecularPath": "Assets\\Textures\\ZombunnySpecular.tif", + "Shininess": 15.999996185302735, + "DiffuseColor": [ + 0.8823530077934265, + 0.8823530077934265, + 0.8823530077934265, + 1.0 + ], + "SpecularColor": [ + 0.5399999618530273, + 0.5399999618530273, + 0.5399999618530273, + 1.0 + ] + } + ], + "GOChildrens": null + } + ] + }, + { + "Uid": 1032590680058568894, + "GOName": "Firetruck", + "Active": true, + "ParentId": 7865191887523500989, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + 6.908659934997559, + 1.0, + -3.021707534790039 + ], + "Rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "Scale": [ + 0.9494000673294067, + 0.9494000673294067, + 0.9494000673294067 + ] + } + ], + "GOChildrens": [ + { + "Uid": 13803576977574689029, + "GOName": "Firetruck", + "Active": true, + "ParentId": 1032590680058568894, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + -30.93461036682129, + 0.0, + 3.42999529838562 + ], + "Rotation": [ + 0.0, + -6.705524668859653e-8, + 0.0, + 1.0 + ], + "Scale": [ + 0.9999998807907105, + 1.0, + 0.9999992847442627 + ] + }, + { + "ComponentID": 0, + "ComponentType": 2 + }, + { + "ComponentID": 0, + "ComponentType": 3, + "HasDiffuse": true, + "HasSpecular": true, + "DiffusePath": "Assets\\Textures\\FiretruckDiffuse.png", + "SpecularPath": "Assets\\Textures\\FiretruckSpecular.tif", + "Shininess": 2.0, + "DiffuseColor": [ + 1.0, + 1.0, + 1.0, + 1.0 + ], + "SpecularColor": [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + } + ], + "GOChildrens": null + } + ] + }, + { + "Uid": 13745403210057286067, + "GOName": "Hearse", + "Active": true, + "ParentId": 7865191887523500989, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + -10.970490455627442, + 1.0, + -10.392746925354004 + ], + "Rotation": [ + 0.0, + 0.8293483853340149, + 0.0, + 0.5587317943572998 + ], + "Scale": [ + 1.0000001192092896, + 1.0, + 1.0000001192092896 + ] + } + ], + "GOChildrens": [ + { + "Uid": 11643719950148465875, + "GOName": "Hearse", + "Active": true, + "ParentId": 13745403210057286067, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + 0.0, + 0.0, + 0.0 + ], + "Rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "Scale": [ + 1.0, + 1.0, + 1.0 + ] + }, + { + "ComponentID": 0, + "ComponentType": 2 + }, + { + "ComponentID": 0, + "ComponentType": 3, + "HasDiffuse": true, + "HasSpecular": true, + "DiffusePath": "Assets\\Textures\\HearseDiffuse.png", + "SpecularPath": "Assets\\Textures\\HearseSpecular.tif", + "Shininess": 7.999998092651367, + "DiffuseColor": [ + 1.0, + 1.0, + 1.0, + 1.0 + ], + "SpecularColor": [ + 0.26999998092651369, + 0.26999998092651369, + 0.26999998092651369, + 1.0 + ] + } + ], + "GOChildrens": null + } + ] + }, + { + "Uid": 13305417017237290851, + "GOName": "Clock", + "Active": true, + "ParentId": 7865191887523500989, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + 0.0, + 14.725876808166504, + -24.420957565307618 + ], + "Rotation": [ + 0.0, + 0.998001217842102, + 0.0, + 0.06319599598646164 + ], + "Scale": [ + 1.0000005960464478, + 1.0, + 1.0000005960464478 + ] + } + ], + "GOChildrens": [ + { + "Uid": 6084385138541190741, + "GOName": "Clock", + "Active": true, + "ParentId": 13305417017237290851, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + -20.0, + 0.0, + 0.0 + ], + "Rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "Scale": [ + 1.0, + 1.0, + 1.0 + ] + }, + { + "ComponentID": 0, + "ComponentType": 2 + }, + { + "ComponentID": 0, + "ComponentType": 3, + "HasDiffuse": true, + "HasSpecular": true, + "DiffusePath": "Assets\\Textures\\ClockDiffuse.png", + "SpecularPath": "Assets\\Textures\\ClockSpecular.tif", + "Shininess": 2.0, + "DiffuseColor": [ + 0.5882353186607361, + 0.5882353186607361, + 0.5882353186607361, + 1.0 + ], + "SpecularColor": [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + } + ], + "GOChildrens": null + }, + { + "Uid": 10954820566735060566, + "GOName": "Clock", + "Active": true, + "ParentId": 13305417017237290851, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + -20.0, + 0.0, + 0.0 + ], + "Rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "Scale": [ + 1.0, + 1.0, + 1.0 + ] + }, + { + "ComponentID": 0, + "ComponentType": 2 + }, + { + "ComponentID": 0, + "ComponentType": 3, + "HasDiffuse": true, + "HasSpecular": true, + "DiffusePath": "Assets\\Textures\\ClockDiffuse.png", + "SpecularPath": "Assets\\Textures\\ClockSpecular.tif", + "Shininess": 2.0, + "DiffuseColor": [ + 0.5882353186607361, + 0.5882353186607361, + 0.5882353186607361, + 1.0 + ], + "SpecularColor": [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + } + ], + "GOChildrens": null + } + ] + }, + { + "Uid": 2506422504271000203, + "GOName": "BakerHouse", + "Active": true, + "ParentId": 7865191887523500989, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + -11.93181037902832, + 1.0, + 1.22265625 + ], + "Rotation": [ + 0.32526496052742007, + -0.627855658531189, + 0.627855658531189, + 0.3252648115158081 + ], + "Scale": [ + 1.6299992799758912, + 1.6301684379577637, + 1.6301679611206055 + ] + } + ], + "GOChildrens": [ + { + "Uid": 13782938144321975347, + "GOName": "Chimney", + "Active": true, + "ParentId": 2506422504271000203, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + 0.0, + 0.0, + 0.0 + ], + "Rotation": [ + -0.7071068286895752, + 0.0, + 0.0, + 0.7071067094802856 + ], + "Scale": [ + 100.0, + 99.99996948242188, + 99.99996948242188 + ] + }, + { + "ComponentID": 0, + "ComponentType": 2 + }, + { + "ComponentID": 0, + "ComponentType": 3, + "HasDiffuse": true, + "HasSpecular": false, + "DiffusePath": "Assets\\Textures\\Baker_house.png", + "SpecularPath": "Assets\\Textures\\ChimneySpecular.tif", + "Shininess": 8.0, + "DiffuseColor": [ + 0.47040000557899477, + 0.47040000557899477, + 0.47040000557899477, + 1.0 + ], + "SpecularColor": [ + 0.8999999761581421, + 0.8999999761581421, + 0.8999999761581421, + 1.0 + ] + } + ], + "GOChildrens": null + }, + { + "Uid": 4122501212356958282, + "GOName": "Baker_house", + "Active": true, + "ParentId": 2506422504271000203, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + 0.0, + 0.0, + 0.0 + ], + "Rotation": [ + -0.7071068286895752, + 0.0, + 0.0, + 0.7071067094802856 + ], + "Scale": [ + 100.0, + 99.99996948242188, + 99.99996948242188 + ] + }, + { + "ComponentID": 0, + "ComponentType": 2 + }, + { + "ComponentID": 0, + "ComponentType": 3, + "HasDiffuse": true, + "HasSpecular": false, + "DiffusePath": "Assets\\Textures\\Baker_house.png", + "SpecularPath": "Assets\\Textures\\ChimneySpecular.tif", + "Shininess": 8.0, + "DiffuseColor": [ + 0.47040000557899477, + 0.47040000557899477, + 0.47040000557899477, + 1.0 + ], + "SpecularColor": [ + 0.8999999761581421, + 0.8999999761581421, + 0.8999999761581421, + 1.0 + ] + } + ], + "GOChildrens": null + } + ] + }, + { + "Uid": 7193860100117459637, + "GOName": "Sun", + "Active": true, + "ParentId": 7865191887523500989, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + 7.217105865478516, + 21.959491729736329, + 0.0 + ], + "Rotation": [ + 0.917763888835907, + -0.025832753628492357, + -0.038700226694345477, + 0.3943914771080017 + ], + "Scale": [ + 0.9999996423721314, + 0.9999996423721314, + 0.9999997615814209 + ] + }, + { + "ComponentID": 0, + "ComponentType": 5, + "LightType": 5, + "Color": [ + 1.0, + 1.0, + 1.0, + 1.0 + ], + "intensity": 1.0, + "active": true, + "drawDirection": true + } + ], + "GOChildrens": null + }, + { + "Uid": 13369872231505684201, + "GOName": "SpotLight", + "Active": true, + "ParentId": 7865191887523500989, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + 0.0, + 28.507427215576173, + 0.0 + ], + "Rotation": [ + 0.6893669962882996, + 0.0, + 0.0, + 0.7244122624397278 + ], + "Scale": [ + 0.9999999403953552, + 0.999998927116394, + 0.999998927116394 + ] + }, + { + "ComponentID": 0, + "ComponentType": 7, + "LightType": 7, + "Color": [ + 0.0665154680609703, + 0.01940036006271839, + 0.3492063283920288, + 1.0 + ], + "inner": 1.0, + "outer": 30.0, + "intensity": 25.0, + "radius": 250.0, + "active": true, + "drawCone": false + } + ], + "GOChildrens": null + }, + { + "Uid": 10265425890820506573, + "GOName": "PointLight", + "Active": true, + "ParentId": 7865191887523500989, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + 17.458114624023439, + 4.5973687171936039, + -5.813863754272461 + ], + "Rotation": [ + 0.0, + -0.1532106101512909, + 0.0, + 0.9881935715675354 + ], + "Scale": [ + 0.9999998211860657, + 1.0, + 0.9999998211860657 + ] + }, + { + "ComponentID": 0, + "ComponentType": 6, + "LightType": 6, + "Color": [ + 1.0, + 0.0, + 0.0, + 1.0 + ], + "intensity": 400.0, + "radius": 50.0, + "active": true, + "drawSphere": false + } + ], + "GOChildrens": null + }, + { + "Uid": 7440720529531775419, + "GOName": "PointLight2", + "Active": true, + "ParentId": 7865191887523500989, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + -13.05864143371582, + 4.9998860359191898, + -20.77186393737793 + ], + "Rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "Scale": [ + 1.0, + 1.0, + 1.0 + ] + }, + { + "ComponentID": 0, + "ComponentType": 6, + "LightType": 6, + "Color": [ + 0.3809523582458496, + 1.0, + 0.0, + 1.0 + ], + "intensity": 200.0, + "radius": 250.0, + "active": true, + "drawSphere": false + } + ], + "GOChildrens": null + }, + { + "Uid": 1618238293309818069, + "GOName": "SpotLight2", + "Active": true, + "ParentId": 7865191887523500989, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + 0.0, + 14.980191230773926, + 0.0 + ], + "Rotation": [ + 0.2571210265159607, + 0.5694409608840942, + 0.38714906573295596, + 0.6780422925949097 + ], + "Scale": [ + 0.9999997615814209, + 0.9999995231628418, + 0.9999997019767761 + ] + }, + { + "ComponentID": 0, + "ComponentType": 7, + "LightType": 7, + "Color": [ + 1.0, + 1.0, + 1.0, + 1.0 + ], + "inner": 1.0, + "outer": 12.0, + "intensity": 20.0, + "radius": 250.0, + "active": true, + "drawCone": false + } + ], + "GOChildrens": null + } + ] + } +} \ No newline at end of file diff --git a/Game/Assets/Scenes/untitled.scene b/Game/Assets/Scenes/untitled.scene new file mode 100644 index 000000000..7d03d0455 --- /dev/null +++ b/Game/Assets/Scenes/untitled.scene @@ -0,0 +1,111 @@ +{ + "GORoot": { + "Uid": 0, + "GOName": "Root", + "Active": true, + "ParentId": 0, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + 0.0, + 0.0, + 0.0 + ], + "Rotation": [ + 0.0, + 0.0, + 0.0 + ], + "Scale": [ + 1.0, + 1.0, + 1.0 + ] + } + ], + "GOChildrens": [ + { + "Uid": 0, + "GOName": "Debug Camera", + "Active": true, + "ParentId": 0, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + 5.0, + 5.0, + 0.0 + ], + "Rotation": [ + 0.0, + -0.7071068286895752, + 0.0 + ], + "Scale": [ + 1.0, + 1.0, + 1.0 + ] + }, + { + "ComponentID": 0, + "ComponentType": 4, + "Frustrum": { + "NearDistance": 0.10000000149011612, + "FarDistance": 100.0, + "Fov": 65.0, + "Pos": [ + 5.0, + 5.0, + 0.0 + ], + "Front": [ + -1.0, + 0.0, + 0.0 + ], + "Up": [ + 0.0, + 1.0, + 0.0 + ] + } + } + ], + "GOChildrens": null + }, + { + "Uid": 0, + "GOName": "BakerHouse", + "Active": true, + "ParentId": 0, + "Components": [ + { + "ComponentID": 0, + "ComponentType": 1, + "Position": [ + 0.0, + 0.0, + 0.0 + ], + "Rotation": [ + 0.0, + 0.0, + 0.0 + ], + "Scale": [ + 1.0, + 1.0, + 1.0 + ] + } + ], + "GOChildrens": null + } + ] + } +} \ No newline at end of file diff --git a/Game/Assets/Shaders/dibug.txt b/Game/Assets/Shaders/dibug.txt new file mode 100644 index 000000000..e619ac17a --- /dev/null +++ b/Game/Assets/Shaders/dibug.txt @@ -0,0 +1,6 @@ + + // Positional + // diffuse_strength = max(dot(norm, normalize(light_position - fragment.pos)), 0.0); + + // Directional + //diffuse_strength = max(dot(norm, normalize(lights.directional.dir.xyz)), 0.0); \ No newline at end of file diff --git a/Game/Assets/Shaders/fragment.glsl b/Game/Assets/Shaders/fragment.glsl new file mode 100644 index 000000000..de098c2b5 --- /dev/null +++ b/Game/Assets/Shaders/fragment.glsl @@ -0,0 +1,218 @@ +# version 440 + +#define DIFFUSE_SAMPLER 0 +#define SPECULAR_SAMPLER 1 +#define N_2D_SAMPLERS 2 + +#define MAX_POINT_LIGHTS 4 +#define MAX_SPOT_LIGHTS 4 + +#define PI 3.141597 + +struct AmbientLight +{ + vec4 color; + float intensity; +}; + +struct DirLight +{ + vec4 direction; + vec4 color; + float intensity; +}; + +struct PointLight +{ + vec4 position; + vec4 color; + float intensity; + float radius; +}; + +struct SpotLight +{ + vec4 position; + vec4 direction; + vec4 color; + float inner; + float outer; + float intensity; + float radius; +}; + +layout(std140, row_major, binding = 0) uniform Camera +{ + mat4 view; + mat4 proj; + vec3 pos; +} camera; + +layout(std140, binding = 1) uniform Material +{ + vec4 diffuse_color; + vec4 specular_color; + uint diffuse_flag; + uint specular_flag; + float shininess; +} material; + +layout(std140, binding = 2) uniform Lights +{ + AmbientLight ambient; + DirLight directional; + PointLight points[MAX_POINT_LIGHTS]; + SpotLight spots[MAX_SPOT_LIGHTS]; + uint n_points; + uint n_spots; +} lights; + +uniform sampler2D textures[N_2D_SAMPLERS]; + +// Inputs +struct VertexData +{ + vec3 normal; + vec3 pos; + vec2 tex_coord; +}; +in VertexData fragment; + +// Outputs +out vec4 color; + +vec3 SchlickFresnel(const vec3 f0, float cos_theta) +{ + return f0 + (vec3(1.0) - f0) * pow(1.0 - cos_theta, 5.0); +} + +vec3 PBR(const vec3 normal, const vec3 view_dir, const vec3 light_dir, const vec3 light_color, + const vec3 diffuse_color, const vec3 specular_color, float shininess, float attenuation) +{ + vec3 reflect_dir = reflect(-light_dir, normal); + // Should equal cos theta + float NdL = max(dot(normal, light_dir), 0.0001); + // Phong specular strength + float VdR = max(dot(view_dir, reflect_dir), 0.0001); + + //vec3 halfway_dir = normalize(light_dir + view_dir); + //float cos_theta = max(dot(view_dir, halfway_dir), 0.0); + //vec3 fresnel = SchlickFresnel(specular_color, cos_theta); + + vec3 fresnel = SchlickFresnel(specular_color, NdL); + + vec3 part1 = (diffuse_color * (vec3(1.0) - specular_color)) / PI; + vec3 part2 = ((shininess + 2.0) / (2.0 * PI)) * fresnel * pow(VdR, shininess); + + return light_color * (part1 + part2) * NdL * attenuation; +} + +vec3 DirectionalPBR(const vec3 normal, const vec3 view_dir, const DirLight light, + const vec3 diffuse_color, const vec3 specular_color, float shininess) +{ + vec3 L = normalize(-light.direction.xyz); + + float attenuation = 1.0; + // Input dir goes from light to fragment, swap it for calculations + return PBR(normal, view_dir, L, light.color.rgb, diffuse_color, specular_color, shininess, attenuation) * light.intensity; +} + +float Attenuation(float distance) +{ + return 1.0/(1.0+0.3*distance+0.3*(distance*distance)); +} + +float EpicAttenuation(float distance, float radius) +{ + return max(pow(max(1 - pow(distance/radius, 4), 0.0), 2.0), 0.0) / (distance * distance + 1); +} + +vec3 PositionalPBR(const vec3 frag_pos, const vec3 normal, const vec3 view_dir, const PointLight light, + const vec3 diffuse_color, const vec3 specular_color, float shininess) +{ + vec3 L = light.position.xyz - frag_pos; + float light_distance = length(L); + L = normalize(L); + + float radius = 250.0; + float attenuation = EpicAttenuation(light_distance, light.radius); + return PBR(normal, view_dir, L, light.color.rgb, diffuse_color, specular_color, shininess, attenuation) * light.intensity; +} + +// TODO: Test more +float SpotAttenuation(const vec3 L, const vec3 cone_direction, float radius) +{ + // Light direction from light to outside + float distance = dot(-L, cone_direction); + return EpicAttenuation(distance, radius); +} + +float Cone(const vec3 L, const vec3 cone_direction, float inner, float outer) +{ + // Light direction from light to fragment (Reverse than pbr) + float C = dot(-L, cone_direction); + float c_inner = cos(inner); + if (C > c_inner) return 1.0; + float c_outer = cos(outer); + if (C > c_outer) return (C - c_outer)/(c_inner - c_outer); + return 0.0; +} + +vec3 SpotPBR(const vec3 frag_pos, const vec3 normal, const vec3 view_dir, const SpotLight light, + const vec3 diffuse_color, const vec3 specular_color, float shininess) +{ + // Not the same as spot light direction (L) + vec3 L = light.position.xyz - frag_pos; + L = normalize(L); + vec3 cone_direction = normalize(light.direction.xyz); + + float attenuation = SpotAttenuation(L, cone_direction, light.radius); + float cone = Cone(L, cone_direction, light.inner, light.outer); + return PBR(normal, view_dir, L, light.color.rgb, diffuse_color, specular_color, shininess, attenuation * cone) * light.intensity; +} + +void main() +{ + vec3 norm = normalize(fragment.normal); + vec3 view_dir = normalize(camera.pos - fragment.pos); + + vec3 diffuse_color = material.diffuse_color.rgb; + if (material.diffuse_flag > 0) + { + diffuse_color = pow(texture(textures[DIFFUSE_SAMPLER], fragment.tex_coord).rgb, vec3(2.2)); + } + + float shininess = material.shininess; + vec3 specular_color = material.specular_color.rgb; + if (material.specular_flag > 0) + { + // Should we gaMma correct specular? + // specular_color = pow(texture(textures[SPECULAR_SAMPLER], fragment.tex_coord).rgb, vec3(2.2)); + specular_color = texture(textures[SPECULAR_SAMPLER], fragment.tex_coord).rgb; + // Use alpha as shininess? + //shininess = texture(textures[SPECULAR_SAMPLER], fragment.tex_coord).a; + } + + + vec3 hdr_color = vec3(0.0); + + hdr_color += DirectionalPBR(norm, view_dir, lights.directional, diffuse_color, specular_color, shininess); + + for(uint i=0; i and others. + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. \ No newline at end of file diff --git a/Licenses/RapidJSON.txt b/Licenses/RapidJSON.txt new file mode 100644 index 000000000..7ccc161c8 --- /dev/null +++ b/Licenses/RapidJSON.txt @@ -0,0 +1,57 @@ +Tencent is pleased to support the open source community by making RapidJSON available. + +Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. + +If you have downloaded a copy of the RapidJSON binary from Tencent, please note that the RapidJSON binary is licensed under the MIT License. +If you have downloaded a copy of the RapidJSON source code from Tencent, please note that RapidJSON source code is licensed under the MIT License, except for the third-party components listed below which are subject to different license terms. Your integration of RapidJSON into your own projects may require compliance with the MIT License, as well as the other licenses applicable to the third-party components included within RapidJSON. To avoid the problematic JSON license in your own projects, it's sufficient to exclude the bin/jsonchecker/ directory, as it's the only code under the JSON license. +A copy of the MIT License is included in this file. + +Other dependencies and licenses: + +Open Source Software Licensed Under the BSD License: +-------------------------------------------------------------------- + +The msinttypes r29 +Copyright (c) 2006-2013 Alexander Chemeris +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +* Neither the name of copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Open Source Software Licensed Under the JSON License: +-------------------------------------------------------------------- + +json.org +Copyright (c) 2002 JSON.org +All Rights Reserved. + +JSON_checker +Copyright (c) 2002 JSON.org +All Rights Reserved. + + +Terms of the JSON License: +--------------------------------------------------- + +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 shall be used for Good, not Evil. + +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. + + +Terms of the MIT License: +-------------------------------------------------------------------- + +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. diff --git a/README.md b/README.md index 9792205eb..497ceddf6 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,90 @@ # Altar Boy +Altar Boy is a custom built C++ Game Engine done for the Videogame Development Master at UPC School. -Project for mastes degree, UPC Advancing programming AAA. A Game engine implemented with C++. +![engine screnshot](Screenshots/example.PNG) -## Get Ready 🚀 +## Developers 💻 -Move to develop and change the path directory project to \..\Game (if you want to debug) +* **Eric Torres** - [Erick9Thor](https://github.com/Erick9Thor) +* **Bernat Martínez** - [Bernatmago](https://github.com/Bernatmago) -### How works 🔧 +## How to use 🔧 -Is just unity, you have the same controls of the camera, are provided to two models to test drag an drop, so you can: +### Using a scene +To start seeing a model you can drop any file from ```Game/Assets/Models``` into the screen. Alternatively you can load a scene going to ``` File -> Load -> Scene ``` and choosing the ```.scene``` file you want to use. -* Move the camera as fps game using W, A, S, D +![load menu](Screenshots/load_menu.PNG) + + +### Navigation +Scene navigation controls resemble the ones from unity: +* Move the camera as fps game using right click + W, A, S, D * Orbit the model using Alt + mouse motion * Pres F to focus on model -Theare are diferents GUIs to show configurations of the current model. +### Game Objects & Components +The engine is based arround hierarchies of game objects that have a set of components asociated to them. All game objects will have at least a transform. -## Build with 🛠️ +You can edit the game objects distribution via the **scene hierarchy** by dragging them or using the contextual menu that pops up with right click. You can also select objects by clicking them on the scene (As long as they have a mesh). -_Menciona las herramientas que utilizaste para crear tu proyecto_ +![scene hierarchy](Screenshots/hierarchy.PNG) -* [OpenGL](https://www.opengl.org//) - A cross-language, cross-platform application programming interface (API) for rendering 2D and 3D vector graphics. -* [DevIL](http://openil.sourceforge.net/about.php) - A programmer's library to develop applications with very powerful image loading capabilities, -* [Assimp](https://www.assimp.org/) - portable Open-Source library to import various well-known 3D model formats in a uniform manner. -* [ImGui](https://github.com/ocornut/imgui) - A bloat-free graphical user interface library for C++. +To tweak components or create them you can select the desired game object and go to the **inspector**, Each compoentn has its own set of options. -## Wiki 📖 +![inspector](Screenshots/inspector.PNG) -You can find out much more about how to use this project in our https://github.com/Erick9Thor/Engine3D +Alternatively you can modify the transform using the **guizmo**, to change its current mode use the buttons at top left of the scene or the W, E, R keys. -## Authors ✒️ +![guimzo](Screenshots/guizmo.PNG) -* **Eric Torres** - *main worker/owner* - [Erick9Thor](https://github.com/Erick9Thor) +## Features +Based on the second assignment requirements +- Unity like camera controls +- Previous menus (Altar Boy + UPC-Engine) and new ones for the new features +- Engine loads with survival shooter scene +- Scene serialization (loading & saving) +- Quadtree frustum culling +- Editable scene with guizmos +- Editable materials that support diffuse and specular maps +- PBR Phong shader with support for a directional light and multiple point & spot lights +- Unity like play stop pause controls, scene returns to its original state on stop -You can also look at the list of all the [contributors](https://github.com/your/project/contributors). +### Extra features +- Raycast selection +- Stencil of the selected object +- Scene configuration + - Ambient light + - Toggleable skybox & background color + - Toggleable Debug Draw & gui information + - Transform & Camera debug information + - Quadtree debug draw + - Bounding boxes drawing + - Light components debug draw (Needs to be tunned) +- Configurable Components + - Lights parameters + - Materials properties + - Camera configuration + - Any camera can be set as culling camera +- Timers window + +![stencil](Screenshots/stencil.PNG) +![scene configuration](Screenshots/scene_config.PNG) +![timers](Screenshots/timers.PNG) -## License 📄 -This project is under the License [LICENSE.md](LICENSE.md). +## Built with 🛠️ + +* [OpenGL](https://www.opengl.org//) - A cross-language, cross-platform application programming interface (API) for rendering 2D and 3D vector graphics. +* [SDL](https://www.libsdl.org/) - Simple DirectMedia Layer to interact with hardware via OpenGL. +* [DevIL](http://openil.sourceforge.net/about.php) - A programmer's library to develop applications with very powerful image loading capabilities. +* [Assimp](https://www.assimp.org/) - portable Open-Source library to import various well-known 3D model formats in a uniform manner. +* [ImGui](https://github.com/ocornut/imgui) - A bloat-free graphical user interface library for C++. +* [MathGeoLib](https://github.com/juj/MathGeoLib) - A library for linear algebra and geometry manipulation for computer graphics. +* [PhysicsFS](https://github.com/icculus/physfs) - A portable, flexible file i/o abstraction. +* [ImGuizmo](https://github.com/CedricGuillemet/ImGuizmo) - Colletion of useful ImGui widgets. +* [ImGuiFileDialog](https://github.com/aiekick/ImGuiFileDialog) - File selection dialog built in ImGui. +* [RapidJSON](https://github.com/Tencent/rapidjson/) - A fast JSON parser/generator for C++. +* [Optik Profiler](https://github.com/bombomby/optick) - A super-lightweight C++ profiler for Games. + +## License 📄 +This project is under the MIT License [LICENSE](LICENSE). diff --git a/Screenshots/example.PNG b/Screenshots/example.PNG new file mode 100644 index 000000000..ceff6f64c Binary files /dev/null and b/Screenshots/example.PNG differ diff --git a/Screenshots/guizmo.PNG b/Screenshots/guizmo.PNG new file mode 100644 index 000000000..b293346fe Binary files /dev/null and b/Screenshots/guizmo.PNG differ diff --git a/Screenshots/hierarchy.PNG b/Screenshots/hierarchy.PNG new file mode 100644 index 000000000..1fcb0c842 Binary files /dev/null and b/Screenshots/hierarchy.PNG differ diff --git a/Screenshots/inspector.PNG b/Screenshots/inspector.PNG new file mode 100644 index 000000000..fd517af7f Binary files /dev/null and b/Screenshots/inspector.PNG differ diff --git a/Screenshots/load_menu.PNG b/Screenshots/load_menu.PNG new file mode 100644 index 000000000..da8e4b02f Binary files /dev/null and b/Screenshots/load_menu.PNG differ diff --git a/Screenshots/scene_config.PNG b/Screenshots/scene_config.PNG new file mode 100644 index 000000000..49b783b73 Binary files /dev/null and b/Screenshots/scene_config.PNG differ diff --git a/Screenshots/stencil.PNG b/Screenshots/stencil.PNG new file mode 100644 index 000000000..9d097ca39 Binary files /dev/null and b/Screenshots/stencil.PNG differ diff --git a/Screenshots/timers.PNG b/Screenshots/timers.PNG new file mode 100644 index 000000000..76d5d2622 Binary files /dev/null and b/Screenshots/timers.PNG differ diff --git a/Source/.clang-format b/Source/.clang-format new file mode 100644 index 000000000..ab98c160e --- /dev/null +++ b/Source/.clang-format @@ -0,0 +1,104 @@ +# Formatting file. Use [Ctrl + K -> Ctrl + D] to format a file +# Using Clang Format version 10.0 + +BasedOnStyle: LLVM + +AccessModifierOffset: -4 + +AlignAfterOpenBracket: AlwaysBreak +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignConsecutiveMacros: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: false + +AllowAllArgumentsOnNextLine: false +AllowAllConstructorInitializersOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false + +AllowShortBlocksOnASingleLine: Empty +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: WithoutElse +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: true + +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: No + +BinPackArguments: false +BinPackParameters: false + +BreakBeforeBraces: Custom +BraceWrapping: + AfterCaseLabel: true + AfterClass: true + AfterControlStatement: Always + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterStruct: true + AfterUnion: true + AfterExternBlock: true + BeforeCatch: true + BeforeElse: true + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false + +BreakBeforeBinaryOperators: All +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeComma +BreakInheritanceList: BeforeComma +BreakStringLiterals: true + +ColumnLimit: 0 +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true + +IndentCaseLabels: false +IndentGotoLabels: false +IndentPPDirectives: None +IndentWidth: 4 +IndentWrappedFunctionNames: true + +KeepEmptyLinesAtTheStartOfBlocks: false +Language: Cpp +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: All +PointerAlignment: Left +ReflowComments: false + +SortIncludes: false +SortUsingDeclarations: false + +SpaceAfterCStyleCast: true +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false + +Standard: Latest +TabWidth: 4 +UseCRLF: false +UseTab: Always \ No newline at end of file diff --git a/Source/.gitattributes b/Source/.gitattributes index 1ff0c4230..9f571d7dc 100644 --- a/Source/.gitattributes +++ b/Source/.gitattributes @@ -61,3 +61,6 @@ #*.PDF diff=astextplain #*.rtf diff=astextplain #*.RTF diff=astextplain + +# ImGui +Game/imgui.ini \ No newline at end of file diff --git a/Source/.vscode/settings.json b/Source/.vscode/settings.json new file mode 100644 index 000000000..dbc3757ba --- /dev/null +++ b/Source/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "files.associations": { + "vector": "cpp", + "xstring": "cpp" + } +} diff --git a/Source/Application.cpp b/Source/Application.cpp deleted file mode 100644 index 35a3727af..000000000 --- a/Source/Application.cpp +++ /dev/null @@ -1,114 +0,0 @@ -#pragma once -#include "Application.h" -#include "ImGuiComponents/AppLog.h" -#include "ModuleWindow.h" -#include "ModuleRender.h" -#include "ModuleInput.h" -#include "ModuleEditor.h" -#include "ModuleCamera.h" -#include "ModuleTexture.h" -#include "ModuleProgram.h" -#include "ModuleHardware.h" -#include "ModuleScene.h" -#include "ModuleDebugDraw.h" - -using namespace std; - -Application::Application(): frames(0), last_frame_ms(-1), last_fps(-1), capped_ms(1000 / 60) -{ - modules.push_back(window = new ModuleWindow()); - modules.push_back(input = new ModuleInput()); - modules.push_back(camera = new ModuleCamera()); - modules.push_back(texture = new ModuleTexture()); - modules.push_back(renderer = new ModuleRender()); - modules.push_back(hw = new ModuleHardware()); - modules.push_back(scene = new ModuleScene()); - modules.push_back(program = new ModuleProgram()); - modules.push_back(debug_draw = new ModuleDebugDraw()); - modules.push_back(editor = new ModuleEditor()); -} - -Application::~Application() -{ - for(vector::iterator it = modules.begin(); it != modules.end(); ++it) - RELEASE(*it) -} - -bool Application::Init() -{ - bool ret = true; - - for(vector::iterator it = modules.begin(); it != modules.end() && ret; ++it) - ret = (*it)->Init(); - - return ret; -} - -update_status Application::Update() -{ - delta_time = (float)ms_timer.Read() / 1000.0f; - ms_timer.Start(); - - update_status ret = UPDATE_CONTINUE; - - for(vector::iterator it = modules.begin(); it != modules.end() && ret == UPDATE_CONTINUE; ++it) - ret = (*it)->PreUpdate(); - - for(vector::iterator it = modules.begin(); it != modules.end() && ret == UPDATE_CONTINUE; ++it) - ret = (*it)->Update(); - - for(vector::iterator it = modules.begin(); it != modules.end() && ret == UPDATE_CONTINUE; ++it) - ret = (*it)->PostUpdate(); - - FinishUpdate(); - return ret; -} - -void Application::FinishUpdate() -{ - ++frames; - ++fps_counter; - - if (fps_timer.Read() >= 1000) - { - last_fps = fps_counter; - fps_counter = 0; - fps_timer.Start(); - } - - last_frame_ms = ms_timer.Read(); - - if (capped_ms > 0 && (last_frame_ms < capped_ms)) - SDL_Delay(capped_ms - last_frame_ms); - - editor->AddFPS((float)last_fps, (float)last_frame_ms); -} - -bool Application::CleanUp() -{ - bool ret = true; - - for(vector::reverse_iterator it = modules.rbegin(); it != modules.rend() && ret; ++it) - ret = (*it)->CleanUp(); - - return ret; -} - -void Application::DebugDraw() -{ - for (vector::iterator it = modules.begin(); it != modules.end(); ++it) - (*it)->DrawDebug(); -} - -void Application::RequestBrowser(const char* url) const -{ - ShellExecuteA(NULL, "open", url, NULL, NULL, SW_SHOWNORMAL); -} - -int Application::GetFramerateLimit() const -{ - if (capped_ms > 0) - return (int)((1.0f / (float)capped_ms) * 1000.0f); - else - return 0; -} diff --git a/Source/Application.h b/Source/Application.h deleted file mode 100644 index f4a77c693..000000000 --- a/Source/Application.h +++ /dev/null @@ -1,75 +0,0 @@ -#pragma once - -#include -#include "Globals.h" -#include "Module.h" -#include -#include "Timer.h" - -class ModuleRender; -class ModuleWindow; -class ModuleTextures; -class ModuleInput; -class ModuleRenderExercise; -class ModuleEditor; -class ModuleDebugDraw; -class ModuleCamera; -class ModuleTexture; -class ModuleScene; -class ModuleProgram; -class ModuleHardware; - -class Program; -class AppLog; - -class Application -{ - public: - - Application(); - ~Application(); - - bool Init(); - update_status Update(); - - void FinishUpdate(); - bool CleanUp(); - - void DebugDraw(); - - void RequestBrowser(const char* url) const; - inline float GetDeltaTime() { return delta_time; }; - - int GetFramerateLimit() const; - - - public: - ModuleRender* renderer = nullptr; - ModuleWindow* window = nullptr; - ModuleInput* input = nullptr; - ModuleEditor* editor = nullptr; - ModuleDebugDraw* debug_draw = nullptr; - ModuleCamera* camera = nullptr; - ModuleTexture* texture = nullptr; - ModuleScene* scene = nullptr; - ModuleProgram* program = nullptr; - ModuleHardware* hw = nullptr; - - AppLog* console = nullptr; - - private: - - Timer ms_timer; - Timer fps_timer; - - int frames; - int capped_ms; - int fps_counter; - int last_frame_ms; - int last_fps; - float delta_time = 0.0f; - - std::vector modules; -}; - -extern Application* App; diff --git a/Source/Component.cpp b/Source/Component.cpp deleted file mode 100644 index 006f10e96..000000000 --- a/Source/Component.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include "Component.h" - -Component::Component(Type type, GameObject*): type(type), gameObject(gameObject) -{ -} - -Component::~Component() -{ -} diff --git a/Source/Component.h b/Source/Component.h deleted file mode 100644 index ebb89a3b8..000000000 --- a/Source/Component.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include "GameObject.h"; - -class Component -{ - public: - enum Type - { - None, - Transform, - Mesh, - Texture, - Camera, - }; - - Component(Type type, GameObject*); - virtual ~Component(); - - GameObject* gameObject = nullptr; - - protected: - bool active = true; - Type type = None; -}; - diff --git a/Source/Dependencies/ImGuiFileDialog-0.6.3/ImGuiFileDialog.cpp b/Source/Dependencies/ImGuiFileDialog-0.6.3/ImGuiFileDialog.cpp new file mode 100644 index 000000000..a5fe3f6c7 --- /dev/null +++ b/Source/Dependencies/ImGuiFileDialog-0.6.3/ImGuiFileDialog.cpp @@ -0,0 +1,5053 @@ +// This is an independent project of an individual developer. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +/* +MIT License + +Copyright (c) 2019-2020 Stephane Cuillerdier (aka aiekick) + +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. +*/ + +#include "ImGuiFileDialog.h" + +#ifdef __cplusplus + +#include +#include // stricmp / strcasecmp +#include // variadic +#include +#include +#include +#include +#include +// this option need c++17 +#ifdef USE_STD_FILESYSTEM + #include +#endif +#if defined (__EMSCRIPTEN__) // EMSCRIPTEN + #include +#endif // EMSCRIPTEN +#if defined(__WIN32__) || defined(_WIN32) + #ifndef WIN32 + #define WIN32 + #endif // WIN32 + #define stat _stat + #define stricmp strcmp // _stricmp + #include + // this option need c++17 + #ifdef USE_STD_FILESYSTEM + #include + #else + #include "dirent/dirent.h" // directly open the dirent file attached to this lib + #endif // USE_STD_FILESYSTEM + #define PATH_SEP '\\' + #ifndef PATH_MAX + #define PATH_MAX 260 + #endif // PATH_MAX +#elif defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__) || defined (__EMSCRIPTEN__) + #define UNIX + #define stricmp strcasecmp + #include + // this option need c++17 + #ifndef USE_STD_FILESYSTEM + #include + #endif // USE_STD_FILESYSTEM + #define PATH_SEP '/' +#endif // defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__) + +#include "imgui.h" +#ifndef IMGUI_DEFINE_MATH_OPERATORS + #define IMGUI_DEFINE_MATH_OPERATORS +#endif // IMGUI_DEFINE_MATH_OPERATORS +#include "imgui_internal.h" + +#include +#include +#include + +#ifdef USE_THUMBNAILS +#ifndef DONT_DEFINE_AGAIN__STB_IMAGE_IMPLEMENTATION +#ifndef STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_IMPLEMENTATION +#endif // STB_IMAGE_IMPLEMENTATION +#endif // DONT_DEFINE_AGAIN__STB_IMAGE_IMPLEMENTATION +#include "stb/stb_image.h" +#ifndef DONT_DEFINE_AGAIN__STB_IMAGE_RESIZE_IMPLEMENTATION +#ifndef STB_IMAGE_RESIZE_IMPLEMENTATION +#define STB_IMAGE_RESIZE_IMPLEMENTATION +#endif // STB_IMAGE_RESIZE_IMPLEMENTATION +#endif // DONT_DEFINE_AGAIN__STB_IMAGE_RESIZE_IMPLEMENTATION +#include "stb/stb_image_resize.h" +#endif // USE_THUMBNAILS + +namespace IGFD +{ +// float comparisons +#ifndef IS_FLOAT_DIFFERENT +#define IS_FLOAT_DIFFERENT(a,b) (fabs((a) - (b)) > FLT_EPSILON) +#endif // IS_FLOAT_DIFFERENT +#ifndef IS_FLOAT_EQUAL +#define IS_FLOAT_EQUAL(a,b) (fabs((a) - (b)) < FLT_EPSILON) +#endif // IS_FLOAT_EQUAL +// width of filter combobox +#ifndef FILTER_COMBO_WIDTH +#define FILTER_COMBO_WIDTH 150.0f +#endif // FILTER_COMBO_WIDTH +// for lets you define your button widget +// if you have like me a special bi-color button +#ifndef IMGUI_PATH_BUTTON +#define IMGUI_PATH_BUTTON ImGui::Button +#endif // IMGUI_PATH_BUTTON +#ifndef IMGUI_BUTTON +#define IMGUI_BUTTON ImGui::Button +#endif // IMGUI_BUTTON +// locales +#ifndef createDirButtonString +#define createDirButtonString "+" +#endif // createDirButtonString +#ifndef okButtonString +#define okButtonString "OK" +#endif // okButtonString +#ifndef cancelButtonString +#define cancelButtonString "Cancel" +#endif // cancelButtonString +#ifndef resetButtonString +#define resetButtonString "R" +#endif // resetButtonString +#ifndef drivesButtonString +#define drivesButtonString "Drives" +#endif // drivesButtonString +#ifndef editPathButtonString +#define editPathButtonString "E" +#endif // editPathButtonString +#ifndef searchString +#define searchString "Search :" +#endif // searchString +#ifndef dirEntryString +#define dirEntryString "[Dir]" +#endif // dirEntryString +#ifndef linkEntryString +#define linkEntryString "[Link]" +#endif // linkEntryString +#ifndef fileEntryString +#define fileEntryString "[File]" +#endif // fileEntryString +#ifndef fileNameString +#define fileNameString "File Name :" +#endif // fileNameString +#ifndef dirNameString +#define dirNameString "Directory Path :" +#endif // dirNameString +#ifndef buttonResetSearchString +#define buttonResetSearchString "Reset search" +#endif // buttonResetSearchString +#ifndef buttonDriveString +#define buttonDriveString "Drives" +#endif // buttonDriveString +#ifndef buttonEditPathString +#define buttonEditPathString "Edit path\nYou can also right click on path buttons" +#endif // buttonEditPathString +#ifndef buttonResetPathString +#define buttonResetPathString "Reset to current directory" +#endif // buttonResetPathString +#ifndef buttonCreateDirString +#define buttonCreateDirString "Create Directory" +#endif // buttonCreateDirString +#ifndef tableHeaderAscendingIcon +#define tableHeaderAscendingIcon "A|" +#endif // tableHeaderAscendingIcon +#ifndef tableHeaderDescendingIcon +#define tableHeaderDescendingIcon "D|" +#endif // tableHeaderDescendingIcon +#ifndef tableHeaderFileNameString +#define tableHeaderFileNameString "File name" +#endif // tableHeaderFileNameString +#ifndef tableHeaderFileTypeString +#define tableHeaderFileTypeString "Type" +#endif // tableHeaderFileTypeString +#ifndef tableHeaderFileSizeString +#define tableHeaderFileSizeString "Size" +#endif // tableHeaderFileSizeString +#ifndef tableHeaderFileDateString +#define tableHeaderFileDateString "Date" +#endif // tableHeaderFileDateString +#ifndef OverWriteDialogTitleString +#define OverWriteDialogTitleString "The file Already Exist !" +#endif // OverWriteDialogTitleString +#ifndef OverWriteDialogMessageString +#define OverWriteDialogMessageString "Would you like to OverWrite it ?" +#endif // OverWriteDialogMessageString +#ifndef OverWriteDialogConfirmButtonString +#define OverWriteDialogConfirmButtonString "Confirm" +#endif // OverWriteDialogConfirmButtonString +#ifndef OverWriteDialogCancelButtonString +#define OverWriteDialogCancelButtonString "Cancel" +#endif // OverWriteDialogCancelButtonString +// see strftime functionin for customize +#ifndef DateTimeFormat +#define DateTimeFormat "%Y/%m/%d %H:%M" +#endif // DateTimeFormat +#ifdef USE_THUMBNAILS +#ifndef tableHeaderFileThumbnailsString +#define tableHeaderFileThumbnailsString "Thumbnails" +#endif // tableHeaderFileThumbnailsString +#ifndef DisplayMode_FilesList_ButtonString +#define DisplayMode_FilesList_ButtonString "FL" +#endif // DisplayMode_FilesList_ButtonString +#ifndef DisplayMode_FilesList_ButtonHelp +#define DisplayMode_FilesList_ButtonHelp "File List" +#endif // DisplayMode_FilesList_ButtonHelp +#ifndef DisplayMode_ThumbailsList_ButtonString +#define DisplayMode_ThumbailsList_ButtonString "TL" +#endif // DisplayMode_ThumbailsList_ButtonString +#ifndef DisplayMode_ThumbailsList_ButtonHelp +#define DisplayMode_ThumbailsList_ButtonHelp "Thumbnails List" +#endif // DisplayMode_ThumbailsList_ButtonHelp +#ifndef DisplayMode_ThumbailsGrid_ButtonString +#define DisplayMode_ThumbailsGrid_ButtonString "TG" +#endif // DisplayMode_ThumbailsGrid_ButtonString +#ifndef DisplayMode_ThumbailsGrid_ButtonHelp +#define DisplayMode_ThumbailsGrid_ButtonHelp "Thumbnails Grid" +#endif // DisplayMode_ThumbailsGrid_ButtonHelp +#ifndef DisplayMode_ThumbailsList_ImageHeight +#define DisplayMode_ThumbailsList_ImageHeight 32.0f +#endif // DisplayMode_ThumbailsList_ImageHeight +#ifndef IMGUI_RADIO_BUTTON + inline bool inRadioButton(const char* vLabel, bool vToggled) + { + bool pressed = false; + + if (vToggled) + { + ImVec4 bua = ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive); + ImVec4 te = ImGui::GetStyleColorVec4(ImGuiCol_Text); + ImGui::PushStyleColor(ImGuiCol_Button, te); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, te); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, te); + ImGui::PushStyleColor(ImGuiCol_Text, bua); + } + + pressed = IMGUI_BUTTON(vLabel); + + if (vToggled) + { + ImGui::PopStyleColor(4); //-V112 + } + + return pressed; + } +#define IMGUI_RADIO_BUTTON inRadioButton +#endif // IMGUI_RADIO_BUTTON +#endif // USE_THUMBNAILS +#ifdef USE_BOOKMARK +#ifndef defaultBookmarkPaneWith +#define defaultBookmarkPaneWith 150.0f +#endif // defaultBookmarkPaneWith +#ifndef bookmarksButtonString +#define bookmarksButtonString "Bookmark" +#endif // bookmarksButtonString +#ifndef bookmarksButtonHelpString +#define bookmarksButtonHelpString "Bookmark" +#endif // bookmarksButtonHelpString +#ifndef addBookmarkButtonString +#define addBookmarkButtonString "+" +#endif // addBookmarkButtonString +#ifndef removeBookmarkButtonString +#define removeBookmarkButtonString "-" +#endif // removeBookmarkButtonString +#ifndef IMGUI_TOGGLE_BUTTON + inline bool inToggleButton(const char* vLabel, bool* vToggled) + { + bool pressed = false; + + if (vToggled && *vToggled) + { + ImVec4 bua = ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive); + //ImVec4 buh = ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered); + //ImVec4 bu = ImGui::GetStyleColorVec4(ImGuiCol_Button); + ImVec4 te = ImGui::GetStyleColorVec4(ImGuiCol_Text); + ImGui::PushStyleColor(ImGuiCol_Button, te); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, te); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, te); + ImGui::PushStyleColor(ImGuiCol_Text, bua); + } + + pressed = IMGUI_BUTTON(vLabel); + + if (vToggled && *vToggled) + { + ImGui::PopStyleColor(4); //-V112 + } + + if (vToggled && pressed) + *vToggled = !*vToggled; + + return pressed; + } +#define IMGUI_TOGGLE_BUTTON inToggleButton +#endif // IMGUI_TOGGLE_BUTTON +#endif // USE_BOOKMARK + + ///////////////////////////////////////////////////////////////////////////////////// + //// INLINE FUNCTIONS /////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// + +#ifndef USE_STD_FILESYSTEM + inline int inAlphaSort(const struct dirent** a, const struct dirent** b) + { + return strcoll((*a)->d_name, (*b)->d_name); + } +#endif + + ///////////////////////////////////////////////////////////////////////////////////// + //// FILE EXTENTIONS INFOS ////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// + + IGFD::FileStyle::FileStyle() + : color(0, 0, 0, 0) + { + + } + + IGFD::FileStyle::FileStyle(const FileStyle& vStyle) + { + color = vStyle.color; + icon = vStyle.icon; + font = vStyle.font; + flags = vStyle.flags; + } + + IGFD::FileStyle::FileStyle(const ImVec4& vColor, const std::string& vIcon, ImFont* vFont) + : color(vColor), icon(vIcon), font(vFont) + { + + } + + ///////////////////////////////////////////////////////////////////////////////////// + //// FILE INFOS ///////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// + + // https://github.com/ocornut/imgui/issues/1720 + bool IGFD::Utils::Splitter(bool split_vertically, float thickness, float* size1, float* size2, float min_size1, float min_size2, float splitter_long_axis_size) + { + using namespace ImGui; + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiID id = window->GetID("##Splitter"); + ImRect bb; + bb.Min = window->DC.CursorPos + (split_vertically ? ImVec2(*size1, 0.0f) : ImVec2(0.0f, *size1)); + bb.Max = bb.Min + CalcItemSize(split_vertically ? ImVec2(thickness, splitter_long_axis_size) : ImVec2(splitter_long_axis_size, thickness), 0.0f, 0.0f); + return SplitterBehavior(bb, id, split_vertically ? ImGuiAxis_X : ImGuiAxis_Y, size1, size2, min_size1, min_size2, 1.0f); + } + +#ifdef WIN32 + bool IGFD::Utils::WReplaceString(std::wstring& str, const std::wstring& oldStr, const std::wstring& newStr) + { + bool found = false; + size_t pos = 0; + while ((pos = str.find(oldStr, pos)) != std::wstring::npos) + { + found = true; + str.replace(pos, oldStr.length(), newStr); + pos += newStr.length(); + } + return found; + } + + std::vector IGFD::Utils::WSplitStringToVector(const std::wstring& text, char delimiter, bool pushEmpty) + { + std::vector arr; + if (!text.empty()) + { + std::wstring::size_type start = 0; + std::wstring::size_type end = text.find(delimiter, start); + while (end != std::wstring::npos) + { + std::wstring token = text.substr(start, end - start); + if (!token.empty() || (token.empty() && pushEmpty)) //-V728 + arr.push_back(token); + start = end + 1; + end = text.find(delimiter, start); + } + std::wstring token = text.substr(start); + if (!token.empty() || (token.empty() && pushEmpty)) //-V728 + arr.push_back(token); + } + return arr; + } + + std::wstring IGFD::Utils::string_to_wstring(const std::string& str) + { + std::wstring ret; + if (!str.empty()) + { + size_t sz = std::mbstowcs(nullptr, str.c_str(), str.size()); + if (sz) + { + ret.resize(sz); + std::mbstowcs((wchar_t*)ret.data(), str.c_str(), sz); + } + } + return ret; + } + + std::string IGFD::Utils::wstring_to_string(const std::wstring& str) + { + std::string ret; + if (!str.empty()) + { + size_t sz = std::wcstombs(nullptr, str.c_str(), str.size()); + if (sz) + { + ret.resize(sz); + std::wcstombs((char*)ret.data(), str.c_str(), sz); + } + } + return ret; + } +#endif // WIN32 + + bool IGFD::Utils::ReplaceString(std::string& str, const std::string& oldStr, const std::string& newStr) + { + bool found = false; + size_t pos = 0; + while ((pos = str.find(oldStr, pos)) != std::string::npos) + { + found = true; + str.replace(pos, oldStr.length(), newStr); + pos += newStr.length(); + } + return found; + } + + std::vector IGFD::Utils::SplitStringToVector(const std::string& text, char delimiter, bool pushEmpty) + { + std::vector arr; + if (!text.empty()) + { + size_t start = 0; + size_t end = text.find(delimiter, start); + while (end != std::string::npos) + { + auto token = text.substr(start, end - start); + if (!token.empty() || (token.empty() && pushEmpty)) //-V728 + arr.push_back(token); + start = end + 1; + end = text.find(delimiter, start); + } + auto token = text.substr(start); + if (!token.empty() || (token.empty() && pushEmpty)) //-V728 + arr.push_back(token); + } + return arr; + } + + std::vector IGFD::Utils::GetDrivesList() + { + std::vector res; + +#ifdef WIN32 + const DWORD mydrives = 2048; + char lpBuffer[2048]; +#define mini(a,b) (((a) < (b)) ? (a) : (b)) + const DWORD countChars = mini(GetLogicalDriveStringsA(mydrives, lpBuffer), 2047); +#undef mini + if (countChars > 0) + { + std::string var = std::string(lpBuffer, (size_t)countChars); + IGFD::Utils::ReplaceString(var, "\\", ""); + res = IGFD::Utils::SplitStringToVector(var, '\0', false); + } +#endif // WIN32 + + return res; + } + + bool IGFD::Utils::IsDirectoryExist(const std::string& name) + { + bool bExists = false; + + if (!name.empty()) + { +#ifdef USE_STD_FILESYSTEM + namespace fs = std::filesystem; +#ifdef WIN32 + std::wstring wname = IGFD::Utils::string_to_wstring(name.c_str()); + fs::path pathName = fs::path(wname); +#else + fs::path pathName = fs::path(name); +#endif + bExists = fs::is_directory(pathName); +#else + DIR* pDir = nullptr; + pDir = opendir(name.c_str()); + if (pDir != nullptr) + { + bExists = true; + (void)closedir(pDir); + } +#endif // USE_STD_FILESYSTEM + } + + return bExists; // this is not a directory! + } + + bool IGFD::Utils::CreateDirectoryIfNotExist(const std::string& name) + { + bool res = false; + + if (!name.empty()) + { + if (!IsDirectoryExist(name)) + { +#ifdef WIN32 +#ifdef USE_STD_FILESYSTEM + namespace fs = std::filesystem; + std::wstring wname = IGFD::Utils::string_to_wstring(name.c_str()); + fs::path pathName = fs::path(wname); + res = fs::create_directory(pathName); +#else + std::wstring wname = IGFD::Utils::string_to_wstring(name); + if (CreateDirectoryW(wname.c_str(), nullptr)) + { + res = true; + } +#endif // USE_STD_FILESYSTEM +#elif defined(__EMSCRIPTEN__) + std::string str = std::string("FS.mkdir('") + name + "');"; + emscripten_run_script(str.c_str()); + res = true; +#elif defined(UNIX) + char buffer[PATH_MAX] = {}; + snprintf(buffer, PATH_MAX, "mkdir -p %s", name.c_str()); + const int dir_err = std::system(buffer); + if (dir_err != -1) + { + res = true; + } +#endif // WIN32 + if (!res) { + std::cout << "Error creating directory " << name << std::endl; + } + } + } + + return res; + } + +#ifdef USE_STD_FILESYSTEM + // https://github.com/aiekick/ImGuiFileDialog/issues/54 + IGFD::Utils::PathStruct IGFD::Utils::ParsePathFileName(const std::string& vPathFileName) + { + namespace fs = std::filesystem; + PathStruct res; + if (vPathFileName.empty()) + return res; + + auto fsPath = fs::path(vPathFileName); + + if (fs::is_regular_file(fsPath)) { + res.name = fsPath.string(); + res.path = fsPath.parent_path().string(); + res.isOk = true; + } + + return res; + } +#else + IGFD::Utils::PathStruct IGFD::Utils::ParsePathFileName(const std::string& vPathFileName) + { + PathStruct res; + + if (!vPathFileName.empty()) + { + std::string pfn = vPathFileName; + std::string separator(1u, PATH_SEP); + IGFD::Utils::ReplaceString(pfn, "\\", separator); + IGFD::Utils::ReplaceString(pfn, "/", separator); + + size_t lastSlash = pfn.find_last_of(separator); + if (lastSlash != std::string::npos) + { + res.name = pfn.substr(lastSlash + 1); + res.path = pfn.substr(0, lastSlash); + res.isOk = true; + } + + size_t lastPoint = pfn.find_last_of('.'); + if (lastPoint != std::string::npos) + { + if (!res.isOk) + { + res.name = pfn; + res.isOk = true; + } + res.ext = pfn.substr(lastPoint + 1); + IGFD::Utils::ReplaceString(res.name, "." + res.ext, ""); + } + + if (!res.isOk) + { + res.name = std::move(pfn); + res.isOk = true; + } + } + + return res; + } +#endif // USE_STD_FILESYSTEM + void IGFD::Utils::AppendToBuffer(char* vBuffer, size_t vBufferLen, const std::string& vStr) + { + std::string st = vStr; + size_t len = vBufferLen - 1u; + size_t slen = strlen(vBuffer); + + if (!st.empty() && st != "\n") + { + IGFD::Utils::ReplaceString(st, "\n", ""); + IGFD::Utils::ReplaceString(st, "\r", ""); + } + vBuffer[slen] = '\0'; + std::string str = std::string(vBuffer); + //if (!str.empty()) str += "\n"; + str += vStr; + if (len > str.size()) len = str.size(); +#ifdef MSVC + strncpy_s(vBuffer, vBufferLen, str.c_str(), len); +#else // MSVC + strncpy(vBuffer, str.c_str(), len); +#endif // MSVC + vBuffer[len] = '\0'; + } + + void IGFD::Utils::ResetBuffer(char* vBuffer) + { + vBuffer[0] = '\0'; + } + + void IGFD::Utils::SetBuffer(char* vBuffer, size_t vBufferLen, const std::string& vStr) + { + ResetBuffer(vBuffer); + AppendToBuffer(vBuffer, vBufferLen, vStr); + } + + ///////////////////////////////////////////////////////////////////////////////////// + //// FILE INFOS ///////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// + + bool IGFD::FileInfos::IsTagFound(const std::string& vTag) const + { + if (!vTag.empty()) + { + if (fileNameExt_optimized == "..") return true; + + return + fileNameExt_optimized.find(vTag) != std::string::npos || // first try wihtout case and accents + fileNameExt.find(vTag) != std::string::npos; // second if searched with case and accents + } + + // if tag is empty => its a special case but all is found + return true; + } + + ///////////////////////////////////////////////////////////////////////////////////// + //// SEARCH MANAGER ///////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// + + void IGFD::SearchManager::Clear() + { + puSearchTag.clear(); + IGFD::Utils::ResetBuffer(puSearchBuffer); + } + + void IGFD::SearchManager::DrawSearchBar(FileDialogInternal& vFileDialogInternal) + { + // search field + if (IMGUI_BUTTON(resetButtonString "##BtnImGuiFileDialogSearchField")) + { + Clear(); + vFileDialogInternal.puFileManager.ApplyFilteringOnFileList(vFileDialogInternal); + } + if (ImGui::IsItemHovered()) + ImGui::SetTooltip(buttonResetSearchString); + ImGui::SameLine(); + ImGui::Text(searchString); + ImGui::SameLine(); + ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); + bool edited = ImGui::InputText("##InputImGuiFileDialogSearchField", puSearchBuffer, MAX_FILE_DIALOG_NAME_BUFFER); + if (ImGui::GetItemID() == ImGui::GetActiveID()) + puSearchInputIsActive = true; + ImGui::PopItemWidth(); + if (edited) + { + puSearchTag = puSearchBuffer; + vFileDialogInternal.puFileManager.ApplyFilteringOnFileList(vFileDialogInternal); + } + } + + ///////////////////////////////////////////////////////////////////////////////////// + //// FILTER INFOS /////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// + + void IGFD::FilterManager::FilterInfosStruct::clear() + { + filter.clear(); + collectionfilters.clear(); + } + + bool IGFD::FilterManager::FilterInfosStruct::empty() const + { + return filter.empty() && collectionfilters.empty(); + } + + bool IGFD::FilterManager::FilterInfosStruct::exist(const std::string& vFilter) const + { + return filter == vFilter || (collectionfilters.find(vFilter) != collectionfilters.end()); + } + + ///////////////////////////////////////////////////////////////////////////////////// + //// FILTER MANAGER ///////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// + + void IGFD::FilterManager::ParseFilters(const char* vFilters) + { + prParsedFilters.clear(); + + if (vFilters) + puDLGFilters = vFilters; // file mode + else + puDLGFilters.clear(); // directory mode + + if (!puDLGFilters.empty()) + { + // ".*,.cpp,.h,.hpp" + // "Source files{.cpp,.h,.hpp},Image files{.png,.gif,.jpg,.jpeg},.md" + + bool currentFilterFound = false; + + size_t nan = std::string::npos; + size_t p = 0, lp = 0; + while ((p = puDLGFilters.find_first_of("{,", p)) != nan) + { + FilterInfosStruct infos; + + if (puDLGFilters[p] == '{') // { + { + infos.filter = puDLGFilters.substr(lp, p - lp); + p++; + lp = puDLGFilters.find('}', p); + if (lp != nan) + { + std::string fs = puDLGFilters.substr(p, lp - p); + auto arr = IGFD::Utils::SplitStringToVector(fs, ',', false); + for (auto a : arr) + { + infos.collectionfilters.emplace(a); + } + } + p = lp + 1; + } + else // , + { + infos.filter = puDLGFilters.substr(lp, p - lp); + p++; + } + + if (!currentFilterFound && prSelectedFilter.filter == infos.filter) + { + currentFilterFound = true; + prSelectedFilter = infos; + } + + lp = p; + if (!infos.empty()) + prParsedFilters.emplace_back(infos); + } + + std::string token = puDLGFilters.substr(lp); + if (!token.empty()) + { + FilterInfosStruct infos; + infos.filter = std::move(token); + prParsedFilters.emplace_back(infos); + } + + if (!currentFilterFound) + if (!prParsedFilters.empty()) + prSelectedFilter = *prParsedFilters.begin(); + } + } + + void IGFD::FilterManager::SetSelectedFilterWithExt(const std::string& vFilter) + { + if (!prParsedFilters.empty()) + { + if (!vFilter.empty()) + { + // std::map + for (const auto& infos : prParsedFilters) + { + if (vFilter == infos.filter) + { + prSelectedFilter = infos; + } + else + { + // maybe this ext is in an extention so we will + // explore the collections is they are existing + for (const auto& filter : infos.collectionfilters) + { + if (vFilter == filter) + { + prSelectedFilter = infos; + } + } + } + } + } + + if (prSelectedFilter.empty()) + prSelectedFilter = *prParsedFilters.begin(); + } + } + + void IGFD::FilterManager::SetFileStyle(const IGFD_FileStyleFlags& vFlags, const char* vCriteria, const FileStyle& vInfos) + { + std::string _criteria; + if (vCriteria) + _criteria = std::string(vCriteria); + prFilesStyle[vFlags][_criteria] = std::make_shared(vInfos); + prFilesStyle[vFlags][_criteria]->flags = vFlags; + } + + // will be called internally + // will not been exposed to IGFD API + bool IGFD::FilterManager::prFillFileStyle(std::shared_ptr vFileInfos) const + { + if (vFileInfos.use_count() && !prFilesStyle.empty()) + { + for (const auto& _flag : prFilesStyle) + { + for (const auto& _file : _flag.second) + { + if (_flag.first & IGFD_FileStyleByTypeDir && vFileInfos->fileType == 'd') + { + if (_file.first.empty()) // for all dirs + { + vFileInfos->fileStyle = _file.second; + } + else if (_file.first == vFileInfos->fileNameExt) // for dirs who are equal to style criteria + { + vFileInfos->fileStyle = _file.second; + } + } + else if (_flag.first & IGFD_FileStyleByTypeFile && vFileInfos->fileType == 'f') + { + if (_file.first.empty()) // for all files + { + vFileInfos->fileStyle = _file.second; + } + else if (_file.first == vFileInfos->fileNameExt) // for files who are equal to style criteria + { + vFileInfos->fileStyle = _file.second; + } + } + else if (_flag.first & IGFD_FileStyleByTypeLink && vFileInfos->fileType == 'l') + { + if (_file.first.empty()) // for all links + { + vFileInfos->fileStyle = _file.second; + } + else if (_file.first == vFileInfos->fileNameExt) // for links who are equal to style criteria + { + vFileInfos->fileStyle = _file.second; + } + } + + if (_flag.first & IGFD_FileStyleByExtention) + { + if (_file.first == vFileInfos->fileExt) + { + vFileInfos->fileStyle = _file.second; + } + + // can make sense for some dirs like the hidden by ex ".git" + if (_flag.first & IGFD_FileStyleByTypeDir && vFileInfos->fileType == 'd') + { + if (_file.first == vFileInfos->fileExt) + { + vFileInfos->fileStyle = _file.second; + } + } + else if (_flag.first & IGFD_FileStyleByTypeFile && vFileInfos->fileType == 'f') + { + if (_file.first == vFileInfos->fileExt) + { + vFileInfos->fileStyle = _file.second; + } + } + else if (_flag.first & IGFD_FileStyleByTypeLink && vFileInfos->fileType == 'l') + { + if (_file.first == vFileInfos->fileExt) + { + vFileInfos->fileStyle = _file.second; + } + } + } + if (_flag.first & IGFD_FileStyleByFullName) + { + if (_file.first == vFileInfos->fileNameExt) + { + vFileInfos->fileStyle = _file.second; + } + + if (_flag.first & IGFD_FileStyleByTypeDir && vFileInfos->fileType == 'd') + { + if (_file.first == vFileInfos->fileNameExt) + { + vFileInfos->fileStyle = _file.second; + } + } + else if (_flag.first & IGFD_FileStyleByTypeFile && vFileInfos->fileType == 'f') + { + if (_file.first == vFileInfos->fileNameExt) + { + vFileInfos->fileStyle = _file.second; + } + } + else if (_flag.first & IGFD_FileStyleByTypeLink && vFileInfos->fileType == 'l') + { + if (_file.first == vFileInfos->fileNameExt) + { + vFileInfos->fileStyle = _file.second; + } + } + } + if (_flag.first & IGFD_FileStyleByContainedInFullName) + { + if (vFileInfos->fileNameExt.find(_file.first) != std::string::npos) + { + vFileInfos->fileStyle = _file.second; + } + + if (_flag.first & IGFD_FileStyleByTypeDir && vFileInfos->fileType == 'd') + { + if (vFileInfos->fileNameExt.find(_file.first) != std::string::npos) + { + vFileInfos->fileStyle = _file.second; + } + } + else if (_flag.first & IGFD_FileStyleByTypeFile && vFileInfos->fileType == 'f') + { + if (vFileInfos->fileNameExt.find(_file.first) != std::string::npos) + { + vFileInfos->fileStyle = _file.second; + } + } + else if (_flag.first & IGFD_FileStyleByTypeLink && vFileInfos->fileType == 'l') + { + if (vFileInfos->fileNameExt.find(_file.first) != std::string::npos) + { + vFileInfos->fileStyle = _file.second; + } + } + } + + if (vFileInfos->fileStyle.use_count()) + return true; + } + } + } + + return false; + } + + void IGFD::FilterManager::SetFileStyle(const IGFD_FileStyleFlags& vFlags, const char* vCriteria, const ImVec4& vColor, const std::string& vIcon, ImFont* vFont) + { + std::string _criteria; + if (vCriteria) + _criteria = std::string(vCriteria); + prFilesStyle[vFlags][_criteria] = std::make_shared(vColor, vIcon, vFont); + prFilesStyle[vFlags][_criteria]->flags = vFlags; + } + + // todo : to refactor this fucking function + bool IGFD::FilterManager::GetFileStyle(const IGFD_FileStyleFlags& vFlags, const std::string& vCriteria, ImVec4* vOutColor, std::string* vOutIcon, ImFont **vOutFont) + { + if (vOutColor) + { + if (!prFilesStyle.empty()) + { + if (prFilesStyle.find(vFlags) != prFilesStyle.end()) // found + { + if (vFlags & IGFD_FileStyleByContainedInFullName) + { + // search for vCriteria who are containing the criteria + for (const auto& _file : prFilesStyle.at(vFlags)) + { + if (vCriteria.find(_file.first) != std::string::npos) + { + if (_file.second.use_count()) + { + *vOutColor = _file.second->color; + if (vOutIcon) + *vOutIcon = _file.second->icon; + if (vOutFont) + *vOutFont = _file.second->font; + return true; + } + } + } + } + else + { + if (prFilesStyle.at(vFlags).find(vCriteria) != prFilesStyle.at(vFlags).end()) // found + { + *vOutColor = prFilesStyle[vFlags][vCriteria]->color; + if (vOutIcon) + *vOutIcon = prFilesStyle[vFlags][vCriteria]->icon; + if (vOutFont) + *vOutFont = prFilesStyle[vFlags][vCriteria]->font; + return true; + } + } + } + else + { + // search for flag composition + for (const auto& _flag : prFilesStyle) + { + if (_flag.first & vFlags) + { + if (_flag.first & IGFD_FileStyleByContainedInFullName) + { + // search for vCriteria who are containing the criteria + for (const auto& _file : prFilesStyle.at(_flag.first)) + { + if (vCriteria.find(_file.first) != std::string::npos) + { + if (_file.second.use_count()) + { + *vOutColor = _file.second->color; + if (vOutIcon) + *vOutIcon = _file.second->icon; + if (vOutFont) + *vOutFont = _file.second->font; + return true; + } + } + } + } + else + { + if (prFilesStyle.at(_flag.first).find(vCriteria) != prFilesStyle.at(_flag.first).end()) // found + { + *vOutColor = prFilesStyle[_flag.first][vCriteria]->color; + if (vOutIcon) + *vOutIcon = prFilesStyle[_flag.first][vCriteria]->icon; + if (vOutFont) + *vOutFont = prFilesStyle[_flag.first][vCriteria]->font; + return true; + } + } + } + } + } + } + } + return false; + } + + void IGFD::FilterManager::ClearFilesStyle() + { + prFilesStyle.clear(); + } + + bool IGFD::FilterManager::IsCoveredByFilters(const std::string& vTag) const + { + if (!puDLGFilters.empty() && !prSelectedFilter.empty()) + { + // check if current file extention is covered by current filter + // we do that here, for avoid doing that during filelist display + // for better fps + if (prSelectedFilter.exist(vTag) || prSelectedFilter.filter == ".*") + { + return true; + } + } + + return false; + } + + bool IGFD::FilterManager::DrawFilterComboBox(FileDialogInternal& vFileDialogInternal) + { + // combobox of filters + if (!puDLGFilters.empty()) + { + ImGui::SameLine(); + + bool needToApllyNewFilter = false; + + ImGui::PushItemWidth(FILTER_COMBO_WIDTH); + if (ImGui::BeginCombo("##Filters", prSelectedFilter.filter.c_str(), ImGuiComboFlags_None)) + { + intptr_t i = 0; + for (const auto& filter : prParsedFilters) + { + const bool item_selected = (filter.filter == prSelectedFilter.filter); + ImGui::PushID((void*)(intptr_t)i++); + if (ImGui::Selectable(filter.filter.c_str(), item_selected)) + { + prSelectedFilter = filter; + needToApllyNewFilter = true; + } + ImGui::PopID(); + } + + ImGui::EndCombo(); + } + ImGui::PopItemWidth(); + + if (needToApllyNewFilter) + { + vFileDialogInternal.puFileManager.OpenCurrentPath(vFileDialogInternal); + } + + return needToApllyNewFilter; + } + + return false; + } + + IGFD::FilterManager::FilterInfosStruct IGFD::FilterManager::GetSelectedFilter() + { + return prSelectedFilter; + } + + std::string IGFD::FilterManager::ReplaceExtentionWithCurrentFilter(const std::string& vFile) const + { + auto result = vFile; + + if (!result.empty()) + { + // if not a collection we can replace the filter by the extention we want + if (prSelectedFilter.collectionfilters.empty()) + { + size_t lastPoint = vFile.find_last_of('.'); + if (lastPoint != std::string::npos) + { + result = result.substr(0, lastPoint); + } + + result += prSelectedFilter.filter; + } + } + + return result; + } + + void IGFD::FilterManager::SetDefaultFilterIfNotDefined() + { + if (prSelectedFilter.empty() && // no filter selected + !prParsedFilters.empty()) // filter exist + prSelectedFilter = *prParsedFilters.begin(); // we take the first filter + } + + ///////////////////////////////////////////////////////////////////////////////////// + //// FILE MANAGER /////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// + + IGFD::FileManager::FileManager() + { + puFsRoot = std::string(1u, PATH_SEP); + } + + void IGFD::FileManager::OpenCurrentPath(const FileDialogInternal& vFileDialogInternal) + { + puShowDrives = false; + ClearComposer(); + ClearFileLists(); + if (puDLGDirectoryMode) // directory mode + SetDefaultFileName("."); + else + SetDefaultFileName(puDLGDefaultFileName); + ScanDir(vFileDialogInternal, GetCurrentPath()); + } + + void IGFD::FileManager::SortFields(const FileDialogInternal& vFileDialogInternal, const SortingFieldEnum& vSortingField, const bool& vCanChangeOrder) + { + if (vSortingField != SortingFieldEnum::FIELD_NONE) + { + puHeaderFileName = tableHeaderFileNameString; + puHeaderFileType = tableHeaderFileTypeString; + puHeaderFileSize = tableHeaderFileSizeString; + puHeaderFileDate = tableHeaderFileDateString; +#ifdef USE_THUMBNAILS + puHeaderFileThumbnails = tableHeaderFileThumbnailsString; +#endif // #ifdef USE_THUMBNAILS + } + + if (vSortingField == SortingFieldEnum::FIELD_FILENAME) + { + if (vCanChangeOrder && puSortingField == vSortingField) + puSortingDirection[0] = !puSortingDirection[0]; + + if (puSortingDirection[0]) + { +#ifdef USE_CUSTOM_SORTING_ICON + puHeaderFileName = tableHeaderDescendingIcon + puHeaderFileName; +#endif // USE_CUSTOM_SORTING_ICON + std::sort(prFileList.begin(), prFileList.end(), + [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool + { + if (!a.use_count() || !b.use_count()) + return false; + + // this code fail in c:\\Users with the link "All users". got a invalid comparator + /* + // use code from https://github.com/jackm97/ImGuiFileDialog/commit/bf40515f5a1de3043e60562dc1a494ee7ecd3571 + // strict ordering for file/directory types beginning in '.' + // common on Linux platforms + if (a->fileNameExt[0] == '.' && b->fileNameExt[0] != '.') + return false; + if (a->fileNameExt[0] != '.' && b->fileNameExt[0] == '.') + return true; + if (a->fileNameExt[0] == '.' && b->fileNameExt[0] == '.') + { + return (stricmp(a->fileNameExt.c_str(), b->fileNameExt.c_str()) < 0); // sort in insensitive case + } + */ + if (a->fileType != b->fileType) return (a->fileType == 'd'); // directory in first + return (stricmp(a->fileNameExt.c_str(), b->fileNameExt.c_str()) < 0); // sort in insensitive case + }); + } + else + { +#ifdef USE_CUSTOM_SORTING_ICON + puHeaderFileName = tableHeaderAscendingIcon + puHeaderFileName; +#endif // USE_CUSTOM_SORTING_ICON + std::sort(prFileList.begin(), prFileList.end(), + [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool + { + if (!a.use_count() || !b.use_count()) + return false; + + // this code fail in c:\\Users with the link "All users". got a invalid comparator + /* + // use code from https://github.com/jackm97/ImGuiFileDialog/commit/bf40515f5a1de3043e60562dc1a494ee7ecd3571 + // strict ordering for file/directory types beginning in '.' + // common on Linux platforms + if (a->fileNameExt[0] == '.' && b->fileNameExt[0] != '.') + return false; + if (a->fileNameExt[0] != '.' && b->fileNameExt[0] == '.') + return true; + if (a->fileNameExt[0] == '.' && b->fileNameExt[0] == '.') + { + return (stricmp(a->fileNameExt.c_str(), b->fileNameExt.c_str()) > 0); // sort in insensitive case + } + */ + return (stricmp(a->fileNameExt.c_str(), b->fileNameExt.c_str()) > 0); // sort in insensitive case + }); + } + } + else if (vSortingField == SortingFieldEnum::FIELD_TYPE) + { + if (vCanChangeOrder && puSortingField == vSortingField) + puSortingDirection[1] = !puSortingDirection[1]; + + if (puSortingDirection[1]) + { +#ifdef USE_CUSTOM_SORTING_ICON + puHeaderFileType = tableHeaderDescendingIcon + puHeaderFileType; +#endif // USE_CUSTOM_SORTING_ICON + std::sort(prFileList.begin(), prFileList.end(), + [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool + { + if (!a.use_count() || !b.use_count()) + return false; + + if (a->fileType != b->fileType) return (a->fileType == 'd'); // directory in first + return (a->fileExt < b->fileExt); // else + }); + } + else + { +#ifdef USE_CUSTOM_SORTING_ICON + puHeaderFileType = tableHeaderAscendingIcon + puHeaderFileType; +#endif // USE_CUSTOM_SORTING_ICON + std::sort(prFileList.begin(), prFileList.end(), + [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool + { + if (!a.use_count() || !b.use_count()) + return false; + + if (a->fileType != b->fileType) return (a->fileType != 'd'); // directory in last + return (a->fileExt > b->fileExt); // else + }); + } + } + else if (vSortingField == SortingFieldEnum::FIELD_SIZE) + { + if (vCanChangeOrder && puSortingField == vSortingField) + puSortingDirection[2] = !puSortingDirection[2]; + + if (puSortingDirection[2]) + { +#ifdef USE_CUSTOM_SORTING_ICON + puHeaderFileSize = tableHeaderDescendingIcon + puHeaderFileSize; +#endif // USE_CUSTOM_SORTING_ICON + std::sort(prFileList.begin(), prFileList.end(), + [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool + { + if (!a.use_count() || !b.use_count()) + return false; + + if (a->fileType != b->fileType) return (a->fileType == 'd'); // directory in first + return (a->fileSize < b->fileSize); // else + }); + } + else + { +#ifdef USE_CUSTOM_SORTING_ICON + puHeaderFileSize = tableHeaderAscendingIcon + puHeaderFileSize; +#endif // USE_CUSTOM_SORTING_ICON + std::sort(prFileList.begin(), prFileList.end(), + [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool + { + if (!a.use_count() || !b.use_count()) + return false; + + if (a->fileType != b->fileType) return (a->fileType != 'd'); // directory in last + return (a->fileSize > b->fileSize); // else + }); + } + } + else if (vSortingField == SortingFieldEnum::FIELD_DATE) + { + if (vCanChangeOrder && puSortingField == vSortingField) + puSortingDirection[3] = !puSortingDirection[3]; + + if (puSortingDirection[3]) + { +#ifdef USE_CUSTOM_SORTING_ICON + puHeaderFileDate = tableHeaderDescendingIcon + puHeaderFileDate; +#endif // USE_CUSTOM_SORTING_ICON + std::sort(prFileList.begin(), prFileList.end(), + [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool + { + if (!a.use_count() || !b.use_count()) + return false; + + if (a->fileType != b->fileType) return (a->fileType == 'd'); // directory in first + return (a->fileModifDate < b->fileModifDate); // else + }); + } + else + { +#ifdef USE_CUSTOM_SORTING_ICON + puHeaderFileDate = tableHeaderAscendingIcon + puHeaderFileDate; +#endif // USE_CUSTOM_SORTING_ICON + std::sort(prFileList.begin(), prFileList.end(), + [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool + { + if (!a.use_count() || !b.use_count()) + return false; + + if (a->fileType != b->fileType) return (a->fileType != 'd'); // directory in last + return (a->fileModifDate > b->fileModifDate); // else + }); + } + } +#ifdef USE_THUMBNAILS + else if (vSortingField == SortingFieldEnum::FIELD_THUMBNAILS) + { + if (vCanChangeOrder && puSortingField == vSortingField) + puSortingDirection[4] = !puSortingDirection[4]; + + // we will compare thumbnails by : + // 1) width + // 2) height + + if (puSortingDirection[4]) + { +#ifdef USE_CUSTOM_SORTING_ICON + puHeaderFileThumbnails = tableHeaderDescendingIcon + puHeaderFileThumbnails; +#endif // USE_CUSTOM_SORTING_ICON + std::sort(prFileList.begin(), prFileList.end(), + [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool + { + if (!a.use_count() || !b.use_count()) + return false; + + if (a->fileType != b->fileType) return (a->fileType == 'd'); // directory in first + if (a->thumbnailInfo.textureWidth == b->thumbnailInfo.textureWidth) + return (a->thumbnailInfo.textureHeight < b->thumbnailInfo.textureHeight); + return (a->thumbnailInfo.textureWidth < b->thumbnailInfo.textureWidth); + }); + } + + else + { +#ifdef USE_CUSTOM_SORTING_ICON + puHeaderFileThumbnails = tableHeaderAscendingIcon + puHeaderFileThumbnails; +#endif // USE_CUSTOM_SORTING_ICON + std::sort(prFileList.begin(), prFileList.end(), + [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool + { + if (!a.use_count() || !b.use_count()) + return false; + + if (a->fileType != b->fileType) return (a->fileType != 'd'); // directory in last + if (a->thumbnailInfo.textureWidth == b->thumbnailInfo.textureWidth) + return (a->thumbnailInfo.textureHeight > b->thumbnailInfo.textureHeight); + return (a->thumbnailInfo.textureWidth > b->thumbnailInfo.textureWidth); + }); + } + } +#endif // USE_THUMBNAILS + + if (vSortingField != SortingFieldEnum::FIELD_NONE) + { + puSortingField = vSortingField; + } + + ApplyFilteringOnFileList(vFileDialogInternal); + } + + void IGFD::FileManager::ClearFileLists() + { + prFilteredFileList.clear(); + prFileList.clear(); + } + + std::string IGFD::FileManager::prOptimizeFilenameForSearchOperations(const std::string& vFileNameExt) + { + auto fileNameExt = vFileNameExt; + // convert to lower case + for (char& c : fileNameExt) + c = (char)std::tolower(c); + return fileNameExt; + } + + void IGFD::FileManager::AddFile(const FileDialogInternal& vFileDialogInternal, const std::string& vPath, const std::string& vFileName, const char& vFileType) + { + auto infos = std::make_shared(); + + infos->filePath = vPath; + infos->fileNameExt = vFileName; + infos->fileNameExt_optimized = prOptimizeFilenameForSearchOperations(infos->fileNameExt); + infos->fileType = vFileType; + + if (infos->fileNameExt.empty() || (infos->fileNameExt == "." && !vFileDialogInternal.puFilterManager.puDLGFilters.empty())) return; // filename empty or filename is the current dir '.' //-V807 + if (infos->fileNameExt != ".." && (vFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_DontShowHiddenFiles) && infos->fileNameExt[0] == '.') // dont show hidden files + if (!vFileDialogInternal.puFilterManager.puDLGFilters.empty() || (vFileDialogInternal.puFilterManager.puDLGFilters.empty() && infos->fileNameExt != ".")) // except "." if in directory mode //-V728 + return; + + if (infos->fileType == 'f' || + infos->fileType == 'l') // link can have the same extention of a file + { + size_t lpt = infos->fileNameExt.find_last_of('.'); + if (lpt != std::string::npos) + { + infos->fileExt = infos->fileNameExt.substr(lpt); + } + + if (!vFileDialogInternal.puFilterManager.IsCoveredByFilters(infos->fileExt)) + { + return; + } + } + + vFileDialogInternal.puFilterManager.prFillFileStyle(infos); + + prCompleteFileInfos(infos); + prFileList.push_back(infos); + } + + void IGFD::FileManager::ScanDir(const FileDialogInternal& vFileDialogInternal, const std::string& vPath) + { + std::string path = vPath; + + if (prCurrentPathDecomposition.empty()) + { + SetCurrentDir(path); + } + + if (!prCurrentPathDecomposition.empty()) + { +#ifdef WIN32 + if (path == puFsRoot) + path += std::string(1u, PATH_SEP); +#endif // WIN32 + + ClearFileLists(); + +#ifdef USE_STD_FILESYSTEM + //const auto wpath = IGFD::Utils::WGetString(path.c_str()); + const std::filesystem::path fspath(path); + const auto dir_iter = std::filesystem::directory_iterator(fspath); + AddFile(vFileDialogInternal, path, "..", 'd'); + for (const auto& file : dir_iter) + { + char fileType = 0; + if (file.is_symlink()) + fileType = 'l'; + else if (file.is_directory()) + fileType = 'd'; + else + fileType = 'f'; + auto fileNameExt = file.path().filename().string(); + AddFile(vFileDialogInternal, path, fileNameExt, fileType); + } +#else // dirent + struct dirent** files = nullptr; + size_t n = scandir(path.c_str(), &files, nullptr, inAlphaSort); + if (n) + { + size_t i; + + for (i = 0; i < n; i++) + { + struct dirent* ent = files[i]; + + char fileType = 0; + switch (ent->d_type) + { + case DT_REG: + fileType = 'f'; break; + case DT_DIR: + fileType = 'd'; break; + case DT_LNK: + fileType = 'l'; break; + } + + auto fileNameExt = ent->d_name; + + AddFile(vFileDialogInternal, path, fileNameExt, fileType); + } + + for (i = 0; i < n; i++) + { + free(files[i]); + } + + free(files); + } +#endif // USE_STD_FILESYSTEM + + SortFields(vFileDialogInternal, puSortingField, false); + } + } + + bool IGFD::FileManager::GetDrives() + { + auto drives = IGFD::Utils::GetDrivesList(); + if (!drives.empty()) + { + prCurrentPath.clear(); + prCurrentPathDecomposition.clear(); + ClearFileLists(); + for (auto& drive : drives) + { + auto info = std::make_shared(); + info->fileNameExt = drive; + info->fileNameExt_optimized = prOptimizeFilenameForSearchOperations(drive); + info->fileType = 'd'; + + if (!info->fileNameExt.empty()) + { + prFileList.push_back(info); + } + } + puShowDrives = true; + return true; + } + return false; + } + + bool IGFD::FileManager::IsComposerEmpty() + { + return prCurrentPathDecomposition.empty(); + } + + size_t IGFD::FileManager::GetComposerSize() + { + return prCurrentPathDecomposition.size(); + } + + bool IGFD::FileManager::IsFileListEmpty() + { + return prFileList.empty(); + } + + size_t IGFD::FileManager::GetFullFileListSize() + { + return prFileList.size(); + } + + std::shared_ptr IGFD::FileManager::GetFullFileAt(size_t vIdx) + { + if (vIdx < prFileList.size()) + return prFileList[vIdx]; + return nullptr; + } + + bool IGFD::FileManager::IsFilteredListEmpty() + { + return prFilteredFileList.empty(); + } + + size_t IGFD::FileManager::GetFilteredListSize() + { + return prFilteredFileList.size(); + } + + std::shared_ptr IGFD::FileManager::GetFilteredFileAt(size_t vIdx) + { + if (vIdx < prFilteredFileList.size()) + return prFilteredFileList[vIdx]; + return nullptr; + } + + bool IGFD::FileManager::IsFileNameSelected(const std::string& vFileName) + { + return prSelectedFileNames.find(vFileName) != prSelectedFileNames.end(); + } + + std::string IGFD::FileManager::GetBack() + { + return prCurrentPathDecomposition.back(); + } + + void IGFD::FileManager::ClearComposer() + { + prCurrentPathDecomposition.clear(); + } + + void IGFD::FileManager::ClearAll() + { + ClearComposer(); + ClearFileLists(); + } + + void IGFD::FileManager::ApplyFilteringOnFileList(const FileDialogInternal& vFileDialogInternal) + { + prFilteredFileList.clear(); + for (const auto& file : prFileList) + { + if (!file.use_count()) + continue; + bool show = true; + if (!file->IsTagFound(vFileDialogInternal.puSearchManager.puSearchTag)) // if search tag + show = false; + if (puDLGDirectoryMode && file->fileType != 'd') // directory mode + show = false; + if (show) + prFilteredFileList.push_back(file); + } + } + + std::string IGFD::FileManager::prRoundNumber(double vvalue, int n) + { + std::stringstream tmp; + tmp << std::setprecision(n) << std::fixed << vvalue; + return tmp.str(); + } + + std::string IGFD::FileManager::prFormatFileSize(size_t vByteSize) + { + if (vByteSize != 0) + { + static double lo = 1024.0; + static double ko = 1024.0 * 1024.0; + static double mo = 1024.0 * 1024.0 * 1024.0; + + auto v = (double)vByteSize; + + if (v < lo) + return prRoundNumber(v, 0) + " o"; // octet + else if (v < ko) + return prRoundNumber(v / lo, 2) + " Ko"; // ko + else if (v < mo) + return prRoundNumber(v / ko, 2) + " Mo"; // Mo + else + return prRoundNumber(v / mo, 2) + " Go"; // Go + } + + return ""; + } + + void IGFD::FileManager::prCompleteFileInfos(const std::shared_ptr& vInfos) + { + if (!vInfos.use_count()) + return; + + if (vInfos->fileNameExt != "." && + vInfos->fileNameExt != "..") + { + // _stat struct : + //dev_t st_dev; /* ID of device containing file */ + //ino_t st_ino; /* inode number */ + //mode_t st_mode; /* protection */ + //nlink_t st_nlink; /* number of hard links */ + //uid_t st_uid; /* user ID of owner */ + //gid_t st_gid; /* group ID of owner */ + //dev_t st_rdev; /* device ID (if special file) */ + //off_t st_size; /* total size, in bytes */ + //blksize_t st_blksize; /* blocksize for file system I/O */ + //blkcnt_t st_blocks; /* number of 512B blocks allocated */ + //time_t st_atime; /* time of last access - not sure out of ntfs */ + //time_t st_mtime; /* time of last modification - not sure out of ntfs */ + //time_t st_ctime; /* time of last status change - not sure out of ntfs */ + + std::string fpn; + + if (vInfos->fileType == 'f' || vInfos->fileType == 'l' || vInfos->fileType == 'd') // file + fpn = vInfos->filePath + std::string(1u, PATH_SEP) + vInfos->fileNameExt; + + struct stat statInfos = {}; + char timebuf[100]; + int result = stat(fpn.c_str(), &statInfos); + if (!result) + { + if (vInfos->fileType != 'd') + { + vInfos->fileSize = (size_t)statInfos.st_size; + vInfos->formatedFileSize = prFormatFileSize(vInfos->fileSize); + } + + size_t len = 0; +#ifdef MSVC + struct tm _tm; + errno_t err = localtime_s(&_tm, &statInfos.st_mtime); + if (!err) len = strftime(timebuf, 99, DateTimeFormat, &_tm); +#else // MSVC + struct tm* _tm = localtime(&statInfos.st_mtime); + if (_tm) len = strftime(timebuf, 99, DateTimeFormat, _tm); +#endif // MSVC + if (len) + { + vInfos->fileModifDate = std::string(timebuf, len); + } + } + } + } + + void IGFD::FileManager::prRemoveFileNameInSelection(const std::string& vFileName) + { + prSelectedFileNames.erase(vFileName); + + if (prSelectedFileNames.size() == 1) + { + snprintf(puFileNameBuffer, MAX_FILE_DIALOG_NAME_BUFFER, "%s", vFileName.c_str()); + } + else + { + snprintf(puFileNameBuffer, MAX_FILE_DIALOG_NAME_BUFFER, "%zu files Selected", prSelectedFileNames.size()); + } + } + + void IGFD::FileManager::prAddFileNameInSelection(const std::string& vFileName, bool vSetLastSelectionFileName) + { + prSelectedFileNames.emplace(vFileName); + + if (prSelectedFileNames.size() == 1) + { + snprintf(puFileNameBuffer, MAX_FILE_DIALOG_NAME_BUFFER, "%s", vFileName.c_str()); + } + else + { + snprintf(puFileNameBuffer, MAX_FILE_DIALOG_NAME_BUFFER, "%zu files Selected", prSelectedFileNames.size()); + } + + if (vSetLastSelectionFileName) + prLastSelectedFileName = vFileName; + } + + void IGFD::FileManager::SetCurrentDir(const std::string& vPath) + { + std::string path = vPath; +#ifdef WIN32 + if (puFsRoot == path) + path += std::string(1u, PATH_SEP); +#endif // WIN32 + +#ifdef USE_STD_FILESYSTEM + namespace fs = std::filesystem; + bool dir_opened = fs::is_directory(vPath); + if (!dir_opened) + { + path = "."; + dir_opened = fs::is_directory(vPath); + } + if (dir_opened) +#else + DIR* dir = opendir(path.c_str()); + if (dir == nullptr) + { + path = "."; + dir = opendir(path.c_str()); + } + + if (dir != nullptr) +#endif // USE_STD_FILESYSTEM + { +#ifdef WIN32 + DWORD numchar = 0; + // numchar = GetFullPathNameA(path.c_str(), PATH_MAX, real_path, nullptr); + std::wstring wpath = IGFD::Utils::string_to_wstring(path); + numchar = GetFullPathNameW(wpath.c_str(), 0, nullptr, nullptr); + std::wstring fpath(numchar, 0); + GetFullPathNameW(wpath.c_str(), numchar, (wchar_t*)fpath.data(), nullptr); + std::string real_path = IGFD::Utils::wstring_to_string(fpath); + if (real_path.back() == '\0') // for fix issue we can have with std::string concatenation.. if there is a \0 at end + real_path = real_path.substr(0, real_path.size() - 1U); + if (!real_path.empty()) +#elif defined(UNIX) // UNIX is LINUX or APPLE + char real_path[PATH_MAX]; + char* numchar = realpath(path.c_str(), real_path); + if (numchar != nullptr) +#endif // WIN32 + { + prCurrentPath = std::move(real_path); + if (prCurrentPath[prCurrentPath.size() - 1] == PATH_SEP) + { + prCurrentPath = prCurrentPath.substr(0, prCurrentPath.size() - 1); + } + IGFD::Utils::SetBuffer(puInputPathBuffer, MAX_PATH_BUFFER_SIZE, prCurrentPath); + prCurrentPathDecomposition = IGFD::Utils::SplitStringToVector(prCurrentPath, PATH_SEP, false); +#ifdef UNIX // UNIX is LINUX or APPLE + prCurrentPathDecomposition.insert(prCurrentPathDecomposition.begin(), std::string(1u, PATH_SEP)); +#endif // UNIX + if (!prCurrentPathDecomposition.empty()) + { +#ifdef WIN32 + puFsRoot = prCurrentPathDecomposition[0]; +#endif // WIN32 + } + } +#ifndef USE_STD_FILESYSTEM + closedir(dir); +#endif + } + } + + bool IGFD::FileManager::CreateDir(const std::string& vPath) + { + bool res = false; + + if (!vPath.empty()) + { + std::string path = prCurrentPath + std::string(1u, PATH_SEP) + vPath; + + res = IGFD::Utils::CreateDirectoryIfNotExist(path); + } + + return res; + } + + void IGFD::FileManager::ComposeNewPath(std::vector::iterator vIter) + { + std::string res; + + while (true) + { + if (!res.empty()) + { +#ifdef WIN32 + res = *vIter + std::string(1u, PATH_SEP) + res; +#elif defined(UNIX) // UNIX is LINUX or APPLE + if (*vIter == puFsRoot) + res = *vIter + res; + else + res = *vIter + PATH_SEP + res; +#endif // WIN32 + } + else + res = *vIter; + + if (vIter == prCurrentPathDecomposition.begin()) + { +#if defined(UNIX) // UNIX is LINUX or APPLE + if (res[0] != PATH_SEP) + res = PATH_SEP + res; +#endif // defined(UNIX) + break; + } + + --vIter; + } + + prCurrentPath = std::move(res); + } + + bool IGFD::FileManager::SetPathOnParentDirectoryIfAny() + { + if (prCurrentPathDecomposition.size() > 1) + { + ComposeNewPath(prCurrentPathDecomposition.end() - 2); + return true; + } + return false; + } + + std::string IGFD::FileManager::GetCurrentPath() + { + if (prCurrentPath.empty()) + prCurrentPath = "."; + return prCurrentPath; + } + + void IGFD::FileManager::SetCurrentPath(const std::string& vCurrentPath) + { + if (vCurrentPath.empty()) + prCurrentPath = "."; + else + prCurrentPath = vCurrentPath; + } + + bool IGFD::FileManager::IsFileExist(const std::string& vFile) + { + std::ifstream docFile(vFile, std::ios::in); + if (docFile.is_open()) + { + docFile.close(); + return true; + } + return false; + } + + void IGFD::FileManager::SetDefaultFileName(const std::string& vFileName) + { + puDLGDefaultFileName = vFileName; + IGFD::Utils::SetBuffer(puFileNameBuffer, MAX_FILE_DIALOG_NAME_BUFFER, vFileName); + } + + bool IGFD::FileManager::SelectDirectory(const std::shared_ptr& vInfos) + { + if (!vInfos.use_count()) + return false; + + bool pathClick = false; + + if (vInfos->fileNameExt == "..") + { + pathClick = SetPathOnParentDirectoryIfAny(); + } + else + { + std::string newPath; + + if (puShowDrives) + { + newPath = vInfos->fileNameExt + std::string(1u, PATH_SEP); + } + else + { +#ifdef __linux__ + if (puFsRoot == prCurrentPath) + newPath = prCurrentPath + vInfos->fileNameExt; + else +#endif // __linux__ + newPath = prCurrentPath + std::string(1u, PATH_SEP) + vInfos->fileNameExt; + } + + if (IGFD::Utils::IsDirectoryExist(newPath)) + { + if (puShowDrives) + { + prCurrentPath = vInfos->fileNameExt; + puFsRoot = prCurrentPath; + } + else + { + prCurrentPath = newPath; //-V820 + } + pathClick = true; + } + } + + return pathClick; + } + + void IGFD::FileManager::SelectFileName(const FileDialogInternal& vFileDialogInternal, const std::shared_ptr& vInfos) + { + if (!vInfos.use_count()) + return; + + if (ImGui::GetIO().KeyCtrl) + { + if (puDLGcountSelectionMax == 0) // infinite selection + { + if (prSelectedFileNames.find(vInfos->fileNameExt) == prSelectedFileNames.end()) // not found +> add + { + prAddFileNameInSelection(vInfos->fileNameExt, true); + } + else // found +> remove + { + prRemoveFileNameInSelection(vInfos->fileNameExt); + } + } + else // selection limited by size + { + if (prSelectedFileNames.size() < puDLGcountSelectionMax) + { + if (prSelectedFileNames.find(vInfos->fileNameExt) == prSelectedFileNames.end()) // not found +> add + { + prAddFileNameInSelection(vInfos->fileNameExt, true); + } + else // found +> remove + { + prRemoveFileNameInSelection(vInfos->fileNameExt); + } + } + } + } + else if (ImGui::GetIO().KeyShift) + { + if (puDLGcountSelectionMax != 1) + { + prSelectedFileNames.clear(); + // we will iterate filelist and get the last selection after the start selection + bool startMultiSelection = false; + std::string fileNameToSelect = vInfos->fileNameExt; + std::string savedLastSelectedFileName; // for invert selection mode + for (const auto& file : prFileList) + { + if (!file.use_count()) + continue; + + bool canTake = true; + if (!file->IsTagFound(vFileDialogInternal.puSearchManager.puSearchTag)) canTake = false; + if (canTake) // if not filtered, we will take files who are filtered by the dialog + { + if (file->fileNameExt == prLastSelectedFileName) + { + startMultiSelection = true; + prAddFileNameInSelection(prLastSelectedFileName, false); + } + else if (startMultiSelection) + { + if (puDLGcountSelectionMax == 0) // infinite selection + { + prAddFileNameInSelection(file->fileNameExt, false); + } + else // selection limited by size + { + if (prSelectedFileNames.size() < puDLGcountSelectionMax) + { + prAddFileNameInSelection(file->fileNameExt, false); + } + else + { + startMultiSelection = false; + if (!savedLastSelectedFileName.empty()) + prLastSelectedFileName = savedLastSelectedFileName; + break; + } + } + } + + if (file->fileNameExt == fileNameToSelect) + { + if (!startMultiSelection) // we are before the last Selected FileName, so we must inverse + { + savedLastSelectedFileName = prLastSelectedFileName; + prLastSelectedFileName = fileNameToSelect; + fileNameToSelect = savedLastSelectedFileName; + startMultiSelection = true; + prAddFileNameInSelection(prLastSelectedFileName, false); + } + else + { + startMultiSelection = false; + if (!savedLastSelectedFileName.empty()) + prLastSelectedFileName = savedLastSelectedFileName; + break; + } + } + } + } + } + } + else + { + prSelectedFileNames.clear(); + IGFD::Utils::ResetBuffer(puFileNameBuffer); + prAddFileNameInSelection(vInfos->fileNameExt, true); + } + } + + void IGFD::FileManager::DrawDirectoryCreation(const FileDialogInternal& vFileDialogInternal) + { + if (vFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_DisableCreateDirectoryButton) + return; + + if (IMGUI_BUTTON(createDirButtonString)) + { + if (!prCreateDirectoryMode) + { + prCreateDirectoryMode = true; + IGFD::Utils::ResetBuffer(puDirectoryNameBuffer); + } + } + if (ImGui::IsItemHovered()) + ImGui::SetTooltip(buttonCreateDirString); + + if (prCreateDirectoryMode) + { + ImGui::SameLine(); + + ImGui::PushItemWidth(100.0f); + ImGui::InputText("##DirectoryFileName", puDirectoryNameBuffer, MAX_FILE_DIALOG_NAME_BUFFER); + ImGui::PopItemWidth(); + + ImGui::SameLine(); + + if (IMGUI_BUTTON(okButtonString)) + { + std::string newDir = std::string(puDirectoryNameBuffer); + if (CreateDir(newDir)) + { + SetCurrentPath(prCurrentPath + std::string(1u, PATH_SEP) + newDir); + OpenCurrentPath(vFileDialogInternal); + } + + prCreateDirectoryMode = false; + } + + ImGui::SameLine(); + + if (IMGUI_BUTTON(cancelButtonString)) + { + prCreateDirectoryMode = false; + } + } + } + + void IGFD::FileManager::DrawPathComposer(const FileDialogInternal& vFileDialogInternal) + { + if (IMGUI_BUTTON(resetButtonString)) + { + SetCurrentPath("."); + OpenCurrentPath(vFileDialogInternal); + } + if (ImGui::IsItemHovered()) + ImGui::SetTooltip(buttonResetPathString); + +#ifdef WIN32 + ImGui::SameLine(); + + if (IMGUI_BUTTON(drivesButtonString)) + { + puDrivesClicked = true; + } + if (ImGui::IsItemHovered()) + ImGui::SetTooltip(buttonDriveString); +#endif // WIN32 + + ImGui::SameLine(); + + if (IMGUI_BUTTON(editPathButtonString)) + { + puInputPathActivated = true; + } + if (ImGui::IsItemHovered()) + ImGui::SetTooltip(buttonEditPathString); + + ImGui::SameLine(); + + ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); + + // show current path + if (!prCurrentPathDecomposition.empty()) + { + ImGui::SameLine(); + + if (puInputPathActivated) + { + ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::InputText("##pathedition", puInputPathBuffer, MAX_PATH_BUFFER_SIZE); + ImGui::PopItemWidth(); + } + else + { + int _id = 0; + for (auto itPathDecomp = prCurrentPathDecomposition.begin(); + itPathDecomp != prCurrentPathDecomposition.end(); ++itPathDecomp) + { + if (itPathDecomp != prCurrentPathDecomposition.begin()) + ImGui::SameLine(); + ImGui::PushID(_id++); + bool click = IMGUI_PATH_BUTTON((*itPathDecomp).c_str()); + ImGui::PopID(); + if (click) + { + ComposeNewPath(itPathDecomp); + puPathClicked = true; + break; + } + // activate input for path + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) + { + ComposeNewPath(itPathDecomp); + IGFD::Utils::SetBuffer(puInputPathBuffer, MAX_PATH_BUFFER_SIZE, prCurrentPath); + puInputPathActivated = true; + break; + } + } + } + } + } + + std::string IGFD::FileManager::GetResultingPath() + { + std::string path = prCurrentPath; + + if (puDLGDirectoryMode) // if directory mode + { + std::string selectedDirectory = puFileNameBuffer; + if (!selectedDirectory.empty() && + selectedDirectory != ".") + path += std::string(1u, PATH_SEP) + selectedDirectory; + } + + return path; + } + + std::string IGFD::FileManager::GetResultingFileName(FileDialogInternal& vFileDialogInternal) + { + if (!puDLGDirectoryMode) // if not directory mode + { + return vFileDialogInternal.puFilterManager.ReplaceExtentionWithCurrentFilter(std::string(puFileNameBuffer)); + } + + return ""; // directory mode + } + + std::string IGFD::FileManager::GetResultingFilePathName(FileDialogInternal& vFileDialogInternal) + { + std::string result = GetResultingPath(); + + std::string filename = GetResultingFileName(vFileDialogInternal); + if (!filename.empty()) + { +#ifdef UNIX + if (puFsRoot != result) +#endif // UNIX + result += std::string(1u, PATH_SEP); + + result += filename; + } + + return result; + } + + std::map IGFD::FileManager::GetResultingSelection() + { + std::map res; + + for (auto& selectedFileName : prSelectedFileNames) + { + std::string result = GetResultingPath(); + +#ifdef UNIX + if (puFsRoot != result) +#endif // UNIX + result += std::string(1u, PATH_SEP); + + result += selectedFileName; + + res[selectedFileName] = result; + } + + return res; + } + + ///////////////////////////////////////////////////////////////////////////////////// + //// FILE DIALOG INTERNAL /////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// + + void IGFD::FileDialogInternal::NewFrame() + { + puCanWeContinue = true; // reset flag for possibily validate the dialog + puIsOk = false; // reset dialog result + puFileManager.puDrivesClicked = false; + puFileManager.puPathClicked = false; + + puNeedToExitDialog = false; + +#ifdef USE_DIALOG_EXIT_WITH_KEY + if (ImGui::IsKeyPressed(IGFD_EXIT_KEY)) + { + // we do that here with the data's defined at the last frame + // because escape key can quit input activation and at the end of the frame all flag will be false + // so we will detect nothing + if (!(puFileManager.puInputPathActivated || + puSearchManager.puSearchInputIsActive || + puFileInputIsActive || + puFileListViewIsActive)) + { + puNeedToExitDialog = true; // need to quit dialog + } + } + else +#endif + { + puSearchManager.puSearchInputIsActive = false; + puFileInputIsActive = false; + puFileListViewIsActive = false; + } + } + + void IGFD::FileDialogInternal::EndFrame() + { + // directory change + if (puFileManager.puPathClicked) + { + puFileManager.OpenCurrentPath(*this); + } + + if (puFileManager.puDrivesClicked) + { + if (puFileManager.GetDrives()) + { + puFileManager.ApplyFilteringOnFileList(*this); + } + } + + if (puFileManager.puInputPathActivated) + { + auto gio = ImGui::GetIO(); + if (ImGui::IsKeyReleased(gio.KeyMap[ImGuiKey_Enter])) + { + puFileManager.SetCurrentPath(std::string(puFileManager.puInputPathBuffer)); + puFileManager.OpenCurrentPath(*this); + puFileManager.puInputPathActivated = false; + } + if (ImGui::IsKeyReleased(gio.KeyMap[ImGuiKey_Escape])) + { + puFileManager.puInputPathActivated = false; + } + } + } + + void IGFD::FileDialogInternal::ResetForNewDialog() + { + + } + + ///////////////////////////////////////////////////////////////////////////////////// + //// THUMBNAIL FEATURE ////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// + + IGFD::ThumbnailFeature::ThumbnailFeature() + { +#ifdef USE_THUMBNAILS + prDisplayMode = DisplayModeEnum::FILE_LIST; +#endif + } + + IGFD::ThumbnailFeature::~ThumbnailFeature() = default; + + void IGFD::ThumbnailFeature::NewThumbnailFrame(FileDialogInternal& vFileDialogInternal) + { + (void)vFileDialogInternal; + +#ifdef USE_THUMBNAILS + prStartThumbnailFileDatasExtraction(); +#endif + } + + void IGFD::ThumbnailFeature::EndThumbnailFrame(FileDialogInternal& vFileDialogInternal) + { +#ifdef USE_THUMBNAILS + prClearThumbnails(vFileDialogInternal); +#endif + } + + void IGFD::ThumbnailFeature::QuitThumbnailFrame(FileDialogInternal& vFileDialogInternal) + { +#ifdef USE_THUMBNAILS + prStopThumbnailFileDatasExtraction(); + prClearThumbnails(vFileDialogInternal); +#endif + } + +#ifdef USE_THUMBNAILS + void IGFD::ThumbnailFeature::prStartThumbnailFileDatasExtraction() + { + const bool res = prThumbnailGenerationThread.use_count() && prThumbnailGenerationThread->joinable(); + if (!res) + { + prIsWorking = true; + prCountFiles = 0U; + prThumbnailGenerationThread = std::shared_ptr( + new std::thread(&IGFD::ThumbnailFeature::prThreadThumbnailFileDatasExtractionFunc, this), + [this](std::thread* obj) + { + prIsWorking = false; + if (obj) + obj->join(); + }); + } + } + + bool IGFD::ThumbnailFeature::prStopThumbnailFileDatasExtraction() + { + const bool res = prThumbnailGenerationThread.use_count() && prThumbnailGenerationThread->joinable(); + if (res) + { + prThumbnailGenerationThread.reset(); + } + + return res; + } + + void IGFD::ThumbnailFeature::prThreadThumbnailFileDatasExtractionFunc() + { + prCountFiles = 0U; + prIsWorking = true; + + // infinite loop while is thread working + while(prIsWorking) + { + if (!prThumbnailFileDatasToGet.empty()) + { + std::shared_ptr file = nullptr; + prThumbnailFileDatasToGetMutex.lock(); + //get the first file in the list + file = (*prThumbnailFileDatasToGet.begin()); + prThumbnailFileDatasToGetMutex.unlock(); + + // retrieve datas of the texture file if its an image file + if (file.use_count()) + { + if (file->fileType == 'f') //-V522 + { + if (file->fileExt == ".png" + || file->fileExt == ".bmp" + || file->fileExt == ".tga" + || file->fileExt == ".jpg" || file->fileExt == ".jpeg" + || file->fileExt == ".gif" + || file->fileExt == ".psd" + || file->fileExt == ".pic" + || file->fileExt == ".ppm" || file->fileExt == ".pgm" + //|| file->fileExt == ".hdr" => format float so in few times + ) + { + auto fpn = file->filePath + std::string(1u, PATH_SEP) + file->fileNameExt; + + int w = 0; + int h = 0; + int chans = 0; + uint8_t *datas = stbi_load(fpn.c_str(), &w, &h, &chans, STBI_rgb_alpha); + if (datas) + { + if (w && h) + { + // resize with respect to glyph ratio + const float ratioX = (float)w / (float)h; + const float newX = DisplayMode_ThumbailsList_ImageHeight * ratioX; + float newY = w / ratioX; + if (newX < w) + newY = DisplayMode_ThumbailsList_ImageHeight; + + const auto newWidth = (int)newX; + const auto newHeight = (int)newY; + const auto newBufSize = (size_t)(newWidth * newHeight * 4U); //-V112 //-V1028 + auto resizedData = new uint8_t[newBufSize]; + + const int resizeSucceeded = stbir_resize_uint8( + datas, w, h, 0, + resizedData, newWidth, newHeight, 0, + 4); //-V112 + + if (resizeSucceeded) + { + auto th = &file->thumbnailInfo; + + th->textureFileDatas = resizedData; + th->textureWidth = newWidth; + th->textureHeight = newHeight; + th->textureChannels = 4; //-V112 + + // we set that at least, because will launch the gpu creation of the texture in the main thread + th->isReadyToUpload = true; + + // need gpu loading + prAddThumbnailToCreate(file); + } + } + else + { + printf("image loading fail : w:%i h:%i c:%i\n", w, h, 4); //-V112 + } + + stbi_image_free(datas); + } + } + } + + // peu importe le resultat on vire le fichicer + // remove form this list + // write => thread concurency issues + prThumbnailFileDatasToGetMutex.lock(); + prThumbnailFileDatasToGet.pop_front(); + prThumbnailFileDatasToGetMutex.unlock(); + } + } + } + } + + inline void inVariadicProgressBar(float fraction, const ImVec2& size_arg, const char* fmt, ...) + { + va_list args; + va_start(args, fmt); + char TempBuffer[512]; + const int w = vsnprintf(TempBuffer, 511, fmt, args); + va_end(args); + if (w) + { + ImGui::ProgressBar(fraction, size_arg, TempBuffer); + } + } + + void IGFD::ThumbnailFeature::prDrawThumbnailGenerationProgress() + { + if (prThumbnailGenerationThread.use_count() && prThumbnailGenerationThread->joinable()) + { + if (!prThumbnailFileDatasToGet.empty()) + { + const auto p = (float)((double)prCountFiles / (double)prThumbnailFileDatasToGet.size()); // read => no thread concurency issues + inVariadicProgressBar(p, ImVec2(50, 0), "%u/%u", prCountFiles, (uint32_t)prThumbnailFileDatasToGet.size()); // read => no thread concurency issues + ImGui::SameLine(); + } + } + } + + void IGFD::ThumbnailFeature::prAddThumbnailToLoad(const std::shared_ptr& vFileInfos) + { + if (vFileInfos.use_count()) + { + if (vFileInfos->fileType == 'f') + { + if (vFileInfos->fileExt == ".png" + || vFileInfos->fileExt == ".bmp" + || vFileInfos->fileExt == ".tga" + || vFileInfos->fileExt == ".jpg" || vFileInfos->fileExt == ".jpeg" + || vFileInfos->fileExt == ".gif" + || vFileInfos->fileExt == ".psd" + || vFileInfos->fileExt == ".pic" + || vFileInfos->fileExt == ".ppm" || vFileInfos->fileExt == ".pgm" + //|| file->fileExt == ".hdr" => format float so in few times + ) + { + // write => thread concurency issues + prThumbnailFileDatasToGetMutex.lock(); + prThumbnailFileDatasToGet.push_back(vFileInfos); + vFileInfos->thumbnailInfo.isLoadingOrLoaded = true; + prThumbnailFileDatasToGetMutex.unlock(); + } + } + } + } + + void IGFD::ThumbnailFeature::prAddThumbnailToCreate(const std::shared_ptr& vFileInfos) + { + if (vFileInfos.use_count()) + { + // write => thread concurency issues + prThumbnailToCreateMutex.lock(); + prThumbnailToCreate.push_back(vFileInfos); + prThumbnailToCreateMutex.unlock(); + } + } + + void IGFD::ThumbnailFeature::prAddThumbnailToDestroy(const IGFD_Thumbnail_Info& vIGFD_Thumbnail_Info) + { + // write => thread concurency issues + prThumbnailToDestroyMutex.lock(); + prThumbnailToDestroy.push_back(vIGFD_Thumbnail_Info); + prThumbnailToDestroyMutex.unlock(); + } + + void IGFD::ThumbnailFeature::prDrawDisplayModeToolBar() + { + if (IMGUI_RADIO_BUTTON(DisplayMode_FilesList_ButtonString, + prDisplayMode == DisplayModeEnum::FILE_LIST)) + prDisplayMode = DisplayModeEnum::FILE_LIST; + if (ImGui::IsItemHovered()) ImGui::SetTooltip(DisplayMode_FilesList_ButtonHelp); + ImGui::SameLine(); + if (IMGUI_RADIO_BUTTON(DisplayMode_ThumbailsList_ButtonString, + prDisplayMode == DisplayModeEnum::THUMBNAILS_LIST)) + prDisplayMode = DisplayModeEnum::THUMBNAILS_LIST; + if (ImGui::IsItemHovered()) ImGui::SetTooltip(DisplayMode_ThumbailsList_ButtonHelp); + ImGui::SameLine(); + /* todo + if (IMGUI_RADIO_BUTTON(DisplayMode_ThumbailsGrid_ButtonString, + prDisplayMode == DisplayModeEnum::THUMBNAILS_GRID)) + prDisplayMode = DisplayModeEnum::THUMBNAILS_GRID; + if (ImGui::IsItemHovered()) ImGui::SetTooltip(DisplayMode_ThumbailsGrid_ButtonHelp); + ImGui::SameLine(); + */ + prDrawThumbnailGenerationProgress(); + } + + void IGFD::ThumbnailFeature::prClearThumbnails(FileDialogInternal& vFileDialogInternal) + { + // directory wil be changed so the file list will be erased + if (vFileDialogInternal.puFileManager.puPathClicked) + { + size_t count = vFileDialogInternal.puFileManager.GetFullFileListSize(); + for (size_t idx = 0U; idx < count; idx++) + { + auto file = vFileDialogInternal.puFileManager.GetFullFileAt(idx); + if (file.use_count()) + { + if (file->thumbnailInfo.isReadyToDisplay) //-V522 + { + prAddThumbnailToDestroy(file->thumbnailInfo); + } + } + } + } + } + + void IGFD::ThumbnailFeature::SetCreateThumbnailCallback(const CreateThumbnailFun& vCreateThumbnailFun) + { + prCreateThumbnailFun = vCreateThumbnailFun; + } + + void IGFD::ThumbnailFeature::SetDestroyThumbnailCallback(const DestroyThumbnailFun& vCreateThumbnailFun) + { + prDestroyThumbnailFun = vCreateThumbnailFun; + } + + void IGFD::ThumbnailFeature::ManageGPUThumbnails() + { + if (prCreateThumbnailFun) + { + if (!prThumbnailToCreate.empty()) + { + for (const auto& file : prThumbnailToCreate) + { + if (file.use_count()) + { + prCreateThumbnailFun(&file->thumbnailInfo); + } + } + prThumbnailToCreateMutex.lock(); + prThumbnailToCreate.clear(); + prThumbnailToCreateMutex.unlock(); + } + } + else + { + printf("No Callback found for create texture\nYou need to define the callback with a call to SetCreateThumbnailCallback\n"); + } + + if (prDestroyThumbnailFun) + { + if (!prThumbnailToDestroy.empty()) + { + for (auto thumbnail : prThumbnailToDestroy) + { + prDestroyThumbnailFun(&thumbnail); + } + prThumbnailToDestroyMutex.lock(); + prThumbnailToDestroy.clear(); + prThumbnailToDestroyMutex.unlock(); + } + } + else + { + printf("No Callback found for destroy texture\nYou need to define the callback with a call to SetCreateThumbnailCallback\n"); + } + } + +#endif // USE_THUMBNAILS + + ///////////////////////////////////////////////////////////////////////////////////// + //// BOOKMARK FEATURE /////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// + + IGFD::BookMarkFeature::BookMarkFeature() + { +#ifdef USE_BOOKMARK + prBookmarkWidth = defaultBookmarkPaneWith; +#endif // USE_BOOKMARK + } + +#ifdef USE_BOOKMARK + void IGFD::BookMarkFeature::prDrawBookmarkButton() + { + IMGUI_TOGGLE_BUTTON(bookmarksButtonString, &prBookmarkPaneShown); + + if (ImGui::IsItemHovered()) + ImGui::SetTooltip(bookmarksButtonHelpString); + } + bool IGFD::BookMarkFeature::prDrawBookmarkPane(FileDialogInternal& vFileDialogInternal, const ImVec2& vSize) + { + bool res = false; + + ImGui::BeginChild("##bookmarkpane", vSize); + + static int selectedBookmarkForEdition = -1; + + if (IMGUI_BUTTON(addBookmarkButtonString "##ImGuiFileDialogAddBookmark")) + { + if (!vFileDialogInternal.puFileManager.IsComposerEmpty()) + { + BookmarkStruct bookmark; + bookmark.name = vFileDialogInternal.puFileManager.GetBack(); + bookmark.path = vFileDialogInternal.puFileManager.GetCurrentPath(); + prBookmarks.push_back(bookmark); + } + } + if (selectedBookmarkForEdition >= 0 && + selectedBookmarkForEdition < (int)prBookmarks.size()) + { + ImGui::SameLine(); + if (IMGUI_BUTTON(removeBookmarkButtonString "##ImGuiFileDialogAddBookmark")) + { + prBookmarks.erase(prBookmarks.begin() + selectedBookmarkForEdition); + if (selectedBookmarkForEdition == (int)prBookmarks.size()) + selectedBookmarkForEdition--; + } + + if (selectedBookmarkForEdition >= 0 && + selectedBookmarkForEdition < (int)prBookmarks.size()) + { + ImGui::SameLine(); + + ImGui::PushItemWidth(vSize.x - ImGui::GetCursorPosX()); + if (ImGui::InputText("##ImGuiFileDialogBookmarkEdit", prBookmarkEditBuffer, MAX_FILE_DIALOG_NAME_BUFFER)) + { + prBookmarks[(size_t)selectedBookmarkForEdition].name = std::string(prBookmarkEditBuffer); + } + ImGui::PopItemWidth(); + } + } + + ImGui::Separator(); + + if (!prBookmarks.empty()) + { + prBookmarkClipper.Begin((int)prBookmarks.size(), ImGui::GetTextLineHeightWithSpacing()); + while (prBookmarkClipper.Step()) + { + for (int i = prBookmarkClipper.DisplayStart; i < prBookmarkClipper.DisplayEnd; i++) + { + if (i < 0) continue; + const BookmarkStruct& bookmark = prBookmarks[(size_t)i]; + ImGui::PushID(i); + if (ImGui::Selectable(bookmark.name.c_str(), selectedBookmarkForEdition == i, + ImGuiSelectableFlags_AllowDoubleClick) | + (selectedBookmarkForEdition == -1 && + bookmark.path == vFileDialogInternal.puFileManager.GetCurrentPath())) // select if path is current + { + selectedBookmarkForEdition = i; + IGFD::Utils::ResetBuffer(prBookmarkEditBuffer); + IGFD::Utils::AppendToBuffer(prBookmarkEditBuffer, MAX_FILE_DIALOG_NAME_BUFFER, bookmark.name); + + if (ImGui::IsMouseDoubleClicked(0)) // apply path + { + vFileDialogInternal.puFileManager.SetCurrentPath(bookmark.path); + vFileDialogInternal.puFileManager.OpenCurrentPath(vFileDialogInternal); + res = true; + } + } + ImGui::PopID(); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s", bookmark.path.c_str()); //-V111 + } + } + prBookmarkClipper.End(); + } + + ImGui::EndChild(); + + return res; + } + + std::string IGFD::BookMarkFeature::SerializeBookmarks() + { + std::string res; + + size_t idx = 0; + for (auto& it : prBookmarks) + { + if (idx++ != 0) + res += "##"; // ## because reserved by imgui, so an input text cant have ## + res += it.name + "##" + it.path; + } + + return res; + } + + void IGFD::BookMarkFeature::DeserializeBookmarks(const std::string& vBookmarks) + { + if (!vBookmarks.empty()) + { + prBookmarks.clear(); + auto arr = IGFD::Utils::SplitStringToVector(vBookmarks, '#', false); + for (size_t i = 0; i < arr.size(); i += 2) + { + BookmarkStruct bookmark; + bookmark.name = arr[i]; + if (i + 1 < arr.size()) // for avoid crash if arr size is impair due to user mistake after edition + { + // if bad format we jump this bookmark + bookmark.path = arr[i + 1]; + prBookmarks.push_back(bookmark); + } + } + } + } +#endif // USE_BOOKMARK + + ///////////////////////////////////////////////////////////////////////////////////// + //// KEY EXPLORER FEATURE /////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// + + KeyExplorerFeature::KeyExplorerFeature() = default; + +#ifdef USE_EXPLORATION_BY_KEYS + bool IGFD::KeyExplorerFeature::prLocateItem_Loop(FileDialogInternal& vFileDialogInternal, ImWchar vC) + { + bool found = false; + + auto& fdi = vFileDialogInternal.puFileManager; + if (!fdi.IsFilteredListEmpty()) + { + auto countFiles = fdi.GetFilteredListSize(); + for (size_t i = prLocateFileByInputChar_lastFileIdx; i < countFiles; i++) + { + auto nfo = fdi.GetFilteredFileAt(i); + if (nfo.use_count()) + { + if (nfo->fileNameExt_optimized[0] == vC || // lower case search //-V522 + nfo->fileNameExt[0] == vC) // maybe upper case search + { + //float p = ((float)i) * ImGui::GetTextLineHeightWithSpacing(); + float p = (float)((double)i / (double)countFiles) * ImGui::GetScrollMaxY(); + ImGui::SetScrollY(p); + prLocateFileByInputChar_lastFound = true; + prLocateFileByInputChar_lastFileIdx = i; + prStartFlashItem(prLocateFileByInputChar_lastFileIdx); + + auto infos = fdi.GetFilteredFileAt(prLocateFileByInputChar_lastFileIdx); + if (infos.use_count()) + { + if (infos->fileType == 'd') //-V522 + { + if (fdi.puDLGDirectoryMode) // directory chooser + { + fdi.SelectFileName(vFileDialogInternal, infos); + } + } + else + { + fdi.SelectFileName(vFileDialogInternal, infos); + } + + found = true; + break; + } + } + } + } + } + + return found; + } + + void IGFD::KeyExplorerFeature::prLocateByInputKey(FileDialogInternal& vFileDialogInternal) + { + ImGuiContext& g = *GImGui; + auto& fdi = vFileDialogInternal.puFileManager; + if (!g.ActiveId && !fdi.IsFilteredListEmpty()) + { + auto& queueChar = ImGui::GetIO().InputQueueCharacters; + auto countFiles = fdi.GetFilteredListSize(); + + // point by char + if (!queueChar.empty()) + { + ImWchar c = queueChar.back(); + if (prLocateFileByInputChar_InputQueueCharactersSize != queueChar.size()) + { + if (c == prLocateFileByInputChar_lastChar) // next file starting with same char until + { + if (prLocateFileByInputChar_lastFileIdx < countFiles - 1U) + prLocateFileByInputChar_lastFileIdx++; + else + prLocateFileByInputChar_lastFileIdx = 0; + } + + if (!prLocateItem_Loop(vFileDialogInternal, c)) + { + // not found, loop again from 0 this time + prLocateFileByInputChar_lastFileIdx = 0; + prLocateItem_Loop(vFileDialogInternal, c); + } + + prLocateFileByInputChar_lastChar = c; + } + } + + prLocateFileByInputChar_InputQueueCharactersSize = queueChar.size(); + } + } + + void IGFD::KeyExplorerFeature::prExploreWithkeys(FileDialogInternal& vFileDialogInternal, ImGuiID vListViewID) + { + auto& fdi = vFileDialogInternal.puFileManager; + if (!fdi.IsFilteredListEmpty()) + { + bool canWeExplore = false; + bool hasNav = (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard); + + ImGuiContext& g = *GImGui; + if (!hasNav && !g.ActiveId) // no nav and no activated inputs + canWeExplore = true; + + if (g.NavId && g.NavId == vListViewID) + { + if (ImGui::IsKeyPressedMap(IGFD_KEY_ENTER) || + ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter) || + ImGui::IsKeyPressedMap(ImGuiKey_Space)) + { + ImGui::ActivateItem(vListViewID); + ImGui::SetActiveID(vListViewID, g.CurrentWindow); + } + } + + if (vListViewID == g.LastActiveId-1) // if listview id is the last acticated nav id (ImGui::ActivateItem(vListViewID);) + canWeExplore = true; + + if (canWeExplore) + { + if (ImGui::IsKeyPressedMap(ImGuiKey_Escape)) + { + ImGui::ClearActiveID(); + g.LastActiveId = 0; + } + + auto countFiles = fdi.GetFilteredListSize(); + + // explore + bool exploreByKey = false; + bool enterInDirectory = false; + bool exitDirectory = false; + + if ((hasNav && ImGui::IsKeyPressedMap(ImGuiKey_UpArrow)) || (!hasNav && ImGui::IsKeyPressed(IGFD_KEY_UP))) + { + exploreByKey = true; + if (prLocateFileByInputChar_lastFileIdx > 0) + prLocateFileByInputChar_lastFileIdx--; + else + prLocateFileByInputChar_lastFileIdx = countFiles - 1U; + } + else if ((hasNav && ImGui::IsKeyPressedMap(ImGuiKey_DownArrow)) || (!hasNav && ImGui::IsKeyPressed(IGFD_KEY_DOWN))) + { + exploreByKey = true; + if (prLocateFileByInputChar_lastFileIdx < countFiles - 1U) + prLocateFileByInputChar_lastFileIdx++; + else + prLocateFileByInputChar_lastFileIdx = 0U; + } + else if (ImGui::IsKeyReleased(IGFD_KEY_ENTER)) + { + exploreByKey = true; + enterInDirectory = true; + } + else if (ImGui::IsKeyReleased(IGFD_KEY_BACKSPACE)) + { + exploreByKey = true; + exitDirectory = true; + } + + if (exploreByKey) + { + //float totalHeight = prFilteredFileList.size() * ImGui::GetTextLineHeightWithSpacing(); + float p = (float)((double)prLocateFileByInputChar_lastFileIdx / (double)(countFiles - 1U)) * ImGui::GetScrollMaxY();// seems not udpated in tables version outside tables + //float p = ((float)locateFileByInputChar_lastFileIdx) * ImGui::GetTextLineHeightWithSpacing(); + ImGui::SetScrollY(p); + prStartFlashItem(prLocateFileByInputChar_lastFileIdx); + + auto infos = fdi.GetFilteredFileAt(prLocateFileByInputChar_lastFileIdx); + if (infos.use_count()) + { + if (infos->fileType == 'd') //-V522 + { + if (!fdi.puDLGDirectoryMode || enterInDirectory) + { + if (enterInDirectory) + { + if (fdi.SelectDirectory(infos)) + { + // changement de repertoire + vFileDialogInternal.puFileManager.OpenCurrentPath(vFileDialogInternal); + if (prLocateFileByInputChar_lastFileIdx > countFiles - 1U) + { + prLocateFileByInputChar_lastFileIdx = 0; + } + } + } + } + else // directory chooser + { + fdi.SelectFileName(vFileDialogInternal, infos); + } + } + else + { + fdi.SelectFileName(vFileDialogInternal, infos); + } + + if (exitDirectory) + { + auto nfo = std::make_shared(); + nfo->fileNameExt = ".."; + + if (fdi.SelectDirectory(nfo)) + { + // changement de repertoire + vFileDialogInternal.puFileManager.OpenCurrentPath(vFileDialogInternal); + if (prLocateFileByInputChar_lastFileIdx > countFiles - 1U) + { + prLocateFileByInputChar_lastFileIdx = 0; + } + } +#ifdef WIN32 + else + { + if (fdi.GetComposerSize() == 1U) + { + if (fdi.GetDrives()) + { + fdi.ApplyFilteringOnFileList(vFileDialogInternal); + } + } + } +#endif // WIN32 + } + } + } + } + } + } + + bool IGFD::KeyExplorerFeature::prFlashableSelectable(const char* label, bool selected, + ImGuiSelectableFlags flags, bool vFlashing, const ImVec2& size_arg) + { + using namespace ImGui; + + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + // Submit label or explicit size to ItemSize(), whereas ItemAdd() will submit a larger/spanning rectangle. + ImGuiID id = window->GetID(label); + ImVec2 label_size = CalcTextSize(label, nullptr, true); + ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y); //-V550 + ImVec2 pos = window->DC.CursorPos; + pos.y += window->DC.CurrLineTextBaseOffset; + ItemSize(size, 0.0f); + + // Fill horizontal space + // We don't support (size < 0.0f) in Selectable() because the ItemSpacing extension would make explicitly right-aligned sizes not visibly match other widgets. + const bool span_all_columns = (flags & ImGuiSelectableFlags_SpanAllColumns) != 0; + const float min_x = span_all_columns ? window->ParentWorkRect.Min.x : pos.x; + const float max_x = span_all_columns ? window->ParentWorkRect.Max.x : window->WorkRect.Max.x; + if (fabs(size_arg.x) < FLT_EPSILON || (flags & ImGuiSelectableFlags_SpanAvailWidth)) + size.x = ImMax(label_size.x, max_x - min_x); + + // Text stays at the submission position, but bounding box may be extended on both sides + const ImVec2 text_min = pos; + const ImVec2 text_max(min_x + size.x, pos.y + size.y); + + // Selectables are meant to be tightly packed together with no click-gap, so we extend their box to cover spacing between selectable. + ImRect bb(min_x, pos.y, text_max.x, text_max.y); + if ((flags & ImGuiSelectableFlags_NoPadWithHalfSpacing) == 0) + { + const float spacing_x = span_all_columns ? 0.0f : style.ItemSpacing.x; + const float spacing_y = style.ItemSpacing.y; + const float spacing_L = IM_FLOOR(spacing_x * 0.50f); + const float spacing_U = IM_FLOOR(spacing_y * 0.50f); + bb.Min.x -= spacing_L; + bb.Min.y -= spacing_U; + bb.Max.x += (spacing_x - spacing_L); + bb.Max.y += (spacing_y - spacing_U); + } + //if (g.IO.KeyCtrl) { GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(0, 255, 0, 255)); } + + // Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackground for every Selectable.. + const float backup_clip_rect_min_x = window->ClipRect.Min.x; + const float backup_clip_rect_max_x = window->ClipRect.Max.x; + if (span_all_columns) + { + window->ClipRect.Min.x = window->ParentWorkRect.Min.x; + window->ClipRect.Max.x = window->ParentWorkRect.Max.x; + } + + bool item_add; + const bool disabled_item = (flags & ImGuiSelectableFlags_Disabled) != 0; + if (disabled_item) + { + ImGuiItemFlags backup_item_flags = g.CurrentItemFlags; + g.CurrentItemFlags |= ImGuiItemFlags_Disabled; + item_add = ItemAdd(bb, id); + g.CurrentItemFlags = backup_item_flags; + } + else + { + item_add = ItemAdd(bb, id); + } + + if (span_all_columns) + { + window->ClipRect.Min.x = backup_clip_rect_min_x; + window->ClipRect.Max.x = backup_clip_rect_max_x; + } + + if (!item_add) + return false; + + const bool disabled_global = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0; + if (disabled_item && !disabled_global) // Only testing this as an optimization + BeginDisabled(true); + + // FIXME: We can standardize the behavior of those two, we could also keep the fast path of override ClipRect + full push on render only, + // which would be advantageous since most selectable are not selected. + if (span_all_columns && window->DC.CurrentColumns) + PushColumnsBackground(); + else if (span_all_columns && g.CurrentTable) + TablePushBackgroundChannel(); + + // We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries + ImGuiButtonFlags button_flags = 0; + if (flags & ImGuiSelectableFlags_NoHoldingActiveID) { button_flags |= ImGuiButtonFlags_NoHoldingActiveId; } + if (flags & ImGuiSelectableFlags_SelectOnClick) { button_flags |= ImGuiButtonFlags_PressedOnClick; } + if (flags & ImGuiSelectableFlags_SelectOnRelease) { button_flags |= ImGuiButtonFlags_PressedOnRelease; } + if (flags & ImGuiSelectableFlags_AllowDoubleClick) { button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; } + if (flags & ImGuiSelectableFlags_AllowItemOverlap) { button_flags |= ImGuiButtonFlags_AllowItemOverlap; } + + const bool was_selected = selected; + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags); + + // Auto-select when moved into + // - This will be more fully fleshed in the range-select branch + // - This is not exposed as it won't nicely work with some user side handling of shift/control + // - We cannot do 'if (g.NavJustMovedToId != id) { selected = false; pressed = was_selected; }' for two reasons + // - (1) it would require focus scope to be set, need exposing PushFocusScope() or equivalent (e.g. BeginSelection() calling PushFocusScope()) + // - (2) usage will fail with clipped items + // The multi-select API aim to fix those issues, e.g. may be replaced with a BeginSelection() API. + if ((flags & ImGuiSelectableFlags_SelectOnNav) && g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == window->DC.NavFocusScopeIdCurrent) + if (g.NavJustMovedToId == id) + selected = pressed = true; + + // Update NavId when clicking or when Hovering (this doesn't happen on most widgets), so navigation can be resumed with gamepad/keyboard + if (pressed || (hovered && (flags & ImGuiSelectableFlags_SetNavIdOnHover))) + { + if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent) + { + SetNavID(id, window->DC.NavLayerCurrent, window->DC.NavFocusScopeIdCurrent, ImRect(bb.Min - window->Pos, bb.Max - window->Pos)); + g.NavDisableHighlight = true; + } + } + if (pressed) + MarkItemEdited(id); + + if (flags & ImGuiSelectableFlags_AllowItemOverlap) + SetItemAllowOverlap(); + + // In this branch, Selectable() cannot toggle the selection so this will never trigger. + if (selected != was_selected) //-V547 + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection; + + // Render + if ((held && (flags & ImGuiSelectableFlags_DrawHoveredWhenHeld)) || vFlashing) + hovered = true; + if (hovered || selected) + { + const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); + RenderFrame(bb.Min, bb.Max, col, false, 0.0f); + } + RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding); + + if (span_all_columns && window->DC.CurrentColumns) + PopColumnsBackground(); + else if (span_all_columns && g.CurrentTable) + TablePopBackgroundChannel(); + + RenderTextClipped(text_min, text_max, label, nullptr, &label_size, style.SelectableTextAlign, &bb); + + // Automatically close popups + if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !(g.LastItemData.InFlags & ImGuiItemFlags_SelectableDontClosePopup)) + CloseCurrentPopup(); + + if (disabled_item && !disabled_global) + EndDisabled(); + + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); + return pressed; //-V1020 + } + + void IGFD::KeyExplorerFeature::prStartFlashItem(size_t vIdx) + { + prFlashAlpha = 1.0f; + prFlashedItem = vIdx; + } + + bool IGFD::KeyExplorerFeature::prBeginFlashItem(size_t vIdx) + { + bool res = false; + + if (prFlashedItem == vIdx && + std::abs(prFlashAlpha - 0.0f) > 0.00001f) + { + prFlashAlpha -= prFlashAlphaAttenInSecs * ImGui::GetIO().DeltaTime; + if (prFlashAlpha < 0.0f) prFlashAlpha = 0.0f; + + ImVec4 hov = ImGui::GetStyleColorVec4(ImGuiCol_HeaderHovered); + hov.w = prFlashAlpha; + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, hov); + res = true; + } + + return res; + } + + void IGFD::KeyExplorerFeature::prEndFlashItem() + { + ImGui::PopStyleColor(); + } + + void IGFD::KeyExplorerFeature::SetFlashingAttenuationInSeconds(float vAttenValue) + { + prFlashAlphaAttenInSecs = 1.0f / ImMax(vAttenValue, 0.01f); + } +#endif // USE_EXPLORATION_BY_KEYS + + ///////////////////////////////////////////////////////////////////////////////////// + //// FILE DIALOG CONSTRUCTOR / DESTRUCTOR /////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// + + IGFD::FileDialog::FileDialog() : BookMarkFeature(), KeyExplorerFeature(), ThumbnailFeature() {} + IGFD::FileDialog::~FileDialog() = default; + + ////////////////////////////////////////////////////////////////////////////////////////////////// + ///// FILE DIALOG STANDARD DIALOG //////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////// + + // path and fileNameExt can be specified + void IGFD::FileDialog::OpenDialog( + const std::string& vKey, + const std::string& vTitle, + const char* vFilters, + const std::string& vPath, + const std::string& vFileName, + const int& vCountSelectionMax, + UserDatas vUserDatas, + ImGuiFileDialogFlags vFlags) + { + if (prFileDialogInternal.puShowDialog) // if already opened, quit + return; + + prFileDialogInternal.ResetForNewDialog(); + + prFileDialogInternal.puDLGkey = vKey; + prFileDialogInternal.puDLGtitle = vTitle; + prFileDialogInternal.puDLGuserDatas = vUserDatas; + prFileDialogInternal.puDLGflags = vFlags; + prFileDialogInternal.puDLGoptionsPane = nullptr; + prFileDialogInternal.puDLGoptionsPaneWidth = 0.0f; + prFileDialogInternal.puDLGmodal = false; + + prFileDialogInternal.puFilterManager.puDLGdefaultExt.clear(); + prFileDialogInternal.puFilterManager.ParseFilters(vFilters); + + prFileDialogInternal.puFileManager.puDLGDirectoryMode = (vFilters == nullptr); + if (vPath.empty()) + prFileDialogInternal.puFileManager.puDLGpath = prFileDialogInternal.puFileManager.GetCurrentPath(); + else + prFileDialogInternal.puFileManager.puDLGpath = vPath; + prFileDialogInternal.puFileManager.SetCurrentPath(vPath); + prFileDialogInternal.puFileManager.puDLGcountSelectionMax = (size_t)vCountSelectionMax; + prFileDialogInternal.puFileManager.SetDefaultFileName(vFileName); + + prFileDialogInternal.puFileManager.ClearAll(); + + prFileDialogInternal.puShowDialog = true; // open dialog + } + + // path and filename are obtained from filePathName + void IGFD::FileDialog::OpenDialog( + const std::string& vKey, + const std::string& vTitle, + const char* vFilters, + const std::string& vFilePathName, + const int& vCountSelectionMax, + UserDatas vUserDatas, + ImGuiFileDialogFlags vFlags) + { + if (prFileDialogInternal.puShowDialog) // if already opened, quit + return; + + prFileDialogInternal.ResetForNewDialog(); + + prFileDialogInternal.puDLGkey = vKey; + prFileDialogInternal.puDLGtitle = vTitle; + prFileDialogInternal.puDLGoptionsPane = nullptr; + prFileDialogInternal.puDLGoptionsPaneWidth = 0.0f; + prFileDialogInternal.puDLGuserDatas = vUserDatas; + prFileDialogInternal.puDLGflags = vFlags; + prFileDialogInternal.puDLGmodal = false; + + auto ps = IGFD::Utils::ParsePathFileName(vFilePathName); + if (ps.isOk) + { + prFileDialogInternal.puFileManager.puDLGpath = ps.path; + prFileDialogInternal.puFileManager.SetDefaultFileName(vFilePathName); + prFileDialogInternal.puFilterManager.puDLGdefaultExt = "." + ps.ext; + } + else + { + prFileDialogInternal.puFileManager.puDLGpath = prFileDialogInternal.puFileManager.GetCurrentPath(); + prFileDialogInternal.puFileManager.SetDefaultFileName(""); + prFileDialogInternal.puFilterManager.puDLGdefaultExt.clear(); + } + + prFileDialogInternal.puFilterManager.ParseFilters(vFilters); + prFileDialogInternal.puFilterManager.SetSelectedFilterWithExt( + prFileDialogInternal.puFilterManager.puDLGdefaultExt); + + prFileDialogInternal.puFileManager.SetCurrentPath(prFileDialogInternal.puFileManager.puDLGpath); + + prFileDialogInternal.puFileManager.puDLGDirectoryMode = (vFilters == nullptr); + prFileDialogInternal.puFileManager.puDLGcountSelectionMax = vCountSelectionMax; //-V101 + + prFileDialogInternal.puFileManager.ClearAll(); + + prFileDialogInternal.puShowDialog = true; + } + + // with pane + // path and fileNameExt can be specified + void IGFD::FileDialog::OpenDialog( + const std::string& vKey, + const std::string& vTitle, + const char* vFilters, + const std::string& vPath, + const std::string& vFileName, + const PaneFun& vSidePane, + const float& vSidePaneWidth, + const int& vCountSelectionMax, + UserDatas vUserDatas, + ImGuiFileDialogFlags vFlags) + { + if (prFileDialogInternal.puShowDialog) // if already opened, quit + return; + + prFileDialogInternal.ResetForNewDialog(); + + prFileDialogInternal.puDLGkey = vKey; + prFileDialogInternal.puDLGtitle = vTitle; + prFileDialogInternal.puDLGuserDatas = vUserDatas; + prFileDialogInternal.puDLGflags = vFlags; + prFileDialogInternal.puDLGoptionsPane = vSidePane; + prFileDialogInternal.puDLGoptionsPaneWidth = vSidePaneWidth; + prFileDialogInternal.puDLGmodal = false; + + prFileDialogInternal.puFilterManager.puDLGdefaultExt.clear(); + prFileDialogInternal.puFilterManager.ParseFilters(vFilters); + + prFileDialogInternal.puFileManager.puDLGcountSelectionMax = (size_t)vCountSelectionMax; + prFileDialogInternal.puFileManager.puDLGDirectoryMode = (vFilters == nullptr); + if (vPath.empty()) + prFileDialogInternal.puFileManager.puDLGpath = prFileDialogInternal.puFileManager.GetCurrentPath(); + else + prFileDialogInternal.puFileManager.puDLGpath = vPath; + + prFileDialogInternal.puFileManager.SetCurrentPath(prFileDialogInternal.puFileManager.puDLGpath); + + prFileDialogInternal.puFileManager.SetDefaultFileName(vFileName); + + prFileDialogInternal.puFileManager.ClearAll(); + + prFileDialogInternal.puShowDialog = true; // open dialog + } + + // with pane + // path and filename are obtained from filePathName + void IGFD::FileDialog::OpenDialog( + const std::string& vKey, + const std::string& vTitle, + const char* vFilters, + const std::string& vFilePathName, + const PaneFun& vSidePane, + const float& vSidePaneWidth, + const int& vCountSelectionMax, + UserDatas vUserDatas, + ImGuiFileDialogFlags vFlags) + { + if (prFileDialogInternal.puShowDialog) // if already opened, quit + return; + + prFileDialogInternal.ResetForNewDialog(); + + prFileDialogInternal.puDLGkey = vKey; + prFileDialogInternal.puDLGtitle = vTitle; + prFileDialogInternal.puDLGoptionsPane = vSidePane; + prFileDialogInternal.puDLGoptionsPaneWidth = vSidePaneWidth; + prFileDialogInternal.puDLGuserDatas = vUserDatas; + prFileDialogInternal.puDLGflags = vFlags; + prFileDialogInternal.puDLGmodal = false; + + auto ps = IGFD::Utils::ParsePathFileName(vFilePathName); + if (ps.isOk) + { + prFileDialogInternal.puFileManager.puDLGpath = ps.path; + prFileDialogInternal.puFileManager.SetDefaultFileName(vFilePathName); + prFileDialogInternal.puFilterManager.puDLGdefaultExt = "." + ps.ext; + } + else + { + prFileDialogInternal.puFileManager.puDLGpath = prFileDialogInternal.puFileManager.GetCurrentPath(); + prFileDialogInternal.puFileManager.SetDefaultFileName(""); + prFileDialogInternal.puFilterManager.puDLGdefaultExt.clear(); + } + + prFileDialogInternal.puFileManager.SetCurrentPath(prFileDialogInternal.puFileManager.puDLGpath); + + prFileDialogInternal.puFileManager.puDLGcountSelectionMax = vCountSelectionMax; //-V101 + prFileDialogInternal.puFileManager.puDLGDirectoryMode = (vFilters == nullptr); + prFileDialogInternal.puFilterManager.ParseFilters(vFilters); + prFileDialogInternal.puFilterManager.SetSelectedFilterWithExt( + prFileDialogInternal.puFilterManager.puDLGdefaultExt); + + prFileDialogInternal.puFileManager.ClearAll(); + + prFileDialogInternal.puShowDialog = true; + } + + ////////////////////////////////////////////////////////////////////////////////////////////////// + ///// FILE DIALOG MODAL DIALOG /////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////// + + void IGFD::FileDialog::OpenModal( + const std::string& vKey, + const std::string& vTitle, + const char* vFilters, + const std::string& vPath, + const std::string& vFileName, + const int& vCountSelectionMax, + UserDatas vUserDatas, + ImGuiFileDialogFlags vFlags) + { + if (prFileDialogInternal.puShowDialog) // if already opened, quit + return; + + OpenDialog( + vKey, vTitle, vFilters, + vPath, vFileName, + vCountSelectionMax, vUserDatas, vFlags); + + prFileDialogInternal.puDLGmodal = true; + } + + void IGFD::FileDialog::OpenModal( + const std::string& vKey, + const std::string& vTitle, + const char* vFilters, + const std::string& vFilePathName, + const int& vCountSelectionMax, + UserDatas vUserDatas, + ImGuiFileDialogFlags vFlags) + { + if (prFileDialogInternal.puShowDialog) // if already opened, quit + return; + + OpenDialog( + vKey, vTitle, vFilters, + vFilePathName, + vCountSelectionMax, vUserDatas, vFlags); + + prFileDialogInternal.puDLGmodal = true; + } + + // with pane + // path and fileNameExt can be specified + void IGFD::FileDialog::OpenModal( + const std::string& vKey, + const std::string& vTitle, + const char* vFilters, + const std::string& vPath, + const std::string& vFileName, + const PaneFun& vSidePane, + const float& vSidePaneWidth, + const int& vCountSelectionMax, + UserDatas vUserDatas, + ImGuiFileDialogFlags vFlags) + { + if (prFileDialogInternal.puShowDialog) // if already opened, quit + return; + + OpenDialog( + vKey, vTitle, vFilters, + vPath, vFileName, + vSidePane, vSidePaneWidth, + vCountSelectionMax, vUserDatas, vFlags); + + prFileDialogInternal.puDLGmodal = true; + } + + // with pane + // path and filename are obtained from filePathName + void IGFD::FileDialog::OpenModal( + const std::string& vKey, + const std::string& vTitle, + const char* vFilters, + const std::string& vFilePathName, + const PaneFun& vSidePane, + const float& vSidePaneWidth, + const int& vCountSelectionMax, + UserDatas vUserDatas, + ImGuiFileDialogFlags vFlags) + { + if (prFileDialogInternal.puShowDialog) // if already opened, quit + return; + + OpenDialog( + vKey, vTitle, vFilters, + vFilePathName, + vSidePane, vSidePaneWidth, + vCountSelectionMax, vUserDatas, vFlags); + + prFileDialogInternal.puDLGmodal = true; + } + + ////////////////////////////////////////////////////////////////////////////////////////////////// + ///// FILE DIALOG DISPLAY FUNCTION /////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////// + + bool IGFD::FileDialog::Display(const std::string& vKey, ImGuiWindowFlags vFlags, ImVec2 vMinSize, ImVec2 vMaxSize) + { + bool res = false; + + if (prFileDialogInternal.puShowDialog && prFileDialogInternal.puDLGkey == vKey) + { + if (prFileDialogInternal.puUseCustomLocale) + setlocale(prFileDialogInternal.puLocaleCategory, prFileDialogInternal.puLocaleBegin.c_str()); + + auto& fdFile = prFileDialogInternal.puFileManager; + auto& fdFilter = prFileDialogInternal.puFilterManager; + + static ImGuiWindowFlags flags; + + // to be sure than only one dialog is displayed per frame + ImGuiContext& g = *GImGui; + if (g.FrameCount == prFileDialogInternal.puLastImGuiFrameCount) // one instance was displayed this frame before for this key +> quit + return res; + prFileDialogInternal.puLastImGuiFrameCount = g.FrameCount; // mark this instance as used this frame + + std::string name = prFileDialogInternal.puDLGtitle + "##" + prFileDialogInternal.puDLGkey; + if (prFileDialogInternal.puName != name) + { + fdFile.ClearComposer(); + fdFile.ClearFileLists(); + flags = vFlags; + } + + NewFrame(); + +#ifdef IMGUI_HAS_VIEWPORT + if (!ImGui::GetIO().ConfigViewportsNoDecoration) + { + // https://github.com/ocornut/imgui/issues/4534 + ImGuiWindowClass window_class; + window_class.ViewportFlagsOverrideClear = ImGuiViewportFlags_NoDecoration; + ImGui::SetNextWindowClass(&window_class); + } +#endif // IMGUI_HAS_VIEWPORT + + ImGui::SetNextWindowSizeConstraints(vMinSize, vMaxSize); + + bool beg = false; + if (prFileDialogInternal.puDLGmodal && + !prFileDialogInternal.puOkResultToConfirm) // disable modal because the confirm dialog for overwrite is a new modal + { + ImGui::OpenPopup(name.c_str()); + beg = ImGui::BeginPopupModal(name.c_str(), (bool*)nullptr, + flags | ImGuiWindowFlags_NoScrollbar); + } + else + { + beg = ImGui::Begin(name.c_str(), (bool*)nullptr, flags | ImGuiWindowFlags_NoScrollbar); + } + if (beg) + { +#ifdef IMGUI_HAS_VIEWPORT + // if decoration is enabled we disable the resizing feature of imgui for avoid crash with SDL2 and GLFW3 + if (ImGui::GetIO().ConfigViewportsNoDecoration) + { + flags = vFlags; + } + else + { + auto win = ImGui::GetCurrentWindowRead(); + if (win->Viewport->Idx != 0) + flags |= ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar; + else + flags = vFlags; + } +#endif // IMGUI_HAS_VIEWPORT + + prFileDialogInternal.puName = name; //-V820 + puAnyWindowsHovered |= ImGui::IsWindowHovered(); + + if (fdFile.puDLGpath.empty()) + fdFile.puDLGpath = "."; // defaut path is '.' + + fdFilter.SetDefaultFilterIfNotDefined(); + + // init list of files + if (fdFile.IsFileListEmpty() && !fdFile.puShowDrives) + { + IGFD::Utils::ReplaceString(fdFile.puDLGDefaultFileName, fdFile.puDLGpath, ""); // local path + if (!fdFile.puDLGDefaultFileName.empty()) + { + fdFile.SetDefaultFileName(fdFile.puDLGDefaultFileName); + fdFilter.SetSelectedFilterWithExt(fdFilter.puDLGdefaultExt); + } + else if (fdFile.puDLGDirectoryMode) // directory mode + fdFile.SetDefaultFileName("."); + fdFile.ScanDir(prFileDialogInternal, fdFile.puDLGpath); + } + + // draw dialog parts + prDrawHeader(); // bookmark, directory, path + prDrawContent(); // bookmark, files view, side pane + res = prDrawFooter(); // file field, filter combobox, ok/cancel buttons + + EndFrame(); + + // for display in dialog center, the confirm to overwrite dlg + prFileDialogInternal.puDialogCenterPos = ImGui::GetCurrentWindowRead()->ContentRegionRect.GetCenter(); + + // when the confirm to overwrite dialog will appear we need to + // disable the modal mode of the main file dialog + // see prOkResultToConfirm under + if (prFileDialogInternal.puDLGmodal && + !prFileDialogInternal.puOkResultToConfirm) + ImGui::EndPopup(); + } + + // same things here regarding prOkResultToConfirm + if (!prFileDialogInternal.puDLGmodal || prFileDialogInternal.puOkResultToConfirm) + ImGui::End(); + + // confirm the result and show the confirm to overwrite dialog if needed + res = prConfirm_Or_OpenOverWriteFileDialog_IfNeeded(res, vFlags); + + if (prFileDialogInternal.puUseCustomLocale) + setlocale(prFileDialogInternal.puLocaleCategory, prFileDialogInternal.puLocaleEnd.c_str()); + } + + return res; + } + + void IGFD::FileDialog::NewFrame() + { + prFileDialogInternal.NewFrame(); + NewThumbnailFrame(prFileDialogInternal); + } + + void IGFD::FileDialog::EndFrame() + { + EndThumbnailFrame(prFileDialogInternal); + prFileDialogInternal.EndFrame(); + + } + void IGFD::FileDialog::QuitFrame() + { + QuitThumbnailFrame(prFileDialogInternal); + } + + void IGFD::FileDialog::prDrawHeader() + { +#ifdef USE_BOOKMARK + prDrawBookmarkButton(); + ImGui::SameLine(); +#endif // USE_BOOKMARK + + prFileDialogInternal.puFileManager.DrawDirectoryCreation(prFileDialogInternal); + ImGui::SameLine(); + ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); + ImGui::SameLine(); + prFileDialogInternal.puFileManager.DrawPathComposer(prFileDialogInternal); + +#ifdef USE_THUMBNAILS + if (!(prFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_DisableThumbnailMode)) + { + prDrawDisplayModeToolBar(); + ImGui::SameLine(); + ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); + ImGui::SameLine(); + } +#endif // USE_THUMBNAILS + + prFileDialogInternal.puSearchManager.DrawSearchBar(prFileDialogInternal); + } + + void IGFD::FileDialog::prDrawContent() + { + ImVec2 size = ImGui::GetContentRegionAvail() - ImVec2(0.0f, prFileDialogInternal.puFooterHeight); + +#ifdef USE_BOOKMARK + if (prBookmarkPaneShown) + { + //size.x -= prBookmarkWidth; + float otherWidth = size.x - prBookmarkWidth; + ImGui::PushID("##splitterbookmark"); + IGFD::Utils::Splitter(true, 4.0f, + &prBookmarkWidth, &otherWidth, 10.0f, + 10.0f + prFileDialogInternal.puDLGoptionsPaneWidth, size.y); + ImGui::PopID(); + size.x -= otherWidth; + prDrawBookmarkPane(prFileDialogInternal, size); + ImGui::SameLine(); + } +#endif // USE_BOOKMARK + + size.x = ImGui::GetContentRegionAvail().x - prFileDialogInternal.puDLGoptionsPaneWidth; + + if (prFileDialogInternal.puDLGoptionsPane) + { + ImGui::PushID("##splittersidepane"); + IGFD::Utils::Splitter(true, 4.0f, &size.x, &prFileDialogInternal.puDLGoptionsPaneWidth, 10.0f, 10.0f, size.y); + ImGui::PopID(); + } + +#ifdef USE_THUMBNAILS + if (prFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_DisableThumbnailMode) + { + prDrawFileListView(size); + } + else + { + switch (prDisplayMode) + { + case DisplayModeEnum::FILE_LIST: + prDrawFileListView(size); + break; + case DisplayModeEnum::THUMBNAILS_LIST: + prDrawThumbnailsListView(size); + break; + case DisplayModeEnum::THUMBNAILS_GRID: + prDrawThumbnailsGridView(size); + } + } +#else + prDrawFileListView(size); +#endif // USE_THUMBNAILS + + if (prFileDialogInternal.puDLGoptionsPane) + { + prDrawSidePane(size.y); + } + } + + bool IGFD::FileDialog::prDrawFooter() + { + auto& fdFile = prFileDialogInternal.puFileManager; + + float posY = ImGui::GetCursorPos().y; // height of last bar calc + + if (!fdFile.puDLGDirectoryMode) + ImGui::Text(fileNameString); + else // directory chooser + ImGui::Text(dirNameString); + + ImGui::SameLine(); + + // Input file fields + float width = ImGui::GetContentRegionAvail().x; + if (!fdFile.puDLGDirectoryMode) + width -= FILTER_COMBO_WIDTH; + ImGui::PushItemWidth(width); + ImGui::InputText("##FileName", fdFile.puFileNameBuffer, MAX_FILE_DIALOG_NAME_BUFFER); + if (ImGui::GetItemID() == ImGui::GetActiveID()) + prFileDialogInternal.puFileInputIsActive = true; + ImGui::PopItemWidth(); + + // combobox of filters + prFileDialogInternal.puFilterManager.DrawFilterComboBox(prFileDialogInternal); + + bool res = false; + + // OK Button + if (prFileDialogInternal.puCanWeContinue && strlen(fdFile.puFileNameBuffer)) + { + if (IMGUI_BUTTON(okButtonString "##validationdialog")) + { + prFileDialogInternal.puIsOk = true; + res = true; + } + + ImGui::SameLine(); + } + + // Cancel Button + if (IMGUI_BUTTON(cancelButtonString "##validationdialog") || + prFileDialogInternal.puNeedToExitDialog) // dialog exit asked + { + prFileDialogInternal.puIsOk = false; + res = true; + } + + prFileDialogInternal.puFooterHeight = ImGui::GetCursorPosY() - posY; + + return res; + } + + bool IGFD::FileDialog::prSelectableItem(int vidx, std::shared_ptr vInfos, bool vSelected, const char* vFmt, ...) + { + if (!vInfos.use_count()) + return false; + + auto& fdi = prFileDialogInternal.puFileManager; + + static ImGuiSelectableFlags selectableFlags = ImGuiSelectableFlags_AllowDoubleClick | + ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_SpanAvailWidth; + + va_list args; + va_start(args, vFmt); + vsnprintf(fdi.puVariadicBuffer, MAX_FILE_DIALOG_NAME_BUFFER, vFmt, args); + va_end(args); + + float h = 0.0f; +#ifdef USE_THUMBNAILS + if (prDisplayMode == DisplayModeEnum::THUMBNAILS_LIST) + h = DisplayMode_ThumbailsList_ImageHeight; +#endif // USE_THUMBNAILS +#ifdef USE_EXPLORATION_BY_KEYS + bool flashed = prBeginFlashItem((size_t)vidx); + bool res = prFlashableSelectable(fdi.puVariadicBuffer, vSelected, selectableFlags, + flashed, ImVec2(-1.0f, h)); + if (flashed) + prEndFlashItem(); +#else // USE_EXPLORATION_BY_KEYS + (void)vidx; // remove a warnings ofr unused var + + bool res = ImGui::Selectable(fdi.puVariadicBuffer, vSelected, selectableFlags, ImVec2(-1.0f, h)); +#endif // USE_EXPLORATION_BY_KEYS + if (res) + { + if (vInfos->fileType == 'd') + { + // nav system, selectebale cause open directory or select directory + if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) + { + if (fdi.puDLGDirectoryMode) // directory chooser + { + fdi.SelectFileName(prFileDialogInternal, vInfos); + } + else + { + fdi.puPathClicked = fdi.SelectDirectory(vInfos); + } + } + else // no nav system => classic behavior + { + if (ImGui::IsMouseDoubleClicked(0)) // 0 -> left mouse button double click + { + fdi.puPathClicked = fdi.SelectDirectory(vInfos); + } + else if (fdi.puDLGDirectoryMode) // directory chooser + { + fdi.SelectFileName(prFileDialogInternal, vInfos); + } + } + + return true; // needToBreakTheloop + } + else + { + fdi.SelectFileName(prFileDialogInternal, vInfos); + } + } + + return false; + } + + void IGFD::FileDialog::prBeginFileColorIconStyle(std::shared_ptr vFileInfos, bool& vOutShowColor, std::string& vOutStr, ImFont** vOutFont) + { + vOutStr.clear(); + vOutShowColor = false; + + if (vFileInfos->fileStyle.use_count()) //-V807 //-V522 + { + vOutShowColor = true; + + *vOutFont = vFileInfos->fileStyle->font; + } + + if (vOutShowColor && !vFileInfos->fileStyle->icon.empty()) vOutStr = vFileInfos->fileStyle->icon; + else if (vFileInfos->fileType == 'd') vOutStr = dirEntryString; + else if (vFileInfos->fileType == 'l') vOutStr = linkEntryString; + else if (vFileInfos->fileType == 'f') vOutStr = fileEntryString; + + vOutStr += " " + vFileInfos->fileNameExt; + + if (vOutShowColor) + ImGui::PushStyleColor(ImGuiCol_Text, vFileInfos->fileStyle->color); + if (*vOutFont) + ImGui::PushFont(*vOutFont); + } + + void IGFD::FileDialog::prEndFileColorIconStyle(const bool& vShowColor, ImFont* vFont) + { + if (vFont) + ImGui::PopFont(); + if (vShowColor) + ImGui::PopStyleColor(); + } + + void IGFD::FileDialog::prDrawFileListView(ImVec2 vSize) + { + auto& fdi = prFileDialogInternal.puFileManager; + + ImGui::PushID(this); + + static ImGuiTableFlags flags = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg | + ImGuiTableFlags_Hideable | ImGuiTableFlags_ScrollY | + ImGuiTableFlags_NoHostExtendY +#ifndef USE_CUSTOM_SORTING_ICON + | ImGuiTableFlags_Sortable +#endif // USE_CUSTOM_SORTING_ICON + ; + auto listViewID = ImGui::GetID("##FileDialog_fileTable"); + if (ImGui::BeginTableEx("##FileDialog_fileTable", listViewID, 4, flags, vSize, 0.0f)) //-V112 + { + ImGui::TableSetupScrollFreeze(0, 1); // Make header always visible + ImGui::TableSetupColumn(fdi.puHeaderFileName.c_str(), ImGuiTableColumnFlags_WidthStretch, -1, 0); + ImGui::TableSetupColumn(fdi.puHeaderFileType.c_str(), ImGuiTableColumnFlags_WidthFixed | + ((prFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_HideColumnType) ? ImGuiTableColumnFlags_DefaultHide : 0), -1, 1); + ImGui::TableSetupColumn(fdi.puHeaderFileSize.c_str(), ImGuiTableColumnFlags_WidthFixed | + ((prFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_HideColumnSize) ? ImGuiTableColumnFlags_DefaultHide : 0), -1, 2); + ImGui::TableSetupColumn(fdi.puHeaderFileDate.c_str(), ImGuiTableColumnFlags_WidthFixed | + ((prFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_HideColumnDate) ? ImGuiTableColumnFlags_DefaultHide : 0), -1, 3); + +#ifndef USE_CUSTOM_SORTING_ICON + // Sort our data if sort specs have been changed! + if (ImGuiTableSortSpecs* sorts_specs = ImGui::TableGetSortSpecs()) + { + if (sorts_specs->SpecsDirty && !fdi.IsFileListEmpty()) + { + if (sorts_specs->Specs->ColumnUserID == 0) + fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_FILENAME, true); + else if (sorts_specs->Specs->ColumnUserID == 1) + fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_TYPE, true); + else if (sorts_specs->Specs->ColumnUserID == 2) + fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_SIZE, true); + else //if (sorts_specs->Specs->ColumnUserID == 3) => alwayd true for the moment, to uncomment if we add a fourth column + fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_DATE, true); + + sorts_specs->SpecsDirty = false; + } + } + + ImGui::TableHeadersRow(); +#else // USE_CUSTOM_SORTING_ICON + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + for (int column = 0; column < 4; column++) //-V112 + { + ImGui::TableSetColumnIndex(column); + const char* column_name = ImGui::TableGetColumnName(column); // Retrieve name passed to TableSetupColumn() + ImGui::PushID(column); + ImGui::TableHeader(column_name); + ImGui::PopID(); + if (ImGui::IsItemClicked()) + { + if (column == 0) + fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_FILENAME, true); + else if (column == 1) + fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_TYPE, true); + else if (column == 2) + fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_SIZE, true); + else //if (column == 3) => alwayd true for the moment, to uncomment if we add a fourth column + fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_DATE, true); + } + } +#endif // USE_CUSTOM_SORTING_ICON + if (!fdi.IsFilteredListEmpty()) + { + std::string _str; + ImFont* _font = nullptr; + bool _showColor = false; + + prFileListClipper.Begin((int)fdi.GetFilteredListSize(), ImGui::GetTextLineHeightWithSpacing()); + while (prFileListClipper.Step()) + { + for (int i = prFileListClipper.DisplayStart; i < prFileListClipper.DisplayEnd; i++) + { + if (i < 0) continue; + + auto infos = fdi.GetFilteredFileAt((size_t)i); + if (!infos.use_count()) + continue; + + prBeginFileColorIconStyle(infos, _showColor, _str, &_font); + + bool selected = fdi.IsFileNameSelected(infos->fileNameExt); // found + + ImGui::TableNextRow(); + + bool needToBreakTheloop = false; + + if (ImGui::TableNextColumn()) // file name + { + needToBreakTheloop = prSelectableItem(i, infos, selected, _str.c_str()); + } + if (ImGui::TableNextColumn()) // file type + { + ImGui::Text("%s", infos->fileExt.c_str()); + } + if (ImGui::TableNextColumn()) // file size + { + if (infos->fileType != 'd') + { + ImGui::Text("%s ", infos->formatedFileSize.c_str()); + } + else + { + ImGui::Text(""); + } + } + if (ImGui::TableNextColumn()) // file date + time + { + ImGui::Text("%s", infos->fileModifDate.c_str()); + } + + prEndFileColorIconStyle(_showColor, _font); + + if (needToBreakTheloop) + break; + } + } + prFileListClipper.End(); + } + +#ifdef USE_EXPLORATION_BY_KEYS + if (!fdi.puInputPathActivated) + { + prLocateByInputKey(prFileDialogInternal); + prExploreWithkeys(prFileDialogInternal, listViewID); + } +#endif // USE_EXPLORATION_BY_KEYS + + ImGuiContext& g = *GImGui; + if (g.LastActiveId - 1 == listViewID || g.LastActiveId == listViewID) + { + prFileDialogInternal.puFileListViewIsActive = true; + } + + ImGui::EndTable(); + } + + ImGui::PopID(); + } + +#ifdef USE_THUMBNAILS + void IGFD::FileDialog::prDrawThumbnailsListView(ImVec2 vSize) + { + auto& fdi = prFileDialogInternal.puFileManager; + + ImGui::PushID(this); + + static ImGuiTableFlags flags = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg | + ImGuiTableFlags_Hideable | ImGuiTableFlags_ScrollY | + ImGuiTableFlags_NoHostExtendY +#ifndef USE_CUSTOM_SORTING_ICON + | ImGuiTableFlags_Sortable +#endif // USE_CUSTOM_SORTING_ICON + ; + auto listViewID = ImGui::GetID("##FileDialog_fileTable"); + if (ImGui::BeginTableEx("##FileDialog_fileTable", listViewID, 5, flags, vSize, 0.0f)) + { + ImGui::TableSetupScrollFreeze(0, 1); // Make header always visible + ImGui::TableSetupColumn(fdi.puHeaderFileName.c_str(), ImGuiTableColumnFlags_WidthStretch, -1, 0); + ImGui::TableSetupColumn(fdi.puHeaderFileType.c_str(), ImGuiTableColumnFlags_WidthFixed | + ((prFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_HideColumnType) ? ImGuiTableColumnFlags_DefaultHide : 0), -1, 1); + ImGui::TableSetupColumn(fdi.puHeaderFileSize.c_str(), ImGuiTableColumnFlags_WidthFixed | + ((prFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_HideColumnSize) ? ImGuiTableColumnFlags_DefaultHide : 0), -1, 2); + ImGui::TableSetupColumn(fdi.puHeaderFileDate.c_str(), ImGuiTableColumnFlags_WidthFixed | + ((prFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_HideColumnDate) ? ImGuiTableColumnFlags_DefaultHide : 0), -1, 3); + // not needed to have an option for hide the thumbnails since this is why this view is used + ImGui::TableSetupColumn(fdi.puHeaderFileThumbnails.c_str(), ImGuiTableColumnFlags_WidthFixed, -1, 4); //-V112 + +#ifndef USE_CUSTOM_SORTING_ICON + // Sort our data if sort specs have been changed! + if (ImGuiTableSortSpecs* sorts_specs = ImGui::TableGetSortSpecs()) + { + if (sorts_specs->SpecsDirty && !fdi.IsFileListEmpty()) + { + if (sorts_specs->Specs->ColumnUserID == 0) + fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_FILENAME, true); + else if (sorts_specs->Specs->ColumnUserID == 1) + fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_TYPE, true); + else if (sorts_specs->Specs->ColumnUserID == 2) + fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_SIZE, true); + else if (sorts_specs->Specs->ColumnUserID == 3) + fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_DATE, true); + else // if (sorts_specs->Specs->ColumnUserID == 4) = > always true for the moment, to uncomment if we add another column + fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_THUMBNAILS, true); + sorts_specs->SpecsDirty = false; + } + } + + ImGui::TableHeadersRow(); +#else // USE_CUSTOM_SORTING_ICON + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + for (int column = 0; column < 5; column++) + { + ImGui::TableSetColumnIndex(column); + const char* column_name = ImGui::TableGetColumnName(column); // Retrieve name passed to TableSetupColumn() + ImGui::PushID(column); + ImGui::TableHeader(column_name); + ImGui::PopID(); + if (ImGui::IsItemClicked()) + { + if (column == 0) + fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_FILENAME, true); + else if (column == 1) + fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_TYPE, true); + else if (column == 2) + fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_SIZE, true); + else if (column == 3) + fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_DATE, true); + else // if (column == 4) = > always true for the moment, to uncomment if we add another column + fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_THUMBNAILS, true); + } + } +#endif // USE_CUSTOM_SORTING_ICON + if (!fdi.IsFilteredListEmpty()) + { + std::string _str; + ImFont* _font = nullptr; + bool _showColor = false; + + ImGuiContext& g = *GImGui; + const float itemHeight = ImMax(g.FontSize, DisplayMode_ThumbailsList_ImageHeight) + g.Style.ItemSpacing.y; + + prFileListClipper.Begin((int)fdi.GetFilteredListSize(), itemHeight); + while (prFileListClipper.Step()) + { + for (int i = prFileListClipper.DisplayStart; i < prFileListClipper.DisplayEnd; i++) + { + if (i < 0) continue; + + auto infos = fdi.GetFilteredFileAt((size_t)i); + if (!infos.use_count()) + continue; + + prBeginFileColorIconStyle(infos, _showColor, _str, &_font); + + bool selected = fdi.IsFileNameSelected(infos->fileNameExt); // found + + ImGui::TableNextRow(); + + bool needToBreakTheloop = false; + + if (ImGui::TableNextColumn()) // file name + { + needToBreakTheloop = prSelectableItem(i, infos, selected, _str.c_str()); + } + if (ImGui::TableNextColumn()) // file type + { + ImGui::Text("%s", infos->fileExt.c_str()); + } + if (ImGui::TableNextColumn()) // file size + { + if (infos->fileType != 'd') + { + ImGui::Text("%s ", infos->formatedFileSize.c_str()); + } + else + { + ImGui::Text(""); + } + } + if (ImGui::TableNextColumn()) // file date + time + { + ImGui::Text("%s", infos->fileModifDate.c_str()); + } + if (ImGui::TableNextColumn()) // file thumbnails + { + auto th = &infos->thumbnailInfo; + + if (!th->isLoadingOrLoaded) + { + prAddThumbnailToLoad(infos); + } + if (th->isReadyToDisplay && + th->textureID) + { + ImGui::Image((ImTextureID)th->textureID, + ImVec2((float)th->textureWidth, + (float)th->textureHeight)); + } + } + + prEndFileColorIconStyle(_showColor, _font); + + if (needToBreakTheloop) + break; + } + } + prFileListClipper.End(); + } + +#ifdef USE_EXPLORATION_BY_KEYS + if (!fdi.puInputPathActivated) + { + prLocateByInputKey(prFileDialogInternal); + prExploreWithkeys(prFileDialogInternal, listViewID); + } +#endif // USE_EXPLORATION_BY_KEYS + + ImGuiContext& g = *GImGui; + if (g.LastActiveId - 1 == listViewID || g.LastActiveId == listViewID) + { + prFileDialogInternal.puFileListViewIsActive = true; + } + + ImGui::EndTable(); + } + + ImGui::PopID(); + } + + void IGFD::FileDialog::prDrawThumbnailsGridView(ImVec2 vSize) + { + if (ImGui::BeginChild("##thumbnailsGridsFiles", vSize)) + { + // todo + } + + ImGui::EndChild(); + } + +#endif + + void IGFD::FileDialog::prDrawSidePane(float vHeight) + { + ImGui::SameLine(); + + ImGui::BeginChild("##FileTypes", ImVec2(0, vHeight)); + + prFileDialogInternal.puDLGoptionsPane( + prFileDialogInternal.puFilterManager.GetSelectedFilter().filter.c_str(), + prFileDialogInternal.puDLGuserDatas, &prFileDialogInternal.puCanWeContinue); + + ImGui::EndChild(); + } + + void IGFD::FileDialog::Close() + { + prFileDialogInternal.puDLGkey.clear(); + prFileDialogInternal.puShowDialog = false; + } + + bool IGFD::FileDialog::WasOpenedThisFrame(const std::string& vKey) const + { + bool res = prFileDialogInternal.puShowDialog && prFileDialogInternal.puDLGkey == vKey; + if (res) + { + ImGuiContext& g = *GImGui; + res &= prFileDialogInternal.puLastImGuiFrameCount == g.FrameCount; // return true if a dialog was displayed in this frame + } + return res; + } + + bool IGFD::FileDialog::WasOpenedThisFrame() const + { + bool res = prFileDialogInternal.puShowDialog; + if (res) + { + ImGuiContext& g = *GImGui; + res &= prFileDialogInternal.puLastImGuiFrameCount == g.FrameCount; // return true if a dialog was displayed in this frame + } + return res; + } + + bool IGFD::FileDialog::IsOpened(const std::string& vKey) const + { + return (prFileDialogInternal.puShowDialog && prFileDialogInternal.puDLGkey == vKey); + } + + bool IGFD::FileDialog::IsOpened() const + { + return prFileDialogInternal.puShowDialog; + } + + std::string IGFD::FileDialog::GetOpenedKey() const + { + if (prFileDialogInternal.puShowDialog) + return prFileDialogInternal.puDLGkey; + return ""; + } + + std::string IGFD::FileDialog::GetFilePathName() + { + return prFileDialogInternal.puFileManager.GetResultingFilePathName(prFileDialogInternal); + } + + std::string IGFD::FileDialog::GetCurrentPath() + { + return prFileDialogInternal.puFileManager.GetResultingPath(); + } + + std::string IGFD::FileDialog::GetCurrentFileName() + { + return prFileDialogInternal.puFileManager.GetResultingFileName(prFileDialogInternal); + } + + std::string IGFD::FileDialog::GetCurrentFilter() + { + return prFileDialogInternal.puFilterManager.GetSelectedFilter().filter; + } + + std::map IGFD::FileDialog::GetSelection() + { + return prFileDialogInternal.puFileManager.GetResultingSelection(); + } + + UserDatas IGFD::FileDialog::GetUserDatas() const + { + return prFileDialogInternal.puDLGuserDatas; + } + + bool IGFD::FileDialog::IsOk() const + { + return prFileDialogInternal.puIsOk; + } + + void IGFD::FileDialog::SetFileStyle(const IGFD_FileStyleFlags& vFlags, const char* vCriteria, const FileStyle& vInfos) + { + prFileDialogInternal.puFilterManager.SetFileStyle(vFlags, vCriteria, vInfos); + } + + void IGFD::FileDialog::SetFileStyle(const IGFD_FileStyleFlags& vFlags, const char* vCriteria, const ImVec4& vColor, const std::string& vIcon, ImFont* vFont) + { + prFileDialogInternal.puFilterManager.SetFileStyle(vFlags, vCriteria, vColor, vIcon, vFont); + } + + bool IGFD::FileDialog::GetFileStyle(const IGFD_FileStyleFlags& vFlags, const std::string& vCriteria, ImVec4* vOutColor, std::string* vOutIcon, ImFont **vOutFont) + { + return prFileDialogInternal.puFilterManager.GetFileStyle(vFlags, vCriteria, vOutColor, vOutIcon, vOutFont); + } + + void IGFD::FileDialog::ClearFilesStyle() + { + prFileDialogInternal.puFilterManager.ClearFilesStyle(); + } + + void IGFD::FileDialog::SetLocales(const int& vLocaleCategory, const std::string& vLocaleBegin, const std::string& vLocaleEnd) + { + prFileDialogInternal.puUseCustomLocale = true; + prFileDialogInternal.puLocaleBegin = vLocaleBegin; + prFileDialogInternal.puLocaleEnd = vLocaleEnd; + } + + ////////////////////////////////////////////////////////////////////////////// + //// OVERWRITE DIALOG //////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////// + + bool IGFD::FileDialog::prConfirm_Or_OpenOverWriteFileDialog_IfNeeded(bool vLastAction, ImGuiWindowFlags vFlags) + { + // if confirmation => return true for confirm the overwrite et quit the dialog + // if cancel => return false && set IsOk to false for keep inside the dialog + + // if IsOk == false => return false for quit the dialog + if (!prFileDialogInternal.puIsOk && vLastAction) + { + QuitFrame(); + return true; + } + + // if IsOk == true && no check of overwrite => return true for confirm the dialog + if (prFileDialogInternal.puIsOk && vLastAction && !(prFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_ConfirmOverwrite)) + { + QuitFrame(); + return true; + } + + // if IsOk == true && check of overwrite => return false and show confirm to overwrite dialog + if ((prFileDialogInternal.puOkResultToConfirm || (prFileDialogInternal.puIsOk && vLastAction)) && + (prFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_ConfirmOverwrite)) + { + if (prFileDialogInternal.puIsOk) // catched only one time + { + if (!prFileDialogInternal.puFileManager.IsFileExist(GetFilePathName())) // not existing => quit dialog + { + QuitFrame(); + return true; + } + else // existing => confirm dialog to open + { + prFileDialogInternal.puIsOk = false; + prFileDialogInternal.puOkResultToConfirm = true; + } + } + + std::string name = OverWriteDialogTitleString "##" + prFileDialogInternal.puDLGtitle + prFileDialogInternal.puDLGkey + "OverWriteDialog"; + + bool res = false; + + ImGui::OpenPopup(name.c_str()); + if (ImGui::BeginPopupModal(name.c_str(), (bool*)0, + vFlags | ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) + { + ImGui::SetWindowPos(prFileDialogInternal.puDialogCenterPos - ImGui::GetWindowSize() * 0.5f); // next frame needed for GetWindowSize to work + + ImGui::Text("%s", OverWriteDialogMessageString); + + if (IMGUI_BUTTON(OverWriteDialogConfirmButtonString)) + { + prFileDialogInternal.puOkResultToConfirm = false; + prFileDialogInternal.puIsOk = true; + res = true; + ImGui::CloseCurrentPopup(); + } + + ImGui::SameLine(); + + if (IMGUI_BUTTON(OverWriteDialogCancelButtonString)) + { + prFileDialogInternal.puOkResultToConfirm = false; + prFileDialogInternal.puIsOk = false; + res = false; + ImGui::CloseCurrentPopup(); + } + + ImGui::EndPopup(); + } + + if (res) + { + QuitFrame(); + } + return res; + } + + return false; + } +} + +#endif // __cplusplus + +///////////////////////////////////////////////////////////////// +///// C Interface /////////////////////////////////////////////// +///////////////////////////////////////////////////////////////// + +// Return an initialized IGFD_Selection_Pair +IMGUIFILEDIALOG_API IGFD_Selection_Pair IGFD_Selection_Pair_Get(void) +{ + IGFD_Selection_Pair res = {}; + res.fileName = nullptr; + res.filePathName = nullptr; + return res; +} + +// destroy only the content of vSelection_Pair +IMGUIFILEDIALOG_API void IGFD_Selection_Pair_DestroyContent(IGFD_Selection_Pair* vSelection_Pair) +{ + if (vSelection_Pair) + { + delete[] vSelection_Pair->fileName; + delete[] vSelection_Pair->filePathName; + } +} + +// Return an initialized IGFD_Selection +IMGUIFILEDIALOG_API IGFD_Selection IGFD_Selection_Get(void) +{ + return { nullptr, 0U }; +} + +// destroy only the content of vSelection +IMGUIFILEDIALOG_API void IGFD_Selection_DestroyContent(IGFD_Selection* vSelection) +{ + if (vSelection) + { + if (vSelection->table) + { + for (size_t i = 0U; i < vSelection->count; i++) + { + IGFD_Selection_Pair_DestroyContent(&vSelection->table[i]); + } + delete[] vSelection->table; + } + vSelection->count = 0U; + } +} + +// create an instance of ImGuiFileDialog +IMGUIFILEDIALOG_API ImGuiFileDialog* IGFD_Create(void) +{ + return new ImGuiFileDialog(); +} + +// destroy the instance of ImGuiFileDialog +IMGUIFILEDIALOG_API void IGFD_Destroy(ImGuiFileDialog* vContext) +{ + if (vContext) + { + delete vContext; + vContext = nullptr; + } +} + +// standard dialog +IMGUIFILEDIALOG_API void IGFD_OpenDialog( + ImGuiFileDialog* vContext, + const char* vKey, + const char* vTitle, + const char* vFilters, + const char* vPath, + const char* vFileName, + const int vCountSelectionMax, + void* vUserDatas, + ImGuiFileDialogFlags flags) +{ + if (vContext) + { + vContext->OpenDialog( + vKey, vTitle, vFilters, vPath, vFileName, + vCountSelectionMax, vUserDatas, flags); + } +} + +IMGUIFILEDIALOG_API void IGFD_OpenDialog2( + ImGuiFileDialog* vContext, + const char* vKey, + const char* vTitle, + const char* vFilters, + const char* vFilePathName, + const int vCountSelectionMax, + void* vUserDatas, + ImGuiFileDialogFlags flags) +{ + if (vContext) + { + vContext->OpenDialog( + vKey, vTitle, vFilters, vFilePathName, + vCountSelectionMax, vUserDatas, flags); + } +} + +IMGUIFILEDIALOG_API void IGFD_OpenPaneDialog( + ImGuiFileDialog* vContext, + const char* vKey, + const char* vTitle, + const char* vFilters, + const char* vPath, + const char* vFileName, + IGFD_PaneFun vSidePane, + const float vSidePaneWidth, + const int vCountSelectionMax, + void* vUserDatas, + ImGuiFileDialogFlags flags) +{ + if (vContext) + { + vContext->OpenDialog( + vKey, vTitle, vFilters, + vPath, vFileName, + vSidePane, vSidePaneWidth, + vCountSelectionMax, vUserDatas, flags); + } +} + +IMGUIFILEDIALOG_API void IGFD_OpenPaneDialog2( + ImGuiFileDialog* vContext, + const char* vKey, + const char* vTitle, + const char* vFilters, + const char* vFilePathName, + IGFD_PaneFun vSidePane, + const float vSidePaneWidth, + const int vCountSelectionMax, + void* vUserDatas, + ImGuiFileDialogFlags flags) +{ + if (vContext) + { + vContext->OpenDialog( + vKey, vTitle, vFilters, + vFilePathName, + vSidePane, vSidePaneWidth, + vCountSelectionMax, vUserDatas, flags); + } +} + +// modal dialog +IMGUIFILEDIALOG_API void IGFD_OpenModal( + ImGuiFileDialog* vContext, + const char* vKey, + const char* vTitle, + const char* vFilters, + const char* vPath, + const char* vFileName, + const int vCountSelectionMax, + void* vUserDatas, + ImGuiFileDialogFlags flags) +{ + if (vContext) + { + vContext->OpenModal( + vKey, vTitle, vFilters, vPath, vFileName, + vCountSelectionMax, vUserDatas, flags); + } +} + +IMGUIFILEDIALOG_API void IGFD_OpenModal2( + ImGuiFileDialog* vContext, + const char* vKey, + const char* vTitle, + const char* vFilters, + const char* vFilePathName, + const int vCountSelectionMax, + void* vUserDatas, + ImGuiFileDialogFlags flags) +{ + if (vContext) + { + vContext->OpenModal( + vKey, vTitle, vFilters, vFilePathName, + vCountSelectionMax, vUserDatas, flags); + } +} + +IMGUIFILEDIALOG_API void IGFD_OpenPaneModal( + ImGuiFileDialog* vContext, + const char* vKey, + const char* vTitle, + const char* vFilters, + const char* vPath, + const char* vFileName, + IGFD_PaneFun vSidePane, + const float vSidePaneWidth, + const int vCountSelectionMax, + void* vUserDatas, + ImGuiFileDialogFlags flags) +{ + if (vContext) + { + vContext->OpenModal( + vKey, vTitle, vFilters, + vPath, vFileName, + vSidePane, vSidePaneWidth, + vCountSelectionMax, vUserDatas, flags); + } +} + +IMGUIFILEDIALOG_API void IGFD_OpenPaneModal2( + ImGuiFileDialog* vContext, + const char* vKey, + const char* vTitle, + const char* vFilters, + const char* vFilePathName, + IGFD_PaneFun vSidePane, + const float vSidePaneWidth, + const int vCountSelectionMax, + void* vUserDatas, + ImGuiFileDialogFlags flags) +{ + if (vContext) + { + vContext->OpenModal( + vKey, vTitle, vFilters, + vFilePathName, + vSidePane, vSidePaneWidth, + vCountSelectionMax, vUserDatas, flags); + } +} + +IMGUIFILEDIALOG_API bool IGFD_DisplayDialog(ImGuiFileDialog* vContext, + const char* vKey, ImGuiWindowFlags vFlags, ImVec2 vMinSize, ImVec2 vMaxSize) +{ + if (vContext) + { + return vContext->Display(vKey, vFlags, vMinSize, vMaxSize); + } + + return false; +} + +IMGUIFILEDIALOG_API void IGFD_CloseDialog(ImGuiFileDialog* vContext) +{ + if (vContext) + { + vContext->Close(); + } +} + +IMGUIFILEDIALOG_API bool IGFD_IsOk(ImGuiFileDialog* vContext) +{ + if (vContext) + { + return vContext->IsOk(); + } + + return false; +} + +IMGUIFILEDIALOG_API bool IGFD_WasKeyOpenedThisFrame(ImGuiFileDialog* vContext, + const char* vKey) +{ + if (vContext) + { + vContext->WasOpenedThisFrame(vKey); + } + + return false; +} + +IMGUIFILEDIALOG_API bool IGFD_WasOpenedThisFrame(ImGuiFileDialog* vContext) +{ + if (vContext) + { + vContext->WasOpenedThisFrame(); + } + + return false; +} + +IMGUIFILEDIALOG_API bool IGFD_IsKeyOpened(ImGuiFileDialog* vContext, + const char* vCurrentOpenedKey) +{ + if (vContext) + { + vContext->IsOpened(vCurrentOpenedKey); + } + + return false; +} + +IMGUIFILEDIALOG_API bool IGFD_IsOpened(ImGuiFileDialog* vContext) +{ + if (vContext) + { + vContext->IsOpened(); + } + + return false; +} + +IMGUIFILEDIALOG_API IGFD_Selection IGFD_GetSelection(ImGuiFileDialog* vContext) +{ + IGFD_Selection res = IGFD_Selection_Get(); + + if (vContext) + { + auto sel = vContext->GetSelection(); + if (!sel.empty()) + { + res.count = sel.size(); + res.table = new IGFD_Selection_Pair[res.count]; + + size_t idx = 0U; + for (const auto& s : sel) + { + IGFD_Selection_Pair* pair = res.table + idx++; + + // fileNameExt + if (!s.first.empty()) + { + size_t siz = s.first.size() + 1U; + pair->fileName = new char[siz]; +#ifndef MSVC + strncpy(pair->fileName, s.first.c_str(), siz); +#else + strncpy_s(pair->fileName, siz, s.first.c_str(), siz); +#endif + pair->fileName[siz - 1U] = '\0'; + } + + // filePathName + if (!s.second.empty()) + { + size_t siz = s.first.size() + 1U; + pair->filePathName = new char[siz]; +#ifndef MSVC + strncpy(pair->filePathName, s.first.c_str(), siz); +#else + strncpy_s(pair->filePathName, siz, s.first.c_str(), siz); +#endif + pair->filePathName[siz - 1U] = '\0'; + } + } + + return res; + } + } + + return res; +} + +IMGUIFILEDIALOG_API char* IGFD_GetFilePathName(ImGuiFileDialog* vContext) +{ + char* res = nullptr; + + if (vContext) + { + auto s = vContext->GetFilePathName(); + if (!s.empty()) + { + size_t siz = s.size() + 1U; + res = new char[siz]; +#ifndef MSVC + strncpy(res, s.c_str(), siz); +#else + strncpy_s(res, siz, s.c_str(), siz); +#endif + res[siz - 1U] = '\0'; + } + } + + return res; +} + +IMGUIFILEDIALOG_API char* IGFD_GetCurrentFileName(ImGuiFileDialog* vContext) +{ + char* res = nullptr; + + if (vContext) + { + auto s = vContext->GetCurrentFileName(); + if (!s.empty()) + { + size_t siz = s.size() + 1U; + res = new char[siz]; +#ifndef MSVC + strncpy(res, s.c_str(), siz); +#else + strncpy_s(res, siz, s.c_str(), siz); +#endif + res[siz - 1U] = '\0'; + } + } + + return res; +} + +IMGUIFILEDIALOG_API char* IGFD_GetCurrentPath(ImGuiFileDialog* vContext) +{ + char* res = nullptr; + + if (vContext) + { + auto s = vContext->GetCurrentPath(); + if (!s.empty()) + { + size_t siz = s.size() + 1U; + res = new char[siz]; +#ifndef MSVC + strncpy(res, s.c_str(), siz); +#else + strncpy_s(res, siz, s.c_str(), siz); +#endif + res[siz - 1U] = '\0'; + } + } + + return res; +} + +IMGUIFILEDIALOG_API char* IGFD_GetCurrentFilter(ImGuiFileDialog* vContext) +{ + char* res = nullptr; + + if (vContext) + { + auto s = vContext->GetCurrentFilter(); + if (!s.empty()) + { + size_t siz = s.size() + 1U; + res = new char[siz]; +#ifndef MSVC + strncpy(res, s.c_str(), siz); +#else + strncpy_s(res, siz, s.c_str(), siz); +#endif + res[siz - 1U] = '\0'; + } + } + + return res; +} + +IMGUIFILEDIALOG_API void* IGFD_GetUserDatas(ImGuiFileDialog* vContext) +{ + if (vContext) + { + return vContext->GetUserDatas(); + } + + return nullptr; +} + +IMGUIFILEDIALOG_API void IGFD_SetFileStyle(ImGuiFileDialog* vContext, + IGFD_FileStyleFlags vFlags, const char* vCriteria, ImVec4 vColor, const char* vIcon, ImFont* vFont) //-V813 +{ + if (vContext) + { + vContext->SetFileStyle(vFlags, vCriteria, vColor, vIcon, vFont); + } +} + +IMGUIFILEDIALOG_API void IGFD_SetFileStyle2(ImGuiFileDialog* vContext, + IGFD_FileStyleFlags vFlags, const char* vCriteria, float vR, float vG, float vB, float vA, const char* vIcon, ImFont* vFont) +{ + if (vContext) + { + vContext->SetFileStyle(vFlags, vCriteria, ImVec4(vR, vG, vB, vA), vIcon, vFont); + } +} + +IMGUIFILEDIALOG_API bool IGFD_GetFileStyle(ImGuiFileDialog* vContext, + IGFD_FileStyleFlags vFlags, const char* vCriteria, ImVec4* vOutColor, char** vOutIcon, ImFont** vOutFont) +{ + if (vContext) + { + std::string icon; + bool res = vContext->GetFileStyle(vFlags, vCriteria, vOutColor, &icon, vOutFont); + if (!icon.empty() && vOutIcon) + { + size_t siz = icon.size() + 1U; + *vOutIcon = new char[siz]; +#ifndef MSVC + strncpy(*vOutIcon, icon.c_str(), siz); +#else + strncpy_s(*vOutIcon, siz, icon.c_str(), siz); +#endif + (*vOutIcon)[siz - 1U] = '\0'; + } + return res; + } + + return false; +} + +IMGUIFILEDIALOG_API void IGFD_ClearFilesStyle(ImGuiFileDialog* vContext) +{ + if (vContext) + { + vContext->ClearFilesStyle(); + } +} + +IMGUIFILEDIALOG_API void SetLocales(ImGuiFileDialog* vContext, const int vCategory, const char* vBeginLocale, const char* vEndLocale) +{ + if (vContext) + { + vContext->SetLocales(vCategory, (vBeginLocale ? vBeginLocale : ""), (vEndLocale ? vEndLocale : "")); + } +} + +#ifdef USE_EXPLORATION_BY_KEYS +IMGUIFILEDIALOG_API void IGFD_SetFlashingAttenuationInSeconds(ImGuiFileDialog* vContext, float vAttenValue) +{ + if (vContext) + { + vContext->SetFlashingAttenuationInSeconds(vAttenValue); + } +} +#endif + +#ifdef USE_BOOKMARK +IMGUIFILEDIALOG_API char* IGFD_SerializeBookmarks(ImGuiFileDialog* vContext) +{ + char* res = nullptr; + + if (vContext) + { + auto s = vContext->SerializeBookmarks(); + if (!s.empty()) + { + size_t siz = s.size() + 1U; + res = new char[siz]; +#ifndef MSVC + strncpy(res, s.c_str(), siz); +#else + strncpy_s(res, siz, s.c_str(), siz); +#endif + res[siz - 1U] = '\0'; + } + } + + return res; +} + +IMGUIFILEDIALOG_API void IGFD_DeserializeBookmarks(ImGuiFileDialog* vContext, const char* vBookmarks) +{ + if (vContext) + { + vContext->DeserializeBookmarks(vBookmarks); + } +} +#endif + +#ifdef USE_THUMBNAILS +IMGUIFILEDIALOG_API void SetCreateThumbnailCallback(ImGuiFileDialog* vContext, const IGFD_CreateThumbnailFun vCreateThumbnailFun) +{ + if (vContext) + { + vContext->SetCreateThumbnailCallback(vCreateThumbnailFun); + } +} + +IMGUIFILEDIALOG_API void SetDestroyThumbnailCallback(ImGuiFileDialog* vContext, const IGFD_DestroyThumbnailFun vDestroyThumbnailFun) +{ + if (vContext) + { + vContext->SetDestroyThumbnailCallback(vDestroyThumbnailFun); + } +} + +IMGUIFILEDIALOG_API void ManageGPUThumbnails(ImGuiFileDialog* vContext) +{ + if (vContext) + { + vContext->ManageGPUThumbnails(); + } +} +#endif // USE_THUMBNAILS diff --git a/Source/Dependencies/ImGuiFileDialog-0.6.3/ImGuiFileDialog.h b/Source/Dependencies/ImGuiFileDialog-0.6.3/ImGuiFileDialog.h new file mode 100644 index 000000000..9b4a3eab4 --- /dev/null +++ b/Source/Dependencies/ImGuiFileDialog-0.6.3/ImGuiFileDialog.h @@ -0,0 +1,1624 @@ +/* +MIT License + +Copyright (c) 2019-2021 Stephane Cuillerdier (aka aiekick) + +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. +*/ + +/* +----------------------------------------------------------------------------------------------------------------- +----------------------------------------------------------------------------------------------------------------- + +github repo : https://github.com/aiekick/ImGuiFileDialog + +----------------------------------------------------------------------------------------------------------------- +## Description : +----------------------------------------------------------------------------------------------------------------- + +this File Dialog is build on top of DearImGui +(On windows, need te lib Dirent : https://github.com/tronkko/dirent, use the branch 1.23 for avoid any issues) +Complete readme here : https://github.com/aiekick/ImGuiFileDialog/blob/master/README.md) + +this filedialog was created principally for have custom pane with widgets accrdoing to file extention. +it was not possible with native filedialog + +An example of the File Dialog integrated within the ImGui Demo App + +----------------------------------------------------------------------------------------------------------------- +## Features : +----------------------------------------------------------------------------------------------------------------- + +- Separate system for call and display + - can be many func calls with different params for one display func by ex +- Can use custom pane via function binding + - this pane can block the validation of the dialog + - can also display different things according to current filter and User Datas +- Support of Filter Custom Coloring / Icons / text +- Multi Selection (ctrl/shift + click) : + - 0 => infinite + - 1 => one file (default) + - n => n files +- Compatible with MacOs, Linux, Win + - On Win version you can list Drives +- Support of Modal/Standard dialog type +- Support both Mode : File Chooser or Directory Chooser +- Support filter collection / Custom filter name +- Support files Exploring with keys : Up / Down / Enter (open dir) / Backspace (come back) +- Support files Exploring by input char (case insensitive) +- Support bookmark creation/edition/call for directory (can have custom name corresponding to a path) +- Support input path edition by right click on a path button +- Support of a 'Confirm to Overwrite" dialog if File Exist + + +----------------------------------------------------------------------------------------------------------------- +## NameSpace / SingleTon +----------------------------------------------------------------------------------------------------------------- + +Use the Namespace IGFD (for avoid conflict with variables, struct and class names) + +you can display only one dialog at a time, this class is a simgleton and must be called like that : +ImGuiFileDialog::Instance()->method_of_your_choice() + +----------------------------------------------------------------------------------------------------------------- +## Simple Dialog : +----------------------------------------------------------------------------------------------------------------- + +Example code : +void drawGui() +{ + // open Dialog Simple + if (ImGui::Button("Open File Dialog")) + ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".cpp,.h,.hpp", "."); + + // display + if (ImGuiFileDialog::Instance()->FileDialog("ChooseFileDlgKey")) + { + // action if OK + if (ImGuiFileDialog::Instance()->IsOk == true) + { + std::string filePathName = ImGuiFileDialog::Instance()->GetFilePathName(); + std::string filePath = ImGuiFileDialog::Instance()->GetCurrentPath(); + // action + } + // close + ImGuiFileDialog::Instance()->CloseDialog("ChooseFileDlgKey"); + } +} + +----------------------------------------------------------------------------------------------------------------- +## Directory Chooser : +----------------------------------------------------------------------------------------------------------------- + +For have only a directory chooser, you just need to specify a filter null : + +Example code : +ImGuiFileDialog::Instance()->OpenDialog("ChooseDirDlgKey", "Choose a Directory", 0, "."); + +In this mode you can select any directory with one click, and open directory with double click + +----------------------------------------------------------------------------------------------------------------- +## Dialog with Custom Pane : +----------------------------------------------------------------------------------------------------------------- + +Example code : +static bool canValidateDialog = false; +inline void InfosPane(std::string& vFilter, IGFD::UserDatas vUserDatas, bool *vCantContinue) // if vCantContinue is false, the user cant validate the dialog +{ + ImGui::TextColored(ImVec4(0, 1, 1, 1), "Infos Pane"); + ImGui::Text("Selected Filter : %s", vFilter.c_str()); + if (vUserDatas) + ImGui::Text("UserDatas : %s", vUserDatas); + ImGui::Checkbox("if not checked you cant validate the dialog", &canValidateDialog); + if (vCantContinue) + *vCantContinue = canValidateDialog; +} + +void drawGui() +{ + // open Dialog with Pane + if (ImGui::Button("Open File Dialog with a custom pane")) + ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".cpp,.h,.hpp", + ".", "", std::bind(&InfosPane, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), 350, 1, IGFD::UserDatas("InfosPane")); + + // display and action if ok + if (ImGuiFileDialog::Instance()->FileDialog("ChooseFileDlgKey")) + { + if (ImGuiFileDialog::Instance()->IsOk == true) + { + std::string filePathName = ImGuiFileDialog::Instance()->GetFilePathName(); + std::string filePath = ImGuiFileDialog::Instance()->GetCurrentPath(); + std::string filter = ImGuiFileDialog::Instance()->GetCurrentFilter(); + // here convert from string because a string was passed as a userDatas, but it can be what you want + std::string userDatas; + if (ImGuiFileDialog::Instance()->GetUserDatas()) + userDatas = std::string((const char*)ImGuiFileDialog::Instance()->GetUserDatas()); + auto selection = ImGuiFileDialog::Instance()->GetSelection(); // multiselection + + // action + } + // close + ImGuiFileDialog::Instance()->CloseDialog("ChooseFileDlgKey"); + } +} + +----------------------------------------------------------------------------------------------------------------- +## File Style +----------------------------------------------------------------------------------------------------------------- + +You can define style for files/dirs/links in many ways : + +the style can be colors, icons and fonts + +the general form is : +ImGuiFileDialog::Instance()->SetFileStyle(styleType, criteria, color, icon, font); + +styleType can be thoses : +IGFD_FileStyle_None // define none style +IGFD_FileStyleByTypeFile // define style for all files +IGFD_FileStyleByTypeDir // define style for all dir +IGFD_FileStyleByTypeLink // define style for all link +IGFD_FileStyleByExtention // define style by extention, for files or links +IGFD_FileStyleByFullName // define style for particular file/dir/link full name (filename + extention) +IGFD_FileStyleByContainedInFullName // define style for file/dir/link when criteria is contained in full name + +samples : + +define style by file extention +ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtention, ".png", ImVec4(0.0f, 1.0f, 1.0f, 0.9f), ICON_IGFD_FILE_PIC, font1); +ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtention, ".gif", ImVec4(0.0f, 1.0f, 0.5f, 0.9f), "[GIF]"); + +define style for all directories +ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeDir, "", ImVec4(0.5f, 1.0f, 0.9f, 0.9f), ICON_IGFD_FOLDER); +can be for a specific directory +ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeDir, ".git", ImVec4(0.5f, 1.0f, 0.9f, 0.9f), ICON_IGFD_FOLDER); + +define style for all files +ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeFile, "", ImVec4(0.5f, 1.0f, 0.9f, 0.9f), ICON_IGFD_FILE); +can be for a specific file +ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeFile, ".git", ImVec4(0.5f, 1.0f, 0.9f, 0.9f), ICON_IGFD_FILE); + +define style for all links +ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeLink, "", ImVec4(0.5f, 1.0f, 0.9f, 0.9f)); +can be for a specific link +ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeLink, "Readme.md", ImVec4(0.5f, 1.0f, 0.9f, 0.9f)); + +define style for any files/dirs/links by fullname +ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByFullName, "doc", ImVec4(0.9f, 0.2f, 0.0f, 0.9f), ICON_IGFD_FILE_PIC); + +define style for any dirs by file who are containing this string +ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeDir | IGFD_FileStyleByContainedInFullName, ".git", ImVec4(0.9f, 0.2f, 0.0f, 0.9f), ICON_IGFD_BOOKMARK); + +define style for any files by file who are containing this string +ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeFile | IGFD_FileStyleByContainedInFullName, ".git", ImVec4(0.5f, 0.8f, 0.5f, 0.9f), ICON_IGFD_SAVE); + +----------------------------------------------------------------------------------------------------------------- +## Filter Collections +----------------------------------------------------------------------------------------------------------------- + +you can define a custom filter name who correspond to a group of filter + +you must use this syntax : custom_name1{filter1,filter2,filter3},custom_name2{filter1,filter2},filter1 +when you will select custom_name1, the gorup of filter 1 to 3 will be applied +the reserved char are {}, you cant use them for define filter name. + +Example code : +const char *filters = "Source files (*.cpp *.h *.hpp){.cpp,.h,.hpp},Image files (*.png *.gif *.jpg *.jpeg){.png,.gif,.jpg,.jpeg},.md"; +ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", ICON_IMFDLG_FOLDER_OPEN " Choose a File", filters, "."); + +## Multi Selection + +You can define in OpenDialog/OpenModal call the count file you wan to select : +- 0 => infinite +- 1 => one file only (default) +- n => n files only + +See the define at the end of these funcs after path. + +Example code : +ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".*,.cpp,.h,.hpp", "."); +ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose 1 File", ".*,.cpp,.h,.hpp", ".", 1); +ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose 5 File", ".*,.cpp,.h,.hpp", ".", 5); +ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose many File", ".*,.cpp,.h,.hpp", ".", 0); +ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".png,.jpg", + ".", "", std::bind(&InfosPane, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), 350, 1, "SaveFile"); // 1 file + +----------------------------------------------------------------------------------------------------------------- +## File Dialog Constraints +----------------------------------------------------------------------------------------------------------------- + +you can define min/max size of the dialog when you display It + +by ex : + +* MaxSize is the full display size +* MinSize in the half display size. + +Example code : +ImVec2 maxSize = ImVec2((float)display_w, (float)display_h); +ImVec2 minSize = maxSize * 0.5f; +ImGuiFileDialog::Instance()->FileDialog("ChooseFileDlgKey", ImGuiWindowFlags_NoCollapse, minSize, maxSize); + +----------------------------------------------------------------------------------------------------------------- +## Detail View Mode +----------------------------------------------------------------------------------------------------------------- + +You can have tables display like that. + +- uncomment "#define USE_IMGUI_TABLES" in you custom config file (CustomImGuiFileDialogConfig.h in this example) + +----------------------------------------------------------------------------------------------------------------- +## Exploring by keys +----------------------------------------------------------------------------------------------------------------- + +you can activate this feature by uncomment : "#define USE_EXPLORATION_BY_KEYS" +in you custom config file (CustomImGuiFileDialogConfig.h in this example) + +you can also uncomment the next lines for define your keys : + +* IGFD_KEY_UP => Up key for explore to the top +* IGFD_KEY_DOWN => Down key for explore to the bottom +* IGFD_KEY_ENTER => Enter key for open directory +* IGFD_KEY_BACKSPACE => BackSpace for comming back to the last directory + +you can also explore a file list by use the current key char. + +as you see the current item is flashed (by default for 1 sec) +you can define the flashing life time by yourself with the function + +Example code : +ImGuiFileDialog::Instance()->SetFlashingAttenuationInSeconds(1.0f); + +----------------------------------------------------------------------------------------------------------------- +## Bookmarks +----------------------------------------------------------------------------------------------------------------- + +you can create/edit/call path bookmarks and load/save them in file + +you can activate it by uncomment : "#define USE_BOOKMARK" + +in you custom config file (CustomImGuiFileDialogConfig.h in this example) + +you can also uncomment the next lines for customize it : +Example code : +#define bookmarkPaneWith 150.0f => width of the bookmark pane +#define IMGUI_TOGGLE_BUTTON ToggleButton => customize the Toggled button (button stamp must be : (const char* label, bool *toggle) +#define bookmarksButtonString "Bookmark" => the text in the toggle button +#define bookmarksButtonHelpString "Bookmark" => the helper text when mouse over the button +#define addBookmarkButtonString "+" => the button for add a bookmark +#define removeBookmarkButtonString "-" => the button for remove the selected bookmark + + +* you can select each bookmark for edit the displayed name corresponding to a path +* you must double click on the label for apply the bookmark + +you can also serialize/deserialize bookmarks by ex for load/save from/to file : (check the app sample by ex) +Example code : +Load => ImGuiFileDialog::Instance()->DeserializeBookmarks(bookmarString); +Save => std::string bookmarkString = ImGuiFileDialog::Instance()->SerializeBookmarks(); + +----------------------------------------------------------------------------------------------------------------- +## Path Edition : +----------------------------------------------------------------------------------------------------------------- + +if you click right on one of any path button, you can input or modify the path pointed by this button. +then press the validate key (Enter by default with GLFW) for validate the new path +or press the escape key (Escape by default with GLFW) for quit the input path edition + +see in this gif doc/inputPathEdition.gif : +1) button edition with mouse button right and escape key for quit the edition +2) focus the input and press validation for set path + +----------------------------------------------------------------------------------------------------------------- +## Confirm to OverWrite Dialog : +----------------------------------------------------------------------------------------------------------------- + +If you want avoid OverWrite your files after confirmation, +you can show a Dialog for confirm or cancel the OverWrite operation. + +You just need to define the flag ImGuiFileDialogFlags_ConfirmOverwrite +in your call to OpenDialog/OpenModal + +By default this flag is not set, since there is no pre-defined way to +define if a dialog will be for Open or Save behavior. (and its wanted :) ) + +Example code For Standard Dialog : +Example code : +ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", + ICON_IGFD_SAVE " Choose a File", filters, + ".", "", 1, nullptr, ImGuiFileDialogFlags_ConfirmOverwrite); + +Example code For Modal Dialog : +Example code : +ImGuiFileDialog::Instance()->OpenModal("ChooseFileDlgKey", + ICON_IGFD_SAVE " Choose a File", filters, + ".", "", 1, nullptr, ImGuiFileDialogFlags_ConfirmOverwrite); + +This dialog will only verify the file in the file field. +So Not to be used with GetSelection() + +The Confirm dialog will be a forced Modal Dialog, not moveable, displayed +in the center of the current FileDialog. + +As usual you can customize the dialog, +in you custom config file (CustomImGuiFileDialogConfig.h in this example) + +you can uncomment the next lines for customize it : + +Example code : +#define OverWriteDialogTitleString "The file Already Exist !" +#define OverWriteDialogMessageString "Would you like to OverWrite it ?" +#define OverWriteDialogConfirmButtonString "Confirm" +#define OverWriteDialogCancelButtonString "Cancel" + +----------------------------------------------------------------------------------------------------------------- +## Flags : +----------------------------------------------------------------------------------------------------------------- + +flag must be specified in OpenDialog or OpenModal +* ImGuiFileDialogFlags_ConfirmOverwrite => show confirm to overwrite dialog +* ImGuiFileDialogFlags_DontShowHiddenFiles => dont show hidden file (file starting with a .) + +----------------------------------------------------------------------------------------------------------------- +## Open / Save dialog Behavior : +----------------------------------------------------------------------------------------------------------------- + +There is no way to distinguish the "open dialog" behavior than "save dialog" behavior. +So you msut adapt the return according to your need : + +if you want open file(s) or directory(s), you must use : GetSelection() method. you will obtain a std::map of the selection +if you want create a file, you must use : GetFilePathName()/GetCurrentFileName() + +the return method's and comments : + +Example code : +std::map GetSelection(); // Open File behavior : will return selection via a map +std::string GetFilePathName(); // Create File behavior : will always return the content of the field with current filter extention and current path +std::string GetCurrentFileName(); // Create File behavior : will always return the content of the field with current filter extention +std::string GetCurrentPath(); // will return current path +std::string GetCurrentFilter(); // get selected filter +UserDatas GetUserDatas(); // get user datas send with Open Dialog + +----------------------------------------------------------------------------------------------------------------- +## Thumbnails Display +----------------------------------------------------------------------------------------------------------------- + +You can now, display thumbnails of pictures. + +The file resize use stb/image so the following files extentions are supported : +(.png, .bmp, .tga, .jpg, .jpeg, .gif, .psd, .pic, .ppm, .pgm) +only tested with .png, .bmp, .tga, .jpg, .jpeg and .gif by the way + +Corresponding to your backend (ex : OpenGl) you need to define two callbacks : +* the first is a callback who will be called by ImGuiFileDialog for create the backend texture +* the second is a callback who will be called by ImGuiFileDialog for destroy the backend texture + +After that you need to call the function who is responsible to create / destroy the textures. +this function must be called in your GPU Rendering zone for avoid destroying of used texture. +if you do that at the same place of your imgui code, some backend can crash your app, by ex with vulkan. + +ex, for opengl : + +Example code : +// Create thumbnails texture +ImGuiFileDialog::Instance()->SetCreateThumbnailCallback([](IGFD_Thumbnail_Info *vThumbnail_Info) -> void +{ + if (vThumbnail_Info && + vThumbnail_Info->isReadyToUpload && + vThumbnail_Info->textureFileDatas) + { + GLuint textureId = 0; + glGenTextures(1, &textureId); + vThumbnail_Info->textureID = (void*)textureId; + + glBindTexture(GL_TEXTURE_2D, textureId); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, + (GLsizei)vThumbnail_Info->textureWidth, (GLsizei)vThumbnail_Info->textureHeight, + 0, GL_RGBA, GL_UNSIGNED_BYTE, vThumbnail_Info->textureFileDatas); + glFinish(); + glBindTexture(GL_TEXTURE_2D, 0); + + delete[] vThumbnail_Info->textureFileDatas; + vThumbnail_Info->textureFileDatas = nullptr; + + vThumbnail_Info->isReadyToUpload = false; + vThumbnail_Info->isReadyToDisplay = true; + } +}); + +Example code : +// Destroy thumbnails texture +ImGuiFileDialog::Instance()->SetDestroyThumbnailCallback([](IGFD_Thumbnail_Info* vThumbnail_Info) +{ + if (vThumbnail_Info) + { + GLuint texID = (GLuint)vThumbnail_Info->textureID; + glDeleteTextures(1, &texID); + glFinish(); + } +}); + +Example code : +// GPU Rendering Zone // To call for Create/ Destroy Textures +ImGuiFileDialog::Instance()->ManageGPUThumbnails(); + +----------------------------------------------------------------------------------------------------------------- +## C API +----------------------------------------------------------------------------------------------------------------- + +A C API is available let you include ImGuiFileDialog in your C project. +btw, ImGuiFileDialog depend of ImGui and dirent (for windows) + +Sample code with cimgui : + +// create ImGuiFileDialog +ImGuiFileDialog *cfileDialog = IGFD_Create(); + +// open dialog +if (igButton("Open File", buttonSize)) +{ + IGFD_OpenDialog(cfiledialog, + "filedlg", // dialog key (make it possible to have different treatment reagrding the dialog key + "Open a File", // dialog title + "c files(*.c *.h){.c,.h}", // dialog filter syntax : simple => .h,.c,.pp, etc and collections : text1{filter0,filter1,filter2}, text2{filter0,filter1,filter2}, etc.. + ".", // base directory for files scan + "", // base filename + 0, // a fucntion for display a right pane if you want + 0.0f, // base width of the pane + 0, // count selection : 0 infinite, 1 one file (default), n (n files) + "User data !", // some user datas + ImGuiFileDialogFlags_ConfirmOverwrite); // ImGuiFileDialogFlags +} + +ImGuiIO* ioptr = igGetIO(); +ImVec2 maxSize; +maxSize.x = ioptr->DisplaySize.x * 0.8f; +maxSize.y = ioptr->DisplaySize.y * 0.8f; +ImVec2 minSize; +minSize.x = maxSize.x * 0.25f; +minSize.y = maxSize.y * 0.25f; + +// display dialog +if (IGFD_DisplayDialog(cfiledialog, "filedlg", ImGuiWindowFlags_NoCollapse, minSize, maxSize)) +{ + if (IGFD_IsOk(cfiledialog)) // result ok + { + char* cfilePathName = IGFD_GetFilePathName(cfiledialog); + printf("GetFilePathName : %s\n", cfilePathName); + char* cfilePath = IGFD_GetCurrentPath(cfiledialog); + printf("GetCurrentPath : %s\n", cfilePath); + char* cfilter = IGFD_GetCurrentFilter(cfiledialog); + printf("GetCurrentFilter : %s\n", cfilter); + // here convert from string because a string was passed as a userDatas, but it can be what you want + void* cdatas = IGFD_GetUserDatas(cfiledialog); + if (cdatas) + printf("GetUserDatas : %s\n", (const char*)cdatas); + struct IGFD_Selection csel = IGFD_GetSelection(cfiledialog); // multi selection + printf("Selection :\n"); + for (int i = 0; i < (int)csel.count; i++) + { + printf("(%i) FileName %s => path %s\n", i, csel.table[i].fileName, csel.table[i].filePathName); + } + // action + + // destroy + if (cfilePathName) free(cfilePathName); + if (cfilePath) free(cfilePath); + if (cfilter) free(cfilter); + + IGFD_Selection_DestroyContent(&csel); + } + IGFD_CloseDialog(cfiledialog); +} + +// destroy ImGuiFileDialog +IGFD_Destroy(cfiledialog); + +----------------------------------------------------------------------------------------------------------------- +## Std::filesystem (c++17) can be used instead of dirent.h +----------------------------------------------------------------------------------------------------------------- + +you just need to uncomment that in the config file + +#define USE_STD_FILESYSTEM + +in this mode dirent is not more required + +----------------------------------------------------------------------------------------------------------------- +## How to Integrate ImGuiFileDialog in your project +----------------------------------------------------------------------------------------------------------------- + +### ImGuiFileDialog require : + +* dirent v1.23 (only when USE_STD_FILESYSTEM is not defined) (https://github.com/tronkko/dirent/tree/v1.23) lib, only for windows. Successfully tested with version v1.23 only +* Dear ImGui (https://github.com/ocornut/imgui/tree/master) (with/without tables widgets) + +### Customize ImGuiFileDialog : + +You just need to write your own config file by override the file : ImGuiFileDialog/ImGuiFileDialogConfig.h +like i do here with CustomImGuiFileDialogConfig.h + +After that, for let ImGuiFileDialog your own custom file, +you must define the preprocessor directive CUSTOM_IMGUIFILEDIALOG_CONFIG with the path of you custom config file. +This path must be relative to the directory where you put ImGuiFileDialog module. + +----------------------------------------------------------------------------------------------------------------- +----------------------------------------------------------------------------------------------------------------- + +Thats all. + +You can check by example in this repo with the file CustomImGuiFileDialogConfig.h : +- this trick was used for have custom icon font instead of labels for buttons or messages titles +- you can also use your custom imgui button, the button call stamp must be same by the way :) + +The Custom Icon Font (in CustomFont.cpp and CustomFont.h) was made with ImGuiFontStudio (https://github.com/aiekick/ImGuiFontStudio) i wrote for that :) +ImGuiFontStudio is using also ImGuiFileDialog. + +----------------------------------------------------------------------------------------------------------------- +----------------------------------------------------------------------------------------------------------------- +*/ + +//////////////////////////////////////////////// CHANGED: MOVED FROM THE CPP +#if defined(__WIN32__) || defined(_WIN32) +#ifndef WIN32 +#define WIN32 +#endif // WIN32 +#define stat _stat +#define stricmp _stricmp +#include +// this option need c++17 +#ifdef USE_STD_FILESYSTEM +#include +#else +#include "dirent/dirent.h" // directly open the dirent file attached to this lib +#endif // USE_STD_FILESYSTEM +#define PATH_SEP '\\' +#ifndef PATH_MAX +#define PATH_MAX 260 +#endif // PATH_MAX +#elif defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__) || defined (__EMSCRIPTEN__) +#define UNIX +#define stricmp strcasecmp +#include +// this option need c++17 +#ifndef USE_STD_FILESYSTEM +#include +#endif // USE_STD_FILESYSTEM +#define PATH_SEP '/' +#endif // defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__) +//////////////////////////////////////////////// + +#ifndef IMGUIFILEDIALOG_H +#define IMGUIFILEDIALOG_H + +#define IMGUIFILEDIALOG_VERSION "v0.6.3" + +#ifndef CUSTOM_IMGUIFILEDIALOG_CONFIG +#include "ImGuiFileDialogConfig.h" +#else // CUSTOM_IMGUIFILEDIALOG_CONFIG +#include CUSTOM_IMGUIFILEDIALOG_CONFIG +#endif // CUSTOM_IMGUIFILEDIALOG_CONFIG + +// file style enum for file display (color, icon, font) +typedef int IGFD_FileStyleFlags; // -> enum IGFD_FileStyleFlags_ +enum IGFD_FileStyleFlags_ // by evaluation / priority order +{ + IGFD_FileStyle_None = 0, // define none style + IGFD_FileStyleByTypeFile = (1 << 0), // define style for all files + IGFD_FileStyleByTypeDir = (1 << 1), // define style for all dir + IGFD_FileStyleByTypeLink = (1 << 2), // define style for all link + IGFD_FileStyleByExtention = (1 << 3), // define style by extention, for files or links + IGFD_FileStyleByFullName = (1 << 4), // define style for particular file/dir/link full name (filename + extention) + IGFD_FileStyleByContainedInFullName = (1 << 5), // define style for file/dir/link when criteria is contained in full name +}; + +typedef int ImGuiFileDialogFlags; // -> enum ImGuiFileDialogFlags_ +enum ImGuiFileDialogFlags_ +{ + ImGuiFileDialogFlags_None = 0, + ImGuiFileDialogFlags_ConfirmOverwrite = (1 << 0), // show confirm to overwrite dialog + ImGuiFileDialogFlags_DontShowHiddenFiles = (1 << 1), // dont show hidden file (file starting with a .) + ImGuiFileDialogFlags_DisableCreateDirectoryButton = (1 << 2), // disable the create directory button + ImGuiFileDialogFlags_HideColumnType = (1 << 3), // hide column file type + ImGuiFileDialogFlags_HideColumnSize = (1 << 4), // hide column file size + ImGuiFileDialogFlags_HideColumnDate = (1 << 5), // hide column file date +#ifdef USE_THUMBNAILS + ImGuiFileDialogFlags_DisableThumbnailMode = (1 << 6), // disable the thumbnail mode +#endif + ImGuiFileDialogFlags_Default = ImGuiFileDialogFlags_ConfirmOverwrite +}; + +#ifdef USE_THUMBNAILS +struct IGFD_Thumbnail_Info +{ + int isReadyToDisplay = 0; // ready to be rendered, so texture created + int isReadyToUpload = 0; // ready to upload to gpu + int isLoadingOrLoaded = 0; // was sent to laoding or loaded + void* textureID = 0; // 2d texture id (void* is like ImtextureID type) (GL, DX, VK, Etc..) + unsigned char* textureFileDatas = 0; // file texture datas, will be rested to null after gpu upload + int textureWidth = 0; // width of the texture to upload + int textureHeight = 0; // height of the texture to upload + int textureChannels = 0; // count channels of the texture to upload + void* userDatas = 0; // user datas +}; +#endif // USE_THUMBNAILS + +#ifdef __cplusplus + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace IGFD +{ +#ifndef MAX_FILE_DIALOG_NAME_BUFFER +#define MAX_FILE_DIALOG_NAME_BUFFER 1024 +#endif // MAX_FILE_DIALOG_NAME_BUFFER + +#ifndef MAX_PATH_BUFFER_SIZE +#define MAX_PATH_BUFFER_SIZE 1024 +#endif // MAX_PATH_BUFFER_SIZE + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + class FileDialogInternal; + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + class SearchManager + { + public: + std::string puSearchTag; + char puSearchBuffer[MAX_FILE_DIALOG_NAME_BUFFER] = ""; + bool puSearchInputIsActive = false; + + public: + void Clear(); // clear datas + void DrawSearchBar(FileDialogInternal& vFileDialogInternal); // draw the search bar + }; + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + class FileStyle + { + public: + ImVec4 color = ImVec4(0, 0, 0, 0); + std::string icon; + ImFont* font = nullptr; + IGFD_FileStyleFlags flags = 0; + + public: + FileStyle(); + FileStyle(const FileStyle& vStyle); + FileStyle(const ImVec4& vColor, const std::string& vIcon = "", ImFont* vFont = nullptr); + }; + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + class Utils + { + public: + struct PathStruct + { + std::string path; + std::string name; + std::string ext; + bool isOk = false; + }; + + public: + static bool Splitter(bool split_vertically, float thickness, float* size1, float* size2, float min_size1, float min_size2, float splitter_long_axis_size = -1.0f); + static bool ReplaceString(std::string& str, const std::string& oldStr, const std::string& newStr); + static bool IsDirectoryExist(const std::string& name); + static bool CreateDirectoryIfNotExist(const std::string& name); + static PathStruct ParsePathFileName(const std::string& vPathFileName); + static void AppendToBuffer(char* vBuffer, size_t vBufferLen, const std::string& vStr); + static void ResetBuffer(char* vBuffer); + static void SetBuffer(char* vBuffer, size_t vBufferLen, const std::string& vStr); +#ifdef WIN32 + static bool WReplaceString(std::wstring& str, const std::wstring& oldStr, const std::wstring& newStr); + static std::vector WSplitStringToVector(const std::wstring& text, char delimiter, bool pushEmpty); + static std::string wstring_to_string(const std::wstring& wstr); + static std::wstring string_to_wstring(const std::string& mbstr); +#endif + static std::vector SplitStringToVector(const std::string& text, char delimiter, bool pushEmpty); + static std::vector GetDrivesList(); + }; + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + class FileInfos; + class FilterManager + { + public: + class FilterInfosStruct + { + public: + std::string filter; + std::set collectionfilters; + + public: + void clear(); // clear the datas + bool empty() const; // is filter empty + bool exist(const std::string& vFilter) const; // is filter exist + }; + + private: + std::vector prParsedFilters; + std::unordered_map>> prFilesStyle; // file infos for file extention only + FilterInfosStruct prSelectedFilter; + + public: + std::string puDLGFilters; + std::string puDLGdefaultExt; + + public: + void ParseFilters(const char* vFilters); // Parse filter syntax, detect and parse filter collection + void SetSelectedFilterWithExt(const std::string& vFilter); // Select filter + + bool prFillFileStyle(std::shared_ptr vFileInfos) const; // fill fille with the good style + + void SetFileStyle( + const IGFD_FileStyleFlags& vFlags, + const char* vCriteria, + const FileStyle& vInfos); // Set FileStyle + void SetFileStyle( + const IGFD_FileStyleFlags& vFlags, + const char* vCriteria, + const ImVec4& vColor, + const std::string& vIcon, + ImFont* vFont); // link file style to Color and Icon and Font + bool GetFileStyle( + const IGFD_FileStyleFlags& vFlags, + const std::string& vCriteria, + ImVec4* vOutColor, + std::string* vOutIcon, + ImFont** vOutFont); // Get Color and Icon for Filter + void ClearFilesStyle(); // clear prFileStyle + + bool IsCoveredByFilters(const std::string& vTag) const; // check if current file extention (vTag) is covered by current filter + bool DrawFilterComboBox(FileDialogInternal& vFileDialogInternal); // draw the filter combobox + FilterInfosStruct GetSelectedFilter(); // get the current selected filter + std::string ReplaceExtentionWithCurrentFilter(const std::string& vFile) const; // replace the extention of the current file by the selected filter + void SetDefaultFilterIfNotDefined(); // define the first filter if no filter is selected + }; + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + class FileInfos + { + public: + char fileType = ' '; // dirent fileType (f:file, d:directory, l:link) + std::string filePath; // path of the file + std::string fileNameExt; // filename of the file (file name + extention) (but no path) + std::string fileNameExt_optimized; // optimized for search => insensitivecase + std::string fileExt; // extention of the file + size_t fileSize = 0; // for sorting operations + std::string formatedFileSize; // file size formated (10 o, 10 ko, 10 mo, 10 go) + std::string fileModifDate; // file user defined format of the date (data + time by default) + std::shared_ptr fileStyle = nullptr; // style of the file +#ifdef USE_THUMBNAILS + IGFD_Thumbnail_Info thumbnailInfo; // structre for the display for image file tetxure +#endif // USE_THUMBNAILS + + public: + bool IsTagFound(const std::string& vTag) const; + }; + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + class FileManager + { + public: // types + enum class SortingFieldEnum // sorting for filetering of the file lsit + { + FIELD_NONE = 0, // no sorting preference, result indetermined haha.. + FIELD_FILENAME, // sorted by filename + FIELD_TYPE, // sorted by filetype + FIELD_SIZE, // sorted by filesize (formated file size) + FIELD_DATE, // sorted by filedate +#ifdef USE_THUMBNAILS + FIELD_THUMBNAILS, // sorted by thumbnails (comparaison by width then by height) +#endif // USE_THUMBNAILS + }; + + private: + std::string prCurrentPath; // current path (to be decomposed in prCurrentPathDecomposition + std::vector prCurrentPathDecomposition; // part words + std::vector> prFileList; // base container + std::vector> prFilteredFileList; // filtered container (search, sorting, etc..) + std::string prLastSelectedFileName; // for shift multi selection + std::set prSelectedFileNames; // the user selection of FilePathNames + bool prCreateDirectoryMode = false; // for create directory widget + + public: + char puVariadicBuffer[MAX_FILE_DIALOG_NAME_BUFFER] = ""; // called by prSelectableItem + bool puInputPathActivated = false; // show input for path edition + bool puDrivesClicked = false; // event when a drive button is clicked + bool puPathClicked = false; // event when a path button was clicked + char puInputPathBuffer[MAX_PATH_BUFFER_SIZE] = ""; // input path buffer for imgui widget input text (displayed in palce of composer) + char puFileNameBuffer[MAX_FILE_DIALOG_NAME_BUFFER] = ""; // file name buffer in footer for imgui widget input text + char puDirectoryNameBuffer[MAX_FILE_DIALOG_NAME_BUFFER] = ""; // directory name buffer in footer for imgui widget input text (when is directory mode) + std::string puHeaderFileName; // detail view name of column file + std::string puHeaderFileType; // detail view name of column type + std::string puHeaderFileSize; // detail view name of column size + std::string puHeaderFileDate; // detail view name of column date + time +#ifdef USE_THUMBNAILS + std::string puHeaderFileThumbnails; // detail view name of column thumbnails + bool puSortingDirection[5] = { true, true, true, true, true }; // detail view // true => Descending, false => Ascending +#else + bool puSortingDirection[4] = { true, true, true, true }; // detail view // true => Descending, false => Ascending +#endif + SortingFieldEnum puSortingField = SortingFieldEnum::FIELD_FILENAME; // detail view sorting column + bool puShowDrives = false; // drives are shown (only on os windows) + + std::string puDLGpath; // base path set by user when OpenDialog/OpenModal was called + std::string puDLGDefaultFileName; // base default file path name set by user when OpenDialog/OpenModal was called + size_t puDLGcountSelectionMax = 1U; // 0 for infinite // base max selection count set by user when OpenDialog/OpenModal was called + bool puDLGDirectoryMode = false; // is directory mode (defiend like : puDLGDirectoryMode = (filters.empty())) + + std::string puFsRoot; + + private: + static std::string prRoundNumber(double vvalue, int n); // custom rounding number + static std::string prFormatFileSize(size_t vByteSize); // format file size field + static std::string prOptimizeFilenameForSearchOperations(const std::string& vFileNameExt); // turn all text in lower case for search facilitie + static void prCompleteFileInfos(const std::shared_ptr& FileInfos); // set time and date infos of a file (detail view mode) + void prRemoveFileNameInSelection(const std::string& vFileName); // selection : remove a file name + void prAddFileNameInSelection(const std::string& vFileName, bool vSetLastSelectionFileName); // selection : add a file name + void AddFile(const FileDialogInternal& vFileDialogInternal, + const std::string& vPath, const std::string& vFileName, const char& vFileType); // add file called by scandir + + public: + FileManager(); + bool IsComposerEmpty(); + size_t GetComposerSize(); + bool IsFileListEmpty(); + bool IsFilteredListEmpty(); + size_t GetFullFileListSize(); + std::shared_ptr GetFullFileAt(size_t vIdx); + size_t GetFilteredListSize(); + std::shared_ptr GetFilteredFileAt(size_t vIdx); + bool IsFileNameSelected(const std::string& vFileName); + std::string GetBack(); + void ClearComposer(); + void ClearFileLists(); // clear file list, will destroy thumbnail textures + void ClearAll(); + void ApplyFilteringOnFileList(const FileDialogInternal& vFileDialogInternal); + void OpenCurrentPath(const FileDialogInternal& vFileDialogInternal); // set the path of the dialog, will launch the directory scan for populate the file listview + void SortFields(const FileDialogInternal& vFileDialogInternal, + const SortingFieldEnum& vSortingField, const bool& vCanChangeOrder); // will sort a column + bool GetDrives(); // list drives on windows platform + bool CreateDir(const std::string& vPath); // create a directory on the file system + void ComposeNewPath(std::vector::iterator vIter); // compose a path from the compose path widget + bool SetPathOnParentDirectoryIfAny(); // compose paht on parent directory + std::string GetCurrentPath(); // get the current path + void SetCurrentPath(const std::string& vCurrentPath); // set the current path + static bool IsFileExist(const std::string& vFile); + void SetDefaultFileName(const std::string& vFileName); + bool SelectDirectory(const std::shared_ptr& vInfos); // enter directory + void SelectFileName(const FileDialogInternal& vFileDialogInternal, + const std::shared_ptr& vInfos); // select filename + + //depend of dirent.h + void SetCurrentDir(const std::string& vPath); // define current directory for scan + void ScanDir(const FileDialogInternal& vFileDialogInternal, const std::string& vPath); // scan the directory for retrieve the file list + + public: + std::string GetResultingPath(); + std::string GetResultingFileName(FileDialogInternal& vFileDialogInternal); + std::string GetResultingFilePathName(FileDialogInternal& vFileDialogInternal); + std::map GetResultingSelection(); + + public: + void DrawDirectoryCreation(const FileDialogInternal& vFileDialogInternal); // draw directory creation widget + void DrawPathComposer(const FileDialogInternal& vFileDialogInternal); // draw path composer widget + }; + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#ifdef USE_THUMBNAILS + typedef std::function CreateThumbnailFun; // texture 2d creation function binding + typedef std::function DestroyThumbnailFun; // texture 2d destroy function binding +#endif + class ThumbnailFeature + { + protected: + ThumbnailFeature(); + ~ThumbnailFeature(); + + void NewThumbnailFrame(FileDialogInternal& vFileDialogInternal); + void EndThumbnailFrame(FileDialogInternal& vFileDialogInternal); + void QuitThumbnailFrame(FileDialogInternal& vFileDialogInternal); + +#ifdef USE_THUMBNAILS + protected: + enum class DisplayModeEnum + { + FILE_LIST = 0, + THUMBNAILS_LIST, + THUMBNAILS_GRID + }; + + private: + uint32_t prCountFiles = 0U; + bool prIsWorking = false; + std::shared_ptr prThumbnailGenerationThread = nullptr; + std::list> prThumbnailFileDatasToGet; // base container + std::mutex prThumbnailFileDatasToGetMutex; + std::list> prThumbnailToCreate; // base container + std::mutex prThumbnailToCreateMutex; + std::list prThumbnailToDestroy; // base container + std::mutex prThumbnailToDestroyMutex; + + CreateThumbnailFun prCreateThumbnailFun = nullptr; + DestroyThumbnailFun prDestroyThumbnailFun = nullptr; + + protected: + DisplayModeEnum prDisplayMode = DisplayModeEnum::FILE_LIST; + + protected: + // will be call in cpu zone (imgui computations, will call a texture file retrieval thread) + void prStartThumbnailFileDatasExtraction(); // start the thread who will get byte buffer from image files + bool prStopThumbnailFileDatasExtraction(); // stop the thread who will get byte buffer from image files + void prThreadThumbnailFileDatasExtractionFunc(); // the thread who will get byte buffer from image files + void prDrawThumbnailGenerationProgress(); // a little progressbar who will display the texture gen status + void prAddThumbnailToLoad(const std::shared_ptr& vFileInfos); // add texture to load in the thread + void prAddThumbnailToCreate(const std::shared_ptr& vFileInfos); + void prAddThumbnailToDestroy(const IGFD_Thumbnail_Info& vIGFD_Thumbnail_Info); + void prDrawDisplayModeToolBar(); // draw display mode toolbar (file list, thumbnails list, small thumbnails grid, big thumbnails grid) + void prClearThumbnails(FileDialogInternal& vFileDialogInternal); + + public: + void SetCreateThumbnailCallback(const CreateThumbnailFun& vCreateThumbnailFun); + void SetDestroyThumbnailCallback(const DestroyThumbnailFun& vCreateThumbnailFun); + + // must be call in gpu zone (rendering, possibly one rendering thread) + void ManageGPUThumbnails(); // in gpu rendering zone, whill create or destroy texture +#endif + }; + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + class BookMarkFeature + { + protected: + BookMarkFeature(); + +#ifdef USE_BOOKMARK + private: + struct BookmarkStruct + { + std::string name; // name of the bookmark + + // todo: the path could be relative, better if the app is movedn but bookmarked path can be outside of the app + std::string path; // absolute path of the bookmarked directory + }; + + private: + ImGuiListClipper prBookmarkClipper; + std::vector prBookmarks; + char prBookmarkEditBuffer[MAX_FILE_DIALOG_NAME_BUFFER] = ""; + + protected: + float prBookmarkWidth = 200.0f; + bool prBookmarkPaneShown = false; + + protected: + void prDrawBookmarkButton(); // draw bookmark button + bool prDrawBookmarkPane(FileDialogInternal& vFileDialogInternal, const ImVec2& vSize); // draw bookmark Pane + + public: + std::string SerializeBookmarks(); // serialize bookmarks : return bookmark buffer to save in a file + void DeserializeBookmarks( // deserialize bookmarks : load bookmark buffer to load in the dialog (saved from previous use with SerializeBookmarks()) + const std::string& vBookmarks); // bookmark buffer to load +#endif // USE_BOOKMARK + }; + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // file localization by input chat // widget flashing + class KeyExplorerFeature + { + protected: + KeyExplorerFeature(); + +#ifdef USE_EXPLORATION_BY_KEYS + private: + size_t prFlashedItem = 0; // flash when select by char + float prFlashAlpha = 0.0f; // flash when select by char + float prFlashAlphaAttenInSecs = 1.0f; // fps display dependant + size_t prLocateFileByInputChar_lastFileIdx = 0; + ImWchar prLocateFileByInputChar_lastChar = 0; + int prLocateFileByInputChar_InputQueueCharactersSize = 0; + bool prLocateFileByInputChar_lastFound = false; + + protected: + void prLocateByInputKey(FileDialogInternal& vFileDialogInternal); // select a file line in listview according to char key + bool prLocateItem_Loop(FileDialogInternal& vFileDialogInternal, ImWchar vC); // restrat for start of list view if not found a corresponding file + void prExploreWithkeys(FileDialogInternal& vFileDialogInternal, ImGuiID vListViewID); // select file/directory line in listview accroding to up/down enter/backspace keys + static bool prFlashableSelectable( // custom flashing selectable widgets, for flash the selected line in a short time + const char* label, bool selected = false, ImGuiSelectableFlags flags = 0, + bool vFlashing = false, const ImVec2& size = ImVec2(0, 0)); + void prStartFlashItem(size_t vIdx); // define than an item must be flashed + bool prBeginFlashItem(size_t vIdx); // start the flashing of a line in lsit view + static void prEndFlashItem(); // end the fleshing accrdoin to var prFlashAlphaAttenInSecs + + public: + void SetFlashingAttenuationInSeconds( // set the flashing time of the line in file list when use exploration keys + float vAttenValue); // set the attenuation (from flashed to not flashed) in seconds +#endif // USE_EXPLORATION_BY_KEYS + }; + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + typedef void* UserDatas; + typedef std::function PaneFun; // side pane function binding + class FileDialogInternal + { + public: + FileManager puFileManager; + FilterManager puFilterManager; + SearchManager puSearchManager; + + public: + std::string puName; + bool puShowDialog = false; + ImVec2 puDialogCenterPos = ImVec2(0, 0); // center pos for display the confirm overwrite dialog + int puLastImGuiFrameCount = 0; // to be sure than only one dialog displayed per frame + float puFooterHeight = 0.0f; + bool puCanWeContinue = true; // events + bool puOkResultToConfirm = false; // to confim if ok for OverWrite + bool puIsOk = false; + bool puFileInputIsActive = false; // when input text for file or directory is active + bool puFileListViewIsActive = false; // when list view is active + std::string puDLGkey; + std::string puDLGtitle; + ImGuiFileDialogFlags puDLGflags = ImGuiFileDialogFlags_None; + UserDatas puDLGuserDatas = nullptr; + PaneFun puDLGoptionsPane = nullptr; + float puDLGoptionsPaneWidth = 0.0f; + bool puDLGmodal = false; + bool puNeedToExitDialog = false; + + bool puUseCustomLocale = false; + int puLocaleCategory = LC_ALL; // locale category to use + std::string puLocaleBegin; // the locale who will be applied at start of the display dialog + std::string puLocaleEnd; // the locale who will be applaied at end of the display dialog + + public: + void NewFrame(); // new frame, so maybe neded to do somethings, like reset events + void EndFrame(); // end frame, so maybe neded to do somethings fater all + void ResetForNewDialog(); // reset what is needed to reset for the openging of a new dialog + }; + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + class FileDialog : + public BookMarkFeature, + public KeyExplorerFeature, + public ThumbnailFeature + { + private: + FileDialogInternal prFileDialogInternal; + ImGuiListClipper prFileListClipper; + + public: + bool puAnyWindowsHovered = false; // not remember why haha :) todo : to check if we can remove + + public: + static FileDialog* Instance() // Singleton for easier accces form anywhere but only one dialog at a time + { + static FileDialog _instance; + return &_instance; + } + + public: + FileDialog(); // ImGuiFileDialog Constructor. can be used for have many dialog at same tiem (not possible with singleton) + virtual ~FileDialog(); // ImGuiFileDialog Destructor + + // standard dialog + void OpenDialog( // open simple dialog (path and fileName can be specified) + const std::string& vKey, // key dialog + const std::string& vTitle, // title + const char* vFilters, // filters + const std::string& vPath, // path + const std::string& vFileName, // defaut file name + const int& vCountSelectionMax = 1, // count selection max + UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane) + ImGuiFileDialogFlags vFlags = 0); // ImGuiFileDialogFlags + + void OpenDialog( // open simple dialog (path and filename are obtained from filePathName) + const std::string& vKey, // key dialog + const std::string& vTitle, // title + const char* vFilters, // filters + const std::string& vFilePathName, // file path name (will be decompsoed in path and fileName) + const int& vCountSelectionMax = 1, // count selection max + UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane) + ImGuiFileDialogFlags vFlags = 0); // ImGuiFileDialogFlags + + // with pane + void OpenDialog( // open dialog with custom right pane (path and fileName can be specified) + const std::string& vKey, // key dialog + const std::string& vTitle, // title + const char* vFilters, // filters + const std::string& vPath, // path + const std::string& vFileName, // defaut file name + const PaneFun& vSidePane, // side pane + const float& vSidePaneWidth = 250.0f, // side pane width + const int& vCountSelectionMax = 1, // count selection max + UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane) + ImGuiFileDialogFlags vFlags = 0); // ImGuiFileDialogFlags + + void OpenDialog( // open dialog with custom right pane (path and filename are obtained from filePathName) + const std::string& vKey, // key dialog + const std::string& vTitle, // title + const char* vFilters, // filters + const std::string& vFilePathName, // file path name (will be decompsoed in path and fileName) + const PaneFun& vSidePane, // side pane + const float& vSidePaneWidth = 250.0f, // side pane width + const int& vCountSelectionMax = 1, // count selection max + UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane) + ImGuiFileDialogFlags vFlags = 0); // ImGuiFileDialogFlags + + // modal dialog + void OpenModal( // open simple modal (path and fileName can be specified) + const std::string& vKey, // key dialog + const std::string& vTitle, // title + const char* vFilters, // filters + const std::string& vPath, // path + const std::string& vFileName, // defaut file name + const int& vCountSelectionMax = 1, // count selection max + UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane) + ImGuiFileDialogFlags vFlags = 0); // ImGuiFileDialogFlags + + void OpenModal( // open simple modal (path and fielname are obtained from filePathName) + const std::string& vKey, // key dialog + const std::string& vTitle, // title + const char* vFilters, // filters + const std::string& vFilePathName, // file path name (will be decompsoed in path and fileName) + const int& vCountSelectionMax = 1, // count selection max + UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane) + ImGuiFileDialogFlags vFlags = 0); // ImGuiFileDialogFlags + + // with pane + void OpenModal( // open modal with custom right pane (path and filename are obtained from filePathName) + const std::string& vKey, // key dialog + const std::string& vTitle, // title + const char* vFilters, // filters + const std::string& vPath, // path + const std::string& vFileName, // defaut file name + const PaneFun& vSidePane, // side pane + const float& vSidePaneWidth = 250.0f, // side pane width + const int& vCountSelectionMax = 1, // count selection max + UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane) + ImGuiFileDialogFlags vFlags = 0); // ImGuiFileDialogFlags + + void OpenModal( // open modal with custom right pane (path and fielname are obtained from filePathName) + const std::string& vKey, // key dialog + const std::string& vTitle, // title + const char* vFilters, // filters + const std::string& vFilePathName, // file path name (will be decompsoed in path and fileName) + const PaneFun& vSidePane, // side pane + const float& vSidePaneWidth = 250.0f, // side pane width + const int& vCountSelectionMax = 1, // count selection max + UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane) + ImGuiFileDialogFlags vFlags = 0); // ImGuiFileDialogFlags + + // Display / Close dialog form + bool Display( // Display the dialog. return true if a result was obtained (Ok or not) + const std::string& vKey, // key dialog to display (if not the same key as defined by OpenDialog/Modal => no opening) + ImGuiWindowFlags vFlags = ImGuiWindowFlags_NoCollapse, // ImGuiWindowFlags + ImVec2 vMinSize = ImVec2(0, 0), // mininmal size contraint for the ImGuiWindow + ImVec2 vMaxSize = ImVec2(FLT_MAX, FLT_MAX)); // maximal size contraint for the ImGuiWindow + void Close(); // close dialog + + // queries + bool WasOpenedThisFrame(const std::string& vKey) const; // say if the dialog key was already opened this frame + bool WasOpenedThisFrame() const; // say if the dialog was already opened this frame + bool IsOpened(const std::string& vKey) const; // say if the key is opened + bool IsOpened() const; // say if the dialog is opened somewhere + std::string GetOpenedKey() const; // return the dialog key who is opened, return nothing if not opened + + // get result + bool IsOk() const; // true => Dialog Closed with Ok result / false : Dialog closed with cancel result + std::map GetSelection(); // Open File behavior : will return selection via a map + std::string GetFilePathName(); // Save File behavior : will always return the content of the field with current filter extention and current path + std::string GetCurrentFileName(); // Save File behavior : will always return the content of the field with current filter extention + std::string GetCurrentPath(); // will return current path + std::string GetCurrentFilter(); // will return selected filter + UserDatas GetUserDatas() const; // will return user datas send with Open Dialog/Modal + + // file style by extentions + void SetFileStyle( // SetExtention datas for have custom display of particular file type + const IGFD_FileStyleFlags& vFlags, // file style + const char* vCriteria, // extention filter to tune + const FileStyle& vInfos); // Filter Extention Struct who contain Color and Icon/Text for the display of the file with extention filter + void SetFileStyle( // SetExtention datas for have custom display of particular file type + const IGFD_FileStyleFlags& vFlags, // file style + const char* vCriteria, // extention filter to tune + const ImVec4& vColor, // wanted color for the display of the file with extention filter + const std::string& vIcon = "", // wanted text or icon of the file with extention filter + ImFont *vFont = nullptr); // wantes font + bool GetFileStyle( // GetExtention datas. return true is extention exist + const IGFD_FileStyleFlags& vFlags, // file style + const std::string& vCriteria, // extention filter (same as used in SetExtentionInfos) + ImVec4* vOutColor, // color to retrieve + std::string* vOutIcon = nullptr, // icon or text to retrieve + ImFont** vOutFont = nullptr); // font to retreive + void ClearFilesStyle(); // clear extentions setttings + + void SetLocales( // set locales to use before and after the dialog display + const int& vLocaleCategory, // set local category + const std::string& vLocaleBegin, // locale to use at begining of the dialog display + const std::string& vLocaleEnd); // locale to use at the end of the dialog display + + protected: + void NewFrame(); // new frame just at begining of display + void EndFrame(); // end frame just at end of display + void QuitFrame(); // quit frame when qui quit the dialog + + // others + bool prConfirm_Or_OpenOverWriteFileDialog_IfNeeded( + bool vLastAction, ImGuiWindowFlags vFlags); // treatment of the result, start the confirm to overwrite dialog if needed (if defined with flag) + + public: + // dialog parts + virtual void prDrawHeader(); // draw header part of the dialog (bookmark btn, dir creation, path composer, search bar) + virtual void prDrawContent(); // draw content part of the dialog (bookmark pane, file list, side pane) + virtual bool prDrawFooter(); // draw footer part of the dialog (file field, fitler combobox, ok/cancel btn's) + + // widgets components + virtual void prDrawSidePane(float vHeight); // draw side pane + virtual bool prSelectableItem(int vidx, + std::shared_ptr vInfos, + bool vSelected, const char* vFmt, ...); // draw a custom selectable behavior item + virtual void prDrawFileListView(ImVec2 vSize); // draw file list view (default mode) + +#ifdef USE_THUMBNAILS + virtual void prDrawThumbnailsListView(ImVec2 vSize); // draw file list view with small thumbnails on the same line + virtual void prDrawThumbnailsGridView(ImVec2 vSize); // draw a grid of small thumbnails +#endif + + // to be called only by these function and theirs overrides + // - prDrawFileListView + // - prDrawThumbnailsListView + // - prDrawThumbnailsGridView + void prBeginFileColorIconStyle( + std::shared_ptr vFileInfos, + bool& vOutShowColor, + std::string& vOutStr, + ImFont** vOutFont); // begin style apply of filter with color an icon if any + void prEndFileColorIconStyle( + const bool& vShowColor, + ImFont* vFont); // end style apply of filter + }; +} + +typedef IGFD::UserDatas IGFDUserDatas; +typedef IGFD::PaneFun IGFDPaneFun; +typedef IGFD::FileDialog ImGuiFileDialog; +#else // __cplusplus +typedef struct ImGuiFileDialog ImGuiFileDialog; +typedef struct IGFD_Selection_Pair IGFD_Selection_Pair; +typedef struct IGFD_Selection IGFD_Selection; +#endif // __cplusplus + +// C Interface + +#include + +#if defined _WIN32 || defined __CYGWIN__ +#ifdef IMGUIFILEDIALOG_NO_EXPORT +#define API +#else // IMGUIFILEDIALOG_NO_EXPORT +#define API __declspec(dllexport) +#endif // IMGUIFILEDIALOG_NO_EXPORT +#else // defined _WIN32 || defined __CYGWIN__ +#ifdef __GNUC__ +#define API __attribute__((__visibility__("default"))) +#else // __GNUC__ +#define API +#endif // __GNUC__ +#endif // defined _WIN32 || defined __CYGWIN__ + +#ifdef __cplusplus +#define IMGUIFILEDIALOG_API extern "C" API +#else // __cplusplus +#define IMGUIFILEDIALOG_API +#endif // __cplusplus + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +///// C API //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +struct IGFD_Selection_Pair +{ + char* fileName; + char* filePathName; +}; + +IMGUIFILEDIALOG_API IGFD_Selection_Pair IGFD_Selection_Pair_Get(); // return an initialized IGFD_Selection_Pair +IMGUIFILEDIALOG_API void IGFD_Selection_Pair_DestroyContent(IGFD_Selection_Pair* vSelection_Pair); // destroy the content of a IGFD_Selection_Pair + +struct IGFD_Selection +{ + IGFD_Selection_Pair* table; // 0 + size_t count; // 0U +}; + +IMGUIFILEDIALOG_API IGFD_Selection IGFD_Selection_Get(); // return an initialized IGFD_Selection +IMGUIFILEDIALOG_API void IGFD_Selection_DestroyContent(IGFD_Selection* vSelection); // destroy the content of a IGFD_Selection + +// constructor / destructor +IMGUIFILEDIALOG_API ImGuiFileDialog* IGFD_Create(void); // create the filedialog context +IMGUIFILEDIALOG_API void IGFD_Destroy(ImGuiFileDialog* vContext); // destroy the filedialog context + +typedef void (*IGFD_PaneFun)(const char*, void*, bool*); // callback fucntion for display the pane + +#ifdef USE_THUMBNAILS +typedef void (*IGFD_CreateThumbnailFun)(IGFD_Thumbnail_Info*); // callback function for create thumbnail texture +typedef void (*IGFD_DestroyThumbnailFun)(IGFD_Thumbnail_Info*); // callback fucntion for destroy thumbnail texture +#endif // USE_THUMBNAILS + +IMGUIFILEDIALOG_API void IGFD_OpenDialog( // open a standard dialog + ImGuiFileDialog* vContext, // ImGuiFileDialog context + const char* vKey, // key dialog + const char* vTitle, // title + const char* vFilters, // filters/filter collections. set it to null for directory mode + const char* vPath, // path + const char* vFileName, // defaut file name + const int vCountSelectionMax, // count selection max + void* vUserDatas, // user datas (can be retrieved in pane) + ImGuiFileDialogFlags vFlags); // ImGuiFileDialogFlags + +IMGUIFILEDIALOG_API void IGFD_OpenDialog2( // open a standard dialog + ImGuiFileDialog* vContext, // ImGuiFileDialog context + const char* vKey, // key dialog + const char* vTitle, // title + const char* vFilters, // filters/filter collections. set it to null for directory mode + const char* vFilePathName, // defaut file path name (path and filename witl be extracted from it) + const int vCountSelectionMax, // count selection max + void* vUserDatas, // user datas (can be retrieved in pane) + ImGuiFileDialogFlags vFlags); // ImGuiFileDialogFlags + +IMGUIFILEDIALOG_API void IGFD_OpenPaneDialog( // open a standard dialog with pane + ImGuiFileDialog* vContext, // ImGuiFileDialog context + const char* vKey, // key dialog + const char* vTitle, // title + const char* vFilters, // filters/filter collections. set it to null for directory mode + const char* vPath, // path + const char* vFileName, // defaut file name + const IGFD_PaneFun vSidePane, // side pane + const float vSidePaneWidth, // side pane base width + const int vCountSelectionMax, // count selection max + void* vUserDatas, // user datas (can be retrieved in pane) + ImGuiFileDialogFlags vFlags); // ImGuiFileDialogFlags + +IMGUIFILEDIALOG_API void IGFD_OpenPaneDialog2( // open a standard dialog with pane + ImGuiFileDialog* vContext, // ImGuiFileDialog context + const char* vKey, // key dialog + const char* vTitle, // title + const char* vFilters, // filters/filter collections. set it to null for directory mode + const char* vFilePathName, // defaut file name (path and filename witl be extracted from it) + const IGFD_PaneFun vSidePane, // side pane + const float vSidePaneWidth, // side pane base width + const int vCountSelectionMax, // count selection max + void* vUserDatas, // user datas (can be retrieved in pane) + ImGuiFileDialogFlags vFlags); // ImGuiFileDialogFlags + +IMGUIFILEDIALOG_API void IGFD_OpenModal( // open a modal dialog + ImGuiFileDialog* vContext, // ImGuiFileDialog context + const char* vKey, // key dialog + const char* vTitle, // title + const char* vFilters, // filters/filter collections. set it to null for directory mode + const char* vPath, // path + const char* vFileName, // defaut file name + const int vCountSelectionMax, // count selection max + void* vUserDatas, // user datas (can be retrieved in pane) + ImGuiFileDialogFlags vFlags); // ImGuiFileDialogFlags + +IMGUIFILEDIALOG_API void IGFD_OpenModal2( // open a modal dialog + ImGuiFileDialog* vContext, // ImGuiFileDialog context + const char* vKey, // key dialog + const char* vTitle, // title + const char* vFilters, // filters/filter collections. set it to null for directory mode + const char* vFilePathName, // defaut file name (path and filename witl be extracted from it) + const int vCountSelectionMax, // count selection max + void* vUserDatas, // user datas (can be retrieved in pane) + ImGuiFileDialogFlags vFlags); // ImGuiFileDialogFlags + +IMGUIFILEDIALOG_API void IGFD_OpenPaneModal( // open a modal dialog with pane + ImGuiFileDialog* vContext, // ImGuiFileDialog context + const char* vKey, // key dialog + const char* vTitle, // title + const char* vFilters, // filters/filter collections. set it to null for directory mode + const char* vPath, // path + const char* vFileName, // defaut file name + const IGFD_PaneFun vSidePane, // side pane + const float vSidePaneWidth, // side pane base width + const int vCountSelectionMax, // count selection max + void* vUserDatas, // user datas (can be retrieved in pane) + ImGuiFileDialogFlags vFlags); // ImGuiFileDialogFlags + +IMGUIFILEDIALOG_API void IGFD_OpenPaneModal2( // open a modal dialog with pane + ImGuiFileDialog* vContext, // ImGuiFileDialog context + const char* vKey, // key dialog + const char* vTitle, // title + const char* vFilters, // filters/filter collections. set it to null for directory mode + const char* vFilePathName, // defaut file name (path and filename witl be extracted from it) + const IGFD_PaneFun vSidePane, // side pane + const float vSidePaneWidth, // side pane base width + const int vCountSelectionMax, // count selection max + void* vUserDatas, // user datas (can be retrieved in pane) + ImGuiFileDialogFlags vFlags); // ImGuiFileDialogFlags + +IMGUIFILEDIALOG_API bool IGFD_DisplayDialog( // Display the dialog + ImGuiFileDialog* vContext, // ImGuiFileDialog context + const char* vKey, // key dialog to display (if not the same key as defined by OpenDialog/Modal => no opening) + ImGuiWindowFlags vFlags, // ImGuiWindowFlags + ImVec2 vMinSize, // mininmal size contraint for the ImGuiWindow + ImVec2 vMaxSize); // maximal size contraint for the ImGuiWindow + +IMGUIFILEDIALOG_API void IGFD_CloseDialog( // Close the dialog + ImGuiFileDialog* vContext); // ImGuiFileDialog context + +IMGUIFILEDIALOG_API bool IGFD_IsOk( // true => Dialog Closed with Ok result / false : Dialog closed with cancel result + ImGuiFileDialog* vContext); // ImGuiFileDialog context + +IMGUIFILEDIALOG_API bool IGFD_WasKeyOpenedThisFrame( // say if the dialog key was already opened this frame + ImGuiFileDialog* vContext, // ImGuiFileDialog context + const char* vKey); + +IMGUIFILEDIALOG_API bool IGFD_WasOpenedThisFrame( // say if the dialog was already opened this frame + ImGuiFileDialog* vContext); // ImGuiFileDialog context + +IMGUIFILEDIALOG_API bool IGFD_IsKeyOpened( // say if the dialog key is opened + ImGuiFileDialog* vContext, // ImGuiFileDialog context + const char* vCurrentOpenedKey); // the dialog key + +IMGUIFILEDIALOG_API bool IGFD_IsOpened( // say if the dialog is opened somewhere + ImGuiFileDialog* vContext); // ImGuiFileDialog context + +IMGUIFILEDIALOG_API IGFD_Selection IGFD_GetSelection( // Open File behavior : will return selection via a map + ImGuiFileDialog* vContext); // ImGuiFileDialog context + +IMGUIFILEDIALOG_API char* IGFD_GetFilePathName( // Save File behavior : will always return the content of the field with current filter extention and current path + ImGuiFileDialog* vContext); // ImGuiFileDialog context + +IMGUIFILEDIALOG_API char* IGFD_GetCurrentFileName( // Save File behavior : will always return the content of the field with current filter extention + ImGuiFileDialog* vContext); // ImGuiFileDialog context + +IMGUIFILEDIALOG_API char* IGFD_GetCurrentPath( // will return current path + ImGuiFileDialog* vContext); // ImGuiFileDialog context + +IMGUIFILEDIALOG_API char* IGFD_GetCurrentFilter( // will return selected filter + ImGuiFileDialog* vContext); // ImGuiFileDialog context + +IMGUIFILEDIALOG_API void* IGFD_GetUserDatas( // will return user datas send with Open Dialog/Modal + ImGuiFileDialog* vContext); // ImGuiFileDialog context + +IMGUIFILEDIALOG_API void IGFD_SetFileStyle( // SetExtention datas for have custom display of particular file type + ImGuiFileDialog* vContext, // ImGuiFileDialog context + IGFD_FileStyleFlags vFileStyleFlags, // file style type + const char* vFilter, // extention filter to tune + ImVec4 vColor, // wanted color for the display of the file with extention filter + const char* vIconText, // wanted text or icon of the file with extention filter (can be sued with font icon) + ImFont* vFont); // wanted font pointer + +IMGUIFILEDIALOG_API void IGFD_SetFileStyle2( // SetExtention datas for have custom display of particular file type + ImGuiFileDialog* vContext, // ImGuiFileDialog context + IGFD_FileStyleFlags vFileStyleFlags, // file style type + const char* vFilter, // extention filter to tune + float vR, float vG, float vB, float vA, // wanted color channels RGBA for the display of the file with extention filter + const char* vIconText, // wanted text or icon of the file with extention filter (can be sued with font icon) + ImFont* vFont); // wanted font pointer + +IMGUIFILEDIALOG_API bool IGFD_GetFileStyle( + ImGuiFileDialog* vContext, // ImGuiFileDialog context + IGFD_FileStyleFlags vFileStyleFlags, // file style type + const char* vFilter, // extention filter (same as used in SetExtentionInfos) + ImVec4* vOutColor, // color to retrieve + char** vOutIconText, // icon or text to retrieve + ImFont** vOutFont); // font pointer to retrived + +IMGUIFILEDIALOG_API void IGFD_ClearFilesStyle( // clear extentions setttings + ImGuiFileDialog* vContext); // ImGuiFileDialog context + +IMGUIFILEDIALOG_API void SetLocales( // set locales to use before and after display + ImGuiFileDialog* vContext, // ImGuiFileDialog context + const int vCategory, // set local category + const char* vBeginLocale, // locale to use at begining of the dialog display + const char* vEndLocale); // locale to set at end of the dialog display + +#ifdef USE_EXPLORATION_BY_KEYS +IMGUIFILEDIALOG_API void IGFD_SetFlashingAttenuationInSeconds( // set the flashing time of the line in file list when use exploration keys + ImGuiFileDialog* vContext, // ImGuiFileDialog context + float vAttenValue); // set the attenuation (from flashed to not flashed) in seconds +#endif + +#ifdef USE_BOOKMARK +IMGUIFILEDIALOG_API char* IGFD_SerializeBookmarks( // serialize bookmarks : return bookmark buffer to save in a file + ImGuiFileDialog* vContext); // ImGuiFileDialog context + +IMGUIFILEDIALOG_API void IGFD_DeserializeBookmarks( // deserialize bookmarks : load bookmar buffer to load in the dialog (saved from previous use with SerializeBookmarks()) + ImGuiFileDialog* vContext, // ImGuiFileDialog context + const char* vBookmarks); // bookmark buffer to load +#endif + +#ifdef USE_THUMBNAILS +IMGUIFILEDIALOG_API void SetCreateThumbnailCallback( // define the callback for create the thumbnails texture + ImGuiFileDialog* vContext, // ImGuiFileDialog context + IGFD_CreateThumbnailFun vCreateThumbnailFun); // the callback for create the thumbnails texture + +IMGUIFILEDIALOG_API void SetDestroyThumbnailCallback( // define the callback for destroy the thumbnails texture + ImGuiFileDialog* vContext, // ImGuiFileDialog context + IGFD_DestroyThumbnailFun vDestroyThumbnailFun); // the callback for destroy the thumbnails texture + +IMGUIFILEDIALOG_API void ManageGPUThumbnails( // must be call in gpu zone, possibly a thread, will call the callback for create / destroy the textures + ImGuiFileDialog* vContext); // ImGuiFileDialog context +#endif // USE_THUMBNAILS + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#endif // IMGUIFILEDIALOG_H diff --git a/Source/Dependencies/ImGuiFileDialog-0.6.3/ImGuiFileDialogConfig.h b/Source/Dependencies/ImGuiFileDialog-0.6.3/ImGuiFileDialogConfig.h new file mode 100644 index 000000000..3416b029b --- /dev/null +++ b/Source/Dependencies/ImGuiFileDialog-0.6.3/ImGuiFileDialogConfig.h @@ -0,0 +1,97 @@ +#pragma once + +// uncomment and modify defines under for customize ImGuiFileDialog + +//this options need c++17 +//#define USE_STD_FILESYSTEM + +//#define MAX_FILE_DIALOG_NAME_BUFFER 1024 +//#define MAX_PATH_BUFFER_SIZE 1024 + +//#define USE_THUMBNAILS +//the thumbnail generation use the stb_image and stb_resize lib who need to define the implementation +//btw if you already use them in your app, you can have compiler error due to "implemntation found in double" +//so uncomment these line for prevent the creation of implementation of these libs again +//#define DONT_DEFINE_AGAIN__STB_IMAGE_IMPLEMENTATION +//#define DONT_DEFINE_AGAIN__STB_IMAGE_RESIZE_IMPLEMENTATION +//#define IMGUI_RADIO_BUTTON RadioButton +//#define DisplayMode_ThumbailsList_ImageHeight 32.0f +//#define tableHeaderFileThumbnailsString "Thumbnails" +//#define DisplayMode_FilesList_ButtonString "FL" +//#define DisplayMode_FilesList_ButtonHelp "File List" +//#define DisplayMode_ThumbailsList_ButtonString "TL" +//#define DisplayMode_ThumbailsList_ButtonHelp "Thumbnails List" +// todo +//#define DisplayMode_ThumbailsGrid_ButtonString "TG" +//#define DisplayMode_ThumbailsGrid_ButtonHelp "Thumbnails Grid" + + +//#define USE_EXPLORATION_BY_KEYS +// this mapping by default is for GLFW but you can use another +//#include +// Up key for explore to the top +//#define IGFD_KEY_UP GLFW_KEY_UP +// Down key for explore to the bottom +//#define IGFD_KEY_DOWN GLFW_KEY_DOWN +// Enter key for open directory +//#define IGFD_KEY_ENTER GLFW_KEY_ENTER +// BackSpace for comming back to the last directory +//#define IGFD_KEY_BACKSPACE GLFW_KEY_BACKSPACE + +// by ex you can quit the dialog by pressing the key excape +//#define USE_DIALOG_EXIT_WITH_KEY +//#define IGFD_EXIT_KEY GLFW_KEY_ESCAPE + +// widget +// filter combobox width +//#define FILTER_COMBO_WIDTH 120.0f +// button widget use for compose path +//#define IMGUI_PATH_BUTTON ImGui::Button +// standard button +//#define IMGUI_BUTTON ImGui::Button + +// locales string +//#define createDirButtonString "+" +//#define okButtonString " OK" +//#define cancelButtonString " Cancel" +//#define resetButtonString "R" +//#define drivesButtonString "Drives" +//#define editPathButtonString "E" +//#define searchString "Search" +//#define dirEntryString "[DIR] " +//#define linkEntryString "[LINK] " +//#define fileEntryString "[FILE] " +//#define fileNameString "File Name : " +//#define dirNameString "Directory Path :" +//#define buttonResetSearchString "Reset search" +//#define buttonDriveString "Drives" +//#define buttonEditPathString "Edit path\nYou can also right click on path buttons" +//#define buttonResetPathString "Reset to current directory" +//#define buttonCreateDirString "Create Directory" +//#define OverWriteDialogTitleString "The file Already Exist !" +//#define OverWriteDialogMessageString "Would you like to OverWrite it ?" +//#define OverWriteDialogConfirmButtonString "Confirm" +//#define OverWriteDialogCancelButtonString "Cancel" + +// DateTimeFormat +// see strftime functionin for customize +// "%Y/%m/%d %H:%M" give 2021:01:22 11:47 +// "%Y/%m/%d %i:%M%p" give 2021:01:22 11:45PM +//#define DateTimeFormat "%Y/%m/%d %i:%M%p" + +// theses icons will appear in table headers +//#define USE_CUSTOM_SORTING_ICON +//#define tableHeaderAscendingIcon "A|" +//#define tableHeaderDescendingIcon "D|" +//#define tableHeaderFileNameString " File name" +//#define tableHeaderFileTypeString " Type" +//#define tableHeaderFileSizeString " Size" +//#define tableHeaderFileDateTimeString " Date" + +//#define USE_BOOKMARK +//#define bookmarkPaneWith 150.0f +//#define IMGUI_TOGGLE_BUTTON ToggleButton +//#define bookmarksButtonString "Bookmark" +//#define bookmarksButtonHelpString "Bookmark" +//#define addBookmarkButtonString "+" +//#define removeBookmarkButtonString "-" diff --git a/Source/Dependencies/ImGuiFileDialog-0.6.3/dirent/ChangeLog b/Source/Dependencies/ImGuiFileDialog-0.6.3/dirent/ChangeLog new file mode 100644 index 000000000..d79cea25f --- /dev/null +++ b/Source/Dependencies/ImGuiFileDialog-0.6.3/dirent/ChangeLog @@ -0,0 +1,129 @@ +2018-05-08 Toni Rönkkö + + * Version 1.23.2: fixes bad scandir prototype. + +2017-08-27 Toni Rönkkö + + * Version 1.23: support readdir_r and scandir functions. + +2017-07-18 Toni Rönkkö + + * Created release branches v1.22 and v1.21 to Git. Published version + 1.22 at softagalleria.net. + +2016-09-11 Toni Rönkkö + + * Version 1.22: added support for CMake. Thanks to Paul Fultz II. + +2014-09-25 Toni Rönkkö + + * Version 1.21: compiles correctly under Open Watcom. Thanks to + Virgil Banowetz for a patch! + +2014-04-07 Toni Rönkkö + + * Version 1.20.1: the zip file from the previous version did not open + correctly with Microsoft's compressed folders. Thanks to Alexandre + for info! + +2014-03-17 Toni Ronkko + + * Version 1.20: dirent.h compiles correctly in 64-bit architecture. + Thanks to Aaron Simmons! + +2014-03-03 Toni Ronkko + + * Version 1.13.2: define DT_LNK for compatibility with Unix + programs. Thanks to Joel Bruick for suggestion! + +2013-01-27 Toni Ronkko + + * Version 1.13.1: patch from Edward Berner fixes set_errno() on + Windows NT 4.0. + + * Revised wcstombs() and mbstowcs() wrappers to make sure that they do + not write past their target string. + + * PATH_MAX from windows.h includes zero terminator so there is no + need to add one extra byte to variables and structures. + +2012-12-12 Toni Ronkko + + * Version 1.13: use the traditional 8+3 file naming scheme if a file + name cannot be represented in the default ANSI code page. Now + compiles again with MSVC 6.0. Thanks to Konstantin Khomoutov for + testing. + +2012-10-01 Toni Ronkko + + * Version 1.12.1: renamed wide-character DIR structure _wDIR to + _WDIR (with capital W) in order to maintain compatibility with MingW. + +2012-09-30 Toni Ronkko + + * Version 1.12: define PATH_MAX and NAME_MAX. Added wide-character + variants _wDIR, _wdirent, _wopendir(), _wreaddir(), _wclosedir() and + _wrewinddir(). Thanks to Edgar Buerkle and Jan Nijtmans for ideas + and code. + + * Now avoiding windows.h. This allows dirent.h to be integrated + more easily into programs using winsock. Thanks to Fernando + Azaldegui. + +2011-03-15 Toni Ronkko + + * Version 1.11: defined FILE_ATTRIBUTE_DEVICE for MSVC 6.0. + +2010-08-11 Toni Ronkko + + * Version 1.10: added d_type and d_namlen fields to dirent structure. + The former is especially useful for determining whether directory + entry represents a file or a directory. For more information, see + http://www.delorie.com/gnu/docs/glibc/libc_270.html + + * Improved conformance to the standards. For example, errno is now + set properly on failure and assert() is never used. Thanks to Peter + Brockam for suggestions. + + * Fixed a bug in rewinddir(): when using relative directory names, + change of working directory no longer causes rewinddir() to fail. + +2009-12-15 John Cunningham + + * Version 1.9: added rewinddir member function + +2008-01-18 Toni Ronkko + + * Version 1.8: Using FindFirstFileA and WIN32_FIND_DATAA to avoid + converting string between multi-byte and unicode representations. + This makes the code simpler and also allows the code to be compiled + under MingW. Thanks to Azriel Fasten for the suggestion. + +2007-03-04 Toni Ronkko + + * Bug fix: due to the strncpy_s() function this file only compiled in + Visual Studio 2005. Using the new string functions only when the + compiler version allows. + +2006-11-02 Toni Ronkko + + * Major update: removed support for Watcom C, MS-DOS and Turbo C to + simplify the file, updated the code to compile cleanly on Visual + Studio 2005 with both unicode and multi-byte character strings, + removed rewinddir() as it had a bug. + +2006-08-20 Toni Ronkko + + * Removed all remarks about MSVC 1.0, which is antiqued now. + Simplified comments by removing SGML tags. + +2002-05-14 Toni Ronkko + + * Embedded the function definitions directly to the header so that no + source modules need to be included in the Visual Studio project. + Removed all the dependencies to other projects so that this header + file can be used independently. + +1998-05-28 Toni Ronkko + + * First version. diff --git a/Source/Dependencies/ImGuiFileDialog-0.6.3/dirent/LICENSE b/Source/Dependencies/ImGuiFileDialog-0.6.3/dirent/LICENSE new file mode 100644 index 000000000..af0436066 --- /dev/null +++ b/Source/Dependencies/ImGuiFileDialog-0.6.3/dirent/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 1998-2019 Toni Ronkko + +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. diff --git a/Source/Dependencies/ImGuiFileDialog-0.6.3/dirent/README.md b/Source/Dependencies/ImGuiFileDialog-0.6.3/dirent/README.md new file mode 100644 index 000000000..53cc9f725 --- /dev/null +++ b/Source/Dependencies/ImGuiFileDialog-0.6.3/dirent/README.md @@ -0,0 +1,96 @@ +# Dirent +Dirent is a C/C++ programming interface that allows programmers to retrieve +information about files and directories under Linux/UNIX. This project +provides Linux compatible Dirent interface for Microsoft Windows. + + +# Installation + +Download the latest Dirent installation package from +[GitHub](https://github.com/tronkko/dirent/releases) and +unpack the installation file with 7-zip, for example. The installation +package contains dirent.h file as well as a few example programs and +tests. + + +## Install Dirent for All Programs + +To make dirent.h available for all C/C++ programs, simply copy the +``include/dirent.h`` file to the system include directory. System include +directory contains header files such as assert.h and windows.h. In Visual +Studio 2008, for example, the system include may be found at +``C:\Program Files\Microsoft Visual Studio 9.0\VC\include``. + +Everything you need is included in the single dirent.h file, and you can +start using Dirent immediately -- there is no need to add files to your +Visual Studio project. + + +## Embed Dirent into Your Own Project + +If you wish to distribute dirent.h alongside with your own source code, then +copy ``include/dirent.h`` file to a new sub-directory within your project and +add that directory to include path on Windows while omitting the directory +under Linux/UNIX. This allows your project to be compiled against native +dirent.h on Linux/UNIX while substituting the functionality on Microsoft +Windows. + + +## Examples + +The installation package contains four example programs: + +Program | Purpose +-------- | ----------------------------------------------------------------- +ls | List files in a directory, e.g. ls "c:\Program Files" +find | Find files in subdirectories, e.g. find "c:\Program Files\CMake" +updatedb | Build database of files in a drive, e.g. updatedb c:\ +locate | Locate a file from database, e.g. locate notepad.exe + +To build the example programs, first install [CMake](https://cmake.org/). +Then, with CMake installed, open command prompt and create a temporary +directory ``c:\temp\dirent`` for the build files as + +``` +c:\ +mkdir temp +mkdir temp\dirent +cd temp\dirent +``` + +Generate build files as + +``` +cmake d:\dirent +``` + +where ``d:\dirent`` is the root directory of the Dirent package (containing +this README.md and LICENSE file). + +Once CMake is finished, open Visual Studio, load the generated dirent.sln file +from the build directory and build the solution. Once the build completes, run +the example programs from the command prompt as + +``` +cd Debug +ls . +find . +updatedb c:\ +locate cmd.exe +``` + + +# Copying + +Dirent may be freely distributed under the MIT license. See the +[LICENSE](LICENSE) file for details. + + +# Alternatives to Dirent + +I ported Dirent to Microsoft Windows in 1998 when only a few alternatives +were available. However, the situation has changed since then and nowadays +both [Cygwin](http://www.cygwin.com) and [MingW](http://www.mingw.org) +allow you to compile a great number of UNIX programs in Microsoft Windows. +They both provide a full dirent API as well as many other UNIX APIs. MingW +can even be used for commercial applications! diff --git a/Source/Dependencies/ImGuiFileDialog-0.6.3/dirent/dirent.h b/Source/Dependencies/ImGuiFileDialog-0.6.3/dirent/dirent.h new file mode 100644 index 000000000..f7a46dafc --- /dev/null +++ b/Source/Dependencies/ImGuiFileDialog-0.6.3/dirent/dirent.h @@ -0,0 +1,1160 @@ +/* + * Dirent interface for Microsoft Visual Studio + * + * Copyright (C) 1998-2019 Toni Ronkko + * This file is part of dirent. Dirent may be freely distributed + * under the MIT license. For all details and documentation, see + * https://github.com/tronkko/dirent + */ +#ifndef DIRENT_H +#define DIRENT_H + +/* Hide warnings about unreferenced local functions */ +#if defined(__clang__) +# pragma clang diagnostic ignored "-Wunused-function" +#elif defined(_MSC_VER) +# pragma warning(disable:4505) +#elif defined(__GNUC__) +# pragma GCC diagnostic ignored "-Wunused-function" +#endif + +/* + * Include windows.h without Windows Sockets 1.1 to prevent conflicts with + * Windows Sockets 2.0. + */ +#ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Indicates that d_type field is available in dirent structure */ +#define _DIRENT_HAVE_D_TYPE + +/* Indicates that d_namlen field is available in dirent structure */ +#define _DIRENT_HAVE_D_NAMLEN + +/* Entries missing from MSVC 6.0 */ +#if !defined(FILE_ATTRIBUTE_DEVICE) +# define FILE_ATTRIBUTE_DEVICE 0x40 +#endif + +/* File type and permission flags for stat(), general mask */ +#if !defined(S_IFMT) +# define S_IFMT _S_IFMT +#endif + +/* Directory bit */ +#if !defined(S_IFDIR) +# define S_IFDIR _S_IFDIR +#endif + +/* Character device bit */ +#if !defined(S_IFCHR) +# define S_IFCHR _S_IFCHR +#endif + +/* Pipe bit */ +#if !defined(S_IFFIFO) +# define S_IFFIFO _S_IFFIFO +#endif + +/* Regular file bit */ +#if !defined(S_IFREG) +# define S_IFREG _S_IFREG +#endif + +/* Read permission */ +#if !defined(S_IREAD) +# define S_IREAD _S_IREAD +#endif + +/* Write permission */ +#if !defined(S_IWRITE) +# define S_IWRITE _S_IWRITE +#endif + +/* Execute permission */ +#if !defined(S_IEXEC) +# define S_IEXEC _S_IEXEC +#endif + +/* Pipe */ +#if !defined(S_IFIFO) +# define S_IFIFO _S_IFIFO +#endif + +/* Block device */ +#if !defined(S_IFBLK) +# define S_IFBLK 0 +#endif + +/* Link */ +#if !defined(S_IFLNK) +# define S_IFLNK 0 +#endif + +/* Socket */ +#if !defined(S_IFSOCK) +# define S_IFSOCK 0 +#endif + +/* Read user permission */ +#if !defined(S_IRUSR) +# define S_IRUSR S_IREAD +#endif + +/* Write user permission */ +#if !defined(S_IWUSR) +# define S_IWUSR S_IWRITE +#endif + +/* Execute user permission */ +#if !defined(S_IXUSR) +# define S_IXUSR 0 +#endif + +/* Read group permission */ +#if !defined(S_IRGRP) +# define S_IRGRP 0 +#endif + +/* Write group permission */ +#if !defined(S_IWGRP) +# define S_IWGRP 0 +#endif + +/* Execute group permission */ +#if !defined(S_IXGRP) +# define S_IXGRP 0 +#endif + +/* Read others permission */ +#if !defined(S_IROTH) +# define S_IROTH 0 +#endif + +/* Write others permission */ +#if !defined(S_IWOTH) +# define S_IWOTH 0 +#endif + +/* Execute others permission */ +#if !defined(S_IXOTH) +# define S_IXOTH 0 +#endif + +/* Maximum length of file name */ +#if !defined(PATH_MAX) +# define PATH_MAX MAX_PATH +#endif +#if !defined(FILENAME_MAX) +# define FILENAME_MAX MAX_PATH +#endif +#if !defined(NAME_MAX) +# define NAME_MAX FILENAME_MAX +#endif + +/* File type flags for d_type */ +#define DT_UNKNOWN 0 +#define DT_REG S_IFREG +#define DT_DIR S_IFDIR +#define DT_FIFO S_IFIFO +#define DT_SOCK S_IFSOCK +#define DT_CHR S_IFCHR +#define DT_BLK S_IFBLK +#define DT_LNK S_IFLNK + +/* Macros for converting between st_mode and d_type */ +#define IFTODT(mode) ((mode) & S_IFMT) +#define DTTOIF(type) (type) + +/* + * File type macros. Note that block devices, sockets and links cannot be + * distinguished on Windows and the macros S_ISBLK, S_ISSOCK and S_ISLNK are + * only defined for compatibility. These macros should always return false + * on Windows. + */ +#if !defined(S_ISFIFO) +# define S_ISFIFO(mode) (((mode) & S_IFMT) == S_IFIFO) +#endif +#if !defined(S_ISDIR) +# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) +#endif +#if !defined(S_ISREG) +# define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) +#endif +#if !defined(S_ISLNK) +# define S_ISLNK(mode) (((mode) & S_IFMT) == S_IFLNK) +#endif +#if !defined(S_ISSOCK) +# define S_ISSOCK(mode) (((mode) & S_IFMT) == S_IFSOCK) +#endif +#if !defined(S_ISCHR) +# define S_ISCHR(mode) (((mode) & S_IFMT) == S_IFCHR) +#endif +#if !defined(S_ISBLK) +# define S_ISBLK(mode) (((mode) & S_IFMT) == S_IFBLK) +#endif + +/* Return the exact length of the file name without zero terminator */ +#define _D_EXACT_NAMLEN(p) ((p)->d_namlen) + +/* Return the maximum size of a file name */ +#define _D_ALLOC_NAMLEN(p) ((PATH_MAX)+1) + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* Wide-character version */ +struct _wdirent { + /* Always zero */ + long d_ino; + + /* File position within stream */ + long d_off; + + /* Structure size */ + unsigned short d_reclen; + + /* Length of name without \0 */ + size_t d_namlen; + + /* File type */ + int d_type; + + /* File name */ + wchar_t d_name[PATH_MAX+1]; +}; +typedef struct _wdirent _wdirent; + +struct _WDIR { + /* Current directory entry */ + struct _wdirent ent; + + /* Private file data */ + WIN32_FIND_DATAW data; + + /* True if data is valid */ + int cached; + + /* Win32 search handle */ + HANDLE handle; + + /* Initial directory name */ + wchar_t *patt; +}; +typedef struct _WDIR _WDIR; + +/* Multi-byte character version */ +struct dirent { + /* Always zero */ + long d_ino; + + /* File position within stream */ + long d_off; + + /* Structure size */ + unsigned short d_reclen; + + /* Length of name without \0 */ + size_t d_namlen; + + /* File type */ + int d_type; + + /* File name */ + char d_name[PATH_MAX+1]; +}; +typedef struct dirent dirent; + +struct DIR { + struct dirent ent; + struct _WDIR *wdirp; +}; +typedef struct DIR DIR; + + +/* Dirent functions */ +static DIR *opendir (const char *dirname); +static _WDIR *_wopendir (const wchar_t *dirname); + +static struct dirent *readdir (DIR *dirp); +static struct _wdirent *_wreaddir (_WDIR *dirp); + +static int readdir_r( + DIR *dirp, struct dirent *entry, struct dirent **result); +static int _wreaddir_r( + _WDIR *dirp, struct _wdirent *entry, struct _wdirent **result); + +static int closedir (DIR *dirp); +static int _wclosedir (_WDIR *dirp); + +static void rewinddir (DIR* dirp); +static void _wrewinddir (_WDIR* dirp); + +static int scandir (const char *dirname, struct dirent ***namelist, + int (*filter)(const struct dirent*), + int (*compare)(const struct dirent**, const struct dirent**)); + +static int alphasort (const struct dirent **a, const struct dirent **b); + +static int versionsort (const struct dirent **a, const struct dirent **b); + + +/* For compatibility with Symbian */ +#define wdirent _wdirent +#define WDIR _WDIR +#define wopendir _wopendir +#define wreaddir _wreaddir +#define wclosedir _wclosedir +#define wrewinddir _wrewinddir + + +/* Internal utility functions */ +static WIN32_FIND_DATAW *dirent_first (_WDIR *dirp); +static WIN32_FIND_DATAW *dirent_next (_WDIR *dirp); + +static int dirent_mbstowcs_s( + size_t *pReturnValue, + wchar_t *wcstr, + size_t sizeInWords, + const char *mbstr, + size_t count); + +static int dirent_wcstombs_s( + size_t *pReturnValue, + char *mbstr, + size_t sizeInBytes, + const wchar_t *wcstr, + size_t count); + +static void dirent_set_errno (int error); + + +/* + * Open directory stream DIRNAME for read and return a pointer to the + * internal working area that is used to retrieve individual directory + * entries. + */ +static _WDIR* +_wopendir( + const wchar_t *dirname) +{ + _WDIR *dirp; + DWORD n; + wchar_t *p; + + /* Must have directory name */ + if (dirname == NULL || dirname[0] == '\0') { + dirent_set_errno (ENOENT); + return NULL; + } + + /* Allocate new _WDIR structure */ + dirp = (_WDIR*) malloc (sizeof (struct _WDIR)); + if (!dirp) { + return NULL; + } + + /* Reset _WDIR structure */ + dirp->handle = INVALID_HANDLE_VALUE; + dirp->patt = NULL; + dirp->cached = 0; + + /* + * Compute the length of full path plus zero terminator + * + * Note that on WinRT there's no way to convert relative paths + * into absolute paths, so just assume it is an absolute path. + */ +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + /* Desktop */ + n = GetFullPathNameW (dirname, 0, NULL, NULL); +#else + /* WinRT */ + n = wcslen (dirname); +#endif + + /* Allocate room for absolute directory name and search pattern */ + dirp->patt = (wchar_t*) malloc (sizeof (wchar_t) * n + 16); + if (dirp->patt == NULL) { + goto exit_closedir; + } + + /* + * Convert relative directory name to an absolute one. This + * allows rewinddir() to function correctly even when current + * working directory is changed between opendir() and rewinddir(). + * + * Note that on WinRT there's no way to convert relative paths + * into absolute paths, so just assume it is an absolute path. + */ +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + /* Desktop */ + n = GetFullPathNameW (dirname, n, dirp->patt, NULL); + if (n <= 0) { + goto exit_closedir; + } +#else + /* WinRT */ + wcsncpy_s (dirp->patt, n+1, dirname, n); +#endif + + /* Append search pattern \* to the directory name */ + p = dirp->patt + n; + switch (p[-1]) { + case '\\': + case '/': + case ':': + /* Directory ends in path separator, e.g. c:\temp\ */ + /*NOP*/; + break; + + default: + /* Directory name doesn't end in path separator */ + *p++ = '\\'; + } + *p++ = '*'; + *p = '\0'; + + /* Open directory stream and retrieve the first entry */ + if (!dirent_first (dirp)) { + goto exit_closedir; + } + + /* Success */ + return dirp; + + /* Failure */ +exit_closedir: + _wclosedir (dirp); + return NULL; +} + +/* + * Read next directory entry. + * + * Returns pointer to static directory entry which may be overwritten by + * subsequent calls to _wreaddir(). + */ +static struct _wdirent* +_wreaddir( + _WDIR *dirp) +{ + struct _wdirent *entry; + + /* + * Read directory entry to buffer. We can safely ignore the return value + * as entry will be set to NULL in case of error. + */ + (void) _wreaddir_r (dirp, &dirp->ent, &entry); + + /* Return pointer to statically allocated directory entry */ + return entry; +} + +/* + * Read next directory entry. + * + * Returns zero on success. If end of directory stream is reached, then sets + * result to NULL and returns zero. + */ +static int +_wreaddir_r( + _WDIR *dirp, + struct _wdirent *entry, + struct _wdirent **result) +{ + WIN32_FIND_DATAW *datap; + + /* Read next directory entry */ + datap = dirent_next (dirp); + if (datap) { + size_t n; + DWORD attr; + + /* + * Copy file name as wide-character string. If the file name is too + * long to fit in to the destination buffer, then truncate file name + * to PATH_MAX characters and zero-terminate the buffer. + */ + n = 0; + while (n < PATH_MAX && datap->cFileName[n] != 0) { + entry->d_name[n] = datap->cFileName[n]; + n++; + } + entry->d_name[n] = 0; + + /* Length of file name excluding zero terminator */ + entry->d_namlen = n; + + /* File type */ + attr = datap->dwFileAttributes; + if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) { + entry->d_type = DT_CHR; + } else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) { + entry->d_type = DT_DIR; + } else { + entry->d_type = DT_REG; + } + + /* Reset dummy fields */ + entry->d_ino = 0; + entry->d_off = 0; + entry->d_reclen = sizeof (struct _wdirent); + + /* Set result address */ + *result = entry; + + } else { + + /* Return NULL to indicate end of directory */ + *result = NULL; + + } + + return /*OK*/0; +} + +/* + * Close directory stream opened by opendir() function. This invalidates the + * DIR structure as well as any directory entry read previously by + * _wreaddir(). + */ +static int +_wclosedir( + _WDIR *dirp) +{ + int ok; + if (dirp) { + + /* Release search handle */ + if (dirp->handle != INVALID_HANDLE_VALUE) { + FindClose (dirp->handle); + } + + /* Release search pattern */ + free (dirp->patt); + + /* Release directory structure */ + free (dirp); + ok = /*success*/0; + + } else { + + /* Invalid directory stream */ + dirent_set_errno (EBADF); + ok = /*failure*/-1; + + } + return ok; +} + +/* + * Rewind directory stream such that _wreaddir() returns the very first + * file name again. + */ +static void +_wrewinddir( + _WDIR* dirp) +{ + if (dirp) { + /* Release existing search handle */ + if (dirp->handle != INVALID_HANDLE_VALUE) { + FindClose (dirp->handle); + } + + /* Open new search handle */ + dirent_first (dirp); + } +} + +/* Get first directory entry (internal) */ +static WIN32_FIND_DATAW* +dirent_first( + _WDIR *dirp) +{ + WIN32_FIND_DATAW *datap; + DWORD error; + + /* Open directory and retrieve the first entry */ + dirp->handle = FindFirstFileExW( + dirp->patt, FindExInfoStandard, &dirp->data, + FindExSearchNameMatch, NULL, 0); + if (dirp->handle != INVALID_HANDLE_VALUE) { + + /* a directory entry is now waiting in memory */ + datap = &dirp->data; + dirp->cached = 1; + + } else { + + /* Failed to open directory: no directory entry in memory */ + dirp->cached = 0; + datap = NULL; + + /* Set error code */ + error = GetLastError (); + switch (error) { + case ERROR_ACCESS_DENIED: + /* No read access to directory */ + dirent_set_errno (EACCES); + break; + + case ERROR_DIRECTORY: + /* Directory name is invalid */ + dirent_set_errno (ENOTDIR); + break; + + case ERROR_PATH_NOT_FOUND: + default: + /* Cannot find the file */ + dirent_set_errno (ENOENT); + } + + } + return datap; +} + +/* + * Get next directory entry (internal). + * + * Returns + */ +static WIN32_FIND_DATAW* +dirent_next( + _WDIR *dirp) +{ + WIN32_FIND_DATAW *p; + + /* Get next directory entry */ + if (dirp->cached != 0) { + + /* A valid directory entry already in memory */ + p = &dirp->data; + dirp->cached = 0; + + } else if (dirp->handle != INVALID_HANDLE_VALUE) { + + /* Get the next directory entry from stream */ + if (FindNextFileW (dirp->handle, &dirp->data) != FALSE) { + /* Got a file */ + p = &dirp->data; + } else { + /* The very last entry has been processed or an error occurred */ + FindClose (dirp->handle); + dirp->handle = INVALID_HANDLE_VALUE; + p = NULL; + } + + } else { + + /* End of directory stream reached */ + p = NULL; + + } + + return p; +} + +/* + * Open directory stream using plain old C-string. + */ +static DIR* +opendir( + const char *dirname) +{ + struct DIR *dirp; + + /* Must have directory name */ + if (dirname == NULL || dirname[0] == '\0') { + dirent_set_errno (ENOENT); + return NULL; + } + + /* Allocate memory for DIR structure */ + dirp = (DIR*) malloc (sizeof (struct DIR)); + if (!dirp) { + return NULL; + } + { + int error; + wchar_t wname[PATH_MAX + 1]; + size_t n; + + /* Convert directory name to wide-character string */ + error = dirent_mbstowcs_s( + &n, wname, PATH_MAX + 1, dirname, PATH_MAX + 1); + if (error) { + /* + * Cannot convert file name to wide-character string. This + * occurs if the string contains invalid multi-byte sequences or + * the output buffer is too small to contain the resulting + * string. + */ + goto exit_free; + } + + + /* Open directory stream using wide-character name */ + dirp->wdirp = _wopendir (wname); + if (!dirp->wdirp) { + goto exit_free; + } + + } + + /* Success */ + return dirp; + + /* Failure */ +exit_free: + free (dirp); + return NULL; +} + +/* + * Read next directory entry. + */ +static struct dirent* +readdir( + DIR *dirp) +{ + struct dirent *entry; + + /* + * Read directory entry to buffer. We can safely ignore the return value + * as entry will be set to NULL in case of error. + */ + (void) readdir_r (dirp, &dirp->ent, &entry); + + /* Return pointer to statically allocated directory entry */ + return entry; +} + +/* + * Read next directory entry into called-allocated buffer. + * + * Returns zero on success. If the end of directory stream is reached, then + * sets result to NULL and returns zero. + */ +static int +readdir_r( + DIR *dirp, + struct dirent *entry, + struct dirent **result) +{ + WIN32_FIND_DATAW *datap; + + /* Read next directory entry */ + datap = dirent_next (dirp->wdirp); + if (datap) { + size_t n; + int error; + + /* Attempt to convert file name to multi-byte string */ + error = dirent_wcstombs_s( + &n, entry->d_name, PATH_MAX + 1, datap->cFileName, PATH_MAX + 1); + + /* + * If the file name cannot be represented by a multi-byte string, + * then attempt to use old 8+3 file name. This allows traditional + * Unix-code to access some file names despite of unicode + * characters, although file names may seem unfamiliar to the user. + * + * Be ware that the code below cannot come up with a short file + * name unless the file system provides one. At least + * VirtualBox shared folders fail to do this. + */ + if (error && datap->cAlternateFileName[0] != '\0') { + error = dirent_wcstombs_s( + &n, entry->d_name, PATH_MAX + 1, + datap->cAlternateFileName, PATH_MAX + 1); + } + + if (!error) { + DWORD attr; + + /* Length of file name excluding zero terminator */ + entry->d_namlen = n - 1; + + /* File attributes */ + attr = datap->dwFileAttributes; + if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) { + entry->d_type = DT_CHR; + } else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) { + entry->d_type = DT_DIR; + } else { + entry->d_type = DT_REG; + } + + /* Reset dummy fields */ + entry->d_ino = 0; + entry->d_off = 0; + entry->d_reclen = sizeof (struct dirent); + + } else { + + /* + * Cannot convert file name to multi-byte string so construct + * an erroneous directory entry and return that. Note that + * we cannot return NULL as that would stop the processing + * of directory entries completely. + */ + entry->d_name[0] = '?'; + entry->d_name[1] = '\0'; + entry->d_namlen = 1; + entry->d_type = DT_UNKNOWN; + entry->d_ino = 0; + entry->d_off = -1; + entry->d_reclen = 0; + + } + + /* Return pointer to directory entry */ + *result = entry; + + } else { + + /* No more directory entries */ + *result = NULL; + + } + + return /*OK*/0; +} + +/* + * Close directory stream. + */ +static int +closedir( + DIR *dirp) +{ + int ok; + if (dirp) { + + /* Close wide-character directory stream */ + ok = _wclosedir (dirp->wdirp); + dirp->wdirp = NULL; + + /* Release multi-byte character version */ + free (dirp); + + } else { + + /* Invalid directory stream */ + dirent_set_errno (EBADF); + ok = /*failure*/-1; + + } + return ok; +} + +/* + * Rewind directory stream to beginning. + */ +static void +rewinddir( + DIR* dirp) +{ + /* Rewind wide-character string directory stream */ + _wrewinddir (dirp->wdirp); +} + +/* + * Scan directory for entries. + */ +static int +scandir( + const char *dirname, + struct dirent ***namelist, + int (*filter)(const struct dirent*), + int (*compare)(const struct dirent**, const struct dirent**)) +{ + struct dirent **files = NULL; + size_t size = 0; + size_t allocated = 0; + const size_t init_size = 1; + DIR *dir = NULL; + struct dirent *entry; + struct dirent *tmp = NULL; + size_t i; + int result = 0; + + /* Open directory stream */ + dir = opendir (dirname); + if (dir) { + + /* Read directory entries to memory */ + while (1) { + + /* Enlarge pointer table to make room for another pointer */ + if (size >= allocated) { + void *p; + size_t num_entries; + + /* Compute number of entries in the enlarged pointer table */ + if (size < init_size) { + /* Allocate initial pointer table */ + num_entries = init_size; + } else { + /* Double the size */ + num_entries = size * 2; + } + + /* Allocate first pointer table or enlarge existing table */ + p = realloc (files, sizeof (void*) * num_entries); + if (p != NULL) { + /* Got the memory */ + files = (dirent**) p; + allocated = num_entries; + } else { + /* Out of memory */ + result = -1; + break; + } + + } + + /* Allocate room for temporary directory entry */ + if (tmp == NULL) { + tmp = (struct dirent*) malloc (sizeof (struct dirent)); + if (tmp == NULL) { + /* Cannot allocate temporary directory entry */ + result = -1; + break; + } + } + + /* Read directory entry to temporary area */ + if (readdir_r (dir, tmp, &entry) == /*OK*/0) { + + /* Did we get an entry? */ + if (entry != NULL) { + int pass; + + /* Determine whether to include the entry in result */ + if (filter) { + /* Let the filter function decide */ + pass = filter (tmp); + } else { + /* No filter function, include everything */ + pass = 1; + } + + if (pass) { + /* Store the temporary entry to pointer table */ + files[size++] = tmp; + tmp = NULL; + + /* Keep up with the number of files */ + result++; + } + + } else { + + /* + * End of directory stream reached => sort entries and + * exit. + */ + qsort (files, size, sizeof (void*), + (int (*) (const void*, const void*)) compare); + break; + + } + + } else { + /* Error reading directory entry */ + result = /*Error*/ -1; + break; + } + + } + + } else { + /* Cannot open directory */ + result = /*Error*/ -1; + } + + /* Release temporary directory entry */ + free (tmp); + + /* Release allocated memory on error */ + if (result < 0) { + for (i = 0; i < size; i++) { + free (files[i]); + } + free (files); + files = NULL; + } + + /* Close directory stream */ + if (dir) { + closedir (dir); + } + + /* Pass pointer table to caller */ + if (namelist) { + *namelist = files; + } + return result; +} + +/* Alphabetical sorting */ +static int +alphasort( + const struct dirent **a, const struct dirent **b) +{ + return strcoll ((*a)->d_name, (*b)->d_name); +} + +/* Sort versions */ +static int +versionsort( + const struct dirent **a, const struct dirent **b) +{ + /* FIXME: implement strverscmp and use that */ + return alphasort (a, b); +} + +/* Convert multi-byte string to wide character string */ +static int +dirent_mbstowcs_s( + size_t *pReturnValue, + wchar_t *wcstr, + size_t sizeInWords, + const char *mbstr, + size_t count) +{ + int error; + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + + /* Microsoft Visual Studio 2005 or later */ + error = mbstowcs_s (pReturnValue, wcstr, sizeInWords, mbstr, count); + +#else + + /* Older Visual Studio or non-Microsoft compiler */ + size_t n; + + /* Convert to wide-character string (or count characters) */ + n = mbstowcs (wcstr, mbstr, sizeInWords); + if (!wcstr || n < count) { + + /* Zero-terminate output buffer */ + if (wcstr && sizeInWords) { + if (n >= sizeInWords) { + n = sizeInWords - 1; + } + wcstr[n] = 0; + } + + /* Length of resulting multi-byte string WITH zero terminator */ + if (pReturnValue) { + *pReturnValue = n + 1; + } + + /* Success */ + error = 0; + + } else { + + /* Could not convert string */ + error = 1; + + } + +#endif + return error; +} + +/* Convert wide-character string to multi-byte string */ +static int +dirent_wcstombs_s( + size_t *pReturnValue, + char *mbstr, + size_t sizeInBytes, /* max size of mbstr */ + const wchar_t *wcstr, + size_t count) +{ + int error; + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + + /* Microsoft Visual Studio 2005 or later */ + error = wcstombs_s (pReturnValue, mbstr, sizeInBytes, wcstr, count); + +#else + + /* Older Visual Studio or non-Microsoft compiler */ + size_t n; + + /* Convert to multi-byte string (or count the number of bytes needed) */ + n = wcstombs (mbstr, wcstr, sizeInBytes); + if (!mbstr || n < count) { + + /* Zero-terminate output buffer */ + if (mbstr && sizeInBytes) { + if (n >= sizeInBytes) { + n = sizeInBytes - 1; + } + mbstr[n] = '\0'; + } + + /* Length of resulting multi-bytes string WITH zero-terminator */ + if (pReturnValue) { + *pReturnValue = n + 1; + } + + /* Success */ + error = 0; + + } else { + + /* Cannot convert string */ + error = 1; + + } + +#endif + return error; +} + +/* Set errno variable */ +static void +dirent_set_errno( + int error) +{ +#if defined(_MSC_VER) && _MSC_VER >= 1400 + + /* Microsoft Visual Studio 2005 and later */ + _set_errno (error); + +#else + + /* Non-Microsoft compiler or older Microsoft compiler */ + errno = error; + +#endif +} + + +#ifdef __cplusplus +} +#endif +#endif /*DIRENT_H*/ diff --git a/Source/Dependencies/ImGuiFileDialog-0.6.3/stb/LICENSE b/Source/Dependencies/ImGuiFileDialog-0.6.3/stb/LICENSE new file mode 100644 index 000000000..a77ae91f3 --- /dev/null +++ b/Source/Dependencies/ImGuiFileDialog-0.6.3/stb/LICENSE @@ -0,0 +1,37 @@ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +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. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +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 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. diff --git a/Source/Dependencies/ImGuiFileDialog-0.6.3/stb/README.md b/Source/Dependencies/ImGuiFileDialog-0.6.3/stb/README.md new file mode 100644 index 000000000..61bbd30d8 --- /dev/null +++ b/Source/Dependencies/ImGuiFileDialog-0.6.3/stb/README.md @@ -0,0 +1,165 @@ + + +stb +=== + +single-file public domain (or MIT licensed) libraries for C/C++ + +Noteworthy: + +* image loader: [stb_image.h](stb_image.h) +* image writer: [stb_image_write.h](stb_image_write.h) +* image resizer: [stb_image_resize.h](stb_image_resize.h) +* font text rasterizer: [stb_truetype.h](stb_truetype.h) +* typesafe containers: [stb_ds.h](stb_ds.h) + +Most libraries by stb, except: stb_dxt by Fabian "ryg" Giesen, stb_image_resize +by Jorge L. "VinoBS" Rodriguez, and stb_sprintf by Jeff Roberts. + + + +library | lastest version | category | LoC | description +--------------------- | ---- | -------- | --- | -------------------------------- +**[stb_vorbis.c](stb_vorbis.c)** | 1.20 | audio | 5563 | decode ogg vorbis files from file/memory to float/16-bit signed output +**[stb_image.h](stb_image.h)** | 2.26 | graphics | 7762 | image loading/decoding from file/memory: JPG, PNG, TGA, BMP, PSD, GIF, HDR, PIC +**[stb_truetype.h](stb_truetype.h)** | 1.24 | graphics | 5011 | parse, decode, and rasterize characters from truetype fonts +**[stb_image_write.h](stb_image_write.h)** | 1.15 | graphics | 1690 | image writing to disk: PNG, TGA, BMP +**[stb_image_resize.h](stb_image_resize.h)** | 0.96 | graphics | 2631 | resize images larger/smaller with good quality +**[stb_rect_pack.h](stb_rect_pack.h)** | 1.00 | graphics | 628 | simple 2D rectangle packer with decent quality +**[stb_ds.h](stb_ds.h)** | 0.65 | utility | 1880 | typesafe dynamic array and hash tables for C, will compile in C++ +**[stb_sprintf.h](stb_sprintf.h)** | 1.09 | utility | 1879 | fast sprintf, snprintf for C/C++ +**[stretchy_buffer.h](stretchy_buffer.h)** | 1.04 | utility | 263 | typesafe dynamic array for C (i.e. approximation to vector<>), doesn't compile as C++ +**[stb_textedit.h](stb_textedit.h)** | 1.13 | user interface | 1404 | guts of a text editor for games etc implementing them from scratch +**[stb_voxel_render.h](stb_voxel_render.h)** | 0.89 | 3D graphics | 3807 | Minecraft-esque voxel rendering "engine" with many more features +**[stb_dxt.h](stb_dxt.h)** | 1.10 | 3D graphics | 753 | Fabian "ryg" Giesen's real-time DXT compressor +**[stb_perlin.h](stb_perlin.h)** | 0.5 | 3D graphics | 428 | revised Perlin noise (3D input, 1D output) +**[stb_easy_font.h](stb_easy_font.h)** | 1.1 | 3D graphics | 305 | quick-and-dirty easy-to-deploy bitmap font for printing frame rate, etc +**[stb_tilemap_editor.h](stb_tilemap_editor.h)** | 0.41 | game dev | 4161 | embeddable tilemap editor +**[stb_herringbone_wa...](stb_herringbone_wang_tile.h)** | 0.7 | game dev | 1221 | herringbone Wang tile map generator +**[stb_c_lexer.h](stb_c_lexer.h)** | 0.11 | parsing | 966 | simplify writing parsers for C-like languages +**[stb_divide.h](stb_divide.h)** | 0.93 | math | 430 | more useful 32-bit modulus e.g. "euclidean divide" +**[stb_connected_comp...](stb_connected_components.h)** | 0.96 | misc | 1049 | incrementally compute reachability on grids +**[stb.h](stb.h)** | 2.37 | misc | 14454 | helper functions for C, mostly redundant in C++; basically author's personal stuff +**[stb_leakcheck.h](stb_leakcheck.h)** | 0.6 | misc | 194 | quick-and-dirty malloc/free leak-checking +**[stb_include.h](stb_include.h)** | 0.02 | misc | 295 | implement recursive #include support, particularly for GLSL + +Total libraries: 22 +Total lines of C code: 56774 + + +FAQ +--- + +#### What's the license? + +These libraries are in the public domain. You can do anything you +want with them. You have no legal obligation +to do anything else, although I appreciate attribution. + +They are also licensed under the MIT open source license, if you have lawyers +who are unhappy with public domain. Every source file includes an explicit +dual-license for you to choose from. + +#### Are there other single-file public-domain/open source libraries with minimal dependencies out there? + +[Yes.](https://github.com/nothings/single_file_libs) + +#### If I wrap an stb library in a new library, does the new library have to be public domain/MIT? + +No, because it's public domain you can freely relicense it to whatever license your new +library wants to be. + +#### What's the deal with SSE support in GCC-based compilers? + +stb_image will either use SSE2 (if you compile with -msse2) or +will not use any SIMD at all, rather than trying to detect the +processor at runtime and handle it correctly. As I understand it, +the approved path in GCC for runtime-detection require +you to use multiple source files, one for each CPU configuration. +Because stb_image is a header-file library that compiles in only +one source file, there's no approved way to build both an +SSE-enabled and a non-SSE-enabled variation. + +While we've tried to work around it, we've had multiple issues over +the years due to specific versions of gcc breaking what we're doing, +so we've given up on it. See https://github.com/nothings/stb/issues/280 +and https://github.com/nothings/stb/issues/410 for examples. + +#### Some of these libraries seem redundant to existing open source libraries. Are they better somehow? + +Generally they're only better in that they're easier to integrate, +easier to use, and easier to release (single file; good API; no +attribution requirement). They may be less featureful, slower, +and/or use more memory. If you're already using an equivalent +library, there's probably no good reason to switch. + +#### Can I link directly to the table of stb libraries? + +You can use [this URL](https://github.com/nothings/stb#stb_libs) to link directly to that list. + +#### Why do you list "lines of code"? It's a terrible metric. + +Just to give you some idea of the internal complexity of the library, +to help you manage your expectations, or to let you know what you're +getting into. While not all the libraries are written in the same +style, they're certainly similar styles, and so comparisons between +the libraries are probably still meaningful. + +Note though that the lines do include both the implementation, the +part that corresponds to a header file, and the documentation. + +#### Why single-file headers? + +Windows doesn't have standard directories where libraries +live. That makes deploying libraries in Windows a lot more +painful than open source developers on Unix-derivates generally +realize. (It also makes library dependencies a lot worse in Windows.) + +There's also a common problem in Windows where a library was built +against a different version of the runtime library, which causes +link conflicts and confusion. Shipping the libs as headers means +you normally just compile them straight into your project without +making libraries, thus sidestepping that problem. + +Making them a single file makes it very easy to just +drop them into a project that needs them. (Of course you can +still put them in a proper shared library tree if you want.) + +Why not two files, one a header and one an implementation? +The difference between 10 files and 9 files is not a big deal, +but the difference between 2 files and 1 file is a big deal. +You don't need to zip or tar the files up, you don't have to +remember to attach *two* files, etc. + +#### Why "stb"? Is this something to do with Set-Top Boxes? + +No, they are just the initials for my name, Sean T. Barrett. +This was not chosen out of egomania, but as a moderately sane +way of namespacing the filenames and source function names. + +#### Will you add more image types to stb_image.h? + +No. As stb_image use has grown, it has become more important +for us to focus on security of the codebase. Adding new image +formats increases the amount of code we need to secure, so it +is no longer worth adding new formats. + +#### Do you have any advice on how to create my own single-file library? + +Yes. https://github.com/nothings/stb/blob/master/docs/stb_howto.txt + +#### Why public domain? + +I prefer it over GPL, LGPL, BSD, zlib, etc. for many reasons. +Some of them are listed here: +https://github.com/nothings/stb/blob/master/docs/why_public_domain.md + +#### Why C? + +Primarily, because I use C, not C++. But it does also make it easier +for other people to use them from other languages. + +#### Why not C99? stdint.h, declare-anywhere, etc. + +I still use MSVC 6 (1998) as my IDE because it has better human factors +for me than later versions of MSVC. diff --git a/Source/Dependencies/ImGuiFileDialog-0.6.3/stb/stb_image.h b/Source/Dependencies/ImGuiFileDialog-0.6.3/stb/stb_image.h new file mode 100644 index 000000000..accef4839 --- /dev/null +++ b/Source/Dependencies/ImGuiFileDialog-0.6.3/stb/stb_image.h @@ -0,0 +1,7762 @@ +/* stb_image - v2.26 - public domain image loader - http://nothings.org/stb + no warranty implied; use at your own risk + + Do this: + #define STB_IMAGE_IMPLEMENTATION + before you include this file in *one* C or C++ file to create the implementation. + + // i.e. it should look like this: + #include ... + #include ... + #include ... + #define STB_IMAGE_IMPLEMENTATION + #include "stb_image.h" + + You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. + And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free + + + QUICK NOTES: + Primarily of interest to game developers and other people who can + avoid problematic images and only need the trivial interface + + JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) + PNG 1/2/4/8/16-bit-per-channel + + TGA (not sure what subset, if a subset) + BMP non-1bpp, non-RLE + PSD (composited view only, no extra channels, 8/16 bit-per-channel) + + GIF (*comp always reports as 4-channel) + HDR (radiance rgbE format) + PIC (Softimage PIC) + PNM (PPM and PGM binary only) + + Animated GIF still needs a proper API, but here's one way to do it: + http://gist.github.com/urraka/685d9a6340b26b830d49 + + - decode from memory or through FILE (define STBI_NO_STDIO to remove code) + - decode from arbitrary I/O callbacks + - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON) + + Full documentation under "DOCUMENTATION" below. + + +LICENSE + + See end of file for license information. + +RECENT REVISION HISTORY: + + 2.26 (2020-07-13) many minor fixes + 2.25 (2020-02-02) fix warnings + 2.24 (2020-02-02) fix warnings; thread-local failure_reason and flip_vertically + 2.23 (2019-08-11) fix clang static analysis warning + 2.22 (2019-03-04) gif fixes, fix warnings + 2.21 (2019-02-25) fix typo in comment + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings + 2.16 (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes + 2.15 (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-12-04) experimental 16-bit API, only for PNG so far; fixes + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64 + RGB-format JPEG; remove white matting in PSD; + allocate large structures on the stack; + correct channel count for PNG & BMP + 2.10 (2016-01-22) avoid warning introduced in 2.09 + 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED + + See end of file for full revision history. + + + ============================ Contributors ========================= + + Image formats Extensions, features + Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info) + Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info) + Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG) + Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks) + Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG) + Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) + Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) + github:urraka (animated gif) Junggon Kim (PNM comments) + Christopher Forseth (animated gif) Daniel Gibson (16-bit TGA) + socks-the-fox (16-bit PNG) + Jeremy Sawicki (handle all ImageNet JPGs) + Optimizations & bugfixes Mikhail Morozov (1-bit BMP) + Fabian "ryg" Giesen Anael Seghezzi (is-16-bit query) + Arseny Kapoulkine + John-Mark Allen + Carmelo J Fdez-Aguera + + Bug & warning fixes + Marc LeBlanc David Woo Guillaume George Martins Mozeiko + Christpher Lloyd Jerry Jansson Joseph Thomson Blazej Dariusz Roszkowski + Phil Jordan Dave Moore Roy Eltham + Hayaki Saito Nathan Reed Won Chun + Luke Graham Johan Duparc Nick Verigakis the Horde3D community + Thomas Ruf Ronny Chevalier github:rlyeh + Janez Zemva John Bartholomew Michal Cichon github:romigrou + Jonathan Blow Ken Hamada Tero Hanninen github:svdijk + Laurent Gomila Cort Stratton github:snagar + Aruelien Pocheville Sergio Gonzalez Thibault Reuille github:Zelex + Cass Everitt Ryamond Barbiero github:grim210 + Paul Du Bois Engin Manap Aldo Culquicondor github:sammyhw + Philipp Wiesemann Dale Weiler Oriol Ferrer Mesia github:phprus + Josh Tobin Matthew Gregan github:poppolopoppo + Julian Raschke Gregory Mullen Christian Floisand github:darealshinji + Baldur Karlsson Kevin Schmidt JR Smith github:Michaelangel007 + Brad Weinberger Matvey Cherevko [reserved] + Luca Sas Alexander Veselov Zack Middleton [reserved] + Ryan C. Gordon [reserved] [reserved] + DO NOT ADD YOUR NAME HERE + + To add your name to the credits, pick a random blank space in the middle and fill it. + 80% of merge conflicts on stb PRs are due to people adding their name at the end + of the credits. +*/ + +#ifndef STBI_INCLUDE_STB_IMAGE_H +#define STBI_INCLUDE_STB_IMAGE_H + +// DOCUMENTATION +// +// Limitations: +// - no 12-bit-per-channel JPEG +// - no JPEGs with arithmetic coding +// - GIF always returns *comp=4 +// +// Basic usage (see HDR discussion below for HDR usage): +// int x,y,n; +// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); +// // ... process data if not NULL ... +// // ... x = width, y = height, n = # 8-bit components per pixel ... +// // ... replace '0' with '1'..'4' to force that many components per pixel +// // ... but 'n' will always be the number that it would have been if you said 0 +// stbi_image_free(data) +// +// Standard parameters: +// int *x -- outputs image width in pixels +// int *y -- outputs image height in pixels +// int *channels_in_file -- outputs # of image components in image file +// int desired_channels -- if non-zero, # of image components requested in result +// +// The return value from an image loader is an 'unsigned char *' which points +// to the pixel data, or NULL on an allocation failure or if the image is +// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, +// with each pixel consisting of N interleaved 8-bit components; the first +// pixel pointed to is top-left-most in the image. There is no padding between +// image scanlines or between pixels, regardless of format. The number of +// components N is 'desired_channels' if desired_channels is non-zero, or +// *channels_in_file otherwise. If desired_channels is non-zero, +// *channels_in_file has the number of components that _would_ have been +// output otherwise. E.g. if you set desired_channels to 4, you will always +// get RGBA output, but you can check *channels_in_file to see if it's trivially +// opaque because e.g. there were only 3 channels in the source image. +// +// An output image with N components has the following components interleaved +// in this order in each pixel: +// +// N=#comp components +// 1 grey +// 2 grey, alpha +// 3 red, green, blue +// 4 red, green, blue, alpha +// +// If image loading fails for any reason, the return value will be NULL, +// and *x, *y, *channels_in_file will be unchanged. The function +// stbi_failure_reason() can be queried for an extremely brief, end-user +// unfriendly explanation of why the load failed. Define STBI_NO_FAILURE_STRINGS +// to avoid compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly +// more user-friendly ones. +// +// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. +// +// =========================================================================== +// +// UNICODE: +// +// If compiling for Windows and you wish to use Unicode filenames, compile +// with +// #define STBI_WINDOWS_UTF8 +// and pass utf8-encoded filenames. Call stbi_convert_wchar_to_utf8 to convert +// Windows wchar_t filenames to utf8. +// +// =========================================================================== +// +// Philosophy +// +// stb libraries are designed with the following priorities: +// +// 1. easy to use +// 2. easy to maintain +// 3. good performance +// +// Sometimes I let "good performance" creep up in priority over "easy to maintain", +// and for best performance I may provide less-easy-to-use APIs that give higher +// performance, in addition to the easy-to-use ones. Nevertheless, it's important +// to keep in mind that from the standpoint of you, a client of this library, +// all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all. +// +// Some secondary priorities arise directly from the first two, some of which +// provide more explicit reasons why performance can't be emphasized. +// +// - Portable ("ease of use") +// - Small source code footprint ("easy to maintain") +// - No dependencies ("ease of use") +// +// =========================================================================== +// +// I/O callbacks +// +// I/O callbacks allow you to read from arbitrary sources, like packaged +// files or some other source. Data read from callbacks are processed +// through a small internal buffer (currently 128 bytes) to try to reduce +// overhead. +// +// The three functions you must define are "read" (reads some bytes of data), +// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). +// +// =========================================================================== +// +// SIMD support +// +// The JPEG decoder will try to automatically use SIMD kernels on x86 when +// supported by the compiler. For ARM Neon support, you must explicitly +// request it. +// +// (The old do-it-yourself SIMD API is no longer supported in the current +// code.) +// +// On x86, SSE2 will automatically be used when available based on a run-time +// test; if not, the generic C versions are used as a fall-back. On ARM targets, +// the typical path is to have separate builds for NEON and non-NEON devices +// (at least this is true for iOS and Android). Therefore, the NEON support is +// toggled by a build flag: define STBI_NEON to get NEON loops. +// +// If for some reason you do not want to use any of SIMD code, or if +// you have issues compiling it, you can disable it entirely by +// defining STBI_NO_SIMD. +// +// =========================================================================== +// +// HDR image support (disable by defining STBI_NO_HDR) +// +// stb_image supports loading HDR images in general, and currently the Radiance +// .HDR file format specifically. You can still load any file through the existing +// interface; if you attempt to load an HDR file, it will be automatically remapped +// to LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; +// both of these constants can be reconfigured through this interface: +// +// stbi_hdr_to_ldr_gamma(2.2f); +// stbi_hdr_to_ldr_scale(1.0f); +// +// (note, do not use _inverse_ constants; stbi_image will invert them +// appropriately). +// +// Additionally, there is a new, parallel interface for loading files as +// (linear) floats to preserve the full dynamic range: +// +// float *data = stbi_loadf(filename, &x, &y, &n, 0); +// +// If you load LDR images through this interface, those images will +// be promoted to floating point values, run through the inverse of +// constants corresponding to the above: +// +// stbi_ldr_to_hdr_scale(1.0f); +// stbi_ldr_to_hdr_gamma(2.2f); +// +// Finally, given a filename (or an open file or memory block--see header +// file for details) containing image data, you can query for the "most +// appropriate" interface to use (that is, whether the image is HDR or +// not), using: +// +// stbi_is_hdr(char *filename); +// +// =========================================================================== +// +// iPhone PNG support: +// +// By default we convert iphone-formatted PNGs back to RGB, even though +// they are internally encoded differently. You can disable this conversion +// by calling stbi_convert_iphone_png_to_rgb(0), in which case +// you will always just get the native iphone "format" through (which +// is BGR stored in RGB). +// +// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per +// pixel to remove any premultiplied alpha *only* if the image file explicitly +// says there's premultiplied data (currently only happens in iPhone images, +// and only if iPhone convert-to-rgb processing is on). +// +// =========================================================================== +// +// ADDITIONAL CONFIGURATION +// +// - You can suppress implementation of any of the decoders to reduce +// your code footprint by #defining one or more of the following +// symbols before creating the implementation. +// +// STBI_NO_JPEG +// STBI_NO_PNG +// STBI_NO_BMP +// STBI_NO_PSD +// STBI_NO_TGA +// STBI_NO_GIF +// STBI_NO_HDR +// STBI_NO_PIC +// STBI_NO_PNM (.ppm and .pgm) +// +// - You can request *only* certain decoders and suppress all other ones +// (this will be more forward-compatible, as addition of new decoders +// doesn't require you to disable them explicitly): +// +// STBI_ONLY_JPEG +// STBI_ONLY_PNG +// STBI_ONLY_BMP +// STBI_ONLY_PSD +// STBI_ONLY_TGA +// STBI_ONLY_GIF +// STBI_ONLY_HDR +// STBI_ONLY_PIC +// STBI_ONLY_PNM (.ppm and .pgm) +// +// - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still +// want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB +// +// - If you define STBI_MAX_DIMENSIONS, stb_image will reject images greater +// than that size (in either width or height) without further processing. +// This is to let programs in the wild set an upper bound to prevent +// denial-of-service attacks on untrusted data, as one could generate a +// valid image of gigantic dimensions and force stb_image to allocate a +// huge block of memory and spend disproportionate time decoding it. By +// default this is set to (1 << 24), which is 16777216, but that's still +// very big. + +#ifndef STBI_NO_STDIO +#include +#endif // STBI_NO_STDIO + +#define STBI_VERSION 1 + +enum +{ + STBI_default = 0, // only used for desired_channels + + STBI_grey = 1, + STBI_grey_alpha = 2, + STBI_rgb = 3, + STBI_rgb_alpha = 4 +}; + +#include +typedef unsigned char stbi_uc; +typedef unsigned short stbi_us; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef STBIDEF +#ifdef STB_IMAGE_STATIC +#define STBIDEF static +#else +#define STBIDEF extern +#endif +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// PRIMARY API - works on images of any type +// + +// +// load image by filename, open file, or memory buffer +// + +typedef struct +{ + int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read + void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative + int (*eof) (void *user); // returns nonzero if we are at end of file/data +} stbi_io_callbacks; + +//////////////////////////////////// +// +// 8-bits-per-channel interface +// + +STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +// for stbi_load_from_file, file pointer is left pointing immediately after image +#endif + +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +#endif + +#ifdef STBI_WINDOWS_UTF8 +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); +#endif + +//////////////////////////////////// +// +// 16-bits-per-channel interface +// + +STBIDEF stbi_us *stbi_load_16_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_us *stbi_load_16 (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +#endif + +//////////////////////////////////// +// +// float-per-channel interface +// +#ifndef STBI_NO_LINEAR + STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + + #ifndef STBI_NO_STDIO + STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); + #endif +#endif + +#ifndef STBI_NO_HDR + STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); + STBIDEF void stbi_hdr_to_ldr_scale(float scale); +#endif // STBI_NO_HDR + +#ifndef STBI_NO_LINEAR + STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); + STBIDEF void stbi_ldr_to_hdr_scale(float scale); +#endif // STBI_NO_LINEAR + +// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename); +STBIDEF int stbi_is_hdr_from_file(FILE *f); +#endif // STBI_NO_STDIO + + +// get a VERY brief reason for failure +// on most compilers (and ALL modern mainstream compilers) this is threadsafe +STBIDEF const char *stbi_failure_reason (void); + +// free the loaded image -- this is just free() +STBIDEF void stbi_image_free (void *retval_from_stbi_load); + +// get image dimensions & components without fully decoding +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len); +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *clbk, void *user); + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit (char const *filename); +STBIDEF int stbi_is_16_bit_from_file(FILE *f); +#endif + + + +// for image formats that explicitly notate that they have premultiplied alpha, +// we just return the colors as stored in the file. set this flag to force +// unpremultiplication. results are undefined if the unpremultiply overflow. +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); + +// indicate whether we should process iphone images back to canonical format, +// or just pass them through "as-is" +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); + +// flip the image vertically, so the first pixel in the output array is the bottom left +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); + +// as above, but only applies to images loaded on the thread that calls the function +// this function is only available if your compiler supports thread-local variables; +// calling it will fail to link if your compiler doesn't +STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip); + +// ZLIB client - used by PNG, available for other purposes + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); +STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + +STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + + +#ifdef __cplusplus +} +#endif + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBI_INCLUDE_STB_IMAGE_H + +#ifdef STB_IMAGE_IMPLEMENTATION + +#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ + || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ + || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ + || defined(STBI_ONLY_ZLIB) + #ifndef STBI_ONLY_JPEG + #define STBI_NO_JPEG + #endif + #ifndef STBI_ONLY_PNG + #define STBI_NO_PNG + #endif + #ifndef STBI_ONLY_BMP + #define STBI_NO_BMP + #endif + #ifndef STBI_ONLY_PSD + #define STBI_NO_PSD + #endif + #ifndef STBI_ONLY_TGA + #define STBI_NO_TGA + #endif + #ifndef STBI_ONLY_GIF + #define STBI_NO_GIF + #endif + #ifndef STBI_ONLY_HDR + #define STBI_NO_HDR + #endif + #ifndef STBI_ONLY_PIC + #define STBI_NO_PIC + #endif + #ifndef STBI_ONLY_PNM + #define STBI_NO_PNM + #endif +#endif + +#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) +#define STBI_NO_ZLIB +#endif + + +#include +#include // ptrdiff_t on osx +#include +#include +#include + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +#include // ldexp, pow +#endif + +#ifndef STBI_NO_STDIO +#include +#endif + +#ifndef STBI_ASSERT +#include +#define STBI_ASSERT(x) assert(x) +#endif + +#ifdef __cplusplus +#define STBI_EXTERN extern "C" +#else +#define STBI_EXTERN extern +#endif + + +#ifndef _MSC_VER + #ifdef __cplusplus + #define stbi_inline inline + #else + #define stbi_inline + #endif +#else + #define stbi_inline __forceinline +#endif + +#ifndef STBI_NO_THREAD_LOCALS + #if defined(__cplusplus) && __cplusplus >= 201103L + #define STBI_THREAD_LOCAL thread_local + #elif defined(__GNUC__) && __GNUC__ < 5 + #define STBI_THREAD_LOCAL __thread + #elif defined(_MSC_VER) + #define STBI_THREAD_LOCAL __declspec(thread) + #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__) + #define STBI_THREAD_LOCAL _Thread_local + #endif + + #ifndef STBI_THREAD_LOCAL + #if defined(__GNUC__) + #define STBI_THREAD_LOCAL __thread + #endif + #endif +#endif + +#ifdef _MSC_VER +typedef unsigned short stbi__uint16; +typedef signed short stbi__int16; +typedef unsigned int stbi__uint32; +typedef signed int stbi__int32; +#else +#include +typedef uint16_t stbi__uint16; +typedef int16_t stbi__int16; +typedef uint32_t stbi__uint32; +typedef int32_t stbi__int32; +#endif + +// should produce compiler error if size is wrong +typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; + +#ifdef _MSC_VER +#define STBI_NOTUSED(v) (void)(v) +#else +#define STBI_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef _MSC_VER +#define STBI_HAS_LROTL +#endif + +#ifdef STBI_HAS_LROTL + #define stbi_lrot(x,y) _lrotl(x,y) +#else + #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (32 - (y)))) +#endif + +#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) +// ok +#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." +#endif + +#ifndef STBI_MALLOC +#define STBI_MALLOC(sz) malloc(sz) +#define STBI_REALLOC(p,newsz) realloc(p,newsz) +#define STBI_FREE(p) free(p) +#endif + +#ifndef STBI_REALLOC_SIZED +#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) +#endif + +// x86/x64 detection +#if defined(__x86_64__) || defined(_M_X64) +#define STBI__X64_TARGET +#elif defined(__i386) || defined(_M_IX86) +#define STBI__X86_TARGET +#endif + +#if defined(__GNUC__) && defined(STBI__X86_TARGET) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) +// gcc doesn't support sse2 intrinsics unless you compile with -msse2, +// which in turn means it gets to use SSE2 everywhere. This is unfortunate, +// but previous attempts to provide the SSE2 functions with runtime +// detection caused numerous issues. The way architecture extensions are +// exposed in GCC/Clang is, sadly, not really suited for one-file libs. +// New behavior: if compiled with -msse2, we use SSE2 without any +// detection; if not, we don't use it at all. +#define STBI_NO_SIMD +#endif + +#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) +// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET +// +// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the +// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. +// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not +// simultaneously enabling "-mstackrealign". +// +// See https://github.com/nothings/stb/issues/81 for more information. +// +// So default to no SSE2 on 32-bit MinGW. If you've read this far and added +// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. +#define STBI_NO_SIMD +#endif + +#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) +#define STBI_SSE2 +#include + +#ifdef _MSC_VER + +#if _MSC_VER >= 1400 // not VC6 +#include // __cpuid +static int stbi__cpuid3(void) +{ + int info[4]; + __cpuid(info,1); + return info[3]; +} +#else +static int stbi__cpuid3(void) +{ + int res; + __asm { + mov eax,1 + cpuid + mov res,edx + } + return res; +} +#endif + +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name + +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) +static int stbi__sse2_available(void) +{ + int info3 = stbi__cpuid3(); + return ((info3 >> 26) & 1) != 0; +} +#endif + +#else // assume GCC-style if not VC++ +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) + +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) +static int stbi__sse2_available(void) +{ + // If we're even attempting to compile this on GCC/Clang, that means + // -msse2 is on, which means the compiler is allowed to use SSE2 + // instructions at will, and so are we. + return 1; +} +#endif + +#endif +#endif + +// ARM NEON +#if defined(STBI_NO_SIMD) && defined(STBI_NEON) +#undef STBI_NEON +#endif + +#ifdef STBI_NEON +#include +// assume GCC or Clang on ARM targets +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) +#endif + +#ifndef STBI_SIMD_ALIGN +#define STBI_SIMD_ALIGN(type, name) type name +#endif + +#ifndef STBI_MAX_DIMENSIONS +#define STBI_MAX_DIMENSIONS (1 << 24) +#endif + +/////////////////////////////////////////////// +// +// stbi__context struct and start_xxx functions + +// stbi__context structure is our basic context used by all images, so it +// contains all the IO context, plus some basic image information +typedef struct +{ + stbi__uint32 img_x, img_y; + int img_n, img_out_n; + + stbi_io_callbacks io; + void *io_user_data; + + int read_from_callbacks; + int buflen; + stbi_uc buffer_start[128]; + int callback_already_read; + + stbi_uc *img_buffer, *img_buffer_end; + stbi_uc *img_buffer_original, *img_buffer_original_end; +} stbi__context; + + +static void stbi__refill_buffer(stbi__context *s); + +// initialize a memory-decode context +static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) +{ + s->io.read = NULL; + s->read_from_callbacks = 0; + s->callback_already_read = 0; + s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; + s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; +} + +// initialize a callback-based context +static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) +{ + s->io = *c; + s->io_user_data = user; + s->buflen = sizeof(s->buffer_start); + s->read_from_callbacks = 1; + s->callback_already_read = 0; + s->img_buffer = s->img_buffer_original = s->buffer_start; + stbi__refill_buffer(s); + s->img_buffer_original_end = s->img_buffer_end; +} + +#ifndef STBI_NO_STDIO + +static int stbi__stdio_read(void *user, char *data, int size) +{ + return (int) fread(data,1,size,(FILE*) user); +} + +static void stbi__stdio_skip(void *user, int n) +{ + int ch; + fseek((FILE*) user, n, SEEK_CUR); + ch = fgetc((FILE*) user); /* have to read a byte to reset feof()'s flag */ + if (ch != EOF) { + ungetc(ch, (FILE *) user); /* push byte back onto stream if valid. */ + } +} + +static int stbi__stdio_eof(void *user) +{ + return feof((FILE*) user) || ferror((FILE *) user); +} + +static stbi_io_callbacks stbi__stdio_callbacks = +{ + stbi__stdio_read, + stbi__stdio_skip, + stbi__stdio_eof, +}; + +static void stbi__start_file(stbi__context *s, FILE *f) +{ + stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); +} + +//static void stop_file(stbi__context *s) { } + +#endif // !STBI_NO_STDIO + +static void stbi__rewind(stbi__context *s) +{ + // conceptually rewind SHOULD rewind to the beginning of the stream, + // but we just rewind to the beginning of the initial buffer, because + // we only use it after doing 'test', which only ever looks at at most 92 bytes + s->img_buffer = s->img_buffer_original; + s->img_buffer_end = s->img_buffer_original_end; +} + +enum +{ + STBI_ORDER_RGB, + STBI_ORDER_BGR +}; + +typedef struct +{ + int bits_per_channel; + int num_channels; + int channel_order; +} stbi__result_info; + +#ifndef STBI_NO_JPEG +static int stbi__jpeg_test(stbi__context *s); +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNG +static int stbi__png_test(stbi__context *s); +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__png_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_BMP +static int stbi__bmp_test(stbi__context *s); +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_TGA +static int stbi__tga_test(stbi__context *s); +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s); +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc); +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__psd_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_HDR +static int stbi__hdr_test(stbi__context *s); +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_test(stbi__context *s); +static void *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_GIF +static int stbi__gif_test(stbi__context *s); +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNM +static int stbi__pnm_test(stbi__context *s); +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +static +#ifdef STBI_THREAD_LOCAL +STBI_THREAD_LOCAL +#endif +const char *stbi__g_failure_reason; + +STBIDEF const char *stbi_failure_reason(void) +{ + return stbi__g_failure_reason; +} + +#ifndef STBI_NO_FAILURE_STRINGS +static int stbi__err(const char *str) +{ + stbi__g_failure_reason = str; + return 0; +} +#endif + +static void *stbi__malloc(size_t size) +{ + return STBI_MALLOC(size); +} + +// stb_image uses ints pervasively, including for offset calculations. +// therefore the largest decoded image size we can support with the +// current code, even on 64-bit targets, is INT_MAX. this is not a +// significant limitation for the intended use case. +// +// we do, however, need to make sure our size calculations don't +// overflow. hence a few helper functions for size calculations that +// multiply integers together, making sure that they're non-negative +// and no overflow occurs. + +// return 1 if the sum is valid, 0 on overflow. +// negative terms are considered invalid. +static int stbi__addsizes_valid(int a, int b) +{ + if (b < 0) return 0; + // now 0 <= b <= INT_MAX, hence also + // 0 <= INT_MAX - b <= INTMAX. + // And "a + b <= INT_MAX" (which might overflow) is the + // same as a <= INT_MAX - b (no overflow) + return a <= INT_MAX - b; +} + +// returns 1 if the product is valid, 0 on overflow. +// negative factors are considered invalid. +static int stbi__mul2sizes_valid(int a, int b) +{ + if (a < 0 || b < 0) return 0; + if (b == 0) return 1; // mul-by-0 is always safe + // portable way to check for no overflows in a*b + return a <= INT_MAX/b; +} + +#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) +// returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow +static int stbi__mad2sizes_valid(int a, int b, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add); +} +#endif + +// returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow +static int stbi__mad3sizes_valid(int a, int b, int c, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__addsizes_valid(a*b*c, add); +} + +// returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add); +} +#endif + +#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) +// mallocs with size overflow checking +static void *stbi__malloc_mad2(int a, int b, int add) +{ + if (!stbi__mad2sizes_valid(a, b, add)) return NULL; + return stbi__malloc(a*b + add); +} +#endif + +static void *stbi__malloc_mad3(int a, int b, int c, int add) +{ + if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL; + return stbi__malloc(a*b*c + add); +} + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) +{ + if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL; + return stbi__malloc(a*b*c*d + add); +} +#endif + +// stbi__err - error +// stbi__errpf - error returning pointer to float +// stbi__errpuc - error returning pointer to unsigned char + +#ifdef STBI_NO_FAILURE_STRINGS + #define stbi__err(x,y) 0 +#elif defined(STBI_FAILURE_USERMSG) + #define stbi__err(x,y) stbi__err(y) +#else + #define stbi__err(x,y) stbi__err(x) +#endif + +#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) +#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) + +STBIDEF void stbi_image_free(void *retval_from_stbi_load) +{ + STBI_FREE(retval_from_stbi_load); +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); +#endif + +#ifndef STBI_NO_HDR +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); +#endif + +static int stbi__vertically_flip_on_load_global = 0; + +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load_global = flag_true_if_should_flip; +} + +#ifndef STBI_THREAD_LOCAL +#define stbi__vertically_flip_on_load stbi__vertically_flip_on_load_global +#else +static STBI_THREAD_LOCAL int stbi__vertically_flip_on_load_local, stbi__vertically_flip_on_load_set; + +STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load_local = flag_true_if_should_flip; + stbi__vertically_flip_on_load_set = 1; +} + +#define stbi__vertically_flip_on_load (stbi__vertically_flip_on_load_set \ + ? stbi__vertically_flip_on_load_local \ + : stbi__vertically_flip_on_load_global) +#endif // STBI_THREAD_LOCAL + +static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields + ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed + ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order + ri->num_channels = 0; + + #ifndef STBI_NO_JPEG + if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PNG + if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_BMP + if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_GIF + if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PSD + if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc); + #else + STBI_NOTUSED(bpc); + #endif + #ifndef STBI_NO_PIC + if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PNM + if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp, ri); + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri); + return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); + } + #endif + + #ifndef STBI_NO_TGA + // test tga last because it's a crappy test! + if (stbi__tga_test(s)) + return stbi__tga_load(s,x,y,comp,req_comp, ri); + #endif + + return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); +} + +static stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi_uc *reduced; + + reduced = (stbi_uc *) stbi__malloc(img_len); + if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling + + STBI_FREE(orig); + return reduced; +} + +static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi__uint16 *enlarged; + + enlarged = (stbi__uint16 *) stbi__malloc(img_len*2); + if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff + + STBI_FREE(orig); + return enlarged; +} + +static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel) +{ + int row; + size_t bytes_per_row = (size_t)w * bytes_per_pixel; + stbi_uc temp[2048]; + stbi_uc *bytes = (stbi_uc *)image; + + for (row = 0; row < (h>>1); row++) { + stbi_uc *row0 = bytes + row*bytes_per_row; + stbi_uc *row1 = bytes + (h - row - 1)*bytes_per_row; + // swap row0 with row1 + size_t bytes_left = bytes_per_row; + while (bytes_left) { + size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp); + memcpy(temp, row0, bytes_copy); + memcpy(row0, row1, bytes_copy); + memcpy(row1, temp, bytes_copy); + row0 += bytes_copy; + row1 += bytes_copy; + bytes_left -= bytes_copy; + } + } +} + +#ifndef STBI_NO_GIF +static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int bytes_per_pixel) +{ + int slice; + int slice_size = w * h * bytes_per_pixel; + + stbi_uc *bytes = (stbi_uc *)image; + for (slice = 0; slice < z; ++slice) { + stbi__vertical_flip(bytes, w, h, bytes_per_pixel); + bytes += slice_size; + } +} +#endif + +static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8); + + if (result == NULL) + return NULL; + + // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. + STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); + + if (ri.bits_per_channel != 8) { + result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 8; + } + + // @TODO: move stbi__convert_format to here + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc)); + } + + return (unsigned char *) result; +} + +static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16); + + if (result == NULL) + return NULL; + + // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. + STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); + + if (ri.bits_per_channel != 16) { + result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 16; + } + + // @TODO: move stbi__convert_format16 to here + // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi__uint16)); + } + + return (stbi__uint16 *) result; +} + +#if !defined(STBI_NO_HDR) && !defined(STBI_NO_LINEAR) +static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) +{ + if (stbi__vertically_flip_on_load && result != NULL) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(float)); + } +} +#endif + +#ifndef STBI_NO_STDIO + +#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) +STBI_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); +STBI_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); +#endif + +#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) +{ + return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); +} +#endif + +static FILE *stbi__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode))) + return 0; + +#if _MSC_VER >= 1400 + if (0 != _wfopen_s(&f, wFilename, wMode)) + f = 0; +#else + f = _wfopen(wFilename, wMode); +#endif + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + + +STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + unsigned char *result; + if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__uint16 *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + stbi__uint16 *result; + if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file_16(f,x,y,comp,req_comp); + fclose(f); + return result; +} + + +#endif //!STBI_NO_STDIO + +STBIDEF stbi_us *stbi_load_16_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_mem(&s,buffer,len); + + result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp); + if (stbi__vertically_flip_on_load) { + stbi__vertical_flip_slices( result, *x, *y, *z, *comp ); + } + + return result; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + stbi__result_info ri; + float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri); + if (hdr_data) + stbi__float_postprocess(hdr_data,x,y,comp,req_comp); + return hdr_data; + } + #endif + data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp); + if (data) + return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); + return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); +} + +STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_STDIO +STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + float *result; + FILE *f = stbi__fopen(filename, "rb"); + if (!f) return stbi__errpf("can't fopen", "Unable to open file"); + result = stbi_loadf_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_file(&s,f); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} +#endif // !STBI_NO_STDIO + +#endif // !STBI_NO_LINEAR + +// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is +// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always +// reports false! + +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(buffer); + STBI_NOTUSED(len); + return 0; + #endif +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result=0; + if (f) { + result = stbi_is_hdr_from_file(f); + fclose(f); + } + return result; +} + +STBIDEF int stbi_is_hdr_from_file(FILE *f) +{ + #ifndef STBI_NO_HDR + long pos = ftell(f); + int res; + stbi__context s; + stbi__start_file(&s,f); + res = stbi__hdr_test(&s); + fseek(f, pos, SEEK_SET); + return res; + #else + STBI_NOTUSED(f); + return 0; + #endif +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(clbk); + STBI_NOTUSED(user); + return 0; + #endif +} + +#ifndef STBI_NO_LINEAR +static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; + +STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } +STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } +#endif + +static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; + +STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } +STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } + + +////////////////////////////////////////////////////////////////////////////// +// +// Common code used by all image loaders +// + +enum +{ + STBI__SCAN_load=0, + STBI__SCAN_type, + STBI__SCAN_header +}; + +static void stbi__refill_buffer(stbi__context *s) +{ + int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); + s->callback_already_read += (int) (s->img_buffer - s->img_buffer_original); + if (n == 0) { + // at end of file, treat same as if from memory, but need to handle case + // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file + s->read_from_callbacks = 0; + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start+1; + *s->img_buffer = 0; + } else { + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start + n; + } +} + +stbi_inline static stbi_uc stbi__get8(stbi__context *s) +{ + if (s->img_buffer < s->img_buffer_end) + return *s->img_buffer++; + if (s->read_from_callbacks) { + stbi__refill_buffer(s); + return *s->img_buffer++; + } + return 0; +} + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_HDR) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +stbi_inline static int stbi__at_eof(stbi__context *s) +{ + if (s->io.read) { + if (!(s->io.eof)(s->io_user_data)) return 0; + // if feof() is true, check if buffer = end + // special case: we've only got the special 0 character at the end + if (s->read_from_callbacks == 0) return 1; + } + + return s->img_buffer >= s->img_buffer_end; +} +#endif + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) +// nothing +#else +static void stbi__skip(stbi__context *s, int n) +{ + if (n == 0) return; // already there! + if (n < 0) { + s->img_buffer = s->img_buffer_end; + return; + } + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + s->img_buffer = s->img_buffer_end; + (s->io.skip)(s->io_user_data, n - blen); + return; + } + } + s->img_buffer += n; +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_TGA) && defined(STBI_NO_HDR) && defined(STBI_NO_PNM) +// nothing +#else +static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) +{ + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + int res, count; + + memcpy(buffer, s->img_buffer, blen); + + count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); + res = (count == (n-blen)); + s->img_buffer = s->img_buffer_end; + return res; + } + } + + if (s->img_buffer+n <= s->img_buffer_end) { + memcpy(buffer, s->img_buffer, n); + s->img_buffer += n; + return 1; + } else + return 0; +} +#endif + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) +// nothing +#else +static int stbi__get16be(stbi__context *s) +{ + int z = stbi__get8(s); + return (z << 8) + stbi__get8(s); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) +// nothing +#else +static stbi__uint32 stbi__get32be(stbi__context *s) +{ + stbi__uint32 z = stbi__get16be(s); + return (z << 16) + stbi__get16be(s); +} +#endif + +#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) +// nothing +#else +static int stbi__get16le(stbi__context *s) +{ + int z = stbi__get8(s); + return z + (stbi__get8(s) << 8); +} +#endif + +#ifndef STBI_NO_BMP +static stbi__uint32 stbi__get32le(stbi__context *s) +{ + stbi__uint32 z = stbi__get16le(s); + return z + (stbi__get16le(s) << 16); +} +#endif + +#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +////////////////////////////////////////////////////////////////////////////// +// +// generic converter from built-in img_n to req_comp +// individual types do this automatically as much as possible (e.g. jpeg +// does all cases internally since it needs to colorspace convert anyway, +// and it never has alpha, so very few cases ). png can automatically +// interleave an alpha=255 channel, but falls back to this for other cases +// +// assume data buffer is malloced, so malloc a new one and free that one +// only failure mode is malloc failing + +static stbi_uc stbi__compute_y(int r, int g, int b) +{ + return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + unsigned char *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0); + if (good == NULL) { + STBI_FREE(data); + return stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + unsigned char *src = data + j * x * img_n ; + unsigned char *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=255; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=255; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=255; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = 255; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; + default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return stbi__errpuc("unsupported", "Unsupported format conversion"); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) +// nothing +#else +static stbi__uint16 stbi__compute_y_16(int r, int g, int b) +{ + return (stbi__uint16) (((r*77) + (g*150) + (29*b)) >> 8); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) +// nothing +#else +static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + stbi__uint16 *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2); + if (good == NULL) { + STBI_FREE(data); + return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + stbi__uint16 *src = data + j * x * img_n ; + stbi__uint16 *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=0xffff; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=0xffff; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=0xffff; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = 0xffff; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; + default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return (stbi__uint16*) stbi__errpuc("unsupported", "Unsupported format conversion"); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) +{ + int i,k,n; + float *output; + if (!data) return NULL; + output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); + } + } + if (n < comp) { + for (i=0; i < x*y; ++i) { + output[i*comp + n] = data[i*comp + n]/255.0f; + } + } + STBI_FREE(data); + return output; +} +#endif + +#ifndef STBI_NO_HDR +#define stbi__float2int(x) ((int) (x)) +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) +{ + int i,k,n; + stbi_uc *output; + if (!data) return NULL; + output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + if (k < comp) { + float z = data[i*comp+k] * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + } + STBI_FREE(data); + return output; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// "baseline" JPEG/JFIF decoder +// +// simple implementation +// - doesn't support delayed output of y-dimension +// - simple interface (only one output format: 8-bit interleaved RGB) +// - doesn't try to recover corrupt jpegs +// - doesn't allow partial loading, loading multiple at once +// - still fast on x86 (copying globals into locals doesn't help x86) +// - allocates lots of intermediate memory (full size of all components) +// - non-interleaved case requires this anyway +// - allows good upsampling (see next) +// high-quality +// - upsampled channels are bilinearly interpolated, even across blocks +// - quality integer IDCT derived from IJG's 'slow' +// performance +// - fast huffman; reasonable integer IDCT +// - some SIMD kernels for common paths on targets with SSE2/NEON +// - uses a lot of intermediate memory, could cache poorly + +#ifndef STBI_NO_JPEG + +// huffman decoding acceleration +#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache + +typedef struct +{ + stbi_uc fast[1 << FAST_BITS]; + // weirdly, repacking this into AoS is a 10% speed loss, instead of a win + stbi__uint16 code[256]; + stbi_uc values[256]; + stbi_uc size[257]; + unsigned int maxcode[18]; + int delta[17]; // old 'firstsymbol' - old 'firstcode' +} stbi__huffman; + +typedef struct +{ + stbi__context *s; + stbi__huffman huff_dc[4]; + stbi__huffman huff_ac[4]; + stbi__uint16 dequant[4][64]; + stbi__int16 fast_ac[4][1 << FAST_BITS]; + +// sizes for components, interleaved MCUs + int img_h_max, img_v_max; + int img_mcu_x, img_mcu_y; + int img_mcu_w, img_mcu_h; + +// definition of jpeg image component + struct + { + int id; + int h,v; + int tq; + int hd,ha; + int dc_pred; + + int x,y,w2,h2; + stbi_uc *data; + void *raw_data, *raw_coeff; + stbi_uc *linebuf; + short *coeff; // progressive only + int coeff_w, coeff_h; // number of 8x8 coefficient blocks + } img_comp[4]; + + stbi__uint32 code_buffer; // jpeg entropy-coded buffer + int code_bits; // number of valid bits + unsigned char marker; // marker seen while filling entropy buffer + int nomore; // flag if we saw a marker so must stop + + int progressive; + int spec_start; + int spec_end; + int succ_high; + int succ_low; + int eob_run; + int jfif; + int app14_color_transform; // Adobe APP14 tag + int rgb; + + int scan_n, order[4]; + int restart_interval, todo; + +// kernels + void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); + void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); + stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); +} stbi__jpeg; + +static int stbi__build_huffman(stbi__huffman *h, int *count) +{ + int i,j,k=0; + unsigned int code; + // build size list for each symbol (from JPEG spec) + for (i=0; i < 16; ++i) + for (j=0; j < count[i]; ++j) + h->size[k++] = (stbi_uc) (i+1); + h->size[k] = 0; + + // compute actual symbols (from jpeg spec) + code = 0; + k = 0; + for(j=1; j <= 16; ++j) { + // compute delta to add to code to compute symbol id + h->delta[j] = k - code; + if (h->size[k] == j) { + while (h->size[k] == j) + h->code[k++] = (stbi__uint16) (code++); + if (code-1 >= (1u << j)) return stbi__err("bad code lengths","Corrupt JPEG"); + } + // compute largest code + 1 for this size, preshifted as needed later + h->maxcode[j] = code << (16-j); + code <<= 1; + } + h->maxcode[j] = 0xffffffff; + + // build non-spec acceleration table; 255 is flag for not-accelerated + memset(h->fast, 255, 1 << FAST_BITS); + for (i=0; i < k; ++i) { + int s = h->size[i]; + if (s <= FAST_BITS) { + int c = h->code[i] << (FAST_BITS-s); + int m = 1 << (FAST_BITS-s); + for (j=0; j < m; ++j) { + h->fast[c+j] = (stbi_uc) i; + } + } + } + return 1; +} + +// build a table that decodes both magnitude and value of small ACs in +// one go. +static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) +{ + int i; + for (i=0; i < (1 << FAST_BITS); ++i) { + stbi_uc fast = h->fast[i]; + fast_ac[i] = 0; + if (fast < 255) { + int rs = h->values[fast]; + int run = (rs >> 4) & 15; + int magbits = rs & 15; + int len = h->size[fast]; + + if (magbits && len + magbits <= FAST_BITS) { + // magnitude code followed by receive_extend code + int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); + int m = 1 << (magbits - 1); + if (k < m) k += (~0U << magbits) + 1; + // if the result is small enough, we can fit it in fast_ac table + if (k >= -128 && k <= 127) + fast_ac[i] = (stbi__int16) ((k * 256) + (run * 16) + (len + magbits)); + } + } + } +} + +static void stbi__grow_buffer_unsafe(stbi__jpeg *j) +{ + do { + unsigned int b = j->nomore ? 0 : stbi__get8(j->s); + if (b == 0xff) { + int c = stbi__get8(j->s); + while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes + if (c != 0) { + j->marker = (unsigned char) c; + j->nomore = 1; + return; + } + } + j->code_buffer |= b << (24 - j->code_bits); + j->code_bits += 8; + } while (j->code_bits <= 24); +} + +// (1 << n) - 1 +static const stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; + +// decode a jpeg huffman value from the bitstream +stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) +{ + unsigned int temp; + int c,k; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + // look at the top FAST_BITS and determine what symbol ID it is, + // if the code is <= FAST_BITS + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + k = h->fast[c]; + if (k < 255) { + int s = h->size[k]; + if (s > j->code_bits) + return -1; + j->code_buffer <<= s; + j->code_bits -= s; + return h->values[k]; + } + + // naive test is to shift the code_buffer down so k bits are + // valid, then test against maxcode. To speed this up, we've + // preshifted maxcode left so that it has (16-k) 0s at the + // end; in other words, regardless of the number of bits, it + // wants to be compared against something shifted to have 16; + // that way we don't need to shift inside the loop. + temp = j->code_buffer >> 16; + for (k=FAST_BITS+1 ; ; ++k) + if (temp < h->maxcode[k]) + break; + if (k == 17) { + // error! code not found + j->code_bits -= 16; + return -1; + } + + if (k > j->code_bits) + return -1; + + // convert the huffman code to the symbol id + c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; + STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); + + // convert the id to a symbol + j->code_bits -= k; + j->code_buffer <<= k; + return h->values[c]; +} + +// bias[n] = (-1<code_bits < n) stbi__grow_buffer_unsafe(j); + + sgn = (stbi__int32)j->code_buffer >> 31; // sign bit is always in MSB + k = stbi_lrot(j->code_buffer, n); + if (n < 0 || n >= (int) (sizeof(stbi__bmask)/sizeof(*stbi__bmask))) return 0; + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k + (stbi__jbias[n] & ~sgn); +} + +// get some unsigned bits +stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) +{ + unsigned int k; + if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k; +} + +stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) +{ + unsigned int k; + if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); + k = j->code_buffer; + j->code_buffer <<= 1; + --j->code_bits; + return k & 0x80000000; +} + +// given a value that's at position X in the zigzag stream, +// where does it appear in the 8x8 matrix coded as row-major? +static const stbi_uc stbi__jpeg_dezigzag[64+15] = +{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + // let corrupt input sample past end + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63 +}; + +// decode one 64-entry block-- +static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi__uint16 *dequant) +{ + int diff,dc,k; + int t; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + + // 0 all the ac values now so we can do it 32-bits at a time + memset(data,0,64*sizeof(data[0])); + + diff = t ? stbi__extend_receive(j, t) : 0; + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc * dequant[0]); + + // decode AC components, see JPEG spec + k = 1; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * dequant[zig]); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (rs != 0xf0) break; // end block + k += 16; + } else { + k += r; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); + } + } + } while (k < 64); + return 1; +} + +static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) +{ + int diff,dc; + int t; + if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + if (j->succ_high == 0) { + // first scan for DC coefficient, must be first + memset(data,0,64*sizeof(data[0])); // 0 all the ac values now + t = stbi__jpeg_huff_decode(j, hdc); + if (t == -1) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + diff = t ? stbi__extend_receive(j, t) : 0; + + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc << j->succ_low); + } else { + // refinement scan for DC coefficient + if (stbi__jpeg_get_bit(j)) + data[0] += (short) (1 << j->succ_low); + } + return 1; +} + +// @OPTIMIZE: store non-zigzagged during the decode passes, +// and only de-zigzag when dequantizing +static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) +{ + int k; + if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->succ_high == 0) { + int shift = j->succ_low; + + if (j->eob_run) { + --j->eob_run; + return 1; + } + + k = j->spec_start; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) << shift); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r); + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + --j->eob_run; + break; + } + k += 16; + } else { + k += r; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) << shift); + } + } + } while (k <= j->spec_end); + } else { + // refinement scan for these AC coefficients + + short bit = (short) (1 << j->succ_low); + + if (j->eob_run) { + --j->eob_run; + for (k = j->spec_start; k <= j->spec_end; ++k) { + short *p = &data[stbi__jpeg_dezigzag[k]]; + if (*p != 0) + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } + } else { + k = j->spec_start; + do { + int r,s; + int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r) - 1; + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + r = 64; // force end of block + } else { + // r=15 s=0 should write 16 0s, so we just do + // a run of 15 0s and then write s (which is 0), + // so we don't have to do anything special here + } + } else { + if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); + // sign bit + if (stbi__jpeg_get_bit(j)) + s = bit; + else + s = -bit; + } + + // advance by r + while (k <= j->spec_end) { + short *p = &data[stbi__jpeg_dezigzag[k++]]; + if (*p != 0) { + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } else { + if (r == 0) { + *p = (short) s; + break; + } + --r; + } + } + } while (k <= j->spec_end); + } + } + return 1; +} + +// take a -128..127 value and stbi__clamp it and convert to 0..255 +stbi_inline static stbi_uc stbi__clamp(int x) +{ + // trick to use a single test to catch both cases + if ((unsigned int) x > 255) { + if (x < 0) return 0; + if (x > 255) return 255; + } + return (stbi_uc) x; +} + +#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) +#define stbi__fsh(x) ((x) * 4096) + +// derived from jidctint -- DCT_ISLOW +#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ + int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ + p2 = s2; \ + p3 = s6; \ + p1 = (p2+p3) * stbi__f2f(0.5411961f); \ + t2 = p1 + p3*stbi__f2f(-1.847759065f); \ + t3 = p1 + p2*stbi__f2f( 0.765366865f); \ + p2 = s0; \ + p3 = s4; \ + t0 = stbi__fsh(p2+p3); \ + t1 = stbi__fsh(p2-p3); \ + x0 = t0+t3; \ + x3 = t0-t3; \ + x1 = t1+t2; \ + x2 = t1-t2; \ + t0 = s7; \ + t1 = s5; \ + t2 = s3; \ + t3 = s1; \ + p3 = t0+t2; \ + p4 = t1+t3; \ + p1 = t0+t3; \ + p2 = t1+t2; \ + p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ + t0 = t0*stbi__f2f( 0.298631336f); \ + t1 = t1*stbi__f2f( 2.053119869f); \ + t2 = t2*stbi__f2f( 3.072711026f); \ + t3 = t3*stbi__f2f( 1.501321110f); \ + p1 = p5 + p1*stbi__f2f(-0.899976223f); \ + p2 = p5 + p2*stbi__f2f(-2.562915447f); \ + p3 = p3*stbi__f2f(-1.961570560f); \ + p4 = p4*stbi__f2f(-0.390180644f); \ + t3 += p1+p4; \ + t2 += p2+p3; \ + t1 += p2+p4; \ + t0 += p1+p3; + +static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) +{ + int i,val[64],*v=val; + stbi_uc *o; + short *d = data; + + // columns + for (i=0; i < 8; ++i,++d, ++v) { + // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing + if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 + && d[40]==0 && d[48]==0 && d[56]==0) { + // no shortcut 0 seconds + // (1|2|3|4|5|6|7)==0 0 seconds + // all separate -0.047 seconds + // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds + int dcterm = d[0]*4; + v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; + } else { + STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) + // constants scaled things up by 1<<12; let's bring them back + // down, but keep 2 extra bits of precision + x0 += 512; x1 += 512; x2 += 512; x3 += 512; + v[ 0] = (x0+t3) >> 10; + v[56] = (x0-t3) >> 10; + v[ 8] = (x1+t2) >> 10; + v[48] = (x1-t2) >> 10; + v[16] = (x2+t1) >> 10; + v[40] = (x2-t1) >> 10; + v[24] = (x3+t0) >> 10; + v[32] = (x3-t0) >> 10; + } + } + + for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { + // no fast case since the first 1D IDCT spread components out + STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) + // constants scaled things up by 1<<12, plus we had 1<<2 from first + // loop, plus horizontal and vertical each scale by sqrt(8) so together + // we've got an extra 1<<3, so 1<<17 total we need to remove. + // so we want to round that, which means adding 0.5 * 1<<17, + // aka 65536. Also, we'll end up with -128 to 127 that we want + // to encode as 0..255 by adding 128, so we'll add that before the shift + x0 += 65536 + (128<<17); + x1 += 65536 + (128<<17); + x2 += 65536 + (128<<17); + x3 += 65536 + (128<<17); + // tried computing the shifts into temps, or'ing the temps to see + // if any were out of range, but that was slower + o[0] = stbi__clamp((x0+t3) >> 17); + o[7] = stbi__clamp((x0-t3) >> 17); + o[1] = stbi__clamp((x1+t2) >> 17); + o[6] = stbi__clamp((x1-t2) >> 17); + o[2] = stbi__clamp((x2+t1) >> 17); + o[5] = stbi__clamp((x2-t1) >> 17); + o[3] = stbi__clamp((x3+t0) >> 17); + o[4] = stbi__clamp((x3-t0) >> 17); + } +} + +#ifdef STBI_SSE2 +// sse2 integer IDCT. not the fastest possible implementation but it +// produces bit-identical results to the generic C version so it's +// fully "transparent". +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + // This is constructed to match our regular (generic) integer IDCT exactly. + __m128i row0, row1, row2, row3, row4, row5, row6, row7; + __m128i tmp; + + // dot product constant: even elems=x, odd elems=y + #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) + + // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) + // out(1) = c1[even]*x + c1[odd]*y + #define dct_rot(out0,out1, x,y,c0,c1) \ + __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ + __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ + __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ + __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ + __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ + __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) + + // out = in << 12 (in 16-bit, out 32-bit) + #define dct_widen(out, in) \ + __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ + __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) + + // wide add + #define dct_wadd(out, a, b) \ + __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_add_epi32(a##_h, b##_h) + + // wide sub + #define dct_wsub(out, a, b) \ + __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) + + // butterfly a/b, add bias, then shift by "s" and pack + #define dct_bfly32o(out0, out1, a,b,bias,s) \ + { \ + __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ + __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ + dct_wadd(sum, abiased, b); \ + dct_wsub(dif, abiased, b); \ + out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ + out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ + } + + // 8-bit interleave step (for transposes) + #define dct_interleave8(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi8(a, b); \ + b = _mm_unpackhi_epi8(tmp, b) + + // 16-bit interleave step (for transposes) + #define dct_interleave16(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi16(a, b); \ + b = _mm_unpackhi_epi16(tmp, b) + + #define dct_pass(bias,shift) \ + { \ + /* even part */ \ + dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ + __m128i sum04 = _mm_add_epi16(row0, row4); \ + __m128i dif04 = _mm_sub_epi16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ + dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ + __m128i sum17 = _mm_add_epi16(row1, row7); \ + __m128i sum35 = _mm_add_epi16(row3, row5); \ + dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ + dct_wadd(x4, y0o, y4o); \ + dct_wadd(x5, y1o, y5o); \ + dct_wadd(x6, y2o, y5o); \ + dct_wadd(x7, y3o, y4o); \ + dct_bfly32o(row0,row7, x0,x7,bias,shift); \ + dct_bfly32o(row1,row6, x1,x6,bias,shift); \ + dct_bfly32o(row2,row5, x2,x5,bias,shift); \ + dct_bfly32o(row3,row4, x3,x4,bias,shift); \ + } + + __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); + __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); + __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); + __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); + __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); + __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); + __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); + __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); + + // rounding biases in column/row passes, see stbi__idct_block for explanation. + __m128i bias_0 = _mm_set1_epi32(512); + __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); + + // load + row0 = _mm_load_si128((const __m128i *) (data + 0*8)); + row1 = _mm_load_si128((const __m128i *) (data + 1*8)); + row2 = _mm_load_si128((const __m128i *) (data + 2*8)); + row3 = _mm_load_si128((const __m128i *) (data + 3*8)); + row4 = _mm_load_si128((const __m128i *) (data + 4*8)); + row5 = _mm_load_si128((const __m128i *) (data + 5*8)); + row6 = _mm_load_si128((const __m128i *) (data + 6*8)); + row7 = _mm_load_si128((const __m128i *) (data + 7*8)); + + // column pass + dct_pass(bias_0, 10); + + { + // 16bit 8x8 transpose pass 1 + dct_interleave16(row0, row4); + dct_interleave16(row1, row5); + dct_interleave16(row2, row6); + dct_interleave16(row3, row7); + + // transpose pass 2 + dct_interleave16(row0, row2); + dct_interleave16(row1, row3); + dct_interleave16(row4, row6); + dct_interleave16(row5, row7); + + // transpose pass 3 + dct_interleave16(row0, row1); + dct_interleave16(row2, row3); + dct_interleave16(row4, row5); + dct_interleave16(row6, row7); + } + + // row pass + dct_pass(bias_1, 17); + + { + // pack + __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 + __m128i p1 = _mm_packus_epi16(row2, row3); + __m128i p2 = _mm_packus_epi16(row4, row5); + __m128i p3 = _mm_packus_epi16(row6, row7); + + // 8bit 8x8 transpose pass 1 + dct_interleave8(p0, p2); // a0e0a1e1... + dct_interleave8(p1, p3); // c0g0c1g1... + + // transpose pass 2 + dct_interleave8(p0, p1); // a0c0e0g0... + dct_interleave8(p2, p3); // b0d0f0h0... + + // transpose pass 3 + dct_interleave8(p0, p2); // a0b0c0d0... + dct_interleave8(p1, p3); // a4b4c4d4... + + // store + _mm_storel_epi64((__m128i *) out, p0); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p2); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p1); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p3); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); + } + +#undef dct_const +#undef dct_rot +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_interleave8 +#undef dct_interleave16 +#undef dct_pass +} + +#endif // STBI_SSE2 + +#ifdef STBI_NEON + +// NEON integer IDCT. should produce bit-identical +// results to the generic C version. +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; + + int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); + int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); + int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); + int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); + int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); + int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); + int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); + int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); + int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); + int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); + int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); + int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); + +#define dct_long_mul(out, inq, coeff) \ + int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) + +#define dct_long_mac(out, acc, inq, coeff) \ + int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) + +#define dct_widen(out, inq) \ + int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ + int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) + +// wide add +#define dct_wadd(out, a, b) \ + int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vaddq_s32(a##_h, b##_h) + +// wide sub +#define dct_wsub(out, a, b) \ + int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vsubq_s32(a##_h, b##_h) + +// butterfly a/b, then shift using "shiftop" by "s" and pack +#define dct_bfly32o(out0,out1, a,b,shiftop,s) \ + { \ + dct_wadd(sum, a, b); \ + dct_wsub(dif, a, b); \ + out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ + out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ + } + +#define dct_pass(shiftop, shift) \ + { \ + /* even part */ \ + int16x8_t sum26 = vaddq_s16(row2, row6); \ + dct_long_mul(p1e, sum26, rot0_0); \ + dct_long_mac(t2e, p1e, row6, rot0_1); \ + dct_long_mac(t3e, p1e, row2, rot0_2); \ + int16x8_t sum04 = vaddq_s16(row0, row4); \ + int16x8_t dif04 = vsubq_s16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + int16x8_t sum15 = vaddq_s16(row1, row5); \ + int16x8_t sum17 = vaddq_s16(row1, row7); \ + int16x8_t sum35 = vaddq_s16(row3, row5); \ + int16x8_t sum37 = vaddq_s16(row3, row7); \ + int16x8_t sumodd = vaddq_s16(sum17, sum35); \ + dct_long_mul(p5o, sumodd, rot1_0); \ + dct_long_mac(p1o, p5o, sum17, rot1_1); \ + dct_long_mac(p2o, p5o, sum35, rot1_2); \ + dct_long_mul(p3o, sum37, rot2_0); \ + dct_long_mul(p4o, sum15, rot2_1); \ + dct_wadd(sump13o, p1o, p3o); \ + dct_wadd(sump24o, p2o, p4o); \ + dct_wadd(sump23o, p2o, p3o); \ + dct_wadd(sump14o, p1o, p4o); \ + dct_long_mac(x4, sump13o, row7, rot3_0); \ + dct_long_mac(x5, sump24o, row5, rot3_1); \ + dct_long_mac(x6, sump23o, row3, rot3_2); \ + dct_long_mac(x7, sump14o, row1, rot3_3); \ + dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ + dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ + dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ + dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ + } + + // load + row0 = vld1q_s16(data + 0*8); + row1 = vld1q_s16(data + 1*8); + row2 = vld1q_s16(data + 2*8); + row3 = vld1q_s16(data + 3*8); + row4 = vld1q_s16(data + 4*8); + row5 = vld1q_s16(data + 5*8); + row6 = vld1q_s16(data + 6*8); + row7 = vld1q_s16(data + 7*8); + + // add DC bias + row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); + + // column pass + dct_pass(vrshrn_n_s32, 10); + + // 16bit 8x8 transpose + { +// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. +// whether compilers actually get this is another story, sadly. +#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } +#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } + + // pass 1 + dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 + dct_trn16(row2, row3); + dct_trn16(row4, row5); + dct_trn16(row6, row7); + + // pass 2 + dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 + dct_trn32(row1, row3); + dct_trn32(row4, row6); + dct_trn32(row5, row7); + + // pass 3 + dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 + dct_trn64(row1, row5); + dct_trn64(row2, row6); + dct_trn64(row3, row7); + +#undef dct_trn16 +#undef dct_trn32 +#undef dct_trn64 + } + + // row pass + // vrshrn_n_s32 only supports shifts up to 16, we need + // 17. so do a non-rounding shift of 16 first then follow + // up with a rounding shift by 1. + dct_pass(vshrn_n_s32, 16); + + { + // pack and round + uint8x8_t p0 = vqrshrun_n_s16(row0, 1); + uint8x8_t p1 = vqrshrun_n_s16(row1, 1); + uint8x8_t p2 = vqrshrun_n_s16(row2, 1); + uint8x8_t p3 = vqrshrun_n_s16(row3, 1); + uint8x8_t p4 = vqrshrun_n_s16(row4, 1); + uint8x8_t p5 = vqrshrun_n_s16(row5, 1); + uint8x8_t p6 = vqrshrun_n_s16(row6, 1); + uint8x8_t p7 = vqrshrun_n_s16(row7, 1); + + // again, these can translate into one instruction, but often don't. +#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } +#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } + + // sadly can't use interleaved stores here since we only write + // 8 bytes to each scan line! + + // 8x8 8-bit transpose pass 1 + dct_trn8_8(p0, p1); + dct_trn8_8(p2, p3); + dct_trn8_8(p4, p5); + dct_trn8_8(p6, p7); + + // pass 2 + dct_trn8_16(p0, p2); + dct_trn8_16(p1, p3); + dct_trn8_16(p4, p6); + dct_trn8_16(p5, p7); + + // pass 3 + dct_trn8_32(p0, p4); + dct_trn8_32(p1, p5); + dct_trn8_32(p2, p6); + dct_trn8_32(p3, p7); + + // store + vst1_u8(out, p0); out += out_stride; + vst1_u8(out, p1); out += out_stride; + vst1_u8(out, p2); out += out_stride; + vst1_u8(out, p3); out += out_stride; + vst1_u8(out, p4); out += out_stride; + vst1_u8(out, p5); out += out_stride; + vst1_u8(out, p6); out += out_stride; + vst1_u8(out, p7); + +#undef dct_trn8_8 +#undef dct_trn8_16 +#undef dct_trn8_32 + } + +#undef dct_long_mul +#undef dct_long_mac +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_pass +} + +#endif // STBI_NEON + +#define STBI__MARKER_none 0xff +// if there's a pending marker from the entropy stream, return that +// otherwise, fetch from the stream and get a marker. if there's no +// marker, return 0xff, which is never a valid marker value +static stbi_uc stbi__get_marker(stbi__jpeg *j) +{ + stbi_uc x; + if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } + x = stbi__get8(j->s); + if (x != 0xff) return STBI__MARKER_none; + while (x == 0xff) + x = stbi__get8(j->s); // consume repeated 0xff fill bytes + return x; +} + +// in each scan, we'll have scan_n components, and the order +// of the components is specified by order[] +#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) + +// after a restart interval, stbi__jpeg_reset the entropy decoder and +// the dc prediction +static void stbi__jpeg_reset(stbi__jpeg *j) +{ + j->code_bits = 0; + j->code_buffer = 0; + j->nomore = 0; + j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = j->img_comp[3].dc_pred = 0; + j->marker = STBI__MARKER_none; + j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; + j->eob_run = 0; + // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, + // since we don't even allow 1<<30 pixels +} + +static int stbi__parse_entropy_coded_data(stbi__jpeg *z) +{ + stbi__jpeg_reset(z); + if (!z->progressive) { + if (z->scan_n == 1) { + int i,j; + STBI_SIMD_ALIGN(short, data[64]); + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + STBI_SIMD_ALIGN(short, data[64]); + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x)*8; + int y2 = (j*z->img_comp[n].v + y)*8; + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } else { + if (z->scan_n == 1) { + int i,j; + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + if (z->spec_start == 0) { + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } else { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) + return 0; + } + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x); + int y2 = (j*z->img_comp[n].v + y); + short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } +} + +static void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant) +{ + int i; + for (i=0; i < 64; ++i) + data[i] *= dequant[i]; +} + +static void stbi__jpeg_finish(stbi__jpeg *z) +{ + if (z->progressive) { + // dequantize and idct the data + int i,j,n; + for (n=0; n < z->s->img_n; ++n) { + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + } + } + } + } +} + +static int stbi__process_marker(stbi__jpeg *z, int m) +{ + int L; + switch (m) { + case STBI__MARKER_none: // no marker found + return stbi__err("expected marker","Corrupt JPEG"); + + case 0xDD: // DRI - specify restart interval + if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); + z->restart_interval = stbi__get16be(z->s); + return 1; + + case 0xDB: // DQT - define quantization table + L = stbi__get16be(z->s)-2; + while (L > 0) { + int q = stbi__get8(z->s); + int p = q >> 4, sixteen = (p != 0); + int t = q & 15,i; + if (p != 0 && p != 1) return stbi__err("bad DQT type","Corrupt JPEG"); + if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); + + for (i=0; i < 64; ++i) + z->dequant[t][stbi__jpeg_dezigzag[i]] = (stbi__uint16)(sixteen ? stbi__get16be(z->s) : stbi__get8(z->s)); + L -= (sixteen ? 129 : 65); + } + return L==0; + + case 0xC4: // DHT - define huffman table + L = stbi__get16be(z->s)-2; + while (L > 0) { + stbi_uc *v; + int sizes[16],i,n=0; + int q = stbi__get8(z->s); + int tc = q >> 4; + int th = q & 15; + if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); + for (i=0; i < 16; ++i) { + sizes[i] = stbi__get8(z->s); + n += sizes[i]; + } + L -= 17; + if (tc == 0) { + if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; + v = z->huff_dc[th].values; + } else { + if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; + v = z->huff_ac[th].values; + } + for (i=0; i < n; ++i) + v[i] = stbi__get8(z->s); + if (tc != 0) + stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); + L -= n; + } + return L==0; + } + + // check for comment block or APP blocks + if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { + L = stbi__get16be(z->s); + if (L < 2) { + if (m == 0xFE) + return stbi__err("bad COM len","Corrupt JPEG"); + else + return stbi__err("bad APP len","Corrupt JPEG"); + } + L -= 2; + + if (m == 0xE0 && L >= 5) { // JFIF APP0 segment + static const unsigned char tag[5] = {'J','F','I','F','\0'}; + int ok = 1; + int i; + for (i=0; i < 5; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 5; + if (ok) + z->jfif = 1; + } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment + static const unsigned char tag[6] = {'A','d','o','b','e','\0'}; + int ok = 1; + int i; + for (i=0; i < 6; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 6; + if (ok) { + stbi__get8(z->s); // version + stbi__get16be(z->s); // flags0 + stbi__get16be(z->s); // flags1 + z->app14_color_transform = stbi__get8(z->s); // color transform + L -= 6; + } + } + + stbi__skip(z->s, L); + return 1; + } + + return stbi__err("unknown marker","Corrupt JPEG"); +} + +// after we see SOS +static int stbi__process_scan_header(stbi__jpeg *z) +{ + int i; + int Ls = stbi__get16be(z->s); + z->scan_n = stbi__get8(z->s); + if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG"); + if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); + for (i=0; i < z->scan_n; ++i) { + int id = stbi__get8(z->s), which; + int q = stbi__get8(z->s); + for (which = 0; which < z->s->img_n; ++which) + if (z->img_comp[which].id == id) + break; + if (which == z->s->img_n) return 0; // no match + z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); + z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); + z->order[i] = which; + } + + { + int aa; + z->spec_start = stbi__get8(z->s); + z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 + aa = stbi__get8(z->s); + z->succ_high = (aa >> 4); + z->succ_low = (aa & 15); + if (z->progressive) { + if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) + return stbi__err("bad SOS", "Corrupt JPEG"); + } else { + if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); + if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); + z->spec_end = 63; + } + } + + return 1; +} + +static int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why) +{ + int i; + for (i=0; i < ncomp; ++i) { + if (z->img_comp[i].raw_data) { + STBI_FREE(z->img_comp[i].raw_data); + z->img_comp[i].raw_data = NULL; + z->img_comp[i].data = NULL; + } + if (z->img_comp[i].raw_coeff) { + STBI_FREE(z->img_comp[i].raw_coeff); + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].coeff = 0; + } + if (z->img_comp[i].linebuf) { + STBI_FREE(z->img_comp[i].linebuf); + z->img_comp[i].linebuf = NULL; + } + } + return why; +} + +static int stbi__process_frame_header(stbi__jpeg *z, int scan) +{ + stbi__context *s = z->s; + int Lf,p,i,q, h_max=1,v_max=1,c; + Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG + p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline + s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG + s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + c = stbi__get8(s); + if (c != 3 && c != 1 && c != 4) return stbi__err("bad component count","Corrupt JPEG"); + s->img_n = c; + for (i=0; i < c; ++i) { + z->img_comp[i].data = NULL; + z->img_comp[i].linebuf = NULL; + } + + if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); + + z->rgb = 0; + for (i=0; i < s->img_n; ++i) { + static const unsigned char rgb[3] = { 'R', 'G', 'B' }; + z->img_comp[i].id = stbi__get8(s); + if (s->img_n == 3 && z->img_comp[i].id == rgb[i]) + ++z->rgb; + q = stbi__get8(s); + z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); + z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); + z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); + } + + if (scan != STBI__SCAN_load) return 1; + + if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err("too large", "Image too large to decode"); + + for (i=0; i < s->img_n; ++i) { + if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; + if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; + } + + // compute interleaved mcu info + z->img_h_max = h_max; + z->img_v_max = v_max; + z->img_mcu_w = h_max * 8; + z->img_mcu_h = v_max * 8; + // these sizes can't be more than 17 bits + z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; + z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; + + for (i=0; i < s->img_n; ++i) { + // number of effective pixels (e.g. for non-interleaved MCU) + z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; + z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; + // to simplify generation, we'll allocate enough memory to decode + // the bogus oversized data from using interleaved MCUs and their + // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't + // discard the extra data until colorspace conversion + // + // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier) + // so these muls can't overflow with 32-bit ints (which we require) + z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; + z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; + z->img_comp[i].coeff = 0; + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].linebuf = NULL; + z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15); + if (z->img_comp[i].raw_data == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + // align blocks for idct using mmx/sse + z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); + if (z->progressive) { + // w2, h2 are multiples of 8 (see above) + z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8; + z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8; + z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15); + if (z->img_comp[i].raw_coeff == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); + } + } + + return 1; +} + +// use comparisons since in some cases we handle more than one case (e.g. SOF) +#define stbi__DNL(x) ((x) == 0xdc) +#define stbi__SOI(x) ((x) == 0xd8) +#define stbi__EOI(x) ((x) == 0xd9) +#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) +#define stbi__SOS(x) ((x) == 0xda) + +#define stbi__SOF_progressive(x) ((x) == 0xc2) + +static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) +{ + int m; + z->jfif = 0; + z->app14_color_transform = -1; // valid values are 0,1,2 + z->marker = STBI__MARKER_none; // initialize cached marker to empty + m = stbi__get_marker(z); + if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); + if (scan == STBI__SCAN_type) return 1; + m = stbi__get_marker(z); + while (!stbi__SOF(m)) { + if (!stbi__process_marker(z,m)) return 0; + m = stbi__get_marker(z); + while (m == STBI__MARKER_none) { + // some files have extra padding after their blocks, so ok, we'll scan + if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); + m = stbi__get_marker(z); + } + } + z->progressive = stbi__SOF_progressive(m); + if (!stbi__process_frame_header(z, scan)) return 0; + return 1; +} + +// decode image to YCbCr format +static int stbi__decode_jpeg_image(stbi__jpeg *j) +{ + int m; + for (m = 0; m < 4; m++) { + j->img_comp[m].raw_data = NULL; + j->img_comp[m].raw_coeff = NULL; + } + j->restart_interval = 0; + if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; + m = stbi__get_marker(j); + while (!stbi__EOI(m)) { + if (stbi__SOS(m)) { + if (!stbi__process_scan_header(j)) return 0; + if (!stbi__parse_entropy_coded_data(j)) return 0; + if (j->marker == STBI__MARKER_none ) { + // handle 0s at the end of image data from IP Kamera 9060 + while (!stbi__at_eof(j->s)) { + int x = stbi__get8(j->s); + if (x == 255) { + j->marker = stbi__get8(j->s); + break; + } + } + // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 + } + } else if (stbi__DNL(m)) { + int Ld = stbi__get16be(j->s); + stbi__uint32 NL = stbi__get16be(j->s); + if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG"); + if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG"); + } else { + if (!stbi__process_marker(j, m)) return 0; + } + m = stbi__get_marker(j); + } + if (j->progressive) + stbi__jpeg_finish(j); + return 1; +} + +// static jfif-centered resampling (across block boundaries) + +typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, + int w, int hs); + +#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) + +static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + STBI_NOTUSED(out); + STBI_NOTUSED(in_far); + STBI_NOTUSED(w); + STBI_NOTUSED(hs); + return in_near; +} + +static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples vertically for every one in input + int i; + STBI_NOTUSED(hs); + for (i=0; i < w; ++i) + out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); + return out; +} + +static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples horizontally for every one in input + int i; + stbi_uc *input = in_near; + + if (w == 1) { + // if only one sample, can't do any interpolation + out[0] = out[1] = input[0]; + return out; + } + + out[0] = input[0]; + out[1] = stbi__div4(input[0]*3 + input[1] + 2); + for (i=1; i < w-1; ++i) { + int n = 3*input[i]+2; + out[i*2+0] = stbi__div4(n+input[i-1]); + out[i*2+1] = stbi__div4(n+input[i+1]); + } + out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); + out[i*2+1] = input[w-1]; + + STBI_NOTUSED(in_far); + STBI_NOTUSED(hs); + + return out; +} + +#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) + +static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i,t0,t1; + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + out[0] = stbi__div4(t1+2); + for (i=1; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i=0,t0,t1; + + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + // process groups of 8 pixels for as long as we can. + // note we can't handle the last pixel in a row in this loop + // because we need to handle the filter boundary conditions. + for (; i < ((w-1) & ~7); i += 8) { +#if defined(STBI_SSE2) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + __m128i zero = _mm_setzero_si128(); + __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); + __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); + __m128i farw = _mm_unpacklo_epi8(farb, zero); + __m128i nearw = _mm_unpacklo_epi8(nearb, zero); + __m128i diff = _mm_sub_epi16(farw, nearw); + __m128i nears = _mm_slli_epi16(nearw, 2); + __m128i curr = _mm_add_epi16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + __m128i prv0 = _mm_slli_si128(curr, 2); + __m128i nxt0 = _mm_srli_si128(curr, 2); + __m128i prev = _mm_insert_epi16(prv0, t1, 0); + __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + __m128i bias = _mm_set1_epi16(8); + __m128i curs = _mm_slli_epi16(curr, 2); + __m128i prvd = _mm_sub_epi16(prev, curr); + __m128i nxtd = _mm_sub_epi16(next, curr); + __m128i curb = _mm_add_epi16(curs, bias); + __m128i even = _mm_add_epi16(prvd, curb); + __m128i odd = _mm_add_epi16(nxtd, curb); + + // interleave even and odd pixels, then undo scaling. + __m128i int0 = _mm_unpacklo_epi16(even, odd); + __m128i int1 = _mm_unpackhi_epi16(even, odd); + __m128i de0 = _mm_srli_epi16(int0, 4); + __m128i de1 = _mm_srli_epi16(int1, 4); + + // pack and write output + __m128i outv = _mm_packus_epi16(de0, de1); + _mm_storeu_si128((__m128i *) (out + i*2), outv); +#elif defined(STBI_NEON) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + uint8x8_t farb = vld1_u8(in_far + i); + uint8x8_t nearb = vld1_u8(in_near + i); + int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); + int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); + int16x8_t curr = vaddq_s16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + int16x8_t prv0 = vextq_s16(curr, curr, 7); + int16x8_t nxt0 = vextq_s16(curr, curr, 1); + int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); + int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + int16x8_t curs = vshlq_n_s16(curr, 2); + int16x8_t prvd = vsubq_s16(prev, curr); + int16x8_t nxtd = vsubq_s16(next, curr); + int16x8_t even = vaddq_s16(curs, prvd); + int16x8_t odd = vaddq_s16(curs, nxtd); + + // undo scaling and round, then store with even/odd phases interleaved + uint8x8x2_t o; + o.val[0] = vqrshrun_n_s16(even, 4); + o.val[1] = vqrshrun_n_s16(odd, 4); + vst2_u8(out + i*2, o); +#endif + + // "previous" value for next iter + t1 = 3*in_near[i+7] + in_far[i+7]; + } + + t0 = t1; + t1 = 3*in_near[i] + in_far[i]; + out[i*2] = stbi__div16(3*t1 + t0 + 8); + + for (++i; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} +#endif + +static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // resample with nearest-neighbor + int i,j; + STBI_NOTUSED(in_far); + for (i=0; i < w; ++i) + for (j=0; j < hs; ++j) + out[i*hs+j] = in_near[i]; + return out; +} + +// this is a reduced-precision calculation of YCbCr-to-RGB introduced +// to make sure the code produces the same results in both SIMD and scalar +#define stbi__float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) +static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) +{ + int i = 0; + +#ifdef STBI_SSE2 + // step == 3 is pretty ugly on the final interleave, and i'm not convinced + // it's useful in practice (you wouldn't use it for textures, for example). + // so just accelerate step == 4 case. + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + __m128i signflip = _mm_set1_epi8(-0x80); + __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); + __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); + __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); + __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); + __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); + __m128i xw = _mm_set1_epi16(255); // alpha channel + + for (; i+7 < count; i += 8) { + // load + __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); + __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); + __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); + __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 + __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 + + // unpack to short (and left-shift cr, cb by 8) + __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); + __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); + __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); + + // color transform + __m128i yws = _mm_srli_epi16(yw, 4); + __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); + __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); + __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); + __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); + __m128i rws = _mm_add_epi16(cr0, yws); + __m128i gwt = _mm_add_epi16(cb0, yws); + __m128i bws = _mm_add_epi16(yws, cb1); + __m128i gws = _mm_add_epi16(gwt, cr1); + + // descale + __m128i rw = _mm_srai_epi16(rws, 4); + __m128i bw = _mm_srai_epi16(bws, 4); + __m128i gw = _mm_srai_epi16(gws, 4); + + // back to byte, set up for transpose + __m128i brb = _mm_packus_epi16(rw, bw); + __m128i gxb = _mm_packus_epi16(gw, xw); + + // transpose to interleave channels + __m128i t0 = _mm_unpacklo_epi8(brb, gxb); + __m128i t1 = _mm_unpackhi_epi8(brb, gxb); + __m128i o0 = _mm_unpacklo_epi16(t0, t1); + __m128i o1 = _mm_unpackhi_epi16(t0, t1); + + // store + _mm_storeu_si128((__m128i *) (out + 0), o0); + _mm_storeu_si128((__m128i *) (out + 16), o1); + out += 32; + } + } +#endif + +#ifdef STBI_NEON + // in this version, step=3 support would be easy to add. but is there demand? + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + uint8x8_t signflip = vdup_n_u8(0x80); + int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); + int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); + int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); + int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); + + for (; i+7 < count; i += 8) { + // load + uint8x8_t y_bytes = vld1_u8(y + i); + uint8x8_t cr_bytes = vld1_u8(pcr + i); + uint8x8_t cb_bytes = vld1_u8(pcb + i); + int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); + int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); + + // expand to s16 + int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); + int16x8_t crw = vshll_n_s8(cr_biased, 7); + int16x8_t cbw = vshll_n_s8(cb_biased, 7); + + // color transform + int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); + int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); + int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); + int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); + int16x8_t rws = vaddq_s16(yws, cr0); + int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); + int16x8_t bws = vaddq_s16(yws, cb1); + + // undo scaling, round, convert to byte + uint8x8x4_t o; + o.val[0] = vqrshrun_n_s16(rws, 4); + o.val[1] = vqrshrun_n_s16(gws, 4); + o.val[2] = vqrshrun_n_s16(bws, 4); + o.val[3] = vdup_n_u8(255); + + // store, interleaving r/g/b/a + vst4_u8(out, o); + out += 8*4; + } + } +#endif + + for (; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#endif + +// set up the kernels +static void stbi__setup_jpeg(stbi__jpeg *j) +{ + j->idct_block_kernel = stbi__idct_block; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; + +#ifdef STBI_SSE2 + if (stbi__sse2_available()) { + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; + } +#endif + +#ifdef STBI_NEON + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; +#endif +} + +// clean up the temporary component buffers +static void stbi__cleanup_jpeg(stbi__jpeg *j) +{ + stbi__free_jpeg_components(j, j->s->img_n, 0); +} + +typedef struct +{ + resample_row_func resample; + stbi_uc *line0,*line1; + int hs,vs; // expansion factor in each axis + int w_lores; // horizontal pixels pre-expansion + int ystep; // how far through vertical expansion we are + int ypos; // which pre-expansion row we're on +} stbi__resample; + +// fast 0..255 * 0..255 => 0..255 rounded multiplication +static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y) +{ + unsigned int t = x*y + 128; + return (stbi_uc) ((t + (t >>8)) >> 8); +} + +static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) +{ + int n, decode_n, is_rgb; + z->s->img_n = 0; // make stbi__cleanup_jpeg safe + + // validate req_comp + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + + // load a jpeg image from whichever source, but leave in YCbCr format + if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } + + // determine actual number of components to generate + n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1; + + is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif)); + + if (z->s->img_n == 3 && n < 3 && !is_rgb) + decode_n = 1; + else + decode_n = z->s->img_n; + + // resample and color-convert + { + int k; + unsigned int i,j; + stbi_uc *output; + stbi_uc *coutput[4] = { NULL, NULL, NULL, NULL }; + + stbi__resample res_comp[4]; + + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + + // allocate line buffer big enough for upsampling off the edges + // with upsample factor of 4 + z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); + if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + r->hs = z->img_h_max / z->img_comp[k].h; + r->vs = z->img_v_max / z->img_comp[k].v; + r->ystep = r->vs >> 1; + r->w_lores = (z->s->img_x + r->hs-1) / r->hs; + r->ypos = 0; + r->line0 = r->line1 = z->img_comp[k].data; + + if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; + else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; + else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; + else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; + else r->resample = stbi__resample_row_generic; + } + + // can't error after this so, this is safe + output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1); + if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + // now go ahead and resample + for (j=0; j < z->s->img_y; ++j) { + stbi_uc *out = output + n * z->s->img_x * j; + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + int y_bot = r->ystep >= (r->vs >> 1); + coutput[k] = r->resample(z->img_comp[k].linebuf, + y_bot ? r->line1 : r->line0, + y_bot ? r->line0 : r->line1, + r->w_lores, r->hs); + if (++r->ystep >= r->vs) { + r->ystep = 0; + r->line0 = r->line1; + if (++r->ypos < z->img_comp[k].y) + r->line1 += z->img_comp[k].w2; + } + } + if (n >= 3) { + stbi_uc *y = coutput[0]; + if (z->s->img_n == 3) { + if (is_rgb) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = y[i]; + out[1] = coutput[1][i]; + out[2] = coutput[2][i]; + out[3] = 255; + out += n; + } + } else { + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else if (z->s->img_n == 4) { + if (z->app14_color_transform == 0) { // CMYK + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(coutput[0][i], m); + out[1] = stbi__blinn_8x8(coutput[1][i], m); + out[2] = stbi__blinn_8x8(coutput[2][i], m); + out[3] = 255; + out += n; + } + } else if (z->app14_color_transform == 2) { // YCCK + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(255 - out[0], m); + out[1] = stbi__blinn_8x8(255 - out[1], m); + out[2] = stbi__blinn_8x8(255 - out[2], m); + out += n; + } + } else { // YCbCr + alpha? Ignore the fourth channel for now + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else + for (i=0; i < z->s->img_x; ++i) { + out[0] = out[1] = out[2] = y[i]; + out[3] = 255; // not used if n==3 + out += n; + } + } else { + if (is_rgb) { + if (n == 1) + for (i=0; i < z->s->img_x; ++i) + *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + else { + for (i=0; i < z->s->img_x; ++i, out += 2) { + out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + out[1] = 255; + } + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 0) { + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + stbi_uc r = stbi__blinn_8x8(coutput[0][i], m); + stbi_uc g = stbi__blinn_8x8(coutput[1][i], m); + stbi_uc b = stbi__blinn_8x8(coutput[2][i], m); + out[0] = stbi__compute_y(r, g, b); + out[1] = 255; + out += n; + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 2) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]); + out[1] = 255; + out += n; + } + } else { + stbi_uc *y = coutput[0]; + if (n == 1) + for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; + else + for (i=0; i < z->s->img_x; ++i) { *out++ = y[i]; *out++ = 255; } + } + } + } + stbi__cleanup_jpeg(z); + *out_x = z->s->img_x; + *out_y = z->s->img_y; + if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output + return output; + } +} + +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + unsigned char* result; + stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); + STBI_NOTUSED(ri); + j->s = s; + stbi__setup_jpeg(j); + result = load_jpeg_image(j, x,y,comp,req_comp); + STBI_FREE(j); + return result; +} + +static int stbi__jpeg_test(stbi__context *s) +{ + int r; + stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg)); + j->s = s; + stbi__setup_jpeg(j); + r = stbi__decode_jpeg_header(j, STBI__SCAN_type); + stbi__rewind(s); + STBI_FREE(j); + return r; +} + +static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) +{ + if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { + stbi__rewind( j->s ); + return 0; + } + if (x) *x = j->s->img_x; + if (y) *y = j->s->img_y; + if (comp) *comp = j->s->img_n >= 3 ? 3 : 1; + return 1; +} + +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) +{ + int result; + stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); + j->s = s; + result = stbi__jpeg_info_raw(j, x, y, comp); + STBI_FREE(j); + return result; +} +#endif + +// public domain zlib decode v0.2 Sean Barrett 2006-11-18 +// simple implementation +// - all input must be provided in an upfront buffer +// - all output is written to a single output buffer (can malloc/realloc) +// performance +// - fast huffman + +#ifndef STBI_NO_ZLIB + +// fast-way is faster to check than jpeg huffman, but slow way is slower +#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables +#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) + +// zlib-style huffman encoding +// (jpegs packs from left, zlib from right, so can't share code) +typedef struct +{ + stbi__uint16 fast[1 << STBI__ZFAST_BITS]; + stbi__uint16 firstcode[16]; + int maxcode[17]; + stbi__uint16 firstsymbol[16]; + stbi_uc size[288]; + stbi__uint16 value[288]; +} stbi__zhuffman; + +stbi_inline static int stbi__bitreverse16(int n) +{ + n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); + n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); + n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); + n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); + return n; +} + +stbi_inline static int stbi__bit_reverse(int v, int bits) +{ + STBI_ASSERT(bits <= 16); + // to bit reverse n bits, reverse 16 and shift + // e.g. 11 bits, bit reverse and shift away 5 + return stbi__bitreverse16(v) >> (16-bits); +} + +static int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num) +{ + int i,k=0; + int code, next_code[16], sizes[17]; + + // DEFLATE spec for generating codes + memset(sizes, 0, sizeof(sizes)); + memset(z->fast, 0, sizeof(z->fast)); + for (i=0; i < num; ++i) + ++sizes[sizelist[i]]; + sizes[0] = 0; + for (i=1; i < 16; ++i) + if (sizes[i] > (1 << i)) + return stbi__err("bad sizes", "Corrupt PNG"); + code = 0; + for (i=1; i < 16; ++i) { + next_code[i] = code; + z->firstcode[i] = (stbi__uint16) code; + z->firstsymbol[i] = (stbi__uint16) k; + code = (code + sizes[i]); + if (sizes[i]) + if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); + z->maxcode[i] = code << (16-i); // preshift for inner loop + code <<= 1; + k += sizes[i]; + } + z->maxcode[16] = 0x10000; // sentinel + for (i=0; i < num; ++i) { + int s = sizelist[i]; + if (s) { + int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; + stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); + z->size [c] = (stbi_uc ) s; + z->value[c] = (stbi__uint16) i; + if (s <= STBI__ZFAST_BITS) { + int j = stbi__bit_reverse(next_code[s],s); + while (j < (1 << STBI__ZFAST_BITS)) { + z->fast[j] = fastv; + j += (1 << s); + } + } + ++next_code[s]; + } + } + return 1; +} + +// zlib-from-memory implementation for PNG reading +// because PNG allows splitting the zlib stream arbitrarily, +// and it's annoying structurally to have PNG call ZLIB call PNG, +// we require PNG read all the IDATs and combine them into a single +// memory buffer + +typedef struct +{ + stbi_uc *zbuffer, *zbuffer_end; + int num_bits; + stbi__uint32 code_buffer; + + char *zout; + char *zout_start; + char *zout_end; + int z_expandable; + + stbi__zhuffman z_length, z_distance; +} stbi__zbuf; + +stbi_inline static int stbi__zeof(stbi__zbuf *z) +{ + return (z->zbuffer >= z->zbuffer_end); +} + +stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) +{ + return stbi__zeof(z) ? 0 : *z->zbuffer++; +} + +static void stbi__fill_bits(stbi__zbuf *z) +{ + do { + if (z->code_buffer >= (1U << z->num_bits)) { + z->zbuffer = z->zbuffer_end; /* treat this as EOF so we fail. */ + return; + } + z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; + z->num_bits += 8; + } while (z->num_bits <= 24); +} + +stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) +{ + unsigned int k; + if (z->num_bits < n) stbi__fill_bits(z); + k = z->code_buffer & ((1 << n) - 1); + z->code_buffer >>= n; + z->num_bits -= n; + return k; +} + +static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s,k; + // not resolved by fast table, so compute it the slow way + // use jpeg approach, which requires MSbits at top + k = stbi__bit_reverse(a->code_buffer, 16); + for (s=STBI__ZFAST_BITS+1; ; ++s) + if (k < z->maxcode[s]) + break; + if (s >= 16) return -1; // invalid code! + // code size is s, so: + b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; + if (b >= sizeof (z->size)) return -1; // some data was corrupt somewhere! + if (z->size[b] != s) return -1; // was originally an assert, but report failure instead. + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; +} + +stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s; + if (a->num_bits < 16) { + if (stbi__zeof(a)) { + return -1; /* report error for unexpected end of data. */ + } + stbi__fill_bits(a); + } + b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; + if (b) { + s = b >> 9; + a->code_buffer >>= s; + a->num_bits -= s; + return b & 511; + } + return stbi__zhuffman_decode_slowpath(a, z); +} + +static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes +{ + char *q; + unsigned int cur, limit, old_limit; + z->zout = zout; + if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); + cur = (unsigned int) (z->zout - z->zout_start); + limit = old_limit = (unsigned) (z->zout_end - z->zout_start); + if (UINT_MAX - cur < (unsigned) n) return stbi__err("outofmem", "Out of memory"); + while (cur + n > limit) { + if(limit > UINT_MAX / 2) return stbi__err("outofmem", "Out of memory"); + limit *= 2; + } + q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); + STBI_NOTUSED(old_limit); + if (q == NULL) return stbi__err("outofmem", "Out of memory"); + z->zout_start = q; + z->zout = q + cur; + z->zout_end = q + limit; + return 1; +} + +static const int stbi__zlength_base[31] = { + 3,4,5,6,7,8,9,10,11,13, + 15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,0,0 }; + +static const int stbi__zlength_extra[31]= +{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + +static const int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, +257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + +static const int stbi__zdist_extra[32] = +{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +static int stbi__parse_huffman_block(stbi__zbuf *a) +{ + char *zout = a->zout; + for(;;) { + int z = stbi__zhuffman_decode(a, &a->z_length); + if (z < 256) { + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes + if (zout >= a->zout_end) { + if (!stbi__zexpand(a, zout, 1)) return 0; + zout = a->zout; + } + *zout++ = (char) z; + } else { + stbi_uc *p; + int len,dist; + if (z == 256) { + a->zout = zout; + return 1; + } + z -= 257; + len = stbi__zlength_base[z]; + if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); + z = stbi__zhuffman_decode(a, &a->z_distance); + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); + dist = stbi__zdist_base[z]; + if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); + if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); + if (zout + len > a->zout_end) { + if (!stbi__zexpand(a, zout, len)) return 0; + zout = a->zout; + } + p = (stbi_uc *) (zout - dist); + if (dist == 1) { // run of one byte; common in images. + stbi_uc v = *p; + if (len) { do *zout++ = v; while (--len); } + } else { + if (len) { do *zout++ = *p++; while (--len); } + } + } + } +} + +static int stbi__compute_huffman_codes(stbi__zbuf *a) +{ + static const stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + stbi__zhuffman z_codelength; + stbi_uc lencodes[286+32+137];//padding for maximum single op + stbi_uc codelength_sizes[19]; + int i,n; + + int hlit = stbi__zreceive(a,5) + 257; + int hdist = stbi__zreceive(a,5) + 1; + int hclen = stbi__zreceive(a,4) + 4; + int ntot = hlit + hdist; + + memset(codelength_sizes, 0, sizeof(codelength_sizes)); + for (i=0; i < hclen; ++i) { + int s = stbi__zreceive(a,3); + codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; + } + if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; + + n = 0; + while (n < ntot) { + int c = stbi__zhuffman_decode(a, &z_codelength); + if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); + if (c < 16) + lencodes[n++] = (stbi_uc) c; + else { + stbi_uc fill = 0; + if (c == 16) { + c = stbi__zreceive(a,2)+3; + if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG"); + fill = lencodes[n-1]; + } else if (c == 17) { + c = stbi__zreceive(a,3)+3; + } else if (c == 18) { + c = stbi__zreceive(a,7)+11; + } else { + return stbi__err("bad codelengths", "Corrupt PNG"); + } + if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG"); + memset(lencodes+n, fill, c); + n += c; + } + } + if (n != ntot) return stbi__err("bad codelengths","Corrupt PNG"); + if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; + return 1; +} + +static int stbi__parse_uncompressed_block(stbi__zbuf *a) +{ + stbi_uc header[4]; + int len,nlen,k; + if (a->num_bits & 7) + stbi__zreceive(a, a->num_bits & 7); // discard + // drain the bit-packed data into header + k = 0; + while (a->num_bits > 0) { + header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check + a->code_buffer >>= 8; + a->num_bits -= 8; + } + if (a->num_bits < 0) return stbi__err("zlib corrupt","Corrupt PNG"); + // now fill header the normal way + while (k < 4) + header[k++] = stbi__zget8(a); + len = header[1] * 256 + header[0]; + nlen = header[3] * 256 + header[2]; + if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); + if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); + if (a->zout + len > a->zout_end) + if (!stbi__zexpand(a, a->zout, len)) return 0; + memcpy(a->zout, a->zbuffer, len); + a->zbuffer += len; + a->zout += len; + return 1; +} + +static int stbi__parse_zlib_header(stbi__zbuf *a) +{ + int cmf = stbi__zget8(a); + int cm = cmf & 15; + /* int cinfo = cmf >> 4; */ + int flg = stbi__zget8(a); + if (stbi__zeof(a)) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png + if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png + // window = 1 << (8 + cinfo)... but who cares, we fully buffer output + return 1; +} + +static const stbi_uc stbi__zdefault_length[288] = +{ + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8 +}; +static const stbi_uc stbi__zdefault_distance[32] = +{ + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5 +}; +/* +Init algorithm: +{ + int i; // use <= to match clearly with spec + for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; + for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; + for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; + for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; + + for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; +} +*/ + +static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) +{ + int final, type; + if (parse_header) + if (!stbi__parse_zlib_header(a)) return 0; + a->num_bits = 0; + a->code_buffer = 0; + do { + final = stbi__zreceive(a,1); + type = stbi__zreceive(a,2); + if (type == 0) { + if (!stbi__parse_uncompressed_block(a)) return 0; + } else if (type == 3) { + return 0; + } else { + if (type == 1) { + // use fixed code lengths + if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , 288)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; + } else { + if (!stbi__compute_huffman_codes(a)) return 0; + } + if (!stbi__parse_huffman_block(a)) return 0; + } + } while (!final); + return 1; +} + +static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) +{ + a->zout_start = obuf; + a->zout = obuf; + a->zout_end = obuf + olen; + a->z_expandable = exp; + + return stbi__parse_zlib(a, parse_header); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) +{ + return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(16384); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer+len; + if (stbi__do_zlib(&a, p, 16384, 1, 0)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) + return (int) (a.zout - a.zout_start); + else + return -1; +} +#endif + +// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 +// simple implementation +// - only 8-bit samples +// - no CRC checking +// - allocates lots of intermediate memory +// - avoids problem of streaming data between subsystems +// - avoids explicit window management +// performance +// - uses stb_zlib, a PD zlib implementation with fast huffman decoding + +#ifndef STBI_NO_PNG +typedef struct +{ + stbi__uint32 length; + stbi__uint32 type; +} stbi__pngchunk; + +static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) +{ + stbi__pngchunk c; + c.length = stbi__get32be(s); + c.type = stbi__get32be(s); + return c; +} + +static int stbi__check_png_header(stbi__context *s) +{ + static const stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; + int i; + for (i=0; i < 8; ++i) + if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); + return 1; +} + +typedef struct +{ + stbi__context *s; + stbi_uc *idata, *expanded, *out; + int depth; +} stbi__png; + + +enum { + STBI__F_none=0, + STBI__F_sub=1, + STBI__F_up=2, + STBI__F_avg=3, + STBI__F_paeth=4, + // synthetic filters used for first scanline to avoid needing a dummy row of 0s + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static stbi_uc first_row_filter[5] = +{ + STBI__F_none, + STBI__F_sub, + STBI__F_none, + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static int stbi__paeth(int a, int b, int c) +{ + int p = a + b - c; + int pa = abs(p-a); + int pb = abs(p-b); + int pc = abs(p-c); + if (pa <= pb && pa <= pc) return a; + if (pb <= pc) return b; + return c; +} + +static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; + +// create the png data from post-deflated data +static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) +{ + int bytes = (depth == 16? 2 : 1); + stbi__context *s = a->s; + stbi__uint32 i,j,stride = x*out_n*bytes; + stbi__uint32 img_len, img_width_bytes; + int k; + int img_n = s->img_n; // copy it into a local for later + + int output_bytes = out_n*bytes; + int filter_bytes = img_n*bytes; + int width = x; + + STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); + a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into + if (!a->out) return stbi__err("outofmem", "Out of memory"); + + if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG"); + img_width_bytes = (((img_n * x * depth) + 7) >> 3); + img_len = (img_width_bytes + 1) * y; + + // we used to check for exact match between raw_len and img_len on non-interlaced PNGs, + // but issue #276 reported a PNG in the wild that had extra data at the end (all zeros), + // so just check for raw_len < img_len always. + if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); + + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *prior; + int filter = *raw++; + + if (filter > 4) + return stbi__err("invalid filter","Corrupt PNG"); + + if (depth < 8) { + if (img_width_bytes > x) return stbi__err("invalid width","Corrupt PNG"); + cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place + filter_bytes = 1; + width = img_width_bytes; + } + prior = cur - stride; // bugfix: need to compute this after 'cur +=' computation above + + // if first row, use special filter that doesn't sample previous row + if (j == 0) filter = first_row_filter[filter]; + + // handle first byte explicitly + for (k=0; k < filter_bytes; ++k) { + switch (filter) { + case STBI__F_none : cur[k] = raw[k]; break; + case STBI__F_sub : cur[k] = raw[k]; break; + case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; + case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break; + case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break; + case STBI__F_avg_first : cur[k] = raw[k]; break; + case STBI__F_paeth_first: cur[k] = raw[k]; break; + } + } + + if (depth == 8) { + if (img_n != out_n) + cur[img_n] = 255; // first pixel + raw += img_n; + cur += out_n; + prior += out_n; + } else if (depth == 16) { + if (img_n != out_n) { + cur[filter_bytes] = 255; // first pixel top byte + cur[filter_bytes+1] = 255; // first pixel bottom byte + } + raw += filter_bytes; + cur += output_bytes; + prior += output_bytes; + } else { + raw += 1; + cur += 1; + prior += 1; + } + + // this is a little gross, so that we don't switch per-pixel or per-component + if (depth < 8 || img_n == out_n) { + int nk = (width - 1)*filter_bytes; + #define STBI__CASE(f) \ + case f: \ + for (k=0; k < nk; ++k) + switch (filter) { + // "none" filter turns into a memcpy here; make that explicit. + case STBI__F_none: memcpy(cur, raw, nk); break; + STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break; + STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; + STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break; + STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break; + STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break; + STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break; + } + #undef STBI__CASE + raw += nk; + } else { + STBI_ASSERT(img_n+1 == out_n); + #define STBI__CASE(f) \ + case f: \ + for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \ + for (k=0; k < filter_bytes; ++k) + switch (filter) { + STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break; + STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break; + STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; + STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break; + STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break; + STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break; + STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break; + } + #undef STBI__CASE + + // the loop above sets the high byte of the pixels' alpha, but for + // 16 bit png files we also need the low byte set. we'll do that here. + if (depth == 16) { + cur = a->out + stride*j; // start at the beginning of the row again + for (i=0; i < x; ++i,cur+=output_bytes) { + cur[filter_bytes+1] = 255; + } + } + } + } + + // we make a separate pass to expand bits to pixels; for performance, + // this could run two scanlines behind the above code, so it won't + // intefere with filtering but will still be in the cache. + if (depth < 8) { + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes; + // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit + // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop + stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range + + // note that the final byte might overshoot and write more data than desired. + // we can allocate enough data that this never writes out of memory, but it + // could also overwrite the next scanline. can it overwrite non-empty data + // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel. + // so we need to explicitly clamp the final ones + + if (depth == 4) { + for (k=x*img_n; k >= 2; k-=2, ++in) { + *cur++ = scale * ((*in >> 4) ); + *cur++ = scale * ((*in ) & 0x0f); + } + if (k > 0) *cur++ = scale * ((*in >> 4) ); + } else if (depth == 2) { + for (k=x*img_n; k >= 4; k-=4, ++in) { + *cur++ = scale * ((*in >> 6) ); + *cur++ = scale * ((*in >> 4) & 0x03); + *cur++ = scale * ((*in >> 2) & 0x03); + *cur++ = scale * ((*in ) & 0x03); + } + if (k > 0) *cur++ = scale * ((*in >> 6) ); + if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); + if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); + } else if (depth == 1) { + for (k=x*img_n; k >= 8; k-=8, ++in) { + *cur++ = scale * ((*in >> 7) ); + *cur++ = scale * ((*in >> 6) & 0x01); + *cur++ = scale * ((*in >> 5) & 0x01); + *cur++ = scale * ((*in >> 4) & 0x01); + *cur++ = scale * ((*in >> 3) & 0x01); + *cur++ = scale * ((*in >> 2) & 0x01); + *cur++ = scale * ((*in >> 1) & 0x01); + *cur++ = scale * ((*in ) & 0x01); + } + if (k > 0) *cur++ = scale * ((*in >> 7) ); + if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); + if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); + if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); + if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); + if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); + if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); + } + if (img_n != out_n) { + int q; + // insert alpha = 255 + cur = a->out + stride*j; + if (img_n == 1) { + for (q=x-1; q >= 0; --q) { + cur[q*2+1] = 255; + cur[q*2+0] = cur[q]; + } + } else { + STBI_ASSERT(img_n == 3); + for (q=x-1; q >= 0; --q) { + cur[q*4+3] = 255; + cur[q*4+2] = cur[q*3+2]; + cur[q*4+1] = cur[q*3+1]; + cur[q*4+0] = cur[q*3+0]; + } + } + } + } + } else if (depth == 16) { + // force the image data from big-endian to platform-native. + // this is done in a separate pass due to the decoding relying + // on the data being untouched, but could probably be done + // per-line during decode if care is taken. + stbi_uc *cur = a->out; + stbi__uint16 *cur16 = (stbi__uint16*)cur; + + for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) { + *cur16 = (cur[0] << 8) | cur[1]; + } + } + + return 1; +} + +static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) +{ + int bytes = (depth == 16 ? 2 : 1); + int out_bytes = out_n * bytes; + stbi_uc *final; + int p; + if (!interlaced) + return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); + + // de-interlacing + final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); + for (p=0; p < 7; ++p) { + int xorig[] = { 0,4,0,2,0,1,0 }; + int yorig[] = { 0,0,4,0,2,0,1 }; + int xspc[] = { 8,8,4,4,2,2,1 }; + int yspc[] = { 8,8,8,4,4,2,2 }; + int i,j,x,y; + // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 + x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; + y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; + if (x && y) { + stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; + if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { + STBI_FREE(final); + return 0; + } + for (j=0; j < y; ++j) { + for (i=0; i < x; ++i) { + int out_y = j*yspc[p]+yorig[p]; + int out_x = i*xspc[p]+xorig[p]; + memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes, + a->out + (j*x+i)*out_bytes, out_bytes); + } + } + STBI_FREE(a->out); + image_data += img_len; + image_data_len -= img_len; + } + } + a->out = final; + + return 1; +} + +static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + // compute color-based transparency, assuming we've + // already got 255 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i=0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 255); + p += 2; + } + } else { + for (i=0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi__uint16 *p = (stbi__uint16*) z->out; + + // compute color-based transparency, assuming we've + // already got 65535 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i = 0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 65535); + p += 2; + } + } else { + for (i = 0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) +{ + stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; + stbi_uc *p, *temp_out, *orig = a->out; + + p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0); + if (p == NULL) return stbi__err("outofmem", "Out of memory"); + + // between here and free(out) below, exitting would leak + temp_out = p; + + if (pal_img_n == 3) { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p += 3; + } + } else { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p[3] = palette[n+3]; + p += 4; + } + } + STBI_FREE(a->out); + a->out = temp_out; + + STBI_NOTUSED(len); + + return 1; +} + +static int stbi__unpremultiply_on_load = 0; +static int stbi__de_iphone_flag = 0; + +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load = flag_true_if_should_unpremultiply; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag = flag_true_if_should_convert; +} + +static void stbi__de_iphone(stbi__png *z) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + if (s->img_out_n == 3) { // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 3; + } + } else { + STBI_ASSERT(s->img_out_n == 4); + if (stbi__unpremultiply_on_load) { + // convert bgr to rgb and unpremultiply + for (i=0; i < pixel_count; ++i) { + stbi_uc a = p[3]; + stbi_uc t = p[0]; + if (a) { + stbi_uc half = a / 2; + p[0] = (p[2] * 255 + half) / a; + p[1] = (p[1] * 255 + half) / a; + p[2] = ( t * 255 + half) / a; + } else { + p[0] = p[2]; + p[2] = t; + } + p += 4; + } + } else { + // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 4; + } + } + } +} + +#define STBI__PNG_TYPE(a,b,c,d) (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d)) + +static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) +{ + stbi_uc palette[1024], pal_img_n=0; + stbi_uc has_trans=0, tc[3]={0}; + stbi__uint16 tc16[3]; + stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; + int first=1,k,interlace=0, color=0, is_iphone=0; + stbi__context *s = z->s; + + z->expanded = NULL; + z->idata = NULL; + z->out = NULL; + + if (!stbi__check_png_header(s)) return 0; + + if (scan == STBI__SCAN_type) return 1; + + for (;;) { + stbi__pngchunk c = stbi__get_chunk_header(s); + switch (c.type) { + case STBI__PNG_TYPE('C','g','B','I'): + is_iphone = 1; + stbi__skip(s, c.length); + break; + case STBI__PNG_TYPE('I','H','D','R'): { + int comp,filter; + if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); + first = 0; + if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); + s->img_x = stbi__get32be(s); + s->img_y = stbi__get32be(s); + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only"); + color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); + comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); + filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); + interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); + if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); + if (!pal_img_n) { + s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); + if (scan == STBI__SCAN_header) return 1; + } else { + // if paletted, then pal_n is our final components, and + // img_n is # components to decompress/filter. + s->img_n = 1; + if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); + // if SCAN_header, have to scan to see if we have a tRNS + } + break; + } + + case STBI__PNG_TYPE('P','L','T','E'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); + pal_len = c.length / 3; + if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); + for (i=0; i < pal_len; ++i) { + palette[i*4+0] = stbi__get8(s); + palette[i*4+1] = stbi__get8(s); + palette[i*4+2] = stbi__get8(s); + palette[i*4+3] = 255; + } + break; + } + + case STBI__PNG_TYPE('t','R','N','S'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); + if (pal_img_n) { + if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } + if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); + if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); + pal_img_n = 4; + for (i=0; i < c.length; ++i) + palette[i*4+3] = stbi__get8(s); + } else { + if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); + if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); + has_trans = 1; + if (z->depth == 16) { + for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is + } else { + for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger + } + } + break; + } + + case STBI__PNG_TYPE('I','D','A','T'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); + if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; } + if ((int)(ioff + c.length) < (int)ioff) return 0; + if (ioff + c.length > idata_limit) { + stbi__uint32 idata_limit_old = idata_limit; + stbi_uc *p; + if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; + while (ioff + c.length > idata_limit) + idata_limit *= 2; + STBI_NOTUSED(idata_limit_old); + p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); + z->idata = p; + } + if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); + ioff += c.length; + break; + } + + case STBI__PNG_TYPE('I','E','N','D'): { + stbi__uint32 raw_len, bpl; + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (scan != STBI__SCAN_load) return 1; + if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); + // initial guess for decoded data size to avoid unnecessary reallocs + bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component + raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; + z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); + if (z->expanded == NULL) return 0; // zlib should set error + STBI_FREE(z->idata); z->idata = NULL; + if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) + s->img_out_n = s->img_n+1; + else + s->img_out_n = s->img_n; + if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; + if (has_trans) { + if (z->depth == 16) { + if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; + } else { + if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; + } + } + if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) + stbi__de_iphone(z); + if (pal_img_n) { + // pal_img_n == 3 or 4 + s->img_n = pal_img_n; // record the actual colors we had + s->img_out_n = pal_img_n; + if (req_comp >= 3) s->img_out_n = req_comp; + if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) + return 0; + } else if (has_trans) { + // non-paletted image with tRNS -> source image has (constant) alpha + ++s->img_n; + } + STBI_FREE(z->expanded); z->expanded = NULL; + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + return 1; + } + + default: + // if critical, fail + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if ((c.type & (1 << 29)) == 0) { + #ifndef STBI_NO_FAILURE_STRINGS + // not threadsafe + static char invalid_chunk[] = "XXXX PNG chunk not known"; + invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); + invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); + invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); + invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); + #endif + return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); + } + stbi__skip(s, c.length); + break; + } + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + } +} + +static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri) +{ + void *result=NULL; + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { + if (p->depth <= 8) + ri->bits_per_channel = 8; + else if (p->depth == 16) + ri->bits_per_channel = 16; + else + return stbi__errpuc("bad bits_per_channel", "PNG not supported: unsupported color depth"); + result = p->out; + p->out = NULL; + if (req_comp && req_comp != p->s->img_out_n) { + if (ri->bits_per_channel == 8) + result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + else + result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + p->s->img_out_n = req_comp; + if (result == NULL) return result; + } + *x = p->s->img_x; + *y = p->s->img_y; + if (n) *n = p->s->img_n; + } + STBI_FREE(p->out); p->out = NULL; + STBI_FREE(p->expanded); p->expanded = NULL; + STBI_FREE(p->idata); p->idata = NULL; + + return result; +} + +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi__png p; + p.s = s; + return stbi__do_png(&p, x,y,comp,req_comp, ri); +} + +static int stbi__png_test(stbi__context *s) +{ + int r; + r = stbi__check_png_header(s); + stbi__rewind(s); + return r; +} + +static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) +{ + if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { + stbi__rewind( p->s ); + return 0; + } + if (x) *x = p->s->img_x; + if (y) *y = p->s->img_y; + if (comp) *comp = p->s->img_n; + return 1; +} + +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__png p; + p.s = s; + return stbi__png_info_raw(&p, x, y, comp); +} + +static int stbi__png_is16(stbi__context *s) +{ + stbi__png p; + p.s = s; + if (!stbi__png_info_raw(&p, NULL, NULL, NULL)) + return 0; + if (p.depth != 16) { + stbi__rewind(p.s); + return 0; + } + return 1; +} +#endif + +// Microsoft/Windows BMP image + +#ifndef STBI_NO_BMP +static int stbi__bmp_test_raw(stbi__context *s) +{ + int r; + int sz; + if (stbi__get8(s) != 'B') return 0; + if (stbi__get8(s) != 'M') return 0; + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + stbi__get32le(s); // discard data offset + sz = stbi__get32le(s); + r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); + return r; +} + +static int stbi__bmp_test(stbi__context *s) +{ + int r = stbi__bmp_test_raw(s); + stbi__rewind(s); + return r; +} + + +// returns 0..31 for the highest set bit +static int stbi__high_bit(unsigned int z) +{ + int n=0; + if (z == 0) return -1; + if (z >= 0x10000) { n += 16; z >>= 16; } + if (z >= 0x00100) { n += 8; z >>= 8; } + if (z >= 0x00010) { n += 4; z >>= 4; } + if (z >= 0x00004) { n += 2; z >>= 2; } + if (z >= 0x00002) { n += 1;/* >>= 1;*/ } + return n; +} + +static int stbi__bitcount(unsigned int a) +{ + a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 + a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits + a = (a + (a >> 8)); // max 16 per 8 bits + a = (a + (a >> 16)); // max 32 per 8 bits + return a & 0xff; +} + +// extract an arbitrarily-aligned N-bit value (N=bits) +// from v, and then make it 8-bits long and fractionally +// extend it to full full range. +static int stbi__shiftsigned(unsigned int v, int shift, int bits) +{ + static unsigned int mul_table[9] = { + 0, + 0xff/*0b11111111*/, 0x55/*0b01010101*/, 0x49/*0b01001001*/, 0x11/*0b00010001*/, + 0x21/*0b00100001*/, 0x41/*0b01000001*/, 0x81/*0b10000001*/, 0x01/*0b00000001*/, + }; + static unsigned int shift_table[9] = { + 0, 0,0,1,0,2,4,6,0, + }; + if (shift < 0) + v <<= -shift; + else + v >>= shift; + STBI_ASSERT(v < 256); + v >>= (8-bits); + STBI_ASSERT(bits >= 0 && bits <= 8); + return (int) ((unsigned) v * mul_table[bits]) >> shift_table[bits]; +} + +typedef struct +{ + int bpp, offset, hsz; + unsigned int mr,mg,mb,ma, all_a; + int extra_read; +} stbi__bmp_data; + +static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) +{ + int hsz; + if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + info->offset = stbi__get32le(s); + info->hsz = hsz = stbi__get32le(s); + info->mr = info->mg = info->mb = info->ma = 0; + info->extra_read = 14; + + if (info->offset < 0) return stbi__errpuc("bad BMP", "bad BMP"); + + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); + if (hsz == 12) { + s->img_x = stbi__get16le(s); + s->img_y = stbi__get16le(s); + } else { + s->img_x = stbi__get32le(s); + s->img_y = stbi__get32le(s); + } + if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); + info->bpp = stbi__get16le(s); + if (hsz != 12) { + int compress = stbi__get32le(s); + if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); + stbi__get32le(s); // discard sizeof + stbi__get32le(s); // discard hres + stbi__get32le(s); // discard vres + stbi__get32le(s); // discard colorsused + stbi__get32le(s); // discard max important + if (hsz == 40 || hsz == 56) { + if (hsz == 56) { + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + } + if (info->bpp == 16 || info->bpp == 32) { + if (compress == 0) { + if (info->bpp == 32) { + info->mr = 0xffu << 16; + info->mg = 0xffu << 8; + info->mb = 0xffu << 0; + info->ma = 0xffu << 24; + info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 + } else { + info->mr = 31u << 10; + info->mg = 31u << 5; + info->mb = 31u << 0; + } + } else if (compress == 3) { + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->extra_read += 12; + // not documented, but generated by photoshop and handled by mspaint + if (info->mr == info->mg && info->mg == info->mb) { + // ?!?!? + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else { + int i; + if (hsz != 108 && hsz != 124) + return stbi__errpuc("bad BMP", "bad BMP"); + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->ma = stbi__get32le(s); + stbi__get32le(s); // discard color space + for (i=0; i < 12; ++i) + stbi__get32le(s); // discard color space parameters + if (hsz == 124) { + stbi__get32le(s); // discard rendering intent + stbi__get32le(s); // discard offset of profile data + stbi__get32le(s); // discard size of profile data + stbi__get32le(s); // discard reserved + } + } + } + return (void *) 1; +} + + +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + unsigned int mr=0,mg=0,mb=0,ma=0, all_a; + stbi_uc pal[256][4]; + int psize=0,i,j,width; + int flip_vertically, pad, target; + stbi__bmp_data info; + STBI_NOTUSED(ri); + + info.all_a = 255; + if (stbi__bmp_parse_header(s, &info) == NULL) + return NULL; // error code already set + + flip_vertically = ((int) s->img_y) > 0; + s->img_y = abs((int) s->img_y); + + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + mr = info.mr; + mg = info.mg; + mb = info.mb; + ma = info.ma; + all_a = info.all_a; + + if (info.hsz == 12) { + if (info.bpp < 24) + psize = (info.offset - info.extra_read - 24) / 3; + } else { + if (info.bpp < 16) + psize = (info.offset - info.extra_read - info.hsz) >> 2; + } + if (psize == 0) { + STBI_ASSERT(info.offset == s->callback_already_read + (int) (s->img_buffer - s->img_buffer_original)); + if (info.offset != s->callback_already_read + (s->img_buffer - s->buffer_start)) { + return stbi__errpuc("bad offset", "Corrupt BMP"); + } + } + + if (info.bpp == 24 && ma == 0xff000000) + s->img_n = 3; + else + s->img_n = ma ? 4 : 3; + if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 + target = req_comp; + else + target = s->img_n; // if they want monochrome, we'll post-convert + + // sanity-check size + if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0)) + return stbi__errpuc("too large", "Corrupt BMP"); + + out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (info.bpp < 16) { + int z=0; + if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } + for (i=0; i < psize; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + if (info.hsz != 12) stbi__get8(s); + pal[i][3] = 255; + } + stbi__skip(s, info.offset - info.extra_read - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); + if (info.bpp == 1) width = (s->img_x + 7) >> 3; + else if (info.bpp == 4) width = (s->img_x + 1) >> 1; + else if (info.bpp == 8) width = s->img_x; + else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } + pad = (-width)&3; + if (info.bpp == 1) { + for (j=0; j < (int) s->img_y; ++j) { + int bit_offset = 7, v = stbi__get8(s); + for (i=0; i < (int) s->img_x; ++i) { + int color = (v>>bit_offset)&0x1; + out[z++] = pal[color][0]; + out[z++] = pal[color][1]; + out[z++] = pal[color][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + if((--bit_offset) < 0) { + bit_offset = 7; + v = stbi__get8(s); + } + } + stbi__skip(s, pad); + } + } else { + for (j=0; j < (int) s->img_y; ++j) { + for (i=0; i < (int) s->img_x; i += 2) { + int v=stbi__get8(s),v2=0; + if (info.bpp == 4) { + v2 = v & 15; + v >>= 4; + } + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + v = (info.bpp == 8) ? stbi__get8(s) : v2; + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + } + stbi__skip(s, pad); + } + } + } else { + int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; + int z = 0; + int easy=0; + stbi__skip(s, info.offset - info.extra_read - info.hsz); + if (info.bpp == 24) width = 3 * s->img_x; + else if (info.bpp == 16) width = 2*s->img_x; + else /* bpp = 32 and pad = 0 */ width=0; + pad = (-width) & 3; + if (info.bpp == 24) { + easy = 1; + } else if (info.bpp == 32) { + if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) + easy = 2; + } + if (!easy) { + if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + // right shift amt to put high bit in position #7 + rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); + gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); + bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); + ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); + if (rcount > 8 || gcount > 8 || bcount > 8 || acount > 8) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + } + for (j=0; j < (int) s->img_y; ++j) { + if (easy) { + for (i=0; i < (int) s->img_x; ++i) { + unsigned char a; + out[z+2] = stbi__get8(s); + out[z+1] = stbi__get8(s); + out[z+0] = stbi__get8(s); + z += 3; + a = (easy == 2 ? stbi__get8(s) : 255); + all_a |= a; + if (target == 4) out[z++] = a; + } + } else { + int bpp = info.bpp; + for (i=0; i < (int) s->img_x; ++i) { + stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); + unsigned int a; + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); + a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); + all_a |= a; + if (target == 4) out[z++] = STBI__BYTECAST(a); + } + } + stbi__skip(s, pad); + } + } + + // if alpha channel is all 0s, replace with all 255s + if (target == 4 && all_a == 0) + for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) + out[i] = 255; + + if (flip_vertically) { + stbi_uc t; + for (j=0; j < (int) s->img_y>>1; ++j) { + stbi_uc *p1 = out + j *s->img_x*target; + stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; + for (i=0; i < (int) s->img_x*target; ++i) { + t = p1[i]; p1[i] = p2[i]; p2[i] = t; + } + } + } + + if (req_comp && req_comp != target) { + out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + return out; +} +#endif + +// Targa Truevision - TGA +// by Jonathan Dummer +#ifndef STBI_NO_TGA +// returns STBI_rgb or whatever, 0 on error +static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) +{ + // only RGB or RGBA (incl. 16bit) or grey allowed + if (is_rgb16) *is_rgb16 = 0; + switch(bits_per_pixel) { + case 8: return STBI_grey; + case 16: if(is_grey) return STBI_grey_alpha; + // fallthrough + case 15: if(is_rgb16) *is_rgb16 = 1; + return STBI_rgb; + case 24: // fallthrough + case 32: return bits_per_pixel/8; + default: return 0; + } +} + +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) +{ + int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; + int sz, tga_colormap_type; + stbi__get8(s); // discard Offset + tga_colormap_type = stbi__get8(s); // colormap type + if( tga_colormap_type > 1 ) { + stbi__rewind(s); + return 0; // only RGB or indexed allowed + } + tga_image_type = stbi__get8(s); // image type + if ( tga_colormap_type == 1 ) { // colormapped (paletted) image + if (tga_image_type != 1 && tga_image_type != 9) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip image x and y origin + tga_colormap_bpp = sz; + } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE + if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { + stbi__rewind(s); + return 0; // only RGB or grey allowed, +/- RLE + } + stbi__skip(s,9); // skip colormap specification and image x/y origin + tga_colormap_bpp = 0; + } + tga_w = stbi__get16le(s); + if( tga_w < 1 ) { + stbi__rewind(s); + return 0; // test width + } + tga_h = stbi__get16le(s); + if( tga_h < 1 ) { + stbi__rewind(s); + return 0; // test height + } + tga_bits_per_pixel = stbi__get8(s); // bits per pixel + stbi__get8(s); // ignore alpha bits + if (tga_colormap_bpp != 0) { + if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { + // when using a colormap, tga_bits_per_pixel is the size of the indexes + // I don't think anything but 8 or 16bit indexes makes sense + stbi__rewind(s); + return 0; + } + tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); + } else { + tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); + } + if(!tga_comp) { + stbi__rewind(s); + return 0; + } + if (x) *x = tga_w; + if (y) *y = tga_h; + if (comp) *comp = tga_comp; + return 1; // seems to have passed everything +} + +static int stbi__tga_test(stbi__context *s) +{ + int res = 0; + int sz, tga_color_type; + stbi__get8(s); // discard Offset + tga_color_type = stbi__get8(s); // color type + if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed + sz = stbi__get8(s); // image type + if ( tga_color_type == 1 ) { // colormapped (paletted) image + if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + stbi__skip(s,4); // skip image x and y origin + } else { // "normal" image w/o colormap + if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE + stbi__skip(s,9); // skip colormap specification and image x/y origin + } + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height + sz = stbi__get8(s); // bits per pixel + if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + + res = 1; // if we got this far, everything's good and we can return 1 instead of 0 + +errorEnd: + stbi__rewind(s); + return res; +} + +// read 16bit value and convert to 24bit RGB +static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) +{ + stbi__uint16 px = (stbi__uint16)stbi__get16le(s); + stbi__uint16 fiveBitMask = 31; + // we have 3 channels with 5bits each + int r = (px >> 10) & fiveBitMask; + int g = (px >> 5) & fiveBitMask; + int b = px & fiveBitMask; + // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later + out[0] = (stbi_uc)((r * 255)/31); + out[1] = (stbi_uc)((g * 255)/31); + out[2] = (stbi_uc)((b * 255)/31); + + // some people claim that the most significant bit might be used for alpha + // (possibly if an alpha-bit is set in the "image descriptor byte") + // but that only made 16bit test images completely translucent.. + // so let's treat all 15 and 16bit TGAs as RGB with no alpha. +} + +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + // read in the TGA header stuff + int tga_offset = stbi__get8(s); + int tga_indexed = stbi__get8(s); + int tga_image_type = stbi__get8(s); + int tga_is_RLE = 0; + int tga_palette_start = stbi__get16le(s); + int tga_palette_len = stbi__get16le(s); + int tga_palette_bits = stbi__get8(s); + int tga_x_origin = stbi__get16le(s); + int tga_y_origin = stbi__get16le(s); + int tga_width = stbi__get16le(s); + int tga_height = stbi__get16le(s); + int tga_bits_per_pixel = stbi__get8(s); + int tga_comp, tga_rgb16=0; + int tga_inverted = stbi__get8(s); + // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) + // image data + unsigned char *tga_data; + unsigned char *tga_palette = NULL; + int i, j; + unsigned char raw_data[4] = {0}; + int RLE_count = 0; + int RLE_repeating = 0; + int read_next_pixel = 1; + STBI_NOTUSED(ri); + STBI_NOTUSED(tga_x_origin); // @TODO + STBI_NOTUSED(tga_y_origin); // @TODO + + if (tga_height > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (tga_width > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + // do a tiny bit of precessing + if ( tga_image_type >= 8 ) + { + tga_image_type -= 8; + tga_is_RLE = 1; + } + tga_inverted = 1 - ((tga_inverted >> 5) & 1); + + // If I'm paletted, then I'll use the number of bits from the palette + if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); + else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); + + if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency + return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); + + // tga info + *x = tga_width; + *y = tga_height; + if (comp) *comp = tga_comp; + + if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0)) + return stbi__errpuc("too large", "Corrupt TGA"); + + tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0); + if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); + + // skip to the data's starting position (offset usually = 0) + stbi__skip(s, tga_offset ); + + if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { + for (i=0; i < tga_height; ++i) { + int row = tga_inverted ? tga_height -i - 1 : i; + stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; + stbi__getn(s, tga_row, tga_width * tga_comp); + } + } else { + // do I need to load a palette? + if ( tga_indexed) + { + if (tga_palette_len == 0) { /* you have to have at least one entry! */ + STBI_FREE(tga_data); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + + // any data to skip? (offset usually = 0) + stbi__skip(s, tga_palette_start ); + // load the palette + tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0); + if (!tga_palette) { + STBI_FREE(tga_data); + return stbi__errpuc("outofmem", "Out of memory"); + } + if (tga_rgb16) { + stbi_uc *pal_entry = tga_palette; + STBI_ASSERT(tga_comp == STBI_rgb); + for (i=0; i < tga_palette_len; ++i) { + stbi__tga_read_rgb16(s, pal_entry); + pal_entry += tga_comp; + } + } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { + STBI_FREE(tga_data); + STBI_FREE(tga_palette); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + } + // load the data + for (i=0; i < tga_width * tga_height; ++i) + { + // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? + if ( tga_is_RLE ) + { + if ( RLE_count == 0 ) + { + // yep, get the next byte as a RLE command + int RLE_cmd = stbi__get8(s); + RLE_count = 1 + (RLE_cmd & 127); + RLE_repeating = RLE_cmd >> 7; + read_next_pixel = 1; + } else if ( !RLE_repeating ) + { + read_next_pixel = 1; + } + } else + { + read_next_pixel = 1; + } + // OK, if I need to read a pixel, do it now + if ( read_next_pixel ) + { + // load however much data we did have + if ( tga_indexed ) + { + // read in index, then perform the lookup + int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); + if ( pal_idx >= tga_palette_len ) { + // invalid index + pal_idx = 0; + } + pal_idx *= tga_comp; + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = tga_palette[pal_idx+j]; + } + } else if(tga_rgb16) { + STBI_ASSERT(tga_comp == STBI_rgb); + stbi__tga_read_rgb16(s, raw_data); + } else { + // read in the data raw + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = stbi__get8(s); + } + } + // clear the reading flag for the next pixel + read_next_pixel = 0; + } // end of reading a pixel + + // copy data + for (j = 0; j < tga_comp; ++j) + tga_data[i*tga_comp+j] = raw_data[j]; + + // in case we're in RLE mode, keep counting down + --RLE_count; + } + // do I need to invert the image? + if ( tga_inverted ) + { + for (j = 0; j*2 < tga_height; ++j) + { + int index1 = j * tga_width * tga_comp; + int index2 = (tga_height - 1 - j) * tga_width * tga_comp; + for (i = tga_width * tga_comp; i > 0; --i) + { + unsigned char temp = tga_data[index1]; + tga_data[index1] = tga_data[index2]; + tga_data[index2] = temp; + ++index1; + ++index2; + } + } + } + // clear my palette, if I had one + if ( tga_palette != NULL ) + { + STBI_FREE( tga_palette ); + } + } + + // swap RGB - if the source data was RGB16, it already is in the right order + if (tga_comp >= 3 && !tga_rgb16) + { + unsigned char* tga_pixel = tga_data; + for (i=0; i < tga_width * tga_height; ++i) + { + unsigned char temp = tga_pixel[0]; + tga_pixel[0] = tga_pixel[2]; + tga_pixel[2] = temp; + tga_pixel += tga_comp; + } + } + + // convert to target component count + if (req_comp && req_comp != tga_comp) + tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); + + // the things I do to get rid of an error message, and yet keep + // Microsoft's C compilers happy... [8^( + tga_palette_start = tga_palette_len = tga_palette_bits = + tga_x_origin = tga_y_origin = 0; + STBI_NOTUSED(tga_palette_start); + // OK, done + return tga_data; +} +#endif + +// ************************************************************************************************* +// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s) +{ + int r = (stbi__get32be(s) == 0x38425053); + stbi__rewind(s); + return r; +} + +static int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount) +{ + int count, nleft, len; + + count = 0; + while ((nleft = pixelCount - count) > 0) { + len = stbi__get8(s); + if (len == 128) { + // No-op. + } else if (len < 128) { + // Copy next len+1 bytes literally. + len++; + if (len > nleft) return 0; // corrupt data + count += len; + while (len) { + *p = stbi__get8(s); + p += 4; + len--; + } + } else if (len > 128) { + stbi_uc val; + // Next -len+1 bytes in the dest are replicated from next source byte. + // (Interpret len as a negative 8-bit int.) + len = 257 - len; + if (len > nleft) return 0; // corrupt data + val = stbi__get8(s); + count += len; + while (len) { + *p = val; + p += 4; + len--; + } + } + } + + return 1; +} + +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + int pixelCount; + int channelCount, compression; + int channel, i; + int bitdepth; + int w,h; + stbi_uc *out; + STBI_NOTUSED(ri); + + // Check identifier + if (stbi__get32be(s) != 0x38425053) // "8BPS" + return stbi__errpuc("not PSD", "Corrupt PSD image"); + + // Check file type version. + if (stbi__get16be(s) != 1) + return stbi__errpuc("wrong version", "Unsupported version of PSD image"); + + // Skip 6 reserved bytes. + stbi__skip(s, 6 ); + + // Read the number of channels (R, G, B, A, etc). + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) + return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); + + // Read the rows and columns of the image. + h = stbi__get32be(s); + w = stbi__get32be(s); + + if (h > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (w > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + // Make sure the depth is 8 bits. + bitdepth = stbi__get16be(s); + if (bitdepth != 8 && bitdepth != 16) + return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); + + // Make sure the color mode is RGB. + // Valid options are: + // 0: Bitmap + // 1: Grayscale + // 2: Indexed color + // 3: RGB color + // 4: CMYK color + // 7: Multichannel + // 8: Duotone + // 9: Lab color + if (stbi__get16be(s) != 3) + return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); + + // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) + stbi__skip(s,stbi__get32be(s) ); + + // Skip the image resources. (resolution, pen tool paths, etc) + stbi__skip(s, stbi__get32be(s) ); + + // Skip the reserved data. + stbi__skip(s, stbi__get32be(s) ); + + // Find out if the data is compressed. + // Known values: + // 0: no compression + // 1: RLE compressed + compression = stbi__get16be(s); + if (compression > 1) + return stbi__errpuc("bad compression", "PSD has an unknown compression format"); + + // Check size + if (!stbi__mad3sizes_valid(4, w, h, 0)) + return stbi__errpuc("too large", "Corrupt PSD"); + + // Create the destination image. + + if (!compression && bitdepth == 16 && bpc == 16) { + out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0); + ri->bits_per_channel = 16; + } else + out = (stbi_uc *) stbi__malloc(4 * w*h); + + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + pixelCount = w*h; + + // Initialize the data to zero. + //memset( out, 0, pixelCount * 4 ); + + // Finally, the image data. + if (compression) { + // RLE as used by .PSD and .TIFF + // Loop until you get the number of unpacked bytes you are expecting: + // Read the next source byte into n. + // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. + // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. + // Else if n is 128, noop. + // Endloop + + // The RLE-compressed data is preceded by a 2-byte data count for each row in the data, + // which we're going to just skip. + stbi__skip(s, h * channelCount * 2 ); + + // Read the RLE data by channel. + for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out+channel; + if (channel >= channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++, p += 4) + *p = (channel == 3 ? 255 : 0); + } else { + // Read the RLE data. + if (!stbi__psd_decode_rle(s, p, pixelCount)) { + STBI_FREE(out); + return stbi__errpuc("corrupt", "bad RLE data"); + } + } + } + + } else { + // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) + // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image. + + // Read the data by channel. + for (channel = 0; channel < 4; channel++) { + if (channel >= channelCount) { + // Fill this channel with default data. + if (bitdepth == 16 && bpc == 16) { + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + stbi__uint16 val = channel == 3 ? 65535 : 0; + for (i = 0; i < pixelCount; i++, q += 4) + *q = val; + } else { + stbi_uc *p = out+channel; + stbi_uc val = channel == 3 ? 255 : 0; + for (i = 0; i < pixelCount; i++, p += 4) + *p = val; + } + } else { + if (ri->bits_per_channel == 16) { // output bpc + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + for (i = 0; i < pixelCount; i++, q += 4) + *q = (stbi__uint16) stbi__get16be(s); + } else { + stbi_uc *p = out+channel; + if (bitdepth == 16) { // input bpc + for (i = 0; i < pixelCount; i++, p += 4) + *p = (stbi_uc) (stbi__get16be(s) >> 8); + } else { + for (i = 0; i < pixelCount; i++, p += 4) + *p = stbi__get8(s); + } + } + } + } + } + + // remove weird white matte from PSD + if (channelCount >= 4) { + if (ri->bits_per_channel == 16) { + for (i=0; i < w*h; ++i) { + stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i; + if (pixel[3] != 0 && pixel[3] != 65535) { + float a = pixel[3] / 65535.0f; + float ra = 1.0f / a; + float inv_a = 65535.0f * (1 - ra); + pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a); + pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a); + pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a); + } + } + } else { + for (i=0; i < w*h; ++i) { + unsigned char *pixel = out + 4*i; + if (pixel[3] != 0 && pixel[3] != 255) { + float a = pixel[3] / 255.0f; + float ra = 1.0f / a; + float inv_a = 255.0f * (1 - ra); + pixel[0] = (unsigned char) (pixel[0]*ra + inv_a); + pixel[1] = (unsigned char) (pixel[1]*ra + inv_a); + pixel[2] = (unsigned char) (pixel[2]*ra + inv_a); + } + } + } + } + + // convert to desired output format + if (req_comp && req_comp != 4) { + if (ri->bits_per_channel == 16) + out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h); + else + out = stbi__convert_format(out, 4, req_comp, w, h); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + if (comp) *comp = 4; + *y = h; + *x = w; + + return out; +} +#endif + +// ************************************************************************************************* +// Softimage PIC loader +// by Tom Seddon +// +// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format +// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ + +#ifndef STBI_NO_PIC +static int stbi__pic_is4(stbi__context *s,const char *str) +{ + int i; + for (i=0; i<4; ++i) + if (stbi__get8(s) != (stbi_uc)str[i]) + return 0; + + return 1; +} + +static int stbi__pic_test_core(stbi__context *s) +{ + int i; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) + return 0; + + for(i=0;i<84;++i) + stbi__get8(s); + + if (!stbi__pic_is4(s,"PICT")) + return 0; + + return 1; +} + +typedef struct +{ + stbi_uc size,type,channel; +} stbi__pic_packet; + +static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) +{ + int mask=0x80, i; + + for (i=0; i<4; ++i, mask>>=1) { + if (channel & mask) { + if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); + dest[i]=stbi__get8(s); + } + } + + return dest; +} + +static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) +{ + int mask=0x80,i; + + for (i=0;i<4; ++i, mask>>=1) + if (channel&mask) + dest[i]=src[i]; +} + +static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) +{ + int act_comp=0,num_packets=0,y,chained; + stbi__pic_packet packets[10]; + + // this will (should...) cater for even some bizarre stuff like having data + // for the same channel in multiple packets. + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return stbi__errpuc("bad format","too many packets"); + + packet = &packets[num_packets++]; + + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + + act_comp |= packet->channel; + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); + if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? + + for(y=0; ytype) { + default: + return stbi__errpuc("bad format","packet has bad compression type"); + + case 0: {//uncompressed + int x; + + for(x=0;xchannel,dest)) + return 0; + break; + } + + case 1://Pure RLE + { + int left=width, i; + + while (left>0) { + stbi_uc count,value[4]; + + count=stbi__get8(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); + + if (count > left) + count = (stbi_uc) left; + + if (!stbi__readval(s,packet->channel,value)) return 0; + + for(i=0; ichannel,dest,value); + left -= count; + } + } + break; + + case 2: {//Mixed RLE + int left=width; + while (left>0) { + int count = stbi__get8(s), i; + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); + + if (count >= 128) { // Repeated + stbi_uc value[4]; + + if (count==128) + count = stbi__get16be(s); + else + count -= 127; + if (count > left) + return stbi__errpuc("bad file","scanline overrun"); + + if (!stbi__readval(s,packet->channel,value)) + return 0; + + for(i=0;ichannel,dest,value); + } else { // Raw + ++count; + if (count>left) return stbi__errpuc("bad file","scanline overrun"); + + for(i=0;ichannel,dest)) + return 0; + } + left-=count; + } + break; + } + } + } + } + + return result; +} + +static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri) +{ + stbi_uc *result; + int i, x,y, internal_comp; + STBI_NOTUSED(ri); + + if (!comp) comp = &internal_comp; + + for (i=0; i<92; ++i) + stbi__get8(s); + + x = stbi__get16be(s); + y = stbi__get16be(s); + + if (y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); + if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode"); + + stbi__get32be(s); //skip `ratio' + stbi__get16be(s); //skip `fields' + stbi__get16be(s); //skip `pad' + + // intermediate buffer is RGBA + result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0); + memset(result, 0xff, x*y*4); + + if (!stbi__pic_load_core(s,x,y,comp, result)) { + STBI_FREE(result); + result=0; + } + *px = x; + *py = y; + if (req_comp == 0) req_comp = *comp; + result=stbi__convert_format(result,4,req_comp,x,y); + + return result; +} + +static int stbi__pic_test(stbi__context *s) +{ + int r = stbi__pic_test_core(s); + stbi__rewind(s); + return r; +} +#endif + +// ************************************************************************************************* +// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb + +#ifndef STBI_NO_GIF +typedef struct +{ + stbi__int16 prefix; + stbi_uc first; + stbi_uc suffix; +} stbi__gif_lzw; + +typedef struct +{ + int w,h; + stbi_uc *out; // output buffer (always 4 components) + stbi_uc *background; // The current "background" as far as a gif is concerned + stbi_uc *history; + int flags, bgindex, ratio, transparent, eflags; + stbi_uc pal[256][4]; + stbi_uc lpal[256][4]; + stbi__gif_lzw codes[8192]; + stbi_uc *color_table; + int parse, step; + int lflags; + int start_x, start_y; + int max_x, max_y; + int cur_x, cur_y; + int line_size; + int delay; +} stbi__gif; + +static int stbi__gif_test_raw(stbi__context *s) +{ + int sz; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; + sz = stbi__get8(s); + if (sz != '9' && sz != '7') return 0; + if (stbi__get8(s) != 'a') return 0; + return 1; +} + +static int stbi__gif_test(stbi__context *s) +{ + int r = stbi__gif_test_raw(s); + stbi__rewind(s); + return r; +} + +static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) +{ + int i; + for (i=0; i < num_entries; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + pal[i][3] = transp == i ? 0 : 255; + } +} + +static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) +{ + stbi_uc version; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') + return stbi__err("not GIF", "Corrupt GIF"); + + version = stbi__get8(s); + if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); + if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); + + stbi__g_failure_reason = ""; + g->w = stbi__get16le(s); + g->h = stbi__get16le(s); + g->flags = stbi__get8(s); + g->bgindex = stbi__get8(s); + g->ratio = stbi__get8(s); + g->transparent = -1; + + if (g->w > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (g->h > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + + if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments + + if (is_info) return 1; + + if (g->flags & 0x80) + stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); + + return 1; +} + +static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); + if (!stbi__gif_header(s, g, comp, 1)) { + STBI_FREE(g); + stbi__rewind( s ); + return 0; + } + if (x) *x = g->w; + if (y) *y = g->h; + STBI_FREE(g); + return 1; +} + +static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) +{ + stbi_uc *p, *c; + int idx; + + // recurse to decode the prefixes, since the linked-list is backwards, + // and working backwards through an interleaved image would be nasty + if (g->codes[code].prefix >= 0) + stbi__out_gif_code(g, g->codes[code].prefix); + + if (g->cur_y >= g->max_y) return; + + idx = g->cur_x + g->cur_y; + p = &g->out[idx]; + g->history[idx / 4] = 1; + + c = &g->color_table[g->codes[code].suffix * 4]; + if (c[3] > 128) { // don't render transparent pixels; + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = c[3]; + } + g->cur_x += 4; + + if (g->cur_x >= g->max_x) { + g->cur_x = g->start_x; + g->cur_y += g->step; + + while (g->cur_y >= g->max_y && g->parse > 0) { + g->step = (1 << g->parse) * g->line_size; + g->cur_y = g->start_y + (g->step >> 1); + --g->parse; + } + } +} + +static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) +{ + stbi_uc lzw_cs; + stbi__int32 len, init_code; + stbi__uint32 first; + stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; + stbi__gif_lzw *p; + + lzw_cs = stbi__get8(s); + if (lzw_cs > 12) return NULL; + clear = 1 << lzw_cs; + first = 1; + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + bits = 0; + valid_bits = 0; + for (init_code = 0; init_code < clear; init_code++) { + g->codes[init_code].prefix = -1; + g->codes[init_code].first = (stbi_uc) init_code; + g->codes[init_code].suffix = (stbi_uc) init_code; + } + + // support no starting clear code + avail = clear+2; + oldcode = -1; + + len = 0; + for(;;) { + if (valid_bits < codesize) { + if (len == 0) { + len = stbi__get8(s); // start new block + if (len == 0) + return g->out; + } + --len; + bits |= (stbi__int32) stbi__get8(s) << valid_bits; + valid_bits += 8; + } else { + stbi__int32 code = bits & codemask; + bits >>= codesize; + valid_bits -= codesize; + // @OPTIMIZE: is there some way we can accelerate the non-clear path? + if (code == clear) { // clear code + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + avail = clear + 2; + oldcode = -1; + first = 0; + } else if (code == clear + 1) { // end of stream code + stbi__skip(s, len); + while ((len = stbi__get8(s)) > 0) + stbi__skip(s,len); + return g->out; + } else if (code <= avail) { + if (first) { + return stbi__errpuc("no clear code", "Corrupt GIF"); + } + + if (oldcode >= 0) { + p = &g->codes[avail++]; + if (avail > 8192) { + return stbi__errpuc("too many codes", "Corrupt GIF"); + } + + p->prefix = (stbi__int16) oldcode; + p->first = g->codes[oldcode].first; + p->suffix = (code == avail) ? p->first : g->codes[code].first; + } else if (code == avail) + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + + stbi__out_gif_code(g, (stbi__uint16) code); + + if ((avail & codemask) == 0 && avail <= 0x0FFF) { + codesize++; + codemask = (1 << codesize) - 1; + } + + oldcode = code; + } else { + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + } + } + } +} + +// this function is designed to support animated gifs, although stb_image doesn't support it +// two back is the image from two frames ago, used for a very specific disposal format +static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back) +{ + int dispose; + int first_frame; + int pi; + int pcount; + STBI_NOTUSED(req_comp); + + // on first frame, any non-written pixels get the background colour (non-transparent) + first_frame = 0; + if (g->out == 0) { + if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header + if (!stbi__mad3sizes_valid(4, g->w, g->h, 0)) + return stbi__errpuc("too large", "GIF image is too large"); + pcount = g->w * g->h; + g->out = (stbi_uc *) stbi__malloc(4 * pcount); + g->background = (stbi_uc *) stbi__malloc(4 * pcount); + g->history = (stbi_uc *) stbi__malloc(pcount); + if (!g->out || !g->background || !g->history) + return stbi__errpuc("outofmem", "Out of memory"); + + // image is treated as "transparent" at the start - ie, nothing overwrites the current background; + // background colour is only used for pixels that are not rendered first frame, after that "background" + // color refers to the color that was there the previous frame. + memset(g->out, 0x00, 4 * pcount); + memset(g->background, 0x00, 4 * pcount); // state of the background (starts transparent) + memset(g->history, 0x00, pcount); // pixels that were affected previous frame + first_frame = 1; + } else { + // second frame - how do we dispose of the previous one? + dispose = (g->eflags & 0x1C) >> 2; + pcount = g->w * g->h; + + if ((dispose == 3) && (two_back == 0)) { + dispose = 2; // if I don't have an image to revert back to, default to the old background + } + + if (dispose == 3) { // use previous graphic + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 ); + } + } + } else if (dispose == 2) { + // restore what was changed last frame to background before that frame; + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 ); + } + } + } else { + // This is a non-disposal case eithe way, so just + // leave the pixels as is, and they will become the new background + // 1: do not dispose + // 0: not specified. + } + + // background is what out is after the undoing of the previou frame; + memcpy( g->background, g->out, 4 * g->w * g->h ); + } + + // clear my history; + memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame + + for (;;) { + int tag = stbi__get8(s); + switch (tag) { + case 0x2C: /* Image Descriptor */ + { + stbi__int32 x, y, w, h; + stbi_uc *o; + + x = stbi__get16le(s); + y = stbi__get16le(s); + w = stbi__get16le(s); + h = stbi__get16le(s); + if (((x + w) > (g->w)) || ((y + h) > (g->h))) + return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); + + g->line_size = g->w * 4; + g->start_x = x * 4; + g->start_y = y * g->line_size; + g->max_x = g->start_x + w * 4; + g->max_y = g->start_y + h * g->line_size; + g->cur_x = g->start_x; + g->cur_y = g->start_y; + + // if the width of the specified rectangle is 0, that means + // we may not see *any* pixels or the image is malformed; + // to make sure this is caught, move the current y down to + // max_y (which is what out_gif_code checks). + if (w == 0) + g->cur_y = g->max_y; + + g->lflags = stbi__get8(s); + + if (g->lflags & 0x40) { + g->step = 8 * g->line_size; // first interlaced spacing + g->parse = 3; + } else { + g->step = g->line_size; + g->parse = 0; + } + + if (g->lflags & 0x80) { + stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); + g->color_table = (stbi_uc *) g->lpal; + } else if (g->flags & 0x80) { + g->color_table = (stbi_uc *) g->pal; + } else + return stbi__errpuc("missing color table", "Corrupt GIF"); + + o = stbi__process_gif_raster(s, g); + if (!o) return NULL; + + // if this was the first frame, + pcount = g->w * g->h; + if (first_frame && (g->bgindex > 0)) { + // if first frame, any pixel not drawn to gets the background color + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi] == 0) { + g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be; + memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 ); + } + } + } + + return o; + } + + case 0x21: // Comment Extension. + { + int len; + int ext = stbi__get8(s); + if (ext == 0xF9) { // Graphic Control Extension. + len = stbi__get8(s); + if (len == 4) { + g->eflags = stbi__get8(s); + g->delay = 10 * stbi__get16le(s); // delay - 1/100th of a second, saving as 1/1000ths. + + // unset old transparent + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 255; + } + if (g->eflags & 0x01) { + g->transparent = stbi__get8(s); + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 0; + } + } else { + // don't need transparent + stbi__skip(s, 1); + g->transparent = -1; + } + } else { + stbi__skip(s, len); + break; + } + } + while ((len = stbi__get8(s)) != 0) { + stbi__skip(s, len); + } + break; + } + + case 0x3B: // gif stream termination code + return (stbi_uc *) s; // using '1' causes warning on some compilers + + default: + return stbi__errpuc("unknown code", "Corrupt GIF"); + } + } +} + +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + if (stbi__gif_test(s)) { + int layers = 0; + stbi_uc *u = 0; + stbi_uc *out = 0; + stbi_uc *two_back = 0; + stbi__gif g; + int stride; + int out_size = 0; + int delays_size = 0; + memset(&g, 0, sizeof(g)); + if (delays) { + *delays = 0; + } + + do { + u = stbi__gif_load_next(s, &g, comp, req_comp, two_back); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + + if (u) { + *x = g.w; + *y = g.h; + ++layers; + stride = g.w * g.h * 4; + + if (out) { + void *tmp = (stbi_uc*) STBI_REALLOC_SIZED( out, out_size, layers * stride ); + if (NULL == tmp) { + STBI_FREE(g.out); + STBI_FREE(g.history); + STBI_FREE(g.background); + return stbi__errpuc("outofmem", "Out of memory"); + } + else { + out = (stbi_uc*) tmp; + out_size = layers * stride; + } + + if (delays) { + *delays = (int*) STBI_REALLOC_SIZED( *delays, delays_size, sizeof(int) * layers ); + delays_size = layers * sizeof(int); + } + } else { + out = (stbi_uc*)stbi__malloc( layers * stride ); + out_size = layers * stride; + if (delays) { + *delays = (int*) stbi__malloc( layers * sizeof(int) ); + delays_size = layers * sizeof(int); + } + } + memcpy( out + ((layers - 1) * stride), u, stride ); + if (layers >= 2) { + two_back = out - 2 * stride; + } + + if (delays) { + (*delays)[layers - 1U] = g.delay; + } + } + } while (u != 0); + + // free temp buffer; + STBI_FREE(g.out); + STBI_FREE(g.history); + STBI_FREE(g.background); + + // do the final conversion after loading everything; + if (req_comp && req_comp != 4) + out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h); + + *z = layers; + return out; + } else { + return stbi__errpuc("not GIF", "Image was not as a gif type."); + } +} + +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *u = 0; + stbi__gif g; + memset(&g, 0, sizeof(g)); + STBI_NOTUSED(ri); + + u = stbi__gif_load_next(s, &g, comp, req_comp, 0); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + if (u) { + *x = g.w; + *y = g.h; + + // moved conversion to after successful load so that the same + // can be done for multiple frames. + if (req_comp && req_comp != 4) + u = stbi__convert_format(u, 4, req_comp, g.w, g.h); + } else if (g.out) { + // if there was an error and we allocated an image buffer, free it! + STBI_FREE(g.out); + } + + // free buffers needed for multiple frame loading; + STBI_FREE(g.history); + STBI_FREE(g.background); + + return u; +} + +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) +{ + return stbi__gif_info_raw(s,x,y,comp); +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR loader +// originally by Nicolas Schulz +#ifndef STBI_NO_HDR +static int stbi__hdr_test_core(stbi__context *s, const char *signature) +{ + int i; + for (i=0; signature[i]; ++i) + if (stbi__get8(s) != signature[i]) + return 0; + stbi__rewind(s); + return 1; +} + +static int stbi__hdr_test(stbi__context* s) +{ + int r = stbi__hdr_test_core(s, "#?RADIANCE\n"); + stbi__rewind(s); + if(!r) { + r = stbi__hdr_test_core(s, "#?RGBE\n"); + stbi__rewind(s); + } + return r; +} + +#define STBI__HDR_BUFLEN 1024 +static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) +{ + int len=0; + char c = '\0'; + + c = (char) stbi__get8(z); + + while (!stbi__at_eof(z) && c != '\n') { + buffer[len++] = c; + if (len == STBI__HDR_BUFLEN-1) { + // flush to end of line + while (!stbi__at_eof(z) && stbi__get8(z) != '\n') + ; + break; + } + c = (char) stbi__get8(z); + } + + buffer[len] = 0; + return buffer; +} + +static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) +{ + if ( input[3] != 0 ) { + float f1; + // Exponent + f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); + if (req_comp <= 2) + output[0] = (input[0] + input[1] + input[2]) * f1 / 3; + else { + output[0] = input[0] * f1; + output[1] = input[1] * f1; + output[2] = input[2] * f1; + } + if (req_comp == 2) output[1] = 1; + if (req_comp == 4) output[3] = 1; + } else { + switch (req_comp) { + case 4: output[3] = 1; /* fallthrough */ + case 3: output[0] = output[1] = output[2] = 0; + break; + case 2: output[1] = 1; /* fallthrough */ + case 1: output[0] = 0; + break; + } + } +} + +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int width, height; + stbi_uc *scanline; + float *hdr_data; + int len; + unsigned char count, value; + int i, j, k, c1,c2, z; + const char *headerToken; + STBI_NOTUSED(ri); + + // Check identifier + headerToken = stbi__hdr_gettoken(s,buffer); + if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0) + return stbi__errpf("not HDR", "Corrupt HDR image"); + + // Parse header + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); + + // Parse width and height + // can't use sscanf() if we're not using stdio! + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + height = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + width = (int) strtol(token, NULL, 10); + + if (height > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); + if (width > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); + + *x = width; + *y = height; + + if (comp) *comp = 3; + if (req_comp == 0) req_comp = 3; + + if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0)) + return stbi__errpf("too large", "HDR image is too large"); + + // Read data + hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0); + if (!hdr_data) + return stbi__errpf("outofmem", "Out of memory"); + + // Load image data + // image data is stored as some number of sca + if ( width < 8 || width >= 32768) { + // Read flat data + for (j=0; j < height; ++j) { + for (i=0; i < width; ++i) { + stbi_uc rgbe[4]; + main_decode_loop: + stbi__getn(s, rgbe, 4); + stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); + } + } + } else { + // Read RLE-encoded data + scanline = NULL; + + for (j = 0; j < height; ++j) { + c1 = stbi__get8(s); + c2 = stbi__get8(s); + len = stbi__get8(s); + if (c1 != 2 || c2 != 2 || (len & 0x80)) { + // not run-length encoded, so we have to actually use THIS data as a decoded + // pixel (note this can't be a valid pixel--one of RGB must be >= 128) + stbi_uc rgbe[4]; + rgbe[0] = (stbi_uc) c1; + rgbe[1] = (stbi_uc) c2; + rgbe[2] = (stbi_uc) len; + rgbe[3] = (stbi_uc) stbi__get8(s); + stbi__hdr_convert(hdr_data, rgbe, req_comp); + i = 1; + j = 0; + STBI_FREE(scanline); + goto main_decode_loop; // yes, this makes no sense + } + len <<= 8; + len |= stbi__get8(s); + if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } + if (scanline == NULL) { + scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0); + if (!scanline) { + STBI_FREE(hdr_data); + return stbi__errpf("outofmem", "Out of memory"); + } + } + + for (k = 0; k < 4; ++k) { + int nleft; + i = 0; + while ((nleft = width - i) > 0) { + count = stbi__get8(s); + if (count > 128) { + // Run + value = stbi__get8(s); + count -= 128; + if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = value; + } else { + // Dump + if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = stbi__get8(s); + } + } + } + for (i=0; i < width; ++i) + stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); + } + if (scanline) + STBI_FREE(scanline); + } + + return hdr_data; +} + +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int dummy; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (stbi__hdr_test(s) == 0) { + stbi__rewind( s ); + return 0; + } + + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) { + stbi__rewind( s ); + return 0; + } + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *y = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *x = (int) strtol(token, NULL, 10); + *comp = 3; + return 1; +} +#endif // STBI_NO_HDR + +#ifndef STBI_NO_BMP +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) +{ + void *p; + stbi__bmp_data info; + + info.all_a = 255; + p = stbi__bmp_parse_header(s, &info); + stbi__rewind( s ); + if (p == NULL) + return 0; + if (x) *x = s->img_x; + if (y) *y = s->img_y; + if (comp) { + if (info.bpp == 24 && info.ma == 0xff000000) + *comp = 3; + else + *comp = info.ma ? 4 : 3; + } + return 1; +} +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) +{ + int channelCount, dummy, depth; + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + *y = stbi__get32be(s); + *x = stbi__get32be(s); + depth = stbi__get16be(s); + if (depth != 8 && depth != 16) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 3) { + stbi__rewind( s ); + return 0; + } + *comp = 4; + return 1; +} + +static int stbi__psd_is16(stbi__context *s) +{ + int channelCount, depth; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + (void) stbi__get32be(s); + (void) stbi__get32be(s); + depth = stbi__get16be(s); + if (depth != 16) { + stbi__rewind( s ); + return 0; + } + return 1; +} +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) +{ + int act_comp=0,num_packets=0,chained,dummy; + stbi__pic_packet packets[10]; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { + stbi__rewind(s); + return 0; + } + + stbi__skip(s, 88); + + *x = stbi__get16be(s); + *y = stbi__get16be(s); + if (stbi__at_eof(s)) { + stbi__rewind( s); + return 0; + } + if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { + stbi__rewind( s ); + return 0; + } + + stbi__skip(s, 8); + + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return 0; + + packet = &packets[num_packets++]; + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + act_comp |= packet->channel; + + if (stbi__at_eof(s)) { + stbi__rewind( s ); + return 0; + } + if (packet->size != 8) { + stbi__rewind( s ); + return 0; + } + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); + + return 1; +} +#endif + +// ************************************************************************************************* +// Portable Gray Map and Portable Pixel Map loader +// by Ken Miller +// +// PGM: http://netpbm.sourceforge.net/doc/pgm.html +// PPM: http://netpbm.sourceforge.net/doc/ppm.html +// +// Known limitations: +// Does not support comments in the header section +// Does not support ASCII image data (formats P2 and P3) +// Does not support 16-bit-per-channel + +#ifndef STBI_NO_PNM + +static int stbi__pnm_test(stbi__context *s) +{ + char p, t; + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind( s ); + return 0; + } + return 1; +} + +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + STBI_NOTUSED(ri); + + if (!stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n)) + return 0; + + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + + if (!stbi__mad3sizes_valid(s->img_n, s->img_x, s->img_y, 0)) + return stbi__errpuc("too large", "PNM too large"); + + out = (stbi_uc *) stbi__malloc_mad3(s->img_n, s->img_x, s->img_y, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + stbi__getn(s, out, s->img_n * s->img_x * s->img_y); + + if (req_comp && req_comp != s->img_n) { + out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + return out; +} + +static int stbi__pnm_isspace(char c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; +} + +static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) +{ + for (;;) { + while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) + *c = (char) stbi__get8(s); + + if (stbi__at_eof(s) || *c != '#') + break; + + while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) + *c = (char) stbi__get8(s); + } +} + +static int stbi__pnm_isdigit(char c) +{ + return c >= '0' && c <= '9'; +} + +static int stbi__pnm_getinteger(stbi__context *s, char *c) +{ + int value = 0; + + while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { + value = value*10 + (*c - '0'); + *c = (char) stbi__get8(s); + } + + return value; +} + +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) +{ + int maxv, dummy; + char c, p, t; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + stbi__rewind(s); + + // Get identifier + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind(s); + return 0; + } + + *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm + + c = (char) stbi__get8(s); + stbi__pnm_skip_whitespace(s, &c); + + *x = stbi__pnm_getinteger(s, &c); // read width + stbi__pnm_skip_whitespace(s, &c); + + *y = stbi__pnm_getinteger(s, &c); // read height + stbi__pnm_skip_whitespace(s, &c); + + maxv = stbi__pnm_getinteger(s, &c); // read max value + + if (maxv > 255) + return stbi__err("max value > 255", "PPM image not 8-bit"); + else + return 1; +} +#endif + +static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) +{ + #ifndef STBI_NO_JPEG + if (stbi__jpeg_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNG + if (stbi__png_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_GIF + if (stbi__gif_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_BMP + if (stbi__bmp_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PIC + if (stbi__pic_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_info(s, x, y, comp)) return 1; + #endif + + // test tga last because it's a crappy test! + #ifndef STBI_NO_TGA + if (stbi__tga_info(s, x, y, comp)) + return 1; + #endif + return stbi__err("unknown image type", "Image not of any known type, or corrupt"); +} + +static int stbi__is_16_main(stbi__context *s) +{ + #ifndef STBI_NO_PNG + if (stbi__png_is16(s)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_is16(s)) return 1; + #endif + + return 0; +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_info_from_file(f, x, y, comp); + fclose(f); + return result; +} + +STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__info_main(&s,x,y,comp); + fseek(f,pos,SEEK_SET); + return r; +} + +STBIDEF int stbi_is_16_bit(char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_is_16_bit_from_file(f); + fclose(f); + return result; +} + +STBIDEF int stbi_is_16_bit_from_file(FILE *f) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__is_16_main(&s); + fseek(f,pos,SEEK_SET); + return r; +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__is_16_main(&s); +} + +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__is_16_main(&s); +} + +#endif // STB_IMAGE_IMPLEMENTATION + +/* + revision history: + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug + 1-bit BMP + *_is_16_bit api + avoid warnings + 2.16 (2017-07-23) all functions have 16-bit variants; + STBI_NO_STDIO works again; + compilation fixes; + fix rounding in unpremultiply; + optimize vertical flip; + disable raw_len validation; + documentation fixes + 2.15 (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode; + warning fixes; disable run-time SSE detection on gcc; + uniform handling of optional "return" values; + thread-safe initialization of zlib tables + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-11-29) add 16-bit API, only supported for PNG right now + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) allocate large structures on the stack + remove white matting for transparent PSD + fix reported channel count for PNG & BMP + re-enable SSE2 in non-gcc 64-bit + support RGB-formatted JPEG + read 16-bit PNGs (only as 8-bit) + 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED + 2.09 (2016-01-16) allow comments in PNM files + 16-bit-per-pixel TGA (not bit-per-component) + info() for TGA could break due to .hdr handling + info() for BMP to shares code instead of sloppy parse + can use STBI_REALLOC_SIZED if allocator doesn't support realloc + code cleanup + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) fix compiler warnings + partial animated GIF support + limited 16-bpc PSD support + #ifdef unused functions + bug with < 92 byte PIC,PNM,HDR,TGA + 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) extra corruption checking (mmozeiko) + stbi_set_flip_vertically_on_load (nguillemot) + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 + 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG + 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) + progressive JPEG (stb) + PGM/PPM support (Ken Miller) + STBI_MALLOC,STBI_REALLOC,STBI_FREE + GIF bugfix -- seemingly never worked + STBI_NO_*, STBI_ONLY_* + 1.48 (2014-12-14) fix incorrectly-named assert() + 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) + optimize PNG (ryg) + fix bug in interlaced PNG with user-specified channel count (stb) + 1.46 (2014-08-26) + fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG + 1.45 (2014-08-16) + fix MSVC-ARM internal compiler error by wrapping malloc + 1.44 (2014-08-07) + various warning fixes from Ronny Chevalier + 1.43 (2014-07-15) + fix MSVC-only compiler problem in code changed in 1.42 + 1.42 (2014-07-09) + don't define _CRT_SECURE_NO_WARNINGS (affects user code) + fixes to stbi__cleanup_jpeg path + added STBI_ASSERT to avoid requiring assert.h + 1.41 (2014-06-25) + fix search&replace from 1.36 that messed up comments/error messages + 1.40 (2014-06-22) + fix gcc struct-initialization warning + 1.39 (2014-06-15) + fix to TGA optimization when req_comp != number of components in TGA; + fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) + add support for BMP version 5 (more ignored fields) + 1.38 (2014-06-06) + suppress MSVC warnings on integer casts truncating values + fix accidental rename of 'skip' field of I/O + 1.37 (2014-06-04) + remove duplicate typedef + 1.36 (2014-06-03) + convert to header file single-file library + if de-iphone isn't set, load iphone images color-swapped instead of returning NULL + 1.35 (2014-05-27) + various warnings + fix broken STBI_SIMD path + fix bug where stbi_load_from_file no longer left file pointer in correct place + fix broken non-easy path for 32-bit BMP (possibly never used) + TGA optimization by Arseny Kapoulkine + 1.34 (unknown) + use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case + 1.33 (2011-07-14) + make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements + 1.32 (2011-07-13) + support for "info" function for all supported filetypes (SpartanJ) + 1.31 (2011-06-20) + a few more leak fixes, bug in PNG handling (SpartanJ) + 1.30 (2011-06-11) + added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) + removed deprecated format-specific test/load functions + removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway + error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) + fix inefficiency in decoding 32-bit BMP (David Woo) + 1.29 (2010-08-16) + various warning fixes from Aurelien Pocheville + 1.28 (2010-08-01) + fix bug in GIF palette transparency (SpartanJ) + 1.27 (2010-08-01) + cast-to-stbi_uc to fix warnings + 1.26 (2010-07-24) + fix bug in file buffering for PNG reported by SpartanJ + 1.25 (2010-07-17) + refix trans_data warning (Won Chun) + 1.24 (2010-07-12) + perf improvements reading from files on platforms with lock-heavy fgetc() + minor perf improvements for jpeg + deprecated type-specific functions so we'll get feedback if they're needed + attempt to fix trans_data warning (Won Chun) + 1.23 fixed bug in iPhone support + 1.22 (2010-07-10) + removed image *writing* support + stbi_info support from Jetro Lauha + GIF support from Jean-Marc Lienher + iPhone PNG-extensions from James Brown + warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) + 1.21 fix use of 'stbi_uc' in header (reported by jon blow) + 1.20 added support for Softimage PIC, by Tom Seddon + 1.19 bug in interlaced PNG corruption check (found by ryg) + 1.18 (2008-08-02) + fix a threading bug (local mutable static) + 1.17 support interlaced PNG + 1.16 major bugfix - stbi__convert_format converted one too many pixels + 1.15 initialize some fields for thread safety + 1.14 fix threadsafe conversion bug + header-file-only version (#define STBI_HEADER_FILE_ONLY before including) + 1.13 threadsafe + 1.12 const qualifiers in the API + 1.11 Support installable IDCT, colorspace conversion routines + 1.10 Fixes for 64-bit (don't use "unsigned long") + optimized upsampling by Fabian "ryg" Giesen + 1.09 Fix format-conversion for PSD code (bad global variables!) + 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz + 1.07 attempt to fix C++ warning/errors again + 1.06 attempt to fix C++ warning/errors again + 1.05 fix TGA loading to return correct *comp and use good luminance calc + 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free + 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR + 1.02 support for (subset of) HDR files, float interface for preferred access to them + 1.01 fix bug: possible bug in handling right-side up bmps... not sure + fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all + 1.00 interface to zlib that skips zlib header + 0.99 correct handling of alpha in palette + 0.98 TGA loader by lonesock; dynamically add loaders (untested) + 0.97 jpeg errors on too large a file; also catch another malloc failure + 0.96 fix detection of invalid v value - particleman@mollyrocket forum + 0.95 during header scan, seek to markers in case of padding + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same + 0.93 handle jpegtran output; verbose errors + 0.92 read 4,8,16,24,32-bit BMP files of several formats + 0.91 output 24-bit Windows 3.0 BMP files + 0.90 fix a few more warnings; bump version number to approach 1.0 + 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd + 0.60 fix compiling as c++ + 0.59 fix warnings: merge Dave Moore's -Wall fixes + 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian + 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available + 0.56 fix bug: zlib uncompressed mode len vs. nlen + 0.55 fix bug: restart_interval not initialized to 0 + 0.54 allow NULL for 'int *comp' + 0.53 fix bug in png 3->4; speedup png decoding + 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments + 0.51 obey req_comp requests, 1-component jpegs return as 1-component, + on 'test' only check type, not whether we support this variant + 0.50 (2006-11-19) + first released version +*/ + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +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. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +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 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. +------------------------------------------------------------------------------ +*/ diff --git a/Source/Dependencies/ImGuiFileDialog-0.6.3/stb/stb_image_resize.h b/Source/Dependencies/ImGuiFileDialog-0.6.3/stb/stb_image_resize.h new file mode 100644 index 000000000..42a8efb13 --- /dev/null +++ b/Source/Dependencies/ImGuiFileDialog-0.6.3/stb/stb_image_resize.h @@ -0,0 +1,2631 @@ +/* stb_image_resize - v0.96 - public domain image resizing + by Jorge L Rodriguez (@VinoBS) - 2014 + http://github.com/nothings/stb + + Written with emphasis on usability, portability, and efficiency. (No + SIMD or threads, so it be easily outperformed by libs that use those.) + Only scaling and translation is supported, no rotations or shears. + Easy API downsamples w/Mitchell filter, upsamples w/cubic interpolation. + + COMPILING & LINKING + In one C/C++ file that #includes this file, do this: + #define STB_IMAGE_RESIZE_IMPLEMENTATION + before the #include. That will create the implementation in that file. + + QUICKSTART + stbir_resize_uint8( input_pixels , in_w , in_h , 0, + output_pixels, out_w, out_h, 0, num_channels) + stbir_resize_float(...) + stbir_resize_uint8_srgb( input_pixels , in_w , in_h , 0, + output_pixels, out_w, out_h, 0, + num_channels , alpha_chan , 0) + stbir_resize_uint8_srgb_edgemode( + input_pixels , in_w , in_h , 0, + output_pixels, out_w, out_h, 0, + num_channels , alpha_chan , 0, STBIR_EDGE_CLAMP) + // WRAP/REFLECT/ZERO + + FULL API + See the "header file" section of the source for API documentation. + + ADDITIONAL DOCUMENTATION + + SRGB & FLOATING POINT REPRESENTATION + The sRGB functions presume IEEE floating point. If you do not have + IEEE floating point, define STBIR_NON_IEEE_FLOAT. This will use + a slower implementation. + + MEMORY ALLOCATION + The resize functions here perform a single memory allocation using + malloc. To control the memory allocation, before the #include that + triggers the implementation, do: + + #define STBIR_MALLOC(size,context) ... + #define STBIR_FREE(ptr,context) ... + + Each resize function makes exactly one call to malloc/free, so to use + temp memory, store the temp memory in the context and return that. + + ASSERT + Define STBIR_ASSERT(boolval) to override assert() and not use assert.h + + OPTIMIZATION + Define STBIR_SATURATE_INT to compute clamp values in-range using + integer operations instead of float operations. This may be faster + on some platforms. + + DEFAULT FILTERS + For functions which don't provide explicit control over what filters + to use, you can change the compile-time defaults with + + #define STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_something + #define STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_something + + See stbir_filter in the header-file section for the list of filters. + + NEW FILTERS + A number of 1D filter kernels are used. For a list of + supported filters see the stbir_filter enum. To add a new filter, + write a filter function and add it to stbir__filter_info_table. + + PROGRESS + For interactive use with slow resize operations, you can install + a progress-report callback: + + #define STBIR_PROGRESS_REPORT(val) some_func(val) + + The parameter val is a float which goes from 0 to 1 as progress is made. + + For example: + + static void my_progress_report(float progress); + #define STBIR_PROGRESS_REPORT(val) my_progress_report(val) + + #define STB_IMAGE_RESIZE_IMPLEMENTATION + #include "stb_image_resize.h" + + static void my_progress_report(float progress) + { + printf("Progress: %f%%\n", progress*100); + } + + MAX CHANNELS + If your image has more than 64 channels, define STBIR_MAX_CHANNELS + to the max you'll have. + + ALPHA CHANNEL + Most of the resizing functions provide the ability to control how + the alpha channel of an image is processed. The important things + to know about this: + + 1. The best mathematically-behaved version of alpha to use is + called "premultiplied alpha", in which the other color channels + have had the alpha value multiplied in. If you use premultiplied + alpha, linear filtering (such as image resampling done by this + library, or performed in texture units on GPUs) does the "right + thing". While premultiplied alpha is standard in the movie CGI + industry, it is still uncommon in the videogame/real-time world. + + If you linearly filter non-premultiplied alpha, strange effects + occur. (For example, the 50/50 average of 99% transparent bright green + and 1% transparent black produces 50% transparent dark green when + non-premultiplied, whereas premultiplied it produces 50% + transparent near-black. The former introduces green energy + that doesn't exist in the source image.) + + 2. Artists should not edit premultiplied-alpha images; artists + want non-premultiplied alpha images. Thus, art tools generally output + non-premultiplied alpha images. + + 3. You will get best results in most cases by converting images + to premultiplied alpha before processing them mathematically. + + 4. If you pass the flag STBIR_FLAG_ALPHA_PREMULTIPLIED, the + resizer does not do anything special for the alpha channel; + it is resampled identically to other channels. This produces + the correct results for premultiplied-alpha images, but produces + less-than-ideal results for non-premultiplied-alpha images. + + 5. If you do not pass the flag STBIR_FLAG_ALPHA_PREMULTIPLIED, + then the resizer weights the contribution of input pixels + based on their alpha values, or, equivalently, it multiplies + the alpha value into the color channels, resamples, then divides + by the resultant alpha value. Input pixels which have alpha=0 do + not contribute at all to output pixels unless _all_ of the input + pixels affecting that output pixel have alpha=0, in which case + the result for that pixel is the same as it would be without + STBIR_FLAG_ALPHA_PREMULTIPLIED. However, this is only true for + input images in integer formats. For input images in float format, + input pixels with alpha=0 have no effect, and output pixels + which have alpha=0 will be 0 in all channels. (For float images, + you can manually achieve the same result by adding a tiny epsilon + value to the alpha channel of every image, and then subtracting + or clamping it at the end.) + + 6. You can suppress the behavior described in #5 and make + all-0-alpha pixels have 0 in all channels by #defining + STBIR_NO_ALPHA_EPSILON. + + 7. You can separately control whether the alpha channel is + interpreted as linear or affected by the colorspace. By default + it is linear; you almost never want to apply the colorspace. + (For example, graphics hardware does not apply sRGB conversion + to the alpha channel.) + + CONTRIBUTORS + Jorge L Rodriguez: Implementation + Sean Barrett: API design, optimizations + Aras Pranckevicius: bugfix + Nathan Reed: warning fixes + + REVISIONS + 0.97 (2020-02-02) fixed warning + 0.96 (2019-03-04) fixed warnings + 0.95 (2017-07-23) fixed warnings + 0.94 (2017-03-18) fixed warnings + 0.93 (2017-03-03) fixed bug with certain combinations of heights + 0.92 (2017-01-02) fix integer overflow on large (>2GB) images + 0.91 (2016-04-02) fix warnings; fix handling of subpixel regions + 0.90 (2014-09-17) first released version + + LICENSE + See end of file for license information. + + TODO + Don't decode all of the image data when only processing a partial tile + Don't use full-width decode buffers when only processing a partial tile + When processing wide images, break processing into tiles so data fits in L1 cache + Installable filters? + Resize that respects alpha test coverage + (Reference code: FloatImage::alphaTestCoverage and FloatImage::scaleAlphaToCoverage: + https://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvimage/FloatImage.cpp ) +*/ + +#ifndef STBIR_INCLUDE_STB_IMAGE_RESIZE_H +#define STBIR_INCLUDE_STB_IMAGE_RESIZE_H + +#ifdef _MSC_VER +typedef unsigned char stbir_uint8; +typedef unsigned short stbir_uint16; +typedef unsigned int stbir_uint32; +#else +#include +typedef uint8_t stbir_uint8; +typedef uint16_t stbir_uint16; +typedef uint32_t stbir_uint32; +#endif + +#ifndef STBIRDEF +#ifdef STB_IMAGE_RESIZE_STATIC +#define STBIRDEF static +#else +#ifdef __cplusplus +#define STBIRDEF extern "C" +#else +#define STBIRDEF extern +#endif +#endif +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// Easy-to-use API: +// +// * "input pixels" points to an array of image data with 'num_channels' channels (e.g. RGB=3, RGBA=4) +// * input_w is input image width (x-axis), input_h is input image height (y-axis) +// * stride is the offset between successive rows of image data in memory, in bytes. you can +// specify 0 to mean packed continuously in memory +// * alpha channel is treated identically to other channels. +// * colorspace is linear or sRGB as specified by function name +// * returned result is 1 for success or 0 in case of an error. +// #define STBIR_ASSERT() to trigger an assert on parameter validation errors. +// * Memory required grows approximately linearly with input and output size, but with +// discontinuities at input_w == output_w and input_h == output_h. +// * These functions use a "default" resampling filter defined at compile time. To change the filter, +// you can change the compile-time defaults by #defining STBIR_DEFAULT_FILTER_UPSAMPLE +// and STBIR_DEFAULT_FILTER_DOWNSAMPLE, or you can use the medium-complexity API. + +STBIRDEF int stbir_resize_uint8( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels); + +STBIRDEF int stbir_resize_float( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + float *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels); + + +// The following functions interpret image data as gamma-corrected sRGB. +// Specify STBIR_ALPHA_CHANNEL_NONE if you have no alpha channel, +// or otherwise provide the index of the alpha channel. Flags value +// of 0 will probably do the right thing if you're not sure what +// the flags mean. + +#define STBIR_ALPHA_CHANNEL_NONE -1 + +// Set this flag if your texture has premultiplied alpha. Otherwise, stbir will +// use alpha-weighted resampling (effectively premultiplying, resampling, +// then unpremultiplying). +#define STBIR_FLAG_ALPHA_PREMULTIPLIED (1 << 0) +// The specified alpha channel should be handled as gamma-corrected value even +// when doing sRGB operations. +#define STBIR_FLAG_ALPHA_USES_COLORSPACE (1 << 1) + +STBIRDEF int stbir_resize_uint8_srgb(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags); + + +typedef enum +{ + STBIR_EDGE_CLAMP = 1, + STBIR_EDGE_REFLECT = 2, + STBIR_EDGE_WRAP = 3, + STBIR_EDGE_ZERO = 4, +} stbir_edge; + +// This function adds the ability to specify how requests to sample off the edge of the image are handled. +STBIRDEF int stbir_resize_uint8_srgb_edgemode(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode); + +////////////////////////////////////////////////////////////////////////////// +// +// Medium-complexity API +// +// This extends the easy-to-use API as follows: +// +// * Alpha-channel can be processed separately +// * If alpha_channel is not STBIR_ALPHA_CHANNEL_NONE +// * Alpha channel will not be gamma corrected (unless flags&STBIR_FLAG_GAMMA_CORRECT) +// * Filters will be weighted by alpha channel (unless flags&STBIR_FLAG_ALPHA_PREMULTIPLIED) +// * Filter can be selected explicitly +// * uint16 image type +// * sRGB colorspace available for all types +// * context parameter for passing to STBIR_MALLOC + +typedef enum +{ + STBIR_FILTER_DEFAULT = 0, // use same filter type that easy-to-use API chooses + STBIR_FILTER_BOX = 1, // A trapezoid w/1-pixel wide ramps, same result as box for integer scale ratios + STBIR_FILTER_TRIANGLE = 2, // On upsampling, produces same results as bilinear texture filtering + STBIR_FILTER_CUBICBSPLINE = 3, // The cubic b-spline (aka Mitchell-Netrevalli with B=1,C=0), gaussian-esque + STBIR_FILTER_CATMULLROM = 4, // An interpolating cubic spline + STBIR_FILTER_MITCHELL = 5, // Mitchell-Netrevalli filter with B=1/3, C=1/3 +} stbir_filter; + +typedef enum +{ + STBIR_COLORSPACE_LINEAR, + STBIR_COLORSPACE_SRGB, + + STBIR_MAX_COLORSPACES, +} stbir_colorspace; + +// The following functions are all identical except for the type of the image data + +STBIRDEF int stbir_resize_uint8_generic( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context); + +STBIRDEF int stbir_resize_uint16_generic(const stbir_uint16 *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + stbir_uint16 *output_pixels , int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context); + +STBIRDEF int stbir_resize_float_generic( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + float *output_pixels , int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context); + + + +////////////////////////////////////////////////////////////////////////////// +// +// Full-complexity API +// +// This extends the medium API as follows: +// +// * uint32 image type +// * not typesafe +// * separate filter types for each axis +// * separate edge modes for each axis +// * can specify scale explicitly for subpixel correctness +// * can specify image source tile using texture coordinates + +typedef enum +{ + STBIR_TYPE_UINT8 , + STBIR_TYPE_UINT16, + STBIR_TYPE_UINT32, + STBIR_TYPE_FLOAT , + + STBIR_MAX_TYPES +} stbir_datatype; + +STBIRDEF int stbir_resize( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context); + +STBIRDEF int stbir_resize_subpixel(const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context, + float x_scale, float y_scale, + float x_offset, float y_offset); + +STBIRDEF int stbir_resize_region( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context, + float s0, float t0, float s1, float t1); +// (s0, t0) & (s1, t1) are the top-left and bottom right corner (uv addressing style: [0, 1]x[0, 1]) of a region of the input image to use. + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBIR_INCLUDE_STB_IMAGE_RESIZE_H + + + + + +#ifdef STB_IMAGE_RESIZE_IMPLEMENTATION + +#ifndef STBIR_ASSERT +#include +#define STBIR_ASSERT(x) assert(x) +#endif + +// For memset +#include + +#include + +#ifndef STBIR_MALLOC +#include +// use comma operator to evaluate c, to avoid "unused parameter" warnings +#define STBIR_MALLOC(size,c) ((void)(c), malloc(size)) +#define STBIR_FREE(ptr,c) ((void)(c), free(ptr)) +#endif + +#ifndef _MSC_VER +#ifdef __cplusplus +#define stbir__inline inline +#else +#define stbir__inline +#endif +#else +#define stbir__inline __forceinline +#endif + + +// should produce compiler error if size is wrong +typedef unsigned char stbir__validate_uint32[sizeof(stbir_uint32) == 4 ? 1 : -1]; + +#ifdef _MSC_VER +#define STBIR__NOTUSED(v) (void)(v) +#else +#define STBIR__NOTUSED(v) (void)sizeof(v) +#endif + +#define STBIR__ARRAY_SIZE(a) (sizeof((a))/sizeof((a)[0])) + +#ifndef STBIR_DEFAULT_FILTER_UPSAMPLE +#define STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_CATMULLROM +#endif + +#ifndef STBIR_DEFAULT_FILTER_DOWNSAMPLE +#define STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_MITCHELL +#endif + +#ifndef STBIR_PROGRESS_REPORT +#define STBIR_PROGRESS_REPORT(float_0_to_1) +#endif + +#ifndef STBIR_MAX_CHANNELS +#define STBIR_MAX_CHANNELS 64 +#endif + +#if STBIR_MAX_CHANNELS > 65536 +#error "Too many channels; STBIR_MAX_CHANNELS must be no more than 65536." +// because we store the indices in 16-bit variables +#endif + +// This value is added to alpha just before premultiplication to avoid +// zeroing out color values. It is equivalent to 2^-80. If you don't want +// that behavior (it may interfere if you have floating point images with +// very small alpha values) then you can define STBIR_NO_ALPHA_EPSILON to +// disable it. +#ifndef STBIR_ALPHA_EPSILON +#define STBIR_ALPHA_EPSILON ((float)1 / (1 << 20) / (1 << 20) / (1 << 20) / (1 << 20)) +#endif + + + +#ifdef _MSC_VER +#define STBIR__UNUSED_PARAM(v) (void)(v) +#else +#define STBIR__UNUSED_PARAM(v) (void)sizeof(v) +#endif + +// must match stbir_datatype +static unsigned char stbir__type_size[] = { + 1, // STBIR_TYPE_UINT8 + 2, // STBIR_TYPE_UINT16 + 4, // STBIR_TYPE_UINT32 + 4, // STBIR_TYPE_FLOAT +}; + +// Kernel function centered at 0 +typedef float (stbir__kernel_fn)(float x, float scale); +typedef float (stbir__support_fn)(float scale); + +typedef struct +{ + stbir__kernel_fn* kernel; + stbir__support_fn* support; +} stbir__filter_info; + +// When upsampling, the contributors are which source pixels contribute. +// When downsampling, the contributors are which destination pixels are contributed to. +typedef struct +{ + int n0; // First contributing pixel + int n1; // Last contributing pixel +} stbir__contributors; + +typedef struct +{ + const void* input_data; + int input_w; + int input_h; + int input_stride_bytes; + + void* output_data; + int output_w; + int output_h; + int output_stride_bytes; + + float s0, t0, s1, t1; + + float horizontal_shift; // Units: output pixels + float vertical_shift; // Units: output pixels + float horizontal_scale; + float vertical_scale; + + int channels; + int alpha_channel; + stbir_uint32 flags; + stbir_datatype type; + stbir_filter horizontal_filter; + stbir_filter vertical_filter; + stbir_edge edge_horizontal; + stbir_edge edge_vertical; + stbir_colorspace colorspace; + + stbir__contributors* horizontal_contributors; + float* horizontal_coefficients; + + stbir__contributors* vertical_contributors; + float* vertical_coefficients; + + int decode_buffer_pixels; + float* decode_buffer; + + float* horizontal_buffer; + + // cache these because ceil/floor are inexplicably showing up in profile + int horizontal_coefficient_width; + int vertical_coefficient_width; + int horizontal_filter_pixel_width; + int vertical_filter_pixel_width; + int horizontal_filter_pixel_margin; + int vertical_filter_pixel_margin; + int horizontal_num_contributors; + int vertical_num_contributors; + + int ring_buffer_length_bytes; // The length of an individual entry in the ring buffer. The total number of ring buffers is stbir__get_filter_pixel_width(filter) + int ring_buffer_num_entries; // Total number of entries in the ring buffer. + int ring_buffer_first_scanline; + int ring_buffer_last_scanline; + int ring_buffer_begin_index; // first_scanline is at this index in the ring buffer + float* ring_buffer; + + float* encode_buffer; // A temporary buffer to store floats so we don't lose precision while we do multiply-adds. + + int horizontal_contributors_size; + int horizontal_coefficients_size; + int vertical_contributors_size; + int vertical_coefficients_size; + int decode_buffer_size; + int horizontal_buffer_size; + int ring_buffer_size; + int encode_buffer_size; +} stbir__info; + + +static const float stbir__max_uint8_as_float = 255.0f; +static const float stbir__max_uint16_as_float = 65535.0f; +static const double stbir__max_uint32_as_float = 4294967295.0; + + +static stbir__inline int stbir__min(int a, int b) +{ + return a < b ? a : b; +} + +static stbir__inline float stbir__saturate(float x) +{ + if (x < 0) + return 0; + + if (x > 1) + return 1; + + return x; +} + +#ifdef STBIR_SATURATE_INT +static stbir__inline stbir_uint8 stbir__saturate8(int x) +{ + if ((unsigned int) x <= 255) + return x; + + if (x < 0) + return 0; + + return 255; +} + +static stbir__inline stbir_uint16 stbir__saturate16(int x) +{ + if ((unsigned int) x <= 65535) + return x; + + if (x < 0) + return 0; + + return 65535; +} +#endif + +static float stbir__srgb_uchar_to_linear_float[256] = { + 0.000000f, 0.000304f, 0.000607f, 0.000911f, 0.001214f, 0.001518f, 0.001821f, 0.002125f, 0.002428f, 0.002732f, 0.003035f, + 0.003347f, 0.003677f, 0.004025f, 0.004391f, 0.004777f, 0.005182f, 0.005605f, 0.006049f, 0.006512f, 0.006995f, 0.007499f, + 0.008023f, 0.008568f, 0.009134f, 0.009721f, 0.010330f, 0.010960f, 0.011612f, 0.012286f, 0.012983f, 0.013702f, 0.014444f, + 0.015209f, 0.015996f, 0.016807f, 0.017642f, 0.018500f, 0.019382f, 0.020289f, 0.021219f, 0.022174f, 0.023153f, 0.024158f, + 0.025187f, 0.026241f, 0.027321f, 0.028426f, 0.029557f, 0.030713f, 0.031896f, 0.033105f, 0.034340f, 0.035601f, 0.036889f, + 0.038204f, 0.039546f, 0.040915f, 0.042311f, 0.043735f, 0.045186f, 0.046665f, 0.048172f, 0.049707f, 0.051269f, 0.052861f, + 0.054480f, 0.056128f, 0.057805f, 0.059511f, 0.061246f, 0.063010f, 0.064803f, 0.066626f, 0.068478f, 0.070360f, 0.072272f, + 0.074214f, 0.076185f, 0.078187f, 0.080220f, 0.082283f, 0.084376f, 0.086500f, 0.088656f, 0.090842f, 0.093059f, 0.095307f, + 0.097587f, 0.099899f, 0.102242f, 0.104616f, 0.107023f, 0.109462f, 0.111932f, 0.114435f, 0.116971f, 0.119538f, 0.122139f, + 0.124772f, 0.127438f, 0.130136f, 0.132868f, 0.135633f, 0.138432f, 0.141263f, 0.144128f, 0.147027f, 0.149960f, 0.152926f, + 0.155926f, 0.158961f, 0.162029f, 0.165132f, 0.168269f, 0.171441f, 0.174647f, 0.177888f, 0.181164f, 0.184475f, 0.187821f, + 0.191202f, 0.194618f, 0.198069f, 0.201556f, 0.205079f, 0.208637f, 0.212231f, 0.215861f, 0.219526f, 0.223228f, 0.226966f, + 0.230740f, 0.234551f, 0.238398f, 0.242281f, 0.246201f, 0.250158f, 0.254152f, 0.258183f, 0.262251f, 0.266356f, 0.270498f, + 0.274677f, 0.278894f, 0.283149f, 0.287441f, 0.291771f, 0.296138f, 0.300544f, 0.304987f, 0.309469f, 0.313989f, 0.318547f, + 0.323143f, 0.327778f, 0.332452f, 0.337164f, 0.341914f, 0.346704f, 0.351533f, 0.356400f, 0.361307f, 0.366253f, 0.371238f, + 0.376262f, 0.381326f, 0.386430f, 0.391573f, 0.396755f, 0.401978f, 0.407240f, 0.412543f, 0.417885f, 0.423268f, 0.428691f, + 0.434154f, 0.439657f, 0.445201f, 0.450786f, 0.456411f, 0.462077f, 0.467784f, 0.473532f, 0.479320f, 0.485150f, 0.491021f, + 0.496933f, 0.502887f, 0.508881f, 0.514918f, 0.520996f, 0.527115f, 0.533276f, 0.539480f, 0.545725f, 0.552011f, 0.558340f, + 0.564712f, 0.571125f, 0.577581f, 0.584078f, 0.590619f, 0.597202f, 0.603827f, 0.610496f, 0.617207f, 0.623960f, 0.630757f, + 0.637597f, 0.644480f, 0.651406f, 0.658375f, 0.665387f, 0.672443f, 0.679543f, 0.686685f, 0.693872f, 0.701102f, 0.708376f, + 0.715694f, 0.723055f, 0.730461f, 0.737911f, 0.745404f, 0.752942f, 0.760525f, 0.768151f, 0.775822f, 0.783538f, 0.791298f, + 0.799103f, 0.806952f, 0.814847f, 0.822786f, 0.830770f, 0.838799f, 0.846873f, 0.854993f, 0.863157f, 0.871367f, 0.879622f, + 0.887923f, 0.896269f, 0.904661f, 0.913099f, 0.921582f, 0.930111f, 0.938686f, 0.947307f, 0.955974f, 0.964686f, 0.973445f, + 0.982251f, 0.991102f, 1.0f +}; + +static float stbir__srgb_to_linear(float f) +{ + if (f <= 0.04045f) + return f / 12.92f; + else + return (float)pow((f + 0.055f) / 1.055f, 2.4f); +} + +static float stbir__linear_to_srgb(float f) +{ + if (f <= 0.0031308f) + return f * 12.92f; + else + return 1.055f * (float)pow(f, 1 / 2.4f) - 0.055f; +} + +#ifndef STBIR_NON_IEEE_FLOAT +// From https://gist.github.com/rygorous/2203834 + +typedef union +{ + stbir_uint32 u; + float f; +} stbir__FP32; + +static const stbir_uint32 fp32_to_srgb8_tab4[104] = { + 0x0073000d, 0x007a000d, 0x0080000d, 0x0087000d, 0x008d000d, 0x0094000d, 0x009a000d, 0x00a1000d, + 0x00a7001a, 0x00b4001a, 0x00c1001a, 0x00ce001a, 0x00da001a, 0x00e7001a, 0x00f4001a, 0x0101001a, + 0x010e0033, 0x01280033, 0x01410033, 0x015b0033, 0x01750033, 0x018f0033, 0x01a80033, 0x01c20033, + 0x01dc0067, 0x020f0067, 0x02430067, 0x02760067, 0x02aa0067, 0x02dd0067, 0x03110067, 0x03440067, + 0x037800ce, 0x03df00ce, 0x044600ce, 0x04ad00ce, 0x051400ce, 0x057b00c5, 0x05dd00bc, 0x063b00b5, + 0x06970158, 0x07420142, 0x07e30130, 0x087b0120, 0x090b0112, 0x09940106, 0x0a1700fc, 0x0a9500f2, + 0x0b0f01cb, 0x0bf401ae, 0x0ccb0195, 0x0d950180, 0x0e56016e, 0x0f0d015e, 0x0fbc0150, 0x10630143, + 0x11070264, 0x1238023e, 0x1357021d, 0x14660201, 0x156601e9, 0x165a01d3, 0x174401c0, 0x182401af, + 0x18fe0331, 0x1a9602fe, 0x1c1502d2, 0x1d7e02ad, 0x1ed4028d, 0x201a0270, 0x21520256, 0x227d0240, + 0x239f0443, 0x25c003fe, 0x27bf03c4, 0x29a10392, 0x2b6a0367, 0x2d1d0341, 0x2ebe031f, 0x304d0300, + 0x31d105b0, 0x34a80555, 0x37520507, 0x39d504c5, 0x3c37048b, 0x3e7c0458, 0x40a8042a, 0x42bd0401, + 0x44c20798, 0x488e071e, 0x4c1c06b6, 0x4f76065d, 0x52a50610, 0x55ac05cc, 0x5892058f, 0x5b590559, + 0x5e0c0a23, 0x631c0980, 0x67db08f6, 0x6c55087f, 0x70940818, 0x74a007bd, 0x787d076c, 0x7c330723, +}; + +static stbir_uint8 stbir__linear_to_srgb_uchar(float in) +{ + static const stbir__FP32 almostone = { 0x3f7fffff }; // 1-eps + static const stbir__FP32 minval = { (127-13) << 23 }; + stbir_uint32 tab,bias,scale,t; + stbir__FP32 f; + + // Clamp to [2^(-13), 1-eps]; these two values map to 0 and 1, respectively. + // The tests are carefully written so that NaNs map to 0, same as in the reference + // implementation. + if (!(in > minval.f)) // written this way to catch NaNs + in = minval.f; + if (in > almostone.f) + in = almostone.f; + + // Do the table lookup and unpack bias, scale + f.f = in; + tab = fp32_to_srgb8_tab4[(f.u - minval.u) >> 20]; + bias = (tab >> 16) << 9; + scale = tab & 0xffff; + + // Grab next-highest mantissa bits and perform linear interpolation + t = (f.u >> 12) & 0xff; + return (unsigned char) ((bias + scale*t) >> 16); +} + +#else +// sRGB transition values, scaled by 1<<28 +static int stbir__srgb_offset_to_linear_scaled[256] = +{ + 0, 40738, 122216, 203693, 285170, 366648, 448125, 529603, + 611080, 692557, 774035, 855852, 942009, 1033024, 1128971, 1229926, + 1335959, 1447142, 1563542, 1685229, 1812268, 1944725, 2082664, 2226148, + 2375238, 2529996, 2690481, 2856753, 3028870, 3206888, 3390865, 3580856, + 3776916, 3979100, 4187460, 4402049, 4622919, 4850123, 5083710, 5323731, + 5570236, 5823273, 6082892, 6349140, 6622065, 6901714, 7188133, 7481369, + 7781466, 8088471, 8402427, 8723380, 9051372, 9386448, 9728650, 10078021, + 10434603, 10798439, 11169569, 11548036, 11933879, 12327139, 12727857, 13136073, + 13551826, 13975156, 14406100, 14844697, 15290987, 15745007, 16206795, 16676389, + 17153826, 17639142, 18132374, 18633560, 19142734, 19659934, 20185196, 20718552, + 21260042, 21809696, 22367554, 22933648, 23508010, 24090680, 24681686, 25281066, + 25888850, 26505076, 27129772, 27762974, 28404716, 29055026, 29713942, 30381490, + 31057708, 31742624, 32436272, 33138682, 33849884, 34569912, 35298800, 36036568, + 36783260, 37538896, 38303512, 39077136, 39859796, 40651528, 41452360, 42262316, + 43081432, 43909732, 44747252, 45594016, 46450052, 47315392, 48190064, 49074096, + 49967516, 50870356, 51782636, 52704392, 53635648, 54576432, 55526772, 56486700, + 57456236, 58435408, 59424248, 60422780, 61431036, 62449032, 63476804, 64514376, + 65561776, 66619028, 67686160, 68763192, 69850160, 70947088, 72053992, 73170912, + 74297864, 75434880, 76581976, 77739184, 78906536, 80084040, 81271736, 82469648, + 83677792, 84896192, 86124888, 87363888, 88613232, 89872928, 91143016, 92423512, + 93714432, 95015816, 96327688, 97650056, 98982952, 100326408, 101680440, 103045072, + 104420320, 105806224, 107202800, 108610064, 110028048, 111456776, 112896264, 114346544, + 115807632, 117279552, 118762328, 120255976, 121760536, 123276016, 124802440, 126339832, + 127888216, 129447616, 131018048, 132599544, 134192112, 135795792, 137410592, 139036528, + 140673648, 142321952, 143981456, 145652208, 147334208, 149027488, 150732064, 152447968, + 154175200, 155913792, 157663776, 159425168, 161197984, 162982240, 164777968, 166585184, + 168403904, 170234160, 172075968, 173929344, 175794320, 177670896, 179559120, 181458992, + 183370528, 185293776, 187228736, 189175424, 191133888, 193104112, 195086128, 197079968, + 199085648, 201103184, 203132592, 205173888, 207227120, 209292272, 211369392, 213458480, + 215559568, 217672656, 219797792, 221934976, 224084240, 226245600, 228419056, 230604656, + 232802400, 235012320, 237234432, 239468736, 241715280, 243974080, 246245120, 248528464, + 250824112, 253132064, 255452368, 257785040, 260130080, 262487520, 264857376, 267239664, +}; + +static stbir_uint8 stbir__linear_to_srgb_uchar(float f) +{ + int x = (int) (f * (1 << 28)); // has headroom so you don't need to clamp + int v = 0; + int i; + + // Refine the guess with a short binary search. + i = v + 128; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 64; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 32; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 16; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 8; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 4; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 2; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 1; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + + return (stbir_uint8) v; +} +#endif + +static float stbir__filter_trapezoid(float x, float scale) +{ + float halfscale = scale / 2; + float t = 0.5f + halfscale; + STBIR_ASSERT(scale <= 1); + + x = (float)fabs(x); + + if (x >= t) + return 0; + else + { + float r = 0.5f - halfscale; + if (x <= r) + return 1; + else + return (t - x) / scale; + } +} + +static float stbir__support_trapezoid(float scale) +{ + STBIR_ASSERT(scale <= 1); + return 0.5f + scale / 2; +} + +static float stbir__filter_triangle(float x, float s) +{ + STBIR__UNUSED_PARAM(s); + + x = (float)fabs(x); + + if (x <= 1.0f) + return 1 - x; + else + return 0; +} + +static float stbir__filter_cubic(float x, float s) +{ + STBIR__UNUSED_PARAM(s); + + x = (float)fabs(x); + + if (x < 1.0f) + return (4 + x*x*(3*x - 6))/6; + else if (x < 2.0f) + return (8 + x*(-12 + x*(6 - x)))/6; + + return (0.0f); +} + +static float stbir__filter_catmullrom(float x, float s) +{ + STBIR__UNUSED_PARAM(s); + + x = (float)fabs(x); + + if (x < 1.0f) + return 1 - x*x*(2.5f - 1.5f*x); + else if (x < 2.0f) + return 2 - x*(4 + x*(0.5f*x - 2.5f)); + + return (0.0f); +} + +static float stbir__filter_mitchell(float x, float s) +{ + STBIR__UNUSED_PARAM(s); + + x = (float)fabs(x); + + if (x < 1.0f) + return (16 + x*x*(21 * x - 36))/18; + else if (x < 2.0f) + return (32 + x*(-60 + x*(36 - 7*x)))/18; + + return (0.0f); +} + +static float stbir__support_zero(float s) +{ + STBIR__UNUSED_PARAM(s); + return 0; +} + +static float stbir__support_one(float s) +{ + STBIR__UNUSED_PARAM(s); + return 1; +} + +static float stbir__support_two(float s) +{ + STBIR__UNUSED_PARAM(s); + return 2; +} + +static stbir__filter_info stbir__filter_info_table[] = { + { NULL, stbir__support_zero }, + { stbir__filter_trapezoid, stbir__support_trapezoid }, + { stbir__filter_triangle, stbir__support_one }, + { stbir__filter_cubic, stbir__support_two }, + { stbir__filter_catmullrom, stbir__support_two }, + { stbir__filter_mitchell, stbir__support_two }, +}; + +stbir__inline static int stbir__use_upsampling(float ratio) +{ + return ratio > 1; +} + +stbir__inline static int stbir__use_width_upsampling(stbir__info* stbir_info) +{ + return stbir__use_upsampling(stbir_info->horizontal_scale); +} + +stbir__inline static int stbir__use_height_upsampling(stbir__info* stbir_info) +{ + return stbir__use_upsampling(stbir_info->vertical_scale); +} + +// This is the maximum number of input samples that can affect an output sample +// with the given filter +static int stbir__get_filter_pixel_width(stbir_filter filter, float scale) +{ + STBIR_ASSERT(filter != 0); + STBIR_ASSERT(filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); + + if (stbir__use_upsampling(scale)) + return (int)ceil(stbir__filter_info_table[filter].support(1/scale) * 2); + else + return (int)ceil(stbir__filter_info_table[filter].support(scale) * 2 / scale); +} + +// This is how much to expand buffers to account for filters seeking outside +// the image boundaries. +static int stbir__get_filter_pixel_margin(stbir_filter filter, float scale) +{ + return stbir__get_filter_pixel_width(filter, scale) / 2; +} + +static int stbir__get_coefficient_width(stbir_filter filter, float scale) +{ + if (stbir__use_upsampling(scale)) + return (int)ceil(stbir__filter_info_table[filter].support(1 / scale) * 2); + else + return (int)ceil(stbir__filter_info_table[filter].support(scale) * 2); +} + +static int stbir__get_contributors(float scale, stbir_filter filter, int input_size, int output_size) +{ + if (stbir__use_upsampling(scale)) + return output_size; + else + return (input_size + stbir__get_filter_pixel_margin(filter, scale) * 2); +} + +static int stbir__get_total_horizontal_coefficients(stbir__info* info) +{ + return info->horizontal_num_contributors + * stbir__get_coefficient_width (info->horizontal_filter, info->horizontal_scale); +} + +static int stbir__get_total_vertical_coefficients(stbir__info* info) +{ + return info->vertical_num_contributors + * stbir__get_coefficient_width (info->vertical_filter, info->vertical_scale); +} + +static stbir__contributors* stbir__get_contributor(stbir__contributors* contributors, int n) +{ + return &contributors[n]; +} + +// For perf reasons this code is duplicated in stbir__resample_horizontal_upsample/downsample, +// if you change it here change it there too. +static float* stbir__get_coefficient(float* coefficients, stbir_filter filter, float scale, int n, int c) +{ + int width = stbir__get_coefficient_width(filter, scale); + return &coefficients[width*n + c]; +} + +static int stbir__edge_wrap_slow(stbir_edge edge, int n, int max) +{ + switch (edge) + { + case STBIR_EDGE_ZERO: + return 0; // we'll decode the wrong pixel here, and then overwrite with 0s later + + case STBIR_EDGE_CLAMP: + if (n < 0) + return 0; + + if (n >= max) + return max - 1; + + return n; // NOTREACHED + + case STBIR_EDGE_REFLECT: + { + if (n < 0) + { + if (n < max) + return -n; + else + return max - 1; + } + + if (n >= max) + { + int max2 = max * 2; + if (n >= max2) + return 0; + else + return max2 - n - 1; + } + + return n; // NOTREACHED + } + + case STBIR_EDGE_WRAP: + if (n >= 0) + return (n % max); + else + { + int m = (-n) % max; + + if (m != 0) + m = max - m; + + return (m); + } + // NOTREACHED + + default: + STBIR_ASSERT(!"Unimplemented edge type"); + return 0; + } +} + +stbir__inline static int stbir__edge_wrap(stbir_edge edge, int n, int max) +{ + // avoid per-pixel switch + if (n >= 0 && n < max) + return n; + return stbir__edge_wrap_slow(edge, n, max); +} + +// What input pixels contribute to this output pixel? +static void stbir__calculate_sample_range_upsample(int n, float out_filter_radius, float scale_ratio, float out_shift, int* in_first_pixel, int* in_last_pixel, float* in_center_of_out) +{ + float out_pixel_center = (float)n + 0.5f; + float out_pixel_influence_lowerbound = out_pixel_center - out_filter_radius; + float out_pixel_influence_upperbound = out_pixel_center + out_filter_radius; + + float in_pixel_influence_lowerbound = (out_pixel_influence_lowerbound + out_shift) / scale_ratio; + float in_pixel_influence_upperbound = (out_pixel_influence_upperbound + out_shift) / scale_ratio; + + *in_center_of_out = (out_pixel_center + out_shift) / scale_ratio; + *in_first_pixel = (int)(floor(in_pixel_influence_lowerbound + 0.5)); + *in_last_pixel = (int)(floor(in_pixel_influence_upperbound - 0.5)); +} + +// What output pixels does this input pixel contribute to? +static void stbir__calculate_sample_range_downsample(int n, float in_pixels_radius, float scale_ratio, float out_shift, int* out_first_pixel, int* out_last_pixel, float* out_center_of_in) +{ + float in_pixel_center = (float)n + 0.5f; + float in_pixel_influence_lowerbound = in_pixel_center - in_pixels_radius; + float in_pixel_influence_upperbound = in_pixel_center + in_pixels_radius; + + float out_pixel_influence_lowerbound = in_pixel_influence_lowerbound * scale_ratio - out_shift; + float out_pixel_influence_upperbound = in_pixel_influence_upperbound * scale_ratio - out_shift; + + *out_center_of_in = in_pixel_center * scale_ratio - out_shift; + *out_first_pixel = (int)(floor(out_pixel_influence_lowerbound + 0.5)); + *out_last_pixel = (int)(floor(out_pixel_influence_upperbound - 0.5)); +} + +static void stbir__calculate_coefficients_upsample(stbir_filter filter, float scale, int in_first_pixel, int in_last_pixel, float in_center_of_out, stbir__contributors* contributor, float* coefficient_group) +{ + int i; + float total_filter = 0; + float filter_scale; + + STBIR_ASSERT(in_last_pixel - in_first_pixel <= (int)ceil(stbir__filter_info_table[filter].support(1/scale) * 2)); // Taken directly from stbir__get_coefficient_width() which we can't call because we don't know if we're horizontal or vertical. + + contributor->n0 = in_first_pixel; + contributor->n1 = in_last_pixel; + + STBIR_ASSERT(contributor->n1 >= contributor->n0); + + for (i = 0; i <= in_last_pixel - in_first_pixel; i++) + { + float in_pixel_center = (float)(i + in_first_pixel) + 0.5f; + coefficient_group[i] = stbir__filter_info_table[filter].kernel(in_center_of_out - in_pixel_center, 1 / scale); + + // If the coefficient is zero, skip it. (Don't do the <0 check here, we want the influence of those outside pixels.) + if (i == 0 && !coefficient_group[i]) + { + contributor->n0 = ++in_first_pixel; + i--; + continue; + } + + total_filter += coefficient_group[i]; + } + + STBIR_ASSERT(stbir__filter_info_table[filter].kernel((float)(in_last_pixel + 1) + 0.5f - in_center_of_out, 1/scale) == 0); + + STBIR_ASSERT(total_filter > 0.9); + STBIR_ASSERT(total_filter < 1.1f); // Make sure it's not way off. + + // Make sure the sum of all coefficients is 1. + filter_scale = 1 / total_filter; + + for (i = 0; i <= in_last_pixel - in_first_pixel; i++) + coefficient_group[i] *= filter_scale; + + for (i = in_last_pixel - in_first_pixel; i >= 0; i--) + { + if (coefficient_group[i]) + break; + + // This line has no weight. We can skip it. + contributor->n1 = contributor->n0 + i - 1; + } +} + +static void stbir__calculate_coefficients_downsample(stbir_filter filter, float scale_ratio, int out_first_pixel, int out_last_pixel, float out_center_of_in, stbir__contributors* contributor, float* coefficient_group) +{ + int i; + + STBIR_ASSERT(out_last_pixel - out_first_pixel <= (int)ceil(stbir__filter_info_table[filter].support(scale_ratio) * 2)); // Taken directly from stbir__get_coefficient_width() which we can't call because we don't know if we're horizontal or vertical. + + contributor->n0 = out_first_pixel; + contributor->n1 = out_last_pixel; + + STBIR_ASSERT(contributor->n1 >= contributor->n0); + + for (i = 0; i <= out_last_pixel - out_first_pixel; i++) + { + float out_pixel_center = (float)(i + out_first_pixel) + 0.5f; + float x = out_pixel_center - out_center_of_in; + coefficient_group[i] = stbir__filter_info_table[filter].kernel(x, scale_ratio) * scale_ratio; + } + + STBIR_ASSERT(stbir__filter_info_table[filter].kernel((float)(out_last_pixel + 1) + 0.5f - out_center_of_in, scale_ratio) == 0); + + for (i = out_last_pixel - out_first_pixel; i >= 0; i--) + { + if (coefficient_group[i]) + break; + + // This line has no weight. We can skip it. + contributor->n1 = contributor->n0 + i - 1; + } +} + +static void stbir__normalize_downsample_coefficients(stbir__contributors* contributors, float* coefficients, stbir_filter filter, float scale_ratio, int input_size, int output_size) +{ + int num_contributors = stbir__get_contributors(scale_ratio, filter, input_size, output_size); + int num_coefficients = stbir__get_coefficient_width(filter, scale_ratio); + int i, j; + int skip; + + for (i = 0; i < output_size; i++) + { + float scale; + float total = 0; + + for (j = 0; j < num_contributors; j++) + { + if (i >= contributors[j].n0 && i <= contributors[j].n1) + { + float coefficient = *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i - contributors[j].n0); + total += coefficient; + } + else if (i < contributors[j].n0) + break; + } + + STBIR_ASSERT(total > 0.9f); + STBIR_ASSERT(total < 1.1f); + + scale = 1 / total; + + for (j = 0; j < num_contributors; j++) + { + if (i >= contributors[j].n0 && i <= contributors[j].n1) + *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i - contributors[j].n0) *= scale; + else if (i < contributors[j].n0) + break; + } + } + + // Optimize: Skip zero coefficients and contributions outside of image bounds. + // Do this after normalizing because normalization depends on the n0/n1 values. + for (j = 0; j < num_contributors; j++) + { + int range, max, width; + + skip = 0; + while (*stbir__get_coefficient(coefficients, filter, scale_ratio, j, skip) == 0) + skip++; + + contributors[j].n0 += skip; + + while (contributors[j].n0 < 0) + { + contributors[j].n0++; + skip++; + } + + range = contributors[j].n1 - contributors[j].n0 + 1; + max = stbir__min(num_coefficients, range); + + width = stbir__get_coefficient_width(filter, scale_ratio); + for (i = 0; i < max; i++) + { + if (i + skip >= width) + break; + + *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i) = *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i + skip); + } + + continue; + } + + // Using min to avoid writing into invalid pixels. + for (i = 0; i < num_contributors; i++) + contributors[i].n1 = stbir__min(contributors[i].n1, output_size - 1); +} + +// Each scan line uses the same kernel values so we should calculate the kernel +// values once and then we can use them for every scan line. +static void stbir__calculate_filters(stbir__contributors* contributors, float* coefficients, stbir_filter filter, float scale_ratio, float shift, int input_size, int output_size) +{ + int n; + int total_contributors = stbir__get_contributors(scale_ratio, filter, input_size, output_size); + + if (stbir__use_upsampling(scale_ratio)) + { + float out_pixels_radius = stbir__filter_info_table[filter].support(1 / scale_ratio) * scale_ratio; + + // Looping through out pixels + for (n = 0; n < total_contributors; n++) + { + float in_center_of_out; // Center of the current out pixel in the in pixel space + int in_first_pixel, in_last_pixel; + + stbir__calculate_sample_range_upsample(n, out_pixels_radius, scale_ratio, shift, &in_first_pixel, &in_last_pixel, &in_center_of_out); + + stbir__calculate_coefficients_upsample(filter, scale_ratio, in_first_pixel, in_last_pixel, in_center_of_out, stbir__get_contributor(contributors, n), stbir__get_coefficient(coefficients, filter, scale_ratio, n, 0)); + } + } + else + { + float in_pixels_radius = stbir__filter_info_table[filter].support(scale_ratio) / scale_ratio; + + // Looping through in pixels + for (n = 0; n < total_contributors; n++) + { + float out_center_of_in; // Center of the current out pixel in the in pixel space + int out_first_pixel, out_last_pixel; + int n_adjusted = n - stbir__get_filter_pixel_margin(filter, scale_ratio); + + stbir__calculate_sample_range_downsample(n_adjusted, in_pixels_radius, scale_ratio, shift, &out_first_pixel, &out_last_pixel, &out_center_of_in); + + stbir__calculate_coefficients_downsample(filter, scale_ratio, out_first_pixel, out_last_pixel, out_center_of_in, stbir__get_contributor(contributors, n), stbir__get_coefficient(coefficients, filter, scale_ratio, n, 0)); + } + + stbir__normalize_downsample_coefficients(contributors, coefficients, filter, scale_ratio, input_size, output_size); + } +} + +static float* stbir__get_decode_buffer(stbir__info* stbir_info) +{ + // The 0 index of the decode buffer starts after the margin. This makes + // it okay to use negative indexes on the decode buffer. + return &stbir_info->decode_buffer[stbir_info->horizontal_filter_pixel_margin * stbir_info->channels]; +} + +#define STBIR__DECODE(type, colorspace) ((int)(type) * (STBIR_MAX_COLORSPACES) + (int)(colorspace)) + +static void stbir__decode_scanline(stbir__info* stbir_info, int n) +{ + int c; + int channels = stbir_info->channels; + int alpha_channel = stbir_info->alpha_channel; + int type = stbir_info->type; + int colorspace = stbir_info->colorspace; + int input_w = stbir_info->input_w; + size_t input_stride_bytes = stbir_info->input_stride_bytes; + float* decode_buffer = stbir__get_decode_buffer(stbir_info); + stbir_edge edge_horizontal = stbir_info->edge_horizontal; + stbir_edge edge_vertical = stbir_info->edge_vertical; + size_t in_buffer_row_offset = stbir__edge_wrap(edge_vertical, n, stbir_info->input_h) * input_stride_bytes; + const void* input_data = (char *) stbir_info->input_data + in_buffer_row_offset; + int max_x = input_w + stbir_info->horizontal_filter_pixel_margin; + int decode = STBIR__DECODE(type, colorspace); + + int x = -stbir_info->horizontal_filter_pixel_margin; + + // special handling for STBIR_EDGE_ZERO because it needs to return an item that doesn't appear in the input, + // and we want to avoid paying overhead on every pixel if not STBIR_EDGE_ZERO + if (edge_vertical == STBIR_EDGE_ZERO && (n < 0 || n >= stbir_info->input_h)) + { + for (; x < max_x; x++) + for (c = 0; c < channels; c++) + decode_buffer[x*channels + c] = 0; + return; + } + + switch (decode) + { + case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_LINEAR): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = ((float)((const unsigned char*)input_data)[input_pixel_index + c]) / stbir__max_uint8_as_float; + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_SRGB): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = stbir__srgb_uchar_to_linear_float[((const unsigned char*)input_data)[input_pixel_index + c]]; + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + decode_buffer[decode_pixel_index + alpha_channel] = ((float)((const unsigned char*)input_data)[input_pixel_index + alpha_channel]) / stbir__max_uint8_as_float; + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_LINEAR): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = ((float)((const unsigned short*)input_data)[input_pixel_index + c]) / stbir__max_uint16_as_float; + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_SRGB): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = stbir__srgb_to_linear(((float)((const unsigned short*)input_data)[input_pixel_index + c]) / stbir__max_uint16_as_float); + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + decode_buffer[decode_pixel_index + alpha_channel] = ((float)((const unsigned short*)input_data)[input_pixel_index + alpha_channel]) / stbir__max_uint16_as_float; + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_LINEAR): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = (float)(((double)((const unsigned int*)input_data)[input_pixel_index + c]) / stbir__max_uint32_as_float); + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_SRGB): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = stbir__srgb_to_linear((float)(((double)((const unsigned int*)input_data)[input_pixel_index + c]) / stbir__max_uint32_as_float)); + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + decode_buffer[decode_pixel_index + alpha_channel] = (float)(((double)((const unsigned int*)input_data)[input_pixel_index + alpha_channel]) / stbir__max_uint32_as_float); + } + break; + + case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_LINEAR): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = ((const float*)input_data)[input_pixel_index + c]; + } + break; + + case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_SRGB): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = stbir__srgb_to_linear(((const float*)input_data)[input_pixel_index + c]); + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + decode_buffer[decode_pixel_index + alpha_channel] = ((const float*)input_data)[input_pixel_index + alpha_channel]; + } + + break; + + default: + STBIR_ASSERT(!"Unknown type/colorspace/channels combination."); + break; + } + + if (!(stbir_info->flags & STBIR_FLAG_ALPHA_PREMULTIPLIED)) + { + for (x = -stbir_info->horizontal_filter_pixel_margin; x < max_x; x++) + { + int decode_pixel_index = x * channels; + + // If the alpha value is 0 it will clobber the color values. Make sure it's not. + float alpha = decode_buffer[decode_pixel_index + alpha_channel]; +#ifndef STBIR_NO_ALPHA_EPSILON + if (stbir_info->type != STBIR_TYPE_FLOAT) { + alpha += STBIR_ALPHA_EPSILON; + decode_buffer[decode_pixel_index + alpha_channel] = alpha; + } +#endif + for (c = 0; c < channels; c++) + { + if (c == alpha_channel) + continue; + + decode_buffer[decode_pixel_index + c] *= alpha; + } + } + } + + if (edge_horizontal == STBIR_EDGE_ZERO) + { + for (x = -stbir_info->horizontal_filter_pixel_margin; x < 0; x++) + { + for (c = 0; c < channels; c++) + decode_buffer[x*channels + c] = 0; + } + for (x = input_w; x < max_x; x++) + { + for (c = 0; c < channels; c++) + decode_buffer[x*channels + c] = 0; + } + } +} + +static float* stbir__get_ring_buffer_entry(float* ring_buffer, int index, int ring_buffer_length) +{ + return &ring_buffer[index * ring_buffer_length]; +} + +static float* stbir__add_empty_ring_buffer_entry(stbir__info* stbir_info, int n) +{ + int ring_buffer_index; + float* ring_buffer; + + stbir_info->ring_buffer_last_scanline = n; + + if (stbir_info->ring_buffer_begin_index < 0) + { + ring_buffer_index = stbir_info->ring_buffer_begin_index = 0; + stbir_info->ring_buffer_first_scanline = n; + } + else + { + ring_buffer_index = (stbir_info->ring_buffer_begin_index + (stbir_info->ring_buffer_last_scanline - stbir_info->ring_buffer_first_scanline)) % stbir_info->ring_buffer_num_entries; + STBIR_ASSERT(ring_buffer_index != stbir_info->ring_buffer_begin_index); + } + + ring_buffer = stbir__get_ring_buffer_entry(stbir_info->ring_buffer, ring_buffer_index, stbir_info->ring_buffer_length_bytes / sizeof(float)); + memset(ring_buffer, 0, stbir_info->ring_buffer_length_bytes); + + return ring_buffer; +} + + +static void stbir__resample_horizontal_upsample(stbir__info* stbir_info, float* output_buffer) +{ + int x, k; + int output_w = stbir_info->output_w; + int channels = stbir_info->channels; + float* decode_buffer = stbir__get_decode_buffer(stbir_info); + stbir__contributors* horizontal_contributors = stbir_info->horizontal_contributors; + float* horizontal_coefficients = stbir_info->horizontal_coefficients; + int coefficient_width = stbir_info->horizontal_coefficient_width; + + for (x = 0; x < output_w; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int out_pixel_index = x * channels; + int coefficient_group = coefficient_width * x; + int coefficient_counter = 0; + + STBIR_ASSERT(n1 >= n0); + STBIR_ASSERT(n0 >= -stbir_info->horizontal_filter_pixel_margin); + STBIR_ASSERT(n1 >= -stbir_info->horizontal_filter_pixel_margin); + STBIR_ASSERT(n0 < stbir_info->input_w + stbir_info->horizontal_filter_pixel_margin); + STBIR_ASSERT(n1 < stbir_info->input_w + stbir_info->horizontal_filter_pixel_margin); + + switch (channels) { + case 1: + for (k = n0; k <= n1; k++) + { + int in_pixel_index = k * 1; + float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + } + break; + case 2: + for (k = n0; k <= n1; k++) + { + int in_pixel_index = k * 2; + float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + } + break; + case 3: + for (k = n0; k <= n1; k++) + { + int in_pixel_index = k * 3; + float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient; + } + break; + case 4: + for (k = n0; k <= n1; k++) + { + int in_pixel_index = k * 4; + float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient; + output_buffer[out_pixel_index + 3] += decode_buffer[in_pixel_index + 3] * coefficient; + } + break; + default: + for (k = n0; k <= n1; k++) + { + int in_pixel_index = k * channels; + float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++]; + int c; + STBIR_ASSERT(coefficient != 0); + for (c = 0; c < channels; c++) + output_buffer[out_pixel_index + c] += decode_buffer[in_pixel_index + c] * coefficient; + } + break; + } + } +} + +static void stbir__resample_horizontal_downsample(stbir__info* stbir_info, float* output_buffer) +{ + int x, k; + int input_w = stbir_info->input_w; + int channels = stbir_info->channels; + float* decode_buffer = stbir__get_decode_buffer(stbir_info); + stbir__contributors* horizontal_contributors = stbir_info->horizontal_contributors; + float* horizontal_coefficients = stbir_info->horizontal_coefficients; + int coefficient_width = stbir_info->horizontal_coefficient_width; + int filter_pixel_margin = stbir_info->horizontal_filter_pixel_margin; + int max_x = input_w + filter_pixel_margin * 2; + + STBIR_ASSERT(!stbir__use_width_upsampling(stbir_info)); + + switch (channels) { + case 1: + for (x = 0; x < max_x; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int in_x = x - filter_pixel_margin; + int in_pixel_index = in_x * 1; + int max_n = n1; + int coefficient_group = coefficient_width * x; + + for (k = n0; k <= max_n; k++) + { + int out_pixel_index = k * 1; + float coefficient = horizontal_coefficients[coefficient_group + k - n0]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + } + } + break; + + case 2: + for (x = 0; x < max_x; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int in_x = x - filter_pixel_margin; + int in_pixel_index = in_x * 2; + int max_n = n1; + int coefficient_group = coefficient_width * x; + + for (k = n0; k <= max_n; k++) + { + int out_pixel_index = k * 2; + float coefficient = horizontal_coefficients[coefficient_group + k - n0]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + } + } + break; + + case 3: + for (x = 0; x < max_x; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int in_x = x - filter_pixel_margin; + int in_pixel_index = in_x * 3; + int max_n = n1; + int coefficient_group = coefficient_width * x; + + for (k = n0; k <= max_n; k++) + { + int out_pixel_index = k * 3; + float coefficient = horizontal_coefficients[coefficient_group + k - n0]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient; + } + } + break; + + case 4: + for (x = 0; x < max_x; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int in_x = x - filter_pixel_margin; + int in_pixel_index = in_x * 4; + int max_n = n1; + int coefficient_group = coefficient_width * x; + + for (k = n0; k <= max_n; k++) + { + int out_pixel_index = k * 4; + float coefficient = horizontal_coefficients[coefficient_group + k - n0]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient; + output_buffer[out_pixel_index + 3] += decode_buffer[in_pixel_index + 3] * coefficient; + } + } + break; + + default: + for (x = 0; x < max_x; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int in_x = x - filter_pixel_margin; + int in_pixel_index = in_x * channels; + int max_n = n1; + int coefficient_group = coefficient_width * x; + + for (k = n0; k <= max_n; k++) + { + int c; + int out_pixel_index = k * channels; + float coefficient = horizontal_coefficients[coefficient_group + k - n0]; + STBIR_ASSERT(coefficient != 0); + for (c = 0; c < channels; c++) + output_buffer[out_pixel_index + c] += decode_buffer[in_pixel_index + c] * coefficient; + } + } + break; + } +} + +static void stbir__decode_and_resample_upsample(stbir__info* stbir_info, int n) +{ + // Decode the nth scanline from the source image into the decode buffer. + stbir__decode_scanline(stbir_info, n); + + // Now resample it into the ring buffer. + if (stbir__use_width_upsampling(stbir_info)) + stbir__resample_horizontal_upsample(stbir_info, stbir__add_empty_ring_buffer_entry(stbir_info, n)); + else + stbir__resample_horizontal_downsample(stbir_info, stbir__add_empty_ring_buffer_entry(stbir_info, n)); + + // Now it's sitting in the ring buffer ready to be used as source for the vertical sampling. +} + +static void stbir__decode_and_resample_downsample(stbir__info* stbir_info, int n) +{ + // Decode the nth scanline from the source image into the decode buffer. + stbir__decode_scanline(stbir_info, n); + + memset(stbir_info->horizontal_buffer, 0, stbir_info->output_w * stbir_info->channels * sizeof(float)); + + // Now resample it into the horizontal buffer. + if (stbir__use_width_upsampling(stbir_info)) + stbir__resample_horizontal_upsample(stbir_info, stbir_info->horizontal_buffer); + else + stbir__resample_horizontal_downsample(stbir_info, stbir_info->horizontal_buffer); + + // Now it's sitting in the horizontal buffer ready to be distributed into the ring buffers. +} + +// Get the specified scan line from the ring buffer. +static float* stbir__get_ring_buffer_scanline(int get_scanline, float* ring_buffer, int begin_index, int first_scanline, int ring_buffer_num_entries, int ring_buffer_length) +{ + int ring_buffer_index = (begin_index + (get_scanline - first_scanline)) % ring_buffer_num_entries; + return stbir__get_ring_buffer_entry(ring_buffer, ring_buffer_index, ring_buffer_length); +} + + +static void stbir__encode_scanline(stbir__info* stbir_info, int num_pixels, void *output_buffer, float *encode_buffer, int channels, int alpha_channel, int decode) +{ + int x; + int n; + int num_nonalpha; + stbir_uint16 nonalpha[STBIR_MAX_CHANNELS]; + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_PREMULTIPLIED)) + { + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + float alpha = encode_buffer[pixel_index + alpha_channel]; + float reciprocal_alpha = alpha ? 1.0f / alpha : 0; + + // unrolling this produced a 1% slowdown upscaling a large RGBA linear-space image on my machine - stb + for (n = 0; n < channels; n++) + if (n != alpha_channel) + encode_buffer[pixel_index + n] *= reciprocal_alpha; + + // We added in a small epsilon to prevent the color channel from being deleted with zero alpha. + // Because we only add it for integer types, it will automatically be discarded on integer + // conversion, so we don't need to subtract it back out (which would be problematic for + // numeric precision reasons). + } + } + + // build a table of all channels that need colorspace correction, so + // we don't perform colorspace correction on channels that don't need it. + for (x = 0, num_nonalpha = 0; x < channels; ++x) + { + if (x != alpha_channel || (stbir_info->flags & STBIR_FLAG_ALPHA_USES_COLORSPACE)) + { + nonalpha[num_nonalpha++] = (stbir_uint16)x; + } + } + + #define STBIR__ROUND_INT(f) ((int) ((f)+0.5)) + #define STBIR__ROUND_UINT(f) ((stbir_uint32) ((f)+0.5)) + + #ifdef STBIR__SATURATE_INT + #define STBIR__ENCODE_LINEAR8(f) stbir__saturate8 (STBIR__ROUND_INT((f) * stbir__max_uint8_as_float )) + #define STBIR__ENCODE_LINEAR16(f) stbir__saturate16(STBIR__ROUND_INT((f) * stbir__max_uint16_as_float)) + #else + #define STBIR__ENCODE_LINEAR8(f) (unsigned char ) STBIR__ROUND_INT(stbir__saturate(f) * stbir__max_uint8_as_float ) + #define STBIR__ENCODE_LINEAR16(f) (unsigned short) STBIR__ROUND_INT(stbir__saturate(f) * stbir__max_uint16_as_float) + #endif + + switch (decode) + { + case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_LINEAR): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < channels; n++) + { + int index = pixel_index + n; + ((unsigned char*)output_buffer)[index] = STBIR__ENCODE_LINEAR8(encode_buffer[index]); + } + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_SRGB): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < num_nonalpha; n++) + { + int index = pixel_index + nonalpha[n]; + ((unsigned char*)output_buffer)[index] = stbir__linear_to_srgb_uchar(encode_buffer[index]); + } + + if (!(stbir_info->flags & STBIR_FLAG_ALPHA_USES_COLORSPACE)) + ((unsigned char *)output_buffer)[pixel_index + alpha_channel] = STBIR__ENCODE_LINEAR8(encode_buffer[pixel_index+alpha_channel]); + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_LINEAR): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < channels; n++) + { + int index = pixel_index + n; + ((unsigned short*)output_buffer)[index] = STBIR__ENCODE_LINEAR16(encode_buffer[index]); + } + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_SRGB): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < num_nonalpha; n++) + { + int index = pixel_index + nonalpha[n]; + ((unsigned short*)output_buffer)[index] = (unsigned short)STBIR__ROUND_INT(stbir__linear_to_srgb(stbir__saturate(encode_buffer[index])) * stbir__max_uint16_as_float); + } + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + ((unsigned short*)output_buffer)[pixel_index + alpha_channel] = STBIR__ENCODE_LINEAR16(encode_buffer[pixel_index + alpha_channel]); + } + + break; + + case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_LINEAR): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < channels; n++) + { + int index = pixel_index + n; + ((unsigned int*)output_buffer)[index] = (unsigned int)STBIR__ROUND_UINT(((double)stbir__saturate(encode_buffer[index])) * stbir__max_uint32_as_float); + } + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_SRGB): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < num_nonalpha; n++) + { + int index = pixel_index + nonalpha[n]; + ((unsigned int*)output_buffer)[index] = (unsigned int)STBIR__ROUND_UINT(((double)stbir__linear_to_srgb(stbir__saturate(encode_buffer[index]))) * stbir__max_uint32_as_float); + } + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + ((unsigned int*)output_buffer)[pixel_index + alpha_channel] = (unsigned int)STBIR__ROUND_INT(((double)stbir__saturate(encode_buffer[pixel_index + alpha_channel])) * stbir__max_uint32_as_float); + } + break; + + case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_LINEAR): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < channels; n++) + { + int index = pixel_index + n; + ((float*)output_buffer)[index] = encode_buffer[index]; + } + } + break; + + case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_SRGB): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < num_nonalpha; n++) + { + int index = pixel_index + nonalpha[n]; + ((float*)output_buffer)[index] = stbir__linear_to_srgb(encode_buffer[index]); + } + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + ((float*)output_buffer)[pixel_index + alpha_channel] = encode_buffer[pixel_index + alpha_channel]; + } + break; + + default: + STBIR_ASSERT(!"Unknown type/colorspace/channels combination."); + break; + } +} + +static void stbir__resample_vertical_upsample(stbir__info* stbir_info, int n) +{ + int x, k; + int output_w = stbir_info->output_w; + stbir__contributors* vertical_contributors = stbir_info->vertical_contributors; + float* vertical_coefficients = stbir_info->vertical_coefficients; + int channels = stbir_info->channels; + int alpha_channel = stbir_info->alpha_channel; + int type = stbir_info->type; + int colorspace = stbir_info->colorspace; + int ring_buffer_entries = stbir_info->ring_buffer_num_entries; + void* output_data = stbir_info->output_data; + float* encode_buffer = stbir_info->encode_buffer; + int decode = STBIR__DECODE(type, colorspace); + int coefficient_width = stbir_info->vertical_coefficient_width; + int coefficient_counter; + int contributor = n; + + float* ring_buffer = stbir_info->ring_buffer; + int ring_buffer_begin_index = stbir_info->ring_buffer_begin_index; + int ring_buffer_first_scanline = stbir_info->ring_buffer_first_scanline; + int ring_buffer_length = stbir_info->ring_buffer_length_bytes/sizeof(float); + + int n0,n1, output_row_start; + int coefficient_group = coefficient_width * contributor; + + n0 = vertical_contributors[contributor].n0; + n1 = vertical_contributors[contributor].n1; + + output_row_start = n * stbir_info->output_stride_bytes; + + STBIR_ASSERT(stbir__use_height_upsampling(stbir_info)); + + memset(encode_buffer, 0, output_w * sizeof(float) * channels); + + // I tried reblocking this for better cache usage of encode_buffer + // (using x_outer, k, x_inner), but it lost speed. -- stb + + coefficient_counter = 0; + switch (channels) { + case 1: + for (k = n0; k <= n1; k++) + { + int coefficient_index = coefficient_counter++; + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + for (x = 0; x < output_w; ++x) + { + int in_pixel_index = x * 1; + encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient; + } + } + break; + case 2: + for (k = n0; k <= n1; k++) + { + int coefficient_index = coefficient_counter++; + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + for (x = 0; x < output_w; ++x) + { + int in_pixel_index = x * 2; + encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient; + encode_buffer[in_pixel_index + 1] += ring_buffer_entry[in_pixel_index + 1] * coefficient; + } + } + break; + case 3: + for (k = n0; k <= n1; k++) + { + int coefficient_index = coefficient_counter++; + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + for (x = 0; x < output_w; ++x) + { + int in_pixel_index = x * 3; + encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient; + encode_buffer[in_pixel_index + 1] += ring_buffer_entry[in_pixel_index + 1] * coefficient; + encode_buffer[in_pixel_index + 2] += ring_buffer_entry[in_pixel_index + 2] * coefficient; + } + } + break; + case 4: + for (k = n0; k <= n1; k++) + { + int coefficient_index = coefficient_counter++; + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + for (x = 0; x < output_w; ++x) + { + int in_pixel_index = x * 4; + encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient; + encode_buffer[in_pixel_index + 1] += ring_buffer_entry[in_pixel_index + 1] * coefficient; + encode_buffer[in_pixel_index + 2] += ring_buffer_entry[in_pixel_index + 2] * coefficient; + encode_buffer[in_pixel_index + 3] += ring_buffer_entry[in_pixel_index + 3] * coefficient; + } + } + break; + default: + for (k = n0; k <= n1; k++) + { + int coefficient_index = coefficient_counter++; + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + for (x = 0; x < output_w; ++x) + { + int in_pixel_index = x * channels; + int c; + for (c = 0; c < channels; c++) + encode_buffer[in_pixel_index + c] += ring_buffer_entry[in_pixel_index + c] * coefficient; + } + } + break; + } + stbir__encode_scanline(stbir_info, output_w, (char *) output_data + output_row_start, encode_buffer, channels, alpha_channel, decode); +} + +static void stbir__resample_vertical_downsample(stbir__info* stbir_info, int n) +{ + int x, k; + int output_w = stbir_info->output_w; + stbir__contributors* vertical_contributors = stbir_info->vertical_contributors; + float* vertical_coefficients = stbir_info->vertical_coefficients; + int channels = stbir_info->channels; + int ring_buffer_entries = stbir_info->ring_buffer_num_entries; + float* horizontal_buffer = stbir_info->horizontal_buffer; + int coefficient_width = stbir_info->vertical_coefficient_width; + int contributor = n + stbir_info->vertical_filter_pixel_margin; + + float* ring_buffer = stbir_info->ring_buffer; + int ring_buffer_begin_index = stbir_info->ring_buffer_begin_index; + int ring_buffer_first_scanline = stbir_info->ring_buffer_first_scanline; + int ring_buffer_length = stbir_info->ring_buffer_length_bytes/sizeof(float); + int n0,n1; + + n0 = vertical_contributors[contributor].n0; + n1 = vertical_contributors[contributor].n1; + + STBIR_ASSERT(!stbir__use_height_upsampling(stbir_info)); + + for (k = n0; k <= n1; k++) + { + int coefficient_index = k - n0; + int coefficient_group = coefficient_width * contributor; + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + + switch (channels) { + case 1: + for (x = 0; x < output_w; x++) + { + int in_pixel_index = x * 1; + ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient; + } + break; + case 2: + for (x = 0; x < output_w; x++) + { + int in_pixel_index = x * 2; + ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient; + ring_buffer_entry[in_pixel_index + 1] += horizontal_buffer[in_pixel_index + 1] * coefficient; + } + break; + case 3: + for (x = 0; x < output_w; x++) + { + int in_pixel_index = x * 3; + ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient; + ring_buffer_entry[in_pixel_index + 1] += horizontal_buffer[in_pixel_index + 1] * coefficient; + ring_buffer_entry[in_pixel_index + 2] += horizontal_buffer[in_pixel_index + 2] * coefficient; + } + break; + case 4: + for (x = 0; x < output_w; x++) + { + int in_pixel_index = x * 4; + ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient; + ring_buffer_entry[in_pixel_index + 1] += horizontal_buffer[in_pixel_index + 1] * coefficient; + ring_buffer_entry[in_pixel_index + 2] += horizontal_buffer[in_pixel_index + 2] * coefficient; + ring_buffer_entry[in_pixel_index + 3] += horizontal_buffer[in_pixel_index + 3] * coefficient; + } + break; + default: + for (x = 0; x < output_w; x++) + { + int in_pixel_index = x * channels; + + int c; + for (c = 0; c < channels; c++) + ring_buffer_entry[in_pixel_index + c] += horizontal_buffer[in_pixel_index + c] * coefficient; + } + break; + } + } +} + +static void stbir__buffer_loop_upsample(stbir__info* stbir_info) +{ + int y; + float scale_ratio = stbir_info->vertical_scale; + float out_scanlines_radius = stbir__filter_info_table[stbir_info->vertical_filter].support(1/scale_ratio) * scale_ratio; + + STBIR_ASSERT(stbir__use_height_upsampling(stbir_info)); + + for (y = 0; y < stbir_info->output_h; y++) + { + float in_center_of_out = 0; // Center of the current out scanline in the in scanline space + int in_first_scanline = 0, in_last_scanline = 0; + + stbir__calculate_sample_range_upsample(y, out_scanlines_radius, scale_ratio, stbir_info->vertical_shift, &in_first_scanline, &in_last_scanline, &in_center_of_out); + + STBIR_ASSERT(in_last_scanline - in_first_scanline + 1 <= stbir_info->ring_buffer_num_entries); + + if (stbir_info->ring_buffer_begin_index >= 0) + { + // Get rid of whatever we don't need anymore. + while (in_first_scanline > stbir_info->ring_buffer_first_scanline) + { + if (stbir_info->ring_buffer_first_scanline == stbir_info->ring_buffer_last_scanline) + { + // We just popped the last scanline off the ring buffer. + // Reset it to the empty state. + stbir_info->ring_buffer_begin_index = -1; + stbir_info->ring_buffer_first_scanline = 0; + stbir_info->ring_buffer_last_scanline = 0; + break; + } + else + { + stbir_info->ring_buffer_first_scanline++; + stbir_info->ring_buffer_begin_index = (stbir_info->ring_buffer_begin_index + 1) % stbir_info->ring_buffer_num_entries; + } + } + } + + // Load in new ones. + if (stbir_info->ring_buffer_begin_index < 0) + stbir__decode_and_resample_upsample(stbir_info, in_first_scanline); + + while (in_last_scanline > stbir_info->ring_buffer_last_scanline) + stbir__decode_and_resample_upsample(stbir_info, stbir_info->ring_buffer_last_scanline + 1); + + // Now all buffers should be ready to write a row of vertical sampling. + stbir__resample_vertical_upsample(stbir_info, y); + + STBIR_PROGRESS_REPORT((float)y / stbir_info->output_h); + } +} + +static void stbir__empty_ring_buffer(stbir__info* stbir_info, int first_necessary_scanline) +{ + int output_stride_bytes = stbir_info->output_stride_bytes; + int channels = stbir_info->channels; + int alpha_channel = stbir_info->alpha_channel; + int type = stbir_info->type; + int colorspace = stbir_info->colorspace; + int output_w = stbir_info->output_w; + void* output_data = stbir_info->output_data; + int decode = STBIR__DECODE(type, colorspace); + + float* ring_buffer = stbir_info->ring_buffer; + int ring_buffer_length = stbir_info->ring_buffer_length_bytes/sizeof(float); + + if (stbir_info->ring_buffer_begin_index >= 0) + { + // Get rid of whatever we don't need anymore. + while (first_necessary_scanline > stbir_info->ring_buffer_first_scanline) + { + if (stbir_info->ring_buffer_first_scanline >= 0 && stbir_info->ring_buffer_first_scanline < stbir_info->output_h) + { + int output_row_start = stbir_info->ring_buffer_first_scanline * output_stride_bytes; + float* ring_buffer_entry = stbir__get_ring_buffer_entry(ring_buffer, stbir_info->ring_buffer_begin_index, ring_buffer_length); + stbir__encode_scanline(stbir_info, output_w, (char *) output_data + output_row_start, ring_buffer_entry, channels, alpha_channel, decode); + STBIR_PROGRESS_REPORT((float)stbir_info->ring_buffer_first_scanline / stbir_info->output_h); + } + + if (stbir_info->ring_buffer_first_scanline == stbir_info->ring_buffer_last_scanline) + { + // We just popped the last scanline off the ring buffer. + // Reset it to the empty state. + stbir_info->ring_buffer_begin_index = -1; + stbir_info->ring_buffer_first_scanline = 0; + stbir_info->ring_buffer_last_scanline = 0; + break; + } + else + { + stbir_info->ring_buffer_first_scanline++; + stbir_info->ring_buffer_begin_index = (stbir_info->ring_buffer_begin_index + 1) % stbir_info->ring_buffer_num_entries; + } + } + } +} + +static void stbir__buffer_loop_downsample(stbir__info* stbir_info) +{ + int y; + float scale_ratio = stbir_info->vertical_scale; + int output_h = stbir_info->output_h; + float in_pixels_radius = stbir__filter_info_table[stbir_info->vertical_filter].support(scale_ratio) / scale_ratio; + int pixel_margin = stbir_info->vertical_filter_pixel_margin; + int max_y = stbir_info->input_h + pixel_margin; + + STBIR_ASSERT(!stbir__use_height_upsampling(stbir_info)); + + for (y = -pixel_margin; y < max_y; y++) + { + float out_center_of_in; // Center of the current out scanline in the in scanline space + int out_first_scanline, out_last_scanline; + + stbir__calculate_sample_range_downsample(y, in_pixels_radius, scale_ratio, stbir_info->vertical_shift, &out_first_scanline, &out_last_scanline, &out_center_of_in); + + STBIR_ASSERT(out_last_scanline - out_first_scanline + 1 <= stbir_info->ring_buffer_num_entries); + + if (out_last_scanline < 0 || out_first_scanline >= output_h) + continue; + + stbir__empty_ring_buffer(stbir_info, out_first_scanline); + + stbir__decode_and_resample_downsample(stbir_info, y); + + // Load in new ones. + if (stbir_info->ring_buffer_begin_index < 0) + stbir__add_empty_ring_buffer_entry(stbir_info, out_first_scanline); + + while (out_last_scanline > stbir_info->ring_buffer_last_scanline) + stbir__add_empty_ring_buffer_entry(stbir_info, stbir_info->ring_buffer_last_scanline + 1); + + // Now the horizontal buffer is ready to write to all ring buffer rows. + stbir__resample_vertical_downsample(stbir_info, y); + } + + stbir__empty_ring_buffer(stbir_info, stbir_info->output_h); +} + +static void stbir__setup(stbir__info *info, int input_w, int input_h, int output_w, int output_h, int channels) +{ + info->input_w = input_w; + info->input_h = input_h; + info->output_w = output_w; + info->output_h = output_h; + info->channels = channels; +} + +static void stbir__calculate_transform(stbir__info *info, float s0, float t0, float s1, float t1, float *transform) +{ + info->s0 = s0; + info->t0 = t0; + info->s1 = s1; + info->t1 = t1; + + if (transform) + { + info->horizontal_scale = transform[0]; + info->vertical_scale = transform[1]; + info->horizontal_shift = transform[2]; + info->vertical_shift = transform[3]; + } + else + { + info->horizontal_scale = ((float)info->output_w / info->input_w) / (s1 - s0); + info->vertical_scale = ((float)info->output_h / info->input_h) / (t1 - t0); + + info->horizontal_shift = s0 * info->output_w / (s1 - s0); + info->vertical_shift = t0 * info->output_h / (t1 - t0); + } +} + +static void stbir__choose_filter(stbir__info *info, stbir_filter h_filter, stbir_filter v_filter) +{ + if (h_filter == 0) + h_filter = stbir__use_upsampling(info->horizontal_scale) ? STBIR_DEFAULT_FILTER_UPSAMPLE : STBIR_DEFAULT_FILTER_DOWNSAMPLE; + if (v_filter == 0) + v_filter = stbir__use_upsampling(info->vertical_scale) ? STBIR_DEFAULT_FILTER_UPSAMPLE : STBIR_DEFAULT_FILTER_DOWNSAMPLE; + info->horizontal_filter = h_filter; + info->vertical_filter = v_filter; +} + +static stbir_uint32 stbir__calculate_memory(stbir__info *info) +{ + int pixel_margin = stbir__get_filter_pixel_margin(info->horizontal_filter, info->horizontal_scale); + int filter_height = stbir__get_filter_pixel_width(info->vertical_filter, info->vertical_scale); + + info->horizontal_num_contributors = stbir__get_contributors(info->horizontal_scale, info->horizontal_filter, info->input_w, info->output_w); + info->vertical_num_contributors = stbir__get_contributors(info->vertical_scale , info->vertical_filter , info->input_h, info->output_h); + + // One extra entry because floating point precision problems sometimes cause an extra to be necessary. + info->ring_buffer_num_entries = filter_height + 1; + + info->horizontal_contributors_size = info->horizontal_num_contributors * sizeof(stbir__contributors); + info->horizontal_coefficients_size = stbir__get_total_horizontal_coefficients(info) * sizeof(float); + info->vertical_contributors_size = info->vertical_num_contributors * sizeof(stbir__contributors); + info->vertical_coefficients_size = stbir__get_total_vertical_coefficients(info) * sizeof(float); + info->decode_buffer_size = (info->input_w + pixel_margin * 2) * info->channels * sizeof(float); + info->horizontal_buffer_size = info->output_w * info->channels * sizeof(float); + info->ring_buffer_size = info->output_w * info->channels * info->ring_buffer_num_entries * sizeof(float); + info->encode_buffer_size = info->output_w * info->channels * sizeof(float); + + STBIR_ASSERT(info->horizontal_filter != 0); + STBIR_ASSERT(info->horizontal_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); // this now happens too late + STBIR_ASSERT(info->vertical_filter != 0); + STBIR_ASSERT(info->vertical_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); // this now happens too late + + if (stbir__use_height_upsampling(info)) + // The horizontal buffer is for when we're downsampling the height and we + // can't output the result of sampling the decode buffer directly into the + // ring buffers. + info->horizontal_buffer_size = 0; + else + // The encode buffer is to retain precision in the height upsampling method + // and isn't used when height downsampling. + info->encode_buffer_size = 0; + + return info->horizontal_contributors_size + info->horizontal_coefficients_size + + info->vertical_contributors_size + info->vertical_coefficients_size + + info->decode_buffer_size + info->horizontal_buffer_size + + info->ring_buffer_size + info->encode_buffer_size; +} + +static int stbir__resize_allocated(stbir__info *info, + const void* input_data, int input_stride_in_bytes, + void* output_data, int output_stride_in_bytes, + int alpha_channel, stbir_uint32 flags, stbir_datatype type, + stbir_edge edge_horizontal, stbir_edge edge_vertical, stbir_colorspace colorspace, + void* tempmem, size_t tempmem_size_in_bytes) +{ + size_t memory_required = stbir__calculate_memory(info); + + int width_stride_input = input_stride_in_bytes ? input_stride_in_bytes : info->channels * info->input_w * stbir__type_size[type]; + int width_stride_output = output_stride_in_bytes ? output_stride_in_bytes : info->channels * info->output_w * stbir__type_size[type]; + +#ifdef STBIR_DEBUG_OVERWRITE_TEST +#define OVERWRITE_ARRAY_SIZE 8 + unsigned char overwrite_output_before_pre[OVERWRITE_ARRAY_SIZE]; + unsigned char overwrite_tempmem_before_pre[OVERWRITE_ARRAY_SIZE]; + unsigned char overwrite_output_after_pre[OVERWRITE_ARRAY_SIZE]; + unsigned char overwrite_tempmem_after_pre[OVERWRITE_ARRAY_SIZE]; + + size_t begin_forbidden = width_stride_output * (info->output_h - 1) + info->output_w * info->channels * stbir__type_size[type]; + memcpy(overwrite_output_before_pre, &((unsigned char*)output_data)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE); + memcpy(overwrite_output_after_pre, &((unsigned char*)output_data)[begin_forbidden], OVERWRITE_ARRAY_SIZE); + memcpy(overwrite_tempmem_before_pre, &((unsigned char*)tempmem)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE); + memcpy(overwrite_tempmem_after_pre, &((unsigned char*)tempmem)[tempmem_size_in_bytes], OVERWRITE_ARRAY_SIZE); +#endif + + STBIR_ASSERT(info->channels >= 0); + STBIR_ASSERT(info->channels <= STBIR_MAX_CHANNELS); + + if (info->channels < 0 || info->channels > STBIR_MAX_CHANNELS) + return 0; + + STBIR_ASSERT(info->horizontal_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); + STBIR_ASSERT(info->vertical_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); + + if (info->horizontal_filter >= STBIR__ARRAY_SIZE(stbir__filter_info_table)) + return 0; + if (info->vertical_filter >= STBIR__ARRAY_SIZE(stbir__filter_info_table)) + return 0; + + if (alpha_channel < 0) + flags |= STBIR_FLAG_ALPHA_USES_COLORSPACE | STBIR_FLAG_ALPHA_PREMULTIPLIED; + + if (!(flags&STBIR_FLAG_ALPHA_USES_COLORSPACE) || !(flags&STBIR_FLAG_ALPHA_PREMULTIPLIED)) { + STBIR_ASSERT(alpha_channel >= 0 && alpha_channel < info->channels); + } + + if (alpha_channel >= info->channels) + return 0; + + STBIR_ASSERT(tempmem); + + if (!tempmem) + return 0; + + STBIR_ASSERT(tempmem_size_in_bytes >= memory_required); + + if (tempmem_size_in_bytes < memory_required) + return 0; + + memset(tempmem, 0, tempmem_size_in_bytes); + + info->input_data = input_data; + info->input_stride_bytes = width_stride_input; + + info->output_data = output_data; + info->output_stride_bytes = width_stride_output; + + info->alpha_channel = alpha_channel; + info->flags = flags; + info->type = type; + info->edge_horizontal = edge_horizontal; + info->edge_vertical = edge_vertical; + info->colorspace = colorspace; + + info->horizontal_coefficient_width = stbir__get_coefficient_width (info->horizontal_filter, info->horizontal_scale); + info->vertical_coefficient_width = stbir__get_coefficient_width (info->vertical_filter , info->vertical_scale ); + info->horizontal_filter_pixel_width = stbir__get_filter_pixel_width (info->horizontal_filter, info->horizontal_scale); + info->vertical_filter_pixel_width = stbir__get_filter_pixel_width (info->vertical_filter , info->vertical_scale ); + info->horizontal_filter_pixel_margin = stbir__get_filter_pixel_margin(info->horizontal_filter, info->horizontal_scale); + info->vertical_filter_pixel_margin = stbir__get_filter_pixel_margin(info->vertical_filter , info->vertical_scale ); + + info->ring_buffer_length_bytes = info->output_w * info->channels * sizeof(float); + info->decode_buffer_pixels = info->input_w + info->horizontal_filter_pixel_margin * 2; + +#define STBIR__NEXT_MEMPTR(current, newtype) (newtype*)(((unsigned char*)current) + current##_size) + + info->horizontal_contributors = (stbir__contributors *) tempmem; + info->horizontal_coefficients = STBIR__NEXT_MEMPTR(info->horizontal_contributors, float); + info->vertical_contributors = STBIR__NEXT_MEMPTR(info->horizontal_coefficients, stbir__contributors); + info->vertical_coefficients = STBIR__NEXT_MEMPTR(info->vertical_contributors, float); + info->decode_buffer = STBIR__NEXT_MEMPTR(info->vertical_coefficients, float); + + if (stbir__use_height_upsampling(info)) + { + info->horizontal_buffer = NULL; + info->ring_buffer = STBIR__NEXT_MEMPTR(info->decode_buffer, float); + info->encode_buffer = STBIR__NEXT_MEMPTR(info->ring_buffer, float); + + STBIR_ASSERT((size_t)STBIR__NEXT_MEMPTR(info->encode_buffer, unsigned char) == (size_t)tempmem + tempmem_size_in_bytes); + } + else + { + info->horizontal_buffer = STBIR__NEXT_MEMPTR(info->decode_buffer, float); + info->ring_buffer = STBIR__NEXT_MEMPTR(info->horizontal_buffer, float); + info->encode_buffer = NULL; + + STBIR_ASSERT((size_t)STBIR__NEXT_MEMPTR(info->ring_buffer, unsigned char) == (size_t)tempmem + tempmem_size_in_bytes); + } + +#undef STBIR__NEXT_MEMPTR + + // This signals that the ring buffer is empty + info->ring_buffer_begin_index = -1; + + stbir__calculate_filters(info->horizontal_contributors, info->horizontal_coefficients, info->horizontal_filter, info->horizontal_scale, info->horizontal_shift, info->input_w, info->output_w); + stbir__calculate_filters(info->vertical_contributors, info->vertical_coefficients, info->vertical_filter, info->vertical_scale, info->vertical_shift, info->input_h, info->output_h); + + STBIR_PROGRESS_REPORT(0); + + if (stbir__use_height_upsampling(info)) + stbir__buffer_loop_upsample(info); + else + stbir__buffer_loop_downsample(info); + + STBIR_PROGRESS_REPORT(1); + +#ifdef STBIR_DEBUG_OVERWRITE_TEST + STBIR_ASSERT(memcmp(overwrite_output_before_pre, &((unsigned char*)output_data)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE) == 0); + STBIR_ASSERT(memcmp(overwrite_output_after_pre, &((unsigned char*)output_data)[begin_forbidden], OVERWRITE_ARRAY_SIZE) == 0); + STBIR_ASSERT(memcmp(overwrite_tempmem_before_pre, &((unsigned char*)tempmem)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE) == 0); + STBIR_ASSERT(memcmp(overwrite_tempmem_after_pre, &((unsigned char*)tempmem)[tempmem_size_in_bytes], OVERWRITE_ARRAY_SIZE) == 0); +#endif + + return 1; +} + + +static int stbir__resize_arbitrary( + void *alloc_context, + const void* input_data, int input_w, int input_h, int input_stride_in_bytes, + void* output_data, int output_w, int output_h, int output_stride_in_bytes, + float s0, float t0, float s1, float t1, float *transform, + int channels, int alpha_channel, stbir_uint32 flags, stbir_datatype type, + stbir_filter h_filter, stbir_filter v_filter, + stbir_edge edge_horizontal, stbir_edge edge_vertical, stbir_colorspace colorspace) +{ + stbir__info info; + int result; + size_t memory_required; + void* extra_memory; + + stbir__setup(&info, input_w, input_h, output_w, output_h, channels); + stbir__calculate_transform(&info, s0,t0,s1,t1,transform); + stbir__choose_filter(&info, h_filter, v_filter); + memory_required = stbir__calculate_memory(&info); + extra_memory = STBIR_MALLOC(memory_required, alloc_context); + + if (!extra_memory) + return 0; + + result = stbir__resize_allocated(&info, input_data, input_stride_in_bytes, + output_data, output_stride_in_bytes, + alpha_channel, flags, type, + edge_horizontal, edge_vertical, + colorspace, extra_memory, memory_required); + + STBIR_FREE(extra_memory, alloc_context); + + return result; +} + +STBIRDEF int stbir_resize_uint8( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels) +{ + return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,-1,0, STBIR_TYPE_UINT8, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT, + STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_LINEAR); +} + +STBIRDEF int stbir_resize_float( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + float *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels) +{ + return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,-1,0, STBIR_TYPE_FLOAT, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT, + STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_LINEAR); +} + +STBIRDEF int stbir_resize_uint8_srgb(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags) +{ + return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT8, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT, + STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_SRGB); +} + +STBIRDEF int stbir_resize_uint8_srgb_edgemode(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode) +{ + return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT8, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT, + edge_wrap_mode, edge_wrap_mode, STBIR_COLORSPACE_SRGB); +} + +STBIRDEF int stbir_resize_uint8_generic( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context) +{ + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT8, filter, filter, + edge_wrap_mode, edge_wrap_mode, space); +} + +STBIRDEF int stbir_resize_uint16_generic(const stbir_uint16 *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + stbir_uint16 *output_pixels , int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context) +{ + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT16, filter, filter, + edge_wrap_mode, edge_wrap_mode, space); +} + + +STBIRDEF int stbir_resize_float_generic( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + float *output_pixels , int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context) +{ + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_FLOAT, filter, filter, + edge_wrap_mode, edge_wrap_mode, space); +} + + +STBIRDEF int stbir_resize( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context) +{ + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, datatype, filter_horizontal, filter_vertical, + edge_mode_horizontal, edge_mode_vertical, space); +} + + +STBIRDEF int stbir_resize_subpixel(const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context, + float x_scale, float y_scale, + float x_offset, float y_offset) +{ + float transform[4]; + transform[0] = x_scale; + transform[1] = y_scale; + transform[2] = x_offset; + transform[3] = y_offset; + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,transform,num_channels,alpha_channel,flags, datatype, filter_horizontal, filter_vertical, + edge_mode_horizontal, edge_mode_vertical, space); +} + +STBIRDEF int stbir_resize_region( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context, + float s0, float t0, float s1, float t1) +{ + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + s0,t0,s1,t1,NULL,num_channels,alpha_channel,flags, datatype, filter_horizontal, filter_vertical, + edge_mode_horizontal, edge_mode_vertical, space); +} + +#endif // STB_IMAGE_RESIZE_IMPLEMENTATION + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +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. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +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 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. +------------------------------------------------------------------------------ +*/ diff --git a/Source/Dependencies/PhysicsFS/physfs.c b/Source/Dependencies/PhysicsFS/physfs.c new file mode 100644 index 000000000..fdb1405d5 --- /dev/null +++ b/Source/Dependencies/PhysicsFS/physfs.c @@ -0,0 +1,3318 @@ +/** + * PhysicsFS; a portable, flexible file i/o abstraction. + * + * Documentation is in physfs.h. It's verbose, honest. :) + * + * Please see the file LICENSE.txt in the source's root directory. + * + * This file written by Ryan C. Gordon. + */ + +#define __PHYSICSFS_INTERNAL__ +#include "physfs_internal.h" + +#if defined(_MSC_VER) +#include + +/* this code came from https://stackoverflow.com/a/8712996 */ +int __PHYSFS_msvc_vsnprintf(char *outBuf, size_t size, const char *format, va_list ap) +{ + int count = -1; + + if (size != 0) + count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap); + if (count == -1) + count = _vscprintf(format, ap); + + return count; +} + +int __PHYSFS_msvc_snprintf(char *outBuf, size_t size, const char *format, ...) +{ + int count; + va_list ap; + + va_start(ap, format); + count = __PHYSFS_msvc_vsnprintf(outBuf, size, format, ap); + va_end(ap); + + return count; +} +#endif + + +typedef struct __PHYSFS_DIRHANDLE__ +{ + void *opaque; /* Instance data unique to the archiver. */ + char *dirName; /* Path to archive in platform-dependent notation. */ + char *mountPoint; /* Mountpoint in virtual file tree. */ + const PHYSFS_Archiver *funcs; /* Ptr to archiver info for this handle. */ + struct __PHYSFS_DIRHANDLE__ *next; /* linked list stuff. */ +} DirHandle; + + +typedef struct __PHYSFS_FILEHANDLE__ +{ + PHYSFS_Io *io; /* Instance data unique to the archiver for this file. */ + PHYSFS_uint8 forReading; /* Non-zero if reading, zero if write/append */ + const DirHandle *dirHandle; /* Archiver instance that created this */ + PHYSFS_uint8 *buffer; /* Buffer, if set (NULL otherwise). Don't touch! */ + size_t bufsize; /* Bufsize, if set (0 otherwise). Don't touch! */ + size_t buffill; /* Buffer fill size. Don't touch! */ + size_t bufpos; /* Buffer position. Don't touch! */ + struct __PHYSFS_FILEHANDLE__ *next; /* linked list stuff. */ +} FileHandle; + + +typedef struct __PHYSFS_ERRSTATETYPE__ +{ + void *tid; + PHYSFS_ErrorCode code; + struct __PHYSFS_ERRSTATETYPE__ *next; +} ErrState; + + +/* General PhysicsFS state ... */ +static int initialized = 0; +static ErrState *errorStates = NULL; +static DirHandle *searchPath = NULL; +static DirHandle *writeDir = NULL; +static FileHandle *openWriteList = NULL; +static FileHandle *openReadList = NULL; +static char *baseDir = NULL; +static char *userDir = NULL; +static char *prefDir = NULL; +static int allowSymLinks = 0; +static PHYSFS_Archiver **archivers = NULL; +static PHYSFS_ArchiveInfo **archiveInfo = NULL; +static volatile size_t numArchivers = 0; + +/* mutexes ... */ +static void *errorLock = NULL; /* protects error message list. */ +static void *stateLock = NULL; /* protects other PhysFS static state. */ + +/* allocator ... */ +static int externalAllocator = 0; +PHYSFS_Allocator allocator; + + +#ifdef PHYSFS_NEED_ATOMIC_OP_FALLBACK +static inline int __PHYSFS_atomicAdd(int *ptrval, const int val) +{ + int retval; + __PHYSFS_platformGrabMutex(stateLock); + retval = *ptrval; + *ptrval = retval + val; + __PHYSFS_platformReleaseMutex(stateLock); + return retval; +} /* __PHYSFS_atomicAdd */ + +int __PHYSFS_ATOMIC_INCR(int *ptrval) +{ + return __PHYSFS_atomicAdd(ptrval, 1); +} /* __PHYSFS_ATOMIC_INCR */ + +int __PHYSFS_ATOMIC_DECR(int *ptrval) +{ + return __PHYSFS_atomicAdd(ptrval, -1); +} /* __PHYSFS_ATOMIC_DECR */ +#endif + + + +/* PHYSFS_Io implementation for i/o to physical filesystem... */ + +/* !!! FIXME: maybe refcount the paths in a string pool? */ +typedef struct __PHYSFS_NativeIoInfo +{ + void *handle; + const char *path; + int mode; /* 'r', 'w', or 'a' */ +} NativeIoInfo; + +static PHYSFS_sint64 nativeIo_read(PHYSFS_Io *io, void *buf, PHYSFS_uint64 len) +{ + NativeIoInfo *info = (NativeIoInfo *) io->opaque; + return __PHYSFS_platformRead(info->handle, buf, len); +} /* nativeIo_read */ + +static PHYSFS_sint64 nativeIo_write(PHYSFS_Io *io, const void *buffer, + PHYSFS_uint64 len) +{ + NativeIoInfo *info = (NativeIoInfo *) io->opaque; + return __PHYSFS_platformWrite(info->handle, buffer, len); +} /* nativeIo_write */ + +static int nativeIo_seek(PHYSFS_Io *io, PHYSFS_uint64 offset) +{ + NativeIoInfo *info = (NativeIoInfo *) io->opaque; + return __PHYSFS_platformSeek(info->handle, offset); +} /* nativeIo_seek */ + +static PHYSFS_sint64 nativeIo_tell(PHYSFS_Io *io) +{ + NativeIoInfo *info = (NativeIoInfo *) io->opaque; + return __PHYSFS_platformTell(info->handle); +} /* nativeIo_tell */ + +static PHYSFS_sint64 nativeIo_length(PHYSFS_Io *io) +{ + NativeIoInfo *info = (NativeIoInfo *) io->opaque; + return __PHYSFS_platformFileLength(info->handle); +} /* nativeIo_length */ + +static PHYSFS_Io *nativeIo_duplicate(PHYSFS_Io *io) +{ + NativeIoInfo *info = (NativeIoInfo *) io->opaque; + return __PHYSFS_createNativeIo(info->path, info->mode); +} /* nativeIo_duplicate */ + +static int nativeIo_flush(PHYSFS_Io *io) +{ + NativeIoInfo *info = (NativeIoInfo *) io->opaque; + return __PHYSFS_platformFlush(info->handle); +} /* nativeIo_flush */ + +static void nativeIo_destroy(PHYSFS_Io *io) +{ + NativeIoInfo *info = (NativeIoInfo *) io->opaque; + __PHYSFS_platformClose(info->handle); + allocator.Free((void *) info->path); + allocator.Free(info); + allocator.Free(io); +} /* nativeIo_destroy */ + +static const PHYSFS_Io __PHYSFS_nativeIoInterface = +{ + CURRENT_PHYSFS_IO_API_VERSION, NULL, + nativeIo_read, + nativeIo_write, + nativeIo_seek, + nativeIo_tell, + nativeIo_length, + nativeIo_duplicate, + nativeIo_flush, + nativeIo_destroy +}; + +PHYSFS_Io *__PHYSFS_createNativeIo(const char *path, const int mode) +{ + PHYSFS_Io *io = NULL; + NativeIoInfo *info = NULL; + void *handle = NULL; + char *pathdup = NULL; + + assert((mode == 'r') || (mode == 'w') || (mode == 'a')); + + io = (PHYSFS_Io *) allocator.Malloc(sizeof (PHYSFS_Io)); + GOTO_IF(!io, PHYSFS_ERR_OUT_OF_MEMORY, createNativeIo_failed); + info = (NativeIoInfo *) allocator.Malloc(sizeof (NativeIoInfo)); + GOTO_IF(!info, PHYSFS_ERR_OUT_OF_MEMORY, createNativeIo_failed); + pathdup = (char *) allocator.Malloc(strlen(path) + 1); + GOTO_IF(!pathdup, PHYSFS_ERR_OUT_OF_MEMORY, createNativeIo_failed); + + if (mode == 'r') + handle = __PHYSFS_platformOpenRead(path); + else if (mode == 'w') + handle = __PHYSFS_platformOpenWrite(path); + else if (mode == 'a') + handle = __PHYSFS_platformOpenAppend(path); + + GOTO_IF_ERRPASS(!handle, createNativeIo_failed); + + strcpy(pathdup, path); + info->handle = handle; + info->path = pathdup; + info->mode = mode; + memcpy(io, &__PHYSFS_nativeIoInterface, sizeof (*io)); + io->opaque = info; + return io; + +createNativeIo_failed: + if (handle != NULL) __PHYSFS_platformClose(handle); + if (pathdup != NULL) allocator.Free(pathdup); + if (info != NULL) allocator.Free(info); + if (io != NULL) allocator.Free(io); + return NULL; +} /* __PHYSFS_createNativeIo */ + + +/* PHYSFS_Io implementation for i/o to a memory buffer... */ + +typedef struct __PHYSFS_MemoryIoInfo +{ + const PHYSFS_uint8 *buf; + PHYSFS_uint64 len; + PHYSFS_uint64 pos; + PHYSFS_Io *parent; + int refcount; + void (*destruct)(void *); +} MemoryIoInfo; + +static PHYSFS_sint64 memoryIo_read(PHYSFS_Io *io, void *buf, PHYSFS_uint64 len) +{ + MemoryIoInfo *info = (MemoryIoInfo *) io->opaque; + const PHYSFS_uint64 avail = info->len - info->pos; + assert(avail <= info->len); + + if (avail == 0) + return 0; /* we're at EOF; nothing to do. */ + + if (len > avail) + len = avail; + + memcpy(buf, info->buf + info->pos, (size_t) len); + info->pos += len; + return len; +} /* memoryIo_read */ + +static PHYSFS_sint64 memoryIo_write(PHYSFS_Io *io, const void *buffer, + PHYSFS_uint64 len) +{ + BAIL(PHYSFS_ERR_OPEN_FOR_READING, -1); +} /* memoryIo_write */ + +static int memoryIo_seek(PHYSFS_Io *io, PHYSFS_uint64 offset) +{ + MemoryIoInfo *info = (MemoryIoInfo *) io->opaque; + BAIL_IF(offset > info->len, PHYSFS_ERR_PAST_EOF, 0); + info->pos = offset; + return 1; +} /* memoryIo_seek */ + +static PHYSFS_sint64 memoryIo_tell(PHYSFS_Io *io) +{ + const MemoryIoInfo *info = (MemoryIoInfo *) io->opaque; + return (PHYSFS_sint64) info->pos; +} /* memoryIo_tell */ + +static PHYSFS_sint64 memoryIo_length(PHYSFS_Io *io) +{ + const MemoryIoInfo *info = (MemoryIoInfo *) io->opaque; + return (PHYSFS_sint64) info->len; +} /* memoryIo_length */ + +static PHYSFS_Io *memoryIo_duplicate(PHYSFS_Io *io) +{ + MemoryIoInfo *info = (MemoryIoInfo *) io->opaque; + MemoryIoInfo *newinfo = NULL; + PHYSFS_Io *parent = info->parent; + PHYSFS_Io *retval = NULL; + + /* avoid deep copies. */ + assert((!parent) || (!((MemoryIoInfo *) parent->opaque)->parent) ); + + /* share the buffer between duplicates. */ + if (parent != NULL) /* dup the parent, increment its refcount. */ + return parent->duplicate(parent); + + /* we're the parent. */ + + retval = (PHYSFS_Io *) allocator.Malloc(sizeof (PHYSFS_Io)); + BAIL_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + newinfo = (MemoryIoInfo *) allocator.Malloc(sizeof (MemoryIoInfo)); + if (!newinfo) + { + allocator.Free(retval); + BAIL(PHYSFS_ERR_OUT_OF_MEMORY, NULL); + } /* if */ + + __PHYSFS_ATOMIC_INCR(&info->refcount); + + memset(newinfo, '\0', sizeof (*info)); + newinfo->buf = info->buf; + newinfo->len = info->len; + newinfo->pos = 0; + newinfo->parent = io; + newinfo->refcount = 0; + newinfo->destruct = NULL; + + memcpy(retval, io, sizeof (*retval)); + retval->opaque = newinfo; + return retval; +} /* memoryIo_duplicate */ + +static int memoryIo_flush(PHYSFS_Io *io) { return 1; /* it's read-only. */ } + +static void memoryIo_destroy(PHYSFS_Io *io) +{ + MemoryIoInfo *info = (MemoryIoInfo *) io->opaque; + PHYSFS_Io *parent = info->parent; + + if (parent != NULL) + { + assert(info->buf == ((MemoryIoInfo *) info->parent->opaque)->buf); + assert(info->len == ((MemoryIoInfo *) info->parent->opaque)->len); + assert(info->refcount == 0); + assert(info->destruct == NULL); + allocator.Free(info); + allocator.Free(io); + parent->destroy(parent); /* decrements refcount. */ + return; + } /* if */ + + /* we _are_ the parent. */ + assert(info->refcount > 0); /* even in a race, we hold a reference. */ + + if (__PHYSFS_ATOMIC_DECR(&info->refcount) == 0) + { + void (*destruct)(void *) = info->destruct; + void *buf = (void *) info->buf; + io->opaque = NULL; /* kill this here in case of race. */ + allocator.Free(info); + allocator.Free(io); + if (destruct != NULL) + destruct(buf); + } /* if */ +} /* memoryIo_destroy */ + + +static const PHYSFS_Io __PHYSFS_memoryIoInterface = +{ + CURRENT_PHYSFS_IO_API_VERSION, NULL, + memoryIo_read, + memoryIo_write, + memoryIo_seek, + memoryIo_tell, + memoryIo_length, + memoryIo_duplicate, + memoryIo_flush, + memoryIo_destroy +}; + +PHYSFS_Io *__PHYSFS_createMemoryIo(const void *buf, PHYSFS_uint64 len, + void (*destruct)(void *)) +{ + PHYSFS_Io *io = NULL; + MemoryIoInfo *info = NULL; + + io = (PHYSFS_Io *) allocator.Malloc(sizeof (PHYSFS_Io)); + GOTO_IF(!io, PHYSFS_ERR_OUT_OF_MEMORY, createMemoryIo_failed); + info = (MemoryIoInfo *) allocator.Malloc(sizeof (MemoryIoInfo)); + GOTO_IF(!info, PHYSFS_ERR_OUT_OF_MEMORY, createMemoryIo_failed); + + memset(info, '\0', sizeof (*info)); + info->buf = (const PHYSFS_uint8 *) buf; + info->len = len; + info->pos = 0; + info->parent = NULL; + info->refcount = 1; + info->destruct = destruct; + + memcpy(io, &__PHYSFS_memoryIoInterface, sizeof (*io)); + io->opaque = info; + return io; + +createMemoryIo_failed: + if (info != NULL) allocator.Free(info); + if (io != NULL) allocator.Free(io); + return NULL; +} /* __PHYSFS_createMemoryIo */ + + +/* PHYSFS_Io implementation for i/o to a PHYSFS_File... */ + +static PHYSFS_sint64 handleIo_read(PHYSFS_Io *io, void *buf, PHYSFS_uint64 len) +{ + return PHYSFS_readBytes((PHYSFS_File *) io->opaque, buf, len); +} /* handleIo_read */ + +static PHYSFS_sint64 handleIo_write(PHYSFS_Io *io, const void *buffer, + PHYSFS_uint64 len) +{ + return PHYSFS_writeBytes((PHYSFS_File *) io->opaque, buffer, len); +} /* handleIo_write */ + +static int handleIo_seek(PHYSFS_Io *io, PHYSFS_uint64 offset) +{ + return PHYSFS_seek((PHYSFS_File *) io->opaque, offset); +} /* handleIo_seek */ + +static PHYSFS_sint64 handleIo_tell(PHYSFS_Io *io) +{ + return PHYSFS_tell((PHYSFS_File *) io->opaque); +} /* handleIo_tell */ + +static PHYSFS_sint64 handleIo_length(PHYSFS_Io *io) +{ + return PHYSFS_fileLength((PHYSFS_File *) io->opaque); +} /* handleIo_length */ + +static PHYSFS_Io *handleIo_duplicate(PHYSFS_Io *io) +{ + /* + * There's no duplicate at the PHYSFS_File level, so we break the + * abstraction. We're allowed to: we're physfs.c! + */ + FileHandle *origfh = (FileHandle *) io->opaque; + FileHandle *newfh = (FileHandle *) allocator.Malloc(sizeof (FileHandle)); + PHYSFS_Io *retval = NULL; + + GOTO_IF(!newfh, PHYSFS_ERR_OUT_OF_MEMORY, handleIo_dupe_failed); + memset(newfh, '\0', sizeof (*newfh)); + + retval = (PHYSFS_Io *) allocator.Malloc(sizeof (PHYSFS_Io)); + GOTO_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, handleIo_dupe_failed); + +#if 0 /* we don't buffer the duplicate, at least not at the moment. */ + if (origfh->buffer != NULL) + { + newfh->buffer = (PHYSFS_uint8 *) allocator.Malloc(origfh->bufsize); + if (!newfh->buffer) + GOTO(PHYSFS_ERR_OUT_OF_MEMORY, handleIo_dupe_failed); + newfh->bufsize = origfh->bufsize; + } /* if */ +#endif + + newfh->io = origfh->io->duplicate(origfh->io); + GOTO_IF_ERRPASS(!newfh->io, handleIo_dupe_failed); + + newfh->forReading = origfh->forReading; + newfh->dirHandle = origfh->dirHandle; + + __PHYSFS_platformGrabMutex(stateLock); + if (newfh->forReading) + { + newfh->next = openReadList; + openReadList = newfh; + } /* if */ + else + { + newfh->next = openWriteList; + openWriteList = newfh; + } /* else */ + __PHYSFS_platformReleaseMutex(stateLock); + + memcpy(retval, io, sizeof (PHYSFS_Io)); + retval->opaque = newfh; + return retval; + +handleIo_dupe_failed: + if (newfh) + { + if (newfh->io != NULL) newfh->io->destroy(newfh->io); + if (newfh->buffer != NULL) allocator.Free(newfh->buffer); + allocator.Free(newfh); + } /* if */ + + return NULL; +} /* handleIo_duplicate */ + +static int handleIo_flush(PHYSFS_Io *io) +{ + return PHYSFS_flush((PHYSFS_File *) io->opaque); +} /* handleIo_flush */ + +static void handleIo_destroy(PHYSFS_Io *io) +{ + if (io->opaque != NULL) + PHYSFS_close((PHYSFS_File *) io->opaque); + allocator.Free(io); +} /* handleIo_destroy */ + +static const PHYSFS_Io __PHYSFS_handleIoInterface = +{ + CURRENT_PHYSFS_IO_API_VERSION, NULL, + handleIo_read, + handleIo_write, + handleIo_seek, + handleIo_tell, + handleIo_length, + handleIo_duplicate, + handleIo_flush, + handleIo_destroy +}; + +static PHYSFS_Io *__PHYSFS_createHandleIo(PHYSFS_File *f) +{ + PHYSFS_Io *io = (PHYSFS_Io *) allocator.Malloc(sizeof (PHYSFS_Io)); + BAIL_IF(!io, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + memcpy(io, &__PHYSFS_handleIoInterface, sizeof (*io)); + io->opaque = f; + return io; +} /* __PHYSFS_createHandleIo */ + + +/* functions ... */ + +typedef struct +{ + char **list; + PHYSFS_uint32 size; + PHYSFS_ErrorCode errcode; +} EnumStringListCallbackData; + +static void enumStringListCallback(void *data, const char *str) +{ + void *ptr; + char *newstr; + EnumStringListCallbackData *pecd = (EnumStringListCallbackData *) data; + + if (pecd->errcode) + return; + + ptr = allocator.Realloc(pecd->list, (pecd->size + 2) * sizeof (char *)); + newstr = (char *) allocator.Malloc(strlen(str) + 1); + if (ptr != NULL) + pecd->list = (char **) ptr; + + if ((ptr == NULL) || (newstr == NULL)) + { + pecd->errcode = PHYSFS_ERR_OUT_OF_MEMORY; + pecd->list[pecd->size] = NULL; + PHYSFS_freeList(pecd->list); + return; + } /* if */ + + strcpy(newstr, str); + pecd->list[pecd->size] = newstr; + pecd->size++; +} /* enumStringListCallback */ + + +static char **doEnumStringList(void (*func)(PHYSFS_StringCallback, void *)) +{ + EnumStringListCallbackData ecd; + memset(&ecd, '\0', sizeof (ecd)); + ecd.list = (char **) allocator.Malloc(sizeof (char *)); + BAIL_IF(!ecd.list, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + func(enumStringListCallback, &ecd); + + if (ecd.errcode) + { + PHYSFS_setErrorCode(ecd.errcode); + return NULL; + } /* if */ + + ecd.list[ecd.size] = NULL; + return ecd.list; +} /* doEnumStringList */ + + +static void __PHYSFS_bubble_sort(void *a, size_t lo, size_t hi, + int (*cmpfn)(void *, size_t, size_t), + void (*swapfn)(void *, size_t, size_t)) +{ + size_t i; + int sorted; + + do + { + sorted = 1; + for (i = lo; i < hi; i++) + { + if (cmpfn(a, i, i + 1) > 0) + { + swapfn(a, i, i + 1); + sorted = 0; + } /* if */ + } /* for */ + } while (!sorted); +} /* __PHYSFS_bubble_sort */ + + +static void __PHYSFS_quick_sort(void *a, size_t lo, size_t hi, + int (*cmpfn)(void *, size_t, size_t), + void (*swapfn)(void *, size_t, size_t)) +{ + size_t i; + size_t j; + size_t v; + + if ((hi - lo) <= PHYSFS_QUICKSORT_THRESHOLD) + __PHYSFS_bubble_sort(a, lo, hi, cmpfn, swapfn); + else + { + i = (hi + lo) / 2; + + if (cmpfn(a, lo, i) > 0) swapfn(a, lo, i); + if (cmpfn(a, lo, hi) > 0) swapfn(a, lo, hi); + if (cmpfn(a, i, hi) > 0) swapfn(a, i, hi); + + j = hi - 1; + swapfn(a, i, j); + i = lo; + v = j; + while (1) + { + while(cmpfn(a, ++i, v) < 0) { /* do nothing */ } + while(cmpfn(a, --j, v) > 0) { /* do nothing */ } + if (j < i) + break; + swapfn(a, i, j); + } /* while */ + if (i != (hi-1)) + swapfn(a, i, hi-1); + __PHYSFS_quick_sort(a, lo, j, cmpfn, swapfn); + __PHYSFS_quick_sort(a, i+1, hi, cmpfn, swapfn); + } /* else */ +} /* __PHYSFS_quick_sort */ + + +void __PHYSFS_sort(void *entries, size_t max, + int (*cmpfn)(void *, size_t, size_t), + void (*swapfn)(void *, size_t, size_t)) +{ + /* + * Quicksort w/ Bubblesort fallback algorithm inspired by code from here: + * https://www.cs.ubc.ca/spider/harrison/Java/sorting-demo.html + */ + if (max > 0) + __PHYSFS_quick_sort(entries, 0, max - 1, cmpfn, swapfn); +} /* __PHYSFS_sort */ + + +static ErrState *findErrorForCurrentThread(void) +{ + ErrState *i; + void *tid; + + if (errorLock != NULL) + __PHYSFS_platformGrabMutex(errorLock); + + if (errorStates != NULL) + { + tid = __PHYSFS_platformGetThreadID(); + + for (i = errorStates; i != NULL; i = i->next) + { + if (i->tid == tid) + { + if (errorLock != NULL) + __PHYSFS_platformReleaseMutex(errorLock); + return i; + } /* if */ + } /* for */ + } /* if */ + + if (errorLock != NULL) + __PHYSFS_platformReleaseMutex(errorLock); + + return NULL; /* no error available. */ +} /* findErrorForCurrentThread */ + + +/* this doesn't reset the error state. */ +static inline PHYSFS_ErrorCode currentErrorCode(void) +{ + const ErrState *err = findErrorForCurrentThread(); + return err ? err->code : PHYSFS_ERR_OK; +} /* currentErrorCode */ + + +PHYSFS_ErrorCode PHYSFS_getLastErrorCode(void) +{ + ErrState *err = findErrorForCurrentThread(); + const PHYSFS_ErrorCode retval = (err) ? err->code : PHYSFS_ERR_OK; + if (err) + err->code = PHYSFS_ERR_OK; + return retval; +} /* PHYSFS_getLastErrorCode */ + + +PHYSFS_DECL const char *PHYSFS_getErrorByCode(PHYSFS_ErrorCode code) +{ + switch (code) + { + case PHYSFS_ERR_OK: return "no error"; + case PHYSFS_ERR_OTHER_ERROR: return "unknown error"; + case PHYSFS_ERR_OUT_OF_MEMORY: return "out of memory"; + case PHYSFS_ERR_NOT_INITIALIZED: return "not initialized"; + case PHYSFS_ERR_IS_INITIALIZED: return "already initialized"; + case PHYSFS_ERR_ARGV0_IS_NULL: return "argv[0] is NULL"; + case PHYSFS_ERR_UNSUPPORTED: return "unsupported"; + case PHYSFS_ERR_PAST_EOF: return "past end of file"; + case PHYSFS_ERR_FILES_STILL_OPEN: return "files still open"; + case PHYSFS_ERR_INVALID_ARGUMENT: return "invalid argument"; + case PHYSFS_ERR_NOT_MOUNTED: return "not mounted"; + case PHYSFS_ERR_NOT_FOUND: return "not found"; + case PHYSFS_ERR_SYMLINK_FORBIDDEN: return "symlinks are forbidden"; + case PHYSFS_ERR_NO_WRITE_DIR: return "write directory is not set"; + case PHYSFS_ERR_OPEN_FOR_READING: return "file open for reading"; + case PHYSFS_ERR_OPEN_FOR_WRITING: return "file open for writing"; + case PHYSFS_ERR_NOT_A_FILE: return "not a file"; + case PHYSFS_ERR_READ_ONLY: return "read-only filesystem"; + case PHYSFS_ERR_CORRUPT: return "corrupted"; + case PHYSFS_ERR_SYMLINK_LOOP: return "infinite symbolic link loop"; + case PHYSFS_ERR_IO: return "i/o error"; + case PHYSFS_ERR_PERMISSION: return "permission denied"; + case PHYSFS_ERR_NO_SPACE: return "no space available for writing"; + case PHYSFS_ERR_BAD_FILENAME: return "filename is illegal or insecure"; + case PHYSFS_ERR_BUSY: return "tried to modify a file the OS needs"; + case PHYSFS_ERR_DIR_NOT_EMPTY: return "directory isn't empty"; + case PHYSFS_ERR_OS_ERROR: return "OS reported an error"; + case PHYSFS_ERR_DUPLICATE: return "duplicate resource"; + case PHYSFS_ERR_BAD_PASSWORD: return "bad password"; + case PHYSFS_ERR_APP_CALLBACK: return "app callback reported error"; + } /* switch */ + + return NULL; /* don't know this error code. */ +} /* PHYSFS_getErrorByCode */ + + +void PHYSFS_setErrorCode(PHYSFS_ErrorCode errcode) +{ + ErrState *err; + + if (!errcode) + return; + + err = findErrorForCurrentThread(); + if (err == NULL) + { + err = (ErrState *) allocator.Malloc(sizeof (ErrState)); + if (err == NULL) + return; /* uhh...? */ + + memset(err, '\0', sizeof (ErrState)); + err->tid = __PHYSFS_platformGetThreadID(); + + if (errorLock != NULL) + __PHYSFS_platformGrabMutex(errorLock); + + err->next = errorStates; + errorStates = err; + + if (errorLock != NULL) + __PHYSFS_platformReleaseMutex(errorLock); + } /* if */ + + err->code = errcode; +} /* PHYSFS_setErrorCode */ + + +const char *PHYSFS_getLastError(void) +{ + const PHYSFS_ErrorCode err = PHYSFS_getLastErrorCode(); + return (err) ? PHYSFS_getErrorByCode(err) : NULL; +} /* PHYSFS_getLastError */ + + +/* MAKE SURE that errorLock is held before calling this! */ +static void freeErrorStates(void) +{ + ErrState *i; + ErrState *next; + + for (i = errorStates; i != NULL; i = next) + { + next = i->next; + allocator.Free(i); + } /* for */ + + errorStates = NULL; +} /* freeErrorStates */ + + +void PHYSFS_getLinkedVersion(PHYSFS_Version *ver) +{ + if (ver != NULL) + { + ver->major = PHYSFS_VER_MAJOR; + ver->minor = PHYSFS_VER_MINOR; + ver->patch = PHYSFS_VER_PATCH; + } /* if */ +} /* PHYSFS_getLinkedVersion */ + + +static const char *find_filename_extension(const char *fname) +{ + const char *retval = NULL; + if (fname != NULL) + { + const char *p = strchr(fname, '.'); + retval = p; + + while (p != NULL) + { + p = strchr(p + 1, '.'); + if (p != NULL) + retval = p; + } /* while */ + + if (retval != NULL) + retval++; /* skip '.' */ + } /* if */ + + return retval; +} /* find_filename_extension */ + + +static DirHandle *tryOpenDir(PHYSFS_Io *io, const PHYSFS_Archiver *funcs, + const char *d, int forWriting, int *_claimed) +{ + DirHandle *retval = NULL; + void *opaque = NULL; + + if (io != NULL) + BAIL_IF_ERRPASS(!io->seek(io, 0), NULL); + + opaque = funcs->openArchive(io, d, forWriting, _claimed); + if (opaque != NULL) + { + retval = (DirHandle *) allocator.Malloc(sizeof (DirHandle)); + if (retval == NULL) + funcs->closeArchive(opaque); + else + { + memset(retval, '\0', sizeof (DirHandle)); + retval->mountPoint = NULL; + retval->funcs = funcs; + retval->opaque = opaque; + } /* else */ + } /* if */ + + return retval; +} /* tryOpenDir */ + + +static DirHandle *openDirectory(PHYSFS_Io *io, const char *d, int forWriting) +{ + DirHandle *retval = NULL; + PHYSFS_Archiver **i; + const char *ext; + int created_io = 0; + int claimed = 0; + PHYSFS_ErrorCode errcode; + + assert((io != NULL) || (d != NULL)); + + if (io == NULL) + { + /* file doesn't exist, etc? Just fail out. */ + PHYSFS_Stat statbuf; + BAIL_IF_ERRPASS(!__PHYSFS_platformStat(d, &statbuf, 1), NULL); + + /* DIR gets first shot (unlike the rest, it doesn't deal with files). */ + if (statbuf.filetype == PHYSFS_FILETYPE_DIRECTORY) + { + retval = tryOpenDir(io, &__PHYSFS_Archiver_DIR, d, forWriting, &claimed); + if (retval || claimed) + return retval; + } /* if */ + + io = __PHYSFS_createNativeIo(d, forWriting ? 'w' : 'r'); + BAIL_IF_ERRPASS(!io, NULL); + created_io = 1; + } /* if */ + + ext = find_filename_extension(d); + if (ext != NULL) + { + /* Look for archivers with matching file extensions first... */ + for (i = archivers; (*i != NULL) && (retval == NULL) && !claimed; i++) + { + if (PHYSFS_utf8stricmp(ext, (*i)->info.extension) == 0) + retval = tryOpenDir(io, *i, d, forWriting, &claimed); + } /* for */ + + /* failing an exact file extension match, try all the others... */ + for (i = archivers; (*i != NULL) && (retval == NULL) && !claimed; i++) + { + if (PHYSFS_utf8stricmp(ext, (*i)->info.extension) != 0) + retval = tryOpenDir(io, *i, d, forWriting, &claimed); + } /* for */ + } /* if */ + + else /* no extension? Try them all. */ + { + for (i = archivers; (*i != NULL) && (retval == NULL) && !claimed; i++) + retval = tryOpenDir(io, *i, d, forWriting, &claimed); + } /* else */ + + errcode = currentErrorCode(); + + if ((!retval) && (created_io)) + io->destroy(io); + + BAIL_IF(!retval, claimed ? errcode : PHYSFS_ERR_UNSUPPORTED, NULL); + return retval; +} /* openDirectory */ + + +/* + * Make a platform-independent path string sane. Doesn't actually check the + * file hierarchy, it just cleans up the string. + * (dst) must be a buffer at least as big as (src), as this is where the + * cleaned up string is deposited. + * If there are illegal bits in the path (".." entries, etc) then we + * return zero and (dst) is undefined. Non-zero if the path was sanitized. + */ +static int sanitizePlatformIndependentPath(const char *src, char *dst) +{ + char *prev; + char ch; + + while (*src == '/') /* skip initial '/' chars... */ + src++; + + /* Make sure the entire string isn't "." or ".." */ + if ((strcmp(src, ".") == 0) || (strcmp(src, "..") == 0)) + BAIL(PHYSFS_ERR_BAD_FILENAME, 0); + + prev = dst; + do + { + ch = *(src++); + + if ((ch == ':') || (ch == '\\')) /* illegal chars in a physfs path. */ + BAIL(PHYSFS_ERR_BAD_FILENAME, 0); + + if (ch == '/') /* path separator. */ + { + *dst = '\0'; /* "." and ".." are illegal pathnames. */ + if ((strcmp(prev, ".") == 0) || (strcmp(prev, "..") == 0)) + BAIL(PHYSFS_ERR_BAD_FILENAME, 0); + + while (*src == '/') /* chop out doubles... */ + src++; + + if (*src == '\0') /* ends with a pathsep? */ + break; /* we're done, don't add final pathsep to dst. */ + + prev = dst + 1; + } /* if */ + + *(dst++) = ch; + } while (ch != '\0'); + + return 1; +} /* sanitizePlatformIndependentPath */ + + +/* + * Figure out if (fname) is part of (h)'s mountpoint. (fname) must be an + * output from sanitizePlatformIndependentPath(), so that it is in a known + * state. + * + * This only finds legitimate segments of a mountpoint. If the mountpoint is + * "/a/b/c" and (fname) is "/a/b/c", "/", or "/a/b/c/d", then the results are + * all zero. "/a/b" will succeed, though. + */ +static int partOfMountPoint(DirHandle *h, char *fname) +{ + int rc; + size_t len, mntpntlen; + + if (h->mountPoint == NULL) + return 0; + else if (*fname == '\0') + return 1; + + len = strlen(fname); + mntpntlen = strlen(h->mountPoint); + if (len > mntpntlen) /* can't be a subset of mountpoint. */ + return 0; + + /* if true, must be not a match or a complete match, but not a subset. */ + if ((len + 1) == mntpntlen) + return 0; + + rc = strncmp(fname, h->mountPoint, len); /* !!! FIXME: case insensitive? */ + if (rc != 0) + return 0; /* not a match. */ + + /* make sure /a/b matches /a/b/ and not /a/bc ... */ + return h->mountPoint[len] == '/'; +} /* partOfMountPoint */ + + +static DirHandle *createDirHandle(PHYSFS_Io *io, const char *newDir, + const char *mountPoint, int forWriting) +{ + DirHandle *dirHandle = NULL; + char *tmpmntpnt = NULL; + + assert(newDir != NULL); /* should have caught this higher up. */ + + if (mountPoint != NULL) + { + const size_t len = strlen(mountPoint) + 1; + tmpmntpnt = (char *) __PHYSFS_smallAlloc(len); + GOTO_IF(!tmpmntpnt, PHYSFS_ERR_OUT_OF_MEMORY, badDirHandle); + if (!sanitizePlatformIndependentPath(mountPoint, tmpmntpnt)) + goto badDirHandle; + mountPoint = tmpmntpnt; /* sanitized version. */ + } /* if */ + + dirHandle = openDirectory(io, newDir, forWriting); + GOTO_IF_ERRPASS(!dirHandle, badDirHandle); + + dirHandle->dirName = (char *) allocator.Malloc(strlen(newDir) + 1); + GOTO_IF(!dirHandle->dirName, PHYSFS_ERR_OUT_OF_MEMORY, badDirHandle); + strcpy(dirHandle->dirName, newDir); + + if ((mountPoint != NULL) && (*mountPoint != '\0')) + { + dirHandle->mountPoint = (char *)allocator.Malloc(strlen(mountPoint)+2); + if (!dirHandle->mountPoint) + GOTO(PHYSFS_ERR_OUT_OF_MEMORY, badDirHandle); + strcpy(dirHandle->mountPoint, mountPoint); + strcat(dirHandle->mountPoint, "/"); + } /* if */ + + __PHYSFS_smallFree(tmpmntpnt); + return dirHandle; + +badDirHandle: + if (dirHandle != NULL) + { + dirHandle->funcs->closeArchive(dirHandle->opaque); + allocator.Free(dirHandle->dirName); + allocator.Free(dirHandle->mountPoint); + allocator.Free(dirHandle); + } /* if */ + + __PHYSFS_smallFree(tmpmntpnt); + return NULL; +} /* createDirHandle */ + + +/* MAKE SURE you've got the stateLock held before calling this! */ +static int freeDirHandle(DirHandle *dh, FileHandle *openList) +{ + FileHandle *i; + + if (dh == NULL) + return 1; + + for (i = openList; i != NULL; i = i->next) + BAIL_IF(i->dirHandle == dh, PHYSFS_ERR_FILES_STILL_OPEN, 0); + + dh->funcs->closeArchive(dh->opaque); + allocator.Free(dh->dirName); + allocator.Free(dh->mountPoint); + allocator.Free(dh); + return 1; +} /* freeDirHandle */ + + +static char *calculateBaseDir(const char *argv0) +{ + const char dirsep = __PHYSFS_platformDirSeparator; + char *retval = NULL; + char *ptr = NULL; + + /* Give the platform layer first shot at this. */ + retval = __PHYSFS_platformCalcBaseDir(argv0); + if (retval != NULL) + return retval; + + /* We need argv0 to go on. */ + BAIL_IF(argv0 == NULL, PHYSFS_ERR_ARGV0_IS_NULL, NULL); + + ptr = strrchr(argv0, dirsep); + if (ptr != NULL) + { + const size_t size = ((size_t) (ptr - argv0)) + 1; + retval = (char *) allocator.Malloc(size + 1); + BAIL_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + memcpy(retval, argv0, size); + retval[size] = '\0'; + return retval; + } /* if */ + + /* argv0 wasn't helpful. */ + BAIL(PHYSFS_ERR_INVALID_ARGUMENT, NULL); +} /* calculateBaseDir */ + + +static int initializeMutexes(void) +{ + errorLock = __PHYSFS_platformCreateMutex(); + if (errorLock == NULL) + goto initializeMutexes_failed; + + stateLock = __PHYSFS_platformCreateMutex(); + if (stateLock == NULL) + goto initializeMutexes_failed; + + return 1; /* success. */ + +initializeMutexes_failed: + if (errorLock != NULL) + __PHYSFS_platformDestroyMutex(errorLock); + + if (stateLock != NULL) + __PHYSFS_platformDestroyMutex(stateLock); + + errorLock = stateLock = NULL; + return 0; /* failed. */ +} /* initializeMutexes */ + + +static int doRegisterArchiver(const PHYSFS_Archiver *_archiver); + +static int initStaticArchivers(void) +{ + #define REGISTER_STATIC_ARCHIVER(arc) { \ + if (!doRegisterArchiver(&__PHYSFS_Archiver_##arc)) { \ + return 0; \ + } \ + } + + #if PHYSFS_SUPPORTS_ZIP + REGISTER_STATIC_ARCHIVER(ZIP); + #endif + #if PHYSFS_SUPPORTS_7Z + SZIP_global_init(); + REGISTER_STATIC_ARCHIVER(7Z); + #endif + #if PHYSFS_SUPPORTS_GRP + REGISTER_STATIC_ARCHIVER(GRP); + #endif + #if PHYSFS_SUPPORTS_QPAK + REGISTER_STATIC_ARCHIVER(QPAK); + #endif + #if PHYSFS_SUPPORTS_HOG + REGISTER_STATIC_ARCHIVER(HOG); + #endif + #if PHYSFS_SUPPORTS_MVL + REGISTER_STATIC_ARCHIVER(MVL); + #endif + #if PHYSFS_SUPPORTS_WAD + REGISTER_STATIC_ARCHIVER(WAD); + #endif + #if PHYSFS_SUPPORTS_SLB + REGISTER_STATIC_ARCHIVER(SLB); + #endif + #if PHYSFS_SUPPORTS_ISO9660 + REGISTER_STATIC_ARCHIVER(ISO9660); + #endif + #if PHYSFS_SUPPORTS_VDF + REGISTER_STATIC_ARCHIVER(VDF) + #endif + + #undef REGISTER_STATIC_ARCHIVER + + return 1; +} /* initStaticArchivers */ + + +static void setDefaultAllocator(void); +static int doDeinit(void); + +int PHYSFS_init(const char *argv0) +{ + BAIL_IF(initialized, PHYSFS_ERR_IS_INITIALIZED, 0); + + if (!externalAllocator) + setDefaultAllocator(); + + if ((allocator.Init != NULL) && (!allocator.Init())) return 0; + + if (!__PHYSFS_platformInit()) + { + if (allocator.Deinit != NULL) allocator.Deinit(); + return 0; + } /* if */ + + /* everything below here can be cleaned up safely by doDeinit(). */ + + if (!initializeMutexes()) goto initFailed; + + baseDir = calculateBaseDir(argv0); + if (!baseDir) goto initFailed; + + userDir = __PHYSFS_platformCalcUserDir(); + if (!userDir) goto initFailed; + + /* Platform layer is required to append a dirsep. */ + assert(baseDir[strlen(baseDir) - 1] == __PHYSFS_platformDirSeparator); + assert(userDir[strlen(userDir) - 1] == __PHYSFS_platformDirSeparator); + + if (!initStaticArchivers()) goto initFailed; + + initialized = 1; + + /* This makes sure that the error subsystem is initialized. */ + PHYSFS_setErrorCode(PHYSFS_getLastErrorCode()); + + return 1; + +initFailed: + doDeinit(); + return 0; +} /* PHYSFS_init */ + + +/* MAKE SURE you hold stateLock before calling this! */ +static int closeFileHandleList(FileHandle **list) +{ + FileHandle *i; + FileHandle *next = NULL; + + for (i = *list; i != NULL; i = next) + { + PHYSFS_Io *io = i->io; + next = i->next; + + if (io->flush && !io->flush(io)) + { + *list = i; + return 0; + } /* if */ + + io->destroy(io); + allocator.Free(i); + } /* for */ + + *list = NULL; + return 1; +} /* closeFileHandleList */ + + +/* MAKE SURE you hold the stateLock before calling this! */ +static void freeSearchPath(void) +{ + DirHandle *i; + DirHandle *next = NULL; + + closeFileHandleList(&openReadList); + + if (searchPath != NULL) + { + for (i = searchPath; i != NULL; i = next) + { + next = i->next; + freeDirHandle(i, openReadList); + } /* for */ + searchPath = NULL; + } /* if */ +} /* freeSearchPath */ + + +/* MAKE SURE you hold stateLock before calling this! */ +static int archiverInUse(const PHYSFS_Archiver *arc, const DirHandle *list) +{ + const DirHandle *i; + for (i = list; i != NULL; i = i->next) + { + if (i->funcs == arc) + return 1; + } /* for */ + + return 0; /* not in use */ +} /* archiverInUse */ + + +/* MAKE SURE you hold stateLock before calling this! */ +static int doDeregisterArchiver(const size_t idx) +{ + const size_t len = (numArchivers - idx) * sizeof (void *); + PHYSFS_ArchiveInfo *info = archiveInfo[idx]; + PHYSFS_Archiver *arc = archivers[idx]; + + /* make sure nothing is still using this archiver */ + if (archiverInUse(arc, searchPath) || archiverInUse(arc, writeDir)) + BAIL(PHYSFS_ERR_FILES_STILL_OPEN, 0); + + allocator.Free((void *) info->extension); + allocator.Free((void *) info->description); + allocator.Free((void *) info->author); + allocator.Free((void *) info->url); + allocator.Free((void *) arc); + + memmove(&archiveInfo[idx], &archiveInfo[idx+1], len); + memmove(&archivers[idx], &archivers[idx+1], len); + + assert(numArchivers > 0); + numArchivers--; + + return 1; +} /* doDeregisterArchiver */ + + +/* Does NOT hold the state lock; we're shutting down. */ +static void freeArchivers(void) +{ + while (numArchivers > 0) + { + if (!doDeregisterArchiver(numArchivers - 1)) + assert(!"nothing should be mounted during shutdown."); + } /* while */ + + allocator.Free(archivers); + allocator.Free(archiveInfo); + archivers = NULL; + archiveInfo = NULL; +} /* freeArchivers */ + + +static int doDeinit(void) +{ + closeFileHandleList(&openWriteList); + BAIL_IF(!PHYSFS_setWriteDir(NULL), PHYSFS_ERR_FILES_STILL_OPEN, 0); + + freeSearchPath(); + freeArchivers(); + freeErrorStates(); + + if (baseDir != NULL) + { + allocator.Free(baseDir); + baseDir = NULL; + } /* if */ + + if (userDir != NULL) + { + allocator.Free(userDir); + userDir = NULL; + } /* if */ + + if (prefDir != NULL) + { + allocator.Free(prefDir); + prefDir = NULL; + } /* if */ + + if (archiveInfo != NULL) + { + allocator.Free(archiveInfo); + archiveInfo = NULL; + } /* if */ + + if (archivers != NULL) + { + allocator.Free(archivers); + archivers = NULL; + } /* if */ + + allowSymLinks = 0; + initialized = 0; + + if (errorLock) __PHYSFS_platformDestroyMutex(errorLock); + if (stateLock) __PHYSFS_platformDestroyMutex(stateLock); + + if (allocator.Deinit != NULL) + allocator.Deinit(); + + errorLock = stateLock = NULL; + + __PHYSFS_platformDeinit(); + + return 1; +} /* doDeinit */ + + +int PHYSFS_deinit(void) +{ + BAIL_IF(!initialized, PHYSFS_ERR_NOT_INITIALIZED, 0); + return doDeinit(); +} /* PHYSFS_deinit */ + + +int PHYSFS_isInit(void) +{ + return initialized; +} /* PHYSFS_isInit */ + + +char *__PHYSFS_strdup(const char *str) +{ + char *retval = (char *) allocator.Malloc(strlen(str) + 1); + if (retval) + strcpy(retval, str); + return retval; +} /* __PHYSFS_strdup */ + + +PHYSFS_uint32 __PHYSFS_hashString(const char *str, size_t len) +{ + PHYSFS_uint32 hash = 5381; + while (len--) + hash = ((hash << 5) + hash) ^ *(str++); + return hash; +} /* __PHYSFS_hashString */ + + +/* MAKE SURE you hold stateLock before calling this! */ +static int doRegisterArchiver(const PHYSFS_Archiver *_archiver) +{ + const PHYSFS_uint32 maxver = CURRENT_PHYSFS_ARCHIVER_API_VERSION; + const size_t len = (numArchivers + 2) * sizeof (void *); + PHYSFS_Archiver *archiver = NULL; + PHYSFS_ArchiveInfo *info = NULL; + const char *ext = NULL; + void *ptr = NULL; + size_t i; + + BAIL_IF(!_archiver, PHYSFS_ERR_INVALID_ARGUMENT, 0); + BAIL_IF(_archiver->version > maxver, PHYSFS_ERR_UNSUPPORTED, 0); + BAIL_IF(!_archiver->info.extension, PHYSFS_ERR_INVALID_ARGUMENT, 0); + BAIL_IF(!_archiver->info.description, PHYSFS_ERR_INVALID_ARGUMENT, 0); + BAIL_IF(!_archiver->info.author, PHYSFS_ERR_INVALID_ARGUMENT, 0); + BAIL_IF(!_archiver->info.url, PHYSFS_ERR_INVALID_ARGUMENT, 0); + BAIL_IF(!_archiver->openArchive, PHYSFS_ERR_INVALID_ARGUMENT, 0); + BAIL_IF(!_archiver->enumerate, PHYSFS_ERR_INVALID_ARGUMENT, 0); + BAIL_IF(!_archiver->openRead, PHYSFS_ERR_INVALID_ARGUMENT, 0); + BAIL_IF(!_archiver->openWrite, PHYSFS_ERR_INVALID_ARGUMENT, 0); + BAIL_IF(!_archiver->openAppend, PHYSFS_ERR_INVALID_ARGUMENT, 0); + BAIL_IF(!_archiver->remove, PHYSFS_ERR_INVALID_ARGUMENT, 0); + BAIL_IF(!_archiver->mkdir, PHYSFS_ERR_INVALID_ARGUMENT, 0); + BAIL_IF(!_archiver->closeArchive, PHYSFS_ERR_INVALID_ARGUMENT, 0); + BAIL_IF(!_archiver->stat, PHYSFS_ERR_INVALID_ARGUMENT, 0); + + ext = _archiver->info.extension; + for (i = 0; i < numArchivers; i++) + { + if (PHYSFS_utf8stricmp(archiveInfo[i]->extension, ext) == 0) + BAIL(PHYSFS_ERR_DUPLICATE, 0); + } /* for */ + + /* make a copy of the data. */ + archiver = (PHYSFS_Archiver *) allocator.Malloc(sizeof (*archiver)); + GOTO_IF(!archiver, PHYSFS_ERR_OUT_OF_MEMORY, regfailed); + + /* Must copy sizeof (OLD_VERSION_OF_STRUCT) when version changes! */ + memcpy(archiver, _archiver, sizeof (*archiver)); + + info = (PHYSFS_ArchiveInfo *) &archiver->info; + memset(info, '\0', sizeof (*info)); /* NULL in case an alloc fails. */ + #define CPYSTR(item) \ + info->item = __PHYSFS_strdup(_archiver->info.item); \ + GOTO_IF(!info->item, PHYSFS_ERR_OUT_OF_MEMORY, regfailed); + CPYSTR(extension); + CPYSTR(description); + CPYSTR(author); + CPYSTR(url); + info->supportsSymlinks = _archiver->info.supportsSymlinks; + #undef CPYSTR + + ptr = allocator.Realloc(archiveInfo, len); + GOTO_IF(!ptr, PHYSFS_ERR_OUT_OF_MEMORY, regfailed); + archiveInfo = (PHYSFS_ArchiveInfo **) ptr; + + ptr = allocator.Realloc(archivers, len); + GOTO_IF(!ptr, PHYSFS_ERR_OUT_OF_MEMORY, regfailed); + archivers = (PHYSFS_Archiver **) ptr; + + archiveInfo[numArchivers] = info; + archiveInfo[numArchivers + 1] = NULL; + + archivers[numArchivers] = archiver; + archivers[numArchivers + 1] = NULL; + + numArchivers++; + + return 1; + +regfailed: + if (info != NULL) + { + allocator.Free((void *) info->extension); + allocator.Free((void *) info->description); + allocator.Free((void *) info->author); + allocator.Free((void *) info->url); + } /* if */ + allocator.Free(archiver); + + return 0; +} /* doRegisterArchiver */ + + +int PHYSFS_registerArchiver(const PHYSFS_Archiver *archiver) +{ + int retval; + BAIL_IF(!initialized, PHYSFS_ERR_NOT_INITIALIZED, 0); + __PHYSFS_platformGrabMutex(stateLock); + retval = doRegisterArchiver(archiver); + __PHYSFS_platformReleaseMutex(stateLock); + return retval; +} /* PHYSFS_registerArchiver */ + + +int PHYSFS_deregisterArchiver(const char *ext) +{ + size_t i; + + BAIL_IF(!initialized, PHYSFS_ERR_NOT_INITIALIZED, 0); + BAIL_IF(!ext, PHYSFS_ERR_INVALID_ARGUMENT, 0); + + __PHYSFS_platformGrabMutex(stateLock); + for (i = 0; i < numArchivers; i++) + { + if (PHYSFS_utf8stricmp(archiveInfo[i]->extension, ext) == 0) + { + const int retval = doDeregisterArchiver(i); + __PHYSFS_platformReleaseMutex(stateLock); + return retval; + } /* if */ + } /* for */ + __PHYSFS_platformReleaseMutex(stateLock); + + BAIL(PHYSFS_ERR_NOT_FOUND, 0); +} /* PHYSFS_deregisterArchiver */ + + +const PHYSFS_ArchiveInfo **PHYSFS_supportedArchiveTypes(void) +{ + BAIL_IF(!initialized, PHYSFS_ERR_NOT_INITIALIZED, NULL); + return (const PHYSFS_ArchiveInfo **) archiveInfo; +} /* PHYSFS_supportedArchiveTypes */ + + +void PHYSFS_freeList(void *list) +{ + void **i; + if (list != NULL) + { + for (i = (void **) list; *i != NULL; i++) + allocator.Free(*i); + + allocator.Free(list); + } /* if */ +} /* PHYSFS_freeList */ + + +const char *PHYSFS_getDirSeparator(void) +{ + static char retval[2] = { __PHYSFS_platformDirSeparator, '\0' }; + return retval; +} /* PHYSFS_getDirSeparator */ + + +char **PHYSFS_getCdRomDirs(void) +{ + return doEnumStringList(__PHYSFS_platformDetectAvailableCDs); +} /* PHYSFS_getCdRomDirs */ + + +void PHYSFS_getCdRomDirsCallback(PHYSFS_StringCallback callback, void *data) +{ + __PHYSFS_platformDetectAvailableCDs(callback, data); +} /* PHYSFS_getCdRomDirsCallback */ + + +const char *PHYSFS_getPrefDir(const char *org, const char *app) +{ + const char dirsep = __PHYSFS_platformDirSeparator; + PHYSFS_Stat statbuf; + char *ptr = NULL; + char *endstr = NULL; + + BAIL_IF(!initialized, PHYSFS_ERR_NOT_INITIALIZED, 0); + BAIL_IF(!org, PHYSFS_ERR_INVALID_ARGUMENT, NULL); + BAIL_IF(*org == '\0', PHYSFS_ERR_INVALID_ARGUMENT, NULL); + BAIL_IF(!app, PHYSFS_ERR_INVALID_ARGUMENT, NULL); + BAIL_IF(*app == '\0', PHYSFS_ERR_INVALID_ARGUMENT, NULL); + + allocator.Free(prefDir); + prefDir = __PHYSFS_platformCalcPrefDir(org, app); + BAIL_IF_ERRPASS(!prefDir, NULL); + + assert(strlen(prefDir) > 0); + endstr = prefDir + (strlen(prefDir) - 1); + assert(*endstr == dirsep); + *endstr = '\0'; /* mask out the final dirsep for now. */ + + if (!__PHYSFS_platformStat(prefDir, &statbuf, 1)) + { + for (ptr = strchr(prefDir, dirsep); ptr; ptr = strchr(ptr+1, dirsep)) + { + *ptr = '\0'; + __PHYSFS_platformMkDir(prefDir); + *ptr = dirsep; + } /* for */ + + if (!__PHYSFS_platformMkDir(prefDir)) + { + allocator.Free(prefDir); + prefDir = NULL; + } /* if */ + } /* if */ + + *endstr = dirsep; /* readd the final dirsep. */ + + return prefDir; +} /* PHYSFS_getPrefDir */ + + +const char *PHYSFS_getBaseDir(void) +{ + return baseDir; /* this is calculated in PHYSFS_init()... */ +} /* PHYSFS_getBaseDir */ + + +const char *__PHYSFS_getUserDir(void) /* not deprecated internal version. */ +{ + return userDir; /* this is calculated in PHYSFS_init()... */ +} /* __PHYSFS_getUserDir */ + + +const char *PHYSFS_getUserDir(void) +{ + return __PHYSFS_getUserDir(); +} /* PHYSFS_getUserDir */ + + +const char *PHYSFS_getWriteDir(void) +{ + const char *retval = NULL; + + __PHYSFS_platformGrabMutex(stateLock); + if (writeDir != NULL) + retval = writeDir->dirName; + __PHYSFS_platformReleaseMutex(stateLock); + + return retval; +} /* PHYSFS_getWriteDir */ + + +int PHYSFS_setWriteDir(const char *newDir) +{ + int retval = 1; + + __PHYSFS_platformGrabMutex(stateLock); + + if (writeDir != NULL) + { + BAIL_IF_MUTEX_ERRPASS(!freeDirHandle(writeDir, openWriteList), + stateLock, 0); + writeDir = NULL; + } /* if */ + + if (newDir != NULL) + { + writeDir = createDirHandle(NULL, newDir, NULL, 1); + retval = (writeDir != NULL); + } /* if */ + + __PHYSFS_platformReleaseMutex(stateLock); + + return retval; +} /* PHYSFS_setWriteDir */ + + +static int doMount(PHYSFS_Io *io, const char *fname, + const char *mountPoint, int appendToPath) +{ + DirHandle *dh; + DirHandle *prev = NULL; + DirHandle *i; + + BAIL_IF(!fname, PHYSFS_ERR_INVALID_ARGUMENT, 0); + + if (mountPoint == NULL) + mountPoint = "/"; + + __PHYSFS_platformGrabMutex(stateLock); + + for (i = searchPath; i != NULL; i = i->next) + { + /* already in search path? */ + if ((i->dirName != NULL) && (strcmp(fname, i->dirName) == 0)) + BAIL_MUTEX_ERRPASS(stateLock, 1); + prev = i; + } /* for */ + + dh = createDirHandle(io, fname, mountPoint, 0); + BAIL_IF_MUTEX_ERRPASS(!dh, stateLock, 0); + + if (appendToPath) + { + if (prev == NULL) + searchPath = dh; + else + prev->next = dh; + } /* if */ + else + { + dh->next = searchPath; + searchPath = dh; + } /* else */ + + __PHYSFS_platformReleaseMutex(stateLock); + return 1; +} /* doMount */ + + +int PHYSFS_mountIo(PHYSFS_Io *io, const char *fname, + const char *mountPoint, int appendToPath) +{ + BAIL_IF(!io, PHYSFS_ERR_INVALID_ARGUMENT, 0); + BAIL_IF(!fname, PHYSFS_ERR_INVALID_ARGUMENT, 0); + BAIL_IF(io->version != 0, PHYSFS_ERR_UNSUPPORTED, 0); + return doMount(io, fname, mountPoint, appendToPath); +} /* PHYSFS_mountIo */ + + +int PHYSFS_mountMemory(const void *buf, PHYSFS_uint64 len, void (*del)(void *), + const char *fname, const char *mountPoint, + int appendToPath) +{ + int retval = 0; + PHYSFS_Io *io = NULL; + + BAIL_IF(!buf, PHYSFS_ERR_INVALID_ARGUMENT, 0); + BAIL_IF(!fname, PHYSFS_ERR_INVALID_ARGUMENT, 0); + + io = __PHYSFS_createMemoryIo(buf, len, del); + BAIL_IF_ERRPASS(!io, 0); + retval = doMount(io, fname, mountPoint, appendToPath); + if (!retval) + { + /* docs say not to call (del) in case of failure, so cheat. */ + MemoryIoInfo *info = (MemoryIoInfo *) io->opaque; + info->destruct = NULL; + io->destroy(io); + } /* if */ + + return retval; +} /* PHYSFS_mountMemory */ + + +int PHYSFS_mountHandle(PHYSFS_File *file, const char *fname, + const char *mountPoint, int appendToPath) +{ + int retval = 0; + PHYSFS_Io *io = NULL; + + BAIL_IF(!file, PHYSFS_ERR_INVALID_ARGUMENT, 0); + BAIL_IF(!fname, PHYSFS_ERR_INVALID_ARGUMENT, 0); + + io = __PHYSFS_createHandleIo(file); + BAIL_IF_ERRPASS(!io, 0); + retval = doMount(io, fname, mountPoint, appendToPath); + if (!retval) + { + /* docs say not to destruct in case of failure, so cheat. */ + io->opaque = NULL; + io->destroy(io); + } /* if */ + + return retval; +} /* PHYSFS_mountHandle */ + + +int PHYSFS_mount(const char *newDir, const char *mountPoint, int appendToPath) +{ + BAIL_IF(!newDir, PHYSFS_ERR_INVALID_ARGUMENT, 0); + return doMount(NULL, newDir, mountPoint, appendToPath); +} /* PHYSFS_mount */ + + +int PHYSFS_addToSearchPath(const char *newDir, int appendToPath) +{ + return PHYSFS_mount(newDir, NULL, appendToPath); +} /* PHYSFS_addToSearchPath */ + + +int PHYSFS_removeFromSearchPath(const char *oldDir) +{ + return PHYSFS_unmount(oldDir); +} /* PHYSFS_removeFromSearchPath */ + + +int PHYSFS_unmount(const char *oldDir) +{ + DirHandle *i; + DirHandle *prev = NULL; + DirHandle *next = NULL; + + BAIL_IF(oldDir == NULL, PHYSFS_ERR_INVALID_ARGUMENT, 0); + + __PHYSFS_platformGrabMutex(stateLock); + for (i = searchPath; i != NULL; i = i->next) + { + if (strcmp(i->dirName, oldDir) == 0) + { + next = i->next; + BAIL_IF_MUTEX_ERRPASS(!freeDirHandle(i, openReadList), + stateLock, 0); + + if (prev == NULL) + searchPath = next; + else + prev->next = next; + + BAIL_MUTEX_ERRPASS(stateLock, 1); + } /* if */ + prev = i; + } /* for */ + + BAIL_MUTEX(PHYSFS_ERR_NOT_MOUNTED, stateLock, 0); +} /* PHYSFS_unmount */ + + +char **PHYSFS_getSearchPath(void) +{ + return doEnumStringList(PHYSFS_getSearchPathCallback); +} /* PHYSFS_getSearchPath */ + + +const char *PHYSFS_getMountPoint(const char *dir) +{ + DirHandle *i; + __PHYSFS_platformGrabMutex(stateLock); + for (i = searchPath; i != NULL; i = i->next) + { + if (strcmp(i->dirName, dir) == 0) + { + const char *retval = ((i->mountPoint) ? i->mountPoint : "/"); + __PHYSFS_platformReleaseMutex(stateLock); + return retval; + } /* if */ + } /* for */ + __PHYSFS_platformReleaseMutex(stateLock); + + BAIL(PHYSFS_ERR_NOT_MOUNTED, NULL); +} /* PHYSFS_getMountPoint */ + + +void PHYSFS_getSearchPathCallback(PHYSFS_StringCallback callback, void *data) +{ + DirHandle *i; + + __PHYSFS_platformGrabMutex(stateLock); + + for (i = searchPath; i != NULL; i = i->next) + callback(data, i->dirName); + + __PHYSFS_platformReleaseMutex(stateLock); +} /* PHYSFS_getSearchPathCallback */ + + +typedef struct setSaneCfgEnumData +{ + const char *archiveExt; + size_t archiveExtLen; + int archivesFirst; + PHYSFS_ErrorCode errcode; +} setSaneCfgEnumData; + +static PHYSFS_EnumerateCallbackResult setSaneCfgEnumCallback(void *_data, + const char *dir, const char *f) +{ + setSaneCfgEnumData *data = (setSaneCfgEnumData *) _data; + const size_t extlen = data->archiveExtLen; + const size_t l = strlen(f); + const char *ext; + + if ((l > extlen) && (f[l - extlen - 1] == '.')) + { + ext = f + (l - extlen); + if (PHYSFS_utf8stricmp(ext, data->archiveExt) == 0) + { + const char dirsep = __PHYSFS_platformDirSeparator; + const char *d = PHYSFS_getRealDir(f); + const size_t allocsize = strlen(d) + l + 2; + char *str = (char *) __PHYSFS_smallAlloc(allocsize); + if (str == NULL) + data->errcode = PHYSFS_ERR_OUT_OF_MEMORY; + else + { + snprintf(str, allocsize, "%s%c%s", d, dirsep, f); + if (!PHYSFS_mount(str, NULL, data->archivesFirst == 0)) + data->errcode = currentErrorCode(); + __PHYSFS_smallFree(str); + } /* else */ + } /* if */ + } /* if */ + + /* !!! FIXME: if we want to abort on errors... */ + /*return (data->errcode != PHYSFS_ERR_OK) ? PHYSFS_ENUM_ERROR : PHYSFS_ENUM_OK;*/ + + return PHYSFS_ENUM_OK; /* keep going */ +} /* setSaneCfgEnumCallback */ + + +int PHYSFS_setSaneConfig(const char *organization, const char *appName, + const char *archiveExt, int includeCdRoms, + int archivesFirst) +{ + const char *basedir; + const char *prefdir; + + BAIL_IF(!initialized, PHYSFS_ERR_NOT_INITIALIZED, 0); + + prefdir = PHYSFS_getPrefDir(organization, appName); + BAIL_IF_ERRPASS(!prefdir, 0); + + basedir = PHYSFS_getBaseDir(); + BAIL_IF_ERRPASS(!basedir, 0); + + BAIL_IF(!PHYSFS_setWriteDir(prefdir), PHYSFS_ERR_NO_WRITE_DIR, 0); + + /* !!! FIXME: these can fail and we should report that... */ + + /* Put write dir first in search path... */ + PHYSFS_mount(prefdir, NULL, 0); + + /* Put base path on search path... */ + PHYSFS_mount(basedir, NULL, 1); + + /* handle CD-ROMs... */ + if (includeCdRoms) + { + char **cds = PHYSFS_getCdRomDirs(); + char **i; + for (i = cds; *i != NULL; i++) + PHYSFS_mount(*i, NULL, 1); + PHYSFS_freeList(cds); + } /* if */ + + /* Root out archives, and add them to search path... */ + if (archiveExt != NULL) + { + setSaneCfgEnumData data; + memset(&data, '\0', sizeof (data)); + data.archiveExt = archiveExt; + data.archiveExtLen = strlen(archiveExt); + data.archivesFirst = archivesFirst; + data.errcode = PHYSFS_ERR_OK; + if (!PHYSFS_enumerate("/", setSaneCfgEnumCallback, &data)) + { + /* !!! FIXME: use this if we're reporting errors. + PHYSFS_ErrorCode errcode = currentErrorCode(); + if (errcode == PHYSFS_ERR_APP_CALLBACK) + errcode = data->errcode; */ + } /* if */ + } /* if */ + + return 1; +} /* PHYSFS_setSaneConfig */ + + +void PHYSFS_permitSymbolicLinks(int allow) +{ + allowSymLinks = allow; +} /* PHYSFS_permitSymbolicLinks */ + + +int PHYSFS_symbolicLinksPermitted(void) +{ + return allowSymLinks; +} /* PHYSFS_symbolicLinksPermitted */ + + +/* + * Verify that (fname) (in platform-independent notation), in relation + * to (h) is secure. That means that each element of fname is checked + * for symlinks (if they aren't permitted). This also allows for quick + * rejection of files that exist outside an archive's mountpoint. + * + * With some exceptions (like PHYSFS_mkdir(), which builds multiple subdirs + * at a time), you should always pass zero for "allowMissing" for efficiency. + * + * (fname) must point to an output from sanitizePlatformIndependentPath(), + * since it will make sure that path names are in the right format for + * passing certain checks. It will also do checks for "insecure" pathnames + * like ".." which should be done once instead of once per archive. This also + * gives us license to treat (fname) as scratch space in this function. + * + * Returns non-zero if string is safe, zero if there's a security issue. + * PHYSFS_getLastError() will specify what was wrong. (*fname) will be + * updated to point past any mount point elements so it is prepared to + * be used with the archiver directly. + */ +static int verifyPath(DirHandle *h, char **_fname, int allowMissing) +{ + char *fname = *_fname; + int retval = 1; + char *start; + char *end; + + if (*fname == '\0') /* quick rejection. */ + return 1; + + /* !!! FIXME: This codeblock sucks. */ + if (h->mountPoint != NULL) /* NULL mountpoint means "/". */ + { + size_t mntpntlen = strlen(h->mountPoint); + size_t len = strlen(fname); + assert(mntpntlen > 1); /* root mount points should be NULL. */ + /* not under the mountpoint, so skip this archive. */ + BAIL_IF(len < mntpntlen-1, PHYSFS_ERR_NOT_FOUND, 0); + /* !!! FIXME: Case insensitive? */ + retval = strncmp(h->mountPoint, fname, mntpntlen-1); + BAIL_IF(retval != 0, PHYSFS_ERR_NOT_FOUND, 0); + if (len > mntpntlen-1) /* corner case... */ + BAIL_IF(fname[mntpntlen-1]!='/', PHYSFS_ERR_NOT_FOUND, 0); + fname += mntpntlen-1; /* move to start of actual archive path. */ + if (*fname == '/') + fname++; + *_fname = fname; /* skip mountpoint for later use. */ + retval = 1; /* may be reset, below. */ + } /* if */ + + start = fname; + if (!allowSymLinks) + { + while (1) + { + PHYSFS_Stat statbuf; + int rc = 0; + end = strchr(start, '/'); + + if (end != NULL) *end = '\0'; + rc = h->funcs->stat(h->opaque, fname, &statbuf); + if (rc) + rc = (statbuf.filetype == PHYSFS_FILETYPE_SYMLINK); + else if (currentErrorCode() == PHYSFS_ERR_NOT_FOUND) + retval = 0; + + if (end != NULL) *end = '/'; + + /* insecure path (has a disallowed symlink in it)? */ + BAIL_IF(rc, PHYSFS_ERR_SYMLINK_FORBIDDEN, 0); + + /* break out early if path element is missing. */ + if (!retval) + { + /* + * We need to clear it if it's the last element of the path, + * since this might be a non-existant file we're opening + * for writing... + */ + if ((end == NULL) || (allowMissing)) + retval = 1; + break; + } /* if */ + + if (end == NULL) + break; + + start = end + 1; + } /* while */ + } /* if */ + + return retval; +} /* verifyPath */ + + +static int doMkdir(const char *_dname, char *dname) +{ + DirHandle *h; + char *start; + char *end; + int retval = 0; + int exists = 1; /* force existance check on first path element. */ + + BAIL_IF_ERRPASS(!sanitizePlatformIndependentPath(_dname, dname), 0); + + __PHYSFS_platformGrabMutex(stateLock); + BAIL_IF_MUTEX(!writeDir, PHYSFS_ERR_NO_WRITE_DIR, stateLock, 0); + h = writeDir; + BAIL_IF_MUTEX_ERRPASS(!verifyPath(h, &dname, 1), stateLock, 0); + + start = dname; + while (1) + { + end = strchr(start, '/'); + if (end != NULL) + *end = '\0'; + + /* only check for existance if all parent dirs existed, too... */ + if (exists) + { + PHYSFS_Stat statbuf; + const int rc = h->funcs->stat(h->opaque, dname, &statbuf); + if ((!rc) && (currentErrorCode() == PHYSFS_ERR_NOT_FOUND)) + exists = 0; + retval = ((rc) && (statbuf.filetype == PHYSFS_FILETYPE_DIRECTORY)); + } /* if */ + + if (!exists) + retval = h->funcs->mkdir(h->opaque, dname); + + if (!retval) + break; + + if (end == NULL) + break; + + *end = '/'; + start = end + 1; + } /* while */ + + __PHYSFS_platformReleaseMutex(stateLock); + return retval; +} /* doMkdir */ + + +int PHYSFS_mkdir(const char *_dname) +{ + int retval = 0; + char *dname; + size_t len; + + BAIL_IF(!_dname, PHYSFS_ERR_INVALID_ARGUMENT, 0); + len = strlen(_dname) + 1; + dname = (char *) __PHYSFS_smallAlloc(len); + BAIL_IF(!dname, PHYSFS_ERR_OUT_OF_MEMORY, 0); + retval = doMkdir(_dname, dname); + __PHYSFS_smallFree(dname); + return retval; +} /* PHYSFS_mkdir */ + + +static int doDelete(const char *_fname, char *fname) +{ + int retval; + DirHandle *h; + BAIL_IF_ERRPASS(!sanitizePlatformIndependentPath(_fname, fname), 0); + + __PHYSFS_platformGrabMutex(stateLock); + + BAIL_IF_MUTEX(!writeDir, PHYSFS_ERR_NO_WRITE_DIR, stateLock, 0); + h = writeDir; + BAIL_IF_MUTEX_ERRPASS(!verifyPath(h, &fname, 0), stateLock, 0); + retval = h->funcs->remove(h->opaque, fname); + + __PHYSFS_platformReleaseMutex(stateLock); + return retval; +} /* doDelete */ + + +int PHYSFS_delete(const char *_fname) +{ + int retval; + char *fname; + size_t len; + + BAIL_IF(!_fname, PHYSFS_ERR_INVALID_ARGUMENT, 0); + len = strlen(_fname) + 1; + fname = (char *) __PHYSFS_smallAlloc(len); + BAIL_IF(!fname, PHYSFS_ERR_OUT_OF_MEMORY, 0); + retval = doDelete(_fname, fname); + __PHYSFS_smallFree(fname); + return retval; +} /* PHYSFS_delete */ + + +static DirHandle *getRealDirHandle(const char *_fname) +{ + DirHandle *retval = NULL; + char *fname = NULL; + size_t len; + + BAIL_IF(!_fname, PHYSFS_ERR_INVALID_ARGUMENT, NULL); + len = strlen(_fname) + 1; + fname = __PHYSFS_smallAlloc(len); + BAIL_IF(!fname, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + if (sanitizePlatformIndependentPath(_fname, fname)) + { + DirHandle *i; + __PHYSFS_platformGrabMutex(stateLock); + for (i = searchPath; i != NULL; i = i->next) + { + char *arcfname = fname; + if (partOfMountPoint(i, arcfname)) + { + retval = i; + break; + } /* if */ + else if (verifyPath(i, &arcfname, 0)) + { + PHYSFS_Stat statbuf; + if (i->funcs->stat(i->opaque, arcfname, &statbuf)) + { + retval = i; + break; + } /* if */ + } /* if */ + } /* for */ + __PHYSFS_platformReleaseMutex(stateLock); + } /* if */ + + __PHYSFS_smallFree(fname); + return retval; +} /* getRealDirHandle */ + +const char *PHYSFS_getRealDir(const char *fname) +{ + DirHandle *dh = getRealDirHandle(fname); + return dh ? dh->dirName : NULL; +} /* PHYSFS_getRealDir */ + + +static int locateInStringList(const char *str, + char **list, + PHYSFS_uint32 *pos) +{ + PHYSFS_uint32 len = *pos; + PHYSFS_uint32 half_len; + PHYSFS_uint32 lo = 0; + PHYSFS_uint32 middle; + int cmp; + + while (len > 0) + { + half_len = len >> 1; + middle = lo + half_len; + cmp = strcmp(list[middle], str); + + if (cmp == 0) /* it's in the list already. */ + return 1; + else if (cmp > 0) + len = half_len; + else + { + lo = middle + 1; + len -= half_len + 1; + } /* else */ + } /* while */ + + *pos = lo; + return 0; +} /* locateInStringList */ + + +static PHYSFS_EnumerateCallbackResult enumFilesCallback(void *data, + const char *origdir, const char *str) +{ + PHYSFS_uint32 pos; + void *ptr; + char *newstr; + EnumStringListCallbackData *pecd = (EnumStringListCallbackData *) data; + + /* + * See if file is in the list already, and if not, insert it in there + * alphabetically... + */ + pos = pecd->size; + if (locateInStringList(str, pecd->list, &pos)) + return PHYSFS_ENUM_OK; /* already in the list, but keep going. */ + + ptr = allocator.Realloc(pecd->list, (pecd->size + 2) * sizeof (char *)); + newstr = (char *) allocator.Malloc(strlen(str) + 1); + if (ptr != NULL) + pecd->list = (char **) ptr; + + if ((ptr == NULL) || (newstr == NULL)) + { + if (newstr) + allocator.Free(newstr); + + pecd->errcode = PHYSFS_ERR_OUT_OF_MEMORY; + return PHYSFS_ENUM_ERROR; /* better luck next time. */ + } /* if */ + + strcpy(newstr, str); + + if (pos != pecd->size) + { + memmove(&pecd->list[pos+1], &pecd->list[pos], + sizeof (char *) * ((pecd->size) - pos)); + } /* if */ + + pecd->list[pos] = newstr; + pecd->size++; + + return PHYSFS_ENUM_OK; +} /* enumFilesCallback */ + + +char **PHYSFS_enumerateFiles(const char *path) +{ + EnumStringListCallbackData ecd; + memset(&ecd, '\0', sizeof (ecd)); + ecd.list = (char **) allocator.Malloc(sizeof (char *)); + BAIL_IF(!ecd.list, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + if (!PHYSFS_enumerate(path, enumFilesCallback, &ecd)) + { + const PHYSFS_ErrorCode errcode = currentErrorCode(); + PHYSFS_uint32 i; + for (i = 0; i < ecd.size; i++) + allocator.Free(ecd.list[i]); + allocator.Free(ecd.list); + BAIL_IF(errcode == PHYSFS_ERR_APP_CALLBACK, ecd.errcode, NULL); + return NULL; + } /* if */ + + ecd.list[ecd.size] = NULL; + return ecd.list; +} /* PHYSFS_enumerateFiles */ + + +/* + * Broke out to seperate function so we can use stack allocation gratuitously. + */ +static PHYSFS_EnumerateCallbackResult enumerateFromMountPoint(DirHandle *i, + const char *arcfname, + PHYSFS_EnumerateCallback callback, + const char *_fname, void *data) +{ + PHYSFS_EnumerateCallbackResult retval; + const size_t len = strlen(arcfname); + char *ptr = NULL; + char *end = NULL; + const size_t slen = strlen(i->mountPoint) + 1; + char *mountPoint = (char *) __PHYSFS_smallAlloc(slen); + + BAIL_IF(!mountPoint, PHYSFS_ERR_OUT_OF_MEMORY, PHYSFS_ENUM_ERROR); + + strcpy(mountPoint, i->mountPoint); + ptr = mountPoint + ((len) ? len + 1 : 0); + end = strchr(ptr, '/'); + assert(end); /* should always find a terminating '/'. */ + *end = '\0'; + retval = callback(data, _fname, ptr); + __PHYSFS_smallFree(mountPoint); + + BAIL_IF(retval == PHYSFS_ENUM_ERROR, PHYSFS_ERR_APP_CALLBACK, retval); + return retval; +} /* enumerateFromMountPoint */ + + +typedef struct SymlinkFilterData +{ + PHYSFS_EnumerateCallback callback; + void *callbackData; + DirHandle *dirhandle; + const char *arcfname; + PHYSFS_ErrorCode errcode; +} SymlinkFilterData; + +static PHYSFS_EnumerateCallbackResult enumCallbackFilterSymLinks(void *_data, + const char *origdir, const char *fname) +{ + SymlinkFilterData *data = (SymlinkFilterData *) _data; + const DirHandle *dh = data->dirhandle; + const char *arcfname = data->arcfname; + PHYSFS_Stat statbuf; + const char *trimmedDir = (*arcfname == '/') ? (arcfname + 1) : arcfname; + const size_t slen = strlen(trimmedDir) + strlen(fname) + 2; + char *path = (char *) __PHYSFS_smallAlloc(slen); + PHYSFS_EnumerateCallbackResult retval = PHYSFS_ENUM_OK; + + if (path == NULL) + { + data->errcode = PHYSFS_ERR_OUT_OF_MEMORY; + return PHYSFS_ENUM_ERROR; + } /* if */ + + snprintf(path, slen, "%s%s%s", trimmedDir, *trimmedDir ? "/" : "", fname); + + if (!dh->funcs->stat(dh->opaque, path, &statbuf)) + { + data->errcode = currentErrorCode(); + retval = PHYSFS_ENUM_ERROR; + } /* if */ + else + { + /* Pass it on to the application if it's not a symlink. */ + if (statbuf.filetype != PHYSFS_FILETYPE_SYMLINK) + { + retval = data->callback(data->callbackData, origdir, fname); + if (retval == PHYSFS_ENUM_ERROR) + data->errcode = PHYSFS_ERR_APP_CALLBACK; + } /* if */ + } /* else */ + + __PHYSFS_smallFree(path); + + return retval; +} /* enumCallbackFilterSymLinks */ + + +int PHYSFS_enumerate(const char *_fn, PHYSFS_EnumerateCallback cb, void *data) +{ + PHYSFS_EnumerateCallbackResult retval = PHYSFS_ENUM_OK; + size_t len; + char *fname; + + BAIL_IF(!_fn, PHYSFS_ERR_INVALID_ARGUMENT, 0); + BAIL_IF(!cb, PHYSFS_ERR_INVALID_ARGUMENT, 0); + + len = strlen(_fn) + 1; + fname = (char *) __PHYSFS_smallAlloc(len); + BAIL_IF(!fname, PHYSFS_ERR_OUT_OF_MEMORY, 0); + + if (!sanitizePlatformIndependentPath(_fn, fname)) + retval = PHYSFS_ENUM_STOP; + else + { + DirHandle *i; + SymlinkFilterData filterdata; + + __PHYSFS_platformGrabMutex(stateLock); + + if (!allowSymLinks) + { + memset(&filterdata, '\0', sizeof (filterdata)); + filterdata.callback = cb; + filterdata.callbackData = data; + } /* if */ + + for (i = searchPath; (retval == PHYSFS_ENUM_OK) && i; i = i->next) + { + char *arcfname = fname; + + if (partOfMountPoint(i, arcfname)) + retval = enumerateFromMountPoint(i, arcfname, cb, _fn, data); + + else if (verifyPath(i, &arcfname, 0)) + { + PHYSFS_Stat statbuf; + if (!i->funcs->stat(i->opaque, arcfname, &statbuf)) + { + if (currentErrorCode() == PHYSFS_ERR_NOT_FOUND) + continue; /* no such dir in this archive, skip it. */ + } /* if */ + + if (statbuf.filetype != PHYSFS_FILETYPE_DIRECTORY) + continue; /* not a directory in this archive, skip it. */ + + else if ((!allowSymLinks) && (i->funcs->info.supportsSymlinks)) + { + filterdata.dirhandle = i; + filterdata.arcfname = arcfname; + filterdata.errcode = PHYSFS_ERR_OK; + retval = i->funcs->enumerate(i->opaque, arcfname, + enumCallbackFilterSymLinks, + _fn, &filterdata); + if (retval == PHYSFS_ENUM_ERROR) + { + if (currentErrorCode() == PHYSFS_ERR_APP_CALLBACK) + PHYSFS_setErrorCode(filterdata.errcode); + } /* if */ + } /* else if */ + else + { + retval = i->funcs->enumerate(i->opaque, arcfname, + cb, _fn, data); + } /* else */ + } /* else if */ + } /* for */ + + __PHYSFS_platformReleaseMutex(stateLock); + } /* if */ + + __PHYSFS_smallFree(fname); + + return (retval == PHYSFS_ENUM_ERROR) ? 0 : 1; +} /* PHYSFS_enumerate */ + + +typedef struct +{ + PHYSFS_EnumFilesCallback callback; + void *data; +} LegacyEnumFilesCallbackData; + +static PHYSFS_EnumerateCallbackResult enumFilesCallbackAlwaysSucceed(void *d, + const char *origdir, const char *fname) +{ + LegacyEnumFilesCallbackData *cbdata = (LegacyEnumFilesCallbackData *) d; + cbdata->callback(cbdata->data, origdir, fname); + return PHYSFS_ENUM_OK; +} /* enumFilesCallbackAlwaysSucceed */ + +void PHYSFS_enumerateFilesCallback(const char *fname, + PHYSFS_EnumFilesCallback callback, + void *data) +{ + LegacyEnumFilesCallbackData cbdata; + cbdata.callback = callback; + cbdata.data = data; + (void) PHYSFS_enumerate(fname, enumFilesCallbackAlwaysSucceed, &cbdata); +} /* PHYSFS_enumerateFilesCallback */ + + +int PHYSFS_exists(const char *fname) +{ + return (getRealDirHandle(fname) != NULL); +} /* PHYSFS_exists */ + + +PHYSFS_sint64 PHYSFS_getLastModTime(const char *fname) +{ + PHYSFS_Stat statbuf; + BAIL_IF_ERRPASS(!PHYSFS_stat(fname, &statbuf), -1); + return statbuf.modtime; +} /* PHYSFS_getLastModTime */ + + +int PHYSFS_isDirectory(const char *fname) +{ + PHYSFS_Stat statbuf; + BAIL_IF_ERRPASS(!PHYSFS_stat(fname, &statbuf), 0); + return (statbuf.filetype == PHYSFS_FILETYPE_DIRECTORY); +} /* PHYSFS_isDirectory */ + + +int PHYSFS_isSymbolicLink(const char *fname) +{ + PHYSFS_Stat statbuf; + BAIL_IF_ERRPASS(!PHYSFS_stat(fname, &statbuf), 0); + return (statbuf.filetype == PHYSFS_FILETYPE_SYMLINK); +} /* PHYSFS_isSymbolicLink */ + + +static PHYSFS_File *doOpenWrite(const char *_fname, int appending) +{ + FileHandle *fh = NULL; + size_t len; + char *fname; + + BAIL_IF(!_fname, PHYSFS_ERR_INVALID_ARGUMENT, 0); + len = strlen(_fname) + 1; + fname = (char *) __PHYSFS_smallAlloc(len); + BAIL_IF(!fname, PHYSFS_ERR_OUT_OF_MEMORY, 0); + + if (sanitizePlatformIndependentPath(_fname, fname)) + { + PHYSFS_Io *io = NULL; + DirHandle *h = NULL; + const PHYSFS_Archiver *f; + + __PHYSFS_platformGrabMutex(stateLock); + + GOTO_IF(!writeDir, PHYSFS_ERR_NO_WRITE_DIR, doOpenWriteEnd); + + h = writeDir; + GOTO_IF_ERRPASS(!verifyPath(h, &fname, 0), doOpenWriteEnd); + + f = h->funcs; + if (appending) + io = f->openAppend(h->opaque, fname); + else + io = f->openWrite(h->opaque, fname); + + GOTO_IF_ERRPASS(!io, doOpenWriteEnd); + + fh = (FileHandle *) allocator.Malloc(sizeof (FileHandle)); + if (fh == NULL) + { + io->destroy(io); + GOTO(PHYSFS_ERR_OUT_OF_MEMORY, doOpenWriteEnd); + } /* if */ + else + { + memset(fh, '\0', sizeof (FileHandle)); + fh->io = io; + fh->dirHandle = h; + fh->next = openWriteList; + openWriteList = fh; + } /* else */ + + doOpenWriteEnd: + __PHYSFS_platformReleaseMutex(stateLock); + } /* if */ + + __PHYSFS_smallFree(fname); + return ((PHYSFS_File *) fh); +} /* doOpenWrite */ + + +PHYSFS_File *PHYSFS_openWrite(const char *filename) +{ + return doOpenWrite(filename, 0); +} /* PHYSFS_openWrite */ + + +PHYSFS_File *PHYSFS_openAppend(const char *filename) +{ + return doOpenWrite(filename, 1); +} /* PHYSFS_openAppend */ + + +PHYSFS_File *PHYSFS_openRead(const char *_fname) +{ + FileHandle *fh = NULL; + char *fname; + size_t len; + + BAIL_IF(!_fname, PHYSFS_ERR_INVALID_ARGUMENT, 0); + len = strlen(_fname) + 1; + fname = (char *) __PHYSFS_smallAlloc(len); + BAIL_IF(!fname, PHYSFS_ERR_OUT_OF_MEMORY, 0); + + if (sanitizePlatformIndependentPath(_fname, fname)) + { + DirHandle *i = NULL; + PHYSFS_Io *io = NULL; + + __PHYSFS_platformGrabMutex(stateLock); + + GOTO_IF(!searchPath, PHYSFS_ERR_NOT_FOUND, openReadEnd); + + for (i = searchPath; i != NULL; i = i->next) + { + char *arcfname = fname; + if (verifyPath(i, &arcfname, 0)) + { + io = i->funcs->openRead(i->opaque, arcfname); + if (io) + break; + } /* if */ + } /* for */ + + GOTO_IF_ERRPASS(!io, openReadEnd); + + fh = (FileHandle *) allocator.Malloc(sizeof (FileHandle)); + if (fh == NULL) + { + io->destroy(io); + GOTO(PHYSFS_ERR_OUT_OF_MEMORY, openReadEnd); + } /* if */ + + memset(fh, '\0', sizeof (FileHandle)); + fh->io = io; + fh->forReading = 1; + fh->dirHandle = i; + fh->next = openReadList; + openReadList = fh; + + openReadEnd: + __PHYSFS_platformReleaseMutex(stateLock); + } /* if */ + + __PHYSFS_smallFree(fname); + return ((PHYSFS_File *) fh); +} /* PHYSFS_openRead */ + + +static int closeHandleInOpenList(FileHandle **list, FileHandle *handle) +{ + FileHandle *prev = NULL; + FileHandle *i; + + for (i = *list; i != NULL; i = i->next) + { + if (i == handle) /* handle is in this list? */ + { + PHYSFS_Io *io = handle->io; + PHYSFS_uint8 *tmp = handle->buffer; + + /* send our buffer to io... */ + if (!handle->forReading) + { + if (!PHYSFS_flush((PHYSFS_File *) handle)) + return -1; + + /* ...then have io send it to the disk... */ + else if (io->flush && !io->flush(io)) + return -1; + } /* if */ + + /* ...then close the underlying file. */ + io->destroy(io); + + if (tmp != NULL) /* free any associated buffer. */ + allocator.Free(tmp); + + if (prev == NULL) + *list = handle->next; + else + prev->next = handle->next; + + allocator.Free(handle); + return 1; + } /* if */ + prev = i; + } /* for */ + + return 0; +} /* closeHandleInOpenList */ + + +int PHYSFS_close(PHYSFS_File *_handle) +{ + FileHandle *handle = (FileHandle *) _handle; + int rc; + + __PHYSFS_platformGrabMutex(stateLock); + + /* -1 == close failure. 0 == not found. 1 == success. */ + rc = closeHandleInOpenList(&openReadList, handle); + BAIL_IF_MUTEX_ERRPASS(rc == -1, stateLock, 0); + if (!rc) + { + rc = closeHandleInOpenList(&openWriteList, handle); + BAIL_IF_MUTEX_ERRPASS(rc == -1, stateLock, 0); + } /* if */ + + __PHYSFS_platformReleaseMutex(stateLock); + BAIL_IF(!rc, PHYSFS_ERR_INVALID_ARGUMENT, 0); + return 1; +} /* PHYSFS_close */ + + +static PHYSFS_sint64 doBufferedRead(FileHandle *fh, void *_buffer, size_t len) +{ + PHYSFS_uint8 *buffer = (PHYSFS_uint8 *) _buffer; + PHYSFS_sint64 retval = 0; + + while (len > 0) + { + const size_t avail = fh->buffill - fh->bufpos; + if (avail > 0) /* data available in the buffer. */ + { + const size_t cpy = (len < avail) ? len : avail; + memcpy(buffer, fh->buffer + fh->bufpos, cpy); + assert(len >= cpy); + buffer += cpy; + len -= cpy; + fh->bufpos += cpy; + retval += cpy; + } /* if */ + + else /* buffer is empty, refill it. */ + { + PHYSFS_Io *io = fh->io; + const PHYSFS_sint64 rc = io->read(io, fh->buffer, fh->bufsize); + fh->bufpos = 0; + if (rc > 0) + fh->buffill = (size_t) rc; + else + { + fh->buffill = 0; + if (retval == 0) /* report already-read data, or failure. */ + retval = rc; + break; + } /* else */ + } /* else */ + } /* while */ + + return retval; +} /* doBufferedRead */ + + +PHYSFS_sint64 PHYSFS_read(PHYSFS_File *handle, void *buffer, + PHYSFS_uint32 size, PHYSFS_uint32 count) +{ + const PHYSFS_uint64 len = ((PHYSFS_uint64) size) * ((PHYSFS_uint64) count); + const PHYSFS_sint64 retval = PHYSFS_readBytes(handle, buffer, len); + return ( (retval <= 0) ? retval : (retval / ((PHYSFS_sint64) size)) ); +} /* PHYSFS_read */ + + +PHYSFS_sint64 PHYSFS_readBytes(PHYSFS_File *handle, void *buffer, + PHYSFS_uint64 _len) +{ + const size_t len = (size_t) _len; + FileHandle *fh = (FileHandle *) handle; + +#ifdef PHYSFS_NO_64BIT_SUPPORT + const PHYSFS_uint64 maxlen = __PHYSFS_UI64(0x7FFFFFFF); +#else + const PHYSFS_uint64 maxlen = __PHYSFS_UI64(0x7FFFFFFFFFFFFFFF); +#endif + + if (!__PHYSFS_ui64FitsAddressSpace(_len)) + BAIL(PHYSFS_ERR_INVALID_ARGUMENT, -1); + + BAIL_IF(_len > maxlen, PHYSFS_ERR_INVALID_ARGUMENT, -1); + BAIL_IF(!fh->forReading, PHYSFS_ERR_OPEN_FOR_WRITING, -1); + BAIL_IF_ERRPASS(len == 0, 0); + if (fh->buffer) + return doBufferedRead(fh, buffer, len); + + return fh->io->read(fh->io, buffer, len); +} /* PHYSFS_readBytes */ + + +static PHYSFS_sint64 doBufferedWrite(PHYSFS_File *handle, const void *buffer, + const size_t len) +{ + FileHandle *fh = (FileHandle *) handle; + + /* whole thing fits in the buffer? */ + if ((fh->buffill + len) < fh->bufsize) + { + memcpy(fh->buffer + fh->buffill, buffer, len); + fh->buffill += len; + return (PHYSFS_sint64) len; + } /* if */ + + /* would overflow buffer. Flush and then write the new objects, too. */ + BAIL_IF_ERRPASS(!PHYSFS_flush(handle), -1); + return fh->io->write(fh->io, buffer, len); +} /* doBufferedWrite */ + + +PHYSFS_sint64 PHYSFS_write(PHYSFS_File *handle, const void *buffer, + PHYSFS_uint32 size, PHYSFS_uint32 count) +{ + const PHYSFS_uint64 len = ((PHYSFS_uint64) size) * ((PHYSFS_uint64) count); + const PHYSFS_sint64 retval = PHYSFS_writeBytes(handle, buffer, len); + return ( (retval <= 0) ? retval : (retval / ((PHYSFS_sint64) size)) ); +} /* PHYSFS_write */ + + +PHYSFS_sint64 PHYSFS_writeBytes(PHYSFS_File *handle, const void *buffer, + PHYSFS_uint64 _len) +{ + const size_t len = (size_t) _len; + FileHandle *fh = (FileHandle *) handle; + +#ifdef PHYSFS_NO_64BIT_SUPPORT + const PHYSFS_uint64 maxlen = __PHYSFS_UI64(0x7FFFFFFF); +#else + const PHYSFS_uint64 maxlen = __PHYSFS_UI64(0x7FFFFFFFFFFFFFFF); +#endif + + if (!__PHYSFS_ui64FitsAddressSpace(_len)) + BAIL(PHYSFS_ERR_INVALID_ARGUMENT, -1); + + BAIL_IF(_len > maxlen, PHYSFS_ERR_INVALID_ARGUMENT, -1); + BAIL_IF(fh->forReading, PHYSFS_ERR_OPEN_FOR_READING, -1); + BAIL_IF_ERRPASS(len == 0, 0); + if (fh->buffer) + return doBufferedWrite(handle, buffer, len); + + return fh->io->write(fh->io, buffer, len); +} /* PHYSFS_write */ + + +int PHYSFS_eof(PHYSFS_File *handle) +{ + FileHandle *fh = (FileHandle *) handle; + + if (!fh->forReading) /* never EOF on files opened for write/append. */ + return 0; + + /* can't be eof if buffer isn't empty */ + if (fh->bufpos == fh->buffill) + { + /* check the Io. */ + PHYSFS_Io *io = fh->io; + const PHYSFS_sint64 pos = io->tell(io); + const PHYSFS_sint64 len = io->length(io); + if ((pos < 0) || (len < 0)) + return 0; /* beats me. */ + return (pos >= len); + } /* if */ + + return 0; +} /* PHYSFS_eof */ + + +PHYSFS_sint64 PHYSFS_tell(PHYSFS_File *handle) +{ + FileHandle *fh = (FileHandle *) handle; + const PHYSFS_sint64 pos = fh->io->tell(fh->io); + const PHYSFS_sint64 retval = fh->forReading ? + (pos - fh->buffill) + fh->bufpos : + (pos + fh->buffill); + return retval; +} /* PHYSFS_tell */ + + +int PHYSFS_seek(PHYSFS_File *handle, PHYSFS_uint64 pos) +{ + FileHandle *fh = (FileHandle *) handle; + BAIL_IF_ERRPASS(!PHYSFS_flush(handle), 0); + + if (fh->buffer && fh->forReading) + { + /* avoid throwing away our precious buffer if seeking within it. */ + PHYSFS_sint64 offset = pos - PHYSFS_tell(handle); + if ( /* seeking within the already-buffered range? */ + /* forward? */ + ((offset >= 0) && (((size_t)offset) <= fh->buffill-fh->bufpos)) || + /* backward? */ + ((offset < 0) && (((size_t) -offset) <= fh->bufpos)) ) + { + fh->bufpos = (size_t) (((PHYSFS_sint64) fh->bufpos) + offset); + return 1; /* successful seek */ + } /* if */ + } /* if */ + + /* we have to fall back to a 'raw' seek. */ + fh->buffill = fh->bufpos = 0; + return fh->io->seek(fh->io, pos); +} /* PHYSFS_seek */ + + +PHYSFS_sint64 PHYSFS_fileLength(PHYSFS_File *handle) +{ + PHYSFS_Io *io = ((FileHandle *) handle)->io; + return io->length(io); +} /* PHYSFS_filelength */ + + +int PHYSFS_setBuffer(PHYSFS_File *handle, PHYSFS_uint64 _bufsize) +{ + FileHandle *fh = (FileHandle *) handle; + const size_t bufsize = (size_t) _bufsize; + + if (!__PHYSFS_ui64FitsAddressSpace(_bufsize)) + BAIL(PHYSFS_ERR_INVALID_ARGUMENT, 0); + + BAIL_IF_ERRPASS(!PHYSFS_flush(handle), 0); + + /* + * For reads, we need to move the file pointer to where it would be + * if we weren't buffering, so that the next read will get the + * right chunk of stuff from the file. PHYSFS_flush() handles writes. + */ + if ((fh->forReading) && (fh->buffill != fh->bufpos)) + { + PHYSFS_uint64 pos; + const PHYSFS_sint64 curpos = fh->io->tell(fh->io); + BAIL_IF_ERRPASS(curpos == -1, 0); + pos = ((curpos - fh->buffill) + fh->bufpos); + BAIL_IF_ERRPASS(!fh->io->seek(fh->io, pos), 0); + } /* if */ + + if (bufsize == 0) /* delete existing buffer. */ + { + if (fh->buffer) + { + allocator.Free(fh->buffer); + fh->buffer = NULL; + } /* if */ + } /* if */ + + else + { + PHYSFS_uint8 *newbuf; + newbuf = (PHYSFS_uint8 *) allocator.Realloc(fh->buffer, bufsize); + BAIL_IF(!newbuf, PHYSFS_ERR_OUT_OF_MEMORY, 0); + fh->buffer = newbuf; + } /* else */ + + fh->bufsize = bufsize; + fh->buffill = fh->bufpos = 0; + return 1; +} /* PHYSFS_setBuffer */ + + +int PHYSFS_flush(PHYSFS_File *handle) +{ + FileHandle *fh = (FileHandle *) handle; + PHYSFS_Io *io; + PHYSFS_sint64 rc; + + if ((fh->forReading) || (fh->bufpos == fh->buffill)) + return 1; /* open for read or buffer empty are successful no-ops. */ + + /* dump buffer to disk. */ + io = fh->io; + rc = io->write(io, fh->buffer + fh->bufpos, fh->buffill - fh->bufpos); + BAIL_IF_ERRPASS(rc <= 0, 0); + fh->bufpos = fh->buffill = 0; + return 1; +} /* PHYSFS_flush */ + + +int PHYSFS_stat(const char *_fname, PHYSFS_Stat *stat) +{ + int retval = 0; + char *fname; + size_t len; + + BAIL_IF(!_fname, PHYSFS_ERR_INVALID_ARGUMENT, 0); + BAIL_IF(!stat, PHYSFS_ERR_INVALID_ARGUMENT, 0); + len = strlen(_fname) + 1; + fname = (char *) __PHYSFS_smallAlloc(len); + BAIL_IF(!fname, PHYSFS_ERR_OUT_OF_MEMORY, 0); + + /* set some sane defaults... */ + stat->filesize = -1; + stat->modtime = -1; + stat->createtime = -1; + stat->accesstime = -1; + stat->filetype = PHYSFS_FILETYPE_OTHER; + stat->readonly = 1; + + if (sanitizePlatformIndependentPath(_fname, fname)) + { + if (*fname == '\0') + { + stat->filetype = PHYSFS_FILETYPE_DIRECTORY; + stat->readonly = !writeDir; /* Writeable if we have a writeDir */ + retval = 1; + } /* if */ + else + { + DirHandle *i; + int exists = 0; + __PHYSFS_platformGrabMutex(stateLock); + for (i = searchPath; ((i != NULL) && (!exists)); i = i->next) + { + char *arcfname = fname; + exists = partOfMountPoint(i, arcfname); + if (exists) + { + stat->filetype = PHYSFS_FILETYPE_DIRECTORY; + stat->readonly = 1; + retval = 1; + } /* if */ + else if (verifyPath(i, &arcfname, 0)) + { + retval = i->funcs->stat(i->opaque, arcfname, stat); + if ((retval) || (currentErrorCode() != PHYSFS_ERR_NOT_FOUND)) + exists = 1; + } /* else if */ + } /* for */ + __PHYSFS_platformReleaseMutex(stateLock); + } /* else */ + } /* if */ + + __PHYSFS_smallFree(fname); + return retval; +} /* PHYSFS_stat */ + + +int __PHYSFS_readAll(PHYSFS_Io *io, void *buf, const size_t _len) +{ + const PHYSFS_uint64 len = (PHYSFS_uint64) _len; + return (io->read(io, buf, len) == len); +} /* __PHYSFS_readAll */ + + +void *__PHYSFS_initSmallAlloc(void *ptr, const size_t len) +{ + void *useHeap = ((ptr == NULL) ? ((void *) 1) : ((void *) 0)); + if (useHeap) /* too large for stack allocation or alloca() failed. */ + ptr = allocator.Malloc(len+sizeof (void *)); + + if (ptr != NULL) + { + void **retval = (void **) ptr; + /*printf("%s alloc'd (%lld) bytes at (%p).\n", + useHeap ? "heap" : "stack", (long long) len, ptr);*/ + *retval = useHeap; + return retval + 1; + } /* if */ + + return NULL; /* allocation failed. */ +} /* __PHYSFS_initSmallAlloc */ + + +void __PHYSFS_smallFree(void *ptr) +{ + if (ptr != NULL) + { + void **block = ((void **) ptr) - 1; + const int useHeap = (*block != NULL); + if (useHeap) + allocator.Free(block); + /*printf("%s free'd (%p).\n", useHeap ? "heap" : "stack", block);*/ + } /* if */ +} /* __PHYSFS_smallFree */ + + +int PHYSFS_setAllocator(const PHYSFS_Allocator *a) +{ + BAIL_IF(initialized, PHYSFS_ERR_IS_INITIALIZED, 0); + externalAllocator = (a != NULL); + if (externalAllocator) + memcpy(&allocator, a, sizeof (PHYSFS_Allocator)); + + return 1; +} /* PHYSFS_setAllocator */ + + +const PHYSFS_Allocator *PHYSFS_getAllocator(void) +{ + BAIL_IF(!initialized, PHYSFS_ERR_NOT_INITIALIZED, NULL); + return &allocator; +} /* PHYSFS_getAllocator */ + + +static void *mallocAllocatorMalloc(PHYSFS_uint64 s) +{ + if (!__PHYSFS_ui64FitsAddressSpace(s)) + BAIL(PHYSFS_ERR_OUT_OF_MEMORY, NULL); + #undef malloc + return malloc((size_t) s); +} /* mallocAllocatorMalloc */ + + +static void *mallocAllocatorRealloc(void *ptr, PHYSFS_uint64 s) +{ + if (!__PHYSFS_ui64FitsAddressSpace(s)) + BAIL(PHYSFS_ERR_OUT_OF_MEMORY, NULL); + #undef realloc + return realloc(ptr, (size_t) s); +} /* mallocAllocatorRealloc */ + + +static void mallocAllocatorFree(void *ptr) +{ + #undef free + free(ptr); +} /* mallocAllocatorFree */ + + +static void setDefaultAllocator(void) +{ + assert(!externalAllocator); + allocator.Init = NULL; + allocator.Deinit = NULL; + allocator.Malloc = mallocAllocatorMalloc; + allocator.Realloc = mallocAllocatorRealloc; + allocator.Free = mallocAllocatorFree; +} /* setDefaultAllocator */ + + +int __PHYSFS_DirTreeInit(__PHYSFS_DirTree *dt, const size_t entrylen) +{ + static char rootpath[2] = { '/', '\0' }; + size_t alloclen; + + assert(entrylen >= sizeof (__PHYSFS_DirTreeEntry)); + + memset(dt, '\0', sizeof (*dt)); + + dt->root = (__PHYSFS_DirTreeEntry *) allocator.Malloc(entrylen); + BAIL_IF(!dt->root, PHYSFS_ERR_OUT_OF_MEMORY, 0); + memset(dt->root, '\0', entrylen); + dt->root->name = rootpath; + dt->root->isdir = 1; + dt->hashBuckets = 64; + if (!dt->hashBuckets) + dt->hashBuckets = 1; + dt->entrylen = entrylen; + + alloclen = dt->hashBuckets * sizeof (__PHYSFS_DirTreeEntry *); + dt->hash = (__PHYSFS_DirTreeEntry **) allocator.Malloc(alloclen); + BAIL_IF(!dt->hash, PHYSFS_ERR_OUT_OF_MEMORY, 0); + memset(dt->hash, '\0', alloclen); + + return 1; +} /* __PHYSFS_DirTreeInit */ + + +static inline PHYSFS_uint32 hashPathName(__PHYSFS_DirTree *dt, const char *name) +{ + return __PHYSFS_hashString(name, strlen(name)) % dt->hashBuckets; +} /* hashPathName */ + + +/* Fill in missing parent directories. */ +static __PHYSFS_DirTreeEntry *addAncestors(__PHYSFS_DirTree *dt, char *name) +{ + __PHYSFS_DirTreeEntry *retval = dt->root; + char *sep = strrchr(name, '/'); + + if (sep) + { + *sep = '\0'; /* chop off last piece. */ + retval = (__PHYSFS_DirTreeEntry *) __PHYSFS_DirTreeFind(dt, name); + + if (retval != NULL) + { + *sep = '/'; + BAIL_IF(!retval->isdir, PHYSFS_ERR_CORRUPT, NULL); + return retval; /* already hashed. */ + } /* if */ + + /* okay, this is a new dir. Build and hash us. */ + retval = (__PHYSFS_DirTreeEntry*)__PHYSFS_DirTreeAdd(dt, name, 1); + *sep = '/'; + } /* if */ + + return retval; +} /* addAncestors */ + + +void *__PHYSFS_DirTreeAdd(__PHYSFS_DirTree *dt, char *name, const int isdir) +{ + __PHYSFS_DirTreeEntry *retval = __PHYSFS_DirTreeFind(dt, name); + if (!retval) + { + const size_t alloclen = strlen(name) + 1 + dt->entrylen; + PHYSFS_uint32 hashval; + __PHYSFS_DirTreeEntry *parent = addAncestors(dt, name); + BAIL_IF_ERRPASS(!parent, NULL); + assert(dt->entrylen >= sizeof (__PHYSFS_DirTreeEntry)); + retval = (__PHYSFS_DirTreeEntry *) allocator.Malloc(alloclen); + BAIL_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + memset(retval, '\0', dt->entrylen); + retval->name = ((char *) retval) + dt->entrylen; + strcpy(retval->name, name); + hashval = hashPathName(dt, name); + retval->hashnext = dt->hash[hashval]; + dt->hash[hashval] = retval; + retval->sibling = parent->children; + retval->isdir = isdir; + parent->children = retval; + } /* if */ + + return retval; +} /* __PHYSFS_DirTreeAdd */ + + +/* Find the __PHYSFS_DirTreeEntry for a path in platform-independent notation. */ +void *__PHYSFS_DirTreeFind(__PHYSFS_DirTree *dt, const char *path) +{ + PHYSFS_uint32 hashval; + __PHYSFS_DirTreeEntry *prev = NULL; + __PHYSFS_DirTreeEntry *retval; + + if (*path == '\0') + return dt->root; + + hashval = hashPathName(dt, path); + for (retval = dt->hash[hashval]; retval; retval = retval->hashnext) + { + if (strcmp(retval->name, path) == 0) + { + if (prev != NULL) /* move this to the front of the list */ + { + prev->hashnext = retval->hashnext; + retval->hashnext = dt->hash[hashval]; + dt->hash[hashval] = retval; + } /* if */ + + return retval; + } /* if */ + + prev = retval; + } /* for */ + + BAIL(PHYSFS_ERR_NOT_FOUND, NULL); +} /* __PHYSFS_DirTreeFind */ + +PHYSFS_EnumerateCallbackResult __PHYSFS_DirTreeEnumerate(void *opaque, + const char *dname, PHYSFS_EnumerateCallback cb, + const char *origdir, void *callbackdata) +{ + PHYSFS_EnumerateCallbackResult retval = PHYSFS_ENUM_OK; + __PHYSFS_DirTree *tree = (__PHYSFS_DirTree *) opaque; + const __PHYSFS_DirTreeEntry *entry = __PHYSFS_DirTreeFind(tree, dname); + BAIL_IF(!entry, PHYSFS_ERR_NOT_FOUND, PHYSFS_ENUM_ERROR); + + entry = entry->children; + + while (entry && (retval == PHYSFS_ENUM_OK)) + { + const char *name = entry->name; + const char *ptr = strrchr(name, '/'); + retval = cb(callbackdata, origdir, ptr ? ptr + 1 : name); + BAIL_IF(retval == PHYSFS_ENUM_ERROR, PHYSFS_ERR_APP_CALLBACK, retval); + entry = entry->sibling; + } /* while */ + + return retval; +} /* __PHYSFS_DirTreeEnumerate */ + + +void __PHYSFS_DirTreeDeinit(__PHYSFS_DirTree *dt) +{ + if (!dt) + return; + + if (dt->root) + { + assert(dt->root->sibling == NULL); + assert(dt->hash || (dt->root->children == NULL)); + allocator.Free(dt->root); + } /* if */ + + if (dt->hash) + { + size_t i; + for (i = 0; i < dt->hashBuckets; i++) + { + __PHYSFS_DirTreeEntry *entry; + __PHYSFS_DirTreeEntry *next; + for (entry = dt->hash[i]; entry; entry = next) + { + next = entry->hashnext; + allocator.Free(entry); + } /* for */ + } /* for */ + allocator.Free(dt->hash); + } /* if */ +} /* __PHYSFS_DirTreeDeinit */ + +/* end of physfs.c ... */ + diff --git a/Source/Dependencies/PhysicsFS/physfs.h b/Source/Dependencies/PhysicsFS/physfs.h new file mode 100644 index 000000000..108555051 --- /dev/null +++ b/Source/Dependencies/PhysicsFS/physfs.h @@ -0,0 +1,3854 @@ +/** + * \file physfs.h + * + * Main header file for PhysicsFS. + */ + +/** + * \mainpage PhysicsFS + * + * The latest version of PhysicsFS can be found at: + * https://icculus.org/physfs/ + * + * PhysicsFS; a portable, flexible file i/o abstraction. + * + * This API gives you access to a system file system in ways superior to the + * stdio or system i/o calls. The brief benefits: + * + * - It's portable. + * - It's safe. No file access is permitted outside the specified dirs. + * - It's flexible. Archives (.ZIP files) can be used transparently as + * directory structures. + * + * With PhysicsFS, you have a single writing directory and multiple + * directories (the "search path") for reading. You can think of this as a + * filesystem within a filesystem. If (on Windows) you were to set the + * writing directory to "C:\MyGame\MyWritingDirectory", then no PHYSFS calls + * could touch anything above this directory, including the "C:\MyGame" and + * "C:\" directories. This prevents an application's internal scripting + * language from piddling over c:\\config.sys, for example. If you'd rather + * give PHYSFS full access to the system's REAL file system, set the writing + * dir to "C:\", but that's generally A Bad Thing for several reasons. + * + * Drive letters are hidden in PhysicsFS once you set up your initial paths. + * The search path creates a single, hierarchical directory structure. + * Not only does this lend itself well to general abstraction with archives, + * it also gives better support to operating systems like MacOS and Unix. + * Generally speaking, you shouldn't ever hardcode a drive letter; not only + * does this hurt portability to non-Microsoft OSes, but it limits your win32 + * users to a single drive, too. Use the PhysicsFS abstraction functions and + * allow user-defined configuration options, too. When opening a file, you + * specify it like it was on a Unix filesystem: if you want to write to + * "C:\MyGame\MyConfigFiles\game.cfg", then you might set the write dir to + * "C:\MyGame" and then open "MyConfigFiles/game.cfg". This gives an + * abstraction across all platforms. Specifying a file in this way is termed + * "platform-independent notation" in this documentation. Specifying a + * a filename in a form such as "C:\mydir\myfile" or + * "MacOS hard drive:My Directory:My File" is termed "platform-dependent + * notation". The only time you use platform-dependent notation is when + * setting up your write directory and search path; after that, all file + * access into those directories are done with platform-independent notation. + * + * All files opened for writing are opened in relation to the write directory, + * which is the root of the writable filesystem. When opening a file for + * reading, PhysicsFS goes through the search path. This is NOT the + * same thing as the PATH environment variable. An application using + * PhysicsFS specifies directories to be searched which may be actual + * directories, or archive files that contain files and subdirectories of + * their own. See the end of these docs for currently supported archive + * formats. + * + * Once the search path is defined, you may open files for reading. If you've + * got the following search path defined (to use a win32 example again): + * + * - C:\\mygame + * - C:\\mygame\\myuserfiles + * - D:\\mygamescdromdatafiles + * - C:\\mygame\\installeddatafiles.zip + * + * Then a call to PHYSFS_openRead("textfiles/myfile.txt") (note the directory + * separator, lack of drive letter, and lack of dir separator at the start of + * the string; this is platform-independent notation) will check for + * C:\\mygame\\textfiles\\myfile.txt, then + * C:\\mygame\\myuserfiles\\textfiles\\myfile.txt, then + * D:\\mygamescdromdatafiles\\textfiles\\myfile.txt, then, finally, for + * textfiles\\myfile.txt inside of C:\\mygame\\installeddatafiles.zip. + * Remember that most archive types and platform filesystems store their + * filenames in a case-sensitive manner, so you should be careful to specify + * it correctly. + * + * Files opened through PhysicsFS may NOT contain "." or ".." or ":" as dir + * elements. Not only are these meaningless on MacOS Classic and/or Unix, + * they are a security hole. Also, symbolic links (which can be found in + * some archive types and directly in the filesystem on Unix platforms) are + * NOT followed until you call PHYSFS_permitSymbolicLinks(). That's left to + * your own discretion, as following a symlink can allow for access outside + * the write dir and search paths. For portability, there is no mechanism for + * creating new symlinks in PhysicsFS. + * + * The write dir is not included in the search path unless you specifically + * add it. While you CAN change the write dir as many times as you like, + * you should probably set it once and stick to it. Remember that your + * program will not have permission to write in every directory on Unix and + * NT systems. + * + * All files are opened in binary mode; there is no endline conversion for + * textfiles. Other than that, PhysicsFS has some convenience functions for + * platform-independence. There is a function to tell you the current + * platform's dir separator ("\\" on windows, "/" on Unix, ":" on MacOS), + * which is needed only to set up your search/write paths. There is a + * function to tell you what CD-ROM drives contain accessible discs, and a + * function to recommend a good search path, etc. + * + * A recommended order for the search path is the write dir, then the base dir, + * then the cdrom dir, then any archives discovered. Quake 3 does something + * like this, but moves the archives to the start of the search path. Build + * Engine games, like Duke Nukem 3D and Blood, place the archives last, and + * use the base dir for both searching and writing. There is a helper + * function (PHYSFS_setSaneConfig()) that puts together a basic configuration + * for you, based on a few parameters. Also see the comments on + * PHYSFS_getBaseDir(), and PHYSFS_getPrefDir() for info on what those + * are and how they can help you determine an optimal search path. + * + * PhysicsFS 2.0 adds the concept of "mounting" archives to arbitrary points + * in the search path. If a zipfile contains "maps/level.map" and you mount + * that archive at "mods/mymod", then you would have to open + * "mods/mymod/maps/level.map" to access the file, even though "mods/mymod" + * isn't actually specified in the .zip file. Unlike the Unix mentality of + * mounting a filesystem, "mods/mymod" doesn't actually have to exist when + * mounting the zipfile. It's a "virtual" directory. The mounting mechanism + * allows the developer to seperate archives in the tree and avoid trampling + * over files when added new archives, such as including mod support in a + * game...keeping external content on a tight leash in this manner can be of + * utmost importance to some applications. + * + * PhysicsFS is mostly thread safe. The errors returned by + * PHYSFS_getLastErrorCode() are unique by thread, and library-state-setting + * functions are mutex'd. For efficiency, individual file accesses are + * not locked, so you can not safely read/write/seek/close/etc the same + * file from two threads at the same time. Other race conditions are bugs + * that should be reported/patched. + * + * While you CAN use stdio/syscall file access in a program that has PHYSFS_* + * calls, doing so is not recommended, and you can not directly use system + * filehandles with PhysicsFS and vice versa (but as of PhysicsFS 2.1, you + * can wrap them in a PHYSFS_Io interface yourself if you wanted to). + * + * Note that archives need not be named as such: if you have a ZIP file and + * rename it with a .PKG extension, the file will still be recognized as a + * ZIP archive by PhysicsFS; the file's contents are used to determine its + * type where possible. + * + * Currently supported archive types: + * - .ZIP (pkZip/WinZip/Info-ZIP compatible) + * - .7Z (7zip archives) + * - .ISO (ISO9660 files, CD-ROM images) + * - .GRP (Build Engine groupfile archives) + * - .PAK (Quake I/II archive format) + * - .HOG (Descent I/II HOG file archives) + * - .MVL (Descent II movielib archives) + * - .WAD (DOOM engine archives) + * - .VDF (Gothic I/II engine archives) + * - .SLB (Independence War archives) + * + * String policy for PhysicsFS 2.0 and later: + * + * PhysicsFS 1.0 could only deal with null-terminated ASCII strings. All high + * ASCII chars resulted in undefined behaviour, and there was no Unicode + * support at all. PhysicsFS 2.0 supports Unicode without breaking binary + * compatibility with the 1.0 API by using UTF-8 encoding of all strings + * passed in and out of the library. + * + * All strings passed through PhysicsFS are in null-terminated UTF-8 format. + * This means that if all you care about is English (ASCII characters <= 127) + * then you just use regular C strings. If you care about Unicode (and you + * should!) then you need to figure out what your platform wants, needs, and + * offers. If you are on Windows before Win2000 and build with Unicode + * support, your TCHAR strings are two bytes per character (this is called + * "UCS-2 encoding"). Any modern Windows uses UTF-16, which is two bytes + * per character for most characters, but some characters are four. You + * should convert them to UTF-8 before handing them to PhysicsFS with + * PHYSFS_utf8FromUtf16(), which handles both UTF-16 and UCS-2. If you're + * using Unix or Mac OS X, your wchar_t strings are four bytes per character + * ("UCS-4 encoding", sometimes called "UTF-32"). Use PHYSFS_utf8FromUcs4(). + * Mac OS X can give you UTF-8 directly from a CFString or NSString, and many + * Unixes generally give you C strings in UTF-8 format everywhere. If you + * have a single-byte high ASCII charset, like so-many European "codepages" + * you may be out of luck. We'll convert from "Latin1" to UTF-8 only, and + * never back to Latin1. If you're above ASCII 127, all bets are off: move + * to Unicode or use your platform's facilities. Passing a C string with + * high-ASCII data that isn't UTF-8 encoded will NOT do what you expect! + * + * Naturally, there's also PHYSFS_utf8ToUcs2(), PHYSFS_utf8ToUtf16(), and + * PHYSFS_utf8ToUcs4() to get data back into a format you like. Behind the + * scenes, PhysicsFS will use Unicode where possible: the UTF-8 strings on + * Windows will be converted and used with the multibyte Windows APIs, for + * example. + * + * PhysicsFS offers basic encoding conversion support, but not a whole string + * library. Get your stuff into whatever format you can work with. + * + * Most platforms supported by PhysicsFS 2.1 and later fully support Unicode. + * Some older platforms have been dropped (Windows 95, Mac OS 9). Some, like + * OS/2, might be able to convert to a local codepage or will just fail to + * open/create the file. Modern OSes (macOS, Linux, Windows, etc) should all + * be fine. + * + * Many game-specific archivers are seriously unprepared for Unicode (the + * Descent HOG/MVL and Build Engine GRP archivers, for example, only offer a + * DOS 8.3 filename, for example). Nothing can be done for these, but they + * tend to be legacy formats for existing content that was all ASCII (and + * thus, valid UTF-8) anyhow. Other formats, like .ZIP, don't explicitly + * offer Unicode support, but unofficially expect filenames to be UTF-8 + * encoded, and thus Just Work. Most everything does the right thing without + * bothering you, but it's good to be aware of these nuances in case they + * don't. + * + * + * Other stuff: + * + * Please see the file LICENSE.txt in the source's root directory for + * licensing and redistribution rights. + * + * Please see the file CREDITS.txt in the source's "docs" directory for + * a more or less complete list of who's responsible for this. + * + * \author Ryan C. Gordon. + */ + +#ifndef _INCLUDE_PHYSFS_H_ +#define _INCLUDE_PHYSFS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(PHYSFS_DECL) +/* do nothing. */ +#elif defined(_MSC_VER) +#define PHYSFS_DECL __declspec(dllexport) +#elif defined(__SUNPRO_C) +#define PHYSFS_DECL __global +#elif ((__GNUC__ >= 3) && (!defined(__EMX__)) && (!defined(sun))) +#define PHYSFS_DECL __attribute__((visibility("default"))) +#else +#define PHYSFS_DECL +#endif + +#if defined(PHYSFS_DEPRECATED) +/* do nothing. */ +#elif (__GNUC__ >= 4) /* technically, this arrived in gcc 3.1, but oh well. */ +#define PHYSFS_DEPRECATED __attribute__((deprecated)) +#else +#define PHYSFS_DEPRECATED +#endif + +#if 0 /* !!! FIXME: look into this later. */ +#if defined(PHYSFS_CALL) +/* do nothing. */ +#elif defined(__WIN32__) && !defined(__GNUC__) +#define PHYSFS_CALL __cdecl +#elif defined(__OS2__) || defined(OS2) /* should work across all compilers. */ +#define PHYSFS_CALL _System +#else +#define PHYSFS_CALL +#endif +#endif + +/** + * \typedef PHYSFS_uint8 + * \brief An unsigned, 8-bit integer type. + */ +typedef unsigned char PHYSFS_uint8; + +/** + * \typedef PHYSFS_sint8 + * \brief A signed, 8-bit integer type. + */ +typedef signed char PHYSFS_sint8; + +/** + * \typedef PHYSFS_uint16 + * \brief An unsigned, 16-bit integer type. + */ +typedef unsigned short PHYSFS_uint16; + +/** + * \typedef PHYSFS_sint16 + * \brief A signed, 16-bit integer type. + */ +typedef signed short PHYSFS_sint16; + +/** + * \typedef PHYSFS_uint32 + * \brief An unsigned, 32-bit integer type. + */ +typedef unsigned int PHYSFS_uint32; + +/** + * \typedef PHYSFS_sint32 + * \brief A signed, 32-bit integer type. + */ +typedef signed int PHYSFS_sint32; + +/** + * \typedef PHYSFS_uint64 + * \brief An unsigned, 64-bit integer type. + * \warning on platforms without any sort of 64-bit datatype, this is + * equivalent to PHYSFS_uint32! + */ + +/** + * \typedef PHYSFS_sint64 + * \brief A signed, 64-bit integer type. + * \warning on platforms without any sort of 64-bit datatype, this is + * equivalent to PHYSFS_sint32! + */ + + +#if (defined PHYSFS_NO_64BIT_SUPPORT) /* oh well. */ +typedef PHYSFS_uint32 PHYSFS_uint64; +typedef PHYSFS_sint32 PHYSFS_sint64; +#elif (defined _MSC_VER) +typedef signed __int64 PHYSFS_sint64; +typedef unsigned __int64 PHYSFS_uint64; +#else +typedef unsigned long long PHYSFS_uint64; +typedef signed long long PHYSFS_sint64; +#endif + + +#ifndef DOXYGEN_SHOULD_IGNORE_THIS +/* Make sure the types really have the right sizes */ +#define PHYSFS_COMPILE_TIME_ASSERT(name, x) \ + typedef int PHYSFS_compile_time_assert_##name[(x) * 2 - 1] + +PHYSFS_COMPILE_TIME_ASSERT(uint8IsOneByte, sizeof(PHYSFS_uint8) == 1); +PHYSFS_COMPILE_TIME_ASSERT(sint8IsOneByte, sizeof(PHYSFS_sint8) == 1); +PHYSFS_COMPILE_TIME_ASSERT(uint16IsTwoBytes, sizeof(PHYSFS_uint16) == 2); +PHYSFS_COMPILE_TIME_ASSERT(sint16IsTwoBytes, sizeof(PHYSFS_sint16) == 2); +PHYSFS_COMPILE_TIME_ASSERT(uint32IsFourBytes, sizeof(PHYSFS_uint32) == 4); +PHYSFS_COMPILE_TIME_ASSERT(sint32IsFourBytes, sizeof(PHYSFS_sint32) == 4); + +#ifndef PHYSFS_NO_64BIT_SUPPORT +PHYSFS_COMPILE_TIME_ASSERT(uint64IsEightBytes, sizeof(PHYSFS_uint64) == 8); +PHYSFS_COMPILE_TIME_ASSERT(sint64IsEightBytes, sizeof(PHYSFS_sint64) == 8); +#endif + +#undef PHYSFS_COMPILE_TIME_ASSERT + +#endif /* DOXYGEN_SHOULD_IGNORE_THIS */ + + +/** + * \struct PHYSFS_File + * \brief A PhysicsFS file handle. + * + * You get a pointer to one of these when you open a file for reading, + * writing, or appending via PhysicsFS. + * + * As you can see from the lack of meaningful fields, you should treat this + * as opaque data. Don't try to manipulate the file handle, just pass the + * pointer you got, unmolested, to various PhysicsFS APIs. + * + * \sa PHYSFS_openRead + * \sa PHYSFS_openWrite + * \sa PHYSFS_openAppend + * \sa PHYSFS_close + * \sa PHYSFS_read + * \sa PHYSFS_write + * \sa PHYSFS_seek + * \sa PHYSFS_tell + * \sa PHYSFS_eof + * \sa PHYSFS_setBuffer + * \sa PHYSFS_flush + */ +typedef struct PHYSFS_File +{ + void *opaque; /**< That's all you get. Don't touch. */ +} PHYSFS_File; + + +/** + * \def PHYSFS_file + * \brief 1.0 API compatibility define. + * + * PHYSFS_file is identical to PHYSFS_File. This #define is here for backwards + * compatibility with the 1.0 API, which had an inconsistent capitalization + * convention in this case. New code should use PHYSFS_File, as this #define + * may go away someday. + * + * \sa PHYSFS_File + */ +#define PHYSFS_file PHYSFS_File + + +/** + * \struct PHYSFS_ArchiveInfo + * \brief Information on various PhysicsFS-supported archives. + * + * This structure gives you details on what sort of archives are supported + * by this implementation of PhysicsFS. Archives tend to be things like + * ZIP files and such. + * + * \warning Not all binaries are created equal! PhysicsFS can be built with + * or without support for various archives. You can check with + * PHYSFS_supportedArchiveTypes() to see if your archive type is + * supported. + * + * \sa PHYSFS_supportedArchiveTypes + * \sa PHYSFS_registerArchiver + * \sa PHYSFS_deregisterArchiver + */ +typedef struct PHYSFS_ArchiveInfo +{ + const char *extension; /**< Archive file extension: "ZIP", for example. */ + const char *description; /**< Human-readable archive description. */ + const char *author; /**< Person who did support for this archive. */ + const char *url; /**< URL related to this archive */ + int supportsSymlinks; /**< non-zero if archive offers symbolic links. */ +} PHYSFS_ArchiveInfo; + + +/** + * \struct PHYSFS_Version + * \brief Information the version of PhysicsFS in use. + * + * Represents the library's version as three levels: major revision + * (increments with massive changes, additions, and enhancements), + * minor revision (increments with backwards-compatible changes to the + * major revision), and patchlevel (increments with fixes to the minor + * revision). + * + * \sa PHYSFS_VERSION + * \sa PHYSFS_getLinkedVersion + */ +typedef struct PHYSFS_Version +{ + PHYSFS_uint8 major; /**< major revision */ + PHYSFS_uint8 minor; /**< minor revision */ + PHYSFS_uint8 patch; /**< patchlevel */ +} PHYSFS_Version; + + +#ifndef DOXYGEN_SHOULD_IGNORE_THIS +#define PHYSFS_VER_MAJOR 3 +#define PHYSFS_VER_MINOR 0 +#define PHYSFS_VER_PATCH 2 +#endif /* DOXYGEN_SHOULD_IGNORE_THIS */ + + +/* PhysicsFS state stuff ... */ + +/** + * \def PHYSFS_VERSION(x) + * \brief Macro to determine PhysicsFS version program was compiled against. + * + * This macro fills in a PHYSFS_Version structure with the version of the + * library you compiled against. This is determined by what header the + * compiler uses. Note that if you dynamically linked the library, you might + * have a slightly newer or older version at runtime. That version can be + * determined with PHYSFS_getLinkedVersion(), which, unlike PHYSFS_VERSION, + * is not a macro. + * + * \param x A pointer to a PHYSFS_Version struct to initialize. + * + * \sa PHYSFS_Version + * \sa PHYSFS_getLinkedVersion + */ +#define PHYSFS_VERSION(x) \ +{ \ + (x)->major = PHYSFS_VER_MAJOR; \ + (x)->minor = PHYSFS_VER_MINOR; \ + (x)->patch = PHYSFS_VER_PATCH; \ +} + + +/** + * \fn void PHYSFS_getLinkedVersion(PHYSFS_Version *ver) + * \brief Get the version of PhysicsFS that is linked against your program. + * + * If you are using a shared library (DLL) version of PhysFS, then it is + * possible that it will be different than the version you compiled against. + * + * This is a real function; the macro PHYSFS_VERSION tells you what version + * of PhysFS you compiled against: + * + * \code + * PHYSFS_Version compiled; + * PHYSFS_Version linked; + * + * PHYSFS_VERSION(&compiled); + * PHYSFS_getLinkedVersion(&linked); + * printf("We compiled against PhysFS version %d.%d.%d ...\n", + * compiled.major, compiled.minor, compiled.patch); + * printf("But we linked against PhysFS version %d.%d.%d.\n", + * linked.major, linked.minor, linked.patch); + * \endcode + * + * This function may be called safely at any time, even before PHYSFS_init(). + * + * \sa PHYSFS_VERSION + */ +PHYSFS_DECL void PHYSFS_getLinkedVersion(PHYSFS_Version *ver); + + +/** + * \fn int PHYSFS_init(const char *argv0) + * \brief Initialize the PhysicsFS library. + * + * This must be called before any other PhysicsFS function. + * + * This should be called prior to any attempts to change your process's + * current working directory. + * + * \param argv0 the argv[0] string passed to your program's mainline. + * This may be NULL on most platforms (such as ones without a + * standard main() function), but you should always try to pass + * something in here. Unix-like systems such as Linux _need_ to + * pass argv[0] from main() in here. + * \return nonzero on success, zero on error. Specifics of the error can be + * gleaned from PHYSFS_getLastError(). + * + * \sa PHYSFS_deinit + * \sa PHYSFS_isInit + */ +PHYSFS_DECL int PHYSFS_init(const char *argv0); + + +/** + * \fn int PHYSFS_deinit(void) + * \brief Deinitialize the PhysicsFS library. + * + * This closes any files opened via PhysicsFS, blanks the search/write paths, + * frees memory, and invalidates all of your file handles. + * + * Note that this call can FAIL if there's a file open for writing that + * refuses to close (for example, the underlying operating system was + * buffering writes to network filesystem, and the fileserver has crashed, + * or a hard drive has failed, etc). It is usually best to close all write + * handles yourself before calling this function, so that you can gracefully + * handle a specific failure. + * + * Once successfully deinitialized, PHYSFS_init() can be called again to + * restart the subsystem. All default API states are restored at this + * point, with the exception of any custom allocator you might have + * specified, which survives between initializations. + * + * \return nonzero on success, zero on error. Specifics of the error can be + * gleaned from PHYSFS_getLastError(). If failure, state of PhysFS is + * undefined, and probably badly screwed up. + * + * \sa PHYSFS_init + * \sa PHYSFS_isInit + */ +PHYSFS_DECL int PHYSFS_deinit(void); + + +/** + * \fn const PHYSFS_ArchiveInfo **PHYSFS_supportedArchiveTypes(void) + * \brief Get a list of supported archive types. + * + * Get a list of archive types supported by this implementation of PhysicFS. + * These are the file formats usable for search path entries. This is for + * informational purposes only. Note that the extension listed is merely + * convention: if we list "ZIP", you can open a PkZip-compatible archive + * with an extension of "XYZ", if you like. + * + * The returned value is an array of pointers to PHYSFS_ArchiveInfo structures, + * with a NULL entry to signify the end of the list: + * + * \code + * PHYSFS_ArchiveInfo **i; + * + * for (i = PHYSFS_supportedArchiveTypes(); *i != NULL; i++) + * { + * printf("Supported archive: [%s], which is [%s].\n", + * (*i)->extension, (*i)->description); + * } + * \endcode + * + * The return values are pointers to internal memory, and should + * be considered READ ONLY, and never freed. The returned values are + * valid until the next call to PHYSFS_deinit(), PHYSFS_registerArchiver(), + * or PHYSFS_deregisterArchiver(). + * + * \return READ ONLY Null-terminated array of READ ONLY structures. + * + * \sa PHYSFS_registerArchiver + * \sa PHYSFS_deregisterArchiver + */ +PHYSFS_DECL const PHYSFS_ArchiveInfo **PHYSFS_supportedArchiveTypes(void); + + +/** + * \fn void PHYSFS_freeList(void *listVar) + * \brief Deallocate resources of lists returned by PhysicsFS. + * + * Certain PhysicsFS functions return lists of information that are + * dynamically allocated. Use this function to free those resources. + * + * It is safe to pass a NULL here, but doing so will cause a crash in versions + * before PhysicsFS 2.1.0. + * + * \param listVar List of information specified as freeable by this function. + * Passing NULL is safe; it is a valid no-op. + * + * \sa PHYSFS_getCdRomDirs + * \sa PHYSFS_enumerateFiles + * \sa PHYSFS_getSearchPath + */ +PHYSFS_DECL void PHYSFS_freeList(void *listVar); + + +/** + * \fn const char *PHYSFS_getLastError(void) + * \brief Get human-readable error information. + * + * \deprecated Use PHYSFS_getLastErrorCode() and PHYSFS_getErrorByCode() instead. + * + * \warning As of PhysicsFS 2.1, this function has been nerfed. + * Before PhysicsFS 2.1, this function was the only way to get + * error details beyond a given function's basic return value. + * This was meant to be a human-readable string in one of several + * languages, and was not useful for application parsing. This was + * a problem, because the developer and not the user chose the + * language at compile time, and the PhysicsFS maintainers had + * to (poorly) maintain a significant amount of localization work. + * The app couldn't parse the strings, even if they counted on a + * specific language, since some were dynamically generated. + * In 2.1 and later, this always returns a static string in + * English; you may use it as a key string for your own + * localizations if you like, as we'll promise not to change + * existing error strings. Also, if your application wants to + * look at specific errors, we now offer a better option: + * use PHYSFS_getLastErrorCode() instead. + * + * Get the last PhysicsFS error message as a human-readable, null-terminated + * string. This will return NULL if there's been no error since the last call + * to this function. The pointer returned by this call points to an internal + * buffer. Each thread has a unique error state associated with it, but each + * time a new error message is set, it will overwrite the previous one + * associated with that thread. It is safe to call this function at anytime, + * even before PHYSFS_init(). + * + * PHYSFS_getLastError() and PHYSFS_getLastErrorCode() both reset the same + * thread-specific error state. Calling one will wipe out the other's + * data. If you need both, call PHYSFS_getLastErrorCode(), then pass that + * value to PHYSFS_getErrorByCode(). + * + * As of PhysicsFS 2.1, this function only presents text in the English + * language, but the strings are static, so you can use them as keys into + * your own localization dictionary. These strings are meant to be passed on + * directly to the user. + * + * Generally, applications should only concern themselves with whether a + * given function failed; however, if your code require more specifics, you + * should use PHYSFS_getLastErrorCode() instead of this function. + * + * \return READ ONLY string of last error message. + * + * \sa PHYSFS_getLastErrorCode + * \sa PHYSFS_getErrorByCode + */ +PHYSFS_DECL const char *PHYSFS_getLastError(void) PHYSFS_DEPRECATED; + + +/** + * \fn const char *PHYSFS_getDirSeparator(void) + * \brief Get platform-dependent dir separator string. + * + * This returns "\\" on win32, "/" on Unix, and ":" on MacOS. It may be more + * than one character, depending on the platform, and your code should take + * that into account. Note that this is only useful for setting up the + * search/write paths, since access into those dirs always use '/' + * (platform-independent notation) to separate directories. This is also + * handy for getting platform-independent access when using stdio calls. + * + * \return READ ONLY null-terminated string of platform's dir separator. + */ +PHYSFS_DECL const char *PHYSFS_getDirSeparator(void); + + +/** + * \fn void PHYSFS_permitSymbolicLinks(int allow) + * \brief Enable or disable following of symbolic links. + * + * Some physical filesystems and archives contain files that are just pointers + * to other files. On the physical filesystem, opening such a link will + * (transparently) open the file that is pointed to. + * + * By default, PhysicsFS will check if a file is really a symlink during open + * calls and fail if it is. Otherwise, the link could take you outside the + * write and search paths, and compromise security. + * + * If you want to take that risk, call this function with a non-zero parameter. + * Note that this is more for sandboxing a program's scripting language, in + * case untrusted scripts try to compromise the system. Generally speaking, + * a user could very well have a legitimate reason to set up a symlink, so + * unless you feel there's a specific danger in allowing them, you should + * permit them. + * + * Symlinks are only explicitly checked when dealing with filenames + * in platform-independent notation. That is, when setting up your + * search and write paths, etc, symlinks are never checked for. + * + * Please note that PHYSFS_stat() will always check the path specified; if + * that path is a symlink, it will not be followed in any case. If symlinks + * aren't permitted through this function, PHYSFS_stat() ignores them, and + * would treat the query as if the path didn't exist at all. + * + * Symbolic link permission can be enabled or disabled at any time after + * you've called PHYSFS_init(), and is disabled by default. + * + * \param allow nonzero to permit symlinks, zero to deny linking. + * + * \sa PHYSFS_symbolicLinksPermitted + */ +PHYSFS_DECL void PHYSFS_permitSymbolicLinks(int allow); + + +/** + * \fn char **PHYSFS_getCdRomDirs(void) + * \brief Get an array of paths to available CD-ROM drives. + * + * The dirs returned are platform-dependent ("D:\" on Win32, "/cdrom" or + * whatnot on Unix). Dirs are only returned if there is a disc ready and + * accessible in the drive. So if you've got two drives (D: and E:), and only + * E: has a disc in it, then that's all you get. If the user inserts a disc + * in D: and you call this function again, you get both drives. If, on a + * Unix box, the user unmounts a disc and remounts it elsewhere, the next + * call to this function will reflect that change. + * + * This function refers to "CD-ROM" media, but it really means "inserted disc + * media," such as DVD-ROM, HD-DVD, CDRW, and Blu-Ray discs. It looks for + * filesystems, and as such won't report an audio CD, unless there's a + * mounted filesystem track on it. + * + * The returned value is an array of strings, with a NULL entry to signify the + * end of the list: + * + * \code + * char **cds = PHYSFS_getCdRomDirs(); + * char **i; + * + * for (i = cds; *i != NULL; i++) + * printf("cdrom dir [%s] is available.\n", *i); + * + * PHYSFS_freeList(cds); + * \endcode + * + * This call may block while drives spin up. Be forewarned. + * + * When you are done with the returned information, you may dispose of the + * resources by calling PHYSFS_freeList() with the returned pointer. + * + * \return Null-terminated array of null-terminated strings. + * + * \sa PHYSFS_getCdRomDirsCallback + */ +PHYSFS_DECL char **PHYSFS_getCdRomDirs(void); + + +/** + * \fn const char *PHYSFS_getBaseDir(void) + * \brief Get the path where the application resides. + * + * Helper function. + * + * Get the "base dir". This is the directory where the application was run + * from, which is probably the installation directory, and may or may not + * be the process's current working directory. + * + * You should probably use the base dir in your search path. + * + * \return READ ONLY string of base dir in platform-dependent notation. + * + * \sa PHYSFS_getPrefDir + */ +PHYSFS_DECL const char *PHYSFS_getBaseDir(void); + + +/** + * \fn const char *PHYSFS_getUserDir(void) + * \brief Get the path where user's home directory resides. + * + * \deprecated As of PhysicsFS 2.1, you probably want PHYSFS_getPrefDir(). + * + * Helper function. + * + * Get the "user dir". This is meant to be a suggestion of where a specific + * user of the system can store files. On Unix, this is her home directory. + * On systems with no concept of multiple home directories (MacOS, win95), + * this will default to something like "C:\mybasedir\users\username" + * where "username" will either be the login name, or "default" if the + * platform doesn't support multiple users, either. + * + * \return READ ONLY string of user dir in platform-dependent notation. + * + * \sa PHYSFS_getBaseDir + * \sa PHYSFS_getPrefDir + */ +PHYSFS_DECL const char *PHYSFS_getUserDir(void) PHYSFS_DEPRECATED; + + +/** + * \fn const char *PHYSFS_getWriteDir(void) + * \brief Get path where PhysicsFS will allow file writing. + * + * Get the current write dir. The default write dir is NULL. + * + * \return READ ONLY string of write dir in platform-dependent notation, + * OR NULL IF NO WRITE PATH IS CURRENTLY SET. + * + * \sa PHYSFS_setWriteDir + */ +PHYSFS_DECL const char *PHYSFS_getWriteDir(void); + + +/** + * \fn int PHYSFS_setWriteDir(const char *newDir) + * \brief Tell PhysicsFS where it may write files. + * + * Set a new write dir. This will override the previous setting. + * + * This call will fail (and fail to change the write dir) if the current + * write dir still has files open in it. + * + * \param newDir The new directory to be the root of the write dir, + * specified in platform-dependent notation. Setting to NULL + * disables the write dir, so no files can be opened for + * writing via PhysicsFS. + * \return non-zero on success, zero on failure. All attempts to open a file + * for writing via PhysicsFS will fail until this call succeeds. + * Use PHYSFS_getLastErrorCode() to obtain the specific error. + * + * \sa PHYSFS_getWriteDir + */ +PHYSFS_DECL int PHYSFS_setWriteDir(const char *newDir); + + +/** + * \fn int PHYSFS_addToSearchPath(const char *newDir, int appendToPath) + * \brief Add an archive or directory to the search path. + * + * \deprecated As of PhysicsFS 2.0, use PHYSFS_mount() instead. This + * function just wraps it anyhow. + * + * This function is equivalent to: + * + * \code + * PHYSFS_mount(newDir, NULL, appendToPath); + * \endcode + * + * You must use this and not PHYSFS_mount if binary compatibility with + * PhysicsFS 1.0 is important (which it may not be for many people). + * + * \sa PHYSFS_mount + * \sa PHYSFS_removeFromSearchPath + * \sa PHYSFS_getSearchPath + */ +PHYSFS_DECL int PHYSFS_addToSearchPath(const char *newDir, int appendToPath) + PHYSFS_DEPRECATED; + +/** + * \fn int PHYSFS_removeFromSearchPath(const char *oldDir) + * \brief Remove a directory or archive from the search path. + * + * \deprecated As of PhysicsFS 2.1, use PHYSFS_unmount() instead. This + * function just wraps it anyhow. There's no functional difference + * except the vocabulary changed from "adding to the search path" + * to "mounting" when that functionality was extended, and thus + * the preferred way to accomplish this function's work is now + * called "unmounting." + * + * This function is equivalent to: + * + * \code + * PHYSFS_unmount(oldDir); + * \endcode + * + * You must use this and not PHYSFS_unmount if binary compatibility with + * PhysicsFS 1.0 is important (which it may not be for many people). + * + * \sa PHYSFS_addToSearchPath + * \sa PHYSFS_getSearchPath + * \sa PHYSFS_unmount + */ +PHYSFS_DECL int PHYSFS_removeFromSearchPath(const char *oldDir) + PHYSFS_DEPRECATED; + + +/** + * \fn char **PHYSFS_getSearchPath(void) + * \brief Get the current search path. + * + * The default search path is an empty list. + * + * The returned value is an array of strings, with a NULL entry to signify the + * end of the list: + * + * \code + * char **i; + * + * for (i = PHYSFS_getSearchPath(); *i != NULL; i++) + * printf("[%s] is in the search path.\n", *i); + * \endcode + * + * When you are done with the returned information, you may dispose of the + * resources by calling PHYSFS_freeList() with the returned pointer. + * + * \return Null-terminated array of null-terminated strings. NULL if there + * was a problem (read: OUT OF MEMORY). + * + * \sa PHYSFS_getSearchPathCallback + * \sa PHYSFS_addToSearchPath + * \sa PHYSFS_removeFromSearchPath + */ +PHYSFS_DECL char **PHYSFS_getSearchPath(void); + + +/** + * \fn int PHYSFS_setSaneConfig(const char *organization, const char *appName, const char *archiveExt, int includeCdRoms, int archivesFirst) + * \brief Set up sane, default paths. + * + * Helper function. + * + * The write dir will be set to the pref dir returned by + * \code PHYSFS_getPrefDir(organization, appName) \endcode, which is + * created if it doesn't exist. + * + * The above is sufficient to make sure your program's configuration directory + * is separated from other clutter, and platform-independent. + * + * The search path will be: + * + * - The Write Dir (created if it doesn't exist) + * - The Base Dir (PHYSFS_getBaseDir()) + * - All found CD-ROM dirs (optionally) + * + * These directories are then searched for files ending with the extension + * (archiveExt), which, if they are valid and supported archives, will also + * be added to the search path. If you specified "PKG" for (archiveExt), and + * there's a file named data.PKG in the base dir, it'll be checked. Archives + * can either be appended or prepended to the search path in alphabetical + * order, regardless of which directories they were found in. All archives + * are mounted in the root of the virtual file system ("/"). + * + * All of this can be accomplished from the application, but this just does it + * all for you. Feel free to add more to the search path manually, too. + * + * \param organization Name of your company/group/etc to be used as a + * dirname, so keep it small, and no-frills. + * + * \param appName Program-specific name of your program, to separate it + * from other programs using PhysicsFS. + * + * \param archiveExt File extension used by your program to specify an + * archive. For example, Quake 3 uses "pk3", even though + * they are just zipfiles. Specify NULL to not dig out + * archives automatically. Do not specify the '.' char; + * If you want to look for ZIP files, specify "ZIP" and + * not ".ZIP" ... the archive search is case-insensitive. + * + * \param includeCdRoms Non-zero to include CD-ROMs in the search path, and + * (if (archiveExt) != NULL) search them for archives. + * This may cause a significant amount of blocking + * while discs are accessed, and if there are no discs + * in the drive (or even not mounted on Unix systems), + * then they may not be made available anyhow. You may + * want to specify zero and handle the disc setup + * yourself. + * + * \param archivesFirst Non-zero to prepend the archives to the search path. + * Zero to append them. Ignored if !(archiveExt). + * + * \return nonzero on success, zero on error. Use PHYSFS_getLastErrorCode() + * to obtain the specific error. + */ +PHYSFS_DECL int PHYSFS_setSaneConfig(const char *organization, + const char *appName, + const char *archiveExt, + int includeCdRoms, + int archivesFirst); + + +/* Directory management stuff ... */ + +/** + * \fn int PHYSFS_mkdir(const char *dirName) + * \brief Create a directory. + * + * This is specified in platform-independent notation in relation to the + * write dir. All missing parent directories are also created if they + * don't exist. + * + * So if you've got the write dir set to "C:\mygame\writedir" and call + * PHYSFS_mkdir("downloads/maps") then the directories + * "C:\mygame\writedir\downloads" and "C:\mygame\writedir\downloads\maps" + * will be created if possible. If the creation of "maps" fails after we + * have successfully created "downloads", then the function leaves the + * created directory behind and reports failure. + * + * \param dirName New dir to create. + * \return nonzero on success, zero on error. Use + * PHYSFS_getLastErrorCode() to obtain the specific error. + * + * \sa PHYSFS_delete + */ +PHYSFS_DECL int PHYSFS_mkdir(const char *dirName); + + +/** + * \fn int PHYSFS_delete(const char *filename) + * \brief Delete a file or directory. + * + * (filename) is specified in platform-independent notation in relation to the + * write dir. + * + * A directory must be empty before this call can delete it. + * + * Deleting a symlink will remove the link, not what it points to, regardless + * of whether you "permitSymLinks" or not. + * + * So if you've got the write dir set to "C:\mygame\writedir" and call + * PHYSFS_delete("downloads/maps/level1.map") then the file + * "C:\mygame\writedir\downloads\maps\level1.map" is removed from the + * physical filesystem, if it exists and the operating system permits the + * deletion. + * + * Note that on Unix systems, deleting a file may be successful, but the + * actual file won't be removed until all processes that have an open + * filehandle to it (including your program) close their handles. + * + * Chances are, the bits that make up the file still exist, they are just + * made available to be written over at a later point. Don't consider this + * a security method or anything. :) + * + * \param filename Filename to delete. + * \return nonzero on success, zero on error. Use PHYSFS_getLastErrorCode() + * to obtain the specific error. + */ +PHYSFS_DECL int PHYSFS_delete(const char *filename); + + +/** + * \fn const char *PHYSFS_getRealDir(const char *filename) + * \brief Figure out where in the search path a file resides. + * + * The file is specified in platform-independent notation. The returned + * filename will be the element of the search path where the file was found, + * which may be a directory, or an archive. Even if there are multiple + * matches in different parts of the search path, only the first one found + * is used, just like when opening a file. + * + * So, if you look for "maps/level1.map", and C:\\mygame is in your search + * path and C:\\mygame\\maps\\level1.map exists, then "C:\mygame" is returned. + * + * If a any part of a match is a symbolic link, and you've not explicitly + * permitted symlinks, then it will be ignored, and the search for a match + * will continue. + * + * If you specify a fake directory that only exists as a mount point, it'll + * be associated with the first archive mounted there, even though that + * directory isn't necessarily contained in a real archive. + * + * \warning This will return NULL if there is no real directory associated + * with (filename). Specifically, PHYSFS_mountIo(), + * PHYSFS_mountMemory(), and PHYSFS_mountHandle() will return NULL + * even if the filename is found in the search path. Plan accordingly. + * + * \param filename file to look for. + * \return READ ONLY string of element of search path containing the + * the file in question. NULL if not found. + */ +PHYSFS_DECL const char *PHYSFS_getRealDir(const char *filename); + + +/** + * \fn char **PHYSFS_enumerateFiles(const char *dir) + * \brief Get a file listing of a search path's directory. + * + * \warning In PhysicsFS versions prior to 2.1, this function would return + * as many items as it could in the face of a failure condition + * (out of memory, disk i/o error, etc). Since this meant apps + * couldn't distinguish between complete success and partial failure, + * and since the function could always return NULL to report + * catastrophic failures anyway, in PhysicsFS 2.1 this function's + * policy changed: it will either return a list of complete results + * or it will return NULL for any failure of any kind, so we can + * guarantee that the enumeration ran to completion and has no gaps + * in its results. + * + * Matching directories are interpolated. That is, if "C:\mydir" is in the + * search path and contains a directory "savegames" that contains "x.sav", + * "y.sav", and "z.sav", and there is also a "C:\userdir" in the search path + * that has a "savegames" subdirectory with "w.sav", then the following code: + * + * \code + * char **rc = PHYSFS_enumerateFiles("savegames"); + * char **i; + * + * for (i = rc; *i != NULL; i++) + * printf(" * We've got [%s].\n", *i); + * + * PHYSFS_freeList(rc); + * \endcode + * + * \...will print: + * + * \verbatim + * We've got [x.sav]. + * We've got [y.sav]. + * We've got [z.sav]. + * We've got [w.sav].\endverbatim + * + * Feel free to sort the list however you like. However, the returned data + * will always contain no duplicates, and will be always sorted in alphabetic + * (rather: case-sensitive Unicode) order for you. + * + * Don't forget to call PHYSFS_freeList() with the return value from this + * function when you are done with it. + * + * \param dir directory in platform-independent notation to enumerate. + * \return Null-terminated array of null-terminated strings, or NULL for + * failure cases. + * + * \sa PHYSFS_enumerate + */ +PHYSFS_DECL char **PHYSFS_enumerateFiles(const char *dir); + + +/** + * \fn int PHYSFS_exists(const char *fname) + * \brief Determine if a file exists in the search path. + * + * Reports true if there is an entry anywhere in the search path by the + * name of (fname). + * + * Note that entries that are symlinks are ignored if + * PHYSFS_permitSymbolicLinks(1) hasn't been called, so you + * might end up further down in the search path than expected. + * + * \param fname filename in platform-independent notation. + * \return non-zero if filename exists. zero otherwise. + */ +PHYSFS_DECL int PHYSFS_exists(const char *fname); + + +/** + * \fn int PHYSFS_isDirectory(const char *fname) + * \brief Determine if a file in the search path is really a directory. + * + * \deprecated As of PhysicsFS 2.1, use PHYSFS_stat() instead. This + * function just wraps it anyhow. + * + * Determine if the first occurence of (fname) in the search path is + * really a directory entry. + * + * Note that entries that are symlinks are ignored if + * PHYSFS_permitSymbolicLinks(1) hasn't been called, so you + * might end up further down in the search path than expected. + * + * \param fname filename in platform-independent notation. + * \return non-zero if filename exists and is a directory. zero otherwise. + * + * \sa PHYSFS_stat + * \sa PHYSFS_exists + */ +PHYSFS_DECL int PHYSFS_isDirectory(const char *fname) PHYSFS_DEPRECATED; + + +/** + * \fn int PHYSFS_isSymbolicLink(const char *fname) + * \brief Determine if a file in the search path is really a symbolic link. + * + * \deprecated As of PhysicsFS 2.1, use PHYSFS_stat() instead. This + * function just wraps it anyhow. + * + * Determine if the first occurence of (fname) in the search path is + * really a symbolic link. + * + * Note that entries that are symlinks are ignored if + * PHYSFS_permitSymbolicLinks(1) hasn't been called, and as such, + * this function will always return 0 in that case. + * + * \param fname filename in platform-independent notation. + * \return non-zero if filename exists and is a symlink. zero otherwise. + * + * \sa PHYSFS_stat + * \sa PHYSFS_exists + */ +PHYSFS_DECL int PHYSFS_isSymbolicLink(const char *fname) PHYSFS_DEPRECATED; + + +/** + * \fn PHYSFS_sint64 PHYSFS_getLastModTime(const char *filename) + * \brief Get the last modification time of a file. + * + * \deprecated As of PhysicsFS 2.1, use PHYSFS_stat() instead. This + * function just wraps it anyhow. + * + * The modtime is returned as a number of seconds since the Unix epoch + * (midnight, Jan 1, 1970). The exact derivation and accuracy of this time + * depends on the particular archiver. If there is no reasonable way to + * obtain this information for a particular archiver, or there was some sort + * of error, this function returns (-1). + * + * You must use this and not PHYSFS_stat() if binary compatibility with + * PhysicsFS 2.0 is important (which it may not be for many people). + * + * \param filename filename to check, in platform-independent notation. + * \return last modified time of the file. -1 if it can't be determined. + * + * \sa PHYSFS_stat + */ +PHYSFS_DECL PHYSFS_sint64 PHYSFS_getLastModTime(const char *filename) + PHYSFS_DEPRECATED; + + +/* i/o stuff... */ + +/** + * \fn PHYSFS_File *PHYSFS_openWrite(const char *filename) + * \brief Open a file for writing. + * + * Open a file for writing, in platform-independent notation and in relation + * to the write dir as the root of the writable filesystem. The specified + * file is created if it doesn't exist. If it does exist, it is truncated to + * zero bytes, and the writing offset is set to the start. + * + * Note that entries that are symlinks are ignored if + * PHYSFS_permitSymbolicLinks(1) hasn't been called, and opening a + * symlink with this function will fail in such a case. + * + * \param filename File to open. + * \return A valid PhysicsFS filehandle on success, NULL on error. Use + * PHYSFS_getLastErrorCode() to obtain the specific error. + * + * \sa PHYSFS_openRead + * \sa PHYSFS_openAppend + * \sa PHYSFS_write + * \sa PHYSFS_close + */ +PHYSFS_DECL PHYSFS_File *PHYSFS_openWrite(const char *filename); + + +/** + * \fn PHYSFS_File *PHYSFS_openAppend(const char *filename) + * \brief Open a file for appending. + * + * Open a file for writing, in platform-independent notation and in relation + * to the write dir as the root of the writable filesystem. The specified + * file is created if it doesn't exist. If it does exist, the writing offset + * is set to the end of the file, so the first write will be the byte after + * the end. + * + * Note that entries that are symlinks are ignored if + * PHYSFS_permitSymbolicLinks(1) hasn't been called, and opening a + * symlink with this function will fail in such a case. + * + * \param filename File to open. + * \return A valid PhysicsFS filehandle on success, NULL on error. Use + * PHYSFS_getLastErrorCode() to obtain the specific error. + * + * \sa PHYSFS_openRead + * \sa PHYSFS_openWrite + * \sa PHYSFS_write + * \sa PHYSFS_close + */ +PHYSFS_DECL PHYSFS_File *PHYSFS_openAppend(const char *filename); + + +/** + * \fn PHYSFS_File *PHYSFS_openRead(const char *filename) + * \brief Open a file for reading. + * + * Open a file for reading, in platform-independent notation. The search path + * is checked one at a time until a matching file is found, in which case an + * abstract filehandle is associated with it, and reading may be done. + * The reading offset is set to the first byte of the file. + * + * Note that entries that are symlinks are ignored if + * PHYSFS_permitSymbolicLinks(1) hasn't been called, and opening a + * symlink with this function will fail in such a case. + * + * \param filename File to open. + * \return A valid PhysicsFS filehandle on success, NULL on error. + * Use PHYSFS_getLastErrorCode() to obtain the specific error. + * + * \sa PHYSFS_openWrite + * \sa PHYSFS_openAppend + * \sa PHYSFS_read + * \sa PHYSFS_close + */ +PHYSFS_DECL PHYSFS_File *PHYSFS_openRead(const char *filename); + + +/** + * \fn int PHYSFS_close(PHYSFS_File *handle) + * \brief Close a PhysicsFS filehandle. + * + * This call is capable of failing if the operating system was buffering + * writes to the physical media, and, now forced to write those changes to + * physical media, can not store the data for some reason. In such a case, + * the filehandle stays open. A well-written program should ALWAYS check the + * return value from the close call in addition to every writing call! + * + * \param handle handle returned from PHYSFS_open*(). + * \return nonzero on success, zero on error. Use PHYSFS_getLastErrorCode() + * to obtain the specific error. + * + * \sa PHYSFS_openRead + * \sa PHYSFS_openWrite + * \sa PHYSFS_openAppend + */ +PHYSFS_DECL int PHYSFS_close(PHYSFS_File *handle); + + +/** + * \fn PHYSFS_sint64 PHYSFS_read(PHYSFS_File *handle, void *buffer, PHYSFS_uint32 objSize, PHYSFS_uint32 objCount) + * \brief Read data from a PhysicsFS filehandle + * + * The file must be opened for reading. + * + * \deprecated As of PhysicsFS 2.1, use PHYSFS_readBytes() instead. This + * function just wraps it anyhow. This function never clarified + * what would happen if you managed to read a partial object, so + * working at the byte level makes this cleaner for everyone, + * especially now that PHYSFS_Io interfaces can be supplied by the + * application. + * + * \param handle handle returned from PHYSFS_openRead(). + * \param buffer buffer to store read data into. + * \param objSize size in bytes of objects being read from (handle). + * \param objCount number of (objSize) objects to read from (handle). + * \return number of objects read. PHYSFS_getLastErrorCode() can shed light + * on the reason this might be < (objCount), as can PHYSFS_eof(). + * -1 if complete failure. + * + * \sa PHYSFS_readBytes + * \sa PHYSFS_eof + */ +PHYSFS_DECL PHYSFS_sint64 PHYSFS_read(PHYSFS_File *handle, + void *buffer, + PHYSFS_uint32 objSize, + PHYSFS_uint32 objCount) + PHYSFS_DEPRECATED; + +/** + * \fn PHYSFS_sint64 PHYSFS_write(PHYSFS_File *handle, const void *buffer, PHYSFS_uint32 objSize, PHYSFS_uint32 objCount) + * \brief Write data to a PhysicsFS filehandle + * + * The file must be opened for writing. + * + * \deprecated As of PhysicsFS 2.1, use PHYSFS_writeBytes() instead. This + * function just wraps it anyhow. This function never clarified + * what would happen if you managed to write a partial object, so + * working at the byte level makes this cleaner for everyone, + * especially now that PHYSFS_Io interfaces can be supplied by the + * application. + * + * \param handle retval from PHYSFS_openWrite() or PHYSFS_openAppend(). + * \param buffer buffer of bytes to write to (handle). + * \param objSize size in bytes of objects being written to (handle). + * \param objCount number of (objSize) objects to write to (handle). + * \return number of objects written. PHYSFS_getLastErrorCode() can shed + * light on the reason this might be < (objCount). -1 if complete + * failure. + * + * \sa PHYSFS_writeBytes + */ +PHYSFS_DECL PHYSFS_sint64 PHYSFS_write(PHYSFS_File *handle, + const void *buffer, + PHYSFS_uint32 objSize, + PHYSFS_uint32 objCount) + PHYSFS_DEPRECATED; + + +/* File position stuff... */ + +/** + * \fn int PHYSFS_eof(PHYSFS_File *handle) + * \brief Check for end-of-file state on a PhysicsFS filehandle. + * + * Determine if the end of file has been reached in a PhysicsFS filehandle. + * + * \param handle handle returned from PHYSFS_openRead(). + * \return nonzero if EOF, zero if not. + * + * \sa PHYSFS_read + * \sa PHYSFS_tell + */ +PHYSFS_DECL int PHYSFS_eof(PHYSFS_File *handle); + + +/** + * \fn PHYSFS_sint64 PHYSFS_tell(PHYSFS_File *handle) + * \brief Determine current position within a PhysicsFS filehandle. + * + * \param handle handle returned from PHYSFS_open*(). + * \return offset in bytes from start of file. -1 if error occurred. + * Use PHYSFS_getLastErrorCode() to obtain the specific error. + * + * \sa PHYSFS_seek + */ +PHYSFS_DECL PHYSFS_sint64 PHYSFS_tell(PHYSFS_File *handle); + + +/** + * \fn int PHYSFS_seek(PHYSFS_File *handle, PHYSFS_uint64 pos) + * \brief Seek to a new position within a PhysicsFS filehandle. + * + * The next read or write will occur at that place. Seeking past the + * beginning or end of the file is not allowed, and causes an error. + * + * \param handle handle returned from PHYSFS_open*(). + * \param pos number of bytes from start of file to seek to. + * \return nonzero on success, zero on error. Use PHYSFS_getLastErrorCode() + * to obtain the specific error. + * + * \sa PHYSFS_tell + */ +PHYSFS_DECL int PHYSFS_seek(PHYSFS_File *handle, PHYSFS_uint64 pos); + + +/** + * \fn PHYSFS_sint64 PHYSFS_fileLength(PHYSFS_File *handle) + * \brief Get total length of a file in bytes. + * + * Note that if another process/thread is writing to this file at the same + * time, then the information this function supplies could be incorrect + * before you get it. Use with caution, or better yet, don't use at all. + * + * \param handle handle returned from PHYSFS_open*(). + * \return size in bytes of the file. -1 if can't be determined. + * + * \sa PHYSFS_tell + * \sa PHYSFS_seek + */ +PHYSFS_DECL PHYSFS_sint64 PHYSFS_fileLength(PHYSFS_File *handle); + + +/* Buffering stuff... */ + +/** + * \fn int PHYSFS_setBuffer(PHYSFS_File *handle, PHYSFS_uint64 bufsize) + * \brief Set up buffering for a PhysicsFS file handle. + * + * Define an i/o buffer for a file handle. A memory block of (bufsize) bytes + * will be allocated and associated with (handle). + * + * For files opened for reading, up to (bufsize) bytes are read from (handle) + * and stored in the internal buffer. Calls to PHYSFS_read() will pull + * from this buffer until it is empty, and then refill it for more reading. + * Note that compressed files, like ZIP archives, will decompress while + * buffering, so this can be handy for offsetting CPU-intensive operations. + * The buffer isn't filled until you do your next read. + * + * For files opened for writing, data will be buffered to memory until the + * buffer is full or the buffer is flushed. Closing a handle implicitly + * causes a flush...check your return values! + * + * Seeking, etc transparently accounts for buffering. + * + * You can resize an existing buffer by calling this function more than once + * on the same file. Setting the buffer size to zero will free an existing + * buffer. + * + * PhysicsFS file handles are unbuffered by default. + * + * Please check the return value of this function! Failures can include + * not being able to seek backwards in a read-only file when removing the + * buffer, not being able to allocate the buffer, and not being able to + * flush the buffer to disk, among other unexpected problems. + * + * \param handle handle returned from PHYSFS_open*(). + * \param bufsize size, in bytes, of buffer to allocate. + * \return nonzero if successful, zero on error. + * + * \sa PHYSFS_flush + * \sa PHYSFS_read + * \sa PHYSFS_write + * \sa PHYSFS_close + */ +PHYSFS_DECL int PHYSFS_setBuffer(PHYSFS_File *handle, PHYSFS_uint64 bufsize); + + +/** + * \fn int PHYSFS_flush(PHYSFS_File *handle) + * \brief Flush a buffered PhysicsFS file handle. + * + * For buffered files opened for writing, this will put the current contents + * of the buffer to disk and flag the buffer as empty if possible. + * + * For buffered files opened for reading or unbuffered files, this is a safe + * no-op, and will report success. + * + * \param handle handle returned from PHYSFS_open*(). + * \return nonzero if successful, zero on error. + * + * \sa PHYSFS_setBuffer + * \sa PHYSFS_close + */ +PHYSFS_DECL int PHYSFS_flush(PHYSFS_File *handle); + + +/* Byteorder stuff... */ + +/** + * \fn PHYSFS_sint16 PHYSFS_swapSLE16(PHYSFS_sint16 val) + * \brief Swap littleendian signed 16 to platform's native byte order. + * + * Take a 16-bit signed value in littleendian format and convert it to + * the platform's native byte order. + * + * \param val value to convert + * \return converted value. + */ +PHYSFS_DECL PHYSFS_sint16 PHYSFS_swapSLE16(PHYSFS_sint16 val); + + +/** + * \fn PHYSFS_uint16 PHYSFS_swapULE16(PHYSFS_uint16 val) + * \brief Swap littleendian unsigned 16 to platform's native byte order. + * + * Take a 16-bit unsigned value in littleendian format and convert it to + * the platform's native byte order. + * + * \param val value to convert + * \return converted value. + */ +PHYSFS_DECL PHYSFS_uint16 PHYSFS_swapULE16(PHYSFS_uint16 val); + +/** + * \fn PHYSFS_sint32 PHYSFS_swapSLE32(PHYSFS_sint32 val) + * \brief Swap littleendian signed 32 to platform's native byte order. + * + * Take a 32-bit signed value in littleendian format and convert it to + * the platform's native byte order. + * + * \param val value to convert + * \return converted value. + */ +PHYSFS_DECL PHYSFS_sint32 PHYSFS_swapSLE32(PHYSFS_sint32 val); + + +/** + * \fn PHYSFS_uint32 PHYSFS_swapULE32(PHYSFS_uint32 val) + * \brief Swap littleendian unsigned 32 to platform's native byte order. + * + * Take a 32-bit unsigned value in littleendian format and convert it to + * the platform's native byte order. + * + * \param val value to convert + * \return converted value. + */ +PHYSFS_DECL PHYSFS_uint32 PHYSFS_swapULE32(PHYSFS_uint32 val); + +/** + * \fn PHYSFS_sint64 PHYSFS_swapSLE64(PHYSFS_sint64 val) + * \brief Swap littleendian signed 64 to platform's native byte order. + * + * Take a 64-bit signed value in littleendian format and convert it to + * the platform's native byte order. + * + * \param val value to convert + * \return converted value. + * + * \warning Remember, PHYSFS_sint64 is only 32 bits on platforms without + * any sort of 64-bit support. + */ +PHYSFS_DECL PHYSFS_sint64 PHYSFS_swapSLE64(PHYSFS_sint64 val); + + +/** + * \fn PHYSFS_uint64 PHYSFS_swapULE64(PHYSFS_uint64 val) + * \brief Swap littleendian unsigned 64 to platform's native byte order. + * + * Take a 64-bit unsigned value in littleendian format and convert it to + * the platform's native byte order. + * + * \param val value to convert + * \return converted value. + * + * \warning Remember, PHYSFS_uint64 is only 32 bits on platforms without + * any sort of 64-bit support. + */ +PHYSFS_DECL PHYSFS_uint64 PHYSFS_swapULE64(PHYSFS_uint64 val); + + +/** + * \fn PHYSFS_sint16 PHYSFS_swapSBE16(PHYSFS_sint16 val) + * \brief Swap bigendian signed 16 to platform's native byte order. + * + * Take a 16-bit signed value in bigendian format and convert it to + * the platform's native byte order. + * + * \param val value to convert + * \return converted value. + */ +PHYSFS_DECL PHYSFS_sint16 PHYSFS_swapSBE16(PHYSFS_sint16 val); + + +/** + * \fn PHYSFS_uint16 PHYSFS_swapUBE16(PHYSFS_uint16 val) + * \brief Swap bigendian unsigned 16 to platform's native byte order. + * + * Take a 16-bit unsigned value in bigendian format and convert it to + * the platform's native byte order. + * + * \param val value to convert + * \return converted value. + */ +PHYSFS_DECL PHYSFS_uint16 PHYSFS_swapUBE16(PHYSFS_uint16 val); + +/** + * \fn PHYSFS_sint32 PHYSFS_swapSBE32(PHYSFS_sint32 val) + * \brief Swap bigendian signed 32 to platform's native byte order. + * + * Take a 32-bit signed value in bigendian format and convert it to + * the platform's native byte order. + * + * \param val value to convert + * \return converted value. + */ +PHYSFS_DECL PHYSFS_sint32 PHYSFS_swapSBE32(PHYSFS_sint32 val); + + +/** + * \fn PHYSFS_uint32 PHYSFS_swapUBE32(PHYSFS_uint32 val) + * \brief Swap bigendian unsigned 32 to platform's native byte order. + * + * Take a 32-bit unsigned value in bigendian format and convert it to + * the platform's native byte order. + * + * \param val value to convert + * \return converted value. + */ +PHYSFS_DECL PHYSFS_uint32 PHYSFS_swapUBE32(PHYSFS_uint32 val); + + +/** + * \fn PHYSFS_sint64 PHYSFS_swapSBE64(PHYSFS_sint64 val) + * \brief Swap bigendian signed 64 to platform's native byte order. + * + * Take a 64-bit signed value in bigendian format and convert it to + * the platform's native byte order. + * + * \param val value to convert + * \return converted value. + * + * \warning Remember, PHYSFS_sint64 is only 32 bits on platforms without + * any sort of 64-bit support. + */ +PHYSFS_DECL PHYSFS_sint64 PHYSFS_swapSBE64(PHYSFS_sint64 val); + + +/** + * \fn PHYSFS_uint64 PHYSFS_swapUBE64(PHYSFS_uint64 val) + * \brief Swap bigendian unsigned 64 to platform's native byte order. + * + * Take a 64-bit unsigned value in bigendian format and convert it to + * the platform's native byte order. + * + * \param val value to convert + * \return converted value. + * + * \warning Remember, PHYSFS_uint64 is only 32 bits on platforms without + * any sort of 64-bit support. + */ +PHYSFS_DECL PHYSFS_uint64 PHYSFS_swapUBE64(PHYSFS_uint64 val); + + +/** + * \fn int PHYSFS_readSLE16(PHYSFS_File *file, PHYSFS_sint16 *val) + * \brief Read and convert a signed 16-bit littleendian value. + * + * Convenience function. Read a signed 16-bit littleendian value from a + * file and convert it to the platform's native byte order. + * + * \param file PhysicsFS file handle from which to read. + * \param val pointer to where value should be stored. + * \return zero on failure, non-zero on success. If successful, (*val) will + * store the result. On failure, you can find out what went wrong + * from PHYSFS_getLastErrorCode(). + */ +PHYSFS_DECL int PHYSFS_readSLE16(PHYSFS_File *file, PHYSFS_sint16 *val); + + +/** + * \fn int PHYSFS_readULE16(PHYSFS_File *file, PHYSFS_uint16 *val) + * \brief Read and convert an unsigned 16-bit littleendian value. + * + * Convenience function. Read an unsigned 16-bit littleendian value from a + * file and convert it to the platform's native byte order. + * + * \param file PhysicsFS file handle from which to read. + * \param val pointer to where value should be stored. + * \return zero on failure, non-zero on success. If successful, (*val) will + * store the result. On failure, you can find out what went wrong + * from PHYSFS_getLastErrorCode(). + * + */ +PHYSFS_DECL int PHYSFS_readULE16(PHYSFS_File *file, PHYSFS_uint16 *val); + + +/** + * \fn int PHYSFS_readSBE16(PHYSFS_File *file, PHYSFS_sint16 *val) + * \brief Read and convert a signed 16-bit bigendian value. + * + * Convenience function. Read a signed 16-bit bigendian value from a + * file and convert it to the platform's native byte order. + * + * \param file PhysicsFS file handle from which to read. + * \param val pointer to where value should be stored. + * \return zero on failure, non-zero on success. If successful, (*val) will + * store the result. On failure, you can find out what went wrong + * from PHYSFS_getLastErrorCode(). + */ +PHYSFS_DECL int PHYSFS_readSBE16(PHYSFS_File *file, PHYSFS_sint16 *val); + + +/** + * \fn int PHYSFS_readUBE16(PHYSFS_File *file, PHYSFS_uint16 *val) + * \brief Read and convert an unsigned 16-bit bigendian value. + * + * Convenience function. Read an unsigned 16-bit bigendian value from a + * file and convert it to the platform's native byte order. + * + * \param file PhysicsFS file handle from which to read. + * \param val pointer to where value should be stored. + * \return zero on failure, non-zero on success. If successful, (*val) will + * store the result. On failure, you can find out what went wrong + * from PHYSFS_getLastErrorCode(). + * + */ +PHYSFS_DECL int PHYSFS_readUBE16(PHYSFS_File *file, PHYSFS_uint16 *val); + + +/** + * \fn int PHYSFS_readSLE32(PHYSFS_File *file, PHYSFS_sint32 *val) + * \brief Read and convert a signed 32-bit littleendian value. + * + * Convenience function. Read a signed 32-bit littleendian value from a + * file and convert it to the platform's native byte order. + * + * \param file PhysicsFS file handle from which to read. + * \param val pointer to where value should be stored. + * \return zero on failure, non-zero on success. If successful, (*val) will + * store the result. On failure, you can find out what went wrong + * from PHYSFS_getLastErrorCode(). + */ +PHYSFS_DECL int PHYSFS_readSLE32(PHYSFS_File *file, PHYSFS_sint32 *val); + + +/** + * \fn int PHYSFS_readULE32(PHYSFS_File *file, PHYSFS_uint32 *val) + * \brief Read and convert an unsigned 32-bit littleendian value. + * + * Convenience function. Read an unsigned 32-bit littleendian value from a + * file and convert it to the platform's native byte order. + * + * \param file PhysicsFS file handle from which to read. + * \param val pointer to where value should be stored. + * \return zero on failure, non-zero on success. If successful, (*val) will + * store the result. On failure, you can find out what went wrong + * from PHYSFS_getLastErrorCode(). + * + */ +PHYSFS_DECL int PHYSFS_readULE32(PHYSFS_File *file, PHYSFS_uint32 *val); + + +/** + * \fn int PHYSFS_readSBE32(PHYSFS_File *file, PHYSFS_sint32 *val) + * \brief Read and convert a signed 32-bit bigendian value. + * + * Convenience function. Read a signed 32-bit bigendian value from a + * file and convert it to the platform's native byte order. + * + * \param file PhysicsFS file handle from which to read. + * \param val pointer to where value should be stored. + * \return zero on failure, non-zero on success. If successful, (*val) will + * store the result. On failure, you can find out what went wrong + * from PHYSFS_getLastErrorCode(). + */ +PHYSFS_DECL int PHYSFS_readSBE32(PHYSFS_File *file, PHYSFS_sint32 *val); + + +/** + * \fn int PHYSFS_readUBE32(PHYSFS_File *file, PHYSFS_uint32 *val) + * \brief Read and convert an unsigned 32-bit bigendian value. + * + * Convenience function. Read an unsigned 32-bit bigendian value from a + * file and convert it to the platform's native byte order. + * + * \param file PhysicsFS file handle from which to read. + * \param val pointer to where value should be stored. + * \return zero on failure, non-zero on success. If successful, (*val) will + * store the result. On failure, you can find out what went wrong + * from PHYSFS_getLastErrorCode(). + * + */ +PHYSFS_DECL int PHYSFS_readUBE32(PHYSFS_File *file, PHYSFS_uint32 *val); + + +/** + * \fn int PHYSFS_readSLE64(PHYSFS_File *file, PHYSFS_sint64 *val) + * \brief Read and convert a signed 64-bit littleendian value. + * + * Convenience function. Read a signed 64-bit littleendian value from a + * file and convert it to the platform's native byte order. + * + * \param file PhysicsFS file handle from which to read. + * \param val pointer to where value should be stored. + * \return zero on failure, non-zero on success. If successful, (*val) will + * store the result. On failure, you can find out what went wrong + * from PHYSFS_getLastErrorCode(). + * + * \warning Remember, PHYSFS_sint64 is only 32 bits on platforms without + * any sort of 64-bit support. + */ +PHYSFS_DECL int PHYSFS_readSLE64(PHYSFS_File *file, PHYSFS_sint64 *val); + + +/** + * \fn int PHYSFS_readULE64(PHYSFS_File *file, PHYSFS_uint64 *val) + * \brief Read and convert an unsigned 64-bit littleendian value. + * + * Convenience function. Read an unsigned 64-bit littleendian value from a + * file and convert it to the platform's native byte order. + * + * \param file PhysicsFS file handle from which to read. + * \param val pointer to where value should be stored. + * \return zero on failure, non-zero on success. If successful, (*val) will + * store the result. On failure, you can find out what went wrong + * from PHYSFS_getLastErrorCode(). + * + * \warning Remember, PHYSFS_uint64 is only 32 bits on platforms without + * any sort of 64-bit support. + */ +PHYSFS_DECL int PHYSFS_readULE64(PHYSFS_File *file, PHYSFS_uint64 *val); + + +/** + * \fn int PHYSFS_readSBE64(PHYSFS_File *file, PHYSFS_sint64 *val) + * \brief Read and convert a signed 64-bit bigendian value. + * + * Convenience function. Read a signed 64-bit bigendian value from a + * file and convert it to the platform's native byte order. + * + * \param file PhysicsFS file handle from which to read. + * \param val pointer to where value should be stored. + * \return zero on failure, non-zero on success. If successful, (*val) will + * store the result. On failure, you can find out what went wrong + * from PHYSFS_getLastErrorCode(). + * + * \warning Remember, PHYSFS_sint64 is only 32 bits on platforms without + * any sort of 64-bit support. + */ +PHYSFS_DECL int PHYSFS_readSBE64(PHYSFS_File *file, PHYSFS_sint64 *val); + + +/** + * \fn int PHYSFS_readUBE64(PHYSFS_File *file, PHYSFS_uint64 *val) + * \brief Read and convert an unsigned 64-bit bigendian value. + * + * Convenience function. Read an unsigned 64-bit bigendian value from a + * file and convert it to the platform's native byte order. + * + * \param file PhysicsFS file handle from which to read. + * \param val pointer to where value should be stored. + * \return zero on failure, non-zero on success. If successful, (*val) will + * store the result. On failure, you can find out what went wrong + * from PHYSFS_getLastErrorCode(). + * + * \warning Remember, PHYSFS_uint64 is only 32 bits on platforms without + * any sort of 64-bit support. + */ +PHYSFS_DECL int PHYSFS_readUBE64(PHYSFS_File *file, PHYSFS_uint64 *val); + + +/** + * \fn int PHYSFS_writeSLE16(PHYSFS_File *file, PHYSFS_sint16 val) + * \brief Convert and write a signed 16-bit littleendian value. + * + * Convenience function. Convert a signed 16-bit value from the platform's + * native byte order to littleendian and write it to a file. + * + * \param file PhysicsFS file handle to which to write. + * \param val Value to convert and write. + * \return zero on failure, non-zero on success. On failure, you can + * find out what went wrong from PHYSFS_getLastErrorCode(). + */ +PHYSFS_DECL int PHYSFS_writeSLE16(PHYSFS_File *file, PHYSFS_sint16 val); + + +/** + * \fn int PHYSFS_writeULE16(PHYSFS_File *file, PHYSFS_uint16 val) + * \brief Convert and write an unsigned 16-bit littleendian value. + * + * Convenience function. Convert an unsigned 16-bit value from the platform's + * native byte order to littleendian and write it to a file. + * + * \param file PhysicsFS file handle to which to write. + * \param val Value to convert and write. + * \return zero on failure, non-zero on success. On failure, you can + * find out what went wrong from PHYSFS_getLastErrorCode(). + */ +PHYSFS_DECL int PHYSFS_writeULE16(PHYSFS_File *file, PHYSFS_uint16 val); + + +/** + * \fn int PHYSFS_writeSBE16(PHYSFS_File *file, PHYSFS_sint16 val) + * \brief Convert and write a signed 16-bit bigendian value. + * + * Convenience function. Convert a signed 16-bit value from the platform's + * native byte order to bigendian and write it to a file. + * + * \param file PhysicsFS file handle to which to write. + * \param val Value to convert and write. + * \return zero on failure, non-zero on success. On failure, you can + * find out what went wrong from PHYSFS_getLastErrorCode(). + */ +PHYSFS_DECL int PHYSFS_writeSBE16(PHYSFS_File *file, PHYSFS_sint16 val); + + +/** + * \fn int PHYSFS_writeUBE16(PHYSFS_File *file, PHYSFS_uint16 val) + * \brief Convert and write an unsigned 16-bit bigendian value. + * + * Convenience function. Convert an unsigned 16-bit value from the platform's + * native byte order to bigendian and write it to a file. + * + * \param file PhysicsFS file handle to which to write. + * \param val Value to convert and write. + * \return zero on failure, non-zero on success. On failure, you can + * find out what went wrong from PHYSFS_getLastErrorCode(). + */ +PHYSFS_DECL int PHYSFS_writeUBE16(PHYSFS_File *file, PHYSFS_uint16 val); + + +/** + * \fn int PHYSFS_writeSLE32(PHYSFS_File *file, PHYSFS_sint32 val) + * \brief Convert and write a signed 32-bit littleendian value. + * + * Convenience function. Convert a signed 32-bit value from the platform's + * native byte order to littleendian and write it to a file. + * + * \param file PhysicsFS file handle to which to write. + * \param val Value to convert and write. + * \return zero on failure, non-zero on success. On failure, you can + * find out what went wrong from PHYSFS_getLastErrorCode(). + */ +PHYSFS_DECL int PHYSFS_writeSLE32(PHYSFS_File *file, PHYSFS_sint32 val); + + +/** + * \fn int PHYSFS_writeULE32(PHYSFS_File *file, PHYSFS_uint32 val) + * \brief Convert and write an unsigned 32-bit littleendian value. + * + * Convenience function. Convert an unsigned 32-bit value from the platform's + * native byte order to littleendian and write it to a file. + * + * \param file PhysicsFS file handle to which to write. + * \param val Value to convert and write. + * \return zero on failure, non-zero on success. On failure, you can + * find out what went wrong from PHYSFS_getLastErrorCode(). + */ +PHYSFS_DECL int PHYSFS_writeULE32(PHYSFS_File *file, PHYSFS_uint32 val); + + +/** + * \fn int PHYSFS_writeSBE32(PHYSFS_File *file, PHYSFS_sint32 val) + * \brief Convert and write a signed 32-bit bigendian value. + * + * Convenience function. Convert a signed 32-bit value from the platform's + * native byte order to bigendian and write it to a file. + * + * \param file PhysicsFS file handle to which to write. + * \param val Value to convert and write. + * \return zero on failure, non-zero on success. On failure, you can + * find out what went wrong from PHYSFS_getLastErrorCode(). + */ +PHYSFS_DECL int PHYSFS_writeSBE32(PHYSFS_File *file, PHYSFS_sint32 val); + + +/** + * \fn int PHYSFS_writeUBE32(PHYSFS_File *file, PHYSFS_uint32 val) + * \brief Convert and write an unsigned 32-bit bigendian value. + * + * Convenience function. Convert an unsigned 32-bit value from the platform's + * native byte order to bigendian and write it to a file. + * + * \param file PhysicsFS file handle to which to write. + * \param val Value to convert and write. + * \return zero on failure, non-zero on success. On failure, you can + * find out what went wrong from PHYSFS_getLastErrorCode(). + */ +PHYSFS_DECL int PHYSFS_writeUBE32(PHYSFS_File *file, PHYSFS_uint32 val); + + +/** + * \fn int PHYSFS_writeSLE64(PHYSFS_File *file, PHYSFS_sint64 val) + * \brief Convert and write a signed 64-bit littleendian value. + * + * Convenience function. Convert a signed 64-bit value from the platform's + * native byte order to littleendian and write it to a file. + * + * \param file PhysicsFS file handle to which to write. + * \param val Value to convert and write. + * \return zero on failure, non-zero on success. On failure, you can + * find out what went wrong from PHYSFS_getLastErrorCode(). + * + * \warning Remember, PHYSFS_sint64 is only 32 bits on platforms without + * any sort of 64-bit support. + */ +PHYSFS_DECL int PHYSFS_writeSLE64(PHYSFS_File *file, PHYSFS_sint64 val); + + +/** + * \fn int PHYSFS_writeULE64(PHYSFS_File *file, PHYSFS_uint64 val) + * \brief Convert and write an unsigned 64-bit littleendian value. + * + * Convenience function. Convert an unsigned 64-bit value from the platform's + * native byte order to littleendian and write it to a file. + * + * \param file PhysicsFS file handle to which to write. + * \param val Value to convert and write. + * \return zero on failure, non-zero on success. On failure, you can + * find out what went wrong from PHYSFS_getLastErrorCode(). + * + * \warning Remember, PHYSFS_uint64 is only 32 bits on platforms without + * any sort of 64-bit support. + */ +PHYSFS_DECL int PHYSFS_writeULE64(PHYSFS_File *file, PHYSFS_uint64 val); + + +/** + * \fn int PHYSFS_writeSBE64(PHYSFS_File *file, PHYSFS_sint64 val) + * \brief Convert and write a signed 64-bit bigending value. + * + * Convenience function. Convert a signed 64-bit value from the platform's + * native byte order to bigendian and write it to a file. + * + * \param file PhysicsFS file handle to which to write. + * \param val Value to convert and write. + * \return zero on failure, non-zero on success. On failure, you can + * find out what went wrong from PHYSFS_getLastErrorCode(). + * + * \warning Remember, PHYSFS_sint64 is only 32 bits on platforms without + * any sort of 64-bit support. + */ +PHYSFS_DECL int PHYSFS_writeSBE64(PHYSFS_File *file, PHYSFS_sint64 val); + + +/** + * \fn int PHYSFS_writeUBE64(PHYSFS_File *file, PHYSFS_uint64 val) + * \brief Convert and write an unsigned 64-bit bigendian value. + * + * Convenience function. Convert an unsigned 64-bit value from the platform's + * native byte order to bigendian and write it to a file. + * + * \param file PhysicsFS file handle to which to write. + * \param val Value to convert and write. + * \return zero on failure, non-zero on success. On failure, you can + * find out what went wrong from PHYSFS_getLastErrorCode(). + * + * \warning Remember, PHYSFS_uint64 is only 32 bits on platforms without + * any sort of 64-bit support. + */ +PHYSFS_DECL int PHYSFS_writeUBE64(PHYSFS_File *file, PHYSFS_uint64 val); + + +/* Everything above this line is part of the PhysicsFS 1.0 API. */ + +/** + * \fn int PHYSFS_isInit(void) + * \brief Determine if the PhysicsFS library is initialized. + * + * Once PHYSFS_init() returns successfully, this will return non-zero. + * Before a successful PHYSFS_init() and after PHYSFS_deinit() returns + * successfully, this will return zero. This function is safe to call at + * any time. + * + * \return non-zero if library is initialized, zero if library is not. + * + * \sa PHYSFS_init + * \sa PHYSFS_deinit + */ +PHYSFS_DECL int PHYSFS_isInit(void); + + +/** + * \fn int PHYSFS_symbolicLinksPermitted(void) + * \brief Determine if the symbolic links are permitted. + * + * This reports the setting from the last call to PHYSFS_permitSymbolicLinks(). + * If PHYSFS_permitSymbolicLinks() hasn't been called since the library was + * last initialized, symbolic links are implicitly disabled. + * + * \return non-zero if symlinks are permitted, zero if not. + * + * \sa PHYSFS_permitSymbolicLinks + */ +PHYSFS_DECL int PHYSFS_symbolicLinksPermitted(void); + + +/** + * \struct PHYSFS_Allocator + * \brief PhysicsFS allocation function pointers. + * + * (This is for limited, hardcore use. If you don't immediately see a need + * for it, you can probably ignore this forever.) + * + * You create one of these structures for use with PHYSFS_setAllocator. + * Allocators are assumed to be reentrant by the caller; please mutex + * accordingly. + * + * Allocations are always discussed in 64-bits, for future expansion...we're + * on the cusp of a 64-bit transition, and we'll probably be allocating 6 + * gigabytes like it's nothing sooner or later, and I don't want to change + * this again at that point. If you're on a 32-bit platform and have to + * downcast, it's okay to return NULL if the allocation is greater than + * 4 gigabytes, since you'd have to do so anyhow. + * + * \sa PHYSFS_setAllocator + */ +typedef struct PHYSFS_Allocator +{ + int (*Init)(void); /**< Initialize. Can be NULL. Zero on failure. */ + void (*Deinit)(void); /**< Deinitialize your allocator. Can be NULL. */ + void *(*Malloc)(PHYSFS_uint64); /**< Allocate like malloc(). */ + void *(*Realloc)(void *, PHYSFS_uint64); /**< Reallocate like realloc(). */ + void (*Free)(void *); /**< Free memory from Malloc or Realloc. */ +} PHYSFS_Allocator; + + +/** + * \fn int PHYSFS_setAllocator(const PHYSFS_Allocator *allocator) + * \brief Hook your own allocation routines into PhysicsFS. + * + * (This is for limited, hardcore use. If you don't immediately see a need + * for it, you can probably ignore this forever.) + * + * By default, PhysicsFS will use whatever is reasonable for a platform + * to manage dynamic memory (usually ANSI C malloc/realloc/free, but + * some platforms might use something else), but in some uncommon cases, the + * app might want more control over the library's memory management. This + * lets you redirect PhysicsFS to use your own allocation routines instead. + * You can only call this function before PHYSFS_init(); if the library is + * initialized, it'll reject your efforts to change the allocator mid-stream. + * You may call this function after PHYSFS_deinit() if you are willing to + * shut down the library and restart it with a new allocator; this is a safe + * and supported operation. The allocator remains intact between deinit/init + * calls. If you want to return to the platform's default allocator, pass a + * NULL in here. + * + * If you aren't immediately sure what to do with this function, you can + * safely ignore it altogether. + * + * \param allocator Structure containing your allocator's entry points. + * \return zero on failure, non-zero on success. This call only fails + * when used between PHYSFS_init() and PHYSFS_deinit() calls. + */ +PHYSFS_DECL int PHYSFS_setAllocator(const PHYSFS_Allocator *allocator); + + +/** + * \fn int PHYSFS_mount(const char *newDir, const char *mountPoint, int appendToPath) + * \brief Add an archive or directory to the search path. + * + * If this is a duplicate, the entry is not added again, even though the + * function succeeds. You may not add the same archive to two different + * mountpoints: duplicate checking is done against the archive and not the + * mountpoint. + * + * When you mount an archive, it is added to a virtual file system...all files + * in all of the archives are interpolated into a single hierachical file + * tree. Two archives mounted at the same place (or an archive with files + * overlapping another mountpoint) may have overlapping files: in such a case, + * the file earliest in the search path is selected, and the other files are + * inaccessible to the application. This allows archives to be used to + * override previous revisions; you can use the mounting mechanism to place + * archives at a specific point in the file tree and prevent overlap; this + * is useful for downloadable mods that might trample over application data + * or each other, for example. + * + * The mountpoint does not need to exist prior to mounting, which is different + * than those familiar with the Unix concept of "mounting" may expect. + * As well, more than one archive can be mounted to the same mountpoint, or + * mountpoints and archive contents can overlap...the interpolation mechanism + * still functions as usual. + * + * Specifying a symbolic link to an archive or directory is allowed here, + * regardless of the state of PHYSFS_permitSymbolicLinks(). That function + * only deals with symlinks inside the mounted directory or archive. + * + * \param newDir directory or archive to add to the path, in + * platform-dependent notation. + * \param mountPoint Location in the interpolated tree that this archive + * will be "mounted", in platform-independent notation. + * NULL or "" is equivalent to "/". + * \param appendToPath nonzero to append to search path, zero to prepend. + * \return nonzero if added to path, zero on failure (bogus archive, dir + * missing, etc). Use PHYSFS_getLastErrorCode() to obtain + * the specific error. + * + * \sa PHYSFS_removeFromSearchPath + * \sa PHYSFS_getSearchPath + * \sa PHYSFS_getMountPoint + * \sa PHYSFS_mountIo + */ +PHYSFS_DECL int PHYSFS_mount(const char *newDir, + const char *mountPoint, + int appendToPath); + +/** + * \fn int PHYSFS_getMountPoint(const char *dir) + * \brief Determine a mounted archive's mountpoint. + * + * You give this function the name of an archive or dir you successfully + * added to the search path, and it reports the location in the interpolated + * tree where it is mounted. Files mounted with a NULL mountpoint or through + * PHYSFS_addToSearchPath() will report "/". The return value is READ ONLY + * and valid until the archive is removed from the search path. + * + * \param dir directory or archive previously added to the path, in + * platform-dependent notation. This must match the string + * used when adding, even if your string would also reference + * the same file with a different string of characters. + * \return READ-ONLY string of mount point if added to path, NULL on failure + * (bogus archive, etc). Use PHYSFS_getLastErrorCode() to obtain the + * specific error. + * + * \sa PHYSFS_removeFromSearchPath + * \sa PHYSFS_getSearchPath + * \sa PHYSFS_getMountPoint + */ +PHYSFS_DECL const char *PHYSFS_getMountPoint(const char *dir); + + +/** + * \typedef PHYSFS_StringCallback + * \brief Function signature for callbacks that report strings. + * + * These are used to report a list of strings to an original caller, one + * string per callback. All strings are UTF-8 encoded. Functions should not + * try to modify or free the string's memory. + * + * These callbacks are used, starting in PhysicsFS 1.1, as an alternative to + * functions that would return lists that need to be cleaned up with + * PHYSFS_freeList(). The callback means that the library doesn't need to + * allocate an entire list and all the strings up front. + * + * Be aware that promises data ordering in the list versions are not + * necessarily so in the callback versions. Check the documentation on + * specific APIs, but strings may not be sorted as you expect. + * + * \param data User-defined data pointer, passed through from the API + * that eventually called the callback. + * \param str The string data about which the callback is meant to inform. + * + * \sa PHYSFS_getCdRomDirsCallback + * \sa PHYSFS_getSearchPathCallback + */ +typedef void (*PHYSFS_StringCallback)(void *data, const char *str); + + +/** + * \typedef PHYSFS_EnumFilesCallback + * \brief Function signature for callbacks that enumerate files. + * + * \warning As of PhysicsFS 2.1, Use PHYSFS_EnumerateCallback with + * PHYSFS_enumerate() instead; it gives you more control over the process. + * + * These are used to report a list of directory entries to an original caller, + * one file/dir/symlink per callback. All strings are UTF-8 encoded. + * Functions should not try to modify or free any string's memory. + * + * These callbacks are used, starting in PhysicsFS 1.1, as an alternative to + * functions that would return lists that need to be cleaned up with + * PHYSFS_freeList(). The callback means that the library doesn't need to + * allocate an entire list and all the strings up front. + * + * Be aware that promised data ordering in the list versions are not + * necessarily so in the callback versions. Check the documentation on + * specific APIs, but strings may not be sorted as you expect and you might + * get duplicate strings. + * + * \param data User-defined data pointer, passed through from the API + * that eventually called the callback. + * \param origdir A string containing the full path, in platform-independent + * notation, of the directory containing this file. In most + * cases, this is the directory on which you requested + * enumeration, passed in the callback for your convenience. + * \param fname The filename that is being enumerated. It may not be in + * alphabetical order compared to other callbacks that have + * fired, and it will not contain the full path. You can + * recreate the fullpath with $origdir/$fname ... The file + * can be a subdirectory, a file, a symlink, etc. + * + * \sa PHYSFS_enumerateFilesCallback + */ +typedef void (*PHYSFS_EnumFilesCallback)(void *data, const char *origdir, + const char *fname); + + +/** + * \fn void PHYSFS_getCdRomDirsCallback(PHYSFS_StringCallback c, void *d) + * \brief Enumerate CD-ROM directories, using an application-defined callback. + * + * Internally, PHYSFS_getCdRomDirs() just calls this function and then builds + * a list before returning to the application, so functionality is identical + * except for how the information is represented to the application. + * + * Unlike PHYSFS_getCdRomDirs(), this function does not return an array. + * Rather, it calls a function specified by the application once per + * detected disc: + * + * \code + * + * static void foundDisc(void *data, const char *cddir) + * { + * printf("cdrom dir [%s] is available.\n", cddir); + * } + * + * // ... + * PHYSFS_getCdRomDirsCallback(foundDisc, NULL); + * \endcode + * + * This call may block while drives spin up. Be forewarned. + * + * \param c Callback function to notify about detected drives. + * \param d Application-defined data passed to callback. Can be NULL. + * + * \sa PHYSFS_StringCallback + * \sa PHYSFS_getCdRomDirs + */ +PHYSFS_DECL void PHYSFS_getCdRomDirsCallback(PHYSFS_StringCallback c, void *d); + + +/** + * \fn void PHYSFS_getSearchPathCallback(PHYSFS_StringCallback c, void *d) + * \brief Enumerate the search path, using an application-defined callback. + * + * Internally, PHYSFS_getSearchPath() just calls this function and then builds + * a list before returning to the application, so functionality is identical + * except for how the information is represented to the application. + * + * Unlike PHYSFS_getSearchPath(), this function does not return an array. + * Rather, it calls a function specified by the application once per + * element of the search path: + * + * \code + * + * static void printSearchPath(void *data, const char *pathItem) + * { + * printf("[%s] is in the search path.\n", pathItem); + * } + * + * // ... + * PHYSFS_getSearchPathCallback(printSearchPath, NULL); + * \endcode + * + * Elements of the search path are reported in order search priority, so the + * first archive/dir that would be examined when looking for a file is the + * first element passed through the callback. + * + * \param c Callback function to notify about search path elements. + * \param d Application-defined data passed to callback. Can be NULL. + * + * \sa PHYSFS_StringCallback + * \sa PHYSFS_getSearchPath + */ +PHYSFS_DECL void PHYSFS_getSearchPathCallback(PHYSFS_StringCallback c, void *d); + + +/** + * \fn void PHYSFS_enumerateFilesCallback(const char *dir, PHYSFS_EnumFilesCallback c, void *d) + * \brief Get a file listing of a search path's directory, using an application-defined callback. + * + * \deprecated As of PhysicsFS 2.1, use PHYSFS_enumerate() instead. This + * function has no way to report errors (or to have the callback signal an + * error or request a stop), so if data will be lost, your callback has no + * way to direct the process, and your calling app has no way to know. + * + * As of PhysicsFS 2.1, this function just wraps PHYSFS_enumerate() and + * ignores errors. Consider using PHYSFS_enumerate() or + * PHYSFS_enumerateFiles() instead. + * + * \sa PHYSFS_enumerate + * \sa PHYSFS_enumerateFiles + * \sa PHYSFS_EnumFilesCallback + */ +PHYSFS_DECL void PHYSFS_enumerateFilesCallback(const char *dir, + PHYSFS_EnumFilesCallback c, + void *d) PHYSFS_DEPRECATED; + +/** + * \fn void PHYSFS_utf8FromUcs4(const PHYSFS_uint32 *src, char *dst, PHYSFS_uint64 len) + * \brief Convert a UCS-4 string to a UTF-8 string. + * + * \warning This function will not report an error if there are invalid UCS-4 + * values in the source string. It will replace them with a '?' + * character and continue on. + * + * UCS-4 (aka UTF-32) strings are 32-bits per character: \c wchar_t on Unix. + * + * To ensure that the destination buffer is large enough for the conversion, + * please allocate a buffer that is the same size as the source buffer. UTF-8 + * never uses more than 32-bits per character, so while it may shrink a UCS-4 + * string, it will never expand it. + * + * Strings that don't fit in the destination buffer will be truncated, but + * will always be null-terminated and never have an incomplete UTF-8 + * sequence at the end. If the buffer length is 0, this function does nothing. + * + * \param src Null-terminated source string in UCS-4 format. + * \param dst Buffer to store converted UTF-8 string. + * \param len Size, in bytes, of destination buffer. + */ +PHYSFS_DECL void PHYSFS_utf8FromUcs4(const PHYSFS_uint32 *src, char *dst, + PHYSFS_uint64 len); + +/** + * \fn void PHYSFS_utf8ToUcs4(const char *src, PHYSFS_uint32 *dst, PHYSFS_uint64 len) + * \brief Convert a UTF-8 string to a UCS-4 string. + * + * \warning This function will not report an error if there are invalid UTF-8 + * sequences in the source string. It will replace them with a '?' + * character and continue on. + * + * UCS-4 (aka UTF-32) strings are 32-bits per character: \c wchar_t on Unix. + * + * To ensure that the destination buffer is large enough for the conversion, + * please allocate a buffer that is four times the size of the source buffer. + * UTF-8 uses from one to four bytes per character, but UCS-4 always uses + * four, so an entirely low-ASCII string will quadruple in size! + * + * Strings that don't fit in the destination buffer will be truncated, but + * will always be null-terminated and never have an incomplete UCS-4 + * sequence at the end. If the buffer length is 0, this function does nothing. + * + * \param src Null-terminated source string in UTF-8 format. + * \param dst Buffer to store converted UCS-4 string. + * \param len Size, in bytes, of destination buffer. + */ +PHYSFS_DECL void PHYSFS_utf8ToUcs4(const char *src, PHYSFS_uint32 *dst, + PHYSFS_uint64 len); + +/** + * \fn void PHYSFS_utf8FromUcs2(const PHYSFS_uint16 *src, char *dst, PHYSFS_uint64 len) + * \brief Convert a UCS-2 string to a UTF-8 string. + * + * \warning you almost certainly should use PHYSFS_utf8FromUtf16(), which + * became available in PhysicsFS 2.1, unless you know what you're doing. + * + * \warning This function will not report an error if there are invalid UCS-2 + * values in the source string. It will replace them with a '?' + * character and continue on. + * + * UCS-2 strings are 16-bits per character: \c TCHAR on Windows, when building + * with Unicode support. Please note that modern versions of Windows use + * UTF-16, which is an extended form of UCS-2, and not UCS-2 itself. You + * almost certainly want PHYSFS_utf8FromUtf16() instead. + * + * To ensure that the destination buffer is large enough for the conversion, + * please allocate a buffer that is double the size of the source buffer. + * UTF-8 never uses more than 32-bits per character, so while it may shrink + * a UCS-2 string, it may also expand it. + * + * Strings that don't fit in the destination buffer will be truncated, but + * will always be null-terminated and never have an incomplete UTF-8 + * sequence at the end. If the buffer length is 0, this function does nothing. + * + * \param src Null-terminated source string in UCS-2 format. + * \param dst Buffer to store converted UTF-8 string. + * \param len Size, in bytes, of destination buffer. + * + * \sa PHYSFS_utf8FromUtf16 + */ +PHYSFS_DECL void PHYSFS_utf8FromUcs2(const PHYSFS_uint16 *src, char *dst, + PHYSFS_uint64 len); + +/** + * \fn PHYSFS_utf8ToUcs2(const char *src, PHYSFS_uint16 *dst, PHYSFS_uint64 len) + * \brief Convert a UTF-8 string to a UCS-2 string. + * + * \warning you almost certainly should use PHYSFS_utf8ToUtf16(), which + * became available in PhysicsFS 2.1, unless you know what you're doing. + * + * \warning This function will not report an error if there are invalid UTF-8 + * sequences in the source string. It will replace them with a '?' + * character and continue on. + * + * UCS-2 strings are 16-bits per character: \c TCHAR on Windows, when building + * with Unicode support. Please note that modern versions of Windows use + * UTF-16, which is an extended form of UCS-2, and not UCS-2 itself. You + * almost certainly want PHYSFS_utf8ToUtf16() instead, but you need to + * understand how that changes things, too. + * + * To ensure that the destination buffer is large enough for the conversion, + * please allocate a buffer that is double the size of the source buffer. + * UTF-8 uses from one to four bytes per character, but UCS-2 always uses + * two, so an entirely low-ASCII string will double in size! + * + * Strings that don't fit in the destination buffer will be truncated, but + * will always be null-terminated and never have an incomplete UCS-2 + * sequence at the end. If the buffer length is 0, this function does nothing. + * + * \param src Null-terminated source string in UTF-8 format. + * \param dst Buffer to store converted UCS-2 string. + * \param len Size, in bytes, of destination buffer. + * + * \sa PHYSFS_utf8ToUtf16 + */ +PHYSFS_DECL void PHYSFS_utf8ToUcs2(const char *src, PHYSFS_uint16 *dst, + PHYSFS_uint64 len); + +/** + * \fn void PHYSFS_utf8FromLatin1(const char *src, char *dst, PHYSFS_uint64 len) + * \brief Convert a UTF-8 string to a Latin1 string. + * + * Latin1 strings are 8-bits per character: a popular "high ASCII" encoding. + * + * To ensure that the destination buffer is large enough for the conversion, + * please allocate a buffer that is double the size of the source buffer. + * UTF-8 expands latin1 codepoints over 127 from 1 to 2 bytes, so the string + * may grow in some cases. + * + * Strings that don't fit in the destination buffer will be truncated, but + * will always be null-terminated and never have an incomplete UTF-8 + * sequence at the end. If the buffer length is 0, this function does nothing. + * + * Please note that we do not supply a UTF-8 to Latin1 converter, since Latin1 + * can't express most Unicode codepoints. It's a legacy encoding; you should + * be converting away from it at all times. + * + * \param src Null-terminated source string in Latin1 format. + * \param dst Buffer to store converted UTF-8 string. + * \param len Size, in bytes, of destination buffer. + */ +PHYSFS_DECL void PHYSFS_utf8FromLatin1(const char *src, char *dst, + PHYSFS_uint64 len); + +/* Everything above this line is part of the PhysicsFS 2.0 API. */ + +/** + * \fn int PHYSFS_caseFold(const PHYSFS_uint32 from, PHYSFS_uint32 *to) + * \brief "Fold" a Unicode codepoint to a lowercase equivalent. + * + * (This is for limited, hardcore use. If you don't immediately see a need + * for it, you can probably ignore this forever.) + * + * This will convert a Unicode codepoint into its lowercase equivalent. + * Bogus codepoints and codepoints without a lowercase equivalent will + * be returned unconverted. + * + * Note that you might get multiple codepoints in return! The German Eszett, + * for example, will fold down to two lowercase latin 's' codepoints. The + * theory is that if you fold two strings, one with an Eszett and one with + * "SS" down, they will match. + * + * \warning Anyone that is a student of Unicode knows about the "Turkish I" + * problem. This API does not handle it. Assume this one letter + * in all of Unicode will definitely fold sort of incorrectly. If + * you don't know what this is about, you can probably ignore this + * problem for most of the planet, but perfection is impossible. + * + * \param from The codepoint to fold. + * \param to Buffer to store the folded codepoint values into. This should + * point to space for at least 3 PHYSFS_uint32 slots. + * \return The number of codepoints the folding produced. Between 1 and 3. + */ +PHYSFS_DECL int PHYSFS_caseFold(const PHYSFS_uint32 from, PHYSFS_uint32 *to); + + +/** + * \fn int PHYSFS_utf8stricmp(const char *str1, const char *str2) + * \brief Case-insensitive compare of two UTF-8 strings. + * + * This is a strcasecmp/stricmp replacement that expects both strings + * to be in UTF-8 encoding. It will do "case folding" to decide if the + * Unicode codepoints in the strings match. + * + * If both strings are exclusively low-ASCII characters, this will do the + * right thing, as that is also valid UTF-8. If there are any high-ASCII + * chars, this will not do what you expect! + * + * It will report which string is "greater than" the other, but be aware that + * this doesn't necessarily mean anything: 'a' may be "less than" 'b', but + * a Japanese kuten has no meaningful alphabetically relationship to + * a Greek lambda, but being able to assign a reliable "value" makes sorting + * algorithms possible, if not entirely sane. Most cases should treat the + * return value as "equal" or "not equal". + * + * Like stricmp, this expects both strings to be NULL-terminated. + * + * \param str1 First string to compare. + * \param str2 Second string to compare. + * \return -1 if str1 is "less than" str2, 1 if "greater than", 0 if equal. + */ +PHYSFS_DECL int PHYSFS_utf8stricmp(const char *str1, const char *str2); + +/** + * \fn int PHYSFS_utf16stricmp(const PHYSFS_uint16 *str1, const PHYSFS_uint16 *str2) + * \brief Case-insensitive compare of two UTF-16 strings. + * + * This is a strcasecmp/stricmp replacement that expects both strings + * to be in UTF-16 encoding. It will do "case folding" to decide if the + * Unicode codepoints in the strings match. + * + * It will report which string is "greater than" the other, but be aware that + * this doesn't necessarily mean anything: 'a' may be "less than" 'b', but + * a Japanese kuten has no meaningful alphabetically relationship to + * a Greek lambda, but being able to assign a reliable "value" makes sorting + * algorithms possible, if not entirely sane. Most cases should treat the + * return value as "equal" or "not equal". + * + * Like stricmp, this expects both strings to be NULL-terminated. + * + * \param str1 First string to compare. + * \param str2 Second string to compare. + * \return -1 if str1 is "less than" str2, 1 if "greater than", 0 if equal. + */ +PHYSFS_DECL int PHYSFS_utf16stricmp(const PHYSFS_uint16 *str1, + const PHYSFS_uint16 *str2); + +/** + * \fn int PHYSFS_ucs4stricmp(const PHYSFS_uint32 *str1, const PHYSFS_uint32 *str2) + * \brief Case-insensitive compare of two UCS-4 strings. + * + * This is a strcasecmp/stricmp replacement that expects both strings + * to be in UCS-4 (aka UTF-32) encoding. It will do "case folding" to decide + * if the Unicode codepoints in the strings match. + * + * It will report which string is "greater than" the other, but be aware that + * this doesn't necessarily mean anything: 'a' may be "less than" 'b', but + * a Japanese kuten has no meaningful alphabetically relationship to + * a Greek lambda, but being able to assign a reliable "value" makes sorting + * algorithms possible, if not entirely sane. Most cases should treat the + * return value as "equal" or "not equal". + * + * Like stricmp, this expects both strings to be NULL-terminated. + * + * \param str1 First string to compare. + * \param str2 Second string to compare. + * \return -1 if str1 is "less than" str2, 1 if "greater than", 0 if equal. + */ +PHYSFS_DECL int PHYSFS_ucs4stricmp(const PHYSFS_uint32 *str1, + const PHYSFS_uint32 *str2); + + +/** + * \typedef PHYSFS_EnumerateCallback + * \brief Possible return values from PHYSFS_EnumerateCallback. + * + * These values dictate if an enumeration callback should continue to fire, + * or stop (and why it is stopping). + * + * \sa PHYSFS_EnumerateCallback + * \sa PHYSFS_enumerate + */ +typedef enum PHYSFS_EnumerateCallbackResult +{ + PHYSFS_ENUM_ERROR = -1, /**< Stop enumerating, report error to app. */ + PHYSFS_ENUM_STOP = 0, /**< Stop enumerating, report success to app. */ + PHYSFS_ENUM_OK = 1 /**< Keep enumerating, no problems */ +} PHYSFS_EnumerateCallbackResult; + +/** + * \typedef PHYSFS_EnumerateCallback + * \brief Function signature for callbacks that enumerate and return results. + * + * This is the same thing as PHYSFS_EnumFilesCallback from PhysicsFS 2.0, + * except it can return a result from the callback: namely: if you're looking + * for something specific, once you find it, you can tell PhysicsFS to stop + * enumerating further. This is used with PHYSFS_enumerate(), which we + * hopefully got right this time. :) + * + * \param data User-defined data pointer, passed through from the API + * that eventually called the callback. + * \param origdir A string containing the full path, in platform-independent + * notation, of the directory containing this file. In most + * cases, this is the directory on which you requested + * enumeration, passed in the callback for your convenience. + * \param fname The filename that is being enumerated. It may not be in + * alphabetical order compared to other callbacks that have + * fired, and it will not contain the full path. You can + * recreate the fullpath with $origdir/$fname ... The file + * can be a subdirectory, a file, a symlink, etc. + * \return A value from PHYSFS_EnumerateCallbackResult. + * All other values are (currently) undefined; don't use them. + * + * \sa PHYSFS_enumerate + * \sa PHYSFS_EnumerateCallbackResult + */ +typedef PHYSFS_EnumerateCallbackResult (*PHYSFS_EnumerateCallback)(void *data, + const char *origdir, const char *fname); + +/** + * \fn int PHYSFS_enumerate(const char *dir, PHYSFS_EnumerateCallback c, void *d) + * \brief Get a file listing of a search path's directory, using an application-defined callback, with errors reported. + * + * Internally, PHYSFS_enumerateFiles() just calls this function and then builds + * a list before returning to the application, so functionality is identical + * except for how the information is represented to the application. + * + * Unlike PHYSFS_enumerateFiles(), this function does not return an array. + * Rather, it calls a function specified by the application once per + * element of the search path: + * + * \code + * + * static int printDir(void *data, const char *origdir, const char *fname) + * { + * printf(" * We've got [%s] in [%s].\n", fname, origdir); + * return 1; // give me more data, please. + * } + * + * // ... + * PHYSFS_enumerate("/some/path", printDir, NULL); + * \endcode + * + * Items sent to the callback are not guaranteed to be in any order whatsoever. + * There is no sorting done at this level, and if you need that, you should + * probably use PHYSFS_enumerateFiles() instead, which guarantees + * alphabetical sorting. This form reports whatever is discovered in each + * archive before moving on to the next. Even within one archive, we can't + * guarantee what order it will discover data. Any sorting you find in + * these callbacks is just pure luck. Do not rely on it. As this walks + * the entire list of archives, you may receive duplicate filenames. + * + * This API and the callbacks themselves are capable of reporting errors. + * Prior to this API, callbacks had to accept every enumerated item, even if + * they were only looking for a specific thing and wanted to stop after that, + * or had a serious error and couldn't alert anyone. Furthermore, if + * PhysicsFS itself had a problem (disk error or whatnot), it couldn't report + * it to the calling app, it would just have to skip items or stop + * enumerating outright, and the caller wouldn't know it had lost some data + * along the way. + * + * Now the caller can be sure it got a complete data set, and its callback has + * control if it wants enumeration to stop early. See the documentation for + * PHYSFS_EnumerateCallback for details on how your callback should behave. + * + * \param dir Directory, in platform-independent notation, to enumerate. + * \param c Callback function to notify about search path elements. + * \param d Application-defined data passed to callback. Can be NULL. + * \return non-zero on success, zero on failure. Use + * PHYSFS_getLastErrorCode() to obtain the specific error. If the + * callback returns PHYSFS_ENUM_STOP to stop early, this will be + * considered success. Callbacks returning PHYSFS_ENUM_ERROR will + * make this function return zero and set the error code to + * PHYSFS_ERR_APP_CALLBACK. + * + * \sa PHYSFS_EnumerateCallback + * \sa PHYSFS_enumerateFiles + */ +PHYSFS_DECL int PHYSFS_enumerate(const char *dir, PHYSFS_EnumerateCallback c, + void *d); + + +/** + * \fn int PHYSFS_unmount(const char *oldDir) + * \brief Remove a directory or archive from the search path. + * + * This is functionally equivalent to PHYSFS_removeFromSearchPath(), but that + * function is deprecated to keep the vocabulary paired with PHYSFS_mount(). + * + * This must be a (case-sensitive) match to a dir or archive already in the + * search path, specified in platform-dependent notation. + * + * This call will fail (and fail to remove from the path) if the element still + * has files open in it. + * + * \warning This function wants the path to the archive or directory that was + * mounted (the same string used for the "newDir" argument of + * PHYSFS_addToSearchPath or any of the mount functions), not the + * path where it is mounted in the tree (the "mountPoint" argument + * to any of the mount functions). + * + * \param oldDir dir/archive to remove. + * \return nonzero on success, zero on failure. Use + * PHYSFS_getLastErrorCode() to obtain the specific error. + * + * \sa PHYSFS_getSearchPath + * \sa PHYSFS_mount + */ +PHYSFS_DECL int PHYSFS_unmount(const char *oldDir); + + +/** + * \fn const PHYSFS_Allocator *PHYSFS_getAllocator(void) + * \brief Discover the current allocator. + * + * (This is for limited, hardcore use. If you don't immediately see a need + * for it, you can probably ignore this forever.) + * + * This function exposes the function pointers that make up the currently used + * allocator. This can be useful for apps that want to access PhysicsFS's + * internal, default allocation routines, as well as for external code that + * wants to share the same allocator, even if the application specified their + * own. + * + * This call is only valid between PHYSFS_init() and PHYSFS_deinit() calls; + * it will return NULL if the library isn't initialized. As we can't + * guarantee the state of the internal allocators unless the library is + * initialized, you shouldn't use any allocator returned here after a call + * to PHYSFS_deinit(). + * + * Do not call the returned allocator's Init() or Deinit() methods under any + * circumstances. + * + * If you aren't immediately sure what to do with this function, you can + * safely ignore it altogether. + * + * \return Current allocator, as set by PHYSFS_setAllocator(), or PhysicsFS's + * internal, default allocator if no application defined allocator + * is currently set. Will return NULL if the library is not + * initialized. + * + * \sa PHYSFS_Allocator + * \sa PHYSFS_setAllocator + */ +PHYSFS_DECL const PHYSFS_Allocator *PHYSFS_getAllocator(void); + + +/** + * \enum PHYSFS_FileType + * \brief Type of a File + * + * Possible types of a file. + * + * \sa PHYSFS_stat + */ +typedef enum PHYSFS_FileType +{ + PHYSFS_FILETYPE_REGULAR, /**< a normal file */ + PHYSFS_FILETYPE_DIRECTORY, /**< a directory */ + PHYSFS_FILETYPE_SYMLINK, /**< a symlink */ + PHYSFS_FILETYPE_OTHER /**< something completely different like a device */ +} PHYSFS_FileType; + +/** + * \struct PHYSFS_Stat + * \brief Meta data for a file or directory + * + * Container for various meta data about a file in the virtual file system. + * PHYSFS_stat() uses this structure for returning the information. The time + * data will be either the number of seconds since the Unix epoch (midnight, + * Jan 1, 1970), or -1 if the information isn't available or applicable. + * The (filesize) field is measured in bytes. + * The (readonly) field tells you whether the archive thinks a file is + * not writable, but tends to be only an estimate (for example, your write + * dir might overlap with a .zip file, meaning you _can_ successfully open + * that path for writing, as it gets created elsewhere. + * + * \sa PHYSFS_stat + * \sa PHYSFS_FileType + */ +typedef struct PHYSFS_Stat +{ + PHYSFS_sint64 filesize; /**< size in bytes, -1 for non-files and unknown */ + PHYSFS_sint64 modtime; /**< last modification time */ + PHYSFS_sint64 createtime; /**< like modtime, but for file creation time */ + PHYSFS_sint64 accesstime; /**< like modtime, but for file access time */ + PHYSFS_FileType filetype; /**< File? Directory? Symlink? */ + int readonly; /**< non-zero if read only, zero if writable. */ +} PHYSFS_Stat; + +/** + * \fn int PHYSFS_stat(const char *fname, PHYSFS_Stat *stat) + * \brief Get various information about a directory or a file. + * + * Obtain various information about a file or directory from the meta data. + * + * This function will never follow symbolic links. If you haven't enabled + * symlinks with PHYSFS_permitSymbolicLinks(), stat'ing a symlink will be + * treated like stat'ing a non-existant file. If symlinks are enabled, + * stat'ing a symlink will give you information on the link itself and not + * what it points to. + * + * \param fname filename to check, in platform-indepedent notation. + * \param stat pointer to structure to fill in with data about (fname). + * \return non-zero on success, zero on failure. On failure, (stat)'s + * contents are undefined. + * + * \sa PHYSFS_Stat + */ +PHYSFS_DECL int PHYSFS_stat(const char *fname, PHYSFS_Stat *stat); + + +/** + * \fn void PHYSFS_utf8FromUtf16(const PHYSFS_uint16 *src, char *dst, PHYSFS_uint64 len) + * \brief Convert a UTF-16 string to a UTF-8 string. + * + * \warning This function will not report an error if there are invalid UTF-16 + * sequences in the source string. It will replace them with a '?' + * character and continue on. + * + * UTF-16 strings are 16-bits per character (except some chars, which are + * 32-bits): \c TCHAR on Windows, when building with Unicode support. Modern + * Windows releases use UTF-16. Windows releases before 2000 used TCHAR, but + * only handled UCS-2. UTF-16 _is_ UCS-2, except for the characters that + * are 4 bytes, which aren't representable in UCS-2 at all anyhow. If you + * aren't sure, you should be using UTF-16 at this point on Windows. + * + * To ensure that the destination buffer is large enough for the conversion, + * please allocate a buffer that is double the size of the source buffer. + * UTF-8 never uses more than 32-bits per character, so while it may shrink + * a UTF-16 string, it may also expand it. + * + * Strings that don't fit in the destination buffer will be truncated, but + * will always be null-terminated and never have an incomplete UTF-8 + * sequence at the end. If the buffer length is 0, this function does nothing. + * + * \param src Null-terminated source string in UTF-16 format. + * \param dst Buffer to store converted UTF-8 string. + * \param len Size, in bytes, of destination buffer. + */ +PHYSFS_DECL void PHYSFS_utf8FromUtf16(const PHYSFS_uint16 *src, char *dst, + PHYSFS_uint64 len); + +/** + * \fn PHYSFS_utf8ToUtf16(const char *src, PHYSFS_uint16 *dst, PHYSFS_uint64 len) + * \brief Convert a UTF-8 string to a UTF-16 string. + * + * \warning This function will not report an error if there are invalid UTF-8 + * sequences in the source string. It will replace them with a '?' + * character and continue on. + * + * UTF-16 strings are 16-bits per character (except some chars, which are + * 32-bits): \c TCHAR on Windows, when building with Unicode support. Modern + * Windows releases use UTF-16. Windows releases before 2000 used TCHAR, but + * only handled UCS-2. UTF-16 _is_ UCS-2, except for the characters that + * are 4 bytes, which aren't representable in UCS-2 at all anyhow. If you + * aren't sure, you should be using UTF-16 at this point on Windows. + * + * To ensure that the destination buffer is large enough for the conversion, + * please allocate a buffer that is double the size of the source buffer. + * UTF-8 uses from one to four bytes per character, but UTF-16 always uses + * two to four, so an entirely low-ASCII string will double in size! The + * UTF-16 characters that would take four bytes also take four bytes in UTF-8, + * so you don't need to allocate 4x the space just in case: double will do. + * + * Strings that don't fit in the destination buffer will be truncated, but + * will always be null-terminated and never have an incomplete UTF-16 + * surrogate pair at the end. If the buffer length is 0, this function does + * nothing. + * + * \param src Null-terminated source string in UTF-8 format. + * \param dst Buffer to store converted UTF-16 string. + * \param len Size, in bytes, of destination buffer. + * + * \sa PHYSFS_utf8ToUtf16 + */ +PHYSFS_DECL void PHYSFS_utf8ToUtf16(const char *src, PHYSFS_uint16 *dst, + PHYSFS_uint64 len); + + +/** + * \fn PHYSFS_sint64 PHYSFS_readBytes(PHYSFS_File *handle, void *buffer, PHYSFS_uint64 len) + * \brief Read bytes from a PhysicsFS filehandle + * + * The file must be opened for reading. + * + * \param handle handle returned from PHYSFS_openRead(). + * \param buffer buffer of at least (len) bytes to store read data into. + * \param len number of bytes being read from (handle). + * \return number of bytes read. This may be less than (len); this does not + * signify an error, necessarily (a short read may mean EOF). + * PHYSFS_getLastErrorCode() can shed light on the reason this might + * be < (len), as can PHYSFS_eof(). -1 if complete failure. + * + * \sa PHYSFS_eof + */ +PHYSFS_DECL PHYSFS_sint64 PHYSFS_readBytes(PHYSFS_File *handle, void *buffer, + PHYSFS_uint64 len); + +/** + * \fn PHYSFS_sint64 PHYSFS_writeBytes(PHYSFS_File *handle, const void *buffer, PHYSFS_uint64 len) + * \brief Write data to a PhysicsFS filehandle + * + * The file must be opened for writing. + * + * Please note that while (len) is an unsigned 64-bit integer, you are limited + * to 63 bits (9223372036854775807 bytes), so we can return a negative value + * on error. If length is greater than 0x7FFFFFFFFFFFFFFF, this function will + * immediately fail. For systems without a 64-bit datatype, you are limited + * to 31 bits (0x7FFFFFFF, or 2147483647 bytes). We trust most things won't + * need to do multiple gigabytes of i/o in one call anyhow, but why limit + * things? + * + * \param handle retval from PHYSFS_openWrite() or PHYSFS_openAppend(). + * \param buffer buffer of (len) bytes to write to (handle). + * \param len number of bytes being written to (handle). + * \return number of bytes written. This may be less than (len); in the case + * of an error, the system may try to write as many bytes as possible, + * so an incomplete write might occur. PHYSFS_getLastErrorCode() can + * shed light on the reason this might be < (len). -1 if complete + * failure. + */ +PHYSFS_DECL PHYSFS_sint64 PHYSFS_writeBytes(PHYSFS_File *handle, + const void *buffer, + PHYSFS_uint64 len); + + +/** + * \struct PHYSFS_Io + * \brief An abstract i/o interface. + * + * \warning This is advanced, hardcore stuff. You don't need this unless you + * really know what you're doing. Most apps will not need this. + * + * Historically, PhysicsFS provided access to the physical filesystem and + * archives within that filesystem. However, sometimes you need more power + * than this. Perhaps you need to provide an archive that is entirely + * contained in RAM, or you need to bridge some other file i/o API to + * PhysicsFS, or you need to translate the bits (perhaps you have a + * a standard .zip file that's encrypted, and you need to decrypt on the fly + * for the unsuspecting zip archiver). + * + * A PHYSFS_Io is the interface that Archivers use to get archive data. + * Historically, this has mapped to file i/o to the physical filesystem, but + * as of PhysicsFS 2.1, applications can provide their own i/o implementations + * at runtime. + * + * This interface isn't necessarily a good universal fit for i/o. There are a + * few requirements of note: + * + * - They only do blocking i/o (at least, for now). + * - They need to be able to duplicate. If you have a file handle from + * fopen(), you need to be able to create a unique clone of it (so we + * have two handles to the same file that can both seek/read/etc without + * stepping on each other). + * - They need to know the size of their entire data set. + * - They need to be able to seek and rewind on demand. + * + * ...in short, you're probably not going to write an HTTP implementation. + * + * Thread safety: PHYSFS_Io implementations are not guaranteed to be thread + * safe in themselves. Under the hood where PhysicsFS uses them, the library + * provides its own locks. If you plan to use them directly from separate + * threads, you should either use mutexes to protect them, or don't use the + * same PHYSFS_Io from two threads at the same time. + * + * \sa PHYSFS_mountIo + */ +typedef struct PHYSFS_Io +{ + /** + * \brief Binary compatibility information. + * + * This must be set to zero at this time. Future versions of this + * struct will increment this field, so we know what a given + * implementation supports. We'll presumably keep supporting older + * versions as we offer new features, though. + */ + PHYSFS_uint32 version; + + /** + * \brief Instance data for this struct. + * + * Each instance has a pointer associated with it that can be used to + * store anything it likes. This pointer is per-instance of the stream, + * so presumably it will change when calling duplicate(). This can be + * deallocated during the destroy() method. + */ + void *opaque; + + /** + * \brief Read more data. + * + * Read (len) bytes from the interface, at the current i/o position, and + * store them in (buffer). The current i/o position should move ahead + * by the number of bytes successfully read. + * + * You don't have to implement this; set it to NULL if not implemented. + * This will only be used if the file is opened for reading. If set to + * NULL, a default implementation that immediately reports failure will + * be used. + * + * \param io The i/o instance to read from. + * \param buf The buffer to store data into. It must be at least + * (len) bytes long and can't be NULL. + * \param len The number of bytes to read from the interface. + * \return number of bytes read from file, 0 on EOF, -1 if complete + * failure. + */ + PHYSFS_sint64 (*read)(struct PHYSFS_Io *io, void *buf, PHYSFS_uint64 len); + + /** + * \brief Write more data. + * + * Write (len) bytes from (buffer) to the interface at the current i/o + * position. The current i/o position should move ahead by the number of + * bytes successfully written. + * + * You don't have to implement this; set it to NULL if not implemented. + * This will only be used if the file is opened for writing. If set to + * NULL, a default implementation that immediately reports failure will + * be used. + * + * You are allowed to buffer; a write can succeed here and then later + * fail when flushing. Note that PHYSFS_setBuffer() may be operating a + * level above your i/o, so you should usually not implement your + * own buffering routines. + * + * \param io The i/o instance to write to. + * \param buffer The buffer to read data from. It must be at least + * (len) bytes long and can't be NULL. + * \param len The number of bytes to read from (buffer). + * \return number of bytes written to file, -1 if complete failure. + */ + PHYSFS_sint64 (*write)(struct PHYSFS_Io *io, const void *buffer, + PHYSFS_uint64 len); + + /** + * \brief Move i/o position to a given byte offset from start. + * + * This method moves the i/o position, so the next read/write will + * be of the byte at (offset) offset. Seeks past the end of file should + * be treated as an error condition. + * + * \param io The i/o instance to seek. + * \param offset The new byte offset for the i/o position. + * \return non-zero on success, zero on error. + */ + int (*seek)(struct PHYSFS_Io *io, PHYSFS_uint64 offset); + + /** + * \brief Report current i/o position. + * + * Return bytes offset, or -1 if you aren't able to determine. A failure + * will almost certainly be fatal to further use of this stream, so you + * may not leave this unimplemented. + * + * \param io The i/o instance to query. + * \return The current byte offset for the i/o position, -1 if unknown. + */ + PHYSFS_sint64 (*tell)(struct PHYSFS_Io *io); + + /** + * \brief Determine size of the i/o instance's dataset. + * + * Return number of bytes available in the file, or -1 if you + * aren't able to determine. A failure will almost certainly be fatal + * to further use of this stream, so you may not leave this unimplemented. + * + * \param io The i/o instance to query. + * \return Total size, in bytes, of the dataset. + */ + PHYSFS_sint64 (*length)(struct PHYSFS_Io *io); + + /** + * \brief Duplicate this i/o instance. + * + * This needs to result in a full copy of this PHYSFS_Io, that can live + * completely independently. The copy needs to be able to perform all + * its operations without altering the original, including either object + * being destroyed separately (so, for example: they can't share a file + * handle; they each need their own). + * + * If you can't duplicate a handle, it's legal to return NULL, but you + * almost certainly need this functionality if you want to use this to + * PHYSFS_Io to back an archive. + * + * \param io The i/o instance to duplicate. + * \return A new value for a stream's (opaque) field, or NULL on error. + */ + struct PHYSFS_Io *(*duplicate)(struct PHYSFS_Io *io); + + /** + * \brief Flush resources to media, or wherever. + * + * This is the chance to report failure for writes that had claimed + * success earlier, but still had a chance to actually fail. This method + * can be NULL if flushing isn't necessary. + * + * This function may be called before destroy(), as it can report failure + * and destroy() can not. It may be called at other times, too. + * + * \param io The i/o instance to flush. + * \return Zero on error, non-zero on success. + */ + int (*flush)(struct PHYSFS_Io *io); + + /** + * \brief Cleanup and deallocate i/o instance. + * + * Free associated resources, including (opaque) if applicable. + * + * This function must always succeed: as such, it returns void. The + * system may call your flush() method before this. You may report + * failure there if necessary. This method may still be called if + * flush() fails, in which case you'll have to abandon unflushed data + * and other failing conditions and clean up. + * + * Once this method is called for a given instance, the system will assume + * it is unsafe to touch that instance again and will discard any + * references to it. + * + * \param s The i/o instance to destroy. + */ + void (*destroy)(struct PHYSFS_Io *io); +} PHYSFS_Io; + + +/** + * \fn int PHYSFS_mountIo(PHYSFS_Io *io, const char *newDir, const char *mountPoint, int appendToPath) + * \brief Add an archive, built on a PHYSFS_Io, to the search path. + * + * \warning Unless you have some special, low-level need, you should be using + * PHYSFS_mount() instead of this. + * + * This function operates just like PHYSFS_mount(), but takes a PHYSFS_Io + * instead of a pathname. Behind the scenes, PHYSFS_mount() calls this + * function with a physical-filesystem-based PHYSFS_Io. + * + * (newDir) must be a unique string to identify this archive. It is used + * to optimize archiver selection (if you name it XXXXX.zip, we might try + * the ZIP archiver first, for example, or directly choose an archiver that + * can only trust the data is valid by filename extension). It doesn't + * need to refer to a real file at all. If the filename extension isn't + * helpful, the system will try every archiver until one works or none + * of them do. This filename must be unique, as the system won't allow you + * to have two archives with the same name. + * + * (io) must remain until the archive is unmounted. When the archive is + * unmounted, the system will call (io)->destroy(io), which will give you + * a chance to free your resources. + * + * If this function fails, (io)->destroy(io) is not called. + * + * \param io i/o instance for archive to add to the path. + * \param newDir Filename that can represent this stream. + * \param mountPoint Location in the interpolated tree that this archive + * will be "mounted", in platform-independent notation. + * NULL or "" is equivalent to "/". + * \param appendToPath nonzero to append to search path, zero to prepend. + * \return nonzero if added to path, zero on failure (bogus archive, stream + * i/o issue, etc). Use PHYSFS_getLastErrorCode() to obtain + * the specific error. + * + * \sa PHYSFS_unmount + * \sa PHYSFS_getSearchPath + * \sa PHYSFS_getMountPoint + */ +PHYSFS_DECL int PHYSFS_mountIo(PHYSFS_Io *io, const char *newDir, + const char *mountPoint, int appendToPath); + + +/** + * \fn int PHYSFS_mountMemory(const void *buf, PHYSFS_uint64 len, void (*del)(void *), const char *newDir, const char *mountPoint, int appendToPath) + * \brief Add an archive, contained in a memory buffer, to the search path. + * + * \warning Unless you have some special, low-level need, you should be using + * PHYSFS_mount() instead of this. + * + * This function operates just like PHYSFS_mount(), but takes a memory buffer + * instead of a pathname. This buffer contains all the data of the archive, + * and is used instead of a real file in the physical filesystem. + * + * (newDir) must be a unique string to identify this archive. It is used + * to optimize archiver selection (if you name it XXXXX.zip, we might try + * the ZIP archiver first, for example, or directly choose an archiver that + * can only trust the data is valid by filename extension). It doesn't + * need to refer to a real file at all. If the filename extension isn't + * helpful, the system will try every archiver until one works or none + * of them do. This filename must be unique, as the system won't allow you + * to have two archives with the same name. + * + * (ptr) must remain until the archive is unmounted. When the archive is + * unmounted, the system will call (del)(ptr), which will notify you that + * the system is done with the buffer, and give you a chance to free your + * resources. (del) can be NULL, in which case the system will make no + * attempt to free the buffer. + * + * If this function fails, (del) is not called. + * + * \param buf Address of the memory buffer containing the archive data. + * \param len Size of memory buffer, in bytes. + * \param del A callback that triggers upon unmount. Can be NULL. + * \param newDir Filename that can represent this stream. + * \param mountPoint Location in the interpolated tree that this archive + * will be "mounted", in platform-independent notation. + * NULL or "" is equivalent to "/". + * \param appendToPath nonzero to append to search path, zero to prepend. + * \return nonzero if added to path, zero on failure (bogus archive, etc). + * Use PHYSFS_getLastErrorCode() to obtain the specific error. + * + * \sa PHYSFS_unmount + * \sa PHYSFS_getSearchPath + * \sa PHYSFS_getMountPoint + */ +PHYSFS_DECL int PHYSFS_mountMemory(const void *buf, PHYSFS_uint64 len, + void (*del)(void *), const char *newDir, + const char *mountPoint, int appendToPath); + + +/** + * \fn int PHYSFS_mountHandle(PHYSFS_File *file, const char *newDir, const char *mountPoint, int appendToPath) + * \brief Add an archive, contained in a PHYSFS_File handle, to the search path. + * + * \warning Unless you have some special, low-level need, you should be using + * PHYSFS_mount() instead of this. + * + * \warning Archives-in-archives may be very slow! While a PHYSFS_File can + * seek even when the data is compressed, it may do so by rewinding + * to the start and decompressing everything before the seek point. + * Normal archive usage may do a lot of seeking behind the scenes. + * As such, you might find normal archive usage extremely painful + * if mounted this way. Plan accordingly: if you, say, have a + * self-extracting .zip file, and want to mount something in it, + * compress the contents of the inner archive and make sure the outer + * .zip file doesn't compress the inner archive too. + * + * This function operates just like PHYSFS_mount(), but takes a PHYSFS_File + * handle instead of a pathname. This handle contains all the data of the + * archive, and is used instead of a real file in the physical filesystem. + * The PHYSFS_File may be backed by a real file in the physical filesystem, + * but isn't necessarily. The most popular use for this is likely to mount + * archives stored inside other archives. + * + * (newDir) must be a unique string to identify this archive. It is used + * to optimize archiver selection (if you name it XXXXX.zip, we might try + * the ZIP archiver first, for example, or directly choose an archiver that + * can only trust the data is valid by filename extension). It doesn't + * need to refer to a real file at all. If the filename extension isn't + * helpful, the system will try every archiver until one works or none + * of them do. This filename must be unique, as the system won't allow you + * to have two archives with the same name. + * + * (file) must remain until the archive is unmounted. When the archive is + * unmounted, the system will call PHYSFS_close(file). If you need this + * handle to survive, you will have to wrap this in a PHYSFS_Io and use + * PHYSFS_mountIo() instead. + * + * If this function fails, PHYSFS_close(file) is not called. + * + * \param file The PHYSFS_File handle containing archive data. + * \param newDir Filename that can represent this stream. + * \param mountPoint Location in the interpolated tree that this archive + * will be "mounted", in platform-independent notation. + * NULL or "" is equivalent to "/". + * \param appendToPath nonzero to append to search path, zero to prepend. + * \return nonzero if added to path, zero on failure (bogus archive, etc). + * Use PHYSFS_getLastErrorCode() to obtain the specific error. + * + * \sa PHYSFS_unmount + * \sa PHYSFS_getSearchPath + * \sa PHYSFS_getMountPoint + */ +PHYSFS_DECL int PHYSFS_mountHandle(PHYSFS_File *file, const char *newDir, + const char *mountPoint, int appendToPath); + + +/** + * \enum PHYSFS_ErrorCode + * \brief Values that represent specific causes of failure. + * + * Most of the time, you should only concern yourself with whether a given + * operation failed or not, but there may be occasions where you plan to + * handle a specific failure case gracefully, so we provide specific error + * codes. + * + * Most of these errors are a little vague, and most aren't things you can + * fix...if there's a permission error, for example, all you can really do + * is pass that information on to the user and let them figure out how to + * handle it. In most these cases, your program should only care that it + * failed to accomplish its goals, and not care specifically why. + * + * \sa PHYSFS_getLastErrorCode + * \sa PHYSFS_getErrorByCode + */ +typedef enum PHYSFS_ErrorCode +{ + PHYSFS_ERR_OK, /**< Success; no error. */ + PHYSFS_ERR_OTHER_ERROR, /**< Error not otherwise covered here. */ + PHYSFS_ERR_OUT_OF_MEMORY, /**< Memory allocation failed. */ + PHYSFS_ERR_NOT_INITIALIZED, /**< PhysicsFS is not initialized. */ + PHYSFS_ERR_IS_INITIALIZED, /**< PhysicsFS is already initialized. */ + PHYSFS_ERR_ARGV0_IS_NULL, /**< Needed argv[0], but it is NULL. */ + PHYSFS_ERR_UNSUPPORTED, /**< Operation or feature unsupported. */ + PHYSFS_ERR_PAST_EOF, /**< Attempted to access past end of file. */ + PHYSFS_ERR_FILES_STILL_OPEN, /**< Files still open. */ + PHYSFS_ERR_INVALID_ARGUMENT, /**< Bad parameter passed to an function. */ + PHYSFS_ERR_NOT_MOUNTED, /**< Requested archive/dir not mounted. */ + PHYSFS_ERR_NOT_FOUND, /**< File (or whatever) not found. */ + PHYSFS_ERR_SYMLINK_FORBIDDEN,/**< Symlink seen when not permitted. */ + PHYSFS_ERR_NO_WRITE_DIR, /**< No write dir has been specified. */ + PHYSFS_ERR_OPEN_FOR_READING, /**< Wrote to a file opened for reading. */ + PHYSFS_ERR_OPEN_FOR_WRITING, /**< Read from a file opened for writing. */ + PHYSFS_ERR_NOT_A_FILE, /**< Needed a file, got a directory (etc). */ + PHYSFS_ERR_READ_ONLY, /**< Wrote to a read-only filesystem. */ + PHYSFS_ERR_CORRUPT, /**< Corrupted data encountered. */ + PHYSFS_ERR_SYMLINK_LOOP, /**< Infinite symbolic link loop. */ + PHYSFS_ERR_IO, /**< i/o error (hardware failure, etc). */ + PHYSFS_ERR_PERMISSION, /**< Permission denied. */ + PHYSFS_ERR_NO_SPACE, /**< No space (disk full, over quota, etc) */ + PHYSFS_ERR_BAD_FILENAME, /**< Filename is bogus/insecure. */ + PHYSFS_ERR_BUSY, /**< Tried to modify a file the OS needs. */ + PHYSFS_ERR_DIR_NOT_EMPTY, /**< Tried to delete dir with files in it. */ + PHYSFS_ERR_OS_ERROR, /**< Unspecified OS-level error. */ + PHYSFS_ERR_DUPLICATE, /**< Duplicate entry. */ + PHYSFS_ERR_BAD_PASSWORD, /**< Bad password. */ + PHYSFS_ERR_APP_CALLBACK /**< Application callback reported error. */ +} PHYSFS_ErrorCode; + + +/** + * \fn PHYSFS_ErrorCode PHYSFS_getLastErrorCode(void) + * \brief Get machine-readable error information. + * + * Get the last PhysicsFS error message as an integer value. This will return + * PHYSFS_ERR_OK if there's been no error since the last call to this + * function. Each thread has a unique error state associated with it, but + * each time a new error message is set, it will overwrite the previous one + * associated with that thread. It is safe to call this function at anytime, + * even before PHYSFS_init(). + * + * PHYSFS_getLastError() and PHYSFS_getLastErrorCode() both reset the same + * thread-specific error state. Calling one will wipe out the other's + * data. If you need both, call PHYSFS_getLastErrorCode(), then pass that + * value to PHYSFS_getErrorByCode(). + * + * Generally, applications should only concern themselves with whether a + * given function failed; however, if you require more specifics, you can + * try this function to glean information, if there's some specific problem + * you're expecting and plan to handle. But with most things that involve + * file systems, the best course of action is usually to give up, report the + * problem to the user, and let them figure out what should be done about it. + * For that, you might prefer PHYSFS_getErrorByCode() instead. + * + * \return Enumeration value that represents last reported error. + * + * \sa PHYSFS_getErrorByCode + */ +PHYSFS_DECL PHYSFS_ErrorCode PHYSFS_getLastErrorCode(void); + + +/** + * \fn const char *PHYSFS_getErrorByCode(PHYSFS_ErrorCode code) + * \brief Get human-readable description string for a given error code. + * + * Get a static string, in UTF-8 format, that represents an English + * description of a given error code. + * + * This string is guaranteed to never change (although we may add new strings + * for new error codes in later versions of PhysicsFS), so you can use it + * for keying a localization dictionary. + * + * It is safe to call this function at anytime, even before PHYSFS_init(). + * + * These strings are meant to be passed on directly to the user. + * Generally, applications should only concern themselves with whether a + * given function failed, but not care about the specifics much. + * + * Do not attempt to free the returned strings; they are read-only and you + * don't own their memory pages. + * + * \param code Error code to convert to a string. + * \return READ ONLY string of requested error message, NULL if this + * is not a valid PhysicsFS error code. Always check for NULL if + * you might be looking up an error code that didn't exist in an + * earlier version of PhysicsFS. + * + * \sa PHYSFS_getLastErrorCode + */ +PHYSFS_DECL const char *PHYSFS_getErrorByCode(PHYSFS_ErrorCode code); + +/** + * \fn void PHYSFS_setErrorCode(PHYSFS_ErrorCode code) + * \brief Set the current thread's error code. + * + * This lets you set the value that will be returned by the next call to + * PHYSFS_getLastErrorCode(). This will replace any existing error code, + * whether set by your application or internally by PhysicsFS. + * + * Error codes are stored per-thread; what you set here will not be + * accessible to another thread. + * + * Any call into PhysicsFS may change the current error code, so any code you + * set here is somewhat fragile, and thus you shouldn't build any serious + * error reporting framework on this function. The primary goal of this + * function is to allow PHYSFS_Io implementations to set the error state, + * which generally will be passed back to your application when PhysicsFS + * makes a PHYSFS_Io call that fails internally. + * + * This function doesn't care if the error code is a value known to PhysicsFS + * or not (but PHYSFS_getErrorByCode() will return NULL for unknown values). + * The value will be reported unmolested by PHYSFS_getLastErrorCode(). + * + * \param code Error code to become the current thread's new error state. + * + * \sa PHYSFS_getLastErrorCode + * \sa PHYSFS_getErrorByCode + */ +PHYSFS_DECL void PHYSFS_setErrorCode(PHYSFS_ErrorCode code); + + +/** + * \fn const char *PHYSFS_getPrefDir(const char *org, const char *app) + * \brief Get the user-and-app-specific path where files can be written. + * + * Helper function. + * + * Get the "pref dir". This is meant to be where users can write personal + * files (preferences and save games, etc) that are specific to your + * application. This directory is unique per user, per application. + * + * This function will decide the appropriate location in the native filesystem, + * create the directory if necessary, and return a string in + * platform-dependent notation, suitable for passing to PHYSFS_setWriteDir(). + * + * On Windows, this might look like: + * "C:\\Users\\bob\\AppData\\Roaming\\My Company\\My Program Name" + * + * On Linux, this might look like: + * "/home/bob/.local/share/My Program Name" + * + * On Mac OS X, this might look like: + * "/Users/bob/Library/Application Support/My Program Name" + * + * (etc.) + * + * You should probably use the pref dir for your write dir, and also put it + * near the beginning of your search path. Older versions of PhysicsFS + * offered only PHYSFS_getUserDir() and left you to figure out where the + * files should go under that tree. This finds the correct location + * for whatever platform, which not only changes between operating systems, + * but also versions of the same operating system. + * + * You specify the name of your organization (if it's not a real organization, + * your name or an Internet domain you own might do) and the name of your + * application. These should be proper names. + * + * Both the (org) and (app) strings may become part of a directory name, so + * please follow these rules: + * + * - Try to use the same org string (including case-sensitivity) for + * all your applications that use this function. + * - Always use a unique app string for each one, and make sure it never + * changes for an app once you've decided on it. + * - Unicode characters are legal, as long as it's UTF-8 encoded, but... + * - ...only use letters, numbers, and spaces. Avoid punctuation like + * "Game Name 2: Bad Guy's Revenge!" ... "Game Name 2" is sufficient. + * + * The pointer returned by this function remains valid until you call this + * function again, or call PHYSFS_deinit(). This is not necessarily a fast + * call, though, so you should call this once at startup and copy the string + * if you need it. + * + * You should assume the path returned by this function is the only safe + * place to write files (and that PHYSFS_getUserDir() and PHYSFS_getBaseDir(), + * while they might be writable, or even parents of the returned path, aren't + * where you should be writing things). + * + * \param org The name of your organization. + * \param app The name of your application. + * \return READ ONLY string of user dir in platform-dependent notation. NULL + * if there's a problem (creating directory failed, etc). + * + * \sa PHYSFS_getBaseDir + * \sa PHYSFS_getUserDir + */ +PHYSFS_DECL const char *PHYSFS_getPrefDir(const char *org, const char *app); + + +/** + * \struct PHYSFS_Archiver + * \brief Abstract interface to provide support for user-defined archives. + * + * \warning This is advanced, hardcore stuff. You don't need this unless you + * really know what you're doing. Most apps will not need this. + * + * Historically, PhysicsFS provided a means to mount various archive file + * formats, and physical directories in the native filesystem. However, + * applications have been limited to the file formats provided by the + * library. This interface allows an application to provide their own + * archive file types. + * + * Conceptually, a PHYSFS_Archiver provides directory entries, while + * PHYSFS_Io provides data streams for those directory entries. The most + * obvious use of PHYSFS_Archiver is to provide support for an archive + * file type that isn't provided by PhysicsFS directly: perhaps some + * proprietary format that only your application needs to understand. + * + * Internally, all the built-in archive support uses this interface, so the + * best examples for building a PHYSFS_Archiver is the source code to + * PhysicsFS itself. + * + * An archiver is added to the system with PHYSFS_registerArchiver(), and then + * it will be available for use automatically with PHYSFS_mount(); if a + * given archive can be handled with your archiver, it will be given control + * as appropriate. + * + * These methods deal with dir handles. You have one instance of your + * archiver, and it generates a unique, opaque handle for each opened + * archive in its openArchive() method. Since the lifetime of an Archiver + * (not an archive) is generally the entire lifetime of the process, and it's + * assumed to be a singleton, we do not provide any instance data for the + * archiver itself; the app can just use some static variables if necessary. + * + * Symlinks should always be followed (except in stat()); PhysicsFS will + * use the stat() method to check for symlinks and make a judgement on + * whether to continue to call other methods based on that. + * + * Archivers, when necessary, should set the PhysicsFS error state with + * PHYSFS_setErrorCode() before returning. PhysicsFS will pass these errors + * back to the application unmolested in most cases. + * + * Thread safety: PHYSFS_Archiver implementations are not guaranteed to be + * thread safe in themselves. PhysicsFS provides thread safety when it calls + * into a given archiver inside the library, but it does not promise that + * using the same PHYSFS_File from two threads at once is thread-safe; as + * such, your PHYSFS_Archiver can assume that locking is handled for you + * so long as the PHYSFS_Io you return from PHYSFS_open* doesn't change any + * of your Archiver state, as the PHYSFS_Io won't be as aggressively + * protected. + * + * \sa PHYSFS_registerArchiver + * \sa PHYSFS_deregisterArchiver + * \sa PHYSFS_supportedArchiveTypes + */ +typedef struct PHYSFS_Archiver +{ + /** + * \brief Binary compatibility information. + * + * This must be set to zero at this time. Future versions of this + * struct will increment this field, so we know what a given + * implementation supports. We'll presumably keep supporting older + * versions as we offer new features, though. + */ + PHYSFS_uint32 version; + + /** + * \brief Basic info about this archiver. + * + * This is used to identify your archive, and is returned in + * PHYSFS_supportedArchiveTypes(). + */ + PHYSFS_ArchiveInfo info; + + /** + * \brief Open an archive provided by (io). + * + * This is where resources are allocated and data is parsed when mounting + * an archive. + * (name) is a filename associated with (io), but doesn't necessarily + * map to anything, let alone a real filename. This possibly- + * meaningless name is in platform-dependent notation. + * (forWrite) is non-zero if this is to be used for + * the write directory, and zero if this is to be used for an + * element of the search path. + * (claimed) should be set to 1 if this is definitely an archive your + * archiver implementation can handle, even if it fails. We use to + * decide if we should stop trying other archivers if you fail to open + * it. For example: the .zip archiver will set this to 1 for something + * that's got a .zip file signature, even if it failed because the file + * was also truncated. No sense in trying other archivers here, we + * already tried to handle it with the appropriate implementation!. + * Return NULL on failure and set (claimed) appropriately. If no archiver + * opened the archive or set (claimed), PHYSFS_mount() will report + * PHYSFS_ERR_UNSUPPORTED. Otherwise, it will report the error from the + * archiver that claimed the data through (claimed). + * Return non-NULL on success. The pointer returned will be + * passed as the "opaque" parameter for later calls. + */ + void *(*openArchive)(PHYSFS_Io *io, const char *name, + int forWrite, int *claimed); + + /** + * \brief List all files in (dirname). + * + * Each file is passed to (cb), where a copy is made if appropriate, so + * you can dispose of it upon return from the callback. (dirname) is in + * platform-independent notation. + * If you have a failure, call PHYSFS_SetErrorCode() with whatever code + * seem appropriate and return PHYSFS_ENUM_ERROR. + * If the callback returns PHYSFS_ENUM_ERROR, please call + * PHYSFS_SetErrorCode(PHYSFS_ERR_APP_CALLBACK) and then return + * PHYSFS_ENUM_ERROR as well. Don't call the callback again in any + * circumstances. + * If the callback returns PHYSFS_ENUM_STOP, stop enumerating and return + * PHYSFS_ENUM_STOP as well. Don't call the callback again in any + * circumstances. Don't set an error code in this case. + * Callbacks are only supposed to return a value from + * PHYSFS_EnumerateCallbackResult. Any other result has undefined + * behavior. + * As long as the callback returned PHYSFS_ENUM_OK and you haven't + * experienced any errors of your own, keep enumerating until you're done + * and then return PHYSFS_ENUM_OK without setting an error code. + * + * \warning PHYSFS_enumerate returns zero or non-zero (success or failure), + * so be aware this function pointer returns different values! + */ + PHYSFS_EnumerateCallbackResult (*enumerate)(void *opaque, + const char *dirname, PHYSFS_EnumerateCallback cb, + const char *origdir, void *callbackdata); + + /** + * \brief Open a file in this archive for reading. + * + * This filename, (fnm), is in platform-independent notation. + * Fail if the file does not exist. + * Returns NULL on failure, and calls PHYSFS_setErrorCode(). + * Returns non-NULL on success. The pointer returned will be + * passed as the "opaque" parameter for later file calls. + */ + PHYSFS_Io *(*openRead)(void *opaque, const char *fnm); + + /** + * \brief Open a file in this archive for writing. + * + * If the file does not exist, it should be created. If it exists, + * it should be truncated to zero bytes. The writing offset should + * be the start of the file. + * If the archive is read-only, this operation should fail. + * This filename is in platform-independent notation. + * Returns NULL on failure, and calls PHYSFS_setErrorCode(). + * Returns non-NULL on success. The pointer returned will be + * passed as the "opaque" parameter for later file calls. + */ + PHYSFS_Io *(*openWrite)(void *opaque, const char *filename); + + /** + * \brief Open a file in this archive for appending. + * + * If the file does not exist, it should be created. The writing + * offset should be the end of the file. + * If the archive is read-only, this operation should fail. + * This filename is in platform-independent notation. + * Returns NULL on failure, and calls PHYSFS_setErrorCode(). + * Returns non-NULL on success. The pointer returned will be + * passed as the "opaque" parameter for later file calls. + */ + PHYSFS_Io *(*openAppend)(void *opaque, const char *filename); + + /** + * \brief Delete a file or directory in the archive. + * + * This same call is used for both files and directories; there is not a + * separate rmdir() call. Directories are only meant to be removed if + * they are empty. + * If the archive is read-only, this operation should fail. + * + * Return non-zero on success, zero on failure. + * This filename is in platform-independent notation. + * On failure, call PHYSFS_setErrorCode(). + */ + int (*remove)(void *opaque, const char *filename); + + /** + * \brief Create a directory in the archive. + * + * If the application is trying to make multiple dirs, PhysicsFS + * will split them up into multiple calls before passing them to + * your driver. + * If the archive is read-only, this operation should fail. + * Return non-zero on success, zero on failure. + * This filename is in platform-independent notation. + * On failure, call PHYSFS_setErrorCode(). + */ + int (*mkdir)(void *opaque, const char *filename); + + /** + * \brief Obtain basic file metadata. + * + * On success, fill in all the fields in (stat), using + * reasonable defaults for fields that apply to your archive. + * + * Returns non-zero on success, zero on failure. + * This filename is in platform-independent notation. + * On failure, call PHYSFS_setErrorCode(). + */ + int (*stat)(void *opaque, const char *fn, PHYSFS_Stat *stat); + + /** + * \brief Destruct a previously-opened archive. + * + * Close this archive, and free any associated memory, + * including the original PHYSFS_Io and (opaque) itself, if + * applicable. Implementation can assume that it won't be called if + * there are still files open from this archive. + */ + void (*closeArchive)(void *opaque); +} PHYSFS_Archiver; + +/** + * \fn int PHYSFS_registerArchiver(const PHYSFS_Archiver *archiver) + * \brief Add a new archiver to the system. + * + * \warning This is advanced, hardcore stuff. You don't need this unless you + * really know what you're doing. Most apps will not need this. + * + * If you want to provide your own archiver (for example, a custom archive + * file format, or some virtual thing you want to make look like a filesystem + * that you can access through the usual PhysicsFS APIs), this is where you + * start. Once an archiver is successfully registered, then you can use + * PHYSFS_mount() to add archives that your archiver supports to the + * search path, or perhaps use it as the write dir. Internally, PhysicsFS + * uses this function to register its own built-in archivers, like .zip + * support, etc. + * + * You may not have two archivers that handle the same extension. If you are + * going to have a clash, you can deregister the other archiver (including + * built-in ones) with PHYSFS_deregisterArchiver(). + * + * The data in (archiver) is copied; you may free this pointer when this + * function returns. + * + * Once this function returns successfully, PhysicsFS will be able to support + * archives of this type until you deregister the archiver again. + * + * \param archiver The archiver to register. + * \return Zero on error, non-zero on success. + * + * \sa PHYSFS_Archiver + * \sa PHYSFS_deregisterArchiver + */ +PHYSFS_DECL int PHYSFS_registerArchiver(const PHYSFS_Archiver *archiver); + +/** + * \fn int PHYSFS_deregisterArchiver(const char *ext) + * \brief Remove an archiver from the system. + * + * If for some reason, you only need your previously-registered archiver to + * live for a portion of your app's lifetime, you can remove it from the + * system once you're done with it through this function. + * + * This fails if there are any archives still open that use this archiver. + * + * This function can also remove internally-supplied archivers, like .zip + * support or whatnot. This could be useful in some situations, like + * disabling support for them outright or overriding them with your own + * implementation. Once an internal archiver is disabled like this, + * PhysicsFS provides no mechanism to recover them, short of calling + * PHYSFS_deinit() and PHYSFS_init() again. + * + * PHYSFS_deinit() will automatically deregister all archivers, so you don't + * need to explicitly deregister yours if you otherwise shut down cleanly. + * + * \param ext Filename extension that the archiver handles. + * \return Zero on error, non-zero on success. + * + * \sa PHYSFS_Archiver + * \sa PHYSFS_registerArchiver + */ +PHYSFS_DECL int PHYSFS_deregisterArchiver(const char *ext); + + +/* Everything above this line is part of the PhysicsFS 2.1 API. */ + +#ifdef __cplusplus +} +#endif + +#endif /* !defined _INCLUDE_PHYSFS_H_ */ + +/* end of physfs.h ... */ + diff --git a/Source/Dependencies/PhysicsFS/physfs_archiver_7z.c b/Source/Dependencies/PhysicsFS/physfs_archiver_7z.c new file mode 100644 index 000000000..914f69164 --- /dev/null +++ b/Source/Dependencies/PhysicsFS/physfs_archiver_7z.c @@ -0,0 +1,430 @@ +/* + * 7zip support routines for PhysicsFS. + * + * Please see the file LICENSE.txt in the source's root directory. + * + * This file was written by Ryan C. Gordon. + */ + +#define __PHYSICSFS_INTERNAL__ +#include "physfs_internal.h" + +#if PHYSFS_SUPPORTS_7Z + +#include "physfs_lzmasdk.h" + +typedef struct +{ + ISeekInStream seekStream; /* lzma sdk i/o interface (lower level). */ + PHYSFS_Io *io; /* physfs i/o interface for this archive. */ + CLookToRead lookStream; /* lzma sdk i/o interface (higher level). */ +} SZIPLookToRead; + +/* One SZIPentry is kept for each file in an open 7zip archive. */ +typedef struct +{ + __PHYSFS_DirTreeEntry tree; /* manages directory tree */ + PHYSFS_uint32 dbidx; /* index into lzma sdk database */ +} SZIPentry; + +/* One SZIPinfo is kept for each open 7zip archive. */ +typedef struct +{ + __PHYSFS_DirTree tree; /* manages directory tree. */ + PHYSFS_Io *io; /* physfs i/o interface for this archive. */ + CSzArEx db; /* lzma sdk archive database object. */ +} SZIPinfo; + + +static PHYSFS_ErrorCode szipErrorCode(const SRes rc) +{ + switch (rc) + { + case SZ_OK: return PHYSFS_ERR_OK; + case SZ_ERROR_DATA: return PHYSFS_ERR_CORRUPT; + case SZ_ERROR_MEM: return PHYSFS_ERR_OUT_OF_MEMORY; + case SZ_ERROR_CRC: return PHYSFS_ERR_CORRUPT; + case SZ_ERROR_UNSUPPORTED: return PHYSFS_ERR_UNSUPPORTED; + case SZ_ERROR_INPUT_EOF: return PHYSFS_ERR_CORRUPT; + case SZ_ERROR_OUTPUT_EOF: return PHYSFS_ERR_IO; + case SZ_ERROR_READ: return PHYSFS_ERR_IO; + case SZ_ERROR_WRITE: return PHYSFS_ERR_IO; + case SZ_ERROR_ARCHIVE: return PHYSFS_ERR_CORRUPT; + case SZ_ERROR_NO_ARCHIVE: return PHYSFS_ERR_UNSUPPORTED; + default: break; + } /* switch */ + + return PHYSFS_ERR_OTHER_ERROR; +} /* szipErrorCode */ + + +/* LZMA SDK's ISzAlloc interface ... */ + +static void *SZIP_ISzAlloc_Alloc(void *p, size_t size) +{ + return allocator.Malloc(size ? size : 1); +} /* SZIP_ISzAlloc_Alloc */ + +static void SZIP_ISzAlloc_Free(void *p, void *address) +{ + if (address) + allocator.Free(address); +} /* SZIP_ISzAlloc_Free */ + +static ISzAlloc SZIP_SzAlloc = { + SZIP_ISzAlloc_Alloc, SZIP_ISzAlloc_Free +}; + + +/* we implement ISeekInStream, and then wrap that in LZMA SDK's CLookToRead, + which implements the higher-level ILookInStream on top of that, handling + buffering and such for us. */ + +/* LZMA SDK's ISeekInStream interface ... */ + +static SRes SZIP_ISeekInStream_Read(void *p, void *buf, size_t *size) +{ + SZIPLookToRead *stream = (SZIPLookToRead *) p; + PHYSFS_Io *io = stream->io; + const PHYSFS_uint64 len = (PHYSFS_uint64) *size; + const PHYSFS_sint64 rc = (len == 0) ? 0 : io->read(io, buf, len); + + if (rc < 0) + { + *size = 0; + return SZ_ERROR_READ; + } /* if */ + + *size = (size_t) rc; + return SZ_OK; +} /* SZIP_ISeekInStream_Read */ + +static SRes SZIP_ISeekInStream_Seek(void *p, Int64 *pos, ESzSeek origin) +{ + SZIPLookToRead *stream = (SZIPLookToRead *) p; + PHYSFS_Io *io = stream->io; + PHYSFS_sint64 base; + PHYSFS_uint64 newpos; + + switch (origin) + { + case SZ_SEEK_SET: + base = 0; + break; + + case SZ_SEEK_CUR: + base = io->tell(io); + break; + + case SZ_SEEK_END: + base = io->length(io); + break; + + default: + return SZ_ERROR_FAIL; + } /* switch */ + + if (base < 0) + return SZ_ERROR_FAIL; + else if ((*pos < 0) && (((Int64) base) < -*pos)) + return SZ_ERROR_FAIL; + + newpos = (PHYSFS_uint64) (((Int64) base) + *pos); + if (!io->seek(io, newpos)) + return SZ_ERROR_FAIL; + + *pos = (Int64) newpos; + return SZ_OK; +} /* SZIP_ISeekInStream_Seek */ + + +static void szipInitStream(SZIPLookToRead *stream, PHYSFS_Io *io) +{ + stream->seekStream.Read = SZIP_ISeekInStream_Read; + stream->seekStream.Seek = SZIP_ISeekInStream_Seek; + + stream->io = io; + + /* !!! FIXME: can we use lookahead? Is there value to it? */ + LookToRead_Init(&stream->lookStream); + LookToRead_CreateVTable(&stream->lookStream, False); + stream->lookStream.realStream = &stream->seekStream; +} /* szipInitStream */ + + +/* Do this in a separate function so we can smallAlloc without looping. */ +static int szipLoadEntry(SZIPinfo *info, const PHYSFS_uint32 idx) +{ + const size_t utf16len = SzArEx_GetFileNameUtf16(&info->db, idx, NULL); + const size_t utf16buflen = utf16len * 2; + PHYSFS_uint16 *utf16 = (PHYSFS_uint16 *) __PHYSFS_smallAlloc(utf16buflen); + const size_t utf8buflen = utf16len * 4; + char *utf8 = (char *) __PHYSFS_smallAlloc(utf8buflen); + int retval = 0; + + if (utf16 && utf8) + { + const int isdir = SzArEx_IsDir(&info->db, idx) != 0; + SZIPentry *entry; + SzArEx_GetFileNameUtf16(&info->db, idx, (UInt16 *) utf16); + PHYSFS_utf8FromUtf16(utf16, utf8, utf8buflen); + entry = (SZIPentry*) __PHYSFS_DirTreeAdd(&info->tree, utf8, isdir); + retval = (entry != NULL); + if (retval) + entry->dbidx = idx; + } /* if */ + + __PHYSFS_smallFree(utf8); + __PHYSFS_smallFree(utf16); + + return retval; +} /* szipLoadEntry */ + + +static int szipLoadEntries(SZIPinfo *info) +{ + int retval = 0; + + if (__PHYSFS_DirTreeInit(&info->tree, sizeof (SZIPentry))) + { + const PHYSFS_uint32 count = info->db.NumFiles; + PHYSFS_uint32 i; + for (i = 0; i < count; i++) + BAIL_IF_ERRPASS(!szipLoadEntry(info, i), 0); + retval = 1; + } /* if */ + + return retval; +} /* szipLoadEntries */ + + +static void SZIP_closeArchive(void *opaque) +{ + SZIPinfo *info = (SZIPinfo *) opaque; + if (info) + { + if (info->io) + info->io->destroy(info->io); + SzArEx_Free(&info->db, &SZIP_SzAlloc); + __PHYSFS_DirTreeDeinit(&info->tree); + allocator.Free(info); + } /* if */ +} /* SZIP_closeArchive */ + + +static void *SZIP_openArchive(PHYSFS_Io *io, const char *name, + int forWriting, int *claimed) +{ + static const PHYSFS_uint8 wantedsig[] = { '7','z',0xBC,0xAF,0x27,0x1C }; + SZIPLookToRead stream; + ISzAlloc *alloc = &SZIP_SzAlloc; + SZIPinfo *info = NULL; + SRes rc; + PHYSFS_uint8 sig[6]; + PHYSFS_sint64 pos; + + BAIL_IF(forWriting, PHYSFS_ERR_READ_ONLY, NULL); + pos = io->tell(io); + BAIL_IF_ERRPASS(pos == -1, NULL); + BAIL_IF_ERRPASS(io->read(io, sig, 6) != 6, NULL); + *claimed = (memcmp(sig, wantedsig, 6) == 0); + BAIL_IF_ERRPASS(!io->seek(io, pos), NULL); + + info = (SZIPinfo *) allocator.Malloc(sizeof (SZIPinfo)); + BAIL_IF(!info, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + memset(info, '\0', sizeof (*info)); + + SzArEx_Init(&info->db); + + info->io = io; + + szipInitStream(&stream, io); + rc = SzArEx_Open(&info->db, &stream.lookStream.s, alloc, alloc); + GOTO_IF(rc != SZ_OK, szipErrorCode(rc), failed); + + GOTO_IF_ERRPASS(!szipLoadEntries(info), failed); + + return info; + +failed: + info->io = NULL; /* don't let cleanup destroy the PHYSFS_Io. */ + SZIP_closeArchive(info); + return NULL; +} /* SZIP_openArchive */ + + +static PHYSFS_Io *SZIP_openRead(void *opaque, const char *path) +{ + /* !!! FIXME: the current lzma sdk C API only allows you to decompress + !!! FIXME: the entire file at once, which isn't ideal. Fix this in the + !!! FIXME: SDK and then convert this all to a streaming interface. */ + + SZIPinfo *info = (SZIPinfo *) opaque; + SZIPentry *entry = (SZIPentry *) __PHYSFS_DirTreeFind(&info->tree, path); + ISzAlloc *alloc = &SZIP_SzAlloc; + SZIPLookToRead stream; + PHYSFS_Io *retval = NULL; + PHYSFS_Io *io = NULL; + UInt32 blockIndex = 0xFFFFFFFF; + Byte *outBuffer = NULL; + size_t outBufferSize = 0; + size_t offset = 0; + size_t outSizeProcessed = 0; + void *buf = NULL; + SRes rc; + + BAIL_IF_ERRPASS(!entry, NULL); + BAIL_IF(entry->tree.isdir, PHYSFS_ERR_NOT_A_FILE, NULL); + + io = info->io->duplicate(info->io); + GOTO_IF_ERRPASS(!io, SZIP_openRead_failed); + + szipInitStream(&stream, io); + + rc = SzArEx_Extract(&info->db, &stream.lookStream.s, entry->dbidx, + &blockIndex, &outBuffer, &outBufferSize, &offset, + &outSizeProcessed, alloc, alloc); + GOTO_IF(rc != SZ_OK, szipErrorCode(rc), SZIP_openRead_failed); + + io->destroy(io); + io = NULL; + + buf = allocator.Malloc(outSizeProcessed); + GOTO_IF(rc != SZ_OK, PHYSFS_ERR_OUT_OF_MEMORY, SZIP_openRead_failed); + memcpy(buf, outBuffer + offset, outSizeProcessed); + + alloc->Free(alloc, outBuffer); + outBuffer = NULL; + + retval = __PHYSFS_createMemoryIo(buf, outSizeProcessed, allocator.Free); + GOTO_IF_ERRPASS(!retval, SZIP_openRead_failed); + + return retval; + +SZIP_openRead_failed: + if (io != NULL) + io->destroy(io); + + if (buf) + allocator.Free(buf); + + if (outBuffer) + alloc->Free(alloc, outBuffer); + + return NULL; +} /* SZIP_openRead */ + + +static PHYSFS_Io *SZIP_openWrite(void *opaque, const char *filename) +{ + BAIL(PHYSFS_ERR_READ_ONLY, NULL); +} /* SZIP_openWrite */ + + +static PHYSFS_Io *SZIP_openAppend(void *opaque, const char *filename) +{ + BAIL(PHYSFS_ERR_READ_ONLY, NULL); +} /* SZIP_openAppend */ + + +static int SZIP_remove(void *opaque, const char *name) +{ + BAIL(PHYSFS_ERR_READ_ONLY, 0); +} /* SZIP_remove */ + + +static int SZIP_mkdir(void *opaque, const char *name) +{ + BAIL(PHYSFS_ERR_READ_ONLY, 0); +} /* SZIP_mkdir */ + + +static inline PHYSFS_uint64 lzmasdkTimeToPhysfsTime(const CNtfsFileTime *t) +{ + const PHYSFS_uint64 winEpochToUnixEpoch = __PHYSFS_UI64(0x019DB1DED53E8000); + const PHYSFS_uint64 nanosecToMillisec = __PHYSFS_UI64(10000000); + const PHYSFS_uint64 quad = (((PHYSFS_uint64) t->High) << 32) | t->Low; + return (quad - winEpochToUnixEpoch) / nanosecToMillisec; +} /* lzmasdkTimeToPhysfsTime */ + + +static int SZIP_stat(void *opaque, const char *path, PHYSFS_Stat *stat) +{ + SZIPinfo *info = (SZIPinfo *) opaque; + SZIPentry *entry; + PHYSFS_uint32 idx; + + entry = (SZIPentry *) __PHYSFS_DirTreeFind(&info->tree, path); + BAIL_IF_ERRPASS(!entry, 0); + idx = entry->dbidx; + + if (entry->tree.isdir) + { + stat->filesize = -1; + stat->filetype = PHYSFS_FILETYPE_DIRECTORY; + } /* if */ + else + { + stat->filesize = (PHYSFS_sint64) SzArEx_GetFileSize(&info->db, idx); + stat->filetype = PHYSFS_FILETYPE_REGULAR; + } /* else */ + + if (info->db.MTime.Vals != NULL) + stat->modtime = lzmasdkTimeToPhysfsTime(&info->db.MTime.Vals[idx]); + else if (info->db.CTime.Vals != NULL) + stat->modtime = lzmasdkTimeToPhysfsTime(&info->db.CTime.Vals[idx]); + else + stat->modtime = -1; + + if (info->db.CTime.Vals != NULL) + stat->createtime = lzmasdkTimeToPhysfsTime(&info->db.CTime.Vals[idx]); + else if (info->db.MTime.Vals != NULL) + stat->createtime = lzmasdkTimeToPhysfsTime(&info->db.MTime.Vals[idx]); + else + stat->createtime = -1; + + stat->accesstime = -1; + stat->readonly = 1; + + return 1; +} /* SZIP_stat */ + + +void SZIP_global_init(void) +{ + /* this just needs to calculate some things, so it only ever + has to run once, even after a deinit. */ + static int generatedTable = 0; + if (!generatedTable) + { + generatedTable = 1; + CrcGenerateTable(); + } /* if */ +} /* SZIP_global_init */ + + +const PHYSFS_Archiver __PHYSFS_Archiver_7Z = +{ + CURRENT_PHYSFS_ARCHIVER_API_VERSION, + { + "7Z", + "7zip archives", + "Ryan C. Gordon ", + "https://icculus.org/physfs/", + 0, /* supportsSymlinks */ + }, + SZIP_openArchive, + __PHYSFS_DirTreeEnumerate, + SZIP_openRead, + SZIP_openWrite, + SZIP_openAppend, + SZIP_remove, + SZIP_mkdir, + SZIP_stat, + SZIP_closeArchive +}; + +#endif /* defined PHYSFS_SUPPORTS_7Z */ + +/* end of physfs_archiver_7z.c ... */ + diff --git a/Source/Dependencies/PhysicsFS/physfs_archiver_dir.c b/Source/Dependencies/PhysicsFS/physfs_archiver_dir.c new file mode 100644 index 000000000..61c0da3f1 --- /dev/null +++ b/Source/Dependencies/PhysicsFS/physfs_archiver_dir.c @@ -0,0 +1,196 @@ +/* + * Standard directory I/O support routines for PhysicsFS. + * + * Please see the file LICENSE.txt in the source's root directory. + * + * This file written by Ryan C. Gordon. + */ + +#define __PHYSICSFS_INTERNAL__ +#include "physfs_internal.h" + +/* There's no PHYSFS_Io interface here. Use __PHYSFS_createNativeIo(). */ + + + +static char *cvtToDependent(const char *prepend, const char *path, + char *buf, const size_t buflen) +{ + BAIL_IF(buf == NULL, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + snprintf(buf, buflen, "%s%s", prepend ? prepend : "", path); + + #if !__PHYSFS_STANDARD_DIRSEP + assert(__PHYSFS_platformDirSeparator != '/'); + { + char *p; + for (p = strchr(buf, '/'); p != NULL; p = strchr(p + 1, '/')) + *p = __PHYSFS_platformDirSeparator; + } /* if */ + #endif + + return buf; +} /* cvtToDependent */ + + +#define CVT_TO_DEPENDENT(buf, pre, dir) { \ + const size_t len = ((pre) ? strlen((char *) pre) : 0) + strlen(dir) + 1; \ + buf = cvtToDependent((char*)pre,dir,(char*)__PHYSFS_smallAlloc(len),len); \ +} + + + +static void *DIR_openArchive(PHYSFS_Io *io, const char *name, + int forWriting, int *claimed) +{ + PHYSFS_Stat st; + const char dirsep = __PHYSFS_platformDirSeparator; + char *retval = NULL; + const size_t namelen = strlen(name); + const size_t seplen = 1; + + assert(io == NULL); /* shouldn't create an Io for these. */ + BAIL_IF_ERRPASS(!__PHYSFS_platformStat(name, &st, 1), NULL); + + if (st.filetype != PHYSFS_FILETYPE_DIRECTORY) + BAIL(PHYSFS_ERR_UNSUPPORTED, NULL); + + *claimed = 1; + retval = allocator.Malloc(namelen + seplen + 1); + BAIL_IF(retval == NULL, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + + strcpy(retval, name); + + /* make sure there's a dir separator at the end of the string */ + if (retval[namelen - 1] != dirsep) + { + retval[namelen] = dirsep; + retval[namelen + 1] = '\0'; + } /* if */ + + return retval; +} /* DIR_openArchive */ + + +static PHYSFS_EnumerateCallbackResult DIR_enumerate(void *opaque, + const char *dname, PHYSFS_EnumerateCallback cb, + const char *origdir, void *callbackdata) +{ + char *d; + PHYSFS_EnumerateCallbackResult retval; + CVT_TO_DEPENDENT(d, opaque, dname); + BAIL_IF_ERRPASS(!d, PHYSFS_ENUM_ERROR); + retval = __PHYSFS_platformEnumerate(d, cb, origdir, callbackdata); + __PHYSFS_smallFree(d); + return retval; +} /* DIR_enumerate */ + + +static PHYSFS_Io *doOpen(void *opaque, const char *name, const int mode) +{ + PHYSFS_Io *io = NULL; + char *f = NULL; + + CVT_TO_DEPENDENT(f, opaque, name); + BAIL_IF_ERRPASS(!f, NULL); + + io = __PHYSFS_createNativeIo(f, mode); + if (io == NULL) + { + const PHYSFS_ErrorCode err = PHYSFS_getLastErrorCode(); + PHYSFS_Stat statbuf; + __PHYSFS_platformStat(f, &statbuf, 0); /* !!! FIXME: why are we stating here? */ + PHYSFS_setErrorCode(err); + } /* if */ + + __PHYSFS_smallFree(f); + + return io; +} /* doOpen */ + + +static PHYSFS_Io *DIR_openRead(void *opaque, const char *filename) +{ + return doOpen(opaque, filename, 'r'); +} /* DIR_openRead */ + + +static PHYSFS_Io *DIR_openWrite(void *opaque, const char *filename) +{ + return doOpen(opaque, filename, 'w'); +} /* DIR_openWrite */ + + +static PHYSFS_Io *DIR_openAppend(void *opaque, const char *filename) +{ + return doOpen(opaque, filename, 'a'); +} /* DIR_openAppend */ + + +static int DIR_remove(void *opaque, const char *name) +{ + int retval; + char *f; + + CVT_TO_DEPENDENT(f, opaque, name); + BAIL_IF_ERRPASS(!f, 0); + retval = __PHYSFS_platformDelete(f); + __PHYSFS_smallFree(f); + return retval; +} /* DIR_remove */ + + +static int DIR_mkdir(void *opaque, const char *name) +{ + int retval; + char *f; + + CVT_TO_DEPENDENT(f, opaque, name); + BAIL_IF_ERRPASS(!f, 0); + retval = __PHYSFS_platformMkDir(f); + __PHYSFS_smallFree(f); + return retval; +} /* DIR_mkdir */ + + +static void DIR_closeArchive(void *opaque) +{ + allocator.Free(opaque); +} /* DIR_closeArchive */ + + +static int DIR_stat(void *opaque, const char *name, PHYSFS_Stat *stat) +{ + int retval = 0; + char *d; + + CVT_TO_DEPENDENT(d, opaque, name); + BAIL_IF_ERRPASS(!d, 0); + retval = __PHYSFS_platformStat(d, stat, 0); + __PHYSFS_smallFree(d); + return retval; +} /* DIR_stat */ + + +const PHYSFS_Archiver __PHYSFS_Archiver_DIR = +{ + CURRENT_PHYSFS_ARCHIVER_API_VERSION, + { + "", + "Non-archive, direct filesystem I/O", + "Ryan C. Gordon ", + "https://icculus.org/physfs/", + 1, /* supportsSymlinks */ + }, + DIR_openArchive, + DIR_enumerate, + DIR_openRead, + DIR_openWrite, + DIR_openAppend, + DIR_remove, + DIR_mkdir, + DIR_stat, + DIR_closeArchive +}; + +/* end of physfs_archiver_dir.c ... */ + diff --git a/Source/Dependencies/PhysicsFS/physfs_archiver_grp.c b/Source/Dependencies/PhysicsFS/physfs_archiver_grp.c new file mode 100644 index 000000000..758475e49 --- /dev/null +++ b/Source/Dependencies/PhysicsFS/physfs_archiver_grp.c @@ -0,0 +1,116 @@ +/* + * GRP support routines for PhysicsFS. + * + * This driver handles BUILD engine archives ("groupfiles"). This format + * (but not this driver) was put together by Ken Silverman. + * + * The format is simple enough. In Ken's words: + * + * What's the .GRP file format? + * + * The ".grp" file format is just a collection of a lot of files stored + * into 1 big one. I tried to make the format as simple as possible: The + * first 12 bytes contains my name, "KenSilverman". The next 4 bytes is + * the number of files that were compacted into the group file. Then for + * each file, there is a 16 byte structure, where the first 12 bytes are + * the filename, and the last 4 bytes are the file's size. The rest of + * the group file is just the raw data packed one after the other in the + * same order as the list of files. + * + * (That info is from http://www.advsys.net/ken/build.htm ...) + * + * Please see the file LICENSE.txt in the source's root directory. + * + * This file written by Ryan C. Gordon. + */ + +#define __PHYSICSFS_INTERNAL__ +#include "physfs_internal.h" + +#if PHYSFS_SUPPORTS_GRP + +static int grpLoadEntries(PHYSFS_Io *io, const PHYSFS_uint32 count, void *arc) +{ + PHYSFS_uint32 pos = 16 + (16 * count); /* past sig+metadata. */ + PHYSFS_uint32 i; + + for (i = 0; i < count; i++) + { + char *ptr; + char name[13]; + PHYSFS_uint32 size; + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, name, 12), 0); + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &size, 4), 0); + + name[12] = '\0'; /* name isn't null-terminated in file. */ + if ((ptr = strchr(name, ' ')) != NULL) + *ptr = '\0'; /* trim extra spaces. */ + + size = PHYSFS_swapULE32(size); + BAIL_IF_ERRPASS(!UNPK_addEntry(arc, name, 0, -1, -1, pos, size), 0); + + pos += size; + } /* for */ + + return 1; +} /* grpLoadEntries */ + + +static void *GRP_openArchive(PHYSFS_Io *io, const char *name, + int forWriting, int *claimed) +{ + PHYSFS_uint8 buf[12]; + PHYSFS_uint32 count = 0; + void *unpkarc = NULL; + + assert(io != NULL); /* shouldn't ever happen. */ + + BAIL_IF(forWriting, PHYSFS_ERR_READ_ONLY, NULL); + + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, buf, sizeof (buf)), NULL); + if (memcmp(buf, "KenSilverman", sizeof (buf)) != 0) + BAIL(PHYSFS_ERR_UNSUPPORTED, NULL); + + *claimed = 1; + + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &count, sizeof(count)), NULL); + count = PHYSFS_swapULE32(count); + + unpkarc = UNPK_openArchive(io); + BAIL_IF_ERRPASS(!unpkarc, NULL); + + if (!grpLoadEntries(io, count, unpkarc)) + { + UNPK_abandonArchive(unpkarc); + return NULL; + } /* if */ + + return unpkarc; +} /* GRP_openArchive */ + + +const PHYSFS_Archiver __PHYSFS_Archiver_GRP = +{ + CURRENT_PHYSFS_ARCHIVER_API_VERSION, + { + "GRP", + "Build engine Groupfile format", + "Ryan C. Gordon ", + "https://icculus.org/physfs/", + 0, /* supportsSymlinks */ + }, + GRP_openArchive, + UNPK_enumerate, + UNPK_openRead, + UNPK_openWrite, + UNPK_openAppend, + UNPK_remove, + UNPK_mkdir, + UNPK_stat, + UNPK_closeArchive +}; + +#endif /* defined PHYSFS_SUPPORTS_GRP */ + +/* end of physfs_archiver_grp.c ... */ + diff --git a/Source/Dependencies/PhysicsFS/physfs_archiver_hog.c b/Source/Dependencies/PhysicsFS/physfs_archiver_hog.c new file mode 100644 index 000000000..832209f86 --- /dev/null +++ b/Source/Dependencies/PhysicsFS/physfs_archiver_hog.c @@ -0,0 +1,114 @@ +/* + * HOG support routines for PhysicsFS. + * + * This driver handles Descent I/II HOG archives. + * + * The format is very simple: + * + * The file always starts with the 3-byte signature "DHF" (Descent + * HOG file). After that the files of a HOG are just attached after + * another, divided by a 17 bytes header, which specifies the name + * and length (in bytes) of the forthcoming file! So you just read + * the header with its information of how big the following file is, + * and then skip exact that number of bytes to get to the next file + * in that HOG. + * + * char sig[3] = {'D', 'H', 'F'}; // "DHF"=Descent HOG File + * + * struct { + * char file_name[13]; // Filename, padded to 13 bytes with 0s + * int file_size; // filesize in bytes + * char data[file_size]; // The file data + * } FILE_STRUCT; // Repeated until the end of the file. + * + * (That info is from http://www.descent2.com/ddn/specs/hog/) + * + * Please see the file LICENSE.txt in the source's root directory. + * + * This file written by Bradley Bell. + * Based on grp.c by Ryan C. Gordon. + */ + +#define __PHYSICSFS_INTERNAL__ +#include "physfs_internal.h" + +#if PHYSFS_SUPPORTS_HOG + +static int hogLoadEntries(PHYSFS_Io *io, void *arc) +{ + const PHYSFS_uint64 iolen = io->length(io); + PHYSFS_uint32 pos = 3; + + while (pos < iolen) + { + PHYSFS_uint32 size; + char name[13]; + + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, name, 13), 0); + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &size, 4), 0); + name[12] = '\0'; /* just in case. */ + pos += 13 + 4; + + size = PHYSFS_swapULE32(size); + BAIL_IF_ERRPASS(!UNPK_addEntry(arc, name, 0, -1, -1, pos, size), 0); + pos += size; + + /* skip over entry */ + BAIL_IF_ERRPASS(!io->seek(io, pos), 0); + } /* while */ + + return 1; +} /* hogLoadEntries */ + + +static void *HOG_openArchive(PHYSFS_Io *io, const char *name, + int forWriting, int *claimed) +{ + PHYSFS_uint8 buf[3]; + void *unpkarc = NULL; + + assert(io != NULL); /* shouldn't ever happen. */ + BAIL_IF(forWriting, PHYSFS_ERR_READ_ONLY, NULL); + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, buf, 3), NULL); + BAIL_IF(memcmp(buf, "DHF", 3) != 0, PHYSFS_ERR_UNSUPPORTED, NULL); + + *claimed = 1; + + unpkarc = UNPK_openArchive(io); + BAIL_IF_ERRPASS(!unpkarc, NULL); + + if (!hogLoadEntries(io, unpkarc)) + { + UNPK_abandonArchive(unpkarc); + return NULL; + } /* if */ + + return unpkarc; +} /* HOG_openArchive */ + + +const PHYSFS_Archiver __PHYSFS_Archiver_HOG = +{ + CURRENT_PHYSFS_ARCHIVER_API_VERSION, + { + "HOG", + "Descent I/II HOG file format", + "Bradley Bell ", + "https://icculus.org/physfs/", + 0, /* supportsSymlinks */ + }, + HOG_openArchive, + UNPK_enumerate, + UNPK_openRead, + UNPK_openWrite, + UNPK_openAppend, + UNPK_remove, + UNPK_mkdir, + UNPK_stat, + UNPK_closeArchive +}; + +#endif /* defined PHYSFS_SUPPORTS_HOG */ + +/* end of physfs_archiver_hog.c ... */ + diff --git a/Source/Dependencies/PhysicsFS/physfs_archiver_iso9660.c b/Source/Dependencies/PhysicsFS/physfs_archiver_iso9660.c new file mode 100644 index 000000000..965c83f65 --- /dev/null +++ b/Source/Dependencies/PhysicsFS/physfs_archiver_iso9660.c @@ -0,0 +1,386 @@ +/* + * ISO9660 support routines for PhysicsFS. + * + * Please see the file LICENSE.txt in the source's root directory. + * + * This file originally written by Christoph Nelles, but was largely + * rewritten by Ryan C. Gordon (so please harass Ryan about bugs and not + * Christoph). + */ + +/* + * Handles CD-ROM disk images (and raw CD-ROM devices). + * + * Not supported: + * - Rock Ridge (needed for sparse files, device nodes and symlinks, etc). + * - Non 2048 Sectors + * - TRANS.TBL (maps 8.3 filenames on old discs to long filenames). + * - Multiextents (4gb max file size without it). + * - UDF + * + * Deviations from the standard + * - Ignores mandatory sort order + * - Allows various invalid file names + * + * Problems + * - Ambiguities in the standard + */ + +#define __PHYSICSFS_INTERNAL__ +#include "physfs_internal.h" + +#if PHYSFS_SUPPORTS_ISO9660 + +#include + +/* ISO9660 often stores values in both big and little endian formats: little + first, followed by big. While technically there might be different values + in each, we just always use the littleendian ones and swap ourselves. The + fields aren't aligned anyhow, so you have to serialize them in any case + to avoid crashes on many CPU archs in any case. */ + +static int iso9660LoadEntries(PHYSFS_Io *io, const int joliet, + const char *base, const PHYSFS_uint64 dirstart, + const PHYSFS_uint64 dirend, void *unpkarc); + +static int iso9660AddEntry(PHYSFS_Io *io, const int joliet, const int isdir, + const char *base, PHYSFS_uint8 *fname, + const int fnamelen, const PHYSFS_sint64 ts, + const PHYSFS_uint64 pos, const PHYSFS_uint64 len, + void *unpkarc) +{ + char *fullpath; + char *fnamecpy; + size_t baselen; + size_t fullpathlen; + void *entry; + int i; + + if (fnamelen == 1 && ((fname[0] == 0) || (fname[0] == 1))) + return 1; /* Magic that represents "." and "..", ignore */ + + BAIL_IF(fnamelen == 0, PHYSFS_ERR_CORRUPT, 0); + assert(fnamelen > 0); + assert(fnamelen <= 255); + BAIL_IF(joliet && (fnamelen % 2), PHYSFS_ERR_CORRUPT, 0); + + /* Joliet is UCS-2, so at most UTF-8 will double the byte size */ + baselen = strlen(base); + fullpathlen = baselen + (fnamelen * (joliet ? 2 : 1)) + 2; + fullpath = (char *) __PHYSFS_smallAlloc(fullpathlen); + BAIL_IF(!fullpath, PHYSFS_ERR_OUT_OF_MEMORY, 0); + fnamecpy = fullpath; + if (baselen > 0) + { + snprintf(fullpath, fullpathlen, "%s/", base); + fnamecpy += baselen + 1; + fullpathlen -= baselen - 1; + } /* if */ + + if (joliet) + { + PHYSFS_uint16 *ucs2 = (PHYSFS_uint16 *) fname; + int total = fnamelen / 2; + for (i = 0; i < total; i++) + ucs2[i] = PHYSFS_swapUBE16(ucs2[i]); + ucs2[total] = '\0'; + PHYSFS_utf8FromUcs2(ucs2, fnamecpy, fullpathlen); + } /* if */ + else + { + for (i = 0; i < fnamelen; i++) + { + /* We assume the filenames are low-ASCII; consider the archive + corrupt if we see something above 127, since we don't know the + encoding. (We can change this later if we find out these exist + and are intended to be, say, latin-1 or UTF-8 encoding). */ + BAIL_IF(fname[i] > 127, PHYSFS_ERR_CORRUPT, 0); + fnamecpy[i] = fname[i]; + } /* for */ + fnamecpy[fnamelen] = '\0'; + + if (!isdir) + { + /* find last SEPARATOR2 */ + char *ptr = strrchr(fnamecpy, ';'); + if (ptr && (ptr != fnamecpy)) + *(ptr--) = '\0'; + else + ptr = fnamecpy + (fnamelen - 1); + + /* chop out any trailing '.', as done in all implementations */ + if (*ptr == '.') + *ptr = '\0'; + } /* if */ + } /* else */ + + entry = UNPK_addEntry(unpkarc, fullpath, isdir, ts, ts, pos, len); + if ((entry) && (isdir)) + { + if (!iso9660LoadEntries(io, joliet, fullpath, pos, pos + len, unpkarc)) + entry = NULL; /* so we report a failure later. */ + } /* if */ + + __PHYSFS_smallFree(fullpath); + return entry != NULL; +} /* iso9660AddEntry */ + +static int iso9660LoadEntries(PHYSFS_Io *io, const int joliet, + const char *base, const PHYSFS_uint64 dirstart, + const PHYSFS_uint64 dirend, void *unpkarc) +{ + PHYSFS_uint64 readpos = dirstart; + + while (1) + { + PHYSFS_uint8 recordlen; + PHYSFS_uint8 extattrlen; + PHYSFS_uint32 extent; + PHYSFS_uint32 datalen; + PHYSFS_uint8 ignore[4]; + PHYSFS_uint8 year, month, day, hour, minute, second, offset; + PHYSFS_uint8 flags; + PHYSFS_uint8 fnamelen; + PHYSFS_uint8 fname[256]; + PHYSFS_sint64 timestamp; + struct tm t; + int isdir; + int multiextent; + + BAIL_IF_ERRPASS(!io->seek(io, readpos), 0); + + /* recordlen = 0 -> no more entries or fill entry */ + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &recordlen, 1), 0); + if (recordlen > 0) + readpos += recordlen; /* ready to seek to next record. */ + else + { + PHYSFS_uint64 nextpos; + + /* if we are in the last sector of the directory & it's 0 -> end */ + if ((dirend - 2048) <= (readpos - 1)) + break; /* finished */ + + /* else skip to the next sector & continue; */ + nextpos = (((readpos - 1) / 2048) + 1) * 2048; + + /* whoops, can't make forward progress! */ + BAIL_IF(nextpos == readpos, PHYSFS_ERR_CORRUPT, 0); + + readpos = nextpos; + continue; /* start back at upper loop. */ + } /* else */ + + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &extattrlen, 1), 0); + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &extent, 4), 0); + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* extent be */ + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &datalen, 4), 0); + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* datalen be */ + + /* record timestamp */ + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &year, 1), 0); + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &month, 1), 0); + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &day, 1), 0); + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &hour, 1), 0); + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &minute, 1), 0); + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &second, 1), 0); + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &offset, 1), 0); + + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &flags, 1), 0); + isdir = (flags & (1 << 1)) != 0; + multiextent = (flags & (1 << 7)) != 0; + BAIL_IF(multiextent, PHYSFS_ERR_UNSUPPORTED, 0); /* !!! FIXME */ + + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 1), 0); /* unit size */ + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 1), 0); /* interleave gap */ + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 2), 0); /* seqnum le */ + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 2), 0); /* seqnum be */ + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &fnamelen, 1), 0); + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, fname, fnamelen), 0); + + t.tm_sec = second; + t.tm_min = minute; + t.tm_hour = hour; + t.tm_mday = day; + t.tm_mon = month - 1; + t.tm_year = year; + t.tm_wday = 0; + t.tm_yday = 0; + t.tm_isdst = -1; + timestamp = (PHYSFS_sint64) mktime(&t); + + extent += extattrlen; /* skip extended attribute record. */ + + /* infinite loop, corrupt file? */ + BAIL_IF((extent * 2048) == dirstart, PHYSFS_ERR_CORRUPT, 0); + + if (!iso9660AddEntry(io, joliet, isdir, base, fname, fnamelen, + timestamp, extent * 2048, datalen, unpkarc)) + { + return 0; + } /* if */ + } /* while */ + + return 1; +} /* iso9660LoadEntries */ + + +static int parseVolumeDescriptor(PHYSFS_Io *io, PHYSFS_uint64 *_rootpos, + PHYSFS_uint64 *_rootlen, int *_joliet, + int *_claimed) +{ + PHYSFS_uint64 pos = 32768; /* start at the Primary Volume Descriptor */ + int found = 0; + int done = 0; + + *_joliet = 0; + + while (!done) + { + PHYSFS_uint8 type; + PHYSFS_uint8 identifier[5]; + PHYSFS_uint8 version; + PHYSFS_uint8 flags; + PHYSFS_uint8 escapeseqs[32]; + PHYSFS_uint8 ignore[32]; + PHYSFS_uint16 blocksize; + PHYSFS_uint32 extent; + PHYSFS_uint32 datalen; + + BAIL_IF_ERRPASS(!io->seek(io, pos), 0); + pos += 2048; /* each volume descriptor is 2048 bytes */ + + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &type, 1), 0); + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &identifier, 5), 0); + + if (memcmp(identifier, "CD001", 5) != 0) /* maybe not an iso? */ + { + BAIL_IF(!*_claimed, PHYSFS_ERR_UNSUPPORTED, 0); + continue; /* just skip this one */ + } /* if */ + + *_claimed = 1; /* okay, this is probably an iso. */ + + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &version, 1), 0); /* version */ + BAIL_IF(version != 1, PHYSFS_ERR_UNSUPPORTED, 0); + + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &flags, 1), 0); + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 32), 0); /* system id */ + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 32), 0); /* volume id */ + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 8), 0); /* reserved */ + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* space le */ + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* space be */ + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, escapeseqs, 32), 0); + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 2), 0); /* setsize le */ + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 2), 0); /* setsize be */ + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 2), 0); /* seq num le */ + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 2), 0); /* seq num be */ + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &blocksize, 2), 0); + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 2), 0); /* blocklen be */ + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* pthtablen le */ + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* pthtablen be */ + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* pthtabpos le */ + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* optpthtabpos le */ + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* pthtabpos be */ + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* optpthtabpos be */ + + /* root directory record... */ + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 1), 0); /* len */ + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 1), 0); /* attr len */ + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &extent, 4), 0); + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* extent be */ + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &datalen, 4), 0); + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* datalen be */ + + /* !!! FIXME: deal with this properly. */ + blocksize = PHYSFS_swapULE32(blocksize); + BAIL_IF(blocksize && (blocksize != 2048), PHYSFS_ERR_UNSUPPORTED, 0); + + switch (type) + { + case 1: /* Primary Volume Descriptor */ + case 2: /* Supplementary Volume Descriptor */ + if (found < type) + { + *_rootpos = PHYSFS_swapULE32(extent) * 2048; + *_rootlen = PHYSFS_swapULE32(datalen); + found = type; + + if (found == 2) /* possible Joliet volume */ + { + const PHYSFS_uint8 *s = escapeseqs; + *_joliet = !(flags & 1) && + (s[0] == 0x25) && (s[1] == 0x2F) && + ((s[2] == 0x40) || (s[2] == 0x43) || (s[2] == 0x45)); + } /* if */ + } /* if */ + break; + + case 255: /* type 255 terminates the volume descriptor list */ + done = 1; + break; + + default: + break; /* skip unknown types. */ + } /* switch */ + } /* while */ + + BAIL_IF(!found, PHYSFS_ERR_CORRUPT, 0); + + return 1; +} /* parseVolumeDescriptor */ + + +static void *ISO9660_openArchive(PHYSFS_Io *io, const char *filename, + int forWriting, int *claimed) +{ + PHYSFS_uint64 rootpos = 0; + PHYSFS_uint64 len = 0; + int joliet = 0; + void *unpkarc = NULL; + + assert(io != NULL); /* shouldn't ever happen. */ + + BAIL_IF(forWriting, PHYSFS_ERR_READ_ONLY, NULL); + + if (!parseVolumeDescriptor(io, &rootpos, &len, &joliet, claimed)) + return NULL; + + unpkarc = UNPK_openArchive(io); + BAIL_IF_ERRPASS(!unpkarc, NULL); + + if (!iso9660LoadEntries(io, joliet, "", rootpos, rootpos + len, unpkarc)) + { + UNPK_abandonArchive(unpkarc); + return NULL; + } /* if */ + + return unpkarc; +} /* ISO9660_openArchive */ + + +const PHYSFS_Archiver __PHYSFS_Archiver_ISO9660 = +{ + CURRENT_PHYSFS_ARCHIVER_API_VERSION, + { + "ISO", + "ISO9660 image file", + "Ryan C. Gordon ", + "https://icculus.org/physfs/", + 0, /* supportsSymlinks */ + }, + ISO9660_openArchive, + UNPK_enumerate, + UNPK_openRead, + UNPK_openWrite, + UNPK_openAppend, + UNPK_remove, + UNPK_mkdir, + UNPK_stat, + UNPK_closeArchive +}; + +#endif /* defined PHYSFS_SUPPORTS_ISO9660 */ + +/* end of physfs_archiver_iso9660.c ... */ + diff --git a/Source/Dependencies/PhysicsFS/physfs_archiver_mvl.c b/Source/Dependencies/PhysicsFS/physfs_archiver_mvl.c new file mode 100644 index 000000000..78b59f1a1 --- /dev/null +++ b/Source/Dependencies/PhysicsFS/physfs_archiver_mvl.c @@ -0,0 +1,110 @@ +/* + * MVL support routines for PhysicsFS. + * + * This driver handles Descent II Movielib archives. + * + * The file format of MVL is quite easy... + * + * //MVL File format - Written by Heiko Herrmann + * char sig[4] = {'D','M', 'V', 'L'}; // "DMVL"=Descent MoVie Library + * + * int num_files; // the number of files in this MVL + * + * struct { + * char file_name[13]; // Filename, padded to 13 bytes with 0s + * int file_size; // filesize in bytes + * }DIR_STRUCT[num_files]; + * + * struct { + * char data[file_size]; // The file data + * }FILE_STRUCT[num_files]; + * + * (That info is from http://www.descent2.com/ddn/specs/mvl/) + * + * Please see the file LICENSE.txt in the source's root directory. + * + * This file written by Bradley Bell. + * Based on grp.c by Ryan C. Gordon. + */ + +#define __PHYSICSFS_INTERNAL__ +#include "physfs_internal.h" + +#if PHYSFS_SUPPORTS_MVL + +static int mvlLoadEntries(PHYSFS_Io *io, const PHYSFS_uint32 count, void *arc) +{ + PHYSFS_uint32 pos = 8 + (17 * count); /* past sig+metadata. */ + PHYSFS_uint32 i; + + for (i = 0; i < count; i++) + { + PHYSFS_uint32 size; + char name[13]; + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, name, 13), 0); + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &size, 4), 0); + name[12] = '\0'; /* just in case. */ + size = PHYSFS_swapULE32(size); + BAIL_IF_ERRPASS(!UNPK_addEntry(arc, name, 0, -1, -1, pos, size), 0); + pos += size; + } /* for */ + + return 1; +} /* mvlLoadEntries */ + + +static void *MVL_openArchive(PHYSFS_Io *io, const char *name, + int forWriting, int *claimed) +{ + PHYSFS_uint8 buf[4]; + PHYSFS_uint32 count = 0; + void *unpkarc; + + assert(io != NULL); /* shouldn't ever happen. */ + BAIL_IF(forWriting, PHYSFS_ERR_READ_ONLY, NULL); + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, buf, 4), NULL); + BAIL_IF(memcmp(buf, "DMVL", 4) != 0, PHYSFS_ERR_UNSUPPORTED, NULL); + + *claimed = 1; + + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &count, sizeof(count)), NULL); + count = PHYSFS_swapULE32(count); + + unpkarc = UNPK_openArchive(io); + BAIL_IF_ERRPASS(!unpkarc, NULL); + + if (!mvlLoadEntries(io, count, unpkarc)) + { + UNPK_abandonArchive(unpkarc); + return NULL; + } /* if */ + + return unpkarc; +} /* MVL_openArchive */ + + +const PHYSFS_Archiver __PHYSFS_Archiver_MVL = +{ + CURRENT_PHYSFS_ARCHIVER_API_VERSION, + { + "MVL", + "Descent II Movielib format", + "Bradley Bell ", + "https://icculus.org/physfs/", + 0, /* supportsSymlinks */ + }, + MVL_openArchive, + UNPK_enumerate, + UNPK_openRead, + UNPK_openWrite, + UNPK_openAppend, + UNPK_remove, + UNPK_mkdir, + UNPK_stat, + UNPK_closeArchive +}; + +#endif /* defined PHYSFS_SUPPORTS_MVL */ + +/* end of physfs_archiver_mvl.c ... */ + diff --git a/Source/Dependencies/PhysicsFS/physfs_archiver_qpak.c b/Source/Dependencies/PhysicsFS/physfs_archiver_qpak.c new file mode 100644 index 000000000..15a5f2dfe --- /dev/null +++ b/Source/Dependencies/PhysicsFS/physfs_archiver_qpak.c @@ -0,0 +1,126 @@ +/* + * QPAK support routines for PhysicsFS. + * + * This archiver handles the archive format utilized by Quake 1 and 2. + * Quake3-based games use the PkZip/Info-Zip format (which our + * physfs_archiver_zip.c handles). + * + * ======================================================================== + * + * This format info (in more detail) comes from: + * https://web.archive.org/web/20040209101748/http://debian.fmi.uni-sofia.bg/~sergei/cgsr/docs/pak.txt + * + * Quake PAK Format + * + * Header + * (4 bytes) signature = 'PACK' + * (4 bytes) directory offset + * (4 bytes) directory length + * + * Directory + * (56 bytes) file name + * (4 bytes) file position + * (4 bytes) file length + * + * ======================================================================== + * + * Please see the file LICENSE.txt in the source's root directory. + * + * This file written by Ryan C. Gordon. + */ + +#define __PHYSICSFS_INTERNAL__ +#include "physfs_internal.h" + +#if PHYSFS_SUPPORTS_QPAK + +#define QPAK_SIG 0x4B434150 /* "PACK" in ASCII. */ + +static int qpakLoadEntries(PHYSFS_Io *io, const PHYSFS_uint32 count, void *arc) +{ + PHYSFS_uint32 i; + for (i = 0; i < count; i++) + { + PHYSFS_uint32 size; + PHYSFS_uint32 pos; + char name[56]; + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, name, 56), 0); + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &pos, 4), 0); + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &size, 4), 0); + size = PHYSFS_swapULE32(size); + pos = PHYSFS_swapULE32(pos); + BAIL_IF_ERRPASS(!UNPK_addEntry(arc, name, 0, -1, -1, pos, size), 0); + } /* for */ + + return 1; +} /* qpakLoadEntries */ + + +static void *QPAK_openArchive(PHYSFS_Io *io, const char *name, + int forWriting, int *claimed) +{ + PHYSFS_uint32 val = 0; + PHYSFS_uint32 pos = 0; + PHYSFS_uint32 count = 0; + void *unpkarc; + + assert(io != NULL); /* shouldn't ever happen. */ + + BAIL_IF(forWriting, PHYSFS_ERR_READ_ONLY, NULL); + + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &val, 4), NULL); + if (PHYSFS_swapULE32(val) != QPAK_SIG) + BAIL(PHYSFS_ERR_UNSUPPORTED, NULL); + + *claimed = 1; + + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &val, 4), NULL); + pos = PHYSFS_swapULE32(val); /* directory table offset. */ + + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &val, 4), NULL); + count = PHYSFS_swapULE32(val); + + /* corrupted archive? */ + BAIL_IF((count % 64) != 0, PHYSFS_ERR_CORRUPT, NULL); + count /= 64; + + BAIL_IF_ERRPASS(!io->seek(io, pos), NULL); + + unpkarc = UNPK_openArchive(io); + BAIL_IF_ERRPASS(!unpkarc, NULL); + + if (!qpakLoadEntries(io, count, unpkarc)) + { + UNPK_abandonArchive(unpkarc); + return NULL; + } /* if */ + + return unpkarc; +} /* QPAK_openArchive */ + + +const PHYSFS_Archiver __PHYSFS_Archiver_QPAK = +{ + CURRENT_PHYSFS_ARCHIVER_API_VERSION, + { + "PAK", + "Quake I/II format", + "Ryan C. Gordon ", + "https://icculus.org/physfs/", + 0, /* supportsSymlinks */ + }, + QPAK_openArchive, + UNPK_enumerate, + UNPK_openRead, + UNPK_openWrite, + UNPK_openAppend, + UNPK_remove, + UNPK_mkdir, + UNPK_stat, + UNPK_closeArchive +}; + +#endif /* defined PHYSFS_SUPPORTS_QPAK */ + +/* end of physfs_archiver_qpak.c ... */ + diff --git a/Source/Dependencies/PhysicsFS/physfs_archiver_slb.c b/Source/Dependencies/PhysicsFS/physfs_archiver_slb.c new file mode 100644 index 000000000..4fc28d40c --- /dev/null +++ b/Source/Dependencies/PhysicsFS/physfs_archiver_slb.c @@ -0,0 +1,135 @@ +/* + * SLB support routines for PhysicsFS. + * + * This driver handles SLB archives ("slab files"). This uncompressed format + * is used in I-War / Independence War and Independence War: Defiance. + * + * The format begins with four zero bytes (version?), the file count and the + * location of the table of contents. Each ToC entry contains a 64-byte buffer + * containing a zero-terminated filename, the offset of the data, and its size. + * All the filenames begin with the separator character '\'. + * + * Please see the file LICENSE.txt in the source's root directory. + * + * This file written by Aleksi Nurmi, based on the GRP archiver by + * Ryan C. Gordon. + */ + +#define __PHYSICSFS_INTERNAL__ +#include "physfs_internal.h" + +#if PHYSFS_SUPPORTS_SLB + +static int slbLoadEntries(PHYSFS_Io *io, const PHYSFS_uint32 count, void *arc) +{ + PHYSFS_uint32 i; + for (i = 0; i < count; i++) + { + PHYSFS_uint32 pos; + PHYSFS_uint32 size; + char name[64]; + char backslash; + char *ptr; + + /* don't include the '\' in the beginning */ + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &backslash, 1), 0); + BAIL_IF(backslash != '\\', PHYSFS_ERR_CORRUPT, 0); + + /* read the rest of the buffer, 63 bytes */ + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &name, 63), 0); + name[63] = '\0'; /* in case the name lacks the null terminator */ + + /* convert backslashes */ + for (ptr = name; *ptr; ptr++) + { + if (*ptr == '\\') + *ptr = '/'; + } /* for */ + + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &pos, 4), 0); + pos = PHYSFS_swapULE32(pos); + + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &size, 4), 0); + size = PHYSFS_swapULE32(size); + + BAIL_IF_ERRPASS(!UNPK_addEntry(arc, name, 0, -1, -1, pos, size), 0); + } /* for */ + + return 1; +} /* slbLoadEntries */ + + +static void *SLB_openArchive(PHYSFS_Io *io, const char *name, + int forWriting, int *claimed) +{ + PHYSFS_uint32 version; + PHYSFS_uint32 count; + PHYSFS_uint32 tocPos; + void *unpkarc; + + /* There's no identifier on an SLB file, so we assume it's _not_ if the + file count or tocPos is zero. Beyond that, we'll assume it's + bogus/corrupt if the entries' filenames don't start with '\' or the + tocPos is past the end of the file (seek will fail). This probably + covers all meaningful cases where we would accidentally accept a non-SLB + file with this archiver. */ + + assert(io != NULL); /* shouldn't ever happen. */ + + BAIL_IF(forWriting, PHYSFS_ERR_READ_ONLY, NULL); + + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &version, sizeof (version)), NULL); + version = PHYSFS_swapULE32(version); + BAIL_IF(version != 0, PHYSFS_ERR_UNSUPPORTED, NULL); + + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &count, sizeof (count)), NULL); + count = PHYSFS_swapULE32(count); + BAIL_IF(!count, PHYSFS_ERR_UNSUPPORTED, NULL); + + /* offset of the table of contents */ + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &tocPos, sizeof (tocPos)), NULL); + tocPos = PHYSFS_swapULE32(tocPos); + BAIL_IF(!tocPos, PHYSFS_ERR_UNSUPPORTED, NULL); + + /* seek to the table of contents */ + BAIL_IF_ERRPASS(!io->seek(io, tocPos), NULL); + + unpkarc = UNPK_openArchive(io); + BAIL_IF_ERRPASS(!unpkarc, NULL); + + if (!slbLoadEntries(io, count, unpkarc)) + { + UNPK_abandonArchive(unpkarc); + return NULL; + } /* if */ + + *claimed = 1; /* oh well. */ + + return unpkarc; +} /* SLB_openArchive */ + + +const PHYSFS_Archiver __PHYSFS_Archiver_SLB = +{ + CURRENT_PHYSFS_ARCHIVER_API_VERSION, + { + "SLB", + "I-War / Independence War Slab file", + "Aleksi Nurmi ", + "https://bitbucket.org/ahnurmi/", + 0, /* supportsSymlinks */ + }, + SLB_openArchive, + UNPK_enumerate, + UNPK_openRead, + UNPK_openWrite, + UNPK_openAppend, + UNPK_remove, + UNPK_mkdir, + UNPK_stat, + UNPK_closeArchive +}; + +#endif /* defined PHYSFS_SUPPORTS_SLB */ + +/* end of physfs_archiver_slb.c ... */ diff --git a/Source/Dependencies/PhysicsFS/physfs_archiver_unpacked.c b/Source/Dependencies/PhysicsFS/physfs_archiver_unpacked.c new file mode 100644 index 000000000..575efef32 --- /dev/null +++ b/Source/Dependencies/PhysicsFS/physfs_archiver_unpacked.c @@ -0,0 +1,305 @@ +/* + * High-level PhysicsFS archiver for simple unpacked file formats. + * + * This is a framework that basic archivers build on top of. It's for simple + * formats that can just hand back a list of files and the offsets of their + * uncompressed data. There are an alarming number of formats like this. + * + * RULES: Archive entries must be uncompressed. Dirs and files allowed, but no + * symlinks, etc. We can relax some of these rules as necessary. + * + * Please see the file LICENSE.txt in the source's root directory. + * + * This file written by Ryan C. Gordon. + */ + +#define __PHYSICSFS_INTERNAL__ +#include "physfs_internal.h" + +typedef struct +{ + __PHYSFS_DirTree tree; + PHYSFS_Io *io; +} UNPKinfo; + +typedef struct +{ + __PHYSFS_DirTreeEntry tree; + PHYSFS_uint64 startPos; + PHYSFS_uint64 size; + PHYSFS_sint64 ctime; + PHYSFS_sint64 mtime; +} UNPKentry; + +typedef struct +{ + PHYSFS_Io *io; + UNPKentry *entry; + PHYSFS_uint32 curPos; +} UNPKfileinfo; + + +void UNPK_closeArchive(void *opaque) +{ + UNPKinfo *info = ((UNPKinfo *) opaque); + if (info) + { + __PHYSFS_DirTreeDeinit(&info->tree); + + if (info->io) + info->io->destroy(info->io); + + allocator.Free(info); + } /* if */ +} /* UNPK_closeArchive */ + +void UNPK_abandonArchive(void *opaque) +{ + UNPKinfo *info = ((UNPKinfo *) opaque); + if (info) + { + info->io = NULL; + UNPK_closeArchive(info); + } /* if */ +} /* UNPK_abandonArchive */ + +static PHYSFS_sint64 UNPK_read(PHYSFS_Io *io, void *buffer, PHYSFS_uint64 len) +{ + UNPKfileinfo *finfo = (UNPKfileinfo *) io->opaque; + const UNPKentry *entry = finfo->entry; + const PHYSFS_uint64 bytesLeft = (PHYSFS_uint64)(entry->size-finfo->curPos); + PHYSFS_sint64 rc; + + if (bytesLeft < len) + len = bytesLeft; + + rc = finfo->io->read(finfo->io, buffer, len); + if (rc > 0) + finfo->curPos += (PHYSFS_uint32) rc; + + return rc; +} /* UNPK_read */ + + +static PHYSFS_sint64 UNPK_write(PHYSFS_Io *io, const void *b, PHYSFS_uint64 len) +{ + BAIL(PHYSFS_ERR_READ_ONLY, -1); +} /* UNPK_write */ + + +static PHYSFS_sint64 UNPK_tell(PHYSFS_Io *io) +{ + return ((UNPKfileinfo *) io->opaque)->curPos; +} /* UNPK_tell */ + + +static int UNPK_seek(PHYSFS_Io *io, PHYSFS_uint64 offset) +{ + UNPKfileinfo *finfo = (UNPKfileinfo *) io->opaque; + const UNPKentry *entry = finfo->entry; + int rc; + + BAIL_IF(offset >= entry->size, PHYSFS_ERR_PAST_EOF, 0); + rc = finfo->io->seek(finfo->io, entry->startPos + offset); + if (rc) + finfo->curPos = (PHYSFS_uint32) offset; + + return rc; +} /* UNPK_seek */ + + +static PHYSFS_sint64 UNPK_length(PHYSFS_Io *io) +{ + const UNPKfileinfo *finfo = (UNPKfileinfo *) io->opaque; + return ((PHYSFS_sint64) finfo->entry->size); +} /* UNPK_length */ + + +static PHYSFS_Io *UNPK_duplicate(PHYSFS_Io *_io) +{ + UNPKfileinfo *origfinfo = (UNPKfileinfo *) _io->opaque; + PHYSFS_Io *io = NULL; + PHYSFS_Io *retval = (PHYSFS_Io *) allocator.Malloc(sizeof (PHYSFS_Io)); + UNPKfileinfo *finfo = (UNPKfileinfo *) allocator.Malloc(sizeof (UNPKfileinfo)); + GOTO_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, UNPK_duplicate_failed); + GOTO_IF(!finfo, PHYSFS_ERR_OUT_OF_MEMORY, UNPK_duplicate_failed); + + io = origfinfo->io->duplicate(origfinfo->io); + if (!io) goto UNPK_duplicate_failed; + finfo->io = io; + finfo->entry = origfinfo->entry; + finfo->curPos = 0; + memcpy(retval, _io, sizeof (PHYSFS_Io)); + retval->opaque = finfo; + return retval; + +UNPK_duplicate_failed: + if (finfo != NULL) allocator.Free(finfo); + if (retval != NULL) allocator.Free(retval); + if (io != NULL) io->destroy(io); + return NULL; +} /* UNPK_duplicate */ + +static int UNPK_flush(PHYSFS_Io *io) { return 1; /* no write support. */ } + +static void UNPK_destroy(PHYSFS_Io *io) +{ + UNPKfileinfo *finfo = (UNPKfileinfo *) io->opaque; + finfo->io->destroy(finfo->io); + allocator.Free(finfo); + allocator.Free(io); +} /* UNPK_destroy */ + + +static const PHYSFS_Io UNPK_Io = +{ + CURRENT_PHYSFS_IO_API_VERSION, NULL, + UNPK_read, + UNPK_write, + UNPK_seek, + UNPK_tell, + UNPK_length, + UNPK_duplicate, + UNPK_flush, + UNPK_destroy +}; + + +static inline UNPKentry *findEntry(UNPKinfo *info, const char *path) +{ + return (UNPKentry *) __PHYSFS_DirTreeFind(&info->tree, path); +} /* findEntry */ + + +PHYSFS_Io *UNPK_openRead(void *opaque, const char *name) +{ + PHYSFS_Io *retval = NULL; + UNPKinfo *info = (UNPKinfo *) opaque; + UNPKfileinfo *finfo = NULL; + UNPKentry *entry = findEntry(info, name); + + BAIL_IF_ERRPASS(!entry, NULL); + BAIL_IF(entry->tree.isdir, PHYSFS_ERR_NOT_A_FILE, NULL); + + retval = (PHYSFS_Io *) allocator.Malloc(sizeof (PHYSFS_Io)); + GOTO_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, UNPK_openRead_failed); + + finfo = (UNPKfileinfo *) allocator.Malloc(sizeof (UNPKfileinfo)); + GOTO_IF(!finfo, PHYSFS_ERR_OUT_OF_MEMORY, UNPK_openRead_failed); + + finfo->io = info->io->duplicate(info->io); + GOTO_IF_ERRPASS(!finfo->io, UNPK_openRead_failed); + + if (!finfo->io->seek(finfo->io, entry->startPos)) + goto UNPK_openRead_failed; + + finfo->curPos = 0; + finfo->entry = entry; + + memcpy(retval, &UNPK_Io, sizeof (*retval)); + retval->opaque = finfo; + return retval; + +UNPK_openRead_failed: + if (finfo != NULL) + { + if (finfo->io != NULL) + finfo->io->destroy(finfo->io); + allocator.Free(finfo); + } /* if */ + + if (retval != NULL) + allocator.Free(retval); + + return NULL; +} /* UNPK_openRead */ + + +PHYSFS_Io *UNPK_openWrite(void *opaque, const char *name) +{ + BAIL(PHYSFS_ERR_READ_ONLY, NULL); +} /* UNPK_openWrite */ + + +PHYSFS_Io *UNPK_openAppend(void *opaque, const char *name) +{ + BAIL(PHYSFS_ERR_READ_ONLY, NULL); +} /* UNPK_openAppend */ + + +int UNPK_remove(void *opaque, const char *name) +{ + BAIL(PHYSFS_ERR_READ_ONLY, 0); +} /* UNPK_remove */ + + +int UNPK_mkdir(void *opaque, const char *name) +{ + BAIL(PHYSFS_ERR_READ_ONLY, 0); +} /* UNPK_mkdir */ + + +int UNPK_stat(void *opaque, const char *path, PHYSFS_Stat *stat) +{ + UNPKinfo *info = (UNPKinfo *) opaque; + const UNPKentry *entry = findEntry(info, path); + + BAIL_IF_ERRPASS(!entry, 0); + + if (entry->tree.isdir) + { + stat->filetype = PHYSFS_FILETYPE_DIRECTORY; + stat->filesize = 0; + } /* if */ + else + { + stat->filetype = PHYSFS_FILETYPE_REGULAR; + stat->filesize = entry->size; + } /* else */ + + stat->modtime = entry->mtime; + stat->createtime = entry->ctime; + stat->accesstime = -1; + stat->readonly = 1; + + return 1; +} /* UNPK_stat */ + + +void *UNPK_addEntry(void *opaque, char *name, const int isdir, + const PHYSFS_sint64 ctime, const PHYSFS_sint64 mtime, + const PHYSFS_uint64 pos, const PHYSFS_uint64 len) +{ + UNPKinfo *info = (UNPKinfo *) opaque; + UNPKentry *entry; + + entry = (UNPKentry *) __PHYSFS_DirTreeAdd(&info->tree, name, isdir); + BAIL_IF_ERRPASS(!entry, NULL); + + entry->startPos = isdir ? 0 : pos; + entry->size = isdir ? 0 : len; + entry->ctime = ctime; + entry->mtime = mtime; + + return entry; +} /* UNPK_addEntry */ + + +void *UNPK_openArchive(PHYSFS_Io *io) +{ + UNPKinfo *info = (UNPKinfo *) allocator.Malloc(sizeof (UNPKinfo)); + BAIL_IF(!info, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + + if (!__PHYSFS_DirTreeInit(&info->tree, sizeof (UNPKentry))) + { + allocator.Free(info); + return NULL; + } /* if */ + + info->io = io; + + return info; +} /* UNPK_openArchive */ + +/* end of physfs_archiver_unpacked.c ... */ + diff --git a/Source/Dependencies/PhysicsFS/physfs_archiver_vdf.c b/Source/Dependencies/PhysicsFS/physfs_archiver_vdf.c new file mode 100644 index 000000000..99bbb2a60 --- /dev/null +++ b/Source/Dependencies/PhysicsFS/physfs_archiver_vdf.c @@ -0,0 +1,168 @@ +/* + * VDF support routines for PhysicsFS. + * + * This driver handles Gothic I/II VDF archives. + * This format (but not this driver) was designed by Piranha Bytes for + * use wih the ZenGin engine. + * + * This file was written by Francesco Bertolaccini, based on the UNPK archiver + * by Ryan C. Gordon and the works of degenerated1123 and Nico Bendlin. + */ + +#define __PHYSICSFS_INTERNAL__ +#include "physfs_internal.h" + +#if PHYSFS_SUPPORTS_VDF + +#include + +#define VDF_COMMENT_LENGTH 256 +#define VDF_SIGNATURE_LENGTH 16 +#define VDF_ENTRY_NAME_LENGTH 64 +#define VDF_ENTRY_DIR 0x80000000 + +static const char* VDF_SIGNATURE_G1 = "PSVDSC_V2.00\r\n\r\n"; +static const char* VDF_SIGNATURE_G2 = "PSVDSC_V2.00\n\r\n\r"; + + +static inline int readui32(PHYSFS_Io *io, PHYSFS_uint32 *val) +{ + PHYSFS_uint32 v; + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &v, sizeof (v)), 0); + *val = PHYSFS_swapULE32(v); + return 1; +} /* readui32 */ + + +static PHYSFS_sint64 vdfDosTimeToEpoch(const PHYSFS_uint32 dostime) +{ + /* VDF stores timestamps as 32bit DOS dates: the seconds are counted in + 2-seconds intervals and the years are counted since 1 Jan. 1980 */ + struct tm t; + memset(&t, '\0', sizeof (t)); + t.tm_year = ((int) ((dostime >> 25) & 0x7F)) + 80; /* 1980 to 1900 */ + t.tm_mon = ((int) ((dostime >> 21) & 0xF)) - 1; /* 1-12 to 0-11 */ + t.tm_mday = (int) ((dostime >> 16) & 0x1F); + t.tm_hour = (int) ((dostime >> 11) & 0x1F); + t.tm_min = (int) ((dostime >> 5) & 0x3F); + t.tm_sec = ((int) ((dostime >> 0) & 0x1F)) * 2; /* 2 seconds to 1. */ + return (PHYSFS_sint64) mktime(&t); +} /* vdfDosTimeToEpoch */ + + +static int vdfLoadEntries(PHYSFS_Io *io, const PHYSFS_uint32 count, + const PHYSFS_sint64 ts, void *arc) +{ + PHYSFS_uint32 i; + + for (i = 0; i < count; i++) + { + char name[VDF_ENTRY_NAME_LENGTH + 1]; + int namei; + PHYSFS_uint32 jump, size, type, attr; + + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, name, sizeof (name) - 1), 0); + BAIL_IF_ERRPASS(!readui32(io, &jump), 0); + BAIL_IF_ERRPASS(!readui32(io, &size), 0); + BAIL_IF_ERRPASS(!readui32(io, &type), 0); + BAIL_IF_ERRPASS(!readui32(io, &attr), 0); + + /* Trim whitespace off the end of the filename */ + name[VDF_ENTRY_NAME_LENGTH] = '\0'; /* always null-terminated. */ + for (namei = VDF_ENTRY_NAME_LENGTH - 1; namei >= 0; namei--) + { + /* We assume the filenames are low-ASCII; consider the archive + corrupt if we see something above 127, since we don't know the + encoding. (We can change this later if we find out these exist + and are intended to be, say, latin-1 or UTF-8 encoding). */ + BAIL_IF(((PHYSFS_uint8) name[namei]) > 127, PHYSFS_ERR_CORRUPT, 0); + + if (name[namei] == ' ') + name[namei] = '\0'; + else + break; + } /* for */ + + BAIL_IF(!name[0], PHYSFS_ERR_CORRUPT, 0); + if (!(type & VDF_ENTRY_DIR)) { + BAIL_IF_ERRPASS(!UNPK_addEntry(arc, name, 0, ts, ts, jump, size), 0); + } + } /* for */ + + return 1; +} /* vdfLoadEntries */ + + +static void *VDF_openArchive(PHYSFS_Io *io, const char *name, + int forWriting, int *claimed) +{ + PHYSFS_uint8 ignore[16]; + PHYSFS_uint8 sig[VDF_SIGNATURE_LENGTH]; + PHYSFS_uint32 count, timestamp, version, dataSize, rootCatOffset; + void *unpkarc; + + assert(io != NULL); /* shouldn't ever happen. */ + + BAIL_IF(forWriting, PHYSFS_ERR_READ_ONLY, NULL); + + /* skip the 256-byte comment field. */ + BAIL_IF_ERRPASS(!io->seek(io, VDF_COMMENT_LENGTH), NULL); + + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, sig, sizeof (sig)), NULL); + + if ((memcmp(sig, VDF_SIGNATURE_G1, VDF_SIGNATURE_LENGTH) != 0) && + (memcmp(sig, VDF_SIGNATURE_G2, VDF_SIGNATURE_LENGTH) != 0)) + { + BAIL(PHYSFS_ERR_UNSUPPORTED, NULL); + } /* if */ + + *claimed = 1; + + BAIL_IF_ERRPASS(!readui32(io, &count), NULL); + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), NULL); /* numFiles */ + BAIL_IF_ERRPASS(!readui32(io, ×tamp), NULL); + BAIL_IF_ERRPASS(!readui32(io, &dataSize), NULL); /* dataSize */ + BAIL_IF_ERRPASS(!readui32(io, &rootCatOffset), NULL); /* rootCatOff */ + BAIL_IF_ERRPASS(!readui32(io, &version), NULL); + + BAIL_IF(version != 0x50, PHYSFS_ERR_UNSUPPORTED, NULL); + + BAIL_IF_ERRPASS(!io->seek(io, rootCatOffset), NULL); + + unpkarc = UNPK_openArchive(io); + BAIL_IF_ERRPASS(!unpkarc, NULL); + + if (!vdfLoadEntries(io, count, vdfDosTimeToEpoch(timestamp), unpkarc)) + { + UNPK_abandonArchive(unpkarc); + return NULL; + } /* if */ + + return unpkarc; +} /* VDF_openArchive */ + + +const PHYSFS_Archiver __PHYSFS_Archiver_VDF = +{ + CURRENT_PHYSFS_ARCHIVER_API_VERSION, + { + "VDF", + "Gothic I/II engine format", + "Francesco Bertolaccini ", + "https://github.com/frabert", + 0, /* supportsSymlinks */ + }, + VDF_openArchive, + UNPK_enumerate, + UNPK_openRead, + UNPK_openWrite, + UNPK_openAppend, + UNPK_remove, + UNPK_mkdir, + UNPK_stat, + UNPK_closeArchive +}; + +#endif /* defined PHYSFS_SUPPORTS_VDF */ + +/* end of physfs_archiver_vdf.c ... */ diff --git a/Source/Dependencies/PhysicsFS/physfs_archiver_wad.c b/Source/Dependencies/PhysicsFS/physfs_archiver_wad.c new file mode 100644 index 000000000..b094c5b82 --- /dev/null +++ b/Source/Dependencies/PhysicsFS/physfs_archiver_wad.c @@ -0,0 +1,135 @@ +/* + * WAD support routines for PhysicsFS. + * + * This driver handles DOOM engine archives ("wads"). + * This format (but not this driver) was designed by id Software for use + * with the DOOM engine. + * The specs of the format are from the unofficial doom specs v1.666 + * found here: http://www.gamers.org/dhs/helpdocs/dmsp1666.html + * The format of the archive: (from the specs) + * + * A WAD file has three parts: + * (1) a twelve-byte header + * (2) one or more "lumps" + * (3) a directory or "info table" that contains the names, offsets, and + * sizes of all the lumps in the WAD + * + * The header consists of three four-byte parts: + * (a) an ASCII string which must be either "IWAD" or "PWAD" + * (b) a uint32 which is the number of lumps in the wad + * (c) a uint32 which is the file offset to the start of + * the directory + * + * The directory has one 16-byte entry for every lump. Each entry consists + * of three parts: + * + * (a) a uint32, the file offset to the start of the lump + * (b) a uint32, the size of the lump in bytes + * (c) an 8-byte ASCII string, the name of the lump, padded with zeros. + * For example, the "DEMO1" entry in hexadecimal would be + * (44 45 4D 4F 31 00 00 00) + * + * Note that there is no way to tell if an opened WAD archive is a + * IWAD or PWAD with this archiver. + * I couldn't think of a way to provide that information, without being too + * hacky. + * I don't think it's really that important though. + * + * + * Please see the file LICENSE.txt in the source's root directory. + * + * This file written by Travis Wells, based on the GRP archiver by + * Ryan C. Gordon. + */ + +#define __PHYSICSFS_INTERNAL__ +#include "physfs_internal.h" + +#if PHYSFS_SUPPORTS_WAD + +static int wadLoadEntries(PHYSFS_Io *io, const PHYSFS_uint32 count, void *arc) +{ + PHYSFS_uint32 i; + for (i = 0; i < count; i++) + { + PHYSFS_uint32 pos; + PHYSFS_uint32 size; + char name[9]; + + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &pos, 4), 0); + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &size, 4), 0); + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, name, 8), 0); + + name[8] = '\0'; /* name might not be null-terminated in file. */ + size = PHYSFS_swapULE32(size); + pos = PHYSFS_swapULE32(pos); + BAIL_IF_ERRPASS(!UNPK_addEntry(arc, name, 0, -1, -1, pos, size), 0); + } /* for */ + + return 1; +} /* wadLoadEntries */ + + +static void *WAD_openArchive(PHYSFS_Io *io, const char *name, + int forWriting, int *claimed) +{ + PHYSFS_uint8 buf[4]; + PHYSFS_uint32 count; + PHYSFS_uint32 directoryOffset; + void *unpkarc; + + assert(io != NULL); /* shouldn't ever happen. */ + + BAIL_IF(forWriting, PHYSFS_ERR_READ_ONLY, NULL); + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, buf, sizeof (buf)), NULL); + if ((memcmp(buf, "IWAD", 4) != 0) && (memcmp(buf, "PWAD", 4) != 0)) + BAIL(PHYSFS_ERR_UNSUPPORTED, NULL); + + *claimed = 1; + + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &count, sizeof (count)), NULL); + count = PHYSFS_swapULE32(count); + + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &directoryOffset, 4), 0); + directoryOffset = PHYSFS_swapULE32(directoryOffset); + + BAIL_IF_ERRPASS(!io->seek(io, directoryOffset), 0); + + unpkarc = UNPK_openArchive(io); + BAIL_IF_ERRPASS(!unpkarc, NULL); + + if (!wadLoadEntries(io, count, unpkarc)) + { + UNPK_abandonArchive(unpkarc); + return NULL; + } /* if */ + + return unpkarc; +} /* WAD_openArchive */ + + +const PHYSFS_Archiver __PHYSFS_Archiver_WAD = +{ + CURRENT_PHYSFS_ARCHIVER_API_VERSION, + { + "WAD", + "DOOM engine format", + "Travis Wells ", + "http://www.3dmm2.com/doom/", + 0, /* supportsSymlinks */ + }, + WAD_openArchive, + UNPK_enumerate, + UNPK_openRead, + UNPK_openWrite, + UNPK_openAppend, + UNPK_remove, + UNPK_mkdir, + UNPK_stat, + UNPK_closeArchive +}; + +#endif /* defined PHYSFS_SUPPORTS_WAD */ + +/* end of physfs_archiver_wad.c ... */ + diff --git a/Source/Dependencies/PhysicsFS/physfs_archiver_zip.c b/Source/Dependencies/PhysicsFS/physfs_archiver_zip.c new file mode 100644 index 000000000..9972628fd --- /dev/null +++ b/Source/Dependencies/PhysicsFS/physfs_archiver_zip.c @@ -0,0 +1,1710 @@ +/* + * ZIP support routines for PhysicsFS. + * + * Please see the file LICENSE.txt in the source's root directory. + * + * This file written by Ryan C. Gordon, with some peeking at "unzip.c" + * by Gilles Vollant. + */ + +#define __PHYSICSFS_INTERNAL__ +#include "physfs_internal.h" + +#if PHYSFS_SUPPORTS_ZIP + +#include +#include + +#include "physfs_miniz.h" + +/* + * A buffer of ZIP_READBUFSIZE is allocated for each compressed file opened, + * and is freed when you close the file; compressed data is read into + * this buffer, and then is decompressed into the buffer passed to + * PHYSFS_read(). + * + * Uncompressed entries in a zipfile do not allocate this buffer; they just + * read data directly into the buffer passed to PHYSFS_read(). + * + * Depending on your speed and memory requirements, you should tweak this + * value. + */ +#define ZIP_READBUFSIZE (16 * 1024) + + +/* + * Entries are "unresolved" until they are first opened. At that time, + * local file headers parsed/validated, data offsets will be updated to look + * at the actual file data instead of the header, and symlinks will be + * followed and optimized. This means that we don't seek and read around the + * archive until forced to do so, and after the first time, we had to do + * less reading and parsing, which is very CD-ROM friendly. + */ +typedef enum +{ + ZIP_UNRESOLVED_FILE, + ZIP_UNRESOLVED_SYMLINK, + ZIP_RESOLVING, + ZIP_RESOLVED, + ZIP_DIRECTORY, + ZIP_BROKEN_FILE, + ZIP_BROKEN_SYMLINK +} ZipResolveType; + + +/* + * One ZIPentry is kept for each file in an open ZIP archive. + */ +typedef struct _ZIPentry +{ + __PHYSFS_DirTreeEntry tree; /* manages directory tree */ + struct _ZIPentry *symlink; /* NULL or file we symlink to */ + ZipResolveType resolved; /* Have we resolved file/symlink? */ + PHYSFS_uint64 offset; /* offset of data in archive */ + PHYSFS_uint16 version; /* version made by */ + PHYSFS_uint16 version_needed; /* version needed to extract */ + PHYSFS_uint16 general_bits; /* general purpose bits */ + PHYSFS_uint16 compression_method; /* compression method */ + PHYSFS_uint32 crc; /* crc-32 */ + PHYSFS_uint64 compressed_size; /* compressed size */ + PHYSFS_uint64 uncompressed_size; /* uncompressed size */ + PHYSFS_sint64 last_mod_time; /* last file mod time */ + PHYSFS_uint32 dos_mod_time; /* original MS-DOS style mod time */ +} ZIPentry; + +/* + * One ZIPinfo is kept for each open ZIP archive. + */ +typedef struct +{ + __PHYSFS_DirTree tree; /* manages directory tree. */ + PHYSFS_Io *io; /* the i/o interface for this archive. */ + int zip64; /* non-zero if this is a Zip64 archive. */ + int has_crypto; /* non-zero if any entry uses encryption. */ +} ZIPinfo; + +/* + * One ZIPfileinfo is kept for each open file in a ZIP archive. + */ +typedef struct +{ + ZIPentry *entry; /* Info on file. */ + PHYSFS_Io *io; /* physical file handle. */ + PHYSFS_uint32 compressed_position; /* offset in compressed data. */ + PHYSFS_uint32 uncompressed_position; /* tell() position. */ + PHYSFS_uint8 *buffer; /* decompression buffer. */ + PHYSFS_uint32 crypto_keys[3]; /* for "traditional" crypto. */ + PHYSFS_uint32 initial_crypto_keys[3]; /* for "traditional" crypto. */ + z_stream stream; /* zlib stream state. */ +} ZIPfileinfo; + + +/* Magic numbers... */ +#define ZIP_LOCAL_FILE_SIG 0x04034b50 +#define ZIP_CENTRAL_DIR_SIG 0x02014b50 +#define ZIP_END_OF_CENTRAL_DIR_SIG 0x06054b50 +#define ZIP64_END_OF_CENTRAL_DIR_SIG 0x06064b50 +#define ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIG 0x07064b50 +#define ZIP64_EXTENDED_INFO_EXTRA_FIELD_SIG 0x0001 + +/* compression methods... */ +#define COMPMETH_NONE 0 +/* ...and others... */ + + +#define UNIX_FILETYPE_MASK 0170000 +#define UNIX_FILETYPE_SYMLINK 0120000 + +#define ZIP_GENERAL_BITS_TRADITIONAL_CRYPTO (1 << 0) +#define ZIP_GENERAL_BITS_IGNORE_LOCAL_HEADER (1 << 3) + +/* support for "traditional" PKWARE encryption. */ +static int zip_entry_is_tradional_crypto(const ZIPentry *entry) +{ + return (entry->general_bits & ZIP_GENERAL_BITS_TRADITIONAL_CRYPTO) != 0; +} /* zip_entry_is_traditional_crypto */ + +static int zip_entry_ignore_local_header(const ZIPentry *entry) +{ + return (entry->general_bits & ZIP_GENERAL_BITS_IGNORE_LOCAL_HEADER) != 0; +} /* zip_entry_is_traditional_crypto */ + +static PHYSFS_uint32 zip_crypto_crc32(const PHYSFS_uint32 crc, const PHYSFS_uint8 val) +{ + int i; + PHYSFS_uint32 xorval = (crc ^ ((PHYSFS_uint32) val)) & 0xFF; + for (i = 0; i < 8; i++) + xorval = ((xorval & 1) ? (0xEDB88320 ^ (xorval >> 1)) : (xorval >> 1)); + return xorval ^ (crc >> 8); +} /* zip_crc32 */ + +static void zip_update_crypto_keys(PHYSFS_uint32 *keys, const PHYSFS_uint8 val) +{ + keys[0] = zip_crypto_crc32(keys[0], val); + keys[1] = keys[1] + (keys[0] & 0x000000FF); + keys[1] = (keys[1] * 134775813) + 1; + keys[2] = zip_crypto_crc32(keys[2], (PHYSFS_uint8) ((keys[1] >> 24) & 0xFF)); +} /* zip_update_crypto_keys */ + +static PHYSFS_uint8 zip_decrypt_byte(const PHYSFS_uint32 *keys) +{ + const PHYSFS_uint16 tmp = keys[2] | 2; + return (PHYSFS_uint8) ((tmp * (tmp ^ 1)) >> 8); +} /* zip_decrypt_byte */ + +static PHYSFS_sint64 zip_read_decrypt(ZIPfileinfo *finfo, void *buf, PHYSFS_uint64 len) +{ + PHYSFS_Io *io = finfo->io; + const PHYSFS_sint64 br = io->read(io, buf, len); + + /* Decompression the new data if necessary. */ + if (zip_entry_is_tradional_crypto(finfo->entry) && (br > 0)) + { + PHYSFS_uint32 *keys = finfo->crypto_keys; + PHYSFS_uint8 *ptr = (PHYSFS_uint8 *) buf; + PHYSFS_sint64 i; + for (i = 0; i < br; i++, ptr++) + { + const PHYSFS_uint8 ch = *ptr ^ zip_decrypt_byte(keys); + zip_update_crypto_keys(keys, ch); + *ptr = ch; + } /* for */ + } /* if */ + + return br; +} /* zip_read_decrypt */ + +static int zip_prep_crypto_keys(ZIPfileinfo *finfo, const PHYSFS_uint8 *crypto_header, const PHYSFS_uint8 *password) +{ + /* It doesn't appear to be documented in PKWare's APPNOTE.TXT, but you + need to use a different byte in the header to verify the password + if general purpose bit 3 is set. Discovered this from Info-Zip. + That's what the (verifier) value is doing, below. */ + + PHYSFS_uint32 *keys = finfo->crypto_keys; + const ZIPentry *entry = finfo->entry; + const int usedate = zip_entry_ignore_local_header(entry); + const PHYSFS_uint8 verifier = (PHYSFS_uint8) ((usedate ? (entry->dos_mod_time >> 8) : (entry->crc >> 24)) & 0xFF); + PHYSFS_uint8 finalbyte = 0; + int i = 0; + + /* initialize vector with defaults, then password, then header. */ + keys[0] = 305419896; + keys[1] = 591751049; + keys[2] = 878082192; + + while (*password) + zip_update_crypto_keys(keys, *(password++)); + + for (i = 0; i < 12; i++) + { + const PHYSFS_uint8 c = crypto_header[i] ^ zip_decrypt_byte(keys); + zip_update_crypto_keys(keys, c); + finalbyte = c; + } /* for */ + + /* you have a 1/256 chance of passing this test incorrectly. :/ */ + if (finalbyte != verifier) + BAIL(PHYSFS_ERR_BAD_PASSWORD, 0); + + /* save the initial vector for seeking purposes. Not secure!! */ + memcpy(finfo->initial_crypto_keys, finfo->crypto_keys, 12); + return 1; +} /* zip_prep_crypto_keys */ + + +/* + * Bridge physfs allocation functions to zlib's format... + */ +static voidpf zlibPhysfsAlloc(voidpf opaque, uInt items, uInt size) +{ + return ((PHYSFS_Allocator *) opaque)->Malloc(items * size); +} /* zlibPhysfsAlloc */ + +/* + * Bridge physfs allocation functions to zlib's format... + */ +static void zlibPhysfsFree(voidpf opaque, voidpf address) +{ + ((PHYSFS_Allocator *) opaque)->Free(address); +} /* zlibPhysfsFree */ + + +/* + * Construct a new z_stream to a sane state. + */ +static void initializeZStream(z_stream *pstr) +{ + memset(pstr, '\0', sizeof (z_stream)); + pstr->zalloc = zlibPhysfsAlloc; + pstr->zfree = zlibPhysfsFree; + pstr->opaque = &allocator; +} /* initializeZStream */ + + +static PHYSFS_ErrorCode zlib_error_code(int rc) +{ + switch (rc) + { + case Z_OK: return PHYSFS_ERR_OK; /* not an error. */ + case Z_STREAM_END: return PHYSFS_ERR_OK; /* not an error. */ + case Z_ERRNO: return PHYSFS_ERR_IO; + case Z_MEM_ERROR: return PHYSFS_ERR_OUT_OF_MEMORY; + default: return PHYSFS_ERR_CORRUPT; + } /* switch */ +} /* zlib_error_string */ + + +/* + * Wrap all zlib calls in this, so the physfs error state is set appropriately. + */ +static int zlib_err(const int rc) +{ + PHYSFS_setErrorCode(zlib_error_code(rc)); + return rc; +} /* zlib_err */ + +/* + * Read an unsigned 64-bit int and swap to native byte order. + */ +static int readui64(PHYSFS_Io *io, PHYSFS_uint64 *val) +{ + PHYSFS_uint64 v; + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &v, sizeof (v)), 0); + *val = PHYSFS_swapULE64(v); + return 1; +} /* readui64 */ + +/* + * Read an unsigned 32-bit int and swap to native byte order. + */ +static int readui32(PHYSFS_Io *io, PHYSFS_uint32 *val) +{ + PHYSFS_uint32 v; + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &v, sizeof (v)), 0); + *val = PHYSFS_swapULE32(v); + return 1; +} /* readui32 */ + + +/* + * Read an unsigned 16-bit int and swap to native byte order. + */ +static int readui16(PHYSFS_Io *io, PHYSFS_uint16 *val) +{ + PHYSFS_uint16 v; + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &v, sizeof (v)), 0); + *val = PHYSFS_swapULE16(v); + return 1; +} /* readui16 */ + + +static PHYSFS_sint64 ZIP_read(PHYSFS_Io *_io, void *buf, PHYSFS_uint64 len) +{ + ZIPfileinfo *finfo = (ZIPfileinfo *) _io->opaque; + ZIPentry *entry = finfo->entry; + PHYSFS_sint64 retval = 0; + PHYSFS_sint64 maxread = (PHYSFS_sint64) len; + PHYSFS_sint64 avail = entry->uncompressed_size - + finfo->uncompressed_position; + + if (avail < maxread) + maxread = avail; + + BAIL_IF_ERRPASS(maxread == 0, 0); /* quick rejection. */ + + if (entry->compression_method == COMPMETH_NONE) + retval = zip_read_decrypt(finfo, buf, maxread); + else + { + finfo->stream.next_out = buf; + finfo->stream.avail_out = (uInt) maxread; + + while (retval < maxread) + { + const PHYSFS_uint32 before = (PHYSFS_uint32) finfo->stream.total_out; + int rc; + + if (finfo->stream.avail_in == 0) + { + PHYSFS_sint64 br; + + br = entry->compressed_size - finfo->compressed_position; + if (br > 0) + { + if (br > ZIP_READBUFSIZE) + br = ZIP_READBUFSIZE; + + br = zip_read_decrypt(finfo, finfo->buffer, (PHYSFS_uint64) br); + if (br <= 0) + break; + + finfo->compressed_position += (PHYSFS_uint32) br; + finfo->stream.next_in = finfo->buffer; + finfo->stream.avail_in = (unsigned int) br; + } /* if */ + } /* if */ + + rc = zlib_err(inflate(&finfo->stream, Z_SYNC_FLUSH)); + retval += (finfo->stream.total_out - before); + + if (rc != Z_OK) + break; + } /* while */ + } /* else */ + + if (retval > 0) + finfo->uncompressed_position += (PHYSFS_uint32) retval; + + return retval; +} /* ZIP_read */ + + +static PHYSFS_sint64 ZIP_write(PHYSFS_Io *io, const void *b, PHYSFS_uint64 len) +{ + BAIL(PHYSFS_ERR_READ_ONLY, -1); +} /* ZIP_write */ + + +static PHYSFS_sint64 ZIP_tell(PHYSFS_Io *io) +{ + return ((ZIPfileinfo *) io->opaque)->uncompressed_position; +} /* ZIP_tell */ + + +static int ZIP_seek(PHYSFS_Io *_io, PHYSFS_uint64 offset) +{ + ZIPfileinfo *finfo = (ZIPfileinfo *) _io->opaque; + ZIPentry *entry = finfo->entry; + PHYSFS_Io *io = finfo->io; + const int encrypted = zip_entry_is_tradional_crypto(entry); + + BAIL_IF(offset > entry->uncompressed_size, PHYSFS_ERR_PAST_EOF, 0); + + if (!encrypted && (entry->compression_method == COMPMETH_NONE)) + { + PHYSFS_sint64 newpos = offset + entry->offset; + BAIL_IF_ERRPASS(!io->seek(io, newpos), 0); + finfo->uncompressed_position = (PHYSFS_uint32) offset; + } /* if */ + + else + { + /* + * If seeking backwards, we need to redecode the file + * from the start and throw away the compressed bits until we hit + * the offset we need. If seeking forward, we still need to + * decode, but we don't rewind first. + */ + if (offset < finfo->uncompressed_position) + { + /* we do a copy so state is sane if inflateInit2() fails. */ + z_stream str; + initializeZStream(&str); + if (zlib_err(inflateInit2(&str, -MAX_WBITS)) != Z_OK) + return 0; + + if (!io->seek(io, entry->offset + (encrypted ? 12 : 0))) + return 0; + + inflateEnd(&finfo->stream); + memcpy(&finfo->stream, &str, sizeof (z_stream)); + finfo->uncompressed_position = finfo->compressed_position = 0; + + if (encrypted) + memcpy(finfo->crypto_keys, finfo->initial_crypto_keys, 12); + } /* if */ + + while (finfo->uncompressed_position != offset) + { + PHYSFS_uint8 buf[512]; + PHYSFS_uint32 maxread; + + maxread = (PHYSFS_uint32) (offset - finfo->uncompressed_position); + if (maxread > sizeof (buf)) + maxread = sizeof (buf); + + if (ZIP_read(_io, buf, maxread) != maxread) + return 0; + } /* while */ + } /* else */ + + return 1; +} /* ZIP_seek */ + + +static PHYSFS_sint64 ZIP_length(PHYSFS_Io *io) +{ + const ZIPfileinfo *finfo = (ZIPfileinfo *) io->opaque; + return (PHYSFS_sint64) finfo->entry->uncompressed_size; +} /* ZIP_length */ + + +static PHYSFS_Io *zip_get_io(PHYSFS_Io *io, ZIPinfo *inf, ZIPentry *entry); + +static PHYSFS_Io *ZIP_duplicate(PHYSFS_Io *io) +{ + ZIPfileinfo *origfinfo = (ZIPfileinfo *) io->opaque; + PHYSFS_Io *retval = (PHYSFS_Io *) allocator.Malloc(sizeof (PHYSFS_Io)); + ZIPfileinfo *finfo = (ZIPfileinfo *) allocator.Malloc(sizeof (ZIPfileinfo)); + GOTO_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, failed); + GOTO_IF(!finfo, PHYSFS_ERR_OUT_OF_MEMORY, failed); + memset(finfo, '\0', sizeof (*finfo)); + + finfo->entry = origfinfo->entry; + finfo->io = zip_get_io(origfinfo->io, NULL, finfo->entry); + GOTO_IF_ERRPASS(!finfo->io, failed); + + initializeZStream(&finfo->stream); + if (finfo->entry->compression_method != COMPMETH_NONE) + { + finfo->buffer = (PHYSFS_uint8 *) allocator.Malloc(ZIP_READBUFSIZE); + GOTO_IF(!finfo->buffer, PHYSFS_ERR_OUT_OF_MEMORY, failed); + if (zlib_err(inflateInit2(&finfo->stream, -MAX_WBITS)) != Z_OK) + goto failed; + } /* if */ + + memcpy(retval, io, sizeof (PHYSFS_Io)); + retval->opaque = finfo; + return retval; + +failed: + if (finfo != NULL) + { + if (finfo->io != NULL) + finfo->io->destroy(finfo->io); + + if (finfo->buffer != NULL) + { + allocator.Free(finfo->buffer); + inflateEnd(&finfo->stream); + } /* if */ + + allocator.Free(finfo); + } /* if */ + + if (retval != NULL) + allocator.Free(retval); + + return NULL; +} /* ZIP_duplicate */ + +static int ZIP_flush(PHYSFS_Io *io) { return 1; /* no write support. */ } + +static void ZIP_destroy(PHYSFS_Io *io) +{ + ZIPfileinfo *finfo = (ZIPfileinfo *) io->opaque; + finfo->io->destroy(finfo->io); + + if (finfo->entry->compression_method != COMPMETH_NONE) + inflateEnd(&finfo->stream); + + if (finfo->buffer != NULL) + allocator.Free(finfo->buffer); + + allocator.Free(finfo); + allocator.Free(io); +} /* ZIP_destroy */ + + +static const PHYSFS_Io ZIP_Io = +{ + CURRENT_PHYSFS_IO_API_VERSION, NULL, + ZIP_read, + ZIP_write, + ZIP_seek, + ZIP_tell, + ZIP_length, + ZIP_duplicate, + ZIP_flush, + ZIP_destroy +}; + + + +static PHYSFS_sint64 zip_find_end_of_central_dir(PHYSFS_Io *io, PHYSFS_sint64 *len) +{ + PHYSFS_uint8 buf[256]; + PHYSFS_uint8 extra[4] = { 0, 0, 0, 0 }; + PHYSFS_sint32 i = 0; + PHYSFS_sint64 filelen; + PHYSFS_sint64 filepos; + PHYSFS_sint32 maxread; + PHYSFS_sint32 totalread = 0; + int found = 0; + + filelen = io->length(io); + BAIL_IF_ERRPASS(filelen == -1, -1); + + /* + * Jump to the end of the file and start reading backwards. + * The last thing in the file is the zipfile comment, which is variable + * length, and the field that specifies its size is before it in the + * file (argh!)...this means that we need to scan backwards until we + * hit the end-of-central-dir signature. We can then sanity check that + * the comment was as big as it should be to make sure we're in the + * right place. The comment length field is 16 bits, so we can stop + * searching for that signature after a little more than 64k at most, + * and call it a corrupted zipfile. + */ + + if (sizeof (buf) < filelen) + { + filepos = filelen - sizeof (buf); + maxread = sizeof (buf); + } /* if */ + else + { + filepos = 0; + maxread = (PHYSFS_uint32) filelen; + } /* else */ + + while ((totalread < filelen) && (totalread < 65557)) + { + BAIL_IF_ERRPASS(!io->seek(io, filepos), -1); + + /* make sure we catch a signature between buffers. */ + if (totalread != 0) + { + if (!__PHYSFS_readAll(io, buf, maxread - 4)) + return -1; + memcpy(&buf[maxread - 4], &extra, sizeof (extra)); + totalread += maxread - 4; + } /* if */ + else + { + if (!__PHYSFS_readAll(io, buf, maxread)) + return -1; + totalread += maxread; + } /* else */ + + memcpy(&extra, buf, sizeof (extra)); + + for (i = maxread - 4; i > 0; i--) + { + if ((buf[i + 0] == 0x50) && + (buf[i + 1] == 0x4B) && + (buf[i + 2] == 0x05) && + (buf[i + 3] == 0x06) ) + { + found = 1; /* that's the signature! */ + break; + } /* if */ + } /* for */ + + if (found) + break; + + filepos -= (maxread - 4); + if (filepos < 0) + filepos = 0; + } /* while */ + + BAIL_IF(!found, PHYSFS_ERR_UNSUPPORTED, -1); + + if (len != NULL) + *len = filelen; + + return (filepos + i); +} /* zip_find_end_of_central_dir */ + + +static int isZip(PHYSFS_Io *io) +{ + PHYSFS_uint32 sig = 0; + int retval = 0; + + /* + * The first thing in a zip file might be the signature of the + * first local file record, so it makes for a quick determination. + */ + if (readui32(io, &sig)) + { + retval = (sig == ZIP_LOCAL_FILE_SIG); + if (!retval) + { + /* + * No sig...might be a ZIP with data at the start + * (a self-extracting executable, etc), so we'll have to do + * it the hard way... + */ + retval = (zip_find_end_of_central_dir(io, NULL) != -1); + } /* if */ + } /* if */ + + return retval; +} /* isZip */ + + +/* Convert paths from old, buggy DOS zippers... */ +static void zip_convert_dos_path(const PHYSFS_uint16 entryversion, char *path) +{ + const PHYSFS_uint8 hosttype = (PHYSFS_uint8) ((entryversion >> 8) & 0xFF); + if (hosttype == 0) /* FS_FAT_ */ + { + while (*path) + { + if (*path == '\\') + *path = '/'; + path++; + } /* while */ + } /* if */ +} /* zip_convert_dos_path */ + + +static void zip_expand_symlink_path(char *path) +{ + char *ptr = path; + char *prevptr = path; + + while (1) + { + ptr = strchr(ptr, '/'); + if (ptr == NULL) + break; + + if (*(ptr + 1) == '.') + { + if (*(ptr + 2) == '/') + { + /* current dir in middle of string: ditch it. */ + memmove(ptr, ptr + 2, strlen(ptr + 2) + 1); + } /* else if */ + + else if (*(ptr + 2) == '\0') + { + /* current dir at end of string: ditch it. */ + *ptr = '\0'; + } /* else if */ + + else if (*(ptr + 2) == '.') + { + if (*(ptr + 3) == '/') + { + /* parent dir in middle: move back one, if possible. */ + memmove(prevptr, ptr + 4, strlen(ptr + 4) + 1); + ptr = prevptr; + while (prevptr != path) + { + prevptr--; + if (*prevptr == '/') + { + prevptr++; + break; + } /* if */ + } /* while */ + } /* if */ + + if (*(ptr + 3) == '\0') + { + /* parent dir at end: move back one, if possible. */ + *prevptr = '\0'; + } /* if */ + } /* if */ + } /* if */ + else + { + prevptr = ptr; + ptr++; + } /* else */ + } /* while */ +} /* zip_expand_symlink_path */ + + +static inline ZIPentry *zip_find_entry(ZIPinfo *info, const char *path) +{ + return (ZIPentry *) __PHYSFS_DirTreeFind(&info->tree, path); +} /* zip_find_entry */ + +/* (forward reference: zip_follow_symlink and zip_resolve call each other.) */ +static int zip_resolve(PHYSFS_Io *io, ZIPinfo *info, ZIPentry *entry); + +/* + * Look for the entry named by (path). If it exists, resolve it, and return + * a pointer to that entry. If it's another symlink, keep resolving until you + * hit a real file and then return a pointer to the final non-symlink entry. + * If there's a problem, return NULL. + */ +static ZIPentry *zip_follow_symlink(PHYSFS_Io *io, ZIPinfo *info, char *path) +{ + ZIPentry *entry; + + zip_expand_symlink_path(path); + entry = zip_find_entry(info, path); + if (entry != NULL) + { + if (!zip_resolve(io, info, entry)) /* recursive! */ + entry = NULL; + else + { + if (entry->symlink != NULL) + entry = entry->symlink; + } /* else */ + } /* if */ + + return entry; +} /* zip_follow_symlink */ + + +static int zip_resolve_symlink(PHYSFS_Io *io, ZIPinfo *info, ZIPentry *entry) +{ + const size_t size = (size_t) entry->uncompressed_size; + char *path = NULL; + int rc = 0; + + /* + * We've already parsed the local file header of the symlink at this + * point. Now we need to read the actual link from the file data and + * follow it. + */ + + BAIL_IF_ERRPASS(!io->seek(io, entry->offset), 0); + + path = (char *) __PHYSFS_smallAlloc(size + 1); + BAIL_IF(!path, PHYSFS_ERR_OUT_OF_MEMORY, 0); + + if (entry->compression_method == COMPMETH_NONE) + rc = __PHYSFS_readAll(io, path, size); + + else /* symlink target path is compressed... */ + { + z_stream stream; + const size_t complen = (size_t) entry->compressed_size; + PHYSFS_uint8 *compressed = (PHYSFS_uint8*) __PHYSFS_smallAlloc(complen); + if (compressed != NULL) + { + if (__PHYSFS_readAll(io, compressed, complen)) + { + initializeZStream(&stream); + stream.next_in = compressed; + stream.avail_in = (unsigned int) complen; + stream.next_out = (unsigned char *) path; + stream.avail_out = (unsigned int) size; + if (zlib_err(inflateInit2(&stream, -MAX_WBITS)) == Z_OK) + { + rc = zlib_err(inflate(&stream, Z_FINISH)); + inflateEnd(&stream); + + /* both are acceptable outcomes... */ + rc = ((rc == Z_OK) || (rc == Z_STREAM_END)); + } /* if */ + } /* if */ + __PHYSFS_smallFree(compressed); + } /* if */ + } /* else */ + + if (rc) + { + path[entry->uncompressed_size] = '\0'; /* null-terminate it. */ + zip_convert_dos_path(entry->version, path); + entry->symlink = zip_follow_symlink(io, info, path); + } /* else */ + + __PHYSFS_smallFree(path); + + return (entry->symlink != NULL); +} /* zip_resolve_symlink */ + + +/* + * Parse the local file header of an entry, and update entry->offset. + */ +static int zip_parse_local(PHYSFS_Io *io, ZIPentry *entry) +{ + PHYSFS_uint32 ui32; + PHYSFS_uint16 ui16; + PHYSFS_uint16 fnamelen; + PHYSFS_uint16 extralen; + + /* + * crc and (un)compressed_size are always zero if this is a "JAR" + * archive created with Sun's Java tools, apparently. We only + * consider this archive corrupted if those entries don't match and + * aren't zero. That seems to work well. + * We also ignore a mismatch if the value is 0xFFFFFFFF here, since it's + * possible that's a Zip64 thing. + */ + + /* !!! FIXME: apparently these are zero if general purpose bit 3 is set, + !!! FIXME: which is probably true for Jar files, fwiw, but we don't + !!! FIXME: care about these values anyhow. */ + + BAIL_IF_ERRPASS(!io->seek(io, entry->offset), 0); + BAIL_IF_ERRPASS(!readui32(io, &ui32), 0); + BAIL_IF(ui32 != ZIP_LOCAL_FILE_SIG, PHYSFS_ERR_CORRUPT, 0); + BAIL_IF_ERRPASS(!readui16(io, &ui16), 0); + BAIL_IF(ui16 != entry->version_needed, PHYSFS_ERR_CORRUPT, 0); + BAIL_IF_ERRPASS(!readui16(io, &ui16), 0); /* general bits. */ + BAIL_IF_ERRPASS(!readui16(io, &ui16), 0); + BAIL_IF(ui16 != entry->compression_method, PHYSFS_ERR_CORRUPT, 0); + BAIL_IF_ERRPASS(!readui32(io, &ui32), 0); /* date/time */ + BAIL_IF_ERRPASS(!readui32(io, &ui32), 0); + BAIL_IF(ui32 && (ui32 != entry->crc), PHYSFS_ERR_CORRUPT, 0); + + BAIL_IF_ERRPASS(!readui32(io, &ui32), 0); + BAIL_IF(ui32 && (ui32 != 0xFFFFFFFF) && + (ui32 != entry->compressed_size), PHYSFS_ERR_CORRUPT, 0); + + BAIL_IF_ERRPASS(!readui32(io, &ui32), 0); + BAIL_IF(ui32 && (ui32 != 0xFFFFFFFF) && + (ui32 != entry->uncompressed_size), PHYSFS_ERR_CORRUPT, 0); + + BAIL_IF_ERRPASS(!readui16(io, &fnamelen), 0); + BAIL_IF_ERRPASS(!readui16(io, &extralen), 0); + + entry->offset += fnamelen + extralen + 30; + return 1; +} /* zip_parse_local */ + + +static int zip_resolve(PHYSFS_Io *io, ZIPinfo *info, ZIPentry *entry) +{ + int retval = 1; + const ZipResolveType resolve_type = entry->resolved; + + if (resolve_type == ZIP_DIRECTORY) + return 1; /* we're good. */ + + /* Don't bother if we've failed to resolve this entry before. */ + BAIL_IF(resolve_type == ZIP_BROKEN_FILE, PHYSFS_ERR_CORRUPT, 0); + BAIL_IF(resolve_type == ZIP_BROKEN_SYMLINK, PHYSFS_ERR_CORRUPT, 0); + + /* uhoh...infinite symlink loop! */ + BAIL_IF(resolve_type == ZIP_RESOLVING, PHYSFS_ERR_SYMLINK_LOOP, 0); + + /* + * We fix up the offset to point to the actual data on the + * first open, since we don't want to seek across the whole file on + * archive open (can be SLOW on large, CD-stored files), but we + * need to check the local file header...not just for corruption, + * but since it stores offset info the central directory does not. + */ + if (resolve_type != ZIP_RESOLVED) + { + if (entry->tree.isdir) /* an ancestor dir that DirTree filled in? */ + { + entry->resolved = ZIP_DIRECTORY; + return 1; + } /* if */ + + retval = zip_parse_local(io, entry); + if (retval) + { + /* + * If it's a symlink, find the original file. This will cause + * resolution of other entries (other symlinks and, eventually, + * the real file) if all goes well. + */ + if (resolve_type == ZIP_UNRESOLVED_SYMLINK) + retval = zip_resolve_symlink(io, info, entry); + } /* if */ + + if (resolve_type == ZIP_UNRESOLVED_SYMLINK) + entry->resolved = ((retval) ? ZIP_RESOLVED : ZIP_BROKEN_SYMLINK); + else if (resolve_type == ZIP_UNRESOLVED_FILE) + entry->resolved = ((retval) ? ZIP_RESOLVED : ZIP_BROKEN_FILE); + } /* if */ + + return retval; +} /* zip_resolve */ + + +static int zip_entry_is_symlink(const ZIPentry *entry) +{ + return ((entry->resolved == ZIP_UNRESOLVED_SYMLINK) || + (entry->resolved == ZIP_BROKEN_SYMLINK) || + (entry->symlink)); +} /* zip_entry_is_symlink */ + + +static int zip_version_does_symlinks(PHYSFS_uint32 version) +{ + int retval = 0; + PHYSFS_uint8 hosttype = (PHYSFS_uint8) ((version >> 8) & 0xFF); + + switch (hosttype) + { + /* + * These are the platforms that can NOT build an archive with + * symlinks, according to the Info-ZIP project. + */ + case 0: /* FS_FAT_ */ + case 1: /* AMIGA_ */ + case 2: /* VMS_ */ + case 4: /* VM_CSM_ */ + case 6: /* FS_HPFS_ */ + case 11: /* FS_NTFS_ */ + case 14: /* FS_VFAT_ */ + case 13: /* ACORN_ */ + case 15: /* MVS_ */ + case 18: /* THEOS_ */ + break; /* do nothing. */ + + default: /* assume the rest to be unix-like. */ + retval = 1; + break; + } /* switch */ + + return retval; +} /* zip_version_does_symlinks */ + + +static inline int zip_has_symlink_attr(const ZIPentry *entry, + const PHYSFS_uint32 extern_attr) +{ + PHYSFS_uint16 xattr = ((extern_attr >> 16) & 0xFFFF); + return ( (zip_version_does_symlinks(entry->version)) && + (entry->uncompressed_size > 0) && + ((xattr & UNIX_FILETYPE_MASK) == UNIX_FILETYPE_SYMLINK) ); +} /* zip_has_symlink_attr */ + + +static PHYSFS_sint64 zip_dos_time_to_physfs_time(PHYSFS_uint32 dostime) +{ + PHYSFS_uint32 dosdate; + struct tm unixtime; + memset(&unixtime, '\0', sizeof (unixtime)); + + dosdate = (PHYSFS_uint32) ((dostime >> 16) & 0xFFFF); + dostime &= 0xFFFF; + + /* dissect date */ + unixtime.tm_year = ((dosdate >> 9) & 0x7F) + 80; + unixtime.tm_mon = ((dosdate >> 5) & 0x0F) - 1; + unixtime.tm_mday = ((dosdate ) & 0x1F); + + /* dissect time */ + unixtime.tm_hour = ((dostime >> 11) & 0x1F); + unixtime.tm_min = ((dostime >> 5) & 0x3F); + unixtime.tm_sec = ((dostime << 1) & 0x3E); + + /* let mktime calculate daylight savings time. */ + unixtime.tm_isdst = -1; + + return ((PHYSFS_sint64) mktime(&unixtime)); +} /* zip_dos_time_to_physfs_time */ + + +static ZIPentry *zip_load_entry(ZIPinfo *info, const int zip64, + const PHYSFS_uint64 ofs_fixup) +{ + PHYSFS_Io *io = info->io; + ZIPentry entry; + ZIPentry *retval = NULL; + PHYSFS_uint16 fnamelen, extralen, commentlen; + PHYSFS_uint32 external_attr; + PHYSFS_uint32 starting_disk; + PHYSFS_uint64 offset; + PHYSFS_uint16 ui16; + PHYSFS_uint32 ui32; + PHYSFS_sint64 si64; + char *name = NULL; + int isdir = 0; + + /* sanity check with central directory signature... */ + BAIL_IF_ERRPASS(!readui32(io, &ui32), NULL); + BAIL_IF(ui32 != ZIP_CENTRAL_DIR_SIG, PHYSFS_ERR_CORRUPT, NULL); + + memset(&entry, '\0', sizeof (entry)); + + /* Get the pertinent parts of the record... */ + BAIL_IF_ERRPASS(!readui16(io, &entry.version), NULL); + BAIL_IF_ERRPASS(!readui16(io, &entry.version_needed), NULL); + BAIL_IF_ERRPASS(!readui16(io, &entry.general_bits), NULL); /* general bits */ + BAIL_IF_ERRPASS(!readui16(io, &entry.compression_method), NULL); + BAIL_IF_ERRPASS(!readui32(io, &entry.dos_mod_time), NULL); + entry.last_mod_time = zip_dos_time_to_physfs_time(entry.dos_mod_time); + BAIL_IF_ERRPASS(!readui32(io, &entry.crc), NULL); + BAIL_IF_ERRPASS(!readui32(io, &ui32), NULL); + entry.compressed_size = (PHYSFS_uint64) ui32; + BAIL_IF_ERRPASS(!readui32(io, &ui32), NULL); + entry.uncompressed_size = (PHYSFS_uint64) ui32; + BAIL_IF_ERRPASS(!readui16(io, &fnamelen), NULL); + BAIL_IF_ERRPASS(!readui16(io, &extralen), NULL); + BAIL_IF_ERRPASS(!readui16(io, &commentlen), NULL); + BAIL_IF_ERRPASS(!readui16(io, &ui16), NULL); + starting_disk = (PHYSFS_uint32) ui16; + BAIL_IF_ERRPASS(!readui16(io, &ui16), NULL); /* internal file attribs */ + BAIL_IF_ERRPASS(!readui32(io, &external_attr), NULL); + BAIL_IF_ERRPASS(!readui32(io, &ui32), NULL); + offset = (PHYSFS_uint64) ui32; + + name = (char *) __PHYSFS_smallAlloc(fnamelen + 1); + BAIL_IF(!name, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + if (!__PHYSFS_readAll(io, name, fnamelen)) + { + __PHYSFS_smallFree(name); + return NULL; + } /* if */ + + if (name[fnamelen - 1] == '/') + { + name[fnamelen - 1] = '\0'; + isdir = 1; + } /* if */ + name[fnamelen] = '\0'; /* null-terminate the filename. */ + + zip_convert_dos_path(entry.version, name); + + retval = (ZIPentry *) __PHYSFS_DirTreeAdd(&info->tree, name, isdir); + __PHYSFS_smallFree(name); + + BAIL_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + + /* It's okay to BAIL without freeing retval, because it's stored in the + __PHYSFS_DirTree and will be freed later anyhow. */ + BAIL_IF(retval->last_mod_time != 0, PHYSFS_ERR_CORRUPT, NULL); /* dupe? */ + + /* Move the data we already read into place in the official object. */ + memcpy(((PHYSFS_uint8 *) retval) + sizeof (__PHYSFS_DirTreeEntry), + ((PHYSFS_uint8 *) &entry) + sizeof (__PHYSFS_DirTreeEntry), + sizeof (*retval) - sizeof (__PHYSFS_DirTreeEntry)); + + retval->symlink = NULL; /* will be resolved later, if necessary. */ + + if (isdir) + retval->resolved = ZIP_DIRECTORY; + else + { + retval->resolved = (zip_has_symlink_attr(retval, external_attr)) ? + ZIP_UNRESOLVED_SYMLINK : ZIP_UNRESOLVED_FILE; + } /* else */ + + si64 = io->tell(io); + BAIL_IF_ERRPASS(si64 == -1, NULL); + + /* If the actual sizes didn't fit in 32-bits, look for the Zip64 + extended information extra field... */ + if ( (zip64) && + ((offset == 0xFFFFFFFF) || + (starting_disk == 0xFFFFFFFF) || + (retval->compressed_size == 0xFFFFFFFF) || + (retval->uncompressed_size == 0xFFFFFFFF)) ) + { + int found = 0; + PHYSFS_uint16 sig = 0; + PHYSFS_uint16 len = 0; + while (extralen > 4) + { + BAIL_IF_ERRPASS(!readui16(io, &sig), NULL); + BAIL_IF_ERRPASS(!readui16(io, &len), NULL); + + si64 += 4 + len; + extralen -= 4 + len; + if (sig != ZIP64_EXTENDED_INFO_EXTRA_FIELD_SIG) + { + BAIL_IF_ERRPASS(!io->seek(io, si64), NULL); + continue; + } /* if */ + + found = 1; + break; + } /* while */ + + BAIL_IF(!found, PHYSFS_ERR_CORRUPT, NULL); + + if (retval->uncompressed_size == 0xFFFFFFFF) + { + BAIL_IF(len < 8, PHYSFS_ERR_CORRUPT, NULL); + BAIL_IF_ERRPASS(!readui64(io, &retval->uncompressed_size), NULL); + len -= 8; + } /* if */ + + if (retval->compressed_size == 0xFFFFFFFF) + { + BAIL_IF(len < 8, PHYSFS_ERR_CORRUPT, NULL); + BAIL_IF_ERRPASS(!readui64(io, &retval->compressed_size), NULL); + len -= 8; + } /* if */ + + if (offset == 0xFFFFFFFF) + { + BAIL_IF(len < 8, PHYSFS_ERR_CORRUPT, NULL); + BAIL_IF_ERRPASS(!readui64(io, &offset), NULL); + len -= 8; + } /* if */ + + if (starting_disk == 0xFFFFFFFF) + { + BAIL_IF(len < 8, PHYSFS_ERR_CORRUPT, NULL); + BAIL_IF_ERRPASS(!readui32(io, &starting_disk), NULL); + len -= 4; + } /* if */ + + BAIL_IF(len != 0, PHYSFS_ERR_CORRUPT, NULL); + } /* if */ + + BAIL_IF(starting_disk != 0, PHYSFS_ERR_CORRUPT, NULL); + + retval->offset = offset + ofs_fixup; + + /* seek to the start of the next entry in the central directory... */ + BAIL_IF_ERRPASS(!io->seek(io, si64 + extralen + commentlen), NULL); + + return retval; /* success. */ +} /* zip_load_entry */ + + +/* This leaves things allocated on error; the caller will clean up the mess. */ +static int zip_load_entries(ZIPinfo *info, + const PHYSFS_uint64 data_ofs, + const PHYSFS_uint64 central_ofs, + const PHYSFS_uint64 entry_count) +{ + PHYSFS_Io *io = info->io; + const int zip64 = info->zip64; + PHYSFS_uint64 i; + + BAIL_IF_ERRPASS(!io->seek(io, central_ofs), 0); + + for (i = 0; i < entry_count; i++) + { + ZIPentry *entry = zip_load_entry(info, zip64, data_ofs); + BAIL_IF_ERRPASS(!entry, 0); + if (zip_entry_is_tradional_crypto(entry)) + info->has_crypto = 1; + } /* for */ + + return 1; +} /* zip_load_entries */ + + +static PHYSFS_sint64 zip64_find_end_of_central_dir(PHYSFS_Io *io, + PHYSFS_sint64 _pos, + PHYSFS_uint64 offset) +{ + /* + * Naturally, the offset is useless to us; it is the offset from the + * start of file, which is meaningless if we've appended this .zip to + * a self-extracting .exe. We need to find this on our own. It should + * be directly before the locator record, but the record in question, + * like the original end-of-central-directory record, ends with a + * variable-length field. Unlike the original, which has to store the + * size of that variable-length field in a 16-bit int and thus has to be + * within 64k, the new one gets 64-bits. + * + * Fortunately, the only currently-specified record for that variable + * length block is some weird proprietary thing that deals with EBCDIC + * and tape backups or something. So we don't seek far. + */ + + PHYSFS_uint32 ui32; + const PHYSFS_uint64 pos = (PHYSFS_uint64) _pos; + + assert(_pos > 0); + + /* Try offset specified in the Zip64 end of central directory locator. */ + /* This works if the entire PHYSFS_Io is the zip file. */ + BAIL_IF_ERRPASS(!io->seek(io, offset), -1); + BAIL_IF_ERRPASS(!readui32(io, &ui32), -1); + if (ui32 == ZIP64_END_OF_CENTRAL_DIR_SIG) + return offset; + + /* Try 56 bytes before the Zip64 end of central directory locator. */ + /* This works if the record isn't variable length and is version 1. */ + if (pos > 56) + { + BAIL_IF_ERRPASS(!io->seek(io, pos-56), -1); + BAIL_IF_ERRPASS(!readui32(io, &ui32), -1); + if (ui32 == ZIP64_END_OF_CENTRAL_DIR_SIG) + return pos-56; + } /* if */ + + /* Try 84 bytes before the Zip64 end of central directory locator. */ + /* This works if the record isn't variable length and is version 2. */ + if (pos > 84) + { + BAIL_IF_ERRPASS(!io->seek(io, pos-84), -1); + BAIL_IF_ERRPASS(!readui32(io, &ui32), -1); + if (ui32 == ZIP64_END_OF_CENTRAL_DIR_SIG) + return pos-84; + } /* if */ + + /* Ok, brute force: we know it's between (offset) and (pos) somewhere. */ + /* Just try moving back at most 256k. Oh well. */ + if ((offset < pos) && (pos > 4)) + { + const size_t maxbuflen = 256 * 1024; + size_t len = (size_t) (pos - offset); + PHYSFS_uint8 *buf = NULL; + PHYSFS_sint32 i; + + if (len > maxbuflen) + len = maxbuflen; + + buf = (PHYSFS_uint8 *) __PHYSFS_smallAlloc(len); + BAIL_IF(!buf, PHYSFS_ERR_OUT_OF_MEMORY, -1); + + if (!io->seek(io, pos - len) || !__PHYSFS_readAll(io, buf, len)) + { + __PHYSFS_smallFree(buf); + return -1; /* error was set elsewhere. */ + } /* if */ + + for (i = (PHYSFS_sint32) (len - 4); i >= 0; i--) + { + if ( (buf[i] == 0x50) && (buf[i+1] == 0x4b) && + (buf[i+2] == 0x06) && (buf[i+3] == 0x06) ) + { + __PHYSFS_smallFree(buf); + return pos - ((PHYSFS_sint64) (len - i)); + } /* if */ + } /* for */ + + __PHYSFS_smallFree(buf); + } /* if */ + + BAIL(PHYSFS_ERR_CORRUPT, -1); /* didn't find it. */ +} /* zip64_find_end_of_central_dir */ + + +static int zip64_parse_end_of_central_dir(ZIPinfo *info, + PHYSFS_uint64 *data_start, + PHYSFS_uint64 *dir_ofs, + PHYSFS_uint64 *entry_count, + PHYSFS_sint64 pos) +{ + PHYSFS_Io *io = info->io; + PHYSFS_uint64 ui64; + PHYSFS_uint32 ui32; + PHYSFS_uint16 ui16; + + /* We should be positioned right past the locator signature. */ + + if ((pos < 0) || (!io->seek(io, pos))) + return 0; + + BAIL_IF_ERRPASS(!readui32(io, &ui32), 0); + if (ui32 != ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIG) + return -1; /* it's not a Zip64 archive. Not an error, though! */ + + info->zip64 = 1; + + /* number of the disk with the start of the central directory. */ + BAIL_IF_ERRPASS(!readui32(io, &ui32), 0); + BAIL_IF(ui32 != 0, PHYSFS_ERR_CORRUPT, 0); + + /* offset of Zip64 end of central directory record. */ + BAIL_IF_ERRPASS(!readui64(io, &ui64), 0); + + /* total number of disks */ + BAIL_IF_ERRPASS(!readui32(io, &ui32), 0); + BAIL_IF(ui32 != 1, PHYSFS_ERR_CORRUPT, 0); + + pos = zip64_find_end_of_central_dir(io, pos, ui64); + if (pos < 0) + return 0; /* oh well. */ + + /* + * For self-extracting archives, etc, there's crapola in the file + * before the zipfile records; we calculate how much data there is + * prepended by determining how far the zip64-end-of-central-directory + * offset is from where it is supposed to be...the difference in bytes + * is how much arbitrary data is at the start of the physical file. + */ + assert(((PHYSFS_uint64) pos) >= ui64); + *data_start = ((PHYSFS_uint64) pos) - ui64; + + BAIL_IF_ERRPASS(!io->seek(io, pos), 0); + + /* check signature again, just in case. */ + BAIL_IF_ERRPASS(!readui32(io, &ui32), 0); + BAIL_IF(ui32 != ZIP64_END_OF_CENTRAL_DIR_SIG, PHYSFS_ERR_CORRUPT, 0); + + /* size of Zip64 end of central directory record. */ + BAIL_IF_ERRPASS(!readui64(io, &ui64), 0); + + /* version made by. */ + BAIL_IF_ERRPASS(!readui16(io, &ui16), 0); + + /* version needed to extract. */ + BAIL_IF_ERRPASS(!readui16(io, &ui16), 0); + + /* number of this disk. */ + BAIL_IF_ERRPASS(!readui32(io, &ui32), 0); + BAIL_IF(ui32 != 0, PHYSFS_ERR_CORRUPT, 0); + + /* number of disk with start of central directory record. */ + BAIL_IF_ERRPASS(!readui32(io, &ui32), 0); + BAIL_IF(ui32 != 0, PHYSFS_ERR_CORRUPT, 0); + + /* total number of entries in the central dir on this disk */ + BAIL_IF_ERRPASS(!readui64(io, &ui64), 0); + + /* total number of entries in the central dir */ + BAIL_IF_ERRPASS(!readui64(io, entry_count), 0); + BAIL_IF(ui64 != *entry_count, PHYSFS_ERR_CORRUPT, 0); + + /* size of the central directory */ + BAIL_IF_ERRPASS(!readui64(io, &ui64), 0); + + /* offset of central directory */ + BAIL_IF_ERRPASS(!readui64(io, dir_ofs), 0); + + /* Since we know the difference, fix up the central dir offset... */ + *dir_ofs += *data_start; + + /* + * There are more fields here, for encryption and feature-specific things, + * but we don't care about any of them at the moment. + */ + + return 1; /* made it. */ +} /* zip64_parse_end_of_central_dir */ + + +static int zip_parse_end_of_central_dir(ZIPinfo *info, + PHYSFS_uint64 *data_start, + PHYSFS_uint64 *dir_ofs, + PHYSFS_uint64 *entry_count) +{ + PHYSFS_Io *io = info->io; + PHYSFS_uint16 entryCount16; + PHYSFS_uint32 offset32; + PHYSFS_uint32 ui32; + PHYSFS_uint16 ui16; + PHYSFS_sint64 len; + PHYSFS_sint64 pos; + int rc; + + /* find the end-of-central-dir record, and seek to it. */ + pos = zip_find_end_of_central_dir(io, &len); + BAIL_IF_ERRPASS(pos == -1, 0); + BAIL_IF_ERRPASS(!io->seek(io, pos), 0); + + /* check signature again, just in case. */ + BAIL_IF_ERRPASS(!readui32(io, &ui32), 0); + BAIL_IF(ui32 != ZIP_END_OF_CENTRAL_DIR_SIG, PHYSFS_ERR_CORRUPT, 0); + + /* Seek back to see if "Zip64 end of central directory locator" exists. */ + /* this record is 20 bytes before end-of-central-dir */ + rc = zip64_parse_end_of_central_dir(info, data_start, dir_ofs, + entry_count, pos - 20); + + /* Error or success? Bounce out of here. Keep going if not zip64. */ + if ((rc == 0) || (rc == 1)) + return rc; + + assert(rc == -1); /* no error, just not a Zip64 archive. */ + + /* Not Zip64? Seek back to where we were and keep processing. */ + BAIL_IF_ERRPASS(!io->seek(io, pos + 4), 0); + + /* number of this disk */ + BAIL_IF_ERRPASS(!readui16(io, &ui16), 0); + BAIL_IF(ui16 != 0, PHYSFS_ERR_CORRUPT, 0); + + /* number of the disk with the start of the central directory */ + BAIL_IF_ERRPASS(!readui16(io, &ui16), 0); + BAIL_IF(ui16 != 0, PHYSFS_ERR_CORRUPT, 0); + + /* total number of entries in the central dir on this disk */ + BAIL_IF_ERRPASS(!readui16(io, &ui16), 0); + + /* total number of entries in the central dir */ + BAIL_IF_ERRPASS(!readui16(io, &entryCount16), 0); + BAIL_IF(ui16 != entryCount16, PHYSFS_ERR_CORRUPT, 0); + + *entry_count = entryCount16; + + /* size of the central directory */ + BAIL_IF_ERRPASS(!readui32(io, &ui32), 0); + + /* offset of central directory */ + BAIL_IF_ERRPASS(!readui32(io, &offset32), 0); + *dir_ofs = (PHYSFS_uint64) offset32; + BAIL_IF(((PHYSFS_uint64) pos) < (*dir_ofs + ui32), PHYSFS_ERR_CORRUPT, 0); + + /* + * For self-extracting archives, etc, there's crapola in the file + * before the zipfile records; we calculate how much data there is + * prepended by determining how far the central directory offset is + * from where it is supposed to be (start of end-of-central-dir minus + * sizeof central dir)...the difference in bytes is how much arbitrary + * data is at the start of the physical file. + */ + *data_start = (PHYSFS_uint64) (pos - (*dir_ofs + ui32)); + + /* Now that we know the difference, fix up the central dir offset... */ + *dir_ofs += *data_start; + + /* zipfile comment length */ + BAIL_IF_ERRPASS(!readui16(io, &ui16), 0); + + /* + * Make sure that the comment length matches to the end of file... + * If it doesn't, we're either in the wrong part of the file, or the + * file is corrupted, but we give up either way. + */ + BAIL_IF((pos + 22 + ui16) != len, PHYSFS_ERR_CORRUPT, 0); + + return 1; /* made it. */ +} /* zip_parse_end_of_central_dir */ + + +static void ZIP_closeArchive(void *opaque) +{ + ZIPinfo *info = (ZIPinfo *) (opaque); + + if (!info) + return; + + if (info->io) + info->io->destroy(info->io); + + __PHYSFS_DirTreeDeinit(&info->tree); + + allocator.Free(info); +} /* ZIP_closeArchive */ + + +static void *ZIP_openArchive(PHYSFS_Io *io, const char *name, + int forWriting, int *claimed) +{ + ZIPinfo *info = NULL; + ZIPentry *root = NULL; + PHYSFS_uint64 dstart = 0; /* data start */ + PHYSFS_uint64 cdir_ofs; /* central dir offset */ + PHYSFS_uint64 count; + + assert(io != NULL); /* shouldn't ever happen. */ + + BAIL_IF(forWriting, PHYSFS_ERR_READ_ONLY, NULL); + BAIL_IF_ERRPASS(!isZip(io), NULL); + + *claimed = 1; + + info = (ZIPinfo *) allocator.Malloc(sizeof (ZIPinfo)); + BAIL_IF(!info, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + memset(info, '\0', sizeof (ZIPinfo)); + + info->io = io; + + if (!zip_parse_end_of_central_dir(info, &dstart, &cdir_ofs, &count)) + goto ZIP_openarchive_failed; + else if (!__PHYSFS_DirTreeInit(&info->tree, sizeof (ZIPentry))) + goto ZIP_openarchive_failed; + + root = (ZIPentry *) info->tree.root; + root->resolved = ZIP_DIRECTORY; + + if (!zip_load_entries(info, dstart, cdir_ofs, count)) + goto ZIP_openarchive_failed; + + assert(info->tree.root->sibling == NULL); + return info; + +ZIP_openarchive_failed: + info->io = NULL; /* don't let ZIP_closeArchive destroy (io). */ + ZIP_closeArchive(info); + return NULL; +} /* ZIP_openArchive */ + + +static PHYSFS_Io *zip_get_io(PHYSFS_Io *io, ZIPinfo *inf, ZIPentry *entry) +{ + int success; + PHYSFS_Io *retval = io->duplicate(io); + BAIL_IF_ERRPASS(!retval, NULL); + + assert(!entry->tree.isdir); /* should have been checked before calling. */ + + /* (inf) can be NULL if we already resolved. */ + success = (inf == NULL) || zip_resolve(retval, inf, entry); + if (success) + { + PHYSFS_sint64 offset; + offset = ((entry->symlink) ? entry->symlink->offset : entry->offset); + success = retval->seek(retval, offset); + } /* if */ + + if (!success) + { + retval->destroy(retval); + retval = NULL; + } /* if */ + + return retval; +} /* zip_get_io */ + + +static PHYSFS_Io *ZIP_openRead(void *opaque, const char *filename) +{ + PHYSFS_Io *retval = NULL; + ZIPinfo *info = (ZIPinfo *) opaque; + ZIPentry *entry = zip_find_entry(info, filename); + ZIPfileinfo *finfo = NULL; + PHYSFS_Io *io = NULL; + PHYSFS_uint8 *password = NULL; + + /* if not found, see if maybe "$PASSWORD" is appended. */ + if ((!entry) && (info->has_crypto)) + { + const char *ptr = strrchr(filename, '$'); + if (ptr != NULL) + { + const size_t len = (size_t) (ptr - filename); + char *str = (char *) __PHYSFS_smallAlloc(len + 1); + BAIL_IF(!str, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + memcpy(str, filename, len); + str[len] = '\0'; + entry = zip_find_entry(info, str); + __PHYSFS_smallFree(str); + password = (PHYSFS_uint8 *) (ptr + 1); + } /* if */ + } /* if */ + + BAIL_IF_ERRPASS(!entry, NULL); + + BAIL_IF_ERRPASS(!zip_resolve(info->io, info, entry), NULL); + + BAIL_IF(entry->tree.isdir, PHYSFS_ERR_NOT_A_FILE, NULL); + + retval = (PHYSFS_Io *) allocator.Malloc(sizeof (PHYSFS_Io)); + GOTO_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, ZIP_openRead_failed); + + finfo = (ZIPfileinfo *) allocator.Malloc(sizeof (ZIPfileinfo)); + GOTO_IF(!finfo, PHYSFS_ERR_OUT_OF_MEMORY, ZIP_openRead_failed); + memset(finfo, '\0', sizeof (ZIPfileinfo)); + + io = zip_get_io(info->io, info, entry); + GOTO_IF_ERRPASS(!io, ZIP_openRead_failed); + finfo->io = io; + finfo->entry = ((entry->symlink != NULL) ? entry->symlink : entry); + initializeZStream(&finfo->stream); + + if (finfo->entry->compression_method != COMPMETH_NONE) + { + finfo->buffer = (PHYSFS_uint8 *) allocator.Malloc(ZIP_READBUFSIZE); + if (!finfo->buffer) + GOTO(PHYSFS_ERR_OUT_OF_MEMORY, ZIP_openRead_failed); + else if (zlib_err(inflateInit2(&finfo->stream, -MAX_WBITS)) != Z_OK) + goto ZIP_openRead_failed; + } /* if */ + + if (!zip_entry_is_tradional_crypto(entry)) + GOTO_IF(password != NULL, PHYSFS_ERR_BAD_PASSWORD, ZIP_openRead_failed); + else + { + PHYSFS_uint8 crypto_header[12]; + GOTO_IF(password == NULL, PHYSFS_ERR_BAD_PASSWORD, ZIP_openRead_failed); + if (io->read(io, crypto_header, 12) != 12) + goto ZIP_openRead_failed; + else if (!zip_prep_crypto_keys(finfo, crypto_header, password)) + goto ZIP_openRead_failed; + } /* if */ + + memcpy(retval, &ZIP_Io, sizeof (PHYSFS_Io)); + retval->opaque = finfo; + + return retval; + +ZIP_openRead_failed: + if (finfo != NULL) + { + if (finfo->io != NULL) + finfo->io->destroy(finfo->io); + + if (finfo->buffer != NULL) + { + allocator.Free(finfo->buffer); + inflateEnd(&finfo->stream); + } /* if */ + + allocator.Free(finfo); + } /* if */ + + if (retval != NULL) + allocator.Free(retval); + + return NULL; +} /* ZIP_openRead */ + + +static PHYSFS_Io *ZIP_openWrite(void *opaque, const char *filename) +{ + BAIL(PHYSFS_ERR_READ_ONLY, NULL); +} /* ZIP_openWrite */ + + +static PHYSFS_Io *ZIP_openAppend(void *opaque, const char *filename) +{ + BAIL(PHYSFS_ERR_READ_ONLY, NULL); +} /* ZIP_openAppend */ + + +static int ZIP_remove(void *opaque, const char *name) +{ + BAIL(PHYSFS_ERR_READ_ONLY, 0); +} /* ZIP_remove */ + + +static int ZIP_mkdir(void *opaque, const char *name) +{ + BAIL(PHYSFS_ERR_READ_ONLY, 0); +} /* ZIP_mkdir */ + + +static int ZIP_stat(void *opaque, const char *filename, PHYSFS_Stat *stat) +{ + ZIPinfo *info = (ZIPinfo *) opaque; + ZIPentry *entry = zip_find_entry(info, filename); + + if (entry == NULL) + return 0; + + else if (!zip_resolve(info->io, info, entry)) + return 0; + + else if (entry->resolved == ZIP_DIRECTORY) + { + stat->filesize = 0; + stat->filetype = PHYSFS_FILETYPE_DIRECTORY; + } /* if */ + + else if (zip_entry_is_symlink(entry)) + { + stat->filesize = 0; + stat->filetype = PHYSFS_FILETYPE_SYMLINK; + } /* else if */ + + else + { + stat->filesize = (PHYSFS_sint64) entry->uncompressed_size; + stat->filetype = PHYSFS_FILETYPE_REGULAR; + } /* else */ + + stat->modtime = ((entry) ? entry->last_mod_time : 0); + stat->createtime = stat->modtime; + stat->accesstime = -1; + stat->readonly = 1; /* .zip files are always read only */ + + return 1; +} /* ZIP_stat */ + + +const PHYSFS_Archiver __PHYSFS_Archiver_ZIP = +{ + CURRENT_PHYSFS_ARCHIVER_API_VERSION, + { + "ZIP", + "PkZip/WinZip/Info-Zip compatible", + "Ryan C. Gordon ", + "https://icculus.org/physfs/", + 1, /* supportsSymlinks */ + }, + ZIP_openArchive, + __PHYSFS_DirTreeEnumerate, + ZIP_openRead, + ZIP_openWrite, + ZIP_openAppend, + ZIP_remove, + ZIP_mkdir, + ZIP_stat, + ZIP_closeArchive +}; + +#endif /* defined PHYSFS_SUPPORTS_ZIP */ + +/* end of physfs_archiver_zip.c ... */ + diff --git a/Source/Dependencies/PhysicsFS/physfs_byteorder.c b/Source/Dependencies/PhysicsFS/physfs_byteorder.c new file mode 100644 index 000000000..b2d3a2cff --- /dev/null +++ b/Source/Dependencies/PhysicsFS/physfs_byteorder.c @@ -0,0 +1,137 @@ +/** + * PhysicsFS; a portable, flexible file i/o abstraction. + * + * Documentation is in physfs.h. It's verbose, honest. :) + * + * Please see the file LICENSE.txt in the source's root directory. + * + * This file written by Ryan C. Gordon. + */ + +#define __PHYSICSFS_INTERNAL__ +#include "physfs_internal.h" + +#ifndef PHYSFS_Swap16 +static inline PHYSFS_uint16 PHYSFS_Swap16(PHYSFS_uint16 D) +{ + return ((D<<8)|(D>>8)); +} +#endif +#ifndef PHYSFS_Swap32 +static inline PHYSFS_uint32 PHYSFS_Swap32(PHYSFS_uint32 D) +{ + return ((D<<24)|((D<<8)&0x00FF0000)|((D>>8)&0x0000FF00)|(D>>24)); +} +#endif +#ifndef PHYSFS_NO_64BIT_SUPPORT +#ifndef PHYSFS_Swap64 +static inline PHYSFS_uint64 PHYSFS_Swap64(PHYSFS_uint64 val) { + PHYSFS_uint32 hi, lo; + + /* Separate into high and low 32-bit values and swap them */ + lo = (PHYSFS_uint32)(val&0xFFFFFFFF); + val >>= 32; + hi = (PHYSFS_uint32)(val&0xFFFFFFFF); + val = PHYSFS_Swap32(lo); + val <<= 32; + val |= PHYSFS_Swap32(hi); + return val; +} +#endif +#else +#ifndef PHYSFS_Swap64 +/* This is mainly to keep compilers from complaining in PHYSFS code. + If there is no real 64-bit datatype, then compilers will complain about + the fake 64-bit datatype that PHYSFS provides when it compiles user code. +*/ +#define PHYSFS_Swap64(X) (X) +#endif +#endif /* PHYSFS_NO_64BIT_SUPPORT */ + + +/* Byteswap item from the specified endianness to the native endianness */ +#if PHYSFS_BYTEORDER == PHYSFS_LIL_ENDIAN +PHYSFS_uint16 PHYSFS_swapULE16(PHYSFS_uint16 x) { return x; } +PHYSFS_sint16 PHYSFS_swapSLE16(PHYSFS_sint16 x) { return x; } +PHYSFS_uint32 PHYSFS_swapULE32(PHYSFS_uint32 x) { return x; } +PHYSFS_sint32 PHYSFS_swapSLE32(PHYSFS_sint32 x) { return x; } +PHYSFS_uint64 PHYSFS_swapULE64(PHYSFS_uint64 x) { return x; } +PHYSFS_sint64 PHYSFS_swapSLE64(PHYSFS_sint64 x) { return x; } + +PHYSFS_uint16 PHYSFS_swapUBE16(PHYSFS_uint16 x) { return PHYSFS_Swap16(x); } +PHYSFS_sint16 PHYSFS_swapSBE16(PHYSFS_sint16 x) { return PHYSFS_Swap16(x); } +PHYSFS_uint32 PHYSFS_swapUBE32(PHYSFS_uint32 x) { return PHYSFS_Swap32(x); } +PHYSFS_sint32 PHYSFS_swapSBE32(PHYSFS_sint32 x) { return PHYSFS_Swap32(x); } +PHYSFS_uint64 PHYSFS_swapUBE64(PHYSFS_uint64 x) { return PHYSFS_Swap64(x); } +PHYSFS_sint64 PHYSFS_swapSBE64(PHYSFS_sint64 x) { return PHYSFS_Swap64(x); } +#else +PHYSFS_uint16 PHYSFS_swapULE16(PHYSFS_uint16 x) { return PHYSFS_Swap16(x); } +PHYSFS_sint16 PHYSFS_swapSLE16(PHYSFS_sint16 x) { return PHYSFS_Swap16(x); } +PHYSFS_uint32 PHYSFS_swapULE32(PHYSFS_uint32 x) { return PHYSFS_Swap32(x); } +PHYSFS_sint32 PHYSFS_swapSLE32(PHYSFS_sint32 x) { return PHYSFS_Swap32(x); } +PHYSFS_uint64 PHYSFS_swapULE64(PHYSFS_uint64 x) { return PHYSFS_Swap64(x); } +PHYSFS_sint64 PHYSFS_swapSLE64(PHYSFS_sint64 x) { return PHYSFS_Swap64(x); } + +PHYSFS_uint16 PHYSFS_swapUBE16(PHYSFS_uint16 x) { return x; } +PHYSFS_sint16 PHYSFS_swapSBE16(PHYSFS_sint16 x) { return x; } +PHYSFS_uint32 PHYSFS_swapUBE32(PHYSFS_uint32 x) { return x; } +PHYSFS_sint32 PHYSFS_swapSBE32(PHYSFS_sint32 x) { return x; } +PHYSFS_uint64 PHYSFS_swapUBE64(PHYSFS_uint64 x) { return x; } +PHYSFS_sint64 PHYSFS_swapSBE64(PHYSFS_sint64 x) { return x; } +#endif + +static inline int readAll(PHYSFS_File *file, void *val, const size_t len) +{ + return (PHYSFS_readBytes(file, val, len) == len); +} /* readAll */ + +#define PHYSFS_BYTEORDER_READ(datatype, swaptype) \ + int PHYSFS_read##swaptype(PHYSFS_File *file, PHYSFS_##datatype *val) { \ + PHYSFS_##datatype in; \ + BAIL_IF(val == NULL, PHYSFS_ERR_INVALID_ARGUMENT, 0); \ + BAIL_IF_ERRPASS(!readAll(file, &in, sizeof (in)), 0); \ + *val = PHYSFS_swap##swaptype(in); \ + return 1; \ + } + +PHYSFS_BYTEORDER_READ(sint16, SLE16) +PHYSFS_BYTEORDER_READ(uint16, ULE16) +PHYSFS_BYTEORDER_READ(sint16, SBE16) +PHYSFS_BYTEORDER_READ(uint16, UBE16) +PHYSFS_BYTEORDER_READ(sint32, SLE32) +PHYSFS_BYTEORDER_READ(uint32, ULE32) +PHYSFS_BYTEORDER_READ(sint32, SBE32) +PHYSFS_BYTEORDER_READ(uint32, UBE32) +PHYSFS_BYTEORDER_READ(sint64, SLE64) +PHYSFS_BYTEORDER_READ(uint64, ULE64) +PHYSFS_BYTEORDER_READ(sint64, SBE64) +PHYSFS_BYTEORDER_READ(uint64, UBE64) + + +static inline int writeAll(PHYSFS_File *f, const void *val, const size_t len) +{ + return (PHYSFS_writeBytes(f, val, len) == len); +} /* writeAll */ + +#define PHYSFS_BYTEORDER_WRITE(datatype, swaptype) \ + int PHYSFS_write##swaptype(PHYSFS_File *file, PHYSFS_##datatype val) { \ + const PHYSFS_##datatype out = PHYSFS_swap##swaptype(val); \ + BAIL_IF_ERRPASS(!writeAll(file, &out, sizeof (out)), 0); \ + return 1; \ + } + +PHYSFS_BYTEORDER_WRITE(sint16, SLE16) +PHYSFS_BYTEORDER_WRITE(uint16, ULE16) +PHYSFS_BYTEORDER_WRITE(sint16, SBE16) +PHYSFS_BYTEORDER_WRITE(uint16, UBE16) +PHYSFS_BYTEORDER_WRITE(sint32, SLE32) +PHYSFS_BYTEORDER_WRITE(uint32, ULE32) +PHYSFS_BYTEORDER_WRITE(sint32, SBE32) +PHYSFS_BYTEORDER_WRITE(uint32, UBE32) +PHYSFS_BYTEORDER_WRITE(sint64, SLE64) +PHYSFS_BYTEORDER_WRITE(uint64, ULE64) +PHYSFS_BYTEORDER_WRITE(sint64, SBE64) +PHYSFS_BYTEORDER_WRITE(uint64, UBE64) + +/* end of physfs_byteorder.c ... */ + diff --git a/Source/Dependencies/PhysicsFS/physfs_casefolding.h b/Source/Dependencies/PhysicsFS/physfs_casefolding.h new file mode 100644 index 000000000..bb6ac189e --- /dev/null +++ b/Source/Dependencies/PhysicsFS/physfs_casefolding.h @@ -0,0 +1,2572 @@ +/* + * This file is part of PhysicsFS (https://icculus.org/physfs/) + * + * This data generated by physfs/extras/makecasefoldhashtable.pl ... + * Do not manually edit this file! + * + * Please see the file LICENSE.txt in the source's root directory. + */ + +#ifndef _INCLUDE_PHYSFS_CASEFOLDING_H_ +#define _INCLUDE_PHYSFS_CASEFOLDING_H_ + +#ifndef __PHYSICSFS_INTERNAL__ +#error Do not include this header from your applications. +#endif + +/* We build three simple hashmaps here: one that maps Unicode codepoints to +a one, two, or three lowercase codepoints. To retrieve this info: look at +case_fold_hashX, where X is 1, 2, or 3. Most foldable codepoints fold to one, +a few dozen fold to two, and a handful fold to three. If the codepoint isn't +in any of these hashes, it doesn't fold (no separate upper and lowercase). + +Almost all these codepoints fit into 16 bits, so we hash them as such to save +memory. If a codepoint is > 0xFFFF, we have separate hashes for them, +since there are (currently) only about 120 of them and (currently) all of them +map to a single lowercase codepoint. */ + +typedef struct CaseFoldMapping1_32 +{ + PHYSFS_uint32 from; + PHYSFS_uint32 to0; +} CaseFoldMapping1_32; + +typedef struct CaseFoldMapping1_16 +{ + PHYSFS_uint16 from; + PHYSFS_uint16 to0; +} CaseFoldMapping1_16; + +typedef struct CaseFoldMapping2_16 +{ + PHYSFS_uint16 from; + PHYSFS_uint16 to0; + PHYSFS_uint16 to1; +} CaseFoldMapping2_16; + +typedef struct CaseFoldMapping3_16 +{ + PHYSFS_uint16 from; + PHYSFS_uint16 to0; + PHYSFS_uint16 to1; + PHYSFS_uint16 to2; +} CaseFoldMapping3_16; + +typedef struct CaseFoldHashBucket1_16 +{ + const CaseFoldMapping1_16 *list; + const PHYSFS_uint8 count; +} CaseFoldHashBucket1_16; + +typedef struct CaseFoldHashBucket1_32 +{ + const CaseFoldMapping1_32 *list; + const PHYSFS_uint8 count; +} CaseFoldHashBucket1_32; + +typedef struct CaseFoldHashBucket2_16 +{ + const CaseFoldMapping2_16 *list; + const PHYSFS_uint8 count; +} CaseFoldHashBucket2_16; + +typedef struct CaseFoldHashBucket3_16 +{ + const CaseFoldMapping3_16 *list; + const PHYSFS_uint8 count; +} CaseFoldHashBucket3_16; + +static const CaseFoldMapping1_16 case_fold1_16_000[] = { + { 0x0202, 0x0203 }, + { 0x0404, 0x0454 }, + { 0x1E1E, 0x1E1F }, + { 0x2C2C, 0x2C5C }, + { 0xABAB, 0x13DB } +}; + +static const CaseFoldMapping1_16 case_fold1_16_001[] = { + { 0x0100, 0x0101 }, + { 0x0405, 0x0455 }, + { 0x0504, 0x0505 }, + { 0x2C2D, 0x2C5D }, + { 0xA7A6, 0xA7A7 }, + { 0xABAA, 0x13DA } +}; + +static const CaseFoldMapping1_16 case_fold1_16_002[] = { + { 0x0200, 0x0201 }, + { 0x0406, 0x0456 }, + { 0x1E1C, 0x1E1D }, + { 0x1F1D, 0x1F15 }, + { 0x2C2E, 0x2C5E }, + { 0xABA9, 0x13D9 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_003[] = { + { 0x0102, 0x0103 }, + { 0x0407, 0x0457 }, + { 0x0506, 0x0507 }, + { 0x1F1C, 0x1F14 }, + { 0xA7A4, 0xA7A5 }, + { 0xABA8, 0x13D8 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_004[] = { + { 0x0206, 0x0207 }, + { 0x0400, 0x0450 }, + { 0x1E1A, 0x1E1B }, + { 0x1F1B, 0x1F13 }, + { 0x2C28, 0x2C58 }, + { 0xABAF, 0x13DF } +}; + +static const CaseFoldMapping1_16 case_fold1_16_005[] = { + { 0x0104, 0x0105 }, + { 0x0401, 0x0451 }, + { 0x0500, 0x0501 }, + { 0x1F1A, 0x1F12 }, + { 0x2C29, 0x2C59 }, + { 0xA7A2, 0xA7A3 }, + { 0xABAE, 0x13DE } +}; + +static const CaseFoldMapping1_16 case_fold1_16_006[] = { + { 0x0204, 0x0205 }, + { 0x0402, 0x0452 }, + { 0x1E18, 0x1E19 }, + { 0x1F19, 0x1F11 }, + { 0x2C2A, 0x2C5A }, + { 0xABAD, 0x13DD } +}; + +static const CaseFoldMapping1_16 case_fold1_16_007[] = { + { 0x0106, 0x0107 }, + { 0x0403, 0x0453 }, + { 0x0502, 0x0503 }, + { 0x1F18, 0x1F10 }, + { 0x2126, 0x03C9 }, + { 0x2C2B, 0x2C5B }, + { 0xA7A0, 0xA7A1 }, + { 0xABAC, 0x13DC } +}; + +static const CaseFoldMapping1_16 case_fold1_16_008[] = { + { 0x020A, 0x020B }, + { 0x040C, 0x045C }, + { 0x1E16, 0x1E17 }, + { 0x2C24, 0x2C54 }, + { 0xABA3, 0x13D3 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_009[] = { + { 0x0108, 0x0109 }, + { 0x040D, 0x045D }, + { 0x050C, 0x050D }, + { 0x2C25, 0x2C55 }, + { 0xABA2, 0x13D2 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_010[] = { + { 0x0208, 0x0209 }, + { 0x040E, 0x045E }, + { 0x1E14, 0x1E15 }, + { 0x212B, 0x00E5 }, + { 0x2C26, 0x2C56 }, + { 0xA7AD, 0x026C }, + { 0xABA1, 0x13D1 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_011[] = { + { 0x010A, 0x010B }, + { 0x040F, 0x045F }, + { 0x050E, 0x050F }, + { 0x212A, 0x006B }, + { 0x2C27, 0x2C57 }, + { 0xA7AC, 0x0261 }, + { 0xABA0, 0x13D0 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_012[] = { + { 0x020E, 0x020F }, + { 0x0408, 0x0458 }, + { 0x1E12, 0x1E13 }, + { 0x2C20, 0x2C50 }, + { 0xA7AB, 0x025C }, + { 0xABA7, 0x13D7 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_013[] = { + { 0x010C, 0x010D }, + { 0x0409, 0x0459 }, + { 0x0508, 0x0509 }, + { 0x2C21, 0x2C51 }, + { 0xA7AA, 0x0266 }, + { 0xABA6, 0x13D6 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_014[] = { + { 0x020C, 0x020D }, + { 0x040A, 0x045A }, + { 0x1E10, 0x1E11 }, + { 0x2C22, 0x2C52 }, + { 0xABA5, 0x13D5 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_015[] = { + { 0x010E, 0x010F }, + { 0x040B, 0x045B }, + { 0x050A, 0x050B }, + { 0x2C23, 0x2C53 }, + { 0xA7A8, 0xA7A9 }, + { 0xABA4, 0x13D4 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_016[] = { + { 0x0212, 0x0213 }, + { 0x0414, 0x0434 }, + { 0x1E0E, 0x1E0F }, + { 0x1F0F, 0x1F07 }, + { 0xABBB, 0x13EB } +}; + +static const CaseFoldMapping1_16 case_fold1_16_017[] = { + { 0x0110, 0x0111 }, + { 0x0415, 0x0435 }, + { 0x0514, 0x0515 }, + { 0x1F0E, 0x1F06 }, + { 0xA7B6, 0xA7B7 }, + { 0xABBA, 0x13EA } +}; + +static const CaseFoldMapping1_16 case_fold1_16_018[] = { + { 0x0210, 0x0211 }, + { 0x0416, 0x0436 }, + { 0x1E0C, 0x1E0D }, + { 0x1F0D, 0x1F05 }, + { 0xABB9, 0x13E9 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_019[] = { + { 0x0112, 0x0113 }, + { 0x0417, 0x0437 }, + { 0x0516, 0x0517 }, + { 0x1F0C, 0x1F04 }, + { 0x2132, 0x214E }, + { 0xA7B4, 0xA7B5 }, + { 0xABB8, 0x13E8 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_020[] = { + { 0x0216, 0x0217 }, + { 0x0410, 0x0430 }, + { 0x1E0A, 0x1E0B }, + { 0x1F0B, 0x1F03 }, + { 0xA7B3, 0xAB53 }, + { 0xABBF, 0x13EF } +}; + +static const CaseFoldMapping1_16 case_fold1_16_021[] = { + { 0x0114, 0x0115 }, + { 0x0411, 0x0431 }, + { 0x0510, 0x0511 }, + { 0x1F0A, 0x1F02 }, + { 0xA7B2, 0x029D }, + { 0xABBE, 0x13EE } +}; + +static const CaseFoldMapping1_16 case_fold1_16_022[] = { + { 0x0214, 0x0215 }, + { 0x0412, 0x0432 }, + { 0x1E08, 0x1E09 }, + { 0x1F09, 0x1F01 }, + { 0xA7B1, 0x0287 }, + { 0xABBD, 0x13ED } +}; + +static const CaseFoldMapping1_16 case_fold1_16_023[] = { + { 0x0116, 0x0117 }, + { 0x0413, 0x0433 }, + { 0x0512, 0x0513 }, + { 0x1F08, 0x1F00 }, + { 0xA7B0, 0x029E }, + { 0xABBC, 0x13EC } +}; + +static const CaseFoldMapping1_16 case_fold1_16_024[] = { + { 0x021A, 0x021B }, + { 0x041C, 0x043C }, + { 0x1E06, 0x1E07 }, + { 0xABB3, 0x13E3 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_025[] = { + { 0x0118, 0x0119 }, + { 0x041D, 0x043D }, + { 0x051C, 0x051D }, + { 0xABB2, 0x13E2 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_026[] = { + { 0x0218, 0x0219 }, + { 0x041E, 0x043E }, + { 0x1E04, 0x1E05 }, + { 0xABB1, 0x13E1 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_027[] = { + { 0x011A, 0x011B }, + { 0x041F, 0x043F }, + { 0x051E, 0x051F }, + { 0xABB0, 0x13E0 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_028[] = { + { 0x021E, 0x021F }, + { 0x0418, 0x0438 }, + { 0x1E02, 0x1E03 }, + { 0xABB7, 0x13E7 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_029[] = { + { 0x011C, 0x011D }, + { 0x0419, 0x0439 }, + { 0x0518, 0x0519 }, + { 0xABB6, 0x13E6 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_030[] = { + { 0x021C, 0x021D }, + { 0x041A, 0x043A }, + { 0x1E00, 0x1E01 }, + { 0xABB5, 0x13E5 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_031[] = { + { 0x011E, 0x011F }, + { 0x041B, 0x043B }, + { 0x051A, 0x051B }, + { 0xABB4, 0x13E4 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_032[] = { + { 0x0222, 0x0223 }, + { 0x0424, 0x0444 }, + { 0x1E3E, 0x1E3F }, + { 0x1F3F, 0x1F37 }, + { 0x2C0C, 0x2C3C }, + { 0xA686, 0xA687 }, + { 0xAB8B, 0x13BB } +}; + +static const CaseFoldMapping1_16 case_fold1_16_033[] = { + { 0x0120, 0x0121 }, + { 0x0425, 0x0445 }, + { 0x0524, 0x0525 }, + { 0x1F3E, 0x1F36 }, + { 0x2C0D, 0x2C3D }, + { 0xA786, 0xA787 }, + { 0xAB8A, 0x13BA } +}; + +static const CaseFoldMapping1_16 case_fold1_16_034[] = { + { 0x0220, 0x019E }, + { 0x0426, 0x0446 }, + { 0x1E3C, 0x1E3D }, + { 0x1F3D, 0x1F35 }, + { 0x2C0E, 0x2C3E }, + { 0xA684, 0xA685 }, + { 0xAB89, 0x13B9 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_035[] = { + { 0x0122, 0x0123 }, + { 0x0427, 0x0447 }, + { 0x0526, 0x0527 }, + { 0x1F3C, 0x1F34 }, + { 0x2C0F, 0x2C3F }, + { 0xA784, 0xA785 }, + { 0xAB88, 0x13B8 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_036[] = { + { 0x0226, 0x0227 }, + { 0x0420, 0x0440 }, + { 0x1E3A, 0x1E3B }, + { 0x1F3B, 0x1F33 }, + { 0x2C08, 0x2C38 }, + { 0xA682, 0xA683 }, + { 0xAB8F, 0x13BF } +}; + +static const CaseFoldMapping1_16 case_fold1_16_037[] = { + { 0x0124, 0x0125 }, + { 0x0421, 0x0441 }, + { 0x0520, 0x0521 }, + { 0x1F3A, 0x1F32 }, + { 0x2C09, 0x2C39 }, + { 0xA782, 0xA783 }, + { 0xAB8E, 0x13BE } +}; + +static const CaseFoldMapping1_16 case_fold1_16_038[] = { + { 0x0224, 0x0225 }, + { 0x0422, 0x0442 }, + { 0x1E38, 0x1E39 }, + { 0x1F39, 0x1F31 }, + { 0x2C0A, 0x2C3A }, + { 0xA680, 0xA681 }, + { 0xAB8D, 0x13BD } +}; + +static const CaseFoldMapping1_16 case_fold1_16_039[] = { + { 0x0126, 0x0127 }, + { 0x0423, 0x0443 }, + { 0x0522, 0x0523 }, + { 0x1F38, 0x1F30 }, + { 0x2C0B, 0x2C3B }, + { 0xA780, 0xA781 }, + { 0xAB8C, 0x13BC } +}; + +static const CaseFoldMapping1_16 case_fold1_16_040[] = { + { 0x022A, 0x022B }, + { 0x042C, 0x044C }, + { 0x1E36, 0x1E37 }, + { 0x2C04, 0x2C34 }, + { 0xA68E, 0xA68F }, + { 0xAB83, 0x13B3 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_041[] = { + { 0x0128, 0x0129 }, + { 0x042D, 0x044D }, + { 0x052C, 0x052D }, + { 0x2C05, 0x2C35 }, + { 0xAB82, 0x13B2 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_042[] = { + { 0x0228, 0x0229 }, + { 0x042E, 0x044E }, + { 0x1E34, 0x1E35 }, + { 0x2C06, 0x2C36 }, + { 0xA68C, 0xA68D }, + { 0xA78D, 0x0265 }, + { 0xAB81, 0x13B1 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_043[] = { + { 0x012A, 0x012B }, + { 0x042F, 0x044F }, + { 0x052E, 0x052F }, + { 0x2C07, 0x2C37 }, + { 0xAB80, 0x13B0 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_044[] = { + { 0x022E, 0x022F }, + { 0x0428, 0x0448 }, + { 0x1E32, 0x1E33 }, + { 0x2C00, 0x2C30 }, + { 0xA68A, 0xA68B }, + { 0xA78B, 0xA78C }, + { 0xAB87, 0x13B7 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_045[] = { + { 0x012C, 0x012D }, + { 0x0429, 0x0449 }, + { 0x0528, 0x0529 }, + { 0x2C01, 0x2C31 }, + { 0xAB86, 0x13B6 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_046[] = { + { 0x022C, 0x022D }, + { 0x042A, 0x044A }, + { 0x1E30, 0x1E31 }, + { 0x2C02, 0x2C32 }, + { 0xA688, 0xA689 }, + { 0xAB85, 0x13B5 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_047[] = { + { 0x012E, 0x012F }, + { 0x042B, 0x044B }, + { 0x052A, 0x052B }, + { 0x2C03, 0x2C33 }, + { 0xAB84, 0x13B4 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_048[] = { + { 0x0232, 0x0233 }, + { 0x0535, 0x0565 }, + { 0x1E2E, 0x1E2F }, + { 0x1F2F, 0x1F27 }, + { 0x2C1C, 0x2C4C }, + { 0xA696, 0xA697 }, + { 0xAB9B, 0x13CB } +}; + +static const CaseFoldMapping1_16 case_fold1_16_049[] = { + { 0x0534, 0x0564 }, + { 0x1F2E, 0x1F26 }, + { 0x2C1D, 0x2C4D }, + { 0xA796, 0xA797 }, + { 0xAB9A, 0x13CA } +}; + +static const CaseFoldMapping1_16 case_fold1_16_050[] = { + { 0x0230, 0x0231 }, + { 0x0537, 0x0567 }, + { 0x1E2C, 0x1E2D }, + { 0x1F2D, 0x1F25 }, + { 0x2C1E, 0x2C4E }, + { 0xA694, 0xA695 }, + { 0xAB99, 0x13C9 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_051[] = { + { 0x0132, 0x0133 }, + { 0x0536, 0x0566 }, + { 0x1F2C, 0x1F24 }, + { 0x2C1F, 0x2C4F }, + { 0xAB98, 0x13C8 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_052[] = { + { 0x0531, 0x0561 }, + { 0x1E2A, 0x1E2B }, + { 0x1F2B, 0x1F23 }, + { 0x2C18, 0x2C48 }, + { 0xA692, 0xA693 }, + { 0xAB9F, 0x13CF } +}; + +static const CaseFoldMapping1_16 case_fold1_16_053[] = { + { 0x0134, 0x0135 }, + { 0x1F2A, 0x1F22 }, + { 0x2C19, 0x2C49 }, + { 0xA792, 0xA793 }, + { 0xAB9E, 0x13CE } +}; + +static const CaseFoldMapping1_16 case_fold1_16_054[] = { + { 0x0533, 0x0563 }, + { 0x1E28, 0x1E29 }, + { 0x1F29, 0x1F21 }, + { 0x2C1A, 0x2C4A }, + { 0xA690, 0xA691 }, + { 0xAB9D, 0x13CD } +}; + +static const CaseFoldMapping1_16 case_fold1_16_055[] = { + { 0x0136, 0x0137 }, + { 0x0532, 0x0562 }, + { 0x1F28, 0x1F20 }, + { 0x2C1B, 0x2C4B }, + { 0xA790, 0xA791 }, + { 0xAB9C, 0x13CC } +}; + +static const CaseFoldMapping1_16 case_fold1_16_056[] = { + { 0x0139, 0x013A }, + { 0x023A, 0x2C65 }, + { 0x053D, 0x056D }, + { 0x1E26, 0x1E27 }, + { 0x2C14, 0x2C44 }, + { 0xAB93, 0x13C3 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_057[] = { + { 0x023B, 0x023C }, + { 0x053C, 0x056C }, + { 0x2C15, 0x2C45 }, + { 0xA79E, 0xA79F }, + { 0xAB92, 0x13C2 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_058[] = { + { 0x013B, 0x013C }, + { 0x053F, 0x056F }, + { 0x1E24, 0x1E25 }, + { 0x2C16, 0x2C46 }, + { 0xAB91, 0x13C1 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_059[] = { + { 0x053E, 0x056E }, + { 0x2C17, 0x2C47 }, + { 0xA79C, 0xA79D }, + { 0xAB90, 0x13C0 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_060[] = { + { 0x013D, 0x013E }, + { 0x023E, 0x2C66 }, + { 0x0539, 0x0569 }, + { 0x1E22, 0x1E23 }, + { 0x2C10, 0x2C40 }, + { 0xA69A, 0xA69B }, + { 0xAB97, 0x13C7 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_061[] = { + { 0x0538, 0x0568 }, + { 0x2C11, 0x2C41 }, + { 0xA79A, 0xA79B }, + { 0xAB96, 0x13C6 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_062[] = { + { 0x013F, 0x0140 }, + { 0x053B, 0x056B }, + { 0x1E20, 0x1E21 }, + { 0x2C12, 0x2C42 }, + { 0xA698, 0xA699 }, + { 0xAB95, 0x13C5 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_063[] = { + { 0x023D, 0x019A }, + { 0x053A, 0x056A }, + { 0x2C13, 0x2C43 }, + { 0xA798, 0xA799 }, + { 0xAB94, 0x13C4 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_064[] = { + { 0x0141, 0x0142 }, + { 0x0545, 0x0575 }, + { 0x1E5E, 0x1E5F }, + { 0x1F5F, 0x1F57 }, + { 0x2161, 0x2171 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_065[] = { + { 0x0041, 0x0061 }, + { 0x0243, 0x0180 }, + { 0x0544, 0x0574 }, + { 0x2160, 0x2170 }, + { 0x2C6D, 0x0251 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_066[] = { + { 0x0042, 0x0062 }, + { 0x0143, 0x0144 }, + { 0x0547, 0x0577 }, + { 0x1E5C, 0x1E5D }, + { 0x1F5D, 0x1F55 }, + { 0x2163, 0x2173 }, + { 0x2C6E, 0x0271 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_067[] = { + { 0x0043, 0x0063 }, + { 0x0241, 0x0242 }, + { 0x0546, 0x0576 }, + { 0x2162, 0x2172 }, + { 0x2C6F, 0x0250 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_068[] = { + { 0x0044, 0x0064 }, + { 0x0145, 0x0146 }, + { 0x0246, 0x0247 }, + { 0x0541, 0x0571 }, + { 0x1E5A, 0x1E5B }, + { 0x1F5B, 0x1F53 }, + { 0x2165, 0x2175 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_069[] = { + { 0x0045, 0x0065 }, + { 0x0540, 0x0570 }, + { 0x2164, 0x2174 }, + { 0x2C69, 0x2C6A } +}; + +static const CaseFoldMapping1_16 case_fold1_16_070[] = { + { 0x0046, 0x0066 }, + { 0x0147, 0x0148 }, + { 0x0244, 0x0289 }, + { 0x0345, 0x03B9 }, + { 0x0543, 0x0573 }, + { 0x1E58, 0x1E59 }, + { 0x1F59, 0x1F51 }, + { 0x2167, 0x2177 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_071[] = { + { 0x0047, 0x0067 }, + { 0x0245, 0x028C }, + { 0x0542, 0x0572 }, + { 0x2166, 0x2176 }, + { 0x2C6B, 0x2C6C } +}; + +static const CaseFoldMapping1_16 case_fold1_16_072[] = { + { 0x0048, 0x0068 }, + { 0x024A, 0x024B }, + { 0x054D, 0x057D }, + { 0x1E56, 0x1E57 }, + { 0x2169, 0x2179 }, + { 0x2C64, 0x027D } +}; + +static const CaseFoldMapping1_16 case_fold1_16_073[] = { + { 0x0049, 0x0069 }, + { 0x054C, 0x057C }, + { 0x2168, 0x2178 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_074[] = { + { 0x004A, 0x006A }, + { 0x0248, 0x0249 }, + { 0x054F, 0x057F }, + { 0x1E54, 0x1E55 }, + { 0x216B, 0x217B } +}; + +static const CaseFoldMapping1_16 case_fold1_16_075[] = { + { 0x004B, 0x006B }, + { 0x014A, 0x014B }, + { 0x054E, 0x057E }, + { 0x216A, 0x217A }, + { 0x2C67, 0x2C68 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_076[] = { + { 0x004C, 0x006C }, + { 0x024E, 0x024F }, + { 0x0549, 0x0579 }, + { 0x1E52, 0x1E53 }, + { 0x216D, 0x217D }, + { 0x2C60, 0x2C61 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_077[] = { + { 0x004D, 0x006D }, + { 0x014C, 0x014D }, + { 0x0548, 0x0578 }, + { 0x216C, 0x217C } +}; + +static const CaseFoldMapping1_16 case_fold1_16_078[] = { + { 0x004E, 0x006E }, + { 0x024C, 0x024D }, + { 0x054B, 0x057B }, + { 0x1E50, 0x1E51 }, + { 0x216F, 0x217F }, + { 0x2C62, 0x026B } +}; + +static const CaseFoldMapping1_16 case_fold1_16_079[] = { + { 0x004F, 0x006F }, + { 0x014E, 0x014F }, + { 0x054A, 0x057A }, + { 0x216E, 0x217E }, + { 0x2C63, 0x1D7D } +}; + +static const CaseFoldMapping1_16 case_fold1_16_080[] = { + { 0x0050, 0x0070 }, + { 0x0555, 0x0585 }, + { 0x1E4E, 0x1E4F } +}; + +static const CaseFoldMapping1_16 case_fold1_16_081[] = { + { 0x0051, 0x0071 }, + { 0x0150, 0x0151 }, + { 0x0554, 0x0584 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_082[] = { + { 0x0052, 0x0072 }, + { 0x1E4C, 0x1E4D }, + { 0x1F4D, 0x1F45 }, + { 0x2C7E, 0x023F } +}; + +static const CaseFoldMapping1_16 case_fold1_16_083[] = { + { 0x0053, 0x0073 }, + { 0x0152, 0x0153 }, + { 0x0556, 0x0586 }, + { 0x1F4C, 0x1F44 }, + { 0x2C7F, 0x0240 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_084[] = { + { 0x0054, 0x0074 }, + { 0x0551, 0x0581 }, + { 0x1E4A, 0x1E4B }, + { 0x1F4B, 0x1F43 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_085[] = { + { 0x0055, 0x0075 }, + { 0x0154, 0x0155 }, + { 0x0550, 0x0580 }, + { 0x1F4A, 0x1F42 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_086[] = { + { 0x0056, 0x0076 }, + { 0x0553, 0x0583 }, + { 0x1E48, 0x1E49 }, + { 0x1F49, 0x1F41 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_087[] = { + { 0x0057, 0x0077 }, + { 0x0156, 0x0157 }, + { 0x0552, 0x0582 }, + { 0x1F48, 0x1F40 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_088[] = { + { 0x0058, 0x0078 }, + { 0x1E46, 0x1E47 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_089[] = { + { 0x0059, 0x0079 }, + { 0x0158, 0x0159 }, + { 0x2C75, 0x2C76 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_090[] = { + { 0x005A, 0x007A }, + { 0x1E44, 0x1E45 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_091[] = { + { 0x015A, 0x015B } +}; + +static const CaseFoldMapping1_16 case_fold1_16_092[] = { + { 0x1E42, 0x1E43 }, + { 0x2C70, 0x0252 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_093[] = { + { 0x015C, 0x015D } +}; + +static const CaseFoldMapping1_16 case_fold1_16_094[] = { + { 0x1E40, 0x1E41 }, + { 0x2C72, 0x2C73 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_095[] = { + { 0x015E, 0x015F } +}; + +static const CaseFoldMapping1_16 case_fold1_16_096[] = { + { 0x0464, 0x0465 }, + { 0x1E7E, 0x1E7F } +}; + +static const CaseFoldMapping1_16 case_fold1_16_097[] = { + { 0x0160, 0x0161 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_098[] = { + { 0x0466, 0x0467 }, + { 0x1E7C, 0x1E7D } +}; + +static const CaseFoldMapping1_16 case_fold1_16_099[] = { + { 0x0162, 0x0163 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_100[] = { + { 0x0460, 0x0461 }, + { 0x1E7A, 0x1E7B } +}; + +static const CaseFoldMapping1_16 case_fold1_16_101[] = { + { 0x0164, 0x0165 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_102[] = { + { 0x0462, 0x0463 }, + { 0x1E78, 0x1E79 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_103[] = { + { 0x0166, 0x0167 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_104[] = { + { 0x046C, 0x046D }, + { 0x1E76, 0x1E77 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_105[] = { + { 0x0168, 0x0169 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_106[] = { + { 0x046E, 0x046F }, + { 0x1E74, 0x1E75 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_107[] = { + { 0x016A, 0x016B } +}; + +static const CaseFoldMapping1_16 case_fold1_16_108[] = { + { 0x0468, 0x0469 }, + { 0x1E72, 0x1E73 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_109[] = { + { 0x016C, 0x016D } +}; + +static const CaseFoldMapping1_16 case_fold1_16_110[] = { + { 0x046A, 0x046B }, + { 0x1E70, 0x1E71 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_111[] = { + { 0x016E, 0x016F } +}; + +static const CaseFoldMapping1_16 case_fold1_16_112[] = { + { 0x0474, 0x0475 }, + { 0x1E6E, 0x1E6F }, + { 0x1F6F, 0x1F67 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_113[] = { + { 0x0170, 0x0171 }, + { 0x0372, 0x0373 }, + { 0x1F6E, 0x1F66 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_114[] = { + { 0x0476, 0x0477 }, + { 0x1E6C, 0x1E6D }, + { 0x1F6D, 0x1F65 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_115[] = { + { 0x0172, 0x0173 }, + { 0x0370, 0x0371 }, + { 0x1F6C, 0x1F64 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_116[] = { + { 0x0470, 0x0471 }, + { 0x1E6A, 0x1E6B }, + { 0x1F6B, 0x1F63 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_117[] = { + { 0x0174, 0x0175 }, + { 0x0376, 0x0377 }, + { 0x1F6A, 0x1F62 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_118[] = { + { 0x0472, 0x0473 }, + { 0x1E68, 0x1E69 }, + { 0x1F69, 0x1F61 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_119[] = { + { 0x0176, 0x0177 }, + { 0x1F68, 0x1F60 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_120[] = { + { 0x0179, 0x017A }, + { 0x047C, 0x047D }, + { 0x1E66, 0x1E67 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_121[] = { + { 0x0178, 0x00FF } +}; + +static const CaseFoldMapping1_16 case_fold1_16_122[] = { + { 0x017B, 0x017C }, + { 0x047E, 0x047F }, + { 0x1E64, 0x1E65 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_124[] = { + { 0x017D, 0x017E }, + { 0x037F, 0x03F3 }, + { 0x0478, 0x0479 }, + { 0x1E62, 0x1E63 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_126[] = { + { 0x017F, 0x0073 }, + { 0x047A, 0x047B }, + { 0x1E60, 0x1E61 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_128[] = { + { 0x0181, 0x0253 }, + { 0x2CAC, 0x2CAD } +}; + +static const CaseFoldMapping1_16 case_fold1_16_129[] = { + { 0xA726, 0xA727 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_130[] = { + { 0x2CAE, 0x2CAF } +}; + +static const CaseFoldMapping1_16 case_fold1_16_131[] = { + { 0x0182, 0x0183 }, + { 0xA724, 0xA725 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_132[] = { + { 0x0480, 0x0481 }, + { 0x2CA8, 0x2CA9 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_133[] = { + { 0x0184, 0x0185 }, + { 0x0386, 0x03AC }, + { 0x1E9B, 0x1E61 }, + { 0xA722, 0xA723 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_134[] = { + { 0x0187, 0x0188 }, + { 0x2CAA, 0x2CAB } +}; + +static const CaseFoldMapping1_16 case_fold1_16_135[] = { + { 0x0186, 0x0254 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_136[] = { + { 0x0189, 0x0256 }, + { 0x048C, 0x048D }, + { 0x2CA4, 0x2CA5 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_137[] = { + { 0x038A, 0x03AF }, + { 0xA72E, 0xA72F } +}; + +static const CaseFoldMapping1_16 case_fold1_16_138[] = { + { 0x018B, 0x018C }, + { 0x0389, 0x03AE }, + { 0x048E, 0x048F }, + { 0x1E94, 0x1E95 }, + { 0x2CA6, 0x2CA7 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_139[] = { + { 0x018A, 0x0257 }, + { 0x0388, 0x03AD }, + { 0xA72C, 0xA72D } +}; + +static const CaseFoldMapping1_16 case_fold1_16_140[] = { + { 0x038F, 0x03CE }, + { 0x1E92, 0x1E93 }, + { 0x2CA0, 0x2CA1 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_141[] = { + { 0x038E, 0x03CD }, + { 0xA72A, 0xA72B } +}; + +static const CaseFoldMapping1_16 case_fold1_16_142[] = { + { 0x018F, 0x0259 }, + { 0x048A, 0x048B }, + { 0x1E90, 0x1E91 }, + { 0x2CA2, 0x2CA3 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_143[] = { + { 0x018E, 0x01DD }, + { 0x038C, 0x03CC }, + { 0xA728, 0xA729 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_144[] = { + { 0x0191, 0x0192 }, + { 0x0393, 0x03B3 }, + { 0x0494, 0x0495 }, + { 0x1E8E, 0x1E8F }, + { 0x2CBC, 0x2CBD } +}; + +static const CaseFoldMapping1_16 case_fold1_16_145[] = { + { 0x0190, 0x025B }, + { 0x0392, 0x03B2 }, + { 0xA736, 0xA737 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_146[] = { + { 0x0193, 0x0260 }, + { 0x0391, 0x03B1 }, + { 0x0496, 0x0497 }, + { 0x1E8C, 0x1E8D }, + { 0x24B6, 0x24D0 }, + { 0x2CBE, 0x2CBF } +}; + +static const CaseFoldMapping1_16 case_fold1_16_147[] = { + { 0x24B7, 0x24D1 }, + { 0xA734, 0xA735 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_148[] = { + { 0x0397, 0x03B7 }, + { 0x0490, 0x0491 }, + { 0x1E8A, 0x1E8B }, + { 0x2CB8, 0x2CB9 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_149[] = { + { 0x0194, 0x0263 }, + { 0x0396, 0x03B6 }, + { 0xA732, 0xA733 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_150[] = { + { 0x0197, 0x0268 }, + { 0x0395, 0x03B5 }, + { 0x0492, 0x0493 }, + { 0x1E88, 0x1E89 }, + { 0x2CBA, 0x2CBB } +}; + +static const CaseFoldMapping1_16 case_fold1_16_151[] = { + { 0x0196, 0x0269 }, + { 0x0394, 0x03B4 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_152[] = { + { 0x039B, 0x03BB }, + { 0x049C, 0x049D }, + { 0x1E86, 0x1E87 }, + { 0x24BC, 0x24D6 }, + { 0x2CB4, 0x2CB5 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_153[] = { + { 0x0198, 0x0199 }, + { 0x039A, 0x03BA }, + { 0x24BD, 0x24D7 }, + { 0xA73E, 0xA73F } +}; + +static const CaseFoldMapping1_16 case_fold1_16_154[] = { + { 0x0399, 0x03B9 }, + { 0x049E, 0x049F }, + { 0x1E84, 0x1E85 }, + { 0x24BE, 0x24D8 }, + { 0x2CB6, 0x2CB7 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_155[] = { + { 0x0398, 0x03B8 }, + { 0x24BF, 0x24D9 }, + { 0xA73C, 0xA73D } +}; + +static const CaseFoldMapping1_16 case_fold1_16_156[] = { + { 0x019D, 0x0272 }, + { 0x039F, 0x03BF }, + { 0x0498, 0x0499 }, + { 0x1E82, 0x1E83 }, + { 0x24B8, 0x24D2 }, + { 0x2CB0, 0x2CB1 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_157[] = { + { 0x019C, 0x026F }, + { 0x039E, 0x03BE }, + { 0x24B9, 0x24D3 }, + { 0xA73A, 0xA73B } +}; + +static const CaseFoldMapping1_16 case_fold1_16_158[] = { + { 0x019F, 0x0275 }, + { 0x039D, 0x03BD }, + { 0x049A, 0x049B }, + { 0x1E80, 0x1E81 }, + { 0x24BA, 0x24D4 }, + { 0x2CB2, 0x2CB3 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_159[] = { + { 0x039C, 0x03BC }, + { 0x24BB, 0x24D5 }, + { 0xA738, 0xA739 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_160[] = { + { 0x03A3, 0x03C3 }, + { 0x04A4, 0x04A5 }, + { 0x10B0, 0x2D10 }, + { 0x1EBE, 0x1EBF }, + { 0x2C8C, 0x2C8D } +}; + +static const CaseFoldMapping1_16 case_fold1_16_161[] = { + { 0x01A0, 0x01A1 }, + { 0x10B1, 0x2D11 }, + { 0x1FBE, 0x03B9 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_162[] = { + { 0x03A1, 0x03C1 }, + { 0x04A6, 0x04A7 }, + { 0x10B2, 0x2D12 }, + { 0x1EBC, 0x1EBD }, + { 0x2183, 0x2184 }, + { 0x2C8E, 0x2C8F } +}; + +static const CaseFoldMapping1_16 case_fold1_16_163[] = { + { 0x01A2, 0x01A3 }, + { 0x03A0, 0x03C0 }, + { 0x10B3, 0x2D13 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_164[] = { + { 0x03A7, 0x03C7 }, + { 0x04A0, 0x04A1 }, + { 0x10B4, 0x2D14 }, + { 0x1EBA, 0x1EBB }, + { 0x1FBB, 0x1F71 }, + { 0x2C88, 0x2C89 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_165[] = { + { 0x01A4, 0x01A5 }, + { 0x03A6, 0x03C6 }, + { 0x10B5, 0x2D15 }, + { 0x1FBA, 0x1F70 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_166[] = { + { 0x01A7, 0x01A8 }, + { 0x03A5, 0x03C5 }, + { 0x04A2, 0x04A3 }, + { 0x10B6, 0x2D16 }, + { 0x1EB8, 0x1EB9 }, + { 0x1FB9, 0x1FB1 }, + { 0x2C8A, 0x2C8B } +}; + +static const CaseFoldMapping1_16 case_fold1_16_167[] = { + { 0x01A6, 0x0280 }, + { 0x03A4, 0x03C4 }, + { 0x10B7, 0x2D17 }, + { 0x1FB8, 0x1FB0 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_168[] = { + { 0x01A9, 0x0283 }, + { 0x03AB, 0x03CB }, + { 0x04AC, 0x04AD }, + { 0x10B8, 0x2D18 }, + { 0x1EB6, 0x1EB7 }, + { 0x2C84, 0x2C85 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_169[] = { + { 0x03AA, 0x03CA }, + { 0x10B9, 0x2D19 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_170[] = { + { 0x03A9, 0x03C9 }, + { 0x04AE, 0x04AF }, + { 0x10BA, 0x2D1A }, + { 0x1EB4, 0x1EB5 }, + { 0x2C86, 0x2C87 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_171[] = { + { 0x03A8, 0x03C8 }, + { 0x10BB, 0x2D1B } +}; + +static const CaseFoldMapping1_16 case_fold1_16_172[] = { + { 0x04A8, 0x04A9 }, + { 0x10BC, 0x2D1C }, + { 0x1EB2, 0x1EB3 }, + { 0x2C80, 0x2C81 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_173[] = { + { 0x01AC, 0x01AD }, + { 0x10BD, 0x2D1D } +}; + +static const CaseFoldMapping1_16 case_fold1_16_174[] = { + { 0x01AF, 0x01B0 }, + { 0x04AA, 0x04AB }, + { 0x10BE, 0x2D1E }, + { 0x1EB0, 0x1EB1 }, + { 0x2C82, 0x2C83 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_175[] = { + { 0x01AE, 0x0288 }, + { 0x10BF, 0x2D1F } +}; + +static const CaseFoldMapping1_16 case_fold1_16_176[] = { + { 0x01B1, 0x028A }, + { 0x04B4, 0x04B5 }, + { 0x10A0, 0x2D00 }, + { 0x1EAE, 0x1EAF }, + { 0x2C9C, 0x2C9D } +}; + +static const CaseFoldMapping1_16 case_fold1_16_177[] = { + { 0x10A1, 0x2D01 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_178[] = { + { 0x01B3, 0x01B4 }, + { 0x04B6, 0x04B7 }, + { 0x10A2, 0x2D02 }, + { 0x1EAC, 0x1EAD }, + { 0x2C9E, 0x2C9F } +}; + +static const CaseFoldMapping1_16 case_fold1_16_179[] = { + { 0x01B2, 0x028B }, + { 0x10A3, 0x2D03 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_180[] = { + { 0x01B5, 0x01B6 }, + { 0x04B0, 0x04B1 }, + { 0x10A4, 0x2D04 }, + { 0x1EAA, 0x1EAB }, + { 0x2C98, 0x2C99 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_181[] = { + { 0x00B5, 0x03BC }, + { 0x10A5, 0x2D05 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_182[] = { + { 0x01B7, 0x0292 }, + { 0x04B2, 0x04B3 }, + { 0x10A6, 0x2D06 }, + { 0x1EA8, 0x1EA9 }, + { 0x2C9A, 0x2C9B } +}; + +static const CaseFoldMapping1_16 case_fold1_16_183[] = { + { 0x10A7, 0x2D07 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_184[] = { + { 0x04BC, 0x04BD }, + { 0x10A8, 0x2D08 }, + { 0x1EA6, 0x1EA7 }, + { 0x2C94, 0x2C95 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_185[] = { + { 0x01B8, 0x01B9 }, + { 0x10A9, 0x2D09 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_186[] = { + { 0x04BE, 0x04BF }, + { 0x10AA, 0x2D0A }, + { 0x1EA4, 0x1EA5 }, + { 0x2C96, 0x2C97 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_187[] = { + { 0x10AB, 0x2D0B } +}; + +static const CaseFoldMapping1_16 case_fold1_16_188[] = { + { 0x04B8, 0x04B9 }, + { 0x10AC, 0x2D0C }, + { 0x1EA2, 0x1EA3 }, + { 0x2C90, 0x2C91 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_189[] = { + { 0x01BC, 0x01BD }, + { 0x10AD, 0x2D0D } +}; + +static const CaseFoldMapping1_16 case_fold1_16_190[] = { + { 0x04BA, 0x04BB }, + { 0x10AE, 0x2D0E }, + { 0x1EA0, 0x1EA1 }, + { 0x2C92, 0x2C93 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_191[] = { + { 0x10AF, 0x2D0F } +}; + +static const CaseFoldMapping1_16 case_fold1_16_192[] = { + { 0x00C0, 0x00E0 }, + { 0x1EDE, 0x1EDF }, + { 0xA666, 0xA667 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_193[] = { + { 0x00C1, 0x00E1 }, + { 0x03C2, 0x03C3 }, + { 0x04C5, 0x04C6 }, + { 0x2CED, 0x2CEE }, + { 0xA766, 0xA767 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_194[] = { + { 0x00C2, 0x00E2 }, + { 0x1EDC, 0x1EDD }, + { 0xA664, 0xA665 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_195[] = { + { 0x00C3, 0x00E3 }, + { 0x04C7, 0x04C8 }, + { 0xA764, 0xA765 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_196[] = { + { 0x00C4, 0x00E4 }, + { 0x01C5, 0x01C6 }, + { 0x04C0, 0x04CF }, + { 0x1EDA, 0x1EDB }, + { 0x1FDB, 0x1F77 }, + { 0xA662, 0xA663 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_197[] = { + { 0x00C5, 0x00E5 }, + { 0x01C4, 0x01C6 }, + { 0x04C1, 0x04C2 }, + { 0x1FDA, 0x1F76 }, + { 0xA762, 0xA763 }, + { 0xFF3A, 0xFF5A } +}; + +static const CaseFoldMapping1_16 case_fold1_16_198[] = { + { 0x00C6, 0x00E6 }, + { 0x01C7, 0x01C9 }, + { 0x1ED8, 0x1ED9 }, + { 0x1FD9, 0x1FD1 }, + { 0xA660, 0xA661 }, + { 0xFF39, 0xFF59 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_199[] = { + { 0x00C7, 0x00E7 }, + { 0x04C3, 0x04C4 }, + { 0x1FD8, 0x1FD0 }, + { 0x2CEB, 0x2CEC }, + { 0xA760, 0xA761 }, + { 0xFF38, 0xFF58 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_200[] = { + { 0x00C8, 0x00E8 }, + { 0x1ED6, 0x1ED7 }, + { 0xFF37, 0xFF57 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_201[] = { + { 0x00C9, 0x00E9 }, + { 0x01C8, 0x01C9 }, + { 0x04CD, 0x04CE }, + { 0xA76E, 0xA76F }, + { 0xFF36, 0xFF56 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_202[] = { + { 0x00CA, 0x00EA }, + { 0x01CB, 0x01CC }, + { 0x1ED4, 0x1ED5 }, + { 0xA66C, 0xA66D }, + { 0xFF35, 0xFF55 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_203[] = { + { 0x00CB, 0x00EB }, + { 0x01CA, 0x01CC }, + { 0xA76C, 0xA76D }, + { 0xFF34, 0xFF54 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_204[] = { + { 0x00CC, 0x00EC }, + { 0x01CD, 0x01CE }, + { 0x03CF, 0x03D7 }, + { 0x1ED2, 0x1ED3 }, + { 0x2CE0, 0x2CE1 }, + { 0xA66A, 0xA66B }, + { 0xFF33, 0xFF53 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_205[] = { + { 0x00CD, 0x00ED }, + { 0x04C9, 0x04CA }, + { 0xA76A, 0xA76B }, + { 0xFF32, 0xFF52 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_206[] = { + { 0x00CE, 0x00EE }, + { 0x01CF, 0x01D0 }, + { 0x1ED0, 0x1ED1 }, + { 0x2CE2, 0x2CE3 }, + { 0xA668, 0xA669 }, + { 0xFF31, 0xFF51 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_207[] = { + { 0x00CF, 0x00EF }, + { 0x04CB, 0x04CC }, + { 0xA768, 0xA769 }, + { 0xFF30, 0xFF50 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_208[] = { + { 0x00D0, 0x00F0 }, + { 0x01D1, 0x01D2 }, + { 0x04D4, 0x04D5 }, + { 0x10C0, 0x2D20 }, + { 0x1ECE, 0x1ECF }, + { 0xAB7B, 0x13AB }, + { 0xFF2F, 0xFF4F } +}; + +static const CaseFoldMapping1_16 case_fold1_16_209[] = { + { 0x00D1, 0x00F1 }, + { 0x10C1, 0x2D21 }, + { 0xAB7A, 0x13AA }, + { 0xFF2E, 0xFF4E } +}; + +static const CaseFoldMapping1_16 case_fold1_16_210[] = { + { 0x00D2, 0x00F2 }, + { 0x01D3, 0x01D4 }, + { 0x03D1, 0x03B8 }, + { 0x04D6, 0x04D7 }, + { 0x10C2, 0x2D22 }, + { 0x1ECC, 0x1ECD }, + { 0xAB79, 0x13A9 }, + { 0xFF2D, 0xFF4D } +}; + +static const CaseFoldMapping1_16 case_fold1_16_211[] = { + { 0x00D3, 0x00F3 }, + { 0x03D0, 0x03B2 }, + { 0x10C3, 0x2D23 }, + { 0xAB78, 0x13A8 }, + { 0xFF2C, 0xFF4C } +}; + +static const CaseFoldMapping1_16 case_fold1_16_212[] = { + { 0x00D4, 0x00F4 }, + { 0x01D5, 0x01D6 }, + { 0x04D0, 0x04D1 }, + { 0x10C4, 0x2D24 }, + { 0x1ECA, 0x1ECB }, + { 0x1FCB, 0x1F75 }, + { 0xAB7F, 0x13AF }, + { 0xFF2B, 0xFF4B } +}; + +static const CaseFoldMapping1_16 case_fold1_16_213[] = { + { 0x00D5, 0x00F5 }, + { 0x03D6, 0x03C0 }, + { 0x10C5, 0x2D25 }, + { 0x1FCA, 0x1F74 }, + { 0xAB7E, 0x13AE }, + { 0xFF2A, 0xFF4A } +}; + +static const CaseFoldMapping1_16 case_fold1_16_214[] = { + { 0x00D6, 0x00F6 }, + { 0x01D7, 0x01D8 }, + { 0x03D5, 0x03C6 }, + { 0x04D2, 0x04D3 }, + { 0x1EC8, 0x1EC9 }, + { 0x1FC9, 0x1F73 }, + { 0xAB7D, 0x13AD }, + { 0xFF29, 0xFF49 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_215[] = { + { 0x10C7, 0x2D27 }, + { 0x1FC8, 0x1F72 }, + { 0xAB7C, 0x13AC }, + { 0xFF28, 0xFF48 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_216[] = { + { 0x00D8, 0x00F8 }, + { 0x01D9, 0x01DA }, + { 0x04DC, 0x04DD }, + { 0x1EC6, 0x1EC7 }, + { 0xAB73, 0x13A3 }, + { 0xFF27, 0xFF47 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_217[] = { + { 0x00D9, 0x00F9 }, + { 0x03DA, 0x03DB }, + { 0xA77E, 0xA77F }, + { 0xAB72, 0x13A2 }, + { 0xFF26, 0xFF46 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_218[] = { + { 0x00DA, 0x00FA }, + { 0x01DB, 0x01DC }, + { 0x04DE, 0x04DF }, + { 0x1EC4, 0x1EC5 }, + { 0xA77D, 0x1D79 }, + { 0xAB71, 0x13A1 }, + { 0xFF25, 0xFF45 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_219[] = { + { 0x00DB, 0x00FB }, + { 0x03D8, 0x03D9 }, + { 0xAB70, 0x13A0 }, + { 0xFF24, 0xFF44 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_220[] = { + { 0x00DC, 0x00FC }, + { 0x04D8, 0x04D9 }, + { 0x1EC2, 0x1EC3 }, + { 0xA77B, 0xA77C }, + { 0xAB77, 0x13A7 }, + { 0xFF23, 0xFF43 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_221[] = { + { 0x00DD, 0x00FD }, + { 0x03DE, 0x03DF }, + { 0x10CD, 0x2D2D }, + { 0xAB76, 0x13A6 }, + { 0xFF22, 0xFF42 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_222[] = { + { 0x00DE, 0x00FE }, + { 0x04DA, 0x04DB }, + { 0x1EC0, 0x1EC1 }, + { 0x2CF2, 0x2CF3 }, + { 0xA779, 0xA77A }, + { 0xAB75, 0x13A5 }, + { 0xFF21, 0xFF41 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_223[] = { + { 0x01DE, 0x01DF }, + { 0x03DC, 0x03DD }, + { 0xAB74, 0x13A4 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_224[] = { + { 0x04E4, 0x04E5 }, + { 0x1EFE, 0x1EFF }, + { 0x24C4, 0x24DE }, + { 0x2CCC, 0x2CCD }, + { 0xA646, 0xA647 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_225[] = { + { 0x01E0, 0x01E1 }, + { 0x03E2, 0x03E3 }, + { 0x24C5, 0x24DF }, + { 0xA746, 0xA747 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_226[] = { + { 0x04E6, 0x04E7 }, + { 0x1EFC, 0x1EFD }, + { 0x24C6, 0x24E0 }, + { 0x2CCE, 0x2CCF }, + { 0xA644, 0xA645 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_227[] = { + { 0x01E2, 0x01E3 }, + { 0x03E0, 0x03E1 }, + { 0x24C7, 0x24E1 }, + { 0xA744, 0xA745 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_228[] = { + { 0x04E0, 0x04E1 }, + { 0x1EFA, 0x1EFB }, + { 0x1FFB, 0x1F7D }, + { 0x24C0, 0x24DA }, + { 0x2CC8, 0x2CC9 }, + { 0xA642, 0xA643 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_229[] = { + { 0x01E4, 0x01E5 }, + { 0x03E6, 0x03E7 }, + { 0x1FFA, 0x1F7C }, + { 0x24C1, 0x24DB }, + { 0xA742, 0xA743 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_230[] = { + { 0x04E2, 0x04E3 }, + { 0x1EF8, 0x1EF9 }, + { 0x1FF9, 0x1F79 }, + { 0x24C2, 0x24DC }, + { 0x2CCA, 0x2CCB }, + { 0xA640, 0xA641 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_231[] = { + { 0x01E6, 0x01E7 }, + { 0x03E4, 0x03E5 }, + { 0x1FF8, 0x1F78 }, + { 0x24C3, 0x24DD }, + { 0xA740, 0xA741 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_232[] = { + { 0x04EC, 0x04ED }, + { 0x13FB, 0x13F3 }, + { 0x1EF6, 0x1EF7 }, + { 0x24CC, 0x24E6 }, + { 0x2CC4, 0x2CC5 }, + { 0xA64E, 0xA64F } +}; + +static const CaseFoldMapping1_16 case_fold1_16_233[] = { + { 0x01E8, 0x01E9 }, + { 0x03EA, 0x03EB }, + { 0x13FA, 0x13F2 }, + { 0x24CD, 0x24E7 }, + { 0xA74E, 0xA74F } +}; + +static const CaseFoldMapping1_16 case_fold1_16_234[] = { + { 0x04EE, 0x04EF }, + { 0x13F9, 0x13F1 }, + { 0x1EF4, 0x1EF5 }, + { 0x24CE, 0x24E8 }, + { 0x2CC6, 0x2CC7 }, + { 0xA64C, 0xA64D } +}; + +static const CaseFoldMapping1_16 case_fold1_16_235[] = { + { 0x01EA, 0x01EB }, + { 0x03E8, 0x03E9 }, + { 0x13F8, 0x13F0 }, + { 0x24CF, 0x24E9 }, + { 0xA74C, 0xA74D } +}; + +static const CaseFoldMapping1_16 case_fold1_16_236[] = { + { 0x04E8, 0x04E9 }, + { 0x1EF2, 0x1EF3 }, + { 0x24C8, 0x24E2 }, + { 0x2CC0, 0x2CC1 }, + { 0xA64A, 0xA64B } +}; + +static const CaseFoldMapping1_16 case_fold1_16_237[] = { + { 0x01EC, 0x01ED }, + { 0x03EE, 0x03EF }, + { 0x24C9, 0x24E3 }, + { 0xA74A, 0xA74B } +}; + +static const CaseFoldMapping1_16 case_fold1_16_238[] = { + { 0x04EA, 0x04EB }, + { 0x13FD, 0x13F5 }, + { 0x1EF0, 0x1EF1 }, + { 0x24CA, 0x24E4 }, + { 0x2CC2, 0x2CC3 }, + { 0xA648, 0xA649 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_239[] = { + { 0x01EE, 0x01EF }, + { 0x03EC, 0x03ED }, + { 0x13FC, 0x13F4 }, + { 0x24CB, 0x24E5 }, + { 0xA748, 0xA749 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_240[] = { + { 0x01F1, 0x01F3 }, + { 0x04F4, 0x04F5 }, + { 0x1EEE, 0x1EEF }, + { 0x2CDC, 0x2CDD }, + { 0xA656, 0xA657 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_241[] = { + { 0xA756, 0xA757 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_242[] = { + { 0x03F1, 0x03C1 }, + { 0x04F6, 0x04F7 }, + { 0x1EEC, 0x1EED }, + { 0x2CDE, 0x2CDF }, + { 0xA654, 0xA655 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_243[] = { + { 0x01F2, 0x01F3 }, + { 0x03F0, 0x03BA }, + { 0x1FEC, 0x1FE5 }, + { 0xA754, 0xA755 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_244[] = { + { 0x03F7, 0x03F8 }, + { 0x04F0, 0x04F1 }, + { 0x1EEA, 0x1EEB }, + { 0x1FEB, 0x1F7B }, + { 0x2CD8, 0x2CD9 }, + { 0xA652, 0xA653 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_245[] = { + { 0x01F4, 0x01F5 }, + { 0x1FEA, 0x1F7A }, + { 0xA752, 0xA753 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_246[] = { + { 0x01F7, 0x01BF }, + { 0x03F5, 0x03B5 }, + { 0x04F2, 0x04F3 }, + { 0x1EE8, 0x1EE9 }, + { 0x1FE9, 0x1FE1 }, + { 0x2CDA, 0x2CDB }, + { 0xA650, 0xA651 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_247[] = { + { 0x01F6, 0x0195 }, + { 0x03F4, 0x03B8 }, + { 0x1FE8, 0x1FE0 }, + { 0xA750, 0xA751 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_248[] = { + { 0x04FC, 0x04FD }, + { 0x1EE6, 0x1EE7 }, + { 0x2CD4, 0x2CD5 }, + { 0xA65E, 0xA65F } +}; + +static const CaseFoldMapping1_16 case_fold1_16_249[] = { + { 0x01F8, 0x01F9 }, + { 0x03FA, 0x03FB }, + { 0xA75E, 0xA75F } +}; + +static const CaseFoldMapping1_16 case_fold1_16_250[] = { + { 0x03F9, 0x03F2 }, + { 0x04FE, 0x04FF }, + { 0x1EE4, 0x1EE5 }, + { 0x2CD6, 0x2CD7 }, + { 0xA65C, 0xA65D } +}; + +static const CaseFoldMapping1_16 case_fold1_16_251[] = { + { 0x01FA, 0x01FB }, + { 0xA75C, 0xA75D } +}; + +static const CaseFoldMapping1_16 case_fold1_16_252[] = { + { 0x03FF, 0x037D }, + { 0x04F8, 0x04F9 }, + { 0x1EE2, 0x1EE3 }, + { 0x2CD0, 0x2CD1 }, + { 0xA65A, 0xA65B } +}; + +static const CaseFoldMapping1_16 case_fold1_16_253[] = { + { 0x01FC, 0x01FD }, + { 0x03FE, 0x037C }, + { 0xA75A, 0xA75B } +}; + +static const CaseFoldMapping1_16 case_fold1_16_254[] = { + { 0x03FD, 0x037B }, + { 0x04FA, 0x04FB }, + { 0x1EE0, 0x1EE1 }, + { 0x2CD2, 0x2CD3 }, + { 0xA658, 0xA659 } +}; + +static const CaseFoldMapping1_16 case_fold1_16_255[] = { + { 0x01FE, 0x01FF }, + { 0xA758, 0xA759 } +}; + +static const CaseFoldMapping1_32 case_fold1_32_000[] = { + { 0x10404, 0x1042C }, + { 0x10414, 0x1043C }, + { 0x10424, 0x1044C }, + { 0x10C8C, 0x10CCC }, + { 0x10C9C, 0x10CDC }, + { 0x10CAC, 0x10CEC }, + { 0x118A8, 0x118C8 }, + { 0x118B8, 0x118D8 } +}; + +static const CaseFoldMapping1_32 case_fold1_32_001[] = { + { 0x10405, 0x1042D }, + { 0x10415, 0x1043D }, + { 0x10425, 0x1044D }, + { 0x10C8D, 0x10CCD }, + { 0x10C9D, 0x10CDD }, + { 0x10CAD, 0x10CED }, + { 0x118A9, 0x118C9 }, + { 0x118B9, 0x118D9 } +}; + +static const CaseFoldMapping1_32 case_fold1_32_002[] = { + { 0x10406, 0x1042E }, + { 0x10416, 0x1043E }, + { 0x10426, 0x1044E }, + { 0x10C8E, 0x10CCE }, + { 0x10C9E, 0x10CDE }, + { 0x10CAE, 0x10CEE }, + { 0x118AA, 0x118CA }, + { 0x118BA, 0x118DA } +}; + +static const CaseFoldMapping1_32 case_fold1_32_003[] = { + { 0x10407, 0x1042F }, + { 0x10417, 0x1043F }, + { 0x10427, 0x1044F }, + { 0x10C8F, 0x10CCF }, + { 0x10C9F, 0x10CDF }, + { 0x10CAF, 0x10CEF }, + { 0x118AB, 0x118CB }, + { 0x118BB, 0x118DB } +}; + +static const CaseFoldMapping1_32 case_fold1_32_004[] = { + { 0x10400, 0x10428 }, + { 0x10410, 0x10438 }, + { 0x10420, 0x10448 }, + { 0x10C88, 0x10CC8 }, + { 0x10C98, 0x10CD8 }, + { 0x10CA8, 0x10CE8 }, + { 0x118AC, 0x118CC }, + { 0x118BC, 0x118DC } +}; + +static const CaseFoldMapping1_32 case_fold1_32_005[] = { + { 0x10401, 0x10429 }, + { 0x10411, 0x10439 }, + { 0x10421, 0x10449 }, + { 0x10C89, 0x10CC9 }, + { 0x10C99, 0x10CD9 }, + { 0x10CA9, 0x10CE9 }, + { 0x118AD, 0x118CD }, + { 0x118BD, 0x118DD } +}; + +static const CaseFoldMapping1_32 case_fold1_32_006[] = { + { 0x10402, 0x1042A }, + { 0x10412, 0x1043A }, + { 0x10422, 0x1044A }, + { 0x10C8A, 0x10CCA }, + { 0x10C9A, 0x10CDA }, + { 0x10CAA, 0x10CEA }, + { 0x118AE, 0x118CE }, + { 0x118BE, 0x118DE } +}; + +static const CaseFoldMapping1_32 case_fold1_32_007[] = { + { 0x10403, 0x1042B }, + { 0x10413, 0x1043B }, + { 0x10423, 0x1044B }, + { 0x10C8B, 0x10CCB }, + { 0x10C9B, 0x10CDB }, + { 0x10CAB, 0x10CEB }, + { 0x118AF, 0x118CF }, + { 0x118BF, 0x118DF } +}; + +static const CaseFoldMapping1_32 case_fold1_32_008[] = { + { 0x1040C, 0x10434 }, + { 0x1041C, 0x10444 }, + { 0x10C84, 0x10CC4 }, + { 0x10C94, 0x10CD4 }, + { 0x10CA4, 0x10CE4 }, + { 0x118A0, 0x118C0 }, + { 0x118B0, 0x118D0 } +}; + +static const CaseFoldMapping1_32 case_fold1_32_009[] = { + { 0x1040D, 0x10435 }, + { 0x1041D, 0x10445 }, + { 0x10C85, 0x10CC5 }, + { 0x10C95, 0x10CD5 }, + { 0x10CA5, 0x10CE5 }, + { 0x118A1, 0x118C1 }, + { 0x118B1, 0x118D1 } +}; + +static const CaseFoldMapping1_32 case_fold1_32_010[] = { + { 0x1040E, 0x10436 }, + { 0x1041E, 0x10446 }, + { 0x10C86, 0x10CC6 }, + { 0x10C96, 0x10CD6 }, + { 0x10CA6, 0x10CE6 }, + { 0x118A2, 0x118C2 }, + { 0x118B2, 0x118D2 } +}; + +static const CaseFoldMapping1_32 case_fold1_32_011[] = { + { 0x1040F, 0x10437 }, + { 0x1041F, 0x10447 }, + { 0x10C87, 0x10CC7 }, + { 0x10C97, 0x10CD7 }, + { 0x10CA7, 0x10CE7 }, + { 0x118A3, 0x118C3 }, + { 0x118B3, 0x118D3 } +}; + +static const CaseFoldMapping1_32 case_fold1_32_012[] = { + { 0x10408, 0x10430 }, + { 0x10418, 0x10440 }, + { 0x10C80, 0x10CC0 }, + { 0x10C90, 0x10CD0 }, + { 0x10CA0, 0x10CE0 }, + { 0x10CB0, 0x10CF0 }, + { 0x118A4, 0x118C4 }, + { 0x118B4, 0x118D4 } +}; + +static const CaseFoldMapping1_32 case_fold1_32_013[] = { + { 0x10409, 0x10431 }, + { 0x10419, 0x10441 }, + { 0x10C81, 0x10CC1 }, + { 0x10C91, 0x10CD1 }, + { 0x10CA1, 0x10CE1 }, + { 0x10CB1, 0x10CF1 }, + { 0x118A5, 0x118C5 }, + { 0x118B5, 0x118D5 } +}; + +static const CaseFoldMapping1_32 case_fold1_32_014[] = { + { 0x1040A, 0x10432 }, + { 0x1041A, 0x10442 }, + { 0x10C82, 0x10CC2 }, + { 0x10C92, 0x10CD2 }, + { 0x10CA2, 0x10CE2 }, + { 0x10CB2, 0x10CF2 }, + { 0x118A6, 0x118C6 }, + { 0x118B6, 0x118D6 } +}; + +static const CaseFoldMapping1_32 case_fold1_32_015[] = { + { 0x1040B, 0x10433 }, + { 0x1041B, 0x10443 }, + { 0x10C83, 0x10CC3 }, + { 0x10C93, 0x10CD3 }, + { 0x10CA3, 0x10CE3 }, + { 0x118A7, 0x118C7 }, + { 0x118B7, 0x118D7 } +}; + +static const CaseFoldMapping2_16 case_fold2_16_000[] = { + { 0x1E9E, 0x0073, 0x0073 }, + { 0x1F8F, 0x1F07, 0x03B9 }, + { 0x1F9F, 0x1F27, 0x03B9 }, + { 0x1FAF, 0x1F67, 0x03B9 } +}; + +static const CaseFoldMapping2_16 case_fold2_16_001[] = { + { 0x0130, 0x0069, 0x0307 }, + { 0x01F0, 0x006A, 0x030C }, + { 0x1F8E, 0x1F06, 0x03B9 }, + { 0x1F9E, 0x1F26, 0x03B9 }, + { 0x1FAE, 0x1F66, 0x03B9 } +}; + +static const CaseFoldMapping2_16 case_fold2_16_002[] = { + { 0x0587, 0x0565, 0x0582 }, + { 0x1F8D, 0x1F05, 0x03B9 }, + { 0x1F9D, 0x1F25, 0x03B9 }, + { 0x1FAD, 0x1F65, 0x03B9 } +}; + +static const CaseFoldMapping2_16 case_fold2_16_003[] = { + { 0x1F8C, 0x1F04, 0x03B9 }, + { 0x1F9C, 0x1F24, 0x03B9 }, + { 0x1FAC, 0x1F64, 0x03B9 }, + { 0x1FBC, 0x03B1, 0x03B9 }, + { 0x1FCC, 0x03B7, 0x03B9 }, + { 0x1FFC, 0x03C9, 0x03B9 } +}; + +static const CaseFoldMapping2_16 case_fold2_16_004[] = { + { 0x1E9A, 0x0061, 0x02BE }, + { 0x1F8B, 0x1F03, 0x03B9 }, + { 0x1F9B, 0x1F23, 0x03B9 }, + { 0x1FAB, 0x1F63, 0x03B9 } +}; + +static const CaseFoldMapping2_16 case_fold2_16_005[] = { + { 0x1F8A, 0x1F02, 0x03B9 }, + { 0x1F9A, 0x1F22, 0x03B9 }, + { 0x1FAA, 0x1F62, 0x03B9 } +}; + +static const CaseFoldMapping2_16 case_fold2_16_006[] = { + { 0x1E98, 0x0077, 0x030A }, + { 0x1F89, 0x1F01, 0x03B9 }, + { 0x1F99, 0x1F21, 0x03B9 }, + { 0x1FA9, 0x1F61, 0x03B9 } +}; + +static const CaseFoldMapping2_16 case_fold2_16_007[] = { + { 0x1E99, 0x0079, 0x030A }, + { 0x1F88, 0x1F00, 0x03B9 }, + { 0x1F98, 0x1F20, 0x03B9 }, + { 0x1FA8, 0x1F60, 0x03B9 } +}; + +static const CaseFoldMapping2_16 case_fold2_16_008[] = { + { 0x0149, 0x02BC, 0x006E }, + { 0x1E96, 0x0068, 0x0331 }, + { 0x1F87, 0x1F07, 0x03B9 }, + { 0x1F97, 0x1F27, 0x03B9 }, + { 0x1FA7, 0x1F67, 0x03B9 }, + { 0xFB13, 0x0574, 0x0576 } +}; + +static const CaseFoldMapping2_16 case_fold2_16_009[] = { + { 0x1E97, 0x0074, 0x0308 }, + { 0x1F86, 0x1F06, 0x03B9 }, + { 0x1F96, 0x1F26, 0x03B9 }, + { 0x1FA6, 0x1F66, 0x03B9 }, + { 0x1FB6, 0x03B1, 0x0342 }, + { 0x1FC6, 0x03B7, 0x0342 }, + { 0x1FD6, 0x03B9, 0x0342 }, + { 0x1FE6, 0x03C5, 0x0342 }, + { 0x1FF6, 0x03C9, 0x0342 }, + { 0xFB02, 0x0066, 0x006C } +}; + +static const CaseFoldMapping2_16 case_fold2_16_010[] = { + { 0x1F85, 0x1F05, 0x03B9 }, + { 0x1F95, 0x1F25, 0x03B9 }, + { 0x1FA5, 0x1F65, 0x03B9 }, + { 0xFB01, 0x0066, 0x0069 } +}; + +static const CaseFoldMapping2_16 case_fold2_16_011[] = { + { 0x1F84, 0x1F04, 0x03B9 }, + { 0x1F94, 0x1F24, 0x03B9 }, + { 0x1FA4, 0x1F64, 0x03B9 }, + { 0x1FB4, 0x03AC, 0x03B9 }, + { 0x1FC4, 0x03AE, 0x03B9 }, + { 0x1FE4, 0x03C1, 0x0313 }, + { 0x1FF4, 0x03CE, 0x03B9 }, + { 0xFB00, 0x0066, 0x0066 } +}; + +static const CaseFoldMapping2_16 case_fold2_16_012[] = { + { 0x1F83, 0x1F03, 0x03B9 }, + { 0x1F93, 0x1F23, 0x03B9 }, + { 0x1FA3, 0x1F63, 0x03B9 }, + { 0x1FB3, 0x03B1, 0x03B9 }, + { 0x1FC3, 0x03B7, 0x03B9 }, + { 0x1FF3, 0x03C9, 0x03B9 }, + { 0xFB17, 0x0574, 0x056D } +}; + +static const CaseFoldMapping2_16 case_fold2_16_013[] = { + { 0x1F82, 0x1F02, 0x03B9 }, + { 0x1F92, 0x1F22, 0x03B9 }, + { 0x1FA2, 0x1F62, 0x03B9 }, + { 0x1FB2, 0x1F70, 0x03B9 }, + { 0x1FC2, 0x1F74, 0x03B9 }, + { 0x1FF2, 0x1F7C, 0x03B9 }, + { 0xFB06, 0x0073, 0x0074 }, + { 0xFB16, 0x057E, 0x0576 } +}; + +static const CaseFoldMapping2_16 case_fold2_16_014[] = { + { 0x1F81, 0x1F01, 0x03B9 }, + { 0x1F91, 0x1F21, 0x03B9 }, + { 0x1FA1, 0x1F61, 0x03B9 }, + { 0xFB05, 0x0073, 0x0074 }, + { 0xFB15, 0x0574, 0x056B } +}; + +static const CaseFoldMapping2_16 case_fold2_16_015[] = { + { 0x00DF, 0x0073, 0x0073 }, + { 0x1F50, 0x03C5, 0x0313 }, + { 0x1F80, 0x1F00, 0x03B9 }, + { 0x1F90, 0x1F20, 0x03B9 }, + { 0x1FA0, 0x1F60, 0x03B9 }, + { 0xFB14, 0x0574, 0x0565 } +}; + +static const CaseFoldMapping3_16 case_fold3_16_000[] = { + { 0x1FB7, 0x03B1, 0x0342, 0x03B9 }, + { 0x1FC7, 0x03B7, 0x0342, 0x03B9 }, + { 0x1FD3, 0x03B9, 0x0308, 0x0301 }, + { 0x1FD7, 0x03B9, 0x0308, 0x0342 }, + { 0x1FE3, 0x03C5, 0x0308, 0x0301 }, + { 0x1FE7, 0x03C5, 0x0308, 0x0342 }, + { 0x1FF7, 0x03C9, 0x0342, 0x03B9 }, + { 0xFB03, 0x0066, 0x0066, 0x0069 } +}; + +static const CaseFoldMapping3_16 case_fold3_16_001[] = { + { 0x1F52, 0x03C5, 0x0313, 0x0300 }, + { 0x1F56, 0x03C5, 0x0313, 0x0342 }, + { 0x1FD2, 0x03B9, 0x0308, 0x0300 }, + { 0x1FE2, 0x03C5, 0x0308, 0x0300 } +}; + +static const CaseFoldMapping3_16 case_fold3_16_003[] = { + { 0x0390, 0x03B9, 0x0308, 0x0301 }, + { 0x03B0, 0x03C5, 0x0308, 0x0301 }, + { 0x1F54, 0x03C5, 0x0313, 0x0301 }, + { 0xFB04, 0x0066, 0x0066, 0x006C } +}; + +static const CaseFoldHashBucket1_16 case_fold_hash1_16[] = { + { case_fold1_16_000, __PHYSFS_ARRAYLEN(case_fold1_16_000) }, + { case_fold1_16_001, __PHYSFS_ARRAYLEN(case_fold1_16_001) }, + { case_fold1_16_002, __PHYSFS_ARRAYLEN(case_fold1_16_002) }, + { case_fold1_16_003, __PHYSFS_ARRAYLEN(case_fold1_16_003) }, + { case_fold1_16_004, __PHYSFS_ARRAYLEN(case_fold1_16_004) }, + { case_fold1_16_005, __PHYSFS_ARRAYLEN(case_fold1_16_005) }, + { case_fold1_16_006, __PHYSFS_ARRAYLEN(case_fold1_16_006) }, + { case_fold1_16_007, __PHYSFS_ARRAYLEN(case_fold1_16_007) }, + { case_fold1_16_008, __PHYSFS_ARRAYLEN(case_fold1_16_008) }, + { case_fold1_16_009, __PHYSFS_ARRAYLEN(case_fold1_16_009) }, + { case_fold1_16_010, __PHYSFS_ARRAYLEN(case_fold1_16_010) }, + { case_fold1_16_011, __PHYSFS_ARRAYLEN(case_fold1_16_011) }, + { case_fold1_16_012, __PHYSFS_ARRAYLEN(case_fold1_16_012) }, + { case_fold1_16_013, __PHYSFS_ARRAYLEN(case_fold1_16_013) }, + { case_fold1_16_014, __PHYSFS_ARRAYLEN(case_fold1_16_014) }, + { case_fold1_16_015, __PHYSFS_ARRAYLEN(case_fold1_16_015) }, + { case_fold1_16_016, __PHYSFS_ARRAYLEN(case_fold1_16_016) }, + { case_fold1_16_017, __PHYSFS_ARRAYLEN(case_fold1_16_017) }, + { case_fold1_16_018, __PHYSFS_ARRAYLEN(case_fold1_16_018) }, + { case_fold1_16_019, __PHYSFS_ARRAYLEN(case_fold1_16_019) }, + { case_fold1_16_020, __PHYSFS_ARRAYLEN(case_fold1_16_020) }, + { case_fold1_16_021, __PHYSFS_ARRAYLEN(case_fold1_16_021) }, + { case_fold1_16_022, __PHYSFS_ARRAYLEN(case_fold1_16_022) }, + { case_fold1_16_023, __PHYSFS_ARRAYLEN(case_fold1_16_023) }, + { case_fold1_16_024, __PHYSFS_ARRAYLEN(case_fold1_16_024) }, + { case_fold1_16_025, __PHYSFS_ARRAYLEN(case_fold1_16_025) }, + { case_fold1_16_026, __PHYSFS_ARRAYLEN(case_fold1_16_026) }, + { case_fold1_16_027, __PHYSFS_ARRAYLEN(case_fold1_16_027) }, + { case_fold1_16_028, __PHYSFS_ARRAYLEN(case_fold1_16_028) }, + { case_fold1_16_029, __PHYSFS_ARRAYLEN(case_fold1_16_029) }, + { case_fold1_16_030, __PHYSFS_ARRAYLEN(case_fold1_16_030) }, + { case_fold1_16_031, __PHYSFS_ARRAYLEN(case_fold1_16_031) }, + { case_fold1_16_032, __PHYSFS_ARRAYLEN(case_fold1_16_032) }, + { case_fold1_16_033, __PHYSFS_ARRAYLEN(case_fold1_16_033) }, + { case_fold1_16_034, __PHYSFS_ARRAYLEN(case_fold1_16_034) }, + { case_fold1_16_035, __PHYSFS_ARRAYLEN(case_fold1_16_035) }, + { case_fold1_16_036, __PHYSFS_ARRAYLEN(case_fold1_16_036) }, + { case_fold1_16_037, __PHYSFS_ARRAYLEN(case_fold1_16_037) }, + { case_fold1_16_038, __PHYSFS_ARRAYLEN(case_fold1_16_038) }, + { case_fold1_16_039, __PHYSFS_ARRAYLEN(case_fold1_16_039) }, + { case_fold1_16_040, __PHYSFS_ARRAYLEN(case_fold1_16_040) }, + { case_fold1_16_041, __PHYSFS_ARRAYLEN(case_fold1_16_041) }, + { case_fold1_16_042, __PHYSFS_ARRAYLEN(case_fold1_16_042) }, + { case_fold1_16_043, __PHYSFS_ARRAYLEN(case_fold1_16_043) }, + { case_fold1_16_044, __PHYSFS_ARRAYLEN(case_fold1_16_044) }, + { case_fold1_16_045, __PHYSFS_ARRAYLEN(case_fold1_16_045) }, + { case_fold1_16_046, __PHYSFS_ARRAYLEN(case_fold1_16_046) }, + { case_fold1_16_047, __PHYSFS_ARRAYLEN(case_fold1_16_047) }, + { case_fold1_16_048, __PHYSFS_ARRAYLEN(case_fold1_16_048) }, + { case_fold1_16_049, __PHYSFS_ARRAYLEN(case_fold1_16_049) }, + { case_fold1_16_050, __PHYSFS_ARRAYLEN(case_fold1_16_050) }, + { case_fold1_16_051, __PHYSFS_ARRAYLEN(case_fold1_16_051) }, + { case_fold1_16_052, __PHYSFS_ARRAYLEN(case_fold1_16_052) }, + { case_fold1_16_053, __PHYSFS_ARRAYLEN(case_fold1_16_053) }, + { case_fold1_16_054, __PHYSFS_ARRAYLEN(case_fold1_16_054) }, + { case_fold1_16_055, __PHYSFS_ARRAYLEN(case_fold1_16_055) }, + { case_fold1_16_056, __PHYSFS_ARRAYLEN(case_fold1_16_056) }, + { case_fold1_16_057, __PHYSFS_ARRAYLEN(case_fold1_16_057) }, + { case_fold1_16_058, __PHYSFS_ARRAYLEN(case_fold1_16_058) }, + { case_fold1_16_059, __PHYSFS_ARRAYLEN(case_fold1_16_059) }, + { case_fold1_16_060, __PHYSFS_ARRAYLEN(case_fold1_16_060) }, + { case_fold1_16_061, __PHYSFS_ARRAYLEN(case_fold1_16_061) }, + { case_fold1_16_062, __PHYSFS_ARRAYLEN(case_fold1_16_062) }, + { case_fold1_16_063, __PHYSFS_ARRAYLEN(case_fold1_16_063) }, + { case_fold1_16_064, __PHYSFS_ARRAYLEN(case_fold1_16_064) }, + { case_fold1_16_065, __PHYSFS_ARRAYLEN(case_fold1_16_065) }, + { case_fold1_16_066, __PHYSFS_ARRAYLEN(case_fold1_16_066) }, + { case_fold1_16_067, __PHYSFS_ARRAYLEN(case_fold1_16_067) }, + { case_fold1_16_068, __PHYSFS_ARRAYLEN(case_fold1_16_068) }, + { case_fold1_16_069, __PHYSFS_ARRAYLEN(case_fold1_16_069) }, + { case_fold1_16_070, __PHYSFS_ARRAYLEN(case_fold1_16_070) }, + { case_fold1_16_071, __PHYSFS_ARRAYLEN(case_fold1_16_071) }, + { case_fold1_16_072, __PHYSFS_ARRAYLEN(case_fold1_16_072) }, + { case_fold1_16_073, __PHYSFS_ARRAYLEN(case_fold1_16_073) }, + { case_fold1_16_074, __PHYSFS_ARRAYLEN(case_fold1_16_074) }, + { case_fold1_16_075, __PHYSFS_ARRAYLEN(case_fold1_16_075) }, + { case_fold1_16_076, __PHYSFS_ARRAYLEN(case_fold1_16_076) }, + { case_fold1_16_077, __PHYSFS_ARRAYLEN(case_fold1_16_077) }, + { case_fold1_16_078, __PHYSFS_ARRAYLEN(case_fold1_16_078) }, + { case_fold1_16_079, __PHYSFS_ARRAYLEN(case_fold1_16_079) }, + { case_fold1_16_080, __PHYSFS_ARRAYLEN(case_fold1_16_080) }, + { case_fold1_16_081, __PHYSFS_ARRAYLEN(case_fold1_16_081) }, + { case_fold1_16_082, __PHYSFS_ARRAYLEN(case_fold1_16_082) }, + { case_fold1_16_083, __PHYSFS_ARRAYLEN(case_fold1_16_083) }, + { case_fold1_16_084, __PHYSFS_ARRAYLEN(case_fold1_16_084) }, + { case_fold1_16_085, __PHYSFS_ARRAYLEN(case_fold1_16_085) }, + { case_fold1_16_086, __PHYSFS_ARRAYLEN(case_fold1_16_086) }, + { case_fold1_16_087, __PHYSFS_ARRAYLEN(case_fold1_16_087) }, + { case_fold1_16_088, __PHYSFS_ARRAYLEN(case_fold1_16_088) }, + { case_fold1_16_089, __PHYSFS_ARRAYLEN(case_fold1_16_089) }, + { case_fold1_16_090, __PHYSFS_ARRAYLEN(case_fold1_16_090) }, + { case_fold1_16_091, __PHYSFS_ARRAYLEN(case_fold1_16_091) }, + { case_fold1_16_092, __PHYSFS_ARRAYLEN(case_fold1_16_092) }, + { case_fold1_16_093, __PHYSFS_ARRAYLEN(case_fold1_16_093) }, + { case_fold1_16_094, __PHYSFS_ARRAYLEN(case_fold1_16_094) }, + { case_fold1_16_095, __PHYSFS_ARRAYLEN(case_fold1_16_095) }, + { case_fold1_16_096, __PHYSFS_ARRAYLEN(case_fold1_16_096) }, + { case_fold1_16_097, __PHYSFS_ARRAYLEN(case_fold1_16_097) }, + { case_fold1_16_098, __PHYSFS_ARRAYLEN(case_fold1_16_098) }, + { case_fold1_16_099, __PHYSFS_ARRAYLEN(case_fold1_16_099) }, + { case_fold1_16_100, __PHYSFS_ARRAYLEN(case_fold1_16_100) }, + { case_fold1_16_101, __PHYSFS_ARRAYLEN(case_fold1_16_101) }, + { case_fold1_16_102, __PHYSFS_ARRAYLEN(case_fold1_16_102) }, + { case_fold1_16_103, __PHYSFS_ARRAYLEN(case_fold1_16_103) }, + { case_fold1_16_104, __PHYSFS_ARRAYLEN(case_fold1_16_104) }, + { case_fold1_16_105, __PHYSFS_ARRAYLEN(case_fold1_16_105) }, + { case_fold1_16_106, __PHYSFS_ARRAYLEN(case_fold1_16_106) }, + { case_fold1_16_107, __PHYSFS_ARRAYLEN(case_fold1_16_107) }, + { case_fold1_16_108, __PHYSFS_ARRAYLEN(case_fold1_16_108) }, + { case_fold1_16_109, __PHYSFS_ARRAYLEN(case_fold1_16_109) }, + { case_fold1_16_110, __PHYSFS_ARRAYLEN(case_fold1_16_110) }, + { case_fold1_16_111, __PHYSFS_ARRAYLEN(case_fold1_16_111) }, + { case_fold1_16_112, __PHYSFS_ARRAYLEN(case_fold1_16_112) }, + { case_fold1_16_113, __PHYSFS_ARRAYLEN(case_fold1_16_113) }, + { case_fold1_16_114, __PHYSFS_ARRAYLEN(case_fold1_16_114) }, + { case_fold1_16_115, __PHYSFS_ARRAYLEN(case_fold1_16_115) }, + { case_fold1_16_116, __PHYSFS_ARRAYLEN(case_fold1_16_116) }, + { case_fold1_16_117, __PHYSFS_ARRAYLEN(case_fold1_16_117) }, + { case_fold1_16_118, __PHYSFS_ARRAYLEN(case_fold1_16_118) }, + { case_fold1_16_119, __PHYSFS_ARRAYLEN(case_fold1_16_119) }, + { case_fold1_16_120, __PHYSFS_ARRAYLEN(case_fold1_16_120) }, + { case_fold1_16_121, __PHYSFS_ARRAYLEN(case_fold1_16_121) }, + { case_fold1_16_122, __PHYSFS_ARRAYLEN(case_fold1_16_122) }, + { NULL, 0 }, + { case_fold1_16_124, __PHYSFS_ARRAYLEN(case_fold1_16_124) }, + { NULL, 0 }, + { case_fold1_16_126, __PHYSFS_ARRAYLEN(case_fold1_16_126) }, + { NULL, 0 }, + { case_fold1_16_128, __PHYSFS_ARRAYLEN(case_fold1_16_128) }, + { case_fold1_16_129, __PHYSFS_ARRAYLEN(case_fold1_16_129) }, + { case_fold1_16_130, __PHYSFS_ARRAYLEN(case_fold1_16_130) }, + { case_fold1_16_131, __PHYSFS_ARRAYLEN(case_fold1_16_131) }, + { case_fold1_16_132, __PHYSFS_ARRAYLEN(case_fold1_16_132) }, + { case_fold1_16_133, __PHYSFS_ARRAYLEN(case_fold1_16_133) }, + { case_fold1_16_134, __PHYSFS_ARRAYLEN(case_fold1_16_134) }, + { case_fold1_16_135, __PHYSFS_ARRAYLEN(case_fold1_16_135) }, + { case_fold1_16_136, __PHYSFS_ARRAYLEN(case_fold1_16_136) }, + { case_fold1_16_137, __PHYSFS_ARRAYLEN(case_fold1_16_137) }, + { case_fold1_16_138, __PHYSFS_ARRAYLEN(case_fold1_16_138) }, + { case_fold1_16_139, __PHYSFS_ARRAYLEN(case_fold1_16_139) }, + { case_fold1_16_140, __PHYSFS_ARRAYLEN(case_fold1_16_140) }, + { case_fold1_16_141, __PHYSFS_ARRAYLEN(case_fold1_16_141) }, + { case_fold1_16_142, __PHYSFS_ARRAYLEN(case_fold1_16_142) }, + { case_fold1_16_143, __PHYSFS_ARRAYLEN(case_fold1_16_143) }, + { case_fold1_16_144, __PHYSFS_ARRAYLEN(case_fold1_16_144) }, + { case_fold1_16_145, __PHYSFS_ARRAYLEN(case_fold1_16_145) }, + { case_fold1_16_146, __PHYSFS_ARRAYLEN(case_fold1_16_146) }, + { case_fold1_16_147, __PHYSFS_ARRAYLEN(case_fold1_16_147) }, + { case_fold1_16_148, __PHYSFS_ARRAYLEN(case_fold1_16_148) }, + { case_fold1_16_149, __PHYSFS_ARRAYLEN(case_fold1_16_149) }, + { case_fold1_16_150, __PHYSFS_ARRAYLEN(case_fold1_16_150) }, + { case_fold1_16_151, __PHYSFS_ARRAYLEN(case_fold1_16_151) }, + { case_fold1_16_152, __PHYSFS_ARRAYLEN(case_fold1_16_152) }, + { case_fold1_16_153, __PHYSFS_ARRAYLEN(case_fold1_16_153) }, + { case_fold1_16_154, __PHYSFS_ARRAYLEN(case_fold1_16_154) }, + { case_fold1_16_155, __PHYSFS_ARRAYLEN(case_fold1_16_155) }, + { case_fold1_16_156, __PHYSFS_ARRAYLEN(case_fold1_16_156) }, + { case_fold1_16_157, __PHYSFS_ARRAYLEN(case_fold1_16_157) }, + { case_fold1_16_158, __PHYSFS_ARRAYLEN(case_fold1_16_158) }, + { case_fold1_16_159, __PHYSFS_ARRAYLEN(case_fold1_16_159) }, + { case_fold1_16_160, __PHYSFS_ARRAYLEN(case_fold1_16_160) }, + { case_fold1_16_161, __PHYSFS_ARRAYLEN(case_fold1_16_161) }, + { case_fold1_16_162, __PHYSFS_ARRAYLEN(case_fold1_16_162) }, + { case_fold1_16_163, __PHYSFS_ARRAYLEN(case_fold1_16_163) }, + { case_fold1_16_164, __PHYSFS_ARRAYLEN(case_fold1_16_164) }, + { case_fold1_16_165, __PHYSFS_ARRAYLEN(case_fold1_16_165) }, + { case_fold1_16_166, __PHYSFS_ARRAYLEN(case_fold1_16_166) }, + { case_fold1_16_167, __PHYSFS_ARRAYLEN(case_fold1_16_167) }, + { case_fold1_16_168, __PHYSFS_ARRAYLEN(case_fold1_16_168) }, + { case_fold1_16_169, __PHYSFS_ARRAYLEN(case_fold1_16_169) }, + { case_fold1_16_170, __PHYSFS_ARRAYLEN(case_fold1_16_170) }, + { case_fold1_16_171, __PHYSFS_ARRAYLEN(case_fold1_16_171) }, + { case_fold1_16_172, __PHYSFS_ARRAYLEN(case_fold1_16_172) }, + { case_fold1_16_173, __PHYSFS_ARRAYLEN(case_fold1_16_173) }, + { case_fold1_16_174, __PHYSFS_ARRAYLEN(case_fold1_16_174) }, + { case_fold1_16_175, __PHYSFS_ARRAYLEN(case_fold1_16_175) }, + { case_fold1_16_176, __PHYSFS_ARRAYLEN(case_fold1_16_176) }, + { case_fold1_16_177, __PHYSFS_ARRAYLEN(case_fold1_16_177) }, + { case_fold1_16_178, __PHYSFS_ARRAYLEN(case_fold1_16_178) }, + { case_fold1_16_179, __PHYSFS_ARRAYLEN(case_fold1_16_179) }, + { case_fold1_16_180, __PHYSFS_ARRAYLEN(case_fold1_16_180) }, + { case_fold1_16_181, __PHYSFS_ARRAYLEN(case_fold1_16_181) }, + { case_fold1_16_182, __PHYSFS_ARRAYLEN(case_fold1_16_182) }, + { case_fold1_16_183, __PHYSFS_ARRAYLEN(case_fold1_16_183) }, + { case_fold1_16_184, __PHYSFS_ARRAYLEN(case_fold1_16_184) }, + { case_fold1_16_185, __PHYSFS_ARRAYLEN(case_fold1_16_185) }, + { case_fold1_16_186, __PHYSFS_ARRAYLEN(case_fold1_16_186) }, + { case_fold1_16_187, __PHYSFS_ARRAYLEN(case_fold1_16_187) }, + { case_fold1_16_188, __PHYSFS_ARRAYLEN(case_fold1_16_188) }, + { case_fold1_16_189, __PHYSFS_ARRAYLEN(case_fold1_16_189) }, + { case_fold1_16_190, __PHYSFS_ARRAYLEN(case_fold1_16_190) }, + { case_fold1_16_191, __PHYSFS_ARRAYLEN(case_fold1_16_191) }, + { case_fold1_16_192, __PHYSFS_ARRAYLEN(case_fold1_16_192) }, + { case_fold1_16_193, __PHYSFS_ARRAYLEN(case_fold1_16_193) }, + { case_fold1_16_194, __PHYSFS_ARRAYLEN(case_fold1_16_194) }, + { case_fold1_16_195, __PHYSFS_ARRAYLEN(case_fold1_16_195) }, + { case_fold1_16_196, __PHYSFS_ARRAYLEN(case_fold1_16_196) }, + { case_fold1_16_197, __PHYSFS_ARRAYLEN(case_fold1_16_197) }, + { case_fold1_16_198, __PHYSFS_ARRAYLEN(case_fold1_16_198) }, + { case_fold1_16_199, __PHYSFS_ARRAYLEN(case_fold1_16_199) }, + { case_fold1_16_200, __PHYSFS_ARRAYLEN(case_fold1_16_200) }, + { case_fold1_16_201, __PHYSFS_ARRAYLEN(case_fold1_16_201) }, + { case_fold1_16_202, __PHYSFS_ARRAYLEN(case_fold1_16_202) }, + { case_fold1_16_203, __PHYSFS_ARRAYLEN(case_fold1_16_203) }, + { case_fold1_16_204, __PHYSFS_ARRAYLEN(case_fold1_16_204) }, + { case_fold1_16_205, __PHYSFS_ARRAYLEN(case_fold1_16_205) }, + { case_fold1_16_206, __PHYSFS_ARRAYLEN(case_fold1_16_206) }, + { case_fold1_16_207, __PHYSFS_ARRAYLEN(case_fold1_16_207) }, + { case_fold1_16_208, __PHYSFS_ARRAYLEN(case_fold1_16_208) }, + { case_fold1_16_209, __PHYSFS_ARRAYLEN(case_fold1_16_209) }, + { case_fold1_16_210, __PHYSFS_ARRAYLEN(case_fold1_16_210) }, + { case_fold1_16_211, __PHYSFS_ARRAYLEN(case_fold1_16_211) }, + { case_fold1_16_212, __PHYSFS_ARRAYLEN(case_fold1_16_212) }, + { case_fold1_16_213, __PHYSFS_ARRAYLEN(case_fold1_16_213) }, + { case_fold1_16_214, __PHYSFS_ARRAYLEN(case_fold1_16_214) }, + { case_fold1_16_215, __PHYSFS_ARRAYLEN(case_fold1_16_215) }, + { case_fold1_16_216, __PHYSFS_ARRAYLEN(case_fold1_16_216) }, + { case_fold1_16_217, __PHYSFS_ARRAYLEN(case_fold1_16_217) }, + { case_fold1_16_218, __PHYSFS_ARRAYLEN(case_fold1_16_218) }, + { case_fold1_16_219, __PHYSFS_ARRAYLEN(case_fold1_16_219) }, + { case_fold1_16_220, __PHYSFS_ARRAYLEN(case_fold1_16_220) }, + { case_fold1_16_221, __PHYSFS_ARRAYLEN(case_fold1_16_221) }, + { case_fold1_16_222, __PHYSFS_ARRAYLEN(case_fold1_16_222) }, + { case_fold1_16_223, __PHYSFS_ARRAYLEN(case_fold1_16_223) }, + { case_fold1_16_224, __PHYSFS_ARRAYLEN(case_fold1_16_224) }, + { case_fold1_16_225, __PHYSFS_ARRAYLEN(case_fold1_16_225) }, + { case_fold1_16_226, __PHYSFS_ARRAYLEN(case_fold1_16_226) }, + { case_fold1_16_227, __PHYSFS_ARRAYLEN(case_fold1_16_227) }, + { case_fold1_16_228, __PHYSFS_ARRAYLEN(case_fold1_16_228) }, + { case_fold1_16_229, __PHYSFS_ARRAYLEN(case_fold1_16_229) }, + { case_fold1_16_230, __PHYSFS_ARRAYLEN(case_fold1_16_230) }, + { case_fold1_16_231, __PHYSFS_ARRAYLEN(case_fold1_16_231) }, + { case_fold1_16_232, __PHYSFS_ARRAYLEN(case_fold1_16_232) }, + { case_fold1_16_233, __PHYSFS_ARRAYLEN(case_fold1_16_233) }, + { case_fold1_16_234, __PHYSFS_ARRAYLEN(case_fold1_16_234) }, + { case_fold1_16_235, __PHYSFS_ARRAYLEN(case_fold1_16_235) }, + { case_fold1_16_236, __PHYSFS_ARRAYLEN(case_fold1_16_236) }, + { case_fold1_16_237, __PHYSFS_ARRAYLEN(case_fold1_16_237) }, + { case_fold1_16_238, __PHYSFS_ARRAYLEN(case_fold1_16_238) }, + { case_fold1_16_239, __PHYSFS_ARRAYLEN(case_fold1_16_239) }, + { case_fold1_16_240, __PHYSFS_ARRAYLEN(case_fold1_16_240) }, + { case_fold1_16_241, __PHYSFS_ARRAYLEN(case_fold1_16_241) }, + { case_fold1_16_242, __PHYSFS_ARRAYLEN(case_fold1_16_242) }, + { case_fold1_16_243, __PHYSFS_ARRAYLEN(case_fold1_16_243) }, + { case_fold1_16_244, __PHYSFS_ARRAYLEN(case_fold1_16_244) }, + { case_fold1_16_245, __PHYSFS_ARRAYLEN(case_fold1_16_245) }, + { case_fold1_16_246, __PHYSFS_ARRAYLEN(case_fold1_16_246) }, + { case_fold1_16_247, __PHYSFS_ARRAYLEN(case_fold1_16_247) }, + { case_fold1_16_248, __PHYSFS_ARRAYLEN(case_fold1_16_248) }, + { case_fold1_16_249, __PHYSFS_ARRAYLEN(case_fold1_16_249) }, + { case_fold1_16_250, __PHYSFS_ARRAYLEN(case_fold1_16_250) }, + { case_fold1_16_251, __PHYSFS_ARRAYLEN(case_fold1_16_251) }, + { case_fold1_16_252, __PHYSFS_ARRAYLEN(case_fold1_16_252) }, + { case_fold1_16_253, __PHYSFS_ARRAYLEN(case_fold1_16_253) }, + { case_fold1_16_254, __PHYSFS_ARRAYLEN(case_fold1_16_254) }, + { case_fold1_16_255, __PHYSFS_ARRAYLEN(case_fold1_16_255) }, +}; + +static const CaseFoldHashBucket1_32 case_fold_hash1_32[] = { + { case_fold1_32_000, __PHYSFS_ARRAYLEN(case_fold1_32_000) }, + { case_fold1_32_001, __PHYSFS_ARRAYLEN(case_fold1_32_001) }, + { case_fold1_32_002, __PHYSFS_ARRAYLEN(case_fold1_32_002) }, + { case_fold1_32_003, __PHYSFS_ARRAYLEN(case_fold1_32_003) }, + { case_fold1_32_004, __PHYSFS_ARRAYLEN(case_fold1_32_004) }, + { case_fold1_32_005, __PHYSFS_ARRAYLEN(case_fold1_32_005) }, + { case_fold1_32_006, __PHYSFS_ARRAYLEN(case_fold1_32_006) }, + { case_fold1_32_007, __PHYSFS_ARRAYLEN(case_fold1_32_007) }, + { case_fold1_32_008, __PHYSFS_ARRAYLEN(case_fold1_32_008) }, + { case_fold1_32_009, __PHYSFS_ARRAYLEN(case_fold1_32_009) }, + { case_fold1_32_010, __PHYSFS_ARRAYLEN(case_fold1_32_010) }, + { case_fold1_32_011, __PHYSFS_ARRAYLEN(case_fold1_32_011) }, + { case_fold1_32_012, __PHYSFS_ARRAYLEN(case_fold1_32_012) }, + { case_fold1_32_013, __PHYSFS_ARRAYLEN(case_fold1_32_013) }, + { case_fold1_32_014, __PHYSFS_ARRAYLEN(case_fold1_32_014) }, + { case_fold1_32_015, __PHYSFS_ARRAYLEN(case_fold1_32_015) }, +}; + +static const CaseFoldHashBucket2_16 case_fold_hash2_16[] = { + { case_fold2_16_000, __PHYSFS_ARRAYLEN(case_fold2_16_000) }, + { case_fold2_16_001, __PHYSFS_ARRAYLEN(case_fold2_16_001) }, + { case_fold2_16_002, __PHYSFS_ARRAYLEN(case_fold2_16_002) }, + { case_fold2_16_003, __PHYSFS_ARRAYLEN(case_fold2_16_003) }, + { case_fold2_16_004, __PHYSFS_ARRAYLEN(case_fold2_16_004) }, + { case_fold2_16_005, __PHYSFS_ARRAYLEN(case_fold2_16_005) }, + { case_fold2_16_006, __PHYSFS_ARRAYLEN(case_fold2_16_006) }, + { case_fold2_16_007, __PHYSFS_ARRAYLEN(case_fold2_16_007) }, + { case_fold2_16_008, __PHYSFS_ARRAYLEN(case_fold2_16_008) }, + { case_fold2_16_009, __PHYSFS_ARRAYLEN(case_fold2_16_009) }, + { case_fold2_16_010, __PHYSFS_ARRAYLEN(case_fold2_16_010) }, + { case_fold2_16_011, __PHYSFS_ARRAYLEN(case_fold2_16_011) }, + { case_fold2_16_012, __PHYSFS_ARRAYLEN(case_fold2_16_012) }, + { case_fold2_16_013, __PHYSFS_ARRAYLEN(case_fold2_16_013) }, + { case_fold2_16_014, __PHYSFS_ARRAYLEN(case_fold2_16_014) }, + { case_fold2_16_015, __PHYSFS_ARRAYLEN(case_fold2_16_015) }, +}; + +static const CaseFoldHashBucket3_16 case_fold_hash3_16[] = { + { case_fold3_16_000, __PHYSFS_ARRAYLEN(case_fold3_16_000) }, + { case_fold3_16_001, __PHYSFS_ARRAYLEN(case_fold3_16_001) }, + { NULL, 0 }, + { case_fold3_16_003, __PHYSFS_ARRAYLEN(case_fold3_16_003) }, +}; + + +#endif /* _INCLUDE_PHYSFS_CASEFOLDING_H_ */ + +/* end of physfs_casefolding.h ... */ + diff --git a/Source/Dependencies/PhysicsFS/physfs_internal.h b/Source/Dependencies/PhysicsFS/physfs_internal.h new file mode 100644 index 000000000..29126234a --- /dev/null +++ b/Source/Dependencies/PhysicsFS/physfs_internal.h @@ -0,0 +1,729 @@ +/* + * Internal function/structure declaration. Do NOT include in your + * application. + * + * Please see the file LICENSE.txt in the source's root directory. + * + * This file written by Ryan C. Gordon. + */ + +#ifndef _INCLUDE_PHYSFS_INTERNAL_H_ +#define _INCLUDE_PHYSFS_INTERNAL_H_ + +#ifndef __PHYSICSFS_INTERNAL__ +#error Do not include this header from your applications. +#endif + +/* Turn off MSVC warnings that are aggressively anti-portability. */ +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS 1 +#endif + +#include "physfs.h" + +/* The holy trinity. */ +#include +#include +#include + +#include "physfs_platforms.h" + +#include + +#define __PHYSFS_COMPILE_TIME_ASSERT(name, x) \ + typedef int __PHYSFS_compile_time_assert_##name[(x) * 2 - 1] + +/* !!! FIXME: remove this when revamping stack allocation code... */ +#if defined(_MSC_VER) || defined(__MINGW32__) || defined(__WATCOMC__) +#include +#endif + +#ifdef PHYSFS_PLATFORM_SOLARIS +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __GNUC__ +#define PHYSFS_MINIMUM_GCC_VERSION(major, minor) \ + ( ((__GNUC__ << 16) + __GNUC_MINOR__) >= (((major) << 16) + (minor)) ) +#else +#define PHYSFS_MINIMUM_GCC_VERSION(major, minor) (0) +#endif + +#ifdef __cplusplus + /* C++ always has a real inline keyword. */ +#elif (defined macintosh) && !(defined __MWERKS__) +# define inline +#elif (defined _MSC_VER) +# define inline __inline +#endif + +#if defined(PHYSFS_PLATFORM_LINUX) && !defined(_FILE_OFFSET_BITS) +#define _FILE_OFFSET_BITS 64 +#endif + +/* All public APIs need to be in physfs.h with a PHYSFS_DECL. + All file-private symbols need to be marked "static". + Everything shared between PhysicsFS sources needs to be in this + file between the visibility pragma blocks. */ +#if PHYSFS_MINIMUM_GCC_VERSION(4,0) || defined(__clang__) +#define PHYSFS_HAVE_PRAGMA_VISIBILITY 1 +#endif + +#if PHYSFS_HAVE_PRAGMA_VISIBILITY +#pragma GCC visibility push(hidden) +#endif + +/* These are the build-in archivers. We list them all as "extern" here without + #ifdefs to keep it tidy, but obviously you need to make sure these are + wrapped in PHYSFS_SUPPORTS_* checks before actually referencing them. */ +extern const PHYSFS_Archiver __PHYSFS_Archiver_DIR; +extern const PHYSFS_Archiver __PHYSFS_Archiver_ZIP; +extern const PHYSFS_Archiver __PHYSFS_Archiver_7Z; +extern const PHYSFS_Archiver __PHYSFS_Archiver_GRP; +extern const PHYSFS_Archiver __PHYSFS_Archiver_QPAK; +extern const PHYSFS_Archiver __PHYSFS_Archiver_HOG; +extern const PHYSFS_Archiver __PHYSFS_Archiver_MVL; +extern const PHYSFS_Archiver __PHYSFS_Archiver_WAD; +extern const PHYSFS_Archiver __PHYSFS_Archiver_SLB; +extern const PHYSFS_Archiver __PHYSFS_Archiver_ISO9660; +extern const PHYSFS_Archiver __PHYSFS_Archiver_VDF; + +/* a real C99-compliant snprintf() is in Visual Studio 2015, + but just use this everywhere for binary compatibility. */ +#if defined(_MSC_VER) +int __PHYSFS_msvc_vsnprintf(char *outBuf, size_t size, const char *format, va_list ap); +int __PHYSFS_msvc_snprintf(char *outBuf, size_t size, const char *format, ...); +#define vsnprintf __PHYSFS_msvc_vsnprintf +#define snprintf __PHYSFS_msvc_snprintf +#endif + +/* Some simple wrappers around WinRT C++ interfaces we can call from C. */ +#ifdef PHYSFS_PLATFORM_WINRT +const void *__PHYSFS_winrtCalcBaseDir(void); +const void *__PHYSFS_winrtCalcPrefDir(void); +#endif + +/* atomic operations. */ +#if defined(_MSC_VER) && (_MSC_VER >= 1500) +#include +__PHYSFS_COMPILE_TIME_ASSERT(LongEqualsInt, sizeof (int) == sizeof (long)); +#define __PHYSFS_ATOMIC_INCR(ptrval) _InterlockedIncrement((long*)(ptrval)) +#define __PHYSFS_ATOMIC_DECR(ptrval) _InterlockedDecrement((long*)(ptrval)) +#elif defined(__clang__) || (defined(__GNUC__) && (((__GNUC__ * 10000) + (__GNUC_MINOR__ * 100)) >= 40100)) +#define __PHYSFS_ATOMIC_INCR(ptrval) __sync_fetch_and_add(ptrval, 1) +#define __PHYSFS_ATOMIC_DECR(ptrval) __sync_fetch_and_add(ptrval, -1) +#else +#define PHYSFS_NEED_ATOMIC_OP_FALLBACK 1 +int __PHYSFS_ATOMIC_INCR(int *ptrval); +int __PHYSFS_ATOMIC_DECR(int *ptrval); +#endif + + +/* + * Interface for small allocations. If you need a little scratch space for + * a throwaway buffer or string, use this. It will make small allocations + * on the stack if possible, and use allocator.Malloc() if they are too + * large. This helps reduce malloc pressure. + * There are some rules, though: + * NEVER return a pointer from this, as stack-allocated buffers go away + * when your function returns. + * NEVER allocate in a loop, as stack-allocated pointers will pile up. Call + * a function that uses smallAlloc from your loop, so the allocation can + * free each time. + * NEVER call smallAlloc with any complex expression (it's a macro that WILL + * have side effects...it references the argument multiple times). Use a + * variable or a literal. + * NEVER free a pointer from this with anything but smallFree. It will not + * be a valid pointer to the allocator, regardless of where the memory came + * from. + * NEVER realloc a pointer from this. + * NEVER forget to use smallFree: it may not be a pointer from the stack. + * NEVER forget to check for NULL...allocation can fail here, of course! + */ +#define __PHYSFS_SMALLALLOCTHRESHOLD 256 +void *__PHYSFS_initSmallAlloc(void *ptr, const size_t len); + +#define __PHYSFS_smallAlloc(bytes) ( \ + __PHYSFS_initSmallAlloc( \ + (((bytes) < __PHYSFS_SMALLALLOCTHRESHOLD) ? \ + alloca((size_t)((bytes)+sizeof(void*))) : NULL), (bytes)) \ +) + +void __PHYSFS_smallFree(void *ptr); + + +/* Use the allocation hooks. */ +#define malloc(x) Do not use malloc() directly. +#define realloc(x, y) Do not use realloc() directly. +#define free(x) Do not use free() directly. +/* !!! FIXME: add alloca check here. */ + + +/* by default, enable things, so builds can opt out of a few things they + want to avoid. But you can build with this #defined to 0 if you would + like to turn off everything except a handful of things you opt into. */ +#ifndef PHYSFS_SUPPORTS_DEFAULT +#define PHYSFS_SUPPORTS_DEFAULT 1 +#endif + + +#ifndef PHYSFS_SUPPORTS_ZIP +#define PHYSFS_SUPPORTS_ZIP PHYSFS_SUPPORTS_DEFAULT +#endif +#ifndef PHYSFS_SUPPORTS_7Z +#define PHYSFS_SUPPORTS_7Z PHYSFS_SUPPORTS_DEFAULT +#endif +#ifndef PHYSFS_SUPPORTS_GRP +#define PHYSFS_SUPPORTS_GRP PHYSFS_SUPPORTS_DEFAULT +#endif +#ifndef PHYSFS_SUPPORTS_HOG +#define PHYSFS_SUPPORTS_HOG PHYSFS_SUPPORTS_DEFAULT +#endif +#ifndef PHYSFS_SUPPORTS_MVL +#define PHYSFS_SUPPORTS_MVL PHYSFS_SUPPORTS_DEFAULT +#endif +#ifndef PHYSFS_SUPPORTS_WAD +#define PHYSFS_SUPPORTS_WAD PHYSFS_SUPPORTS_DEFAULT +#endif +#ifndef PHYSFS_SUPPORTS_QPAK +#define PHYSFS_SUPPORTS_QPAK PHYSFS_SUPPORTS_DEFAULT +#endif +#ifndef PHYSFS_SUPPORTS_SLB +#define PHYSFS_SUPPORTS_SLB PHYSFS_SUPPORTS_DEFAULT +#endif +#ifndef PHYSFS_SUPPORTS_ISO9660 +#define PHYSFS_SUPPORTS_ISO9660 PHYSFS_SUPPORTS_DEFAULT +#endif +#ifndef PHYSFS_SUPPORTS_VDF +#define PHYSFS_SUPPORTS_VDF PHYSFS_SUPPORTS_DEFAULT +#endif + +#if PHYSFS_SUPPORTS_7Z +/* 7zip support needs a global init function called at startup (no deinit). */ +extern void SZIP_global_init(void); +#endif + +/* The latest supported PHYSFS_Io::version value. */ +#define CURRENT_PHYSFS_IO_API_VERSION 0 + +/* The latest supported PHYSFS_Archiver::version value. */ +#define CURRENT_PHYSFS_ARCHIVER_API_VERSION 0 + +/* This byteorder stuff was lifted from SDL. https://www.libsdl.org/ */ +#define PHYSFS_LIL_ENDIAN 1234 +#define PHYSFS_BIG_ENDIAN 4321 + +#ifdef __linux__ +#include +#define PHYSFS_BYTEORDER __BYTE_ORDER +#else /* __linux__ */ +#if defined(__hppa__) || \ + defined(__m68k__) || defined(mc68000) || defined(_M_M68K) || \ + (defined(__MIPS__) && defined(__MISPEB__)) || \ + defined(__ppc__) || defined(__POWERPC__) || defined(_M_PPC) || \ + defined(__sparc__) +#define PHYSFS_BYTEORDER PHYSFS_BIG_ENDIAN +#else +#define PHYSFS_BYTEORDER PHYSFS_LIL_ENDIAN +#endif +#endif /* __linux__ */ + + +/* + * When sorting the entries in an archive, we use a modified QuickSort. + * When there are less then PHYSFS_QUICKSORT_THRESHOLD entries left to sort, + * we switch over to a BubbleSort for the remainder. Tweak to taste. + * + * You can override this setting by defining PHYSFS_QUICKSORT_THRESHOLD + * before #including "physfs_internal.h". + */ +#ifndef PHYSFS_QUICKSORT_THRESHOLD +#define PHYSFS_QUICKSORT_THRESHOLD 4 +#endif + +/* + * Sort an array (or whatever) of (max) elements. This uses a mixture of + * a QuickSort and BubbleSort internally. + * (cmpfn) is used to determine ordering, and (swapfn) does the actual + * swapping of elements in the list. + */ +void __PHYSFS_sort(void *entries, size_t max, + int (*cmpfn)(void *, size_t, size_t), + void (*swapfn)(void *, size_t, size_t)); + +/* These get used all over for lessening code clutter. */ +/* "ERRPASS" means "something else just set the error state for us" and is + just to make it clear where the responsibility for the error state lays. */ +#define BAIL(e, r) do { if (e) PHYSFS_setErrorCode(e); return r; } while (0) +#define BAIL_ERRPASS(r) do { return r; } while (0) +#define BAIL_IF(c, e, r) do { if (c) { if (e) PHYSFS_setErrorCode(e); return r; } } while (0) +#define BAIL_IF_ERRPASS(c, r) do { if (c) { return r; } } while (0) +#define BAIL_MUTEX(e, m, r) do { if (e) PHYSFS_setErrorCode(e); __PHYSFS_platformReleaseMutex(m); return r; } while (0) +#define BAIL_MUTEX_ERRPASS(m, r) do { __PHYSFS_platformReleaseMutex(m); return r; } while (0) +#define BAIL_IF_MUTEX(c, e, m, r) do { if (c) { if (e) PHYSFS_setErrorCode(e); __PHYSFS_platformReleaseMutex(m); return r; } } while (0) +#define BAIL_IF_MUTEX_ERRPASS(c, m, r) do { if (c) { __PHYSFS_platformReleaseMutex(m); return r; } } while (0) +#define GOTO(e, g) do { if (e) PHYSFS_setErrorCode(e); goto g; } while (0) +#define GOTO_ERRPASS(g) do { goto g; } while (0) +#define GOTO_IF(c, e, g) do { if (c) { if (e) PHYSFS_setErrorCode(e); goto g; } } while (0) +#define GOTO_IF_ERRPASS(c, g) do { if (c) { goto g; } } while (0) +#define GOTO_MUTEX(e, m, g) do { if (e) PHYSFS_setErrorCode(e); __PHYSFS_platformReleaseMutex(m); goto g; } while (0) +#define GOTO_MUTEX_ERRPASS(m, g) do { __PHYSFS_platformReleaseMutex(m); goto g; } while (0) +#define GOTO_IF_MUTEX(c, e, m, g) do { if (c) { if (e) PHYSFS_setErrorCode(e); __PHYSFS_platformReleaseMutex(m); goto g; } } while (0) +#define GOTO_IF_MUTEX_ERRPASS(c, m, g) do { if (c) { __PHYSFS_platformReleaseMutex(m); goto g; } } while (0) + +#define __PHYSFS_ARRAYLEN(x) ( (sizeof (x)) / (sizeof (x[0])) ) + +#ifdef PHYSFS_NO_64BIT_SUPPORT +#define __PHYSFS_SI64(x) ((PHYSFS_sint64) (x)) +#define __PHYSFS_UI64(x) ((PHYSFS_uint64) (x)) +#elif (defined __GNUC__) +#define __PHYSFS_SI64(x) x##LL +#define __PHYSFS_UI64(x) x##ULL +#elif (defined _MSC_VER) +#define __PHYSFS_SI64(x) x##i64 +#define __PHYSFS_UI64(x) x##ui64 +#else +#define __PHYSFS_SI64(x) ((PHYSFS_sint64) (x)) +#define __PHYSFS_UI64(x) ((PHYSFS_uint64) (x)) +#endif + + +/* + * Check if a ui64 will fit in the platform's address space. + * The initial sizeof check will optimize this macro out entirely on + * 64-bit (and larger?!) platforms, and the other condition will + * return zero or non-zero if the variable will fit in the platform's + * size_t, suitable to pass to malloc. This is kinda messy, but effective. + */ +#define __PHYSFS_ui64FitsAddressSpace(s) ( \ + (sizeof (PHYSFS_uint64) <= sizeof (size_t)) || \ + ((s) < (__PHYSFS_UI64(0xFFFFFFFFFFFFFFFF) >> (64-(sizeof(size_t)*8)))) \ +) + +/* + * Like strdup(), but uses the current PhysicsFS allocator. + */ +char *__PHYSFS_strdup(const char *str); + +/* + * Give a hash value for a C string (uses djb's xor hashing algorithm). + */ +PHYSFS_uint32 __PHYSFS_hashString(const char *str, size_t len); + + +/* + * The current allocator. Not valid before PHYSFS_init is called! + */ +extern PHYSFS_Allocator __PHYSFS_AllocatorHooks; + +/* convenience macro to make this less cumbersome internally... */ +#define allocator __PHYSFS_AllocatorHooks + +/* + * Create a PHYSFS_Io for a file in the physical filesystem. + * This path is in platform-dependent notation. (mode) must be 'r', 'w', or + * 'a' for Read, Write, or Append. + */ +PHYSFS_Io *__PHYSFS_createNativeIo(const char *path, const int mode); + +/* + * Create a PHYSFS_Io for a buffer of memory (READ-ONLY). If you already + * have one of these, just use its duplicate() method, and it'll increment + * its refcount without allocating a copy of the buffer. + */ +PHYSFS_Io *__PHYSFS_createMemoryIo(const void *buf, PHYSFS_uint64 len, + void (*destruct)(void *)); + + +/* + * Read (len) bytes from (io) into (buf). Returns non-zero on success, + * zero on i/o error. Literally: "return (io->read(io, buf, len) == len);" + */ +int __PHYSFS_readAll(PHYSFS_Io *io, void *buf, const size_t len); + + +/* These are shared between some archivers. */ + +void UNPK_abandonArchive(void *opaque); +void UNPK_closeArchive(void *opaque); +void *UNPK_openArchive(PHYSFS_Io *io); +void *UNPK_addEntry(void *opaque, char *name, const int isdir, + const PHYSFS_sint64 ctime, const PHYSFS_sint64 mtime, + const PHYSFS_uint64 pos, const PHYSFS_uint64 len); +PHYSFS_Io *UNPK_openRead(void *opaque, const char *name); +PHYSFS_Io *UNPK_openWrite(void *opaque, const char *name); +PHYSFS_Io *UNPK_openAppend(void *opaque, const char *name); +int UNPK_remove(void *opaque, const char *name); +int UNPK_mkdir(void *opaque, const char *name); +int UNPK_stat(void *opaque, const char *fn, PHYSFS_Stat *st); +#define UNPK_enumerate __PHYSFS_DirTreeEnumerate + + + +/* Optional API many archivers use this to manage their directory tree. */ +/* !!! FIXME: document this better. */ + +typedef struct __PHYSFS_DirTreeEntry +{ + char *name; /* Full path in archive. */ + struct __PHYSFS_DirTreeEntry *hashnext; /* next item in hash bucket. */ + struct __PHYSFS_DirTreeEntry *children; /* linked list of kids, if dir. */ + struct __PHYSFS_DirTreeEntry *sibling; /* next item in same dir. */ + int isdir; +} __PHYSFS_DirTreeEntry; + +typedef struct __PHYSFS_DirTree +{ + __PHYSFS_DirTreeEntry *root; /* root of directory tree. */ + __PHYSFS_DirTreeEntry **hash; /* all entries hashed for fast lookup. */ + size_t hashBuckets; /* number of buckets in hash. */ + size_t entrylen; /* size in bytes of entries (including subclass). */ +} __PHYSFS_DirTree; + + +int __PHYSFS_DirTreeInit(__PHYSFS_DirTree *dt, const size_t entrylen); +void *__PHYSFS_DirTreeAdd(__PHYSFS_DirTree *dt, char *name, const int isdir); +void *__PHYSFS_DirTreeFind(__PHYSFS_DirTree *dt, const char *path); +PHYSFS_EnumerateCallbackResult __PHYSFS_DirTreeEnumerate(void *opaque, + const char *dname, PHYSFS_EnumerateCallback cb, + const char *origdir, void *callbackdata); +void __PHYSFS_DirTreeDeinit(__PHYSFS_DirTree *dt); + + + +/*--------------------------------------------------------------------------*/ +/*--------------------------------------------------------------------------*/ +/*------------ ----------------*/ +/*------------ You MUST implement the following functions ----------------*/ +/*------------ if porting to a new platform. ----------------*/ +/*------------ (see platform/unix.c for an example) ----------------*/ +/*------------ ----------------*/ +/*--------------------------------------------------------------------------*/ +/*--------------------------------------------------------------------------*/ + + +/* + * The dir separator; '/' on unix, '\\' on win32, ":" on MacOS, etc... + * Obviously, this isn't a function. If you need more than one char for this, + * you'll need to pull some old pieces of PhysicsFS out of revision control. + */ +#if defined(PHYSFS_PLATFORM_WINDOWS) || defined(PHYSFS_PLATFORM_OS2) +#define __PHYSFS_platformDirSeparator '\\' +#else +#define __PHYSFS_STANDARD_DIRSEP 1 +#define __PHYSFS_platformDirSeparator '/' +#endif + +/* + * Initialize the platform. This is called when PHYSFS_init() is called from + * the application. + * + * Return zero if there was a catastrophic failure (which prevents you from + * functioning at all), and non-zero otherwise. + */ +int __PHYSFS_platformInit(void); + + +/* + * Deinitialize the platform. This is called when PHYSFS_deinit() is called + * from the application. You can use this to clean up anything you've + * allocated in your platform driver. + */ +void __PHYSFS_platformDeinit(void); + + +/* + * Open a file for reading. (filename) is in platform-dependent notation. The + * file pointer should be positioned on the first byte of the file. + * + * The return value will be some platform-specific datatype that is opaque to + * the caller; it could be a (FILE *) under Unix, or a (HANDLE *) under win32. + * + * The same file can be opened for read multiple times, and each should have + * a unique file handle; this is frequently employed to prevent race + * conditions in the archivers. + * + * Call PHYSFS_setErrorCode() and return (NULL) if the file can't be opened. + */ +void *__PHYSFS_platformOpenRead(const char *filename); + + +/* + * Open a file for writing. (filename) is in platform-dependent notation. If + * the file exists, it should be truncated to zero bytes, and if it doesn't + * exist, it should be created as a zero-byte file. The file pointer should + * be positioned on the first byte of the file. + * + * The return value will be some platform-specific datatype that is opaque to + * the caller; it could be a (FILE *) under Unix, or a (HANDLE *) under win32, + * etc. + * + * Opening a file for write multiple times has undefined results. + * + * Call PHYSFS_setErrorCode() and return (NULL) if the file can't be opened. + */ +void *__PHYSFS_platformOpenWrite(const char *filename); + + +/* + * Open a file for appending. (filename) is in platform-dependent notation. If + * the file exists, the file pointer should be place just past the end of the + * file, so that the first write will be one byte after the current end of + * the file. If the file doesn't exist, it should be created as a zero-byte + * file. The file pointer should be positioned on the first byte of the file. + * + * The return value will be some platform-specific datatype that is opaque to + * the caller; it could be a (FILE *) under Unix, or a (HANDLE *) under win32, + * etc. + * + * Opening a file for append multiple times has undefined results. + * + * Call PHYSFS_setErrorCode() and return (NULL) if the file can't be opened. + */ +void *__PHYSFS_platformOpenAppend(const char *filename); + +/* + * Read more data from a platform-specific file handle. (opaque) should be + * cast to whatever data type your platform uses. Read a maximum of (len) + * 8-bit bytes to the area pointed to by (buf). If there isn't enough data + * available, return the number of bytes read, and position the file pointer + * immediately after those bytes. + * On success, return (len) and position the file pointer immediately past + * the end of the last read byte. Return (-1) if there is a catastrophic + * error, and call PHYSFS_setErrorCode() to describe the problem; the file + * pointer should not move in such a case. A partial read is success; only + * return (-1) on total failure; presumably, the next read call after a + * partial read will fail as such. + */ +PHYSFS_sint64 __PHYSFS_platformRead(void *opaque, void *buf, PHYSFS_uint64 len); + +/* + * Write more data to a platform-specific file handle. (opaque) should be + * cast to whatever data type your platform uses. Write a maximum of (len) + * 8-bit bytes from the area pointed to by (buffer). If there is a problem, + * return the number of bytes written, and position the file pointer + * immediately after those bytes. Return (-1) if there is a catastrophic + * error, and call PHYSFS_setErrorCode() to describe the problem; the file + * pointer should not move in such a case. A partial write is success; only + * return (-1) on total failure; presumably, the next write call after a + * partial write will fail as such. + */ +PHYSFS_sint64 __PHYSFS_platformWrite(void *opaque, const void *buffer, + PHYSFS_uint64 len); + +/* + * Set the file pointer to a new position. (opaque) should be cast to + * whatever data type your platform uses. (pos) specifies the number + * of 8-bit bytes to seek to from the start of the file. Seeking past the + * end of the file is an error condition, and you should check for it. + * + * Not all file types can seek; this is to be expected by the caller. + * + * On error, call PHYSFS_setErrorCode() and return zero. On success, return + * a non-zero value. + */ +int __PHYSFS_platformSeek(void *opaque, PHYSFS_uint64 pos); + + +/* + * Get the file pointer's position, in an 8-bit byte offset from the start of + * the file. (opaque) should be cast to whatever data type your platform + * uses. + * + * Not all file types can "tell"; this is to be expected by the caller. + * + * On error, call PHYSFS_setErrorCode() and return -1. On success, return >= 0. + */ +PHYSFS_sint64 __PHYSFS_platformTell(void *opaque); + + +/* + * Determine the current size of a file, in 8-bit bytes, from an open file. + * + * The caller expects that this information may not be available for all + * file types on all platforms. + * + * Return -1 if you can't do it, and call PHYSFS_setErrorCode(). Otherwise, + * return the file length in 8-bit bytes. + */ +PHYSFS_sint64 __PHYSFS_platformFileLength(void *handle); + + +/* + * Read filesystem metadata for a specific path. + * + * This needs to fill in all the fields of (stat). For fields that might not + * mean anything on a platform (access time, perhaps), choose a reasonable + * default. if (follow), we want to follow symlinks and stat what they + * link to and not the link itself. + * + * Return zero on failure, non-zero on success. + */ +int __PHYSFS_platformStat(const char *fn, PHYSFS_Stat *stat, const int follow); + +/* + * Flush any pending writes to disk. (opaque) should be cast to whatever data + * type your platform uses. Be sure to check for errors; the caller expects + * that this function can fail if there was a flushing error, etc. + * + * Return zero on failure, non-zero on success. + */ +int __PHYSFS_platformFlush(void *opaque); + +/* + * Close file and deallocate resources. (opaque) should be cast to whatever + * data type your platform uses. This should close the file in any scenario: + * flushing is a separate function call, and this function should never fail. + * + * You should clean up all resources associated with (opaque); the pointer + * will be considered invalid after this call. + */ +void __PHYSFS_platformClose(void *opaque); + +/* + * Platform implementation of PHYSFS_getCdRomDirsCallback()... + * CD directories are discovered and reported to the callback one at a time. + * Pointers passed to the callback are assumed to be invalid to the + * application after the callback returns, so you can free them or whatever. + * Callback does not assume results will be sorted in any meaningful way. + */ +void __PHYSFS_platformDetectAvailableCDs(PHYSFS_StringCallback cb, void *data); + +/* + * Calculate the base dir, if your platform needs special consideration. + * Just return NULL if the standard routines will suffice. (see + * calculateBaseDir() in physfs.c ...) + * Your string must end with a dir separator if you don't return NULL. + * Caller will allocator.Free() the retval if it's not NULL. + */ +char *__PHYSFS_platformCalcBaseDir(const char *argv0); + +/* + * Get the platform-specific user dir. + * As of PhysicsFS 2.1, returning NULL means fatal error. + * Your string must end with a dir separator if you don't return NULL. + * Caller will allocator.Free() the retval if it's not NULL. + */ +char *__PHYSFS_platformCalcUserDir(void); + + +/* This is the cached version from PHYSFS_init(). This is a fast call. */ +const char *__PHYSFS_getUserDir(void); /* not deprecated internal version. */ + + +/* + * Get the platform-specific pref dir. + * Returning NULL means fatal error. + * Your string must end with a dir separator if you don't return NULL. + * Caller will allocator.Free() the retval if it's not NULL. + * Caller will make missing directories if necessary; this just reports + * the final path. + */ +char *__PHYSFS_platformCalcPrefDir(const char *org, const char *app); + + +/* + * Return a pointer that uniquely identifies the current thread. + * On a platform without threading, (0x1) will suffice. These numbers are + * arbitrary; the only requirement is that no two threads have the same + * pointer. + */ +void *__PHYSFS_platformGetThreadID(void); + + +/* + * Enumerate a directory of files. This follows the rules for the + * PHYSFS_Archiver::enumerate() method, except that the (dirName) that is + * passed to this function is converted to platform-DEPENDENT notation by + * the caller. The PHYSFS_Archiver version uses platform-independent + * notation. Note that ".", "..", and other meta-entries should always + * be ignored. + */ +PHYSFS_EnumerateCallbackResult __PHYSFS_platformEnumerate(const char *dirname, + PHYSFS_EnumerateCallback callback, + const char *origdir, void *callbackdata); + +/* + * Make a directory in the actual filesystem. (path) is specified in + * platform-dependent notation. On error, return zero and set the error + * message. Return non-zero on success. + */ +int __PHYSFS_platformMkDir(const char *path); + + +/* + * Remove a file or directory entry in the actual filesystem. (path) is + * specified in platform-dependent notation. Note that this deletes files + * _and_ directories, so you might need to do some determination. + * Non-empty directories should report an error and not delete themselves + * or their contents. + * + * Deleting a symlink should remove the link, not what it points to. + * + * On error, return zero and set the error message. Return non-zero on success. + */ +int __PHYSFS_platformDelete(const char *path); + + +/* + * Create a platform-specific mutex. This can be whatever datatype your + * platform uses for mutexes, but it is cast to a (void *) for abstractness. + * + * Return (NULL) if you couldn't create one. Systems without threads can + * return any arbitrary non-NULL value. + */ +void *__PHYSFS_platformCreateMutex(void); + +/* + * Destroy a platform-specific mutex, and clean up any resources associated + * with it. (mutex) is a value previously returned by + * __PHYSFS_platformCreateMutex(). This can be a no-op on single-threaded + * platforms. + */ +void __PHYSFS_platformDestroyMutex(void *mutex); + +/* + * Grab possession of a platform-specific mutex. Mutexes should be recursive; + * that is, the same thread should be able to call this function multiple + * times in a row without causing a deadlock. This function should block + * until a thread can gain possession of the mutex. + * + * Return non-zero if the mutex was grabbed, zero if there was an + * unrecoverable problem grabbing it (this should not be a matter of + * timing out! We're talking major system errors; block until the mutex + * is available otherwise.) + * + * _DO NOT_ call PHYSFS_setErrorCode() in here! Since setErrorCode calls this + * function, you'll cause an infinite recursion. This means you can't + * use the BAIL_*MACRO* macros, either. + */ +int __PHYSFS_platformGrabMutex(void *mutex); + +/* + * Relinquish possession of the mutex when this method has been called + * once for each time that platformGrabMutex was called. Once possession has + * been released, the next thread in line to grab the mutex (if any) may + * proceed. + * + * _DO NOT_ call PHYSFS_setErrorCode() in here! Since setErrorCode calls this + * function, you'll cause an infinite recursion. This means you can't + * use the BAIL_*MACRO* macros, either. + */ +void __PHYSFS_platformReleaseMutex(void *mutex); + +#if PHYSFS_HAVE_PRAGMA_VISIBILITY +#pragma GCC visibility pop +#endif + +#ifdef __cplusplus +} +#endif + +#endif + +/* end of physfs_internal.h ... */ + diff --git a/Source/Dependencies/PhysicsFS/physfs_lzmasdk.h b/Source/Dependencies/PhysicsFS/physfs_lzmasdk.h new file mode 100644 index 000000000..c86972e57 --- /dev/null +++ b/Source/Dependencies/PhysicsFS/physfs_lzmasdk.h @@ -0,0 +1,6028 @@ +#ifndef _INCLUDE_PHYSFS_LZMASDK_H_ +#define _INCLUDE_PHYSFS_LZMASDK_H_ + +/* This is just a bunch of the LZMA SDK mushed together into one header. +This code is all public domain, and mostly (if not entirely) written by +Igor Pavlov. http://www.7-zip.org/sdk.html +--ryan. */ + + + +/* 7zTypes.h -- Basic types +2013-11-12 : Igor Pavlov : Public domain */ + +#ifndef __7Z_TYPES_H +#define __7Z_TYPES_H + +#ifdef _WIN32 +/* #include */ +#endif + +#include + +#ifndef EXTERN_C_BEGIN +#ifdef __cplusplus +#define EXTERN_C_BEGIN extern "C" { +#define EXTERN_C_END } +#else +#define EXTERN_C_BEGIN +#define EXTERN_C_END +#endif +#endif + +EXTERN_C_BEGIN + +#define SZ_OK 0 + +#define SZ_ERROR_DATA 1 +#define SZ_ERROR_MEM 2 +#define SZ_ERROR_CRC 3 +#define SZ_ERROR_UNSUPPORTED 4 +#define SZ_ERROR_PARAM 5 +#define SZ_ERROR_INPUT_EOF 6 +#define SZ_ERROR_OUTPUT_EOF 7 +#define SZ_ERROR_READ 8 +#define SZ_ERROR_WRITE 9 +#define SZ_ERROR_PROGRESS 10 +#define SZ_ERROR_FAIL 11 +#define SZ_ERROR_THREAD 12 + +#define SZ_ERROR_ARCHIVE 16 +#define SZ_ERROR_NO_ARCHIVE 17 + +typedef int SRes; + +#ifdef _WIN32 +/* typedef DWORD WRes; */ +typedef unsigned WRes; +#else +typedef int WRes; +#endif + +#ifndef RINOK +#define RINOK(x) { int __result__ = (x); if (__result__ != 0) return __result__; } +#endif + +typedef unsigned char Byte; +typedef short Int16; +typedef unsigned short UInt16; + +#ifdef _LZMA_UINT32_IS_ULONG +typedef long Int32; +typedef unsigned long UInt32; +#else +typedef int Int32; +typedef unsigned int UInt32; +#endif + +#ifdef _SZ_NO_INT_64 + +/* define _SZ_NO_INT_64, if your compiler doesn't support 64-bit integers. + NOTES: Some code will work incorrectly in that case! */ + +typedef long Int64; +typedef unsigned long UInt64; + +#else + +#if defined(_MSC_VER) || defined(__BORLANDC__) || defined(__WATCOMC__) +typedef __int64 Int64; +typedef unsigned __int64 UInt64; +#define UINT64_CONST(n) n ## ui64 +#else +typedef long long int Int64; +typedef unsigned long long int UInt64; +#define UINT64_CONST(n) n ## ULL +#endif + +#endif + +#ifdef _LZMA_NO_SYSTEM_SIZE_T +typedef UInt32 SizeT; +#else +typedef size_t SizeT; +#endif + +typedef int Bool; +#define True 1 +#define False 0 + + +#ifdef _WIN32 +#define MY_STD_CALL __stdcall +#else +#define MY_STD_CALL +#endif + +#ifdef _MSC_VER + +#if _MSC_VER >= 1300 +#define MY_NO_INLINE __declspec(noinline) +#else +#define MY_NO_INLINE +#endif + +#define MY_CDECL __cdecl +#define MY_FAST_CALL __fastcall + +#else + +#define MY_NO_INLINE +#define MY_CDECL +#define MY_FAST_CALL + +#endif + + +/* The following interfaces use first parameter as pointer to structure */ + +typedef struct +{ + Byte (*Read)(void *p); /* reads one byte, returns 0 in case of EOF or error */ +} IByteIn; + +typedef struct +{ + void (*Write)(void *p, Byte b); +} IByteOut; + +typedef struct +{ + SRes (*Read)(void *p, void *buf, size_t *size); + /* if (input(*size) != 0 && output(*size) == 0) means end_of_stream. + (output(*size) < input(*size)) is allowed */ +} ISeqInStream; + +typedef struct +{ + size_t (*Write)(void *p, const void *buf, size_t size); + /* Returns: result - the number of actually written bytes. + (result < size) means error */ +} ISeqOutStream; + +typedef enum +{ + SZ_SEEK_SET = 0, + SZ_SEEK_CUR = 1, + SZ_SEEK_END = 2 +} ESzSeek; + +typedef struct +{ + SRes (*Read)(void *p, void *buf, size_t *size); /* same as ISeqInStream::Read */ + SRes (*Seek)(void *p, Int64 *pos, ESzSeek origin); +} ISeekInStream; + +typedef struct +{ + SRes (*Look)(void *p, const void **buf, size_t *size); + /* if (input(*size) != 0 && output(*size) == 0) means end_of_stream. + (output(*size) > input(*size)) is not allowed + (output(*size) < input(*size)) is allowed */ + SRes (*Skip)(void *p, size_t offset); + /* offset must be <= output(*size) of Look */ + + SRes (*Read)(void *p, void *buf, size_t *size); + /* reads directly (without buffer). It's same as ISeqInStream::Read */ + SRes (*Seek)(void *p, Int64 *pos, ESzSeek origin); +} ILookInStream; + +static SRes LookInStream_SeekTo(ILookInStream *stream, UInt64 offset); + +/* reads via ILookInStream::Read */ +static SRes LookInStream_Read2(ILookInStream *stream, void *buf, size_t size, SRes errorType); +static SRes LookInStream_Read(ILookInStream *stream, void *buf, size_t size); + +#define LookToRead_BUF_SIZE (1 << 14) + +typedef struct +{ + ILookInStream s; + ISeekInStream *realStream; + size_t pos; + size_t size; + Byte buf[LookToRead_BUF_SIZE]; +} CLookToRead; + +static void LookToRead_CreateVTable(CLookToRead *p, int lookahead); +static void LookToRead_Init(CLookToRead *p); + +typedef struct +{ + ISeqInStream s; + ILookInStream *realStream; +} CSecToLook; + +typedef struct +{ + ISeqInStream s; + ILookInStream *realStream; +} CSecToRead; + +typedef struct +{ + SRes (*Progress)(void *p, UInt64 inSize, UInt64 outSize); + /* Returns: result. (result != SZ_OK) means break. + Value (UInt64)(Int64)-1 for size means unknown value. */ +} ICompressProgress; + +typedef struct +{ + void *(*Alloc)(void *p, size_t size); + void (*Free)(void *p, void *address); /* address can be 0 */ +} ISzAlloc; + +#define IAlloc_Alloc(p, size) (p)->Alloc((p), size) +#define IAlloc_Free(p, a) (p)->Free((p), a) + +#ifdef _WIN32 + +#define CHAR_PATH_SEPARATOR '\\' +#define WCHAR_PATH_SEPARATOR L'\\' +#define STRING_PATH_SEPARATOR "\\" +#define WSTRING_PATH_SEPARATOR L"\\" + +#else + +#define CHAR_PATH_SEPARATOR '/' +#define WCHAR_PATH_SEPARATOR L'/' +#define STRING_PATH_SEPARATOR "/" +#define WSTRING_PATH_SEPARATOR L"/" + +#endif + +EXTERN_C_END + +#endif + +/* 7z.h -- 7z interface +2015-11-18 : Igor Pavlov : Public domain */ + +#ifndef __7Z_H +#define __7Z_H + +/*#include "7zTypes.h"*/ + +EXTERN_C_BEGIN + +#define k7zStartHeaderSize 0x20 +#define k7zSignatureSize 6 + +static const Byte k7zSignature[k7zSignatureSize]; + +typedef struct +{ + const Byte *Data; + size_t Size; +} CSzData; + +/* CSzCoderInfo & CSzFolder support only default methods */ + +typedef struct +{ + size_t PropsOffset; + UInt32 MethodID; + Byte NumStreams; + Byte PropsSize; +} CSzCoderInfo; + +typedef struct +{ + UInt32 InIndex; + UInt32 OutIndex; +} CSzBond; + +#define SZ_NUM_CODERS_IN_FOLDER_MAX 4 +#define SZ_NUM_BONDS_IN_FOLDER_MAX 3 +#define SZ_NUM_PACK_STREAMS_IN_FOLDER_MAX 4 + +typedef struct +{ + UInt32 NumCoders; + UInt32 NumBonds; + UInt32 NumPackStreams; + UInt32 UnpackStream; + UInt32 PackStreams[SZ_NUM_PACK_STREAMS_IN_FOLDER_MAX]; + CSzBond Bonds[SZ_NUM_BONDS_IN_FOLDER_MAX]; + CSzCoderInfo Coders[SZ_NUM_CODERS_IN_FOLDER_MAX]; +} CSzFolder; + + +static SRes SzGetNextFolderItem(CSzFolder *f, CSzData *sd); + +typedef struct +{ + UInt32 Low; + UInt32 High; +} CNtfsFileTime; + +typedef struct +{ + Byte *Defs; /* MSB 0 bit numbering */ + UInt32 *Vals; +} CSzBitUi32s; + +typedef struct +{ + Byte *Defs; /* MSB 0 bit numbering */ + /* UInt64 *Vals; */ + CNtfsFileTime *Vals; +} CSzBitUi64s; + +#define SzBitArray_Check(p, i) (((p)[(i) >> 3] & (0x80 >> ((i) & 7))) != 0) + +#define SzBitWithVals_Check(p, i) ((p)->Defs && ((p)->Defs[(i) >> 3] & (0x80 >> ((i) & 7))) != 0) + +typedef struct +{ + UInt32 NumPackStreams; + UInt32 NumFolders; + + UInt64 *PackPositions; /* NumPackStreams + 1 */ + CSzBitUi32s FolderCRCs; /* NumFolders */ + + size_t *FoCodersOffsets; /* NumFolders + 1 */ + UInt32 *FoStartPackStreamIndex; /* NumFolders + 1 */ + UInt32 *FoToCoderUnpackSizes; /* NumFolders + 1 */ + Byte *FoToMainUnpackSizeIndex; /* NumFolders */ + UInt64 *CoderUnpackSizes; /* for all coders in all folders */ + + Byte *CodersData; +} CSzAr; + +static UInt64 SzAr_GetFolderUnpackSize(const CSzAr *p, UInt32 folderIndex); + +static SRes SzAr_DecodeFolder(const CSzAr *p, UInt32 folderIndex, + ILookInStream *stream, UInt64 startPos, + Byte *outBuffer, size_t outSize, + ISzAlloc *allocMain); + +typedef struct +{ + CSzAr db; + + UInt64 startPosAfterHeader; + UInt64 dataPos; + + UInt32 NumFiles; + + UInt64 *UnpackPositions; /* NumFiles + 1 */ + /* Byte *IsEmptyFiles; */ + Byte *IsDirs; + CSzBitUi32s CRCs; + + CSzBitUi32s Attribs; + /* CSzBitUi32s Parents; */ + CSzBitUi64s MTime; + CSzBitUi64s CTime; + + UInt32 *FolderToFile; /* NumFolders + 1 */ + UInt32 *FileToFolder; /* NumFiles */ + + size_t *FileNameOffsets; /* in 2-byte steps */ + Byte *FileNames; /* UTF-16-LE */ +} CSzArEx; + +#define SzArEx_IsDir(p, i) (SzBitArray_Check((p)->IsDirs, i)) + +#define SzArEx_GetFileSize(p, i) ((p)->UnpackPositions[(i) + 1] - (p)->UnpackPositions[i]) + +static void SzArEx_Init(CSzArEx *p); +static void SzArEx_Free(CSzArEx *p, ISzAlloc *alloc); + +/* +if dest == NULL, the return value specifies the required size of the buffer, + in 16-bit characters, including the null-terminating character. +if dest != NULL, the return value specifies the number of 16-bit characters that + are written to the dest, including the null-terminating character. */ + +static size_t SzArEx_GetFileNameUtf16(const CSzArEx *p, size_t fileIndex, UInt16 *dest); + +/* +size_t SzArEx_GetFullNameLen(const CSzArEx *p, size_t fileIndex); +UInt16 *SzArEx_GetFullNameUtf16_Back(const CSzArEx *p, size_t fileIndex, UInt16 *dest); +*/ + + + +/* + SzArEx_Extract extracts file from archive + + *outBuffer must be 0 before first call for each new archive. + + Extracting cache: + If you need to decompress more than one file, you can send + these values from previous call: + *blockIndex, + *outBuffer, + *outBufferSize + You can consider "*outBuffer" as cache of solid block. If your archive is solid, + it will increase decompression speed. + + If you use external function, you can declare these 3 cache variables + (blockIndex, outBuffer, outBufferSize) as static in that external function. + + Free *outBuffer and set *outBuffer to 0, if you want to flush cache. +*/ + +static SRes SzArEx_Extract( + const CSzArEx *db, + ILookInStream *inStream, + UInt32 fileIndex, /* index of file */ + UInt32 *blockIndex, /* index of solid block */ + Byte **outBuffer, /* pointer to pointer to output buffer (allocated with allocMain) */ + size_t *outBufferSize, /* buffer size for output buffer */ + size_t *offset, /* offset of stream for required file in *outBuffer */ + size_t *outSizeProcessed, /* size of file in *outBuffer */ + ISzAlloc *allocMain, + ISzAlloc *allocTemp); + + +/* +SzArEx_Open Errors: +SZ_ERROR_NO_ARCHIVE +SZ_ERROR_ARCHIVE +SZ_ERROR_UNSUPPORTED +SZ_ERROR_MEM +SZ_ERROR_CRC +SZ_ERROR_INPUT_EOF +SZ_ERROR_FAIL +*/ + +static SRes SzArEx_Open(CSzArEx *p, ILookInStream *inStream, + ISzAlloc *allocMain, ISzAlloc *allocTemp); + +EXTERN_C_END + +#endif + +/* 7zCrc.h -- CRC32 calculation +2013-01-18 : Igor Pavlov : Public domain */ + +#ifndef __7Z_CRC_H +#define __7Z_CRC_H + +/*#include "7zTypes.h" */ + +EXTERN_C_BEGIN + +/* Call CrcGenerateTable one time before other CRC functions */ +static void MY_FAST_CALL CrcGenerateTable(void); + +#define CRC_INIT_VAL 0xFFFFFFFF +#define CRC_GET_DIGEST(crc) ((crc) ^ CRC_INIT_VAL) +#define CRC_UPDATE_BYTE(crc, b) (g_CrcTable[((crc) ^ (b)) & 0xFF] ^ ((crc) >> 8)) + +static UInt32 MY_FAST_CALL CrcCalc(const void *data, size_t size); + +EXTERN_C_END + +#endif + +/* CpuArch.h -- CPU specific code +2016-06-09: Igor Pavlov : Public domain */ + +#ifndef __CPU_ARCH_H +#define __CPU_ARCH_H + +/*#include "7zTypes.h"*/ + +EXTERN_C_BEGIN + +/* +MY_CPU_LE means that CPU is LITTLE ENDIAN. +MY_CPU_BE means that CPU is BIG ENDIAN. +If MY_CPU_LE and MY_CPU_BE are not defined, we don't know about ENDIANNESS of platform. + +MY_CPU_LE_UNALIGN means that CPU is LITTLE ENDIAN and CPU supports unaligned memory accesses. +*/ + +#if defined(_M_X64) \ + || defined(_M_AMD64) \ + || defined(__x86_64__) \ + || defined(__AMD64__) \ + || defined(__amd64__) + #define MY_CPU_AMD64 +#endif + +#if defined(MY_CPU_AMD64) \ + || defined(_M_IA64) \ + || defined(__AARCH64EL__) \ + || defined(__AARCH64EB__) + #define MY_CPU_64BIT +#endif + +#if defined(_M_IX86) || defined(__i386__) +#define MY_CPU_X86 +#endif + +#if defined(MY_CPU_X86) || defined(MY_CPU_AMD64) +#define MY_CPU_X86_OR_AMD64 +#endif + +#if defined(MY_CPU_X86) \ + || defined(_M_ARM) \ + || defined(__ARMEL__) \ + || defined(__THUMBEL__) \ + || defined(__ARMEB__) \ + || defined(__THUMBEB__) + #define MY_CPU_32BIT +#endif + +#if defined(_WIN32) && defined(_M_ARM) +#define MY_CPU_ARM_LE +#endif + +#if defined(_WIN32) && defined(_M_IA64) +#define MY_CPU_IA64_LE +#endif + +#if defined(MY_CPU_X86_OR_AMD64) \ + || defined(MY_CPU_ARM_LE) \ + || defined(MY_CPU_IA64_LE) \ + || defined(__LITTLE_ENDIAN__) \ + || defined(__ARMEL__) \ + || defined(__THUMBEL__) \ + || defined(__AARCH64EL__) \ + || defined(__MIPSEL__) \ + || defined(__MIPSEL) \ + || defined(_MIPSEL) \ + || defined(__BFIN__) \ + || (defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) + #define MY_CPU_LE +#endif + +#if defined(__BIG_ENDIAN__) \ + || defined(__ARMEB__) \ + || defined(__THUMBEB__) \ + || defined(__AARCH64EB__) \ + || defined(__MIPSEB__) \ + || defined(__MIPSEB) \ + || defined(_MIPSEB) \ + || defined(__m68k__) \ + || defined(__s390__) \ + || defined(__s390x__) \ + || defined(__zarch__) \ + || (defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) + #define MY_CPU_BE +#endif + +#if defined(MY_CPU_LE) && defined(MY_CPU_BE) +Stop_Compiling_Bad_Endian +#endif + + +#ifdef MY_CPU_LE + #if defined(MY_CPU_X86_OR_AMD64) \ + /* || defined(__AARCH64EL__) */ + /*#define MY_CPU_LE_UNALIGN*/ + #endif +#endif + + +#ifdef MY_CPU_LE_UNALIGN + +#define GetUi16(p) (*(const UInt16 *)(const void *)(p)) +#define GetUi32(p) (*(const UInt32 *)(const void *)(p)) +#define GetUi64(p) (*(const UInt64 *)(const void *)(p)) + +#define SetUi16(p, v) { *(UInt16 *)(p) = (v); } +#define SetUi32(p, v) { *(UInt32 *)(p) = (v); } +#define SetUi64(p, v) { *(UInt64 *)(p) = (v); } + +#else + +#define GetUi16(p) ( (UInt16) ( \ + ((const Byte *)(p))[0] | \ + ((UInt16)((const Byte *)(p))[1] << 8) )) + +#define GetUi32(p) ( \ + ((const Byte *)(p))[0] | \ + ((UInt32)((const Byte *)(p))[1] << 8) | \ + ((UInt32)((const Byte *)(p))[2] << 16) | \ + ((UInt32)((const Byte *)(p))[3] << 24)) + +#define GetUi64(p) (GetUi32(p) | ((UInt64)GetUi32(((const Byte *)(p)) + 4) << 32)) + +#define SetUi16(p, v) { Byte *_ppp_ = (Byte *)(p); UInt32 _vvv_ = (v); \ + _ppp_[0] = (Byte)_vvv_; \ + _ppp_[1] = (Byte)(_vvv_ >> 8); } + +#define SetUi32(p, v) { Byte *_ppp_ = (Byte *)(p); UInt32 _vvv_ = (v); \ + _ppp_[0] = (Byte)_vvv_; \ + _ppp_[1] = (Byte)(_vvv_ >> 8); \ + _ppp_[2] = (Byte)(_vvv_ >> 16); \ + _ppp_[3] = (Byte)(_vvv_ >> 24); } + +#define SetUi64(p, v) { Byte *_ppp2_ = (Byte *)(p); UInt64 _vvv2_ = (v); \ + SetUi32(_ppp2_ , (UInt32)_vvv2_); \ + SetUi32(_ppp2_ + 4, (UInt32)(_vvv2_ >> 32)); } + +#endif + + +#if defined(MY_CPU_LE_UNALIGN) && /* defined(_WIN64) && */ (_MSC_VER >= 1300) + +/* Note: we use bswap instruction, that is unsupported in 386 cpu */ + +#include + +#pragma intrinsic(_byteswap_ulong) +#pragma intrinsic(_byteswap_uint64) +#define GetBe32(p) _byteswap_ulong(*(const UInt32 *)(const Byte *)(p)) +#define GetBe64(p) _byteswap_uint64(*(const UInt64 *)(const Byte *)(p)) + +#define SetBe32(p, v) (*(UInt32 *)(void *)(p)) = _byteswap_ulong(v) + +#elif defined(MY_CPU_LE_UNALIGN) && defined (__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) + +#define GetBe32(p) __builtin_bswap32(*(const UInt32 *)(const Byte *)(p)) +#define GetBe64(p) __builtin_bswap64(*(const UInt64 *)(const Byte *)(p)) + +#define SetBe32(p, v) (*(UInt32 *)(void *)(p)) = __builtin_bswap32(v) + +#else + +#define GetBe32(p) ( \ + ((UInt32)((const Byte *)(p))[0] << 24) | \ + ((UInt32)((const Byte *)(p))[1] << 16) | \ + ((UInt32)((const Byte *)(p))[2] << 8) | \ + ((const Byte *)(p))[3] ) + +#define GetBe64(p) (((UInt64)GetBe32(p) << 32) | GetBe32(((const Byte *)(p)) + 4)) + +#define SetBe32(p, v) { Byte *_ppp_ = (Byte *)(p); UInt32 _vvv_ = (v); \ + _ppp_[0] = (Byte)(_vvv_ >> 24); \ + _ppp_[1] = (Byte)(_vvv_ >> 16); \ + _ppp_[2] = (Byte)(_vvv_ >> 8); \ + _ppp_[3] = (Byte)_vvv_; } + +#endif + + +#define GetBe16(p) ( (UInt16) ( \ + ((UInt16)((const Byte *)(p))[0] << 8) | \ + ((const Byte *)(p))[1] )) + + + +#ifdef MY_CPU_X86_OR_AMD64 + +typedef struct +{ + UInt32 maxFunc; + UInt32 vendor[3]; + UInt32 ver; + UInt32 b; + UInt32 c; + UInt32 d; +} Cx86cpuid; + +enum +{ + CPU_FIRM_INTEL, + CPU_FIRM_AMD, + CPU_FIRM_VIA +}; + +static void MyCPUID(UInt32 function, UInt32 *a, UInt32 *b, UInt32 *c, UInt32 *d); + +static Bool x86cpuid_CheckAndRead(Cx86cpuid *p); +static int x86cpuid_GetFirm(const Cx86cpuid *p); + +#define x86cpuid_GetFamily(ver) (((ver >> 16) & 0xFF0) | ((ver >> 8) & 0xF)) +#define x86cpuid_GetModel(ver) (((ver >> 12) & 0xF0) | ((ver >> 4) & 0xF)) +#define x86cpuid_GetStepping(ver) (ver & 0xF) + +static Bool CPU_Is_InOrder(); + +#endif + +EXTERN_C_END + +#endif + +/* 7zBuf.h -- Byte Buffer +2013-01-18 : Igor Pavlov : Public domain */ + +#ifndef __7Z_BUF_H +#define __7Z_BUF_H + +/*#include "7zTypes.h" */ + +EXTERN_C_BEGIN + +typedef struct +{ + Byte *data; + size_t size; +} CBuf; + +static void Buf_Init(CBuf *p); +static int Buf_Create(CBuf *p, size_t size, ISzAlloc *alloc); +static void Buf_Free(CBuf *p, ISzAlloc *alloc); + +EXTERN_C_END + +#endif + + +/* Bcj2.h -- BCJ2 Converter for x86 code +2014-11-10 : Igor Pavlov : Public domain */ + +#ifndef __BCJ2_H +#define __BCJ2_H + +/*#include "7zTypes.h" */ + +EXTERN_C_BEGIN + +#define BCJ2_NUM_STREAMS 4 + +enum +{ + BCJ2_STREAM_MAIN, + BCJ2_STREAM_CALL, + BCJ2_STREAM_JUMP, + BCJ2_STREAM_RC +}; + +enum +{ + BCJ2_DEC_STATE_ORIG_0 = BCJ2_NUM_STREAMS, + BCJ2_DEC_STATE_ORIG_1, + BCJ2_DEC_STATE_ORIG_2, + BCJ2_DEC_STATE_ORIG_3, + + BCJ2_DEC_STATE_ORIG, + BCJ2_DEC_STATE_OK +}; + +enum +{ + BCJ2_ENC_STATE_ORIG = BCJ2_NUM_STREAMS, + BCJ2_ENC_STATE_OK +}; + + +#define BCJ2_IS_32BIT_STREAM(s) ((s) == BCJ2_STREAM_CALL || (s) == BCJ2_STREAM_JUMP) + +/* +CBcj2Dec / CBcj2Enc +bufs sizes: + BUF_SIZE(n) = lims[n] - bufs[n] +bufs sizes for BCJ2_STREAM_CALL and BCJ2_STREAM_JUMP must be mutliply of 4: + (BUF_SIZE(BCJ2_STREAM_CALL) & 3) == 0 + (BUF_SIZE(BCJ2_STREAM_JUMP) & 3) == 0 +*/ + +/* +CBcj2Dec: +dest is allowed to overlap with bufs[BCJ2_STREAM_MAIN], with the following conditions: + bufs[BCJ2_STREAM_MAIN] >= dest && + bufs[BCJ2_STREAM_MAIN] - dest >= tempReserv + + BUF_SIZE(BCJ2_STREAM_CALL) + + BUF_SIZE(BCJ2_STREAM_JUMP) + tempReserv = 0 : for first call of Bcj2Dec_Decode + tempReserv = 4 : for any other calls of Bcj2Dec_Decode + overlap with offset = 1 is not allowed +*/ + +typedef struct +{ + const Byte *bufs[BCJ2_NUM_STREAMS]; + const Byte *lims[BCJ2_NUM_STREAMS]; + Byte *dest; + const Byte *destLim; + + unsigned state; /* BCJ2_STREAM_MAIN has more priority than BCJ2_STATE_ORIG */ + + UInt32 ip; + Byte temp[4]; + UInt32 range; + UInt32 code; + UInt16 probs[2 + 256]; +} CBcj2Dec; + +static void Bcj2Dec_Init(CBcj2Dec *p); + +/* Returns: SZ_OK or SZ_ERROR_DATA */ +static SRes Bcj2Dec_Decode(CBcj2Dec *p); + +#define Bcj2Dec_IsFinished(_p_) ((_p_)->code == 0) + +#define BCJ2_RELAT_LIMIT_NUM_BITS 26 +#define BCJ2_RELAT_LIMIT ((UInt32)1 << BCJ2_RELAT_LIMIT_NUM_BITS) + +/* limit for CBcj2Enc::fileSize variable */ +#define BCJ2_FileSize_MAX ((UInt32)1 << 31) + +EXTERN_C_END + +#endif + +/* Bra.h -- Branch converters for executables +2013-01-18 : Igor Pavlov : Public domain */ + +#ifndef __BRA_H +#define __BRA_H + +/*#include "7zTypes.h"*/ + +EXTERN_C_BEGIN + +/* +These functions convert relative addresses to absolute addresses +in CALL instructions to increase the compression ratio. + + In: + data - data buffer + size - size of data + ip - current virtual Instruction Pinter (IP) value + state - state variable for x86 converter + encoding - 0 (for decoding), 1 (for encoding) + + Out: + state - state variable for x86 converter + + Returns: + The number of processed bytes. If you call these functions with multiple calls, + you must start next call with first byte after block of processed bytes. + + Type Endian Alignment LookAhead + + x86 little 1 4 + ARMT little 2 2 + ARM little 4 0 + PPC big 4 0 + SPARC big 4 0 + IA64 little 16 0 + + size must be >= Alignment + LookAhead, if it's not last block. + If (size < Alignment + LookAhead), converter returns 0. + + Example: + + UInt32 ip = 0; + for () + { + ; size must be >= Alignment + LookAhead, if it's not last block + SizeT processed = Convert(data, size, ip, 1); + data += processed; + size -= processed; + ip += processed; + } +*/ + +#define x86_Convert_Init(state) { state = 0; } +static SizeT x86_Convert(Byte *data, SizeT size, UInt32 ip, UInt32 *state, int encoding); +static SizeT ARM_Convert(Byte *data, SizeT size, UInt32 ip, int encoding); +static SizeT ARMT_Convert(Byte *data, SizeT size, UInt32 ip, int encoding); +static SizeT PPC_Convert(Byte *data, SizeT size, UInt32 ip, int encoding); +static SizeT SPARC_Convert(Byte *data, SizeT size, UInt32 ip, int encoding); +static SizeT IA64_Convert(Byte *data, SizeT size, UInt32 ip, int encoding); + +EXTERN_C_END + +#endif + +/* Delta.h -- Delta converter +2013-01-18 : Igor Pavlov : Public domain */ + +#ifndef __DELTA_H +#define __DELTA_H + +/*#include "7zTypes.h" */ + +EXTERN_C_BEGIN + +#define DELTA_STATE_SIZE 256 + +static void Delta_Init(Byte *state); +static void Delta_Decode(Byte *state, unsigned delta, Byte *data, SizeT size); + +EXTERN_C_END + +#endif + +/* LzmaDec.h -- LZMA Decoder +2013-01-18 : Igor Pavlov : Public domain */ + +#ifndef __LZMA_DEC_H +#define __LZMA_DEC_H + +/*#include "7zTypes.h"*/ + +EXTERN_C_BEGIN + +/* #define _LZMA_PROB32 */ +/* _LZMA_PROB32 can increase the speed on some CPUs, + but memory usage for CLzmaDec::probs will be doubled in that case */ + +#ifdef _LZMA_PROB32 +#define CLzmaProb UInt32 +#else +#define CLzmaProb UInt16 +#endif + + +/* ---------- LZMA Properties ---------- */ + +#define LZMA_PROPS_SIZE 5 + +typedef struct _CLzmaProps +{ + unsigned lc, lp, pb; + UInt32 dicSize; +} CLzmaProps; + +/* LzmaProps_Decode - decodes properties +Returns: + SZ_OK + SZ_ERROR_UNSUPPORTED - Unsupported properties +*/ + +static SRes LzmaProps_Decode(CLzmaProps *p, const Byte *data, unsigned size); + + +/* ---------- LZMA Decoder state ---------- */ + +/* LZMA_REQUIRED_INPUT_MAX = number of required input bytes for worst case. + Num bits = log2((2^11 / 31) ^ 22) + 26 < 134 + 26 = 160; */ + +#define LZMA_REQUIRED_INPUT_MAX 20 + +typedef struct +{ + CLzmaProps prop; + CLzmaProb *probs; + Byte *dic; + const Byte *buf; + UInt32 range, code; + SizeT dicPos; + SizeT dicBufSize; + UInt32 processedPos; + UInt32 checkDicSize; + unsigned state; + UInt32 reps[4]; + unsigned remainLen; + int needFlush; + int needInitState; + UInt32 numProbs; + unsigned tempBufSize; + Byte tempBuf[LZMA_REQUIRED_INPUT_MAX]; +} CLzmaDec; + +#define LzmaDec_Construct(p) { (p)->dic = 0; (p)->probs = 0; } + +static void LzmaDec_Init(CLzmaDec *p); + +/* There are two types of LZMA streams: + 0) Stream with end mark. That end mark adds about 6 bytes to compressed size. + 1) Stream without end mark. You must know exact uncompressed size to decompress such stream. */ + +typedef enum +{ + LZMA_FINISH_ANY, /* finish at any point */ + LZMA_FINISH_END /* block must be finished at the end */ +} ELzmaFinishMode; + +/* ELzmaFinishMode has meaning only if the decoding reaches output limit !!! + + You must use LZMA_FINISH_END, when you know that current output buffer + covers last bytes of block. In other cases you must use LZMA_FINISH_ANY. + + If LZMA decoder sees end marker before reaching output limit, it returns SZ_OK, + and output value of destLen will be less than output buffer size limit. + You can check status result also. + + You can use multiple checks to test data integrity after full decompression: + 1) Check Result and "status" variable. + 2) Check that output(destLen) = uncompressedSize, if you know real uncompressedSize. + 3) Check that output(srcLen) = compressedSize, if you know real compressedSize. + You must use correct finish mode in that case. */ + +typedef enum +{ + LZMA_STATUS_NOT_SPECIFIED, /* use main error code instead */ + LZMA_STATUS_FINISHED_WITH_MARK, /* stream was finished with end mark. */ + LZMA_STATUS_NOT_FINISHED, /* stream was not finished */ + LZMA_STATUS_NEEDS_MORE_INPUT, /* you must provide more input bytes */ + LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK /* there is probability that stream was finished without end mark */ +} ELzmaStatus; + +/* ELzmaStatus is used only as output value for function call */ + + +/* ---------- Interfaces ---------- */ + +/* There are 3 levels of interfaces: + 1) Dictionary Interface + 2) Buffer Interface + 3) One Call Interface + You can select any of these interfaces, but don't mix functions from different + groups for same object. */ + + +/* There are two variants to allocate state for Dictionary Interface: + 1) LzmaDec_Allocate / LzmaDec_Free + 2) LzmaDec_AllocateProbs / LzmaDec_FreeProbs + You can use variant 2, if you set dictionary buffer manually. + For Buffer Interface you must always use variant 1. + +LzmaDec_Allocate* can return: + SZ_OK + SZ_ERROR_MEM - Memory allocation error + SZ_ERROR_UNSUPPORTED - Unsupported properties +*/ + +static SRes LzmaDec_AllocateProbs(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAlloc *alloc); +static void LzmaDec_FreeProbs(CLzmaDec *p, ISzAlloc *alloc); + +/* ---------- Dictionary Interface ---------- */ + +/* You can use it, if you want to eliminate the overhead for data copying from + dictionary to some other external buffer. + You must work with CLzmaDec variables directly in this interface. + + STEPS: + LzmaDec_Constr() + LzmaDec_Allocate() + for (each new stream) + { + LzmaDec_Init() + while (it needs more decompression) + { + LzmaDec_DecodeToDic() + use data from CLzmaDec::dic and update CLzmaDec::dicPos + } + } + LzmaDec_Free() +*/ + +/* LzmaDec_DecodeToDic + + The decoding to internal dictionary buffer (CLzmaDec::dic). + You must manually update CLzmaDec::dicPos, if it reaches CLzmaDec::dicBufSize !!! + +finishMode: + It has meaning only if the decoding reaches output limit (dicLimit). + LZMA_FINISH_ANY - Decode just dicLimit bytes. + LZMA_FINISH_END - Stream must be finished after dicLimit. + +Returns: + SZ_OK + status: + LZMA_STATUS_FINISHED_WITH_MARK + LZMA_STATUS_NOT_FINISHED + LZMA_STATUS_NEEDS_MORE_INPUT + LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK + SZ_ERROR_DATA - Data error +*/ + +static SRes LzmaDec_DecodeToDic(CLzmaDec *p, SizeT dicLimit, + const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status); + +EXTERN_C_END + +#endif + +/* Lzma2Dec.h -- LZMA2 Decoder +2015-05-13 : Igor Pavlov : Public domain */ + +#ifndef __LZMA2_DEC_H +#define __LZMA2_DEC_H + +/*#include "LzmaDec.h"*/ + +EXTERN_C_BEGIN + +/* ---------- State Interface ---------- */ + +typedef struct +{ + CLzmaDec decoder; + UInt32 packSize; + UInt32 unpackSize; + unsigned state; + Byte control; + Bool needInitDic; + Bool needInitState; + Bool needInitProp; +} CLzma2Dec; + +#define Lzma2Dec_Construct(p) LzmaDec_Construct(&(p)->decoder) +#define Lzma2Dec_FreeProbs(p, alloc) LzmaDec_FreeProbs(&(p)->decoder, alloc); +#define Lzma2Dec_Free(p, alloc) LzmaDec_Free(&(p)->decoder, alloc); + +static SRes Lzma2Dec_AllocateProbs(CLzma2Dec *p, Byte prop, ISzAlloc *alloc); +static void Lzma2Dec_Init(CLzma2Dec *p); + + +/* +finishMode: + It has meaning only if the decoding reaches output limit (*destLen or dicLimit). + LZMA_FINISH_ANY - use smallest number of input bytes + LZMA_FINISH_END - read EndOfStream marker after decoding + +Returns: + SZ_OK + status: + LZMA_STATUS_FINISHED_WITH_MARK + LZMA_STATUS_NOT_FINISHED + LZMA_STATUS_NEEDS_MORE_INPUT + SZ_ERROR_DATA - Data error +*/ + +static SRes Lzma2Dec_DecodeToDic(CLzma2Dec *p, SizeT dicLimit, + const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status); + + +EXTERN_C_END + +#endif + + +/* END HEADERS */ + + +/* 7zCrc.c -- CRC32 init +2015-03-10 : Igor Pavlov : Public domain */ + +/* +#include "Precomp.h" + +#include "7zCrc.h" +#include "CpuArch.h" +*/ +#define UNUSED_VAR(x) (void)x; + +#define kCrcPoly 0xEDB88320 + +#ifdef MY_CPU_LE + #define CRC_NUM_TABLES 8 +#else + #define CRC_NUM_TABLES 9 + + #define CRC_UINT32_SWAP(v) ((v >> 24) | ((v >> 8) & 0xFF00) | ((v << 8) & 0xFF0000) | (v << 24)) + + static UInt32 MY_FAST_CALL CrcUpdateT1_BeT4(UInt32 v, const void *data, size_t size, const UInt32 *table); + static UInt32 MY_FAST_CALL CrcUpdateT1_BeT8(UInt32 v, const void *data, size_t size, const UInt32 *table); +#endif + +#ifndef MY_CPU_BE + static UInt32 MY_FAST_CALL CrcUpdateT4(UInt32 v, const void *data, size_t size, const UInt32 *table); + static UInt32 MY_FAST_CALL CrcUpdateT8(UInt32 v, const void *data, size_t size, const UInt32 *table); +#endif + +typedef UInt32 (MY_FAST_CALL *CRC_FUNC)(UInt32 v, const void *data, size_t size, const UInt32 *table); + +static CRC_FUNC g_CrcUpdateT4; +static CRC_FUNC g_CrcUpdateT8; +static CRC_FUNC g_CrcUpdate; + +static UInt32 g_CrcTable[256 * CRC_NUM_TABLES]; + +static UInt32 MY_FAST_CALL CrcCalc(const void *data, size_t size) +{ + return g_CrcUpdate(CRC_INIT_VAL, data, size, g_CrcTable) ^ CRC_INIT_VAL; +} + +#define CRC_UPDATE_BYTE_2(crc, b) (table[((crc) ^ (b)) & 0xFF] ^ ((crc) >> 8)) + +#if CRC_NUM_TABLES < 4 +static UInt32 MY_FAST_CALL CrcUpdateT1(UInt32 v, const void *data, size_t size, const UInt32 *table) +{ + const Byte *p = (const Byte *)data; + const Byte *pEnd = p + size; + for (; p != pEnd; p++) + v = CRC_UPDATE_BYTE_2(v, *p); + return v; +} +#endif + +static void MY_FAST_CALL CrcGenerateTable() +{ + UInt32 i; + for (i = 0; i < 256; i++) + { + UInt32 r = i; + unsigned j; + for (j = 0; j < 8; j++) + r = (r >> 1) ^ (kCrcPoly & ~((r & 1) - 1)); + g_CrcTable[i] = r; + } + for (; i < 256 * CRC_NUM_TABLES; i++) + { + UInt32 r = g_CrcTable[i - 256]; + g_CrcTable[i] = g_CrcTable[r & 0xFF] ^ (r >> 8); + } + + #if CRC_NUM_TABLES < 4 + + g_CrcUpdate = CrcUpdateT1; + + #else + + #ifdef MY_CPU_LE + + g_CrcUpdateT4 = CrcUpdateT4; + g_CrcUpdate = CrcUpdateT4; + + #if CRC_NUM_TABLES >= 8 + g_CrcUpdateT8 = CrcUpdateT8; + + #ifdef MY_CPU_X86_OR_AMD64 + if (!CPU_Is_InOrder()) + g_CrcUpdate = CrcUpdateT8; + #endif + #endif + + #else + { + #ifndef MY_CPU_BE + UInt32 k = 0x01020304; + const Byte *p = (const Byte *)&k; + if (p[0] == 4 && p[1] == 3) + { + g_CrcUpdateT4 = CrcUpdateT4; + g_CrcUpdate = CrcUpdateT4; + #if CRC_NUM_TABLES >= 8 + g_CrcUpdateT8 = CrcUpdateT8; + /* g_CrcUpdate = CrcUpdateT8; */ + #endif + } + else if (p[0] != 1 || p[1] != 2) + g_CrcUpdate = CrcUpdateT1; + else + #endif + { + for (i = 256 * CRC_NUM_TABLES - 1; i >= 256; i--) + { + UInt32 x = g_CrcTable[i - 256]; + g_CrcTable[i] = CRC_UINT32_SWAP(x); + } + g_CrcUpdateT4 = CrcUpdateT1_BeT4; + g_CrcUpdate = CrcUpdateT1_BeT4; + #if CRC_NUM_TABLES >= 8 + g_CrcUpdateT8 = CrcUpdateT1_BeT8; + /* g_CrcUpdate = CrcUpdateT1_BeT8; */ + #endif + } + } + #endif + + #endif +} + +/* 7zCrcOpt.c -- CRC32 calculation +2015-03-01 : Igor Pavlov : Public domain */ + +/* +#include "Precomp.h" + +#include "CpuArch.h" +*/ + +#ifndef MY_CPU_BE + +#define CRC_UPDATE_BYTE_2(crc, b) (table[((crc) ^ (b)) & 0xFF] ^ ((crc) >> 8)) + +static UInt32 MY_FAST_CALL CrcUpdateT4(UInt32 v, const void *data, size_t size, const UInt32 *table) +{ + const Byte *p = (const Byte *)data; + for (; size > 0 && ((unsigned)(ptrdiff_t)p & 3) != 0; size--, p++) + v = CRC_UPDATE_BYTE_2(v, *p); + for (; size >= 4; size -= 4, p += 4) + { + v ^= *(const UInt32 *)p; + v = + table[0x300 + ((v ) & 0xFF)] + ^ table[0x200 + ((v >> 8) & 0xFF)] + ^ table[0x100 + ((v >> 16) & 0xFF)] + ^ table[0x000 + ((v >> 24))]; + } + for (; size > 0; size--, p++) + v = CRC_UPDATE_BYTE_2(v, *p); + return v; +} + +static UInt32 MY_FAST_CALL CrcUpdateT8(UInt32 v, const void *data, size_t size, const UInt32 *table) +{ + const Byte *p = (const Byte *)data; + for (; size > 0 && ((unsigned)(ptrdiff_t)p & 7) != 0; size--, p++) + v = CRC_UPDATE_BYTE_2(v, *p); + for (; size >= 8; size -= 8, p += 8) + { + UInt32 d; + v ^= *(const UInt32 *)p; + v = + table[0x700 + ((v ) & 0xFF)] + ^ table[0x600 + ((v >> 8) & 0xFF)] + ^ table[0x500 + ((v >> 16) & 0xFF)] + ^ table[0x400 + ((v >> 24))]; + d = *((const UInt32 *)p + 1); + v ^= + table[0x300 + ((d ) & 0xFF)] + ^ table[0x200 + ((d >> 8) & 0xFF)] + ^ table[0x100 + ((d >> 16) & 0xFF)] + ^ table[0x000 + ((d >> 24))]; + } + for (; size > 0; size--, p++) + v = CRC_UPDATE_BYTE_2(v, *p); + return v; +} + +#endif + + +#ifndef MY_CPU_LE + +#define CRC_UINT32_SWAP(v) ((v >> 24) | ((v >> 8) & 0xFF00) | ((v << 8) & 0xFF0000) | (v << 24)) + +#define CRC_UPDATE_BYTE_2_BE(crc, b) (table[(((crc) >> 24) ^ (b))] ^ ((crc) << 8)) + +static UInt32 MY_FAST_CALL CrcUpdateT1_BeT4(UInt32 v, const void *data, size_t size, const UInt32 *table) +{ + const Byte *p = (const Byte *)data; + table += 0x100; + v = CRC_UINT32_SWAP(v); + for (; size > 0 && ((unsigned)(ptrdiff_t)p & 3) != 0; size--, p++) + v = CRC_UPDATE_BYTE_2_BE(v, *p); + for (; size >= 4; size -= 4, p += 4) + { + v ^= *(const UInt32 *)p; + v = + table[0x000 + ((v ) & 0xFF)] + ^ table[0x100 + ((v >> 8) & 0xFF)] + ^ table[0x200 + ((v >> 16) & 0xFF)] + ^ table[0x300 + ((v >> 24))]; + } + for (; size > 0; size--, p++) + v = CRC_UPDATE_BYTE_2_BE(v, *p); + return CRC_UINT32_SWAP(v); +} + +static UInt32 MY_FAST_CALL CrcUpdateT1_BeT8(UInt32 v, const void *data, size_t size, const UInt32 *table) +{ + const Byte *p = (const Byte *)data; + table += 0x100; + v = CRC_UINT32_SWAP(v); + for (; size > 0 && ((unsigned)(ptrdiff_t)p & 7) != 0; size--, p++) + v = CRC_UPDATE_BYTE_2_BE(v, *p); + for (; size >= 8; size -= 8, p += 8) + { + UInt32 d; + v ^= *(const UInt32 *)p; + v = + table[0x400 + ((v ) & 0xFF)] + ^ table[0x500 + ((v >> 8) & 0xFF)] + ^ table[0x600 + ((v >> 16) & 0xFF)] + ^ table[0x700 + ((v >> 24))]; + d = *((const UInt32 *)p + 1); + v ^= + table[0x000 + ((d ) & 0xFF)] + ^ table[0x100 + ((d >> 8) & 0xFF)] + ^ table[0x200 + ((d >> 16) & 0xFF)] + ^ table[0x300 + ((d >> 24))]; + } + for (; size > 0; size--, p++) + v = CRC_UPDATE_BYTE_2_BE(v, *p); + return CRC_UINT32_SWAP(v); +} + +#endif + +/* CpuArch.c -- CPU specific code +2016-02-25: Igor Pavlov : Public domain */ + +/* +#include "Precomp.h" + +#include "CpuArch.h" +*/ + +#ifdef MY_CPU_X86_OR_AMD64 + +#if (defined(_MSC_VER) && !defined(MY_CPU_AMD64)) || defined(__GNUC__) +#define USE_ASM +#endif + +#if !defined(USE_ASM) && _MSC_VER >= 1500 +#include +#endif + +#if defined(USE_ASM) && !defined(MY_CPU_AMD64) +static UInt32 CheckFlag(UInt32 flag) +{ + #ifdef _MSC_VER + __asm pushfd; + __asm pop EAX; + __asm mov EDX, EAX; + __asm xor EAX, flag; + __asm push EAX; + __asm popfd; + __asm pushfd; + __asm pop EAX; + __asm xor EAX, EDX; + __asm push EDX; + __asm popfd; + __asm and flag, EAX; + #else + __asm__ __volatile__ ( + "pushf\n\t" + "pop %%EAX\n\t" + "movl %%EAX,%%EDX\n\t" + "xorl %0,%%EAX\n\t" + "push %%EAX\n\t" + "popf\n\t" + "pushf\n\t" + "pop %%EAX\n\t" + "xorl %%EDX,%%EAX\n\t" + "push %%EDX\n\t" + "popf\n\t" + "andl %%EAX, %0\n\t": + "=c" (flag) : "c" (flag) : + "%eax", "%edx"); + #endif + return flag; +} +#define CHECK_CPUID_IS_SUPPORTED if (CheckFlag(1 << 18) == 0 || CheckFlag(1 << 21) == 0) return False; +#else +#define CHECK_CPUID_IS_SUPPORTED +#endif + +#if defined(__WATCOMC__) +static void __cpuid(int *cpuinfo, const UInt32 infotype); +#pragma aux __cpuid = \ + ".586" \ + "cpuid" \ + "mov [esi+0],eax" \ + "mov [esi+4],ebx" \ + "mov [esi+8],ecx" \ + "mov [esi+12],edx" \ + parm [esi] [eax] modify [ebx ecx edx]; +#endif + + +static void MyCPUID(UInt32 function, UInt32 *a, UInt32 *b, UInt32 *c, UInt32 *d) +{ + #ifdef USE_ASM + + #ifdef _MSC_VER + + UInt32 a2, b2, c2, d2; + __asm xor EBX, EBX; + __asm xor ECX, ECX; + __asm xor EDX, EDX; + __asm mov EAX, function; + __asm cpuid; + __asm mov a2, EAX; + __asm mov b2, EBX; + __asm mov c2, ECX; + __asm mov d2, EDX; + + *a = a2; + *b = b2; + *c = c2; + *d = d2; + + #else + + __asm__ __volatile__ ( + #if defined(MY_CPU_AMD64) && defined(__PIC__) + "mov %%rbx, %%rdi;" + "cpuid;" + "xchg %%rbx, %%rdi;" + : "=a" (*a) , + "=D" (*b) , + #elif defined(MY_CPU_X86) && defined(__PIC__) + "mov %%ebx, %%edi;" + "cpuid;" + "xchgl %%ebx, %%edi;" + : "=a" (*a) , + "=D" (*b) , + #else + "cpuid" + : "=a" (*a) , + "=b" (*b) , + #endif + "=c" (*c) , + "=d" (*d) + : "0" (function)) ; + + #endif + + #else + + int CPUInfo[4]; + __cpuid(CPUInfo, function); + *a = CPUInfo[0]; + *b = CPUInfo[1]; + *c = CPUInfo[2]; + *d = CPUInfo[3]; + + #endif +} + +static Bool x86cpuid_CheckAndRead(Cx86cpuid *p) +{ + CHECK_CPUID_IS_SUPPORTED + MyCPUID(0, &p->maxFunc, &p->vendor[0], &p->vendor[2], &p->vendor[1]); + MyCPUID(1, &p->ver, &p->b, &p->c, &p->d); + return True; +} + +static const UInt32 kVendors[][3] = +{ + { 0x756E6547, 0x49656E69, 0x6C65746E}, + { 0x68747541, 0x69746E65, 0x444D4163}, + { 0x746E6543, 0x48727561, 0x736C7561} +}; + +static int x86cpuid_GetFirm(const Cx86cpuid *p) +{ + unsigned i; + for (i = 0; i < sizeof(kVendors) / sizeof(kVendors[i]); i++) + { + const UInt32 *v = kVendors[i]; + if (v[0] == p->vendor[0] && + v[1] == p->vendor[1] && + v[2] == p->vendor[2]) + return (int)i; + } + return -1; +} + +static Bool CPU_Is_InOrder() +{ + Cx86cpuid p; + int firm; + UInt32 family, model; + if (!x86cpuid_CheckAndRead(&p)) + return True; + + family = x86cpuid_GetFamily(p.ver); + model = x86cpuid_GetModel(p.ver); + + firm = x86cpuid_GetFirm(&p); + + switch (firm) + { + case CPU_FIRM_INTEL: return (family < 6 || (family == 6 && ( + /* In-Order Atom CPU */ + model == 0x1C /* 45 nm, N4xx, D4xx, N5xx, D5xx, 230, 330 */ + || model == 0x26 /* 45 nm, Z6xx */ + || model == 0x27 /* 32 nm, Z2460 */ + || model == 0x35 /* 32 nm, Z2760 */ + || model == 0x36 /* 32 nm, N2xxx, D2xxx */ + ))); + case CPU_FIRM_AMD: return (family < 5 || (family == 5 && (model < 6 || model == 0xA))); + case CPU_FIRM_VIA: return (family < 6 || (family == 6 && model < 0xF)); + } + return True; +} + +#endif + +/* 7zStream.c -- 7z Stream functions +2013-11-12 : Igor Pavlov : Public domain */ + +/*#include "Precomp.h"*/ + +#include + +/*#include "7zTypes.h"*/ + +static SRes LookInStream_SeekTo(ILookInStream *stream, UInt64 offset) +{ + Int64 t = offset; + return stream->Seek(stream, &t, SZ_SEEK_SET); +} + +static SRes LookInStream_Read2(ILookInStream *stream, void *buf, size_t size, SRes errorType) +{ + while (size != 0) + { + size_t processed = size; + RINOK(stream->Read(stream, buf, &processed)); + if (processed == 0) + return errorType; + buf = (void *)((Byte *)buf + processed); + size -= processed; + } + return SZ_OK; +} + +static SRes LookInStream_Read(ILookInStream *stream, void *buf, size_t size) +{ + return LookInStream_Read2(stream, buf, size, SZ_ERROR_INPUT_EOF); +} + +static SRes LookToRead_Look_Lookahead(void *pp, const void **buf, size_t *size) +{ + SRes res = SZ_OK; + CLookToRead *p = (CLookToRead *)pp; + size_t size2 = p->size - p->pos; + if (size2 == 0 && *size > 0) + { + p->pos = 0; + size2 = LookToRead_BUF_SIZE; + res = p->realStream->Read(p->realStream, p->buf, &size2); + p->size = size2; + } + if (size2 < *size) + *size = size2; + *buf = p->buf + p->pos; + return res; +} + +static SRes LookToRead_Look_Exact(void *pp, const void **buf, size_t *size) +{ + SRes res = SZ_OK; + CLookToRead *p = (CLookToRead *)pp; + size_t size2 = p->size - p->pos; + if (size2 == 0 && *size > 0) + { + p->pos = 0; + if (*size > LookToRead_BUF_SIZE) + *size = LookToRead_BUF_SIZE; + res = p->realStream->Read(p->realStream, p->buf, size); + size2 = p->size = *size; + } + if (size2 < *size) + *size = size2; + *buf = p->buf + p->pos; + return res; +} + +static SRes LookToRead_Skip(void *pp, size_t offset) +{ + CLookToRead *p = (CLookToRead *)pp; + p->pos += offset; + return SZ_OK; +} + +static SRes LookToRead_Read(void *pp, void *buf, size_t *size) +{ + CLookToRead *p = (CLookToRead *)pp; + size_t rem = p->size - p->pos; + if (rem == 0) + return p->realStream->Read(p->realStream, buf, size); + if (rem > *size) + rem = *size; + memcpy(buf, p->buf + p->pos, rem); + p->pos += rem; + *size = rem; + return SZ_OK; +} + +static SRes LookToRead_Seek(void *pp, Int64 *pos, ESzSeek origin) +{ + CLookToRead *p = (CLookToRead *)pp; + p->pos = p->size = 0; + return p->realStream->Seek(p->realStream, pos, origin); +} + +static void LookToRead_CreateVTable(CLookToRead *p, int lookahead) +{ + p->s.Look = lookahead ? + LookToRead_Look_Lookahead : + LookToRead_Look_Exact; + p->s.Skip = LookToRead_Skip; + p->s.Read = LookToRead_Read; + p->s.Seek = LookToRead_Seek; +} + +static void LookToRead_Init(CLookToRead *p) +{ + p->pos = p->size = 0; +} + + +/* 7zArcIn.c -- 7z Input functions +2016-05-16 : Igor Pavlov : Public domain */ + +/* +#include "Precomp.h" + +#include + +#include "7z.h" +#include "7zBuf.h" +#include "7zCrc.h" +#include "CpuArch.h" +*/ + +#define MY_ALLOC(T, p, size, alloc) { \ + if ((p = (T *)IAlloc_Alloc(alloc, (size) * sizeof(T))) == NULL) return SZ_ERROR_MEM; } + +#define MY_ALLOC_ZE(T, p, size, alloc) { if ((size) == 0) p = NULL; else MY_ALLOC(T, p, size, alloc) } + +#define MY_ALLOC_AND_CPY(to, size, from, alloc) \ + { MY_ALLOC(Byte, to, size, alloc); memcpy(to, from, size); } + +#define MY_ALLOC_ZE_AND_CPY(to, size, from, alloc) \ + { if ((size) == 0) p = NULL; else { MY_ALLOC_AND_CPY(to, size, from, alloc) } } + +#define k7zMajorVersion 0 + +enum EIdEnum +{ + k7zIdEnd, + k7zIdHeader, + k7zIdArchiveProperties, + k7zIdAdditionalStreamsInfo, + k7zIdMainStreamsInfo, + k7zIdFilesInfo, + k7zIdPackInfo, + k7zIdUnpackInfo, + k7zIdSubStreamsInfo, + k7zIdSize, + k7zIdCRC, + k7zIdFolder, + k7zIdCodersUnpackSize, + k7zIdNumUnpackStream, + k7zIdEmptyStream, + k7zIdEmptyFile, + k7zIdAnti, + k7zIdName, + k7zIdCTime, + k7zIdATime, + k7zIdMTime, + k7zIdWinAttrib, + k7zIdComment, + k7zIdEncodedHeader, + k7zIdStartPos, + k7zIdDummy + /* k7zNtSecure, */ + /* k7zParent, */ + /* k7zIsReal */ +}; + +static const Byte k7zSignature[k7zSignatureSize] = {'7', 'z', 0xBC, 0xAF, 0x27, 0x1C}; + +#define SzBitUi32s_Init(p) { (p)->Defs = NULL; (p)->Vals = NULL; } + +static SRes SzBitUi32s_Alloc(CSzBitUi32s *p, size_t num, ISzAlloc *alloc) +{ + if (num == 0) + { + p->Defs = NULL; + p->Vals = NULL; + } + else + { + MY_ALLOC(Byte, p->Defs, (num + 7) >> 3, alloc); + MY_ALLOC(UInt32, p->Vals, num, alloc); + } + return SZ_OK; +} + +static void SzBitUi32s_Free(CSzBitUi32s *p, ISzAlloc *alloc) +{ + IAlloc_Free(alloc, p->Defs); p->Defs = NULL; + IAlloc_Free(alloc, p->Vals); p->Vals = NULL; +} + +#define SzBitUi64s_Init(p) { (p)->Defs = NULL; (p)->Vals = NULL; } + +static void SzBitUi64s_Free(CSzBitUi64s *p, ISzAlloc *alloc) +{ + IAlloc_Free(alloc, p->Defs); p->Defs = NULL; + IAlloc_Free(alloc, p->Vals); p->Vals = NULL; +} + + +static void SzAr_Init(CSzAr *p) +{ + p->NumPackStreams = 0; + p->NumFolders = 0; + + p->PackPositions = NULL; + SzBitUi32s_Init(&p->FolderCRCs); + + p->FoCodersOffsets = NULL; + p->FoStartPackStreamIndex = NULL; + p->FoToCoderUnpackSizes = NULL; + p->FoToMainUnpackSizeIndex = NULL; + p->CoderUnpackSizes = NULL; + + p->CodersData = NULL; +} + +static void SzAr_Free(CSzAr *p, ISzAlloc *alloc) +{ + IAlloc_Free(alloc, p->PackPositions); + SzBitUi32s_Free(&p->FolderCRCs, alloc); + + IAlloc_Free(alloc, p->FoCodersOffsets); + IAlloc_Free(alloc, p->FoStartPackStreamIndex); + IAlloc_Free(alloc, p->FoToCoderUnpackSizes); + IAlloc_Free(alloc, p->FoToMainUnpackSizeIndex); + IAlloc_Free(alloc, p->CoderUnpackSizes); + + IAlloc_Free(alloc, p->CodersData); + + SzAr_Init(p); +} + + +static void SzArEx_Init(CSzArEx *p) +{ + SzAr_Init(&p->db); + + p->NumFiles = 0; + p->dataPos = 0; + + p->UnpackPositions = NULL; + p->IsDirs = NULL; + + p->FolderToFile = NULL; + p->FileToFolder = NULL; + + p->FileNameOffsets = NULL; + p->FileNames = NULL; + + SzBitUi32s_Init(&p->CRCs); + SzBitUi32s_Init(&p->Attribs); + /* SzBitUi32s_Init(&p->Parents); */ + SzBitUi64s_Init(&p->MTime); + SzBitUi64s_Init(&p->CTime); +} + +static void SzArEx_Free(CSzArEx *p, ISzAlloc *alloc) +{ + IAlloc_Free(alloc, p->UnpackPositions); + IAlloc_Free(alloc, p->IsDirs); + + IAlloc_Free(alloc, p->FolderToFile); + IAlloc_Free(alloc, p->FileToFolder); + + IAlloc_Free(alloc, p->FileNameOffsets); + IAlloc_Free(alloc, p->FileNames); + + SzBitUi32s_Free(&p->CRCs, alloc); + SzBitUi32s_Free(&p->Attribs, alloc); + /* SzBitUi32s_Free(&p->Parents, alloc); */ + SzBitUi64s_Free(&p->MTime, alloc); + SzBitUi64s_Free(&p->CTime, alloc); + + SzAr_Free(&p->db, alloc); + SzArEx_Init(p); +} + + +static int TestSignatureCandidate(const Byte *testBytes) +{ + unsigned i; + for (i = 0; i < k7zSignatureSize; i++) + if (testBytes[i] != k7zSignature[i]) + return 0; + return 1; +} + +#define SzData_Clear(p) { (p)->Data = NULL; (p)->Size = 0; } + +#define SZ_READ_BYTE_SD(_sd_, dest) if ((_sd_)->Size == 0) return SZ_ERROR_ARCHIVE; (_sd_)->Size--; dest = *(_sd_)->Data++; +#define SZ_READ_BYTE(dest) SZ_READ_BYTE_SD(sd, dest) +#define SZ_READ_BYTE_2(dest) if (sd.Size == 0) return SZ_ERROR_ARCHIVE; sd.Size--; dest = *sd.Data++; + +#define SKIP_DATA(sd, size) { sd->Size -= (size_t)(size); sd->Data += (size_t)(size); } +#define SKIP_DATA2(sd, size) { sd.Size -= (size_t)(size); sd.Data += (size_t)(size); } + +#define SZ_READ_32(dest) if (sd.Size < 4) return SZ_ERROR_ARCHIVE; \ + dest = GetUi32(sd.Data); SKIP_DATA2(sd, 4); + +static MY_NO_INLINE SRes ReadNumber(CSzData *sd, UInt64 *value) +{ + Byte firstByte, mask; + unsigned i; + UInt32 v; + + SZ_READ_BYTE(firstByte); + if ((firstByte & 0x80) == 0) + { + *value = firstByte; + return SZ_OK; + } + SZ_READ_BYTE(v); + if ((firstByte & 0x40) == 0) + { + *value = (((UInt32)firstByte & 0x3F) << 8) | v; + return SZ_OK; + } + SZ_READ_BYTE(mask); + *value = v | ((UInt32)mask << 8); + mask = 0x20; + for (i = 2; i < 8; i++) + { + Byte b; + if ((firstByte & mask) == 0) + { + UInt64 highPart = (unsigned)firstByte & (unsigned)(mask - 1); + *value |= (highPart << (8 * i)); + return SZ_OK; + } + SZ_READ_BYTE(b); + *value |= ((UInt64)b << (8 * i)); + mask >>= 1; + } + return SZ_OK; +} + + +static MY_NO_INLINE SRes SzReadNumber32(CSzData *sd, UInt32 *value) +{ + Byte firstByte; + UInt64 value64; + if (sd->Size == 0) + return SZ_ERROR_ARCHIVE; + firstByte = *sd->Data; + if ((firstByte & 0x80) == 0) + { + *value = firstByte; + sd->Data++; + sd->Size--; + return SZ_OK; + } + RINOK(ReadNumber(sd, &value64)); + if (value64 >= (UInt32)0x80000000 - 1) + return SZ_ERROR_UNSUPPORTED; + if (value64 >= ((UInt64)(1) << ((sizeof(size_t) - 1) * 8 + 4))) + return SZ_ERROR_UNSUPPORTED; + *value = (UInt32)value64; + return SZ_OK; +} + +#define ReadID(sd, value) ReadNumber(sd, value) + +static SRes SkipData(CSzData *sd) +{ + UInt64 size; + RINOK(ReadNumber(sd, &size)); + if (size > sd->Size) + return SZ_ERROR_ARCHIVE; + SKIP_DATA(sd, size); + return SZ_OK; +} + +static SRes WaitId(CSzData *sd, UInt32 id) +{ + for (;;) + { + UInt64 type; + RINOK(ReadID(sd, &type)); + if (type == id) + return SZ_OK; + if (type == k7zIdEnd) + return SZ_ERROR_ARCHIVE; + RINOK(SkipData(sd)); + } +} + +static SRes RememberBitVector(CSzData *sd, UInt32 numItems, const Byte **v) +{ + UInt32 numBytes = (numItems + 7) >> 3; + if (numBytes > sd->Size) + return SZ_ERROR_ARCHIVE; + *v = sd->Data; + SKIP_DATA(sd, numBytes); + return SZ_OK; +} + +static UInt32 CountDefinedBits(const Byte *bits, UInt32 numItems) +{ + Byte b = 0; + unsigned m = 0; + UInt32 sum = 0; + for (; numItems != 0; numItems--) + { + if (m == 0) + { + b = *bits++; + m = 8; + } + m--; + sum += ((b >> m) & 1); + } + return sum; +} + +static MY_NO_INLINE SRes ReadBitVector(CSzData *sd, UInt32 numItems, Byte **v, ISzAlloc *alloc) +{ + Byte allAreDefined; + Byte *v2; + UInt32 numBytes = (numItems + 7) >> 3; + *v = NULL; + SZ_READ_BYTE(allAreDefined); + if (numBytes == 0) + return SZ_OK; + if (allAreDefined == 0) + { + if (numBytes > sd->Size) + return SZ_ERROR_ARCHIVE; + MY_ALLOC_AND_CPY(*v, numBytes, sd->Data, alloc); + SKIP_DATA(sd, numBytes); + return SZ_OK; + } + MY_ALLOC(Byte, *v, numBytes, alloc); + v2 = *v; + memset(v2, 0xFF, (size_t)numBytes); + { + unsigned numBits = (unsigned)numItems & 7; + if (numBits != 0) + v2[numBytes - 1] = (Byte)((((UInt32)1 << numBits) - 1) << (8 - numBits)); + } + return SZ_OK; +} + +static MY_NO_INLINE SRes ReadUi32s(CSzData *sd2, UInt32 numItems, CSzBitUi32s *crcs, ISzAlloc *alloc) +{ + UInt32 i; + CSzData sd; + UInt32 *vals; + const Byte *defs; + MY_ALLOC_ZE(UInt32, crcs->Vals, numItems, alloc); + sd = *sd2; + defs = crcs->Defs; + vals = crcs->Vals; + for (i = 0; i < numItems; i++) + if (SzBitArray_Check(defs, i)) + { + SZ_READ_32(vals[i]); + } + else + vals[i] = 0; + *sd2 = sd; + return SZ_OK; +} + +static SRes ReadBitUi32s(CSzData *sd, UInt32 numItems, CSzBitUi32s *crcs, ISzAlloc *alloc) +{ + SzBitUi32s_Free(crcs, alloc); + RINOK(ReadBitVector(sd, numItems, &crcs->Defs, alloc)); + return ReadUi32s(sd, numItems, crcs, alloc); +} + +static SRes SkipBitUi32s(CSzData *sd, UInt32 numItems) +{ + Byte allAreDefined; + UInt32 numDefined = numItems; + SZ_READ_BYTE(allAreDefined); + if (!allAreDefined) + { + size_t numBytes = (numItems + 7) >> 3; + if (numBytes > sd->Size) + return SZ_ERROR_ARCHIVE; + numDefined = CountDefinedBits(sd->Data, numItems); + SKIP_DATA(sd, numBytes); + } + if (numDefined > (sd->Size >> 2)) + return SZ_ERROR_ARCHIVE; + SKIP_DATA(sd, (size_t)numDefined * 4); + return SZ_OK; +} + +static SRes ReadPackInfo(CSzAr *p, CSzData *sd, ISzAlloc *alloc) +{ + RINOK(SzReadNumber32(sd, &p->NumPackStreams)); + + RINOK(WaitId(sd, k7zIdSize)); + MY_ALLOC(UInt64, p->PackPositions, (size_t)p->NumPackStreams + 1, alloc); + { + UInt64 sum = 0; + UInt32 i; + UInt32 numPackStreams = p->NumPackStreams; + for (i = 0; i < numPackStreams; i++) + { + UInt64 packSize; + p->PackPositions[i] = sum; + RINOK(ReadNumber(sd, &packSize)); + sum += packSize; + if (sum < packSize) + return SZ_ERROR_ARCHIVE; + } + p->PackPositions[i] = sum; + } + + for (;;) + { + UInt64 type; + RINOK(ReadID(sd, &type)); + if (type == k7zIdEnd) + return SZ_OK; + if (type == k7zIdCRC) + { + /* CRC of packed streams is unused now */ + RINOK(SkipBitUi32s(sd, p->NumPackStreams)); + continue; + } + RINOK(SkipData(sd)); + } +} + +/* +static SRes SzReadSwitch(CSzData *sd) +{ + Byte external; + RINOK(SzReadByte(sd, &external)); + return (external == 0) ? SZ_OK: SZ_ERROR_UNSUPPORTED; +} +*/ + +#define k_NumCodersStreams_in_Folder_MAX (SZ_NUM_BONDS_IN_FOLDER_MAX + SZ_NUM_PACK_STREAMS_IN_FOLDER_MAX) + +static SRes SzGetNextFolderItem(CSzFolder *f, CSzData *sd) +{ + UInt32 numCoders, i; + UInt32 numInStreams = 0; + const Byte *dataStart = sd->Data; + + f->NumCoders = 0; + f->NumBonds = 0; + f->NumPackStreams = 0; + f->UnpackStream = 0; + + RINOK(SzReadNumber32(sd, &numCoders)); + if (numCoders == 0 || numCoders > SZ_NUM_CODERS_IN_FOLDER_MAX) + return SZ_ERROR_UNSUPPORTED; + + for (i = 0; i < numCoders; i++) + { + Byte mainByte; + CSzCoderInfo *coder = f->Coders + i; + unsigned idSize, j; + UInt64 id; + + SZ_READ_BYTE(mainByte); + if ((mainByte & 0xC0) != 0) + return SZ_ERROR_UNSUPPORTED; + + idSize = (unsigned)(mainByte & 0xF); + if (idSize > sizeof(id)) + return SZ_ERROR_UNSUPPORTED; + if (idSize > sd->Size) + return SZ_ERROR_ARCHIVE; + id = 0; + for (j = 0; j < idSize; j++) + { + id = ((id << 8) | *sd->Data); + sd->Data++; + sd->Size--; + } + if (id > UINT64_CONST(0xFFFFFFFF)) + return SZ_ERROR_UNSUPPORTED; + coder->MethodID = (UInt32)id; + + coder->NumStreams = 1; + coder->PropsOffset = 0; + coder->PropsSize = 0; + + if ((mainByte & 0x10) != 0) + { + UInt32 numStreams; + + RINOK(SzReadNumber32(sd, &numStreams)); + if (numStreams > k_NumCodersStreams_in_Folder_MAX) + return SZ_ERROR_UNSUPPORTED; + coder->NumStreams = (Byte)numStreams; + + RINOK(SzReadNumber32(sd, &numStreams)); + if (numStreams != 1) + return SZ_ERROR_UNSUPPORTED; + } + + numInStreams += coder->NumStreams; + + if (numInStreams > k_NumCodersStreams_in_Folder_MAX) + return SZ_ERROR_UNSUPPORTED; + + if ((mainByte & 0x20) != 0) + { + UInt32 propsSize = 0; + RINOK(SzReadNumber32(sd, &propsSize)); + if (propsSize > sd->Size) + return SZ_ERROR_ARCHIVE; + if (propsSize >= 0x80) + return SZ_ERROR_UNSUPPORTED; + coder->PropsOffset = sd->Data - dataStart; + coder->PropsSize = (Byte)propsSize; + sd->Data += (size_t)propsSize; + sd->Size -= (size_t)propsSize; + } + } + + /* + if (numInStreams == 1 && numCoders == 1) + { + f->NumPackStreams = 1; + f->PackStreams[0] = 0; + } + else + */ + { + Byte streamUsed[k_NumCodersStreams_in_Folder_MAX]; + UInt32 numBonds, numPackStreams; + + numBonds = numCoders - 1; + if (numInStreams < numBonds) + return SZ_ERROR_ARCHIVE; + if (numBonds > SZ_NUM_BONDS_IN_FOLDER_MAX) + return SZ_ERROR_UNSUPPORTED; + f->NumBonds = numBonds; + + numPackStreams = numInStreams - numBonds; + if (numPackStreams > SZ_NUM_PACK_STREAMS_IN_FOLDER_MAX) + return SZ_ERROR_UNSUPPORTED; + f->NumPackStreams = numPackStreams; + + for (i = 0; i < numInStreams; i++) + streamUsed[i] = False; + + if (numBonds != 0) + { + Byte coderUsed[SZ_NUM_CODERS_IN_FOLDER_MAX]; + + for (i = 0; i < numCoders; i++) + coderUsed[i] = False; + + for (i = 0; i < numBonds; i++) + { + CSzBond *bp = f->Bonds + i; + + RINOK(SzReadNumber32(sd, &bp->InIndex)); + if (bp->InIndex >= numInStreams || streamUsed[bp->InIndex]) + return SZ_ERROR_ARCHIVE; + streamUsed[bp->InIndex] = True; + + RINOK(SzReadNumber32(sd, &bp->OutIndex)); + if (bp->OutIndex >= numCoders || coderUsed[bp->OutIndex]) + return SZ_ERROR_ARCHIVE; + coderUsed[bp->OutIndex] = True; + } + + for (i = 0; i < numCoders; i++) + if (!coderUsed[i]) + { + f->UnpackStream = i; + break; + } + + if (i == numCoders) + return SZ_ERROR_ARCHIVE; + } + + if (numPackStreams == 1) + { + for (i = 0; i < numInStreams; i++) + if (!streamUsed[i]) + break; + if (i == numInStreams) + return SZ_ERROR_ARCHIVE; + f->PackStreams[0] = i; + } + else + for (i = 0; i < numPackStreams; i++) + { + UInt32 index; + RINOK(SzReadNumber32(sd, &index)); + if (index >= numInStreams || streamUsed[index]) + return SZ_ERROR_ARCHIVE; + streamUsed[index] = True; + f->PackStreams[i] = index; + } + } + + f->NumCoders = numCoders; + + return SZ_OK; +} + + +static MY_NO_INLINE SRes SkipNumbers(CSzData *sd2, UInt32 num) +{ + CSzData sd; + sd = *sd2; + for (; num != 0; num--) + { + Byte firstByte, mask; + unsigned i; + SZ_READ_BYTE_2(firstByte); + if ((firstByte & 0x80) == 0) + continue; + if ((firstByte & 0x40) == 0) + { + if (sd.Size == 0) + return SZ_ERROR_ARCHIVE; + sd.Size--; + sd.Data++; + continue; + } + mask = 0x20; + for (i = 2; i < 8 && (firstByte & mask) != 0; i++) + mask >>= 1; + if (i > sd.Size) + return SZ_ERROR_ARCHIVE; + SKIP_DATA2(sd, i); + } + *sd2 = sd; + return SZ_OK; +} + + +#define k_Scan_NumCoders_MAX 64 +#define k_Scan_NumCodersStreams_in_Folder_MAX 64 + + +static SRes ReadUnpackInfo(CSzAr *p, + CSzData *sd2, + UInt32 numFoldersMax, + const CBuf *tempBufs, UInt32 numTempBufs, + ISzAlloc *alloc) +{ + CSzData sd; + + UInt32 fo, numFolders, numCodersOutStreams, packStreamIndex; + const Byte *startBufPtr; + Byte external; + + RINOK(WaitId(sd2, k7zIdFolder)); + + RINOK(SzReadNumber32(sd2, &numFolders)); + if (numFolders > numFoldersMax) + return SZ_ERROR_UNSUPPORTED; + p->NumFolders = numFolders; + + SZ_READ_BYTE_SD(sd2, external); + if (external == 0) + sd = *sd2; + else + { + UInt32 index; + RINOK(SzReadNumber32(sd2, &index)); + if (index >= numTempBufs) + return SZ_ERROR_ARCHIVE; + sd.Data = tempBufs[index].data; + sd.Size = tempBufs[index].size; + } + + MY_ALLOC(size_t, p->FoCodersOffsets, (size_t)numFolders + 1, alloc); + MY_ALLOC(UInt32, p->FoStartPackStreamIndex, (size_t)numFolders + 1, alloc); + MY_ALLOC(UInt32, p->FoToCoderUnpackSizes, (size_t)numFolders + 1, alloc); + MY_ALLOC(Byte, p->FoToMainUnpackSizeIndex, (size_t)numFolders, alloc); + + startBufPtr = sd.Data; + + packStreamIndex = 0; + numCodersOutStreams = 0; + + for (fo = 0; fo < numFolders; fo++) + { + UInt32 numCoders, ci, numInStreams = 0; + + p->FoCodersOffsets[fo] = sd.Data - startBufPtr; + + RINOK(SzReadNumber32(&sd, &numCoders)); + if (numCoders == 0 || numCoders > k_Scan_NumCoders_MAX) + return SZ_ERROR_UNSUPPORTED; + + for (ci = 0; ci < numCoders; ci++) + { + Byte mainByte; + unsigned idSize; + UInt32 coderInStreams; + + SZ_READ_BYTE_2(mainByte); + if ((mainByte & 0xC0) != 0) + return SZ_ERROR_UNSUPPORTED; + idSize = (mainByte & 0xF); + if (idSize > 8) + return SZ_ERROR_UNSUPPORTED; + if (idSize > sd.Size) + return SZ_ERROR_ARCHIVE; + SKIP_DATA2(sd, idSize); + + coderInStreams = 1; + + if ((mainByte & 0x10) != 0) + { + UInt32 coderOutStreams; + RINOK(SzReadNumber32(&sd, &coderInStreams)); + RINOK(SzReadNumber32(&sd, &coderOutStreams)); + if (coderInStreams > k_Scan_NumCodersStreams_in_Folder_MAX || coderOutStreams != 1) + return SZ_ERROR_UNSUPPORTED; + } + + numInStreams += coderInStreams; + + if ((mainByte & 0x20) != 0) + { + UInt32 propsSize; + RINOK(SzReadNumber32(&sd, &propsSize)); + if (propsSize > sd.Size) + return SZ_ERROR_ARCHIVE; + SKIP_DATA2(sd, propsSize); + } + } + + { + UInt32 indexOfMainStream = 0; + UInt32 numPackStreams = 1; + + if (numCoders != 1 || numInStreams != 1) + { + Byte streamUsed[k_Scan_NumCodersStreams_in_Folder_MAX]; + Byte coderUsed[k_Scan_NumCoders_MAX]; + + UInt32 i; + UInt32 numBonds = numCoders - 1; + if (numInStreams < numBonds) + return SZ_ERROR_ARCHIVE; + + if (numInStreams > k_Scan_NumCodersStreams_in_Folder_MAX) + return SZ_ERROR_UNSUPPORTED; + + for (i = 0; i < numInStreams; i++) + streamUsed[i] = False; + for (i = 0; i < numCoders; i++) + coderUsed[i] = False; + + for (i = 0; i < numBonds; i++) + { + UInt32 index; + + RINOK(SzReadNumber32(&sd, &index)); + if (index >= numInStreams || streamUsed[index]) + return SZ_ERROR_ARCHIVE; + streamUsed[index] = True; + + RINOK(SzReadNumber32(&sd, &index)); + if (index >= numCoders || coderUsed[index]) + return SZ_ERROR_ARCHIVE; + coderUsed[index] = True; + } + + numPackStreams = numInStreams - numBonds; + + if (numPackStreams != 1) + for (i = 0; i < numPackStreams; i++) + { + UInt32 index; + RINOK(SzReadNumber32(&sd, &index)); + if (index >= numInStreams || streamUsed[index]) + return SZ_ERROR_ARCHIVE; + streamUsed[index] = True; + } + + for (i = 0; i < numCoders; i++) + if (!coderUsed[i]) + { + indexOfMainStream = i; + break; + } + + if (i == numCoders) + return SZ_ERROR_ARCHIVE; + } + + p->FoStartPackStreamIndex[fo] = packStreamIndex; + p->FoToCoderUnpackSizes[fo] = numCodersOutStreams; + p->FoToMainUnpackSizeIndex[fo] = (Byte)indexOfMainStream; + numCodersOutStreams += numCoders; + if (numCodersOutStreams < numCoders) + return SZ_ERROR_UNSUPPORTED; + if (numPackStreams > p->NumPackStreams - packStreamIndex) + return SZ_ERROR_ARCHIVE; + packStreamIndex += numPackStreams; + } + } + + p->FoToCoderUnpackSizes[fo] = numCodersOutStreams; + + { + size_t dataSize = sd.Data - startBufPtr; + p->FoStartPackStreamIndex[fo] = packStreamIndex; + p->FoCodersOffsets[fo] = dataSize; + MY_ALLOC_ZE_AND_CPY(p->CodersData, dataSize, startBufPtr, alloc); + } + + if (external != 0) + { + if (sd.Size != 0) + return SZ_ERROR_ARCHIVE; + sd = *sd2; + } + + RINOK(WaitId(&sd, k7zIdCodersUnpackSize)); + + MY_ALLOC_ZE(UInt64, p->CoderUnpackSizes, (size_t)numCodersOutStreams, alloc); + { + UInt32 i; + for (i = 0; i < numCodersOutStreams; i++) + { + RINOK(ReadNumber(&sd, p->CoderUnpackSizes + i)); + } + } + + for (;;) + { + UInt64 type; + RINOK(ReadID(&sd, &type)); + if (type == k7zIdEnd) + { + *sd2 = sd; + return SZ_OK; + } + if (type == k7zIdCRC) + { + RINOK(ReadBitUi32s(&sd, numFolders, &p->FolderCRCs, alloc)); + continue; + } + RINOK(SkipData(&sd)); + } +} + + +static UInt64 SzAr_GetFolderUnpackSize(const CSzAr *p, UInt32 folderIndex) +{ + return p->CoderUnpackSizes[p->FoToCoderUnpackSizes[folderIndex] + p->FoToMainUnpackSizeIndex[folderIndex]]; +} + + +typedef struct +{ + UInt32 NumTotalSubStreams; + UInt32 NumSubDigests; + CSzData sdNumSubStreams; + CSzData sdSizes; + CSzData sdCRCs; +} CSubStreamInfo; + + +static SRes ReadSubStreamsInfo(CSzAr *p, CSzData *sd, CSubStreamInfo *ssi) +{ + UInt64 type = 0; + UInt32 numSubDigests = 0; + UInt32 numFolders = p->NumFolders; + UInt32 numUnpackStreams = numFolders; + UInt32 numUnpackSizesInData = 0; + + for (;;) + { + RINOK(ReadID(sd, &type)); + if (type == k7zIdNumUnpackStream) + { + UInt32 i; + ssi->sdNumSubStreams.Data = sd->Data; + numUnpackStreams = 0; + numSubDigests = 0; + for (i = 0; i < numFolders; i++) + { + UInt32 numStreams; + RINOK(SzReadNumber32(sd, &numStreams)); + if (numUnpackStreams > numUnpackStreams + numStreams) + return SZ_ERROR_UNSUPPORTED; + numUnpackStreams += numStreams; + if (numStreams != 0) + numUnpackSizesInData += (numStreams - 1); + if (numStreams != 1 || !SzBitWithVals_Check(&p->FolderCRCs, i)) + numSubDigests += numStreams; + } + ssi->sdNumSubStreams.Size = sd->Data - ssi->sdNumSubStreams.Data; + continue; + } + if (type == k7zIdCRC || type == k7zIdSize || type == k7zIdEnd) + break; + RINOK(SkipData(sd)); + } + + if (!ssi->sdNumSubStreams.Data) + { + numSubDigests = numFolders; + if (p->FolderCRCs.Defs) + numSubDigests = numFolders - CountDefinedBits(p->FolderCRCs.Defs, numFolders); + } + + ssi->NumTotalSubStreams = numUnpackStreams; + ssi->NumSubDigests = numSubDigests; + + if (type == k7zIdSize) + { + ssi->sdSizes.Data = sd->Data; + RINOK(SkipNumbers(sd, numUnpackSizesInData)); + ssi->sdSizes.Size = sd->Data - ssi->sdSizes.Data; + RINOK(ReadID(sd, &type)); + } + + for (;;) + { + if (type == k7zIdEnd) + return SZ_OK; + if (type == k7zIdCRC) + { + ssi->sdCRCs.Data = sd->Data; + RINOK(SkipBitUi32s(sd, numSubDigests)); + ssi->sdCRCs.Size = sd->Data - ssi->sdCRCs.Data; + } + else + { + RINOK(SkipData(sd)); + } + RINOK(ReadID(sd, &type)); + } +} + +static SRes SzReadStreamsInfo(CSzAr *p, + CSzData *sd, + UInt32 numFoldersMax, const CBuf *tempBufs, UInt32 numTempBufs, + UInt64 *dataOffset, + CSubStreamInfo *ssi, + ISzAlloc *alloc) +{ + UInt64 type; + + SzData_Clear(&ssi->sdSizes); + SzData_Clear(&ssi->sdCRCs); + SzData_Clear(&ssi->sdNumSubStreams); + + *dataOffset = 0; + RINOK(ReadID(sd, &type)); + if (type == k7zIdPackInfo) + { + RINOK(ReadNumber(sd, dataOffset)); + RINOK(ReadPackInfo(p, sd, alloc)); + RINOK(ReadID(sd, &type)); + } + if (type == k7zIdUnpackInfo) + { + RINOK(ReadUnpackInfo(p, sd, numFoldersMax, tempBufs, numTempBufs, alloc)); + RINOK(ReadID(sd, &type)); + } + if (type == k7zIdSubStreamsInfo) + { + RINOK(ReadSubStreamsInfo(p, sd, ssi)); + RINOK(ReadID(sd, &type)); + } + else + { + ssi->NumTotalSubStreams = p->NumFolders; + /* ssi->NumSubDigests = 0; */ + } + + return (type == k7zIdEnd ? SZ_OK : SZ_ERROR_UNSUPPORTED); +} + +static SRes SzReadAndDecodePackedStreams( + ILookInStream *inStream, + CSzData *sd, + CBuf *tempBufs, + UInt32 numFoldersMax, + UInt64 baseOffset, + CSzAr *p, + ISzAlloc *allocTemp) +{ + UInt64 dataStartPos = 0; + UInt32 fo; + CSubStreamInfo ssi; + UInt32 numFolders; + + RINOK(SzReadStreamsInfo(p, sd, numFoldersMax, NULL, 0, &dataStartPos, &ssi, allocTemp)); + + numFolders = p->NumFolders; + if (numFolders == 0) + return SZ_ERROR_ARCHIVE; + else if (numFolders > numFoldersMax) + return SZ_ERROR_UNSUPPORTED; + + dataStartPos += baseOffset; + + for (fo = 0; fo < numFolders; fo++) + Buf_Init(tempBufs + fo); + + for (fo = 0; fo < numFolders; fo++) + { + CBuf *tempBuf = tempBufs + fo; + UInt64 unpackSize = SzAr_GetFolderUnpackSize(p, fo); + if ((size_t)unpackSize != unpackSize) + return SZ_ERROR_MEM; + if (!Buf_Create(tempBuf, (size_t)unpackSize, allocTemp)) + return SZ_ERROR_MEM; + } + + for (fo = 0; fo < numFolders; fo++) + { + const CBuf *tempBuf = tempBufs + fo; + RINOK(LookInStream_SeekTo(inStream, dataStartPos)); + RINOK(SzAr_DecodeFolder(p, fo, inStream, dataStartPos, tempBuf->data, tempBuf->size, allocTemp)); + } + + return SZ_OK; +} + +static SRes SzReadFileNames(const Byte *data, size_t size, UInt32 numFiles, size_t *offsets) +{ + size_t pos = 0; + *offsets++ = 0; + if (numFiles == 0) + return (size == 0) ? SZ_OK : SZ_ERROR_ARCHIVE; + if (size < 2) + return SZ_ERROR_ARCHIVE; + if (data[size - 2] != 0 || data[size - 1] != 0) + return SZ_ERROR_ARCHIVE; + do + { + const Byte *p; + if (pos == size) + return SZ_ERROR_ARCHIVE; + for (p = data + pos; + #ifdef _WIN32 + *(const UInt16 *)p != 0 + #else + p[0] != 0 || p[1] != 0 + #endif + ; p += 2); + pos = p - data + 2; + *offsets++ = (pos >> 1); + } + while (--numFiles); + return (pos == size) ? SZ_OK : SZ_ERROR_ARCHIVE; +} + +static MY_NO_INLINE SRes ReadTime(CSzBitUi64s *p, UInt32 num, + CSzData *sd2, + const CBuf *tempBufs, UInt32 numTempBufs, + ISzAlloc *alloc) +{ + CSzData sd; + UInt32 i; + CNtfsFileTime *vals; + Byte *defs; + Byte external; + + RINOK(ReadBitVector(sd2, num, &p->Defs, alloc)); + + SZ_READ_BYTE_SD(sd2, external); + if (external == 0) + sd = *sd2; + else + { + UInt32 index; + RINOK(SzReadNumber32(sd2, &index)); + if (index >= numTempBufs) + return SZ_ERROR_ARCHIVE; + sd.Data = tempBufs[index].data; + sd.Size = tempBufs[index].size; + } + + MY_ALLOC_ZE(CNtfsFileTime, p->Vals, num, alloc); + vals = p->Vals; + defs = p->Defs; + for (i = 0; i < num; i++) + if (SzBitArray_Check(defs, i)) + { + if (sd.Size < 8) + return SZ_ERROR_ARCHIVE; + vals[i].Low = GetUi32(sd.Data); + vals[i].High = GetUi32(sd.Data + 4); + SKIP_DATA2(sd, 8); + } + else + vals[i].High = vals[i].Low = 0; + + if (external == 0) + *sd2 = sd; + + return SZ_OK; +} + + +#define NUM_ADDITIONAL_STREAMS_MAX 8 + + +static SRes SzReadHeader2( + CSzArEx *p, /* allocMain */ + CSzData *sd, + ILookInStream *inStream, + CBuf *tempBufs, UInt32 *numTempBufs, + ISzAlloc *allocMain, + ISzAlloc *allocTemp + ) +{ + CSubStreamInfo ssi; + +{ + UInt64 type; + + SzData_Clear(&ssi.sdSizes); + SzData_Clear(&ssi.sdCRCs); + SzData_Clear(&ssi.sdNumSubStreams); + + ssi.NumSubDigests = 0; + ssi.NumTotalSubStreams = 0; + + RINOK(ReadID(sd, &type)); + + if (type == k7zIdArchiveProperties) + { + for (;;) + { + UInt64 type2; + RINOK(ReadID(sd, &type2)); + if (type2 == k7zIdEnd) + break; + RINOK(SkipData(sd)); + } + RINOK(ReadID(sd, &type)); + } + + if (type == k7zIdAdditionalStreamsInfo) + { + CSzAr tempAr; + SRes res; + + SzAr_Init(&tempAr); + res = SzReadAndDecodePackedStreams(inStream, sd, tempBufs, NUM_ADDITIONAL_STREAMS_MAX, + p->startPosAfterHeader, &tempAr, allocTemp); + *numTempBufs = tempAr.NumFolders; + SzAr_Free(&tempAr, allocTemp); + + if (res != SZ_OK) + return res; + RINOK(ReadID(sd, &type)); + } + + if (type == k7zIdMainStreamsInfo) + { + RINOK(SzReadStreamsInfo(&p->db, sd, (UInt32)1 << 30, tempBufs, *numTempBufs, + &p->dataPos, &ssi, allocMain)); + p->dataPos += p->startPosAfterHeader; + RINOK(ReadID(sd, &type)); + } + + if (type == k7zIdEnd) + { + return SZ_OK; + } + + if (type != k7zIdFilesInfo) + return SZ_ERROR_ARCHIVE; +} + +{ + UInt32 numFiles = 0; + UInt32 numEmptyStreams = 0; + const Byte *emptyStreams = NULL; + const Byte *emptyFiles = NULL; + + RINOK(SzReadNumber32(sd, &numFiles)); + p->NumFiles = numFiles; + + for (;;) + { + UInt64 type; + UInt64 size; + RINOK(ReadID(sd, &type)); + if (type == k7zIdEnd) + break; + RINOK(ReadNumber(sd, &size)); + if (size > sd->Size) + return SZ_ERROR_ARCHIVE; + + if (type >= ((UInt32)1 << 8)) + { + SKIP_DATA(sd, size); + } + else switch ((unsigned)type) + { + case k7zIdName: + { + size_t namesSize; + const Byte *namesData; + Byte external; + + SZ_READ_BYTE(external); + if (external == 0) + { + namesSize = (size_t)size - 1; + namesData = sd->Data; + } + else + { + UInt32 index; + RINOK(SzReadNumber32(sd, &index)); + if (index >= *numTempBufs) + return SZ_ERROR_ARCHIVE; + namesData = (tempBufs)[index].data; + namesSize = (tempBufs)[index].size; + } + + if ((namesSize & 1) != 0) + return SZ_ERROR_ARCHIVE; + MY_ALLOC(size_t, p->FileNameOffsets, numFiles + 1, allocMain); + MY_ALLOC_ZE_AND_CPY(p->FileNames, namesSize, namesData, allocMain); + RINOK(SzReadFileNames(p->FileNames, namesSize, numFiles, p->FileNameOffsets)) + if (external == 0) + { + SKIP_DATA(sd, namesSize); + } + break; + } + case k7zIdEmptyStream: + { + RINOK(RememberBitVector(sd, numFiles, &emptyStreams)); + numEmptyStreams = CountDefinedBits(emptyStreams, numFiles); + emptyFiles = NULL; + break; + } + case k7zIdEmptyFile: + { + RINOK(RememberBitVector(sd, numEmptyStreams, &emptyFiles)); + break; + } + case k7zIdWinAttrib: + { + Byte external; + CSzData sdSwitch; + CSzData *sdPtr; + SzBitUi32s_Free(&p->Attribs, allocMain); + RINOK(ReadBitVector(sd, numFiles, &p->Attribs.Defs, allocMain)); + + SZ_READ_BYTE(external); + if (external == 0) + sdPtr = sd; + else + { + UInt32 index; + RINOK(SzReadNumber32(sd, &index)); + if (index >= *numTempBufs) + return SZ_ERROR_ARCHIVE; + sdSwitch.Data = (tempBufs)[index].data; + sdSwitch.Size = (tempBufs)[index].size; + sdPtr = &sdSwitch; + } + RINOK(ReadUi32s(sdPtr, numFiles, &p->Attribs, allocMain)); + break; + } + /* + case k7zParent: + { + SzBitUi32s_Free(&p->Parents, allocMain); + RINOK(ReadBitVector(sd, numFiles, &p->Parents.Defs, allocMain)); + RINOK(SzReadSwitch(sd)); + RINOK(ReadUi32s(sd, numFiles, &p->Parents, allocMain)); + break; + } + */ + case k7zIdMTime: RINOK(ReadTime(&p->MTime, numFiles, sd, tempBufs, *numTempBufs, allocMain)); break; + case k7zIdCTime: RINOK(ReadTime(&p->CTime, numFiles, sd, tempBufs, *numTempBufs, allocMain)); break; + default: + { + SKIP_DATA(sd, size); + } + } + } + + if (numFiles - numEmptyStreams != ssi.NumTotalSubStreams) + return SZ_ERROR_ARCHIVE; + + for (;;) + { + UInt64 type; + RINOK(ReadID(sd, &type)); + if (type == k7zIdEnd) + break; + RINOK(SkipData(sd)); + } + + { + UInt32 i; + UInt32 emptyFileIndex = 0; + UInt32 folderIndex = 0; + UInt32 remSubStreams = 0; + UInt32 numSubStreams = 0; + UInt64 unpackPos = 0; + const Byte *digestsDefs = NULL; + const Byte *digestsVals = NULL; + UInt32 digestsValsIndex = 0; + UInt32 digestIndex; + Byte allDigestsDefined = 0; + Byte isDirMask = 0; + Byte crcMask = 0; + Byte mask = 0x80; + + MY_ALLOC(UInt32, p->FolderToFile, p->db.NumFolders + 1, allocMain); + MY_ALLOC_ZE(UInt32, p->FileToFolder, p->NumFiles, allocMain); + MY_ALLOC(UInt64, p->UnpackPositions, p->NumFiles + 1, allocMain); + MY_ALLOC_ZE(Byte, p->IsDirs, (p->NumFiles + 7) >> 3, allocMain); + + RINOK(SzBitUi32s_Alloc(&p->CRCs, p->NumFiles, allocMain)); + + if (ssi.sdCRCs.Size != 0) + { + SZ_READ_BYTE_SD(&ssi.sdCRCs, allDigestsDefined); + if (allDigestsDefined) + digestsVals = ssi.sdCRCs.Data; + else + { + size_t numBytes = (ssi.NumSubDigests + 7) >> 3; + digestsDefs = ssi.sdCRCs.Data; + digestsVals = digestsDefs + numBytes; + } + } + + digestIndex = 0; + + for (i = 0; i < numFiles; i++, mask >>= 1) + { + if (mask == 0) + { + UInt32 byteIndex = (i - 1) >> 3; + p->IsDirs[byteIndex] = isDirMask; + p->CRCs.Defs[byteIndex] = crcMask; + isDirMask = 0; + crcMask = 0; + mask = 0x80; + } + + p->UnpackPositions[i] = unpackPos; + p->CRCs.Vals[i] = 0; + + if (emptyStreams && SzBitArray_Check(emptyStreams, i)) + { + if (emptyFiles) + { + if (!SzBitArray_Check(emptyFiles, emptyFileIndex)) + isDirMask |= mask; + emptyFileIndex++; + } + else + isDirMask |= mask; + if (remSubStreams == 0) + { + p->FileToFolder[i] = (UInt32)-1; + continue; + } + } + + if (remSubStreams == 0) + { + for (;;) + { + if (folderIndex >= p->db.NumFolders) + return SZ_ERROR_ARCHIVE; + p->FolderToFile[folderIndex] = i; + numSubStreams = 1; + if (ssi.sdNumSubStreams.Data) + { + RINOK(SzReadNumber32(&ssi.sdNumSubStreams, &numSubStreams)); + } + remSubStreams = numSubStreams; + if (numSubStreams != 0) + break; + { + UInt64 folderUnpackSize = SzAr_GetFolderUnpackSize(&p->db, folderIndex); + unpackPos += folderUnpackSize; + if (unpackPos < folderUnpackSize) + return SZ_ERROR_ARCHIVE; + } + + folderIndex++; + } + } + + p->FileToFolder[i] = folderIndex; + + if (emptyStreams && SzBitArray_Check(emptyStreams, i)) + continue; + + if (--remSubStreams == 0) + { + UInt64 folderUnpackSize = SzAr_GetFolderUnpackSize(&p->db, folderIndex); + UInt64 startFolderUnpackPos = p->UnpackPositions[p->FolderToFile[folderIndex]]; + if (folderUnpackSize < unpackPos - startFolderUnpackPos) + return SZ_ERROR_ARCHIVE; + unpackPos = startFolderUnpackPos + folderUnpackSize; + if (unpackPos < folderUnpackSize) + return SZ_ERROR_ARCHIVE; + + if (numSubStreams == 1 && SzBitWithVals_Check(&p->db.FolderCRCs, i)) + { + p->CRCs.Vals[i] = p->db.FolderCRCs.Vals[folderIndex]; + crcMask |= mask; + } + else if (allDigestsDefined || (digestsDefs && SzBitArray_Check(digestsDefs, digestIndex))) + { + p->CRCs.Vals[i] = GetUi32(digestsVals + (size_t)digestsValsIndex * 4); + digestsValsIndex++; + crcMask |= mask; + } + + folderIndex++; + } + else + { + UInt64 v; + RINOK(ReadNumber(&ssi.sdSizes, &v)); + unpackPos += v; + if (unpackPos < v) + return SZ_ERROR_ARCHIVE; + if (allDigestsDefined || (digestsDefs && SzBitArray_Check(digestsDefs, digestIndex))) + { + p->CRCs.Vals[i] = GetUi32(digestsVals + (size_t)digestsValsIndex * 4); + digestsValsIndex++; + crcMask |= mask; + } + } + } + + if (mask != 0x80) + { + UInt32 byteIndex = (i - 1) >> 3; + p->IsDirs[byteIndex] = isDirMask; + p->CRCs.Defs[byteIndex] = crcMask; + } + + p->UnpackPositions[i] = unpackPos; + + if (remSubStreams != 0) + return SZ_ERROR_ARCHIVE; + + for (;;) + { + p->FolderToFile[folderIndex] = i; + if (folderIndex >= p->db.NumFolders) + break; + if (!ssi.sdNumSubStreams.Data) + return SZ_ERROR_ARCHIVE; + RINOK(SzReadNumber32(&ssi.sdNumSubStreams, &numSubStreams)); + if (numSubStreams != 0) + return SZ_ERROR_ARCHIVE; + /* + { + UInt64 folderUnpackSize = SzAr_GetFolderUnpackSize(&p->db, folderIndex); + unpackPos += folderUnpackSize; + if (unpackPos < folderUnpackSize) + return SZ_ERROR_ARCHIVE; + } + */ + folderIndex++; + } + + if (ssi.sdNumSubStreams.Data && ssi.sdNumSubStreams.Size != 0) + return SZ_ERROR_ARCHIVE; + } +} + return SZ_OK; +} + + +static SRes SzReadHeader( + CSzArEx *p, + CSzData *sd, + ILookInStream *inStream, + ISzAlloc *allocMain, + ISzAlloc *allocTemp) +{ + UInt32 i; + UInt32 numTempBufs = 0; + SRes res; + CBuf tempBufs[NUM_ADDITIONAL_STREAMS_MAX]; + + for (i = 0; i < NUM_ADDITIONAL_STREAMS_MAX; i++) + Buf_Init(tempBufs + i); + + res = SzReadHeader2(p, sd, inStream, + tempBufs, &numTempBufs, + allocMain, allocTemp); + + for (i = 0; i < NUM_ADDITIONAL_STREAMS_MAX; i++) + Buf_Free(tempBufs + i, allocTemp); + + RINOK(res); + + if (sd->Size != 0) + return SZ_ERROR_FAIL; + + return res; +} + +static SRes SzArEx_Open2( + CSzArEx *p, + ILookInStream *inStream, + ISzAlloc *allocMain, + ISzAlloc *allocTemp) +{ + Byte header[k7zStartHeaderSize]; + Int64 startArcPos; + UInt64 nextHeaderOffset, nextHeaderSize; + size_t nextHeaderSizeT; + UInt32 nextHeaderCRC; + CBuf buf; + SRes res; + + startArcPos = 0; + RINOK(inStream->Seek(inStream, &startArcPos, SZ_SEEK_CUR)); + + RINOK(LookInStream_Read2(inStream, header, k7zStartHeaderSize, SZ_ERROR_NO_ARCHIVE)); + + if (!TestSignatureCandidate(header)) + return SZ_ERROR_NO_ARCHIVE; + if (header[6] != k7zMajorVersion) + return SZ_ERROR_UNSUPPORTED; + + nextHeaderOffset = GetUi64(header + 12); + nextHeaderSize = GetUi64(header + 20); + nextHeaderCRC = GetUi32(header + 28); + + p->startPosAfterHeader = startArcPos + k7zStartHeaderSize; + + if (CrcCalc(header + 12, 20) != GetUi32(header + 8)) + return SZ_ERROR_CRC; + + nextHeaderSizeT = (size_t)nextHeaderSize; + if (nextHeaderSizeT != nextHeaderSize) + return SZ_ERROR_MEM; + if (nextHeaderSizeT == 0) + return SZ_OK; + if (nextHeaderOffset > nextHeaderOffset + nextHeaderSize || + nextHeaderOffset > nextHeaderOffset + nextHeaderSize + k7zStartHeaderSize) + return SZ_ERROR_NO_ARCHIVE; + + { + Int64 pos = 0; + RINOK(inStream->Seek(inStream, &pos, SZ_SEEK_END)); + if ((UInt64)pos < startArcPos + nextHeaderOffset || + (UInt64)pos < startArcPos + k7zStartHeaderSize + nextHeaderOffset || + (UInt64)pos < startArcPos + k7zStartHeaderSize + nextHeaderOffset + nextHeaderSize) + return SZ_ERROR_INPUT_EOF; + } + + RINOK(LookInStream_SeekTo(inStream, startArcPos + k7zStartHeaderSize + nextHeaderOffset)); + + if (!Buf_Create(&buf, nextHeaderSizeT, allocTemp)) + return SZ_ERROR_MEM; + + res = LookInStream_Read(inStream, buf.data, nextHeaderSizeT); + + if (res == SZ_OK) + { + res = SZ_ERROR_ARCHIVE; + if (CrcCalc(buf.data, nextHeaderSizeT) == nextHeaderCRC) + { + CSzData sd; + UInt64 type; + sd.Data = buf.data; + sd.Size = buf.size; + + res = ReadID(&sd, &type); + + if (res == SZ_OK && type == k7zIdEncodedHeader) + { + CSzAr tempAr; + CBuf tempBuf; + Buf_Init(&tempBuf); + + SzAr_Init(&tempAr); + res = SzReadAndDecodePackedStreams(inStream, &sd, &tempBuf, 1, p->startPosAfterHeader, &tempAr, allocTemp); + SzAr_Free(&tempAr, allocTemp); + + if (res != SZ_OK) + { + Buf_Free(&tempBuf, allocTemp); + } + else + { + Buf_Free(&buf, allocTemp); + buf.data = tempBuf.data; + buf.size = tempBuf.size; + sd.Data = buf.data; + sd.Size = buf.size; + res = ReadID(&sd, &type); + } + } + + if (res == SZ_OK) + { + if (type == k7zIdHeader) + { + /* + CSzData sd2; + unsigned ttt; + for (ttt = 0; ttt < 40000; ttt++) + { + SzArEx_Free(p, allocMain); + sd2 = sd; + res = SzReadHeader(p, &sd2, inStream, allocMain, allocTemp); + if (res != SZ_OK) + break; + } + */ + res = SzReadHeader(p, &sd, inStream, allocMain, allocTemp); + } + else + res = SZ_ERROR_UNSUPPORTED; + } + } + } + + Buf_Free(&buf, allocTemp); + return res; +} + + +static SRes SzArEx_Open(CSzArEx *p, ILookInStream *inStream, + ISzAlloc *allocMain, ISzAlloc *allocTemp) +{ + SRes res = SzArEx_Open2(p, inStream, allocMain, allocTemp); + if (res != SZ_OK) + SzArEx_Free(p, allocMain); + return res; +} + + +static SRes SzArEx_Extract( + const CSzArEx *p, + ILookInStream *inStream, + UInt32 fileIndex, + UInt32 *blockIndex, + Byte **tempBuf, + size_t *outBufferSize, + size_t *offset, + size_t *outSizeProcessed, + ISzAlloc *allocMain, + ISzAlloc *allocTemp) +{ + UInt32 folderIndex = p->FileToFolder[fileIndex]; + SRes res = SZ_OK; + + *offset = 0; + *outSizeProcessed = 0; + + if (folderIndex == (UInt32)-1) + { + IAlloc_Free(allocMain, *tempBuf); + *blockIndex = folderIndex; + *tempBuf = NULL; + *outBufferSize = 0; + return SZ_OK; + } + + if (*tempBuf == NULL || *blockIndex != folderIndex) + { + UInt64 unpackSizeSpec = SzAr_GetFolderUnpackSize(&p->db, folderIndex); + /* + UInt64 unpackSizeSpec = + p->UnpackPositions[p->FolderToFile[folderIndex + 1]] - + p->UnpackPositions[p->FolderToFile[folderIndex]]; + */ + size_t unpackSize = (size_t)unpackSizeSpec; + + if (unpackSize != unpackSizeSpec) + return SZ_ERROR_MEM; + *blockIndex = folderIndex; + IAlloc_Free(allocMain, *tempBuf); + *tempBuf = NULL; + + if (res == SZ_OK) + { + *outBufferSize = unpackSize; + if (unpackSize != 0) + { + *tempBuf = (Byte *)IAlloc_Alloc(allocMain, unpackSize); + if (*tempBuf == NULL) + res = SZ_ERROR_MEM; + } + + if (res == SZ_OK) + { + res = SzAr_DecodeFolder(&p->db, folderIndex, + inStream, p->dataPos, *tempBuf, unpackSize, allocTemp); + } + } + } + + if (res == SZ_OK) + { + UInt64 unpackPos = p->UnpackPositions[fileIndex]; + *offset = (size_t)(unpackPos - p->UnpackPositions[p->FolderToFile[folderIndex]]); + *outSizeProcessed = (size_t)(p->UnpackPositions[fileIndex + 1] - unpackPos); + if (*offset + *outSizeProcessed > *outBufferSize) + return SZ_ERROR_FAIL; + if (SzBitWithVals_Check(&p->CRCs, fileIndex)) + if (CrcCalc(*tempBuf + *offset, *outSizeProcessed) != p->CRCs.Vals[fileIndex]) + res = SZ_ERROR_CRC; + } + + return res; +} + + +static size_t SzArEx_GetFileNameUtf16(const CSzArEx *p, size_t fileIndex, UInt16 *dest) +{ + size_t offs = p->FileNameOffsets[fileIndex]; + size_t len = p->FileNameOffsets[fileIndex + 1] - offs; + if (dest != 0) + { + size_t i; + const Byte *src = p->FileNames + offs * 2; + for (i = 0; i < len; i++) + dest[i] = GetUi16(src + i * 2); + } + return len; +} + +/* +static size_t SzArEx_GetFullNameLen(const CSzArEx *p, size_t fileIndex) +{ + size_t len; + if (!p->FileNameOffsets) + return 1; + len = 0; + for (;;) + { + UInt32 parent = (UInt32)(Int32)-1; + len += p->FileNameOffsets[fileIndex + 1] - p->FileNameOffsets[fileIndex]; + if SzBitWithVals_Check(&p->Parents, fileIndex) + parent = p->Parents.Vals[fileIndex]; + if (parent == (UInt32)(Int32)-1) + return len; + fileIndex = parent; + } +} + +static UInt16 *SzArEx_GetFullNameUtf16_Back(const CSzArEx *p, size_t fileIndex, UInt16 *dest) +{ + Bool needSlash; + if (!p->FileNameOffsets) + { + *(--dest) = 0; + return dest; + } + needSlash = False; + for (;;) + { + UInt32 parent = (UInt32)(Int32)-1; + size_t curLen = p->FileNameOffsets[fileIndex + 1] - p->FileNameOffsets[fileIndex]; + SzArEx_GetFileNameUtf16(p, fileIndex, dest - curLen); + if (needSlash) + *(dest - 1) = '/'; + needSlash = True; + dest -= curLen; + + if SzBitWithVals_Check(&p->Parents, fileIndex) + parent = p->Parents.Vals[fileIndex]; + if (parent == (UInt32)(Int32)-1) + return dest; + fileIndex = parent; + } +} +*/ + +/* 7zBuf.c -- Byte Buffer +2013-01-21 : Igor Pavlov : Public domain */ + +/* +#include "Precomp.h" + +#include "7zBuf.h" +*/ + +static void Buf_Init(CBuf *p) +{ + p->data = 0; + p->size = 0; +} + +static int Buf_Create(CBuf *p, size_t size, ISzAlloc *alloc) +{ + p->size = 0; + if (size == 0) + { + p->data = 0; + return 1; + } + p->data = (Byte *)alloc->Alloc(alloc, size); + if (p->data != 0) + { + p->size = size; + return 1; + } + return 0; +} + +static void Buf_Free(CBuf *p, ISzAlloc *alloc) +{ + alloc->Free(alloc, p->data); + p->data = 0; + p->size = 0; +} + +/* 7zDec.c -- Decoding from 7z folder +2015-11-18 : Igor Pavlov : Public domain */ + +/* #define _7ZIP_PPMD_SUPPPORT */ + +/* +#include "Precomp.h" + +#include + +#include "7z.h" +#include "7zCrc.h" + +#include "Bcj2.h" +#include "Bra.h" +#include "CpuArch.h" +#include "Delta.h" +#include "LzmaDec.h" +#include "Lzma2Dec.h" +#ifdef _7ZIP_PPMD_SUPPPORT +#include "Ppmd7.h" +#endif +*/ + +#define k_Copy 0 +#define k_Delta 3 +#define k_LZMA2 0x21 +#define k_LZMA 0x30101 +#define k_BCJ 0x3030103 +#define k_BCJ2 0x303011B +#define k_PPC 0x3030205 +#define k_IA64 0x3030401 +#define k_ARM 0x3030501 +#define k_ARMT 0x3030701 +#define k_SPARC 0x3030805 + + +#ifdef _7ZIP_PPMD_SUPPPORT + +#define k_PPMD 0x30401 + +typedef struct +{ + IByteIn p; + const Byte *cur; + const Byte *end; + const Byte *begin; + UInt64 processed; + Bool extra; + SRes res; + ILookInStream *inStream; +} CByteInToLook; + +static Byte ReadByte(void *pp) +{ + CByteInToLook *p = (CByteInToLook *)pp; + if (p->cur != p->end) + return *p->cur++; + if (p->res == SZ_OK) + { + size_t size = p->cur - p->begin; + p->processed += size; + p->res = p->inStream->Skip(p->inStream, size); + size = (1 << 25); + p->res = p->inStream->Look(p->inStream, (const void **)&p->begin, &size); + p->cur = p->begin; + p->end = p->begin + size; + if (size != 0) + return *p->cur++;; + } + p->extra = True; + return 0; +} + +static SRes SzDecodePpmd(const Byte *props, unsigned propsSize, UInt64 inSize, ILookInStream *inStream, + Byte *outBuffer, SizeT outSize, ISzAlloc *allocMain) +{ + CPpmd7 ppmd; + CByteInToLook s; + SRes res = SZ_OK; + + s.p.Read = ReadByte; + s.inStream = inStream; + s.begin = s.end = s.cur = NULL; + s.extra = False; + s.res = SZ_OK; + s.processed = 0; + + if (propsSize != 5) + return SZ_ERROR_UNSUPPORTED; + + { + unsigned order = props[0]; + UInt32 memSize = GetUi32(props + 1); + if (order < PPMD7_MIN_ORDER || + order > PPMD7_MAX_ORDER || + memSize < PPMD7_MIN_MEM_SIZE || + memSize > PPMD7_MAX_MEM_SIZE) + return SZ_ERROR_UNSUPPORTED; + Ppmd7_Construct(&ppmd); + if (!Ppmd7_Alloc(&ppmd, memSize, allocMain)) + return SZ_ERROR_MEM; + Ppmd7_Init(&ppmd, order); + } + { + CPpmd7z_RangeDec rc; + Ppmd7z_RangeDec_CreateVTable(&rc); + rc.Stream = &s.p; + if (!Ppmd7z_RangeDec_Init(&rc)) + res = SZ_ERROR_DATA; + else if (s.extra) + res = (s.res != SZ_OK ? s.res : SZ_ERROR_DATA); + else + { + SizeT i; + for (i = 0; i < outSize; i++) + { + int sym = Ppmd7_DecodeSymbol(&ppmd, &rc.p); + if (s.extra || sym < 0) + break; + outBuffer[i] = (Byte)sym; + } + if (i != outSize) + res = (s.res != SZ_OK ? s.res : SZ_ERROR_DATA); + else if (s.processed + (s.cur - s.begin) != inSize || !Ppmd7z_RangeDec_IsFinishedOK(&rc)) + res = SZ_ERROR_DATA; + } + } + Ppmd7_Free(&ppmd, allocMain); + return res; +} + +#endif + + +static SRes SzDecodeLzma(const Byte *props, unsigned propsSize, UInt64 inSize, ILookInStream *inStream, + Byte *outBuffer, SizeT outSize, ISzAlloc *allocMain) +{ + CLzmaDec state; + SRes res = SZ_OK; + + LzmaDec_Construct(&state); + RINOK(LzmaDec_AllocateProbs(&state, props, propsSize, allocMain)); + state.dic = outBuffer; + state.dicBufSize = outSize; + LzmaDec_Init(&state); + + for (;;) + { + const void *inBuf = NULL; + size_t lookahead = (1 << 18); + if (lookahead > inSize) + lookahead = (size_t)inSize; + res = inStream->Look(inStream, &inBuf, &lookahead); + if (res != SZ_OK) + break; + + { + SizeT inProcessed = (SizeT)lookahead, dicPos = state.dicPos; + ELzmaStatus status; + res = LzmaDec_DecodeToDic(&state, outSize, inBuf, &inProcessed, LZMA_FINISH_END, &status); + lookahead -= inProcessed; + inSize -= inProcessed; + if (res != SZ_OK) + break; + + if (status == LZMA_STATUS_FINISHED_WITH_MARK) + { + if (outSize != state.dicPos || inSize != 0) + res = SZ_ERROR_DATA; + break; + } + + if (outSize == state.dicPos && inSize == 0 && status == LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK) + break; + + if (inProcessed == 0 && dicPos == state.dicPos) + { + res = SZ_ERROR_DATA; + break; + } + + res = inStream->Skip((void *)inStream, inProcessed); + if (res != SZ_OK) + break; + } + } + + LzmaDec_FreeProbs(&state, allocMain); + return res; +} + + +#ifndef _7Z_NO_METHOD_LZMA2 + +static SRes SzDecodeLzma2(const Byte *props, unsigned propsSize, UInt64 inSize, ILookInStream *inStream, + Byte *outBuffer, SizeT outSize, ISzAlloc *allocMain) +{ + CLzma2Dec state; + SRes res = SZ_OK; + + Lzma2Dec_Construct(&state); + if (propsSize != 1) + return SZ_ERROR_DATA; + RINOK(Lzma2Dec_AllocateProbs(&state, props[0], allocMain)); + state.decoder.dic = outBuffer; + state.decoder.dicBufSize = outSize; + Lzma2Dec_Init(&state); + + for (;;) + { + const void *inBuf = NULL; + size_t lookahead = (1 << 18); + if (lookahead > inSize) + lookahead = (size_t)inSize; + res = inStream->Look(inStream, &inBuf, &lookahead); + if (res != SZ_OK) + break; + + { + SizeT inProcessed = (SizeT)lookahead, dicPos = state.decoder.dicPos; + ELzmaStatus status; + res = Lzma2Dec_DecodeToDic(&state, outSize, inBuf, &inProcessed, LZMA_FINISH_END, &status); + lookahead -= inProcessed; + inSize -= inProcessed; + if (res != SZ_OK) + break; + + if (status == LZMA_STATUS_FINISHED_WITH_MARK) + { + if (outSize != state.decoder.dicPos || inSize != 0) + res = SZ_ERROR_DATA; + break; + } + + if (inProcessed == 0 && dicPos == state.decoder.dicPos) + { + res = SZ_ERROR_DATA; + break; + } + + res = inStream->Skip((void *)inStream, inProcessed); + if (res != SZ_OK) + break; + } + } + + Lzma2Dec_FreeProbs(&state, allocMain); + return res; +} + +#endif + + +static SRes SzDecodeCopy(UInt64 inSize, ILookInStream *inStream, Byte *outBuffer) +{ + while (inSize > 0) + { + const void *inBuf; + size_t curSize = (1 << 18); + if (curSize > inSize) + curSize = (size_t)inSize; + RINOK(inStream->Look(inStream, &inBuf, &curSize)); + if (curSize == 0) + return SZ_ERROR_INPUT_EOF; + memcpy(outBuffer, inBuf, curSize); + outBuffer += curSize; + inSize -= curSize; + RINOK(inStream->Skip((void *)inStream, curSize)); + } + return SZ_OK; +} + +static Bool IS_MAIN_METHOD(UInt32 m) +{ + switch (m) + { + case k_Copy: + case k_LZMA: + #ifndef _7Z_NO_METHOD_LZMA2 + case k_LZMA2: + #endif + #ifdef _7ZIP_PPMD_SUPPPORT + case k_PPMD: + #endif + return True; + } + return False; +} + +static Bool IS_SUPPORTED_CODER(const CSzCoderInfo *c) +{ + return + c->NumStreams == 1 + /* && c->MethodID <= (UInt32)0xFFFFFFFF */ + && IS_MAIN_METHOD((UInt32)c->MethodID); +} + +#define IS_BCJ2(c) ((c)->MethodID == k_BCJ2 && (c)->NumStreams == 4) + +static SRes CheckSupportedFolder(const CSzFolder *f) +{ + if (f->NumCoders < 1 || f->NumCoders > 4) + return SZ_ERROR_UNSUPPORTED; + if (!IS_SUPPORTED_CODER(&f->Coders[0])) + return SZ_ERROR_UNSUPPORTED; + if (f->NumCoders == 1) + { + if (f->NumPackStreams != 1 || f->PackStreams[0] != 0 || f->NumBonds != 0) + return SZ_ERROR_UNSUPPORTED; + return SZ_OK; + } + + + #ifndef _7Z_NO_METHODS_FILTERS + + if (f->NumCoders == 2) + { + const CSzCoderInfo *c = &f->Coders[1]; + if ( + /* c->MethodID > (UInt32)0xFFFFFFFF || */ + c->NumStreams != 1 + || f->NumPackStreams != 1 + || f->PackStreams[0] != 0 + || f->NumBonds != 1 + || f->Bonds[0].InIndex != 1 + || f->Bonds[0].OutIndex != 0) + return SZ_ERROR_UNSUPPORTED; + switch ((UInt32)c->MethodID) + { + case k_Delta: + case k_BCJ: + case k_PPC: + case k_IA64: + case k_SPARC: + case k_ARM: + case k_ARMT: + break; + default: + return SZ_ERROR_UNSUPPORTED; + } + return SZ_OK; + } + + #endif + + + if (f->NumCoders == 4) + { + if (!IS_SUPPORTED_CODER(&f->Coders[1]) + || !IS_SUPPORTED_CODER(&f->Coders[2]) + || !IS_BCJ2(&f->Coders[3])) + return SZ_ERROR_UNSUPPORTED; + if (f->NumPackStreams != 4 + || f->PackStreams[0] != 2 + || f->PackStreams[1] != 6 + || f->PackStreams[2] != 1 + || f->PackStreams[3] != 0 + || f->NumBonds != 3 + || f->Bonds[0].InIndex != 5 || f->Bonds[0].OutIndex != 0 + || f->Bonds[1].InIndex != 4 || f->Bonds[1].OutIndex != 1 + || f->Bonds[2].InIndex != 3 || f->Bonds[2].OutIndex != 2) + return SZ_ERROR_UNSUPPORTED; + return SZ_OK; + } + + return SZ_ERROR_UNSUPPORTED; +} + +#define CASE_BRA_CONV(isa) case k_ ## isa: isa ## _Convert(outBuffer, outSize, 0, 0); break; + +static SRes SzFolder_Decode2(const CSzFolder *folder, + const Byte *propsData, + const UInt64 *unpackSizes, + const UInt64 *packPositions, + ILookInStream *inStream, UInt64 startPos, + Byte *outBuffer, SizeT outSize, ISzAlloc *allocMain, + Byte *tempBuf[]) +{ + UInt32 ci; + SizeT tempSizes[3] = { 0, 0, 0}; + SizeT tempSize3 = 0; + Byte *tempBuf3 = 0; + + RINOK(CheckSupportedFolder(folder)); + + for (ci = 0; ci < folder->NumCoders; ci++) + { + const CSzCoderInfo *coder = &folder->Coders[ci]; + + if (IS_MAIN_METHOD((UInt32)coder->MethodID)) + { + UInt32 si = 0; + UInt64 offset; + UInt64 inSize; + Byte *outBufCur = outBuffer; + SizeT outSizeCur = outSize; + if (folder->NumCoders == 4) + { + UInt32 indices[] = { 3, 2, 0 }; + UInt64 unpackSize = unpackSizes[ci]; + si = indices[ci]; + if (ci < 2) + { + Byte *temp; + outSizeCur = (SizeT)unpackSize; + if (outSizeCur != unpackSize) + return SZ_ERROR_MEM; + temp = (Byte *)IAlloc_Alloc(allocMain, outSizeCur); + if (!temp && outSizeCur != 0) + return SZ_ERROR_MEM; + outBufCur = tempBuf[1 - ci] = temp; + tempSizes[1 - ci] = outSizeCur; + } + else if (ci == 2) + { + if (unpackSize > outSize) /* check it */ + return SZ_ERROR_PARAM; + tempBuf3 = outBufCur = outBuffer + (outSize - (size_t)unpackSize); + tempSize3 = outSizeCur = (SizeT)unpackSize; + } + else + return SZ_ERROR_UNSUPPORTED; + } + offset = packPositions[si]; + inSize = packPositions[si + 1] - offset; + RINOK(LookInStream_SeekTo(inStream, startPos + offset)); + + if (coder->MethodID == k_Copy) + { + if (inSize != outSizeCur) /* check it */ + return SZ_ERROR_DATA; + RINOK(SzDecodeCopy(inSize, inStream, outBufCur)); + } + else if (coder->MethodID == k_LZMA) + { + RINOK(SzDecodeLzma(propsData + coder->PropsOffset, coder->PropsSize, inSize, inStream, outBufCur, outSizeCur, allocMain)); + } + #ifndef _7Z_NO_METHOD_LZMA2 + else if (coder->MethodID == k_LZMA2) + { + RINOK(SzDecodeLzma2(propsData + coder->PropsOffset, coder->PropsSize, inSize, inStream, outBufCur, outSizeCur, allocMain)); + } + #endif + #ifdef _7ZIP_PPMD_SUPPPORT + else if (coder->MethodID == k_PPMD) + { + RINOK(SzDecodePpmd(propsData + coder->PropsOffset, coder->PropsSize, inSize, inStream, outBufCur, outSizeCur, allocMain)); + } + #endif + else + return SZ_ERROR_UNSUPPORTED; + } + else if (coder->MethodID == k_BCJ2) + { + UInt64 offset = packPositions[1]; + UInt64 s3Size = packPositions[2] - offset; + + if (ci != 3) + return SZ_ERROR_UNSUPPORTED; + + tempSizes[2] = (SizeT)s3Size; + if (tempSizes[2] != s3Size) + return SZ_ERROR_MEM; + tempBuf[2] = (Byte *)IAlloc_Alloc(allocMain, tempSizes[2]); + if (!tempBuf[2] && tempSizes[2] != 0) + return SZ_ERROR_MEM; + + RINOK(LookInStream_SeekTo(inStream, startPos + offset)); + RINOK(SzDecodeCopy(s3Size, inStream, tempBuf[2])); + + if ((tempSizes[0] & 3) != 0 || + (tempSizes[1] & 3) != 0 || + tempSize3 + tempSizes[0] + tempSizes[1] != outSize) + return SZ_ERROR_DATA; + + { + CBcj2Dec p; + + p.bufs[0] = tempBuf3; p.lims[0] = tempBuf3 + tempSize3; + p.bufs[1] = tempBuf[0]; p.lims[1] = tempBuf[0] + tempSizes[0]; + p.bufs[2] = tempBuf[1]; p.lims[2] = tempBuf[1] + tempSizes[1]; + p.bufs[3] = tempBuf[2]; p.lims[3] = tempBuf[2] + tempSizes[2]; + + p.dest = outBuffer; + p.destLim = outBuffer + outSize; + + Bcj2Dec_Init(&p); + RINOK(Bcj2Dec_Decode(&p)); + + { + unsigned i; + for (i = 0; i < 4; i++) + if (p.bufs[i] != p.lims[i]) + return SZ_ERROR_DATA; + + if (!Bcj2Dec_IsFinished(&p)) + return SZ_ERROR_DATA; + + if (p.dest != p.destLim + || p.state != BCJ2_STREAM_MAIN) + return SZ_ERROR_DATA; + } + } + } + #ifndef _7Z_NO_METHODS_FILTERS + else if (ci == 1) + { + if (coder->MethodID == k_Delta) + { + if (coder->PropsSize != 1) + return SZ_ERROR_UNSUPPORTED; + { + Byte state[DELTA_STATE_SIZE]; + Delta_Init(state); + Delta_Decode(state, (unsigned)(propsData[coder->PropsOffset]) + 1, outBuffer, outSize); + } + } + else + { + if (coder->PropsSize != 0) + return SZ_ERROR_UNSUPPORTED; + switch (coder->MethodID) + { + case k_BCJ: + { + UInt32 state; + x86_Convert_Init(state); + x86_Convert(outBuffer, outSize, 0, &state, 0); + break; + } + CASE_BRA_CONV(PPC) + CASE_BRA_CONV(IA64) + CASE_BRA_CONV(SPARC) + CASE_BRA_CONV(ARM) + CASE_BRA_CONV(ARMT) + default: + return SZ_ERROR_UNSUPPORTED; + } + } + } + #endif + else + return SZ_ERROR_UNSUPPORTED; + } + + return SZ_OK; +} + + +static SRes SzAr_DecodeFolder(const CSzAr *p, UInt32 folderIndex, + ILookInStream *inStream, UInt64 startPos, + Byte *outBuffer, size_t outSize, + ISzAlloc *allocMain) +{ + SRes res; + CSzFolder folder; + CSzData sd; + + const Byte *data = p->CodersData + p->FoCodersOffsets[folderIndex]; + sd.Data = data; + sd.Size = p->FoCodersOffsets[folderIndex + 1] - p->FoCodersOffsets[folderIndex]; + + res = SzGetNextFolderItem(&folder, &sd); + + if (res != SZ_OK) + return res; + + if (sd.Size != 0 + || folder.UnpackStream != p->FoToMainUnpackSizeIndex[folderIndex] + || outSize != SzAr_GetFolderUnpackSize(p, folderIndex)) + return SZ_ERROR_FAIL; + { + unsigned i; + Byte *tempBuf[3] = { 0, 0, 0}; + + res = SzFolder_Decode2(&folder, data, + &p->CoderUnpackSizes[p->FoToCoderUnpackSizes[folderIndex]], + p->PackPositions + p->FoStartPackStreamIndex[folderIndex], + inStream, startPos, + outBuffer, (SizeT)outSize, allocMain, tempBuf); + + for (i = 0; i < 3; i++) + IAlloc_Free(allocMain, tempBuf[i]); + + if (res == SZ_OK) + if (SzBitWithVals_Check(&p->FolderCRCs, folderIndex)) + if (CrcCalc(outBuffer, outSize) != p->FolderCRCs.Vals[folderIndex]) + res = SZ_ERROR_CRC; + + return res; + } +} + +/* Bcj2.c -- BCJ2 Decoder (Converter for x86 code) +2015-08-01 : Igor Pavlov : Public domain */ + +/* +#include "Precomp.h" + +#include "Bcj2.h" +#include "CpuArch.h" +*/ + +#define CProb UInt16 + +#define kTopValue ((UInt32)1 << 24) +#define kNumModelBits 11 +#define kBitModelTotal (1 << kNumModelBits) +#define kNumMoveBits 5 + +#define _IF_BIT_0 ttt = *prob; bound = (p->range >> kNumModelBits) * ttt; if (p->code < bound) +#define _UPDATE_0 p->range = bound; *prob = (CProb)(ttt + ((kBitModelTotal - ttt) >> kNumMoveBits)); +#define _UPDATE_1 p->range -= bound; p->code -= bound; *prob = (CProb)(ttt - (ttt >> kNumMoveBits)); + +static void Bcj2Dec_Init(CBcj2Dec *p) +{ + unsigned i; + + p->state = BCJ2_DEC_STATE_OK; + p->ip = 0; + p->temp[3] = 0; + p->range = 0; + p->code = 0; + for (i = 0; i < sizeof(p->probs) / sizeof(p->probs[0]); i++) + p->probs[i] = kBitModelTotal >> 1; +} + +static SRes Bcj2Dec_Decode(CBcj2Dec *p) +{ + if (p->range <= 5) + { + p->state = BCJ2_DEC_STATE_OK; + for (; p->range != 5; p->range++) + { + if (p->range == 1 && p->code != 0) + return SZ_ERROR_DATA; + + if (p->bufs[BCJ2_STREAM_RC] == p->lims[BCJ2_STREAM_RC]) + { + p->state = BCJ2_STREAM_RC; + return SZ_OK; + } + + p->code = (p->code << 8) | *(p->bufs[BCJ2_STREAM_RC])++; + } + + if (p->code == 0xFFFFFFFF) + return SZ_ERROR_DATA; + + p->range = 0xFFFFFFFF; + } + else if (p->state >= BCJ2_DEC_STATE_ORIG_0) + { + while (p->state <= BCJ2_DEC_STATE_ORIG_3) + { + Byte *dest = p->dest; + if (dest == p->destLim) + return SZ_OK; + *dest = p->temp[p->state++ - BCJ2_DEC_STATE_ORIG_0]; + p->dest = dest + 1; + } + } + + /* + if (BCJ2_IS_32BIT_STREAM(p->state)) + { + const Byte *cur = p->bufs[p->state]; + if (cur == p->lims[p->state]) + return SZ_OK; + p->bufs[p->state] = cur + 4; + + { + UInt32 val; + Byte *dest; + SizeT rem; + + p->ip += 4; + val = GetBe32(cur) - p->ip; + dest = p->dest; + rem = p->destLim - dest; + if (rem < 4) + { + SizeT i; + SetUi32(p->temp, val); + for (i = 0; i < rem; i++) + dest[i] = p->temp[i]; + p->dest = dest + rem; + p->state = BCJ2_DEC_STATE_ORIG_0 + (unsigned)rem; + return SZ_OK; + } + SetUi32(dest, val); + p->temp[3] = (Byte)(val >> 24); + p->dest = dest + 4; + p->state = BCJ2_DEC_STATE_OK; + } + } + */ + + for (;;) + { + if (BCJ2_IS_32BIT_STREAM(p->state)) + p->state = BCJ2_DEC_STATE_OK; + else + { + if (p->range < kTopValue) + { + if (p->bufs[BCJ2_STREAM_RC] == p->lims[BCJ2_STREAM_RC]) + { + p->state = BCJ2_STREAM_RC; + return SZ_OK; + } + p->range <<= 8; + p->code = (p->code << 8) | *(p->bufs[BCJ2_STREAM_RC])++; + } + + { + const Byte *src = p->bufs[BCJ2_STREAM_MAIN]; + const Byte *srcLim; + Byte *dest; + SizeT num = p->lims[BCJ2_STREAM_MAIN] - src; + + if (num == 0) + { + p->state = BCJ2_STREAM_MAIN; + return SZ_OK; + } + + dest = p->dest; + if (num > (SizeT)(p->destLim - dest)) + { + num = p->destLim - dest; + if (num == 0) + { + p->state = BCJ2_DEC_STATE_ORIG; + return SZ_OK; + } + } + + srcLim = src + num; + + if (p->temp[3] == 0x0F && (src[0] & 0xF0) == 0x80) + *dest = src[0]; + else for (;;) + { + Byte b = *src; + *dest = b; + if (b != 0x0F) + { + if ((b & 0xFE) == 0xE8) + break; + dest++; + if (++src != srcLim) + continue; + break; + } + dest++; + if (++src == srcLim) + break; + if ((*src & 0xF0) != 0x80) + continue; + *dest = *src; + break; + } + + num = src - p->bufs[BCJ2_STREAM_MAIN]; + + if (src == srcLim) + { + p->temp[3] = src[-1]; + p->bufs[BCJ2_STREAM_MAIN] = src; + p->ip += (UInt32)num; + p->dest += num; + p->state = + p->bufs[BCJ2_STREAM_MAIN] == + p->lims[BCJ2_STREAM_MAIN] ? + (unsigned)BCJ2_STREAM_MAIN : + (unsigned)BCJ2_DEC_STATE_ORIG; + return SZ_OK; + } + + { + UInt32 bound, ttt; + CProb *prob; + Byte b = src[0]; + Byte prev = (Byte)(num == 0 ? p->temp[3] : src[-1]); + + p->temp[3] = b; + p->bufs[BCJ2_STREAM_MAIN] = src + 1; + num++; + p->ip += (UInt32)num; + p->dest += num; + + prob = p->probs + (unsigned)(b == 0xE8 ? 2 + (unsigned)prev : (b == 0xE9 ? 1 : 0)); + + _IF_BIT_0 + { + _UPDATE_0 + continue; + } + _UPDATE_1 + + } + } + } + + { + UInt32 val; + unsigned cj = (p->temp[3] == 0xE8) ? BCJ2_STREAM_CALL : BCJ2_STREAM_JUMP; + const Byte *cur = p->bufs[cj]; + Byte *dest; + SizeT rem; + + if (cur == p->lims[cj]) + { + p->state = cj; + break; + } + + val = GetBe32(cur); + p->bufs[cj] = cur + 4; + + p->ip += 4; + val -= p->ip; + dest = p->dest; + rem = p->destLim - dest; + + if (rem < 4) + { + SizeT i; + SetUi32(p->temp, val); + for (i = 0; i < rem; i++) + dest[i] = p->temp[i]; + p->dest = dest + rem; + p->state = BCJ2_DEC_STATE_ORIG_0 + (unsigned)rem; + break; + } + + SetUi32(dest, val); + p->temp[3] = (Byte)(val >> 24); + p->dest = dest + 4; + } + } + + if (p->range < kTopValue && p->bufs[BCJ2_STREAM_RC] != p->lims[BCJ2_STREAM_RC]) + { + p->range <<= 8; + p->code = (p->code << 8) | *(p->bufs[BCJ2_STREAM_RC])++; + } + + return SZ_OK; +} + +#undef kTopValue /* reused later. --ryan. */ +#undef kBitModelTotal /* reused later. --ryan. */ + + +/* Bra.c -- Converters for RISC code +2010-04-16 : Igor Pavlov : Public domain */ + +/* +#include "Precomp.h" + +#include "Bra.h" +*/ + +static SizeT ARM_Convert(Byte *data, SizeT size, UInt32 ip, int encoding) +{ + SizeT i; + if (size < 4) + return 0; + size -= 4; + ip += 8; + for (i = 0; i <= size; i += 4) + { + if (data[i + 3] == 0xEB) + { + UInt32 dest; + UInt32 src = ((UInt32)data[i + 2] << 16) | ((UInt32)data[i + 1] << 8) | (data[i + 0]); + src <<= 2; + if (encoding) + dest = ip + (UInt32)i + src; + else + dest = src - (ip + (UInt32)i); + dest >>= 2; + data[i + 2] = (Byte)(dest >> 16); + data[i + 1] = (Byte)(dest >> 8); + data[i + 0] = (Byte)dest; + } + } + return i; +} + +static SizeT ARMT_Convert(Byte *data, SizeT size, UInt32 ip, int encoding) +{ + SizeT i; + if (size < 4) + return 0; + size -= 4; + ip += 4; + for (i = 0; i <= size; i += 2) + { + if ((data[i + 1] & 0xF8) == 0xF0 && + (data[i + 3] & 0xF8) == 0xF8) + { + UInt32 dest; + UInt32 src = + (((UInt32)data[i + 1] & 0x7) << 19) | + ((UInt32)data[i + 0] << 11) | + (((UInt32)data[i + 3] & 0x7) << 8) | + (data[i + 2]); + + src <<= 1; + if (encoding) + dest = ip + (UInt32)i + src; + else + dest = src - (ip + (UInt32)i); + dest >>= 1; + + data[i + 1] = (Byte)(0xF0 | ((dest >> 19) & 0x7)); + data[i + 0] = (Byte)(dest >> 11); + data[i + 3] = (Byte)(0xF8 | ((dest >> 8) & 0x7)); + data[i + 2] = (Byte)dest; + i += 2; + } + } + return i; +} + +static SizeT PPC_Convert(Byte *data, SizeT size, UInt32 ip, int encoding) +{ + SizeT i; + if (size < 4) + return 0; + size -= 4; + for (i = 0; i <= size; i += 4) + { + if ((data[i] >> 2) == 0x12 && (data[i + 3] & 3) == 1) + { + UInt32 src = ((UInt32)(data[i + 0] & 3) << 24) | + ((UInt32)data[i + 1] << 16) | + ((UInt32)data[i + 2] << 8) | + ((UInt32)data[i + 3] & (~3)); + + UInt32 dest; + if (encoding) + dest = ip + (UInt32)i + src; + else + dest = src - (ip + (UInt32)i); + data[i + 0] = (Byte)(0x48 | ((dest >> 24) & 0x3)); + data[i + 1] = (Byte)(dest >> 16); + data[i + 2] = (Byte)(dest >> 8); + data[i + 3] &= 0x3; + data[i + 3] |= dest; + } + } + return i; +} + +static SizeT SPARC_Convert(Byte *data, SizeT size, UInt32 ip, int encoding) +{ + UInt32 i; + if (size < 4) + return 0; + size -= 4; + for (i = 0; i <= size; i += 4) + { + if ((data[i] == 0x40 && (data[i + 1] & 0xC0) == 0x00) || + (data[i] == 0x7F && (data[i + 1] & 0xC0) == 0xC0)) + { + UInt32 src = + ((UInt32)data[i + 0] << 24) | + ((UInt32)data[i + 1] << 16) | + ((UInt32)data[i + 2] << 8) | + ((UInt32)data[i + 3]); + UInt32 dest; + + src <<= 2; + if (encoding) + dest = ip + i + src; + else + dest = src - (ip + i); + dest >>= 2; + + dest = (((0 - ((dest >> 22) & 1)) << 22) & 0x3FFFFFFF) | (dest & 0x3FFFFF) | 0x40000000; + + data[i + 0] = (Byte)(dest >> 24); + data[i + 1] = (Byte)(dest >> 16); + data[i + 2] = (Byte)(dest >> 8); + data[i + 3] = (Byte)dest; + } + } + return i; +} + +/* Bra86.c -- Converter for x86 code (BCJ) +2013-11-12 : Igor Pavlov : Public domain */ + +/* +#include "Precomp.h" + +#include "Bra.h" +*/ + +#define Test86MSByte(b) ((((b) + 1) & 0xFE) == 0) + +static SizeT x86_Convert(Byte *data, SizeT size, UInt32 ip, UInt32 *state, int encoding) +{ + SizeT pos = 0; + UInt32 mask = *state & 7; + if (size < 5) + return 0; + size -= 4; + ip += 5; + + for (;;) + { + Byte *p = data + pos; + const Byte *limit = data + size; + for (; p < limit; p++) + if ((*p & 0xFE) == 0xE8) + break; + + { + SizeT d = (SizeT)(p - data - pos); + pos = (SizeT)(p - data); + if (p >= limit) + { + *state = (d > 2 ? 0 : mask >> (unsigned)d); + return pos; + } + if (d > 2) + mask = 0; + else + { + mask >>= (unsigned)d; + if (mask != 0 && (mask > 4 || mask == 3 || Test86MSByte(p[(mask >> 1) + 1]))) + { + mask = (mask >> 1) | 4; + pos++; + continue; + } + } + } + + if (Test86MSByte(p[4])) + { + UInt32 v = ((UInt32)p[4] << 24) | ((UInt32)p[3] << 16) | ((UInt32)p[2] << 8) | ((UInt32)p[1]); + UInt32 cur = ip + (UInt32)pos; + pos += 5; + if (encoding) + v += cur; + else + v -= cur; + if (mask != 0) + { + unsigned sh = (mask & 6) << 2; + if (Test86MSByte((Byte)(v >> sh))) + { + v ^= (((UInt32)0x100 << sh) - 1); + if (encoding) + v += cur; + else + v -= cur; + } + mask = 0; + } + p[1] = (Byte)v; + p[2] = (Byte)(v >> 8); + p[3] = (Byte)(v >> 16); + p[4] = (Byte)(0 - ((v >> 24) & 1)); + } + else + { + mask = (mask >> 1) | 4; + pos++; + } + } +} + + +/* BraIA64.c -- Converter for IA-64 code +2013-11-12 : Igor Pavlov : Public domain */ + +/* +#include "Precomp.h" + +#include "Bra.h" +*/ +static const Byte kBranchTable[32] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 4, 4, 6, 6, 0, 0, 7, 7, + 4, 4, 0, 0, 4, 4, 0, 0 +}; + +static SizeT IA64_Convert(Byte *data, SizeT size, UInt32 ip, int encoding) +{ + SizeT i; + if (size < 16) + return 0; + size -= 16; + for (i = 0; i <= size; i += 16) + { + UInt32 instrTemplate = data[i] & 0x1F; + UInt32 mask = kBranchTable[instrTemplate]; + UInt32 bitPos = 5; + int slot; + for (slot = 0; slot < 3; slot++, bitPos += 41) + { + UInt32 bytePos, bitRes; + UInt64 instruction, instNorm; + int j; + if (((mask >> slot) & 1) == 0) + continue; + bytePos = (bitPos >> 3); + bitRes = bitPos & 0x7; + instruction = 0; + for (j = 0; j < 6; j++) + instruction += (UInt64)data[i + j + bytePos] << (8 * j); + + instNorm = instruction >> bitRes; + if (((instNorm >> 37) & 0xF) == 0x5 && ((instNorm >> 9) & 0x7) == 0) + { + UInt32 src = (UInt32)((instNorm >> 13) & 0xFFFFF); + UInt32 dest; + src |= ((UInt32)(instNorm >> 36) & 1) << 20; + + src <<= 4; + + if (encoding) + dest = ip + (UInt32)i + src; + else + dest = src - (ip + (UInt32)i); + + dest >>= 4; + + instNorm &= ~((UInt64)(0x8FFFFF) << 13); + instNorm |= ((UInt64)(dest & 0xFFFFF) << 13); + instNorm |= ((UInt64)(dest & 0x100000) << (36 - 20)); + + instruction &= (1 << bitRes) - 1; + instruction |= (instNorm << bitRes); + for (j = 0; j < 6; j++) + data[i + j + bytePos] = (Byte)(instruction >> (8 * j)); + } + } + } + return i; +} + + +/* Delta.c -- Delta converter +2009-05-26 : Igor Pavlov : Public domain */ + +/* +#include "Precomp.h" + +#include "Delta.h" +*/ + +static void Delta_Init(Byte *state) +{ + unsigned i; + for (i = 0; i < DELTA_STATE_SIZE; i++) + state[i] = 0; +} + +static void MyMemCpy(Byte *dest, const Byte *src, unsigned size) +{ + unsigned i; + for (i = 0; i < size; i++) + dest[i] = src[i]; +} + +static void Delta_Decode(Byte *state, unsigned delta, Byte *data, SizeT size) +{ + Byte buf[DELTA_STATE_SIZE]; + unsigned j = 0; + MyMemCpy(buf, state, delta); + { + SizeT i; + for (i = 0; i < size;) + { + for (j = 0; j < delta && i < size; i++, j++) + { + buf[j] = data[i] = (Byte)(buf[j] + data[i]); + } + } + } + if (j == delta) + j = 0; + MyMemCpy(state, buf + j, delta - j); + MyMemCpy(state + delta - j, buf, j); +} + +/* LzmaDec.c -- LZMA Decoder +2016-05-16 : Igor Pavlov : Public domain */ + +/* +#include "Precomp.h" + +#include "LzmaDec.h" + +#include +*/ + +#define kNumTopBits 24 +#define kTopValue ((UInt32)1 << kNumTopBits) + +#define kNumBitModelTotalBits 11 +#define kBitModelTotal (1 << kNumBitModelTotalBits) +#define kNumMoveBits 5 + +#define RC_INIT_SIZE 5 + +#define NORMALIZE if (range < kTopValue) { range <<= 8; code = (code << 8) | (*buf++); } + +#define IF_BIT_0(p) ttt = *(p); NORMALIZE; bound = (range >> kNumBitModelTotalBits) * ttt; if (code < bound) +#define UPDATE_0(p) range = bound; *(p) = (CLzmaProb)(ttt + ((kBitModelTotal - ttt) >> kNumMoveBits)); +#define UPDATE_1(p) range -= bound; code -= bound; *(p) = (CLzmaProb)(ttt - (ttt >> kNumMoveBits)); +#define GET_BIT2(p, i, A0, A1) IF_BIT_0(p) \ + { UPDATE_0(p); i = (i + i); A0; } else \ + { UPDATE_1(p); i = (i + i) + 1; A1; } +#define GET_BIT(p, i) GET_BIT2(p, i, ; , ;) + +#define TREE_GET_BIT(probs, i) { GET_BIT((probs + i), i); } +#define TREE_DECODE(probs, limit, i) \ + { i = 1; do { TREE_GET_BIT(probs, i); } while (i < limit); i -= limit; } + +/* #define _LZMA_SIZE_OPT */ + +#ifdef _LZMA_SIZE_OPT +#define TREE_6_DECODE(probs, i) TREE_DECODE(probs, (1 << 6), i) +#else +#define TREE_6_DECODE(probs, i) \ + { i = 1; \ + TREE_GET_BIT(probs, i); \ + TREE_GET_BIT(probs, i); \ + TREE_GET_BIT(probs, i); \ + TREE_GET_BIT(probs, i); \ + TREE_GET_BIT(probs, i); \ + TREE_GET_BIT(probs, i); \ + i -= 0x40; } +#endif + +#define NORMAL_LITER_DEC GET_BIT(prob + symbol, symbol) +#define MATCHED_LITER_DEC \ + matchByte <<= 1; \ + bit = (matchByte & offs); \ + probLit = prob + offs + bit + symbol; \ + GET_BIT2(probLit, symbol, offs &= ~bit, offs &= bit) + +#define NORMALIZE_CHECK if (range < kTopValue) { if (buf >= bufLimit) return DUMMY_ERROR; range <<= 8; code = (code << 8) | (*buf++); } + +#define IF_BIT_0_CHECK(p) ttt = *(p); NORMALIZE_CHECK; bound = (range >> kNumBitModelTotalBits) * ttt; if (code < bound) +#define UPDATE_0_CHECK range = bound; +#define UPDATE_1_CHECK range -= bound; code -= bound; +#define GET_BIT2_CHECK(p, i, A0, A1) IF_BIT_0_CHECK(p) \ + { UPDATE_0_CHECK; i = (i + i); A0; } else \ + { UPDATE_1_CHECK; i = (i + i) + 1; A1; } +#define GET_BIT_CHECK(p, i) GET_BIT2_CHECK(p, i, ; , ;) +#define TREE_DECODE_CHECK(probs, limit, i) \ + { i = 1; do { GET_BIT_CHECK(probs + i, i) } while (i < limit); i -= limit; } + + +#define kNumPosBitsMax 4 +#define kNumPosStatesMax (1 << kNumPosBitsMax) + +#define kLenNumLowBits 3 +#define kLenNumLowSymbols (1 << kLenNumLowBits) +#define kLenNumMidBits 3 +#define kLenNumMidSymbols (1 << kLenNumMidBits) +#define kLenNumHighBits 8 +#define kLenNumHighSymbols (1 << kLenNumHighBits) + +#define LenChoice 0 +#define LenChoice2 (LenChoice + 1) +#define LenLow (LenChoice2 + 1) +#define LenMid (LenLow + (kNumPosStatesMax << kLenNumLowBits)) +#define LenHigh (LenMid + (kNumPosStatesMax << kLenNumMidBits)) +#define kNumLenProbs (LenHigh + kLenNumHighSymbols) + + +#define kNumStates 12 +#define kNumLitStates 7 + +#define kStartPosModelIndex 4 +#define kEndPosModelIndex 14 +#define kNumFullDistances (1 << (kEndPosModelIndex >> 1)) + +#define kNumPosSlotBits 6 +#define kNumLenToPosStates 4 + +#define kNumAlignBits 4 +#define kAlignTableSize (1 << kNumAlignBits) + +#define kMatchMinLen 2 +#define kMatchSpecLenStart (kMatchMinLen + kLenNumLowSymbols + kLenNumMidSymbols + kLenNumHighSymbols) + +#define IsMatch 0 +#define IsRep (IsMatch + (kNumStates << kNumPosBitsMax)) +#define IsRepG0 (IsRep + kNumStates) +#define IsRepG1 (IsRepG0 + kNumStates) +#define IsRepG2 (IsRepG1 + kNumStates) +#define IsRep0Long (IsRepG2 + kNumStates) +#define PosSlot (IsRep0Long + (kNumStates << kNumPosBitsMax)) +#define SpecPos (PosSlot + (kNumLenToPosStates << kNumPosSlotBits)) +#define Align (SpecPos + kNumFullDistances - kEndPosModelIndex) +#define LenCoder (Align + kAlignTableSize) +#define RepLenCoder (LenCoder + kNumLenProbs) +#define Literal (RepLenCoder + kNumLenProbs) + +#define LZMA_BASE_SIZE 1846 +#define LZMA_LIT_SIZE 0x300 + +#if Literal != LZMA_BASE_SIZE +StopCompilingDueBUG +#endif + +#define LzmaProps_GetNumProbs(p) (Literal + ((UInt32)LZMA_LIT_SIZE << ((p)->lc + (p)->lp))) + +#define LZMA_DIC_MIN (1 << 12) + +/* First LZMA-symbol is always decoded. +And it decodes new LZMA-symbols while (buf < bufLimit), but "buf" is without last normalization +Out: + Result: + SZ_OK - OK + SZ_ERROR_DATA - Error + p->remainLen: + < kMatchSpecLenStart : normal remain + = kMatchSpecLenStart : finished + = kMatchSpecLenStart + 1 : Flush marker (unused now) + = kMatchSpecLenStart + 2 : State Init Marker (unused now) +*/ + +static int MY_FAST_CALL LzmaDec_DecodeReal(CLzmaDec *p, SizeT limit, const Byte *bufLimit) +{ + CLzmaProb *probs = p->probs; + + unsigned state = p->state; + UInt32 rep0 = p->reps[0], rep1 = p->reps[1], rep2 = p->reps[2], rep3 = p->reps[3]; + unsigned pbMask = ((unsigned)1 << (p->prop.pb)) - 1; + unsigned lpMask = ((unsigned)1 << (p->prop.lp)) - 1; + unsigned lc = p->prop.lc; + + Byte *dic = p->dic; + SizeT dicBufSize = p->dicBufSize; + SizeT dicPos = p->dicPos; + + UInt32 processedPos = p->processedPos; + UInt32 checkDicSize = p->checkDicSize; + unsigned len = 0; + + const Byte *buf = p->buf; + UInt32 range = p->range; + UInt32 code = p->code; + + do + { + CLzmaProb *prob; + UInt32 bound; + unsigned ttt; + unsigned posState = processedPos & pbMask; + + prob = probs + IsMatch + (state << kNumPosBitsMax) + posState; + IF_BIT_0(prob) + { + unsigned symbol; + UPDATE_0(prob); + prob = probs + Literal; + if (processedPos != 0 || checkDicSize != 0) + prob += ((UInt32)LZMA_LIT_SIZE * (((processedPos & lpMask) << lc) + + (dic[(dicPos == 0 ? dicBufSize : dicPos) - 1] >> (8 - lc)))); + processedPos++; + + if (state < kNumLitStates) + { + state -= (state < 4) ? state : 3; + symbol = 1; + #ifdef _LZMA_SIZE_OPT + do { NORMAL_LITER_DEC } while (symbol < 0x100); + #else + NORMAL_LITER_DEC + NORMAL_LITER_DEC + NORMAL_LITER_DEC + NORMAL_LITER_DEC + NORMAL_LITER_DEC + NORMAL_LITER_DEC + NORMAL_LITER_DEC + NORMAL_LITER_DEC + #endif + } + else + { + unsigned matchByte = dic[dicPos - rep0 + (dicPos < rep0 ? dicBufSize : 0)]; + unsigned offs = 0x100; + state -= (state < 10) ? 3 : 6; + symbol = 1; + #ifdef _LZMA_SIZE_OPT + do + { + unsigned bit; + CLzmaProb *probLit; + MATCHED_LITER_DEC + } + while (symbol < 0x100); + #else + { + unsigned bit; + CLzmaProb *probLit; + MATCHED_LITER_DEC + MATCHED_LITER_DEC + MATCHED_LITER_DEC + MATCHED_LITER_DEC + MATCHED_LITER_DEC + MATCHED_LITER_DEC + MATCHED_LITER_DEC + MATCHED_LITER_DEC + } + #endif + } + + dic[dicPos++] = (Byte)symbol; + continue; + } + + { + UPDATE_1(prob); + prob = probs + IsRep + state; + IF_BIT_0(prob) + { + UPDATE_0(prob); + state += kNumStates; + prob = probs + LenCoder; + } + else + { + UPDATE_1(prob); + if (checkDicSize == 0 && processedPos == 0) + return SZ_ERROR_DATA; + prob = probs + IsRepG0 + state; + IF_BIT_0(prob) + { + UPDATE_0(prob); + prob = probs + IsRep0Long + (state << kNumPosBitsMax) + posState; + IF_BIT_0(prob) + { + UPDATE_0(prob); + dic[dicPos] = dic[dicPos - rep0 + (dicPos < rep0 ? dicBufSize : 0)]; + dicPos++; + processedPos++; + state = state < kNumLitStates ? 9 : 11; + continue; + } + UPDATE_1(prob); + } + else + { + UInt32 distance; + UPDATE_1(prob); + prob = probs + IsRepG1 + state; + IF_BIT_0(prob) + { + UPDATE_0(prob); + distance = rep1; + } + else + { + UPDATE_1(prob); + prob = probs + IsRepG2 + state; + IF_BIT_0(prob) + { + UPDATE_0(prob); + distance = rep2; + } + else + { + UPDATE_1(prob); + distance = rep3; + rep3 = rep2; + } + rep2 = rep1; + } + rep1 = rep0; + rep0 = distance; + } + state = state < kNumLitStates ? 8 : 11; + prob = probs + RepLenCoder; + } + + #ifdef _LZMA_SIZE_OPT + { + unsigned lim, offset; + CLzmaProb *probLen = prob + LenChoice; + IF_BIT_0(probLen) + { + UPDATE_0(probLen); + probLen = prob + LenLow + (posState << kLenNumLowBits); + offset = 0; + lim = (1 << kLenNumLowBits); + } + else + { + UPDATE_1(probLen); + probLen = prob + LenChoice2; + IF_BIT_0(probLen) + { + UPDATE_0(probLen); + probLen = prob + LenMid + (posState << kLenNumMidBits); + offset = kLenNumLowSymbols; + lim = (1 << kLenNumMidBits); + } + else + { + UPDATE_1(probLen); + probLen = prob + LenHigh; + offset = kLenNumLowSymbols + kLenNumMidSymbols; + lim = (1 << kLenNumHighBits); + } + } + TREE_DECODE(probLen, lim, len); + len += offset; + } + #else + { + CLzmaProb *probLen = prob + LenChoice; + IF_BIT_0(probLen) + { + UPDATE_0(probLen); + probLen = prob + LenLow + (posState << kLenNumLowBits); + len = 1; + TREE_GET_BIT(probLen, len); + TREE_GET_BIT(probLen, len); + TREE_GET_BIT(probLen, len); + len -= 8; + } + else + { + UPDATE_1(probLen); + probLen = prob + LenChoice2; + IF_BIT_0(probLen) + { + UPDATE_0(probLen); + probLen = prob + LenMid + (posState << kLenNumMidBits); + len = 1; + TREE_GET_BIT(probLen, len); + TREE_GET_BIT(probLen, len); + TREE_GET_BIT(probLen, len); + } + else + { + UPDATE_1(probLen); + probLen = prob + LenHigh; + TREE_DECODE(probLen, (1 << kLenNumHighBits), len); + len += kLenNumLowSymbols + kLenNumMidSymbols; + } + } + } + #endif + + if (state >= kNumStates) + { + UInt32 distance; + prob = probs + PosSlot + + ((len < kNumLenToPosStates ? len : kNumLenToPosStates - 1) << kNumPosSlotBits); + TREE_6_DECODE(prob, distance); + if (distance >= kStartPosModelIndex) + { + unsigned posSlot = (unsigned)distance; + unsigned numDirectBits = (unsigned)(((distance >> 1) - 1)); + distance = (2 | (distance & 1)); + if (posSlot < kEndPosModelIndex) + { + distance <<= numDirectBits; + prob = probs + SpecPos + distance - posSlot - 1; + { + UInt32 mask = 1; + unsigned i = 1; + do + { + GET_BIT2(prob + i, i, ; , distance |= mask); + mask <<= 1; + } + while (--numDirectBits != 0); + } + } + else + { + numDirectBits -= kNumAlignBits; + do + { + NORMALIZE + range >>= 1; + + { + UInt32 t; + code -= range; + t = (0 - ((UInt32)code >> 31)); /* (UInt32)((Int32)code >> 31) */ + distance = (distance << 1) + (t + 1); + code += range & t; + } + /* + distance <<= 1; + if (code >= range) + { + code -= range; + distance |= 1; + } + */ + } + while (--numDirectBits != 0); + prob = probs + Align; + distance <<= kNumAlignBits; + { + unsigned i = 1; + GET_BIT2(prob + i, i, ; , distance |= 1); + GET_BIT2(prob + i, i, ; , distance |= 2); + GET_BIT2(prob + i, i, ; , distance |= 4); + GET_BIT2(prob + i, i, ; , distance |= 8); + } + if (distance == (UInt32)0xFFFFFFFF) + { + len += kMatchSpecLenStart; + state -= kNumStates; + break; + } + } + } + + rep3 = rep2; + rep2 = rep1; + rep1 = rep0; + rep0 = distance + 1; + if (checkDicSize == 0) + { + if (distance >= processedPos) + { + p->dicPos = dicPos; + return SZ_ERROR_DATA; + } + } + else if (distance >= checkDicSize) + { + p->dicPos = dicPos; + return SZ_ERROR_DATA; + } + state = (state < kNumStates + kNumLitStates) ? kNumLitStates : kNumLitStates + 3; + } + + len += kMatchMinLen; + + { + SizeT rem; + unsigned curLen; + SizeT pos; + + if ((rem = limit - dicPos) == 0) + { + p->dicPos = dicPos; + return SZ_ERROR_DATA; + } + + curLen = ((rem < len) ? (unsigned)rem : len); + pos = dicPos - rep0 + (dicPos < rep0 ? dicBufSize : 0); + + processedPos += curLen; + + len -= curLen; + if (curLen <= dicBufSize - pos) + { + Byte *dest = dic + dicPos; + ptrdiff_t src = (ptrdiff_t)pos - (ptrdiff_t)dicPos; + const Byte *lim = dest + curLen; + dicPos += curLen; + do + *(dest) = (Byte)*(dest + src); + while (++dest != lim); + } + else + { + do + { + dic[dicPos++] = dic[pos]; + if (++pos == dicBufSize) + pos = 0; + } + while (--curLen != 0); + } + } + } + } + while (dicPos < limit && buf < bufLimit); + + NORMALIZE; + + p->buf = buf; + p->range = range; + p->code = code; + p->remainLen = len; + p->dicPos = dicPos; + p->processedPos = processedPos; + p->reps[0] = rep0; + p->reps[1] = rep1; + p->reps[2] = rep2; + p->reps[3] = rep3; + p->state = state; + + return SZ_OK; +} + +static void MY_FAST_CALL LzmaDec_WriteRem(CLzmaDec *p, SizeT limit) +{ + if (p->remainLen != 0 && p->remainLen < kMatchSpecLenStart) + { + Byte *dic = p->dic; + SizeT dicPos = p->dicPos; + SizeT dicBufSize = p->dicBufSize; + unsigned len = p->remainLen; + SizeT rep0 = p->reps[0]; /* we use SizeT to avoid the BUG of VC14 for AMD64 */ + SizeT rem = limit - dicPos; + if (rem < len) + len = (unsigned)(rem); + + if (p->checkDicSize == 0 && p->prop.dicSize - p->processedPos <= len) + p->checkDicSize = p->prop.dicSize; + + p->processedPos += len; + p->remainLen -= len; + while (len != 0) + { + len--; + dic[dicPos] = dic[dicPos - rep0 + (dicPos < rep0 ? dicBufSize : 0)]; + dicPos++; + } + p->dicPos = dicPos; + } +} + +static int MY_FAST_CALL LzmaDec_DecodeReal2(CLzmaDec *p, SizeT limit, const Byte *bufLimit) +{ + do + { + SizeT limit2 = limit; + if (p->checkDicSize == 0) + { + UInt32 rem = p->prop.dicSize - p->processedPos; + if (limit - p->dicPos > rem) + limit2 = p->dicPos + rem; + } + + RINOK(LzmaDec_DecodeReal(p, limit2, bufLimit)); + + if (p->checkDicSize == 0 && p->processedPos >= p->prop.dicSize) + p->checkDicSize = p->prop.dicSize; + + LzmaDec_WriteRem(p, limit); + } + while (p->dicPos < limit && p->buf < bufLimit && p->remainLen < kMatchSpecLenStart); + + if (p->remainLen > kMatchSpecLenStart) + p->remainLen = kMatchSpecLenStart; + + return 0; +} + +typedef enum +{ + DUMMY_ERROR, /* unexpected end of input stream */ + DUMMY_LIT, + DUMMY_MATCH, + DUMMY_REP +} ELzmaDummy; + +static ELzmaDummy LzmaDec_TryDummy(const CLzmaDec *p, const Byte *buf, SizeT inSize) +{ + UInt32 range = p->range; + UInt32 code = p->code; + const Byte *bufLimit = buf + inSize; + const CLzmaProb *probs = p->probs; + unsigned state = p->state; + ELzmaDummy res; + + { + const CLzmaProb *prob; + UInt32 bound; + unsigned ttt; + unsigned posState = (p->processedPos) & ((1 << p->prop.pb) - 1); + + prob = probs + IsMatch + (state << kNumPosBitsMax) + posState; + IF_BIT_0_CHECK(prob) + { + UPDATE_0_CHECK + + /* if (bufLimit - buf >= 7) return DUMMY_LIT; */ + + prob = probs + Literal; + if (p->checkDicSize != 0 || p->processedPos != 0) + prob += ((UInt32)LZMA_LIT_SIZE * + ((((p->processedPos) & ((1 << (p->prop.lp)) - 1)) << p->prop.lc) + + (p->dic[(p->dicPos == 0 ? p->dicBufSize : p->dicPos) - 1] >> (8 - p->prop.lc)))); + + if (state < kNumLitStates) + { + unsigned symbol = 1; + do { GET_BIT_CHECK(prob + symbol, symbol) } while (symbol < 0x100); + } + else + { + unsigned matchByte = p->dic[p->dicPos - p->reps[0] + + (p->dicPos < p->reps[0] ? p->dicBufSize : 0)]; + unsigned offs = 0x100; + unsigned symbol = 1; + do + { + unsigned bit; + const CLzmaProb *probLit; + matchByte <<= 1; + bit = (matchByte & offs); + probLit = prob + offs + bit + symbol; + GET_BIT2_CHECK(probLit, symbol, offs &= ~bit, offs &= bit) + } + while (symbol < 0x100); + } + res = DUMMY_LIT; + } + else + { + unsigned len; + UPDATE_1_CHECK; + + prob = probs + IsRep + state; + IF_BIT_0_CHECK(prob) + { + UPDATE_0_CHECK; + state = 0; + prob = probs + LenCoder; + res = DUMMY_MATCH; + } + else + { + UPDATE_1_CHECK; + res = DUMMY_REP; + prob = probs + IsRepG0 + state; + IF_BIT_0_CHECK(prob) + { + UPDATE_0_CHECK; + prob = probs + IsRep0Long + (state << kNumPosBitsMax) + posState; + IF_BIT_0_CHECK(prob) + { + UPDATE_0_CHECK; + NORMALIZE_CHECK; + return DUMMY_REP; + } + else + { + UPDATE_1_CHECK; + } + } + else + { + UPDATE_1_CHECK; + prob = probs + IsRepG1 + state; + IF_BIT_0_CHECK(prob) + { + UPDATE_0_CHECK; + } + else + { + UPDATE_1_CHECK; + prob = probs + IsRepG2 + state; + IF_BIT_0_CHECK(prob) + { + UPDATE_0_CHECK; + } + else + { + UPDATE_1_CHECK; + } + } + } + state = kNumStates; + prob = probs + RepLenCoder; + } + { + unsigned limit, offset; + const CLzmaProb *probLen = prob + LenChoice; + IF_BIT_0_CHECK(probLen) + { + UPDATE_0_CHECK; + probLen = prob + LenLow + (posState << kLenNumLowBits); + offset = 0; + limit = 1 << kLenNumLowBits; + } + else + { + UPDATE_1_CHECK; + probLen = prob + LenChoice2; + IF_BIT_0_CHECK(probLen) + { + UPDATE_0_CHECK; + probLen = prob + LenMid + (posState << kLenNumMidBits); + offset = kLenNumLowSymbols; + limit = 1 << kLenNumMidBits; + } + else + { + UPDATE_1_CHECK; + probLen = prob + LenHigh; + offset = kLenNumLowSymbols + kLenNumMidSymbols; + limit = 1 << kLenNumHighBits; + } + } + TREE_DECODE_CHECK(probLen, limit, len); + len += offset; + } + + if (state < 4) + { + unsigned posSlot; + prob = probs + PosSlot + + ((len < kNumLenToPosStates ? len : kNumLenToPosStates - 1) << + kNumPosSlotBits); + TREE_DECODE_CHECK(prob, 1 << kNumPosSlotBits, posSlot); + if (posSlot >= kStartPosModelIndex) + { + unsigned numDirectBits = ((posSlot >> 1) - 1); + + /* if (bufLimit - buf >= 8) return DUMMY_MATCH; */ + + if (posSlot < kEndPosModelIndex) + { + prob = probs + SpecPos + ((2 | (posSlot & 1)) << numDirectBits) - posSlot - 1; + } + else + { + numDirectBits -= kNumAlignBits; + do + { + NORMALIZE_CHECK + range >>= 1; + code -= range & (((code - range) >> 31) - 1); + /* if (code >= range) code -= range; */ + } + while (--numDirectBits != 0); + prob = probs + Align; + numDirectBits = kNumAlignBits; + } + { + unsigned i = 1; + do + { + GET_BIT_CHECK(prob + i, i); + } + while (--numDirectBits != 0); + } + } + } + } + } + NORMALIZE_CHECK; + return res; +} + + +static void LzmaDec_InitDicAndState(CLzmaDec *p, Bool initDic, Bool initState) +{ + p->needFlush = 1; + p->remainLen = 0; + p->tempBufSize = 0; + + if (initDic) + { + p->processedPos = 0; + p->checkDicSize = 0; + p->needInitState = 1; + } + if (initState) + p->needInitState = 1; +} + +static void LzmaDec_Init(CLzmaDec *p) +{ + p->dicPos = 0; + LzmaDec_InitDicAndState(p, True, True); +} + +static void LzmaDec_InitStateReal(CLzmaDec *p) +{ + SizeT numProbs = LzmaProps_GetNumProbs(&p->prop); + SizeT i; + CLzmaProb *probs = p->probs; + for (i = 0; i < numProbs; i++) + probs[i] = kBitModelTotal >> 1; + p->reps[0] = p->reps[1] = p->reps[2] = p->reps[3] = 1; + p->state = 0; + p->needInitState = 0; +} + +static SRes LzmaDec_DecodeToDic(CLzmaDec *p, SizeT dicLimit, const Byte *src, SizeT *srcLen, + ELzmaFinishMode finishMode, ELzmaStatus *status) +{ + SizeT inSize = *srcLen; + (*srcLen) = 0; + LzmaDec_WriteRem(p, dicLimit); + + *status = LZMA_STATUS_NOT_SPECIFIED; + + while (p->remainLen != kMatchSpecLenStart) + { + int checkEndMarkNow; + + if (p->needFlush) + { + for (; inSize > 0 && p->tempBufSize < RC_INIT_SIZE; (*srcLen)++, inSize--) + p->tempBuf[p->tempBufSize++] = *src++; + if (p->tempBufSize < RC_INIT_SIZE) + { + *status = LZMA_STATUS_NEEDS_MORE_INPUT; + return SZ_OK; + } + if (p->tempBuf[0] != 0) + return SZ_ERROR_DATA; + p->code = + ((UInt32)p->tempBuf[1] << 24) + | ((UInt32)p->tempBuf[2] << 16) + | ((UInt32)p->tempBuf[3] << 8) + | ((UInt32)p->tempBuf[4]); + p->range = 0xFFFFFFFF; + p->needFlush = 0; + p->tempBufSize = 0; + } + + checkEndMarkNow = 0; + if (p->dicPos >= dicLimit) + { + if (p->remainLen == 0 && p->code == 0) + { + *status = LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK; + return SZ_OK; + } + if (finishMode == LZMA_FINISH_ANY) + { + *status = LZMA_STATUS_NOT_FINISHED; + return SZ_OK; + } + if (p->remainLen != 0) + { + *status = LZMA_STATUS_NOT_FINISHED; + return SZ_ERROR_DATA; + } + checkEndMarkNow = 1; + } + + if (p->needInitState) + LzmaDec_InitStateReal(p); + + if (p->tempBufSize == 0) + { + SizeT processed; + const Byte *bufLimit; + if (inSize < LZMA_REQUIRED_INPUT_MAX || checkEndMarkNow) + { + int dummyRes = LzmaDec_TryDummy(p, src, inSize); + if (dummyRes == DUMMY_ERROR) + { + memcpy(p->tempBuf, src, inSize); + p->tempBufSize = (unsigned)inSize; + (*srcLen) += inSize; + *status = LZMA_STATUS_NEEDS_MORE_INPUT; + return SZ_OK; + } + if (checkEndMarkNow && dummyRes != DUMMY_MATCH) + { + *status = LZMA_STATUS_NOT_FINISHED; + return SZ_ERROR_DATA; + } + bufLimit = src; + } + else + bufLimit = src + inSize - LZMA_REQUIRED_INPUT_MAX; + p->buf = src; + if (LzmaDec_DecodeReal2(p, dicLimit, bufLimit) != 0) + return SZ_ERROR_DATA; + processed = (SizeT)(p->buf - src); + (*srcLen) += processed; + src += processed; + inSize -= processed; + } + else + { + unsigned rem = p->tempBufSize, lookAhead = 0; + while (rem < LZMA_REQUIRED_INPUT_MAX && lookAhead < inSize) + p->tempBuf[rem++] = src[lookAhead++]; + p->tempBufSize = rem; + if (rem < LZMA_REQUIRED_INPUT_MAX || checkEndMarkNow) + { + int dummyRes = LzmaDec_TryDummy(p, p->tempBuf, rem); + if (dummyRes == DUMMY_ERROR) + { + (*srcLen) += lookAhead; + *status = LZMA_STATUS_NEEDS_MORE_INPUT; + return SZ_OK; + } + if (checkEndMarkNow && dummyRes != DUMMY_MATCH) + { + *status = LZMA_STATUS_NOT_FINISHED; + return SZ_ERROR_DATA; + } + } + p->buf = p->tempBuf; + if (LzmaDec_DecodeReal2(p, dicLimit, p->buf) != 0) + return SZ_ERROR_DATA; + + { + unsigned kkk = (unsigned)(p->buf - p->tempBuf); + if (rem < kkk) + return SZ_ERROR_FAIL; /* some internal error */ + rem -= kkk; + if (lookAhead < rem) + return SZ_ERROR_FAIL; /* some internal error */ + lookAhead -= rem; + } + (*srcLen) += lookAhead; + src += lookAhead; + inSize -= lookAhead; + p->tempBufSize = 0; + } + } + if (p->code == 0) + *status = LZMA_STATUS_FINISHED_WITH_MARK; + return (p->code == 0) ? SZ_OK : SZ_ERROR_DATA; +} + +static void LzmaDec_FreeProbs(CLzmaDec *p, ISzAlloc *alloc) +{ + alloc->Free(alloc, p->probs); + p->probs = NULL; +} + +static SRes LzmaProps_Decode(CLzmaProps *p, const Byte *data, unsigned size) +{ + UInt32 dicSize; + Byte d; + + if (size < LZMA_PROPS_SIZE) + return SZ_ERROR_UNSUPPORTED; + else + dicSize = data[1] | ((UInt32)data[2] << 8) | ((UInt32)data[3] << 16) | ((UInt32)data[4] << 24); + + if (dicSize < LZMA_DIC_MIN) + dicSize = LZMA_DIC_MIN; + p->dicSize = dicSize; + + d = data[0]; + if (d >= (9 * 5 * 5)) + return SZ_ERROR_UNSUPPORTED; + + p->lc = d % 9; + d /= 9; + p->pb = d / 5; + p->lp = d % 5; + + return SZ_OK; +} + +static SRes LzmaDec_AllocateProbs2(CLzmaDec *p, const CLzmaProps *propNew, ISzAlloc *alloc) +{ + UInt32 numProbs = LzmaProps_GetNumProbs(propNew); + if (!p->probs || numProbs != p->numProbs) + { + LzmaDec_FreeProbs(p, alloc); + p->probs = (CLzmaProb *)alloc->Alloc(alloc, numProbs * sizeof(CLzmaProb)); + p->numProbs = numProbs; + if (!p->probs) + return SZ_ERROR_MEM; + } + return SZ_OK; +} + +static SRes LzmaDec_AllocateProbs(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAlloc *alloc) +{ + CLzmaProps propNew; + RINOK(LzmaProps_Decode(&propNew, props, propsSize)); + RINOK(LzmaDec_AllocateProbs2(p, &propNew, alloc)); + p->prop = propNew; + return SZ_OK; +} + +/* Lzma2Dec.c -- LZMA2 Decoder +2015-11-09 : Igor Pavlov : Public domain */ + +/* #define SHOW_DEBUG_INFO */ + +/* +#include "Precomp.h" + +#ifdef SHOW_DEBUG_INFO +#include +#endif + +#include + +#include "Lzma2Dec.h" +*/ + +/* +00000000 - EOS +00000001 U U - Uncompressed Reset Dic +00000010 U U - Uncompressed No Reset +100uuuuu U U P P - LZMA no reset +101uuuuu U U P P - LZMA reset state +110uuuuu U U P P S - LZMA reset state + new prop +111uuuuu U U P P S - LZMA reset state + new prop + reset dic + + u, U - Unpack Size + P - Pack Size + S - Props +*/ + +#define LZMA2_CONTROL_LZMA (1 << 7) +#define LZMA2_CONTROL_COPY_NO_RESET 2 +#define LZMA2_CONTROL_COPY_RESET_DIC 1 +#define LZMA2_CONTROL_EOF 0 + +#define LZMA2_IS_UNCOMPRESSED_STATE(p) (((p)->control & LZMA2_CONTROL_LZMA) == 0) + +#define LZMA2_GET_LZMA_MODE(p) (((p)->control >> 5) & 3) +#define LZMA2_IS_THERE_PROP(mode) ((mode) >= 2) + +#define LZMA2_LCLP_MAX 4 +#define LZMA2_DIC_SIZE_FROM_PROP(p) (((UInt32)2 | ((p) & 1)) << ((p) / 2 + 11)) + +#ifdef SHOW_DEBUG_INFO +#define PRF(x) x +#else +#define PRF(x) +#endif + +typedef enum +{ + LZMA2_STATE_CONTROL, + LZMA2_STATE_UNPACK0, + LZMA2_STATE_UNPACK1, + LZMA2_STATE_PACK0, + LZMA2_STATE_PACK1, + LZMA2_STATE_PROP, + LZMA2_STATE_DATA, + LZMA2_STATE_DATA_CONT, + LZMA2_STATE_FINISHED, + LZMA2_STATE_ERROR +} ELzma2State; + +static SRes Lzma2Dec_GetOldProps(Byte prop, Byte *props) +{ + UInt32 dicSize; + if (prop > 40) + return SZ_ERROR_UNSUPPORTED; + dicSize = (prop == 40) ? 0xFFFFFFFF : LZMA2_DIC_SIZE_FROM_PROP(prop); + props[0] = (Byte)LZMA2_LCLP_MAX; + props[1] = (Byte)(dicSize); + props[2] = (Byte)(dicSize >> 8); + props[3] = (Byte)(dicSize >> 16); + props[4] = (Byte)(dicSize >> 24); + return SZ_OK; +} + +static SRes Lzma2Dec_AllocateProbs(CLzma2Dec *p, Byte prop, ISzAlloc *alloc) +{ + Byte props[LZMA_PROPS_SIZE]; + RINOK(Lzma2Dec_GetOldProps(prop, props)); + return LzmaDec_AllocateProbs(&p->decoder, props, LZMA_PROPS_SIZE, alloc); +} + +static void Lzma2Dec_Init(CLzma2Dec *p) +{ + p->state = LZMA2_STATE_CONTROL; + p->needInitDic = True; + p->needInitState = True; + p->needInitProp = True; + LzmaDec_Init(&p->decoder); +} + +static ELzma2State Lzma2Dec_UpdateState(CLzma2Dec *p, Byte b) +{ + switch (p->state) + { + case LZMA2_STATE_CONTROL: + p->control = b; + PRF(printf("\n %4X ", (unsigned)p->decoder.dicPos)); + PRF(printf(" %2X", (unsigned)b)); + if (p->control == 0) + return LZMA2_STATE_FINISHED; + if (LZMA2_IS_UNCOMPRESSED_STATE(p)) + { + if ((p->control & 0x7F) > 2) + return LZMA2_STATE_ERROR; + p->unpackSize = 0; + } + else + p->unpackSize = (UInt32)(p->control & 0x1F) << 16; + return LZMA2_STATE_UNPACK0; + + case LZMA2_STATE_UNPACK0: + p->unpackSize |= (UInt32)b << 8; + return LZMA2_STATE_UNPACK1; + + case LZMA2_STATE_UNPACK1: + p->unpackSize |= (UInt32)b; + p->unpackSize++; + PRF(printf(" %8u", (unsigned)p->unpackSize)); + return (LZMA2_IS_UNCOMPRESSED_STATE(p)) ? LZMA2_STATE_DATA : LZMA2_STATE_PACK0; + + case LZMA2_STATE_PACK0: + p->packSize = (UInt32)b << 8; + return LZMA2_STATE_PACK1; + + case LZMA2_STATE_PACK1: + p->packSize |= (UInt32)b; + p->packSize++; + PRF(printf(" %8u", (unsigned)p->packSize)); + return LZMA2_IS_THERE_PROP(LZMA2_GET_LZMA_MODE(p)) ? LZMA2_STATE_PROP: + (p->needInitProp ? LZMA2_STATE_ERROR : LZMA2_STATE_DATA); + + case LZMA2_STATE_PROP: + { + unsigned lc, lp; + if (b >= (9 * 5 * 5)) + return LZMA2_STATE_ERROR; + lc = b % 9; + b /= 9; + p->decoder.prop.pb = b / 5; + lp = b % 5; + if (lc + lp > LZMA2_LCLP_MAX) + return LZMA2_STATE_ERROR; + p->decoder.prop.lc = lc; + p->decoder.prop.lp = lp; + p->needInitProp = False; + return LZMA2_STATE_DATA; + } + } + return LZMA2_STATE_ERROR; +} + +static void LzmaDec_UpdateWithUncompressed(CLzmaDec *p, const Byte *src, SizeT size) +{ + memcpy(p->dic + p->dicPos, src, size); + p->dicPos += size; + if (p->checkDicSize == 0 && p->prop.dicSize - p->processedPos <= size) + p->checkDicSize = p->prop.dicSize; + p->processedPos += (UInt32)size; +} + +static void LzmaDec_InitDicAndState(CLzmaDec *p, Bool initDic, Bool initState); + +static SRes Lzma2Dec_DecodeToDic(CLzma2Dec *p, SizeT dicLimit, + const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status) +{ + SizeT inSize = *srcLen; + *srcLen = 0; + *status = LZMA_STATUS_NOT_SPECIFIED; + + while (p->state != LZMA2_STATE_FINISHED) + { + SizeT dicPos = p->decoder.dicPos; + + if (p->state == LZMA2_STATE_ERROR) + return SZ_ERROR_DATA; + + if (dicPos == dicLimit && finishMode == LZMA_FINISH_ANY) + { + *status = LZMA_STATUS_NOT_FINISHED; + return SZ_OK; + } + + if (p->state != LZMA2_STATE_DATA && p->state != LZMA2_STATE_DATA_CONT) + { + if (*srcLen == inSize) + { + *status = LZMA_STATUS_NEEDS_MORE_INPUT; + return SZ_OK; + } + (*srcLen)++; + p->state = Lzma2Dec_UpdateState(p, *src++); + + if (dicPos == dicLimit && p->state != LZMA2_STATE_FINISHED) + { + p->state = LZMA2_STATE_ERROR; + return SZ_ERROR_DATA; + } + continue; + } + + { + SizeT destSizeCur = dicLimit - dicPos; + SizeT srcSizeCur = inSize - *srcLen; + ELzmaFinishMode curFinishMode = LZMA_FINISH_ANY; + + if (p->unpackSize <= destSizeCur) + { + destSizeCur = (SizeT)p->unpackSize; + curFinishMode = LZMA_FINISH_END; + } + + if (LZMA2_IS_UNCOMPRESSED_STATE(p)) + { + if (*srcLen == inSize) + { + *status = LZMA_STATUS_NEEDS_MORE_INPUT; + return SZ_OK; + } + + if (p->state == LZMA2_STATE_DATA) + { + Bool initDic = (p->control == LZMA2_CONTROL_COPY_RESET_DIC); + if (initDic) + p->needInitProp = p->needInitState = True; + else if (p->needInitDic) + { + p->state = LZMA2_STATE_ERROR; + return SZ_ERROR_DATA; + } + p->needInitDic = False; + LzmaDec_InitDicAndState(&p->decoder, initDic, False); + } + + if (srcSizeCur > destSizeCur) + srcSizeCur = destSizeCur; + + if (srcSizeCur == 0) + { + p->state = LZMA2_STATE_ERROR; + return SZ_ERROR_DATA; + } + + LzmaDec_UpdateWithUncompressed(&p->decoder, src, srcSizeCur); + + src += srcSizeCur; + *srcLen += srcSizeCur; + p->unpackSize -= (UInt32)srcSizeCur; + p->state = (p->unpackSize == 0) ? LZMA2_STATE_CONTROL : LZMA2_STATE_DATA_CONT; + } + else + { + SizeT outSizeProcessed; + SRes res; + + if (p->state == LZMA2_STATE_DATA) + { + unsigned mode = LZMA2_GET_LZMA_MODE(p); + Bool initDic = (mode == 3); + Bool initState = (mode != 0); + if ((!initDic && p->needInitDic) || (!initState && p->needInitState)) + { + p->state = LZMA2_STATE_ERROR; + return SZ_ERROR_DATA; + } + + LzmaDec_InitDicAndState(&p->decoder, initDic, initState); + p->needInitDic = False; + p->needInitState = False; + p->state = LZMA2_STATE_DATA_CONT; + } + + if (srcSizeCur > p->packSize) + srcSizeCur = (SizeT)p->packSize; + + res = LzmaDec_DecodeToDic(&p->decoder, dicPos + destSizeCur, src, &srcSizeCur, curFinishMode, status); + + src += srcSizeCur; + *srcLen += srcSizeCur; + p->packSize -= (UInt32)srcSizeCur; + + outSizeProcessed = p->decoder.dicPos - dicPos; + p->unpackSize -= (UInt32)outSizeProcessed; + + RINOK(res); + if (*status == LZMA_STATUS_NEEDS_MORE_INPUT) + return res; + + if (srcSizeCur == 0 && outSizeProcessed == 0) + { + if (*status != LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK + || p->unpackSize != 0 + || p->packSize != 0) + { + p->state = LZMA2_STATE_ERROR; + return SZ_ERROR_DATA; + } + p->state = LZMA2_STATE_CONTROL; + } + + if (*status == LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK) + *status = LZMA_STATUS_NOT_FINISHED; + } + } + } + + *status = LZMA_STATUS_FINISHED_WITH_MARK; + return SZ_OK; +} + +#endif /* _INCLUDE_PHYSFS_LZMASDK_H_ */ + +/* end of physfs_lzmasdk.h ... */ + diff --git a/Source/Dependencies/PhysicsFS/physfs_miniz.h b/Source/Dependencies/PhysicsFS/physfs_miniz.h new file mode 100644 index 000000000..30d6f4cc7 --- /dev/null +++ b/Source/Dependencies/PhysicsFS/physfs_miniz.h @@ -0,0 +1,701 @@ +/* tinfl.c v1.11 - public domain inflate with zlib header parsing/adler32 checking (inflate-only subset of miniz.c) + See "unlicense" statement at the end of this file. + Rich Geldreich , last updated May 20, 2011 + Implements RFC 1950: https://www.ietf.org/rfc/rfc1950.txt and RFC 1951: https://www.ietf.org/rfc/rfc1951.txt + + The entire decompressor coroutine is implemented in tinfl_decompress(). The other functions are optional high-level helpers. +*/ +#ifndef TINFL_HEADER_INCLUDED +#define TINFL_HEADER_INCLUDED + +typedef PHYSFS_uint8 mz_uint8; +typedef PHYSFS_sint16 mz_int16; +typedef PHYSFS_uint16 mz_uint16; +typedef PHYSFS_uint32 mz_uint32; +typedef unsigned int mz_uint; +typedef PHYSFS_uint64 mz_uint64; + +/* For more compatibility with zlib, miniz.c uses unsigned long for some parameters/struct members. */ +typedef unsigned long mz_ulong; + +/* Heap allocation callbacks. */ +typedef void *(*mz_alloc_func)(void *opaque, unsigned int items, unsigned int size); +typedef void (*mz_free_func)(void *opaque, void *address); + +#if defined(_M_IX86) || defined(_M_X64) +/* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 if integer loads and stores to unaligned addresses are acceptable on the target platform (slightly faster). */ +#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 +/* Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. */ +#define MINIZ_LITTLE_ENDIAN 1 +#endif + +#if defined(_WIN64) || defined(__MINGW64__) || defined(_LP64) || defined(__LP64__) +/* Set MINIZ_HAS_64BIT_REGISTERS to 1 if the processor has 64-bit general purpose registers (enables 64-bit bitbuffer in inflator) */ +#define MINIZ_HAS_64BIT_REGISTERS 1 +#endif + +/* Works around MSVC's spammy "warning C4127: conditional expression is constant" message. */ +#ifdef _MSC_VER +#define MZ_MACRO_END while (0, 0) +#else +#define MZ_MACRO_END while (0) +#endif + +/* Decompression flags. */ +enum +{ + TINFL_FLAG_PARSE_ZLIB_HEADER = 1, + TINFL_FLAG_HAS_MORE_INPUT = 2, + TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF = 4, + TINFL_FLAG_COMPUTE_ADLER32 = 8 +}; + +struct tinfl_decompressor_tag; typedef struct tinfl_decompressor_tag tinfl_decompressor; + +/* Max size of LZ dictionary. */ +#define TINFL_LZ_DICT_SIZE 32768 + +/* Return status. */ +typedef enum +{ + TINFL_STATUS_BAD_PARAM = -3, + TINFL_STATUS_ADLER32_MISMATCH = -2, + TINFL_STATUS_FAILED = -1, + TINFL_STATUS_DONE = 0, + TINFL_STATUS_NEEDS_MORE_INPUT = 1, + TINFL_STATUS_HAS_MORE_OUTPUT = 2 +} tinfl_status; + +/* Initializes the decompressor to its initial state. */ +#define tinfl_init(r) do { (r)->m_state = 0; } MZ_MACRO_END +#define tinfl_get_adler32(r) (r)->m_check_adler32 + +/* Main low-level decompressor coroutine function. This is the only function actually needed for decompression. All the other functions are just high-level helpers for improved usability. */ +/* This is a universal API, i.e. it can be used as a building block to build any desired higher level decompression API. In the limit case, it can be called once per every byte input or output. */ +static tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags); + +/* Internal/private bits follow. */ +enum +{ + TINFL_MAX_HUFF_TABLES = 3, TINFL_MAX_HUFF_SYMBOLS_0 = 288, TINFL_MAX_HUFF_SYMBOLS_1 = 32, TINFL_MAX_HUFF_SYMBOLS_2 = 19, + TINFL_FAST_LOOKUP_BITS = 10, TINFL_FAST_LOOKUP_SIZE = 1 << TINFL_FAST_LOOKUP_BITS +}; + +typedef struct +{ + mz_uint8 m_code_size[TINFL_MAX_HUFF_SYMBOLS_0]; + mz_int16 m_look_up[TINFL_FAST_LOOKUP_SIZE], m_tree[TINFL_MAX_HUFF_SYMBOLS_0 * 2]; +} tinfl_huff_table; + +#if MINIZ_HAS_64BIT_REGISTERS + #define TINFL_USE_64BIT_BITBUF 1 +#endif + +#if TINFL_USE_64BIT_BITBUF + typedef mz_uint64 tinfl_bit_buf_t; + #define TINFL_BITBUF_SIZE (64) +#else + typedef mz_uint32 tinfl_bit_buf_t; + #define TINFL_BITBUF_SIZE (32) +#endif + +struct tinfl_decompressor_tag +{ + mz_uint32 m_state, m_num_bits, m_zhdr0, m_zhdr1, m_z_adler32, m_final, m_type, m_check_adler32, m_dist, m_counter, m_num_extra, m_table_sizes[TINFL_MAX_HUFF_TABLES]; + tinfl_bit_buf_t m_bit_buf; + size_t m_dist_from_out_buf_start; + tinfl_huff_table m_tables[TINFL_MAX_HUFF_TABLES]; + mz_uint8 m_raw_header[4], m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137]; +}; + +#endif /* #ifdef TINFL_HEADER_INCLUDED */ + +/* ------------------- End of Header: Implementation follows. (If you only want the header, define MINIZ_HEADER_FILE_ONLY.) */ + +#ifndef TINFL_HEADER_FILE_ONLY + +#define MZ_MAX(a,b) (((a)>(b))?(a):(b)) +#define MZ_MIN(a,b) (((a)<(b))?(a):(b)) +#define MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj)) + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + #define MZ_READ_LE16(p) *((const mz_uint16 *)(p)) + #define MZ_READ_LE32(p) *((const mz_uint32 *)(p)) +#else + #define MZ_READ_LE16(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U)) + #define MZ_READ_LE32(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U) | ((mz_uint32)(((const mz_uint8 *)(p))[2]) << 16U) | ((mz_uint32)(((const mz_uint8 *)(p))[3]) << 24U)) +#endif + +#define TINFL_MEMCPY(d, s, l) memcpy(d, s, l) +#define TINFL_MEMSET(p, c, l) memset(p, c, l) + +#define TINFL_CR_BEGIN switch(r->m_state) { case 0: +#define TINFL_CR_RETURN(state_index, result) do { status = result; r->m_state = state_index; goto common_exit; case state_index:; } MZ_MACRO_END +#define TINFL_CR_RETURN_FOREVER(state_index, result) do { for ( ; ; ) { TINFL_CR_RETURN(state_index, result); } } MZ_MACRO_END +#define TINFL_CR_FINISH } + +/* TODO: If the caller has indicated that there's no more input, and we attempt to read beyond the input buf, then something is wrong with the input because the inflator never */ +/* reads ahead more than it needs to. Currently TINFL_GET_BYTE() pads the end of the stream with 0's in this scenario. */ +#define TINFL_GET_BYTE(state_index, c) do { \ + if (pIn_buf_cur >= pIn_buf_end) { \ + for ( ; ; ) { \ + if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) { \ + TINFL_CR_RETURN(state_index, TINFL_STATUS_NEEDS_MORE_INPUT); \ + if (pIn_buf_cur < pIn_buf_end) { \ + c = *pIn_buf_cur++; \ + break; \ + } \ + } else { \ + c = 0; \ + break; \ + } \ + } \ + } else c = *pIn_buf_cur++; } MZ_MACRO_END + +#define TINFL_NEED_BITS(state_index, n) do { mz_uint c; TINFL_GET_BYTE(state_index, c); bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); num_bits += 8; } while (num_bits < (mz_uint)(n)) +#define TINFL_SKIP_BITS(state_index, n) do { if (num_bits < (mz_uint)(n)) { TINFL_NEED_BITS(state_index, n); } bit_buf >>= (n); num_bits -= (n); } MZ_MACRO_END +#define TINFL_GET_BITS(state_index, b, n) do { if (num_bits < (mz_uint)(n)) { TINFL_NEED_BITS(state_index, n); } b = bit_buf & ((1 << (n)) - 1); bit_buf >>= (n); num_bits -= (n); } MZ_MACRO_END + +/* TINFL_HUFF_BITBUF_FILL() is only used rarely, when the number of bytes remaining in the input buffer falls below 2. */ +/* It reads just enough bytes from the input stream that are needed to decode the next Huffman code (and absolutely no more). It works by trying to fully decode a */ +/* Huffman code by using whatever bits are currently present in the bit buffer. If this fails, it reads another byte, and tries again until it succeeds or until the */ +/* bit buffer contains >=15 bits (deflate's max. Huffman code size). */ +#define TINFL_HUFF_BITBUF_FILL(state_index, pHuff) \ + do { \ + temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]; \ + if (temp >= 0) { \ + code_len = temp >> 9; \ + if ((code_len) && (num_bits >= code_len)) \ + break; \ + } else if (num_bits > TINFL_FAST_LOOKUP_BITS) { \ + code_len = TINFL_FAST_LOOKUP_BITS; \ + do { \ + temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ + } while ((temp < 0) && (num_bits >= (code_len + 1))); if (temp >= 0) break; \ + } TINFL_GET_BYTE(state_index, c); bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); num_bits += 8; \ + } while (num_bits < 15); + +/* TINFL_HUFF_DECODE() decodes the next Huffman coded symbol. It's more complex than you would initially expect because the zlib API expects the decompressor to never read */ +/* beyond the final byte of the deflate stream. (In other words, when this macro wants to read another byte from the input, it REALLY needs another byte in order to fully */ +/* decode the next Huffman code.) Handling this properly is particularly important on raw deflate (non-zlib) streams, which aren't followed by a byte aligned adler-32. */ +/* The slow path is only executed at the very end of the input buffer. */ +#define TINFL_HUFF_DECODE(state_index, sym, pHuff) do { \ + int temp; mz_uint code_len, c; \ + if (num_bits < 15) { \ + if ((pIn_buf_end - pIn_buf_cur) < 2) { \ + TINFL_HUFF_BITBUF_FILL(state_index, pHuff); \ + } else { \ + bit_buf |= (((tinfl_bit_buf_t)pIn_buf_cur[0]) << num_bits) | (((tinfl_bit_buf_t)pIn_buf_cur[1]) << (num_bits + 8)); pIn_buf_cur += 2; num_bits += 16; \ + } \ + } \ + if ((temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) \ + code_len = temp >> 9, temp &= 511; \ + else { \ + code_len = TINFL_FAST_LOOKUP_BITS; do { temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; } while (temp < 0); \ + } sym = temp; bit_buf >>= code_len; num_bits -= code_len; } MZ_MACRO_END + +static tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags) +{ + static const int s_length_base[31] = { 3,4,5,6,7,8,9,10,11,13, 15,17,19,23,27,31,35,43,51,59, 67,83,99,115,131,163,195,227,258,0,0 }; + static const int s_length_extra[31]= { 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + static const int s_dist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, 257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + static const int s_dist_extra[32] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + static const mz_uint8 s_length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + static const int s_min_table_sizes[3] = { 257, 1, 4 }; + + tinfl_status status = TINFL_STATUS_FAILED; mz_uint32 num_bits, dist, counter, num_extra; tinfl_bit_buf_t bit_buf; + const mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = pIn_buf_next + *pIn_buf_size; + mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = pOut_buf_next + *pOut_buf_size; + size_t out_buf_size_mask = (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF) ? (size_t)-1 : ((pOut_buf_next - pOut_buf_start) + *pOut_buf_size) - 1, dist_from_out_buf_start; + + /* Ensure the output buffer's size is a power of 2, unless the output buffer is large enough to hold the entire output file (in which case it doesn't matter). */ + if (((out_buf_size_mask + 1) & out_buf_size_mask) || (pOut_buf_next < pOut_buf_start)) { *pIn_buf_size = *pOut_buf_size = 0; return TINFL_STATUS_BAD_PARAM; } + + num_bits = r->m_num_bits; bit_buf = r->m_bit_buf; dist = r->m_dist; counter = r->m_counter; num_extra = r->m_num_extra; dist_from_out_buf_start = r->m_dist_from_out_buf_start; + TINFL_CR_BEGIN + + bit_buf = num_bits = dist = counter = num_extra = r->m_zhdr0 = r->m_zhdr1 = 0; r->m_z_adler32 = r->m_check_adler32 = 1; + if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) + { + TINFL_GET_BYTE(1, r->m_zhdr0); TINFL_GET_BYTE(2, r->m_zhdr1); + counter = (((r->m_zhdr0 * 256 + r->m_zhdr1) % 31 != 0) || (r->m_zhdr1 & 32) || ((r->m_zhdr0 & 15) != 8)); + if (!(decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || ((out_buf_size_mask + 1) < (size_t)(1U << (8U + (r->m_zhdr0 >> 4))))); + if (counter) { TINFL_CR_RETURN_FOREVER(36, TINFL_STATUS_FAILED); } + } + + do + { + TINFL_GET_BITS(3, r->m_final, 3); r->m_type = r->m_final >> 1; + if (r->m_type == 0) + { + TINFL_SKIP_BITS(5, num_bits & 7); + for (counter = 0; counter < 4; ++counter) { if (num_bits) TINFL_GET_BITS(6, r->m_raw_header[counter], 8); else TINFL_GET_BYTE(7, r->m_raw_header[counter]); } + if ((counter = (r->m_raw_header[0] | (r->m_raw_header[1] << 8))) != (mz_uint)(0xFFFF ^ (r->m_raw_header[2] | (r->m_raw_header[3] << 8)))) { TINFL_CR_RETURN_FOREVER(39, TINFL_STATUS_FAILED); } + while ((counter) && (num_bits)) + { + TINFL_GET_BITS(51, dist, 8); + while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(52, TINFL_STATUS_HAS_MORE_OUTPUT); } + *pOut_buf_cur++ = (mz_uint8)dist; + counter--; + } + while (counter) + { + size_t n; while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(9, TINFL_STATUS_HAS_MORE_OUTPUT); } + while (pIn_buf_cur >= pIn_buf_end) + { + if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) + { + TINFL_CR_RETURN(38, TINFL_STATUS_NEEDS_MORE_INPUT); + } + else + { + TINFL_CR_RETURN_FOREVER(40, TINFL_STATUS_FAILED); + } + } + n = MZ_MIN(MZ_MIN((size_t)(pOut_buf_end - pOut_buf_cur), (size_t)(pIn_buf_end - pIn_buf_cur)), counter); + TINFL_MEMCPY(pOut_buf_cur, pIn_buf_cur, n); pIn_buf_cur += n; pOut_buf_cur += n; counter -= (mz_uint)n; + } + } + else if (r->m_type == 3) + { + TINFL_CR_RETURN_FOREVER(10, TINFL_STATUS_FAILED); + } + else + { + if (r->m_type == 1) + { + mz_uint8 *p = r->m_tables[0].m_code_size; mz_uint i; + r->m_table_sizes[0] = 288; r->m_table_sizes[1] = 32; TINFL_MEMSET(r->m_tables[1].m_code_size, 5, 32); + for ( i = 0; i <= 143; ++i) *p++ = 8; + for ( ; i <= 255; ++i) *p++ = 9; + for ( ; i <= 279; ++i) *p++ = 7; + for ( ; i <= 287; ++i) *p++ = 8; + } + else + { + for (counter = 0; counter < 3; counter++) { TINFL_GET_BITS(11, r->m_table_sizes[counter], "\05\05\04"[counter]); r->m_table_sizes[counter] += s_min_table_sizes[counter]; } + MZ_CLEAR_OBJ(r->m_tables[2].m_code_size); for (counter = 0; counter < r->m_table_sizes[2]; counter++) { mz_uint s; TINFL_GET_BITS(14, s, 3); r->m_tables[2].m_code_size[s_length_dezigzag[counter]] = (mz_uint8)s; } + r->m_table_sizes[2] = 19; + } + for ( ; (int)r->m_type >= 0; r->m_type--) + { + int tree_next, tree_cur; tinfl_huff_table *pTable; + mz_uint i, j, used_syms, total, sym_index, next_code[17], total_syms[16]; pTable = &r->m_tables[r->m_type]; MZ_CLEAR_OBJ(total_syms); MZ_CLEAR_OBJ(pTable->m_look_up); MZ_CLEAR_OBJ(pTable->m_tree); + for (i = 0; i < r->m_table_sizes[r->m_type]; ++i) total_syms[pTable->m_code_size[i]]++; + used_syms = 0, total = 0; next_code[0] = next_code[1] = 0; + for (i = 1; i <= 15; ++i) { used_syms += total_syms[i]; next_code[i + 1] = (total = ((total + total_syms[i]) << 1)); } + if ((65536 != total) && (used_syms > 1)) + { + TINFL_CR_RETURN_FOREVER(35, TINFL_STATUS_FAILED); + } + for (tree_next = -1, sym_index = 0; sym_index < r->m_table_sizes[r->m_type]; ++sym_index) + { + mz_uint rev_code = 0, l, cur_code, code_size = pTable->m_code_size[sym_index]; if (!code_size) continue; + cur_code = next_code[code_size]++; for (l = code_size; l > 0; l--, cur_code >>= 1) rev_code = (rev_code << 1) | (cur_code & 1); + if (code_size <= TINFL_FAST_LOOKUP_BITS) { mz_int16 k = (mz_int16)((code_size << 9) | sym_index); while (rev_code < TINFL_FAST_LOOKUP_SIZE) { pTable->m_look_up[rev_code] = k; rev_code += (1 << code_size); } continue; } + if (0 == (tree_cur = pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)])) { pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } + rev_code >>= (TINFL_FAST_LOOKUP_BITS - 1); + for (j = code_size; j > (TINFL_FAST_LOOKUP_BITS + 1); j--) + { + tree_cur -= ((rev_code >>= 1) & 1); + if (!pTable->m_tree[-tree_cur - 1]) { pTable->m_tree[-tree_cur - 1] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } else tree_cur = pTable->m_tree[-tree_cur - 1]; + } + tree_cur -= ((rev_code >>= 1) & 1); pTable->m_tree[-tree_cur - 1] = (mz_int16)sym_index; + } + if (r->m_type == 2) + { + for (counter = 0; counter < (r->m_table_sizes[0] + r->m_table_sizes[1]); ) + { + mz_uint s; TINFL_HUFF_DECODE(16, dist, &r->m_tables[2]); if (dist < 16) { r->m_len_codes[counter++] = (mz_uint8)dist; continue; } + if ((dist == 16) && (!counter)) + { + TINFL_CR_RETURN_FOREVER(17, TINFL_STATUS_FAILED); + } + num_extra = "\02\03\07"[dist - 16]; TINFL_GET_BITS(18, s, num_extra); s += "\03\03\013"[dist - 16]; + TINFL_MEMSET(r->m_len_codes + counter, (dist == 16) ? r->m_len_codes[counter - 1] : 0, s); counter += s; + } + if ((r->m_table_sizes[0] + r->m_table_sizes[1]) != counter) + { + TINFL_CR_RETURN_FOREVER(21, TINFL_STATUS_FAILED); + } + TINFL_MEMCPY(r->m_tables[0].m_code_size, r->m_len_codes, r->m_table_sizes[0]); TINFL_MEMCPY(r->m_tables[1].m_code_size, r->m_len_codes + r->m_table_sizes[0], r->m_table_sizes[1]); + } + } + for ( ; ; ) + { + mz_uint8 *pSrc; + for ( ; ; ) + { + if (((pIn_buf_end - pIn_buf_cur) < 4) || ((pOut_buf_end - pOut_buf_cur) < 2)) + { + TINFL_HUFF_DECODE(23, counter, &r->m_tables[0]); + if (counter >= 256) + break; + while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(24, TINFL_STATUS_HAS_MORE_OUTPUT); } + *pOut_buf_cur++ = (mz_uint8)counter; + } + else + { + int sym2; mz_uint code_len; +#if TINFL_USE_64BIT_BITBUF + if (num_bits < 30) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE32(pIn_buf_cur)) << num_bits); pIn_buf_cur += 4; num_bits += 32; } +#else + if (num_bits < 15) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; } +#endif + if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) + code_len = sym2 >> 9; + else + { + code_len = TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0); + } + counter = sym2; bit_buf >>= code_len; num_bits -= code_len; + if (counter & 256) + break; + +#if !TINFL_USE_64BIT_BITBUF + if (num_bits < 15) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; } +#endif + if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) + code_len = sym2 >> 9; + else + { + code_len = TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0); + } + bit_buf >>= code_len; num_bits -= code_len; + + pOut_buf_cur[0] = (mz_uint8)counter; + if (sym2 & 256) + { + pOut_buf_cur++; + counter = sym2; + break; + } + pOut_buf_cur[1] = (mz_uint8)sym2; + pOut_buf_cur += 2; + } + } + if ((counter &= 511) == 256) break; + + num_extra = s_length_extra[counter - 257]; counter = s_length_base[counter - 257]; + if (num_extra) { mz_uint extra_bits; TINFL_GET_BITS(25, extra_bits, num_extra); counter += extra_bits; } + + TINFL_HUFF_DECODE(26, dist, &r->m_tables[1]); + num_extra = s_dist_extra[dist]; dist = s_dist_base[dist]; + if (num_extra) { mz_uint extra_bits; TINFL_GET_BITS(27, extra_bits, num_extra); dist += extra_bits; } + + dist_from_out_buf_start = pOut_buf_cur - pOut_buf_start; + if ((dist > dist_from_out_buf_start) && (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) + { + TINFL_CR_RETURN_FOREVER(37, TINFL_STATUS_FAILED); + } + + pSrc = pOut_buf_start + ((dist_from_out_buf_start - dist) & out_buf_size_mask); + + if ((MZ_MAX(pOut_buf_cur, pSrc) + counter) > pOut_buf_end) + { + while (counter--) + { + while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(53, TINFL_STATUS_HAS_MORE_OUTPUT); } + *pOut_buf_cur++ = pOut_buf_start[(dist_from_out_buf_start++ - dist) & out_buf_size_mask]; + } + continue; + } +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES + else if ((counter >= 9) && (counter <= dist)) + { + const mz_uint8 *pSrc_end = pSrc + (counter & ~7); + do + { + ((mz_uint32 *)pOut_buf_cur)[0] = ((const mz_uint32 *)pSrc)[0]; + ((mz_uint32 *)pOut_buf_cur)[1] = ((const mz_uint32 *)pSrc)[1]; + pOut_buf_cur += 8; + } while ((pSrc += 8) < pSrc_end); + if ((counter &= 7) < 3) + { + if (counter) + { + pOut_buf_cur[0] = pSrc[0]; + if (counter > 1) + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur += counter; + } + continue; + } + } +#endif + do + { + pOut_buf_cur[0] = pSrc[0]; + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur[2] = pSrc[2]; + pOut_buf_cur += 3; pSrc += 3; + } while ((int)(counter -= 3) > 2); + if ((int)counter > 0) + { + pOut_buf_cur[0] = pSrc[0]; + if ((int)counter > 1) + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur += counter; + } + } + } + } while (!(r->m_final & 1)); + if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) + { + TINFL_SKIP_BITS(32, num_bits & 7); for (counter = 0; counter < 4; ++counter) { mz_uint s; if (num_bits) TINFL_GET_BITS(41, s, 8); else TINFL_GET_BYTE(42, s); r->m_z_adler32 = (r->m_z_adler32 << 8) | s; } + } + TINFL_CR_RETURN_FOREVER(34, TINFL_STATUS_DONE); + TINFL_CR_FINISH + +common_exit: + r->m_num_bits = num_bits; r->m_bit_buf = bit_buf; r->m_dist = dist; r->m_counter = counter; r->m_num_extra = num_extra; r->m_dist_from_out_buf_start = dist_from_out_buf_start; + *pIn_buf_size = pIn_buf_cur - pIn_buf_next; *pOut_buf_size = pOut_buf_cur - pOut_buf_next; + if ((decomp_flags & (TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_COMPUTE_ADLER32)) && (status >= 0)) + { + const mz_uint8 *ptr = pOut_buf_next; size_t buf_len = *pOut_buf_size; + mz_uint32 i, s1 = r->m_check_adler32 & 0xffff, s2 = r->m_check_adler32 >> 16; size_t block_len = buf_len % 5552; + while (buf_len) + { + for (i = 0; i + 7 < block_len; i += 8, ptr += 8) + { + s1 += ptr[0], s2 += s1; s1 += ptr[1], s2 += s1; s1 += ptr[2], s2 += s1; s1 += ptr[3], s2 += s1; + s1 += ptr[4], s2 += s1; s1 += ptr[5], s2 += s1; s1 += ptr[6], s2 += s1; s1 += ptr[7], s2 += s1; + } + for ( ; i < block_len; ++i) s1 += *ptr++, s2 += s1; + s1 %= 65521U, s2 %= 65521U; buf_len -= block_len; block_len = 5552; + } + r->m_check_adler32 = (s2 << 16) + s1; if ((status == TINFL_STATUS_DONE) && (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) && (r->m_check_adler32 != r->m_z_adler32)) status = TINFL_STATUS_ADLER32_MISMATCH; + } + return status; +} + +/* Flush values. For typical usage you only need MZ_NO_FLUSH and MZ_FINISH. The other stuff is for advanced use. */ +enum { MZ_NO_FLUSH = 0, MZ_PARTIAL_FLUSH = 1, MZ_SYNC_FLUSH = 2, MZ_FULL_FLUSH = 3, MZ_FINISH = 4, MZ_BLOCK = 5 }; + +/* Return status codes. MZ_PARAM_ERROR is non-standard. */ +enum { MZ_OK = 0, MZ_STREAM_END = 1, MZ_NEED_DICT = 2, MZ_ERRNO = -1, MZ_STREAM_ERROR = -2, MZ_DATA_ERROR = -3, MZ_MEM_ERROR = -4, MZ_BUF_ERROR = -5, MZ_VERSION_ERROR = -6, MZ_PARAM_ERROR = -10000 }; + +/* Compression levels. */ +enum { MZ_NO_COMPRESSION = 0, MZ_BEST_SPEED = 1, MZ_BEST_COMPRESSION = 9, MZ_DEFAULT_COMPRESSION = -1 }; + +/* Window bits */ +#define MZ_DEFAULT_WINDOW_BITS 15 + +struct mz_internal_state; + +/* Compression/decompression stream struct. */ +typedef struct mz_stream_s +{ + const unsigned char *next_in; /* pointer to next byte to read */ + unsigned int avail_in; /* number of bytes available at next_in */ + mz_ulong total_in; /* total number of bytes consumed so far */ + + unsigned char *next_out; /* pointer to next byte to write */ + unsigned int avail_out; /* number of bytes that can be written to next_out */ + mz_ulong total_out; /* total number of bytes produced so far */ + + char *msg; /* error msg (unused) */ + struct mz_internal_state *state; /* internal state, allocated by zalloc/zfree */ + + mz_alloc_func zalloc; /* optional heap allocation function (defaults to malloc) */ + mz_free_func zfree; /* optional heap free function (defaults to free) */ + void *opaque; /* heap alloc function user pointer */ + + int data_type; /* data_type (unused) */ + mz_ulong adler; /* adler32 of the source or uncompressed data */ + mz_ulong reserved; /* not used */ +} mz_stream; + +typedef mz_stream *mz_streamp; + + +typedef struct +{ + tinfl_decompressor m_decomp; + mz_uint m_dict_ofs, m_dict_avail, m_first_call, m_has_flushed; int m_window_bits; + mz_uint8 m_dict[TINFL_LZ_DICT_SIZE]; + tinfl_status m_last_status; +} inflate_state; + +static int mz_inflateInit2(mz_streamp pStream, int window_bits) +{ + inflate_state *pDecomp; + if (!pStream) return MZ_STREAM_ERROR; + if ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS)) return MZ_PARAM_ERROR; + + pStream->data_type = 0; + pStream->adler = 0; + pStream->msg = NULL; + pStream->total_in = 0; + pStream->total_out = 0; + pStream->reserved = 0; + /* if (!pStream->zalloc) pStream->zalloc = def_alloc_func; */ + /* if (!pStream->zfree) pStream->zfree = def_free_func; */ + + pDecomp = (inflate_state*)pStream->zalloc(pStream->opaque, 1, sizeof(inflate_state)); + if (!pDecomp) return MZ_MEM_ERROR; + + pStream->state = (struct mz_internal_state *)pDecomp; + + tinfl_init(&pDecomp->m_decomp); + pDecomp->m_dict_ofs = 0; + pDecomp->m_dict_avail = 0; + pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT; + pDecomp->m_first_call = 1; + pDecomp->m_has_flushed = 0; + pDecomp->m_window_bits = window_bits; + + return MZ_OK; +} + +static int mz_inflate(mz_streamp pStream, int flush) +{ + inflate_state* pState; + mz_uint n, first_call, decomp_flags = TINFL_FLAG_COMPUTE_ADLER32; + size_t in_bytes, out_bytes, orig_avail_in; + tinfl_status status; + + if ((!pStream) || (!pStream->state)) return MZ_STREAM_ERROR; + if (flush == MZ_PARTIAL_FLUSH) flush = MZ_SYNC_FLUSH; + if ((flush) && (flush != MZ_SYNC_FLUSH) && (flush != MZ_FINISH)) return MZ_STREAM_ERROR; + + pState = (inflate_state*)pStream->state; + if (pState->m_window_bits > 0) decomp_flags |= TINFL_FLAG_PARSE_ZLIB_HEADER; + orig_avail_in = pStream->avail_in; + + first_call = pState->m_first_call; pState->m_first_call = 0; + if (pState->m_last_status < 0) return MZ_DATA_ERROR; + + if (pState->m_has_flushed && (flush != MZ_FINISH)) return MZ_STREAM_ERROR; + pState->m_has_flushed |= (flush == MZ_FINISH); + + if ((flush == MZ_FINISH) && (first_call)) + { + /* MZ_FINISH on the first call implies that the input and output buffers are large enough to hold the entire compressed/decompressed file. */ + decomp_flags |= TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF; + in_bytes = pStream->avail_in; out_bytes = pStream->avail_out; + status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pStream->next_out, pStream->next_out, &out_bytes, decomp_flags); + pState->m_last_status = status; + pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; pStream->total_in += (mz_uint)in_bytes; + pStream->adler = tinfl_get_adler32(&pState->m_decomp); + pStream->next_out += (mz_uint)out_bytes; pStream->avail_out -= (mz_uint)out_bytes; pStream->total_out += (mz_uint)out_bytes; + + if (status < 0) + return MZ_DATA_ERROR; + else if (status != TINFL_STATUS_DONE) + { + pState->m_last_status = TINFL_STATUS_FAILED; + return MZ_BUF_ERROR; + } + return MZ_STREAM_END; + } + /* flush != MZ_FINISH then we must assume there's more input. */ + if (flush != MZ_FINISH) decomp_flags |= TINFL_FLAG_HAS_MORE_INPUT; + + if (pState->m_dict_avail) + { + n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); + memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); + pStream->next_out += n; pStream->avail_out -= n; pStream->total_out += n; + pState->m_dict_avail -= n; pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); + return ((pState->m_last_status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; + } + + for ( ; ; ) + { + in_bytes = pStream->avail_in; + out_bytes = TINFL_LZ_DICT_SIZE - pState->m_dict_ofs; + + status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pState->m_dict, pState->m_dict + pState->m_dict_ofs, &out_bytes, decomp_flags); + pState->m_last_status = status; + + pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; pStream->adler = tinfl_get_adler32(&pState->m_decomp); + + pState->m_dict_avail = (mz_uint)out_bytes; + + n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); + memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); + pStream->next_out += n; pStream->avail_out -= n; pStream->total_out += n; + pState->m_dict_avail -= n; pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); + + if (status < 0) + return MZ_DATA_ERROR; /* Stream is corrupted (there could be some uncompressed data left in the output dictionary - oh well). */ + else if ((status == TINFL_STATUS_NEEDS_MORE_INPUT) && (!orig_avail_in)) + return MZ_BUF_ERROR; /* Signal caller that we can't make forward progress without supplying more input or by setting flush to MZ_FINISH. */ + else if (flush == MZ_FINISH) + { + /* The output buffer MUST be large to hold the remaining uncompressed data when flush==MZ_FINISH. */ + if (status == TINFL_STATUS_DONE) + return pState->m_dict_avail ? MZ_BUF_ERROR : MZ_STREAM_END; + /* status here must be TINFL_STATUS_HAS_MORE_OUTPUT, which means there's at least 1 more byte on the way. If there's no more room left in the output buffer then something is wrong. */ + else if (!pStream->avail_out) + return MZ_BUF_ERROR; + } + else if ((status == TINFL_STATUS_DONE) || (!pStream->avail_in) || (!pStream->avail_out) || (pState->m_dict_avail)) + break; + } + + return ((status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; +} + +static int mz_inflateEnd(mz_streamp pStream) +{ + if (!pStream) + return MZ_STREAM_ERROR; + if (pStream->state) + { + pStream->zfree(pStream->opaque, pStream->state); + pStream->state = NULL; + } + return MZ_OK; +} + +/* make this a drop-in replacement for zlib... */ + #define voidpf void* + #define uInt unsigned int + #define z_stream mz_stream + #define inflateInit2 mz_inflateInit2 + #define inflate mz_inflate + #define inflateEnd mz_inflateEnd + #define Z_SYNC_FLUSH MZ_SYNC_FLUSH + #define Z_FINISH MZ_FINISH + #define Z_OK MZ_OK + #define Z_STREAM_END MZ_STREAM_END + #define Z_NEED_DICT MZ_NEED_DICT + #define Z_ERRNO MZ_ERRNO + #define Z_STREAM_ERROR MZ_STREAM_ERROR + #define Z_DATA_ERROR MZ_DATA_ERROR + #define Z_MEM_ERROR MZ_MEM_ERROR + #define Z_BUF_ERROR MZ_BUF_ERROR + #define Z_VERSION_ERROR MZ_VERSION_ERROR + #define MAX_WBITS 15 + +#endif /* #ifndef TINFL_HEADER_FILE_ONLY */ + +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + 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 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. + + For more information, please refer to +*/ diff --git a/Source/Dependencies/PhysicsFS/physfs_platform_apple.m b/Source/Dependencies/PhysicsFS/physfs_platform_apple.m new file mode 100644 index 000000000..e6207f5b7 --- /dev/null +++ b/Source/Dependencies/PhysicsFS/physfs_platform_apple.m @@ -0,0 +1,188 @@ +/* + * Apple platform (macOS, iOS, watchOS, etc) support routines for PhysicsFS. + * + * Please see the file LICENSE.txt in the source's root directory. + * + * This file written by Ryan C. Gordon. + */ + +#define __PHYSICSFS_INTERNAL__ +#include "physfs_platforms.h" + +#ifdef PHYSFS_PLATFORM_APPLE + +#include + +#include "physfs_internal.h" + +int __PHYSFS_platformInit(void) +{ + return 1; /* success. */ +} /* __PHYSFS_platformInit */ + + +void __PHYSFS_platformDeinit(void) +{ + /* no-op */ +} /* __PHYSFS_platformDeinit */ + + +char *__PHYSFS_platformCalcBaseDir(const char *argv0) +{ + @autoreleasepool + { + NSString *path = [[NSBundle mainBundle] bundlePath]; + BAIL_IF(!path, PHYSFS_ERR_OS_ERROR, NULL); + size_t len = [path lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + char *retval = (char *) allocator.Malloc(len + 2); + BAIL_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + [path getCString:retval maxLength:len+1 encoding:NSUTF8StringEncoding]; + retval[len] = '/'; + retval[len+1] = '\0'; + return retval; /* whew. */ + } /* @autoreleasepool */ +} /* __PHYSFS_platformCalcBaseDir */ + + +char *__PHYSFS_platformCalcPrefDir(const char *org, const char *app) +{ + @autoreleasepool + { + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, TRUE); + BAIL_IF(!paths, PHYSFS_ERR_OS_ERROR, NULL); + NSString *path = (NSString *) [paths objectAtIndex:0]; + BAIL_IF(!path, PHYSFS_ERR_OS_ERROR, NULL); + size_t len = [path lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + const size_t applen = strlen(app); + char *retval = (char *) allocator.Malloc(len + applen + 3); + BAIL_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + [path getCString:retval maxLength:len+1 encoding:NSUTF8StringEncoding]; + snprintf(retval + len, applen + 3, "/%s/", app); + return retval; /* whew. */ + } /* @autoreleasepool */ +} /* __PHYSFS_platformCalcPrefDir */ + + +/* CD-ROM detection code... */ + +/* + * Code based on sample from Apple Developer Connection: + * https://developer.apple.com/samplecode/Sample_Code/Devices_and_Hardware/Disks/VolumeToBSDNode/VolumeToBSDNode.c.htm + */ + +#if !defined(PHYSFS_NO_CDROM_SUPPORT) + +#include +#include +#include +#include +#include + +static int darwinIsWholeMedia(io_service_t service) +{ + int retval = 0; + CFTypeRef wholeMedia; + + if (!IOObjectConformsTo(service, kIOMediaClass)) + return 0; + + wholeMedia = IORegistryEntryCreateCFProperty(service, + CFSTR(kIOMediaWholeKey), + NULL, 0); + if (wholeMedia == NULL) + return 0; + + retval = CFBooleanGetValue(wholeMedia); + CFRelease(wholeMedia); + + return retval; +} /* darwinIsWholeMedia */ + + +static int darwinIsMountedDisc(char *bsdName, mach_port_t masterPort) +{ + int retval = 0; + CFMutableDictionaryRef matchingDict; + kern_return_t rc; + io_iterator_t iter; + io_service_t service; + + if ((matchingDict = IOBSDNameMatching(masterPort, 0, bsdName)) == NULL) + return 0; + + rc = IOServiceGetMatchingServices(masterPort, matchingDict, &iter); + if ((rc != KERN_SUCCESS) || (!iter)) + return 0; + + service = IOIteratorNext(iter); + IOObjectRelease(iter); + if (!service) + return 0; + + rc = IORegistryEntryCreateIterator(service, kIOServicePlane, + kIORegistryIterateRecursively | kIORegistryIterateParents, &iter); + + if (!iter) + return 0; + + if (rc != KERN_SUCCESS) + { + IOObjectRelease(iter); + return 0; + } /* if */ + + IOObjectRetain(service); /* add an extra object reference... */ + + do + { + if (darwinIsWholeMedia(service)) + { + if ( (IOObjectConformsTo(service, kIOCDMediaClass)) || + (IOObjectConformsTo(service, kIODVDMediaClass)) ) + { + retval = 1; + } /* if */ + } /* if */ + IOObjectRelease(service); + } while ((service = IOIteratorNext(iter)) && (!retval)); + + IOObjectRelease(iter); + IOObjectRelease(service); + + return retval; +} /* darwinIsMountedDisc */ + +#endif /* !defined(PHYSFS_NO_CDROM_SUPPORT) */ + + +void __PHYSFS_platformDetectAvailableCDs(PHYSFS_StringCallback cb, void *data) +{ +#if !defined(PHYSFS_NO_CDROM_SUPPORT) + const char *devPrefix = "/dev/"; + const int prefixLen = strlen(devPrefix); + mach_port_t masterPort = 0; + struct statfs *mntbufp; + int i, mounts; + + if (IOMasterPort(MACH_PORT_NULL, &masterPort) != KERN_SUCCESS) + BAIL(PHYSFS_ERR_OS_ERROR, ) /*return void*/; + + mounts = getmntinfo(&mntbufp, MNT_WAIT); /* NOT THREAD SAFE! */ + for (i = 0; i < mounts; i++) + { + char *dev = mntbufp[i].f_mntfromname; + char *mnt = mntbufp[i].f_mntonname; + if (strncmp(dev, devPrefix, prefixLen) != 0) /* a virtual device? */ + continue; + + dev += prefixLen; + if (darwinIsMountedDisc(dev, masterPort)) + cb(data, mnt); + } /* for */ +#endif /* !defined(PHYSFS_NO_CDROM_SUPPORT) */ +} /* __PHYSFS_platformDetectAvailableCDs */ + +#endif /* PHYSFS_PLATFORM_APPLE */ + +/* end of physfs_platform_apple.m ... */ + diff --git a/Source/Dependencies/PhysicsFS/physfs_platform_haiku.cpp b/Source/Dependencies/PhysicsFS/physfs_platform_haiku.cpp new file mode 100644 index 000000000..fcf8ebde7 --- /dev/null +++ b/Source/Dependencies/PhysicsFS/physfs_platform_haiku.cpp @@ -0,0 +1,186 @@ +/* + * Haiku platform-dependent support routines for PhysicsFS. + * + * Please see the file LICENSE.txt in the source's root directory. + * + * This file written by Ryan C. Gordon. + */ + +#define __PHYSICSFS_INTERNAL__ +#include "physfs_platforms.h" + +#ifdef PHYSFS_PLATFORM_HAIKU + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "physfs_internal.h" + +int __PHYSFS_platformInit(void) +{ + return 1; /* always succeed. */ +} /* __PHYSFS_platformInit */ + + +void __PHYSFS_platformDeinit(void) +{ + /* no-op */ +} /* __PHYSFS_platformDeinit */ + + +static char *getMountPoint(const char *devname, char *buf, size_t bufsize) +{ + BVolumeRoster mounts; + BVolume vol; + + mounts.Rewind(); + while (mounts.GetNextVolume(&vol) == B_NO_ERROR) + { + fs_info fsinfo; + fs_stat_dev(vol.Device(), &fsinfo); + if (strcmp(devname, fsinfo.device_name) == 0) + { + BDirectory directory; + BEntry entry; + BPath path; + const char *str; + + if ( (vol.GetRootDirectory(&directory) < B_OK) || + (directory.GetEntry(&entry) < B_OK) || + (entry.GetPath(&path) < B_OK) || + ( (str = path.Path()) == NULL) ) + return NULL; + + strncpy(buf, str, bufsize-1); + buf[bufsize-1] = '\0'; + return buf; + } /* if */ + } /* while */ + + return NULL; +} /* getMountPoint */ + + + /* + * This function is lifted from Simple Directmedia Layer (SDL): + * https://www.libsdl.org/ ... this is zlib-licensed code, too. + */ +static void tryDir(const char *d, PHYSFS_StringCallback callback, void *data) +{ + BDirectory dir; + dir.SetTo(d); + if (dir.InitCheck() != B_NO_ERROR) + return; + + dir.Rewind(); + BEntry entry; + while (dir.GetNextEntry(&entry) >= 0) + { + BPath path; + const char *name; + entry_ref e; + + if (entry.GetPath(&path) != B_NO_ERROR) + continue; + + name = path.Path(); + + if (entry.GetRef(&e) != B_NO_ERROR) + continue; + + if (entry.IsDirectory()) + { + if (strcmp(e.name, "floppy") != 0) + tryDir(name, callback, data); + continue; + } /* if */ + + const int devfd = open(name, O_RDONLY); + if (devfd < 0) + continue; + + device_geometry g; + const int rc = ioctl(devfd, B_GET_GEOMETRY, &g, sizeof (g)); + close(devfd); + if (rc < 0) + continue; + + if (g.device_type != B_CD) + continue; + + char mntpnt[B_FILE_NAME_LENGTH]; + if (getMountPoint(name, mntpnt, sizeof (mntpnt))) + callback(data, mntpnt); + } /* while */ +} /* tryDir */ + + +void __PHYSFS_platformDetectAvailableCDs(PHYSFS_StringCallback cb, void *data) +{ + tryDir("/dev/disk", cb, data); +} /* __PHYSFS_platformDetectAvailableCDs */ + + +static team_id getTeamID(void) +{ + thread_info info; + thread_id tid = find_thread(NULL); + get_thread_info(tid, &info); + return info.team; +} /* getTeamID */ + + +char *__PHYSFS_platformCalcBaseDir(const char *argv0) +{ + image_info info; + int32 cookie = 0; + + while (get_next_image_info(0, &cookie, &info) == B_OK) + { + if (info.type == B_APP_IMAGE) + break; + } /* while */ + + BEntry entry(info.name, true); + BPath path; + status_t rc = entry.GetPath(&path); /* (path) now has binary's path. */ + assert(rc == B_OK); + rc = path.GetParent(&path); /* chop filename, keep directory. */ + assert(rc == B_OK); + const char *str = path.Path(); + assert(str != NULL); + const size_t len = strlen(str); + char *retval = (char *) allocator.Malloc(len + 2); + BAIL_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + strcpy(retval, str); + retval[len] = '/'; + retval[len+1] = '\0'; + return retval; +} /* __PHYSFS_platformCalcBaseDir */ + + +char *__PHYSFS_platformCalcPrefDir(const char *org, const char *app) +{ + const char *userdir = __PHYSFS_getUserDir(); + const char *append = "config/settings/"; + const size_t len = strlen(userdir) + strlen(append) + strlen(app) + 2; + char *retval = (char *) allocator.Malloc(len); + BAIL_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + snprintf(retval, len, "%s%s%s/", userdir, append, app); + return retval; +} /* __PHYSFS_platformCalcPrefDir */ + +#endif /* PHYSFS_PLATFORM_HAIKU */ + +/* end of physfs_platform_haiku.cpp ... */ + diff --git a/Source/Dependencies/PhysicsFS/physfs_platform_os2.c b/Source/Dependencies/PhysicsFS/physfs_platform_os2.c new file mode 100644 index 000000000..8cc8044cf --- /dev/null +++ b/Source/Dependencies/PhysicsFS/physfs_platform_os2.c @@ -0,0 +1,812 @@ +/* + * OS/2 support routines for PhysicsFS. + * + * Please see the file LICENSE.txt in the source's root directory. + * + * This file written by Ryan C. Gordon. + */ + +#define __PHYSICSFS_INTERNAL__ +#include "physfs_platforms.h" + +#ifdef PHYSFS_PLATFORM_OS2 + +#define INCL_DOSMODULEMGR +#define INCL_DOSSEMAPHORES +#define INCL_DOSDATETIME +#define INCL_DOSFILEMGR +#define INCL_DOSMODULEMGR +#define INCL_DOSERRORS +#define INCL_DOSPROCESS +#define INCL_DOSDEVICES +#define INCL_DOSDEVIOCTL +#define INCL_DOSMISC +#include +#include + +#include +#include +#include + +#include "physfs_internal.h" + +static HMODULE uconvdll = 0; +static UconvObject uconv = 0; +static int (_System *pUniCreateUconvObject)(UniChar *, UconvObject *) = NULL; +static int (_System *pUniFreeUconvObject)(UconvObject *) = NULL; +static int (_System *pUniUconvToUcs)(UconvObject,void **,size_t *, UniChar**, size_t *, size_t *) = NULL; +static int (_System *pUniUconvFromUcs)(UconvObject,UniChar **,size_t *,void **,size_t *,size_t *) = NULL; + +static PHYSFS_ErrorCode errcodeFromAPIRET(const APIRET rc) +{ + switch (rc) + { + case NO_ERROR: return PHYSFS_ERR_OK; /* not an error. */ + case ERROR_INTERRUPT: return PHYSFS_ERR_OK; /* not an error. */ + case ERROR_TIMEOUT: return PHYSFS_ERR_OK; /* not an error. */ + case ERROR_NOT_ENOUGH_MEMORY: return PHYSFS_ERR_OUT_OF_MEMORY; + case ERROR_FILE_NOT_FOUND: return PHYSFS_ERR_NOT_FOUND; + case ERROR_PATH_NOT_FOUND: return PHYSFS_ERR_NOT_FOUND; + case ERROR_ACCESS_DENIED: return PHYSFS_ERR_PERMISSION; + case ERROR_NOT_DOS_DISK: return PHYSFS_ERR_NOT_FOUND; + case ERROR_SHARING_VIOLATION: return PHYSFS_ERR_PERMISSION; + case ERROR_CANNOT_MAKE: return PHYSFS_ERR_IO; /* maybe this is wrong? */ + case ERROR_DEVICE_IN_USE: return PHYSFS_ERR_BUSY; + case ERROR_OPEN_FAILED: return PHYSFS_ERR_IO; /* maybe this is wrong? */ + case ERROR_DISK_FULL: return PHYSFS_ERR_NO_SPACE; + case ERROR_PIPE_BUSY: return PHYSFS_ERR_BUSY; + case ERROR_SHARING_BUFFER_EXCEEDED: return PHYSFS_ERR_IO; + case ERROR_FILENAME_EXCED_RANGE: return PHYSFS_ERR_BAD_FILENAME; + case ERROR_META_EXPANSION_TOO_LONG: return PHYSFS_ERR_BAD_FILENAME; + case ERROR_TOO_MANY_HANDLES: return PHYSFS_ERR_IO; + case ERROR_TOO_MANY_OPEN_FILES: return PHYSFS_ERR_IO; + case ERROR_NO_MORE_SEARCH_HANDLES: return PHYSFS_ERR_IO; + case ERROR_SEEK_ON_DEVICE: return PHYSFS_ERR_IO; + case ERROR_NEGATIVE_SEEK: return PHYSFS_ERR_INVALID_ARGUMENT; + case ERROR_WRITE_PROTECT: return PHYSFS_ERR_PERMISSION; + case ERROR_WRITE_FAULT: return PHYSFS_ERR_IO; + case ERROR_UNCERTAIN_MEDIA: return PHYSFS_ERR_IO; + case ERROR_PROTECTION_VIOLATION: return PHYSFS_ERR_IO; + case ERROR_BROKEN_PIPE: return PHYSFS_ERR_IO; + + /* !!! FIXME: some of these might be PHYSFS_ERR_BAD_FILENAME, etc */ + case ERROR_LOCK_VIOLATION: + case ERROR_GEN_FAILURE: + case ERROR_INVALID_PARAMETER: + case ERROR_INVALID_NAME: + case ERROR_INVALID_DRIVE: + case ERROR_INVALID_HANDLE: + case ERROR_INVALID_FUNCTION: + case ERROR_INVALID_LEVEL: + case ERROR_INVALID_CATEGORY: + case ERROR_DUPLICATE_NAME: + case ERROR_BUFFER_OVERFLOW: + case ERROR_BAD_LENGTH: + case ERROR_BAD_DRIVER_LEVEL: + case ERROR_DIRECT_ACCESS_HANDLE: + case ERROR_NOT_OWNER: + return PHYSFS_ERR_OS_ERROR; + + default: break; + } /* switch */ + + return PHYSFS_ERR_OTHER_ERROR; +} /* errcodeFromAPIRET */ + +static char *cvtUtf8ToCodepage(const char *utf8str) +{ + const size_t len = strlen(utf8str) + 1; + const size_t uc2buflen = len * sizeof (UniChar); + UniChar *uc2ptr = (UniChar *) __PHYSFS_smallAlloc(uc2buflen); + UniChar *uc2str = uc2ptr; + char *cpptr = NULL; + char *cpstr = NULL; + size_t subs = 0; + size_t unilen; + + BAIL_IF(!uc2str, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + PHYSFS_utf8ToUcs2(utf8str, (PHYSFS_uint16 *) uc2str, uc2buflen); + for (unilen = 0; uc2str[unilen]; unilen++) { /* spin */ } + unilen++; /* null terminator. */ + + if (!uconvdll) + { + /* There's really not much we can do on older OS/2s except pray this + is latin1-compatible. */ + size_t i; + cpptr = (char *) allocator.Malloc(unilen); + cpstr = cpptr; + GOTO_IF(!cpptr, PHYSFS_ERR_OUT_OF_MEMORY, failed); + for (i = 0; i < unilen; i++) + { + const UniChar ch = uc2str[i]; + GOTO_IF(ch > 0xFF, PHYSFS_ERR_BAD_FILENAME, failed); + cpptr[i] = (char) ((unsigned char) ch); + } /* for */ + + __PHYSFS_smallFree(uc2ptr); + return cpstr; + } /* if */ + else + { + int rc; + size_t cplen = unilen * 4; /* overallocate, just in case. */ + cpptr = (char *) allocator.Malloc(cplen); + GOTO_IF(!cpptr, PHYSFS_ERR_OUT_OF_MEMORY, failed); + cpstr = cpptr; + + rc = pUniUconvFromUcs(uconv, &uc2str, &unilen, (void **) &cpstr, &cplen, &subs); + GOTO_IF(rc != ULS_SUCCESS, PHYSFS_ERR_BAD_FILENAME, failed); + GOTO_IF(subs > 0, PHYSFS_ERR_BAD_FILENAME, failed); + assert(unilen == 0); + + __PHYSFS_smallFree(uc2ptr); + return cpptr; + } /* else */ + +failed: + __PHYSFS_smallFree(uc2ptr); + allocator.Free(cpptr); + + return NULL; +} /* cvtUtf8ToCodepage */ + +static char *cvtCodepageToUtf8(const char *cpstr) +{ + const size_t len = strlen(cpstr) + 1; + char *retvalbuf = (char *) allocator.Malloc(len * 4); + char *retval = NULL; + + BAIL_IF(!retvalbuf, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + + if (!uconvdll) + { + /* There's really not much we can do on older OS/2s except pray this + is latin1-compatible. */ + retval = retvalbuf; + PHYSFS_utf8FromLatin1(cpstr, retval, len * 4); + } /* if */ + else + { + int rc; + size_t cplen = len; + size_t unilen = len; + size_t subs = 0; + UniChar *uc2ptr = __PHYSFS_smallAlloc(len * sizeof (UniChar)); + UniChar *uc2str = uc2ptr; + + BAIL_IF(!uc2ptr, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + rc = pUniUconvToUcs(uconv, (void **) &cpstr, &cplen, &uc2str, &unilen, &subs); + GOTO_IF(rc != ULS_SUCCESS, PHYSFS_ERR_BAD_FILENAME, done); + GOTO_IF(subs > 0, PHYSFS_ERR_BAD_FILENAME, done); + assert(cplen == 0); + retval = retvalbuf; + PHYSFS_utf8FromUcs2((const PHYSFS_uint16 *) uc2ptr, retval, len * 4); + done: + __PHYSFS_smallFree(uc2ptr); + } /* else */ + + return retval; +} /* cvtCodepageToUtf8 */ + + +/* (be gentle, this function isn't very robust.) */ +static char *cvtPathToCorrectCase(char *buf) +{ + char *retval = buf; + char *fname = buf + 3; /* point to first element. */ + char *ptr = strchr(fname, '\\'); /* find end of first element. */ + + buf[0] = toupper(buf[0]); /* capitalize drive letter. */ + + /* + * Go through each path element, and enumerate its parent dir until + * a case-insensitive match is found. If one is (and it SHOULD be) + * then overwrite the original element with the correct case. + * If there's an error, or the path has vanished for some reason, it + * won't hurt to have the original case, so we just keep going. + */ + while ((fname != NULL) && (*fname != '\0')) + { + char spec[CCHMAXPATH]; + FILEFINDBUF3 fb; + HDIR hdir = HDIR_CREATE; + ULONG count = 1; + APIRET rc; + + *(fname - 1) = '\0'; /* isolate parent dir string. */ + + strcpy(spec, buf); /* copy isolated parent dir... */ + strcat(spec, "\\*.*"); /* ...and add wildcard search spec. */ + + if (ptr != NULL) /* isolate element to find (fname is the start). */ + *ptr = '\0'; + + rc = DosFindFirst((unsigned char *) spec, &hdir, FILE_DIRECTORY, + &fb, sizeof (fb), &count, FIL_STANDARD); + if (rc == NO_ERROR) + { + while (count == 1) /* while still entries to enumerate... */ + { + int cmp; + char *utf8 = cvtCodepageToUtf8(fb.achName); + if (!utf8) /* ugh, maybe we'll get lucky with the C runtime. */ + cmp = stricmp(fb.achName, fname); + else + { + cmp = PHYSFS_utf8stricmp(utf8, fname); + allocator.Free(utf8); + } /* else */ + + if (cmp == 0) + { + strcpy(fname, fb.achName); + break; /* there it is. Overwrite and stop searching. */ + } /* if */ + + DosFindNext(hdir, &fb, sizeof (fb), &count); + } /* while */ + DosFindClose(hdir); + } /* if */ + + *(fname - 1) = '\\'; /* unisolate parent dir. */ + fname = ptr; /* point to next element. */ + if (ptr != NULL) + { + *ptr = '\\'; /* unisolate element. */ + ptr = strchr(++fname, '\\'); /* find next element. */ + } /* if */ + } /* while */ + + return retval; +} /* cvtPathToCorrectCase */ + +static void prepUnicodeSupport(void) +{ + /* really old OS/2 might not have Unicode support _at all_, so load + the system library and do without if it doesn't exist. */ + int ok = 0; + char buf[CCHMAXPATH]; + UniChar defstr[] = { 0 }; + if (DosLoadModule(buf, sizeof (buf) - 1, "uconv", &uconvdll) == NO_ERROR) + { + #define LOAD(x) (DosQueryProcAddr(uconvdll,0,#x,(PFN*)&p##x)==NO_ERROR) + ok = LOAD(UniCreateUconvObject) && + LOAD(UniFreeUconvObject) && + LOAD(UniUconvToUcs) && + LOAD(UniUconvFromUcs); + #undef LOAD + } /* else */ + + if (!ok || (pUniCreateUconvObject(defstr, &uconv) != ULS_SUCCESS)) + { + /* oh well, live without it. */ + if (uconvdll) + { + if (uconv) + pUniFreeUconvObject(uconv); + DosFreeModule(uconvdll); + uconvdll = 0; + } /* if */ + } /* if */ +} /* prepUnicodeSupport */ + + +int __PHYSFS_platformInit(void) +{ + prepUnicodeSupport(); + return 1; /* ready to go! */ +} /* __PHYSFS_platformInit */ + + +void __PHYSFS_platformDeinit(void) +{ + if (uconvdll) + { + pUniFreeUconvObject(uconv); + uconv = 0; + DosFreeModule(uconvdll); + uconvdll = 0; + } /* if */ +} /* __PHYSFS_platformDeinit */ + + +static int discIsInserted(ULONG drive) +{ + int rc; + char buf[20]; + DosError(FERR_DISABLEHARDERR | FERR_DISABLEEXCEPTION); + rc = DosQueryFSInfo(drive + 1, FSIL_VOLSER, buf, sizeof (buf)); + DosError(FERR_ENABLEHARDERR | FERR_ENABLEEXCEPTION); + return (rc == NO_ERROR); +} /* is_cdrom_inserted */ + + +/* looks like "CD01" in ASCII (littleendian)...used for an ioctl. */ +#define CD01 0x31304443 + +static int isCdRomDrive(ULONG drive) +{ + PHYSFS_uint32 param, data; + ULONG ul1, ul2; + APIRET rc; + HFILE hfile = NULLHANDLE; + unsigned char drivename[3] = { 0, ':', '\0' }; + + drivename[0] = 'A' + drive; + + rc = DosOpen(drivename, &hfile, &ul1, 0, 0, + OPEN_ACTION_OPEN_IF_EXISTS | OPEN_ACTION_FAIL_IF_NEW, + OPEN_FLAGS_DASD | OPEN_FLAGS_FAIL_ON_ERROR | + OPEN_FLAGS_NOINHERIT | OPEN_SHARE_DENYNONE, NULL); + if (rc != NO_ERROR) + return 0; + + data = 0; + param = PHYSFS_swapULE32(CD01); + ul1 = ul2 = sizeof (PHYSFS_uint32); + rc = DosDevIOCtl(hfile, IOCTL_CDROMDISK, CDROMDISK_GETDRIVER, + ¶m, sizeof (param), &ul1, &data, sizeof (data), &ul2); + + DosClose(hfile); + return ((rc == NO_ERROR) && (PHYSFS_swapULE32(data) == CD01)); +} /* isCdRomDrive */ + + +void __PHYSFS_platformDetectAvailableCDs(PHYSFS_StringCallback cb, void *data) +{ + ULONG dummy = 0; + ULONG drivemap = 0; + ULONG i, bit; + const APIRET rc = DosQueryCurrentDisk(&dummy, &drivemap); + BAIL_IF(rc != NO_ERROR, errcodeFromAPIRET(rc),); + + for (i = 0, bit = 1; i < 26; i++, bit <<= 1) + { + if (drivemap & bit) /* this logical drive exists. */ + { + if ((isCdRomDrive(i)) && (discIsInserted(i))) + { + char drive[4] = "x:\\"; + drive[0] = ('A' + i); + cb(data, drive); + } /* if */ + } /* if */ + } /* for */ +} /* __PHYSFS_platformDetectAvailableCDs */ + + +char *__PHYSFS_platformCalcBaseDir(const char *argv0) +{ + char *retval = NULL; + char buf[CCHMAXPATH]; + APIRET rc; + PTIB ptib; + PPIB ppib; + PHYSFS_sint32 len; + + rc = DosGetInfoBlocks(&ptib, &ppib); + BAIL_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), 0); + rc = DosQueryModuleName(ppib->pib_hmte, sizeof (buf), (PCHAR) buf); + BAIL_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), 0); + retval = cvtCodepageToUtf8(buf); + BAIL_IF_ERRPASS(!retval, NULL); + + /* chop off filename, leave path. */ + for (len = strlen(retval) - 1; len >= 0; len--) + { + if (retval[len] == '\\') + { + retval[len + 1] = '\0'; + break; + } /* if */ + } /* for */ + + assert(len > 0); /* should have been a "x:\\" on the front on string. */ + + /* The string is capitalized! Figure out the REAL case... */ + return cvtPathToCorrectCase(retval); +} /* __PHYSFS_platformCalcBaseDir */ + +char *__PHYSFS_platformCalcUserDir(void) +{ + return __PHYSFS_platformCalcBaseDir(NULL); /* !!! FIXME: ? */ +} /* __PHYSFS_platformCalcUserDir */ + +char *__PHYSFS_platformCalcPrefDir(const char *org, const char *app) +{ + return __PHYSFS_platformCalcBaseDir(NULL); /* !!! FIXME: ? */ +} /* __PHYSFS_platformCalcPrefDir */ + +PHYSFS_EnumerateCallbackResult __PHYSFS_platformEnumerate(const char *dirname, + PHYSFS_EnumerateCallback callback, + const char *origdir, void *callbackdata) +{ + PHYSFS_EnumerateCallbackResult retval = PHYSFS_ENUM_OK; + size_t utf8len = strlen(dirname); + char *utf8 = (char *) __PHYSFS_smallAlloc(utf8len + 5); + char *cpspec = NULL; + FILEFINDBUF3 fb; + HDIR hdir = HDIR_CREATE; + ULONG count = 1; + APIRET rc; + + BAIL_IF(!utf8, PHYSFS_ERR_OUT_OF_MEMORY, PHYSFS_ENUM_ERROR); + + strcpy(utf8, dirname); + if (utf8[utf8len - 1] != '\\') + strcpy(utf8 + utf8len, "\\*.*"); + else + strcpy(utf8 + utf8len, "*.*"); + + cpspec = cvtUtf8ToCodepage(utf8); + __PHYSFS_smallFree(utf8); + BAIL_IF_ERRPASS(!cpspec, PHYSFS_ENUM_ERROR); + + rc = DosFindFirst((unsigned char *) cpspec, &hdir, + FILE_DIRECTORY | FILE_ARCHIVED | + FILE_READONLY | FILE_HIDDEN | FILE_SYSTEM, + &fb, sizeof (fb), &count, FIL_STANDARD); + allocator.Free(cpspec); + + BAIL_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), PHYSFS_ENUM_ERROR); + + while (count == 1) + { + if ((strcmp(fb.achName, ".") != 0) && (strcmp(fb.achName, "..") != 0)) + { + utf8 = cvtCodepageToUtf8(fb.achName); + if (!utf8) + retval = PHYSFS_ENUM_ERROR; + else + { + retval = callback(callbackdata, origdir, utf8); + allocator.Free(utf8); + if (retval == PHYSFS_ENUM_ERROR) + PHYSFS_setErrorCode(PHYSFS_ERR_APP_CALLBACK); + } /* else */ + } /* if */ + + if (retval != PHYSFS_ENUM_OK) + break; + + DosFindNext(hdir, &fb, sizeof (fb), &count); + } /* while */ + + DosFindClose(hdir); + + return retval; +} /* __PHYSFS_platformEnumerate */ + + +char *__PHYSFS_platformCurrentDir(void) +{ + char *retval; + char *cpstr; + char *utf8; + ULONG currentDisk; + ULONG dummy; + ULONG pathSize = 0; + APIRET rc; + BYTE byte; + + rc = DosQueryCurrentDisk(¤tDisk, &dummy); + BAIL_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), NULL); + + /* The first call just tells us how much space we need for the string. */ + rc = DosQueryCurrentDir(currentDisk, &byte, &pathSize); + pathSize++; /* Add space for null terminator. */ + cpstr = (char *) __PHYSFS_smallAlloc(pathSize); + BAIL_IF(cpstr == NULL, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + + /* Actually get the string this time. */ + rc = DosQueryCurrentDir(currentDisk, (PBYTE) cpstr, &pathSize); + if (rc != NO_ERROR) + { + __PHYSFS_smallFree(cpstr); + BAIL(errcodeFromAPIRET(rc), NULL); + } /* if */ + + utf8 = cvtCodepageToUtf8(cpstr); + __PHYSFS_smallFree(cpstr); + BAIL_IF_ERRPASS(utf8 == NULL, NULL); + + /* +4 for "x:\\" drive selector and null terminator. */ + retval = (char *) allocator.Malloc(strlen(utf8) + 4); + if (retval == NULL) + { + allocator.Free(utf8); + BAIL(PHYSFS_ERR_OUT_OF_MEMORY, NULL); + } /* if */ + + retval[0] = ('A' + (currentDisk - 1)); + retval[1] = ':'; + retval[2] = '\\'; + strcpy(retval + 3, utf8); + + allocator.Free(utf8); + + return retval; +} /* __PHYSFS_platformCurrentDir */ + + +int __PHYSFS_platformMkDir(const char *filename) +{ + APIRET rc; + char *cpstr = cvtUtf8ToCodepage(filename); + BAIL_IF_ERRPASS(!cpstr, 0); + rc = DosCreateDir((unsigned char *) cpstr, NULL); + allocator.Free(cpstr); + BAIL_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), 0); + return 1; +} /* __PHYSFS_platformMkDir */ + + +static HFILE openFile(const char *filename, const ULONG flags, const ULONG mode) +{ + char *cpfname = cvtUtf8ToCodepage(filename); + ULONG action = 0; + HFILE hfile = NULLHANDLE; + APIRET rc; + + BAIL_IF_ERRPASS(!cpfname, 0); + + rc = DosOpen(cpfname, &hfile, &action, 0, FILE_NORMAL, flags, mode, NULL); + allocator.Free(cpfname); + BAIL_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), 0); + + return hfile; +} /* openFile */ + +void *__PHYSFS_platformOpenRead(const char *filename) +{ + /* + * File must be opened SHARE_DENYWRITE and ACCESS_READONLY, otherwise + * DosQueryFileInfo() will fail if we try to get a file length, etc. + */ + return (void *) openFile(filename, + OPEN_ACTION_OPEN_IF_EXISTS | OPEN_ACTION_FAIL_IF_NEW, + OPEN_FLAGS_FAIL_ON_ERROR | OPEN_FLAGS_NO_LOCALITY | + OPEN_FLAGS_NOINHERIT | OPEN_SHARE_DENYWRITE | + OPEN_ACCESS_READONLY); +} /* __PHYSFS_platformOpenRead */ + + +void *__PHYSFS_platformOpenWrite(const char *filename) +{ + return (void *) openFile(filename, + OPEN_ACTION_REPLACE_IF_EXISTS | + OPEN_ACTION_CREATE_IF_NEW, + OPEN_FLAGS_FAIL_ON_ERROR | OPEN_FLAGS_NO_LOCALITY | + OPEN_FLAGS_NOINHERIT | OPEN_SHARE_DENYWRITE); +} /* __PHYSFS_platformOpenWrite */ + + +void *__PHYSFS_platformOpenAppend(const char *filename) +{ + APIRET rc; + ULONG dummy = 0; + HFILE hfile; + + /* + * File must be opened SHARE_DENYWRITE and ACCESS_READWRITE, otherwise + * DosQueryFileInfo() will fail if we try to get a file length, etc. + */ + hfile = openFile(filename, + OPEN_ACTION_OPEN_IF_EXISTS | OPEN_ACTION_CREATE_IF_NEW, + OPEN_FLAGS_FAIL_ON_ERROR | OPEN_FLAGS_NO_LOCALITY | + OPEN_FLAGS_NOINHERIT | OPEN_SHARE_DENYWRITE | + OPEN_ACCESS_READWRITE); + BAIL_IF_ERRPASS(!hfile, NULL); + + rc = DosSetFilePtr(hfile, 0, FILE_END, &dummy); + if (rc != NO_ERROR) + { + DosClose(hfile); + BAIL(errcodeFromAPIRET(rc), NULL); + } /* if */ + + return ((void *) hfile); +} /* __PHYSFS_platformOpenAppend */ + + +PHYSFS_sint64 __PHYSFS_platformRead(void *opaque, void *buf, PHYSFS_uint64 len) +{ + ULONG br = 0; + APIRET rc; + BAIL_IF(!__PHYSFS_ui64FitsAddressSpace(len),PHYSFS_ERR_INVALID_ARGUMENT,-1); + rc = DosRead((HFILE) opaque, buf, (ULONG) len, &br); + BAIL_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), (br > 0) ? ((PHYSFS_sint64) br) : -1); + return (PHYSFS_sint64) br; +} /* __PHYSFS_platformRead */ + + +PHYSFS_sint64 __PHYSFS_platformWrite(void *opaque, const void *buf, + PHYSFS_uint64 len) +{ + ULONG bw = 0; + APIRET rc; + BAIL_IF(!__PHYSFS_ui64FitsAddressSpace(len),PHYSFS_ERR_INVALID_ARGUMENT,-1); + rc = DosWrite((HFILE) opaque, (void *) buf, (ULONG) len, &bw); + BAIL_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), (bw > 0) ? ((PHYSFS_sint64) bw) : -1); + return (PHYSFS_sint64) bw; +} /* __PHYSFS_platformWrite */ + + +int __PHYSFS_platformSeek(void *opaque, PHYSFS_uint64 pos) +{ + ULONG dummy; + HFILE hfile = (HFILE) opaque; + LONG dist = (LONG) pos; + APIRET rc; + + /* hooray for 32-bit filesystem limits! :) */ + BAIL_IF((PHYSFS_uint64) dist != pos, PHYSFS_ERR_INVALID_ARGUMENT, 0); + rc = DosSetFilePtr(hfile, dist, FILE_BEGIN, &dummy); + BAIL_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), 0); + return 1; +} /* __PHYSFS_platformSeek */ + + +PHYSFS_sint64 __PHYSFS_platformTell(void *opaque) +{ + ULONG pos; + HFILE hfile = (HFILE) opaque; + const APIRET rc = DosSetFilePtr(hfile, 0, FILE_CURRENT, &pos); + BAIL_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), -1); + return ((PHYSFS_sint64) pos); +} /* __PHYSFS_platformTell */ + + +PHYSFS_sint64 __PHYSFS_platformFileLength(void *opaque) +{ + FILESTATUS3 fs; + HFILE hfile = (HFILE) opaque; + const APIRET rc = DosQueryFileInfo(hfile, FIL_STANDARD, &fs, sizeof (fs)); + BAIL_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), -1); + return ((PHYSFS_sint64) fs.cbFile); +} /* __PHYSFS_platformFileLength */ + + +int __PHYSFS_platformFlush(void *opaque) +{ + const APIRET rc = DosResetBuffer((HFILE) opaque); + BAIL_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), 0); + return 1; +} /* __PHYSFS_platformFlush */ + + +void __PHYSFS_platformClose(void *opaque) +{ + DosClose((HFILE) opaque); /* ignore errors. You should have flushed! */ +} /* __PHYSFS_platformClose */ + + +int __PHYSFS_platformDelete(const char *path) +{ + char *cppath = cvtUtf8ToCodepage(path); + FILESTATUS3 fs; + APIRET rc; + int retval = 0; + + BAIL_IF_ERRPASS(!cppath, 0); + rc = DosQueryPathInfo(cppath, FIL_STANDARD, &fs, sizeof (fs)); + GOTO_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), done); + rc = (fs.attrFile & FILE_DIRECTORY) ? DosDeleteDir(path) : DosDelete(path); + GOTO_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), done); + retval = 1; /* success */ + +done: + allocator.Free(cppath); + return retval; +} /* __PHYSFS_platformDelete */ + + +/* Convert to a format PhysicsFS can grok... */ +PHYSFS_sint64 os2TimeToUnixTime(const FDATE *date, const FTIME *time) +{ + struct tm tm; + + tm.tm_sec = ((PHYSFS_uint32) time->twosecs) * 2; + tm.tm_min = time->minutes; + tm.tm_hour = time->hours; + tm.tm_mday = date->day; + tm.tm_mon = date->month; + tm.tm_year = ((PHYSFS_uint32) date->year) + 80; + tm.tm_wday = -1 /*st_localtz.wDayOfWeek*/; + tm.tm_yday = -1; + tm.tm_isdst = -1; + + return (PHYSFS_sint64) mktime(&tm); +} /* os2TimeToUnixTime */ + + +int __PHYSFS_platformStat(const char *filename, PHYSFS_Stat *stat, const int follow) +{ + char *cpfname = cvtUtf8ToCodepage(filename); + FILESTATUS3 fs; + int retval = 0; + APIRET rc; + + BAIL_IF_ERRPASS(!cpfname, 0); + + rc = DosQueryPathInfo(cpfname, FIL_STANDARD, &fs, sizeof (fs)); + GOTO_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), done); + + if (fs.attrFile & FILE_DIRECTORY) + { + stat->filetype = PHYSFS_FILETYPE_DIRECTORY; + stat->filesize = 0; + } /* if */ + else + { + stat->filetype = PHYSFS_FILETYPE_REGULAR; + stat->filesize = fs.cbFile; + } /* else */ + + stat->modtime = os2TimeToUnixTime(&fs.fdateLastWrite, &fs.ftimeLastWrite); + if (stat->modtime < 0) + stat->modtime = 0; + + stat->accesstime = os2TimeToUnixTime(&fs.fdateLastAccess, &fs.ftimeLastAccess); + if (stat->accesstime < 0) + stat->accesstime = 0; + + stat->createtime = os2TimeToUnixTime(&fs.fdateCreation, &fs.ftimeCreation); + if (stat->createtime < 0) + stat->createtime = 0; + + stat->readonly = ((fs.attrFile & FILE_READONLY) == FILE_READONLY); + return 1; /* success */ + +done: + allocator.Free(cpfname); + return retval; +} /* __PHYSFS_platformStat */ + + +void *__PHYSFS_platformGetThreadID(void) +{ + PTIB ptib; + PPIB ppib; + + /* + * Allegedly, this API never fails, but we'll punt and return a + * default value (zero might as well do) if it does. + */ + const APIRET rc = DosGetInfoBlocks(&ptib, &ppib); + BAIL_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), 0); + return ((void *) ptib->tib_ordinal); +} /* __PHYSFS_platformGetThreadID */ + + +void *__PHYSFS_platformCreateMutex(void) +{ + HMTX hmtx = NULLHANDLE; + const APIRET rc = DosCreateMutexSem(NULL, &hmtx, 0, 0); + BAIL_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), NULL); + return ((void *) hmtx); +} /* __PHYSFS_platformCreateMutex */ + + +void __PHYSFS_platformDestroyMutex(void *mutex) +{ + DosCloseMutexSem((HMTX) mutex); +} /* __PHYSFS_platformDestroyMutex */ + + +int __PHYSFS_platformGrabMutex(void *mutex) +{ + /* Do _NOT_ set the physfs error message in here! */ + return (DosRequestMutexSem((HMTX) mutex, SEM_INDEFINITE_WAIT) == NO_ERROR); +} /* __PHYSFS_platformGrabMutex */ + + +void __PHYSFS_platformReleaseMutex(void *mutex) +{ + DosReleaseMutexSem((HMTX) mutex); +} /* __PHYSFS_platformReleaseMutex */ + +#endif /* PHYSFS_PLATFORM_OS2 */ + +/* end of physfs_platform_os2.c ... */ diff --git a/Source/Dependencies/PhysicsFS/physfs_platform_posix.c b/Source/Dependencies/PhysicsFS/physfs_platform_posix.c new file mode 100644 index 000000000..fa2159cd8 --- /dev/null +++ b/Source/Dependencies/PhysicsFS/physfs_platform_posix.c @@ -0,0 +1,417 @@ +/* + * Posix-esque support routines for PhysicsFS. + * + * Please see the file LICENSE.txt in the source's root directory. + * + * This file written by Ryan C. Gordon. + */ + +/* !!! FIXME: check for EINTR? */ + +#define __PHYSICSFS_INTERNAL__ +#include "physfs_platforms.h" + +#ifdef PHYSFS_PLATFORM_POSIX + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "physfs_internal.h" + + +static PHYSFS_ErrorCode errcodeFromErrnoError(const int err) +{ + switch (err) + { + case 0: return PHYSFS_ERR_OK; + case EACCES: return PHYSFS_ERR_PERMISSION; + case EPERM: return PHYSFS_ERR_PERMISSION; + case EDQUOT: return PHYSFS_ERR_NO_SPACE; + case EIO: return PHYSFS_ERR_IO; + case ELOOP: return PHYSFS_ERR_SYMLINK_LOOP; + case EMLINK: return PHYSFS_ERR_NO_SPACE; + case ENAMETOOLONG: return PHYSFS_ERR_BAD_FILENAME; + case ENOENT: return PHYSFS_ERR_NOT_FOUND; + case ENOSPC: return PHYSFS_ERR_NO_SPACE; + case ENOTDIR: return PHYSFS_ERR_NOT_FOUND; + case EISDIR: return PHYSFS_ERR_NOT_A_FILE; + case EROFS: return PHYSFS_ERR_READ_ONLY; + case ETXTBSY: return PHYSFS_ERR_BUSY; + case EBUSY: return PHYSFS_ERR_BUSY; + case ENOMEM: return PHYSFS_ERR_OUT_OF_MEMORY; + case ENOTEMPTY: return PHYSFS_ERR_DIR_NOT_EMPTY; + default: return PHYSFS_ERR_OS_ERROR; + } /* switch */ +} /* errcodeFromErrnoError */ + + +static inline PHYSFS_ErrorCode errcodeFromErrno(void) +{ + return errcodeFromErrnoError(errno); +} /* errcodeFromErrno */ + + +static char *getUserDirByUID(void) +{ + uid_t uid = getuid(); + struct passwd *pw; + char *retval = NULL; + + pw = getpwuid(uid); + if ((pw != NULL) && (pw->pw_dir != NULL) && (*pw->pw_dir != '\0')) + { + const size_t dlen = strlen(pw->pw_dir); + const size_t add_dirsep = (pw->pw_dir[dlen-1] != '/') ? 1 : 0; + retval = (char *) allocator.Malloc(dlen + 1 + add_dirsep); + if (retval != NULL) + { + strcpy(retval, pw->pw_dir); + if (add_dirsep) + { + retval[dlen] = '/'; + retval[dlen+1] = '\0'; + } /* if */ + } /* if */ + } /* if */ + + return retval; +} /* getUserDirByUID */ + + +char *__PHYSFS_platformCalcUserDir(void) +{ + char *retval = NULL; + char *envr = getenv("HOME"); + + /* if the environment variable was set, make sure it's really a dir. */ + if (envr != NULL) + { + struct stat statbuf; + if ((stat(envr, &statbuf) != -1) && (S_ISDIR(statbuf.st_mode))) + { + const size_t envrlen = strlen(envr); + const size_t add_dirsep = (envr[envrlen-1] != '/') ? 1 : 0; + retval = allocator.Malloc(envrlen + 1 + add_dirsep); + if (retval) + { + strcpy(retval, envr); + if (add_dirsep) + { + retval[envrlen] = '/'; + retval[envrlen+1] = '\0'; + } /* if */ + } /* if */ + } /* if */ + } /* if */ + + if (retval == NULL) + retval = getUserDirByUID(); + + return retval; +} /* __PHYSFS_platformCalcUserDir */ + + +PHYSFS_EnumerateCallbackResult __PHYSFS_platformEnumerate(const char *dirname, + PHYSFS_EnumerateCallback callback, + const char *origdir, void *callbackdata) +{ + DIR *dir; + struct dirent *ent; + PHYSFS_EnumerateCallbackResult retval = PHYSFS_ENUM_OK; + + dir = opendir(dirname); + BAIL_IF(dir == NULL, errcodeFromErrno(), PHYSFS_ENUM_ERROR); + + while ((retval == PHYSFS_ENUM_OK) && ((ent = readdir(dir)) != NULL)) + { + const char *name = ent->d_name; + if (name[0] == '.') /* ignore "." and ".." */ + { + if ((name[1] == '\0') || ((name[1] == '.') && (name[2] == '\0'))) + continue; + } /* if */ + + retval = callback(callbackdata, origdir, name); + if (retval == PHYSFS_ENUM_ERROR) + PHYSFS_setErrorCode(PHYSFS_ERR_APP_CALLBACK); + } /* while */ + + closedir(dir); + + return retval; +} /* __PHYSFS_platformEnumerate */ + + +int __PHYSFS_platformMkDir(const char *path) +{ + const int rc = mkdir(path, S_IRWXU); + BAIL_IF(rc == -1, errcodeFromErrno(), 0); + return 1; +} /* __PHYSFS_platformMkDir */ + + +static void *doOpen(const char *filename, int mode) +{ + const int appending = (mode & O_APPEND); + int fd; + int *retval; + errno = 0; + + /* O_APPEND doesn't actually behave as we'd like. */ + mode &= ~O_APPEND; + + fd = open(filename, mode, S_IRUSR | S_IWUSR); + BAIL_IF(fd < 0, errcodeFromErrno(), NULL); + + if (appending) + { + if (lseek(fd, 0, SEEK_END) < 0) + { + const int err = errno; + close(fd); + BAIL(errcodeFromErrnoError(err), NULL); + } /* if */ + } /* if */ + + retval = (int *) allocator.Malloc(sizeof (int)); + if (!retval) + { + close(fd); + BAIL(PHYSFS_ERR_OUT_OF_MEMORY, NULL); + } /* if */ + + *retval = fd; + return ((void *) retval); +} /* doOpen */ + + +void *__PHYSFS_platformOpenRead(const char *filename) +{ + return doOpen(filename, O_RDONLY); +} /* __PHYSFS_platformOpenRead */ + + +void *__PHYSFS_platformOpenWrite(const char *filename) +{ + return doOpen(filename, O_WRONLY | O_CREAT | O_TRUNC); +} /* __PHYSFS_platformOpenWrite */ + + +void *__PHYSFS_platformOpenAppend(const char *filename) +{ + return doOpen(filename, O_WRONLY | O_CREAT | O_APPEND); +} /* __PHYSFS_platformOpenAppend */ + + +PHYSFS_sint64 __PHYSFS_platformRead(void *opaque, void *buffer, + PHYSFS_uint64 len) +{ + const int fd = *((int *) opaque); + ssize_t rc = 0; + + if (!__PHYSFS_ui64FitsAddressSpace(len)) + BAIL(PHYSFS_ERR_INVALID_ARGUMENT, -1); + + rc = read(fd, buffer, (size_t) len); + BAIL_IF(rc == -1, errcodeFromErrno(), -1); + assert(rc >= 0); + assert(rc <= len); + return (PHYSFS_sint64) rc; +} /* __PHYSFS_platformRead */ + + +PHYSFS_sint64 __PHYSFS_platformWrite(void *opaque, const void *buffer, + PHYSFS_uint64 len) +{ + const int fd = *((int *) opaque); + ssize_t rc = 0; + + if (!__PHYSFS_ui64FitsAddressSpace(len)) + BAIL(PHYSFS_ERR_INVALID_ARGUMENT, -1); + + rc = write(fd, (void *) buffer, (size_t) len); + BAIL_IF(rc == -1, errcodeFromErrno(), rc); + assert(rc >= 0); + assert(rc <= len); + return (PHYSFS_sint64) rc; +} /* __PHYSFS_platformWrite */ + + +int __PHYSFS_platformSeek(void *opaque, PHYSFS_uint64 pos) +{ + const int fd = *((int *) opaque); + const off_t rc = lseek(fd, (off_t) pos, SEEK_SET); + BAIL_IF(rc == -1, errcodeFromErrno(), 0); + return 1; +} /* __PHYSFS_platformSeek */ + + +PHYSFS_sint64 __PHYSFS_platformTell(void *opaque) +{ + const int fd = *((int *) opaque); + PHYSFS_sint64 retval; + retval = (PHYSFS_sint64) lseek(fd, 0, SEEK_CUR); + BAIL_IF(retval == -1, errcodeFromErrno(), -1); + return retval; +} /* __PHYSFS_platformTell */ + + +PHYSFS_sint64 __PHYSFS_platformFileLength(void *opaque) +{ + const int fd = *((int *) opaque); + struct stat statbuf; + BAIL_IF(fstat(fd, &statbuf) == -1, errcodeFromErrno(), -1); + return ((PHYSFS_sint64) statbuf.st_size); +} /* __PHYSFS_platformFileLength */ + + +int __PHYSFS_platformFlush(void *opaque) +{ + const int fd = *((int *) opaque); + if ((fcntl(fd, F_GETFL) & O_ACCMODE) != O_RDONLY) + BAIL_IF(fsync(fd) == -1, errcodeFromErrno(), 0); + return 1; +} /* __PHYSFS_platformFlush */ + + +void __PHYSFS_platformClose(void *opaque) +{ + const int fd = *((int *) opaque); + (void) close(fd); /* we don't check this. You should have used flush! */ + allocator.Free(opaque); +} /* __PHYSFS_platformClose */ + + +int __PHYSFS_platformDelete(const char *path) +{ + BAIL_IF(remove(path) == -1, errcodeFromErrno(), 0); + return 1; +} /* __PHYSFS_platformDelete */ + + +int __PHYSFS_platformStat(const char *fname, PHYSFS_Stat *st, const int follow) +{ + struct stat statbuf; + const int rc = follow ? stat(fname, &statbuf) : lstat(fname, &statbuf); + BAIL_IF(rc == -1, errcodeFromErrno(), 0); + + if (S_ISREG(statbuf.st_mode)) + { + st->filetype = PHYSFS_FILETYPE_REGULAR; + st->filesize = statbuf.st_size; + } /* if */ + + else if(S_ISDIR(statbuf.st_mode)) + { + st->filetype = PHYSFS_FILETYPE_DIRECTORY; + st->filesize = 0; + } /* else if */ + + else if(S_ISLNK(statbuf.st_mode)) + { + st->filetype = PHYSFS_FILETYPE_SYMLINK; + st->filesize = 0; + } /* else if */ + + else + { + st->filetype = PHYSFS_FILETYPE_OTHER; + st->filesize = statbuf.st_size; + } /* else */ + + st->modtime = statbuf.st_mtime; + st->createtime = statbuf.st_ctime; + st->accesstime = statbuf.st_atime; + + st->readonly = (access(fname, W_OK) == -1); + return 1; +} /* __PHYSFS_platformStat */ + + +typedef struct +{ + pthread_mutex_t mutex; + pthread_t owner; + PHYSFS_uint32 count; +} PthreadMutex; + + +void *__PHYSFS_platformGetThreadID(void) +{ + return ( (void *) ((size_t) pthread_self()) ); +} /* __PHYSFS_platformGetThreadID */ + + +void *__PHYSFS_platformCreateMutex(void) +{ + int rc; + PthreadMutex *m = (PthreadMutex *) allocator.Malloc(sizeof (PthreadMutex)); + BAIL_IF(!m, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + rc = pthread_mutex_init(&m->mutex, NULL); + if (rc != 0) + { + allocator.Free(m); + BAIL(PHYSFS_ERR_OS_ERROR, NULL); + } /* if */ + + m->count = 0; + m->owner = (pthread_t) 0xDEADBEEF; + return ((void *) m); +} /* __PHYSFS_platformCreateMutex */ + + +void __PHYSFS_platformDestroyMutex(void *mutex) +{ + PthreadMutex *m = (PthreadMutex *) mutex; + + /* Destroying a locked mutex is a bug, but we'll try to be helpful. */ + if ((m->owner == pthread_self()) && (m->count > 0)) + pthread_mutex_unlock(&m->mutex); + + pthread_mutex_destroy(&m->mutex); + allocator.Free(m); +} /* __PHYSFS_platformDestroyMutex */ + + +int __PHYSFS_platformGrabMutex(void *mutex) +{ + PthreadMutex *m = (PthreadMutex *) mutex; + pthread_t tid = pthread_self(); + if (m->owner != tid) + { + if (pthread_mutex_lock(&m->mutex) != 0) + return 0; + m->owner = tid; + } /* if */ + + m->count++; + return 1; +} /* __PHYSFS_platformGrabMutex */ + + +void __PHYSFS_platformReleaseMutex(void *mutex) +{ + PthreadMutex *m = (PthreadMutex *) mutex; + assert(m->owner == pthread_self()); /* catch programming errors. */ + assert(m->count > 0); /* catch programming errors. */ + if (m->owner == pthread_self()) + { + if (--m->count == 0) + { + m->owner = (pthread_t) 0xDEADBEEF; + pthread_mutex_unlock(&m->mutex); + } /* if */ + } /* if */ +} /* __PHYSFS_platformReleaseMutex */ + +#endif /* PHYSFS_PLATFORM_POSIX */ + +/* end of physfs_platform_posix.c ... */ + diff --git a/Source/Dependencies/PhysicsFS/physfs_platform_qnx.c b/Source/Dependencies/PhysicsFS/physfs_platform_qnx.c new file mode 100644 index 000000000..46f9aa70a --- /dev/null +++ b/Source/Dependencies/PhysicsFS/physfs_platform_qnx.c @@ -0,0 +1,169 @@ +/* + * QNX support routines for PhysicsFS. + * + * Please see the file LICENSE.txt in the source's root directory. + * + * This file written by Ryan C. Gordon. + */ + +/* This is tested against QNX 7 at the moment. */ + +#define __PHYSICSFS_INTERNAL__ +#include "physfs_platforms.h" + +#ifdef PHYSFS_PLATFORM_QNX + +#include +#include +#include +#include + +#include "physfs_internal.h" + +int __PHYSFS_platformInit(void) +{ + return 1; /* always succeed. */ +} /* __PHYSFS_platformInit */ + + +void __PHYSFS_platformDeinit(void) +{ + /* no-op */ +} /* __PHYSFS_platformDeinit */ + + +char *__PHYSFS_platformCalcBaseDir(const char *argv0) +{ + char *retval = (char *) allocator.Malloc(PATH_MAX+1); + if (retval == NULL) + BAIL(PHYSFS_ERR_OUT_OF_MEMORY, NULL); + else + { + const int fd = open("/proc/self/exefile", O_RDONLY); + const ssize_t br = (fd == -1) ? -1 : read(fd, retval, PATH_MAX); + char *ptr; + + if (fd != -1) + close(fd); + + if ((br < 0) || (br > PATH_MAX)) + { + allocator.Free(retval); + BAIL(PHYSFS_ERR_OS_ERROR, NULL); + } /* if */ + + retval[br] = '\0'; + ptr = strrchr(retval, '/'); + if (ptr == NULL) /* uhoh! */ + { + allocator.Free(retval); + BAIL(PHYSFS_ERR_OS_ERROR, NULL); + } /* if */ + + ptr[1] = '\0'; /* chop off filename, leave dirs and '/' */ + + ptr = (char *) allocator.Realloc(retval, (ptr - retval) + 2); + if (ptr != NULL) /* just shrinking buffer; don't care if it failed. */ + retval = ptr; + } /* else */ + + return retval; +} /* __PHYSFS_platformCalcBaseDir */ + + +char *__PHYSFS_platformCalcPrefDir(const char *org, const char *app) +{ + /* !!! FIXME: this might be wrong; I don't know if there's a better method + on QNX, or if it follows XDG specs, etc. */ + char *retval = NULL; + const char *home = __PHYSFS_getUserDir(); + if (home) + { + const size_t len = strlen(home) + strlen(app) + 3; + retval = (char *) allocator.Malloc(len); + BAIL_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + snprintf(retval, len, "%s.%s/", home, app); + } /* if */ + return retval; +} /* __PHYSFS_platformCalcPrefDir */ + + +#if !PHYSFS_NO_CDROM_SUPPORT +#include +#include +#include +#include +#include + +static void checkPathForCD(const char *path, PHYSFS_StringCallback cb, void *d) +{ + struct stat statbuf; + int fd; + + /* The devctl() thing is QNX-specific. In this case, we query what is + probably the mountpoint for the device. statvfs() on that mountpoint + will tell use its filesystem type. */ + + if ( (stat(path, &statbuf) == 0) && + (S_ISBLK(statbuf.st_mode)) && + ((fd = open(path, O_RDONLY | O_NONBLOCK)) != -1) ) + { + char mnt[256] = { 0 }; + const int rc = devctl(fd, DCMD_FSYS_MOUNTED_BY, mnt, sizeof (mnt), 0); + close(fd); + if ( (rc == EOK) && (mnt[0]) ) + { + struct statvfs statvfsbuf; + if (statvfs(mnt, &statvfsbuf) == 0) + { + /* I don't know if this is a complete or accurate list. */ + const char *fstype = statvfsbuf.f_basetype; + const int iscd = ( (strcmp(fstype, "cd") == 0) || + (strcmp(fstype, "udf") == 0) ); + if (iscd) + cb(d, mnt); + } /* if */ + } /* if */ + } /* if */ +} /* checkPathForCD */ + +static void checkDevForCD(const char *dev, PHYSFS_StringCallback cb, void *d) +{ + size_t len; + char *path; + + if (dev[0] == '.') /* ignore "." and ".." */ + { + if ((dev[1] == '\0') || ((dev[1] == '.') && (dev[2] == '\0'))) + return; + } /* if */ + + len = strlen(dev) + 6; + path = (char *) __PHYSFS_smallAlloc(len); + if (!path) + return; /* oh well. */ + + snprintf(path, len, "/dev/%s", dev); + checkPathForCD(path, cb, d); + __PHYSFS_smallFree(path); +} /* checkDevForCD */ +#endif /* !PHYSFS_NO_CDROM_SUPPORT */ + +void __PHYSFS_platformDetectAvailableCDs(PHYSFS_StringCallback cb, void *data) +{ +#if !PHYSFS_NO_CDROM_SUPPORT + DIR *dirp = opendir("/dev"); + if (dirp) + { + struct dirent *dent; + while ((dent = readdir(dirp)) != NULL) + checkDevForCD(dent->d_name, cb, data); + closedir(dirp); + } /* if */ +#endif +} /* __PHYSFS_platformDetectAvailableCDs */ + +#endif /* PHYSFS_PLATFORM_QNX */ + +/* end of physfs_platform_qnx.c ... */ + diff --git a/Source/Dependencies/PhysicsFS/physfs_platform_unix.c b/Source/Dependencies/PhysicsFS/physfs_platform_unix.c new file mode 100644 index 000000000..10d93a7e5 --- /dev/null +++ b/Source/Dependencies/PhysicsFS/physfs_platform_unix.c @@ -0,0 +1,367 @@ +/* + * Unix support routines for PhysicsFS. + * + * Please see the file LICENSE.txt in the source's root directory. + * + * This file written by Ryan C. Gordon. + */ + +#define __PHYSICSFS_INTERNAL__ +#include "physfs_platforms.h" + +#ifdef PHYSFS_PLATFORM_UNIX + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if PHYSFS_NO_CDROM_SUPPORT +#elif PHYSFS_PLATFORM_LINUX +# define PHYSFS_HAVE_MNTENT_H 1 +#elif defined __CYGWIN__ +# define PHYSFS_HAVE_MNTENT_H 1 +#elif PHYSFS_PLATFORM_SOLARIS +# define PHYSFS_HAVE_SYS_MNTTAB_H 1 +#elif PHYSFS_PLATFORM_BSD +# define PHYSFS_HAVE_SYS_UCRED_H 1 +#else +# warning No CD-ROM support included. Either define your platform here, +# warning or define PHYSFS_NO_CDROM_SUPPORT=1 to confirm this is intentional. +#endif + +#ifdef PHYSFS_HAVE_SYS_UCRED_H +# ifdef PHYSFS_HAVE_MNTENT_H +# undef PHYSFS_HAVE_MNTENT_H /* don't do both... */ +# endif +# include +# include +#endif + +#ifdef PHYSFS_HAVE_MNTENT_H +#include +#endif + +#ifdef PHYSFS_HAVE_SYS_MNTTAB_H +#include +#endif + +#ifdef PHYSFS_PLATFORM_FREEBSD +#include +#endif + + +#include "physfs_internal.h" + +int __PHYSFS_platformInit(void) +{ + return 1; /* always succeed. */ +} /* __PHYSFS_platformInit */ + + +void __PHYSFS_platformDeinit(void) +{ + /* no-op */ +} /* __PHYSFS_platformDeinit */ + + +void __PHYSFS_platformDetectAvailableCDs(PHYSFS_StringCallback cb, void *data) +{ +#if (defined PHYSFS_NO_CDROM_SUPPORT) + /* no-op. */ + +#elif (defined PHYSFS_HAVE_SYS_UCRED_H) + int i; + struct statfs *mntbufp = NULL; + int mounts = getmntinfo(&mntbufp, MNT_NOWAIT); + + for (i = 0; i < mounts; i++) + { + int add_it = 0; + + if (strcmp(mntbufp[i].f_fstypename, "iso9660") == 0) + add_it = 1; + else if (strcmp( mntbufp[i].f_fstypename, "cd9660") == 0) + add_it = 1; + + /* add other mount types here */ + + if (add_it) + cb(data, mntbufp[i].f_mntonname); + } /* for */ + +#elif (defined PHYSFS_HAVE_MNTENT_H) + FILE *mounts = NULL; + struct mntent *ent = NULL; + + mounts = setmntent("/etc/mtab", "r"); + BAIL_IF(mounts == NULL, PHYSFS_ERR_IO, /*return void*/); + + while ( (ent = getmntent(mounts)) != NULL ) + { + int add_it = 0; + if (strcmp(ent->mnt_type, "iso9660") == 0) + add_it = 1; + else if (strcmp(ent->mnt_type, "udf") == 0) + add_it = 1; + + /* !!! FIXME: these might pick up floppy drives, right? */ + else if (strcmp(ent->mnt_type, "auto") == 0) + add_it = 1; + else if (strcmp(ent->mnt_type, "supermount") == 0) + add_it = 1; + + /* add other mount types here */ + + if (add_it) + cb(data, ent->mnt_dir); + } /* while */ + + endmntent(mounts); + +#elif (defined PHYSFS_HAVE_SYS_MNTTAB_H) + FILE *mounts = fopen(MNTTAB, "r"); + struct mnttab ent; + + BAIL_IF(mounts == NULL, PHYSFS_ERR_IO, /*return void*/); + while (getmntent(mounts, &ent) == 0) + { + int add_it = 0; + if (strcmp(ent.mnt_fstype, "hsfs") == 0) + add_it = 1; + + /* add other mount types here */ + + if (add_it) + cb(data, ent.mnt_mountp); + } /* while */ + + fclose(mounts); +#endif +} /* __PHYSFS_platformDetectAvailableCDs */ + + +/* + * See where program (bin) resides in the $PATH specified by (envr). + * returns a copy of the first element in envr that contains it, or NULL + * if it doesn't exist or there were other problems. PHYSFS_SetError() is + * called if we have a problem. + * + * (envr) will be scribbled over, and you are expected to allocator.Free() the + * return value when you're done with it. + */ +static char *findBinaryInPath(const char *bin, char *envr) +{ + size_t alloc_size = 0; + char *exe = NULL; + char *start = envr; + char *ptr; + + assert(bin != NULL); + assert(envr != NULL); + + do + { + size_t size; + size_t binlen; + + ptr = strchr(start, ':'); /* find next $PATH separator. */ + if (ptr) + *ptr = '\0'; + + binlen = strlen(bin); + size = strlen(start) + binlen + 2; + if (size >= alloc_size) + { + char *x = (char *) allocator.Realloc(exe, size); + if (!x) + { + if (exe != NULL) + allocator.Free(exe); + BAIL(PHYSFS_ERR_OUT_OF_MEMORY, NULL); + } /* if */ + + alloc_size = size; + exe = x; + } /* if */ + + /* build full binary path... */ + strcpy(exe, start); + if ((exe[0] == '\0') || (exe[strlen(exe) - 1] != '/')) + strcat(exe, "/"); + strcat(exe, bin); + + if (access(exe, X_OK) == 0) /* Exists as executable? We're done. */ + { + exe[(size - binlen) - 1] = '\0'; /* chop off filename, leave '/' */ + return exe; + } /* if */ + + start = ptr + 1; /* start points to beginning of next element. */ + } while (ptr != NULL); + + if (exe != NULL) + allocator.Free(exe); + + return NULL; /* doesn't exist in path. */ +} /* findBinaryInPath */ + + +static char *readSymLink(const char *path) +{ + ssize_t len = 64; + ssize_t rc = -1; + char *retval = NULL; + + while (1) + { + char *ptr = (char *) allocator.Realloc(retval, (size_t) len); + if (ptr == NULL) + break; /* out of memory. */ + retval = ptr; + + rc = readlink(path, retval, len); + if (rc == -1) + break; /* not a symlink, i/o error, etc. */ + + else if (rc < len) + { + retval[rc] = '\0'; /* readlink doesn't null-terminate. */ + return retval; /* we're good to go. */ + } /* else if */ + + len *= 2; /* grow buffer, try again. */ + } /* while */ + + if (retval != NULL) + allocator.Free(retval); + return NULL; +} /* readSymLink */ + + +char *__PHYSFS_platformCalcBaseDir(const char *argv0) +{ + char *retval = NULL; + const char *envr = NULL; + + /* Try to avoid using argv0 unless forced to. Try system-specific stuff. */ + + #if defined(PHYSFS_PLATFORM_FREEBSD) + { + char fullpath[PATH_MAX]; + size_t buflen = sizeof (fullpath); + int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; + if (sysctl(mib, 4, fullpath, &buflen, NULL, 0) != -1) + retval = __PHYSFS_strdup(fullpath); + } + #elif defined(PHYSFS_PLATFORM_SOLARIS) + { + const char *path = getexecname(); + if ((path != NULL) && (path[0] == '/')) /* must be absolute path... */ + retval = __PHYSFS_strdup(path); + } + #endif + + /* If there's a Linux-like /proc filesystem, you can get the full path to + * the current process from a symlink in there. + */ + + if (!retval && (access("/proc", F_OK) == 0)) + { + retval = readSymLink("/proc/self/exe"); + if (!retval) retval = readSymLink("/proc/curproc/file"); + if (!retval) retval = readSymLink("/proc/curproc/exe"); + if (retval == NULL) + { + /* older kernels don't have /proc/self ... try PID version... */ + const unsigned long long pid = (unsigned long long) getpid(); + char path[64]; + const int rc = (int) snprintf(path,sizeof(path),"/proc/%llu/exe",pid); + if ( (rc > 0) && (rc < sizeof(path)) ) + retval = readSymLink(path); + } /* if */ + } /* if */ + + if (retval != NULL) /* chop off filename. */ + { + char *ptr = strrchr(retval, '/'); + if (ptr != NULL) + *(ptr+1) = '\0'; + else /* shouldn't happen, but just in case... */ + { + allocator.Free(retval); + retval = NULL; + } /* else */ + } /* if */ + + /* No /proc/self/exe, etc, but we have an argv[0] we can parse? */ + if ((retval == NULL) && (argv0 != NULL)) + { + /* fast path: default behaviour can handle this. */ + if (strchr(argv0, '/') != NULL) + return NULL; /* higher level parses out real path from argv0. */ + + /* If there's no dirsep on argv0, then look through $PATH for it. */ + envr = getenv("PATH"); + if (envr != NULL) + { + char *path = (char *) __PHYSFS_smallAlloc(strlen(envr) + 1); + BAIL_IF(!path, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + strcpy(path, envr); + retval = findBinaryInPath(argv0, path); + __PHYSFS_smallFree(path); + } /* if */ + } /* if */ + + if (retval != NULL) + { + /* try to shrink buffer... */ + char *ptr = (char *) allocator.Realloc(retval, strlen(retval) + 1); + if (ptr != NULL) + retval = ptr; /* oh well if it failed. */ + } /* if */ + + return retval; +} /* __PHYSFS_platformCalcBaseDir */ + + +char *__PHYSFS_platformCalcPrefDir(const char *org, const char *app) +{ + /* + * We use XDG's base directory spec, even if you're not on Linux. + * This isn't strictly correct, but the results are relatively sane + * in any case. + * + * https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html + */ + const char *envr = getenv("XDG_DATA_HOME"); + const char *append = "/"; + char *retval = NULL; + size_t len = 0; + + if (!envr) + { + /* You end up with "$HOME/.local/share/Game Name 2" */ + envr = __PHYSFS_getUserDir(); + BAIL_IF_ERRPASS(!envr, NULL); /* oh well. */ + append = ".local/share/"; + } /* if */ + + len = strlen(envr) + strlen(append) + strlen(app) + 2; + retval = (char *) allocator.Malloc(len); + BAIL_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + snprintf(retval, len, "%s%s%s/", envr, append, app); + return retval; +} /* __PHYSFS_platformCalcPrefDir */ + +#endif /* PHYSFS_PLATFORM_UNIX */ + +/* end of physfs_platform_unix.c ... */ + diff --git a/Source/Dependencies/PhysicsFS/physfs_platform_windows.c b/Source/Dependencies/PhysicsFS/physfs_platform_windows.c new file mode 100644 index 000000000..c92e98d07 --- /dev/null +++ b/Source/Dependencies/PhysicsFS/physfs_platform_windows.c @@ -0,0 +1,1028 @@ +/* + * Windows support routines for PhysicsFS. + * + * Please see the file LICENSE.txt in the source's root directory. + * + * This file written by Ryan C. Gordon, and made sane by Gregory S. Read. + */ + +#define __PHYSICSFS_INTERNAL__ +#include "physfs_platforms.h" + +#ifdef PHYSFS_PLATFORM_WINDOWS + +/* Forcibly disable UNICODE macro, since we manage this ourselves. */ +#ifdef UNICODE +#undef UNICODE +#endif + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS 1 +#endif + +#define WIN32_LEAN_AND_MEAN 1 +#include + +#ifndef PHYSFS_PLATFORM_WINRT +#include +#include +#endif + +#if !defined(PHYSFS_NO_CDROM_SUPPORT) +#include +#endif + +#include +#include +#include + +#ifdef allocator /* apparently Windows 10 SDK conflicts here. */ +#undef allocator +#endif + +#include "physfs_internal.h" + +/* + * Users without the platform SDK don't have this defined. The original docs + * for SetFilePointer() just said to compare with 0xFFFFFFFF, so this should + * work as desired. + */ +#define PHYSFS_INVALID_SET_FILE_POINTER 0xFFFFFFFF + +/* just in case... */ +#define PHYSFS_INVALID_FILE_ATTRIBUTES 0xFFFFFFFF + +/* Not defined before the Vista SDK. */ +#define PHYSFS_FILE_ATTRIBUTE_REPARSE_POINT 0x400 +#define PHYSFS_IO_REPARSE_TAG_SYMLINK 0xA000000C + + +#define UTF8_TO_UNICODE_STACK(w_assignto, str) { \ + if (str == NULL) \ + w_assignto = NULL; \ + else { \ + const size_t len = (PHYSFS_uint64) ((strlen(str) + 1) * 2); \ + w_assignto = (WCHAR *) __PHYSFS_smallAlloc(len); \ + if (w_assignto != NULL) \ + PHYSFS_utf8ToUtf16(str, (PHYSFS_uint16 *) w_assignto, len); \ + } \ +} \ + +/* Note this counts WCHARs, not codepoints! */ +static PHYSFS_uint64 wStrLen(const WCHAR *wstr) +{ + PHYSFS_uint64 len = 0; + while (*(wstr++)) + len++; + return len; +} /* wStrLen */ + +static char *unicodeToUtf8Heap(const WCHAR *w_str) +{ + char *retval = NULL; + if (w_str != NULL) + { + void *ptr = NULL; + const PHYSFS_uint64 len = (wStrLen(w_str) * 4) + 1; + retval = allocator.Malloc(len); + BAIL_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + PHYSFS_utf8FromUtf16((const PHYSFS_uint16 *) w_str, retval, len); + ptr = allocator.Realloc(retval, strlen(retval) + 1); /* shrink. */ + if (ptr != NULL) + retval = (char *) ptr; + } /* if */ + return retval; +} /* unicodeToUtf8Heap */ + + +/* Some older APIs aren't in WinRT (only the "Ex" version, etc). + Since non-WinRT might not have the "Ex" version, we tapdance to use + the perfectly-fine-and-available-even-on-Win95 API on non-WinRT targets. */ + +static inline HANDLE winFindFirstFileW(const WCHAR *path, LPWIN32_FIND_DATAW d) +{ + #ifdef PHYSFS_PLATFORM_WINRT + return FindFirstFileExW(path, FindExInfoStandard, d, + FindExSearchNameMatch, NULL, 0); + #else + return FindFirstFileW(path, d); + #endif +} /* winFindFirstFileW */ + +static inline BOOL winInitializeCriticalSection(LPCRITICAL_SECTION lpcs) +{ + #ifdef PHYSFS_PLATFORM_WINRT + return InitializeCriticalSectionEx(lpcs, 2000, 0); + #else + InitializeCriticalSection(lpcs); + return TRUE; + #endif +} /* winInitializeCriticalSection */ + +static inline HANDLE winCreateFileW(const WCHAR *wfname, const DWORD mode, + const DWORD creation) +{ + const DWORD share = FILE_SHARE_READ | FILE_SHARE_WRITE; + #ifdef PHYSFS_PLATFORM_WINRT + return CreateFile2(wfname, mode, share, creation, NULL); + #else + return CreateFileW(wfname, mode, share, NULL, creation, + FILE_ATTRIBUTE_NORMAL, NULL); + #endif +} /* winCreateFileW */ + +static BOOL winSetFilePointer(HANDLE h, const PHYSFS_sint64 pos, + PHYSFS_sint64 *_newpos, const DWORD whence) +{ + #ifdef PHYSFS_PLATFORM_WINRT + LARGE_INTEGER lipos; + LARGE_INTEGER linewpos; + BOOL rc; + lipos.QuadPart = (LONGLONG) pos; + rc = SetFilePointerEx(h, lipos, &linewpos, whence); + if (_newpos) + *_newpos = (PHYSFS_sint64) linewpos.QuadPart; + return rc; + #else + const LONG low = (LONG) (pos & 0xFFFFFFFF); + LONG high = (LONG) ((pos >> 32) & 0xFFFFFFFF); + const DWORD rc = SetFilePointer(h, low, &high, whence); + /* 0xFFFFFFFF could be valid, so you have to check GetLastError too! */ + if (_newpos) + *_newpos = ((PHYSFS_sint64) rc) | (((PHYSFS_sint64) high) << 32); + if ((rc == PHYSFS_INVALID_SET_FILE_POINTER) && (GetLastError() != NO_ERROR)) + return FALSE; + return TRUE; + #endif +} /* winSetFilePointer */ + +static PHYSFS_sint64 winGetFileSize(HANDLE h) +{ + #ifdef PHYSFS_PLATFORM_WINRT + FILE_STANDARD_INFO info; + const BOOL rc = GetFileInformationByHandleEx(h, FileStandardInfo, + &info, sizeof (info)); + return rc ? (PHYSFS_sint64) info.EndOfFile.QuadPart : -1; + #else + DWORD high = 0; + const DWORD rc = GetFileSize(h, &high); + if ((rc == PHYSFS_INVALID_SET_FILE_POINTER) && (GetLastError() != NO_ERROR)) + return -1; + return (PHYSFS_sint64) ((((PHYSFS_uint64) high) << 32) | rc); + #endif +} /* winGetFileSize */ + + +static PHYSFS_ErrorCode errcodeFromWinApiError(const DWORD err) +{ + /* + * win32 error codes are sort of a tricky thing; Microsoft intentionally + * doesn't list which ones a given API might trigger, there are several + * with overlapping and unclear meanings...and there's 16 thousand of + * them in Windows 7. It looks like the ones we care about are in the + * first 500, but I can't say this list is perfect; we might miss + * important values or misinterpret others. + * + * Don't treat this list as anything other than a work in progress. + */ + switch (err) + { + case ERROR_SUCCESS: return PHYSFS_ERR_OK; + case ERROR_ACCESS_DENIED: return PHYSFS_ERR_PERMISSION; + case ERROR_NETWORK_ACCESS_DENIED: return PHYSFS_ERR_PERMISSION; + case ERROR_NOT_READY: return PHYSFS_ERR_IO; + case ERROR_CRC: return PHYSFS_ERR_IO; + case ERROR_SEEK: return PHYSFS_ERR_IO; + case ERROR_SECTOR_NOT_FOUND: return PHYSFS_ERR_IO; + case ERROR_NOT_DOS_DISK: return PHYSFS_ERR_IO; + case ERROR_WRITE_FAULT: return PHYSFS_ERR_IO; + case ERROR_READ_FAULT: return PHYSFS_ERR_IO; + case ERROR_DEV_NOT_EXIST: return PHYSFS_ERR_IO; + case ERROR_BUFFER_OVERFLOW: return PHYSFS_ERR_BAD_FILENAME; + case ERROR_INVALID_NAME: return PHYSFS_ERR_BAD_FILENAME; + case ERROR_BAD_PATHNAME: return PHYSFS_ERR_BAD_FILENAME; + case ERROR_DIRECTORY: return PHYSFS_ERR_BAD_FILENAME; + case ERROR_FILE_NOT_FOUND: return PHYSFS_ERR_NOT_FOUND; + case ERROR_PATH_NOT_FOUND: return PHYSFS_ERR_NOT_FOUND; + case ERROR_DELETE_PENDING: return PHYSFS_ERR_NOT_FOUND; + case ERROR_INVALID_DRIVE: return PHYSFS_ERR_NOT_FOUND; + case ERROR_HANDLE_DISK_FULL: return PHYSFS_ERR_NO_SPACE; + case ERROR_DISK_FULL: return PHYSFS_ERR_NO_SPACE; + case ERROR_WRITE_PROTECT: return PHYSFS_ERR_READ_ONLY; + case ERROR_LOCK_VIOLATION: return PHYSFS_ERR_BUSY; + case ERROR_SHARING_VIOLATION: return PHYSFS_ERR_BUSY; + case ERROR_CURRENT_DIRECTORY: return PHYSFS_ERR_BUSY; + case ERROR_DRIVE_LOCKED: return PHYSFS_ERR_BUSY; + case ERROR_PATH_BUSY: return PHYSFS_ERR_BUSY; + case ERROR_BUSY: return PHYSFS_ERR_BUSY; + case ERROR_NOT_ENOUGH_MEMORY: return PHYSFS_ERR_OUT_OF_MEMORY; + case ERROR_OUTOFMEMORY: return PHYSFS_ERR_OUT_OF_MEMORY; + case ERROR_DIR_NOT_EMPTY: return PHYSFS_ERR_DIR_NOT_EMPTY; + default: return PHYSFS_ERR_OS_ERROR; + } /* switch */ +} /* errcodeFromWinApiError */ + +static inline PHYSFS_ErrorCode errcodeFromWinApi(void) +{ + return errcodeFromWinApiError(GetLastError()); +} /* errcodeFromWinApi */ + + +#if defined(PHYSFS_NO_CDROM_SUPPORT) +#define detectAvailableCDs(cb, data) +#define deinitCDThread() +#else +static HANDLE detectCDThreadHandle = NULL; +static HWND detectCDHwnd = NULL; +static volatile DWORD drivesWithMediaBitmap = 0; + +typedef BOOL (WINAPI *fnSTEM)(DWORD, LPDWORD b); + +static DWORD pollDiscDrives(void) +{ + /* Try to use SetThreadErrorMode(), which showed up in Windows 7. */ + HANDLE lib = LoadLibraryA("kernel32.dll"); + fnSTEM stem = NULL; + char drive[4] = { 'x', ':', '\\', '\0' }; + DWORD oldErrorMode = 0; + DWORD drives = 0; + DWORD i; + + if (lib) + stem = (fnSTEM) GetProcAddress(lib, "SetThreadErrorMode"); + + if (stem) + stem(SEM_FAILCRITICALERRORS, &oldErrorMode); + else + oldErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS); + + /* Do detection. This may block if a disc is spinning up. */ + for (i = 'A'; i <= 'Z'; i++) + { + DWORD tmp = 0; + drive[0] = (char) i; + if (GetDriveTypeA(drive) != DRIVE_CDROM) + continue; + + /* If this function succeeds, there's media in the drive */ + if (GetVolumeInformationA(drive, NULL, 0, NULL, NULL, &tmp, NULL, 0)) + drives |= (1 << (i - 'A')); + } /* for */ + + if (stem) + stem(oldErrorMode, NULL); + else + SetErrorMode(oldErrorMode); + + if (lib) + FreeLibrary(lib); + + return drives; +} /* pollDiscDrives */ + + +static LRESULT CALLBACK detectCDWndProc(HWND hwnd, UINT msg, + WPARAM wp, LPARAM lparam) +{ + PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR) lparam; + PDEV_BROADCAST_VOLUME lpdbv = (PDEV_BROADCAST_VOLUME) lparam; + const int removed = (wp == DBT_DEVICEREMOVECOMPLETE); + + if (msg == WM_DESTROY) + return 0; + else if ((msg != WM_DEVICECHANGE) || + ((wp != DBT_DEVICEARRIVAL) && (wp != DBT_DEVICEREMOVECOMPLETE)) || + (lpdb->dbch_devicetype != DBT_DEVTYP_VOLUME) || + ((lpdbv->dbcv_flags & DBTF_MEDIA) == 0)) + { + return DefWindowProcW(hwnd, msg, wp, lparam); + } /* else if */ + + if (removed) + drivesWithMediaBitmap &= ~lpdbv->dbcv_unitmask; + else + drivesWithMediaBitmap |= lpdbv->dbcv_unitmask; + + return TRUE; +} /* detectCDWndProc */ + + +static DWORD WINAPI detectCDThread(LPVOID arg) +{ + HANDLE initialDiscDetectionComplete = *((HANDLE *) arg); + const char *classname = "PhysicsFSDetectCDCatcher"; + const char *winname = "PhysicsFSDetectCDMsgWindow"; + HINSTANCE hInstance = GetModuleHandleW(NULL); + ATOM class_atom = 0; + WNDCLASSEXA wce; + MSG msg; + + memset(&wce, '\0', sizeof (wce)); + wce.cbSize = sizeof (wce); + wce.lpfnWndProc = detectCDWndProc; + wce.lpszClassName = classname; + wce.hInstance = hInstance; + class_atom = RegisterClassExA(&wce); + if (class_atom == 0) + { + SetEvent(initialDiscDetectionComplete); /* let main thread go on. */ + return 0; + } /* if */ + + detectCDHwnd = CreateWindowExA(0, classname, winname, WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, HWND_DESKTOP, NULL, hInstance, NULL); + + if (detectCDHwnd == NULL) + { + SetEvent(initialDiscDetectionComplete); /* let main thread go on. */ + UnregisterClassA(classname, hInstance); + return 0; + } /* if */ + + /* We'll get events when discs come and go from now on. */ + + /* Do initial detection, possibly blocking awhile... */ + drivesWithMediaBitmap = pollDiscDrives(); + + SetEvent(initialDiscDetectionComplete); /* let main thread go on. */ + + do + { + const BOOL rc = GetMessageW(&msg, detectCDHwnd, 0, 0); + if ((rc == 0) || (rc == -1)) + break; /* don't care if WM_QUIT or error break this loop. */ + TranslateMessage(&msg); + DispatchMessageW(&msg); + } while (1); + + /* we've been asked to quit. */ + DestroyWindow(detectCDHwnd); + UnregisterClassA(classname, hInstance); + return 0; +} /* detectCDThread */ + +static void detectAvailableCDs(PHYSFS_StringCallback cb, void *data) +{ + char drive_str[4] = { 'x', ':', '\\', '\0' }; + DWORD drives = 0; + DWORD i; + + /* + * If you poll a drive while a user is inserting a disc, the OS will + * block this thread until the drive has spun up. So we swallow the risk + * once for initial detection, and spin a thread that will get device + * events thereafter, for apps that use this interface to poll for + * disc insertion. + */ + if (!detectCDThreadHandle) + { + HANDLE initialDetectDone = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!initialDetectDone) + return; /* oh well. */ + + detectCDThreadHandle = CreateThread(NULL, 0, detectCDThread, + &initialDetectDone, 0, NULL); + if (detectCDThreadHandle) + WaitForSingleObject(initialDetectDone, INFINITE); + CloseHandle(initialDetectDone); + + if (!detectCDThreadHandle) + return; /* oh well. */ + } /* if */ + + drives = drivesWithMediaBitmap; /* whatever the thread has seen, we take. */ + for (i = 'A'; i <= 'Z'; i++) + { + if (drives & (1 << (i - 'A'))) + { + drive_str[0] = (char) i; + cb(data, drive_str); + } /* if */ + } /* for */ +} /* detectAvailableCDs */ + +static void deinitCDThread(void) +{ + if (detectCDThreadHandle) + { + if (detectCDHwnd) + PostMessageW(detectCDHwnd, WM_QUIT, 0, 0); + CloseHandle(detectCDThreadHandle); + detectCDThreadHandle = NULL; + drivesWithMediaBitmap = 0; + } /* if */ +} /* deinitCDThread */ +#endif + + +void __PHYSFS_platformDetectAvailableCDs(PHYSFS_StringCallback cb, void *data) +{ + detectAvailableCDs(cb, data); +} /* __PHYSFS_platformDetectAvailableCDs */ + +#ifdef PHYSFS_PLATFORM_WINRT +static char *calcDirAppendSep(const WCHAR *wdir) +{ + size_t len; + void *ptr; + char *retval; + BAIL_IF(!wdir, errcodeFromWinApi(), NULL); + retval = unicodeToUtf8Heap(wdir); + BAIL_IF_ERRPASS(!retval, NULL); + len = strlen(retval); + ptr = allocator.Realloc(retval, len + 2); + if (!ptr) + { + allocator.Free(retval); + BAIL(PHYSFS_ERR_OUT_OF_MEMORY, NULL); + } /* if */ + retval = (char *) ptr; + retval[len] = '\\'; + retval[len+1] = '\0'; + return retval; +} /* calcDirAppendSep */ +#endif + +char *__PHYSFS_platformCalcBaseDir(const char *argv0) +{ +#ifdef PHYSFS_PLATFORM_WINRT + return calcDirAppendSep((const WCHAR *) __PHYSFS_winrtCalcBaseDir()); +#else + char *retval = NULL; + DWORD buflen = 64; + LPWSTR modpath = NULL; + + while (1) + { + DWORD rc; + void *ptr; + + if ( (ptr = allocator.Realloc(modpath, buflen*sizeof(WCHAR))) == NULL ) + { + allocator.Free(modpath); + BAIL(PHYSFS_ERR_OUT_OF_MEMORY, NULL); + } /* if */ + modpath = (LPWSTR) ptr; + + rc = GetModuleFileNameW(NULL, modpath, buflen); + if (rc == 0) + { + allocator.Free(modpath); + BAIL(errcodeFromWinApi(), NULL); + } /* if */ + + if (rc < buflen) + { + buflen = rc; + break; + } /* if */ + + buflen *= 2; + } /* while */ + + if (buflen > 0) /* just in case... */ + { + WCHAR *ptr = (modpath + buflen) - 1; + while (ptr != modpath) + { + if (*ptr == '\\') + break; + ptr--; + } /* while */ + + if ((ptr == modpath) && (*ptr != '\\')) + PHYSFS_setErrorCode(PHYSFS_ERR_OTHER_ERROR); /* oh well. */ + else + { + *(ptr+1) = '\0'; /* chop off filename. */ + retval = unicodeToUtf8Heap(modpath); + } /* else */ + } /* else */ + allocator.Free(modpath); + + return retval; /* w00t. */ +#endif +} /* __PHYSFS_platformCalcBaseDir */ + + +char *__PHYSFS_platformCalcPrefDir(const char *org, const char *app) +{ +#ifdef PHYSFS_PLATFORM_WINRT + return calcDirAppendSep((const WCHAR *) __PHYSFS_winrtCalcPrefDir()); +#else + /* + * Vista and later has a new API for this, but SHGetFolderPath works there, + * and apparently just wraps the new API. This is the new way to do it: + * + * SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_CREATE, + * NULL, &wszPath); + */ + + WCHAR path[MAX_PATH]; + char *utf8 = NULL; + size_t len = 0; + char *retval = NULL; + + if (!SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_APPDATA | CSIDL_FLAG_CREATE, + NULL, 0, path))) + BAIL(PHYSFS_ERR_OS_ERROR, NULL); + + utf8 = unicodeToUtf8Heap(path); + BAIL_IF_ERRPASS(!utf8, NULL); + len = strlen(utf8) + strlen(org) + strlen(app) + 4; + retval = allocator.Malloc(len); + if (!retval) + { + allocator.Free(utf8); + BAIL(PHYSFS_ERR_OUT_OF_MEMORY, NULL); + } /* if */ + + snprintf(retval, len, "%s\\%s\\%s\\", utf8, org, app); + allocator.Free(utf8); + return retval; +#endif +} /* __PHYSFS_platformCalcPrefDir */ + + +char *__PHYSFS_platformCalcUserDir(void) +{ +#ifdef PHYSFS_PLATFORM_WINRT + return calcDirAppendSep((const WCHAR *) __PHYSFS_winrtCalcPrefDir()); +#else + typedef BOOL (WINAPI *fnGetUserProfDirW)(HANDLE, LPWSTR, LPDWORD); + fnGetUserProfDirW pGetDir = NULL; + HANDLE lib = NULL; + HANDLE accessToken = NULL; /* Security handle to process */ + char *retval = NULL; + + lib = LoadLibraryA("userenv.dll"); + BAIL_IF(!lib, errcodeFromWinApi(), NULL); + pGetDir=(fnGetUserProfDirW) GetProcAddress(lib,"GetUserProfileDirectoryW"); + GOTO_IF(!pGetDir, errcodeFromWinApi(), done); + + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &accessToken)) + GOTO(errcodeFromWinApi(), done); + else + { + DWORD psize = 0; + LPWSTR wstr = NULL; + BOOL rc = 0; + + /* + * Should fail. Will write the size of the profile path in + * psize. Also note that the second parameter can't be + * NULL or the function fails on Windows XP, but has to be NULL on + * Windows 10 or it will fail. :( + */ + rc = pGetDir(accessToken, NULL, &psize); + GOTO_IF(rc, PHYSFS_ERR_OS_ERROR, done); /* should have failed! */ + + if (psize == 0) /* probably on Windows XP, try a different way. */ + { + WCHAR x = 0; + rc = pGetDir(accessToken, &x, &psize); + GOTO_IF(rc, PHYSFS_ERR_OS_ERROR, done); /* should have failed! */ + GOTO_IF(!psize, PHYSFS_ERR_OS_ERROR, done); /* Uhoh... */ + } /* if */ + + /* Allocate memory for the profile directory */ + wstr = (LPWSTR) __PHYSFS_smallAlloc((psize + 1) * sizeof (WCHAR)); + if (wstr != NULL) + { + if (pGetDir(accessToken, wstr, &psize)) + { + /* Make sure it ends in a dirsep. We allocated +1 for this. */ + if (wstr[psize - 2] != '\\') + { + wstr[psize - 1] = '\\'; + wstr[psize - 0] = '\0'; + } /* if */ + retval = unicodeToUtf8Heap(wstr); + } /* if */ + __PHYSFS_smallFree(wstr); + } /* if */ + } /* if */ + +done: + if (accessToken) + CloseHandle(accessToken); + FreeLibrary(lib); + return retval; /* We made it: hit the showers. */ +#endif +} /* __PHYSFS_platformCalcUserDir */ + + +int __PHYSFS_platformInit(void) +{ + return 1; /* It's all good */ +} /* __PHYSFS_platformInit */ + + +void __PHYSFS_platformDeinit(void) +{ + deinitCDThread(); +} /* __PHYSFS_platformDeinit */ + + +void *__PHYSFS_platformGetThreadID(void) +{ + return ( (void *) ((size_t) GetCurrentThreadId()) ); +} /* __PHYSFS_platformGetThreadID */ + + +PHYSFS_EnumerateCallbackResult __PHYSFS_platformEnumerate(const char *dirname, + PHYSFS_EnumerateCallback callback, + const char *origdir, void *callbackdata) +{ + PHYSFS_EnumerateCallbackResult retval = PHYSFS_ENUM_OK; + HANDLE dir = INVALID_HANDLE_VALUE; + WIN32_FIND_DATAW entw; + size_t len = strlen(dirname); + char *searchPath = NULL; + WCHAR *wSearchPath = NULL; + + /* Allocate a new string for path, maybe '\\', "*", and NULL terminator */ + searchPath = (char *) __PHYSFS_smallAlloc(len + 3); + BAIL_IF(!searchPath, PHYSFS_ERR_OUT_OF_MEMORY, PHYSFS_ENUM_ERROR); + + /* Copy current dirname */ + strcpy(searchPath, dirname); + + /* if there's no '\\' at the end of the path, stick one in there. */ + if (searchPath[len - 1] != '\\') + { + searchPath[len++] = '\\'; + searchPath[len] = '\0'; + } /* if */ + + /* Append the "*" to the end of the string */ + strcat(searchPath, "*"); + + UTF8_TO_UNICODE_STACK(wSearchPath, searchPath); + __PHYSFS_smallFree(searchPath); + BAIL_IF_ERRPASS(!wSearchPath, PHYSFS_ENUM_ERROR); + + dir = winFindFirstFileW(wSearchPath, &entw); + __PHYSFS_smallFree(wSearchPath); + BAIL_IF(dir==INVALID_HANDLE_VALUE, errcodeFromWinApi(), PHYSFS_ENUM_ERROR); + + do + { + const WCHAR *fn = entw.cFileName; + char *utf8; + + if (fn[0] == '.') /* ignore "." and ".." */ + { + if ((fn[1] == '\0') || ((fn[1] == '.') && (fn[2] == '\0'))) + continue; + } /* if */ + + utf8 = unicodeToUtf8Heap(fn); + if (utf8 == NULL) + retval = -1; + else + { + retval = callback(callbackdata, origdir, utf8); + allocator.Free(utf8); + if (retval == PHYSFS_ENUM_ERROR) + PHYSFS_setErrorCode(PHYSFS_ERR_APP_CALLBACK); + } /* else */ + } while ((retval == PHYSFS_ENUM_OK) && (FindNextFileW(dir, &entw) != 0)); + + FindClose(dir); + + return retval; +} /* __PHYSFS_platformEnumerate */ + + +int __PHYSFS_platformMkDir(const char *path) +{ + WCHAR *wpath; + DWORD rc; + UTF8_TO_UNICODE_STACK(wpath, path); + rc = CreateDirectoryW(wpath, NULL); + __PHYSFS_smallFree(wpath); + BAIL_IF(rc == 0, errcodeFromWinApi(), 0); + return 1; +} /* __PHYSFS_platformMkDir */ + + +static HANDLE doOpen(const char *fname, DWORD mode, DWORD creation) +{ + HANDLE fileh; + WCHAR *wfname; + + UTF8_TO_UNICODE_STACK(wfname, fname); + BAIL_IF(!wfname, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + + fileh = winCreateFileW(wfname, mode, creation); + __PHYSFS_smallFree(wfname); + + if (fileh == INVALID_HANDLE_VALUE) + BAIL(errcodeFromWinApi(), INVALID_HANDLE_VALUE); + + return fileh; +} /* doOpen */ + + +void *__PHYSFS_platformOpenRead(const char *filename) +{ + HANDLE h = doOpen(filename, GENERIC_READ, OPEN_EXISTING); + return (h == INVALID_HANDLE_VALUE) ? NULL : (void *) h; +} /* __PHYSFS_platformOpenRead */ + + +void *__PHYSFS_platformOpenWrite(const char *filename) +{ + HANDLE h = doOpen(filename, GENERIC_WRITE, CREATE_ALWAYS); + return (h == INVALID_HANDLE_VALUE) ? NULL : (void *) h; +} /* __PHYSFS_platformOpenWrite */ + + +void *__PHYSFS_platformOpenAppend(const char *filename) +{ + HANDLE h = doOpen(filename, GENERIC_WRITE, OPEN_ALWAYS); + BAIL_IF_ERRPASS(h == INVALID_HANDLE_VALUE, NULL); + + if (!winSetFilePointer(h, 0, NULL, FILE_END)) + { + const PHYSFS_ErrorCode err = errcodeFromWinApi(); + CloseHandle(h); + BAIL(err, NULL); + } /* if */ + + return (void *) h; +} /* __PHYSFS_platformOpenAppend */ + + +PHYSFS_sint64 __PHYSFS_platformRead(void *opaque, void *buf, PHYSFS_uint64 len) +{ + HANDLE h = (HANDLE) opaque; + PHYSFS_sint64 totalRead = 0; + + if (!__PHYSFS_ui64FitsAddressSpace(len)) + BAIL(PHYSFS_ERR_INVALID_ARGUMENT, -1); + + while (len > 0) + { + const DWORD thislen = (len > 0xFFFFFFFF) ? 0xFFFFFFFF : (DWORD) len; + DWORD numRead = 0; + if (!ReadFile(h, buf, thislen, &numRead, NULL)) + BAIL(errcodeFromWinApi(), -1); + len -= (PHYSFS_uint64) numRead; + totalRead += (PHYSFS_sint64) numRead; + if (numRead != thislen) + break; + } /* while */ + + return totalRead; +} /* __PHYSFS_platformRead */ + + +PHYSFS_sint64 __PHYSFS_platformWrite(void *opaque, const void *buffer, + PHYSFS_uint64 len) +{ + HANDLE h = (HANDLE) opaque; + PHYSFS_sint64 totalWritten = 0; + + if (!__PHYSFS_ui64FitsAddressSpace(len)) + BAIL(PHYSFS_ERR_INVALID_ARGUMENT, -1); + + while (len > 0) + { + const DWORD thislen = (len > 0xFFFFFFFF) ? 0xFFFFFFFF : (DWORD) len; + DWORD numWritten = 0; + if (!WriteFile(h, buffer, thislen, &numWritten, NULL)) + BAIL(errcodeFromWinApi(), -1); + len -= (PHYSFS_uint64) numWritten; + totalWritten += (PHYSFS_sint64) numWritten; + if (numWritten != thislen) + break; + } /* while */ + + return totalWritten; +} /* __PHYSFS_platformWrite */ + + +int __PHYSFS_platformSeek(void *opaque, PHYSFS_uint64 pos) +{ + HANDLE h = (HANDLE) opaque; + const PHYSFS_sint64 spos = (PHYSFS_sint64) pos; + BAIL_IF(!winSetFilePointer(h,spos,NULL,FILE_BEGIN), errcodeFromWinApi(), 0); + return 1; /* No error occured */ +} /* __PHYSFS_platformSeek */ + + +PHYSFS_sint64 __PHYSFS_platformTell(void *opaque) +{ + HANDLE h = (HANDLE) opaque; + PHYSFS_sint64 pos = 0; + BAIL_IF(!winSetFilePointer(h,0,&pos,FILE_CURRENT), errcodeFromWinApi(), -1); + return pos; +} /* __PHYSFS_platformTell */ + + +PHYSFS_sint64 __PHYSFS_platformFileLength(void *opaque) +{ + HANDLE h = (HANDLE) opaque; + const PHYSFS_sint64 retval = winGetFileSize(h); + BAIL_IF(retval < 0, errcodeFromWinApi(), -1); + return retval; +} /* __PHYSFS_platformFileLength */ + + +int __PHYSFS_platformFlush(void *opaque) +{ + HANDLE h = (HANDLE) opaque; + BAIL_IF(!FlushFileBuffers(h), errcodeFromWinApi(), 0); + return 1; +} /* __PHYSFS_platformFlush */ + + +void __PHYSFS_platformClose(void *opaque) +{ + HANDLE h = (HANDLE) opaque; + (void) CloseHandle(h); /* ignore errors. You should have flushed! */ +} /* __PHYSFS_platformClose */ + + +static int doPlatformDelete(LPWSTR wpath) +{ + WIN32_FILE_ATTRIBUTE_DATA info; + if (!GetFileAttributesExW(wpath, GetFileExInfoStandard, &info)) + BAIL(errcodeFromWinApi(), 0); + else + { + const int isdir = (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); + const BOOL rc = isdir ? RemoveDirectoryW(wpath) : DeleteFileW(wpath); + BAIL_IF(!rc, errcodeFromWinApi(), 0); + } /* else */ + return 1; /* if you made it here, it worked. */ +} /* doPlatformDelete */ + + +int __PHYSFS_platformDelete(const char *path) +{ + int retval = 0; + LPWSTR wpath = NULL; + UTF8_TO_UNICODE_STACK(wpath, path); + BAIL_IF(!wpath, PHYSFS_ERR_OUT_OF_MEMORY, 0); + retval = doPlatformDelete(wpath); + __PHYSFS_smallFree(wpath); + return retval; +} /* __PHYSFS_platformDelete */ + + +void *__PHYSFS_platformCreateMutex(void) +{ + LPCRITICAL_SECTION lpcs; + lpcs = (LPCRITICAL_SECTION) allocator.Malloc(sizeof (CRITICAL_SECTION)); + BAIL_IF(!lpcs, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + + if (!winInitializeCriticalSection(lpcs)) + { + allocator.Free(lpcs); + BAIL(errcodeFromWinApi(), NULL); + } /* if */ + + return lpcs; +} /* __PHYSFS_platformCreateMutex */ + + +void __PHYSFS_platformDestroyMutex(void *mutex) +{ + DeleteCriticalSection((LPCRITICAL_SECTION) mutex); + allocator.Free(mutex); +} /* __PHYSFS_platformDestroyMutex */ + + +int __PHYSFS_platformGrabMutex(void *mutex) +{ + EnterCriticalSection((LPCRITICAL_SECTION) mutex); + return 1; +} /* __PHYSFS_platformGrabMutex */ + + +void __PHYSFS_platformReleaseMutex(void *mutex) +{ + LeaveCriticalSection((LPCRITICAL_SECTION) mutex); +} /* __PHYSFS_platformReleaseMutex */ + + +static PHYSFS_sint64 FileTimeToPhysfsTime(const FILETIME *ft) +{ + SYSTEMTIME st_utc; + SYSTEMTIME st_localtz; + TIME_ZONE_INFORMATION tzi; + DWORD tzid; + PHYSFS_sint64 retval; + struct tm tm; + BOOL rc; + + BAIL_IF(!FileTimeToSystemTime(ft, &st_utc), errcodeFromWinApi(), -1); + tzid = GetTimeZoneInformation(&tzi); + BAIL_IF(tzid == TIME_ZONE_ID_INVALID, errcodeFromWinApi(), -1); + rc = SystemTimeToTzSpecificLocalTime(&tzi, &st_utc, &st_localtz); + BAIL_IF(!rc, errcodeFromWinApi(), -1); + + /* Convert to a format that mktime() can grok... */ + tm.tm_sec = st_localtz.wSecond; + tm.tm_min = st_localtz.wMinute; + tm.tm_hour = st_localtz.wHour; + tm.tm_mday = st_localtz.wDay; + tm.tm_mon = st_localtz.wMonth - 1; + tm.tm_year = st_localtz.wYear - 1900; + tm.tm_wday = -1 /*st_localtz.wDayOfWeek*/; + tm.tm_yday = -1; + tm.tm_isdst = -1; + + /* Convert to a format PhysicsFS can grok... */ + retval = (PHYSFS_sint64) mktime(&tm); + BAIL_IF(retval == -1, PHYSFS_ERR_OS_ERROR, -1); + return retval; +} /* FileTimeToPhysfsTime */ + + +/* check for symlinks. These exist in NTFS 3.1 (WinXP), even though + they aren't really available to userspace before Vista. I wonder + what would happen if you put an NTFS disk with a symlink on it + into an XP machine, though; would this flag get set? + NTFS symlinks are a form of "reparse point" (junction, volume mount, + etc), so if the REPARSE_POINT attribute is set, check for the symlink + tag thereafter. This assumes you already read in the file attributes. */ +static int isSymlink(const WCHAR *wpath, const DWORD attr) +{ + WIN32_FIND_DATAW w32dw; + HANDLE h; + + if ((attr & PHYSFS_FILE_ATTRIBUTE_REPARSE_POINT) == 0) + return 0; /* not a reparse point? Definitely not a symlink. */ + + h = winFindFirstFileW(wpath, &w32dw); + if (h == INVALID_HANDLE_VALUE) + return 0; /* ...maybe the file just vanished...? */ + + FindClose(h); + return (w32dw.dwReserved0 == PHYSFS_IO_REPARSE_TAG_SYMLINK); +} /* isSymlink */ + + +int __PHYSFS_platformStat(const char *filename, PHYSFS_Stat *st, const int follow) +{ + WIN32_FILE_ATTRIBUTE_DATA winstat; + WCHAR *wstr = NULL; + DWORD err = 0; + BOOL rc = 0; + int issymlink = 0; + + UTF8_TO_UNICODE_STACK(wstr, filename); + BAIL_IF(!wstr, PHYSFS_ERR_OUT_OF_MEMORY, 0); + rc = GetFileAttributesExW(wstr, GetFileExInfoStandard, &winstat); + + if (!rc) + err = GetLastError(); + else /* check for symlink while wstr is still available */ + issymlink = !follow && isSymlink(wstr, winstat.dwFileAttributes); + + __PHYSFS_smallFree(wstr); + BAIL_IF(!rc, errcodeFromWinApiError(err), 0); + + st->modtime = FileTimeToPhysfsTime(&winstat.ftLastWriteTime); + st->accesstime = FileTimeToPhysfsTime(&winstat.ftLastAccessTime); + st->createtime = FileTimeToPhysfsTime(&winstat.ftCreationTime); + + if (issymlink) + { + st->filetype = PHYSFS_FILETYPE_SYMLINK; + st->filesize = 0; + } /* if */ + + else if (winstat.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + st->filetype = PHYSFS_FILETYPE_DIRECTORY; + st->filesize = 0; + } /* else if */ + + else if (winstat.dwFileAttributes & (FILE_ATTRIBUTE_OFFLINE | FILE_ATTRIBUTE_DEVICE)) + { + st->filetype = PHYSFS_FILETYPE_OTHER; + st->filesize = (((PHYSFS_uint64) winstat.nFileSizeHigh) << 32) | winstat.nFileSizeLow; + } /* else if */ + + else + { + st->filetype = PHYSFS_FILETYPE_REGULAR; + st->filesize = (((PHYSFS_uint64) winstat.nFileSizeHigh) << 32) | winstat.nFileSizeLow; + } /* else */ + + st->readonly = ((winstat.dwFileAttributes & FILE_ATTRIBUTE_READONLY) != 0); + + return 1; +} /* __PHYSFS_platformStat */ + +#endif /* PHYSFS_PLATFORM_WINDOWS */ + +/* end of physfs_platform_windows.c ... */ + + diff --git a/Source/Dependencies/PhysicsFS/physfs_platform_winrt.cpp b/Source/Dependencies/PhysicsFS/physfs_platform_winrt.cpp new file mode 100644 index 000000000..f50fc9d3a --- /dev/null +++ b/Source/Dependencies/PhysicsFS/physfs_platform_winrt.cpp @@ -0,0 +1,41 @@ +/* + * Windows Runtime (WinRT) support routines for PhysicsFS. + * + * Please see the file LICENSE.txt in the source's root directory. + * + * This file originally written by Martin "T-Bone" Ahrnbom, but was mostly + * merged into physfs_platform_windows.c by Ryan C. Gordon (so please harass + * Ryan about bugs and not Martin). + */ + +/* (There used to be instructions on how to make a WinRT project, but at + this point, either CMake will do it for you or you should just drop + PhysicsFS's sources into your existing project. --ryan.) */ + +#define __PHYSICSFS_INTERNAL__ +#include "physfs_platforms.h" + +#ifdef PHYSFS_PLATFORM_WINRT + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS 1 +#endif +#include + +#include "physfs_internal.h" + +const void *__PHYSFS_winrtCalcBaseDir(void) +{ + return Windows::ApplicationModel::Package::Current->InstalledLocation->Path->Data(); +} /* __PHYSFS_winrtCalcBaseDir */ + +const void *__PHYSFS_winrtCalcPrefDir(void) +{ + return Windows::Storage::ApplicationData::Current->LocalFolder->Path->Data(); +} /* __PHYSFS_winrtCalcBaseDir */ + + +#endif /* PHYSFS_PLATFORM_WINRT */ + +/* end of physfs_platform_winrt.cpp ... */ + diff --git a/Source/Dependencies/PhysicsFS/physfs_platforms.h b/Source/Dependencies/PhysicsFS/physfs_platforms.h new file mode 100644 index 000000000..d4e4bfdc3 --- /dev/null +++ b/Source/Dependencies/PhysicsFS/physfs_platforms.h @@ -0,0 +1,80 @@ +#ifndef _INCL_PHYSFS_PLATFORMS +#define _INCL_PHYSFS_PLATFORMS + +#ifndef __PHYSICSFS_INTERNAL__ +#error Do not include this header from your applications. +#endif + +/* + * These only define the platforms to determine which files in the platforms + * directory should be compiled. For example, technically BeOS can be called + * a "unix" system, but since it doesn't use unix.c, we don't define + * PHYSFS_PLATFORM_UNIX on that system. + */ + +#if (defined __HAIKU__) +# define PHYSFS_PLATFORM_HAIKU 1 +# define PHYSFS_PLATFORM_POSIX 1 +#elif ((defined __BEOS__) || (defined __beos__)) +# error BeOS support was dropped since PhysicsFS 2.1. Sorry. Try Haiku! +#elif (defined _WIN32_WCE) || (defined _WIN64_WCE) +# error PocketPC support was dropped since PhysicsFS 2.1. Sorry. Try WinRT! +#elif (defined(_MSC_VER) && (_MSC_VER >= 1700) && !_USING_V110_SDK71_) /* _MSC_VER==1700 for MSVC 2012 */ +# include +# define PHYSFS_PLATFORM_WINDOWS 1 +# if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP) && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) +# define PHYSFS_NO_CDROM_SUPPORT 1 +# define PHYSFS_PLATFORM_WINRT 1 +# endif +#elif (((defined _WIN32) || (defined _WIN64)) && (!defined __CYGWIN__)) +# define PHYSFS_PLATFORM_WINDOWS 1 +#elif defined(__OS2__) || defined(OS2) +# define PHYSFS_PLATFORM_OS2 1 +#elif ((defined __MACH__) && (defined __APPLE__)) +/* To check if iOS or not, we need to include this file */ +# include +# if ((TARGET_IPHONE_SIMULATOR) || (TARGET_OS_IPHONE)) +# define PHYSFS_NO_CDROM_SUPPORT 1 +# endif +# define PHYSFS_PLATFORM_APPLE 1 +# define PHYSFS_PLATFORM_POSIX 1 +#elif defined(macintosh) +# error Classic Mac OS support was dropped from PhysicsFS 2.0. Move to OS X. +#elif defined(ANDROID) +# define PHYSFS_PLATFORM_LINUX 1 +# define PHYSFS_PLATFORM_UNIX 1 +# define PHYSFS_PLATFORM_POSIX 1 +# define PHYSFS_NO_CDROM_SUPPORT 1 +#elif defined(__linux) +# define PHYSFS_PLATFORM_LINUX 1 +# define PHYSFS_PLATFORM_UNIX 1 +# define PHYSFS_PLATFORM_POSIX 1 +#elif defined(__sun) || defined(sun) +# define PHYSFS_PLATFORM_SOLARIS 1 +# define PHYSFS_PLATFORM_UNIX 1 +# define PHYSFS_PLATFORM_POSIX 1 +#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) +# define PHYSFS_PLATFORM_FREEBSD 1 +# define PHYSFS_PLATFORM_BSD 1 +# define PHYSFS_PLATFORM_UNIX 1 +# define PHYSFS_PLATFORM_POSIX 1 +#elif defined(__NetBSD__) || defined(__OpenBSD__) || defined(__bsdi__) +# define PHYSFS_PLATFORM_BSD 1 +# define PHYSFS_PLATFORM_UNIX 1 +# define PHYSFS_PLATFORM_POSIX 1 +#elif defined(__EMSCRIPTEN__) +# define PHYSFS_NO_CDROM_SUPPORT 1 +# define PHYSFS_PLATFORM_UNIX 1 +# define PHYSFS_PLATFORM_POSIX 1 +#elif defined(__QNX__) +# define PHYSFS_PLATFORM_QNX 1 +# define PHYSFS_PLATFORM_POSIX 1 +#elif defined(unix) || defined(__unix__) +# define PHYSFS_PLATFORM_UNIX 1 +# define PHYSFS_PLATFORM_POSIX 1 +#else +# error Unknown platform. +#endif + +#endif /* include-once blocker. */ + diff --git a/Source/Dependencies/PhysicsFS/physfs_unicode.c b/Source/Dependencies/PhysicsFS/physfs_unicode.c new file mode 100644 index 000000000..0e0060224 --- /dev/null +++ b/Source/Dependencies/PhysicsFS/physfs_unicode.c @@ -0,0 +1,567 @@ +#define __PHYSICSFS_INTERNAL__ +#include "physfs_internal.h" + +#include "physfs_casefolding.h" + + +/* + * From rfc3629, the UTF-8 spec: + * https://www.ietf.org/rfc/rfc3629.txt + * + * Char. number range | UTF-8 octet sequence + * (hexadecimal) | (binary) + * --------------------+--------------------------------------------- + * 0000 0000-0000 007F | 0xxxxxxx + * 0000 0080-0000 07FF | 110xxxxx 10xxxxxx + * 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx + * 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + + +/* + * This may not be the best value, but it's one that isn't represented + * in Unicode (0x10FFFF is the largest codepoint value). We return this + * value from utf8codepoint() if there's bogus bits in the + * stream. utf8codepoint() will turn this value into something + * reasonable (like a question mark), for text that wants to try to recover, + * whereas utf8valid() will use the value to determine if a string has bad + * bits. + */ +#define UNICODE_BOGUS_CHAR_VALUE 0xFFFFFFFF + +/* + * This is the codepoint we currently return when there was bogus bits in a + * UTF-8 string. May not fly in Asian locales? + */ +#define UNICODE_BOGUS_CHAR_CODEPOINT '?' + +static PHYSFS_uint32 utf8codepoint(const char **_str) +{ + const char *str = *_str; + PHYSFS_uint32 retval = 0; + PHYSFS_uint32 octet = (PHYSFS_uint32) ((PHYSFS_uint8) *str); + PHYSFS_uint32 octet2, octet3, octet4; + + if (octet == 0) /* null terminator, end of string. */ + return 0; + + else if (octet < 128) /* one octet char: 0 to 127 */ + { + (*_str)++; /* skip to next possible start of codepoint. */ + return octet; + } /* else if */ + + else if ((octet > 127) && (octet < 192)) /* bad (starts with 10xxxxxx). */ + { + /* + * Apparently each of these is supposed to be flagged as a bogus + * char, instead of just resyncing to the next valid codepoint. + */ + (*_str)++; /* skip to next possible start of codepoint. */ + return UNICODE_BOGUS_CHAR_VALUE; + } /* else if */ + + else if (octet < 224) /* two octets */ + { + (*_str)++; /* advance at least one byte in case of an error */ + octet -= (128+64); + octet2 = (PHYSFS_uint32) ((PHYSFS_uint8) *(++str)); + if ((octet2 & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + *_str += 1; /* skip to next possible start of codepoint. */ + retval = ((octet << 6) | (octet2 - 128)); + if ((retval >= 0x80) && (retval <= 0x7FF)) + return retval; + } /* else if */ + + else if (octet < 240) /* three octets */ + { + (*_str)++; /* advance at least one byte in case of an error */ + octet -= (128+64+32); + octet2 = (PHYSFS_uint32) ((PHYSFS_uint8) *(++str)); + if ((octet2 & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + octet3 = (PHYSFS_uint32) ((PHYSFS_uint8) *(++str)); + if ((octet3 & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + *_str += 2; /* skip to next possible start of codepoint. */ + retval = ( ((octet << 12)) | ((octet2-128) << 6) | ((octet3-128)) ); + + /* There are seven "UTF-16 surrogates" that are illegal in UTF-8. */ + switch (retval) + { + case 0xD800: + case 0xDB7F: + case 0xDB80: + case 0xDBFF: + case 0xDC00: + case 0xDF80: + case 0xDFFF: + return UNICODE_BOGUS_CHAR_VALUE; + } /* switch */ + + /* 0xFFFE and 0xFFFF are illegal, too, so we check them at the edge. */ + if ((retval >= 0x800) && (retval <= 0xFFFD)) + return retval; + } /* else if */ + + else if (octet < 248) /* four octets */ + { + (*_str)++; /* advance at least one byte in case of an error */ + octet -= (128+64+32+16); + octet2 = (PHYSFS_uint32) ((PHYSFS_uint8) *(++str)); + if ((octet2 & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + octet3 = (PHYSFS_uint32) ((PHYSFS_uint8) *(++str)); + if ((octet3 & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + octet4 = (PHYSFS_uint32) ((PHYSFS_uint8) *(++str)); + if ((octet4 & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + *_str += 3; /* skip to next possible start of codepoint. */ + retval = ( ((octet << 18)) | ((octet2 - 128) << 12) | + ((octet3 - 128) << 6) | ((octet4 - 128)) ); + if ((retval >= 0x10000) && (retval <= 0x10FFFF)) + return retval; + } /* else if */ + + /* + * Five and six octet sequences became illegal in rfc3629. + * We throw the codepoint away, but parse them to make sure we move + * ahead the right number of bytes and don't overflow the buffer. + */ + + else if (octet < 252) /* five octets */ + { + (*_str)++; /* advance at least one byte in case of an error */ + octet = (PHYSFS_uint32) ((PHYSFS_uint8) *(++str)); + if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + octet = (PHYSFS_uint32) ((PHYSFS_uint8) *(++str)); + if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + octet = (PHYSFS_uint32) ((PHYSFS_uint8) *(++str)); + if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + octet = (PHYSFS_uint32) ((PHYSFS_uint8) *(++str)); + if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + *_str += 4; /* skip to next possible start of codepoint. */ + return UNICODE_BOGUS_CHAR_VALUE; + } /* else if */ + + else /* six octets */ + { + (*_str)++; /* advance at least one byte in case of an error */ + octet = (PHYSFS_uint32) ((PHYSFS_uint8) *(++str)); + if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + octet = (PHYSFS_uint32) ((PHYSFS_uint8) *(++str)); + if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + octet = (PHYSFS_uint32) ((PHYSFS_uint8) *(++str)); + if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + octet = (PHYSFS_uint32) ((PHYSFS_uint8) *(++str)); + if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + octet = (PHYSFS_uint32) ((PHYSFS_uint8) *(++str)); + if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + *_str += 6; /* skip to next possible start of codepoint. */ + return UNICODE_BOGUS_CHAR_VALUE; + } /* else if */ + + return UNICODE_BOGUS_CHAR_VALUE; +} /* utf8codepoint */ + +static PHYSFS_uint32 utf16codepoint(const PHYSFS_uint16 **_str) +{ + const PHYSFS_uint16 *src = *_str; + PHYSFS_uint32 cp = (PHYSFS_uint32) *(src++); + + if (cp == 0) /* null terminator, end of string. */ + return 0; + /* Orphaned second half of surrogate pair? */ + else if ((cp >= 0xDC00) && (cp <= 0xDFFF)) + cp = UNICODE_BOGUS_CHAR_CODEPOINT; + else if ((cp >= 0xD800) && (cp <= 0xDBFF)) /* start surrogate pair! */ + { + const PHYSFS_uint32 pair = (PHYSFS_uint32) *src; + if (pair == 0) + cp = UNICODE_BOGUS_CHAR_CODEPOINT; + else if ((pair < 0xDC00) || (pair > 0xDFFF)) + cp = UNICODE_BOGUS_CHAR_CODEPOINT; + else + { + src++; /* eat the other surrogate. */ + cp = (((cp - 0xD800) << 10) | (pair - 0xDC00)); + } /* else */ + } /* else if */ + + *_str = src; + return cp; +} /* utf16codepoint */ + +static PHYSFS_uint32 utf32codepoint(const PHYSFS_uint32 **_str) +{ + const PHYSFS_uint32 *src = *_str; + PHYSFS_uint32 cp = *(src++); + + if (cp == 0) /* null terminator, end of string. */ + return 0; + else if (cp > 0x10FFF) + cp = UNICODE_BOGUS_CHAR_CODEPOINT; + + *_str = src; + return cp; +} /* utf32codepoint */ + + +void PHYSFS_utf8ToUcs4(const char *src, PHYSFS_uint32 *dst, PHYSFS_uint64 len) +{ + len -= sizeof (PHYSFS_uint32); /* save room for null char. */ + while (len >= sizeof (PHYSFS_uint32)) + { + PHYSFS_uint32 cp = utf8codepoint(&src); + if (cp == 0) + break; + else if (cp == UNICODE_BOGUS_CHAR_VALUE) + cp = UNICODE_BOGUS_CHAR_CODEPOINT; + *(dst++) = cp; + len -= sizeof (PHYSFS_uint32); + } /* while */ + + *dst = 0; +} /* PHYSFS_utf8ToUcs4 */ + + +void PHYSFS_utf8ToUcs2(const char *src, PHYSFS_uint16 *dst, PHYSFS_uint64 len) +{ + len -= sizeof (PHYSFS_uint16); /* save room for null char. */ + while (len >= sizeof (PHYSFS_uint16)) + { + PHYSFS_uint32 cp = utf8codepoint(&src); + if (cp == 0) + break; + else if (cp == UNICODE_BOGUS_CHAR_VALUE) + cp = UNICODE_BOGUS_CHAR_CODEPOINT; + + if (cp > 0xFFFF) /* UTF-16 surrogates (bogus chars in UCS-2) */ + cp = UNICODE_BOGUS_CHAR_CODEPOINT; + + *(dst++) = cp; + len -= sizeof (PHYSFS_uint16); + } /* while */ + + *dst = 0; +} /* PHYSFS_utf8ToUcs2 */ + + +void PHYSFS_utf8ToUtf16(const char *src, PHYSFS_uint16 *dst, PHYSFS_uint64 len) +{ + len -= sizeof (PHYSFS_uint16); /* save room for null char. */ + while (len >= sizeof (PHYSFS_uint16)) + { + PHYSFS_uint32 cp = utf8codepoint(&src); + if (cp == 0) + break; + else if (cp == UNICODE_BOGUS_CHAR_VALUE) + cp = UNICODE_BOGUS_CHAR_CODEPOINT; + + if (cp > 0xFFFF) /* encode as surrogate pair */ + { + if (len < (sizeof (PHYSFS_uint16) * 2)) + break; /* not enough room for the pair, stop now. */ + + cp -= 0x10000; /* Make this a 20-bit value */ + + *(dst++) = 0xD800 + ((cp >> 10) & 0x3FF); + len -= sizeof (PHYSFS_uint16); + + cp = 0xDC00 + (cp & 0x3FF); + } /* if */ + + *(dst++) = cp; + len -= sizeof (PHYSFS_uint16); + } /* while */ + + *dst = 0; +} /* PHYSFS_utf8ToUtf16 */ + +static void utf8fromcodepoint(PHYSFS_uint32 cp, char **_dst, PHYSFS_uint64 *_len) +{ + char *dst = *_dst; + PHYSFS_uint64 len = *_len; + + if (len == 0) + return; + + if (cp > 0x10FFFF) + cp = UNICODE_BOGUS_CHAR_CODEPOINT; + else if ((cp == 0xFFFE) || (cp == 0xFFFF)) /* illegal values. */ + cp = UNICODE_BOGUS_CHAR_CODEPOINT; + else + { + /* There are seven "UTF-16 surrogates" that are illegal in UTF-8. */ + switch (cp) + { + case 0xD800: + case 0xDB7F: + case 0xDB80: + case 0xDBFF: + case 0xDC00: + case 0xDF80: + case 0xDFFF: + cp = UNICODE_BOGUS_CHAR_CODEPOINT; + } /* switch */ + } /* else */ + + /* Do the encoding... */ + if (cp < 0x80) + { + *(dst++) = (char) cp; + len--; + } /* if */ + + else if (cp < 0x800) + { + if (len < 2) + len = 0; + else + { + *(dst++) = (char) ((cp >> 6) | 128 | 64); + *(dst++) = (char) (cp & 0x3F) | 128; + len -= 2; + } /* else */ + } /* else if */ + + else if (cp < 0x10000) + { + if (len < 3) + len = 0; + else + { + *(dst++) = (char) ((cp >> 12) | 128 | 64 | 32); + *(dst++) = (char) ((cp >> 6) & 0x3F) | 128; + *(dst++) = (char) (cp & 0x3F) | 128; + len -= 3; + } /* else */ + } /* else if */ + + else + { + if (len < 4) + len = 0; + else + { + *(dst++) = (char) ((cp >> 18) | 128 | 64 | 32 | 16); + *(dst++) = (char) ((cp >> 12) & 0x3F) | 128; + *(dst++) = (char) ((cp >> 6) & 0x3F) | 128; + *(dst++) = (char) (cp & 0x3F) | 128; + len -= 4; + } /* else if */ + } /* else */ + + *_dst = dst; + *_len = len; +} /* utf8fromcodepoint */ + +#define UTF8FROMTYPE(typ, src, dst, len) \ + if (len == 0) return; \ + len--; \ + while (len) \ + { \ + const PHYSFS_uint32 cp = (PHYSFS_uint32) ((typ) (*(src++))); \ + if (cp == 0) break; \ + utf8fromcodepoint(cp, &dst, &len); \ + } \ + *dst = '\0'; \ + +void PHYSFS_utf8FromUcs4(const PHYSFS_uint32 *src, char *dst, PHYSFS_uint64 len) +{ + UTF8FROMTYPE(PHYSFS_uint32, src, dst, len); +} /* PHYSFS_utf8FromUcs4 */ + +void PHYSFS_utf8FromUcs2(const PHYSFS_uint16 *src, char *dst, PHYSFS_uint64 len) +{ + UTF8FROMTYPE(PHYSFS_uint64, src, dst, len); +} /* PHYSFS_utf8FromUcs2 */ + +/* latin1 maps to unicode codepoints directly, we just utf-8 encode it. */ +void PHYSFS_utf8FromLatin1(const char *src, char *dst, PHYSFS_uint64 len) +{ + UTF8FROMTYPE(PHYSFS_uint8, src, dst, len); +} /* PHYSFS_utf8FromLatin1 */ + +#undef UTF8FROMTYPE + + +void PHYSFS_utf8FromUtf16(const PHYSFS_uint16 *src, char *dst, PHYSFS_uint64 len) +{ + if (len == 0) + return; + + len--; + while (len) + { + const PHYSFS_uint32 cp = utf16codepoint(&src); + if (!cp) + break; + utf8fromcodepoint(cp, &dst, &len); + } /* while */ + + *dst = '\0'; +} /* PHYSFS_utf8FromUtf16 */ + + +int PHYSFS_caseFold(const PHYSFS_uint32 from, PHYSFS_uint32 *to) +{ + int i; + + if (from < 128) /* low-ASCII, easy! */ + { + if ((from >= 'A') && (from <= 'Z')) + *to = from - ('A' - 'a'); + else + *to = from; + return 1; + } /* if */ + + else if (from <= 0xFFFF) + { + const PHYSFS_uint8 hash = ((from ^ (from >> 8)) & 0xFF); + const PHYSFS_uint16 from16 = (PHYSFS_uint16) from; + + { + const CaseFoldHashBucket1_16 *bucket = &case_fold_hash1_16[hash]; + const int count = (int) bucket->count; + for (i = 0; i < count; i++) + { + const CaseFoldMapping1_16 *mapping = &bucket->list[i]; + if (mapping->from == from16) + { + *to = mapping->to0; + return 1; + } /* if */ + } /* for */ + } + + { + const CaseFoldHashBucket2_16 *bucket = &case_fold_hash2_16[hash & 15]; + const int count = (int) bucket->count; + for (i = 0; i < count; i++) + { + const CaseFoldMapping2_16 *mapping = &bucket->list[i]; + if (mapping->from == from16) + { + to[0] = mapping->to0; + to[1] = mapping->to1; + return 2; + } /* if */ + } /* for */ + } + + { + const CaseFoldHashBucket3_16 *bucket = &case_fold_hash3_16[hash & 3]; + const int count = (int) bucket->count; + for (i = 0; i < count; i++) + { + const CaseFoldMapping3_16 *mapping = &bucket->list[i]; + if (mapping->from == from16) + { + to[0] = mapping->to0; + to[1] = mapping->to1; + to[2] = mapping->to2; + return 3; + } /* if */ + } /* for */ + } + } /* else if */ + + else /* codepoint that doesn't fit in 16 bits. */ + { + const PHYSFS_uint8 hash = ((from ^ (from >> 8)) & 0xFF); + const CaseFoldHashBucket1_32 *bucket = &case_fold_hash1_32[hash & 15]; + const int count = (int) bucket->count; + for (i = 0; i < count; i++) + { + const CaseFoldMapping1_32 *mapping = &bucket->list[i]; + if (mapping->from == from) + { + *to = mapping->to0; + return 1; + } /* if */ + } /* for */ + } /* else */ + + + /* Not found...there's no remapping for this codepoint. */ + *to = from; + return 1; +} /* PHYSFS_caseFold */ + + +#define UTFSTRICMP(bits) \ + PHYSFS_uint32 folded1[3], folded2[3]; \ + int head1 = 0, tail1 = 0, head2 = 0, tail2 = 0; \ + while (1) { \ + PHYSFS_uint32 cp1, cp2; \ + if (head1 != tail1) { \ + cp1 = folded1[tail1++]; \ + } else { \ + head1 = PHYSFS_caseFold(utf##bits##codepoint(&str1), folded1); \ + cp1 = folded1[0]; \ + tail1 = 1; \ + } \ + if (head2 != tail2) { \ + cp2 = folded2[tail2++]; \ + } else { \ + head2 = PHYSFS_caseFold(utf##bits##codepoint(&str2), folded2); \ + cp2 = folded2[0]; \ + tail2 = 1; \ + } \ + if (cp1 < cp2) { \ + return -1; \ + } else if (cp1 > cp2) { \ + return 1; \ + } else if (cp1 == 0) { \ + break; /* complete match. */ \ + } \ + } \ + return 0 + +int PHYSFS_utf8stricmp(const char *str1, const char *str2) +{ + UTFSTRICMP(8); +} /* PHYSFS_utf8stricmp */ + +int PHYSFS_utf16stricmp(const PHYSFS_uint16 *str1, const PHYSFS_uint16 *str2) +{ + UTFSTRICMP(16); +} /* PHYSFS_utf16stricmp */ + +int PHYSFS_ucs4stricmp(const PHYSFS_uint32 *str1, const PHYSFS_uint32 *str2) +{ + UTFSTRICMP(32); +} /* PHYSFS_ucs4stricmp */ + +#undef UTFSTRICMP + +/* end of physfs_unicode.c ... */ + diff --git a/Source/Dependencies/assimp/lib/assimp-vc141-mt.lib b/Source/Dependencies/assimp/lib/assimp-vc141-mt.lib new file mode 100644 index 000000000..9d5fc187c Binary files /dev/null and b/Source/Dependencies/assimp/lib/assimp-vc141-mt.lib differ diff --git a/Source/Dependencies/assimp/lib/assimp-vc142-mtd.lib b/Source/Dependencies/assimp/lib/assimp-vc142-mtd.lib deleted file mode 100644 index eae8b6206..000000000 Binary files a/Source/Dependencies/assimp/lib/assimp-vc142-mtd.lib and /dev/null differ diff --git a/Source/Dependencies/imGuizmo/GraphEditor.cpp b/Source/Dependencies/imGuizmo/GraphEditor.cpp new file mode 100644 index 000000000..0f45a4078 --- /dev/null +++ b/Source/Dependencies/imGuizmo/GraphEditor.cpp @@ -0,0 +1,1110 @@ +// https://github.com/CedricGuillemet/ImGuizmo +// v 1.83 +// +// The MIT License(MIT) +// +// Copyright(c) 2021 Cedric Guillemet +// +// 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. +// + +#include "imgui.h" +#define IMGUI_DEFINE_MATH_OPERATORS +#include "imgui_internal.h" +#include +#include +#include +#include +#include "GraphEditor.h" + +namespace GraphEditor { + +static inline float Distance(const ImVec2& a, const ImVec2& b) +{ + return sqrtf((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y)); +} + +static inline float sign(float v) +{ + return (v >= 0.f) ? 1.f : -1.f; +} + +static ImVec2 GetInputSlotPos(Delegate& delegate, const Node& node, SlotIndex slotIndex, float factor) +{ + ImVec2 Size = node.mRect.GetSize() * factor; + size_t InputsCount = delegate.GetTemplate(node.mTemplateIndex).mInputCount; + return ImVec2(node.mRect.Min.x * factor, + node.mRect.Min.y * factor + Size.y * ((float)slotIndex + 1) / ((float)InputsCount + 1) + 8.f); +} + +static ImVec2 GetOutputSlotPos(Delegate& delegate, const Node& node, SlotIndex slotIndex, float factor) +{ + ImVec2 Size = node.mRect.GetSize() * factor; + size_t OutputsCount = delegate.GetTemplate(node.mTemplateIndex).mOutputCount; + return ImVec2(node.mRect.Min.x * factor + Size.x, + node.mRect.Min.y * factor + Size.y * ((float)slotIndex + 1) / ((float)OutputsCount + 1) + 8.f); +} + +static ImRect GetNodeRect(const Node& node, float factor) +{ + ImVec2 Size = node.mRect.GetSize() * factor; + return ImRect(node.mRect.Min * factor, node.mRect.Min * factor + Size); +} + +static ImVec2 editingNodeSource; +static bool editingInput = false; +static ImVec2 captureOffset; + +enum NodeOperation +{ + NO_None, + NO_EditingLink, + NO_QuadSelecting, + NO_MovingNodes, + NO_EditInput, + NO_PanView, +}; +static NodeOperation nodeOperation = NO_None; + +static void HandleZoomScroll(ImRect regionRect, ViewState& viewState, const Options& options) +{ + ImGuiIO& io = ImGui::GetIO(); + + if (regionRect.Contains(io.MousePos)) + { + if (io.MouseWheel < -FLT_EPSILON) + { + viewState.mFactorTarget *= 1.f - options.mZoomRatio; + } + + if (io.MouseWheel > FLT_EPSILON) + { + viewState.mFactorTarget *= 1.0f + options.mZoomRatio; + } + } + + ImVec2 mouseWPosPre = (io.MousePos - ImGui::GetCursorScreenPos()) / viewState.mFactor; + viewState.mFactorTarget = ImClamp(viewState.mFactorTarget, options.mMinZoom, options.mMaxZoom); + viewState.mFactor = ImLerp(viewState.mFactor, viewState.mFactorTarget, options.mZoomLerpFactor); + ImVec2 mouseWPosPost = (io.MousePos - ImGui::GetCursorScreenPos()) / viewState.mFactor; + if (ImGui::IsMousePosValid()) + { + viewState.mPosition += mouseWPosPost - mouseWPosPre; + } +} + +void GraphEditorClear() +{ + nodeOperation = NO_None; +} + +static void FitNodes(Delegate& delegate, ViewState& viewState, const ImVec2 viewSize, bool selectedNodesOnly) +{ + const size_t nodeCount = delegate.GetNodeCount(); + + if (!nodeCount) + { + return; + } + + bool validNode = false; + ImVec2 min(FLT_MAX, FLT_MAX); + ImVec2 max(-FLT_MAX, -FLT_MAX); + for (NodeIndex nodeIndex = 0; nodeIndex < nodeCount; nodeIndex++) + { + const Node& node = delegate.GetNode(nodeIndex); + + if (selectedNodesOnly && !node.mSelected) + { + continue; + } + + min = ImMin(min, node.mRect.Min); + min = ImMin(min, node.mRect.Max); + max = ImMax(max, node.mRect.Min); + max = ImMax(max, node.mRect.Max); + validNode = true; + } + + if (!validNode) + { + return; + } + + min -= viewSize * 0.05f; + max += viewSize * 0.05f; + ImVec2 nodesSize = max - min; + ImVec2 nodeCenter = (max + min) * 0.5f; + + float ratioY = viewSize.y / nodesSize.y; + float ratioX = viewSize.x / nodesSize.x; + + viewState.mFactor = viewState.mFactorTarget = ImMin(ImMin(ratioY, ratioX), 1.f); + viewState.mPosition = ImVec2(-nodeCenter.x, -nodeCenter.y) + (viewSize * 0.5f) / viewState.mFactorTarget; +} + +static void DisplayLinks(Delegate& delegate, + ImDrawList* drawList, + const ImVec2 offset, + const float factor, + const ImRect regionRect, + NodeIndex hoveredNode, + const Options& options) +{ + const size_t linkCount = delegate.GetLinkCount(); + for (LinkIndex linkIndex = 0; linkIndex < linkCount; linkIndex++) + { + const auto link = delegate.GetLink(linkIndex); + const auto nodeInput = delegate.GetNode(link.mInputNodeIndex); + const auto nodeOutput = delegate.GetNode(link.mOutputNodeIndex); + ImVec2 p1 = offset + GetOutputSlotPos(delegate, nodeInput, link.mInputSlotIndex, factor); + ImVec2 p2 = offset + GetInputSlotPos(delegate, nodeOutput, link.mOutputSlotIndex, factor); + + // con. view clipping + if ((p1.y < 0.f && p2.y < 0.f) || (p1.y > regionRect.Max.y && p2.y > regionRect.Max.y) || + (p1.x < 0.f && p2.x < 0.f) || (p1.x > regionRect.Max.x && p2.x > regionRect.Max.x)) + continue; + + bool highlightCons = hoveredNode == link.mInputNodeIndex || hoveredNode == link.mOutputNodeIndex; + uint32_t col = delegate.GetTemplate(nodeInput.mTemplateIndex).mHeaderColor | (highlightCons ? 0xF0F0F0 : 0); + if (options.mDisplayLinksAsCurves) + { + // curves + drawList->AddBezierCurve(p1, p1 + ImVec2(50, 0) * factor, p2 + ImVec2(-50, 0) * factor, p2, 0xFF000000, options.mLineThickness * 1.5f * factor); + drawList->AddBezierCurve(p1, p1 + ImVec2(50, 0) * factor, p2 + ImVec2(-50, 0) * factor, p2, col, options.mLineThickness * 1.5f * factor); + /* + ImVec2 p10 = p1 + ImVec2(20.f * factor, 0.f); + ImVec2 p20 = p2 - ImVec2(20.f * factor, 0.f); + + ImVec2 dif = p20 - p10; + ImVec2 p1a, p1b; + if (fabsf(dif.x) > fabsf(dif.y)) + { + p1a = p10 + ImVec2(fabsf(fabsf(dif.x) - fabsf(dif.y)) * 0.5 * sign(dif.x), 0.f); + p1b = p1a + ImVec2(fabsf(dif.y) * sign(dif.x) , dif.y); + } + else + { + p1a = p10 + ImVec2(0.f, fabsf(fabsf(dif.y) - fabsf(dif.x)) * 0.5 * sign(dif.y)); + p1b = p1a + ImVec2(dif.x, fabsf(dif.x) * sign(dif.y)); + } + drawList->AddLine(p1, p10, col, 3.f * factor); + drawList->AddLine(p10, p1a, col, 3.f * factor); + drawList->AddLine(p1a, p1b, col, 3.f * factor); + drawList->AddLine(p1b, p20, col, 3.f * factor); + drawList->AddLine(p20, p2, col, 3.f * factor); + */ + } + else + { + // straight lines + std::array pts; + int ptCount = 0; + ImVec2 dif = p2 - p1; + + ImVec2 p1a, p1b; + const float limitx = 12.f * factor; + if (dif.x < limitx) + { + ImVec2 p10 = p1 + ImVec2(limitx, 0.f); + ImVec2 p20 = p2 - ImVec2(limitx, 0.f); + + dif = p20 - p10; + p1a = p10 + ImVec2(0.f, dif.y * 0.5f); + p1b = p1a + ImVec2(dif.x, 0.f); + + pts = { p1, p10, p1a, p1b, p20, p2 }; + ptCount = 6; + } + else + { + if (fabsf(dif.y) < 1.f) + { + pts = { p1, (p1 + p2) * 0.5f, p2 }; + ptCount = 3; + } + else + { + if (fabsf(dif.y) < 10.f) + { + if (fabsf(dif.x) > fabsf(dif.y)) + { + p1a = p1 + ImVec2(fabsf(fabsf(dif.x) - fabsf(dif.y)) * 0.5f * sign(dif.x), 0.f); + p1b = p1a + ImVec2(fabsf(dif.y) * sign(dif.x), dif.y); + } + else + { + p1a = p1 + ImVec2(0.f, fabsf(fabsf(dif.y) - fabsf(dif.x)) * 0.5f * sign(dif.y)); + p1b = p1a + ImVec2(dif.x, fabsf(dif.x) * sign(dif.y)); + } + } + else + { + if (fabsf(dif.x) > fabsf(dif.y)) + { + float d = fabsf(dif.y) * sign(dif.x) * 0.5f; + p1a = p1 + ImVec2(d, dif.y * 0.5f); + p1b = p1a + ImVec2(fabsf(fabsf(dif.x) - fabsf(d) * 2.f) * sign(dif.x), 0.f); + } + else + { + float d = fabsf(dif.x) * sign(dif.y) * 0.5f; + p1a = p1 + ImVec2(dif.x * 0.5f, d); + p1b = p1a + ImVec2(0.f, fabsf(fabsf(dif.y) - fabsf(d) * 2.f) * sign(dif.y)); + } + } + pts = { p1, p1a, p1b, p2 }; + ptCount = 4; + } + } + float highLightFactor = factor * (highlightCons ? 2.0f : 1.f); + for (int pass = 0; pass < 2; pass++) + { + drawList->AddPolyline(pts.data(), ptCount, pass ? col : 0xFF000000, false, (pass ? options.mLineThickness : (options.mLineThickness * 1.5f)) * highLightFactor); + } + } + } +} + +static void HandleQuadSelection(Delegate& delegate, ImDrawList* drawList, const ImVec2 offset, const float factor, ImRect contentRect, const Options& options) +{ + if (!options.mAllowQuadSelection) + { + return; + } + ImGuiIO& io = ImGui::GetIO(); + static ImVec2 quadSelectPos; + //auto& nodes = delegate->GetNodes(); + auto nodeCount = delegate.GetNodeCount(); + + if (nodeOperation == NO_QuadSelecting && ImGui::IsWindowFocused()) + { + const ImVec2 bmin = ImMin(quadSelectPos, io.MousePos); + const ImVec2 bmax = ImMax(quadSelectPos, io.MousePos); + drawList->AddRectFilled(bmin, bmax, options.mQuadSelection, 1.f); + drawList->AddRect(bmin, bmax, options.mQuadSelectionBorder, 1.f); + if (!io.MouseDown[0]) + { + if (!io.KeyCtrl && !io.KeyShift) + { + for (size_t nodeIndex = 0; nodeIndex < nodeCount; nodeIndex++) + { + delegate.SelectNode(nodeIndex, false); + } + } + + nodeOperation = NO_None; + ImRect selectionRect(bmin, bmax); + for (int nodeIndex = 0; nodeIndex < nodeCount; nodeIndex++) + { + const auto node = delegate.GetNode(nodeIndex); + ImVec2 nodeRectangleMin = offset + node.mRect.Min * factor; + ImVec2 nodeRectangleMax = nodeRectangleMin + node.mRect.GetSize() * factor; + if (selectionRect.Overlaps(ImRect(nodeRectangleMin, nodeRectangleMax))) + { + if (io.KeyCtrl) + { + delegate.SelectNode(nodeIndex, false); + } + else + { + delegate.SelectNode(nodeIndex, true); + } + } + else + { + if (!io.KeyShift) + { + delegate.SelectNode(nodeIndex, false); + } + } + } + } + } + else if (nodeOperation == NO_None && io.MouseDown[0] && ImGui::IsWindowFocused() && + contentRect.Contains(io.MousePos)) + { + nodeOperation = NO_QuadSelecting; + quadSelectPos = io.MousePos; + } +} + +static bool HandleConnections(ImDrawList* drawList, + NodeIndex nodeIndex, + const ImVec2 offset, + const float factor, + Delegate& delegate, + const Options& options, + bool bDrawOnly, + SlotIndex& inputSlotOver, + SlotIndex& outputSlotOver, + const bool inMinimap) +{ + static NodeIndex editingNodeIndex; + static SlotIndex editingSlotIndex; + + ImGuiIO& io = ImGui::GetIO(); + const auto node = delegate.GetNode(nodeIndex); + const auto nodeTemplate = delegate.GetTemplate(node.mTemplateIndex); + const auto linkCount = delegate.GetLinkCount(); + + size_t InputsCount = nodeTemplate.mInputCount; + size_t OutputsCount = nodeTemplate.mOutputCount; + inputSlotOver = -1; + outputSlotOver = -1; + + // draw/use inputs/outputs + bool hoverSlot = false; + for (int i = 0; i < 2; i++) + { + float closestDistance = FLT_MAX; + SlotIndex closestConn = -1; + ImVec2 closestTextPos; + ImVec2 closestPos; + const size_t slotCount[2] = {InputsCount, OutputsCount}; + + for (SlotIndex slotIndex = 0; slotIndex < slotCount[i]; slotIndex++) + { + const char** con = i ? nodeTemplate.mOutputNames : nodeTemplate.mInputNames; + const char* conText = (con && con[slotIndex]) ? con[slotIndex] : ""; + + ImVec2 p = + offset + (i ? GetOutputSlotPos(delegate, node, slotIndex, factor) : GetInputSlotPos(delegate, node, slotIndex, factor)); + float distance = Distance(p, io.MousePos); + bool overCon = (nodeOperation == NO_None || nodeOperation == NO_EditingLink) && + (distance < options.mNodeSlotRadius * 2.f) && (distance < closestDistance); + + + ImVec2 textSize; + textSize = ImGui::CalcTextSize(conText); + ImVec2 textPos = + p + ImVec2(-options.mNodeSlotRadius * (i ? -1.f : 1.f) * (overCon ? 3.f : 2.f) - (i ? 0 : textSize.x), + -textSize.y / 2); + + ImRect nodeRect = GetNodeRect(node, factor); + if (!inMinimap && (overCon || (nodeRect.Contains(io.MousePos - offset) && closestConn == -1 && + (editingInput == (i != 0)) && nodeOperation == NO_EditingLink))) + { + closestDistance = distance; + closestConn = slotIndex; + closestTextPos = textPos; + closestPos = p; + + if (i) + { + outputSlotOver = slotIndex; + } + else + { + inputSlotOver = slotIndex; + } + } + else + { + const ImU32* slotColorSource = i ? nodeTemplate.mOutputColors : nodeTemplate.mInputColors; + const ImU32 slotColor = slotColorSource ? slotColorSource[slotIndex] : options.mDefaultSlotColor; + drawList->AddCircleFilled(p, options.mNodeSlotRadius, IM_COL32(0, 0, 0, 200)); + drawList->AddCircleFilled(p, options.mNodeSlotRadius * 0.75f, slotColor); + if (!options.mDrawIONameOnHover) + { + drawList->AddText(io.FontDefault, 14, textPos + ImVec2(2, 2), IM_COL32(0, 0, 0, 255), conText); + drawList->AddText(io.FontDefault, 14, textPos, IM_COL32(150, 150, 150, 255), conText); + } + } + } + + if (closestConn != -1) + { + const char** con = i ? nodeTemplate.mOutputNames : nodeTemplate.mInputNames; + const char* conText = (con && con[closestConn]) ? con[closestConn] : ""; + const ImU32* slotColorSource = i ? nodeTemplate.mOutputColors : nodeTemplate.mInputColors; + const ImU32 slotColor = slotColorSource ? slotColorSource[closestConn] : options.mDefaultSlotColor; + hoverSlot = true; + drawList->AddCircleFilled(closestPos, options.mNodeSlotRadius * options.mNodeSlotHoverFactor * 0.75f, IM_COL32(0, 0, 0, 200)); + drawList->AddCircleFilled(closestPos, options.mNodeSlotRadius * options.mNodeSlotHoverFactor, slotColor); + drawList->AddText(io.FontDefault, 16, closestTextPos + ImVec2(1, 1), IM_COL32(0, 0, 0, 255), conText); + drawList->AddText(io.FontDefault, 16, closestTextPos, IM_COL32(250, 250, 250, 255), conText); + bool inputToOutput = (!editingInput && !i) || (editingInput && i); + if (nodeOperation == NO_EditingLink && !io.MouseDown[0] && !bDrawOnly) + { + if (inputToOutput) + { + // check loopback + Link nl; + if (editingInput) + nl = Link{nodeIndex, closestConn, editingNodeIndex, editingSlotIndex}; + else + nl = Link{editingNodeIndex, editingSlotIndex, nodeIndex, closestConn}; + + if (!delegate.AllowedLink(nl.mOutputNodeIndex, nl.mInputNodeIndex)) + { + break; + } + bool alreadyExisting = false; + for (size_t linkIndex = 0; linkIndex < linkCount; linkIndex++) + { + const auto link = delegate.GetLink(linkIndex); + if (!memcmp(&link, &nl, sizeof(Link))) + { + alreadyExisting = true; + break; + } + } + + if (!alreadyExisting) + { + for (int linkIndex = 0; linkIndex < linkCount; linkIndex++) + { + const auto link = delegate.GetLink(linkIndex); + if (link.mOutputNodeIndex == nl.mOutputNodeIndex && link.mOutputSlotIndex == nl.mOutputSlotIndex) + { + delegate.DelLink(linkIndex); + + break; + } + } + + delegate.AddLink(nl.mInputNodeIndex, nl.mInputSlotIndex, nl.mOutputNodeIndex, nl.mOutputSlotIndex); + } + } + } + // when ImGui::IsWindowHovered() && !ImGui::IsAnyItemActive() is uncommented, one can't click the node + // input/output when mouse is over the node itself. + if (nodeOperation == NO_None && + /*ImGui::IsWindowHovered() && !ImGui::IsAnyItemActive() &&*/ io.MouseClicked[0] && !bDrawOnly) + { + nodeOperation = NO_EditingLink; + editingInput = i == 0; + editingNodeSource = closestPos; + editingNodeIndex = nodeIndex; + editingSlotIndex = closestConn; + if (editingInput) + { + // remove existing link + for (int linkIndex = 0; linkIndex < linkCount; linkIndex++) + { + const auto link = delegate.GetLink(linkIndex); + if (link.mOutputNodeIndex == nodeIndex && link.mOutputSlotIndex == closestConn) + { + delegate.DelLink(linkIndex); + break; + } + } + } + } + } + } + return hoverSlot; +} + +static void DrawGrid(ImDrawList* drawList, ImVec2 windowPos, const ViewState& viewState, const ImVec2 canvasSize, ImU32 gridColor, ImU32 gridColor2, float gridSize) +{ + float gridSpace = gridSize * viewState.mFactor; + int divx = static_cast(-viewState.mPosition.x / gridSize); + int divy = static_cast(-viewState.mPosition.y / gridSize); + for (float x = fmodf(viewState.mPosition.x * viewState.mFactor, gridSpace); x < canvasSize.x; x += gridSpace, divx ++) + { + bool tenth = !(divx % 10); + drawList->AddLine(ImVec2(x, 0.0f) + windowPos, ImVec2(x, canvasSize.y) + windowPos, tenth ? gridColor2 : gridColor); + } + for (float y = fmodf(viewState.mPosition.y * viewState.mFactor, gridSpace); y < canvasSize.y; y += gridSpace, divy ++) + { + bool tenth = !(divy % 10); + drawList->AddLine(ImVec2(0.0f, y) + windowPos, ImVec2(canvasSize.x, y) + windowPos, tenth ? gridColor2 : gridColor); + } +} + +// return true if node is hovered +static bool DrawNode(ImDrawList* drawList, + NodeIndex nodeIndex, + const ImVec2 offset, + const float factor, + Delegate& delegate, + bool overInput, + const Options& options, + const bool inMinimap, + const ImRect& viewPort) +{ + ImGuiIO& io = ImGui::GetIO(); + const auto node = delegate.GetNode(nodeIndex); + const auto nodeTemplate = delegate.GetTemplate(node.mTemplateIndex); + const ImVec2 nodeRectangleMin = offset + node.mRect.Min * factor; + + const bool old_any_active = ImGui::IsAnyItemActive(); + ImGui::SetCursorScreenPos(nodeRectangleMin); + const ImVec2 nodeSize = node.mRect.GetSize() * factor; + + // test nested IO + drawList->ChannelsSetCurrent(1); // Background + const size_t InputsCount = nodeTemplate.mInputCount; + const size_t OutputsCount = nodeTemplate.mOutputCount; + + /* + for (int i = 0; i < 2; i++) + { + const size_t slotCount[2] = {InputsCount, OutputsCount}; + + for (size_t slotIndex = 0; slotIndex < slotCount[i]; slotIndex++) + { + const char* con = i ? nodeTemplate.mOutputNames[slotIndex] : nodeTemplate.mInputNames[slotIndex];//node.mOutputs[slot_idx] : node->mInputs[slot_idx]; + if (!delegate->IsIOPinned(nodeIndex, slot_idx, i == 1)) + { + + } + continue; + + ImVec2 p = offset + (i ? GetOutputSlotPos(delegate, node, slotIndex, factor) : GetInputSlotPos(delegate, node, slotIndex, factor)); + const float arc = 28.f * (float(i) * 0.3f + 1.0f) * (i ? 1.f : -1.f); + const float ofs = 0.f; + + ImVec2 pts[3] = {p + ImVec2(arc + ofs, 0.f), p + ImVec2(0.f + ofs, -arc), p + ImVec2(0.f + ofs, arc)}; + drawList->AddTriangleFilled(pts[0], pts[1], pts[2], i ? 0xFFAA5030 : 0xFF30AA50); + drawList->AddTriangle(pts[0], pts[1], pts[2], 0xFF000000, 2.f); + } + } + */ + + ImGui::SetCursorScreenPos(nodeRectangleMin); + float maxHeight = ImMin(viewPort.Max.y, nodeRectangleMin.y + nodeSize.y) - nodeRectangleMin.y; + float maxWidth = ImMin(viewPort.Max.x, nodeRectangleMin.x + nodeSize.x) - nodeRectangleMin.x; + ImGui::InvisibleButton("node", ImVec2(maxWidth, maxHeight)); + // must be called right after creating the control we want to be able to move + bool nodeMovingActive = ImGui::IsItemActive(); + + // Save the size of what we have emitted and whether any of the widgets are being used + bool nodeWidgetsActive = (!old_any_active && ImGui::IsAnyItemActive()); + ImVec2 nodeRectangleMax = nodeRectangleMin + nodeSize; + + bool nodeHovered = false; + if (ImGui::IsItemHovered() && nodeOperation == NO_None && !overInput) + { + nodeHovered = true; + } + + if (ImGui::IsWindowFocused()) + { + if ((nodeWidgetsActive || nodeMovingActive) && !inMinimap) + { + if (!node.mSelected) + { + if (!io.KeyShift) + { + const auto nodeCount = delegate.GetNodeCount(); + for (size_t i = 0; i < nodeCount; i++) + { + delegate.SelectNode(i, false); + } + } + delegate.SelectNode(nodeIndex, true); + } + } + } + if (nodeMovingActive && io.MouseDown[0] && nodeHovered && !inMinimap) + { + if (nodeOperation != NO_MovingNodes) + { + nodeOperation = NO_MovingNodes; + } + } + + const bool currentSelectedNode = node.mSelected; + const ImU32 node_bg_color = nodeHovered ? nodeTemplate.mBackgroundColorOver : nodeTemplate.mBackgroundColor; + + drawList->AddRect(nodeRectangleMin, + nodeRectangleMax, + currentSelectedNode ? options.mSelectedNodeBorderColor : options.mNodeBorderColor, + options.mRounding, + ImDrawFlags_RoundCornersAll, + currentSelectedNode ? options.mBorderSelectionThickness : options.mBorderThickness); + + ImVec2 imgPos = nodeRectangleMin + ImVec2(14, 25); + ImVec2 imgSize = nodeRectangleMax + ImVec2(-5, -5) - imgPos; + float imgSizeComp = std::min(imgSize.x, imgSize.y); + + drawList->AddRectFilled(nodeRectangleMin, nodeRectangleMax, node_bg_color, options.mRounding); + /*float progress = delegate->NodeProgress(nodeIndex); + if (progress > FLT_EPSILON && progress < 1.f - FLT_EPSILON) + { + ImVec2 progressLineA = nodeRectangleMax - ImVec2(nodeSize.x - 2.f, 3.f); + ImVec2 progressLineB = progressLineA + ImVec2(nodeSize.x * factor - 4.f, 0.f); + drawList->AddLine(progressLineA, progressLineB, 0xFF400000, 3.f); + drawList->AddLine(progressLineA, ImLerp(progressLineA, progressLineB, progress), 0xFFFF0000, 3.f); + }*/ + ImVec2 imgPosMax = imgPos + ImVec2(imgSizeComp, imgSizeComp); + + //ImVec2 imageSize = delegate->GetEvaluationSize(nodeIndex); + /*float imageRatio = 1.f; + if (imageSize.x > 0.f && imageSize.y > 0.f) + { + imageRatio = imageSize.y / imageSize.x; + } + ImVec2 quadSize = imgPosMax - imgPos; + ImVec2 marge(0.f, 0.f); + if (imageRatio > 1.f) + { + marge.x = (quadSize.x - quadSize.y / imageRatio) * 0.5f; + } + else + { + marge.y = (quadSize.y - quadSize.y * imageRatio) * 0.5f; + }*/ + + //delegate->DrawNodeImage(drawList, ImRect(imgPos, imgPosMax), marge, nodeIndex); + + drawList->AddRectFilled(nodeRectangleMin, + ImVec2(nodeRectangleMax.x, nodeRectangleMin.y + 20), + nodeTemplate.mHeaderColor, options.mRounding); + + drawList->PushClipRect(nodeRectangleMin, ImVec2(nodeRectangleMax.x, nodeRectangleMin.y + 20), true); + drawList->AddText(nodeRectangleMin + ImVec2(2, 2), IM_COL32(0, 0, 0, 255), node.mName); + drawList->PopClipRect(); + + ImRect customDrawRect(nodeRectangleMin + ImVec2(options.mRounding, 20 + options.mRounding), nodeRectangleMax - ImVec2(options.mRounding, options.mRounding)); + if (customDrawRect.Max.y > customDrawRect.Min.y && customDrawRect.Max.x > customDrawRect.Min.x) + { + delegate.CustomDraw(drawList, customDrawRect, nodeIndex); + } +/* + const ImTextureID bmpInfo = (ImTextureID)(uint64_t)delegate->GetBitmapInfo(nodeIndex).idx; + if (bmpInfo) + { + ImVec2 bmpInfoPos(nodeRectangleMax - ImVec2(26, 12)); + ImVec2 bmpInfoSize(20, 20); + if (delegate->NodeIsCompute(nodeIndex)) + { + drawList->AddImageQuad(bmpInfo, + bmpInfoPos, + bmpInfoPos + ImVec2(bmpInfoSize.x, 0.f), + bmpInfoPos + bmpInfoSize, + bmpInfoPos + ImVec2(0., bmpInfoSize.y)); + } + else if (delegate->NodeIs2D(nodeIndex)) + { + drawList->AddImageQuad(bmpInfo, + bmpInfoPos, + bmpInfoPos + ImVec2(bmpInfoSize.x, 0.f), + bmpInfoPos + bmpInfoSize, + bmpInfoPos + ImVec2(0., bmpInfoSize.y)); + } + else if (delegate->NodeIsCubemap(nodeIndex)) + { + drawList->AddImageQuad(bmpInfo, + bmpInfoPos + ImVec2(0., bmpInfoSize.y), + bmpInfoPos + bmpInfoSize, + bmpInfoPos + ImVec2(bmpInfoSize.x, 0.f), + bmpInfoPos); + } + }*/ + return nodeHovered; +} + +bool DrawMiniMap(ImDrawList* drawList, Delegate& delegate, ViewState& viewState, const Options& options, const ImVec2 windowPos, const ImVec2 canvasSize) +{ + if (Distance(options.mMinimap.Min, options.mMinimap.Max) <= FLT_EPSILON) + { + return false; + } + + const size_t nodeCount = delegate.GetNodeCount(); + + if (!nodeCount) + { + return false; + } + + ImVec2 min(FLT_MAX, FLT_MAX); + ImVec2 max(-FLT_MAX, -FLT_MAX); + const ImVec2 margin(50, 50); + for (NodeIndex nodeIndex = 0; nodeIndex < nodeCount; nodeIndex++) + { + const Node& node = delegate.GetNode(nodeIndex); + min = ImMin(min, node.mRect.Min - margin); + min = ImMin(min, node.mRect.Max + margin); + max = ImMax(max, node.mRect.Min - margin); + max = ImMax(max, node.mRect.Max + margin); + } + + // add view in world space + const ImVec2 worldSizeView = canvasSize / viewState.mFactor; + const ImVec2 viewMin(-viewState.mPosition.x, -viewState.mPosition.y); + const ImVec2 viewMax = viewMin + worldSizeView; + min = ImMin(min, viewMin); + max = ImMax(max, viewMax); + const ImVec2 nodesSize = max - min; + const ImVec2 middleWorld = (min + max) * 0.5f; + const ImVec2 minScreen = windowPos + options.mMinimap.Min * canvasSize; + const ImVec2 maxScreen = windowPos + options.mMinimap.Max * canvasSize; + const ImVec2 viewSize = maxScreen - minScreen; + const ImVec2 middleScreen = (minScreen + maxScreen) * 0.5f; + const float ratioY = viewSize.y / nodesSize.y; + const float ratioX = viewSize.x / nodesSize.x; + const float factor = ImMin(ImMin(ratioY, ratioX), 1.f); + + drawList->AddRectFilled(minScreen, maxScreen, IM_COL32(30, 30, 30, 200), 3, ImDrawFlags_RoundCornersAll); + + for (NodeIndex nodeIndex = 0; nodeIndex < nodeCount; nodeIndex++) + { + const Node& node = delegate.GetNode(nodeIndex); + const auto nodeTemplate = delegate.GetTemplate(node.mTemplateIndex); + + ImRect rect = node.mRect; + rect.Min -= middleWorld; + rect.Min *= factor; + rect.Min += middleScreen; + + rect.Max -= middleWorld; + rect.Max *= factor; + rect.Max += middleScreen; + + drawList->AddRectFilled(rect.Min, rect.Max, nodeTemplate.mBackgroundColor, 1, ImDrawFlags_RoundCornersAll); + if (node.mSelected) + { + drawList->AddRect(rect.Min, rect.Max, options.mSelectedNodeBorderColor, 1, ImDrawFlags_RoundCornersAll); + } + } + + // add view + ImVec2 viewMinScreen = (viewMin - middleWorld) * factor + middleScreen; + ImVec2 viewMaxScreen = (viewMax - middleWorld) * factor + middleScreen; + drawList->AddRectFilled(viewMinScreen, viewMaxScreen, IM_COL32(255, 255, 255, 32), 1, ImDrawFlags_RoundCornersAll); + drawList->AddRect(viewMinScreen, viewMaxScreen, IM_COL32(255, 255, 255, 128), 1, ImDrawFlags_RoundCornersAll); + + ImGuiIO& io = ImGui::GetIO(); + const bool mouseInMinimap = ImRect(minScreen, maxScreen).Contains(io.MousePos); + if (mouseInMinimap && io.MouseClicked[0]) + { + const ImVec2 clickedRatio = (io.MousePos - minScreen) / viewSize; + const ImVec2 worldPosCenter = ImVec2(ImLerp(min.x, max.x, clickedRatio.x), ImLerp(min.y, max.y, clickedRatio.y)); + + ImVec2 worldPosViewMin = worldPosCenter - worldSizeView * 0.5; + ImVec2 worldPosViewMax = worldPosCenter + worldSizeView * 0.5; + if (worldPosViewMin.x < min.x) + { + worldPosViewMin.x = min.x; + worldPosViewMax.x = worldPosViewMin.x + worldSizeView.x; + } + if (worldPosViewMin.y < min.y) + { + worldPosViewMin.y = min.y; + worldPosViewMax.y = worldPosViewMin.y + worldSizeView.y; + } + if (worldPosViewMax.x > max.x) + { + worldPosViewMax.x = max.x; + worldPosViewMin.x = worldPosViewMax.x - worldSizeView.x; + } + if (worldPosViewMax.y > max.y) + { + worldPosViewMax.y = max.y; + worldPosViewMin.y = worldPosViewMax.y - worldSizeView.y; + } + viewState.mPosition = ImVec2(-worldPosViewMin.x, -worldPosViewMin.y); + } + return mouseInMinimap; +} + +void Show(Delegate& delegate, const Options& options, ViewState& viewState, bool enabled, FitOnScreen* fit) +{ + ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, 0.f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.f, 0.f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.f); + + const ImVec2 windowPos = ImGui::GetCursorScreenPos(); + const ImVec2 canvasSize = ImGui::GetContentRegionAvail(); + const ImVec2 scrollRegionLocalPos(0, 0); + + ImRect regionRect(windowPos, windowPos + canvasSize); + + HandleZoomScroll(regionRect, viewState, options); + ImVec2 offset = ImGui::GetCursorScreenPos() + viewState.mPosition * viewState.mFactor; + captureOffset = viewState.mPosition * viewState.mFactor; + + //ImGui::InvisibleButton("GraphEditorButton", canvasSize); + ImGui::BeginChildFrame(71711, canvasSize); + + ImGui::SetCursorPos(windowPos); + ImGui::BeginGroup(); + + ImGuiIO& io = ImGui::GetIO(); + + // Create our child canvas + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(1, 1)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(30, 30, 30, 200)); + + ImDrawList* drawList = ImGui::GetWindowDrawList(); + ImGui::PushClipRect(regionRect.Min, regionRect.Max, true); + drawList->AddRectFilled(windowPos, windowPos + canvasSize, options.mBackgroundColor); + + // Background or Display grid + if (options.mRenderGrid) + { + DrawGrid(drawList, windowPos, viewState, canvasSize, options.mGridColor, options.mGridColor2, options.mGridSize); + } + + // Fit view + if (fit && ((*fit == Fit_AllNodes) || (*fit == Fit_SelectedNodes))) + { + FitNodes(delegate, viewState, canvasSize, (*fit == Fit_SelectedNodes)); + } + + if (enabled) + { + static NodeIndex hoveredNode = -1; + // Display links + drawList->ChannelsSplit(3); + + // minimap + drawList->ChannelsSetCurrent(2); // minimap + const bool inMinimap = DrawMiniMap(drawList, delegate, viewState, options, windowPos, canvasSize); + + // Focus rectangle + if (ImGui::IsWindowFocused()) + { + drawList->AddRect(regionRect.Min, regionRect.Max, options.mFrameFocus, 1.f, 0, 2.f); + } + + drawList->ChannelsSetCurrent(1); // Background + + // Links + DisplayLinks(delegate, drawList, offset, viewState.mFactor, regionRect, hoveredNode, options); + + // edit node link + if (nodeOperation == NO_EditingLink) + { + ImVec2 p1 = editingNodeSource; + ImVec2 p2 = io.MousePos; + drawList->AddLine(p1, p2, IM_COL32(200, 200, 200, 255), 3.0f); + } + + // Display nodes + drawList->PushClipRect(regionRect.Min, regionRect.Max, true); + hoveredNode = -1; + + SlotIndex inputSlotOver = -1; + SlotIndex outputSlotOver = -1; + NodeIndex nodeOver = -1; + + const auto nodeCount = delegate.GetNodeCount(); + for (int i = 0; i < 2; i++) + { + for (NodeIndex nodeIndex = 0; nodeIndex < nodeCount; nodeIndex++) + { + //const auto* node = &nodes[nodeIndex]; + const auto node = delegate.GetNode(nodeIndex); + if (node.mSelected != (i != 0)) + { + continue; + } + + // node view clipping + ImRect nodeRect = GetNodeRect(node, viewState.mFactor); + nodeRect.Min += offset; + nodeRect.Max += offset; + if (!regionRect.Overlaps(nodeRect)) + { + continue; + } + + ImGui::PushID((int)nodeIndex); + SlotIndex inputSlot = -1; + SlotIndex outputSlot = -1; + + bool overInput = (!inMinimap) && HandleConnections(drawList, nodeIndex, offset, viewState.mFactor, delegate, options, false, inputSlot, outputSlot, inMinimap); + + // shadow + /* + ImVec2 shadowOffset = ImVec2(30, 30); + ImVec2 shadowPivot = (nodeRect.Min + nodeRect.Max) /2.f; + ImVec2 shadowPointMiddle = shadowPivot + shadowOffset; + ImVec2 shadowPointTop = ImVec2(shadowPivot.x, nodeRect.Min.y) + shadowOffset; + ImVec2 shadowPointBottom = ImVec2(shadowPivot.x, nodeRect.Max.y) + shadowOffset; + ImVec2 shadowPointLeft = ImVec2(nodeRect.Min.x, shadowPivot.y) + shadowOffset; + ImVec2 shadowPointRight = ImVec2(nodeRect.Max.x, shadowPivot.y) + shadowOffset; + + // top left + drawList->AddRectFilledMultiColor(nodeRect.Min + shadowOffset, shadowPointMiddle, IM_COL32(0 ,0, 0, 0), IM_COL32(0,0,0,0), IM_COL32(0, 0, 0, 255), IM_COL32(0, 0, 0, 0)); + + // top right + drawList->AddRectFilledMultiColor(shadowPointTop, shadowPointRight, IM_COL32(0 ,0, 0, 0), IM_COL32(0,0,0,0), IM_COL32(0, 0, 0, 0), IM_COL32(0, 0, 0, 255)); + + // bottom left + drawList->AddRectFilledMultiColor(shadowPointLeft, shadowPointBottom, IM_COL32(0 ,0, 0, 0), IM_COL32(0, 0, 0, 255), IM_COL32(0, 0, 0, 0), IM_COL32(0,0,0,0)); + + // bottom right + drawList->AddRectFilledMultiColor(shadowPointMiddle, nodeRect.Max + shadowOffset, IM_COL32(0, 0, 0, 255), IM_COL32(0 ,0, 0, 0), IM_COL32(0,0,0,0), IM_COL32(0, 0, 0, 0)); + */ + if (DrawNode(drawList, nodeIndex, offset, viewState.mFactor, delegate, overInput, options, inMinimap, regionRect)) + { + hoveredNode = nodeIndex; + } + + HandleConnections(drawList, nodeIndex, offset, viewState.mFactor, delegate, options, true, inputSlot, outputSlot, inMinimap); + if (inputSlot != -1 || outputSlot != -1) + { + inputSlotOver = inputSlot; + outputSlotOver = outputSlot; + nodeOver = nodeIndex; + } + + ImGui::PopID(); + } + } + + + + drawList->PopClipRect(); + + if (nodeOperation == NO_MovingNodes) + { + if (ImGui::IsMouseDragging(0, 1)) + { + ImVec2 delta = io.MouseDelta / viewState.mFactor; + if (fabsf(delta.x) >= 1.f || fabsf(delta.y) >= 1.f) + { + delegate.MoveSelectedNodes(delta); + } + } + } + + drawList->ChannelsSetCurrent(0); + + // quad selection + if (!inMinimap) + { + HandleQuadSelection(delegate, drawList, offset, viewState.mFactor, regionRect, options); + } + + drawList->ChannelsMerge(); + + // releasing mouse button means it's done in any operation + if (nodeOperation == NO_PanView) + { + if (!io.MouseDown[2]) + { + nodeOperation = NO_None; + } + } + else if (nodeOperation != NO_None && !io.MouseDown[0]) + { + nodeOperation = NO_None; + } + + // right click + if (!inMinimap && nodeOperation == NO_None && regionRect.Contains(io.MousePos) && + (ImGui::IsMouseClicked(1) /*|| (ImGui::IsWindowFocused() && ImGui::IsKeyPressedMap(ImGuiKey_Tab))*/)) + { + delegate.RightClick(nodeOver, inputSlotOver, outputSlotOver); + } + + // Scrolling + if (ImGui::IsWindowHovered() && !ImGui::IsAnyItemActive() && io.MouseClicked[2] && nodeOperation == NO_None) + { + nodeOperation = NO_PanView; + } + if (nodeOperation == NO_PanView) + { + viewState.mPosition += io.MouseDelta / viewState.mFactor; + } + } + + ImGui::PopClipRect(); + + ImGui::PopStyleColor(1); + ImGui::PopStyleVar(2); + ImGui::EndGroup(); + ImGui::EndChildFrame(); + + ImGui::PopStyleVar(3); + + // change fit to none + if (fit) + { + *fit = Fit_None; + } +} + +bool EditOptions(Options& options) +{ + bool updated = false; + if (ImGui::CollapsingHeader("Colors", nullptr)) + { + ImColor backgroundColor(options.mBackgroundColor); + ImColor gridColor(options.mGridColor); + ImColor selectedNodeBorderColor(options.mSelectedNodeBorderColor); + ImColor nodeBorderColor(options.mNodeBorderColor); + ImColor quadSelection(options.mQuadSelection); + ImColor quadSelectionBorder(options.mQuadSelectionBorder); + ImColor defaultSlotColor(options.mDefaultSlotColor); + ImColor frameFocus(options.mFrameFocus); + + updated |= ImGui::ColorEdit4("Background", (float*)&backgroundColor); + updated |= ImGui::ColorEdit4("Grid", (float*)&gridColor); + updated |= ImGui::ColorEdit4("Selected Node Border", (float*)&selectedNodeBorderColor); + updated |= ImGui::ColorEdit4("Node Border", (float*)&nodeBorderColor); + updated |= ImGui::ColorEdit4("Quad Selection", (float*)&quadSelection); + updated |= ImGui::ColorEdit4("Quad Selection Border", (float*)&quadSelectionBorder); + updated |= ImGui::ColorEdit4("Default Slot", (float*)&defaultSlotColor); + updated |= ImGui::ColorEdit4("Frame when has focus", (float*)&frameFocus); + + options.mBackgroundColor = backgroundColor; + options.mGridColor = gridColor; + options.mSelectedNodeBorderColor = selectedNodeBorderColor; + options.mNodeBorderColor = nodeBorderColor; + options.mQuadSelection = quadSelection; + options.mQuadSelectionBorder = quadSelectionBorder; + options.mDefaultSlotColor = defaultSlotColor; + options.mFrameFocus = frameFocus; + } + + if (ImGui::CollapsingHeader("Options", nullptr)) + { + updated |= ImGui::InputFloat4("Minimap", &options.mMinimap.Min.x); + updated |= ImGui::InputFloat("Line Thickness", &options.mLineThickness); + updated |= ImGui::InputFloat("Grid Size", &options.mGridSize); + updated |= ImGui::InputFloat("Rounding", &options.mRounding); + updated |= ImGui::InputFloat("Zoom Ratio", &options.mZoomRatio); + updated |= ImGui::InputFloat("Zoom Lerp Factor", &options.mZoomLerpFactor); + updated |= ImGui::InputFloat("Border Selection Thickness", &options.mBorderSelectionThickness); + updated |= ImGui::InputFloat("Border Thickness", &options.mBorderThickness); + updated |= ImGui::InputFloat("Slot Radius", &options.mNodeSlotRadius); + updated |= ImGui::InputFloat("Slot Hover Factor", &options.mNodeSlotHoverFactor); + updated |= ImGui::InputFloat2("Zoom min/max", &options.mMinZoom); + updated |= ImGui::InputFloat("Slot Hover Factor", &options.mSnap); + + if (ImGui::RadioButton("Curved Links", options.mDisplayLinksAsCurves)) + { + options.mDisplayLinksAsCurves = !options.mDisplayLinksAsCurves; + updated = true; + } + if (ImGui::RadioButton("Straight Links", !options.mDisplayLinksAsCurves)) + { + options.mDisplayLinksAsCurves = !options.mDisplayLinksAsCurves; + updated = true; + } + + updated |= ImGui::Checkbox("Allow Quad Selection", &options.mAllowQuadSelection); + updated |= ImGui::Checkbox("Render Grid", &options.mRenderGrid); + updated |= ImGui::Checkbox("Draw IO names on hover", &options.mDrawIONameOnHover); + } + + return updated; +} + +} // namespace diff --git a/Source/Dependencies/imGuizmo/GraphEditor.h b/Source/Dependencies/imGuizmo/GraphEditor.h new file mode 100644 index 000000000..11b73ad47 --- /dev/null +++ b/Source/Dependencies/imGuizmo/GraphEditor.h @@ -0,0 +1,149 @@ +// https://github.com/CedricGuillemet/ImGuizmo +// v 1.83 +// +// The MIT License(MIT) +// +// Copyright(c) 2021 Cedric Guillemet +// +// 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. +// +#pragma once + +#include +#include +#include +#include "imgui.h" +#include "imgui_internal.h" + +namespace GraphEditor { + +typedef size_t NodeIndex; +typedef size_t SlotIndex; +typedef size_t LinkIndex; +typedef size_t TemplateIndex; + +// Force the view to be respositionned and zoom to fit nodes with Show function. +// Parameter value will be changed to Fit_None by the function. +enum FitOnScreen +{ + Fit_None, + Fit_AllNodes, + Fit_SelectedNodes +}; + +// Display options and colors +struct Options +{ + ImRect mMinimap{{0.75f, 0.8f, 0.99f, 0.99f}}; // rectangle coordinates of minimap + ImU32 mBackgroundColor{ IM_COL32(40, 40, 40, 255) }; // full background color + ImU32 mGridColor{ IM_COL32(0, 0, 0, 60) }; // grid lines color + ImU32 mGridColor2{ IM_COL32(0, 0, 0, 160) }; // grid lines color every 10th + ImU32 mSelectedNodeBorderColor{ IM_COL32(255, 130, 30, 255) }; // node border color when it's selected + ImU32 mNodeBorderColor{ IM_COL32(100, 100, 100, 0) }; // node border color when it's not selected + ImU32 mQuadSelection{ IM_COL32(255, 32, 32, 64) }; // quad selection inside color + ImU32 mQuadSelectionBorder{ IM_COL32(255, 32, 32, 255) }; // quad selection border color + ImU32 mDefaultSlotColor{ IM_COL32(128, 128, 128, 255) }; // when no color is provided in node template, use this value + ImU32 mFrameFocus{ IM_COL32(64, 128, 255, 255) }; // rectangle border when graph editor has focus + float mLineThickness{ 5 }; // links width in pixels when zoom value is 1 + float mGridSize{ 64.f }; // background grid size in pixels when zoom value is 1 + float mRounding{ 3.f }; // rounding at node corners + float mZoomRatio{ 0.1f }; // factor per mouse wheel delta + float mZoomLerpFactor{ 0.25f }; // the smaller, the smoother + float mBorderSelectionThickness{ 6.f }; // thickness of selection border around nodes + float mBorderThickness{ 6.f }; // thickness of selection border around nodes + float mNodeSlotRadius{ 8.f }; // circle radius for inputs and outputs + float mNodeSlotHoverFactor{ 1.2f }; // increase size when hovering + float mMinZoom{ 0.2f }, mMaxZoom { 1.1f }; + float mSnap{ 5.f }; + bool mDisplayLinksAsCurves{ true }; // false is straight and 45deg lines + bool mAllowQuadSelection{ true }; // multiple selection using drag and drop + bool mRenderGrid{ true }; // grid or nothing + bool mDrawIONameOnHover{ true }; // only draw node input/output when hovering +}; + +// View state: scroll position and zoom factor +struct ViewState +{ + ImVec2 mPosition{0.0f, 0.0f}; // scroll position + float mFactor{ 1.0f }; // current zoom factor + float mFactorTarget{ 1.0f }; // targeted zoom factor interpolated using Options.mZoomLerpFactor +}; + +struct Template +{ + ImU32 mHeaderColor; + ImU32 mBackgroundColor; + ImU32 mBackgroundColorOver; + ImU8 mInputCount; + const char** mInputNames; // can be nullptr. No text displayed. + ImU32* mInputColors; // can be nullptr, default slot color will be used. + ImU8 mOutputCount; + const char** mOutputNames; // can be nullptr. No text displayed. + ImU32* mOutputColors; // can be nullptr, default slot color will be used. +}; + +struct Node +{ + const char* mName; + TemplateIndex mTemplateIndex; + ImRect mRect; + bool mSelected{ false }; +}; + +struct Link +{ + NodeIndex mInputNodeIndex; + SlotIndex mInputSlotIndex; + NodeIndex mOutputNodeIndex; + SlotIndex mOutputSlotIndex; +}; + +struct Delegate +{ + virtual bool AllowedLink(NodeIndex from, NodeIndex to) = 0; + + virtual void SelectNode(NodeIndex nodeIndex, bool selected) = 0; + virtual void MoveSelectedNodes(const ImVec2 delta) = 0; + + virtual void AddLink(NodeIndex inputNodeIndex, SlotIndex inputSlotIndex, NodeIndex outputNodeIndex, SlotIndex outputSlotIndex) = 0; + virtual void DelLink(LinkIndex linkIndex) = 0; + + // user is responsible for clipping + virtual void CustomDraw(ImDrawList* drawList, ImRect rectangle, NodeIndex nodeIndex) = 0; + + // use mouse position to open context menu + // if nodeIndex != -1, right click happens on the specified node + virtual void RightClick(NodeIndex nodeIndex, SlotIndex slotIndexInput, SlotIndex slotIndexOutput) = 0; + + virtual const size_t GetTemplateCount() = 0; + virtual const Template GetTemplate(TemplateIndex index) = 0; + + virtual const size_t GetNodeCount() = 0; + virtual const Node GetNode(NodeIndex index) = 0; + + virtual const size_t GetLinkCount() = 0; + virtual const Link GetLink(LinkIndex index) = 0; +}; + +void Show(Delegate& delegate, const Options& options, ViewState& viewState, bool enabled, FitOnScreen* fit = nullptr); +void GraphEditorClear(); + +bool EditOptions(Options& options); + +} // namespace diff --git a/Source/Dependencies/imGuizmo/ImCurveEdit.cpp b/Source/Dependencies/imGuizmo/ImCurveEdit.cpp new file mode 100644 index 000000000..52b62ce4b --- /dev/null +++ b/Source/Dependencies/imGuizmo/ImCurveEdit.cpp @@ -0,0 +1,457 @@ +// https://github.com/CedricGuillemet/ImGuizmo +// v 1.83 +// +// The MIT License(MIT) +// +// Copyright(c) 2021 Cedric Guillemet +// +// 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. +// +#include "ImCurveEdit.h" +#include "imgui.h" +#include "imgui_internal.h" +#include +#include +#include + +#if defined(_MSC_VER) || defined(__MINGW32__) +#include +#endif +#if !defined(_MSC_VER) && !defined(__MINGW64_VERSION_MAJOR) +#define _malloca(x) alloca(x) +#define _freea(x) +#endif + +namespace ImCurveEdit +{ + +#ifndef IMGUI_DEFINE_MATH_OPERATORS + static ImVec2 operator+(const ImVec2& a, const ImVec2& b) { + return ImVec2(a.x + b.x, a.y + b.y); + } + + static ImVec2 operator-(const ImVec2& a, const ImVec2& b) { + return ImVec2(a.x - b.x, a.y - b.y); + } + + static ImVec2 operator*(const ImVec2& a, const ImVec2& b) { + return ImVec2(a.x * b.x, a.y * b.y); + } + + static ImVec2 operator/(const ImVec2& a, const ImVec2& b) { + return ImVec2(a.x / b.x, a.y / b.y); + } + + static ImVec2 operator*(const ImVec2& a, const float b) { + return ImVec2(a.x * b, a.y * b); + } +#endif + + static float smoothstep(float edge0, float edge1, float x) + { + x = ImClamp((x - edge0) / (edge1 - edge0), 0.0f, 1.0f); + return x * x * (3 - 2 * x); + } + + static float distance(float x, float y, float x1, float y1, float x2, float y2) + { + float A = x - x1; + float B = y - y1; + float C = x2 - x1; + float D = y2 - y1; + + float dot = A * C + B * D; + float len_sq = C * C + D * D; + float param = -1.f; + if (len_sq > FLT_EPSILON) + param = dot / len_sq; + + float xx, yy; + + if (param < 0.f) { + xx = x1; + yy = y1; + } + else if (param > 1.f) { + xx = x2; + yy = y2; + } + else { + xx = x1 + param * C; + yy = y1 + param * D; + } + + float dx = x - xx; + float dy = y - yy; + return sqrtf(dx * dx + dy * dy); + } + + static int DrawPoint(ImDrawList* draw_list, ImVec2 pos, const ImVec2 size, const ImVec2 offset, bool edited) + { + int ret = 0; + ImGuiIO& io = ImGui::GetIO(); + + static const ImVec2 localOffsets[4] = { ImVec2(1,0), ImVec2(0,1), ImVec2(-1,0), ImVec2(0,-1) }; + ImVec2 offsets[4]; + for (int i = 0; i < 4; i++) + { + offsets[i] = pos * size + localOffsets[i] * 4.5f + offset; + } + + const ImVec2 center = pos * size + offset; + const ImRect anchor(center - ImVec2(5, 5), center + ImVec2(5, 5)); + draw_list->AddConvexPolyFilled(offsets, 4, 0xFF000000); + if (anchor.Contains(io.MousePos)) + { + ret = 1; + if (io.MouseDown[0]) + ret = 2; + } + if (edited) + draw_list->AddPolyline(offsets, 4, 0xFFFFFFFF, true, 3.0f); + else if (ret) + draw_list->AddPolyline(offsets, 4, 0xFF80B0FF, true, 2.0f); + else + draw_list->AddPolyline(offsets, 4, 0xFF0080FF, true, 2.0f); + + return ret; + } + + int Edit(Delegate& delegate, const ImVec2& size, unsigned int id, const ImRect* clippingRect, ImVector* selectedPoints) + { + static bool selectingQuad = false; + static ImVec2 quadSelection; + static int overCurve = -1; + static int movingCurve = -1; + static bool scrollingV = false; + static std::set selection; + static bool overSelectedPoint = false; + + int ret = 0; + + ImGuiIO& io = ImGui::GetIO(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + ImGui::PushStyleColor(ImGuiCol_Border, 0); + ImGui::BeginChildFrame(id, size); + delegate.focused = ImGui::IsWindowFocused(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + if (clippingRect) + draw_list->PushClipRect(clippingRect->Min, clippingRect->Max, true); + + const ImVec2 offset = ImGui::GetCursorScreenPos() + ImVec2(0.f, size.y); + const ImVec2 ssize(size.x, -size.y); + const ImRect container(offset + ImVec2(0.f, ssize.y), offset + ImVec2(ssize.x, 0.f)); + ImVec2& min = delegate.GetMin(); + ImVec2& max = delegate.GetMax(); + + // handle zoom and VScroll + if (container.Contains(io.MousePos)) + { + if (fabsf(io.MouseWheel) > FLT_EPSILON) + { + const float r = (io.MousePos.y - offset.y) / ssize.y; + float ratioY = ImLerp(min.y, max.y, r); + auto scaleValue = [&](float v) { + v -= ratioY; + v *= (1.f - io.MouseWheel * 0.05f); + v += ratioY; + return v; + }; + min.y = scaleValue(min.y); + max.y = scaleValue(max.y); + } + if (!scrollingV && ImGui::IsMouseDown(2)) + { + scrollingV = true; + } + } + ImVec2 range = max - min + ImVec2(1.f, 0.f); // +1 because of inclusive last frame + + const ImVec2 viewSize(size.x, -size.y); + const ImVec2 sizeOfPixel = ImVec2(1.f, 1.f) / viewSize; + const size_t curveCount = delegate.GetCurveCount(); + + if (scrollingV) + { + float deltaH = io.MouseDelta.y * range.y * sizeOfPixel.y; + min.y -= deltaH; + max.y -= deltaH; + if (!ImGui::IsMouseDown(2)) + scrollingV = false; + } + + draw_list->AddRectFilled(offset, offset + ssize, delegate.GetBackgroundColor()); + + auto pointToRange = [&](ImVec2 pt) { return (pt - min) / range; }; + auto rangeToPoint = [&](ImVec2 pt) { return (pt * range) + min; }; + + draw_list->AddLine(ImVec2(-1.f, -min.y / range.y) * viewSize + offset, ImVec2(1.f, -min.y / range.y) * viewSize + offset, 0xFF000000, 1.5f); + bool overCurveOrPoint = false; + + int localOverCurve = -1; + // make sure highlighted curve is rendered last + int* curvesIndex = (int*)_malloca(sizeof(int) * curveCount); + for (size_t c = 0; c < curveCount; c++) + curvesIndex[c] = int(c); + int highLightedCurveIndex = -1; + if (overCurve != -1 && curveCount) + { + ImSwap(curvesIndex[overCurve], curvesIndex[curveCount - 1]); + highLightedCurveIndex = overCurve; + } + + for (size_t cur = 0; cur < curveCount; cur++) + { + int c = curvesIndex[cur]; + if (!delegate.IsVisible(c)) + continue; + const size_t ptCount = delegate.GetPointCount(c); + if (ptCount < 1) + continue; + CurveType curveType = delegate.GetCurveType(c); + if (curveType == CurveNone) + continue; + const ImVec2* pts = delegate.GetPoints(c); + uint32_t curveColor = delegate.GetCurveColor(c); + if ((c == highLightedCurveIndex && selection.empty() && !selectingQuad) || movingCurve == c) + curveColor = 0xFFFFFFFF; + + for (size_t p = 0; p < ptCount - 1; p++) + { + const ImVec2 p1 = pointToRange(pts[p]); + const ImVec2 p2 = pointToRange(pts[p + 1]); + + if (curveType == CurveSmooth || curveType == CurveLinear) + { + size_t subStepCount = (curveType == CurveSmooth) ? 20 : 2; + float step = 1.f / float(subStepCount - 1); + for (size_t substep = 0; substep < subStepCount - 1; substep++) + { + float t = float(substep) * step; + + const ImVec2 sp1 = ImLerp(p1, p2, t); + const ImVec2 sp2 = ImLerp(p1, p2, t + step); + + const float rt1 = smoothstep(p1.x, p2.x, sp1.x); + const float rt2 = smoothstep(p1.x, p2.x, sp2.x); + + const ImVec2 pos1 = ImVec2(sp1.x, ImLerp(p1.y, p2.y, rt1)) * viewSize + offset; + const ImVec2 pos2 = ImVec2(sp2.x, ImLerp(p1.y, p2.y, rt2)) * viewSize + offset; + + if (distance(io.MousePos.x, io.MousePos.y, pos1.x, pos1.y, pos2.x, pos2.y) < 8.f && !scrollingV) + { + localOverCurve = int(c); + overCurve = int(c); + overCurveOrPoint = true; + } + + draw_list->AddLine(pos1, pos2, curveColor, 1.3f); + } // substep + } + else if (curveType == CurveDiscrete) + { + ImVec2 dp1 = p1 * viewSize + offset; + ImVec2 dp2 = ImVec2(p2.x, p1.y) * viewSize + offset; + ImVec2 dp3 = p2 * viewSize + offset; + draw_list->AddLine(dp1, dp2, curveColor, 1.3f); + draw_list->AddLine(dp2, dp3, curveColor, 1.3f); + + if ((distance(io.MousePos.x, io.MousePos.y, dp1.x, dp1.y, dp3.x, dp1.y) < 8.f || + distance(io.MousePos.x, io.MousePos.y, dp3.x, dp1.y, dp3.x, dp3.y) < 8.f) + /*&& localOverCurve == -1*/) + { + localOverCurve = int(c); + overCurve = int(c); + overCurveOrPoint = true; + } + } + } // point loop + + for (size_t p = 0; p < ptCount; p++) + { + const int drawState = DrawPoint(draw_list, pointToRange(pts[p]), viewSize, offset, (selection.find({ int(c), int(p) }) != selection.end() && movingCurve == -1 && !scrollingV)); + if (drawState && movingCurve == -1 && !selectingQuad) + { + overCurveOrPoint = true; + overSelectedPoint = true; + overCurve = -1; + if (drawState == 2) + { + if (!io.KeyShift && selection.find({ int(c), int(p) }) == selection.end()) + selection.clear(); + selection.insert({ int(c), int(p) }); + } + } + } + } // curves loop + + if (localOverCurve == -1) + overCurve = -1; + + // move selection + static bool pointsMoved = false; + static ImVec2 mousePosOrigin; + static std::vector originalPoints; + if (overSelectedPoint && io.MouseDown[0]) + { + if ((fabsf(io.MouseDelta.x) > 0.f || fabsf(io.MouseDelta.y) > 0.f) && !selection.empty()) + { + if (!pointsMoved) + { + delegate.BeginEdit(0); + mousePosOrigin = io.MousePos; + originalPoints.resize(selection.size()); + int index = 0; + for (auto& sel : selection) + { + const ImVec2* pts = delegate.GetPoints(sel.curveIndex); + originalPoints[index++] = pts[sel.pointIndex]; + } + } + pointsMoved = true; + ret = 1; + auto prevSelection = selection; + int originalIndex = 0; + for (auto& sel : prevSelection) + { + const ImVec2 p = rangeToPoint(pointToRange(originalPoints[originalIndex]) + (io.MousePos - mousePosOrigin) * sizeOfPixel); + const int newIndex = delegate.EditPoint(sel.curveIndex, sel.pointIndex, p); + if (newIndex != sel.pointIndex) + { + selection.erase(sel); + selection.insert({ sel.curveIndex, newIndex }); + } + originalIndex++; + } + } + } + + if (overSelectedPoint && !io.MouseDown[0]) + { + overSelectedPoint = false; + if (pointsMoved) + { + pointsMoved = false; + delegate.EndEdit(); + } + } + + // add point + if (overCurve != -1 && io.MouseDoubleClicked[0]) + { + const ImVec2 np = rangeToPoint((io.MousePos - offset) / viewSize); + delegate.BeginEdit(overCurve); + delegate.AddPoint(overCurve, np); + delegate.EndEdit(); + ret = 1; + } + + // move curve + + if (movingCurve != -1) + { + const size_t ptCount = delegate.GetPointCount(movingCurve); + const ImVec2* pts = delegate.GetPoints(movingCurve); + if (!pointsMoved) + { + mousePosOrigin = io.MousePos; + pointsMoved = true; + originalPoints.resize(ptCount); + for (size_t index = 0; index < ptCount; index++) + { + originalPoints[index] = pts[index]; + } + } + if (ptCount >= 1) + { + for (size_t p = 0; p < ptCount; p++) + { + delegate.EditPoint(movingCurve, int(p), rangeToPoint(pointToRange(originalPoints[p]) + (io.MousePos - mousePosOrigin) * sizeOfPixel)); + } + ret = 1; + } + if (!io.MouseDown[0]) + { + movingCurve = -1; + pointsMoved = false; + delegate.EndEdit(); + } + } + if (movingCurve == -1 && overCurve != -1 && ImGui::IsMouseClicked(0) && selection.empty() && !selectingQuad) + { + movingCurve = overCurve; + delegate.BeginEdit(overCurve); + } + + // quad selection + if (selectingQuad) + { + const ImVec2 bmin = ImMin(quadSelection, io.MousePos); + const ImVec2 bmax = ImMax(quadSelection, io.MousePos); + draw_list->AddRectFilled(bmin, bmax, 0x40FF0000, 1.f); + draw_list->AddRect(bmin, bmax, 0xFFFF0000, 1.f); + const ImRect selectionQuad(bmin, bmax); + if (!io.MouseDown[0]) + { + if (!io.KeyShift) + selection.clear(); + // select everythnig is quad + for (size_t c = 0; c < curveCount; c++) + { + if (!delegate.IsVisible(c)) + continue; + + const size_t ptCount = delegate.GetPointCount(c); + if (ptCount < 1) + continue; + + const ImVec2* pts = delegate.GetPoints(c); + for (size_t p = 0; p < ptCount; p++) + { + const ImVec2 center = pointToRange(pts[p]) * viewSize + offset; + if (selectionQuad.Contains(center)) + selection.insert({ int(c), int(p) }); + } + } + // done + selectingQuad = false; + } + } + if (!overCurveOrPoint && ImGui::IsMouseClicked(0) && !selectingQuad && movingCurve == -1 && !overSelectedPoint && container.Contains(io.MousePos)) + { + selectingQuad = true; + quadSelection = io.MousePos; + } + if (clippingRect) + draw_list->PopClipRect(); + + ImGui::EndChildFrame(); + ImGui::PopStyleVar(); + ImGui::PopStyleColor(1); + + if (selectedPoints) + { + selectedPoints->resize(int(selection.size())); + int index = 0; + for (auto& point : selection) + (*selectedPoints)[index++] = point; + } + return ret; + } +} diff --git a/Source/Dependencies/imGuizmo/ImCurveEdit.h b/Source/Dependencies/imGuizmo/ImCurveEdit.h new file mode 100644 index 000000000..09d0abe2c --- /dev/null +++ b/Source/Dependencies/imGuizmo/ImCurveEdit.h @@ -0,0 +1,80 @@ +// https://github.com/CedricGuillemet/ImGuizmo +// v 1.83 +// +// The MIT License(MIT) +// +// Copyright(c) 2021 Cedric Guillemet +// +// 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. +// +#pragma once +#include +#include "imgui.h" + +struct ImRect; + +namespace ImCurveEdit +{ + enum CurveType + { + CurveNone, + CurveDiscrete, + CurveLinear, + CurveSmooth, + CurveBezier, + }; + + struct EditPoint + { + int curveIndex; + int pointIndex; + bool operator <(const EditPoint& other) const + { + if (curveIndex < other.curveIndex) + return true; + if (curveIndex > other.curveIndex) + return false; + + if (pointIndex < other.pointIndex) + return true; + return false; + } + }; + + struct Delegate + { + bool focused = false; + virtual size_t GetCurveCount() = 0; + virtual bool IsVisible(size_t /*curveIndex*/) { return true; } + virtual CurveType GetCurveType(size_t /*curveIndex*/) const { return CurveLinear; } + virtual ImVec2& GetMin() = 0; + virtual ImVec2& GetMax() = 0; + virtual size_t GetPointCount(size_t curveIndex) = 0; + virtual uint32_t GetCurveColor(size_t curveIndex) = 0; + virtual ImVec2* GetPoints(size_t curveIndex) = 0; + virtual int EditPoint(size_t curveIndex, int pointIndex, ImVec2 value) = 0; + virtual void AddPoint(size_t curveIndex, ImVec2 value) = 0; + virtual unsigned int GetBackgroundColor() { return 0xFF202020; } + // handle undo/redo thru this functions + virtual void BeginEdit(int /*index*/) {} + virtual void EndEdit() {} + }; + + int Edit(Delegate& delegate, const ImVec2& size, unsigned int id, const ImRect* clippingRect = NULL, ImVector* selectedPoints = NULL); +} diff --git a/Source/Dependencies/imGuizmo/ImGradient.cpp b/Source/Dependencies/imGuizmo/ImGradient.cpp new file mode 100644 index 000000000..fc9b3ce2d --- /dev/null +++ b/Source/Dependencies/imGuizmo/ImGradient.cpp @@ -0,0 +1,116 @@ +// https://github.com/CedricGuillemet/ImGuizmo +// v 1.83 +// +// The MIT License(MIT) +// +// Copyright(c) 2021 Cedric Guillemet +// +// 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. +// +#include "ImGradient.h" +#include "imgui.h" +#include "imgui_internal.h" + +namespace ImGradient +{ +#ifndef IMGUI_DEFINE_MATH_OPERATORS + static inline ImVec2 operator*(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x * rhs, lhs.y * rhs); } + static inline ImVec2 operator/(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x / rhs, lhs.y / rhs); } + static inline ImVec2 operator+(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x + rhs.x, lhs.y + rhs.y); } + static inline ImVec2 operator-(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x - rhs.x, lhs.y - rhs.y); } + static inline ImVec2 operator*(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } + static inline ImVec2 operator/(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x / rhs.x, lhs.y / rhs.y); } +#endif + + static int DrawPoint(ImDrawList* draw_list, ImVec4 color, const ImVec2 size, bool editing, ImVec2 pos) + { + ImGuiIO& io = ImGui::GetIO(); + + ImVec2 p1 = ImLerp(pos, ImVec2(pos + ImVec2(size.x - size.y, 0.f)), color.w) + ImVec2(3, 3); + ImVec2 p2 = ImLerp(pos + ImVec2(size.y, size.y), ImVec2(pos + size), color.w) - ImVec2(3, 3); + ImRect rc(p1, p2); + + color.w = 1.f; + draw_list->AddRectFilled(p1, p2, ImColor(color)); + if (editing) + draw_list->AddRect(p1, p2, 0xFFFFFFFF, 2.f, 15, 2.5f); + else + draw_list->AddRect(p1, p2, 0x80FFFFFF, 2.f, 15, 1.25f); + + if (rc.Contains(io.MousePos)) + { + if (io.MouseClicked[0]) + return 2; + return 1; + } + return 0; + } + + bool Edit(Delegate& delegate, const ImVec2& size, int& selection) + { + bool ret = false; + ImGuiIO& io = ImGui::GetIO(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + ImGui::BeginChildFrame(137, size); + + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + const ImVec2 offset = ImGui::GetCursorScreenPos(); + + const ImVec4* pts = delegate.GetPoints(); + static int currentSelection = -1; + static int movingPt = -1; + if (currentSelection >= int(delegate.GetPointCount())) + currentSelection = -1; + if (movingPt != -1) + { + ImVec4 current = pts[movingPt]; + current.w += io.MouseDelta.x / size.x; + current.w = ImClamp(current.w, 0.f, 1.f); + delegate.EditPoint(movingPt, current); + ret = true; + if (!io.MouseDown[0]) + movingPt = -1; + } + for (size_t i = 0; i < delegate.GetPointCount(); i++) + { + int ptSel = DrawPoint(draw_list, pts[i], size, i == currentSelection, offset); + if (ptSel == 2) + { + currentSelection = int(i); + ret = true; + } + if (ptSel == 1 && io.MouseDown[0] && movingPt == -1) + { + movingPt = int(i); + } + } + ImRect rc(offset, offset + size); + if (rc.Contains(io.MousePos) && io.MouseDoubleClicked[0]) + { + float t = (io.MousePos.x - offset.x) / size.x; + delegate.AddPoint(delegate.GetPoint(t)); + ret = true; + } + ImGui::EndChildFrame(); + ImGui::PopStyleVar(); + + selection = currentSelection; + return ret; + } +} diff --git a/Source/Dependencies/imGuizmo/ImGradient.h b/Source/Dependencies/imGuizmo/ImGradient.h new file mode 100644 index 000000000..62c5cf078 --- /dev/null +++ b/Source/Dependencies/imGuizmo/ImGradient.h @@ -0,0 +1,44 @@ +// https://github.com/CedricGuillemet/ImGuizmo +// v 1.83 +// +// The MIT License(MIT) +// +// Copyright(c) 2021 Cedric Guillemet +// +// 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. +// +#pragma once +#include + +struct ImVec4; +struct ImVec2; + +namespace ImGradient +{ + struct Delegate + { + virtual size_t GetPointCount() = 0; + virtual ImVec4* GetPoints() = 0; + virtual int EditPoint(int pointIndex, ImVec4 value) = 0; + virtual ImVec4 GetPoint(float t) = 0; + virtual void AddPoint(ImVec4 value) = 0; + }; + + bool Edit(Delegate& delegate, const ImVec2& size, int& selection); +} diff --git a/Source/Dependencies/imGuizmo/ImGuizmo.cpp b/Source/Dependencies/imGuizmo/ImGuizmo.cpp new file mode 100644 index 000000000..ed05fd821 --- /dev/null +++ b/Source/Dependencies/imGuizmo/ImGuizmo.cpp @@ -0,0 +1,2730 @@ +// https://github.com/CedricGuillemet/ImGuizmo +// v 1.83 +// +// The MIT License(MIT) +// +// Copyright(c) 2021 Cedric Guillemet +// +// 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. +// + +#include "imgui.h" +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif +#include "imgui_internal.h" +#include "ImGuizmo.h" + +#if defined(_MSC_VER) || defined(__MINGW32__) +#include +#endif +#if !defined(_MSC_VER) && !defined(__MINGW64_VERSION_MAJOR) +#define _malloca(x) alloca(x) +#define _freea(x) +#endif + +// includes patches for multiview from +// https://github.com/CedricGuillemet/ImGuizmo/issues/15 + +namespace ImGuizmo +{ + static const float ZPI = 3.14159265358979323846f; + static const float RAD2DEG = (180.f / ZPI); + static const float DEG2RAD = (ZPI / 180.f); + const float screenRotateSize = 0.06f; + + static OPERATION operator&(OPERATION lhs, OPERATION rhs) + { + return static_cast(static_cast(lhs) & static_cast(rhs)); + } + + static bool operator!=(OPERATION lhs, int rhs) + { + return static_cast(lhs) != rhs; + } + + static bool operator==(OPERATION lhs, int rhs) + { + return static_cast(lhs) == rhs; + } + + static bool Intersects(OPERATION lhs, OPERATION rhs) + { + return (lhs & rhs) != 0; + } + + // True if lhs contains rhs + static bool Contains(OPERATION lhs, OPERATION rhs) + { + return (lhs & rhs) == rhs; + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // utility and math + + void FPU_MatrixF_x_MatrixF(const float* a, const float* b, float* r) + { + r[0] = a[0] * b[0] + a[1] * b[4] + a[2] * b[8] + a[3] * b[12]; + r[1] = a[0] * b[1] + a[1] * b[5] + a[2] * b[9] + a[3] * b[13]; + r[2] = a[0] * b[2] + a[1] * b[6] + a[2] * b[10] + a[3] * b[14]; + r[3] = a[0] * b[3] + a[1] * b[7] + a[2] * b[11] + a[3] * b[15]; + + r[4] = a[4] * b[0] + a[5] * b[4] + a[6] * b[8] + a[7] * b[12]; + r[5] = a[4] * b[1] + a[5] * b[5] + a[6] * b[9] + a[7] * b[13]; + r[6] = a[4] * b[2] + a[5] * b[6] + a[6] * b[10] + a[7] * b[14]; + r[7] = a[4] * b[3] + a[5] * b[7] + a[6] * b[11] + a[7] * b[15]; + + r[8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[8] + a[11] * b[12]; + r[9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[9] + a[11] * b[13]; + r[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10] + a[11] * b[14]; + r[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11] * b[15]; + + r[12] = a[12] * b[0] + a[13] * b[4] + a[14] * b[8] + a[15] * b[12]; + r[13] = a[12] * b[1] + a[13] * b[5] + a[14] * b[9] + a[15] * b[13]; + r[14] = a[12] * b[2] + a[13] * b[6] + a[14] * b[10] + a[15] * b[14]; + r[15] = a[12] * b[3] + a[13] * b[7] + a[14] * b[11] + a[15] * b[15]; + } + + void Frustum(float left, float right, float bottom, float top, float znear, float zfar, float* m16) + { + float temp, temp2, temp3, temp4; + temp = 2.0f * znear; + temp2 = right - left; + temp3 = top - bottom; + temp4 = zfar - znear; + m16[0] = temp / temp2; + m16[1] = 0.0; + m16[2] = 0.0; + m16[3] = 0.0; + m16[4] = 0.0; + m16[5] = temp / temp3; + m16[6] = 0.0; + m16[7] = 0.0; + m16[8] = (right + left) / temp2; + m16[9] = (top + bottom) / temp3; + m16[10] = (-zfar - znear) / temp4; + m16[11] = -1.0f; + m16[12] = 0.0; + m16[13] = 0.0; + m16[14] = (-temp * zfar) / temp4; + m16[15] = 0.0; + } + + void Perspective(float fovyInDegrees, float aspectRatio, float znear, float zfar, float* m16) + { + float ymax, xmax; + ymax = znear * tanf(fovyInDegrees * DEG2RAD); + xmax = ymax * aspectRatio; + Frustum(-xmax, xmax, -ymax, ymax, znear, zfar, m16); + } + + void Cross(const float* a, const float* b, float* r) + { + r[0] = a[1] * b[2] - a[2] * b[1]; + r[1] = a[2] * b[0] - a[0] * b[2]; + r[2] = a[0] * b[1] - a[1] * b[0]; + } + + float Dot(const float* a, const float* b) + { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; + } + + void Normalize(const float* a, float* r) + { + float il = 1.f / (sqrtf(Dot(a, a)) + FLT_EPSILON); + r[0] = a[0] * il; + r[1] = a[1] * il; + r[2] = a[2] * il; + } + + void LookAt(const float* eye, const float* at, const float* up, float* m16) + { + float X[3], Y[3], Z[3], tmp[3]; + + tmp[0] = eye[0] - at[0]; + tmp[1] = eye[1] - at[1]; + tmp[2] = eye[2] - at[2]; + Normalize(tmp, Z); + Normalize(up, Y); + Cross(Y, Z, tmp); + Normalize(tmp, X); + Cross(Z, X, tmp); + Normalize(tmp, Y); + + m16[0] = X[0]; + m16[1] = Y[0]; + m16[2] = Z[0]; + m16[3] = 0.0f; + m16[4] = X[1]; + m16[5] = Y[1]; + m16[6] = Z[1]; + m16[7] = 0.0f; + m16[8] = X[2]; + m16[9] = Y[2]; + m16[10] = Z[2]; + m16[11] = 0.0f; + m16[12] = -Dot(X, eye); + m16[13] = -Dot(Y, eye); + m16[14] = -Dot(Z, eye); + m16[15] = 1.0f; + } + + template T Clamp(T x, T y, T z) { return ((x < y) ? y : ((x > z) ? z : x)); } + template T max(T x, T y) { return (x > y) ? x : y; } + template T min(T x, T y) { return (x < y) ? x : y; } + template bool IsWithin(T x, T y, T z) { return (x >= y) && (x <= z); } + + struct matrix_t; + struct vec_t + { + public: + float x, y, z, w; + + void Lerp(const vec_t& v, float t) + { + x += (v.x - x) * t; + y += (v.y - y) * t; + z += (v.z - z) * t; + w += (v.w - w) * t; + } + + void Set(float v) { x = y = z = w = v; } + void Set(float _x, float _y, float _z = 0.f, float _w = 0.f) { x = _x; y = _y; z = _z; w = _w; } + + vec_t& operator -= (const vec_t& v) { x -= v.x; y -= v.y; z -= v.z; w -= v.w; return *this; } + vec_t& operator += (const vec_t& v) { x += v.x; y += v.y; z += v.z; w += v.w; return *this; } + vec_t& operator *= (const vec_t& v) { x *= v.x; y *= v.y; z *= v.z; w *= v.w; return *this; } + vec_t& operator *= (float v) { x *= v; y *= v; z *= v; w *= v; return *this; } + + vec_t operator * (float f) const; + vec_t operator - () const; + vec_t operator - (const vec_t& v) const; + vec_t operator + (const vec_t& v) const; + vec_t operator * (const vec_t& v) const; + + const vec_t& operator + () const { return (*this); } + float Length() const { return sqrtf(x * x + y * y + z * z); }; + float LengthSq() const { return (x * x + y * y + z * z); }; + vec_t Normalize() { (*this) *= (1.f / Length()); return (*this); } + vec_t Normalize(const vec_t& v) { this->Set(v.x, v.y, v.z, v.w); this->Normalize(); return (*this); } + vec_t Abs() const; + + void Cross(const vec_t& v) + { + vec_t res; + res.x = y * v.z - z * v.y; + res.y = z * v.x - x * v.z; + res.z = x * v.y - y * v.x; + + x = res.x; + y = res.y; + z = res.z; + w = 0.f; + } + + void Cross(const vec_t& v1, const vec_t& v2) + { + x = v1.y * v2.z - v1.z * v2.y; + y = v1.z * v2.x - v1.x * v2.z; + z = v1.x * v2.y - v1.y * v2.x; + w = 0.f; + } + + float Dot(const vec_t& v) const + { + return (x * v.x) + (y * v.y) + (z * v.z) + (w * v.w); + } + + float Dot3(const vec_t& v) const + { + return (x * v.x) + (y * v.y) + (z * v.z); + } + + void Transform(const matrix_t& matrix); + void Transform(const vec_t& s, const matrix_t& matrix); + + void TransformVector(const matrix_t& matrix); + void TransformPoint(const matrix_t& matrix); + void TransformVector(const vec_t& v, const matrix_t& matrix) { (*this) = v; this->TransformVector(matrix); } + void TransformPoint(const vec_t& v, const matrix_t& matrix) { (*this) = v; this->TransformPoint(matrix); } + + float& operator [] (size_t index) { return ((float*)&x)[index]; } + const float& operator [] (size_t index) const { return ((float*)&x)[index]; } + bool operator!=(const vec_t& other) const { return memcmp(this, &other, sizeof(vec_t)); } + }; + + vec_t makeVect(float _x, float _y, float _z = 0.f, float _w = 0.f) { vec_t res; res.x = _x; res.y = _y; res.z = _z; res.w = _w; return res; } + vec_t makeVect(ImVec2 v) { vec_t res; res.x = v.x; res.y = v.y; res.z = 0.f; res.w = 0.f; return res; } + vec_t vec_t::operator * (float f) const { return makeVect(x * f, y * f, z * f, w * f); } + vec_t vec_t::operator - () const { return makeVect(-x, -y, -z, -w); } + vec_t vec_t::operator - (const vec_t& v) const { return makeVect(x - v.x, y - v.y, z - v.z, w - v.w); } + vec_t vec_t::operator + (const vec_t& v) const { return makeVect(x + v.x, y + v.y, z + v.z, w + v.w); } + vec_t vec_t::operator * (const vec_t& v) const { return makeVect(x * v.x, y * v.y, z * v.z, w * v.w); } + vec_t vec_t::Abs() const { return makeVect(fabsf(x), fabsf(y), fabsf(z)); } + + vec_t Normalized(const vec_t& v) { vec_t res; res = v; res.Normalize(); return res; } + vec_t Cross(const vec_t& v1, const vec_t& v2) + { + vec_t res; + res.x = v1.y * v2.z - v1.z * v2.y; + res.y = v1.z * v2.x - v1.x * v2.z; + res.z = v1.x * v2.y - v1.y * v2.x; + res.w = 0.f; + return res; + } + + float Dot(const vec_t& v1, const vec_t& v2) + { + return (v1.x * v2.x) + (v1.y * v2.y) + (v1.z * v2.z); + } + + vec_t BuildPlan(const vec_t& p_point1, const vec_t& p_normal) + { + vec_t normal, res; + normal.Normalize(p_normal); + res.w = normal.Dot(p_point1); + res.x = normal.x; + res.y = normal.y; + res.z = normal.z; + return res; + } + + struct matrix_t + { + public: + + union + { + float m[4][4]; + float m16[16]; + struct + { + vec_t right, up, dir, position; + } v; + vec_t component[4]; + }; + + matrix_t(const matrix_t& other) { memcpy(&m16[0], &other.m16[0], sizeof(float) * 16); } + matrix_t() {} + + operator float* () { return m16; } + operator const float* () const { return m16; } + void Translation(float _x, float _y, float _z) { this->Translation(makeVect(_x, _y, _z)); } + + void Translation(const vec_t& vt) + { + v.right.Set(1.f, 0.f, 0.f, 0.f); + v.up.Set(0.f, 1.f, 0.f, 0.f); + v.dir.Set(0.f, 0.f, 1.f, 0.f); + v.position.Set(vt.x, vt.y, vt.z, 1.f); + } + + void Scale(float _x, float _y, float _z) + { + v.right.Set(_x, 0.f, 0.f, 0.f); + v.up.Set(0.f, _y, 0.f, 0.f); + v.dir.Set(0.f, 0.f, _z, 0.f); + v.position.Set(0.f, 0.f, 0.f, 1.f); + } + void Scale(const vec_t& s) { Scale(s.x, s.y, s.z); } + + matrix_t& operator *= (const matrix_t& mat) + { + matrix_t tmpMat; + tmpMat = *this; + tmpMat.Multiply(mat); + *this = tmpMat; + return *this; + } + matrix_t operator * (const matrix_t& mat) const + { + matrix_t matT; + matT.Multiply(*this, mat); + return matT; + } + + void Multiply(const matrix_t& matrix) + { + matrix_t tmp; + tmp = *this; + + FPU_MatrixF_x_MatrixF((float*)&tmp, (float*)&matrix, (float*)this); + } + + void Multiply(const matrix_t& m1, const matrix_t& m2) + { + FPU_MatrixF_x_MatrixF((float*)&m1, (float*)&m2, (float*)this); + } + + float GetDeterminant() const + { + return m[0][0] * m[1][1] * m[2][2] + m[0][1] * m[1][2] * m[2][0] + m[0][2] * m[1][0] * m[2][1] - + m[0][2] * m[1][1] * m[2][0] - m[0][1] * m[1][0] * m[2][2] - m[0][0] * m[1][2] * m[2][1]; + } + + float Inverse(const matrix_t& srcMatrix, bool affine = false); + void SetToIdentity() + { + v.right.Set(1.f, 0.f, 0.f, 0.f); + v.up.Set(0.f, 1.f, 0.f, 0.f); + v.dir.Set(0.f, 0.f, 1.f, 0.f); + v.position.Set(0.f, 0.f, 0.f, 1.f); + } + void Transpose() + { + matrix_t tmpm; + for (int l = 0; l < 4; l++) + { + for (int c = 0; c < 4; c++) + { + tmpm.m[l][c] = m[c][l]; + } + } + (*this) = tmpm; + } + + void RotationAxis(const vec_t& axis, float angle); + + void OrthoNormalize() + { + v.right.Normalize(); + v.up.Normalize(); + v.dir.Normalize(); + } + }; + + void vec_t::Transform(const matrix_t& matrix) + { + vec_t out; + + out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0] + w * matrix.m[3][0]; + out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1] + w * matrix.m[3][1]; + out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2] + w * matrix.m[3][2]; + out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3] + w * matrix.m[3][3]; + + x = out.x; + y = out.y; + z = out.z; + w = out.w; + } + + void vec_t::Transform(const vec_t& s, const matrix_t& matrix) + { + *this = s; + Transform(matrix); + } + + void vec_t::TransformPoint(const matrix_t& matrix) + { + vec_t out; + + out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0] + matrix.m[3][0]; + out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1] + matrix.m[3][1]; + out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2] + matrix.m[3][2]; + out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3] + matrix.m[3][3]; + + x = out.x; + y = out.y; + z = out.z; + w = out.w; + } + + void vec_t::TransformVector(const matrix_t& matrix) + { + vec_t out; + + out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0]; + out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1]; + out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2]; + out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3]; + + x = out.x; + y = out.y; + z = out.z; + w = out.w; + } + + float matrix_t::Inverse(const matrix_t& srcMatrix, bool affine) + { + float det = 0; + + if (affine) + { + det = GetDeterminant(); + float s = 1 / det; + m[0][0] = (srcMatrix.m[1][1] * srcMatrix.m[2][2] - srcMatrix.m[1][2] * srcMatrix.m[2][1]) * s; + m[0][1] = (srcMatrix.m[2][1] * srcMatrix.m[0][2] - srcMatrix.m[2][2] * srcMatrix.m[0][1]) * s; + m[0][2] = (srcMatrix.m[0][1] * srcMatrix.m[1][2] - srcMatrix.m[0][2] * srcMatrix.m[1][1]) * s; + m[1][0] = (srcMatrix.m[1][2] * srcMatrix.m[2][0] - srcMatrix.m[1][0] * srcMatrix.m[2][2]) * s; + m[1][1] = (srcMatrix.m[2][2] * srcMatrix.m[0][0] - srcMatrix.m[2][0] * srcMatrix.m[0][2]) * s; + m[1][2] = (srcMatrix.m[0][2] * srcMatrix.m[1][0] - srcMatrix.m[0][0] * srcMatrix.m[1][2]) * s; + m[2][0] = (srcMatrix.m[1][0] * srcMatrix.m[2][1] - srcMatrix.m[1][1] * srcMatrix.m[2][0]) * s; + m[2][1] = (srcMatrix.m[2][0] * srcMatrix.m[0][1] - srcMatrix.m[2][1] * srcMatrix.m[0][0]) * s; + m[2][2] = (srcMatrix.m[0][0] * srcMatrix.m[1][1] - srcMatrix.m[0][1] * srcMatrix.m[1][0]) * s; + m[3][0] = -(m[0][0] * srcMatrix.m[3][0] + m[1][0] * srcMatrix.m[3][1] + m[2][0] * srcMatrix.m[3][2]); + m[3][1] = -(m[0][1] * srcMatrix.m[3][0] + m[1][1] * srcMatrix.m[3][1] + m[2][1] * srcMatrix.m[3][2]); + m[3][2] = -(m[0][2] * srcMatrix.m[3][0] + m[1][2] * srcMatrix.m[3][1] + m[2][2] * srcMatrix.m[3][2]); + } + else + { + // transpose matrix + float src[16]; + for (int i = 0; i < 4; ++i) + { + src[i] = srcMatrix.m16[i * 4]; + src[i + 4] = srcMatrix.m16[i * 4 + 1]; + src[i + 8] = srcMatrix.m16[i * 4 + 2]; + src[i + 12] = srcMatrix.m16[i * 4 + 3]; + } + + // calculate pairs for first 8 elements (cofactors) + float tmp[12]; // temp array for pairs + tmp[0] = src[10] * src[15]; + tmp[1] = src[11] * src[14]; + tmp[2] = src[9] * src[15]; + tmp[3] = src[11] * src[13]; + tmp[4] = src[9] * src[14]; + tmp[5] = src[10] * src[13]; + tmp[6] = src[8] * src[15]; + tmp[7] = src[11] * src[12]; + tmp[8] = src[8] * src[14]; + tmp[9] = src[10] * src[12]; + tmp[10] = src[8] * src[13]; + tmp[11] = src[9] * src[12]; + + // calculate first 8 elements (cofactors) + m16[0] = (tmp[0] * src[5] + tmp[3] * src[6] + tmp[4] * src[7]) - (tmp[1] * src[5] + tmp[2] * src[6] + tmp[5] * src[7]); + m16[1] = (tmp[1] * src[4] + tmp[6] * src[6] + tmp[9] * src[7]) - (tmp[0] * src[4] + tmp[7] * src[6] + tmp[8] * src[7]); + m16[2] = (tmp[2] * src[4] + tmp[7] * src[5] + tmp[10] * src[7]) - (tmp[3] * src[4] + tmp[6] * src[5] + tmp[11] * src[7]); + m16[3] = (tmp[5] * src[4] + tmp[8] * src[5] + tmp[11] * src[6]) - (tmp[4] * src[4] + tmp[9] * src[5] + tmp[10] * src[6]); + m16[4] = (tmp[1] * src[1] + tmp[2] * src[2] + tmp[5] * src[3]) - (tmp[0] * src[1] + tmp[3] * src[2] + tmp[4] * src[3]); + m16[5] = (tmp[0] * src[0] + tmp[7] * src[2] + tmp[8] * src[3]) - (tmp[1] * src[0] + tmp[6] * src[2] + tmp[9] * src[3]); + m16[6] = (tmp[3] * src[0] + tmp[6] * src[1] + tmp[11] * src[3]) - (tmp[2] * src[0] + tmp[7] * src[1] + tmp[10] * src[3]); + m16[7] = (tmp[4] * src[0] + tmp[9] * src[1] + tmp[10] * src[2]) - (tmp[5] * src[0] + tmp[8] * src[1] + tmp[11] * src[2]); + + // calculate pairs for second 8 elements (cofactors) + tmp[0] = src[2] * src[7]; + tmp[1] = src[3] * src[6]; + tmp[2] = src[1] * src[7]; + tmp[3] = src[3] * src[5]; + tmp[4] = src[1] * src[6]; + tmp[5] = src[2] * src[5]; + tmp[6] = src[0] * src[7]; + tmp[7] = src[3] * src[4]; + tmp[8] = src[0] * src[6]; + tmp[9] = src[2] * src[4]; + tmp[10] = src[0] * src[5]; + tmp[11] = src[1] * src[4]; + + // calculate second 8 elements (cofactors) + m16[8] = (tmp[0] * src[13] + tmp[3] * src[14] + tmp[4] * src[15]) - (tmp[1] * src[13] + tmp[2] * src[14] + tmp[5] * src[15]); + m16[9] = (tmp[1] * src[12] + tmp[6] * src[14] + tmp[9] * src[15]) - (tmp[0] * src[12] + tmp[7] * src[14] + tmp[8] * src[15]); + m16[10] = (tmp[2] * src[12] + tmp[7] * src[13] + tmp[10] * src[15]) - (tmp[3] * src[12] + tmp[6] * src[13] + tmp[11] * src[15]); + m16[11] = (tmp[5] * src[12] + tmp[8] * src[13] + tmp[11] * src[14]) - (tmp[4] * src[12] + tmp[9] * src[13] + tmp[10] * src[14]); + m16[12] = (tmp[2] * src[10] + tmp[5] * src[11] + tmp[1] * src[9]) - (tmp[4] * src[11] + tmp[0] * src[9] + tmp[3] * src[10]); + m16[13] = (tmp[8] * src[11] + tmp[0] * src[8] + tmp[7] * src[10]) - (tmp[6] * src[10] + tmp[9] * src[11] + tmp[1] * src[8]); + m16[14] = (tmp[6] * src[9] + tmp[11] * src[11] + tmp[3] * src[8]) - (tmp[10] * src[11] + tmp[2] * src[8] + tmp[7] * src[9]); + m16[15] = (tmp[10] * src[10] + tmp[4] * src[8] + tmp[9] * src[9]) - (tmp[8] * src[9] + tmp[11] * src[10] + tmp[5] * src[8]); + + // calculate determinant + det = src[0] * m16[0] + src[1] * m16[1] + src[2] * m16[2] + src[3] * m16[3]; + + // calculate matrix inverse + float invdet = 1 / det; + for (int j = 0; j < 16; ++j) + { + m16[j] *= invdet; + } + } + + return det; + } + + void matrix_t::RotationAxis(const vec_t& axis, float angle) + { + float length2 = axis.LengthSq(); + if (length2 < FLT_EPSILON) + { + SetToIdentity(); + return; + } + + vec_t n = axis * (1.f / sqrtf(length2)); + float s = sinf(angle); + float c = cosf(angle); + float k = 1.f - c; + + float xx = n.x * n.x * k + c; + float yy = n.y * n.y * k + c; + float zz = n.z * n.z * k + c; + float xy = n.x * n.y * k; + float yz = n.y * n.z * k; + float zx = n.z * n.x * k; + float xs = n.x * s; + float ys = n.y * s; + float zs = n.z * s; + + m[0][0] = xx; + m[0][1] = xy + zs; + m[0][2] = zx - ys; + m[0][3] = 0.f; + m[1][0] = xy - zs; + m[1][1] = yy; + m[1][2] = yz + xs; + m[1][3] = 0.f; + m[2][0] = zx + ys; + m[2][1] = yz - xs; + m[2][2] = zz; + m[2][3] = 0.f; + m[3][0] = 0.f; + m[3][1] = 0.f; + m[3][2] = 0.f; + m[3][3] = 1.f; + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // + + enum MOVETYPE + { + MT_NONE, + MT_MOVE_X, + MT_MOVE_Y, + MT_MOVE_Z, + MT_MOVE_YZ, + MT_MOVE_ZX, + MT_MOVE_XY, + MT_MOVE_SCREEN, + MT_ROTATE_X, + MT_ROTATE_Y, + MT_ROTATE_Z, + MT_ROTATE_SCREEN, + MT_SCALE_X, + MT_SCALE_Y, + MT_SCALE_Z, + MT_SCALE_XYZ + }; + + static bool IsTranslateType(int type) + { + return type >= MT_MOVE_X && type <= MT_MOVE_SCREEN; + } + + static bool IsRotateType(int type) + { + return type >= MT_ROTATE_X && type <= MT_ROTATE_SCREEN; + } + + static bool IsScaleType(int type) + { + return type >= MT_SCALE_X && type <= MT_SCALE_XYZ; + } + + // Matches MT_MOVE_AB order + static const OPERATION TRANSLATE_PLANS[3] = { TRANSLATE_Y | TRANSLATE_Z, TRANSLATE_X | TRANSLATE_Z, TRANSLATE_X | TRANSLATE_Y }; + + struct Context + { + Context() : mbUsing(false), mbEnable(true), mbUsingBounds(false) + { + } + + ImDrawList* mDrawList; + + MODE mMode; + matrix_t mViewMat; + matrix_t mProjectionMat; + matrix_t mModel; + matrix_t mModelInverse; + matrix_t mModelSource; + matrix_t mModelSourceInverse; + matrix_t mMVP; + matrix_t mViewProjection; + + vec_t mModelScaleOrigin; + vec_t mCameraEye; + vec_t mCameraRight; + vec_t mCameraDir; + vec_t mCameraUp; + vec_t mRayOrigin; + vec_t mRayVector; + + float mRadiusSquareCenter; + ImVec2 mScreenSquareCenter; + ImVec2 mScreenSquareMin; + ImVec2 mScreenSquareMax; + + float mScreenFactor; + vec_t mRelativeOrigin; + + bool mbUsing; + bool mbEnable; + + bool mReversed; // reversed projection matrix + + // translation + vec_t mTranslationPlan; + vec_t mTranslationPlanOrigin; + vec_t mMatrixOrigin; + vec_t mTranslationLastDelta; + + // rotation + vec_t mRotationVectorSource; + float mRotationAngle; + float mRotationAngleOrigin; + //vec_t mWorldToLocalAxis; + + // scale + vec_t mScale; + vec_t mScaleValueOrigin; + vec_t mScaleLast; + float mSaveMousePosx; + + // save axis factor when using gizmo + bool mBelowAxisLimit[3]; + bool mBelowPlaneLimit[3]; + float mAxisFactor[3]; + + // bounds stretching + vec_t mBoundsPivot; + vec_t mBoundsAnchor; + vec_t mBoundsPlan; + vec_t mBoundsLocalPivot; + int mBoundsBestAxis; + int mBoundsAxis[2]; + bool mbUsingBounds; + matrix_t mBoundsMatrix; + + // + int mCurrentOperation; + + float mX = 0.f; + float mY = 0.f; + float mWidth = 0.f; + float mHeight = 0.f; + float mXMax = 0.f; + float mYMax = 0.f; + float mDisplayRatio = 1.f; + + bool mIsOrthographic = false; + + int mActualID = -1; + int mEditingID = -1; + OPERATION mOperation = OPERATION(-1); + + bool mAllowAxisFlip = true; + float mGizmoSizeClipSpace = 0.1f; + }; + + static Context gContext; + + static const vec_t directionUnary[3] = { makeVect(1.f, 0.f, 0.f), makeVect(0.f, 1.f, 0.f), makeVect(0.f, 0.f, 1.f) }; + static const ImU32 directionColor[3] = { IM_COL32(0xAA, 0, 0, 0xFF), IM_COL32(0, 0xAA, 0, 0xFF), IM_COL32(0, 0, 0xAA, 0XFF) }; + + // Alpha: 100%: FF, 87%: DE, 70%: B3, 54%: 8A, 50%: 80, 38%: 61, 12%: 1F + static const ImU32 planeColor[3] = { IM_COL32(0xAA, 0, 0, 0x61), IM_COL32(0, 0xAA, 0, 0x61), IM_COL32(0, 0, 0xAA, 0x61) }; + static const ImU32 selectionColor = IM_COL32(0xFF, 0x80, 0x10, 0x8A); + static const ImU32 inactiveColor = IM_COL32(0x99, 0x99, 0x99, 0x99); + static const ImU32 translationLineColor = IM_COL32(0xAA, 0xAA, 0xAA, 0xAA); + static const char* translationInfoMask[] = { "X : %5.3f", "Y : %5.3f", "Z : %5.3f", + "Y : %5.3f Z : %5.3f", "X : %5.3f Z : %5.3f", "X : %5.3f Y : %5.3f", + "X : %5.3f Y : %5.3f Z : %5.3f" }; + static const char* scaleInfoMask[] = { "X : %5.2f", "Y : %5.2f", "Z : %5.2f", "XYZ : %5.2f" }; + static const char* rotationInfoMask[] = { "X : %5.2f deg %5.2f rad", "Y : %5.2f deg %5.2f rad", "Z : %5.2f deg %5.2f rad", "Screen : %5.2f deg %5.2f rad" }; + static const int translationInfoIndex[] = { 0,0,0, 1,0,0, 2,0,0, 1,2,0, 0,2,0, 0,1,0, 0,1,2 }; + static const float quadMin = 0.5f; + static const float quadMax = 0.8f; + static const float quadUV[8] = { quadMin, quadMin, quadMin, quadMax, quadMax, quadMax, quadMax, quadMin }; + static const int halfCircleSegmentCount = 64; + static const float snapTension = 0.5f; + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // + static int GetMoveType(OPERATION op, vec_t* gizmoHitProportion); + static int GetRotateType(OPERATION op); + static int GetScaleType(OPERATION op); + + static ImVec2 worldToPos(const vec_t& worldPos, const matrix_t& mat, ImVec2 position = ImVec2(gContext.mX, gContext.mY), ImVec2 size = ImVec2(gContext.mWidth, gContext.mHeight)) + { + vec_t trans; + trans.TransformPoint(worldPos, mat); + trans *= 0.5f / trans.w; + trans += makeVect(0.5f, 0.5f); + trans.y = 1.f - trans.y; + trans.x *= size.x; + trans.y *= size.y; + trans.x += position.x; + trans.y += position.y; + return ImVec2(trans.x, trans.y); + } + + static void ComputeCameraRay(vec_t& rayOrigin, vec_t& rayDir, ImVec2 position = ImVec2(gContext.mX, gContext.mY), ImVec2 size = ImVec2(gContext.mWidth, gContext.mHeight)) + { + ImGuiIO& io = ImGui::GetIO(); + + matrix_t mViewProjInverse; + mViewProjInverse.Inverse(gContext.mViewMat * gContext.mProjectionMat); + + const float mox = ((io.MousePos.x - position.x) / size.x) * 2.f - 1.f; + const float moy = (1.f - ((io.MousePos.y - position.y) / size.y)) * 2.f - 1.f; + + const float zNear = gContext.mReversed ? (1.f - FLT_EPSILON) : 0.f; + const float zFar = gContext.mReversed ? 0.f : (1.f - FLT_EPSILON); + + rayOrigin.Transform(makeVect(mox, moy, zNear, 1.f), mViewProjInverse); + rayOrigin *= 1.f / rayOrigin.w; + vec_t rayEnd; + rayEnd.Transform(makeVect(mox, moy, zFar, 1.f), mViewProjInverse); + rayEnd *= 1.f / rayEnd.w; + rayDir = Normalized(rayEnd - rayOrigin); + } + + static float GetSegmentLengthClipSpace(const vec_t& start, const vec_t& end) + { + vec_t startOfSegment = start; + startOfSegment.TransformPoint(gContext.mMVP); + if (fabsf(startOfSegment.w) > FLT_EPSILON) // check for axis aligned with camera direction + { + startOfSegment *= 1.f / startOfSegment.w; + } + + vec_t endOfSegment = end; + endOfSegment.TransformPoint(gContext.mMVP); + if (fabsf(endOfSegment.w) > FLT_EPSILON) // check for axis aligned with camera direction + { + endOfSegment *= 1.f / endOfSegment.w; + } + + vec_t clipSpaceAxis = endOfSegment - startOfSegment; + clipSpaceAxis.y /= gContext.mDisplayRatio; + float segmentLengthInClipSpace = sqrtf(clipSpaceAxis.x * clipSpaceAxis.x + clipSpaceAxis.y * clipSpaceAxis.y); + return segmentLengthInClipSpace; + } + + static float GetParallelogram(const vec_t& ptO, const vec_t& ptA, const vec_t& ptB) + { + vec_t pts[] = { ptO, ptA, ptB }; + for (unsigned int i = 0; i < 3; i++) + { + pts[i].TransformPoint(gContext.mMVP); + if (fabsf(pts[i].w) > FLT_EPSILON) // check for axis aligned with camera direction + { + pts[i] *= 1.f / pts[i].w; + } + } + vec_t segA = pts[1] - pts[0]; + vec_t segB = pts[2] - pts[0]; + segA.y /= gContext.mDisplayRatio; + segB.y /= gContext.mDisplayRatio; + vec_t segAOrtho = makeVect(-segA.y, segA.x); + segAOrtho.Normalize(); + float dt = segAOrtho.Dot3(segB); + float surface = sqrtf(segA.x * segA.x + segA.y * segA.y) * fabsf(dt); + return surface; + } + + inline vec_t PointOnSegment(const vec_t& point, const vec_t& vertPos1, const vec_t& vertPos2) + { + vec_t c = point - vertPos1; + vec_t V; + + V.Normalize(vertPos2 - vertPos1); + float d = (vertPos2 - vertPos1).Length(); + float t = V.Dot3(c); + + if (t < 0.f) + { + return vertPos1; + } + + if (t > d) + { + return vertPos2; + } + + return vertPos1 + V * t; + } + + static float IntersectRayPlane(const vec_t& rOrigin, const vec_t& rVector, const vec_t& plan) + { + float numer = plan.Dot3(rOrigin) - plan.w; + float denom = plan.Dot3(rVector); + + if (fabsf(denom) < FLT_EPSILON) // normal is orthogonal to vector, cant intersect + { + return -1.0f; + } + + return -(numer / denom); + } + + static float DistanceToPlane(const vec_t& point, const vec_t& plan) + { + return plan.Dot3(point) + plan.w; + } + + static bool IsInContextRect(ImVec2 p) + { + return IsWithin(p.x, gContext.mX, gContext.mXMax) && IsWithin(p.y, gContext.mY, gContext.mYMax); + } + + void SetRect(float x, float y, float width, float height) + { + gContext.mX = x; + gContext.mY = y; + gContext.mWidth = width; + gContext.mHeight = height; + gContext.mXMax = gContext.mX + gContext.mWidth; + gContext.mYMax = gContext.mY + gContext.mXMax; + gContext.mDisplayRatio = width / height; + } + + void SetOrthographic(bool isOrthographic) + { + gContext.mIsOrthographic = isOrthographic; + } + + void SetDrawlist(ImDrawList* drawlist) + { + gContext.mDrawList = drawlist ? drawlist : ImGui::GetWindowDrawList(); + } + + void SetImGuiContext(ImGuiContext* ctx) + { + ImGui::SetCurrentContext(ctx); + } + + void BeginFrame() + { + const ImU32 flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoBringToFrontOnFocus; + +#ifdef IMGUI_HAS_VIEWPORT + ImGui::SetNextWindowSize(ImGui::GetMainViewport()->Size); + ImGui::SetNextWindowPos(ImGui::GetMainViewport()->Pos); +#else + ImGuiIO& io = ImGui::GetIO(); + ImGui::SetNextWindowSize(io.DisplaySize); + ImGui::SetNextWindowPos(ImVec2(0, 0)); +#endif + + ImGui::PushStyleColor(ImGuiCol_WindowBg, 0); + ImGui::PushStyleColor(ImGuiCol_Border, 0); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + + ImGui::Begin("gizmo", NULL, flags); + gContext.mDrawList = ImGui::GetWindowDrawList(); + ImGui::End(); + ImGui::PopStyleVar(); + ImGui::PopStyleColor(2); + } + + bool IsUsing() + { + return gContext.mbUsing || gContext.mbUsingBounds; + } + + bool IsOver() + { + return (Intersects(gContext.mOperation, TRANSLATE) && GetMoveType(gContext.mOperation, NULL) != MT_NONE) || + (Intersects(gContext.mOperation, ROTATE) && GetRotateType(gContext.mOperation) != MT_NONE) || + (Intersects(gContext.mOperation, SCALE) && GetScaleType(gContext.mOperation) != MT_NONE) || IsUsing(); + } + + bool IsOver(OPERATION op) + { + if(IsUsing()) + { + return true; + } + if(Intersects(op, SCALE) && GetScaleType(op) != MT_NONE) + { + return true; + } + if(Intersects(op, ROTATE) && GetRotateType(op) != MT_NONE) + { + return true; + } + if(Intersects(op, TRANSLATE) && GetMoveType(op, NULL) != MT_NONE) + { + return true; + } + return false; + } + + void Enable(bool enable) + { + gContext.mbEnable = enable; + if (!enable) + { + gContext.mbUsing = false; + gContext.mbUsingBounds = false; + } + } + + static void ComputeContext(const float* view, const float* projection, float* matrix, MODE mode) + { + gContext.mMode = mode; + gContext.mViewMat = *(matrix_t*)view; + gContext.mProjectionMat = *(matrix_t*)projection; + + if (mode == LOCAL) + { + gContext.mModel = *(matrix_t*)matrix; + gContext.mModel.OrthoNormalize(); + } + else + { + gContext.mModel.Translation(((matrix_t*)matrix)->v.position); + } + gContext.mModelSource = *(matrix_t*)matrix; + gContext.mModelScaleOrigin.Set(gContext.mModelSource.v.right.Length(), gContext.mModelSource.v.up.Length(), gContext.mModelSource.v.dir.Length()); + + gContext.mModelInverse.Inverse(gContext.mModel); + gContext.mModelSourceInverse.Inverse(gContext.mModelSource); + gContext.mViewProjection = gContext.mViewMat * gContext.mProjectionMat; + gContext.mMVP = gContext.mModel * gContext.mViewProjection; + + matrix_t viewInverse; + viewInverse.Inverse(gContext.mViewMat); + gContext.mCameraDir = viewInverse.v.dir; + gContext.mCameraEye = viewInverse.v.position; + gContext.mCameraRight = viewInverse.v.right; + gContext.mCameraUp = viewInverse.v.up; + + // projection reverse + vec_t nearPos, farPos; + nearPos.Transform(makeVect(0, 0, 1.f, 1.f), gContext.mProjectionMat); + farPos.Transform(makeVect(0, 0, 2.f, 1.f), gContext.mProjectionMat); + + gContext.mReversed = (nearPos.z/nearPos.w) > (farPos.z / farPos.w); + + // compute scale from the size of camera right vector projected on screen at the matrix position + vec_t pointRight = viewInverse.v.right; + pointRight.TransformPoint(gContext.mViewProjection); + gContext.mScreenFactor = gContext.mGizmoSizeClipSpace / (pointRight.x / pointRight.w - gContext.mMVP.v.position.x / gContext.mMVP.v.position.w); + + vec_t rightViewInverse = viewInverse.v.right; + rightViewInverse.TransformVector(gContext.mModelInverse); + float rightLength = GetSegmentLengthClipSpace(makeVect(0.f, 0.f), rightViewInverse); + gContext.mScreenFactor = gContext.mGizmoSizeClipSpace / rightLength; + + ImVec2 centerSSpace = worldToPos(makeVect(0.f, 0.f), gContext.mMVP); + gContext.mScreenSquareCenter = centerSSpace; + gContext.mScreenSquareMin = ImVec2(centerSSpace.x - 10.f, centerSSpace.y - 10.f); + gContext.mScreenSquareMax = ImVec2(centerSSpace.x + 10.f, centerSSpace.y + 10.f); + + ComputeCameraRay(gContext.mRayOrigin, gContext.mRayVector); + } + + static void ComputeColors(ImU32* colors, int type, OPERATION operation) + { + if (gContext.mbEnable) + { + switch (operation) + { + case TRANSLATE: + colors[0] = (type == MT_MOVE_SCREEN) ? selectionColor : IM_COL32_WHITE; + for (int i = 0; i < 3; i++) + { + colors[i + 1] = (type == (int)(MT_MOVE_X + i)) ? selectionColor : directionColor[i]; + colors[i + 4] = (type == (int)(MT_MOVE_YZ + i)) ? selectionColor : planeColor[i]; + colors[i + 4] = (type == MT_MOVE_SCREEN) ? selectionColor : colors[i + 4]; + } + break; + case ROTATE: + colors[0] = (type == MT_ROTATE_SCREEN) ? selectionColor : IM_COL32_WHITE; + for (int i = 0; i < 3; i++) + { + colors[i + 1] = (type == (int)(MT_ROTATE_X + i)) ? selectionColor : directionColor[i]; + } + break; + case SCALE: + colors[0] = (type == MT_SCALE_XYZ) ? selectionColor : IM_COL32_WHITE; + for (int i = 0; i < 3; i++) + { + colors[i + 1] = (type == (int)(MT_SCALE_X + i)) ? selectionColor : directionColor[i]; + } + break; + // note: this internal function is only called with three possible values for operation + default: + break; + } + } + else + { + for (int i = 0; i < 7; i++) + { + colors[i] = inactiveColor; + } + } + } + + static void ComputeTripodAxisAndVisibility(int axisIndex, vec_t& dirAxis, vec_t& dirPlaneX, vec_t& dirPlaneY, bool& belowAxisLimit, bool& belowPlaneLimit) + { + dirAxis = directionUnary[axisIndex]; + dirPlaneX = directionUnary[(axisIndex + 1) % 3]; + dirPlaneY = directionUnary[(axisIndex + 2) % 3]; + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + // when using, use stored factors so the gizmo doesn't flip when we translate + belowAxisLimit = gContext.mBelowAxisLimit[axisIndex]; + belowPlaneLimit = gContext.mBelowPlaneLimit[axisIndex]; + + dirAxis *= gContext.mAxisFactor[axisIndex]; + dirPlaneX *= gContext.mAxisFactor[(axisIndex + 1) % 3]; + dirPlaneY *= gContext.mAxisFactor[(axisIndex + 2) % 3]; + } + else + { + // new method + float lenDir = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirAxis); + float lenDirMinus = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), -dirAxis); + + float lenDirPlaneX = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirPlaneX); + float lenDirMinusPlaneX = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), -dirPlaneX); + + float lenDirPlaneY = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirPlaneY); + float lenDirMinusPlaneY = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), -dirPlaneY); + + // For readability + bool & allowFlip = gContext.mAllowAxisFlip; + float mulAxis = (allowFlip && lenDir < lenDirMinus&& fabsf(lenDir - lenDirMinus) > FLT_EPSILON) ? -1.f : 1.f; + float mulAxisX = (allowFlip && lenDirPlaneX < lenDirMinusPlaneX&& fabsf(lenDirPlaneX - lenDirMinusPlaneX) > FLT_EPSILON) ? -1.f : 1.f; + float mulAxisY = (allowFlip && lenDirPlaneY < lenDirMinusPlaneY&& fabsf(lenDirPlaneY - lenDirMinusPlaneY) > FLT_EPSILON) ? -1.f : 1.f; + dirAxis *= mulAxis; + dirPlaneX *= mulAxisX; + dirPlaneY *= mulAxisY; + + // for axis + float axisLengthInClipSpace = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirAxis * gContext.mScreenFactor); + + float paraSurf = GetParallelogram(makeVect(0.f, 0.f, 0.f), dirPlaneX * gContext.mScreenFactor, dirPlaneY * gContext.mScreenFactor); + belowPlaneLimit = (paraSurf > 0.0025f); + belowAxisLimit = (axisLengthInClipSpace > 0.02f); + + // and store values + gContext.mAxisFactor[axisIndex] = mulAxis; + gContext.mAxisFactor[(axisIndex + 1) % 3] = mulAxisX; + gContext.mAxisFactor[(axisIndex + 2) % 3] = mulAxisY; + gContext.mBelowAxisLimit[axisIndex] = belowAxisLimit; + gContext.mBelowPlaneLimit[axisIndex] = belowPlaneLimit; + } + } + + static void ComputeSnap(float* value, float snap) + { + if (snap <= FLT_EPSILON) + { + return; + } + + float modulo = fmodf(*value, snap); + float moduloRatio = fabsf(modulo) / snap; + if (moduloRatio < snapTension) + { + *value -= modulo; + } + else if (moduloRatio > (1.f - snapTension)) + { + *value = *value - modulo + snap * ((*value < 0.f) ? -1.f : 1.f); + } + } + static void ComputeSnap(vec_t& value, const float* snap) + { + for (int i = 0; i < 3; i++) + { + ComputeSnap(&value[i], snap[i]); + } + } + + static float ComputeAngleOnPlan() + { + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + vec_t localPos = Normalized(gContext.mRayOrigin + gContext.mRayVector * len - gContext.mModel.v.position); + + vec_t perpendicularVector; + perpendicularVector.Cross(gContext.mRotationVectorSource, gContext.mTranslationPlan); + perpendicularVector.Normalize(); + float acosAngle = Clamp(Dot(localPos, gContext.mRotationVectorSource), -1.f, 1.f); + float angle = acosf(acosAngle); + angle *= (Dot(localPos, perpendicularVector) < 0.f) ? 1.f : -1.f; + return angle; + } + + static void DrawRotationGizmo(OPERATION op, int type) + { + if(!Intersects(op, ROTATE)) + { + return; + } + ImDrawList* drawList = gContext.mDrawList; + + // colors + ImU32 colors[7]; + ComputeColors(colors, type, ROTATE); + + vec_t cameraToModelNormalized; + if (gContext.mIsOrthographic) + { + matrix_t viewInverse; + viewInverse.Inverse(*(matrix_t*)&gContext.mViewMat); + cameraToModelNormalized = viewInverse.v.dir; + } + else + { + cameraToModelNormalized = Normalized(gContext.mModel.v.position - gContext.mCameraEye); + } + + cameraToModelNormalized.TransformVector(gContext.mModelInverse); + + gContext.mRadiusSquareCenter = screenRotateSize * gContext.mHeight; + + bool hasRSC = Intersects(op, ROTATE_SCREEN); + int circleMul = hasRSC ? 1 : 2; + for (int axis = 0; axis < 3; axis++) + { + if(!Intersects(op, static_cast(ROTATE_Z >> axis))) + { + continue; + } + ImVec2* circlePos = (ImVec2*) alloca(sizeof(ImVec2) * (circleMul * halfCircleSegmentCount + 1)); + + float angleStart = atan2f(cameraToModelNormalized[(4 - axis) % 3], cameraToModelNormalized[(3 - axis) % 3]) + ZPI * 0.5f; + + for (int i = 0; i < circleMul * halfCircleSegmentCount + 1; i++) + { + float ng = angleStart + circleMul * ZPI * ((float)i / (float)halfCircleSegmentCount); + vec_t axisPos = makeVect(cosf(ng), sinf(ng), 0.f); + vec_t pos = makeVect(axisPos[axis], axisPos[(axis + 1) % 3], axisPos[(axis + 2) % 3]) * gContext.mScreenFactor; + circlePos[i] = worldToPos(pos, gContext.mMVP); + } + + float radiusAxis = sqrtf((ImLengthSqr(worldToPos(gContext.mModel.v.position, gContext.mViewProjection) - circlePos[0]))); + if (radiusAxis > gContext.mRadiusSquareCenter) + { + gContext.mRadiusSquareCenter = radiusAxis; + } + + drawList->AddPolyline(circlePos, circleMul * halfCircleSegmentCount + 1, colors[3 - axis], false, 2); + } + if(hasRSC) + { + drawList->AddCircle(worldToPos(gContext.mModel.v.position, gContext.mViewProjection), gContext.mRadiusSquareCenter, colors[0], 64, 3.f); + } + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsRotateType(type)) + { + ImVec2 circlePos[halfCircleSegmentCount + 1]; + + circlePos[0] = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + for (unsigned int i = 1; i < halfCircleSegmentCount; i++) + { + float ng = gContext.mRotationAngle * ((float)(i - 1) / (float)(halfCircleSegmentCount - 1)); + matrix_t rotateVectorMatrix; + rotateVectorMatrix.RotationAxis(gContext.mTranslationPlan, ng); + vec_t pos; + pos.TransformPoint(gContext.mRotationVectorSource, rotateVectorMatrix); + pos *= gContext.mScreenFactor; + circlePos[i] = worldToPos(pos + gContext.mModel.v.position, gContext.mViewProjection); + } + drawList->AddConvexPolyFilled(circlePos, halfCircleSegmentCount, IM_COL32(0xFF, 0x80, 0x10, 0x80)); + drawList->AddPolyline(circlePos, halfCircleSegmentCount, IM_COL32(0xFF, 0x80, 0x10, 0xFF), true, 2); + + ImVec2 destinationPosOnScreen = circlePos[1]; + char tmps[512]; + ImFormatString(tmps, sizeof(tmps), rotationInfoMask[type - MT_ROTATE_X], (gContext.mRotationAngle / ZPI) * 180.f, gContext.mRotationAngle); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), IM_COL32_BLACK, tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), IM_COL32_WHITE, tmps); + } + } + + static void DrawHatchedAxis(const vec_t& axis) + { + for (int j = 1; j < 10; j++) + { + ImVec2 baseSSpace2 = worldToPos(axis * 0.05f * (float)(j * 2) * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace2 = worldToPos(axis * 0.05f * (float)(j * 2 + 1) * gContext.mScreenFactor, gContext.mMVP); + gContext.mDrawList->AddLine(baseSSpace2, worldDirSSpace2, IM_COL32(0, 0, 0, 0x80), 6.f); + } + } + + static void DrawScaleGizmo(OPERATION op, int type) + { + ImDrawList* drawList = gContext.mDrawList; + + if(!Intersects(op, SCALE)) + { + return; + } + + // colors + ImU32 colors[7]; + ComputeColors(colors, type, SCALE); + + // draw + vec_t scaleDisplay = { 1.f, 1.f, 1.f, 1.f }; + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + scaleDisplay = gContext.mScale; + } + + for (unsigned int i = 0; i < 3; i++) + { + if(!Intersects(op, static_cast(SCALE_X << i))) + { + continue; + } + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit); + + // draw axis + if (belowAxisLimit) + { + bool hasTranslateOnAxis = Contains(op, static_cast(TRANSLATE_X << i)) ; + float markerScale = hasTranslateOnAxis ? 1.4f : 1.0f; + ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpaceNoScale = worldToPos(dirAxis * markerScale * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace = worldToPos((dirAxis * markerScale * scaleDisplay[i]) * gContext.mScreenFactor, gContext.mMVP); + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + drawList->AddLine(baseSSpace, worldDirSSpaceNoScale, IM_COL32(0x40, 0x40, 0x40, 0xFF), 3.f); + drawList->AddCircleFilled(worldDirSSpaceNoScale, 6.f, IM_COL32(0x40, 0x40, 0x40, 0xFF)); + } + + if(!hasTranslateOnAxis || gContext.mbUsing) + { + drawList->AddLine(baseSSpace, worldDirSSpace, colors[i + 1], 3.f); + } + drawList->AddCircleFilled(worldDirSSpace, 6.f, colors[i + 1]); + + if (gContext.mAxisFactor[i] < 0.f) + { + DrawHatchedAxis(dirAxis * scaleDisplay[i]); + } + } + } + + // draw screen cirle + drawList->AddCircleFilled(gContext.mScreenSquareCenter, 6.f, colors[0], 32); + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsScaleType(type)) + { + //ImVec2 sourcePosOnScreen = worldToPos(gContext.mMatrixOrigin, gContext.mViewProjection); + ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + /*vec_t dif(destinationPosOnScreen.x - sourcePosOnScreen.x, destinationPosOnScreen.y - sourcePosOnScreen.y); + dif.Normalize(); + dif *= 5.f; + drawList->AddCircle(sourcePosOnScreen, 6.f, translationLineColor); + drawList->AddCircle(destinationPosOnScreen, 6.f, translationLineColor); + drawList->AddLine(ImVec2(sourcePosOnScreen.x + dif.x, sourcePosOnScreen.y + dif.y), ImVec2(destinationPosOnScreen.x - dif.x, destinationPosOnScreen.y - dif.y), translationLineColor, 2.f); + */ + char tmps[512]; + //vec_t deltaInfo = gContext.mModel.v.position - gContext.mMatrixOrigin; + int componentInfoIndex = (type - MT_SCALE_X) * 3; + ImFormatString(tmps, sizeof(tmps), scaleInfoMask[type - MT_SCALE_X], scaleDisplay[translationInfoIndex[componentInfoIndex]]); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), IM_COL32_BLACK, tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), IM_COL32_WHITE, tmps); + } + } + + + static void DrawTranslationGizmo(OPERATION op, int type) + { + ImDrawList* drawList = gContext.mDrawList; + if (!drawList) + { + return; + } + + if(!Intersects(op, TRANSLATE)) + { + return; + } + + // colors + ImU32 colors[7]; + ComputeColors(colors, type, TRANSLATE); + + const ImVec2 origin = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + + // draw + bool belowAxisLimit = false; + bool belowPlaneLimit = false; + for (unsigned int i = 0; i < 3; ++i) + { + vec_t dirPlaneX, dirPlaneY, dirAxis; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit); + + // draw axis + if (belowAxisLimit && Intersects(op, static_cast(TRANSLATE_X << i))) + { + ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace = worldToPos(dirAxis * gContext.mScreenFactor, gContext.mMVP); + + drawList->AddLine(baseSSpace, worldDirSSpace, colors[i + 1], 3.f); + + // Arrow head begin + ImVec2 dir(origin - worldDirSSpace); + + float d = sqrtf(ImLengthSqr(dir)); + dir /= d; // Normalize + dir *= 6.0f; + + ImVec2 ortogonalDir(dir.y, -dir.x); // Perpendicular vector + ImVec2 a(worldDirSSpace + dir); + drawList->AddTriangleFilled(worldDirSSpace - dir, a + ortogonalDir, a - ortogonalDir, colors[i + 1]); + // Arrow head end + + if (gContext.mAxisFactor[i] < 0.f) + { + DrawHatchedAxis(dirAxis); + } + } + + // draw plane + if (belowPlaneLimit && Contains(op, TRANSLATE_PLANS[i])) + { + ImVec2 screenQuadPts[4]; + for (int j = 0; j < 4; ++j) + { + vec_t cornerWorldPos = (dirPlaneX * quadUV[j * 2] + dirPlaneY * quadUV[j * 2 + 1]) * gContext.mScreenFactor; + screenQuadPts[j] = worldToPos(cornerWorldPos, gContext.mMVP); + } + drawList->AddPolyline(screenQuadPts, 4, directionColor[i], true, 1.0f); + drawList->AddConvexPolyFilled(screenQuadPts, 4, colors[i + 4]); + } + } + + drawList->AddCircleFilled(gContext.mScreenSquareCenter, 6.f, colors[0], 32); + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsTranslateType(type)) + { + ImVec2 sourcePosOnScreen = worldToPos(gContext.mMatrixOrigin, gContext.mViewProjection); + ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + vec_t dif = { destinationPosOnScreen.x - sourcePosOnScreen.x, destinationPosOnScreen.y - sourcePosOnScreen.y, 0.f, 0.f }; + dif.Normalize(); + dif *= 5.f; + drawList->AddCircle(sourcePosOnScreen, 6.f, translationLineColor); + drawList->AddCircle(destinationPosOnScreen, 6.f, translationLineColor); + drawList->AddLine(ImVec2(sourcePosOnScreen.x + dif.x, sourcePosOnScreen.y + dif.y), ImVec2(destinationPosOnScreen.x - dif.x, destinationPosOnScreen.y - dif.y), translationLineColor, 2.f); + + char tmps[512]; + vec_t deltaInfo = gContext.mModel.v.position - gContext.mMatrixOrigin; + int componentInfoIndex = (type - MT_MOVE_X) * 3; + ImFormatString(tmps, sizeof(tmps), translationInfoMask[type - MT_MOVE_X], deltaInfo[translationInfoIndex[componentInfoIndex]], deltaInfo[translationInfoIndex[componentInfoIndex + 1]], deltaInfo[translationInfoIndex[componentInfoIndex + 2]]); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), IM_COL32_BLACK, tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), IM_COL32_WHITE, tmps); + } + } + + static bool CanActivate() + { + if (ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered() && !ImGui::IsAnyItemActive()) + { + return true; + } + return false; + } + + static void HandleAndDrawLocalBounds(const float* bounds, matrix_t* matrix, const float* snapValues, OPERATION operation) + { + ImGuiIO& io = ImGui::GetIO(); + ImDrawList* drawList = gContext.mDrawList; + + // compute best projection axis + vec_t axesWorldDirections[3]; + vec_t bestAxisWorldDirection = { 0.0f, 0.0f, 0.0f, 0.0f }; + int axes[3]; + unsigned int numAxes = 1; + axes[0] = gContext.mBoundsBestAxis; + int bestAxis = axes[0]; + if (!gContext.mbUsingBounds) + { + numAxes = 0; + float bestDot = 0.f; + for (unsigned int i = 0; i < 3; i++) + { + vec_t dirPlaneNormalWorld; + dirPlaneNormalWorld.TransformVector(directionUnary[i], gContext.mModelSource); + dirPlaneNormalWorld.Normalize(); + + float dt = fabsf(Dot(Normalized(gContext.mCameraEye - gContext.mModelSource.v.position), dirPlaneNormalWorld)); + if (dt >= bestDot) + { + bestDot = dt; + bestAxis = i; + bestAxisWorldDirection = dirPlaneNormalWorld; + } + + if (dt >= 0.1f) + { + axes[numAxes] = i; + axesWorldDirections[numAxes] = dirPlaneNormalWorld; + ++numAxes; + } + } + } + + if (numAxes == 0) + { + axes[0] = bestAxis; + axesWorldDirections[0] = bestAxisWorldDirection; + numAxes = 1; + } + + else if (bestAxis != axes[0]) + { + unsigned int bestIndex = 0; + for (unsigned int i = 0; i < numAxes; i++) + { + if (axes[i] == bestAxis) + { + bestIndex = i; + break; + } + } + int tempAxis = axes[0]; + axes[0] = axes[bestIndex]; + axes[bestIndex] = tempAxis; + vec_t tempDirection = axesWorldDirections[0]; + axesWorldDirections[0] = axesWorldDirections[bestIndex]; + axesWorldDirections[bestIndex] = tempDirection; + } + + for (unsigned int axisIndex = 0; axisIndex < numAxes; ++axisIndex) + { + bestAxis = axes[axisIndex]; + bestAxisWorldDirection = axesWorldDirections[axisIndex]; + + // corners + vec_t aabb[4]; + + int secondAxis = (bestAxis + 1) % 3; + int thirdAxis = (bestAxis + 2) % 3; + + for (int i = 0; i < 4; i++) + { + aabb[i][3] = aabb[i][bestAxis] = 0.f; + aabb[i][secondAxis] = bounds[secondAxis + 3 * (i >> 1)]; + aabb[i][thirdAxis] = bounds[thirdAxis + 3 * ((i >> 1) ^ (i & 1))]; + } + + // draw bounds + unsigned int anchorAlpha = gContext.mbEnable ? IM_COL32_BLACK : IM_COL32(0, 0, 0, 0x80); + + matrix_t boundsMVP = gContext.mModelSource * gContext.mViewProjection; + for (int i = 0; i < 4; i++) + { + ImVec2 worldBound1 = worldToPos(aabb[i], boundsMVP); + ImVec2 worldBound2 = worldToPos(aabb[(i + 1) % 4], boundsMVP); + if (!IsInContextRect(worldBound1) || !IsInContextRect(worldBound2)) + { + continue; + } + float boundDistance = sqrtf(ImLengthSqr(worldBound1 - worldBound2)); + int stepCount = (int)(boundDistance / 10.f); + stepCount = min(stepCount, 1000); + float stepLength = 1.f / (float)stepCount; + for (int j = 0; j < stepCount; j++) + { + float t1 = (float)j * stepLength; + float t2 = (float)j * stepLength + stepLength * 0.5f; + ImVec2 worldBoundSS1 = ImLerp(worldBound1, worldBound2, ImVec2(t1, t1)); + ImVec2 worldBoundSS2 = ImLerp(worldBound1, worldBound2, ImVec2(t2, t2)); + //drawList->AddLine(worldBoundSS1, worldBoundSS2, IM_COL32(0, 0, 0, 0) + anchorAlpha, 3.f); + drawList->AddLine(worldBoundSS1, worldBoundSS2, IM_COL32(0xAA, 0xAA, 0xAA, 0) + anchorAlpha, 2.f); + } + vec_t midPoint = (aabb[i] + aabb[(i + 1) % 4]) * 0.5f; + ImVec2 midBound = worldToPos(midPoint, boundsMVP); + static const float AnchorBigRadius = 8.f; + static const float AnchorSmallRadius = 6.f; + bool overBigAnchor = ImLengthSqr(worldBound1 - io.MousePos) <= (AnchorBigRadius * AnchorBigRadius); + bool overSmallAnchor = ImLengthSqr(midBound - io.MousePos) <= (AnchorBigRadius * AnchorBigRadius); + + int type = MT_NONE; + vec_t gizmoHitProportion; + + if(Intersects(operation, TRANSLATE)) + { + type = GetMoveType(operation, &gizmoHitProportion); + } + if(Intersects(operation, ROTATE) && type == MT_NONE) + { + type = GetRotateType(operation); + } + if(Intersects(operation, SCALE) && type == MT_NONE) + { + type = GetScaleType(operation); + } + + if (type != MT_NONE) + { + overBigAnchor = false; + overSmallAnchor = false; + } + + unsigned int bigAnchorColor = overBigAnchor ? selectionColor : (IM_COL32(0xAA, 0xAA, 0xAA, 0) + anchorAlpha); + unsigned int smallAnchorColor = overSmallAnchor ? selectionColor : (IM_COL32(0xAA, 0xAA, 0xAA, 0) + anchorAlpha); + + drawList->AddCircleFilled(worldBound1, AnchorBigRadius, IM_COL32_BLACK); + drawList->AddCircleFilled(worldBound1, AnchorBigRadius - 1.2f, bigAnchorColor); + + drawList->AddCircleFilled(midBound, AnchorSmallRadius, IM_COL32_BLACK); + drawList->AddCircleFilled(midBound, AnchorSmallRadius - 1.2f, smallAnchorColor); + int oppositeIndex = (i + 2) % 4; + // big anchor on corners + if (!gContext.mbUsingBounds && gContext.mbEnable && overBigAnchor && CanActivate()) + { + gContext.mBoundsPivot.TransformPoint(aabb[(i + 2) % 4], gContext.mModelSource); + gContext.mBoundsAnchor.TransformPoint(aabb[i], gContext.mModelSource); + gContext.mBoundsPlan = BuildPlan(gContext.mBoundsAnchor, bestAxisWorldDirection); + gContext.mBoundsBestAxis = bestAxis; + gContext.mBoundsAxis[0] = secondAxis; + gContext.mBoundsAxis[1] = thirdAxis; + + gContext.mBoundsLocalPivot.Set(0.f); + gContext.mBoundsLocalPivot[secondAxis] = aabb[oppositeIndex][secondAxis]; + gContext.mBoundsLocalPivot[thirdAxis] = aabb[oppositeIndex][thirdAxis]; + + gContext.mbUsingBounds = true; + gContext.mEditingID = gContext.mActualID; + gContext.mBoundsMatrix = gContext.mModelSource; + } + // small anchor on middle of segment + if (!gContext.mbUsingBounds && gContext.mbEnable && overSmallAnchor && CanActivate()) + { + vec_t midPointOpposite = (aabb[(i + 2) % 4] + aabb[(i + 3) % 4]) * 0.5f; + gContext.mBoundsPivot.TransformPoint(midPointOpposite, gContext.mModelSource); + gContext.mBoundsAnchor.TransformPoint(midPoint, gContext.mModelSource); + gContext.mBoundsPlan = BuildPlan(gContext.mBoundsAnchor, bestAxisWorldDirection); + gContext.mBoundsBestAxis = bestAxis; + int indices[] = { secondAxis , thirdAxis }; + gContext.mBoundsAxis[0] = indices[i % 2]; + gContext.mBoundsAxis[1] = -1; + + gContext.mBoundsLocalPivot.Set(0.f); + gContext.mBoundsLocalPivot[gContext.mBoundsAxis[0]] = aabb[oppositeIndex][indices[i % 2]];// bounds[gContext.mBoundsAxis[0]] * (((i + 1) & 2) ? 1.f : -1.f); + + gContext.mbUsingBounds = true; + gContext.mEditingID = gContext.mActualID; + gContext.mBoundsMatrix = gContext.mModelSource; + } + } + + if (gContext.mbUsingBounds && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + matrix_t scale; + scale.SetToIdentity(); + + // compute projected mouse position on plan + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mBoundsPlan); + vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len; + + // compute a reference and delta vectors base on mouse move + vec_t deltaVector = (newPos - gContext.mBoundsPivot).Abs(); + vec_t referenceVector = (gContext.mBoundsAnchor - gContext.mBoundsPivot).Abs(); + + // for 1 or 2 axes, compute a ratio that's used for scale and snap it based on resulting length + for (int i = 0; i < 2; i++) + { + int axisIndex1 = gContext.mBoundsAxis[i]; + if (axisIndex1 == -1) + { + continue; + } + + float ratioAxis = 1.f; + vec_t axisDir = gContext.mBoundsMatrix.component[axisIndex1].Abs(); + + float dtAxis = axisDir.Dot(referenceVector); + float boundSize = bounds[axisIndex1 + 3] - bounds[axisIndex1]; + if (dtAxis > FLT_EPSILON) + { + ratioAxis = axisDir.Dot(deltaVector) / dtAxis; + } + + if (snapValues) + { + float length = boundSize * ratioAxis; + ComputeSnap(&length, snapValues[axisIndex1]); + if (boundSize > FLT_EPSILON) + { + ratioAxis = length / boundSize; + } + } + scale.component[axisIndex1] *= ratioAxis; + } + + // transform matrix + matrix_t preScale, postScale; + preScale.Translation(-gContext.mBoundsLocalPivot); + postScale.Translation(gContext.mBoundsLocalPivot); + matrix_t res = preScale * scale * postScale * gContext.mBoundsMatrix; + *matrix = res; + + // info text + char tmps[512]; + ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + ImFormatString(tmps, sizeof(tmps), "X: %.2f Y: %.2f Z:%.2f" + , (bounds[3] - bounds[0]) * gContext.mBoundsMatrix.component[0].Length() * scale.component[0].Length() + , (bounds[4] - bounds[1]) * gContext.mBoundsMatrix.component[1].Length() * scale.component[1].Length() + , (bounds[5] - bounds[2]) * gContext.mBoundsMatrix.component[2].Length() * scale.component[2].Length() + ); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), IM_COL32_BLACK, tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), IM_COL32_WHITE, tmps); + } + + if (!io.MouseDown[0]) { + gContext.mbUsingBounds = false; + gContext.mEditingID = -1; + } + if (gContext.mbUsingBounds) + { + break; + } + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // + + static int GetScaleType(OPERATION op) + { + ImGuiIO& io = ImGui::GetIO(); + int type = MT_NONE; + + // screen + if (io.MousePos.x >= gContext.mScreenSquareMin.x && io.MousePos.x <= gContext.mScreenSquareMax.x && + io.MousePos.y >= gContext.mScreenSquareMin.y && io.MousePos.y <= gContext.mScreenSquareMax.y && + Contains(op, SCALE)) + { + type = MT_SCALE_XYZ; + } + + // compute + for (unsigned int i = 0; i < 3 && type == MT_NONE; i++) + { + if(!Intersects(op, static_cast(SCALE_X << i))) + { + continue; + } + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit); + dirAxis.TransformVector(gContext.mModel); + dirPlaneX.TransformVector(gContext.mModel); + dirPlaneY.TransformVector(gContext.mModel); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, BuildPlan(gContext.mModel.v.position, dirAxis)); + vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len; + + const float startOffset = Contains(op, static_cast(TRANSLATE_X << i)) ? 1.0f : 0.1f; + const float endOffset = Contains(op, static_cast(TRANSLATE_X << i)) ? 1.4f : 1.0f; + const ImVec2 posOnPlanScreen = worldToPos(posOnPlan, gContext.mViewProjection); + const ImVec2 axisStartOnScreen = worldToPos(gContext.mModel.v.position + dirAxis * gContext.mScreenFactor * startOffset, gContext.mViewProjection); + const ImVec2 axisEndOnScreen = worldToPos(gContext.mModel.v.position + dirAxis * gContext.mScreenFactor * endOffset, gContext.mViewProjection); + + vec_t closestPointOnAxis = PointOnSegment(makeVect(posOnPlanScreen), makeVect(axisStartOnScreen), makeVect(axisEndOnScreen)); + + if ((closestPointOnAxis - makeVect(posOnPlanScreen)).Length() < 12.f) // pixel size + { + type = MT_SCALE_X + i; + } + } + return type; + } + + static int GetRotateType(OPERATION op) + { + ImGuiIO& io = ImGui::GetIO(); + int type = MT_NONE; + + vec_t deltaScreen = { io.MousePos.x - gContext.mScreenSquareCenter.x, io.MousePos.y - gContext.mScreenSquareCenter.y, 0.f, 0.f }; + float dist = deltaScreen.Length(); + if (Intersects(op, ROTATE_SCREEN) && dist >= (gContext.mRadiusSquareCenter - 1.0f) && dist < (gContext.mRadiusSquareCenter + 1.0f)) + { + type = MT_ROTATE_SCREEN; + } + + const vec_t planNormals[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir }; + + vec_t modelViewPos; + modelViewPos.TransformPoint(gContext.mModel.v.position, gContext.mViewMat); + + for (unsigned int i = 0; i < 3 && type == MT_NONE; i++) + { + if(!Intersects(op, static_cast(ROTATE_X << i))) + { + continue; + } + // pickup plan + vec_t pickupPlan = BuildPlan(gContext.mModel.v.position, planNormals[i]); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, pickupPlan); + const vec_t intersectWorldPos = gContext.mRayOrigin + gContext.mRayVector * len; + vec_t intersectViewPos; + intersectViewPos.TransformPoint(intersectWorldPos, gContext.mViewMat); + + if (ImAbs(modelViewPos.z) - ImAbs(intersectViewPos.z) < -FLT_EPSILON) + { + continue; + } + + const vec_t localPos = intersectWorldPos - gContext.mModel.v.position; + vec_t idealPosOnCircle = Normalized(localPos); + idealPosOnCircle.TransformVector(gContext.mModelInverse); + const ImVec2 idealPosOnCircleScreen = worldToPos(idealPosOnCircle * gContext.mScreenFactor, gContext.mMVP); + + //gContext.mDrawList->AddCircle(idealPosOnCircleScreen, 5.f, IM_COL32_WHITE); + const ImVec2 distanceOnScreen = idealPosOnCircleScreen - io.MousePos; + + const float distance = makeVect(distanceOnScreen).Length(); + if (distance < 8.f) // pixel size + { + type = MT_ROTATE_X + i; + } + } + + return type; + } + + static int GetMoveType(OPERATION op, vec_t* gizmoHitProportion) + { + if(!Intersects(op, TRANSLATE)) + { + return MT_NONE; + } + ImGuiIO& io = ImGui::GetIO(); + int type = MT_NONE; + + // screen + if (io.MousePos.x >= gContext.mScreenSquareMin.x && io.MousePos.x <= gContext.mScreenSquareMax.x && + io.MousePos.y >= gContext.mScreenSquareMin.y && io.MousePos.y <= gContext.mScreenSquareMax.y && + Contains(op, TRANSLATE)) + { + type = MT_MOVE_SCREEN; + } + + const vec_t screenCoord = makeVect(io.MousePos - ImVec2(gContext.mX, gContext.mY)); + + // compute + for (unsigned int i = 0; i < 3 && type == MT_NONE; i++) + { + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit); + dirAxis.TransformVector(gContext.mModel); + dirPlaneX.TransformVector(gContext.mModel); + dirPlaneY.TransformVector(gContext.mModel); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, BuildPlan(gContext.mModel.v.position, dirAxis)); + vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len; + + const ImVec2 axisStartOnScreen = worldToPos(gContext.mModel.v.position + dirAxis * gContext.mScreenFactor * 0.1f, gContext.mViewProjection) - ImVec2(gContext.mX, gContext.mY); + const ImVec2 axisEndOnScreen = worldToPos(gContext.mModel.v.position + dirAxis * gContext.mScreenFactor, gContext.mViewProjection) - ImVec2(gContext.mX, gContext.mY); + + vec_t closestPointOnAxis = PointOnSegment(screenCoord, makeVect(axisStartOnScreen), makeVect(axisEndOnScreen)); + if ((closestPointOnAxis - screenCoord).Length() < 12.f && Intersects(op, static_cast(TRANSLATE_X << i))) // pixel size + { + type = MT_MOVE_X + i; + } + + const float dx = dirPlaneX.Dot3((posOnPlan - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor)); + const float dy = dirPlaneY.Dot3((posOnPlan - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor)); + if (belowPlaneLimit && dx >= quadUV[0] && dx <= quadUV[4] && dy >= quadUV[1] && dy <= quadUV[3] && Contains(op, TRANSLATE_PLANS[i])) + { + type = MT_MOVE_YZ + i; + } + + if (gizmoHitProportion) + { + *gizmoHitProportion = makeVect(dx, dy, 0.f); + } + } + return type; + } + + static bool HandleTranslation(float* matrix, float* deltaMatrix, OPERATION op, int& type, const float* snap) + { + if(!Intersects(op, TRANSLATE) || type != MT_NONE) + { + return false; + } + ImGuiIO& io = ImGui::GetIO(); + bool applyRotationLocaly = gContext.mMode == LOCAL || type == MT_MOVE_SCREEN; + bool modified = false; + + // move + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsTranslateType(gContext.mCurrentOperation)) + { + ImGui::CaptureMouseFromApp(); + const float len = fabsf(IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan)); // near plan + vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len; + + // compute delta + vec_t newOrigin = newPos - gContext.mRelativeOrigin * gContext.mScreenFactor; + vec_t delta = newOrigin - gContext.mModel.v.position; + + // 1 axis constraint + if (gContext.mCurrentOperation >= MT_MOVE_X && gContext.mCurrentOperation <= MT_MOVE_Z) + { + int axisIndex = gContext.mCurrentOperation - MT_MOVE_X; + const vec_t& axisValue = *(vec_t*)&gContext.mModel.m[axisIndex]; + float lengthOnAxis = Dot(axisValue, delta); + delta = axisValue * lengthOnAxis; + } + + // snap + if (snap) + { + vec_t cumulativeDelta = gContext.mModel.v.position + delta - gContext.mMatrixOrigin; + if (applyRotationLocaly) + { + matrix_t modelSourceNormalized = gContext.mModelSource; + modelSourceNormalized.OrthoNormalize(); + matrix_t modelSourceNormalizedInverse; + modelSourceNormalizedInverse.Inverse(modelSourceNormalized); + cumulativeDelta.TransformVector(modelSourceNormalizedInverse); + ComputeSnap(cumulativeDelta, snap); + cumulativeDelta.TransformVector(modelSourceNormalized); + } + else + { + ComputeSnap(cumulativeDelta, snap); + } + delta = gContext.mMatrixOrigin + cumulativeDelta - gContext.mModel.v.position; + + } + + if (delta != gContext.mTranslationLastDelta) + { + modified = true; + } + gContext.mTranslationLastDelta = delta; + + // compute matrix & delta + matrix_t deltaMatrixTranslation; + deltaMatrixTranslation.Translation(delta); + if (deltaMatrix) + { + memcpy(deltaMatrix, deltaMatrixTranslation.m16, sizeof(float) * 16); + } + + matrix_t res = gContext.mModelSource * deltaMatrixTranslation; + *(matrix_t*)matrix = res; + + if (!io.MouseDown[0]) + { + gContext.mbUsing = false; + } + + type = gContext.mCurrentOperation; + } + else + { + // find new possible way to move + vec_t gizmoHitProportion; + type = GetMoveType(op, &gizmoHitProportion); + if (type != MT_NONE) + { + ImGui::CaptureMouseFromApp(); + } + if (CanActivate() && type != MT_NONE) + { + gContext.mbUsing = true; + gContext.mEditingID = gContext.mActualID; + gContext.mCurrentOperation = type; + vec_t movePlanNormal[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir, + gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir, + -gContext.mCameraDir }; + + vec_t cameraToModelNormalized = Normalized(gContext.mModel.v.position - gContext.mCameraEye); + for (unsigned int i = 0; i < 3; i++) + { + vec_t orthoVector = Cross(movePlanNormal[i], cameraToModelNormalized); + movePlanNormal[i].Cross(orthoVector); + movePlanNormal[i].Normalize(); + } + // pickup plan + gContext.mTranslationPlan = BuildPlan(gContext.mModel.v.position, movePlanNormal[type - MT_MOVE_X]); + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + gContext.mTranslationPlanOrigin = gContext.mRayOrigin + gContext.mRayVector * len; + gContext.mMatrixOrigin = gContext.mModel.v.position; + + gContext.mRelativeOrigin = (gContext.mTranslationPlanOrigin - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor); + } + } + return modified; + } + + static bool HandleScale(float* matrix, float* deltaMatrix, OPERATION op, int& type, const float* snap) + { + if(!Intersects(op, SCALE) || type != MT_NONE) + { + return false; + } + ImGuiIO& io = ImGui::GetIO(); + bool modified = false; + + if (!gContext.mbUsing) + { + // find new possible way to scale + type = GetScaleType(op); + if (type != MT_NONE) + { + ImGui::CaptureMouseFromApp(); + } + if (CanActivate() && type != MT_NONE) + { + gContext.mbUsing = true; + gContext.mEditingID = gContext.mActualID; + gContext.mCurrentOperation = type; + const vec_t movePlanNormal[] = { gContext.mModel.v.up, gContext.mModel.v.dir, gContext.mModel.v.right, gContext.mModel.v.dir, gContext.mModel.v.up, gContext.mModel.v.right, -gContext.mCameraDir }; + // pickup plan + + gContext.mTranslationPlan = BuildPlan(gContext.mModel.v.position, movePlanNormal[type - MT_SCALE_X]); + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + gContext.mTranslationPlanOrigin = gContext.mRayOrigin + gContext.mRayVector * len; + gContext.mMatrixOrigin = gContext.mModel.v.position; + gContext.mScale.Set(1.f, 1.f, 1.f); + gContext.mRelativeOrigin = (gContext.mTranslationPlanOrigin - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor); + gContext.mScaleValueOrigin = makeVect(gContext.mModelSource.v.right.Length(), gContext.mModelSource.v.up.Length(), gContext.mModelSource.v.dir.Length()); + gContext.mSaveMousePosx = io.MousePos.x; + } + } + // scale + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsScaleType(gContext.mCurrentOperation)) + { + ImGui::CaptureMouseFromApp(); + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len; + vec_t newOrigin = newPos - gContext.mRelativeOrigin * gContext.mScreenFactor; + vec_t delta = newOrigin - gContext.mModel.v.position; + + // 1 axis constraint + if (gContext.mCurrentOperation >= MT_SCALE_X && gContext.mCurrentOperation <= MT_SCALE_Z) + { + int axisIndex = gContext.mCurrentOperation - MT_SCALE_X; + const vec_t& axisValue = *(vec_t*)&gContext.mModel.m[axisIndex]; + float lengthOnAxis = Dot(axisValue, delta); + delta = axisValue * lengthOnAxis; + + vec_t baseVector = gContext.mTranslationPlanOrigin - gContext.mModel.v.position; + float ratio = Dot(axisValue, baseVector + delta) / Dot(axisValue, baseVector); + + gContext.mScale[axisIndex] = max(ratio, 0.001f); + } + else + { + float scaleDelta = (io.MousePos.x - gContext.mSaveMousePosx) * 0.01f; + gContext.mScale.Set(max(1.f + scaleDelta, 0.001f)); + } + + // snap + if (snap) + { + float scaleSnap[] = { snap[0], snap[0], snap[0] }; + ComputeSnap(gContext.mScale, scaleSnap); + } + + // no 0 allowed + for (int i = 0; i < 3; i++) + gContext.mScale[i] = max(gContext.mScale[i], 0.001f); + + if (gContext.mScaleLast != gContext.mScale) + { + modified = true; + } + gContext.mScaleLast = gContext.mScale; + + // compute matrix & delta + matrix_t deltaMatrixScale; + deltaMatrixScale.Scale(gContext.mScale * gContext.mScaleValueOrigin); + + matrix_t res = deltaMatrixScale * gContext.mModel; + *(matrix_t*)matrix = res; + + if (deltaMatrix) + { + vec_t deltaScale = gContext.mScale * gContext.mScaleValueOrigin; + + vec_t originalScaleDivider; + originalScaleDivider.x = 1 / gContext.mModelScaleOrigin.x; + originalScaleDivider.y = 1 / gContext.mModelScaleOrigin.y; + originalScaleDivider.z = 1 / gContext.mModelScaleOrigin.z; + + deltaScale = deltaScale * originalScaleDivider; + + deltaMatrixScale.Scale(deltaScale); + memcpy(deltaMatrix, deltaMatrixScale.m16, sizeof(float) * 16); + } + + if (!io.MouseDown[0]) + { + gContext.mbUsing = false; + gContext.mScale.Set(1.f, 1.f, 1.f); + } + + type = gContext.mCurrentOperation; + } + return modified; + } + + static bool HandleRotation(float* matrix, float* deltaMatrix, OPERATION op, int& type, const float* snap) + { + if(!Intersects(op, ROTATE) || type != MT_NONE) + { + return false; + } + ImGuiIO& io = ImGui::GetIO(); + bool applyRotationLocaly = gContext.mMode == LOCAL; + bool modified = false; + + if (!gContext.mbUsing) + { + type = GetRotateType(op); + + if (type != MT_NONE) + { + ImGui::CaptureMouseFromApp(); + } + + if (type == MT_ROTATE_SCREEN) + { + applyRotationLocaly = true; + } + + if (CanActivate() && type != MT_NONE) + { + gContext.mbUsing = true; + gContext.mEditingID = gContext.mActualID; + gContext.mCurrentOperation = type; + const vec_t rotatePlanNormal[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir, -gContext.mCameraDir }; + // pickup plan + if (applyRotationLocaly) + { + gContext.mTranslationPlan = BuildPlan(gContext.mModel.v.position, rotatePlanNormal[type - MT_ROTATE_X]); + } + else + { + gContext.mTranslationPlan = BuildPlan(gContext.mModelSource.v.position, directionUnary[type - MT_ROTATE_X]); + } + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + vec_t localPos = gContext.mRayOrigin + gContext.mRayVector * len - gContext.mModel.v.position; + gContext.mRotationVectorSource = Normalized(localPos); + gContext.mRotationAngleOrigin = ComputeAngleOnPlan(); + } + } + + // rotation + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsRotateType(gContext.mCurrentOperation)) + { + ImGui::CaptureMouseFromApp(); + gContext.mRotationAngle = ComputeAngleOnPlan(); + if (snap) + { + float snapInRadian = snap[0] * DEG2RAD; + ComputeSnap(&gContext.mRotationAngle, snapInRadian); + } + vec_t rotationAxisLocalSpace; + + rotationAxisLocalSpace.TransformVector(makeVect(gContext.mTranslationPlan.x, gContext.mTranslationPlan.y, gContext.mTranslationPlan.z, 0.f), gContext.mModelInverse); + rotationAxisLocalSpace.Normalize(); + + matrix_t deltaRotation; + deltaRotation.RotationAxis(rotationAxisLocalSpace, gContext.mRotationAngle - gContext.mRotationAngleOrigin); + if (gContext.mRotationAngle != gContext.mRotationAngleOrigin) + { + modified = true; + } + gContext.mRotationAngleOrigin = gContext.mRotationAngle; + + matrix_t scaleOrigin; + scaleOrigin.Scale(gContext.mModelScaleOrigin); + + if (applyRotationLocaly) + { + *(matrix_t*)matrix = scaleOrigin * deltaRotation * gContext.mModel; + } + else + { + matrix_t res = gContext.mModelSource; + res.v.position.Set(0.f); + + *(matrix_t*)matrix = res * deltaRotation; + ((matrix_t*)matrix)->v.position = gContext.mModelSource.v.position; + } + + if (deltaMatrix) + { + *(matrix_t*)deltaMatrix = gContext.mModelInverse * deltaRotation * gContext.mModel; + } + + if (!io.MouseDown[0]) + { + gContext.mbUsing = false; + gContext.mEditingID = -1; + } + type = gContext.mCurrentOperation; + } + return modified; + } + + void DecomposeMatrixToComponents(const float* matrix, float* translation, float* rotation, float* scale) + { + matrix_t mat = *(matrix_t*)matrix; + + scale[0] = mat.v.right.Length(); + scale[1] = mat.v.up.Length(); + scale[2] = mat.v.dir.Length(); + + mat.OrthoNormalize(); + + rotation[0] = RAD2DEG * atan2f(mat.m[1][2], mat.m[2][2]); + rotation[1] = RAD2DEG * atan2f(-mat.m[0][2], sqrtf(mat.m[1][2] * mat.m[1][2] + mat.m[2][2] * mat.m[2][2])); + rotation[2] = RAD2DEG * atan2f(mat.m[0][1], mat.m[0][0]); + + translation[0] = mat.v.position.x; + translation[1] = mat.v.position.y; + translation[2] = mat.v.position.z; + } + + void RecomposeMatrixFromComponents(const float* translation, const float* rotation, const float* scale, float* matrix) + { + matrix_t& mat = *(matrix_t*)matrix; + + matrix_t rot[3]; + for (int i = 0; i < 3; i++) + { + rot[i].RotationAxis(directionUnary[i], rotation[i] * DEG2RAD); + } + + mat = rot[0] * rot[1] * rot[2]; + + float validScale[3]; + for (int i = 0; i < 3; i++) + { + if (fabsf(scale[i]) < FLT_EPSILON) + { + validScale[i] = 0.001f; + } + else + { + validScale[i] = scale[i]; + } + } + mat.v.right *= validScale[0]; + mat.v.up *= validScale[1]; + mat.v.dir *= validScale[2]; + mat.v.position.Set(translation[0], translation[1], translation[2], 1.f); + } + + void SetID(int id) + { + gContext.mActualID = id; + } + + void AllowAxisFlip(bool value) + { + gContext.mAllowAxisFlip = value; + } + + bool Manipulate(const float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float* deltaMatrix, const float* snap, const float* localBounds, const float* boundsSnap) + { + ComputeContext(view, projection, matrix, mode); + + // set delta to identity + if (deltaMatrix) + { + ((matrix_t*)deltaMatrix)->SetToIdentity(); + } + + // behind camera + vec_t camSpacePosition; + camSpacePosition.TransformPoint(makeVect(0.f, 0.f, 0.f), gContext.mMVP); + if (!gContext.mIsOrthographic && camSpacePosition.z < 0.001f) + { + return false; + } + + // -- + int type = MT_NONE; + bool manipulated = false; + if (gContext.mbEnable) + { + if (!gContext.mbUsingBounds) + { + manipulated = HandleTranslation(matrix, deltaMatrix, operation, type, snap) || + HandleScale(matrix, deltaMatrix, operation, type, snap) || + HandleRotation(matrix, deltaMatrix, operation, type, snap); + } + } + + if (localBounds && !gContext.mbUsing) + { + HandleAndDrawLocalBounds(localBounds, (matrix_t*)matrix, boundsSnap, operation); + } + + gContext.mOperation = operation; + if (!gContext.mbUsingBounds) + { + DrawRotationGizmo(operation, type); + DrawTranslationGizmo(operation, type); + DrawScaleGizmo(operation, type); + } + return manipulated; + } + + void SetGizmoSizeClipSpace(float value) + { + gContext.mGizmoSizeClipSpace = value; + } + + /////////////////////////////////////////////////////////////////////////////////////////////////// + void ComputeFrustumPlanes(vec_t* frustum, const float* clip) + { + frustum[0].x = clip[3] - clip[0]; + frustum[0].y = clip[7] - clip[4]; + frustum[0].z = clip[11] - clip[8]; + frustum[0].w = clip[15] - clip[12]; + + frustum[1].x = clip[3] + clip[0]; + frustum[1].y = clip[7] + clip[4]; + frustum[1].z = clip[11] + clip[8]; + frustum[1].w = clip[15] + clip[12]; + + frustum[2].x = clip[3] + clip[1]; + frustum[2].y = clip[7] + clip[5]; + frustum[2].z = clip[11] + clip[9]; + frustum[2].w = clip[15] + clip[13]; + + frustum[3].x = clip[3] - clip[1]; + frustum[3].y = clip[7] - clip[5]; + frustum[3].z = clip[11] - clip[9]; + frustum[3].w = clip[15] - clip[13]; + + frustum[4].x = clip[3] - clip[2]; + frustum[4].y = clip[7] - clip[6]; + frustum[4].z = clip[11] - clip[10]; + frustum[4].w = clip[15] - clip[14]; + + frustum[5].x = clip[3] + clip[2]; + frustum[5].y = clip[7] + clip[6]; + frustum[5].z = clip[11] + clip[10]; + frustum[5].w = clip[15] + clip[14]; + + for (int i = 0; i < 6; i++) + { + frustum[i].Normalize(); + } + } + + void DrawCubes(const float* view, const float* projection, const float* matrices, int matrixCount) + { + matrix_t viewInverse; + viewInverse.Inverse(*(matrix_t*)view); + + struct CubeFace + { + float z; + ImVec2 faceCoordsScreen[4]; + ImU32 color; + }; + CubeFace* faces = (CubeFace*)_malloca(sizeof(CubeFace) * matrixCount * 6); + + if (!faces) + { + return; + } + + vec_t frustum[6]; + matrix_t viewProjection = *(matrix_t*)view * *(matrix_t*)projection; + ComputeFrustumPlanes(frustum, viewProjection.m16); + + int cubeFaceCount = 0; + for (int cube = 0; cube < matrixCount; cube++) + { + const float* matrix = &matrices[cube * 16]; + + matrix_t res = *(matrix_t*)matrix * *(matrix_t*)view * *(matrix_t*)projection; + + for (int iFace = 0; iFace < 6; iFace++) + { + const int normalIndex = (iFace % 3); + const int perpXIndex = (normalIndex + 1) % 3; + const int perpYIndex = (normalIndex + 2) % 3; + const float invert = (iFace > 2) ? -1.f : 1.f; + + const vec_t faceCoords[4] = { directionUnary[normalIndex] + directionUnary[perpXIndex] + directionUnary[perpYIndex], + directionUnary[normalIndex] + directionUnary[perpXIndex] - directionUnary[perpYIndex], + directionUnary[normalIndex] - directionUnary[perpXIndex] - directionUnary[perpYIndex], + directionUnary[normalIndex] - directionUnary[perpXIndex] + directionUnary[perpYIndex], + }; + + // clipping + /* + bool skipFace = false; + for (unsigned int iCoord = 0; iCoord < 4; iCoord++) + { + vec_t camSpacePosition; + camSpacePosition.TransformPoint(faceCoords[iCoord] * 0.5f * invert, res); + if (camSpacePosition.z < 0.001f) + { + skipFace = true; + break; + } + } + if (skipFace) + { + continue; + } + */ + vec_t centerPosition, centerPositionVP; + centerPosition.TransformPoint(directionUnary[normalIndex] * 0.5f * invert, *(matrix_t*)matrix); + centerPositionVP.TransformPoint(directionUnary[normalIndex] * 0.5f * invert, res); + + bool inFrustum = true; + for (int iFrustum = 0; iFrustum < 6; iFrustum++) + { + float dist = DistanceToPlane(centerPosition, frustum[iFrustum]); + if (dist < 0.f) + { + inFrustum = false; + break; + } + } + + if (!inFrustum) + { + continue; + } + CubeFace& cubeFace = faces[cubeFaceCount]; + + // 3D->2D + //ImVec2 faceCoordsScreen[4]; + for (unsigned int iCoord = 0; iCoord < 4; iCoord++) + { + cubeFace.faceCoordsScreen[iCoord] = worldToPos(faceCoords[iCoord] * 0.5f * invert, res); + } + cubeFace.color = directionColor[normalIndex] | IM_COL32(0x80, 0x80, 0x80, 0); + + cubeFace.z = centerPositionVP.z / centerPositionVP.w; + cubeFaceCount++; + } + } + qsort(faces, cubeFaceCount, sizeof(CubeFace), [](void const* _a, void const* _b) { + CubeFace* a = (CubeFace*)_a; + CubeFace* b = (CubeFace*)_b; + if (a->z < b->z) + { + return 1; + } + return -1; + }); + // draw face with lighter color + for (int iFace = 0; iFace < cubeFaceCount; iFace++) + { + const CubeFace& cubeFace = faces[iFace]; + gContext.mDrawList->AddConvexPolyFilled(cubeFace.faceCoordsScreen, 4, cubeFace.color); + } + + _freea(faces); + } + + void DrawGrid(const float* view, const float* projection, const float* matrix, const float gridSize) + { + matrix_t viewProjection = *(matrix_t*)view * *(matrix_t*)projection; + vec_t frustum[6]; + ComputeFrustumPlanes(frustum, viewProjection.m16); + matrix_t res = *(matrix_t*)matrix * viewProjection; + + for (float f = -gridSize; f <= gridSize; f += 1.f) + { + for (int dir = 0; dir < 2; dir++) + { + vec_t ptA = makeVect(dir ? -gridSize : f, 0.f, dir ? f : -gridSize); + vec_t ptB = makeVect(dir ? gridSize : f, 0.f, dir ? f : gridSize); + bool visible = true; + for (int i = 0; i < 6; i++) + { + float dA = DistanceToPlane(ptA, frustum[i]); + float dB = DistanceToPlane(ptB, frustum[i]); + if (dA < 0.f && dB < 0.f) + { + visible = false; + break; + } + if (dA > 0.f && dB > 0.f) + { + continue; + } + if (dA < 0.f) + { + float len = fabsf(dA - dB); + float t = fabsf(dA) / len; + ptA.Lerp(ptB, t); + } + if (dB < 0.f) + { + float len = fabsf(dB - dA); + float t = fabsf(dB) / len; + ptB.Lerp(ptA, t); + } + } + if (visible) + { + ImU32 col = IM_COL32(0x80, 0x80, 0x80, 0xFF); + col = (fmodf(fabsf(f), 10.f) < FLT_EPSILON) ? IM_COL32(0x90, 0x90, 0x90, 0xFF) : col; + col = (fabsf(f) < FLT_EPSILON) ? IM_COL32(0x40, 0x40, 0x40, 0xFF): col; + + float thickness = 1.f; + thickness = (fmodf(fabsf(f), 10.f) < FLT_EPSILON) ? 1.5f : thickness; + thickness = (fabsf(f) < FLT_EPSILON) ? 2.3f : thickness; + + gContext.mDrawList->AddLine(worldToPos(ptA, res), worldToPos(ptB, res), col, thickness); + } + } + } + } + + void ViewManipulate(float* view, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor) + { + static bool isDraging = false; + static bool isClicking = false; + static bool isInside = false; + static vec_t interpolationUp; + static vec_t interpolationDir; + static int interpolationFrames = 0; + const vec_t referenceUp = makeVect(0.f, 1.f, 0.f); + + matrix_t svgView, svgProjection; + svgView = gContext.mViewMat; + svgProjection = gContext.mProjectionMat; + + ImGuiIO& io = ImGui::GetIO(); + gContext.mDrawList->AddRectFilled(position, position + size, backgroundColor); + matrix_t viewInverse; + viewInverse.Inverse(*(matrix_t*)view); + + const vec_t camTarget = viewInverse.v.position - viewInverse.v.dir * length; + + // view/projection matrices + const float distance = 3.f; + matrix_t cubeProjection, cubeView; + float fov = acosf(distance / (sqrtf(distance * distance + 3.f))) * RAD2DEG; + Perspective(fov / sqrtf(2.f), size.x / size.y, 0.01f, 1000.f, cubeProjection.m16); + + vec_t dir = makeVect(viewInverse.m[2][0], viewInverse.m[2][1], viewInverse.m[2][2]); + vec_t up = makeVect(viewInverse.m[1][0], viewInverse.m[1][1], viewInverse.m[1][2]); + vec_t eye = dir * distance; + vec_t zero = makeVect(0.f, 0.f); + LookAt(&eye.x, &zero.x, &up.x, cubeView.m16); + + // set context + gContext.mViewMat = cubeView; + gContext.mProjectionMat = cubeProjection; + ComputeCameraRay(gContext.mRayOrigin, gContext.mRayVector, position, size); + + const matrix_t res = cubeView * cubeProjection; + + // panels + static const ImVec2 panelPosition[9] = { ImVec2(0.75f,0.75f), ImVec2(0.25f, 0.75f), ImVec2(0.f, 0.75f), + ImVec2(0.75f, 0.25f), ImVec2(0.25f, 0.25f), ImVec2(0.f, 0.25f), + ImVec2(0.75f, 0.f), ImVec2(0.25f, 0.f), ImVec2(0.f, 0.f) }; + + static const ImVec2 panelSize[9] = { ImVec2(0.25f,0.25f), ImVec2(0.5f, 0.25f), ImVec2(0.25f, 0.25f), + ImVec2(0.25f, 0.5f), ImVec2(0.5f, 0.5f), ImVec2(0.25f, 0.5f), + ImVec2(0.25f, 0.25f), ImVec2(0.5f, 0.25f), ImVec2(0.25f, 0.25f) }; + + // tag faces + bool boxes[27]{}; + for (int iPass = 0; iPass < 2; iPass++) + { + for (int iFace = 0; iFace < 6; iFace++) + { + const int normalIndex = (iFace % 3); + const int perpXIndex = (normalIndex + 1) % 3; + const int perpYIndex = (normalIndex + 2) % 3; + const float invert = (iFace > 2) ? -1.f : 1.f; + const vec_t indexVectorX = directionUnary[perpXIndex] * invert; + const vec_t indexVectorY = directionUnary[perpYIndex] * invert; + const vec_t boxOrigin = directionUnary[normalIndex] * -invert - indexVectorX - indexVectorY; + + // plan local space + const vec_t n = directionUnary[normalIndex] * invert; + vec_t viewSpaceNormal = n; + vec_t viewSpacePoint = n * 0.5f; + viewSpaceNormal.TransformVector(cubeView); + viewSpaceNormal.Normalize(); + viewSpacePoint.TransformPoint(cubeView); + const vec_t viewSpaceFacePlan = BuildPlan(viewSpacePoint, viewSpaceNormal); + + // back face culling + if (viewSpaceFacePlan.w > 0.f) + { + continue; + } + + const vec_t facePlan = BuildPlan(n * 0.5f, n); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, facePlan); + vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len - (n * 0.5f); + + float localx = Dot(directionUnary[perpXIndex], posOnPlan) * invert + 0.5f; + float localy = Dot(directionUnary[perpYIndex], posOnPlan) * invert + 0.5f; + + // panels + const vec_t dx = directionUnary[perpXIndex]; + const vec_t dy = directionUnary[perpYIndex]; + const vec_t origin = directionUnary[normalIndex] - dx - dy; + for (int iPanel = 0; iPanel < 9; iPanel++) + { + vec_t boxCoord = boxOrigin + indexVectorX * float(iPanel % 3) + indexVectorY * float(iPanel / 3) + makeVect(1.f, 1.f, 1.f); + const ImVec2 p = panelPosition[iPanel] * 2.f; + const ImVec2 s = panelSize[iPanel] * 2.f; + ImVec2 faceCoordsScreen[4]; + vec_t panelPos[4] = { dx * p.x + dy * p.y, + dx * p.x + dy * (p.y + s.y), + dx * (p.x + s.x) + dy * (p.y + s.y), + dx * (p.x + s.x) + dy * p.y }; + + for (unsigned int iCoord = 0; iCoord < 4; iCoord++) + { + faceCoordsScreen[iCoord] = worldToPos((panelPos[iCoord] + origin) * 0.5f * invert, res, position, size); + } + + const ImVec2 panelCorners[2] = { panelPosition[iPanel], panelPosition[iPanel] + panelSize[iPanel] }; + bool insidePanel = localx > panelCorners[0].x && localx < panelCorners[1].x&& localy > panelCorners[0].y && localy < panelCorners[1].y; + int boxCoordInt = int(boxCoord.x * 9.f + boxCoord.y * 3.f + boxCoord.z); + assert(boxCoordInt < 27); + boxes[boxCoordInt] |= insidePanel && (!isDraging); + + // draw face with lighter color + if (iPass) + { + gContext.mDrawList->AddConvexPolyFilled(faceCoordsScreen, 4, (directionColor[normalIndex] | IM_COL32(0x80, 0x80, 0x80, 0x80)) | (isInside ? IM_COL32(0x08, 0x08, 0x08, 0) : 0)); + if (boxes[boxCoordInt]) + { + gContext.mDrawList->AddConvexPolyFilled(faceCoordsScreen, 4, IM_COL32(0xF0, 0xA0, 0x60, 0x80)); + + if (!io.MouseDown[0] && !isDraging && isClicking) + { + // apply new view direction + int cx = boxCoordInt / 9; + int cy = (boxCoordInt - cx * 9) / 3; + int cz = boxCoordInt % 3; + interpolationDir = makeVect(1.f - cx, 1.f - cy, 1.f - cz); + interpolationDir.Normalize(); + + if (fabsf(Dot(interpolationDir, referenceUp)) > 1.0f - 0.01f) + { + vec_t right = viewInverse.v.right; + if (fabsf(right.x) > fabsf(right.z)) + { + right.z = 0.f; + } + else + { + right.x = 0.f; + } + right.Normalize(); + interpolationUp = Cross(interpolationDir, right); + interpolationUp.Normalize(); + } + else + { + interpolationUp = referenceUp; + } + interpolationFrames = 40; + isClicking = false; + } + if (io.MouseDown[0] && !isDraging) + { + isClicking = true; + } + } + } + } + } + } + if (interpolationFrames) + { + interpolationFrames--; + vec_t newDir = viewInverse.v.dir; + newDir.Lerp(interpolationDir, 0.2f); + newDir.Normalize(); + + vec_t newUp = viewInverse.v.up; + newUp.Lerp(interpolationUp, 0.3f); + newUp.Normalize(); + newUp = interpolationUp; + vec_t newEye = camTarget + newDir * length; + LookAt(&newEye.x, &camTarget.x, &newUp.x, view); + } + isInside = ImRect(position, position + size).Contains(io.MousePos); + + // drag view + if (!isDraging && io.MouseDown[0] && isInside && (fabsf(io.MouseDelta.x) > 0.f || fabsf(io.MouseDelta.y) > 0.f)) + { + isDraging = true; + isClicking = false; + } + else if (isDraging && !io.MouseDown[0]) + { + isDraging = false; + } + + if (isDraging) + { + matrix_t rx, ry, roll; + + rx.RotationAxis(referenceUp, -io.MouseDelta.x * 0.01f); + ry.RotationAxis(viewInverse.v.right, -io.MouseDelta.y * 0.01f); + + roll = rx * ry; + + vec_t newDir = viewInverse.v.dir; + newDir.TransformVector(roll); + newDir.Normalize(); + + // clamp + vec_t planDir = Cross(viewInverse.v.right, referenceUp); + planDir.y = 0.f; + planDir.Normalize(); + float dt = Dot(planDir, newDir); + if (dt < 0.0f) + { + newDir += planDir * dt; + newDir.Normalize(); + } + + vec_t newEye = camTarget + newDir * length; + LookAt(&newEye.x, &camTarget.x, &referenceUp.x, view); + } + + // restore view/projection because it was used to compute ray + ComputeContext(svgView.m16, svgProjection.m16, gContext.mModelSource.m16, gContext.mMode); + } +}; diff --git a/Source/Dependencies/imGuizmo/ImGuizmo.h b/Source/Dependencies/imGuizmo/ImGuizmo.h new file mode 100644 index 000000000..1b236d25b --- /dev/null +++ b/Source/Dependencies/imGuizmo/ImGuizmo.h @@ -0,0 +1,213 @@ +// https://github.com/CedricGuillemet/ImGuizmo +// v 1.83 +// +// The MIT License(MIT) +// +// Copyright(c) 2021 Cedric Guillemet +// +// 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. +// +// ------------------------------------------------------------------------------------------- +// History : +// 2019/11/03 View gizmo +// 2016/09/11 Behind camera culling. Scaling Delta matrix not multiplied by source matrix scales. local/world rotation and translation fixed. Display message is incorrect (X: ... Y:...) in local mode. +// 2016/09/09 Hatched negative axis. Snapping. Documentation update. +// 2016/09/04 Axis switch and translation plan autohiding. Scale transform stability improved +// 2016/09/01 Mogwai changed to Manipulate. Draw debug cube. Fixed inverted scale. Mixing scale and translation/rotation gives bad results. +// 2016/08/31 First version +// +// ------------------------------------------------------------------------------------------- +// Future (no order): +// +// - Multi view +// - display rotation/translation/scale infos in local/world space and not only local +// - finish local/world matrix application +// - OPERATION as bitmask +// +// ------------------------------------------------------------------------------------------- +// Example +#if 0 +void EditTransform(const Camera& camera, matrix_t& matrix) +{ + static ImGuizmo::OPERATION mCurrentGizmoOperation(ImGuizmo::ROTATE); + static ImGuizmo::MODE mCurrentGizmoMode(ImGuizmo::WORLD); + if (ImGui::IsKeyPressed(90)) + mCurrentGizmoOperation = ImGuizmo::TRANSLATE; + if (ImGui::IsKeyPressed(69)) + mCurrentGizmoOperation = ImGuizmo::ROTATE; + if (ImGui::IsKeyPressed(82)) // r Key + mCurrentGizmoOperation = ImGuizmo::SCALE; + if (ImGui::RadioButton("Translate", mCurrentGizmoOperation == ImGuizmo::TRANSLATE)) + mCurrentGizmoOperation = ImGuizmo::TRANSLATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Rotate", mCurrentGizmoOperation == ImGuizmo::ROTATE)) + mCurrentGizmoOperation = ImGuizmo::ROTATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Scale", mCurrentGizmoOperation == ImGuizmo::SCALE)) + mCurrentGizmoOperation = ImGuizmo::SCALE; + float matrixTranslation[3], matrixRotation[3], matrixScale[3]; + ImGuizmo::DecomposeMatrixToComponents(matrix.m16, matrixTranslation, matrixRotation, matrixScale); + ImGui::InputFloat3("Tr", matrixTranslation, 3); + ImGui::InputFloat3("Rt", matrixRotation, 3); + ImGui::InputFloat3("Sc", matrixScale, 3); + ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, matrix.m16); + + if (mCurrentGizmoOperation != ImGuizmo::SCALE) + { + if (ImGui::RadioButton("Local", mCurrentGizmoMode == ImGuizmo::LOCAL)) + mCurrentGizmoMode = ImGuizmo::LOCAL; + ImGui::SameLine(); + if (ImGui::RadioButton("World", mCurrentGizmoMode == ImGuizmo::WORLD)) + mCurrentGizmoMode = ImGuizmo::WORLD; + } + static bool useSnap(false); + if (ImGui::IsKeyPressed(83)) + useSnap = !useSnap; + ImGui::Checkbox("", &useSnap); + ImGui::SameLine(); + vec_t snap; + switch (mCurrentGizmoOperation) + { + case ImGuizmo::TRANSLATE: + snap = config.mSnapTranslation; + ImGui::InputFloat3("Snap", &snap.x); + break; + case ImGuizmo::ROTATE: + snap = config.mSnapRotation; + ImGui::InputFloat("Angle Snap", &snap.x); + break; + case ImGuizmo::SCALE: + snap = config.mSnapScale; + ImGui::InputFloat("Scale Snap", &snap.x); + break; + } + ImGuiIO& io = ImGui::GetIO(); + ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y); + ImGuizmo::Manipulate(camera.mView.m16, camera.mProjection.m16, mCurrentGizmoOperation, mCurrentGizmoMode, matrix.m16, NULL, useSnap ? &snap.x : NULL); +} +#endif +#pragma once + +#ifdef USE_IMGUI_API +#include "imconfig.h" +#endif +#ifndef IMGUI_API +#define IMGUI_API +#endif + +namespace ImGuizmo +{ + // call inside your own window and before Manipulate() in order to draw gizmo to that window. + // Or pass a specific ImDrawList to draw to (e.g. ImGui::GetForegroundDrawList()). + IMGUI_API void SetDrawlist(ImDrawList* drawlist = nullptr); + + // call BeginFrame right after ImGui_XXXX_NewFrame(); + IMGUI_API void BeginFrame(); + + // this is necessary because when imguizmo is compiled into a dll, and imgui into another + // globals are not shared between them. + // More details at https://stackoverflow.com/questions/19373061/what-happens-to-global-and-static-variables-in-a-shared-library-when-it-is-dynam + // expose method to set imgui context + IMGUI_API void SetImGuiContext(ImGuiContext* ctx); + + // return true if mouse cursor is over any gizmo control (axis, plan or screen component) + IMGUI_API bool IsOver(); + + // return true if mouse IsOver or if the gizmo is in moving state + IMGUI_API bool IsUsing(); + + // enable/disable the gizmo. Stay in the state until next call to Enable. + // gizmo is rendered with gray half transparent color when disabled + IMGUI_API void Enable(bool enable); + + // helper functions for manualy editing translation/rotation/scale with an input float + // translation, rotation and scale float points to 3 floats each + // Angles are in degrees (more suitable for human editing) + // example: + // float matrixTranslation[3], matrixRotation[3], matrixScale[3]; + // ImGuizmo::DecomposeMatrixToComponents(gizmoMatrix.m16, matrixTranslation, matrixRotation, matrixScale); + // ImGui::InputFloat3("Tr", matrixTranslation, 3); + // ImGui::InputFloat3("Rt", matrixRotation, 3); + // ImGui::InputFloat3("Sc", matrixScale, 3); + // ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, gizmoMatrix.m16); + // + // These functions have some numerical stability issues for now. Use with caution. + IMGUI_API void DecomposeMatrixToComponents(const float* matrix, float* translation, float* rotation, float* scale); + IMGUI_API void RecomposeMatrixFromComponents(const float* translation, const float* rotation, const float* scale, float* matrix); + + IMGUI_API void SetRect(float x, float y, float width, float height); + // default is false + IMGUI_API void SetOrthographic(bool isOrthographic); + + // Render a cube with face color corresponding to face normal. Usefull for debug/tests + IMGUI_API void DrawCubes(const float* view, const float* projection, const float* matrices, int matrixCount); + IMGUI_API void DrawGrid(const float* view, const float* projection, const float* matrix, const float gridSize); + + // call it when you want a gizmo + // Needs view and projection matrices. + // matrix parameter is the source matrix (where will be gizmo be drawn) and might be transformed by the function. Return deltaMatrix is optional + // translation is applied in world space + enum OPERATION + { + TRANSLATE_X = (1u << 0), + TRANSLATE_Y = (1u << 1), + TRANSLATE_Z = (1u << 2), + ROTATE_X = (1u << 3), + ROTATE_Y = (1u << 4), + ROTATE_Z = (1u << 5), + ROTATE_SCREEN = (1u << 6), + SCALE_X = (1u << 7), + SCALE_Y = (1u << 8), + SCALE_Z = (1u << 9), + BOUNDS = (1u << 10), + TRANSLATE = TRANSLATE_X | TRANSLATE_Y | TRANSLATE_Z, + ROTATE = ROTATE_X | ROTATE_Y | ROTATE_Z | ROTATE_SCREEN, + SCALE = SCALE_X | SCALE_Y | SCALE_Z + }; + + inline OPERATION operator|(OPERATION lhs, OPERATION rhs) + { + return static_cast(static_cast(lhs) | static_cast(rhs)); + } + + enum MODE + { + LOCAL, + WORLD + }; + + IMGUI_API bool Manipulate(const float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float* deltaMatrix = NULL, const float* snap = NULL, const float* localBounds = NULL, const float* boundsSnap = NULL); + // + // Please note that this cubeview is patented by Autodesk : https://patents.google.com/patent/US7782319B2/en + // It seems to be a defensive patent in the US. I don't think it will bring troubles using it as + // other software are using the same mechanics. But just in case, you are now warned! + // + IMGUI_API void ViewManipulate(float* view, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor); + + IMGUI_API void SetID(int id); + + // return true if the cursor is over the operation's gizmo + IMGUI_API bool IsOver(OPERATION op); + IMGUI_API void SetGizmoSizeClipSpace(float value); + + // Allow axis to flip + // When true (default), the guizmo axis flip for better visibility + // When false, they always stay along the positive world/local axis + IMGUI_API void AllowAxisFlip(bool value); +} diff --git a/Source/Dependencies/imGuizmo/ImSequencer.cpp b/Source/Dependencies/imGuizmo/ImSequencer.cpp new file mode 100644 index 000000000..61e2b3392 --- /dev/null +++ b/Source/Dependencies/imGuizmo/ImSequencer.cpp @@ -0,0 +1,688 @@ +// https://github.com/CedricGuillemet/ImGuizmo +// v 1.83 +// +// The MIT License(MIT) +// +// Copyright(c) 2021 Cedric Guillemet +// +// 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. +// +#include "ImSequencer.h" +#include "imgui.h" +#include "imgui_internal.h" +#include + +namespace ImSequencer +{ +#ifndef IMGUI_DEFINE_MATH_OPERATORS + static ImVec2 operator+(const ImVec2& a, const ImVec2& b) { + return ImVec2(a.x + b.x, a.y + b.y); + } +#endif + static bool SequencerAddDelButton(ImDrawList* draw_list, ImVec2 pos, bool add = true) + { + ImGuiIO& io = ImGui::GetIO(); + ImRect delRect(pos, ImVec2(pos.x + 16, pos.y + 16)); + bool overDel = delRect.Contains(io.MousePos); + int delColor = overDel ? 0xFFAAAAAA : 0x50000000; + float midy = pos.y + 16 / 2 - 0.5f; + float midx = pos.x + 16 / 2 - 0.5f; + draw_list->AddRect(delRect.Min, delRect.Max, delColor, 4); + draw_list->AddLine(ImVec2(delRect.Min.x + 3, midy), ImVec2(delRect.Max.x - 3, midy), delColor, 2); + if (add) + draw_list->AddLine(ImVec2(midx, delRect.Min.y + 3), ImVec2(midx, delRect.Max.y - 3), delColor, 2); + return overDel; + } + + bool Sequencer(SequenceInterface* sequence, int* currentFrame, bool* expanded, int* selectedEntry, int* firstFrame, int sequenceOptions) + { + bool ret = false; + ImGuiIO& io = ImGui::GetIO(); + int cx = (int)(io.MousePos.x); + int cy = (int)(io.MousePos.y); + static float framePixelWidth = 10.f; + static float framePixelWidthTarget = 10.f; + int legendWidth = 200; + + static int movingEntry = -1; + static int movingPos = -1; + static int movingPart = -1; + int delEntry = -1; + int dupEntry = -1; + int ItemHeight = 20; + + bool popupOpened = false; + int sequenceCount = sequence->GetItemCount(); + if (!sequenceCount) + return false; + ImGui::BeginGroup(); + + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + ImVec2 canvas_pos = ImGui::GetCursorScreenPos(); // ImDrawList API uses screen coordinates! + ImVec2 canvas_size = ImGui::GetContentRegionAvail(); // Resize canvas to what's available + int firstFrameUsed = firstFrame ? *firstFrame : 0; + + + int controlHeight = sequenceCount * ItemHeight; + for (int i = 0; i < sequenceCount; i++) + controlHeight += int(sequence->GetCustomHeight(i)); + int frameCount = ImMax(sequence->GetFrameMax() - sequence->GetFrameMin(), 1); + + static bool MovingScrollBar = false; + static bool MovingCurrentFrame = false; + struct CustomDraw + { + int index; + ImRect customRect; + ImRect legendRect; + ImRect clippingRect; + ImRect legendClippingRect; + }; + ImVector customDraws; + ImVector compactCustomDraws; + // zoom in/out + const int visibleFrameCount = (int)floorf((canvas_size.x - legendWidth) / framePixelWidth); + const float barWidthRatio = ImMin(visibleFrameCount / (float)frameCount, 1.f); + const float barWidthInPixels = barWidthRatio * (canvas_size.x - legendWidth); + + ImRect regionRect(canvas_pos, canvas_pos + canvas_size); + + static bool panningView = false; + static ImVec2 panningViewSource; + static int panningViewFrame; + if (ImGui::IsWindowFocused() && io.KeyAlt && io.MouseDown[2]) + { + if (!panningView) + { + panningViewSource = io.MousePos; + panningView = true; + panningViewFrame = *firstFrame; + } + *firstFrame = panningViewFrame - int((io.MousePos.x - panningViewSource.x) / framePixelWidth); + *firstFrame = ImClamp(*firstFrame, sequence->GetFrameMin(), sequence->GetFrameMax() - visibleFrameCount); + } + if (panningView && !io.MouseDown[2]) + { + panningView = false; + } + framePixelWidthTarget = ImClamp(framePixelWidthTarget, 0.1f, 50.f); + + framePixelWidth = ImLerp(framePixelWidth, framePixelWidthTarget, 0.33f); + + frameCount = sequence->GetFrameMax() - sequence->GetFrameMin(); + if (visibleFrameCount >= frameCount && firstFrame) + *firstFrame = sequence->GetFrameMin(); + + + // -- + if (expanded && !*expanded) + { + ImGui::InvisibleButton("canvas", ImVec2(canvas_size.x - canvas_pos.x, (float)ItemHeight)); + draw_list->AddRectFilled(canvas_pos, ImVec2(canvas_size.x + canvas_pos.x, canvas_pos.y + ItemHeight), 0xFF3D3837, 0); + char tmps[512]; + ImFormatString(tmps, IM_ARRAYSIZE(tmps), "%d Frames / %d entries", frameCount, sequenceCount); + draw_list->AddText(ImVec2(canvas_pos.x + 26, canvas_pos.y + 2), 0xFFFFFFFF, tmps); + } + else + { + bool hasScrollBar(true); + /* + int framesPixelWidth = int(frameCount * framePixelWidth); + if ((framesPixelWidth + legendWidth) >= canvas_size.x) + { + hasScrollBar = true; + } + */ + // test scroll area + ImVec2 headerSize(canvas_size.x, (float)ItemHeight); + ImVec2 scrollBarSize(canvas_size.x, 14.f); + ImGui::InvisibleButton("topBar", headerSize); + draw_list->AddRectFilled(canvas_pos, canvas_pos + headerSize, 0xFFFF0000, 0); + ImVec2 childFramePos = ImGui::GetCursorScreenPos(); + ImVec2 childFrameSize(canvas_size.x, canvas_size.y - 8.f - headerSize.y - (hasScrollBar ? scrollBarSize.y : 0)); + ImGui::PushStyleColor(ImGuiCol_FrameBg, 0); + ImGui::BeginChildFrame(889, childFrameSize); + sequence->focused = ImGui::IsWindowFocused(); + ImGui::InvisibleButton("contentBar", ImVec2(canvas_size.x, float(controlHeight))); + const ImVec2 contentMin = ImGui::GetItemRectMin(); + const ImVec2 contentMax = ImGui::GetItemRectMax(); + const ImRect contentRect(contentMin, contentMax); + const float contentHeight = contentMax.y - contentMin.y; + + // full background + draw_list->AddRectFilled(canvas_pos, canvas_pos + canvas_size, 0xFF242424, 0); + + // current frame top + ImRect topRect(ImVec2(canvas_pos.x + legendWidth, canvas_pos.y), ImVec2(canvas_pos.x + canvas_size.x, canvas_pos.y + ItemHeight)); + + if (!MovingCurrentFrame && !MovingScrollBar && movingEntry == -1 && sequenceOptions & SEQUENCER_CHANGE_FRAME && currentFrame && *currentFrame >= 0 && topRect.Contains(io.MousePos) && io.MouseDown[0]) + { + MovingCurrentFrame = true; + } + if (MovingCurrentFrame) + { + if (frameCount) + { + *currentFrame = (int)((io.MousePos.x - topRect.Min.x) / framePixelWidth) + firstFrameUsed; + if (*currentFrame < sequence->GetFrameMin()) + *currentFrame = sequence->GetFrameMin(); + if (*currentFrame >= sequence->GetFrameMax()) + *currentFrame = sequence->GetFrameMax(); + } + if (!io.MouseDown[0]) + MovingCurrentFrame = false; + } + + //header + draw_list->AddRectFilled(canvas_pos, ImVec2(canvas_size.x + canvas_pos.x, canvas_pos.y + ItemHeight), 0xFF3D3837, 0); + if (sequenceOptions & SEQUENCER_ADD) + { + if (SequencerAddDelButton(draw_list, ImVec2(canvas_pos.x + legendWidth - ItemHeight, canvas_pos.y + 2), true) && io.MouseReleased[0]) + ImGui::OpenPopup("addEntry"); + + if (ImGui::BeginPopup("addEntry")) + { + for (int i = 0; i < sequence->GetItemTypeCount(); i++) + if (ImGui::Selectable(sequence->GetItemTypeName(i))) + { + sequence->Add(i); + *selectedEntry = sequence->GetItemCount() - 1; + } + + ImGui::EndPopup(); + popupOpened = true; + } + } + + //header frame number and lines + int modFrameCount = 10; + int frameStep = 1; + while ((modFrameCount * framePixelWidth) < 150) + { + modFrameCount *= 2; + frameStep *= 2; + }; + int halfModFrameCount = modFrameCount / 2; + + auto drawLine = [&](int i, int regionHeight) { + bool baseIndex = ((i % modFrameCount) == 0) || (i == sequence->GetFrameMax() || i == sequence->GetFrameMin()); + bool halfIndex = (i % halfModFrameCount) == 0; + int px = (int)canvas_pos.x + int(i * framePixelWidth) + legendWidth - int(firstFrameUsed * framePixelWidth); + int tiretStart = baseIndex ? 4 : (halfIndex ? 10 : 14); + int tiretEnd = baseIndex ? regionHeight : ItemHeight; + + if (px <= (canvas_size.x + canvas_pos.x) && px >= (canvas_pos.x + legendWidth)) + { + draw_list->AddLine(ImVec2((float)px, canvas_pos.y + (float)tiretStart), ImVec2((float)px, canvas_pos.y + (float)tiretEnd - 1), 0xFF606060, 1); + + draw_list->AddLine(ImVec2((float)px, canvas_pos.y + (float)ItemHeight), ImVec2((float)px, canvas_pos.y + (float)regionHeight - 1), 0x30606060, 1); + } + + if (baseIndex && px > (canvas_pos.x + legendWidth)) + { + char tmps[512]; + ImFormatString(tmps, IM_ARRAYSIZE(tmps), "%d", i); + draw_list->AddText(ImVec2((float)px + 3.f, canvas_pos.y), 0xFFBBBBBB, tmps); + } + + }; + + auto drawLineContent = [&](int i, int /*regionHeight*/) { + int px = (int)canvas_pos.x + int(i * framePixelWidth) + legendWidth - int(firstFrameUsed * framePixelWidth); + int tiretStart = int(contentMin.y); + int tiretEnd = int(contentMax.y); + + if (px <= (canvas_size.x + canvas_pos.x) && px >= (canvas_pos.x + legendWidth)) + { + //draw_list->AddLine(ImVec2((float)px, canvas_pos.y + (float)tiretStart), ImVec2((float)px, canvas_pos.y + (float)tiretEnd - 1), 0xFF606060, 1); + + draw_list->AddLine(ImVec2(float(px), float(tiretStart)), ImVec2(float(px), float(tiretEnd)), 0x30606060, 1); + } + }; + for (int i = sequence->GetFrameMin(); i <= sequence->GetFrameMax(); i += frameStep) + { + drawLine(i, ItemHeight); + } + drawLine(sequence->GetFrameMin(), ItemHeight); + drawLine(sequence->GetFrameMax(), ItemHeight); + /* + draw_list->AddLine(canvas_pos, ImVec2(canvas_pos.x, canvas_pos.y + controlHeight), 0xFF000000, 1); + draw_list->AddLine(ImVec2(canvas_pos.x, canvas_pos.y + ItemHeight), ImVec2(canvas_size.x, canvas_pos.y + ItemHeight), 0xFF000000, 1); + */ + // clip content + + draw_list->PushClipRect(childFramePos, childFramePos + childFrameSize); + + // draw item names in the legend rect on the left + size_t customHeight = 0; + for (int i = 0; i < sequenceCount; i++) + { + int type; + sequence->Get(i, NULL, NULL, &type, NULL); + ImVec2 tpos(contentMin.x + 3, contentMin.y + i * ItemHeight + 2 + customHeight); + draw_list->AddText(tpos, 0xFFFFFFFF, sequence->GetItemLabel(i)); + + if (sequenceOptions & SEQUENCER_DEL) + { + bool overDel = SequencerAddDelButton(draw_list, ImVec2(contentMin.x + legendWidth - ItemHeight + 2 - 10, tpos.y + 2), false); + if (overDel && io.MouseReleased[0]) + delEntry = i; + + bool overDup = SequencerAddDelButton(draw_list, ImVec2(contentMin.x + legendWidth - ItemHeight - ItemHeight + 2 - 10, tpos.y + 2), true); + if (overDup && io.MouseReleased[0]) + dupEntry = i; + } + customHeight += sequence->GetCustomHeight(i); + } + + // clipping rect so items bars are not visible in the legend on the left when scrolled + // + + // slots background + customHeight = 0; + for (int i = 0; i < sequenceCount; i++) + { + unsigned int col = (i & 1) ? 0xFF3A3636 : 0xFF413D3D; + + size_t localCustomHeight = sequence->GetCustomHeight(i); + ImVec2 pos = ImVec2(contentMin.x + legendWidth, contentMin.y + ItemHeight * i + 1 + customHeight); + ImVec2 sz = ImVec2(canvas_size.x + canvas_pos.x, pos.y + ItemHeight - 1 + localCustomHeight); + if (!popupOpened && cy >= pos.y && cy < pos.y + (ItemHeight + localCustomHeight) && movingEntry == -1 && cx>contentMin.x && cx < contentMin.x + canvas_size.x) + { + col += 0x80201008; + pos.x -= legendWidth; + } + draw_list->AddRectFilled(pos, sz, col, 0); + customHeight += localCustomHeight; + } + + draw_list->PushClipRect(childFramePos + ImVec2(float(legendWidth), 0.f), childFramePos + childFrameSize); + + // vertical frame lines in content area + for (int i = sequence->GetFrameMin(); i <= sequence->GetFrameMax(); i += frameStep) + { + drawLineContent(i, int(contentHeight)); + } + drawLineContent(sequence->GetFrameMin(), int(contentHeight)); + drawLineContent(sequence->GetFrameMax(), int(contentHeight)); + + // selection + bool selected = selectedEntry && (*selectedEntry >= 0); + if (selected) + { + customHeight = 0; + for (int i = 0; i < *selectedEntry; i++) + customHeight += sequence->GetCustomHeight(i);; + draw_list->AddRectFilled(ImVec2(contentMin.x, contentMin.y + ItemHeight * *selectedEntry + customHeight), ImVec2(contentMin.x + canvas_size.x, contentMin.y + ItemHeight * (*selectedEntry + 1) + customHeight), 0x801080FF, 1.f); + } + + // slots + customHeight = 0; + for (int i = 0; i < sequenceCount; i++) + { + int* start, * end; + unsigned int color; + sequence->Get(i, &start, &end, NULL, &color); + size_t localCustomHeight = sequence->GetCustomHeight(i); + + ImVec2 pos = ImVec2(contentMin.x + legendWidth - firstFrameUsed * framePixelWidth, contentMin.y + ItemHeight * i + 1 + customHeight); + ImVec2 slotP1(pos.x + *start * framePixelWidth, pos.y + 2); + ImVec2 slotP2(pos.x + *end * framePixelWidth + framePixelWidth, pos.y + ItemHeight - 2); + ImVec2 slotP3(pos.x + *end * framePixelWidth + framePixelWidth, pos.y + ItemHeight - 2 + localCustomHeight); + unsigned int slotColor = color | 0xFF000000; + unsigned int slotColorHalf = (color & 0xFFFFFF) | 0x40000000; + + if (slotP1.x <= (canvas_size.x + contentMin.x) && slotP2.x >= (contentMin.x + legendWidth)) + { + draw_list->AddRectFilled(slotP1, slotP3, slotColorHalf, 2); + draw_list->AddRectFilled(slotP1, slotP2, slotColor, 2); + } + if (ImRect(slotP1, slotP2).Contains(io.MousePos) && io.MouseDoubleClicked[0]) + { + sequence->DoubleClick(i); + } + ImRect rects[3] = { ImRect(slotP1, ImVec2(slotP1.x + framePixelWidth / 2, slotP2.y)) + , ImRect(ImVec2(slotP2.x - framePixelWidth / 2, slotP1.y), slotP2) + , ImRect(slotP1, slotP2) }; + + const unsigned int quadColor[] = { 0xFFFFFFFF, 0xFFFFFFFF, slotColor + (selected ? 0 : 0x202020) }; + if (movingEntry == -1 && (sequenceOptions & SEQUENCER_EDIT_STARTEND))// TODOFOCUS && backgroundRect.Contains(io.MousePos)) + { + for (int j = 2; j >= 0; j--) + { + ImRect& rc = rects[j]; + if (!rc.Contains(io.MousePos)) + continue; + draw_list->AddRectFilled(rc.Min, rc.Max, quadColor[j], 2); + } + + for (int j = 0; j < 3; j++) + { + ImRect& rc = rects[j]; + if (!rc.Contains(io.MousePos)) + continue; + if (!ImRect(childFramePos, childFramePos + childFrameSize).Contains(io.MousePos)) + continue; + if (ImGui::IsMouseClicked(0) && !MovingScrollBar && !MovingCurrentFrame) + { + movingEntry = i; + movingPos = cx; + movingPart = j + 1; + sequence->BeginEdit(movingEntry); + break; + } + } + } + + // custom draw + if (localCustomHeight > 0) + { + ImVec2 rp(canvas_pos.x, contentMin.y + ItemHeight * i + 1 + customHeight); + ImRect customRect(rp + ImVec2(legendWidth - (firstFrameUsed - sequence->GetFrameMin() - 0.5f) * framePixelWidth, float(ItemHeight)), + rp + ImVec2(legendWidth + (sequence->GetFrameMax() - firstFrameUsed - 0.5f + 2.f) * framePixelWidth, float(localCustomHeight + ItemHeight))); + ImRect clippingRect(rp + ImVec2(float(legendWidth), float(ItemHeight)), rp + ImVec2(canvas_size.x, float(localCustomHeight + ItemHeight))); + + ImRect legendRect(rp + ImVec2(0.f, float(ItemHeight)), rp + ImVec2(float(legendWidth), float(localCustomHeight))); + ImRect legendClippingRect(canvas_pos + ImVec2(0.f, float(ItemHeight)), canvas_pos + ImVec2(float(legendWidth), float(localCustomHeight + ItemHeight))); + customDraws.push_back({ i, customRect, legendRect, clippingRect, legendClippingRect }); + } + else + { + ImVec2 rp(canvas_pos.x, contentMin.y + ItemHeight * i + customHeight); + ImRect customRect(rp + ImVec2(legendWidth - (firstFrameUsed - sequence->GetFrameMin() - 0.5f) * framePixelWidth, float(0.f)), + rp + ImVec2(legendWidth + (sequence->GetFrameMax() - firstFrameUsed - 0.5f + 2.f) * framePixelWidth, float(ItemHeight))); + ImRect clippingRect(rp + ImVec2(float(legendWidth), float(0.f)), rp + ImVec2(canvas_size.x, float(ItemHeight))); + + compactCustomDraws.push_back({ i, customRect, ImRect(), clippingRect, ImRect() }); + } + customHeight += localCustomHeight; + } + + + // moving + if (/*backgroundRect.Contains(io.MousePos) && */movingEntry >= 0) + { + ImGui::CaptureMouseFromApp(); + int diffFrame = int((cx - movingPos) / framePixelWidth); + if (std::abs(diffFrame) > 0) + { + int* start, * end; + sequence->Get(movingEntry, &start, &end, NULL, NULL); + if (selectedEntry) + *selectedEntry = movingEntry; + int& l = *start; + int& r = *end; + if (movingPart & 1) + l += diffFrame; + if (movingPart & 2) + r += diffFrame; + if (l < 0) + { + if (movingPart & 2) + r -= l; + l = 0; + } + if (movingPart & 1 && l > r) + l = r; + if (movingPart & 2 && r < l) + r = l; + movingPos += int(diffFrame * framePixelWidth); + } + if (!io.MouseDown[0]) + { + // single select + if (!diffFrame && movingPart && selectedEntry) + { + *selectedEntry = movingEntry; + ret = true; + } + + movingEntry = -1; + sequence->EndEdit(); + } + } + + // cursor + if (currentFrame && firstFrame && *currentFrame >= *firstFrame && *currentFrame <= sequence->GetFrameMax()) + { + static const float cursorWidth = 8.f; + float cursorOffset = contentMin.x + legendWidth + (*currentFrame - firstFrameUsed) * framePixelWidth + framePixelWidth / 2 - cursorWidth * 0.5f; + draw_list->AddLine(ImVec2(cursorOffset, canvas_pos.y), ImVec2(cursorOffset, contentMax.y), 0xA02A2AFF, cursorWidth); + char tmps[512]; + ImFormatString(tmps, IM_ARRAYSIZE(tmps), "%d", *currentFrame); + draw_list->AddText(ImVec2(cursorOffset + 10, canvas_pos.y + 2), 0xFF2A2AFF, tmps); + } + + draw_list->PopClipRect(); + draw_list->PopClipRect(); + + for (auto& customDraw : customDraws) + sequence->CustomDraw(customDraw.index, draw_list, customDraw.customRect, customDraw.legendRect, customDraw.clippingRect, customDraw.legendClippingRect); + for (auto& customDraw : compactCustomDraws) + sequence->CustomDrawCompact(customDraw.index, draw_list, customDraw.customRect, customDraw.clippingRect); + + // copy paste + if (sequenceOptions & SEQUENCER_COPYPASTE) + { + ImRect rectCopy(ImVec2(contentMin.x + 100, canvas_pos.y + 2) + , ImVec2(contentMin.x + 100 + 30, canvas_pos.y + ItemHeight - 2)); + bool inRectCopy = rectCopy.Contains(io.MousePos); + unsigned int copyColor = inRectCopy ? 0xFF1080FF : 0xFF000000; + draw_list->AddText(rectCopy.Min, copyColor, "Copy"); + + ImRect rectPaste(ImVec2(contentMin.x + 140, canvas_pos.y + 2) + , ImVec2(contentMin.x + 140 + 30, canvas_pos.y + ItemHeight - 2)); + bool inRectPaste = rectPaste.Contains(io.MousePos); + unsigned int pasteColor = inRectPaste ? 0xFF1080FF : 0xFF000000; + draw_list->AddText(rectPaste.Min, pasteColor, "Paste"); + + if (inRectCopy && io.MouseReleased[0]) + { + sequence->Copy(); + } + if (inRectPaste && io.MouseReleased[0]) + { + sequence->Paste(); + } + } + // + + ImGui::EndChildFrame(); + ImGui::PopStyleColor(); + if (hasScrollBar) + { + ImGui::InvisibleButton("scrollBar", scrollBarSize); + ImVec2 scrollBarMin = ImGui::GetItemRectMin(); + ImVec2 scrollBarMax = ImGui::GetItemRectMax(); + + // ratio = number of frames visible in control / number to total frames + + float startFrameOffset = ((float)(firstFrameUsed - sequence->GetFrameMin()) / (float)frameCount) * (canvas_size.x - legendWidth); + ImVec2 scrollBarA(scrollBarMin.x + legendWidth, scrollBarMin.y - 2); + ImVec2 scrollBarB(scrollBarMin.x + canvas_size.x, scrollBarMax.y - 1); + draw_list->AddRectFilled(scrollBarA, scrollBarB, 0xFF222222, 0); + + ImRect scrollBarRect(scrollBarA, scrollBarB); + bool inScrollBar = scrollBarRect.Contains(io.MousePos); + + draw_list->AddRectFilled(scrollBarA, scrollBarB, 0xFF101010, 8); + + + ImVec2 scrollBarC(scrollBarMin.x + legendWidth + startFrameOffset, scrollBarMin.y); + ImVec2 scrollBarD(scrollBarMin.x + legendWidth + barWidthInPixels + startFrameOffset, scrollBarMax.y - 2); + draw_list->AddRectFilled(scrollBarC, scrollBarD, (inScrollBar || MovingScrollBar) ? 0xFF606060 : 0xFF505050, 6); + + ImRect barHandleLeft(scrollBarC, ImVec2(scrollBarC.x + 14, scrollBarD.y)); + ImRect barHandleRight(ImVec2(scrollBarD.x - 14, scrollBarC.y), scrollBarD); + + bool onLeft = barHandleLeft.Contains(io.MousePos); + bool onRight = barHandleRight.Contains(io.MousePos); + + static bool sizingRBar = false; + static bool sizingLBar = false; + + draw_list->AddRectFilled(barHandleLeft.Min, barHandleLeft.Max, (onLeft || sizingLBar) ? 0xFFAAAAAA : 0xFF666666, 6); + draw_list->AddRectFilled(barHandleRight.Min, barHandleRight.Max, (onRight || sizingRBar) ? 0xFFAAAAAA : 0xFF666666, 6); + + ImRect scrollBarThumb(scrollBarC, scrollBarD); + static const float MinBarWidth = 44.f; + if (sizingRBar) + { + if (!io.MouseDown[0]) + { + sizingRBar = false; + } + else + { + float barNewWidth = ImMax(barWidthInPixels + io.MouseDelta.x, MinBarWidth); + float barRatio = barNewWidth / barWidthInPixels; + framePixelWidthTarget = framePixelWidth = framePixelWidth / barRatio; + int newVisibleFrameCount = int((canvas_size.x - legendWidth) / framePixelWidthTarget); + int lastFrame = *firstFrame + newVisibleFrameCount; + if (lastFrame > sequence->GetFrameMax()) + { + framePixelWidthTarget = framePixelWidth = (canvas_size.x - legendWidth) / float(sequence->GetFrameMax() - *firstFrame); + } + } + } + else if (sizingLBar) + { + if (!io.MouseDown[0]) + { + sizingLBar = false; + } + else + { + if (fabsf(io.MouseDelta.x) > FLT_EPSILON) + { + float barNewWidth = ImMax(barWidthInPixels - io.MouseDelta.x, MinBarWidth); + float barRatio = barNewWidth / barWidthInPixels; + float previousFramePixelWidthTarget = framePixelWidthTarget; + framePixelWidthTarget = framePixelWidth = framePixelWidth / barRatio; + int newVisibleFrameCount = int(visibleFrameCount / barRatio); + int newFirstFrame = *firstFrame + newVisibleFrameCount - visibleFrameCount; + newFirstFrame = ImClamp(newFirstFrame, sequence->GetFrameMin(), ImMax(sequence->GetFrameMax() - visibleFrameCount, sequence->GetFrameMin())); + if (newFirstFrame == *firstFrame) + { + framePixelWidth = framePixelWidthTarget = previousFramePixelWidthTarget; + } + else + { + *firstFrame = newFirstFrame; + } + } + } + } + else + { + if (MovingScrollBar) + { + if (!io.MouseDown[0]) + { + MovingScrollBar = false; + } + else + { + float framesPerPixelInBar = barWidthInPixels / (float)visibleFrameCount; + *firstFrame = int((io.MousePos.x - panningViewSource.x) / framesPerPixelInBar) - panningViewFrame; + *firstFrame = ImClamp(*firstFrame, sequence->GetFrameMin(), ImMax(sequence->GetFrameMax() - visibleFrameCount, sequence->GetFrameMin())); + } + } + else + { + if (scrollBarThumb.Contains(io.MousePos) && ImGui::IsMouseClicked(0) && firstFrame && !MovingCurrentFrame && movingEntry == -1) + { + MovingScrollBar = true; + panningViewSource = io.MousePos; + panningViewFrame = -*firstFrame; + } + if (!sizingRBar && onRight && ImGui::IsMouseClicked(0)) + sizingRBar = true; + if (!sizingLBar && onLeft && ImGui::IsMouseClicked(0)) + sizingLBar = true; + + } + } + } + } + + ImGui::EndGroup(); + + if (regionRect.Contains(io.MousePos)) + { + bool overCustomDraw = false; + for (auto& custom : customDraws) + { + if (custom.customRect.Contains(io.MousePos)) + { + overCustomDraw = true; + } + } + if (overCustomDraw) + { + } + else + { +#if 0 + frameOverCursor = *firstFrame + (int)(visibleFrameCount * ((io.MousePos.x - (float)legendWidth - canvas_pos.x) / (canvas_size.x - legendWidth))); + //frameOverCursor = max(min(*firstFrame - visibleFrameCount / 2, frameCount - visibleFrameCount), 0); + + /**firstFrame -= frameOverCursor; + *firstFrame *= framePixelWidthTarget / framePixelWidth; + *firstFrame += frameOverCursor;*/ + if (io.MouseWheel < -FLT_EPSILON) + { + *firstFrame -= frameOverCursor; + *firstFrame = int(*firstFrame * 1.1f); + framePixelWidthTarget *= 0.9f; + *firstFrame += frameOverCursor; + } + + if (io.MouseWheel > FLT_EPSILON) + { + *firstFrame -= frameOverCursor; + *firstFrame = int(*firstFrame * 0.9f); + framePixelWidthTarget *= 1.1f; + *firstFrame += frameOverCursor; + } +#endif + } + } + + if (expanded) + { + bool overExpanded = SequencerAddDelButton(draw_list, ImVec2(canvas_pos.x + 2, canvas_pos.y + 2), !*expanded); + if (overExpanded && io.MouseReleased[0]) + *expanded = !*expanded; + } + + if (delEntry != -1) + { + sequence->Del(delEntry); + if (selectedEntry && (*selectedEntry == delEntry || *selectedEntry >= sequence->GetItemCount())) + *selectedEntry = -1; + } + + if (dupEntry != -1) + { + sequence->Duplicate(dupEntry); + } + return ret; + } +} diff --git a/Source/Dependencies/imGuizmo/ImSequencer.h b/Source/Dependencies/imGuizmo/ImSequencer.h new file mode 100644 index 000000000..6e18453dd --- /dev/null +++ b/Source/Dependencies/imGuizmo/ImSequencer.h @@ -0,0 +1,76 @@ +// https://github.com/CedricGuillemet/ImGuizmo +// v 1.83 +// +// The MIT License(MIT) +// +// Copyright(c) 2021 Cedric Guillemet +// +// 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. +// +#pragma once + +#include + +struct ImDrawList; +struct ImRect; +namespace ImSequencer +{ + enum SEQUENCER_OPTIONS + { + SEQUENCER_EDIT_NONE = 0, + SEQUENCER_EDIT_STARTEND = 1 << 1, + SEQUENCER_CHANGE_FRAME = 1 << 3, + SEQUENCER_ADD = 1 << 4, + SEQUENCER_DEL = 1 << 5, + SEQUENCER_COPYPASTE = 1 << 6, + SEQUENCER_EDIT_ALL = SEQUENCER_EDIT_STARTEND | SEQUENCER_CHANGE_FRAME + }; + + struct SequenceInterface + { + bool focused = false; + virtual int GetFrameMin() const = 0; + virtual int GetFrameMax() const = 0; + virtual int GetItemCount() const = 0; + + virtual void BeginEdit(int /*index*/) {} + virtual void EndEdit() {} + virtual int GetItemTypeCount() const { return 0; } + virtual const char* GetItemTypeName(int /*typeIndex*/) const { return ""; } + virtual const char* GetItemLabel(int /*index*/) const { return ""; } + + virtual void Get(int index, int** start, int** end, int* type, unsigned int* color) = 0; + virtual void Add(int /*type*/) {} + virtual void Del(int /*index*/) {} + virtual void Duplicate(int /*index*/) {} + + virtual void Copy() {} + virtual void Paste() {} + + virtual size_t GetCustomHeight(int /*index*/) { return 0; } + virtual void DoubleClick(int /*index*/) {} + virtual void CustomDraw(int /*index*/, ImDrawList* /*draw_list*/, const ImRect& /*rc*/, const ImRect& /*legendRect*/, const ImRect& /*clippingRect*/, const ImRect& /*legendClippingRect*/) {} + virtual void CustomDrawCompact(int /*index*/, ImDrawList* /*draw_list*/, const ImRect& /*rc*/, const ImRect& /*clippingRect*/) {} + }; + + + // return true if selection is made + bool Sequencer(SequenceInterface* sequence, int* currentFrame, bool* expanded, int* selectedEntry, int* firstFrame, int sequenceOptions); + +} diff --git a/Source/Dependencies/imGuizmo/ImZoomSlider.h b/Source/Dependencies/imGuizmo/ImZoomSlider.h new file mode 100644 index 000000000..9a6cc718d --- /dev/null +++ b/Source/Dependencies/imGuizmo/ImZoomSlider.h @@ -0,0 +1,245 @@ +// https://github.com/CedricGuillemet/ImGuizmo +// v 1.83 +// +// The MIT License(MIT) +// +// Copyright(c) 2021 Cedric Guillemet +// +// 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. +// +#pragma once + +namespace ImZoomSlider +{ + typedef int ImGuiZoomSliderFlags; + enum ImGuiPopupFlags_ + { + ImGuiZoomSliderFlags_None = 0, + ImGuiZoomSliderFlags_Vertical = 1, + ImGuiZoomSliderFlags_NoAnchors = 2, + ImGuiZoomSliderFlags_NoMiddleCarets = 4, + ImGuiZoomSliderFlags_NoWheel = 8, + }; + + template bool ImZoomSlider(const T lower, const T higher, T& viewLower, T& viewHigher, float wheelRatio = 0.01f, ImGuiZoomSliderFlags flags = ImGuiZoomSliderFlags_None) + { + bool interacted = false; + ImGuiIO& io = ImGui::GetIO(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + + static const float handleSize = 12; + static const float roundRadius = 3.f; + static const char* controlName = "ImZoomSlider"; + + static bool movingScrollBarSvg = false; + static bool sizingRBarSvg = false; + static bool sizingLBarSvg = false; + static ImGuiID editingId = (ImGuiID)-1; + static float scrollingSource = 0.f; + static float saveViewLower; + static float saveViewHigher; + + const bool isVertical = flags & ImGuiZoomSliderFlags_Vertical; + const ImVec2 canvasPos = ImGui::GetCursorScreenPos(); + const ImVec2 canvasSize = ImGui::GetContentRegionAvail(); + const float canvasSizeLength = isVertical ? ImGui::GetItemRectSize().y : canvasSize.x; + const ImVec2 scrollBarSize = isVertical ? ImVec2(14.f, canvasSizeLength) : ImVec2(canvasSizeLength, 14.f); + + ImGui::InvisibleButton(controlName, scrollBarSize); + const ImGuiID currentId = ImGui::GetID(controlName); + + const bool usingEditingId = currentId == editingId; + const bool canUseControl = usingEditingId || editingId == -1; + const bool movingScrollBar = usingEditingId ? movingScrollBarSvg : false; + const bool sizingRBar = usingEditingId ? sizingRBarSvg : false; + const bool sizingLBar = usingEditingId ? sizingLBarSvg : false; + const int componentIndex = isVertical ? 1 : 0; + const ImVec2 scrollBarMin = ImGui::GetItemRectMin(); + const ImVec2 scrollBarMax = ImGui::GetItemRectMax(); + const ImVec2 scrollBarA = ImVec2(scrollBarMin.x, scrollBarMin.y) - (isVertical ? ImVec2(2,0) : ImVec2(0,2)); + const ImVec2 scrollBarB = isVertical ? ImVec2(scrollBarMax.x - 1.f, scrollBarMin.y + canvasSizeLength) : ImVec2(scrollBarMin.x + canvasSizeLength, scrollBarMax.y - 1.f); + const float scrollStart = ((viewLower - lower) / (higher - lower)) * canvasSizeLength + scrollBarMin[componentIndex]; + const float scrollEnd = ((viewHigher - lower) / (higher - lower)) * canvasSizeLength + scrollBarMin[componentIndex]; + const float screenSize = scrollEnd - scrollStart; + const ImVec2 scrollTopLeft = isVertical ? ImVec2(scrollBarMin.x, scrollStart) : ImVec2(scrollStart, scrollBarMin.y); + const ImVec2 scrollBottomRight = isVertical ? ImVec2(scrollBarMax.x - 2.f, scrollEnd) : ImVec2(scrollEnd, scrollBarMax.y - 2.f); + const bool inScrollBar = canUseControl && ImRect(scrollTopLeft, scrollBottomRight).Contains(io.MousePos); + const ImRect scrollBarRect(scrollBarA, scrollBarB); + const float deltaScreen = io.MousePos[componentIndex] - scrollingSource; + const float deltaView = ((higher - lower) / canvasSizeLength) * deltaScreen; + const uint32_t barColor = ImGui::GetColorU32((inScrollBar || movingScrollBar) ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); + const float middleCoord = (scrollStart + scrollEnd) * 0.5f; + const bool insideControl = canUseControl && ImRect(scrollBarMin, scrollBarMax).Contains(io.MousePos); + const bool hasAnchors = !(flags & ImGuiZoomSliderFlags_NoAnchors); + const float viewMinSize = ((3.f * handleSize) / canvasSizeLength) * (higher - lower); + const auto ClipView = [lower, higher, &viewLower, &viewHigher]() { + if (viewLower < lower) + { + const float deltaClip = lower - viewLower; + viewLower += deltaClip; + viewHigher += deltaClip; + } + if (viewHigher > higher) + { + const float deltaClip = viewHigher - higher; + viewLower -= deltaClip; + viewHigher -= deltaClip; + } + }; + + bool onLeft = false; + bool onRight = false; + + draw_list->AddRectFilled(scrollBarA, scrollBarB, 0xFF101010, roundRadius); + draw_list->AddRectFilled(scrollBarA, scrollBarB, 0xFF222222, 0); + draw_list->AddRectFilled(scrollTopLeft, scrollBottomRight, barColor, roundRadius); + + if (!(flags & ImGuiZoomSliderFlags_NoMiddleCarets)) + { + for (float i = 0.5f; i < 3.f; i += 1.f) + { + const float coordA = middleCoord - handleSize * 0.5f; + const float coordB = middleCoord + handleSize * 0.5f; + ImVec2 base = scrollBarMin; + base.x += scrollBarSize.x * 0.25f * i; + base.y += scrollBarSize.y * 0.25f * i; + + if (isVertical) + { + draw_list->AddLine(ImVec2(base.x, coordA), ImVec2(base.x, coordB), ImGui::GetColorU32(ImGuiCol_SliderGrab)); + } + else + { + draw_list->AddLine(ImVec2(coordA, base.y), ImVec2(coordB, base.y), ImGui::GetColorU32(ImGuiCol_SliderGrab)); + } + } + } + + // Mouse wheel + if (io.MouseClicked[0] && insideControl && !inScrollBar) + { + const float ratio = (io.MousePos[componentIndex] - scrollBarMin[componentIndex]) / (scrollBarMax[componentIndex] - scrollBarMin[componentIndex]); + const float size = (higher - lower); + const float halfViewSize = (viewHigher - viewLower) * 0.5f; + const float middle = ratio * size + lower; + viewLower = middle - halfViewSize; + viewHigher = middle + halfViewSize; + ClipView(); + interacted = true; + } + + if (!(flags & ImGuiZoomSliderFlags_NoWheel) && inScrollBar && fabsf(io.MouseWheel) > 0.f) + { + const float ratio = (io.MousePos[componentIndex] - scrollStart) / (scrollEnd - scrollStart); + const float amount = io.MouseWheel * wheelRatio * (viewHigher - viewLower); + + viewLower -= ratio * amount; + viewHigher += (1.f - ratio) * amount; + ClipView(); + interacted = true; + } + + if (screenSize > handleSize * 2.f && hasAnchors) + { + const ImRect barHandleLeft(scrollTopLeft, isVertical ? ImVec2(scrollBottomRight.x, scrollTopLeft.y + handleSize) : ImVec2(scrollTopLeft.x + handleSize, scrollBottomRight.y)); + const ImRect barHandleRight(isVertical ? ImVec2(scrollTopLeft.x, scrollBottomRight.y - handleSize) : ImVec2(scrollBottomRight.x - handleSize, scrollTopLeft.y), scrollBottomRight); + + onLeft = barHandleLeft.Contains(io.MousePos); + onRight = barHandleRight.Contains(io.MousePos); + + draw_list->AddRectFilled(barHandleLeft.Min, barHandleLeft.Max, ImGui::GetColorU32((onLeft || sizingLBar) ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), roundRadius); + draw_list->AddRectFilled(barHandleRight.Min, barHandleRight.Max, ImGui::GetColorU32((onRight || sizingRBar) ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), roundRadius); + } + + if (sizingRBar) + { + if (!io.MouseDown[0]) + { + sizingRBarSvg = false; + editingId = (ImGuiID)-1; + } + else + { + viewHigher = ImMin(saveViewHigher + deltaView, higher); + } + } + else if (sizingLBar) + { + if (!io.MouseDown[0]) + { + sizingLBarSvg = false; + editingId = (ImGuiID)-1; + } + else + { + viewLower = ImMax(saveViewLower + deltaView, lower); + } + } + else + { + if (movingScrollBar) + { + if (!io.MouseDown[0]) + { + movingScrollBarSvg = false; + editingId = (ImGuiID)-1; + } + else + { + viewLower = saveViewLower + deltaView; + viewHigher = saveViewHigher + deltaView; + ClipView(); + } + } + else + { + if (inScrollBar && ImGui::IsMouseClicked(0)) + { + movingScrollBarSvg = true; + scrollingSource = io.MousePos[componentIndex]; + saveViewLower = viewLower; + saveViewHigher = viewHigher; + editingId = currentId; + } + if (!sizingRBar && onRight && ImGui::IsMouseClicked(0) && hasAnchors) + { + sizingRBarSvg = true; + editingId = currentId; + } + if (!sizingLBar && onLeft && ImGui::IsMouseClicked(0) && hasAnchors) + { + sizingLBarSvg = true; + editingId = currentId; + } + } + } + + // minimal size check + if ((viewHigher - viewLower) < viewMinSize) + { + const float middle = (viewLower + viewHigher) * 0.5f; + viewLower = middle - viewMinSize * 0.5f; + viewHigher = middle + viewMinSize * 0.5f; + ClipView(); + } + + return movingScrollBar || sizingRBar || sizingLBar || interacted; + } + +} // namespace diff --git a/Source/Dependencies/imgui/IconsFontAwesome5.h b/Source/Dependencies/imgui/IconsFontAwesome5.h new file mode 100644 index 000000000..b4614e5e9 --- /dev/null +++ b/Source/Dependencies/imgui/IconsFontAwesome5.h @@ -0,0 +1,1006 @@ +// Generated by https://github.com/juliettef/IconFontCppHeaders script GenerateIconFontCppHeaders.py for language C++11 +// from https://raw.githubusercontent.com/FortAwesome/Font-Awesome/master/metadata/icons.yml +// for use with https://github.com/FortAwesome/Font-Awesome/blob/master/webfonts/fa-solid-900.ttf, https://github.com/FortAwesome/Font-Awesome/blob/master/webfonts/fa-regular-400.ttf, +#pragma once + +#define FONT_ICON_FILE_NAME_FAR "fa-regular-400.ttf" +#define FONT_ICON_FILE_NAME_FAS "fa-solid-900.ttf" + +#define ICON_MIN_FA 0xf000 +#define ICON_MAX_FA 0xf976 +#define ICON_FA_AD u8"\uf641" +#define ICON_FA_ADDRESS_BOOK u8"\uf2b9" +#define ICON_FA_ADDRESS_CARD u8"\uf2bb" +#define ICON_FA_ADJUST u8"\uf042" +#define ICON_FA_AIR_FRESHENER u8"\uf5d0" +#define ICON_FA_ALIGN_CENTER u8"\uf037" +#define ICON_FA_ALIGN_JUSTIFY u8"\uf039" +#define ICON_FA_ALIGN_LEFT u8"\uf036" +#define ICON_FA_ALIGN_RIGHT u8"\uf038" +#define ICON_FA_ALLERGIES u8"\uf461" +#define ICON_FA_AMBULANCE u8"\uf0f9" +#define ICON_FA_AMERICAN_SIGN_LANGUAGE_INTERPRETING u8"\uf2a3" +#define ICON_FA_ANCHOR u8"\uf13d" +#define ICON_FA_ANGLE_DOUBLE_DOWN u8"\uf103" +#define ICON_FA_ANGLE_DOUBLE_LEFT u8"\uf100" +#define ICON_FA_ANGLE_DOUBLE_RIGHT u8"\uf101" +#define ICON_FA_ANGLE_DOUBLE_UP u8"\uf102" +#define ICON_FA_ANGLE_DOWN u8"\uf107" +#define ICON_FA_ANGLE_LEFT u8"\uf104" +#define ICON_FA_ANGLE_RIGHT u8"\uf105" +#define ICON_FA_ANGLE_UP u8"\uf106" +#define ICON_FA_ANGRY u8"\uf556" +#define ICON_FA_ANKH u8"\uf644" +#define ICON_FA_APPLE_ALT u8"\uf5d1" +#define ICON_FA_ARCHIVE u8"\uf187" +#define ICON_FA_ARCHWAY u8"\uf557" +#define ICON_FA_ARROW_ALT_CIRCLE_DOWN u8"\uf358" +#define ICON_FA_ARROW_ALT_CIRCLE_LEFT u8"\uf359" +#define ICON_FA_ARROW_ALT_CIRCLE_RIGHT u8"\uf35a" +#define ICON_FA_ARROW_ALT_CIRCLE_UP u8"\uf35b" +#define ICON_FA_ARROW_CIRCLE_DOWN u8"\uf0ab" +#define ICON_FA_ARROW_CIRCLE_LEFT u8"\uf0a8" +#define ICON_FA_ARROW_CIRCLE_RIGHT u8"\uf0a9" +#define ICON_FA_ARROW_CIRCLE_UP u8"\uf0aa" +#define ICON_FA_ARROW_DOWN u8"\uf063" +#define ICON_FA_ARROW_LEFT u8"\uf060" +#define ICON_FA_ARROW_RIGHT u8"\uf061" +#define ICON_FA_ARROW_UP u8"\uf062" +#define ICON_FA_ARROWS_ALT u8"\uf0b2" +#define ICON_FA_ARROWS_ALT_H u8"\uf337" +#define ICON_FA_ARROWS_ALT_V u8"\uf338" +#define ICON_FA_ASSISTIVE_LISTENING_SYSTEMS u8"\uf2a2" +#define ICON_FA_ASTERISK u8"\uf069" +#define ICON_FA_AT u8"\uf1fa" +#define ICON_FA_ATLAS u8"\uf558" +#define ICON_FA_ATOM u8"\uf5d2" +#define ICON_FA_AUDIO_DESCRIPTION u8"\uf29e" +#define ICON_FA_AWARD u8"\uf559" +#define ICON_FA_BABY u8"\uf77c" +#define ICON_FA_BABY_CARRIAGE u8"\uf77d" +#define ICON_FA_BACKSPACE u8"\uf55a" +#define ICON_FA_BACKWARD u8"\uf04a" +#define ICON_FA_BACON u8"\uf7e5" +#define ICON_FA_BAHAI u8"\uf666" +#define ICON_FA_BALANCE_SCALE u8"\uf24e" +#define ICON_FA_BALANCE_SCALE_LEFT u8"\uf515" +#define ICON_FA_BALANCE_SCALE_RIGHT u8"\uf516" +#define ICON_FA_BAN u8"\uf05e" +#define ICON_FA_BAND_AID u8"\uf462" +#define ICON_FA_BARCODE u8"\uf02a" +#define ICON_FA_BARS u8"\uf0c9" +#define ICON_FA_BASEBALL_BALL u8"\uf433" +#define ICON_FA_BASKETBALL_BALL u8"\uf434" +#define ICON_FA_BATH u8"\uf2cd" +#define ICON_FA_BATTERY_EMPTY u8"\uf244" +#define ICON_FA_BATTERY_FULL u8"\uf240" +#define ICON_FA_BATTERY_HALF u8"\uf242" +#define ICON_FA_BATTERY_QUARTER u8"\uf243" +#define ICON_FA_BATTERY_THREE_QUARTERS u8"\uf241" +#define ICON_FA_BED u8"\uf236" +#define ICON_FA_BEER u8"\uf0fc" +#define ICON_FA_BELL u8"\uf0f3" +#define ICON_FA_BELL_SLASH u8"\uf1f6" +#define ICON_FA_BEZIER_CURVE u8"\uf55b" +#define ICON_FA_BIBLE u8"\uf647" +#define ICON_FA_BICYCLE u8"\uf206" +#define ICON_FA_BIKING u8"\uf84a" +#define ICON_FA_BINOCULARS u8"\uf1e5" +#define ICON_FA_BIOHAZARD u8"\uf780" +#define ICON_FA_BIRTHDAY_CAKE u8"\uf1fd" +#define ICON_FA_BLENDER u8"\uf517" +#define ICON_FA_BLENDER_PHONE u8"\uf6b6" +#define ICON_FA_BLIND u8"\uf29d" +#define ICON_FA_BLOG u8"\uf781" +#define ICON_FA_BOLD u8"\uf032" +#define ICON_FA_BOLT u8"\uf0e7" +#define ICON_FA_BOMB u8"\uf1e2" +#define ICON_FA_BONE u8"\uf5d7" +#define ICON_FA_BONG u8"\uf55c" +#define ICON_FA_BOOK u8"\uf02d" +#define ICON_FA_BOOK_DEAD u8"\uf6b7" +#define ICON_FA_BOOK_MEDICAL u8"\uf7e6" +#define ICON_FA_BOOK_OPEN u8"\uf518" +#define ICON_FA_BOOK_READER u8"\uf5da" +#define ICON_FA_BOOKMARK u8"\uf02e" +#define ICON_FA_BORDER_ALL u8"\uf84c" +#define ICON_FA_BORDER_NONE u8"\uf850" +#define ICON_FA_BORDER_STYLE u8"\uf853" +#define ICON_FA_BOWLING_BALL u8"\uf436" +#define ICON_FA_BOX u8"\uf466" +#define ICON_FA_BOX_OPEN u8"\uf49e" +#define ICON_FA_BOX_TISSUE u8"\uf95b" +#define ICON_FA_BOXES u8"\uf468" +#define ICON_FA_BRAILLE u8"\uf2a1" +#define ICON_FA_BRAIN u8"\uf5dc" +#define ICON_FA_BREAD_SLICE u8"\uf7ec" +#define ICON_FA_BRIEFCASE u8"\uf0b1" +#define ICON_FA_BRIEFCASE_MEDICAL u8"\uf469" +#define ICON_FA_BROADCAST_TOWER u8"\uf519" +#define ICON_FA_BROOM u8"\uf51a" +#define ICON_FA_BRUSH u8"\uf55d" +#define ICON_FA_BUG u8"\uf188" +#define ICON_FA_BUILDING u8"\uf1ad" +#define ICON_FA_BULLHORN u8"\uf0a1" +#define ICON_FA_BULLSEYE u8"\uf140" +#define ICON_FA_BURN u8"\uf46a" +#define ICON_FA_BUS u8"\uf207" +#define ICON_FA_BUS_ALT u8"\uf55e" +#define ICON_FA_BUSINESS_TIME u8"\uf64a" +#define ICON_FA_CALCULATOR u8"\uf1ec" +#define ICON_FA_CALENDAR u8"\uf133" +#define ICON_FA_CALENDAR_ALT u8"\uf073" +#define ICON_FA_CALENDAR_CHECK u8"\uf274" +#define ICON_FA_CALENDAR_DAY u8"\uf783" +#define ICON_FA_CALENDAR_MINUS u8"\uf272" +#define ICON_FA_CALENDAR_PLUS u8"\uf271" +#define ICON_FA_CALENDAR_TIMES u8"\uf273" +#define ICON_FA_CALENDAR_WEEK u8"\uf784" +#define ICON_FA_CAMERA u8"\uf030" +#define ICON_FA_CAMERA_RETRO u8"\uf083" +#define ICON_FA_CAMPGROUND u8"\uf6bb" +#define ICON_FA_CANDY_CANE u8"\uf786" +#define ICON_FA_CANNABIS u8"\uf55f" +#define ICON_FA_CAPSULES u8"\uf46b" +#define ICON_FA_CAR u8"\uf1b9" +#define ICON_FA_CAR_ALT u8"\uf5de" +#define ICON_FA_CAR_BATTERY u8"\uf5df" +#define ICON_FA_CAR_CRASH u8"\uf5e1" +#define ICON_FA_CAR_SIDE u8"\uf5e4" +#define ICON_FA_CARAVAN u8"\uf8ff" +#define ICON_FA_CARET_DOWN u8"\uf0d7" +#define ICON_FA_CARET_LEFT u8"\uf0d9" +#define ICON_FA_CARET_RIGHT u8"\uf0da" +#define ICON_FA_CARET_SQUARE_DOWN u8"\uf150" +#define ICON_FA_CARET_SQUARE_LEFT u8"\uf191" +#define ICON_FA_CARET_SQUARE_RIGHT u8"\uf152" +#define ICON_FA_CARET_SQUARE_UP u8"\uf151" +#define ICON_FA_CARET_UP u8"\uf0d8" +#define ICON_FA_CARROT u8"\uf787" +#define ICON_FA_CART_ARROW_DOWN u8"\uf218" +#define ICON_FA_CART_PLUS u8"\uf217" +#define ICON_FA_CASH_REGISTER u8"\uf788" +#define ICON_FA_CAT u8"\uf6be" +#define ICON_FA_CERTIFICATE u8"\uf0a3" +#define ICON_FA_CHAIR u8"\uf6c0" +#define ICON_FA_CHALKBOARD u8"\uf51b" +#define ICON_FA_CHALKBOARD_TEACHER u8"\uf51c" +#define ICON_FA_CHARGING_STATION u8"\uf5e7" +#define ICON_FA_CHART_AREA u8"\uf1fe" +#define ICON_FA_CHART_BAR u8"\uf080" +#define ICON_FA_CHART_LINE u8"\uf201" +#define ICON_FA_CHART_PIE u8"\uf200" +#define ICON_FA_CHECK u8"\uf00c" +#define ICON_FA_CHECK_CIRCLE u8"\uf058" +#define ICON_FA_CHECK_DOUBLE u8"\uf560" +#define ICON_FA_CHECK_SQUARE u8"\uf14a" +#define ICON_FA_CHEESE u8"\uf7ef" +#define ICON_FA_CHESS u8"\uf439" +#define ICON_FA_CHESS_BISHOP u8"\uf43a" +#define ICON_FA_CHESS_BOARD u8"\uf43c" +#define ICON_FA_CHESS_KING u8"\uf43f" +#define ICON_FA_CHESS_KNIGHT u8"\uf441" +#define ICON_FA_CHESS_PAWN u8"\uf443" +#define ICON_FA_CHESS_QUEEN u8"\uf445" +#define ICON_FA_CHESS_ROOK u8"\uf447" +#define ICON_FA_CHEVRON_CIRCLE_DOWN u8"\uf13a" +#define ICON_FA_CHEVRON_CIRCLE_LEFT u8"\uf137" +#define ICON_FA_CHEVRON_CIRCLE_RIGHT u8"\uf138" +#define ICON_FA_CHEVRON_CIRCLE_UP u8"\uf139" +#define ICON_FA_CHEVRON_DOWN u8"\uf078" +#define ICON_FA_CHEVRON_LEFT u8"\uf053" +#define ICON_FA_CHEVRON_RIGHT u8"\uf054" +#define ICON_FA_CHEVRON_UP u8"\uf077" +#define ICON_FA_CHILD u8"\uf1ae" +#define ICON_FA_CHURCH u8"\uf51d" +#define ICON_FA_CIRCLE u8"\uf111" +#define ICON_FA_CIRCLE_NOTCH u8"\uf1ce" +#define ICON_FA_CITY u8"\uf64f" +#define ICON_FA_CLINIC_MEDICAL u8"\uf7f2" +#define ICON_FA_CLIPBOARD u8"\uf328" +#define ICON_FA_CLIPBOARD_CHECK u8"\uf46c" +#define ICON_FA_CLIPBOARD_LIST u8"\uf46d" +#define ICON_FA_CLOCK u8"\uf017" +#define ICON_FA_CLONE u8"\uf24d" +#define ICON_FA_CLOSED_CAPTIONING u8"\uf20a" +#define ICON_FA_CLOUD u8"\uf0c2" +#define ICON_FA_CLOUD_DOWNLOAD_ALT u8"\uf381" +#define ICON_FA_CLOUD_MEATBALL u8"\uf73b" +#define ICON_FA_CLOUD_MOON u8"\uf6c3" +#define ICON_FA_CLOUD_MOON_RAIN u8"\uf73c" +#define ICON_FA_CLOUD_RAIN u8"\uf73d" +#define ICON_FA_CLOUD_SHOWERS_HEAVY u8"\uf740" +#define ICON_FA_CLOUD_SUN u8"\uf6c4" +#define ICON_FA_CLOUD_SUN_RAIN u8"\uf743" +#define ICON_FA_CLOUD_UPLOAD_ALT u8"\uf382" +#define ICON_FA_COCKTAIL u8"\uf561" +#define ICON_FA_CODE u8"\uf121" +#define ICON_FA_CODE_BRANCH u8"\uf126" +#define ICON_FA_COFFEE u8"\uf0f4" +#define ICON_FA_COG u8"\uf013" +#define ICON_FA_COGS u8"\uf085" +#define ICON_FA_COINS u8"\uf51e" +#define ICON_FA_COLUMNS u8"\uf0db" +#define ICON_FA_COMMENT u8"\uf075" +#define ICON_FA_COMMENT_ALT u8"\uf27a" +#define ICON_FA_COMMENT_DOLLAR u8"\uf651" +#define ICON_FA_COMMENT_DOTS u8"\uf4ad" +#define ICON_FA_COMMENT_MEDICAL u8"\uf7f5" +#define ICON_FA_COMMENT_SLASH u8"\uf4b3" +#define ICON_FA_COMMENTS u8"\uf086" +#define ICON_FA_COMMENTS_DOLLAR u8"\uf653" +#define ICON_FA_COMPACT_DISC u8"\uf51f" +#define ICON_FA_COMPASS u8"\uf14e" +#define ICON_FA_COMPRESS u8"\uf066" +#define ICON_FA_COMPRESS_ALT u8"\uf422" +#define ICON_FA_COMPRESS_ARROWS_ALT u8"\uf78c" +#define ICON_FA_CONCIERGE_BELL u8"\uf562" +#define ICON_FA_COOKIE u8"\uf563" +#define ICON_FA_COOKIE_BITE u8"\uf564" +#define ICON_FA_COPY u8"\uf0c5" +#define ICON_FA_COPYRIGHT u8"\uf1f9" +#define ICON_FA_COUCH u8"\uf4b8" +#define ICON_FA_CREDIT_CARD u8"\uf09d" +#define ICON_FA_CROP u8"\uf125" +#define ICON_FA_CROP_ALT u8"\uf565" +#define ICON_FA_CROSS u8"\uf654" +#define ICON_FA_CROSSHAIRS u8"\uf05b" +#define ICON_FA_CROW u8"\uf520" +#define ICON_FA_CROWN u8"\uf521" +#define ICON_FA_CRUTCH u8"\uf7f7" +#define ICON_FA_CUBE u8"\uf1b2" +#define ICON_FA_CUBES u8"\uf1b3" +#define ICON_FA_CUT u8"\uf0c4" +#define ICON_FA_DATABASE u8"\uf1c0" +#define ICON_FA_DEAF u8"\uf2a4" +#define ICON_FA_DEMOCRAT u8"\uf747" +#define ICON_FA_DESKTOP u8"\uf108" +#define ICON_FA_DHARMACHAKRA u8"\uf655" +#define ICON_FA_DIAGNOSES u8"\uf470" +#define ICON_FA_DICE u8"\uf522" +#define ICON_FA_DICE_D20 u8"\uf6cf" +#define ICON_FA_DICE_D6 u8"\uf6d1" +#define ICON_FA_DICE_FIVE u8"\uf523" +#define ICON_FA_DICE_FOUR u8"\uf524" +#define ICON_FA_DICE_ONE u8"\uf525" +#define ICON_FA_DICE_SIX u8"\uf526" +#define ICON_FA_DICE_THREE u8"\uf527" +#define ICON_FA_DICE_TWO u8"\uf528" +#define ICON_FA_DIGITAL_TACHOGRAPH u8"\uf566" +#define ICON_FA_DIRECTIONS u8"\uf5eb" +#define ICON_FA_DISEASE u8"\uf7fa" +#define ICON_FA_DIVIDE u8"\uf529" +#define ICON_FA_DIZZY u8"\uf567" +#define ICON_FA_DNA u8"\uf471" +#define ICON_FA_DOG u8"\uf6d3" +#define ICON_FA_DOLLAR_SIGN u8"\uf155" +#define ICON_FA_DOLLY u8"\uf472" +#define ICON_FA_DOLLY_FLATBED u8"\uf474" +#define ICON_FA_DONATE u8"\uf4b9" +#define ICON_FA_DOOR_CLOSED u8"\uf52a" +#define ICON_FA_DOOR_OPEN u8"\uf52b" +#define ICON_FA_DOT_CIRCLE u8"\uf192" +#define ICON_FA_DOVE u8"\uf4ba" +#define ICON_FA_DOWNLOAD u8"\uf019" +#define ICON_FA_DRAFTING_COMPASS u8"\uf568" +#define ICON_FA_DRAGON u8"\uf6d5" +#define ICON_FA_DRAW_POLYGON u8"\uf5ee" +#define ICON_FA_DRUM u8"\uf569" +#define ICON_FA_DRUM_STEELPAN u8"\uf56a" +#define ICON_FA_DRUMSTICK_BITE u8"\uf6d7" +#define ICON_FA_DUMBBELL u8"\uf44b" +#define ICON_FA_DUMPSTER u8"\uf793" +#define ICON_FA_DUMPSTER_FIRE u8"\uf794" +#define ICON_FA_DUNGEON u8"\uf6d9" +#define ICON_FA_EDIT u8"\uf044" +#define ICON_FA_EGG u8"\uf7fb" +#define ICON_FA_EJECT u8"\uf052" +#define ICON_FA_ELLIPSIS_H u8"\uf141" +#define ICON_FA_ELLIPSIS_V u8"\uf142" +#define ICON_FA_ENVELOPE u8"\uf0e0" +#define ICON_FA_ENVELOPE_OPEN u8"\uf2b6" +#define ICON_FA_ENVELOPE_OPEN_TEXT u8"\uf658" +#define ICON_FA_ENVELOPE_SQUARE u8"\uf199" +#define ICON_FA_EQUALS u8"\uf52c" +#define ICON_FA_ERASER u8"\uf12d" +#define ICON_FA_ETHERNET u8"\uf796" +#define ICON_FA_EURO_SIGN u8"\uf153" +#define ICON_FA_EXCHANGE_ALT u8"\uf362" +#define ICON_FA_EXCLAMATION u8"\uf12a" +#define ICON_FA_EXCLAMATION_CIRCLE u8"\uf06a" +#define ICON_FA_EXCLAMATION_TRIANGLE u8"\uf071" +#define ICON_FA_EXPAND u8"\uf065" +#define ICON_FA_EXPAND_ALT u8"\uf424" +#define ICON_FA_EXPAND_ARROWS_ALT u8"\uf31e" +#define ICON_FA_EXTERNAL_LINK_ALT u8"\uf35d" +#define ICON_FA_EXTERNAL_LINK_SQUARE_ALT u8"\uf360" +#define ICON_FA_EYE u8"\uf06e" +#define ICON_FA_EYE_DROPPER u8"\uf1fb" +#define ICON_FA_EYE_SLASH u8"\uf070" +#define ICON_FA_FAN u8"\uf863" +#define ICON_FA_FAST_BACKWARD u8"\uf049" +#define ICON_FA_FAST_FORWARD u8"\uf050" +#define ICON_FA_FAUCET u8"\uf905" +#define ICON_FA_FAX u8"\uf1ac" +#define ICON_FA_FEATHER u8"\uf52d" +#define ICON_FA_FEATHER_ALT u8"\uf56b" +#define ICON_FA_FEMALE u8"\uf182" +#define ICON_FA_FIGHTER_JET u8"\uf0fb" +#define ICON_FA_FILE u8"\uf15b" +#define ICON_FA_FILE_ALT u8"\uf15c" +#define ICON_FA_FILE_ARCHIVE u8"\uf1c6" +#define ICON_FA_FILE_AUDIO u8"\uf1c7" +#define ICON_FA_FILE_CODE u8"\uf1c9" +#define ICON_FA_FILE_CONTRACT u8"\uf56c" +#define ICON_FA_FILE_CSV u8"\uf6dd" +#define ICON_FA_FILE_DOWNLOAD u8"\uf56d" +#define ICON_FA_FILE_EXCEL u8"\uf1c3" +#define ICON_FA_FILE_EXPORT u8"\uf56e" +#define ICON_FA_FILE_IMAGE u8"\uf1c5" +#define ICON_FA_FILE_IMPORT u8"\uf56f" +#define ICON_FA_FILE_INVOICE u8"\uf570" +#define ICON_FA_FILE_INVOICE_DOLLAR u8"\uf571" +#define ICON_FA_FILE_MEDICAL u8"\uf477" +#define ICON_FA_FILE_MEDICAL_ALT u8"\uf478" +#define ICON_FA_FILE_PDF u8"\uf1c1" +#define ICON_FA_FILE_POWERPOINT u8"\uf1c4" +#define ICON_FA_FILE_PRESCRIPTION u8"\uf572" +#define ICON_FA_FILE_SIGNATURE u8"\uf573" +#define ICON_FA_FILE_UPLOAD u8"\uf574" +#define ICON_FA_FILE_VIDEO u8"\uf1c8" +#define ICON_FA_FILE_WORD u8"\uf1c2" +#define ICON_FA_FILL u8"\uf575" +#define ICON_FA_FILL_DRIP u8"\uf576" +#define ICON_FA_FILM u8"\uf008" +#define ICON_FA_FILTER u8"\uf0b0" +#define ICON_FA_FINGERPRINT u8"\uf577" +#define ICON_FA_FIRE u8"\uf06d" +#define ICON_FA_FIRE_ALT u8"\uf7e4" +#define ICON_FA_FIRE_EXTINGUISHER u8"\uf134" +#define ICON_FA_FIRST_AID u8"\uf479" +#define ICON_FA_FISH u8"\uf578" +#define ICON_FA_FIST_RAISED u8"\uf6de" +#define ICON_FA_FLAG u8"\uf024" +#define ICON_FA_FLAG_CHECKERED u8"\uf11e" +#define ICON_FA_FLAG_USA u8"\uf74d" +#define ICON_FA_FLASK u8"\uf0c3" +#define ICON_FA_FLUSHED u8"\uf579" +#define ICON_FA_FOLDER u8"\uf07b" +#define ICON_FA_FOLDER_MINUS u8"\uf65d" +#define ICON_FA_FOLDER_OPEN u8"\uf07c" +#define ICON_FA_FOLDER_PLUS u8"\uf65e" +#define ICON_FA_FONT u8"\uf031" +#define ICON_FA_FONT_AWESOME_LOGO_FULL u8"\uf4e6" +#define ICON_FA_FOOTBALL_BALL u8"\uf44e" +#define ICON_FA_FORWARD u8"\uf04e" +#define ICON_FA_FROG u8"\uf52e" +#define ICON_FA_FROWN u8"\uf119" +#define ICON_FA_FROWN_OPEN u8"\uf57a" +#define ICON_FA_FUNNEL_DOLLAR u8"\uf662" +#define ICON_FA_FUTBOL u8"\uf1e3" +#define ICON_FA_GAMEPAD u8"\uf11b" +#define ICON_FA_GAS_PUMP u8"\uf52f" +#define ICON_FA_GAVEL u8"\uf0e3" +#define ICON_FA_GEM u8"\uf3a5" +#define ICON_FA_GENDERLESS u8"\uf22d" +#define ICON_FA_GHOST u8"\uf6e2" +#define ICON_FA_GIFT u8"\uf06b" +#define ICON_FA_GIFTS u8"\uf79c" +#define ICON_FA_GLASS_CHEERS u8"\uf79f" +#define ICON_FA_GLASS_MARTINI u8"\uf000" +#define ICON_FA_GLASS_MARTINI_ALT u8"\uf57b" +#define ICON_FA_GLASS_WHISKEY u8"\uf7a0" +#define ICON_FA_GLASSES u8"\uf530" +#define ICON_FA_GLOBE u8"\uf0ac" +#define ICON_FA_GLOBE_AFRICA u8"\uf57c" +#define ICON_FA_GLOBE_AMERICAS u8"\uf57d" +#define ICON_FA_GLOBE_ASIA u8"\uf57e" +#define ICON_FA_GLOBE_EUROPE u8"\uf7a2" +#define ICON_FA_GOLF_BALL u8"\uf450" +#define ICON_FA_GOPURAM u8"\uf664" +#define ICON_FA_GRADUATION_CAP u8"\uf19d" +#define ICON_FA_GREATER_THAN u8"\uf531" +#define ICON_FA_GREATER_THAN_EQUAL u8"\uf532" +#define ICON_FA_GRIMACE u8"\uf57f" +#define ICON_FA_GRIN u8"\uf580" +#define ICON_FA_GRIN_ALT u8"\uf581" +#define ICON_FA_GRIN_BEAM u8"\uf582" +#define ICON_FA_GRIN_BEAM_SWEAT u8"\uf583" +#define ICON_FA_GRIN_HEARTS u8"\uf584" +#define ICON_FA_GRIN_SQUINT u8"\uf585" +#define ICON_FA_GRIN_SQUINT_TEARS u8"\uf586" +#define ICON_FA_GRIN_STARS u8"\uf587" +#define ICON_FA_GRIN_TEARS u8"\uf588" +#define ICON_FA_GRIN_TONGUE u8"\uf589" +#define ICON_FA_GRIN_TONGUE_SQUINT u8"\uf58a" +#define ICON_FA_GRIN_TONGUE_WINK u8"\uf58b" +#define ICON_FA_GRIN_WINK u8"\uf58c" +#define ICON_FA_GRIP_HORIZONTAL u8"\uf58d" +#define ICON_FA_GRIP_LINES u8"\uf7a4" +#define ICON_FA_GRIP_LINES_VERTICAL u8"\uf7a5" +#define ICON_FA_GRIP_VERTICAL u8"\uf58e" +#define ICON_FA_GUITAR u8"\uf7a6" +#define ICON_FA_H_SQUARE u8"\uf0fd" +#define ICON_FA_HAMBURGER u8"\uf805" +#define ICON_FA_HAMMER u8"\uf6e3" +#define ICON_FA_HAMSA u8"\uf665" +#define ICON_FA_HAND_HOLDING u8"\uf4bd" +#define ICON_FA_HAND_HOLDING_HEART u8"\uf4be" +#define ICON_FA_HAND_HOLDING_MEDICAL u8"\uf95c" +#define ICON_FA_HAND_HOLDING_USD u8"\uf4c0" +#define ICON_FA_HAND_HOLDING_WATER u8"\uf4c1" +#define ICON_FA_HAND_LIZARD u8"\uf258" +#define ICON_FA_HAND_MIDDLE_FINGER u8"\uf806" +#define ICON_FA_HAND_PAPER u8"\uf256" +#define ICON_FA_HAND_PEACE u8"\uf25b" +#define ICON_FA_HAND_POINT_DOWN u8"\uf0a7" +#define ICON_FA_HAND_POINT_LEFT u8"\uf0a5" +#define ICON_FA_HAND_POINT_RIGHT u8"\uf0a4" +#define ICON_FA_HAND_POINT_UP u8"\uf0a6" +#define ICON_FA_HAND_POINTER u8"\uf25a" +#define ICON_FA_HAND_ROCK u8"\uf255" +#define ICON_FA_HAND_SCISSORS u8"\uf257" +#define ICON_FA_HAND_SPARKLES u8"\uf95d" +#define ICON_FA_HAND_SPOCK u8"\uf259" +#define ICON_FA_HANDS u8"\uf4c2" +#define ICON_FA_HANDS_HELPING u8"\uf4c4" +#define ICON_FA_HANDS_WASH u8"\uf95e" +#define ICON_FA_HANDSHAKE u8"\uf2b5" +#define ICON_FA_HANDSHAKE_ALT_SLASH u8"\uf95f" +#define ICON_FA_HANDSHAKE_SLASH u8"\uf960" +#define ICON_FA_HANUKIAH u8"\uf6e6" +#define ICON_FA_HARD_HAT u8"\uf807" +#define ICON_FA_HASHTAG u8"\uf292" +#define ICON_FA_HAT_COWBOY u8"\uf8c0" +#define ICON_FA_HAT_COWBOY_SIDE u8"\uf8c1" +#define ICON_FA_HAT_WIZARD u8"\uf6e8" +#define ICON_FA_HDD u8"\uf0a0" +#define ICON_FA_HEAD_SIDE_COUGH u8"\uf961" +#define ICON_FA_HEAD_SIDE_COUGH_SLASH u8"\uf962" +#define ICON_FA_HEAD_SIDE_MASK u8"\uf963" +#define ICON_FA_HEAD_SIDE_VIRUS u8"\uf964" +#define ICON_FA_HEADING u8"\uf1dc" +#define ICON_FA_HEADPHONES u8"\uf025" +#define ICON_FA_HEADPHONES_ALT u8"\uf58f" +#define ICON_FA_HEADSET u8"\uf590" +#define ICON_FA_HEART u8"\uf004" +#define ICON_FA_HEART_BROKEN u8"\uf7a9" +#define ICON_FA_HEARTBEAT u8"\uf21e" +#define ICON_FA_HELICOPTER u8"\uf533" +#define ICON_FA_HIGHLIGHTER u8"\uf591" +#define ICON_FA_HIKING u8"\uf6ec" +#define ICON_FA_HIPPO u8"\uf6ed" +#define ICON_FA_HISTORY u8"\uf1da" +#define ICON_FA_HOCKEY_PUCK u8"\uf453" +#define ICON_FA_HOLLY_BERRY u8"\uf7aa" +#define ICON_FA_HOME u8"\uf015" +#define ICON_FA_HORSE u8"\uf6f0" +#define ICON_FA_HORSE_HEAD u8"\uf7ab" +#define ICON_FA_HOSPITAL u8"\uf0f8" +#define ICON_FA_HOSPITAL_ALT u8"\uf47d" +#define ICON_FA_HOSPITAL_SYMBOL u8"\uf47e" +#define ICON_FA_HOSPITAL_USER u8"\uf80d" +#define ICON_FA_HOT_TUB u8"\uf593" +#define ICON_FA_HOTDOG u8"\uf80f" +#define ICON_FA_HOTEL u8"\uf594" +#define ICON_FA_HOURGLASS u8"\uf254" +#define ICON_FA_HOURGLASS_END u8"\uf253" +#define ICON_FA_HOURGLASS_HALF u8"\uf252" +#define ICON_FA_HOURGLASS_START u8"\uf251" +#define ICON_FA_HOUSE_DAMAGE u8"\uf6f1" +#define ICON_FA_HOUSE_USER u8"\uf965" +#define ICON_FA_HRYVNIA u8"\uf6f2" +#define ICON_FA_I_CURSOR u8"\uf246" +#define ICON_FA_ICE_CREAM u8"\uf810" +#define ICON_FA_ICICLES u8"\uf7ad" +#define ICON_FA_ICONS u8"\uf86d" +#define ICON_FA_ID_BADGE u8"\uf2c1" +#define ICON_FA_ID_CARD u8"\uf2c2" +#define ICON_FA_ID_CARD_ALT u8"\uf47f" +#define ICON_FA_IGLOO u8"\uf7ae" +#define ICON_FA_IMAGE u8"\uf03e" +#define ICON_FA_IMAGES u8"\uf302" +#define ICON_FA_INBOX u8"\uf01c" +#define ICON_FA_INDENT u8"\uf03c" +#define ICON_FA_INDUSTRY u8"\uf275" +#define ICON_FA_INFINITY u8"\uf534" +#define ICON_FA_INFO u8"\uf129" +#define ICON_FA_INFO_CIRCLE u8"\uf05a" +#define ICON_FA_ITALIC u8"\uf033" +#define ICON_FA_JEDI u8"\uf669" +#define ICON_FA_JOINT u8"\uf595" +#define ICON_FA_JOURNAL_WHILLS u8"\uf66a" +#define ICON_FA_KAABA u8"\uf66b" +#define ICON_FA_KEY u8"\uf084" +#define ICON_FA_KEYBOARD u8"\uf11c" +#define ICON_FA_KHANDA u8"\uf66d" +#define ICON_FA_KISS u8"\uf596" +#define ICON_FA_KISS_BEAM u8"\uf597" +#define ICON_FA_KISS_WINK_HEART u8"\uf598" +#define ICON_FA_KIWI_BIRD u8"\uf535" +#define ICON_FA_LANDMARK u8"\uf66f" +#define ICON_FA_LANGUAGE u8"\uf1ab" +#define ICON_FA_LAPTOP u8"\uf109" +#define ICON_FA_LAPTOP_CODE u8"\uf5fc" +#define ICON_FA_LAPTOP_HOUSE u8"\uf966" +#define ICON_FA_LAPTOP_MEDICAL u8"\uf812" +#define ICON_FA_LAUGH u8"\uf599" +#define ICON_FA_LAUGH_BEAM u8"\uf59a" +#define ICON_FA_LAUGH_SQUINT u8"\uf59b" +#define ICON_FA_LAUGH_WINK u8"\uf59c" +#define ICON_FA_LAYER_GROUP u8"\uf5fd" +#define ICON_FA_LEAF u8"\uf06c" +#define ICON_FA_LEMON u8"\uf094" +#define ICON_FA_LESS_THAN u8"\uf536" +#define ICON_FA_LESS_THAN_EQUAL u8"\uf537" +#define ICON_FA_LEVEL_DOWN_ALT u8"\uf3be" +#define ICON_FA_LEVEL_UP_ALT u8"\uf3bf" +#define ICON_FA_LIFE_RING u8"\uf1cd" +#define ICON_FA_LIGHTBULB u8"\uf0eb" +#define ICON_FA_LINK u8"\uf0c1" +#define ICON_FA_LIRA_SIGN u8"\uf195" +#define ICON_FA_LIST u8"\uf03a" +#define ICON_FA_LIST_ALT u8"\uf022" +#define ICON_FA_LIST_OL u8"\uf0cb" +#define ICON_FA_LIST_UL u8"\uf0ca" +#define ICON_FA_LOCATION_ARROW u8"\uf124" +#define ICON_FA_LOCK u8"\uf023" +#define ICON_FA_LOCK_OPEN u8"\uf3c1" +#define ICON_FA_LONG_ARROW_ALT_DOWN u8"\uf309" +#define ICON_FA_LONG_ARROW_ALT_LEFT u8"\uf30a" +#define ICON_FA_LONG_ARROW_ALT_RIGHT u8"\uf30b" +#define ICON_FA_LONG_ARROW_ALT_UP u8"\uf30c" +#define ICON_FA_LOW_VISION u8"\uf2a8" +#define ICON_FA_LUGGAGE_CART u8"\uf59d" +#define ICON_FA_LUNGS u8"\uf604" +#define ICON_FA_LUNGS_VIRUS u8"\uf967" +#define ICON_FA_MAGIC u8"\uf0d0" +#define ICON_FA_MAGNET u8"\uf076" +#define ICON_FA_MAIL_BULK u8"\uf674" +#define ICON_FA_MALE u8"\uf183" +#define ICON_FA_MAP u8"\uf279" +#define ICON_FA_MAP_MARKED u8"\uf59f" +#define ICON_FA_MAP_MARKED_ALT u8"\uf5a0" +#define ICON_FA_MAP_MARKER u8"\uf041" +#define ICON_FA_MAP_MARKER_ALT u8"\uf3c5" +#define ICON_FA_MAP_PIN u8"\uf276" +#define ICON_FA_MAP_SIGNS u8"\uf277" +#define ICON_FA_MARKER u8"\uf5a1" +#define ICON_FA_MARS u8"\uf222" +#define ICON_FA_MARS_DOUBLE u8"\uf227" +#define ICON_FA_MARS_STROKE u8"\uf229" +#define ICON_FA_MARS_STROKE_H u8"\uf22b" +#define ICON_FA_MARS_STROKE_V u8"\uf22a" +#define ICON_FA_MASK u8"\uf6fa" +#define ICON_FA_MEDAL u8"\uf5a2" +#define ICON_FA_MEDKIT u8"\uf0fa" +#define ICON_FA_MEH u8"\uf11a" +#define ICON_FA_MEH_BLANK u8"\uf5a4" +#define ICON_FA_MEH_ROLLING_EYES u8"\uf5a5" +#define ICON_FA_MEMORY u8"\uf538" +#define ICON_FA_MENORAH u8"\uf676" +#define ICON_FA_MERCURY u8"\uf223" +#define ICON_FA_METEOR u8"\uf753" +#define ICON_FA_MICROCHIP u8"\uf2db" +#define ICON_FA_MICROPHONE u8"\uf130" +#define ICON_FA_MICROPHONE_ALT u8"\uf3c9" +#define ICON_FA_MICROPHONE_ALT_SLASH u8"\uf539" +#define ICON_FA_MICROPHONE_SLASH u8"\uf131" +#define ICON_FA_MICROSCOPE u8"\uf610" +#define ICON_FA_MINUS u8"\uf068" +#define ICON_FA_MINUS_CIRCLE u8"\uf056" +#define ICON_FA_MINUS_SQUARE u8"\uf146" +#define ICON_FA_MITTEN u8"\uf7b5" +#define ICON_FA_MOBILE u8"\uf10b" +#define ICON_FA_MOBILE_ALT u8"\uf3cd" +#define ICON_FA_MONEY_BILL u8"\uf0d6" +#define ICON_FA_MONEY_BILL_ALT u8"\uf3d1" +#define ICON_FA_MONEY_BILL_WAVE u8"\uf53a" +#define ICON_FA_MONEY_BILL_WAVE_ALT u8"\uf53b" +#define ICON_FA_MONEY_CHECK u8"\uf53c" +#define ICON_FA_MONEY_CHECK_ALT u8"\uf53d" +#define ICON_FA_MONUMENT u8"\uf5a6" +#define ICON_FA_MOON u8"\uf186" +#define ICON_FA_MORTAR_PESTLE u8"\uf5a7" +#define ICON_FA_MOSQUE u8"\uf678" +#define ICON_FA_MOTORCYCLE u8"\uf21c" +#define ICON_FA_MOUNTAIN u8"\uf6fc" +#define ICON_FA_MOUSE u8"\uf8cc" +#define ICON_FA_MOUSE_POINTER u8"\uf245" +#define ICON_FA_MUG_HOT u8"\uf7b6" +#define ICON_FA_MUSIC u8"\uf001" +#define ICON_FA_NETWORK_WIRED u8"\uf6ff" +#define ICON_FA_NEUTER u8"\uf22c" +#define ICON_FA_NEWSPAPER u8"\uf1ea" +#define ICON_FA_NOT_EQUAL u8"\uf53e" +#define ICON_FA_NOTES_MEDICAL u8"\uf481" +#define ICON_FA_OBJECT_GROUP u8"\uf247" +#define ICON_FA_OBJECT_UNGROUP u8"\uf248" +#define ICON_FA_OIL_CAN u8"\uf613" +#define ICON_FA_OM u8"\uf679" +#define ICON_FA_OTTER u8"\uf700" +#define ICON_FA_OUTDENT u8"\uf03b" +#define ICON_FA_PAGER u8"\uf815" +#define ICON_FA_PAINT_BRUSH u8"\uf1fc" +#define ICON_FA_PAINT_ROLLER u8"\uf5aa" +#define ICON_FA_PALETTE u8"\uf53f" +#define ICON_FA_PALLET u8"\uf482" +#define ICON_FA_PAPER_PLANE u8"\uf1d8" +#define ICON_FA_PAPERCLIP u8"\uf0c6" +#define ICON_FA_PARACHUTE_BOX u8"\uf4cd" +#define ICON_FA_PARAGRAPH u8"\uf1dd" +#define ICON_FA_PARKING u8"\uf540" +#define ICON_FA_PASSPORT u8"\uf5ab" +#define ICON_FA_PASTAFARIANISM u8"\uf67b" +#define ICON_FA_PASTE u8"\uf0ea" +#define ICON_FA_PAUSE u8"\uf04c" +#define ICON_FA_PAUSE_CIRCLE u8"\uf28b" +#define ICON_FA_PAW u8"\uf1b0" +#define ICON_FA_PEACE u8"\uf67c" +#define ICON_FA_PEN u8"\uf304" +#define ICON_FA_PEN_ALT u8"\uf305" +#define ICON_FA_PEN_FANCY u8"\uf5ac" +#define ICON_FA_PEN_NIB u8"\uf5ad" +#define ICON_FA_PEN_SQUARE u8"\uf14b" +#define ICON_FA_PENCIL_ALT u8"\uf303" +#define ICON_FA_PENCIL_RULER u8"\uf5ae" +#define ICON_FA_PEOPLE_ARROWS u8"\uf968" +#define ICON_FA_PEOPLE_CARRY u8"\uf4ce" +#define ICON_FA_PEPPER_HOT u8"\uf816" +#define ICON_FA_PERCENT u8"\uf295" +#define ICON_FA_PERCENTAGE u8"\uf541" +#define ICON_FA_PERSON_BOOTH u8"\uf756" +#define ICON_FA_PHONE u8"\uf095" +#define ICON_FA_PHONE_ALT u8"\uf879" +#define ICON_FA_PHONE_SLASH u8"\uf3dd" +#define ICON_FA_PHONE_SQUARE u8"\uf098" +#define ICON_FA_PHONE_SQUARE_ALT u8"\uf87b" +#define ICON_FA_PHONE_VOLUME u8"\uf2a0" +#define ICON_FA_PHOTO_VIDEO u8"\uf87c" +#define ICON_FA_PIGGY_BANK u8"\uf4d3" +#define ICON_FA_PILLS u8"\uf484" +#define ICON_FA_PIZZA_SLICE u8"\uf818" +#define ICON_FA_PLACE_OF_WORSHIP u8"\uf67f" +#define ICON_FA_PLANE u8"\uf072" +#define ICON_FA_PLANE_ARRIVAL u8"\uf5af" +#define ICON_FA_PLANE_DEPARTURE u8"\uf5b0" +#define ICON_FA_PLANE_SLASH u8"\uf969" +#define ICON_FA_PLAY u8"\uf04b" +#define ICON_FA_PLAY_CIRCLE u8"\uf144" +#define ICON_FA_PLUG u8"\uf1e6" +#define ICON_FA_PLUS u8"\uf067" +#define ICON_FA_PLUS_CIRCLE u8"\uf055" +#define ICON_FA_PLUS_SQUARE u8"\uf0fe" +#define ICON_FA_PODCAST u8"\uf2ce" +#define ICON_FA_POLL u8"\uf681" +#define ICON_FA_POLL_H u8"\uf682" +#define ICON_FA_POO u8"\uf2fe" +#define ICON_FA_POO_STORM u8"\uf75a" +#define ICON_FA_POOP u8"\uf619" +#define ICON_FA_PORTRAIT u8"\uf3e0" +#define ICON_FA_POUND_SIGN u8"\uf154" +#define ICON_FA_POWER_OFF u8"\uf011" +#define ICON_FA_PRAY u8"\uf683" +#define ICON_FA_PRAYING_HANDS u8"\uf684" +#define ICON_FA_PRESCRIPTION u8"\uf5b1" +#define ICON_FA_PRESCRIPTION_BOTTLE u8"\uf485" +#define ICON_FA_PRESCRIPTION_BOTTLE_ALT u8"\uf486" +#define ICON_FA_PRINT u8"\uf02f" +#define ICON_FA_PROCEDURES u8"\uf487" +#define ICON_FA_PROJECT_DIAGRAM u8"\uf542" +#define ICON_FA_PUMP_MEDICAL u8"\uf96a" +#define ICON_FA_PUMP_SOAP u8"\uf96b" +#define ICON_FA_PUZZLE_PIECE u8"\uf12e" +#define ICON_FA_QRCODE u8"\uf029" +#define ICON_FA_QUESTION u8"\uf128" +#define ICON_FA_QUESTION_CIRCLE u8"\uf059" +#define ICON_FA_QUIDDITCH u8"\uf458" +#define ICON_FA_QUOTE_LEFT u8"\uf10d" +#define ICON_FA_QUOTE_RIGHT u8"\uf10e" +#define ICON_FA_QURAN u8"\uf687" +#define ICON_FA_RADIATION u8"\uf7b9" +#define ICON_FA_RADIATION_ALT u8"\uf7ba" +#define ICON_FA_RAINBOW u8"\uf75b" +#define ICON_FA_RANDOM u8"\uf074" +#define ICON_FA_RECEIPT u8"\uf543" +#define ICON_FA_RECORD_VINYL u8"\uf8d9" +#define ICON_FA_RECYCLE u8"\uf1b8" +#define ICON_FA_REDO u8"\uf01e" +#define ICON_FA_REDO_ALT u8"\uf2f9" +#define ICON_FA_REGISTERED u8"\uf25d" +#define ICON_FA_REMOVE_FORMAT u8"\uf87d" +#define ICON_FA_REPLY u8"\uf3e5" +#define ICON_FA_REPLY_ALL u8"\uf122" +#define ICON_FA_REPUBLICAN u8"\uf75e" +#define ICON_FA_RESTROOM u8"\uf7bd" +#define ICON_FA_RETWEET u8"\uf079" +#define ICON_FA_RIBBON u8"\uf4d6" +#define ICON_FA_RING u8"\uf70b" +#define ICON_FA_ROAD u8"\uf018" +#define ICON_FA_ROBOT u8"\uf544" +#define ICON_FA_ROCKET u8"\uf135" +#define ICON_FA_ROUTE u8"\uf4d7" +#define ICON_FA_RSS u8"\uf09e" +#define ICON_FA_RSS_SQUARE u8"\uf143" +#define ICON_FA_RUBLE_SIGN u8"\uf158" +#define ICON_FA_RULER u8"\uf545" +#define ICON_FA_RULER_COMBINED u8"\uf546" +#define ICON_FA_RULER_HORIZONTAL u8"\uf547" +#define ICON_FA_RULER_VERTICAL u8"\uf548" +#define ICON_FA_RUNNING u8"\uf70c" +#define ICON_FA_RUPEE_SIGN u8"\uf156" +#define ICON_FA_SAD_CRY u8"\uf5b3" +#define ICON_FA_SAD_TEAR u8"\uf5b4" +#define ICON_FA_SATELLITE u8"\uf7bf" +#define ICON_FA_SATELLITE_DISH u8"\uf7c0" +#define ICON_FA_SAVE u8"\uf0c7" +#define ICON_FA_SCHOOL u8"\uf549" +#define ICON_FA_SCREWDRIVER u8"\uf54a" +#define ICON_FA_SCROLL u8"\uf70e" +#define ICON_FA_SD_CARD u8"\uf7c2" +#define ICON_FA_SEARCH u8"\uf002" +#define ICON_FA_SEARCH_DOLLAR u8"\uf688" +#define ICON_FA_SEARCH_LOCATION u8"\uf689" +#define ICON_FA_SEARCH_MINUS u8"\uf010" +#define ICON_FA_SEARCH_PLUS u8"\uf00e" +#define ICON_FA_SEEDLING u8"\uf4d8" +#define ICON_FA_SERVER u8"\uf233" +#define ICON_FA_SHAPES u8"\uf61f" +#define ICON_FA_SHARE u8"\uf064" +#define ICON_FA_SHARE_ALT u8"\uf1e0" +#define ICON_FA_SHARE_ALT_SQUARE u8"\uf1e1" +#define ICON_FA_SHARE_SQUARE u8"\uf14d" +#define ICON_FA_SHEKEL_SIGN u8"\uf20b" +#define ICON_FA_SHIELD_ALT u8"\uf3ed" +#define ICON_FA_SHIELD_VIRUS u8"\uf96c" +#define ICON_FA_SHIP u8"\uf21a" +#define ICON_FA_SHIPPING_FAST u8"\uf48b" +#define ICON_FA_SHOE_PRINTS u8"\uf54b" +#define ICON_FA_SHOPPING_BAG u8"\uf290" +#define ICON_FA_SHOPPING_BASKET u8"\uf291" +#define ICON_FA_SHOPPING_CART u8"\uf07a" +#define ICON_FA_SHOWER u8"\uf2cc" +#define ICON_FA_SHUTTLE_VAN u8"\uf5b6" +#define ICON_FA_SIGN u8"\uf4d9" +#define ICON_FA_SIGN_IN_ALT u8"\uf2f6" +#define ICON_FA_SIGN_LANGUAGE u8"\uf2a7" +#define ICON_FA_SIGN_OUT_ALT u8"\uf2f5" +#define ICON_FA_SIGNAL u8"\uf012" +#define ICON_FA_SIGNATURE u8"\uf5b7" +#define ICON_FA_SIM_CARD u8"\uf7c4" +#define ICON_FA_SITEMAP u8"\uf0e8" +#define ICON_FA_SKATING u8"\uf7c5" +#define ICON_FA_SKIING u8"\uf7c9" +#define ICON_FA_SKIING_NORDIC u8"\uf7ca" +#define ICON_FA_SKULL u8"\uf54c" +#define ICON_FA_SKULL_CROSSBONES u8"\uf714" +#define ICON_FA_SLASH u8"\uf715" +#define ICON_FA_SLEIGH u8"\uf7cc" +#define ICON_FA_SLIDERS_H u8"\uf1de" +#define ICON_FA_SMILE u8"\uf118" +#define ICON_FA_SMILE_BEAM u8"\uf5b8" +#define ICON_FA_SMILE_WINK u8"\uf4da" +#define ICON_FA_SMOG u8"\uf75f" +#define ICON_FA_SMOKING u8"\uf48d" +#define ICON_FA_SMOKING_BAN u8"\uf54d" +#define ICON_FA_SMS u8"\uf7cd" +#define ICON_FA_SNOWBOARDING u8"\uf7ce" +#define ICON_FA_SNOWFLAKE u8"\uf2dc" +#define ICON_FA_SNOWMAN u8"\uf7d0" +#define ICON_FA_SNOWPLOW u8"\uf7d2" +#define ICON_FA_SOAP u8"\uf96e" +#define ICON_FA_SOCKS u8"\uf696" +#define ICON_FA_SOLAR_PANEL u8"\uf5ba" +#define ICON_FA_SORT u8"\uf0dc" +#define ICON_FA_SORT_ALPHA_DOWN u8"\uf15d" +#define ICON_FA_SORT_ALPHA_DOWN_ALT u8"\uf881" +#define ICON_FA_SORT_ALPHA_UP u8"\uf15e" +#define ICON_FA_SORT_ALPHA_UP_ALT u8"\uf882" +#define ICON_FA_SORT_AMOUNT_DOWN u8"\uf160" +#define ICON_FA_SORT_AMOUNT_DOWN_ALT u8"\uf884" +#define ICON_FA_SORT_AMOUNT_UP u8"\uf161" +#define ICON_FA_SORT_AMOUNT_UP_ALT u8"\uf885" +#define ICON_FA_SORT_DOWN u8"\uf0dd" +#define ICON_FA_SORT_NUMERIC_DOWN u8"\uf162" +#define ICON_FA_SORT_NUMERIC_DOWN_ALT u8"\uf886" +#define ICON_FA_SORT_NUMERIC_UP u8"\uf163" +#define ICON_FA_SORT_NUMERIC_UP_ALT u8"\uf887" +#define ICON_FA_SORT_UP u8"\uf0de" +#define ICON_FA_SPA u8"\uf5bb" +#define ICON_FA_SPACE_SHUTTLE u8"\uf197" +#define ICON_FA_SPELL_CHECK u8"\uf891" +#define ICON_FA_SPIDER u8"\uf717" +#define ICON_FA_SPINNER u8"\uf110" +#define ICON_FA_SPLOTCH u8"\uf5bc" +#define ICON_FA_SPRAY_CAN u8"\uf5bd" +#define ICON_FA_SQUARE u8"\uf0c8" +#define ICON_FA_SQUARE_FULL u8"\uf45c" +#define ICON_FA_SQUARE_ROOT_ALT u8"\uf698" +#define ICON_FA_STAMP u8"\uf5bf" +#define ICON_FA_STAR u8"\uf005" +#define ICON_FA_STAR_AND_CRESCENT u8"\uf699" +#define ICON_FA_STAR_HALF u8"\uf089" +#define ICON_FA_STAR_HALF_ALT u8"\uf5c0" +#define ICON_FA_STAR_OF_DAVID u8"\uf69a" +#define ICON_FA_STAR_OF_LIFE u8"\uf621" +#define ICON_FA_STEP_BACKWARD u8"\uf048" +#define ICON_FA_STEP_FORWARD u8"\uf051" +#define ICON_FA_STETHOSCOPE u8"\uf0f1" +#define ICON_FA_STICKY_NOTE u8"\uf249" +#define ICON_FA_STOP u8"\uf04d" +#define ICON_FA_STOP_CIRCLE u8"\uf28d" +#define ICON_FA_STOPWATCH u8"\uf2f2" +#define ICON_FA_STOPWATCH_20 u8"\uf96f" +#define ICON_FA_STORE u8"\uf54e" +#define ICON_FA_STORE_ALT u8"\uf54f" +#define ICON_FA_STORE_ALT_SLASH u8"\uf970" +#define ICON_FA_STORE_SLASH u8"\uf971" +#define ICON_FA_STREAM u8"\uf550" +#define ICON_FA_STREET_VIEW u8"\uf21d" +#define ICON_FA_STRIKETHROUGH u8"\uf0cc" +#define ICON_FA_STROOPWAFEL u8"\uf551" +#define ICON_FA_SUBSCRIPT u8"\uf12c" +#define ICON_FA_SUBWAY u8"\uf239" +#define ICON_FA_SUITCASE u8"\uf0f2" +#define ICON_FA_SUITCASE_ROLLING u8"\uf5c1" +#define ICON_FA_SUN u8"\uf185" +#define ICON_FA_SUPERSCRIPT u8"\uf12b" +#define ICON_FA_SURPRISE u8"\uf5c2" +#define ICON_FA_SWATCHBOOK u8"\uf5c3" +#define ICON_FA_SWIMMER u8"\uf5c4" +#define ICON_FA_SWIMMING_POOL u8"\uf5c5" +#define ICON_FA_SYNAGOGUE u8"\uf69b" +#define ICON_FA_SYNC u8"\uf021" +#define ICON_FA_SYNC_ALT u8"\uf2f1" +#define ICON_FA_SYRINGE u8"\uf48e" +#define ICON_FA_TABLE u8"\uf0ce" +#define ICON_FA_TABLE_TENNIS u8"\uf45d" +#define ICON_FA_TABLET u8"\uf10a" +#define ICON_FA_TABLET_ALT u8"\uf3fa" +#define ICON_FA_TABLETS u8"\uf490" +#define ICON_FA_TACHOMETER_ALT u8"\uf3fd" +#define ICON_FA_TAG u8"\uf02b" +#define ICON_FA_TAGS u8"\uf02c" +#define ICON_FA_TAPE u8"\uf4db" +#define ICON_FA_TASKS u8"\uf0ae" +#define ICON_FA_TAXI u8"\uf1ba" +#define ICON_FA_TEETH u8"\uf62e" +#define ICON_FA_TEETH_OPEN u8"\uf62f" +#define ICON_FA_TEMPERATURE_HIGH u8"\uf769" +#define ICON_FA_TEMPERATURE_LOW u8"\uf76b" +#define ICON_FA_TENGE u8"\uf7d7" +#define ICON_FA_TERMINAL u8"\uf120" +#define ICON_FA_TEXT_HEIGHT u8"\uf034" +#define ICON_FA_TEXT_WIDTH u8"\uf035" +#define ICON_FA_TH u8"\uf00a" +#define ICON_FA_TH_LARGE u8"\uf009" +#define ICON_FA_TH_LIST u8"\uf00b" +#define ICON_FA_THEATER_MASKS u8"\uf630" +#define ICON_FA_THERMOMETER u8"\uf491" +#define ICON_FA_THERMOMETER_EMPTY u8"\uf2cb" +#define ICON_FA_THERMOMETER_FULL u8"\uf2c7" +#define ICON_FA_THERMOMETER_HALF u8"\uf2c9" +#define ICON_FA_THERMOMETER_QUARTER u8"\uf2ca" +#define ICON_FA_THERMOMETER_THREE_QUARTERS u8"\uf2c8" +#define ICON_FA_THUMBS_DOWN u8"\uf165" +#define ICON_FA_THUMBS_UP u8"\uf164" +#define ICON_FA_THUMBTACK u8"\uf08d" +#define ICON_FA_TICKET_ALT u8"\uf3ff" +#define ICON_FA_TIMES u8"\uf00d" +#define ICON_FA_TIMES_CIRCLE u8"\uf057" +#define ICON_FA_TINT u8"\uf043" +#define ICON_FA_TINT_SLASH u8"\uf5c7" +#define ICON_FA_TIRED u8"\uf5c8" +#define ICON_FA_TOGGLE_OFF u8"\uf204" +#define ICON_FA_TOGGLE_ON u8"\uf205" +#define ICON_FA_TOILET u8"\uf7d8" +#define ICON_FA_TOILET_PAPER u8"\uf71e" +#define ICON_FA_TOILET_PAPER_SLASH u8"\uf972" +#define ICON_FA_TOOLBOX u8"\uf552" +#define ICON_FA_TOOLS u8"\uf7d9" +#define ICON_FA_TOOTH u8"\uf5c9" +#define ICON_FA_TORAH u8"\uf6a0" +#define ICON_FA_TORII_GATE u8"\uf6a1" +#define ICON_FA_TRACTOR u8"\uf722" +#define ICON_FA_TRADEMARK u8"\uf25c" +#define ICON_FA_TRAFFIC_LIGHT u8"\uf637" +#define ICON_FA_TRAILER u8"\uf941" +#define ICON_FA_TRAIN u8"\uf238" +#define ICON_FA_TRAM u8"\uf7da" +#define ICON_FA_TRANSGENDER u8"\uf224" +#define ICON_FA_TRANSGENDER_ALT u8"\uf225" +#define ICON_FA_TRASH u8"\uf1f8" +#define ICON_FA_TRASH_ALT u8"\uf2ed" +#define ICON_FA_TRASH_RESTORE u8"\uf829" +#define ICON_FA_TRASH_RESTORE_ALT u8"\uf82a" +#define ICON_FA_TREE u8"\uf1bb" +#define ICON_FA_TROPHY u8"\uf091" +#define ICON_FA_TRUCK u8"\uf0d1" +#define ICON_FA_TRUCK_LOADING u8"\uf4de" +#define ICON_FA_TRUCK_MONSTER u8"\uf63b" +#define ICON_FA_TRUCK_MOVING u8"\uf4df" +#define ICON_FA_TRUCK_PICKUP u8"\uf63c" +#define ICON_FA_TSHIRT u8"\uf553" +#define ICON_FA_TTY u8"\uf1e4" +#define ICON_FA_TV u8"\uf26c" +#define ICON_FA_UMBRELLA u8"\uf0e9" +#define ICON_FA_UMBRELLA_BEACH u8"\uf5ca" +#define ICON_FA_UNDERLINE u8"\uf0cd" +#define ICON_FA_UNDO u8"\uf0e2" +#define ICON_FA_UNDO_ALT u8"\uf2ea" +#define ICON_FA_UNIVERSAL_ACCESS u8"\uf29a" +#define ICON_FA_UNIVERSITY u8"\uf19c" +#define ICON_FA_UNLINK u8"\uf127" +#define ICON_FA_UNLOCK u8"\uf09c" +#define ICON_FA_UNLOCK_ALT u8"\uf13e" +#define ICON_FA_UPLOAD u8"\uf093" +#define ICON_FA_USER u8"\uf007" +#define ICON_FA_USER_ALT u8"\uf406" +#define ICON_FA_USER_ALT_SLASH u8"\uf4fa" +#define ICON_FA_USER_ASTRONAUT u8"\uf4fb" +#define ICON_FA_USER_CHECK u8"\uf4fc" +#define ICON_FA_USER_CIRCLE u8"\uf2bd" +#define ICON_FA_USER_CLOCK u8"\uf4fd" +#define ICON_FA_USER_COG u8"\uf4fe" +#define ICON_FA_USER_EDIT u8"\uf4ff" +#define ICON_FA_USER_FRIENDS u8"\uf500" +#define ICON_FA_USER_GRADUATE u8"\uf501" +#define ICON_FA_USER_INJURED u8"\uf728" +#define ICON_FA_USER_LOCK u8"\uf502" +#define ICON_FA_USER_MD u8"\uf0f0" +#define ICON_FA_USER_MINUS u8"\uf503" +#define ICON_FA_USER_NINJA u8"\uf504" +#define ICON_FA_USER_NURSE u8"\uf82f" +#define ICON_FA_USER_PLUS u8"\uf234" +#define ICON_FA_USER_SECRET u8"\uf21b" +#define ICON_FA_USER_SHIELD u8"\uf505" +#define ICON_FA_USER_SLASH u8"\uf506" +#define ICON_FA_USER_TAG u8"\uf507" +#define ICON_FA_USER_TIE u8"\uf508" +#define ICON_FA_USER_TIMES u8"\uf235" +#define ICON_FA_USERS u8"\uf0c0" +#define ICON_FA_USERS_COG u8"\uf509" +#define ICON_FA_UTENSIL_SPOON u8"\uf2e5" +#define ICON_FA_UTENSILS u8"\uf2e7" +#define ICON_FA_VECTOR_SQUARE u8"\uf5cb" +#define ICON_FA_VENUS u8"\uf221" +#define ICON_FA_VENUS_DOUBLE u8"\uf226" +#define ICON_FA_VENUS_MARS u8"\uf228" +#define ICON_FA_VIAL u8"\uf492" +#define ICON_FA_VIALS u8"\uf493" +#define ICON_FA_VIDEO u8"\uf03d" +#define ICON_FA_VIDEO_SLASH u8"\uf4e2" +#define ICON_FA_VIHARA u8"\uf6a7" +#define ICON_FA_VIRUS u8"\uf974" +#define ICON_FA_VIRUS_SLASH u8"\uf975" +#define ICON_FA_VIRUSES u8"\uf976" +#define ICON_FA_VOICEMAIL u8"\uf897" +#define ICON_FA_VOLLEYBALL_BALL u8"\uf45f" +#define ICON_FA_VOLUME_DOWN u8"\uf027" +#define ICON_FA_VOLUME_MUTE u8"\uf6a9" +#define ICON_FA_VOLUME_OFF u8"\uf026" +#define ICON_FA_VOLUME_UP u8"\uf028" +#define ICON_FA_VOTE_YEA u8"\uf772" +#define ICON_FA_VR_CARDBOARD u8"\uf729" +#define ICON_FA_WALKING u8"\uf554" +#define ICON_FA_WALLET u8"\uf555" +#define ICON_FA_WAREHOUSE u8"\uf494" +#define ICON_FA_WATER u8"\uf773" +#define ICON_FA_WAVE_SQUARE u8"\uf83e" +#define ICON_FA_WEIGHT u8"\uf496" +#define ICON_FA_WEIGHT_HANGING u8"\uf5cd" +#define ICON_FA_WHEELCHAIR u8"\uf193" +#define ICON_FA_WIFI u8"\uf1eb" +#define ICON_FA_WIND u8"\uf72e" +#define ICON_FA_WINDOW_CLOSE u8"\uf410" +#define ICON_FA_WINDOW_MAXIMIZE u8"\uf2d0" +#define ICON_FA_WINDOW_MINIMIZE u8"\uf2d1" +#define ICON_FA_WINDOW_RESTORE u8"\uf2d2" +#define ICON_FA_WINE_BOTTLE u8"\uf72f" +#define ICON_FA_WINE_GLASS u8"\uf4e3" +#define ICON_FA_WINE_GLASS_ALT u8"\uf5ce" +#define ICON_FA_WON_SIGN u8"\uf159" +#define ICON_FA_WRENCH u8"\uf0ad" +#define ICON_FA_X_RAY u8"\uf497" +#define ICON_FA_YEN_SIGN u8"\uf157" +#define ICON_FA_YIN_YANG u8"\uf6ad" diff --git a/Source/Dependencies/imgui/example.cpp b/Source/Dependencies/imgui/example.cpp deleted file mode 100644 index e88d94258..000000000 --- a/Source/Dependencies/imgui/example.cpp +++ /dev/null @@ -1,203 +0,0 @@ -// Dear ImGui: standalone example application for SDL2 + OpenGL -// (SDL is a cross-platform general purpose library for handling windows, inputs, OpenGL/Vulkan/Metal graphics context creation, etc.) -// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. -// Read online: https://github.com/ocornut/imgui/tree/master/docs - -#include "imgui.h" -#include "imgui_impl_sdl.h" -#include "imgui_impl_opengl3.h" -#include -#include -#if defined(IMGUI_IMPL_OPENGL_ES2) -#include -#else -#include -#endif - -// Main code -int main(int, char**) -{ - // Setup SDL - // (Some versions of SDL before <2.0.10 appears to have performance/stalling issues on a minority of Windows systems, - // depending on whether SDL_INIT_GAMECONTROLLER is enabled or disabled.. updating to latest version of SDL is recommended!) - if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) != 0) - { - printf("Error: %s\n", SDL_GetError()); - return -1; - } - - // Decide GL+GLSL versions -#if defined(IMGUI_IMPL_OPENGL_ES2) - // GL ES 2.0 + GLSL 100 - const char* glsl_version = "#version 100"; - SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); -#elif defined(__APPLE__) - // GL 3.2 Core + GLSL 150 - const char* glsl_version = "#version 150"; - SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); // Always required on Mac - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); -#else - // GL 3.0 + GLSL 130 - const char* glsl_version = "#version 130"; - SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); -#endif - - // Create window with graphics context - SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); - SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); - SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); - SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL2+OpenGL3 example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, window_flags); - SDL_GLContext gl_context = SDL_GL_CreateContext(window); - SDL_GL_MakeCurrent(window, gl_context); - SDL_GL_SetSwapInterval(1); // Enable vsync - - // Setup Dear ImGui context - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); - ImGuiIO& io = ImGui::GetIO(); (void)io; - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls - //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls - io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking - io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows - //io.ConfigViewportsNoAutoMerge = true; - //io.ConfigViewportsNoTaskBarIcon = true; - - // Setup Dear ImGui style - ImGui::StyleColorsDark(); - //ImGui::StyleColorsClassic(); - - // When viewports are enabled we tweak WindowRounding/WindowBg so platform windows can look identical to regular ones. - ImGuiStyle& style = ImGui::GetStyle(); - if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) - { - style.WindowRounding = 0.0f; - style.Colors[ImGuiCol_WindowBg].w = 1.0f; - } - - // Setup Platform/Renderer backends - ImGui_ImplSDL2_InitForOpenGL(window, gl_context); - ImGui_ImplOpenGL3_Init(glsl_version); - - // Load Fonts - // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. - // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. - // - If the file cannot be loaded, the function will return NULL. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). - // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. - // - Read 'docs/FONTS.md' for more instructions and details. - // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! - //io.Fonts->AddFontDefault(); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/ProggyTiny.ttf", 10.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, NULL, io.Fonts->GetGlyphRangesJapanese()); - //IM_ASSERT(font != NULL); - - // Our state - bool show_demo_window = true; - bool show_another_window = false; - ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); - - // Main loop - bool done = false; - while (!done) - { - // Poll and handle events (inputs, window resize, etc.) - // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs. - // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application. - // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application. - // Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags. - SDL_Event event; - while (SDL_PollEvent(&event)) - { - ImGui_ImplSDL2_ProcessEvent(&event); - if (event.type == SDL_QUIT) - done = true; - if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window)) - done = true; - } - - // Start the Dear ImGui frame - ImGui_ImplOpenGL3_NewFrame(); - ImGui_ImplSDL2_NewFrame(); - ImGui::NewFrame(); - - // 1. Show the big demo window (Most of the sample code is in ImGui::ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!). - if (show_demo_window) - ImGui::ShowDemoWindow(&show_demo_window); - - // 2. Show a simple window that we create ourselves. We use a Begin/End pair to created a named window. - { - static float f = 0.0f; - static int counter = 0; - - ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it. - - ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too) - ImGui::Checkbox("Demo Window", &show_demo_window); // Edit bools storing our window open/close state - ImGui::Checkbox("Another Window", &show_another_window); - - ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f - ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color - - if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated) - counter++; - ImGui::SameLine(); - ImGui::Text("counter = %d", counter); - - ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate); - ImGui::End(); - } - - // 3. Show another simple window. - if (show_another_window) - { - ImGui::Begin("Another Window", &show_another_window); // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked) - ImGui::Text("Hello from another window!"); - if (ImGui::Button("Close Me")) - show_another_window = false; - ImGui::End(); - } - - // Rendering - ImGui::Render(); - glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y); - glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); - glClear(GL_COLOR_BUFFER_BIT); - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - - // Update and Render additional Platform Windows - // (Platform functions may change the current OpenGL context, so we save/restore it to make it easier to paste this code elsewhere. - // For this specific demo app we could also call SDL_GL_MakeCurrent(window, gl_context) directly) - if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) - { - SDL_Window* backup_current_window = SDL_GL_GetCurrentWindow(); - SDL_GLContext backup_current_context = SDL_GL_GetCurrentContext(); - ImGui::UpdatePlatformWindows(); - ImGui::RenderPlatformWindowsDefault(); - SDL_GL_MakeCurrent(backup_current_window, backup_current_context); - } - - SDL_GL_SwapWindow(window); - } - - // Cleanup - ImGui_ImplOpenGL3_Shutdown(); - ImGui_ImplSDL2_Shutdown(); - ImGui::DestroyContext(); - - SDL_GL_DeleteContext(gl_context); - SDL_DestroyWindow(window); - SDL_Quit(); - - return 0; -} diff --git a/Source/Dependencies/imgui/imconfig.h b/Source/Dependencies/imgui/imconfig.h index 7082c5507..a99728c90 100644 --- a/Source/Dependencies/imgui/imconfig.h +++ b/Source/Dependencies/imgui/imconfig.h @@ -79,15 +79,14 @@ //---- Define constructor and implicit cast operators to convert back<>forth between your math types and ImVec2/ImVec4. // This will be inlined as part of ImVec2 and ImVec4 class declarations. -/* + #define IM_VEC2_CLASS_EXTRA \ - ImVec2(const MyVec2& f) { x = f.x; y = f.y; } \ - operator MyVec2() const { return MyVec2(x,y); } + ImVec2 operator+(const ImVec2& rhs) { return ImVec2(x + rhs.x, y + rhs.y); } -#define IM_VEC4_CLASS_EXTRA \ +/*#define IM_VEC4_CLASS_EXTRA \ ImVec4(const MyVec4& f) { x = f.x; y = f.y; z = f.z; w = f.w; } \ - operator MyVec4() const { return MyVec4(x,y,z,w); } -*/ + operator MyVec4() const { return MyVec4(x,y,z,w); }*/ + //---- Use 32-bit vertex indices (default is 16-bit) is one way to allow large meshes with more than 64K vertices. // Your renderer backend will need to support it (most example renderer backends support both 16/32-bit indices). diff --git a/Source/Dependencies/optick/include/optick.config.h b/Source/Dependencies/optick/include/optick.config.h new file mode 100644 index 000000000..0270bf94d --- /dev/null +++ b/Source/Dependencies/optick/include/optick.config.h @@ -0,0 +1,73 @@ +// The MIT License(MIT) +// +// Copyright(c) 2019 Vadim Slyusarev +// +// 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. + +#pragma once +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// GLOBAL SETTINGS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// [x] USE_OPTICK - (Master Switch) +// [x] OPTICK_ENABLE_TRACING - (Enable Kernel-level tracing) +// [x] OPTICK_ENABLE_GPU_D3D12 - (GPU D3D12) +// [x] OPTICK_ENABLE_GPU_VULKAN - (GPU VULKAN) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// MASTER SWITCH - use it for disabling profiler in final builds // +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#if !defined(USE_OPTICK) +#define USE_OPTICK (1) +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Enable Low-level platform-specific tracing (Switch Contexts, Autosampling, etc.) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#if !defined(OPTICK_ENABLE_TRACING) +#define OPTICK_ENABLE_TRACING (USE_OPTICK /*&& 0*/) +#endif //OPTICK_ENABLE_TRACING +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// GPU Counters +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#if !defined(OPTICK_ENABLE_GPU) +#define OPTICK_ENABLE_GPU (USE_OPTICK /*&& 0*/) +#endif //OPTICK_ENABLE_GPU + +// D3D12 +#if !defined(OPTICK_ENABLE_GPU_D3D12) +#if defined(_MSC_VER) +#define OPTICK_ENABLE_GPU_D3D12 (OPTICK_ENABLE_GPU /*&& 0*/) +#else +#define OPTICK_ENABLE_GPU_D3D12 (0) +#endif +#endif + +// VULKAN +#if !defined(OPTICK_ENABLE_GPU_VULKAN) +#if defined(_MSC_VER) +#define OPTICK_ENABLE_GPU_VULKAN (OPTICK_ENABLE_GPU /*&& 0*/) +#else +#define OPTICK_ENABLE_GPU_VULKAN (0) +#endif +#endif + diff --git a/Source/Dependencies/optick/include/optick.h b/Source/Dependencies/optick/include/optick.h new file mode 100644 index 000000000..da6d53120 --- /dev/null +++ b/Source/Dependencies/optick/include/optick.h @@ -0,0 +1,1095 @@ +// The MIT License(MIT) +// +// Copyright(c) 2019 Vadim Slyusarev +// +// 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. + +#pragma once +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Config +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#include "optick.config.h" + +#if USE_OPTICK +#include +#include + +#if defined(__clang__) || defined(__GNUC__) +# define OPTICK_GCC (1) +# if defined(__APPLE_CC__) +# define OPTICK_OSX (1) +# elif defined(__linux__) +# define OPTICK_LINUX (1) +# elif defined(__FreeBSD__) +# define OPTICK_FREEBSD (1) +# endif +#elif defined(_MSC_VER) +# define OPTICK_MSVC (1) +# if defined(_DURANGO) +# define OPTICK_PC (0) +# else +# define OPTICK_PC (1) +#endif +#else +#error Compiler not supported +#endif + +//////////////////////////////////////////////////////////////////////// +// Target Platform +//////////////////////////////////////////////////////////////////////// + +#if defined(OPTICK_GCC) +#define OPTICK_FUNC __PRETTY_FUNCTION__ +#elif defined(OPTICK_MSVC) +#define OPTICK_FUNC __FUNCSIG__ +#else +#error Compiler not supported +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// EXPORTS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#if defined(OPTICK_EXPORTS) && defined(OPTICK_MSVC) +#define OPTICK_API __declspec(dllexport) +#else +#define OPTICK_API +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#define OPTICK_CONCAT_IMPL(x, y) x##y +#define OPTICK_CONCAT(x, y) OPTICK_CONCAT_IMPL(x, y) + +#if defined(OPTICK_MSVC) +#define OPTICK_INLINE __forceinline +#elif defined(OPTICK_GCC) +#define OPTICK_INLINE __attribute__((always_inline)) inline +#else +#error Compiler is not supported +#endif + + +// Vulkan Forward Declarations +#define OPTICK_DEFINE_HANDLE(object) typedef struct object##_T *object; +OPTICK_DEFINE_HANDLE(VkDevice); +OPTICK_DEFINE_HANDLE(VkPhysicalDevice); +OPTICK_DEFINE_HANDLE(VkQueue); +OPTICK_DEFINE_HANDLE(VkCommandBuffer); +OPTICK_DEFINE_HANDLE(VkQueryPool); +OPTICK_DEFINE_HANDLE(VkCommandPool); +OPTICK_DEFINE_HANDLE(VkFence); + +struct VkPhysicalDeviceProperties; +struct VkQueryPoolCreateInfo; +struct VkAllocationCallbacks; +struct VkCommandPoolCreateInfo; +struct VkCommandBufferAllocateInfo; +struct VkFenceCreateInfo; +struct VkSubmitInfo; +struct VkCommandBufferBeginInfo; + +#ifndef VKAPI_PTR +#if defined(_WIN32) + // On Windows, Vulkan commands use the stdcall convention + #define VKAPI_PTR __stdcall +#else + #define VKAPI_PTR +#endif +#endif + +typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceProperties_)(VkPhysicalDevice physicalDevice, VkPhysicalDeviceProperties* pProperties); +typedef int32_t (VKAPI_PTR *PFN_vkCreateQueryPool_)(VkDevice device, const VkQueryPoolCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkQueryPool* pQueryPool); +typedef int32_t (VKAPI_PTR *PFN_vkCreateCommandPool_)(VkDevice device, const VkCommandPoolCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkCommandPool* pCommandPool); +typedef int32_t (VKAPI_PTR *PFN_vkAllocateCommandBuffers_)(VkDevice device, const VkCommandBufferAllocateInfo* pAllocateInfo, VkCommandBuffer* pCommandBuffers); +typedef int32_t (VKAPI_PTR *PFN_vkCreateFence_)(VkDevice device, const VkFenceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkFence* pFence); +typedef void (VKAPI_PTR *PFN_vkCmdResetQueryPool_)(VkCommandBuffer commandBuffer, VkQueryPool queryPool, uint32_t firstQuery, uint32_t queryCount); +typedef int32_t (VKAPI_PTR *PFN_vkQueueSubmit_)(VkQueue queue, uint32_t submitCount, const VkSubmitInfo* pSubmits, VkFence fence); +typedef int32_t (VKAPI_PTR *PFN_vkWaitForFences_)(VkDevice device, uint32_t fenceCount, const VkFence* pFences, uint32_t waitAll, uint64_t timeout); +typedef int32_t (VKAPI_PTR *PFN_vkResetCommandBuffer_)(VkCommandBuffer commandBuffer, uint32_t flags); +typedef void (VKAPI_PTR *PFN_vkCmdWriteTimestamp_)(VkCommandBuffer commandBuffer, uint32_t pipelineStage, VkQueryPool queryPool, uint32_t query); +typedef int32_t (VKAPI_PTR *PFN_vkGetQueryPoolResults_)(VkDevice device, VkQueryPool queryPool, uint32_t firstQuery, uint32_t queryCount, size_t dataSize, void* pData, uint64_t stride, uint32_t flags); +typedef int32_t (VKAPI_PTR *PFN_vkBeginCommandBuffer_)(VkCommandBuffer commandBuffer, const VkCommandBufferBeginInfo* pBeginInfo); +typedef int32_t (VKAPI_PTR *PFN_vkEndCommandBuffer_)(VkCommandBuffer commandBuffer); +typedef int32_t (VKAPI_PTR *PFN_vkResetFences_)(VkDevice device, uint32_t fenceCount, const VkFence* pFences); +typedef void (VKAPI_PTR *PFN_vkDestroyCommandPool_)(VkDevice device, VkCommandPool commandPool, const VkAllocationCallbacks* pAllocator); +typedef void (VKAPI_PTR *PFN_vkDestroyQueryPool_)(VkDevice device, VkQueryPool queryPool, const VkAllocationCallbacks* pAllocator); +typedef void (VKAPI_PTR *PFN_vkDestroyFence_)(VkDevice device, VkFence fence, const VkAllocationCallbacks* pAllocator); +typedef void (VKAPI_PTR *PFN_vkFreeCommandBuffers_)(VkDevice device, VkCommandPool commandPool, uint32_t commandBufferCount, const VkCommandBuffer* pCommandBuffers); + +// D3D12 Forward Declarations +struct ID3D12CommandList; +struct ID3D12Device; +struct ID3D12CommandQueue; +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +namespace Optick +{ + struct OPTICK_API VulkanFunctions + { + PFN_vkGetPhysicalDeviceProperties_ vkGetPhysicalDeviceProperties; + PFN_vkCreateQueryPool_ vkCreateQueryPool; + PFN_vkCreateCommandPool_ vkCreateCommandPool; + PFN_vkAllocateCommandBuffers_ vkAllocateCommandBuffers; + PFN_vkCreateFence_ vkCreateFence; + PFN_vkCmdResetQueryPool_ vkCmdResetQueryPool; + PFN_vkQueueSubmit_ vkQueueSubmit; + PFN_vkWaitForFences_ vkWaitForFences; + PFN_vkResetCommandBuffer_ vkResetCommandBuffer; + PFN_vkCmdWriteTimestamp_ vkCmdWriteTimestamp; + PFN_vkGetQueryPoolResults_ vkGetQueryPoolResults; + PFN_vkBeginCommandBuffer_ vkBeginCommandBuffer; + PFN_vkEndCommandBuffer_ vkEndCommandBuffer; + PFN_vkResetFences_ vkResetFences; + PFN_vkDestroyCommandPool_ vkDestroyCommandPool; + PFN_vkDestroyQueryPool_ vkDestroyQueryPool; + PFN_vkDestroyFence_ vkDestroyFence; + PFN_vkFreeCommandBuffers_ vkFreeCommandBuffers; + }; + + // Source: http://msdn.microsoft.com/en-us/library/system.windows.media.colors(v=vs.110).aspx + // Image: http://i.msdn.microsoft.com/dynimg/IC24340.png + struct Color + { + enum + { + Null = 0x00000000, + AliceBlue = 0xFFF0F8FF, + AntiqueWhite = 0xFFFAEBD7, + Aqua = 0xFF00FFFF, + Aquamarine = 0xFF7FFFD4, + Azure = 0xFFF0FFFF, + Beige = 0xFFF5F5DC, + Bisque = 0xFFFFE4C4, + Black = 0xFF000000, + BlanchedAlmond = 0xFFFFEBCD, + Blue = 0xFF0000FF, + BlueViolet = 0xFF8A2BE2, + Brown = 0xFFA52A2A, + BurlyWood = 0xFFDEB887, + CadetBlue = 0xFF5F9EA0, + Chartreuse = 0xFF7FFF00, + Chocolate = 0xFFD2691E, + Coral = 0xFFFF7F50, + CornflowerBlue = 0xFF6495ED, + Cornsilk = 0xFFFFF8DC, + Crimson = 0xFFDC143C, + Cyan = 0xFF00FFFF, + DarkBlue = 0xFF00008B, + DarkCyan = 0xFF008B8B, + DarkGoldenRod = 0xFFB8860B, + DarkGray = 0xFFA9A9A9, + DarkGreen = 0xFF006400, + DarkKhaki = 0xFFBDB76B, + DarkMagenta = 0xFF8B008B, + DarkOliveGreen = 0xFF556B2F, + DarkOrange = 0xFFFF8C00, + DarkOrchid = 0xFF9932CC, + DarkRed = 0xFF8B0000, + DarkSalmon = 0xFFE9967A, + DarkSeaGreen = 0xFF8FBC8F, + DarkSlateBlue = 0xFF483D8B, + DarkSlateGray = 0xFF2F4F4F, + DarkTurquoise = 0xFF00CED1, + DarkViolet = 0xFF9400D3, + DeepPink = 0xFFFF1493, + DeepSkyBlue = 0xFF00BFFF, + DimGray = 0xFF696969, + DodgerBlue = 0xFF1E90FF, + FireBrick = 0xFFB22222, + FloralWhite = 0xFFFFFAF0, + ForestGreen = 0xFF228B22, + Fuchsia = 0xFFFF00FF, + Gainsboro = 0xFFDCDCDC, + GhostWhite = 0xFFF8F8FF, + Gold = 0xFFFFD700, + GoldenRod = 0xFFDAA520, + Gray = 0xFF808080, + Green = 0xFF008000, + GreenYellow = 0xFFADFF2F, + HoneyDew = 0xFFF0FFF0, + HotPink = 0xFFFF69B4, + IndianRed = 0xFFCD5C5C, + Indigo = 0xFF4B0082, + Ivory = 0xFFFFFFF0, + Khaki = 0xFFF0E68C, + Lavender = 0xFFE6E6FA, + LavenderBlush = 0xFFFFF0F5, + LawnGreen = 0xFF7CFC00, + LemonChiffon = 0xFFFFFACD, + LightBlue = 0xFFADD8E6, + LightCoral = 0xFFF08080, + LightCyan = 0xFFE0FFFF, + LightGoldenRodYellow = 0xFFFAFAD2, + LightGray = 0xFFD3D3D3, + LightGreen = 0xFF90EE90, + LightPink = 0xFFFFB6C1, + LightSalmon = 0xFFFFA07A, + LightSeaGreen = 0xFF20B2AA, + LightSkyBlue = 0xFF87CEFA, + LightSlateGray = 0xFF778899, + LightSteelBlue = 0xFFB0C4DE, + LightYellow = 0xFFFFFFE0, + Lime = 0xFF00FF00, + LimeGreen = 0xFF32CD32, + Linen = 0xFFFAF0E6, + Magenta = 0xFFFF00FF, + Maroon = 0xFF800000, + MediumAquaMarine = 0xFF66CDAA, + MediumBlue = 0xFF0000CD, + MediumOrchid = 0xFFBA55D3, + MediumPurple = 0xFF9370DB, + MediumSeaGreen = 0xFF3CB371, + MediumSlateBlue = 0xFF7B68EE, + MediumSpringGreen = 0xFF00FA9A, + MediumTurquoise = 0xFF48D1CC, + MediumVioletRed = 0xFFC71585, + MidnightBlue = 0xFF191970, + MintCream = 0xFFF5FFFA, + MistyRose = 0xFFFFE4E1, + Moccasin = 0xFFFFE4B5, + NavajoWhite = 0xFFFFDEAD, + Navy = 0xFF000080, + OldLace = 0xFFFDF5E6, + Olive = 0xFF808000, + OliveDrab = 0xFF6B8E23, + Orange = 0xFFFFA500, + OrangeRed = 0xFFFF4500, + Orchid = 0xFFDA70D6, + PaleGoldenRod = 0xFFEEE8AA, + PaleGreen = 0xFF98FB98, + PaleTurquoise = 0xFFAFEEEE, + PaleVioletRed = 0xFFDB7093, + PapayaWhip = 0xFFFFEFD5, + PeachPuff = 0xFFFFDAB9, + Peru = 0xFFCD853F, + Pink = 0xFFFFC0CB, + Plum = 0xFFDDA0DD, + PowderBlue = 0xFFB0E0E6, + Purple = 0xFF800080, + Red = 0xFFFF0000, + RosyBrown = 0xFFBC8F8F, + RoyalBlue = 0xFF4169E1, + SaddleBrown = 0xFF8B4513, + Salmon = 0xFFFA8072, + SandyBrown = 0xFFF4A460, + SeaGreen = 0xFF2E8B57, + SeaShell = 0xFFFFF5EE, + Sienna = 0xFFA0522D, + Silver = 0xFFC0C0C0, + SkyBlue = 0xFF87CEEB, + SlateBlue = 0xFF6A5ACD, + SlateGray = 0xFF708090, + Snow = 0xFFFFFAFA, + SpringGreen = 0xFF00FF7F, + SteelBlue = 0xFF4682B4, + Tan = 0xFFD2B48C, + Teal = 0xFF008080, + Thistle = 0xFFD8BFD8, + Tomato = 0xFFFF6347, + Turquoise = 0xFF40E0D0, + Violet = 0xFFEE82EE, + Wheat = 0xFFF5DEB3, + White = 0xFFFFFFFF, + WhiteSmoke = 0xFFF5F5F5, + Yellow = 0xFFFFFF00, + YellowGreen = 0xFF9ACD32, + }; + }; + + struct Filter + { + enum Type : uint32_t + { + None, + + // CPU + AI, + Animation, + Audio, + Debug, + Camera, + Cloth, + GameLogic, + Input, + Navigation, + Network, + Physics, + Rendering, + Scene, + Script, + Streaming, + UI, + VFX, + Visibility, + Wait, + + // IO + IO, + + // GPU + GPU_Cloth, + GPU_Lighting, + GPU_PostFX, + GPU_Reflections, + GPU_Scene, + GPU_Shadows, + GPU_UI, + GPU_VFX, + GPU_Water, + + }; + }; + + #define OPTICK_MAKE_CATEGORY(filter, color) ((Optick::Category::Type)(((uint64_t)(1ull) << (filter + 32)) | (uint64_t)color)) + + struct Category + { + enum Type : uint64_t + { + // CPU + None = OPTICK_MAKE_CATEGORY(Filter::None, Color::Null), + AI = OPTICK_MAKE_CATEGORY(Filter::AI, Color::Purple), + Animation = OPTICK_MAKE_CATEGORY(Filter::Animation, Color::LightSkyBlue), + Audio = OPTICK_MAKE_CATEGORY(Filter::Audio, Color::HotPink), + Debug = OPTICK_MAKE_CATEGORY(Filter::Debug, Color::Black), + Camera = OPTICK_MAKE_CATEGORY(Filter::Camera, Color::Black), + Cloth = OPTICK_MAKE_CATEGORY(Filter::Cloth, Color::DarkGreen), + GameLogic = OPTICK_MAKE_CATEGORY(Filter::GameLogic, Color::RoyalBlue), + Input = OPTICK_MAKE_CATEGORY(Filter::Input, Color::Ivory), + Navigation = OPTICK_MAKE_CATEGORY(Filter::Navigation, Color::Magenta), + Network = OPTICK_MAKE_CATEGORY(Filter::Network, Color::Olive), + Physics = OPTICK_MAKE_CATEGORY(Filter::Physics, Color::LawnGreen), + Rendering = OPTICK_MAKE_CATEGORY(Filter::Rendering, Color::BurlyWood), + Scene = OPTICK_MAKE_CATEGORY(Filter::Scene, Color::RoyalBlue), + Script = OPTICK_MAKE_CATEGORY(Filter::Script, Color::Plum), + Streaming = OPTICK_MAKE_CATEGORY(Filter::Streaming, Color::Gold), + UI = OPTICK_MAKE_CATEGORY(Filter::UI, Color::PaleTurquoise), + VFX = OPTICK_MAKE_CATEGORY(Filter::VFX, Color::SaddleBrown), + Visibility = OPTICK_MAKE_CATEGORY(Filter::Visibility, Color::Snow), + Wait = OPTICK_MAKE_CATEGORY(Filter::Wait, Color::Tomato), + WaitEmpty = OPTICK_MAKE_CATEGORY(Filter::Wait, Color::White), + // IO + IO = OPTICK_MAKE_CATEGORY(Filter::IO, Color::Khaki), + // GPU + GPU_Cloth = OPTICK_MAKE_CATEGORY(Filter::GPU_Cloth, Color::DarkGreen), + GPU_Lighting = OPTICK_MAKE_CATEGORY(Filter::GPU_Lighting, Color::Khaki), + GPU_PostFX = OPTICK_MAKE_CATEGORY(Filter::GPU_PostFX, Color::Maroon), + GPU_Reflections = OPTICK_MAKE_CATEGORY(Filter::GPU_Reflections, Color::CadetBlue), + GPU_Scene = OPTICK_MAKE_CATEGORY(Filter::GPU_Scene, Color::RoyalBlue), + GPU_Shadows = OPTICK_MAKE_CATEGORY(Filter::GPU_Shadows, Color::LightSlateGray), + GPU_UI = OPTICK_MAKE_CATEGORY(Filter::GPU_UI, Color::PaleTurquoise), + GPU_VFX = OPTICK_MAKE_CATEGORY(Filter::GPU_VFX, Color::SaddleBrown), + GPU_Water = OPTICK_MAKE_CATEGORY(Filter::GPU_Water, Color::SteelBlue), + }; + + static uint32_t GetMask(Type t) { return (uint32_t)(t >> 32); } + static uint32_t GetColor(Type t) { return (uint32_t)(t); } + }; + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +} + + +namespace Optick +{ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +struct Mode +{ + enum Type + { + // OFF + OFF = 0x0, + // Collect Categories (top-level events) + INSTRUMENTATION_CATEGORIES = (1 << 0), + // Collect Events + INSTRUMENTATION_EVENTS = (1 << 1), + // Collect Events + Categories + INSTRUMENTATION = (INSTRUMENTATION_CATEGORIES | INSTRUMENTATION_EVENTS), + // Legacy (keep for compatibility reasons) + SAMPLING = (1 << 2), + // Collect Data Tags + TAGS = (1 << 3), + // Enable Autosampling Events (automatic callstacks) + AUTOSAMPLING = (1 << 4), + // Enable Switch-Contexts Events + SWITCH_CONTEXT = (1 << 5), + // Collect I/O Events + IO = (1 << 6), + // Collect GPU Events + GPU = (1 << 7), + END_SCREENSHOT = (1 << 8), + RESERVED_0 = (1 << 9), + RESERVED_1 = (1 << 10), + // Collect HW Events + HW_COUNTERS = (1 << 11), + // Collect Events in Live mode + LIVE = (1 << 12), + RESERVED_2 = (1 << 13), + RESERVED_3 = (1 << 14), + RESERVED_4 = (1 << 15), + // Collect System Calls + SYS_CALLS = (1 << 16), + // Collect Events from Other Processes + OTHER_PROCESSES = (1 << 17), + // Automation + NOGUI = (1 << 18), + + TRACER = AUTOSAMPLING | SWITCH_CONTEXT | SYS_CALLS, + DEFAULT = INSTRUMENTATION | TAGS | AUTOSAMPLING | SWITCH_CONTEXT | IO | GPU | SYS_CALLS | OTHER_PROCESSES, + }; +}; +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +struct FrameType +{ + enum Type + { + CPU, + GPU, + Render, + COUNT, + + NONE = -1, + }; +}; +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +OPTICK_API int64_t GetHighPrecisionTime(); +OPTICK_API int64_t GetHighPrecisionFrequency(); +OPTICK_API void Update(); +OPTICK_API uint32_t BeginFrame(FrameType::Type type = FrameType::CPU, int64_t timestamp = -1, uint64_t threadID = (uint64_t)-1); +OPTICK_API uint32_t EndFrame(FrameType::Type type = FrameType::CPU, int64_t timestamp = -1, uint64_t threadID = (uint64_t)-1); +OPTICK_API bool IsActive(Mode::Type mode = Mode::INSTRUMENTATION_EVENTS); +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +struct EventStorage; +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +OPTICK_API bool RegisterFiber(uint64_t fiberId, EventStorage** slot); +OPTICK_API bool RegisterThread(const char* name); +OPTICK_API bool RegisterThread(const wchar_t* name); +OPTICK_API bool UnRegisterThread(bool keepAlive); +OPTICK_API EventStorage** GetEventStorageSlotForCurrentThread(); +OPTICK_API bool IsFiberStorage(EventStorage* fiberStorage); +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +struct ThreadMask +{ + enum Type + { + None = 0, + Main = 1 << 0, + GPU = 1 << 1, + IO = 1 << 2, + Idle = 1 << 3, + Render = 1 << 4, + }; +}; + +OPTICK_API EventStorage* RegisterStorage(const char* name, uint64_t threadID = uint64_t(-1), ThreadMask::Type type = ThreadMask::None); +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +struct State +{ + enum Type + { + // Starting a new capture + START_CAPTURE, + + // Stopping current capture + STOP_CAPTURE, + + // Dumping capture to the GUI + // Useful for attaching summary and screenshot to the capture + DUMP_CAPTURE, + + // Cancel current capture + CANCEL_CAPTURE, + }; +}; +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Sets a state change callback +typedef bool (*StateCallback)(State::Type state); +OPTICK_API bool SetStateChangedCallback(StateCallback cb); + +// Attaches a key-value pair to the capture's summary +// Example: AttachSummary("Version", "v12.0.1"); +// AttachSummary("Platform", "Windows"); +// AttachSummary("Config", "Release_x64"); +// AttachSummary("Settings", "Ultra"); +// AttachSummary("Map", "Atlantida"); +// AttachSummary("Position", "123.0,120.0,41.1"); +// AttachSummary("CPU", "Intel(R) Xeon(R) CPU E5410@2.33GHz"); +// AttachSummary("GPU", "NVIDIA GeForce GTX 980 Ti"); +OPTICK_API bool AttachSummary(const char* key, const char* value); + +struct File +{ + enum Type + { + // Supported formats: PNG, JPEG, BMP, TIFF + OPTICK_IMAGE, + + // Text file + OPTICK_TEXT, + + // Any other type + OPTICK_OTHER, + }; +}; +// Attaches a file to the current capture +OPTICK_API bool AttachFile(File::Type type, const char* name, const uint8_t* data, uint32_t size); +OPTICK_API bool AttachFile(File::Type type, const char* name, const char* path); +OPTICK_API bool AttachFile(File::Type type, const char* name, const wchar_t* path); +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +struct EventDescription; +struct Frame; +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +struct EventTime +{ + static const int64_t INVALID_TIMESTAMP = (int64_t)-1; + + int64_t start; + int64_t finish; + + OPTICK_INLINE void Start() { start = Optick::GetHighPrecisionTime(); } + OPTICK_INLINE void Stop() { finish = Optick::GetHighPrecisionTime(); } + OPTICK_INLINE bool IsValid() const { return start < finish && start != INVALID_TIMESTAMP && finish != INVALID_TIMESTAMP; } +}; +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +struct EventData : public EventTime +{ + const EventDescription* description; + + bool operator<(const EventData& other) const + { + if (start != other.start) + return start < other.start; + + // Reversed order for finish intervals (parent first) + return finish > other.finish; + } +}; +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +struct OPTICK_API SyncData : public EventTime +{ + uint64_t newThreadId; + uint64_t oldThreadId; + uint8_t core; + int8_t reason; +}; +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +struct OPTICK_API FiberSyncData : public EventTime +{ + uint64_t threadId; + + static void AttachToThread(EventStorage* storage, uint64_t threadId); + static void DetachFromThread(EventStorage* storage); +}; +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +template +struct TagData +{ + const EventDescription* description; + int64_t timestamp; + T data; + TagData() {} + TagData(const EventDescription& desc, T d) : description(&desc), timestamp(Optick::GetHighPrecisionTime()), data(d) {} + TagData(const EventDescription& desc, T d, int64_t t) : description(&desc), timestamp(t), data(d) {} +}; +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +struct OPTICK_API EventDescription +{ + enum Flags : uint8_t + { + IS_CUSTOM_NAME = 1 << 0, + COPY_NAME_STRING = 1 << 1, + COPY_FILENAME_STRING = 1 << 2, + }; + + const char* name; + const char* file; + uint32_t line; + uint32_t index; + uint32_t color; + uint32_t filter; + uint8_t flags; + + static EventDescription* Create(const char* eventName, const char* fileName, const unsigned long fileLine, const unsigned long eventColor = Color::Null, const unsigned long filter = 0, const uint8_t eventFlags = 0); + static EventDescription* CreateShared(const char* eventName, const char* fileName = nullptr, const unsigned long fileLine = 0, const unsigned long eventColor = Color::Null, const unsigned long filter = 0); + + EventDescription(); +private: + friend class EventDescriptionBoard; + EventDescription& operator=(const EventDescription&); +}; +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +struct OPTICK_API Event +{ + EventData* data; + + static EventData* Start(const EventDescription& description); + static void Stop(EventData& data); + + static void Push(const char* name); + static void Push(const EventDescription& description); + static void Pop(); + + static void Add(EventStorage* storage, const EventDescription* description, int64_t timestampStart, int64_t timestampFinish); + static void Push(EventStorage* storage, const EventDescription* description, int64_t timestampStart); + static void Pop(EventStorage* storage, int64_t timestampStart); + + + Event(const EventDescription& description) + { + data = Start(description); + } + + ~Event() + { + if (data) + Stop(*data); + } +}; +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +OPTICK_INLINE Optick::EventDescription* CreateDescription(const char* functionName, const char* fileName, int fileLine, const char* eventName = nullptr, const ::Optick::Category::Type category = ::Optick::Category::None, uint8_t flags = 0) +{ + if (eventName != nullptr) + flags |= ::Optick::EventDescription::IS_CUSTOM_NAME; + + return ::Optick::EventDescription::Create(eventName != nullptr ? eventName : functionName, fileName, (unsigned long)fileLine, ::Optick::Category::GetColor(category), ::Optick::Category::GetMask(category), flags); +} +OPTICK_INLINE Optick::EventDescription* CreateDescription(const char* functionName, const char* fileName, int fileLine, const ::Optick::Category::Type category) +{ + return ::Optick::EventDescription::Create(functionName, fileName, (unsigned long)fileLine, ::Optick::Category::GetColor(category), ::Optick::Category::GetMask(category)); +} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +struct OPTICK_API GPUEvent +{ + EventData* data; + + static EventData* Start(const EventDescription& description); + static void Stop(EventData& data); + + GPUEvent(const EventDescription& description) + { + data = Start(description); + } + + ~GPUEvent() + { + if (data) + Stop(*data); + } +}; +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +struct OPTICK_API Tag +{ + static void Attach(const EventDescription& description, float val); + static void Attach(const EventDescription& description, int32_t val); + static void Attach(const EventDescription& description, uint32_t val); + static void Attach(const EventDescription& description, uint64_t val); + static void Attach(const EventDescription& description, float val[3]); + static void Attach(const EventDescription& description, const char* val); + static void Attach(const EventDescription& description, const char* val, uint16_t length); + + // Derived + static void Attach(const EventDescription& description, float x, float y, float z) + { + float p[3] = { x, y, z }; Attach(description, p); + } + +}; +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +struct ThreadScope +{ + ThreadScope(const char* name) + { + RegisterThread(name); + } + + ThreadScope(const wchar_t* name) + { + RegisterThread(name); + } + + ~ThreadScope() + { + UnRegisterThread(false); + } +}; +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +enum OPTICK_API GPUQueueType +{ + GPU_QUEUE_GRAPHICS, + GPU_QUEUE_COMPUTE, + GPU_QUEUE_TRANSFER, + GPU_QUEUE_VSYNC, + + GPU_QUEUE_COUNT, +}; +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +struct OPTICK_API GPUContext +{ + void* cmdBuffer; + GPUQueueType queue; + int node; + GPUContext(void* c = nullptr, GPUQueueType q = GPU_QUEUE_GRAPHICS, int n = 0) : cmdBuffer(c), queue(q), node(n) {} +}; +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +OPTICK_API void InitGpuD3D12(ID3D12Device* device, ID3D12CommandQueue** cmdQueues, uint32_t numQueues); +OPTICK_API void InitGpuVulkan(VkDevice* vkDevices, VkPhysicalDevice* vkPhysicalDevices, VkQueue* vkQueues, uint32_t* cmdQueuesFamily, uint32_t numQueues, const VulkanFunctions* functions); +OPTICK_API void GpuFlip(void* swapChain); +OPTICK_API GPUContext SetGpuContext(GPUContext context); +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +struct OPTICK_API GPUContextScope +{ + GPUContext prevContext; + + GPUContextScope(ID3D12CommandList* cmdList, GPUQueueType queue = GPU_QUEUE_GRAPHICS, int node = 0) + { + prevContext = SetGpuContext(GPUContext(cmdList, queue, node)); + } + + GPUContextScope(VkCommandBuffer cmdBuffer, GPUQueueType queue = GPU_QUEUE_GRAPHICS, int node = 0) + { + prevContext = SetGpuContext(GPUContext(cmdBuffer, queue, node)); + } + + ~GPUContextScope() + { + SetGpuContext(prevContext); + } +}; +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +OPTICK_API const EventDescription* GetFrameDescription(FrameType::Type frame = FrameType::CPU); +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +typedef void* (*AllocateFn)(size_t); +typedef void (*DeallocateFn)(void*); +typedef void (*InitThreadCb)(void); +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +OPTICK_API void SetAllocator(AllocateFn allocateFn, DeallocateFn deallocateFn, InitThreadCb initThreadCb); +OPTICK_API void Shutdown(); +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +typedef void(*CaptureSaveChunkCb)(const char*,size_t); +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +OPTICK_API bool StartCapture(Mode::Type mode = Mode::DEFAULT, int samplingFrequency = 1000, bool force = true); +OPTICK_API bool StopCapture(bool force = true); +OPTICK_API bool SaveCapture(CaptureSaveChunkCb dataCb, bool force = true); +OPTICK_API bool SaveCapture(const char* path, bool force = true); +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +struct OptickApp +{ + const char* m_Name; + OptickApp(const char* name) : m_Name(name) { StartCapture(); } + ~OptickApp() { StopCapture(); SaveCapture(m_Name); } +}; +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +} + +#define OPTICK_UNUSED(x) (void)(x) +// Workaround for gcc compiler +#define OPTICK_VA_ARGS(...) , ##__VA_ARGS__ + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Scoped profiling event which automatically grabs current function name. +// Use tis macro 95% of the time. +// Example A: +// void Function() +// { +// OPTICK_EVENT(); +// ... code ... +// } +// or +// void Function() +// { +// OPTICK_EVENT("CustomFunctionName"); +// ... code ... +// } +// Notes: +// Optick captures full name of the function including name space and arguments. +// Full name is usually shortened in the Optick GUI in order to highlight the most important bits. +#define OPTICK_EVENT(...) static ::Optick::EventDescription* OPTICK_CONCAT(autogen_description_, __LINE__) = nullptr; \ + if (OPTICK_CONCAT(autogen_description_, __LINE__) == nullptr) OPTICK_CONCAT(autogen_description_, __LINE__) = ::Optick::CreateDescription(OPTICK_FUNC, __FILE__, __LINE__, ##__VA_ARGS__); \ + ::Optick::Event OPTICK_CONCAT(autogen_event_, __LINE__)( *(OPTICK_CONCAT(autogen_description_, __LINE__)) ); + +// Backward compatibility with previous versions of Optick +//#if !defined(PROFILE) +//#define PROFILE OPTICK_EVENT() +//#endif + +// Scoped profiling macro with predefined color. +// Use this macro for high-level function calls (e.g. AI, Physics, Audio, Render etc.). +// Example: +// void UpdateAI() +// { +// OPTICK_CATEGORY("UpdateAI", Optick::Category::AI); +// ... code ... +// } +// +// Macro could automatically capture current function name: +// void UpdateAI() +// { +// OPTICK_CATEGORY(OPTICK_FUNC, Optick::Category::AI); +// ... code ... +// } +#define OPTICK_CATEGORY(NAME, CATEGORY) OPTICK_EVENT(NAME, CATEGORY) + +// Profiling event for Main Loop update. +// You need to call this function in the beginning of the each new frame. +// Example: +// while (true) +// { +// OPTICK_FRAME("MainThread"); +// ... code ... +// } +#define OPTICK_FRAME(FRAME_NAME, ...) static ::Optick::ThreadScope mainThreadScope(FRAME_NAME); \ + OPTICK_UNUSED(mainThreadScope); \ + ::Optick::EndFrame(__VA_ARGS__); \ + ::Optick::Update(); \ + uint32_t frameNumber = ::Optick::BeginFrame(__VA_ARGS__); \ + ::Optick::Event OPTICK_CONCAT(autogen_event_, __LINE__)(*::Optick::GetFrameDescription(__VA_ARGS__)); \ + OPTICK_TAG("Frame", frameNumber); + +#define OPTICK_UPDATE() ::Optick::Update(); +#define OPTICK_FRAME_FLIP(...) ::Optick::EndFrame(__VA_ARGS__); ::Optick::BeginFrame(__VA_ARGS__); + +// Scoped event for categorized frame types. +// Example: +// void UpdateFrame() +// { +// // Flip "Main/Update" frame +// OPTICK_FRAME_EVENT(Optick::FrameType::CPU); +// +// // Root category event +// OPTICK_CATEGORY("UpdateFrame", Optick::Category::GameLogic); +// +// ... +// } +// +#define OPTICK_FRAME_EVENT(FRAME_TYPE, ...) ::Optick::EndFrame(FRAME_TYPE); \ + switch (FRAME_TYPE) { \ + case Optick::FrameType::CPU: \ + ::Optick::Update(); \ + break; \ + } \ + ::Optick::BeginFrame(FRAME_TYPE); \ + ::Optick::Event OPTICK_CONCAT(autogen_event_, __LINE__)(*::Optick::GetFrameDescription(FRAME_TYPE)); + + +// Thread registration macro. +// Example: +// void WorkerThread(...) +// { +// OPTICK_THREAD("Worker"); +// while (isRunning) +// { +// ... +// } +// } +#define OPTICK_THREAD(THREAD_NAME) ::Optick::ThreadScope brofilerThreadScope(THREAD_NAME); \ + OPTICK_UNUSED(brofilerThreadScope); \ + + +// Thread registration macros. +// Useful for integration with custom job-managers. +#define OPTICK_START_THREAD(THREAD_NAME) ::Optick::RegisterThread(THREAD_NAME); +#define OPTICK_STOP_THREAD() ::Optick::UnRegisterThread(false); + +// Attaches a custom data-tag. +// Supported types: int32, uint32, uint64, vec3, string (cut to 32 characters) +// Example: +// OPTICK_TAG("PlayerName", name[index]); +// OPTICK_TAG("Health", 100); +// OPTICK_TAG("Score", 0x80000000u); +// OPTICK_TAG("Height(cm)", 176.3f); +// OPTICK_TAG("Address", (uint64)*this); +// OPTICK_TAG("Position", 123.0f, 456.0f, 789.0f); +#define OPTICK_TAG(NAME, ...) static ::Optick::EventDescription* OPTICK_CONCAT(autogen_tag_, __LINE__) = nullptr; \ + if (OPTICK_CONCAT(autogen_tag_, __LINE__) == nullptr) OPTICK_CONCAT(autogen_tag_, __LINE__) = ::Optick::EventDescription::Create( NAME, __FILE__, __LINE__ ); \ + ::Optick::Tag::Attach(*OPTICK_CONCAT(autogen_tag_, __LINE__), __VA_ARGS__); \ + +// Scoped macro with DYNAMIC name. +// Optick holds a copy of the provided name. +// Each scope does a search in hashmap for the name. +// Please use variations with STATIC names where it's possible. +// Use this macro for quick prototyping or intergratoin with other profiling systems (e.g. UE4) +// Example: +// const char* name = ... ; +// OPTICK_EVENT_DYNAMIC(name); +#define OPTICK_EVENT_DYNAMIC(NAME) OPTICK_CUSTOM_EVENT(::Optick::EventDescription::CreateShared(NAME, __FILE__, __LINE__)); +// Push\Pop profiling macro with DYNAMIC name. +#define OPTICK_PUSH_DYNAMIC(NAME) ::Optick::Event::Push(NAME); + +// Push\Pop profiling macro with STATIC name. +// Please avoid using Push\Pop approach in favor for scoped macros. +// For backward compatibility with some engines. +// Example: +// OPTICK_PUSH("ScopeName"); +// ... +// OPTICK_POP(); +#define OPTICK_PUSH(NAME) static ::Optick::EventDescription* OPTICK_CONCAT(autogen_description_, __LINE__) = nullptr; \ + if (OPTICK_CONCAT(autogen_description_, __LINE__) == nullptr) OPTICK_CONCAT(autogen_description_, __LINE__) = ::Optick::EventDescription::Create( NAME, __FILE__, __LINE__ ); \ + ::Optick::Event::Push(*OPTICK_CONCAT(autogen_description_, __LINE__)); +#define OPTICK_POP() ::Optick::Event::Pop(); + + +// Scoped macro with predefined Optick::EventDescription. +// Use these events instead of DYNAMIC macros to minimize overhead. +// Common use-case: integrating Optick with internal script languages (e.g. Lua, Actionscript(Scaleform), etc.). +// Example: +// Generating EventDescription once during initialization: +// Optick::EventDescription* description = Optick::EventDescription::CreateShared("FunctionName"); +// +// Then we could just use a pointer to cached description later for profiling: +// OPTICK_CUSTOM_EVENT(description); +#define OPTICK_CUSTOM_EVENT(DESCRIPTION) ::Optick::Event OPTICK_CONCAT(autogen_event_, __LINE__)( *DESCRIPTION ); \ + +// Registration of a custom EventStorage (e.g. GPU, IO, etc.) +// Use it to present any extra information on the timeline. +// Example: +// Optick::EventStorage* IOStorage = Optick::RegisterStorage("I/O"); +// Notes: +// Registration of a new storage is thread-safe. +#define OPTICK_STORAGE_REGISTER(STORAGE_NAME) ::Optick::RegisterStorage(STORAGE_NAME); + +// Adding events to the custom storage. +// Helps to integrate Optick into already existing profiling systems (e.g. GPU Profiler, I/O profiler, etc.). +// Example: +// //Registering a storage - should be done once during initialization +// static Optick::EventStorage* IOStorage = Optick::RegisterStorage("I/O"); +// +// int64_t cpuTimestampStart = Optick::GetHighPrecisionTime(); +// ... +// int64_t cpuTimestampFinish = Optick::GetHighPrecisionTime(); +// +// //Creating a shared event-description +// static Optick::EventDescription* IORead = Optick::EventDescription::CreateShared("IO Read"); +// +// OPTICK_STORAGE_EVENT(IOStorage, IORead, cpuTimestampStart, cpuTimestampFinish); +// Notes: +// It's not thread-safe to add events to the same storage from multiple threads. +// Please guarantee thread-safety on the higher level if access from multiple threads to the same storage is required. +#define OPTICK_STORAGE_EVENT(STORAGE, DESCRIPTION, CPU_TIMESTAMP_START, CPU_TIMESTAMP_FINISH) if (::Optick::IsActive()) { ::Optick::Event::Add(STORAGE, DESCRIPTION, CPU_TIMESTAMP_START, CPU_TIMESTAMP_FINISH); } +#define OPTICK_STORAGE_PUSH(STORAGE, DESCRIPTION, CPU_TIMESTAMP_START) if (::Optick::IsActive()) { ::Optick::Event::Push(STORAGE, DESCRIPTION, CPU_TIMESTAMP_START); } +#define OPTICK_STORAGE_POP(STORAGE, CPU_TIMESTAMP_FINISH) if (::Optick::IsActive()) { ::Optick::Event::Pop(STORAGE, CPU_TIMESTAMP_FINISH); } + + +// Registers state change callback +// If callback returns false - the call is repeated the next frame +#define OPTICK_SET_STATE_CHANGED_CALLBACK(CALLBACK) ::Optick::SetStateChangedCallback(CALLBACK); + + +// Registers custom memory allocator within Optick core +// Example: +// OPTICK_SET_MEMORY_ALLOCATOR([](size_t size) -> void* { return operator new(size); }, [](void* p) { operator delete(p); }, nullptr); +// Params: +// INIT_THREAD_CALLBACK - callback for internal Optick threads (useful if you need to setup some TLS variables related to the memory allocator for your thread) +// Notes: +// Should be called before the first call to OPTICK_FRAME +// Allocation and deallocation functions should be thread-safe - Optick doesn't do any synchronization for these calls +#define OPTICK_SET_MEMORY_ALLOCATOR(ALLOCATE_FUNCTION, DEALLOCATE_FUNCTION, INIT_THREAD_CALLBACK) ::Optick::SetAllocator(ALLOCATE_FUNCTION, DEALLOCATE_FUNCTION, INIT_THREAD_CALLBACK); + +// Shutdown +// Clears all the internal buffers allocated by Optick +// Notes: +// You shouldn't call any Optick functions after shutting down the system (it might lead to the undefined behaviour) +#define OPTICK_SHUTDOWN() ::Optick::Shutdown(); + +// GPU events +#define OPTICK_GPU_INIT_D3D12(DEVICE, CMD_QUEUES, NUM_CMD_QUEUS) ::Optick::InitGpuD3D12(DEVICE, CMD_QUEUES, NUM_CMD_QUEUS); +#define OPTICK_GPU_INIT_VULKAN(DEVICES, PHYSICAL_DEVICES, CMD_QUEUES, CMD_QUEUES_FAMILY, NUM_CMD_QUEUS, FUNCTIONS) ::Optick::InitGpuVulkan(DEVICES, PHYSICAL_DEVICES, CMD_QUEUES, CMD_QUEUES_FAMILY, NUM_CMD_QUEUS, FUNCTIONS); + +// Setup GPU context: +// Params: +// (CommandBuffer\CommandList, [Optional] Optick::GPUQueue queue, [Optional] int NodeIndex) +// Examples: +// OPTICK_GPU_CONTEXT(cmdBuffer); - all OPTICK_GPU_EVENT will use the same command buffer within the scope +// OPTICK_GPU_CONTEXT(cmdBuffer, Optick::GPU_QUEUE_COMPUTE); - all events will use the same command buffer and queue for the scope +// OPTICK_GPU_CONTEXT(cmdBuffer, Optick::GPU_QUEUE_COMPUTE, gpuIndex); - all events will use the same command buffer and queue for the scope +#define OPTICK_GPU_CONTEXT(...) ::Optick::GPUContextScope OPTICK_CONCAT(gpu_autogen_context_, __LINE__)(__VA_ARGS__); \ + (void)OPTICK_CONCAT(gpu_autogen_context_, __LINE__); + +#define OPTICK_GPU_EVENT(NAME) OPTICK_EVENT(NAME); \ + static ::Optick::EventDescription* OPTICK_CONCAT(gpu_autogen_description_, __LINE__) = nullptr; \ + if (OPTICK_CONCAT(gpu_autogen_description_, __LINE__) == nullptr) OPTICK_CONCAT(gpu_autogen_description_, __LINE__) = ::Optick::EventDescription::Create( NAME, __FILE__, __LINE__ ); \ + ::Optick::GPUEvent OPTICK_CONCAT(gpu_autogen_event_, __LINE__)( *(OPTICK_CONCAT(gpu_autogen_description_, __LINE__)) ); \ + +#define OPTICK_GPU_FLIP(SWAP_CHAIN) ::Optick::GpuFlip(SWAP_CHAIN); + +///////////////////////////////////////////////////////////////////////////////// +// [Automation][Startup] +///////////////////////////////////////////////////////////////////////////////// + +// Starts a new capture +// Params: +// [Optional] Mode::Type mode /*= Mode::DEFAULT*/ +// [Optional] int samplingFrequency /*= 1000*/ +#define OPTICK_START_CAPTURE(...) ::Optick::StartCapture(__VA_ARGS__); + +// Stops a new capture (Keeps data intact in the local buffers) +#define OPTICK_STOP_CAPTURE(...) ::Optick::StopCapture(__VA_ARGS__); + +// Saves capture +// Params: +// const char* FilePath - path to the capture +// or +// CaptureSaveChunkCb dataCb - callback for saving chunks of data +// Example: +// OPTICK_SAVE_CAPTURE("ConsoleApp.opt"); +#define OPTICK_SAVE_CAPTURE(...) ::Optick::SaveCapture(__VA_ARGS__); + +// Generate a capture for the whole scope +// Params: +// NAME - name of the application +// Examples: +// int main() { +// OPTICK_APP("MyGame"); //Optick will automatically save a capture in the working directory with the name "MyGame(2019-09-08.14-30-19).opt" +// ... +// } +#define OPTICK_APP(NAME) OPTICK_THREAD(NAME); \ + ::Optick::OptickApp _optickApp(NAME); \ + OPTICK_UNUSED(_optickApp); + + +#else +#define OPTICK_EVENT(...) +#define OPTICK_CATEGORY(NAME, CATEGORY) +#define OPTICK_FRAME(NAME) +#define OPTICK_THREAD(THREAD_NAME) +#define OPTICK_START_THREAD(THREAD_NAME) +#define OPTICK_STOP_THREAD() +#define OPTICK_TAG(NAME, DATA) +#define OPTICK_EVENT_DYNAMIC(NAME) +#define OPTICK_PUSH_DYNAMIC(NAME) +#define OPTICK_PUSH(NAME) +#define OPTICK_POP() +#define OPTICK_CUSTOM_EVENT(DESCRIPTION) +#define OPTICK_STORAGE_REGISTER(STORAGE_NAME) +#define OPTICK_STORAGE_EVENT(STORAGE, DESCRIPTION, CPU_TIMESTAMP_START, CPU_TIMESTAMP_FINISH) +#define OPTICK_STORAGE_PUSH(STORAGE, DESCRIPTION, CPU_TIMESTAMP_START) +#define OPTICK_STORAGE_POP(STORAGE, CPU_TIMESTAMP_FINISH) +#define OPTICK_SET_STATE_CHANGED_CALLBACK(CALLBACK) +#define OPTICK_SET_MEMORY_ALLOCATOR(ALLOCATE_FUNCTION, DEALLOCATE_FUNCTION) +#define OPTICK_SHUTDOWN() +#define OPTICK_GPU_INIT_D3D12(DEVICE, CMD_QUEUES, NUM_CMD_QUEUS) +#define OPTICK_GPU_INIT_VULKAN(DEVICES, PHYSICAL_DEVICES, CMD_QUEUES, CMD_QUEUES_FAMILY, NUM_CMD_QUEUS, FUNCTIONS) +#define OPTICK_GPU_CONTEXT(...) +#define OPTICK_GPU_EVENT(NAME) +#define OPTICK_GPU_FLIP(SWAP_CHAIN) +#define OPTICK_UPDATE() +#define OPTICK_FRAME_FLIP(...) +#define OPTICK_FRAME_EVENT(FRAME_TYPE, ...) +#define OPTICK_START_CAPTURE(...) +#define OPTICK_STOP_CAPTURE() +#define OPTICK_SAVE_CAPTURE(...) +#define OPTICK_APP(NAME) +#endif diff --git a/Source/Dependencies/optick/lib/OptickCore.lib b/Source/Dependencies/optick/lib/OptickCore.lib new file mode 100644 index 000000000..a596b8715 Binary files /dev/null and b/Source/Dependencies/optick/lib/OptickCore.lib differ diff --git a/Source/Dependencies/rapidjson/include/rapidjson/allocators.h b/Source/Dependencies/rapidjson/include/rapidjson/allocators.h new file mode 100644 index 000000000..98affe03f --- /dev/null +++ b/Source/Dependencies/rapidjson/include/rapidjson/allocators.h @@ -0,0 +1,271 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ALLOCATORS_H_ +#define RAPIDJSON_ALLOCATORS_H_ + +#include "rapidjson.h" + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// Allocator + +/*! \class rapidjson::Allocator + \brief Concept for allocating, resizing and freeing memory block. + + Note that Malloc() and Realloc() are non-static but Free() is static. + + So if an allocator need to support Free(), it needs to put its pointer in + the header of memory block. + +\code +concept Allocator { + static const bool kNeedFree; //!< Whether this allocator needs to call Free(). + + // Allocate a memory block. + // \param size of the memory block in bytes. + // \returns pointer to the memory block. + void* Malloc(size_t size); + + // Resize a memory block. + // \param originalPtr The pointer to current memory block. Null pointer is permitted. + // \param originalSize The current size in bytes. (Design issue: since some allocator may not book-keep this, explicitly pass to it can save memory.) + // \param newSize the new size in bytes. + void* Realloc(void* originalPtr, size_t originalSize, size_t newSize); + + // Free a memory block. + // \param pointer to the memory block. Null pointer is permitted. + static void Free(void *ptr); +}; +\endcode +*/ + +/////////////////////////////////////////////////////////////////////////////// +// CrtAllocator + +//! C-runtime library allocator. +/*! This class is just wrapper for standard C library memory routines. + \note implements Allocator concept +*/ +class CrtAllocator { +public: + static const bool kNeedFree = true; + void* Malloc(size_t size) { + if (size) // behavior of malloc(0) is implementation defined. + return std::malloc(size); + else + return NULL; // standardize to returning NULL. + } + void* Realloc(void* originalPtr, size_t originalSize, size_t newSize) { + (void)originalSize; + if (newSize == 0) { + std::free(originalPtr); + return NULL; + } + return std::realloc(originalPtr, newSize); + } + static void Free(void *ptr) { std::free(ptr); } +}; + +/////////////////////////////////////////////////////////////////////////////// +// MemoryPoolAllocator + +//! Default memory allocator used by the parser and DOM. +/*! This allocator allocate memory blocks from pre-allocated memory chunks. + + It does not free memory blocks. And Realloc() only allocate new memory. + + The memory chunks are allocated by BaseAllocator, which is CrtAllocator by default. + + User may also supply a buffer as the first chunk. + + If the user-buffer is full then additional chunks are allocated by BaseAllocator. + + The user-buffer is not deallocated by this allocator. + + \tparam BaseAllocator the allocator type for allocating memory chunks. Default is CrtAllocator. + \note implements Allocator concept +*/ +template +class MemoryPoolAllocator { +public: + static const bool kNeedFree = false; //!< Tell users that no need to call Free() with this allocator. (concept Allocator) + + //! Constructor with chunkSize. + /*! \param chunkSize The size of memory chunk. The default is kDefaultChunkSize. + \param baseAllocator The allocator for allocating memory chunks. + */ + MemoryPoolAllocator(size_t chunkSize = kDefaultChunkCapacity, BaseAllocator* baseAllocator = 0) : + chunkHead_(0), chunk_capacity_(chunkSize), userBuffer_(0), baseAllocator_(baseAllocator), ownBaseAllocator_(0) + { + } + + //! Constructor with user-supplied buffer. + /*! The user buffer will be used firstly. When it is full, memory pool allocates new chunk with chunk size. + + The user buffer will not be deallocated when this allocator is destructed. + + \param buffer User supplied buffer. + \param size Size of the buffer in bytes. It must at least larger than sizeof(ChunkHeader). + \param chunkSize The size of memory chunk. The default is kDefaultChunkSize. + \param baseAllocator The allocator for allocating memory chunks. + */ + MemoryPoolAllocator(void *buffer, size_t size, size_t chunkSize = kDefaultChunkCapacity, BaseAllocator* baseAllocator = 0) : + chunkHead_(0), chunk_capacity_(chunkSize), userBuffer_(buffer), baseAllocator_(baseAllocator), ownBaseAllocator_(0) + { + RAPIDJSON_ASSERT(buffer != 0); + RAPIDJSON_ASSERT(size > sizeof(ChunkHeader)); + chunkHead_ = reinterpret_cast(buffer); + chunkHead_->capacity = size - sizeof(ChunkHeader); + chunkHead_->size = 0; + chunkHead_->next = 0; + } + + //! Destructor. + /*! This deallocates all memory chunks, excluding the user-supplied buffer. + */ + ~MemoryPoolAllocator() { + Clear(); + RAPIDJSON_DELETE(ownBaseAllocator_); + } + + //! Deallocates all memory chunks, excluding the user-supplied buffer. + void Clear() { + while (chunkHead_ && chunkHead_ != userBuffer_) { + ChunkHeader* next = chunkHead_->next; + baseAllocator_->Free(chunkHead_); + chunkHead_ = next; + } + if (chunkHead_ && chunkHead_ == userBuffer_) + chunkHead_->size = 0; // Clear user buffer + } + + //! Computes the total capacity of allocated memory chunks. + /*! \return total capacity in bytes. + */ + size_t Capacity() const { + size_t capacity = 0; + for (ChunkHeader* c = chunkHead_; c != 0; c = c->next) + capacity += c->capacity; + return capacity; + } + + //! Computes the memory blocks allocated. + /*! \return total used bytes. + */ + size_t Size() const { + size_t size = 0; + for (ChunkHeader* c = chunkHead_; c != 0; c = c->next) + size += c->size; + return size; + } + + //! Allocates a memory block. (concept Allocator) + void* Malloc(size_t size) { + if (!size) + return NULL; + + size = RAPIDJSON_ALIGN(size); + if (chunkHead_ == 0 || chunkHead_->size + size > chunkHead_->capacity) + if (!AddChunk(chunk_capacity_ > size ? chunk_capacity_ : size)) + return NULL; + + void *buffer = reinterpret_cast(chunkHead_) + RAPIDJSON_ALIGN(sizeof(ChunkHeader)) + chunkHead_->size; + chunkHead_->size += size; + return buffer; + } + + //! Resizes a memory block (concept Allocator) + void* Realloc(void* originalPtr, size_t originalSize, size_t newSize) { + if (originalPtr == 0) + return Malloc(newSize); + + if (newSize == 0) + return NULL; + + originalSize = RAPIDJSON_ALIGN(originalSize); + newSize = RAPIDJSON_ALIGN(newSize); + + // Do not shrink if new size is smaller than original + if (originalSize >= newSize) + return originalPtr; + + // Simply expand it if it is the last allocation and there is sufficient space + if (originalPtr == reinterpret_cast(chunkHead_) + RAPIDJSON_ALIGN(sizeof(ChunkHeader)) + chunkHead_->size - originalSize) { + size_t increment = static_cast(newSize - originalSize); + if (chunkHead_->size + increment <= chunkHead_->capacity) { + chunkHead_->size += increment; + return originalPtr; + } + } + + // Realloc process: allocate and copy memory, do not free original buffer. + if (void* newBuffer = Malloc(newSize)) { + if (originalSize) + std::memcpy(newBuffer, originalPtr, originalSize); + return newBuffer; + } + else + return NULL; + } + + //! Frees a memory block (concept Allocator) + static void Free(void *ptr) { (void)ptr; } // Do nothing + +private: + //! Copy constructor is not permitted. + MemoryPoolAllocator(const MemoryPoolAllocator& rhs) /* = delete */; + //! Copy assignment operator is not permitted. + MemoryPoolAllocator& operator=(const MemoryPoolAllocator& rhs) /* = delete */; + + //! Creates a new chunk. + /*! \param capacity Capacity of the chunk in bytes. + \return true if success. + */ + bool AddChunk(size_t capacity) { + if (!baseAllocator_) + ownBaseAllocator_ = baseAllocator_ = RAPIDJSON_NEW(BaseAllocator()); + if (ChunkHeader* chunk = reinterpret_cast(baseAllocator_->Malloc(RAPIDJSON_ALIGN(sizeof(ChunkHeader)) + capacity))) { + chunk->capacity = capacity; + chunk->size = 0; + chunk->next = chunkHead_; + chunkHead_ = chunk; + return true; + } + else + return false; + } + + static const int kDefaultChunkCapacity = 64 * 1024; //!< Default chunk capacity. + + //! Chunk header for perpending to each chunk. + /*! Chunks are stored as a singly linked list. + */ + struct ChunkHeader { + size_t capacity; //!< Capacity of the chunk in bytes (excluding the header itself). + size_t size; //!< Current size of allocated memory in bytes. + ChunkHeader *next; //!< Next chunk in the linked list. + }; + + ChunkHeader *chunkHead_; //!< Head of the chunk linked-list. Only the head chunk serves allocation. + size_t chunk_capacity_; //!< The minimum capacity of chunk when they are allocated. + void *userBuffer_; //!< User supplied buffer. + BaseAllocator* baseAllocator_; //!< base allocator for allocating memory chunks. + BaseAllocator* ownBaseAllocator_; //!< base allocator created by this object. +}; + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_ENCODINGS_H_ diff --git a/Source/Dependencies/rapidjson/include/rapidjson/document.h b/Source/Dependencies/rapidjson/include/rapidjson/document.h new file mode 100644 index 000000000..4921317b7 --- /dev/null +++ b/Source/Dependencies/rapidjson/include/rapidjson/document.h @@ -0,0 +1,2575 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_DOCUMENT_H_ +#define RAPIDJSON_DOCUMENT_H_ + +/*! \file document.h */ + +#include "reader.h" +#include "internal/meta.h" +#include "internal/strfunc.h" +#include "memorystream.h" +#include "encodedstream.h" +#include // placement new +#include + +RAPIDJSON_DIAG_PUSH +#ifdef _MSC_VER +RAPIDJSON_DIAG_OFF(4127) // conditional expression is constant +RAPIDJSON_DIAG_OFF(4244) // conversion from kXxxFlags to 'uint16_t', possible loss of data +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_OFF(padded) +RAPIDJSON_DIAG_OFF(switch-enum) +RAPIDJSON_DIAG_OFF(c++98-compat) +#endif + +#ifdef __GNUC__ +RAPIDJSON_DIAG_OFF(effc++) +#if __GNUC__ >= 6 +RAPIDJSON_DIAG_OFF(terminate) // ignore throwing RAPIDJSON_ASSERT in RAPIDJSON_NOEXCEPT functions +#endif +#endif // __GNUC__ + +#ifndef RAPIDJSON_NOMEMBERITERATORCLASS +#include // std::iterator, std::random_access_iterator_tag +#endif + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS +#include // std::move +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +// Forward declaration. +template +class GenericValue; + +template +class GenericDocument; + +//! Name-value pair in a JSON object value. +/*! + This class was internal to GenericValue. It used to be a inner struct. + But a compiler (IBM XL C/C++ for AIX) have reported to have problem with that so it moved as a namespace scope struct. + https://code.google.com/p/rapidjson/issues/detail?id=64 +*/ +template +struct GenericMember { + GenericValue name; //!< name of member (must be a string) + GenericValue value; //!< value of member. +}; + +/////////////////////////////////////////////////////////////////////////////// +// GenericMemberIterator + +#ifndef RAPIDJSON_NOMEMBERITERATORCLASS + +//! (Constant) member iterator for a JSON object value +/*! + \tparam Const Is this a constant iterator? + \tparam Encoding Encoding of the value. (Even non-string values need to have the same encoding in a document) + \tparam Allocator Allocator type for allocating memory of object, array and string. + + This class implements a Random Access Iterator for GenericMember elements + of a GenericValue, see ISO/IEC 14882:2003(E) C++ standard, 24.1 [lib.iterator.requirements]. + + \note This iterator implementation is mainly intended to avoid implicit + conversions from iterator values to \c NULL, + e.g. from GenericValue::FindMember. + + \note Define \c RAPIDJSON_NOMEMBERITERATORCLASS to fall back to a + pointer-based implementation, if your platform doesn't provide + the C++ header. + + \see GenericMember, GenericValue::MemberIterator, GenericValue::ConstMemberIterator + */ +template +class GenericMemberIterator + : public std::iterator >::Type> { + + friend class GenericValue; + template friend class GenericMemberIterator; + + typedef GenericMember PlainType; + typedef typename internal::MaybeAddConst::Type ValueType; + typedef std::iterator BaseType; + +public: + //! Iterator type itself + typedef GenericMemberIterator Iterator; + //! Constant iterator type + typedef GenericMemberIterator ConstIterator; + //! Non-constant iterator type + typedef GenericMemberIterator NonConstIterator; + + //! Pointer to (const) GenericMember + typedef typename BaseType::pointer Pointer; + //! Reference to (const) GenericMember + typedef typename BaseType::reference Reference; + //! Signed integer type (e.g. \c ptrdiff_t) + typedef typename BaseType::difference_type DifferenceType; + + //! Default constructor (singular value) + /*! Creates an iterator pointing to no element. + \note All operations, except for comparisons, are undefined on such values. + */ + GenericMemberIterator() : ptr_() {} + + //! Iterator conversions to more const + /*! + \param it (Non-const) iterator to copy from + + Allows the creation of an iterator from another GenericMemberIterator + that is "less const". Especially, creating a non-constant iterator + from a constant iterator are disabled: + \li const -> non-const (not ok) + \li const -> const (ok) + \li non-const -> const (ok) + \li non-const -> non-const (ok) + + \note If the \c Const template parameter is already \c false, this + constructor effectively defines a regular copy-constructor. + Otherwise, the copy constructor is implicitly defined. + */ + GenericMemberIterator(const NonConstIterator & it) : ptr_(it.ptr_) {} + Iterator& operator=(const NonConstIterator & it) { ptr_ = it.ptr_; return *this; } + + //! @name stepping + //@{ + Iterator& operator++(){ ++ptr_; return *this; } + Iterator& operator--(){ --ptr_; return *this; } + Iterator operator++(int){ Iterator old(*this); ++ptr_; return old; } + Iterator operator--(int){ Iterator old(*this); --ptr_; return old; } + //@} + + //! @name increment/decrement + //@{ + Iterator operator+(DifferenceType n) const { return Iterator(ptr_+n); } + Iterator operator-(DifferenceType n) const { return Iterator(ptr_-n); } + + Iterator& operator+=(DifferenceType n) { ptr_+=n; return *this; } + Iterator& operator-=(DifferenceType n) { ptr_-=n; return *this; } + //@} + + //! @name relations + //@{ + bool operator==(ConstIterator that) const { return ptr_ == that.ptr_; } + bool operator!=(ConstIterator that) const { return ptr_ != that.ptr_; } + bool operator<=(ConstIterator that) const { return ptr_ <= that.ptr_; } + bool operator>=(ConstIterator that) const { return ptr_ >= that.ptr_; } + bool operator< (ConstIterator that) const { return ptr_ < that.ptr_; } + bool operator> (ConstIterator that) const { return ptr_ > that.ptr_; } + //@} + + //! @name dereference + //@{ + Reference operator*() const { return *ptr_; } + Pointer operator->() const { return ptr_; } + Reference operator[](DifferenceType n) const { return ptr_[n]; } + //@} + + //! Distance + DifferenceType operator-(ConstIterator that) const { return ptr_-that.ptr_; } + +private: + //! Internal constructor from plain pointer + explicit GenericMemberIterator(Pointer p) : ptr_(p) {} + + Pointer ptr_; //!< raw pointer +}; + +#else // RAPIDJSON_NOMEMBERITERATORCLASS + +// class-based member iterator implementation disabled, use plain pointers + +template +struct GenericMemberIterator; + +//! non-const GenericMemberIterator +template +struct GenericMemberIterator { + //! use plain pointer as iterator type + typedef GenericMember* Iterator; +}; +//! const GenericMemberIterator +template +struct GenericMemberIterator { + //! use plain const pointer as iterator type + typedef const GenericMember* Iterator; +}; + +#endif // RAPIDJSON_NOMEMBERITERATORCLASS + +/////////////////////////////////////////////////////////////////////////////// +// GenericStringRef + +//! Reference to a constant string (not taking a copy) +/*! + \tparam CharType character type of the string + + This helper class is used to automatically infer constant string + references for string literals, especially from \c const \b (!) + character arrays. + + The main use is for creating JSON string values without copying the + source string via an \ref Allocator. This requires that the referenced + string pointers have a sufficient lifetime, which exceeds the lifetime + of the associated GenericValue. + + \b Example + \code + Value v("foo"); // ok, no need to copy & calculate length + const char foo[] = "foo"; + v.SetString(foo); // ok + + const char* bar = foo; + // Value x(bar); // not ok, can't rely on bar's lifetime + Value x(StringRef(bar)); // lifetime explicitly guaranteed by user + Value y(StringRef(bar, 3)); // ok, explicitly pass length + \endcode + + \see StringRef, GenericValue::SetString +*/ +template +struct GenericStringRef { + typedef CharType Ch; //!< character type of the string + + //! Create string reference from \c const character array +#ifndef __clang__ // -Wdocumentation + /*! + This constructor implicitly creates a constant string reference from + a \c const character array. It has better performance than + \ref StringRef(const CharType*) by inferring the string \ref length + from the array length, and also supports strings containing null + characters. + + \tparam N length of the string, automatically inferred + + \param str Constant character array, lifetime assumed to be longer + than the use of the string in e.g. a GenericValue + + \post \ref s == str + + \note Constant complexity. + \note There is a hidden, private overload to disallow references to + non-const character arrays to be created via this constructor. + By this, e.g. function-scope arrays used to be filled via + \c snprintf are excluded from consideration. + In such cases, the referenced string should be \b copied to the + GenericValue instead. + */ +#endif + template + GenericStringRef(const CharType (&str)[N]) RAPIDJSON_NOEXCEPT + : s(str), length(N-1) {} + + //! Explicitly create string reference from \c const character pointer +#ifndef __clang__ // -Wdocumentation + /*! + This constructor can be used to \b explicitly create a reference to + a constant string pointer. + + \see StringRef(const CharType*) + + \param str Constant character pointer, lifetime assumed to be longer + than the use of the string in e.g. a GenericValue + + \post \ref s == str + + \note There is a hidden, private overload to disallow references to + non-const character arrays to be created via this constructor. + By this, e.g. function-scope arrays used to be filled via + \c snprintf are excluded from consideration. + In such cases, the referenced string should be \b copied to the + GenericValue instead. + */ +#endif + explicit GenericStringRef(const CharType* str) + : s(str), length(internal::StrLen(str)){ RAPIDJSON_ASSERT(s != 0); } + + //! Create constant string reference from pointer and length +#ifndef __clang__ // -Wdocumentation + /*! \param str constant string, lifetime assumed to be longer than the use of the string in e.g. a GenericValue + \param len length of the string, excluding the trailing NULL terminator + + \post \ref s == str && \ref length == len + \note Constant complexity. + */ +#endif + GenericStringRef(const CharType* str, SizeType len) + : s(str), length(len) { RAPIDJSON_ASSERT(s != 0); } + + GenericStringRef(const GenericStringRef& rhs) : s(rhs.s), length(rhs.length) {} + + GenericStringRef& operator=(const GenericStringRef& rhs) { s = rhs.s; length = rhs.length; } + + //! implicit conversion to plain CharType pointer + operator const Ch *() const { return s; } + + const Ch* const s; //!< plain CharType pointer + const SizeType length; //!< length of the string (excluding the trailing NULL terminator) + +private: + //! Disallow construction from non-const array + template + GenericStringRef(CharType (&str)[N]) /* = delete */; +}; + +//! Mark a character pointer as constant string +/*! Mark a plain character pointer as a "string literal". This function + can be used to avoid copying a character string to be referenced as a + value in a JSON GenericValue object, if the string's lifetime is known + to be valid long enough. + \tparam CharType Character type of the string + \param str Constant string, lifetime assumed to be longer than the use of the string in e.g. a GenericValue + \return GenericStringRef string reference object + \relatesalso GenericStringRef + + \see GenericValue::GenericValue(StringRefType), GenericValue::operator=(StringRefType), GenericValue::SetString(StringRefType), GenericValue::PushBack(StringRefType, Allocator&), GenericValue::AddMember +*/ +template +inline GenericStringRef StringRef(const CharType* str) { + return GenericStringRef(str, internal::StrLen(str)); +} + +//! Mark a character pointer as constant string +/*! Mark a plain character pointer as a "string literal". This function + can be used to avoid copying a character string to be referenced as a + value in a JSON GenericValue object, if the string's lifetime is known + to be valid long enough. + + This version has better performance with supplied length, and also + supports string containing null characters. + + \tparam CharType character type of the string + \param str Constant string, lifetime assumed to be longer than the use of the string in e.g. a GenericValue + \param length The length of source string. + \return GenericStringRef string reference object + \relatesalso GenericStringRef +*/ +template +inline GenericStringRef StringRef(const CharType* str, size_t length) { + return GenericStringRef(str, SizeType(length)); +} + +#if RAPIDJSON_HAS_STDSTRING +//! Mark a string object as constant string +/*! Mark a string object (e.g. \c std::string) as a "string literal". + This function can be used to avoid copying a string to be referenced as a + value in a JSON GenericValue object, if the string's lifetime is known + to be valid long enough. + + \tparam CharType character type of the string + \param str Constant string, lifetime assumed to be longer than the use of the string in e.g. a GenericValue + \return GenericStringRef string reference object + \relatesalso GenericStringRef + \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. +*/ +template +inline GenericStringRef StringRef(const std::basic_string& str) { + return GenericStringRef(str.data(), SizeType(str.size())); +} +#endif + +/////////////////////////////////////////////////////////////////////////////// +// GenericValue type traits +namespace internal { + +template +struct IsGenericValueImpl : FalseType {}; + +// select candidates according to nested encoding and allocator types +template struct IsGenericValueImpl::Type, typename Void::Type> + : IsBaseOf, T>::Type {}; + +// helper to match arbitrary GenericValue instantiations, including derived classes +template struct IsGenericValue : IsGenericValueImpl::Type {}; + +} // namespace internal + +/////////////////////////////////////////////////////////////////////////////// +// TypeHelper + +namespace internal { + +template +struct TypeHelper {}; + +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsBool(); } + static bool Get(const ValueType& v) { return v.GetBool(); } + static ValueType& Set(ValueType& v, bool data) { return v.SetBool(data); } + static ValueType& Set(ValueType& v, bool data, typename ValueType::AllocatorType&) { return v.SetBool(data); } +}; + +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsInt(); } + static int Get(const ValueType& v) { return v.GetInt(); } + static ValueType& Set(ValueType& v, int data) { return v.SetInt(data); } + static ValueType& Set(ValueType& v, int data, typename ValueType::AllocatorType&) { return v.SetInt(data); } +}; + +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsUint(); } + static unsigned Get(const ValueType& v) { return v.GetUint(); } + static ValueType& Set(ValueType& v, unsigned data) { return v.SetUint(data); } + static ValueType& Set(ValueType& v, unsigned data, typename ValueType::AllocatorType&) { return v.SetUint(data); } +}; + +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsInt64(); } + static int64_t Get(const ValueType& v) { return v.GetInt64(); } + static ValueType& Set(ValueType& v, int64_t data) { return v.SetInt64(data); } + static ValueType& Set(ValueType& v, int64_t data, typename ValueType::AllocatorType&) { return v.SetInt64(data); } +}; + +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsUint64(); } + static uint64_t Get(const ValueType& v) { return v.GetUint64(); } + static ValueType& Set(ValueType& v, uint64_t data) { return v.SetUint64(data); } + static ValueType& Set(ValueType& v, uint64_t data, typename ValueType::AllocatorType&) { return v.SetUint64(data); } +}; + +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsDouble(); } + static double Get(const ValueType& v) { return v.GetDouble(); } + static ValueType& Set(ValueType& v, double data) { return v.SetDouble(data); } + static ValueType& Set(ValueType& v, double data, typename ValueType::AllocatorType&) { return v.SetDouble(data); } +}; + +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsFloat(); } + static float Get(const ValueType& v) { return v.GetFloat(); } + static ValueType& Set(ValueType& v, float data) { return v.SetFloat(data); } + static ValueType& Set(ValueType& v, float data, typename ValueType::AllocatorType&) { return v.SetFloat(data); } +}; + +template +struct TypeHelper { + typedef const typename ValueType::Ch* StringType; + static bool Is(const ValueType& v) { return v.IsString(); } + static StringType Get(const ValueType& v) { return v.GetString(); } + static ValueType& Set(ValueType& v, const StringType data) { return v.SetString(typename ValueType::StringRefType(data)); } + static ValueType& Set(ValueType& v, const StringType data, typename ValueType::AllocatorType& a) { return v.SetString(data, a); } +}; + +#if RAPIDJSON_HAS_STDSTRING +template +struct TypeHelper > { + typedef std::basic_string StringType; + static bool Is(const ValueType& v) { return v.IsString(); } + static StringType Get(const ValueType& v) { return StringType(v.GetString(), v.GetStringLength()); } + static ValueType& Set(ValueType& v, const StringType& data, typename ValueType::AllocatorType& a) { return v.SetString(data, a); } +}; +#endif + +template +struct TypeHelper { + typedef typename ValueType::Array ArrayType; + static bool Is(const ValueType& v) { return v.IsArray(); } + static ArrayType Get(ValueType& v) { return v.GetArray(); } + static ValueType& Set(ValueType& v, ArrayType data) { return v = data; } + static ValueType& Set(ValueType& v, ArrayType data, typename ValueType::AllocatorType&) { return v = data; } +}; + +template +struct TypeHelper { + typedef typename ValueType::ConstArray ArrayType; + static bool Is(const ValueType& v) { return v.IsArray(); } + static ArrayType Get(const ValueType& v) { return v.GetArray(); } +}; + +template +struct TypeHelper { + typedef typename ValueType::Object ObjectType; + static bool Is(const ValueType& v) { return v.IsObject(); } + static ObjectType Get(ValueType& v) { return v.GetObject(); } + static ValueType& Set(ValueType& v, ObjectType data) { return v = data; } + static ValueType& Set(ValueType& v, ObjectType data, typename ValueType::AllocatorType&) { v = data; } +}; + +template +struct TypeHelper { + typedef typename ValueType::ConstObject ObjectType; + static bool Is(const ValueType& v) { return v.IsObject(); } + static ObjectType Get(const ValueType& v) { return v.GetObject(); } +}; + +} // namespace internal + +// Forward declarations +template class GenericArray; +template class GenericObject; + +/////////////////////////////////////////////////////////////////////////////// +// GenericValue + +//! Represents a JSON value. Use Value for UTF8 encoding and default allocator. +/*! + A JSON value can be one of 7 types. This class is a variant type supporting + these types. + + Use the Value if UTF8 and default allocator + + \tparam Encoding Encoding of the value. (Even non-string values need to have the same encoding in a document) + \tparam Allocator Allocator type for allocating memory of object, array and string. +*/ +template > +class GenericValue { +public: + //! Name-value pair in an object. + typedef GenericMember Member; + typedef Encoding EncodingType; //!< Encoding type from template parameter. + typedef Allocator AllocatorType; //!< Allocator type from template parameter. + typedef typename Encoding::Ch Ch; //!< Character type derived from Encoding. + typedef GenericStringRef StringRefType; //!< Reference to a constant string + typedef typename GenericMemberIterator::Iterator MemberIterator; //!< Member iterator for iterating in object. + typedef typename GenericMemberIterator::Iterator ConstMemberIterator; //!< Constant member iterator for iterating in object. + typedef GenericValue* ValueIterator; //!< Value iterator for iterating in array. + typedef const GenericValue* ConstValueIterator; //!< Constant value iterator for iterating in array. + typedef GenericValue ValueType; //!< Value type of itself. + typedef GenericArray Array; + typedef GenericArray ConstArray; + typedef GenericObject Object; + typedef GenericObject ConstObject; + + //!@name Constructors and destructor. + //@{ + + //! Default constructor creates a null value. + GenericValue() RAPIDJSON_NOEXCEPT : data_() { data_.f.flags = kNullFlag; } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Move constructor in C++11 + GenericValue(GenericValue&& rhs) RAPIDJSON_NOEXCEPT : data_(rhs.data_) { + rhs.data_.f.flags = kNullFlag; // give up contents + } +#endif + +private: + //! Copy constructor is not permitted. + GenericValue(const GenericValue& rhs); + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Moving from a GenericDocument is not permitted. + template + GenericValue(GenericDocument&& rhs); + + //! Move assignment from a GenericDocument is not permitted. + template + GenericValue& operator=(GenericDocument&& rhs); +#endif + +public: + + //! Constructor with JSON value type. + /*! This creates a Value of specified type with default content. + \param type Type of the value. + \note Default content for number is zero. + */ + explicit GenericValue(Type type) RAPIDJSON_NOEXCEPT : data_() { + static const uint16_t defaultFlags[7] = { + kNullFlag, kFalseFlag, kTrueFlag, kObjectFlag, kArrayFlag, kShortStringFlag, + kNumberAnyFlag + }; + RAPIDJSON_ASSERT(type <= kNumberType); + data_.f.flags = defaultFlags[type]; + + // Use ShortString to store empty string. + if (type == kStringType) + data_.ss.SetLength(0); + } + + //! Explicit copy constructor (with allocator) + /*! Creates a copy of a Value by using the given Allocator + \tparam SourceAllocator allocator of \c rhs + \param rhs Value to copy from (read-only) + \param allocator Allocator for allocating copied elements and buffers. Commonly use GenericDocument::GetAllocator(). + \see CopyFrom() + */ + template< typename SourceAllocator > + GenericValue(const GenericValue& rhs, Allocator & allocator); + + //! Constructor for boolean value. + /*! \param b Boolean value + \note This constructor is limited to \em real boolean values and rejects + implicitly converted types like arbitrary pointers. Use an explicit cast + to \c bool, if you want to construct a boolean JSON value in such cases. + */ +#ifndef RAPIDJSON_DOXYGEN_RUNNING // hide SFINAE from Doxygen + template + explicit GenericValue(T b, RAPIDJSON_ENABLEIF((internal::IsSame))) RAPIDJSON_NOEXCEPT // See #472 +#else + explicit GenericValue(bool b) RAPIDJSON_NOEXCEPT +#endif + : data_() { + // safe-guard against failing SFINAE + RAPIDJSON_STATIC_ASSERT((internal::IsSame::Value)); + data_.f.flags = b ? kTrueFlag : kFalseFlag; + } + + //! Constructor for int value. + explicit GenericValue(int i) RAPIDJSON_NOEXCEPT : data_() { + data_.n.i64 = i; + data_.f.flags = (i >= 0) ? (kNumberIntFlag | kUintFlag | kUint64Flag) : kNumberIntFlag; + } + + //! Constructor for unsigned value. + explicit GenericValue(unsigned u) RAPIDJSON_NOEXCEPT : data_() { + data_.n.u64 = u; + data_.f.flags = (u & 0x80000000) ? kNumberUintFlag : (kNumberUintFlag | kIntFlag | kInt64Flag); + } + + //! Constructor for int64_t value. + explicit GenericValue(int64_t i64) RAPIDJSON_NOEXCEPT : data_() { + data_.n.i64 = i64; + data_.f.flags = kNumberInt64Flag; + if (i64 >= 0) { + data_.f.flags |= kNumberUint64Flag; + if (!(static_cast(i64) & RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x00000000))) + data_.f.flags |= kUintFlag; + if (!(static_cast(i64) & RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x80000000))) + data_.f.flags |= kIntFlag; + } + else if (i64 >= static_cast(RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x80000000))) + data_.f.flags |= kIntFlag; + } + + //! Constructor for uint64_t value. + explicit GenericValue(uint64_t u64) RAPIDJSON_NOEXCEPT : data_() { + data_.n.u64 = u64; + data_.f.flags = kNumberUint64Flag; + if (!(u64 & RAPIDJSON_UINT64_C2(0x80000000, 0x00000000))) + data_.f.flags |= kInt64Flag; + if (!(u64 & RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x00000000))) + data_.f.flags |= kUintFlag; + if (!(u64 & RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x80000000))) + data_.f.flags |= kIntFlag; + } + + //! Constructor for double value. + explicit GenericValue(double d) RAPIDJSON_NOEXCEPT : data_() { data_.n.d = d; data_.f.flags = kNumberDoubleFlag; } + + //! Constructor for constant string (i.e. do not make a copy of string) + GenericValue(const Ch* s, SizeType length) RAPIDJSON_NOEXCEPT : data_() { SetStringRaw(StringRef(s, length)); } + + //! Constructor for constant string (i.e. do not make a copy of string) + explicit GenericValue(StringRefType s) RAPIDJSON_NOEXCEPT : data_() { SetStringRaw(s); } + + //! Constructor for copy-string (i.e. do make a copy of string) + GenericValue(const Ch* s, SizeType length, Allocator& allocator) : data_() { SetStringRaw(StringRef(s, length), allocator); } + + //! Constructor for copy-string (i.e. do make a copy of string) + GenericValue(const Ch*s, Allocator& allocator) : data_() { SetStringRaw(StringRef(s), allocator); } + +#if RAPIDJSON_HAS_STDSTRING + //! Constructor for copy-string from a string object (i.e. do make a copy of string) + /*! \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. + */ + GenericValue(const std::basic_string& s, Allocator& allocator) : data_() { SetStringRaw(StringRef(s), allocator); } +#endif + + //! Constructor for Array. + /*! + \param a An array obtained by \c GetArray(). + \note \c Array is always pass-by-value. + \note the source array is moved into this value and the sourec array becomes empty. + */ + GenericValue(Array a) RAPIDJSON_NOEXCEPT : data_(a.value_.data_) { + a.value_.data_ = Data(); + a.value_.data_.f.flags = kArrayFlag; + } + + //! Constructor for Object. + /*! + \param o An object obtained by \c GetObject(). + \note \c Object is always pass-by-value. + \note the source object is moved into this value and the sourec object becomes empty. + */ + GenericValue(Object o) RAPIDJSON_NOEXCEPT : data_(o.value_.data_) { + o.value_.data_ = Data(); + o.value_.data_.f.flags = kObjectFlag; + } + + //! Destructor. + /*! Need to destruct elements of array, members of object, or copy-string. + */ + ~GenericValue() { + if (Allocator::kNeedFree) { // Shortcut by Allocator's trait + switch(data_.f.flags) { + case kArrayFlag: + { + GenericValue* e = GetElementsPointer(); + for (GenericValue* v = e; v != e + data_.a.size; ++v) + v->~GenericValue(); + Allocator::Free(e); + } + break; + + case kObjectFlag: + for (MemberIterator m = MemberBegin(); m != MemberEnd(); ++m) + m->~Member(); + Allocator::Free(GetMembersPointer()); + break; + + case kCopyStringFlag: + Allocator::Free(const_cast(GetStringPointer())); + break; + + default: + break; // Do nothing for other types. + } + } + } + + //@} + + //!@name Assignment operators + //@{ + + //! Assignment with move semantics. + /*! \param rhs Source of the assignment. It will become a null value after assignment. + */ + GenericValue& operator=(GenericValue& rhs) RAPIDJSON_NOEXCEPT { + RAPIDJSON_ASSERT(this != &rhs); + this->~GenericValue(); + RawAssign(rhs); + return *this; + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Move assignment in C++11 + GenericValue& operator=(GenericValue&& rhs) RAPIDJSON_NOEXCEPT { + return *this = rhs.Move(); + } +#endif + + //! Assignment of constant string reference (no copy) + /*! \param str Constant string reference to be assigned + \note This overload is needed to avoid clashes with the generic primitive type assignment overload below. + \see GenericStringRef, operator=(T) + */ + GenericValue& operator=(StringRefType str) RAPIDJSON_NOEXCEPT { + GenericValue s(str); + return *this = s; + } + + //! Assignment with primitive types. + /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t + \param value The value to be assigned. + + \note The source type \c T explicitly disallows all pointer types, + especially (\c const) \ref Ch*. This helps avoiding implicitly + referencing character strings with insufficient lifetime, use + \ref SetString(const Ch*, Allocator&) (for copying) or + \ref StringRef() (to explicitly mark the pointer as constant) instead. + All other pointer types would implicitly convert to \c bool, + use \ref SetBool() instead. + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::IsPointer), (GenericValue&)) + operator=(T value) { + GenericValue v(value); + return *this = v; + } + + //! Deep-copy assignment from Value + /*! Assigns a \b copy of the Value to the current Value object + \tparam SourceAllocator Allocator type of \c rhs + \param rhs Value to copy from (read-only) + \param allocator Allocator to use for copying + */ + template + GenericValue& CopyFrom(const GenericValue& rhs, Allocator& allocator) { + RAPIDJSON_ASSERT(static_cast(this) != static_cast(&rhs)); + this->~GenericValue(); + new (this) GenericValue(rhs, allocator); + return *this; + } + + //! Exchange the contents of this value with those of other. + /*! + \param other Another value. + \note Constant complexity. + */ + GenericValue& Swap(GenericValue& other) RAPIDJSON_NOEXCEPT { + GenericValue temp; + temp.RawAssign(*this); + RawAssign(other); + other.RawAssign(temp); + return *this; + } + + //! free-standing swap function helper + /*! + Helper function to enable support for common swap implementation pattern based on \c std::swap: + \code + void swap(MyClass& a, MyClass& b) { + using std::swap; + swap(a.value, b.value); + // ... + } + \endcode + \see Swap() + */ + friend inline void swap(GenericValue& a, GenericValue& b) RAPIDJSON_NOEXCEPT { a.Swap(b); } + + //! Prepare Value for move semantics + /*! \return *this */ + GenericValue& Move() RAPIDJSON_NOEXCEPT { return *this; } + //@} + + //!@name Equal-to and not-equal-to operators + //@{ + //! Equal-to operator + /*! + \note If an object contains duplicated named member, comparing equality with any object is always \c false. + \note Linear time complexity (number of all values in the subtree and total lengths of all strings). + */ + template + bool operator==(const GenericValue& rhs) const { + typedef GenericValue RhsType; + if (GetType() != rhs.GetType()) + return false; + + switch (GetType()) { + case kObjectType: // Warning: O(n^2) inner-loop + if (data_.o.size != rhs.data_.o.size) + return false; + for (ConstMemberIterator lhsMemberItr = MemberBegin(); lhsMemberItr != MemberEnd(); ++lhsMemberItr) { + typename RhsType::ConstMemberIterator rhsMemberItr = rhs.FindMember(lhsMemberItr->name); + if (rhsMemberItr == rhs.MemberEnd() || lhsMemberItr->value != rhsMemberItr->value) + return false; + } + return true; + + case kArrayType: + if (data_.a.size != rhs.data_.a.size) + return false; + for (SizeType i = 0; i < data_.a.size; i++) + if ((*this)[i] != rhs[i]) + return false; + return true; + + case kStringType: + return StringEqual(rhs); + + case kNumberType: + if (IsDouble() || rhs.IsDouble()) { + double a = GetDouble(); // May convert from integer to double. + double b = rhs.GetDouble(); // Ditto + return a >= b && a <= b; // Prevent -Wfloat-equal + } + else + return data_.n.u64 == rhs.data_.n.u64; + + default: + return true; + } + } + + //! Equal-to operator with const C-string pointer + bool operator==(const Ch* rhs) const { return *this == GenericValue(StringRef(rhs)); } + +#if RAPIDJSON_HAS_STDSTRING + //! Equal-to operator with string object + /*! \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. + */ + bool operator==(const std::basic_string& rhs) const { return *this == GenericValue(StringRef(rhs)); } +#endif + + //! Equal-to operator with primitive types + /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c double, \c true, \c false + */ + template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr,internal::IsGenericValue >), (bool)) operator==(const T& rhs) const { return *this == GenericValue(rhs); } + + //! Not-equal-to operator + /*! \return !(*this == rhs) + */ + template + bool operator!=(const GenericValue& rhs) const { return !(*this == rhs); } + + //! Not-equal-to operator with const C-string pointer + bool operator!=(const Ch* rhs) const { return !(*this == rhs); } + + //! Not-equal-to operator with arbitrary types + /*! \return !(*this == rhs) + */ + template RAPIDJSON_DISABLEIF_RETURN((internal::IsGenericValue), (bool)) operator!=(const T& rhs) const { return !(*this == rhs); } + + //! Equal-to operator with arbitrary types (symmetric version) + /*! \return (rhs == lhs) + */ + template friend RAPIDJSON_DISABLEIF_RETURN((internal::IsGenericValue), (bool)) operator==(const T& lhs, const GenericValue& rhs) { return rhs == lhs; } + + //! Not-Equal-to operator with arbitrary types (symmetric version) + /*! \return !(rhs == lhs) + */ + template friend RAPIDJSON_DISABLEIF_RETURN((internal::IsGenericValue), (bool)) operator!=(const T& lhs, const GenericValue& rhs) { return !(rhs == lhs); } + //@} + + //!@name Type + //@{ + + Type GetType() const { return static_cast(data_.f.flags & kTypeMask); } + bool IsNull() const { return data_.f.flags == kNullFlag; } + bool IsFalse() const { return data_.f.flags == kFalseFlag; } + bool IsTrue() const { return data_.f.flags == kTrueFlag; } + bool IsBool() const { return (data_.f.flags & kBoolFlag) != 0; } + bool IsObject() const { return data_.f.flags == kObjectFlag; } + bool IsArray() const { return data_.f.flags == kArrayFlag; } + bool IsNumber() const { return (data_.f.flags & kNumberFlag) != 0; } + bool IsInt() const { return (data_.f.flags & kIntFlag) != 0; } + bool IsUint() const { return (data_.f.flags & kUintFlag) != 0; } + bool IsInt64() const { return (data_.f.flags & kInt64Flag) != 0; } + bool IsUint64() const { return (data_.f.flags & kUint64Flag) != 0; } + bool IsDouble() const { return (data_.f.flags & kDoubleFlag) != 0; } + bool IsString() const { return (data_.f.flags & kStringFlag) != 0; } + + // Checks whether a number can be losslessly converted to a double. + bool IsLosslessDouble() const { + if (!IsNumber()) return false; + if (IsUint64()) { + uint64_t u = GetUint64(); + volatile double d = static_cast(u); + return (d >= 0.0) + && (d < static_cast((std::numeric_limits::max)())) + && (u == static_cast(d)); + } + if (IsInt64()) { + int64_t i = GetInt64(); + volatile double d = static_cast(i); + return (d >= static_cast((std::numeric_limits::min)())) + && (d < static_cast((std::numeric_limits::max)())) + && (i == static_cast(d)); + } + return true; // double, int, uint are always lossless + } + + // Checks whether a number is a float (possible lossy). + bool IsFloat() const { + if ((data_.f.flags & kDoubleFlag) == 0) + return false; + double d = GetDouble(); + return d >= -3.4028234e38 && d <= 3.4028234e38; + } + // Checks whether a number can be losslessly converted to a float. + bool IsLosslessFloat() const { + if (!IsNumber()) return false; + double a = GetDouble(); + if (a < static_cast(-(std::numeric_limits::max)()) + || a > static_cast((std::numeric_limits::max)())) + return false; + double b = static_cast(static_cast(a)); + return a >= b && a <= b; // Prevent -Wfloat-equal + } + + //@} + + //!@name Null + //@{ + + GenericValue& SetNull() { this->~GenericValue(); new (this) GenericValue(); return *this; } + + //@} + + //!@name Bool + //@{ + + bool GetBool() const { RAPIDJSON_ASSERT(IsBool()); return data_.f.flags == kTrueFlag; } + //!< Set boolean value + /*! \post IsBool() == true */ + GenericValue& SetBool(bool b) { this->~GenericValue(); new (this) GenericValue(b); return *this; } + + //@} + + //!@name Object + //@{ + + //! Set this value as an empty object. + /*! \post IsObject() == true */ + GenericValue& SetObject() { this->~GenericValue(); new (this) GenericValue(kObjectType); return *this; } + + //! Get the number of members in the object. + SizeType MemberCount() const { RAPIDJSON_ASSERT(IsObject()); return data_.o.size; } + + //! Check whether the object is empty. + bool ObjectEmpty() const { RAPIDJSON_ASSERT(IsObject()); return data_.o.size == 0; } + + //! Get a value from an object associated with the name. + /*! \pre IsObject() == true + \tparam T Either \c Ch or \c const \c Ch (template used for disambiguation with \ref operator[](SizeType)) + \note In version 0.1x, if the member is not found, this function returns a null value. This makes issue 7. + Since 0.2, if the name is not correct, it will assert. + If user is unsure whether a member exists, user should use HasMember() first. + A better approach is to use FindMember(). + \note Linear time complexity. + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::NotExpr::Type, Ch> >),(GenericValue&)) operator[](T* name) { + GenericValue n(StringRef(name)); + return (*this)[n]; + } + template + RAPIDJSON_DISABLEIF_RETURN((internal::NotExpr::Type, Ch> >),(const GenericValue&)) operator[](T* name) const { return const_cast(*this)[name]; } + + //! Get a value from an object associated with the name. + /*! \pre IsObject() == true + \tparam SourceAllocator Allocator of the \c name value + + \note Compared to \ref operator[](T*), this version is faster because it does not need a StrLen(). + And it can also handle strings with embedded null characters. + + \note Linear time complexity. + */ + template + GenericValue& operator[](const GenericValue& name) { + MemberIterator member = FindMember(name); + if (member != MemberEnd()) + return member->value; + else { + RAPIDJSON_ASSERT(false); // see above note + + // This will generate -Wexit-time-destructors in clang + // static GenericValue NullValue; + // return NullValue; + + // Use static buffer and placement-new to prevent destruction + static char buffer[sizeof(GenericValue)]; + return *new (buffer) GenericValue(); + } + } + template + const GenericValue& operator[](const GenericValue& name) const { return const_cast(*this)[name]; } + +#if RAPIDJSON_HAS_STDSTRING + //! Get a value from an object associated with name (string object). + GenericValue& operator[](const std::basic_string& name) { return (*this)[GenericValue(StringRef(name))]; } + const GenericValue& operator[](const std::basic_string& name) const { return (*this)[GenericValue(StringRef(name))]; } +#endif + + //! Const member iterator + /*! \pre IsObject() == true */ + ConstMemberIterator MemberBegin() const { RAPIDJSON_ASSERT(IsObject()); return ConstMemberIterator(GetMembersPointer()); } + //! Const \em past-the-end member iterator + /*! \pre IsObject() == true */ + ConstMemberIterator MemberEnd() const { RAPIDJSON_ASSERT(IsObject()); return ConstMemberIterator(GetMembersPointer() + data_.o.size); } + //! Member iterator + /*! \pre IsObject() == true */ + MemberIterator MemberBegin() { RAPIDJSON_ASSERT(IsObject()); return MemberIterator(GetMembersPointer()); } + //! \em Past-the-end member iterator + /*! \pre IsObject() == true */ + MemberIterator MemberEnd() { RAPIDJSON_ASSERT(IsObject()); return MemberIterator(GetMembersPointer() + data_.o.size); } + + //! Check whether a member exists in the object. + /*! + \param name Member name to be searched. + \pre IsObject() == true + \return Whether a member with that name exists. + \note It is better to use FindMember() directly if you need the obtain the value as well. + \note Linear time complexity. + */ + bool HasMember(const Ch* name) const { return FindMember(name) != MemberEnd(); } + +#if RAPIDJSON_HAS_STDSTRING + //! Check whether a member exists in the object with string object. + /*! + \param name Member name to be searched. + \pre IsObject() == true + \return Whether a member with that name exists. + \note It is better to use FindMember() directly if you need the obtain the value as well. + \note Linear time complexity. + */ + bool HasMember(const std::basic_string& name) const { return FindMember(name) != MemberEnd(); } +#endif + + //! Check whether a member exists in the object with GenericValue name. + /*! + This version is faster because it does not need a StrLen(). It can also handle string with null character. + \param name Member name to be searched. + \pre IsObject() == true + \return Whether a member with that name exists. + \note It is better to use FindMember() directly if you need the obtain the value as well. + \note Linear time complexity. + */ + template + bool HasMember(const GenericValue& name) const { return FindMember(name) != MemberEnd(); } + + //! Find member by name. + /*! + \param name Member name to be searched. + \pre IsObject() == true + \return Iterator to member, if it exists. + Otherwise returns \ref MemberEnd(). + + \note Earlier versions of Rapidjson returned a \c NULL pointer, in case + the requested member doesn't exist. For consistency with e.g. + \c std::map, this has been changed to MemberEnd() now. + \note Linear time complexity. + */ + MemberIterator FindMember(const Ch* name) { + GenericValue n(StringRef(name)); + return FindMember(n); + } + + ConstMemberIterator FindMember(const Ch* name) const { return const_cast(*this).FindMember(name); } + + //! Find member by name. + /*! + This version is faster because it does not need a StrLen(). It can also handle string with null character. + \param name Member name to be searched. + \pre IsObject() == true + \return Iterator to member, if it exists. + Otherwise returns \ref MemberEnd(). + + \note Earlier versions of Rapidjson returned a \c NULL pointer, in case + the requested member doesn't exist. For consistency with e.g. + \c std::map, this has been changed to MemberEnd() now. + \note Linear time complexity. + */ + template + MemberIterator FindMember(const GenericValue& name) { + RAPIDJSON_ASSERT(IsObject()); + RAPIDJSON_ASSERT(name.IsString()); + MemberIterator member = MemberBegin(); + for ( ; member != MemberEnd(); ++member) + if (name.StringEqual(member->name)) + break; + return member; + } + template ConstMemberIterator FindMember(const GenericValue& name) const { return const_cast(*this).FindMember(name); } + +#if RAPIDJSON_HAS_STDSTRING + //! Find member by string object name. + /*! + \param name Member name to be searched. + \pre IsObject() == true + \return Iterator to member, if it exists. + Otherwise returns \ref MemberEnd(). + */ + MemberIterator FindMember(const std::basic_string& name) { return FindMember(GenericValue(StringRef(name))); } + ConstMemberIterator FindMember(const std::basic_string& name) const { return FindMember(GenericValue(StringRef(name))); } +#endif + + //! Add a member (name-value pair) to the object. + /*! \param name A string value as name of member. + \param value Value of any type. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \note The ownership of \c name and \c value will be transferred to this object on success. + \pre IsObject() && name.IsString() + \post name.IsNull() && value.IsNull() + \note Amortized Constant time complexity. + */ + GenericValue& AddMember(GenericValue& name, GenericValue& value, Allocator& allocator) { + RAPIDJSON_ASSERT(IsObject()); + RAPIDJSON_ASSERT(name.IsString()); + + ObjectData& o = data_.o; + if (o.size >= o.capacity) { + if (o.capacity == 0) { + o.capacity = kDefaultObjectCapacity; + SetMembersPointer(reinterpret_cast(allocator.Malloc(o.capacity * sizeof(Member)))); + } + else { + SizeType oldCapacity = o.capacity; + o.capacity += (oldCapacity + 1) / 2; // grow by factor 1.5 + SetMembersPointer(reinterpret_cast(allocator.Realloc(GetMembersPointer(), oldCapacity * sizeof(Member), o.capacity * sizeof(Member)))); + } + } + Member* members = GetMembersPointer(); + members[o.size].name.RawAssign(name); + members[o.size].value.RawAssign(value); + o.size++; + return *this; + } + + //! Add a constant string value as member (name-value pair) to the object. + /*! \param name A string value as name of member. + \param value constant string reference as value of member. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \pre IsObject() + \note This overload is needed to avoid clashes with the generic primitive type AddMember(GenericValue&,T,Allocator&) overload below. + \note Amortized Constant time complexity. + */ + GenericValue& AddMember(GenericValue& name, StringRefType value, Allocator& allocator) { + GenericValue v(value); + return AddMember(name, v, allocator); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Add a string object as member (name-value pair) to the object. + /*! \param name A string value as name of member. + \param value constant string reference as value of member. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \pre IsObject() + \note This overload is needed to avoid clashes with the generic primitive type AddMember(GenericValue&,T,Allocator&) overload below. + \note Amortized Constant time complexity. + */ + GenericValue& AddMember(GenericValue& name, std::basic_string& value, Allocator& allocator) { + GenericValue v(value, allocator); + return AddMember(name, v, allocator); + } +#endif + + //! Add any primitive value as member (name-value pair) to the object. + /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t + \param name A string value as name of member. + \param value Value of primitive type \c T as value of member + \param allocator Allocator for reallocating memory. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \pre IsObject() + + \note The source type \c T explicitly disallows all pointer types, + especially (\c const) \ref Ch*. This helps avoiding implicitly + referencing character strings with insufficient lifetime, use + \ref AddMember(StringRefType, GenericValue&, Allocator&) or \ref + AddMember(StringRefType, StringRefType, Allocator&). + All other pointer types would implicitly convert to \c bool, + use an explicit cast instead, if needed. + \note Amortized Constant time complexity. + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (GenericValue&)) + AddMember(GenericValue& name, T value, Allocator& allocator) { + GenericValue v(value); + return AddMember(name, v, allocator); + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericValue& AddMember(GenericValue&& name, GenericValue&& value, Allocator& allocator) { + return AddMember(name, value, allocator); + } + GenericValue& AddMember(GenericValue&& name, GenericValue& value, Allocator& allocator) { + return AddMember(name, value, allocator); + } + GenericValue& AddMember(GenericValue& name, GenericValue&& value, Allocator& allocator) { + return AddMember(name, value, allocator); + } + GenericValue& AddMember(StringRefType name, GenericValue&& value, Allocator& allocator) { + GenericValue n(name); + return AddMember(n, value, allocator); + } +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS + + + //! Add a member (name-value pair) to the object. + /*! \param name A constant string reference as name of member. + \param value Value of any type. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \note The ownership of \c value will be transferred to this object on success. + \pre IsObject() + \post value.IsNull() + \note Amortized Constant time complexity. + */ + GenericValue& AddMember(StringRefType name, GenericValue& value, Allocator& allocator) { + GenericValue n(name); + return AddMember(n, value, allocator); + } + + //! Add a constant string value as member (name-value pair) to the object. + /*! \param name A constant string reference as name of member. + \param value constant string reference as value of member. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \pre IsObject() + \note This overload is needed to avoid clashes with the generic primitive type AddMember(StringRefType,T,Allocator&) overload below. + \note Amortized Constant time complexity. + */ + GenericValue& AddMember(StringRefType name, StringRefType value, Allocator& allocator) { + GenericValue v(value); + return AddMember(name, v, allocator); + } + + //! Add any primitive value as member (name-value pair) to the object. + /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t + \param name A constant string reference as name of member. + \param value Value of primitive type \c T as value of member + \param allocator Allocator for reallocating memory. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \pre IsObject() + + \note The source type \c T explicitly disallows all pointer types, + especially (\c const) \ref Ch*. This helps avoiding implicitly + referencing character strings with insufficient lifetime, use + \ref AddMember(StringRefType, GenericValue&, Allocator&) or \ref + AddMember(StringRefType, StringRefType, Allocator&). + All other pointer types would implicitly convert to \c bool, + use an explicit cast instead, if needed. + \note Amortized Constant time complexity. + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (GenericValue&)) + AddMember(StringRefType name, T value, Allocator& allocator) { + GenericValue n(name); + return AddMember(n, value, allocator); + } + + //! Remove all members in the object. + /*! This function do not deallocate memory in the object, i.e. the capacity is unchanged. + \note Linear time complexity. + */ + void RemoveAllMembers() { + RAPIDJSON_ASSERT(IsObject()); + for (MemberIterator m = MemberBegin(); m != MemberEnd(); ++m) + m->~Member(); + data_.o.size = 0; + } + + //! Remove a member in object by its name. + /*! \param name Name of member to be removed. + \return Whether the member existed. + \note This function may reorder the object members. Use \ref + EraseMember(ConstMemberIterator) if you need to preserve the + relative order of the remaining members. + \note Linear time complexity. + */ + bool RemoveMember(const Ch* name) { + GenericValue n(StringRef(name)); + return RemoveMember(n); + } + +#if RAPIDJSON_HAS_STDSTRING + bool RemoveMember(const std::basic_string& name) { return RemoveMember(GenericValue(StringRef(name))); } +#endif + + template + bool RemoveMember(const GenericValue& name) { + MemberIterator m = FindMember(name); + if (m != MemberEnd()) { + RemoveMember(m); + return true; + } + else + return false; + } + + //! Remove a member in object by iterator. + /*! \param m member iterator (obtained by FindMember() or MemberBegin()). + \return the new iterator after removal. + \note This function may reorder the object members. Use \ref + EraseMember(ConstMemberIterator) if you need to preserve the + relative order of the remaining members. + \note Constant time complexity. + */ + MemberIterator RemoveMember(MemberIterator m) { + RAPIDJSON_ASSERT(IsObject()); + RAPIDJSON_ASSERT(data_.o.size > 0); + RAPIDJSON_ASSERT(GetMembersPointer() != 0); + RAPIDJSON_ASSERT(m >= MemberBegin() && m < MemberEnd()); + + MemberIterator last(GetMembersPointer() + (data_.o.size - 1)); + if (data_.o.size > 1 && m != last) + *m = *last; // Move the last one to this place + else + m->~Member(); // Only one left, just destroy + --data_.o.size; + return m; + } + + //! Remove a member from an object by iterator. + /*! \param pos iterator to the member to remove + \pre IsObject() == true && \ref MemberBegin() <= \c pos < \ref MemberEnd() + \return Iterator following the removed element. + If the iterator \c pos refers to the last element, the \ref MemberEnd() iterator is returned. + \note This function preserves the relative order of the remaining object + members. If you do not need this, use the more efficient \ref RemoveMember(MemberIterator). + \note Linear time complexity. + */ + MemberIterator EraseMember(ConstMemberIterator pos) { + return EraseMember(pos, pos +1); + } + + //! Remove members in the range [first, last) from an object. + /*! \param first iterator to the first member to remove + \param last iterator following the last member to remove + \pre IsObject() == true && \ref MemberBegin() <= \c first <= \c last <= \ref MemberEnd() + \return Iterator following the last removed element. + \note This function preserves the relative order of the remaining object + members. + \note Linear time complexity. + */ + MemberIterator EraseMember(ConstMemberIterator first, ConstMemberIterator last) { + RAPIDJSON_ASSERT(IsObject()); + RAPIDJSON_ASSERT(data_.o.size > 0); + RAPIDJSON_ASSERT(GetMembersPointer() != 0); + RAPIDJSON_ASSERT(first >= MemberBegin()); + RAPIDJSON_ASSERT(first <= last); + RAPIDJSON_ASSERT(last <= MemberEnd()); + + MemberIterator pos = MemberBegin() + (first - MemberBegin()); + for (MemberIterator itr = pos; itr != last; ++itr) + itr->~Member(); + std::memmove(&*pos, &*last, static_cast(MemberEnd() - last) * sizeof(Member)); + data_.o.size -= static_cast(last - first); + return pos; + } + + //! Erase a member in object by its name. + /*! \param name Name of member to be removed. + \return Whether the member existed. + \note Linear time complexity. + */ + bool EraseMember(const Ch* name) { + GenericValue n(StringRef(name)); + return EraseMember(n); + } + +#if RAPIDJSON_HAS_STDSTRING + bool EraseMember(const std::basic_string& name) { return EraseMember(GenericValue(StringRef(name))); } +#endif + + template + bool EraseMember(const GenericValue& name) { + MemberIterator m = FindMember(name); + if (m != MemberEnd()) { + EraseMember(m); + return true; + } + else + return false; + } + + Object GetObject() { RAPIDJSON_ASSERT(IsObject()); return Object(*this); } + ConstObject GetObject() const { RAPIDJSON_ASSERT(IsObject()); return ConstObject(*this); } + + //@} + + //!@name Array + //@{ + + //! Set this value as an empty array. + /*! \post IsArray == true */ + GenericValue& SetArray() { this->~GenericValue(); new (this) GenericValue(kArrayType); return *this; } + + //! Get the number of elements in array. + SizeType Size() const { RAPIDJSON_ASSERT(IsArray()); return data_.a.size; } + + //! Get the capacity of array. + SizeType Capacity() const { RAPIDJSON_ASSERT(IsArray()); return data_.a.capacity; } + + //! Check whether the array is empty. + bool Empty() const { RAPIDJSON_ASSERT(IsArray()); return data_.a.size == 0; } + + //! Remove all elements in the array. + /*! This function do not deallocate memory in the array, i.e. the capacity is unchanged. + \note Linear time complexity. + */ + void Clear() { + RAPIDJSON_ASSERT(IsArray()); + GenericValue* e = GetElementsPointer(); + for (GenericValue* v = e; v != e + data_.a.size; ++v) + v->~GenericValue(); + data_.a.size = 0; + } + + //! Get an element from array by index. + /*! \pre IsArray() == true + \param index Zero-based index of element. + \see operator[](T*) + */ + GenericValue& operator[](SizeType index) { + RAPIDJSON_ASSERT(IsArray()); + RAPIDJSON_ASSERT(index < data_.a.size); + return GetElementsPointer()[index]; + } + const GenericValue& operator[](SizeType index) const { return const_cast(*this)[index]; } + + //! Element iterator + /*! \pre IsArray() == true */ + ValueIterator Begin() { RAPIDJSON_ASSERT(IsArray()); return GetElementsPointer(); } + //! \em Past-the-end element iterator + /*! \pre IsArray() == true */ + ValueIterator End() { RAPIDJSON_ASSERT(IsArray()); return GetElementsPointer() + data_.a.size; } + //! Constant element iterator + /*! \pre IsArray() == true */ + ConstValueIterator Begin() const { return const_cast(*this).Begin(); } + //! Constant \em past-the-end element iterator + /*! \pre IsArray() == true */ + ConstValueIterator End() const { return const_cast(*this).End(); } + + //! Request the array to have enough capacity to store elements. + /*! \param newCapacity The capacity that the array at least need to have. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \note Linear time complexity. + */ + GenericValue& Reserve(SizeType newCapacity, Allocator &allocator) { + RAPIDJSON_ASSERT(IsArray()); + if (newCapacity > data_.a.capacity) { + SetElementsPointer(reinterpret_cast(allocator.Realloc(GetElementsPointer(), data_.a.capacity * sizeof(GenericValue), newCapacity * sizeof(GenericValue)))); + data_.a.capacity = newCapacity; + } + return *this; + } + + //! Append a GenericValue at the end of the array. + /*! \param value Value to be appended. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \pre IsArray() == true + \post value.IsNull() == true + \return The value itself for fluent API. + \note The ownership of \c value will be transferred to this array on success. + \note If the number of elements to be appended is known, calls Reserve() once first may be more efficient. + \note Amortized constant time complexity. + */ + GenericValue& PushBack(GenericValue& value, Allocator& allocator) { + RAPIDJSON_ASSERT(IsArray()); + if (data_.a.size >= data_.a.capacity) + Reserve(data_.a.capacity == 0 ? kDefaultArrayCapacity : (data_.a.capacity + (data_.a.capacity + 1) / 2), allocator); + GetElementsPointer()[data_.a.size++].RawAssign(value); + return *this; + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericValue& PushBack(GenericValue&& value, Allocator& allocator) { + return PushBack(value, allocator); + } +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS + + //! Append a constant string reference at the end of the array. + /*! \param value Constant string reference to be appended. + \param allocator Allocator for reallocating memory. It must be the same one used previously. Commonly use GenericDocument::GetAllocator(). + \pre IsArray() == true + \return The value itself for fluent API. + \note If the number of elements to be appended is known, calls Reserve() once first may be more efficient. + \note Amortized constant time complexity. + \see GenericStringRef + */ + GenericValue& PushBack(StringRefType value, Allocator& allocator) { + return (*this).template PushBack(value, allocator); + } + + //! Append a primitive value at the end of the array. + /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t + \param value Value of primitive type T to be appended. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \pre IsArray() == true + \return The value itself for fluent API. + \note If the number of elements to be appended is known, calls Reserve() once first may be more efficient. + + \note The source type \c T explicitly disallows all pointer types, + especially (\c const) \ref Ch*. This helps avoiding implicitly + referencing character strings with insufficient lifetime, use + \ref PushBack(GenericValue&, Allocator&) or \ref + PushBack(StringRefType, Allocator&). + All other pointer types would implicitly convert to \c bool, + use an explicit cast instead, if needed. + \note Amortized constant time complexity. + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (GenericValue&)) + PushBack(T value, Allocator& allocator) { + GenericValue v(value); + return PushBack(v, allocator); + } + + //! Remove the last element in the array. + /*! + \note Constant time complexity. + */ + GenericValue& PopBack() { + RAPIDJSON_ASSERT(IsArray()); + RAPIDJSON_ASSERT(!Empty()); + GetElementsPointer()[--data_.a.size].~GenericValue(); + return *this; + } + + //! Remove an element of array by iterator. + /*! + \param pos iterator to the element to remove + \pre IsArray() == true && \ref Begin() <= \c pos < \ref End() + \return Iterator following the removed element. If the iterator pos refers to the last element, the End() iterator is returned. + \note Linear time complexity. + */ + ValueIterator Erase(ConstValueIterator pos) { + return Erase(pos, pos + 1); + } + + //! Remove elements in the range [first, last) of the array. + /*! + \param first iterator to the first element to remove + \param last iterator following the last element to remove + \pre IsArray() == true && \ref Begin() <= \c first <= \c last <= \ref End() + \return Iterator following the last removed element. + \note Linear time complexity. + */ + ValueIterator Erase(ConstValueIterator first, ConstValueIterator last) { + RAPIDJSON_ASSERT(IsArray()); + RAPIDJSON_ASSERT(data_.a.size > 0); + RAPIDJSON_ASSERT(GetElementsPointer() != 0); + RAPIDJSON_ASSERT(first >= Begin()); + RAPIDJSON_ASSERT(first <= last); + RAPIDJSON_ASSERT(last <= End()); + ValueIterator pos = Begin() + (first - Begin()); + for (ValueIterator itr = pos; itr != last; ++itr) + itr->~GenericValue(); + std::memmove(pos, last, static_cast(End() - last) * sizeof(GenericValue)); + data_.a.size -= static_cast(last - first); + return pos; + } + + Array GetArray() { RAPIDJSON_ASSERT(IsArray()); return Array(*this); } + ConstArray GetArray() const { RAPIDJSON_ASSERT(IsArray()); return ConstArray(*this); } + + //@} + + //!@name Number + //@{ + + int GetInt() const { RAPIDJSON_ASSERT(data_.f.flags & kIntFlag); return data_.n.i.i; } + unsigned GetUint() const { RAPIDJSON_ASSERT(data_.f.flags & kUintFlag); return data_.n.u.u; } + int64_t GetInt64() const { RAPIDJSON_ASSERT(data_.f.flags & kInt64Flag); return data_.n.i64; } + uint64_t GetUint64() const { RAPIDJSON_ASSERT(data_.f.flags & kUint64Flag); return data_.n.u64; } + + //! Get the value as double type. + /*! \note If the value is 64-bit integer type, it may lose precision. Use \c IsLosslessDouble() to check whether the converison is lossless. + */ + double GetDouble() const { + RAPIDJSON_ASSERT(IsNumber()); + if ((data_.f.flags & kDoubleFlag) != 0) return data_.n.d; // exact type, no conversion. + if ((data_.f.flags & kIntFlag) != 0) return data_.n.i.i; // int -> double + if ((data_.f.flags & kUintFlag) != 0) return data_.n.u.u; // unsigned -> double + if ((data_.f.flags & kInt64Flag) != 0) return static_cast(data_.n.i64); // int64_t -> double (may lose precision) + RAPIDJSON_ASSERT((data_.f.flags & kUint64Flag) != 0); return static_cast(data_.n.u64); // uint64_t -> double (may lose precision) + } + + //! Get the value as float type. + /*! \note If the value is 64-bit integer type, it may lose precision. Use \c IsLosslessFloat() to check whether the converison is lossless. + */ + float GetFloat() const { + return static_cast(GetDouble()); + } + + GenericValue& SetInt(int i) { this->~GenericValue(); new (this) GenericValue(i); return *this; } + GenericValue& SetUint(unsigned u) { this->~GenericValue(); new (this) GenericValue(u); return *this; } + GenericValue& SetInt64(int64_t i64) { this->~GenericValue(); new (this) GenericValue(i64); return *this; } + GenericValue& SetUint64(uint64_t u64) { this->~GenericValue(); new (this) GenericValue(u64); return *this; } + GenericValue& SetDouble(double d) { this->~GenericValue(); new (this) GenericValue(d); return *this; } + GenericValue& SetFloat(float f) { this->~GenericValue(); new (this) GenericValue(f); return *this; } + + //@} + + //!@name String + //@{ + + const Ch* GetString() const { RAPIDJSON_ASSERT(IsString()); return (data_.f.flags & kInlineStrFlag) ? data_.ss.str : GetStringPointer(); } + + //! Get the length of string. + /*! Since rapidjson permits "\\u0000" in the json string, strlen(v.GetString()) may not equal to v.GetStringLength(). + */ + SizeType GetStringLength() const { RAPIDJSON_ASSERT(IsString()); return ((data_.f.flags & kInlineStrFlag) ? (data_.ss.GetLength()) : data_.s.length); } + + //! Set this value as a string without copying source string. + /*! This version has better performance with supplied length, and also support string containing null character. + \param s source string pointer. + \param length The length of source string, excluding the trailing null terminator. + \return The value itself for fluent API. + \post IsString() == true && GetString() == s && GetStringLength() == length + \see SetString(StringRefType) + */ + GenericValue& SetString(const Ch* s, SizeType length) { return SetString(StringRef(s, length)); } + + //! Set this value as a string without copying source string. + /*! \param s source string reference + \return The value itself for fluent API. + \post IsString() == true && GetString() == s && GetStringLength() == s.length + */ + GenericValue& SetString(StringRefType s) { this->~GenericValue(); SetStringRaw(s); return *this; } + + //! Set this value as a string by copying from source string. + /*! This version has better performance with supplied length, and also support string containing null character. + \param s source string. + \param length The length of source string, excluding the trailing null terminator. + \param allocator Allocator for allocating copied buffer. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \post IsString() == true && GetString() != s && strcmp(GetString(),s) == 0 && GetStringLength() == length + */ + GenericValue& SetString(const Ch* s, SizeType length, Allocator& allocator) { this->~GenericValue(); SetStringRaw(StringRef(s, length), allocator); return *this; } + + //! Set this value as a string by copying from source string. + /*! \param s source string. + \param allocator Allocator for allocating copied buffer. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \post IsString() == true && GetString() != s && strcmp(GetString(),s) == 0 && GetStringLength() == length + */ + GenericValue& SetString(const Ch* s, Allocator& allocator) { return SetString(s, internal::StrLen(s), allocator); } + +#if RAPIDJSON_HAS_STDSTRING + //! Set this value as a string by copying from source string. + /*! \param s source string. + \param allocator Allocator for allocating copied buffer. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \post IsString() == true && GetString() != s.data() && strcmp(GetString(),s.data() == 0 && GetStringLength() == s.size() + \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. + */ + GenericValue& SetString(const std::basic_string& s, Allocator& allocator) { return SetString(s.data(), SizeType(s.size()), allocator); } +#endif + + //@} + + //!@name Array + //@{ + + //! Templated version for checking whether this value is type T. + /*! + \tparam T Either \c bool, \c int, \c unsigned, \c int64_t, \c uint64_t, \c double, \c float, \c const \c char*, \c std::basic_string + */ + template + bool Is() const { return internal::TypeHelper::Is(*this); } + + template + T Get() const { return internal::TypeHelper::Get(*this); } + + template + T Get() { return internal::TypeHelper::Get(*this); } + + template + ValueType& Set(const T& data) { return internal::TypeHelper::Set(*this, data); } + + template + ValueType& Set(const T& data, AllocatorType& allocator) { return internal::TypeHelper::Set(*this, data, allocator); } + + //@} + + //! Generate events of this value to a Handler. + /*! This function adopts the GoF visitor pattern. + Typical usage is to output this JSON value as JSON text via Writer, which is a Handler. + It can also be used to deep clone this value via GenericDocument, which is also a Handler. + \tparam Handler type of handler. + \param handler An object implementing concept Handler. + */ + template + bool Accept(Handler& handler) const { + switch(GetType()) { + case kNullType: return handler.Null(); + case kFalseType: return handler.Bool(false); + case kTrueType: return handler.Bool(true); + + case kObjectType: + if (RAPIDJSON_UNLIKELY(!handler.StartObject())) + return false; + for (ConstMemberIterator m = MemberBegin(); m != MemberEnd(); ++m) { + RAPIDJSON_ASSERT(m->name.IsString()); // User may change the type of name by MemberIterator. + if (RAPIDJSON_UNLIKELY(!handler.Key(m->name.GetString(), m->name.GetStringLength(), (m->name.data_.f.flags & kCopyFlag) != 0))) + return false; + if (RAPIDJSON_UNLIKELY(!m->value.Accept(handler))) + return false; + } + return handler.EndObject(data_.o.size); + + case kArrayType: + if (RAPIDJSON_UNLIKELY(!handler.StartArray())) + return false; + for (const GenericValue* v = Begin(); v != End(); ++v) + if (RAPIDJSON_UNLIKELY(!v->Accept(handler))) + return false; + return handler.EndArray(data_.a.size); + + case kStringType: + return handler.String(GetString(), GetStringLength(), (data_.f.flags & kCopyFlag) != 0); + + default: + RAPIDJSON_ASSERT(GetType() == kNumberType); + if (IsDouble()) return handler.Double(data_.n.d); + else if (IsInt()) return handler.Int(data_.n.i.i); + else if (IsUint()) return handler.Uint(data_.n.u.u); + else if (IsInt64()) return handler.Int64(data_.n.i64); + else return handler.Uint64(data_.n.u64); + } + } + +private: + template friend class GenericValue; + template friend class GenericDocument; + + enum { + kBoolFlag = 0x0008, + kNumberFlag = 0x0010, + kIntFlag = 0x0020, + kUintFlag = 0x0040, + kInt64Flag = 0x0080, + kUint64Flag = 0x0100, + kDoubleFlag = 0x0200, + kStringFlag = 0x0400, + kCopyFlag = 0x0800, + kInlineStrFlag = 0x1000, + + // Initial flags of different types. + kNullFlag = kNullType, + kTrueFlag = kTrueType | kBoolFlag, + kFalseFlag = kFalseType | kBoolFlag, + kNumberIntFlag = kNumberType | kNumberFlag | kIntFlag | kInt64Flag, + kNumberUintFlag = kNumberType | kNumberFlag | kUintFlag | kUint64Flag | kInt64Flag, + kNumberInt64Flag = kNumberType | kNumberFlag | kInt64Flag, + kNumberUint64Flag = kNumberType | kNumberFlag | kUint64Flag, + kNumberDoubleFlag = kNumberType | kNumberFlag | kDoubleFlag, + kNumberAnyFlag = kNumberType | kNumberFlag | kIntFlag | kInt64Flag | kUintFlag | kUint64Flag | kDoubleFlag, + kConstStringFlag = kStringType | kStringFlag, + kCopyStringFlag = kStringType | kStringFlag | kCopyFlag, + kShortStringFlag = kStringType | kStringFlag | kCopyFlag | kInlineStrFlag, + kObjectFlag = kObjectType, + kArrayFlag = kArrayType, + + kTypeMask = 0x07 + }; + + static const SizeType kDefaultArrayCapacity = 16; + static const SizeType kDefaultObjectCapacity = 16; + + struct Flag { +#if RAPIDJSON_48BITPOINTER_OPTIMIZATION + char payload[sizeof(SizeType) * 2 + 6]; // 2 x SizeType + lower 48-bit pointer +#elif RAPIDJSON_64BIT + char payload[sizeof(SizeType) * 2 + sizeof(void*) + 6]; // 6 padding bytes +#else + char payload[sizeof(SizeType) * 2 + sizeof(void*) + 2]; // 2 padding bytes +#endif + uint16_t flags; + }; + + struct String { + SizeType length; + SizeType hashcode; //!< reserved + const Ch* str; + }; // 12 bytes in 32-bit mode, 16 bytes in 64-bit mode + + // implementation detail: ShortString can represent zero-terminated strings up to MaxSize chars + // (excluding the terminating zero) and store a value to determine the length of the contained + // string in the last character str[LenPos] by storing "MaxSize - length" there. If the string + // to store has the maximal length of MaxSize then str[LenPos] will be 0 and therefore act as + // the string terminator as well. For getting the string length back from that value just use + // "MaxSize - str[LenPos]". + // This allows to store 13-chars strings in 32-bit mode, 21-chars strings in 64-bit mode, + // 13-chars strings for RAPIDJSON_48BITPOINTER_OPTIMIZATION=1 inline (for `UTF8`-encoded strings). + struct ShortString { + enum { MaxChars = sizeof(static_cast(0)->payload) / sizeof(Ch), MaxSize = MaxChars - 1, LenPos = MaxSize }; + Ch str[MaxChars]; + + inline static bool Usable(SizeType len) { return (MaxSize >= len); } + inline void SetLength(SizeType len) { str[LenPos] = static_cast(MaxSize - len); } + inline SizeType GetLength() const { return static_cast(MaxSize - str[LenPos]); } + }; // at most as many bytes as "String" above => 12 bytes in 32-bit mode, 16 bytes in 64-bit mode + + // By using proper binary layout, retrieval of different integer types do not need conversions. + union Number { +#if RAPIDJSON_ENDIAN == RAPIDJSON_LITTLEENDIAN + struct I { + int i; + char padding[4]; + }i; + struct U { + unsigned u; + char padding2[4]; + }u; +#else + struct I { + char padding[4]; + int i; + }i; + struct U { + char padding2[4]; + unsigned u; + }u; +#endif + int64_t i64; + uint64_t u64; + double d; + }; // 8 bytes + + struct ObjectData { + SizeType size; + SizeType capacity; + Member* members; + }; // 12 bytes in 32-bit mode, 16 bytes in 64-bit mode + + struct ArrayData { + SizeType size; + SizeType capacity; + GenericValue* elements; + }; // 12 bytes in 32-bit mode, 16 bytes in 64-bit mode + + union Data { + String s; + ShortString ss; + Number n; + ObjectData o; + ArrayData a; + Flag f; + }; // 16 bytes in 32-bit mode, 24 bytes in 64-bit mode, 16 bytes in 64-bit with RAPIDJSON_48BITPOINTER_OPTIMIZATION + + RAPIDJSON_FORCEINLINE const Ch* GetStringPointer() const { return RAPIDJSON_GETPOINTER(Ch, data_.s.str); } + RAPIDJSON_FORCEINLINE const Ch* SetStringPointer(const Ch* str) { return RAPIDJSON_SETPOINTER(Ch, data_.s.str, str); } + RAPIDJSON_FORCEINLINE GenericValue* GetElementsPointer() const { return RAPIDJSON_GETPOINTER(GenericValue, data_.a.elements); } + RAPIDJSON_FORCEINLINE GenericValue* SetElementsPointer(GenericValue* elements) { return RAPIDJSON_SETPOINTER(GenericValue, data_.a.elements, elements); } + RAPIDJSON_FORCEINLINE Member* GetMembersPointer() const { return RAPIDJSON_GETPOINTER(Member, data_.o.members); } + RAPIDJSON_FORCEINLINE Member* SetMembersPointer(Member* members) { return RAPIDJSON_SETPOINTER(Member, data_.o.members, members); } + + // Initialize this value as array with initial data, without calling destructor. + void SetArrayRaw(GenericValue* values, SizeType count, Allocator& allocator) { + data_.f.flags = kArrayFlag; + if (count) { + GenericValue* e = static_cast(allocator.Malloc(count * sizeof(GenericValue))); + SetElementsPointer(e); + std::memcpy(e, values, count * sizeof(GenericValue)); + } + else + SetElementsPointer(0); + data_.a.size = data_.a.capacity = count; + } + + //! Initialize this value as object with initial data, without calling destructor. + void SetObjectRaw(Member* members, SizeType count, Allocator& allocator) { + data_.f.flags = kObjectFlag; + if (count) { + Member* m = static_cast(allocator.Malloc(count * sizeof(Member))); + SetMembersPointer(m); + std::memcpy(m, members, count * sizeof(Member)); + } + else + SetMembersPointer(0); + data_.o.size = data_.o.capacity = count; + } + + //! Initialize this value as constant string, without calling destructor. + void SetStringRaw(StringRefType s) RAPIDJSON_NOEXCEPT { + data_.f.flags = kConstStringFlag; + SetStringPointer(s); + data_.s.length = s.length; + } + + //! Initialize this value as copy string with initial data, without calling destructor. + void SetStringRaw(StringRefType s, Allocator& allocator) { + Ch* str = 0; + if (ShortString::Usable(s.length)) { + data_.f.flags = kShortStringFlag; + data_.ss.SetLength(s.length); + str = data_.ss.str; + } else { + data_.f.flags = kCopyStringFlag; + data_.s.length = s.length; + str = static_cast(allocator.Malloc((s.length + 1) * sizeof(Ch))); + SetStringPointer(str); + } + std::memcpy(str, s, s.length * sizeof(Ch)); + str[s.length] = '\0'; + } + + //! Assignment without calling destructor + void RawAssign(GenericValue& rhs) RAPIDJSON_NOEXCEPT { + data_ = rhs.data_; + // data_.f.flags = rhs.data_.f.flags; + rhs.data_.f.flags = kNullFlag; + } + + template + bool StringEqual(const GenericValue& rhs) const { + RAPIDJSON_ASSERT(IsString()); + RAPIDJSON_ASSERT(rhs.IsString()); + + const SizeType len1 = GetStringLength(); + const SizeType len2 = rhs.GetStringLength(); + if(len1 != len2) { return false; } + + const Ch* const str1 = GetString(); + const Ch* const str2 = rhs.GetString(); + if(str1 == str2) { return true; } // fast path for constant string + + return (std::memcmp(str1, str2, sizeof(Ch) * len1) == 0); + } + + Data data_; +}; + +//! GenericValue with UTF8 encoding +typedef GenericValue > Value; + +/////////////////////////////////////////////////////////////////////////////// +// GenericDocument + +//! A document for parsing JSON text as DOM. +/*! + \note implements Handler concept + \tparam Encoding Encoding for both parsing and string storage. + \tparam Allocator Allocator for allocating memory for the DOM + \tparam StackAllocator Allocator for allocating memory for stack during parsing. + \warning Although GenericDocument inherits from GenericValue, the API does \b not provide any virtual functions, especially no virtual destructor. To avoid memory leaks, do not \c delete a GenericDocument object via a pointer to a GenericValue. +*/ +template , typename StackAllocator = CrtAllocator> +class GenericDocument : public GenericValue { +public: + typedef typename Encoding::Ch Ch; //!< Character type derived from Encoding. + typedef GenericValue ValueType; //!< Value type of the document. + typedef Allocator AllocatorType; //!< Allocator type from template parameter. + + //! Constructor + /*! Creates an empty document of specified type. + \param type Mandatory type of object to create. + \param allocator Optional allocator for allocating memory. + \param stackCapacity Optional initial capacity of stack in bytes. + \param stackAllocator Optional allocator for allocating memory for stack. + */ + explicit GenericDocument(Type type, Allocator* allocator = 0, size_t stackCapacity = kDefaultStackCapacity, StackAllocator* stackAllocator = 0) : + GenericValue(type), allocator_(allocator), ownAllocator_(0), stack_(stackAllocator, stackCapacity), parseResult_() + { + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator()); + } + + //! Constructor + /*! Creates an empty document which type is Null. + \param allocator Optional allocator for allocating memory. + \param stackCapacity Optional initial capacity of stack in bytes. + \param stackAllocator Optional allocator for allocating memory for stack. + */ + GenericDocument(Allocator* allocator = 0, size_t stackCapacity = kDefaultStackCapacity, StackAllocator* stackAllocator = 0) : + allocator_(allocator), ownAllocator_(0), stack_(stackAllocator, stackCapacity), parseResult_() + { + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator()); + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Move constructor in C++11 + GenericDocument(GenericDocument&& rhs) RAPIDJSON_NOEXCEPT + : ValueType(std::forward(rhs)), // explicit cast to avoid prohibited move from Document + allocator_(rhs.allocator_), + ownAllocator_(rhs.ownAllocator_), + stack_(std::move(rhs.stack_)), + parseResult_(rhs.parseResult_) + { + rhs.allocator_ = 0; + rhs.ownAllocator_ = 0; + rhs.parseResult_ = ParseResult(); + } +#endif + + ~GenericDocument() { + Destroy(); + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Move assignment in C++11 + GenericDocument& operator=(GenericDocument&& rhs) RAPIDJSON_NOEXCEPT + { + // The cast to ValueType is necessary here, because otherwise it would + // attempt to call GenericValue's templated assignment operator. + ValueType::operator=(std::forward(rhs)); + + // Calling the destructor here would prematurely call stack_'s destructor + Destroy(); + + allocator_ = rhs.allocator_; + ownAllocator_ = rhs.ownAllocator_; + stack_ = std::move(rhs.stack_); + parseResult_ = rhs.parseResult_; + + rhs.allocator_ = 0; + rhs.ownAllocator_ = 0; + rhs.parseResult_ = ParseResult(); + + return *this; + } +#endif + + //! Exchange the contents of this document with those of another. + /*! + \param rhs Another document. + \note Constant complexity. + \see GenericValue::Swap + */ + GenericDocument& Swap(GenericDocument& rhs) RAPIDJSON_NOEXCEPT { + ValueType::Swap(rhs); + stack_.Swap(rhs.stack_); + internal::Swap(allocator_, rhs.allocator_); + internal::Swap(ownAllocator_, rhs.ownAllocator_); + internal::Swap(parseResult_, rhs.parseResult_); + return *this; + } + + //! free-standing swap function helper + /*! + Helper function to enable support for common swap implementation pattern based on \c std::swap: + \code + void swap(MyClass& a, MyClass& b) { + using std::swap; + swap(a.doc, b.doc); + // ... + } + \endcode + \see Swap() + */ + friend inline void swap(GenericDocument& a, GenericDocument& b) RAPIDJSON_NOEXCEPT { a.Swap(b); } + + //! Populate this document by a generator which produces SAX events. + /*! \tparam Generator A functor with bool f(Handler) prototype. + \param g Generator functor which sends SAX events to the parameter. + \return The document itself for fluent API. + */ + template + GenericDocument& Populate(Generator& g) { + ClearStackOnExit scope(*this); + if (g(*this)) { + RAPIDJSON_ASSERT(stack_.GetSize() == sizeof(ValueType)); // Got one and only one root object + ValueType::operator=(*stack_.template Pop(1));// Move value from stack to document + } + return *this; + } + + //!@name Parse from stream + //!@{ + + //! Parse JSON text from an input stream (with Encoding conversion) + /*! \tparam parseFlags Combination of \ref ParseFlag. + \tparam SourceEncoding Encoding of input stream + \tparam InputStream Type of input stream, implementing Stream concept + \param is Input stream to be parsed. + \return The document itself for fluent API. + */ + template + GenericDocument& ParseStream(InputStream& is) { + GenericReader reader( + stack_.HasAllocator() ? &stack_.GetAllocator() : 0); + ClearStackOnExit scope(*this); + parseResult_ = reader.template Parse(is, *this); + if (parseResult_) { + RAPIDJSON_ASSERT(stack_.GetSize() == sizeof(ValueType)); // Got one and only one root object + ValueType::operator=(*stack_.template Pop(1));// Move value from stack to document + } + return *this; + } + + //! Parse JSON text from an input stream + /*! \tparam parseFlags Combination of \ref ParseFlag. + \tparam InputStream Type of input stream, implementing Stream concept + \param is Input stream to be parsed. + \return The document itself for fluent API. + */ + template + GenericDocument& ParseStream(InputStream& is) { + return ParseStream(is); + } + + //! Parse JSON text from an input stream (with \ref kParseDefaultFlags) + /*! \tparam InputStream Type of input stream, implementing Stream concept + \param is Input stream to be parsed. + \return The document itself for fluent API. + */ + template + GenericDocument& ParseStream(InputStream& is) { + return ParseStream(is); + } + //!@} + + //!@name Parse in-place from mutable string + //!@{ + + //! Parse JSON text from a mutable string + /*! \tparam parseFlags Combination of \ref ParseFlag. + \param str Mutable zero-terminated string to be parsed. + \return The document itself for fluent API. + */ + template + GenericDocument& ParseInsitu(Ch* str) { + GenericInsituStringStream s(str); + return ParseStream(s); + } + + //! Parse JSON text from a mutable string (with \ref kParseDefaultFlags) + /*! \param str Mutable zero-terminated string to be parsed. + \return The document itself for fluent API. + */ + GenericDocument& ParseInsitu(Ch* str) { + return ParseInsitu(str); + } + //!@} + + //!@name Parse from read-only string + //!@{ + + //! Parse JSON text from a read-only string (with Encoding conversion) + /*! \tparam parseFlags Combination of \ref ParseFlag (must not contain \ref kParseInsituFlag). + \tparam SourceEncoding Transcoding from input Encoding + \param str Read-only zero-terminated string to be parsed. + */ + template + GenericDocument& Parse(const typename SourceEncoding::Ch* str) { + RAPIDJSON_ASSERT(!(parseFlags & kParseInsituFlag)); + GenericStringStream s(str); + return ParseStream(s); + } + + //! Parse JSON text from a read-only string + /*! \tparam parseFlags Combination of \ref ParseFlag (must not contain \ref kParseInsituFlag). + \param str Read-only zero-terminated string to be parsed. + */ + template + GenericDocument& Parse(const Ch* str) { + return Parse(str); + } + + //! Parse JSON text from a read-only string (with \ref kParseDefaultFlags) + /*! \param str Read-only zero-terminated string to be parsed. + */ + GenericDocument& Parse(const Ch* str) { + return Parse(str); + } + + template + GenericDocument& Parse(const typename SourceEncoding::Ch* str, size_t length) { + RAPIDJSON_ASSERT(!(parseFlags & kParseInsituFlag)); + MemoryStream ms(static_cast(str), length * sizeof(typename SourceEncoding::Ch)); + EncodedInputStream is(ms); + ParseStream(is); + return *this; + } + + template + GenericDocument& Parse(const Ch* str, size_t length) { + return Parse(str, length); + } + + GenericDocument& Parse(const Ch* str, size_t length) { + return Parse(str, length); + } + +#if RAPIDJSON_HAS_STDSTRING + template + GenericDocument& Parse(const std::basic_string& str) { + // c_str() is constant complexity according to standard. Should be faster than Parse(const char*, size_t) + return Parse(str.c_str()); + } + + template + GenericDocument& Parse(const std::basic_string& str) { + return Parse(str.c_str()); + } + + GenericDocument& Parse(const std::basic_string& str) { + return Parse(str); + } +#endif // RAPIDJSON_HAS_STDSTRING + + //!@} + + //!@name Handling parse errors + //!@{ + + //! Whether a parse error has occured in the last parsing. + bool HasParseError() const { return parseResult_.IsError(); } + + //! Get the \ref ParseErrorCode of last parsing. + ParseErrorCode GetParseError() const { return parseResult_.Code(); } + + //! Get the position of last parsing error in input, 0 otherwise. + size_t GetErrorOffset() const { return parseResult_.Offset(); } + + //! Implicit conversion to get the last parse result +#ifndef __clang // -Wdocumentation + /*! \return \ref ParseResult of the last parse operation + + \code + Document doc; + ParseResult ok = doc.Parse(json); + if (!ok) + printf( "JSON parse error: %s (%u)\n", GetParseError_En(ok.Code()), ok.Offset()); + \endcode + */ +#endif + operator ParseResult() const { return parseResult_; } + //!@} + + //! Get the allocator of this document. + Allocator& GetAllocator() { + RAPIDJSON_ASSERT(allocator_); + return *allocator_; + } + + //! Get the capacity of stack in bytes. + size_t GetStackCapacity() const { return stack_.GetCapacity(); } + +private: + // clear stack on any exit from ParseStream, e.g. due to exception + struct ClearStackOnExit { + explicit ClearStackOnExit(GenericDocument& d) : d_(d) {} + ~ClearStackOnExit() { d_.ClearStack(); } + private: + ClearStackOnExit(const ClearStackOnExit&); + ClearStackOnExit& operator=(const ClearStackOnExit&); + GenericDocument& d_; + }; + + // callers of the following private Handler functions + // template friend class GenericReader; // for parsing + template friend class GenericValue; // for deep copying + +public: + // Implementation of Handler + bool Null() { new (stack_.template Push()) ValueType(); return true; } + bool Bool(bool b) { new (stack_.template Push()) ValueType(b); return true; } + bool Int(int i) { new (stack_.template Push()) ValueType(i); return true; } + bool Uint(unsigned i) { new (stack_.template Push()) ValueType(i); return true; } + bool Int64(int64_t i) { new (stack_.template Push()) ValueType(i); return true; } + bool Uint64(uint64_t i) { new (stack_.template Push()) ValueType(i); return true; } + bool Double(double d) { new (stack_.template Push()) ValueType(d); return true; } + + bool RawNumber(const Ch* str, SizeType length, bool copy) { + if (copy) + new (stack_.template Push()) ValueType(str, length, GetAllocator()); + else + new (stack_.template Push()) ValueType(str, length); + return true; + } + + bool String(const Ch* str, SizeType length, bool copy) { + if (copy) + new (stack_.template Push()) ValueType(str, length, GetAllocator()); + else + new (stack_.template Push()) ValueType(str, length); + return true; + } + + bool StartObject() { new (stack_.template Push()) ValueType(kObjectType); return true; } + + bool Key(const Ch* str, SizeType length, bool copy) { return String(str, length, copy); } + + bool EndObject(SizeType memberCount) { + typename ValueType::Member* members = stack_.template Pop(memberCount); + stack_.template Top()->SetObjectRaw(members, memberCount, GetAllocator()); + return true; + } + + bool StartArray() { new (stack_.template Push()) ValueType(kArrayType); return true; } + + bool EndArray(SizeType elementCount) { + ValueType* elements = stack_.template Pop(elementCount); + stack_.template Top()->SetArrayRaw(elements, elementCount, GetAllocator()); + return true; + } + +private: + //! Prohibit copying + GenericDocument(const GenericDocument&); + //! Prohibit assignment + GenericDocument& operator=(const GenericDocument&); + + void ClearStack() { + if (Allocator::kNeedFree) + while (stack_.GetSize() > 0) // Here assumes all elements in stack array are GenericValue (Member is actually 2 GenericValue objects) + (stack_.template Pop(1))->~ValueType(); + else + stack_.Clear(); + stack_.ShrinkToFit(); + } + + void Destroy() { + RAPIDJSON_DELETE(ownAllocator_); + } + + static const size_t kDefaultStackCapacity = 1024; + Allocator* allocator_; + Allocator* ownAllocator_; + internal::Stack stack_; + ParseResult parseResult_; +}; + +//! GenericDocument with UTF8 encoding +typedef GenericDocument > Document; + +// defined here due to the dependency on GenericDocument +template +template +inline +GenericValue::GenericValue(const GenericValue& rhs, Allocator& allocator) +{ + switch (rhs.GetType()) { + case kObjectType: + case kArrayType: { // perform deep copy via SAX Handler + GenericDocument d(&allocator); + rhs.Accept(d); + RawAssign(*d.stack_.template Pop(1)); + } + break; + case kStringType: + if (rhs.data_.f.flags == kConstStringFlag) { + data_.f.flags = rhs.data_.f.flags; + data_ = *reinterpret_cast(&rhs.data_); + } else { + SetStringRaw(StringRef(rhs.GetString(), rhs.GetStringLength()), allocator); + } + break; + default: + data_.f.flags = rhs.data_.f.flags; + data_ = *reinterpret_cast(&rhs.data_); + break; + } +} + +//! Helper class for accessing Value of array type. +/*! + Instance of this helper class is obtained by \c GenericValue::GetArray(). + In addition to all APIs for array type, it provides range-based for loop if \c RAPIDJSON_HAS_CXX11_RANGE_FOR=1. +*/ +template +class GenericArray { +public: + typedef GenericArray ConstArray; + typedef GenericArray Array; + typedef ValueT PlainType; + typedef typename internal::MaybeAddConst::Type ValueType; + typedef ValueType* ValueIterator; // This may be const or non-const iterator + typedef const ValueT* ConstValueIterator; + typedef typename ValueType::AllocatorType AllocatorType; + typedef typename ValueType::StringRefType StringRefType; + + template + friend class GenericValue; + + GenericArray(const GenericArray& rhs) : value_(rhs.value_) {} + GenericArray& operator=(const GenericArray& rhs) { value_ = rhs.value_; return *this; } + ~GenericArray() {} + + SizeType Size() const { return value_.Size(); } + SizeType Capacity() const { return value_.Capacity(); } + bool Empty() const { return value_.Empty(); } + void Clear() const { value_.Clear(); } + ValueType& operator[](SizeType index) const { return value_[index]; } + ValueIterator Begin() const { return value_.Begin(); } + ValueIterator End() const { return value_.End(); } + GenericArray Reserve(SizeType newCapacity, AllocatorType &allocator) const { value_.Reserve(newCapacity, allocator); return *this; } + GenericArray PushBack(ValueType& value, AllocatorType& allocator) const { value_.PushBack(value, allocator); return *this; } +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericArray PushBack(ValueType&& value, AllocatorType& allocator) const { value_.PushBack(value, allocator); return *this; } +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericArray PushBack(StringRefType value, AllocatorType& allocator) const { value_.PushBack(value, allocator); return *this; } + template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (const GenericArray&)) PushBack(T value, AllocatorType& allocator) const { value_.PushBack(value, allocator); return *this; } + GenericArray PopBack() const { value_.PopBack(); return *this; } + ValueIterator Erase(ConstValueIterator pos) const { return value_.Erase(pos); } + ValueIterator Erase(ConstValueIterator first, ConstValueIterator last) const { return value_.Erase(first, last); } + +#if RAPIDJSON_HAS_CXX11_RANGE_FOR + ValueIterator begin() const { return value_.Begin(); } + ValueIterator end() const { return value_.End(); } +#endif + +private: + GenericArray(); + GenericArray(ValueType& value) : value_(value) {} + ValueType& value_; +}; + +//! Helper class for accessing Value of object type. +/*! + Instance of this helper class is obtained by \c GenericValue::GetObject(). + In addition to all APIs for array type, it provides range-based for loop if \c RAPIDJSON_HAS_CXX11_RANGE_FOR=1. +*/ +template +class GenericObject { +public: + typedef GenericObject ConstObject; + typedef GenericObject Object; + typedef ValueT PlainType; + typedef typename internal::MaybeAddConst::Type ValueType; + typedef GenericMemberIterator MemberIterator; // This may be const or non-const iterator + typedef GenericMemberIterator ConstMemberIterator; + typedef typename ValueType::AllocatorType AllocatorType; + typedef typename ValueType::StringRefType StringRefType; + typedef typename ValueType::EncodingType EncodingType; + typedef typename ValueType::Ch Ch; + + template + friend class GenericValue; + + GenericObject(const GenericObject& rhs) : value_(rhs.value_) {} + GenericObject& operator=(const GenericObject& rhs) { value_ = rhs.value_; return *this; } + ~GenericObject() {} + + SizeType MemberCount() const { return value_.MemberCount(); } + bool ObjectEmpty() const { return value_.ObjectEmpty(); } + template ValueType& operator[](T* name) const { return value_[name]; } + template ValueType& operator[](const GenericValue& name) const { return value_[name]; } +#if RAPIDJSON_HAS_STDSTRING + ValueType& operator[](const std::basic_string& name) const { return value_[name]; } +#endif + MemberIterator MemberBegin() const { return value_.MemberBegin(); } + MemberIterator MemberEnd() const { return value_.MemberEnd(); } + bool HasMember(const Ch* name) const { return value_.HasMember(name); } +#if RAPIDJSON_HAS_STDSTRING + bool HasMember(const std::basic_string& name) const { return value_.HasMember(name); } +#endif + template bool HasMember(const GenericValue& name) const { return value_.HasMember(name); } + MemberIterator FindMember(const Ch* name) const { return value_.FindMember(name); } + template MemberIterator FindMember(const GenericValue& name) const { return value_.FindMember(name); } +#if RAPIDJSON_HAS_STDSTRING + MemberIterator FindMember(const std::basic_string& name) const { return value_.FindMember(name); } +#endif + GenericObject AddMember(ValueType& name, ValueType& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + GenericObject AddMember(ValueType& name, StringRefType value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } +#if RAPIDJSON_HAS_STDSTRING + GenericObject AddMember(ValueType& name, std::basic_string& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } +#endif + template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) AddMember(ValueType& name, T value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericObject AddMember(ValueType&& name, ValueType&& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + GenericObject AddMember(ValueType&& name, ValueType& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + GenericObject AddMember(ValueType& name, ValueType&& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + GenericObject AddMember(StringRefType name, ValueType&& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericObject AddMember(StringRefType name, ValueType& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + GenericObject AddMember(StringRefType name, StringRefType value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (GenericObject)) AddMember(StringRefType name, T value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + void RemoveAllMembers() { return value_.RemoveAllMembers(); } + bool RemoveMember(const Ch* name) const { return value_.RemoveMember(name); } +#if RAPIDJSON_HAS_STDSTRING + bool RemoveMember(const std::basic_string& name) const { return value_.RemoveMember(name); } +#endif + template bool RemoveMember(const GenericValue& name) const { return value_.RemoveMember(name); } + MemberIterator RemoveMember(MemberIterator m) const { return value_.RemoveMember(m); } + MemberIterator EraseMember(ConstMemberIterator pos) const { return value_.EraseMember(pos); } + MemberIterator EraseMember(ConstMemberIterator first, ConstMemberIterator last) const { return value_.EraseMember(first, last); } + bool EraseMember(const Ch* name) const { return value_.EraseMember(name); } +#if RAPIDJSON_HAS_STDSTRING + bool EraseMember(const std::basic_string& name) const { return EraseMember(ValueType(StringRef(name))); } +#endif + template bool EraseMember(const GenericValue& name) const { return value_.EraseMember(name); } + +#if RAPIDJSON_HAS_CXX11_RANGE_FOR + MemberIterator begin() const { return value_.MemberBegin(); } + MemberIterator end() const { return value_.MemberEnd(); } +#endif + +private: + GenericObject(); + GenericObject(ValueType& value) : value_(value) {} + ValueType& value_; +}; + +RAPIDJSON_NAMESPACE_END +RAPIDJSON_DIAG_POP + +#endif // RAPIDJSON_DOCUMENT_H_ diff --git a/Source/Dependencies/rapidjson/include/rapidjson/encodedstream.h b/Source/Dependencies/rapidjson/include/rapidjson/encodedstream.h new file mode 100644 index 000000000..145068386 --- /dev/null +++ b/Source/Dependencies/rapidjson/include/rapidjson/encodedstream.h @@ -0,0 +1,299 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ENCODEDSTREAM_H_ +#define RAPIDJSON_ENCODEDSTREAM_H_ + +#include "stream.h" +#include "memorystream.h" + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Input byte stream wrapper with a statically bound encoding. +/*! + \tparam Encoding The interpretation of encoding of the stream. Either UTF8, UTF16LE, UTF16BE, UTF32LE, UTF32BE. + \tparam InputByteStream Type of input byte stream. For example, FileReadStream. +*/ +template +class EncodedInputStream { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); +public: + typedef typename Encoding::Ch Ch; + + EncodedInputStream(InputByteStream& is) : is_(is) { + current_ = Encoding::TakeBOM(is_); + } + + Ch Peek() const { return current_; } + Ch Take() { Ch c = current_; current_ = Encoding::Take(is_); return c; } + size_t Tell() const { return is_.Tell(); } + + // Not implemented + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + EncodedInputStream(const EncodedInputStream&); + EncodedInputStream& operator=(const EncodedInputStream&); + + InputByteStream& is_; + Ch current_; +}; + +//! Specialized for UTF8 MemoryStream. +template <> +class EncodedInputStream, MemoryStream> { +public: + typedef UTF8<>::Ch Ch; + + EncodedInputStream(MemoryStream& is) : is_(is) { + if (static_cast(is_.Peek()) == 0xEFu) is_.Take(); + if (static_cast(is_.Peek()) == 0xBBu) is_.Take(); + if (static_cast(is_.Peek()) == 0xBFu) is_.Take(); + } + Ch Peek() const { return is_.Peek(); } + Ch Take() { return is_.Take(); } + size_t Tell() const { return is_.Tell(); } + + // Not implemented + void Put(Ch) {} + void Flush() {} + Ch* PutBegin() { return 0; } + size_t PutEnd(Ch*) { return 0; } + + MemoryStream& is_; + +private: + EncodedInputStream(const EncodedInputStream&); + EncodedInputStream& operator=(const EncodedInputStream&); +}; + +//! Output byte stream wrapper with statically bound encoding. +/*! + \tparam Encoding The interpretation of encoding of the stream. Either UTF8, UTF16LE, UTF16BE, UTF32LE, UTF32BE. + \tparam OutputByteStream Type of input byte stream. For example, FileWriteStream. +*/ +template +class EncodedOutputStream { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); +public: + typedef typename Encoding::Ch Ch; + + EncodedOutputStream(OutputByteStream& os, bool putBOM = true) : os_(os) { + if (putBOM) + Encoding::PutBOM(os_); + } + + void Put(Ch c) { Encoding::Put(os_, c); } + void Flush() { os_.Flush(); } + + // Not implemented + Ch Peek() const { RAPIDJSON_ASSERT(false); return 0;} + Ch Take() { RAPIDJSON_ASSERT(false); return 0;} + size_t Tell() const { RAPIDJSON_ASSERT(false); return 0; } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + EncodedOutputStream(const EncodedOutputStream&); + EncodedOutputStream& operator=(const EncodedOutputStream&); + + OutputByteStream& os_; +}; + +#define RAPIDJSON_ENCODINGS_FUNC(x) UTF8::x, UTF16LE::x, UTF16BE::x, UTF32LE::x, UTF32BE::x + +//! Input stream wrapper with dynamically bound encoding and automatic encoding detection. +/*! + \tparam CharType Type of character for reading. + \tparam InputByteStream type of input byte stream to be wrapped. +*/ +template +class AutoUTFInputStream { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); +public: + typedef CharType Ch; + + //! Constructor. + /*! + \param is input stream to be wrapped. + \param type UTF encoding type if it is not detected from the stream. + */ + AutoUTFInputStream(InputByteStream& is, UTFType type = kUTF8) : is_(&is), type_(type), hasBOM_(false) { + RAPIDJSON_ASSERT(type >= kUTF8 && type <= kUTF32BE); + DetectType(); + static const TakeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Take) }; + takeFunc_ = f[type_]; + current_ = takeFunc_(*is_); + } + + UTFType GetType() const { return type_; } + bool HasBOM() const { return hasBOM_; } + + Ch Peek() const { return current_; } + Ch Take() { Ch c = current_; current_ = takeFunc_(*is_); return c; } + size_t Tell() const { return is_->Tell(); } + + // Not implemented + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + AutoUTFInputStream(const AutoUTFInputStream&); + AutoUTFInputStream& operator=(const AutoUTFInputStream&); + + // Detect encoding type with BOM or RFC 4627 + void DetectType() { + // BOM (Byte Order Mark): + // 00 00 FE FF UTF-32BE + // FF FE 00 00 UTF-32LE + // FE FF UTF-16BE + // FF FE UTF-16LE + // EF BB BF UTF-8 + + const unsigned char* c = reinterpret_cast(is_->Peek4()); + if (!c) + return; + + unsigned bom = static_cast(c[0] | (c[1] << 8) | (c[2] << 16) | (c[3] << 24)); + hasBOM_ = false; + if (bom == 0xFFFE0000) { type_ = kUTF32BE; hasBOM_ = true; is_->Take(); is_->Take(); is_->Take(); is_->Take(); } + else if (bom == 0x0000FEFF) { type_ = kUTF32LE; hasBOM_ = true; is_->Take(); is_->Take(); is_->Take(); is_->Take(); } + else if ((bom & 0xFFFF) == 0xFFFE) { type_ = kUTF16BE; hasBOM_ = true; is_->Take(); is_->Take(); } + else if ((bom & 0xFFFF) == 0xFEFF) { type_ = kUTF16LE; hasBOM_ = true; is_->Take(); is_->Take(); } + else if ((bom & 0xFFFFFF) == 0xBFBBEF) { type_ = kUTF8; hasBOM_ = true; is_->Take(); is_->Take(); is_->Take(); } + + // RFC 4627: Section 3 + // "Since the first two characters of a JSON text will always be ASCII + // characters [RFC0020], it is possible to determine whether an octet + // stream is UTF-8, UTF-16 (BE or LE), or UTF-32 (BE or LE) by looking + // at the pattern of nulls in the first four octets." + // 00 00 00 xx UTF-32BE + // 00 xx 00 xx UTF-16BE + // xx 00 00 00 UTF-32LE + // xx 00 xx 00 UTF-16LE + // xx xx xx xx UTF-8 + + if (!hasBOM_) { + unsigned pattern = (c[0] ? 1 : 0) | (c[1] ? 2 : 0) | (c[2] ? 4 : 0) | (c[3] ? 8 : 0); + switch (pattern) { + case 0x08: type_ = kUTF32BE; break; + case 0x0A: type_ = kUTF16BE; break; + case 0x01: type_ = kUTF32LE; break; + case 0x05: type_ = kUTF16LE; break; + case 0x0F: type_ = kUTF8; break; + default: break; // Use type defined by user. + } + } + + // Runtime check whether the size of character type is sufficient. It only perform checks with assertion. + if (type_ == kUTF16LE || type_ == kUTF16BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 2); + if (type_ == kUTF32LE || type_ == kUTF32BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 4); + } + + typedef Ch (*TakeFunc)(InputByteStream& is); + InputByteStream* is_; + UTFType type_; + Ch current_; + TakeFunc takeFunc_; + bool hasBOM_; +}; + +//! Output stream wrapper with dynamically bound encoding and automatic encoding detection. +/*! + \tparam CharType Type of character for writing. + \tparam OutputByteStream type of output byte stream to be wrapped. +*/ +template +class AutoUTFOutputStream { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); +public: + typedef CharType Ch; + + //! Constructor. + /*! + \param os output stream to be wrapped. + \param type UTF encoding type. + \param putBOM Whether to write BOM at the beginning of the stream. + */ + AutoUTFOutputStream(OutputByteStream& os, UTFType type, bool putBOM) : os_(&os), type_(type) { + RAPIDJSON_ASSERT(type >= kUTF8 && type <= kUTF32BE); + + // Runtime check whether the size of character type is sufficient. It only perform checks with assertion. + if (type_ == kUTF16LE || type_ == kUTF16BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 2); + if (type_ == kUTF32LE || type_ == kUTF32BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 4); + + static const PutFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Put) }; + putFunc_ = f[type_]; + + if (putBOM) + PutBOM(); + } + + UTFType GetType() const { return type_; } + + void Put(Ch c) { putFunc_(*os_, c); } + void Flush() { os_->Flush(); } + + // Not implemented + Ch Peek() const { RAPIDJSON_ASSERT(false); return 0;} + Ch Take() { RAPIDJSON_ASSERT(false); return 0;} + size_t Tell() const { RAPIDJSON_ASSERT(false); return 0; } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + AutoUTFOutputStream(const AutoUTFOutputStream&); + AutoUTFOutputStream& operator=(const AutoUTFOutputStream&); + + void PutBOM() { + typedef void (*PutBOMFunc)(OutputByteStream&); + static const PutBOMFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(PutBOM) }; + f[type_](*os_); + } + + typedef void (*PutFunc)(OutputByteStream&, Ch); + + OutputByteStream* os_; + UTFType type_; + PutFunc putFunc_; +}; + +#undef RAPIDJSON_ENCODINGS_FUNC + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_FILESTREAM_H_ diff --git a/Source/Dependencies/rapidjson/include/rapidjson/encodings.h b/Source/Dependencies/rapidjson/include/rapidjson/encodings.h new file mode 100644 index 000000000..baa7c2b17 --- /dev/null +++ b/Source/Dependencies/rapidjson/include/rapidjson/encodings.h @@ -0,0 +1,716 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ENCODINGS_H_ +#define RAPIDJSON_ENCODINGS_H_ + +#include "rapidjson.h" + +#ifdef _MSC_VER +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4244) // conversion from 'type1' to 'type2', possible loss of data +RAPIDJSON_DIAG_OFF(4702) // unreachable code +#elif defined(__GNUC__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +RAPIDJSON_DIAG_OFF(overflow) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// Encoding + +/*! \class rapidjson::Encoding + \brief Concept for encoding of Unicode characters. + +\code +concept Encoding { + typename Ch; //! Type of character. A "character" is actually a code unit in unicode's definition. + + enum { supportUnicode = 1 }; // or 0 if not supporting unicode + + //! \brief Encode a Unicode codepoint to an output stream. + //! \param os Output stream. + //! \param codepoint An unicode codepoint, ranging from 0x0 to 0x10FFFF inclusively. + template + static void Encode(OutputStream& os, unsigned codepoint); + + //! \brief Decode a Unicode codepoint from an input stream. + //! \param is Input stream. + //! \param codepoint Output of the unicode codepoint. + //! \return true if a valid codepoint can be decoded from the stream. + template + static bool Decode(InputStream& is, unsigned* codepoint); + + //! \brief Validate one Unicode codepoint from an encoded stream. + //! \param is Input stream to obtain codepoint. + //! \param os Output for copying one codepoint. + //! \return true if it is valid. + //! \note This function just validating and copying the codepoint without actually decode it. + template + static bool Validate(InputStream& is, OutputStream& os); + + // The following functions are deal with byte streams. + + //! Take a character from input byte stream, skip BOM if exist. + template + static CharType TakeBOM(InputByteStream& is); + + //! Take a character from input byte stream. + template + static Ch Take(InputByteStream& is); + + //! Put BOM to output byte stream. + template + static void PutBOM(OutputByteStream& os); + + //! Put a character to output byte stream. + template + static void Put(OutputByteStream& os, Ch c); +}; +\endcode +*/ + +/////////////////////////////////////////////////////////////////////////////// +// UTF8 + +//! UTF-8 encoding. +/*! http://en.wikipedia.org/wiki/UTF-8 + http://tools.ietf.org/html/rfc3629 + \tparam CharType Code unit for storing 8-bit UTF-8 data. Default is char. + \note implements Encoding concept +*/ +template +struct UTF8 { + typedef CharType Ch; + + enum { supportUnicode = 1 }; + + template + static void Encode(OutputStream& os, unsigned codepoint) { + if (codepoint <= 0x7F) + os.Put(static_cast(codepoint & 0xFF)); + else if (codepoint <= 0x7FF) { + os.Put(static_cast(0xC0 | ((codepoint >> 6) & 0xFF))); + os.Put(static_cast(0x80 | ((codepoint & 0x3F)))); + } + else if (codepoint <= 0xFFFF) { + os.Put(static_cast(0xE0 | ((codepoint >> 12) & 0xFF))); + os.Put(static_cast(0x80 | ((codepoint >> 6) & 0x3F))); + os.Put(static_cast(0x80 | (codepoint & 0x3F))); + } + else { + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + os.Put(static_cast(0xF0 | ((codepoint >> 18) & 0xFF))); + os.Put(static_cast(0x80 | ((codepoint >> 12) & 0x3F))); + os.Put(static_cast(0x80 | ((codepoint >> 6) & 0x3F))); + os.Put(static_cast(0x80 | (codepoint & 0x3F))); + } + } + + template + static void EncodeUnsafe(OutputStream& os, unsigned codepoint) { + if (codepoint <= 0x7F) + PutUnsafe(os, static_cast(codepoint & 0xFF)); + else if (codepoint <= 0x7FF) { + PutUnsafe(os, static_cast(0xC0 | ((codepoint >> 6) & 0xFF))); + PutUnsafe(os, static_cast(0x80 | ((codepoint & 0x3F)))); + } + else if (codepoint <= 0xFFFF) { + PutUnsafe(os, static_cast(0xE0 | ((codepoint >> 12) & 0xFF))); + PutUnsafe(os, static_cast(0x80 | ((codepoint >> 6) & 0x3F))); + PutUnsafe(os, static_cast(0x80 | (codepoint & 0x3F))); + } + else { + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + PutUnsafe(os, static_cast(0xF0 | ((codepoint >> 18) & 0xFF))); + PutUnsafe(os, static_cast(0x80 | ((codepoint >> 12) & 0x3F))); + PutUnsafe(os, static_cast(0x80 | ((codepoint >> 6) & 0x3F))); + PutUnsafe(os, static_cast(0x80 | (codepoint & 0x3F))); + } + } + + template + static bool Decode(InputStream& is, unsigned* codepoint) { +#define COPY() c = is.Take(); *codepoint = (*codepoint << 6) | (static_cast(c) & 0x3Fu) +#define TRANS(mask) result &= ((GetRange(static_cast(c)) & mask) != 0) +#define TAIL() COPY(); TRANS(0x70) + typename InputStream::Ch c = is.Take(); + if (!(c & 0x80)) { + *codepoint = static_cast(c); + return true; + } + + unsigned char type = GetRange(static_cast(c)); + if (type >= 32) { + *codepoint = 0; + } else { + *codepoint = (0xFF >> type) & static_cast(c); + } + bool result = true; + switch (type) { + case 2: TAIL(); return result; + case 3: TAIL(); TAIL(); return result; + case 4: COPY(); TRANS(0x50); TAIL(); return result; + case 5: COPY(); TRANS(0x10); TAIL(); TAIL(); return result; + case 6: TAIL(); TAIL(); TAIL(); return result; + case 10: COPY(); TRANS(0x20); TAIL(); return result; + case 11: COPY(); TRANS(0x60); TAIL(); TAIL(); return result; + default: return false; + } +#undef COPY +#undef TRANS +#undef TAIL + } + + template + static bool Validate(InputStream& is, OutputStream& os) { +#define COPY() os.Put(c = is.Take()) +#define TRANS(mask) result &= ((GetRange(static_cast(c)) & mask) != 0) +#define TAIL() COPY(); TRANS(0x70) + Ch c; + COPY(); + if (!(c & 0x80)) + return true; + + bool result = true; + switch (GetRange(static_cast(c))) { + case 2: TAIL(); return result; + case 3: TAIL(); TAIL(); return result; + case 4: COPY(); TRANS(0x50); TAIL(); return result; + case 5: COPY(); TRANS(0x10); TAIL(); TAIL(); return result; + case 6: TAIL(); TAIL(); TAIL(); return result; + case 10: COPY(); TRANS(0x20); TAIL(); return result; + case 11: COPY(); TRANS(0x60); TAIL(); TAIL(); return result; + default: return false; + } +#undef COPY +#undef TRANS +#undef TAIL + } + + static unsigned char GetRange(unsigned char c) { + // Referring to DFA of http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ + // With new mapping 1 -> 0x10, 7 -> 0x20, 9 -> 0x40, such that AND operation can test multiple types. + static const unsigned char type[] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10, + 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, + }; + return type[c]; + } + + template + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + typename InputByteStream::Ch c = Take(is); + if (static_cast(c) != 0xEFu) return c; + c = is.Take(); + if (static_cast(c) != 0xBBu) return c; + c = is.Take(); + if (static_cast(c) != 0xBFu) return c; + c = is.Take(); + return c; + } + + template + static Ch Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + return static_cast(is.Take()); + } + + template + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(0xEFu)); + os.Put(static_cast(0xBBu)); + os.Put(static_cast(0xBFu)); + } + + template + static void Put(OutputByteStream& os, Ch c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(c)); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// UTF16 + +//! UTF-16 encoding. +/*! http://en.wikipedia.org/wiki/UTF-16 + http://tools.ietf.org/html/rfc2781 + \tparam CharType Type for storing 16-bit UTF-16 data. Default is wchar_t. C++11 may use char16_t instead. + \note implements Encoding concept + + \note For in-memory access, no need to concern endianness. The code units and code points are represented by CPU's endianness. + For streaming, use UTF16LE and UTF16BE, which handle endianness. +*/ +template +struct UTF16 { + typedef CharType Ch; + RAPIDJSON_STATIC_ASSERT(sizeof(Ch) >= 2); + + enum { supportUnicode = 1 }; + + template + static void Encode(OutputStream& os, unsigned codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 2); + if (codepoint <= 0xFFFF) { + RAPIDJSON_ASSERT(codepoint < 0xD800 || codepoint > 0xDFFF); // Code point itself cannot be surrogate pair + os.Put(static_cast(codepoint)); + } + else { + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + unsigned v = codepoint - 0x10000; + os.Put(static_cast((v >> 10) | 0xD800)); + os.Put((v & 0x3FF) | 0xDC00); + } + } + + + template + static void EncodeUnsafe(OutputStream& os, unsigned codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 2); + if (codepoint <= 0xFFFF) { + RAPIDJSON_ASSERT(codepoint < 0xD800 || codepoint > 0xDFFF); // Code point itself cannot be surrogate pair + PutUnsafe(os, static_cast(codepoint)); + } + else { + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + unsigned v = codepoint - 0x10000; + PutUnsafe(os, static_cast((v >> 10) | 0xD800)); + PutUnsafe(os, (v & 0x3FF) | 0xDC00); + } + } + + template + static bool Decode(InputStream& is, unsigned* codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 2); + typename InputStream::Ch c = is.Take(); + if (c < 0xD800 || c > 0xDFFF) { + *codepoint = static_cast(c); + return true; + } + else if (c <= 0xDBFF) { + *codepoint = (static_cast(c) & 0x3FF) << 10; + c = is.Take(); + *codepoint |= (static_cast(c) & 0x3FF); + *codepoint += 0x10000; + return c >= 0xDC00 && c <= 0xDFFF; + } + return false; + } + + template + static bool Validate(InputStream& is, OutputStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 2); + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 2); + typename InputStream::Ch c; + os.Put(static_cast(c = is.Take())); + if (c < 0xD800 || c > 0xDFFF) + return true; + else if (c <= 0xDBFF) { + os.Put(c = is.Take()); + return c >= 0xDC00 && c <= 0xDFFF; + } + return false; + } +}; + +//! UTF-16 little endian encoding. +template +struct UTF16LE : UTF16 { + template + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + CharType c = Take(is); + return static_cast(c) == 0xFEFFu ? Take(is) : c; + } + + template + static CharType Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + unsigned c = static_cast(is.Take()); + c |= static_cast(static_cast(is.Take())) << 8; + return static_cast(c); + } + + template + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(0xFFu)); + os.Put(static_cast(0xFEu)); + } + + template + static void Put(OutputByteStream& os, CharType c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(static_cast(c) & 0xFFu)); + os.Put(static_cast((static_cast(c) >> 8) & 0xFFu)); + } +}; + +//! UTF-16 big endian encoding. +template +struct UTF16BE : UTF16 { + template + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + CharType c = Take(is); + return static_cast(c) == 0xFEFFu ? Take(is) : c; + } + + template + static CharType Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + unsigned c = static_cast(static_cast(is.Take())) << 8; + c |= static_cast(is.Take()); + return static_cast(c); + } + + template + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(0xFEu)); + os.Put(static_cast(0xFFu)); + } + + template + static void Put(OutputByteStream& os, CharType c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast((static_cast(c) >> 8) & 0xFFu)); + os.Put(static_cast(static_cast(c) & 0xFFu)); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// UTF32 + +//! UTF-32 encoding. +/*! http://en.wikipedia.org/wiki/UTF-32 + \tparam CharType Type for storing 32-bit UTF-32 data. Default is unsigned. C++11 may use char32_t instead. + \note implements Encoding concept + + \note For in-memory access, no need to concern endianness. The code units and code points are represented by CPU's endianness. + For streaming, use UTF32LE and UTF32BE, which handle endianness. +*/ +template +struct UTF32 { + typedef CharType Ch; + RAPIDJSON_STATIC_ASSERT(sizeof(Ch) >= 4); + + enum { supportUnicode = 1 }; + + template + static void Encode(OutputStream& os, unsigned codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 4); + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + os.Put(codepoint); + } + + template + static void EncodeUnsafe(OutputStream& os, unsigned codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 4); + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + PutUnsafe(os, codepoint); + } + + template + static bool Decode(InputStream& is, unsigned* codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 4); + Ch c = is.Take(); + *codepoint = c; + return c <= 0x10FFFF; + } + + template + static bool Validate(InputStream& is, OutputStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 4); + Ch c; + os.Put(c = is.Take()); + return c <= 0x10FFFF; + } +}; + +//! UTF-32 little endian enocoding. +template +struct UTF32LE : UTF32 { + template + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + CharType c = Take(is); + return static_cast(c) == 0x0000FEFFu ? Take(is) : c; + } + + template + static CharType Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + unsigned c = static_cast(is.Take()); + c |= static_cast(static_cast(is.Take())) << 8; + c |= static_cast(static_cast(is.Take())) << 16; + c |= static_cast(static_cast(is.Take())) << 24; + return static_cast(c); + } + + template + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(0xFFu)); + os.Put(static_cast(0xFEu)); + os.Put(static_cast(0x00u)); + os.Put(static_cast(0x00u)); + } + + template + static void Put(OutputByteStream& os, CharType c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(c & 0xFFu)); + os.Put(static_cast((c >> 8) & 0xFFu)); + os.Put(static_cast((c >> 16) & 0xFFu)); + os.Put(static_cast((c >> 24) & 0xFFu)); + } +}; + +//! UTF-32 big endian encoding. +template +struct UTF32BE : UTF32 { + template + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + CharType c = Take(is); + return static_cast(c) == 0x0000FEFFu ? Take(is) : c; + } + + template + static CharType Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + unsigned c = static_cast(static_cast(is.Take())) << 24; + c |= static_cast(static_cast(is.Take())) << 16; + c |= static_cast(static_cast(is.Take())) << 8; + c |= static_cast(static_cast(is.Take())); + return static_cast(c); + } + + template + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(0x00u)); + os.Put(static_cast(0x00u)); + os.Put(static_cast(0xFEu)); + os.Put(static_cast(0xFFu)); + } + + template + static void Put(OutputByteStream& os, CharType c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast((c >> 24) & 0xFFu)); + os.Put(static_cast((c >> 16) & 0xFFu)); + os.Put(static_cast((c >> 8) & 0xFFu)); + os.Put(static_cast(c & 0xFFu)); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// ASCII + +//! ASCII encoding. +/*! http://en.wikipedia.org/wiki/ASCII + \tparam CharType Code unit for storing 7-bit ASCII data. Default is char. + \note implements Encoding concept +*/ +template +struct ASCII { + typedef CharType Ch; + + enum { supportUnicode = 0 }; + + template + static void Encode(OutputStream& os, unsigned codepoint) { + RAPIDJSON_ASSERT(codepoint <= 0x7F); + os.Put(static_cast(codepoint & 0xFF)); + } + + template + static void EncodeUnsafe(OutputStream& os, unsigned codepoint) { + RAPIDJSON_ASSERT(codepoint <= 0x7F); + PutUnsafe(os, static_cast(codepoint & 0xFF)); + } + + template + static bool Decode(InputStream& is, unsigned* codepoint) { + uint8_t c = static_cast(is.Take()); + *codepoint = c; + return c <= 0X7F; + } + + template + static bool Validate(InputStream& is, OutputStream& os) { + uint8_t c = static_cast(is.Take()); + os.Put(static_cast(c)); + return c <= 0x7F; + } + + template + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + uint8_t c = static_cast(Take(is)); + return static_cast(c); + } + + template + static Ch Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + return static_cast(is.Take()); + } + + template + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + (void)os; + } + + template + static void Put(OutputByteStream& os, Ch c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(c)); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// AutoUTF + +//! Runtime-specified UTF encoding type of a stream. +enum UTFType { + kUTF8 = 0, //!< UTF-8. + kUTF16LE = 1, //!< UTF-16 little endian. + kUTF16BE = 2, //!< UTF-16 big endian. + kUTF32LE = 3, //!< UTF-32 little endian. + kUTF32BE = 4 //!< UTF-32 big endian. +}; + +//! Dynamically select encoding according to stream's runtime-specified UTF encoding type. +/*! \note This class can be used with AutoUTFInputtStream and AutoUTFOutputStream, which provides GetType(). +*/ +template +struct AutoUTF { + typedef CharType Ch; + + enum { supportUnicode = 1 }; + +#define RAPIDJSON_ENCODINGS_FUNC(x) UTF8::x, UTF16LE::x, UTF16BE::x, UTF32LE::x, UTF32BE::x + + template + RAPIDJSON_FORCEINLINE static void Encode(OutputStream& os, unsigned codepoint) { + typedef void (*EncodeFunc)(OutputStream&, unsigned); + static const EncodeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Encode) }; + (*f[os.GetType()])(os, codepoint); + } + + template + RAPIDJSON_FORCEINLINE static void EncodeUnsafe(OutputStream& os, unsigned codepoint) { + typedef void (*EncodeFunc)(OutputStream&, unsigned); + static const EncodeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(EncodeUnsafe) }; + (*f[os.GetType()])(os, codepoint); + } + + template + RAPIDJSON_FORCEINLINE static bool Decode(InputStream& is, unsigned* codepoint) { + typedef bool (*DecodeFunc)(InputStream&, unsigned*); + static const DecodeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Decode) }; + return (*f[is.GetType()])(is, codepoint); + } + + template + RAPIDJSON_FORCEINLINE static bool Validate(InputStream& is, OutputStream& os) { + typedef bool (*ValidateFunc)(InputStream&, OutputStream&); + static const ValidateFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Validate) }; + return (*f[is.GetType()])(is, os); + } + +#undef RAPIDJSON_ENCODINGS_FUNC +}; + +/////////////////////////////////////////////////////////////////////////////// +// Transcoder + +//! Encoding conversion. +template +struct Transcoder { + //! Take one Unicode codepoint from source encoding, convert it to target encoding and put it to the output stream. + template + RAPIDJSON_FORCEINLINE static bool Transcode(InputStream& is, OutputStream& os) { + unsigned codepoint; + if (!SourceEncoding::Decode(is, &codepoint)) + return false; + TargetEncoding::Encode(os, codepoint); + return true; + } + + template + RAPIDJSON_FORCEINLINE static bool TranscodeUnsafe(InputStream& is, OutputStream& os) { + unsigned codepoint; + if (!SourceEncoding::Decode(is, &codepoint)) + return false; + TargetEncoding::EncodeUnsafe(os, codepoint); + return true; + } + + //! Validate one Unicode codepoint from an encoded stream. + template + RAPIDJSON_FORCEINLINE static bool Validate(InputStream& is, OutputStream& os) { + return Transcode(is, os); // Since source/target encoding is different, must transcode. + } +}; + +// Forward declaration. +template +inline void PutUnsafe(Stream& stream, typename Stream::Ch c); + +//! Specialization of Transcoder with same source and target encoding. +template +struct Transcoder { + template + RAPIDJSON_FORCEINLINE static bool Transcode(InputStream& is, OutputStream& os) { + os.Put(is.Take()); // Just copy one code unit. This semantic is different from primary template class. + return true; + } + + template + RAPIDJSON_FORCEINLINE static bool TranscodeUnsafe(InputStream& is, OutputStream& os) { + PutUnsafe(os, is.Take()); // Just copy one code unit. This semantic is different from primary template class. + return true; + } + + template + RAPIDJSON_FORCEINLINE static bool Validate(InputStream& is, OutputStream& os) { + return Encoding::Validate(is, os); // source/target encoding are the same + } +}; + +RAPIDJSON_NAMESPACE_END + +#if defined(__GNUC__) || defined(_MSC_VER) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_ENCODINGS_H_ diff --git a/Source/Dependencies/rapidjson/include/rapidjson/error/en.h b/Source/Dependencies/rapidjson/include/rapidjson/error/en.h new file mode 100644 index 000000000..2db838bff --- /dev/null +++ b/Source/Dependencies/rapidjson/include/rapidjson/error/en.h @@ -0,0 +1,74 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ERROR_EN_H_ +#define RAPIDJSON_ERROR_EN_H_ + +#include "error.h" + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(switch-enum) +RAPIDJSON_DIAG_OFF(covered-switch-default) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Maps error code of parsing into error message. +/*! + \ingroup RAPIDJSON_ERRORS + \param parseErrorCode Error code obtained in parsing. + \return the error message. + \note User can make a copy of this function for localization. + Using switch-case is safer for future modification of error codes. +*/ +inline const RAPIDJSON_ERROR_CHARTYPE* GetParseError_En(ParseErrorCode parseErrorCode) { + switch (parseErrorCode) { + case kParseErrorNone: return RAPIDJSON_ERROR_STRING("No error."); + + case kParseErrorDocumentEmpty: return RAPIDJSON_ERROR_STRING("The document is empty."); + case kParseErrorDocumentRootNotSingular: return RAPIDJSON_ERROR_STRING("The document root must not be followed by other values."); + + case kParseErrorValueInvalid: return RAPIDJSON_ERROR_STRING("Invalid value."); + + case kParseErrorObjectMissName: return RAPIDJSON_ERROR_STRING("Missing a name for object member."); + case kParseErrorObjectMissColon: return RAPIDJSON_ERROR_STRING("Missing a colon after a name of object member."); + case kParseErrorObjectMissCommaOrCurlyBracket: return RAPIDJSON_ERROR_STRING("Missing a comma or '}' after an object member."); + + case kParseErrorArrayMissCommaOrSquareBracket: return RAPIDJSON_ERROR_STRING("Missing a comma or ']' after an array element."); + + case kParseErrorStringUnicodeEscapeInvalidHex: return RAPIDJSON_ERROR_STRING("Incorrect hex digit after \\u escape in string."); + case kParseErrorStringUnicodeSurrogateInvalid: return RAPIDJSON_ERROR_STRING("The surrogate pair in string is invalid."); + case kParseErrorStringEscapeInvalid: return RAPIDJSON_ERROR_STRING("Invalid escape character in string."); + case kParseErrorStringMissQuotationMark: return RAPIDJSON_ERROR_STRING("Missing a closing quotation mark in string."); + case kParseErrorStringInvalidEncoding: return RAPIDJSON_ERROR_STRING("Invalid encoding in string."); + + case kParseErrorNumberTooBig: return RAPIDJSON_ERROR_STRING("Number too big to be stored in double."); + case kParseErrorNumberMissFraction: return RAPIDJSON_ERROR_STRING("Miss fraction part in number."); + case kParseErrorNumberMissExponent: return RAPIDJSON_ERROR_STRING("Miss exponent in number."); + + case kParseErrorTermination: return RAPIDJSON_ERROR_STRING("Terminate parsing due to Handler error."); + case kParseErrorUnspecificSyntaxError: return RAPIDJSON_ERROR_STRING("Unspecific syntax error."); + + default: return RAPIDJSON_ERROR_STRING("Unknown error."); + } +} + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_ERROR_EN_H_ diff --git a/Source/Dependencies/rapidjson/include/rapidjson/error/error.h b/Source/Dependencies/rapidjson/include/rapidjson/error/error.h new file mode 100644 index 000000000..95cb31a72 --- /dev/null +++ b/Source/Dependencies/rapidjson/include/rapidjson/error/error.h @@ -0,0 +1,155 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ERROR_ERROR_H_ +#define RAPIDJSON_ERROR_ERROR_H_ + +#include "../rapidjson.h" + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +#endif + +/*! \file error.h */ + +/*! \defgroup RAPIDJSON_ERRORS RapidJSON error handling */ + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ERROR_CHARTYPE + +//! Character type of error messages. +/*! \ingroup RAPIDJSON_ERRORS + The default character type is \c char. + On Windows, user can define this macro as \c TCHAR for supporting both + unicode/non-unicode settings. +*/ +#ifndef RAPIDJSON_ERROR_CHARTYPE +#define RAPIDJSON_ERROR_CHARTYPE char +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ERROR_STRING + +//! Macro for converting string literial to \ref RAPIDJSON_ERROR_CHARTYPE[]. +/*! \ingroup RAPIDJSON_ERRORS + By default this conversion macro does nothing. + On Windows, user can define this macro as \c _T(x) for supporting both + unicode/non-unicode settings. +*/ +#ifndef RAPIDJSON_ERROR_STRING +#define RAPIDJSON_ERROR_STRING(x) x +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// ParseErrorCode + +//! Error code of parsing. +/*! \ingroup RAPIDJSON_ERRORS + \see GenericReader::Parse, GenericReader::GetParseErrorCode +*/ +enum ParseErrorCode { + kParseErrorNone = 0, //!< No error. + + kParseErrorDocumentEmpty, //!< The document is empty. + kParseErrorDocumentRootNotSingular, //!< The document root must not follow by other values. + + kParseErrorValueInvalid, //!< Invalid value. + + kParseErrorObjectMissName, //!< Missing a name for object member. + kParseErrorObjectMissColon, //!< Missing a colon after a name of object member. + kParseErrorObjectMissCommaOrCurlyBracket, //!< Missing a comma or '}' after an object member. + + kParseErrorArrayMissCommaOrSquareBracket, //!< Missing a comma or ']' after an array element. + + kParseErrorStringUnicodeEscapeInvalidHex, //!< Incorrect hex digit after \\u escape in string. + kParseErrorStringUnicodeSurrogateInvalid, //!< The surrogate pair in string is invalid. + kParseErrorStringEscapeInvalid, //!< Invalid escape character in string. + kParseErrorStringMissQuotationMark, //!< Missing a closing quotation mark in string. + kParseErrorStringInvalidEncoding, //!< Invalid encoding in string. + + kParseErrorNumberTooBig, //!< Number too big to be stored in double. + kParseErrorNumberMissFraction, //!< Miss fraction part in number. + kParseErrorNumberMissExponent, //!< Miss exponent in number. + + kParseErrorTermination, //!< Parsing was terminated. + kParseErrorUnspecificSyntaxError //!< Unspecific syntax error. +}; + +//! Result of parsing (wraps ParseErrorCode) +/*! + \ingroup RAPIDJSON_ERRORS + \code + Document doc; + ParseResult ok = doc.Parse("[42]"); + if (!ok) { + fprintf(stderr, "JSON parse error: %s (%u)", + GetParseError_En(ok.Code()), ok.Offset()); + exit(EXIT_FAILURE); + } + \endcode + \see GenericReader::Parse, GenericDocument::Parse +*/ +struct ParseResult { +public: + //! Default constructor, no error. + ParseResult() : code_(kParseErrorNone), offset_(0) {} + //! Constructor to set an error. + ParseResult(ParseErrorCode code, size_t offset) : code_(code), offset_(offset) {} + + //! Get the error code. + ParseErrorCode Code() const { return code_; } + //! Get the error offset, if \ref IsError(), 0 otherwise. + size_t Offset() const { return offset_; } + + //! Conversion to \c bool, returns \c true, iff !\ref IsError(). + operator bool() const { return !IsError(); } + //! Whether the result is an error. + bool IsError() const { return code_ != kParseErrorNone; } + + bool operator==(const ParseResult& that) const { return code_ == that.code_; } + bool operator==(ParseErrorCode code) const { return code_ == code; } + friend bool operator==(ParseErrorCode code, const ParseResult & err) { return code == err.code_; } + + //! Reset error code. + void Clear() { Set(kParseErrorNone); } + //! Update error code and offset. + void Set(ParseErrorCode code, size_t offset = 0) { code_ = code; offset_ = offset; } + +private: + ParseErrorCode code_; + size_t offset_; +}; + +//! Function pointer type of GetParseError(). +/*! \ingroup RAPIDJSON_ERRORS + + This is the prototype for \c GetParseError_X(), where \c X is a locale. + User can dynamically change locale in runtime, e.g.: +\code + GetParseErrorFunc GetParseError = GetParseError_En; // or whatever + const RAPIDJSON_ERROR_CHARTYPE* s = GetParseError(document.GetParseErrorCode()); +\endcode +*/ +typedef const RAPIDJSON_ERROR_CHARTYPE* (*GetParseErrorFunc)(ParseErrorCode); + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_ERROR_ERROR_H_ diff --git a/Source/Dependencies/rapidjson/include/rapidjson/filereadstream.h b/Source/Dependencies/rapidjson/include/rapidjson/filereadstream.h new file mode 100644 index 000000000..b56ea13b3 --- /dev/null +++ b/Source/Dependencies/rapidjson/include/rapidjson/filereadstream.h @@ -0,0 +1,99 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_FILEREADSTREAM_H_ +#define RAPIDJSON_FILEREADSTREAM_H_ + +#include "stream.h" +#include + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +RAPIDJSON_DIAG_OFF(unreachable-code) +RAPIDJSON_DIAG_OFF(missing-noreturn) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! File byte stream for input using fread(). +/*! + \note implements Stream concept +*/ +class FileReadStream { +public: + typedef char Ch; //!< Character type (byte). + + //! Constructor. + /*! + \param fp File pointer opened for read. + \param buffer user-supplied buffer. + \param bufferSize size of buffer in bytes. Must >=4 bytes. + */ + FileReadStream(std::FILE* fp, char* buffer, size_t bufferSize) : fp_(fp), buffer_(buffer), bufferSize_(bufferSize), bufferLast_(0), current_(buffer_), readCount_(0), count_(0), eof_(false) { + RAPIDJSON_ASSERT(fp_ != 0); + RAPIDJSON_ASSERT(bufferSize >= 4); + Read(); + } + + Ch Peek() const { return *current_; } + Ch Take() { Ch c = *current_; Read(); return c; } + size_t Tell() const { return count_ + static_cast(current_ - buffer_); } + + // Not implemented + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + + // For encoding detection only. + const Ch* Peek4() const { + return (current_ + 4 <= bufferLast_) ? current_ : 0; + } + +private: + void Read() { + if (current_ < bufferLast_) + ++current_; + else if (!eof_) { + count_ += readCount_; + readCount_ = fread(buffer_, 1, bufferSize_, fp_); + bufferLast_ = buffer_ + readCount_ - 1; + current_ = buffer_; + + if (readCount_ < bufferSize_) { + buffer_[readCount_] = '\0'; + ++bufferLast_; + eof_ = true; + } + } + } + + std::FILE* fp_; + Ch *buffer_; + size_t bufferSize_; + Ch *bufferLast_; + Ch *current_; + size_t readCount_; + size_t count_; //!< Number of characters read + bool eof_; +}; + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_FILESTREAM_H_ diff --git a/Source/Dependencies/rapidjson/include/rapidjson/filewritestream.h b/Source/Dependencies/rapidjson/include/rapidjson/filewritestream.h new file mode 100644 index 000000000..6378dd60e --- /dev/null +++ b/Source/Dependencies/rapidjson/include/rapidjson/filewritestream.h @@ -0,0 +1,104 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_FILEWRITESTREAM_H_ +#define RAPIDJSON_FILEWRITESTREAM_H_ + +#include "stream.h" +#include + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(unreachable-code) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Wrapper of C file stream for input using fread(). +/*! + \note implements Stream concept +*/ +class FileWriteStream { +public: + typedef char Ch; //!< Character type. Only support char. + + FileWriteStream(std::FILE* fp, char* buffer, size_t bufferSize) : fp_(fp), buffer_(buffer), bufferEnd_(buffer + bufferSize), current_(buffer_) { + RAPIDJSON_ASSERT(fp_ != 0); + } + + void Put(char c) { + if (current_ >= bufferEnd_) + Flush(); + + *current_++ = c; + } + + void PutN(char c, size_t n) { + size_t avail = static_cast(bufferEnd_ - current_); + while (n > avail) { + std::memset(current_, c, avail); + current_ += avail; + Flush(); + n -= avail; + avail = static_cast(bufferEnd_ - current_); + } + + if (n > 0) { + std::memset(current_, c, n); + current_ += n; + } + } + + void Flush() { + if (current_ != buffer_) { + size_t result = fwrite(buffer_, 1, static_cast(current_ - buffer_), fp_); + if (result < static_cast(current_ - buffer_)) { + // failure deliberately ignored at this time + // added to avoid warn_unused_result build errors + } + current_ = buffer_; + } + } + + // Not implemented + char Peek() const { RAPIDJSON_ASSERT(false); return 0; } + char Take() { RAPIDJSON_ASSERT(false); return 0; } + size_t Tell() const { RAPIDJSON_ASSERT(false); return 0; } + char* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(char*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + // Prohibit copy constructor & assignment operator. + FileWriteStream(const FileWriteStream&); + FileWriteStream& operator=(const FileWriteStream&); + + std::FILE* fp_; + char *buffer_; + char *bufferEnd_; + char *current_; +}; + +//! Implement specialized version of PutN() with memset() for better performance. +template<> +inline void PutN(FileWriteStream& stream, char c, size_t n) { + stream.PutN(c, n); +} + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_FILESTREAM_H_ diff --git a/Source/Dependencies/rapidjson/include/rapidjson/fwd.h b/Source/Dependencies/rapidjson/include/rapidjson/fwd.h new file mode 100644 index 000000000..e8104e841 --- /dev/null +++ b/Source/Dependencies/rapidjson/include/rapidjson/fwd.h @@ -0,0 +1,151 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_FWD_H_ +#define RAPIDJSON_FWD_H_ + +#include "rapidjson.h" + +RAPIDJSON_NAMESPACE_BEGIN + +// encodings.h + +template struct UTF8; +template struct UTF16; +template struct UTF16BE; +template struct UTF16LE; +template struct UTF32; +template struct UTF32BE; +template struct UTF32LE; +template struct ASCII; +template struct AutoUTF; + +template +struct Transcoder; + +// allocators.h + +class CrtAllocator; + +template +class MemoryPoolAllocator; + +// stream.h + +template +struct GenericStringStream; + +typedef GenericStringStream > StringStream; + +template +struct GenericInsituStringStream; + +typedef GenericInsituStringStream > InsituStringStream; + +// stringbuffer.h + +template +class GenericStringBuffer; + +typedef GenericStringBuffer, CrtAllocator> StringBuffer; + +// filereadstream.h + +class FileReadStream; + +// filewritestream.h + +class FileWriteStream; + +// memorybuffer.h + +template +struct GenericMemoryBuffer; + +typedef GenericMemoryBuffer MemoryBuffer; + +// memorystream.h + +struct MemoryStream; + +// reader.h + +template +struct BaseReaderHandler; + +template +class GenericReader; + +typedef GenericReader, UTF8, CrtAllocator> Reader; + +// writer.h + +template +class Writer; + +// prettywriter.h + +template +class PrettyWriter; + +// document.h + +template +struct GenericMember; + +template +class GenericMemberIterator; + +template +struct GenericStringRef; + +template +class GenericValue; + +typedef GenericValue, MemoryPoolAllocator > Value; + +template +class GenericDocument; + +typedef GenericDocument, MemoryPoolAllocator, CrtAllocator> Document; + +// pointer.h + +template +class GenericPointer; + +typedef GenericPointer Pointer; + +// schema.h + +template +class IGenericRemoteSchemaDocumentProvider; + +template +class GenericSchemaDocument; + +typedef GenericSchemaDocument SchemaDocument; +typedef IGenericRemoteSchemaDocumentProvider IRemoteSchemaDocumentProvider; + +template < + typename SchemaDocumentType, + typename OutputHandler, + typename StateAllocator> +class GenericSchemaValidator; + +typedef GenericSchemaValidator, void>, CrtAllocator> SchemaValidator; + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_RAPIDJSONFWD_H_ diff --git a/Source/Dependencies/rapidjson/include/rapidjson/internal/biginteger.h b/Source/Dependencies/rapidjson/include/rapidjson/internal/biginteger.h new file mode 100644 index 000000000..9d3e88c99 --- /dev/null +++ b/Source/Dependencies/rapidjson/include/rapidjson/internal/biginteger.h @@ -0,0 +1,290 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_BIGINTEGER_H_ +#define RAPIDJSON_BIGINTEGER_H_ + +#include "../rapidjson.h" + +#if defined(_MSC_VER) && defined(_M_AMD64) +#include // for _umul128 +#pragma intrinsic(_umul128) +#endif + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +class BigInteger { +public: + typedef uint64_t Type; + + BigInteger(const BigInteger& rhs) : count_(rhs.count_) { + std::memcpy(digits_, rhs.digits_, count_ * sizeof(Type)); + } + + explicit BigInteger(uint64_t u) : count_(1) { + digits_[0] = u; + } + + BigInteger(const char* decimals, size_t length) : count_(1) { + RAPIDJSON_ASSERT(length > 0); + digits_[0] = 0; + size_t i = 0; + const size_t kMaxDigitPerIteration = 19; // 2^64 = 18446744073709551616 > 10^19 + while (length >= kMaxDigitPerIteration) { + AppendDecimal64(decimals + i, decimals + i + kMaxDigitPerIteration); + length -= kMaxDigitPerIteration; + i += kMaxDigitPerIteration; + } + + if (length > 0) + AppendDecimal64(decimals + i, decimals + i + length); + } + + BigInteger& operator=(const BigInteger &rhs) + { + if (this != &rhs) { + count_ = rhs.count_; + std::memcpy(digits_, rhs.digits_, count_ * sizeof(Type)); + } + return *this; + } + + BigInteger& operator=(uint64_t u) { + digits_[0] = u; + count_ = 1; + return *this; + } + + BigInteger& operator+=(uint64_t u) { + Type backup = digits_[0]; + digits_[0] += u; + for (size_t i = 0; i < count_ - 1; i++) { + if (digits_[i] >= backup) + return *this; // no carry + backup = digits_[i + 1]; + digits_[i + 1] += 1; + } + + // Last carry + if (digits_[count_ - 1] < backup) + PushBack(1); + + return *this; + } + + BigInteger& operator*=(uint64_t u) { + if (u == 0) return *this = 0; + if (u == 1) return *this; + if (*this == 1) return *this = u; + + uint64_t k = 0; + for (size_t i = 0; i < count_; i++) { + uint64_t hi; + digits_[i] = MulAdd64(digits_[i], u, k, &hi); + k = hi; + } + + if (k > 0) + PushBack(k); + + return *this; + } + + BigInteger& operator*=(uint32_t u) { + if (u == 0) return *this = 0; + if (u == 1) return *this; + if (*this == 1) return *this = u; + + uint64_t k = 0; + for (size_t i = 0; i < count_; i++) { + const uint64_t c = digits_[i] >> 32; + const uint64_t d = digits_[i] & 0xFFFFFFFF; + const uint64_t uc = u * c; + const uint64_t ud = u * d; + const uint64_t p0 = ud + k; + const uint64_t p1 = uc + (p0 >> 32); + digits_[i] = (p0 & 0xFFFFFFFF) | (p1 << 32); + k = p1 >> 32; + } + + if (k > 0) + PushBack(k); + + return *this; + } + + BigInteger& operator<<=(size_t shift) { + if (IsZero() || shift == 0) return *this; + + size_t offset = shift / kTypeBit; + size_t interShift = shift % kTypeBit; + RAPIDJSON_ASSERT(count_ + offset <= kCapacity); + + if (interShift == 0) { + std::memmove(&digits_[count_ - 1 + offset], &digits_[count_ - 1], count_ * sizeof(Type)); + count_ += offset; + } + else { + digits_[count_] = 0; + for (size_t i = count_; i > 0; i--) + digits_[i + offset] = (digits_[i] << interShift) | (digits_[i - 1] >> (kTypeBit - interShift)); + digits_[offset] = digits_[0] << interShift; + count_ += offset; + if (digits_[count_]) + count_++; + } + + std::memset(digits_, 0, offset * sizeof(Type)); + + return *this; + } + + bool operator==(const BigInteger& rhs) const { + return count_ == rhs.count_ && std::memcmp(digits_, rhs.digits_, count_ * sizeof(Type)) == 0; + } + + bool operator==(const Type rhs) const { + return count_ == 1 && digits_[0] == rhs; + } + + BigInteger& MultiplyPow5(unsigned exp) { + static const uint32_t kPow5[12] = { + 5, + 5 * 5, + 5 * 5 * 5, + 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 + }; + if (exp == 0) return *this; + for (; exp >= 27; exp -= 27) *this *= RAPIDJSON_UINT64_C2(0X6765C793, 0XFA10079D); // 5^27 + for (; exp >= 13; exp -= 13) *this *= static_cast(1220703125u); // 5^13 + if (exp > 0) *this *= kPow5[exp - 1]; + return *this; + } + + // Compute absolute difference of this and rhs. + // Assume this != rhs + bool Difference(const BigInteger& rhs, BigInteger* out) const { + int cmp = Compare(rhs); + RAPIDJSON_ASSERT(cmp != 0); + const BigInteger *a, *b; // Makes a > b + bool ret; + if (cmp < 0) { a = &rhs; b = this; ret = true; } + else { a = this; b = &rhs; ret = false; } + + Type borrow = 0; + for (size_t i = 0; i < a->count_; i++) { + Type d = a->digits_[i] - borrow; + if (i < b->count_) + d -= b->digits_[i]; + borrow = (d > a->digits_[i]) ? 1 : 0; + out->digits_[i] = d; + if (d != 0) + out->count_ = i + 1; + } + + return ret; + } + + int Compare(const BigInteger& rhs) const { + if (count_ != rhs.count_) + return count_ < rhs.count_ ? -1 : 1; + + for (size_t i = count_; i-- > 0;) + if (digits_[i] != rhs.digits_[i]) + return digits_[i] < rhs.digits_[i] ? -1 : 1; + + return 0; + } + + size_t GetCount() const { return count_; } + Type GetDigit(size_t index) const { RAPIDJSON_ASSERT(index < count_); return digits_[index]; } + bool IsZero() const { return count_ == 1 && digits_[0] == 0; } + +private: + void AppendDecimal64(const char* begin, const char* end) { + uint64_t u = ParseUint64(begin, end); + if (IsZero()) + *this = u; + else { + unsigned exp = static_cast(end - begin); + (MultiplyPow5(exp) <<= exp) += u; // *this = *this * 10^exp + u + } + } + + void PushBack(Type digit) { + RAPIDJSON_ASSERT(count_ < kCapacity); + digits_[count_++] = digit; + } + + static uint64_t ParseUint64(const char* begin, const char* end) { + uint64_t r = 0; + for (const char* p = begin; p != end; ++p) { + RAPIDJSON_ASSERT(*p >= '0' && *p <= '9'); + r = r * 10u + static_cast(*p - '0'); + } + return r; + } + + // Assume a * b + k < 2^128 + static uint64_t MulAdd64(uint64_t a, uint64_t b, uint64_t k, uint64_t* outHigh) { +#if defined(_MSC_VER) && defined(_M_AMD64) + uint64_t low = _umul128(a, b, outHigh) + k; + if (low < k) + (*outHigh)++; + return low; +#elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) && defined(__x86_64__) + __extension__ typedef unsigned __int128 uint128; + uint128 p = static_cast(a) * static_cast(b); + p += k; + *outHigh = static_cast(p >> 64); + return static_cast(p); +#else + const uint64_t a0 = a & 0xFFFFFFFF, a1 = a >> 32, b0 = b & 0xFFFFFFFF, b1 = b >> 32; + uint64_t x0 = a0 * b0, x1 = a0 * b1, x2 = a1 * b0, x3 = a1 * b1; + x1 += (x0 >> 32); // can't give carry + x1 += x2; + if (x1 < x2) + x3 += (static_cast(1) << 32); + uint64_t lo = (x1 << 32) + (x0 & 0xFFFFFFFF); + uint64_t hi = x3 + (x1 >> 32); + + lo += k; + if (lo < k) + hi++; + *outHigh = hi; + return lo; +#endif + } + + static const size_t kBitCount = 3328; // 64bit * 54 > 10^1000 + static const size_t kCapacity = kBitCount / sizeof(Type); + static const size_t kTypeBit = sizeof(Type) * 8; + + Type digits_[kCapacity]; + size_t count_; +}; + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_BIGINTEGER_H_ diff --git a/Source/Dependencies/rapidjson/include/rapidjson/internal/diyfp.h b/Source/Dependencies/rapidjson/include/rapidjson/internal/diyfp.h new file mode 100644 index 000000000..c9fefdc61 --- /dev/null +++ b/Source/Dependencies/rapidjson/include/rapidjson/internal/diyfp.h @@ -0,0 +1,258 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +// This is a C++ header-only implementation of Grisu2 algorithm from the publication: +// Loitsch, Florian. "Printing floating-point numbers quickly and accurately with +// integers." ACM Sigplan Notices 45.6 (2010): 233-243. + +#ifndef RAPIDJSON_DIYFP_H_ +#define RAPIDJSON_DIYFP_H_ + +#include "../rapidjson.h" + +#if defined(_MSC_VER) && defined(_M_AMD64) +#include +#pragma intrinsic(_BitScanReverse64) +#pragma intrinsic(_umul128) +#endif + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +#endif + +struct DiyFp { + DiyFp() : f(), e() {} + + DiyFp(uint64_t fp, int exp) : f(fp), e(exp) {} + + explicit DiyFp(double d) { + union { + double d; + uint64_t u64; + } u = { d }; + + int biased_e = static_cast((u.u64 & kDpExponentMask) >> kDpSignificandSize); + uint64_t significand = (u.u64 & kDpSignificandMask); + if (biased_e != 0) { + f = significand + kDpHiddenBit; + e = biased_e - kDpExponentBias; + } + else { + f = significand; + e = kDpMinExponent + 1; + } + } + + DiyFp operator-(const DiyFp& rhs) const { + return DiyFp(f - rhs.f, e); + } + + DiyFp operator*(const DiyFp& rhs) const { +#if defined(_MSC_VER) && defined(_M_AMD64) + uint64_t h; + uint64_t l = _umul128(f, rhs.f, &h); + if (l & (uint64_t(1) << 63)) // rounding + h++; + return DiyFp(h, e + rhs.e + 64); +#elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) && defined(__x86_64__) + __extension__ typedef unsigned __int128 uint128; + uint128 p = static_cast(f) * static_cast(rhs.f); + uint64_t h = static_cast(p >> 64); + uint64_t l = static_cast(p); + if (l & (uint64_t(1) << 63)) // rounding + h++; + return DiyFp(h, e + rhs.e + 64); +#else + const uint64_t M32 = 0xFFFFFFFF; + const uint64_t a = f >> 32; + const uint64_t b = f & M32; + const uint64_t c = rhs.f >> 32; + const uint64_t d = rhs.f & M32; + const uint64_t ac = a * c; + const uint64_t bc = b * c; + const uint64_t ad = a * d; + const uint64_t bd = b * d; + uint64_t tmp = (bd >> 32) + (ad & M32) + (bc & M32); + tmp += 1U << 31; /// mult_round + return DiyFp(ac + (ad >> 32) + (bc >> 32) + (tmp >> 32), e + rhs.e + 64); +#endif + } + + DiyFp Normalize() const { +#if defined(_MSC_VER) && defined(_M_AMD64) + unsigned long index; + _BitScanReverse64(&index, f); + return DiyFp(f << (63 - index), e - (63 - index)); +#elif defined(__GNUC__) && __GNUC__ >= 4 + int s = __builtin_clzll(f); + return DiyFp(f << s, e - s); +#else + DiyFp res = *this; + while (!(res.f & (static_cast(1) << 63))) { + res.f <<= 1; + res.e--; + } + return res; +#endif + } + + DiyFp NormalizeBoundary() const { + DiyFp res = *this; + while (!(res.f & (kDpHiddenBit << 1))) { + res.f <<= 1; + res.e--; + } + res.f <<= (kDiySignificandSize - kDpSignificandSize - 2); + res.e = res.e - (kDiySignificandSize - kDpSignificandSize - 2); + return res; + } + + void NormalizedBoundaries(DiyFp* minus, DiyFp* plus) const { + DiyFp pl = DiyFp((f << 1) + 1, e - 1).NormalizeBoundary(); + DiyFp mi = (f == kDpHiddenBit) ? DiyFp((f << 2) - 1, e - 2) : DiyFp((f << 1) - 1, e - 1); + mi.f <<= mi.e - pl.e; + mi.e = pl.e; + *plus = pl; + *minus = mi; + } + + double ToDouble() const { + union { + double d; + uint64_t u64; + }u; + const uint64_t be = (e == kDpDenormalExponent && (f & kDpHiddenBit) == 0) ? 0 : + static_cast(e + kDpExponentBias); + u.u64 = (f & kDpSignificandMask) | (be << kDpSignificandSize); + return u.d; + } + + static const int kDiySignificandSize = 64; + static const int kDpSignificandSize = 52; + static const int kDpExponentBias = 0x3FF + kDpSignificandSize; + static const int kDpMaxExponent = 0x7FF - kDpExponentBias; + static const int kDpMinExponent = -kDpExponentBias; + static const int kDpDenormalExponent = -kDpExponentBias + 1; + static const uint64_t kDpExponentMask = RAPIDJSON_UINT64_C2(0x7FF00000, 0x00000000); + static const uint64_t kDpSignificandMask = RAPIDJSON_UINT64_C2(0x000FFFFF, 0xFFFFFFFF); + static const uint64_t kDpHiddenBit = RAPIDJSON_UINT64_C2(0x00100000, 0x00000000); + + uint64_t f; + int e; +}; + +inline DiyFp GetCachedPowerByIndex(size_t index) { + // 10^-348, 10^-340, ..., 10^340 + static const uint64_t kCachedPowers_F[] = { + RAPIDJSON_UINT64_C2(0xfa8fd5a0, 0x081c0288), RAPIDJSON_UINT64_C2(0xbaaee17f, 0xa23ebf76), + RAPIDJSON_UINT64_C2(0x8b16fb20, 0x3055ac76), RAPIDJSON_UINT64_C2(0xcf42894a, 0x5dce35ea), + RAPIDJSON_UINT64_C2(0x9a6bb0aa, 0x55653b2d), RAPIDJSON_UINT64_C2(0xe61acf03, 0x3d1a45df), + RAPIDJSON_UINT64_C2(0xab70fe17, 0xc79ac6ca), RAPIDJSON_UINT64_C2(0xff77b1fc, 0xbebcdc4f), + RAPIDJSON_UINT64_C2(0xbe5691ef, 0x416bd60c), RAPIDJSON_UINT64_C2(0x8dd01fad, 0x907ffc3c), + RAPIDJSON_UINT64_C2(0xd3515c28, 0x31559a83), RAPIDJSON_UINT64_C2(0x9d71ac8f, 0xada6c9b5), + RAPIDJSON_UINT64_C2(0xea9c2277, 0x23ee8bcb), RAPIDJSON_UINT64_C2(0xaecc4991, 0x4078536d), + RAPIDJSON_UINT64_C2(0x823c1279, 0x5db6ce57), RAPIDJSON_UINT64_C2(0xc2109436, 0x4dfb5637), + RAPIDJSON_UINT64_C2(0x9096ea6f, 0x3848984f), RAPIDJSON_UINT64_C2(0xd77485cb, 0x25823ac7), + RAPIDJSON_UINT64_C2(0xa086cfcd, 0x97bf97f4), RAPIDJSON_UINT64_C2(0xef340a98, 0x172aace5), + RAPIDJSON_UINT64_C2(0xb23867fb, 0x2a35b28e), RAPIDJSON_UINT64_C2(0x84c8d4df, 0xd2c63f3b), + RAPIDJSON_UINT64_C2(0xc5dd4427, 0x1ad3cdba), RAPIDJSON_UINT64_C2(0x936b9fce, 0xbb25c996), + RAPIDJSON_UINT64_C2(0xdbac6c24, 0x7d62a584), RAPIDJSON_UINT64_C2(0xa3ab6658, 0x0d5fdaf6), + RAPIDJSON_UINT64_C2(0xf3e2f893, 0xdec3f126), RAPIDJSON_UINT64_C2(0xb5b5ada8, 0xaaff80b8), + RAPIDJSON_UINT64_C2(0x87625f05, 0x6c7c4a8b), RAPIDJSON_UINT64_C2(0xc9bcff60, 0x34c13053), + RAPIDJSON_UINT64_C2(0x964e858c, 0x91ba2655), RAPIDJSON_UINT64_C2(0xdff97724, 0x70297ebd), + RAPIDJSON_UINT64_C2(0xa6dfbd9f, 0xb8e5b88f), RAPIDJSON_UINT64_C2(0xf8a95fcf, 0x88747d94), + RAPIDJSON_UINT64_C2(0xb9447093, 0x8fa89bcf), RAPIDJSON_UINT64_C2(0x8a08f0f8, 0xbf0f156b), + RAPIDJSON_UINT64_C2(0xcdb02555, 0x653131b6), RAPIDJSON_UINT64_C2(0x993fe2c6, 0xd07b7fac), + RAPIDJSON_UINT64_C2(0xe45c10c4, 0x2a2b3b06), RAPIDJSON_UINT64_C2(0xaa242499, 0x697392d3), + RAPIDJSON_UINT64_C2(0xfd87b5f2, 0x8300ca0e), RAPIDJSON_UINT64_C2(0xbce50864, 0x92111aeb), + RAPIDJSON_UINT64_C2(0x8cbccc09, 0x6f5088cc), RAPIDJSON_UINT64_C2(0xd1b71758, 0xe219652c), + RAPIDJSON_UINT64_C2(0x9c400000, 0x00000000), RAPIDJSON_UINT64_C2(0xe8d4a510, 0x00000000), + RAPIDJSON_UINT64_C2(0xad78ebc5, 0xac620000), RAPIDJSON_UINT64_C2(0x813f3978, 0xf8940984), + RAPIDJSON_UINT64_C2(0xc097ce7b, 0xc90715b3), RAPIDJSON_UINT64_C2(0x8f7e32ce, 0x7bea5c70), + RAPIDJSON_UINT64_C2(0xd5d238a4, 0xabe98068), RAPIDJSON_UINT64_C2(0x9f4f2726, 0x179a2245), + RAPIDJSON_UINT64_C2(0xed63a231, 0xd4c4fb27), RAPIDJSON_UINT64_C2(0xb0de6538, 0x8cc8ada8), + RAPIDJSON_UINT64_C2(0x83c7088e, 0x1aab65db), RAPIDJSON_UINT64_C2(0xc45d1df9, 0x42711d9a), + RAPIDJSON_UINT64_C2(0x924d692c, 0xa61be758), RAPIDJSON_UINT64_C2(0xda01ee64, 0x1a708dea), + RAPIDJSON_UINT64_C2(0xa26da399, 0x9aef774a), RAPIDJSON_UINT64_C2(0xf209787b, 0xb47d6b85), + RAPIDJSON_UINT64_C2(0xb454e4a1, 0x79dd1877), RAPIDJSON_UINT64_C2(0x865b8692, 0x5b9bc5c2), + RAPIDJSON_UINT64_C2(0xc83553c5, 0xc8965d3d), RAPIDJSON_UINT64_C2(0x952ab45c, 0xfa97a0b3), + RAPIDJSON_UINT64_C2(0xde469fbd, 0x99a05fe3), RAPIDJSON_UINT64_C2(0xa59bc234, 0xdb398c25), + RAPIDJSON_UINT64_C2(0xf6c69a72, 0xa3989f5c), RAPIDJSON_UINT64_C2(0xb7dcbf53, 0x54e9bece), + RAPIDJSON_UINT64_C2(0x88fcf317, 0xf22241e2), RAPIDJSON_UINT64_C2(0xcc20ce9b, 0xd35c78a5), + RAPIDJSON_UINT64_C2(0x98165af3, 0x7b2153df), RAPIDJSON_UINT64_C2(0xe2a0b5dc, 0x971f303a), + RAPIDJSON_UINT64_C2(0xa8d9d153, 0x5ce3b396), RAPIDJSON_UINT64_C2(0xfb9b7cd9, 0xa4a7443c), + RAPIDJSON_UINT64_C2(0xbb764c4c, 0xa7a44410), RAPIDJSON_UINT64_C2(0x8bab8eef, 0xb6409c1a), + RAPIDJSON_UINT64_C2(0xd01fef10, 0xa657842c), RAPIDJSON_UINT64_C2(0x9b10a4e5, 0xe9913129), + RAPIDJSON_UINT64_C2(0xe7109bfb, 0xa19c0c9d), RAPIDJSON_UINT64_C2(0xac2820d9, 0x623bf429), + RAPIDJSON_UINT64_C2(0x80444b5e, 0x7aa7cf85), RAPIDJSON_UINT64_C2(0xbf21e440, 0x03acdd2d), + RAPIDJSON_UINT64_C2(0x8e679c2f, 0x5e44ff8f), RAPIDJSON_UINT64_C2(0xd433179d, 0x9c8cb841), + RAPIDJSON_UINT64_C2(0x9e19db92, 0xb4e31ba9), RAPIDJSON_UINT64_C2(0xeb96bf6e, 0xbadf77d9), + RAPIDJSON_UINT64_C2(0xaf87023b, 0x9bf0ee6b) + }; + static const int16_t kCachedPowers_E[] = { + -1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, + -954, -927, -901, -874, -847, -821, -794, -768, -741, -715, + -688, -661, -635, -608, -582, -555, -529, -502, -475, -449, + -422, -396, -369, -343, -316, -289, -263, -236, -210, -183, + -157, -130, -103, -77, -50, -24, 3, 30, 56, 83, + 109, 136, 162, 189, 216, 242, 269, 295, 322, 348, + 375, 402, 428, 455, 481, 508, 534, 561, 588, 614, + 641, 667, 694, 720, 747, 774, 800, 827, 853, 880, + 907, 933, 960, 986, 1013, 1039, 1066 + }; + return DiyFp(kCachedPowers_F[index], kCachedPowers_E[index]); +} + +inline DiyFp GetCachedPower(int e, int* K) { + + //int k = static_cast(ceil((-61 - e) * 0.30102999566398114)) + 374; + double dk = (-61 - e) * 0.30102999566398114 + 347; // dk must be positive, so can do ceiling in positive + int k = static_cast(dk); + if (dk - k > 0.0) + k++; + + unsigned index = static_cast((k >> 3) + 1); + *K = -(-348 + static_cast(index << 3)); // decimal exponent no need lookup table + + return GetCachedPowerByIndex(index); +} + +inline DiyFp GetCachedPower10(int exp, int *outExp) { + unsigned index = (static_cast(exp) + 348u) / 8u; + *outExp = -348 + static_cast(index) * 8; + return GetCachedPowerByIndex(index); + } + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +RAPIDJSON_DIAG_OFF(padded) +#endif + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_DIYFP_H_ diff --git a/Source/Dependencies/rapidjson/include/rapidjson/internal/dtoa.h b/Source/Dependencies/rapidjson/include/rapidjson/internal/dtoa.h new file mode 100644 index 000000000..8d6350e62 --- /dev/null +++ b/Source/Dependencies/rapidjson/include/rapidjson/internal/dtoa.h @@ -0,0 +1,245 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +// This is a C++ header-only implementation of Grisu2 algorithm from the publication: +// Loitsch, Florian. "Printing floating-point numbers quickly and accurately with +// integers." ACM Sigplan Notices 45.6 (2010): 233-243. + +#ifndef RAPIDJSON_DTOA_ +#define RAPIDJSON_DTOA_ + +#include "itoa.h" // GetDigitsLut() +#include "diyfp.h" +#include "ieee754.h" + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +RAPIDJSON_DIAG_OFF(array-bounds) // some gcc versions generate wrong warnings https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59124 +#endif + +inline void GrisuRound(char* buffer, int len, uint64_t delta, uint64_t rest, uint64_t ten_kappa, uint64_t wp_w) { + while (rest < wp_w && delta - rest >= ten_kappa && + (rest + ten_kappa < wp_w || /// closer + wp_w - rest > rest + ten_kappa - wp_w)) { + buffer[len - 1]--; + rest += ten_kappa; + } +} + +inline unsigned CountDecimalDigit32(uint32_t n) { + // Simple pure C++ implementation was faster than __builtin_clz version in this situation. + if (n < 10) return 1; + if (n < 100) return 2; + if (n < 1000) return 3; + if (n < 10000) return 4; + if (n < 100000) return 5; + if (n < 1000000) return 6; + if (n < 10000000) return 7; + if (n < 100000000) return 8; + // Will not reach 10 digits in DigitGen() + //if (n < 1000000000) return 9; + //return 10; + return 9; +} + +inline void DigitGen(const DiyFp& W, const DiyFp& Mp, uint64_t delta, char* buffer, int* len, int* K) { + static const uint32_t kPow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 }; + const DiyFp one(uint64_t(1) << -Mp.e, Mp.e); + const DiyFp wp_w = Mp - W; + uint32_t p1 = static_cast(Mp.f >> -one.e); + uint64_t p2 = Mp.f & (one.f - 1); + unsigned kappa = CountDecimalDigit32(p1); // kappa in [0, 9] + *len = 0; + + while (kappa > 0) { + uint32_t d = 0; + switch (kappa) { + case 9: d = p1 / 100000000; p1 %= 100000000; break; + case 8: d = p1 / 10000000; p1 %= 10000000; break; + case 7: d = p1 / 1000000; p1 %= 1000000; break; + case 6: d = p1 / 100000; p1 %= 100000; break; + case 5: d = p1 / 10000; p1 %= 10000; break; + case 4: d = p1 / 1000; p1 %= 1000; break; + case 3: d = p1 / 100; p1 %= 100; break; + case 2: d = p1 / 10; p1 %= 10; break; + case 1: d = p1; p1 = 0; break; + default:; + } + if (d || *len) + buffer[(*len)++] = static_cast('0' + static_cast(d)); + kappa--; + uint64_t tmp = (static_cast(p1) << -one.e) + p2; + if (tmp <= delta) { + *K += kappa; + GrisuRound(buffer, *len, delta, tmp, static_cast(kPow10[kappa]) << -one.e, wp_w.f); + return; + } + } + + // kappa = 0 + for (;;) { + p2 *= 10; + delta *= 10; + char d = static_cast(p2 >> -one.e); + if (d || *len) + buffer[(*len)++] = static_cast('0' + d); + p2 &= one.f - 1; + kappa--; + if (p2 < delta) { + *K += kappa; + int index = -static_cast(kappa); + GrisuRound(buffer, *len, delta, p2, one.f, wp_w.f * (index < 9 ? kPow10[-static_cast(kappa)] : 0)); + return; + } + } +} + +inline void Grisu2(double value, char* buffer, int* length, int* K) { + const DiyFp v(value); + DiyFp w_m, w_p; + v.NormalizedBoundaries(&w_m, &w_p); + + const DiyFp c_mk = GetCachedPower(w_p.e, K); + const DiyFp W = v.Normalize() * c_mk; + DiyFp Wp = w_p * c_mk; + DiyFp Wm = w_m * c_mk; + Wm.f++; + Wp.f--; + DigitGen(W, Wp, Wp.f - Wm.f, buffer, length, K); +} + +inline char* WriteExponent(int K, char* buffer) { + if (K < 0) { + *buffer++ = '-'; + K = -K; + } + + if (K >= 100) { + *buffer++ = static_cast('0' + static_cast(K / 100)); + K %= 100; + const char* d = GetDigitsLut() + K * 2; + *buffer++ = d[0]; + *buffer++ = d[1]; + } + else if (K >= 10) { + const char* d = GetDigitsLut() + K * 2; + *buffer++ = d[0]; + *buffer++ = d[1]; + } + else + *buffer++ = static_cast('0' + static_cast(K)); + + return buffer; +} + +inline char* Prettify(char* buffer, int length, int k, int maxDecimalPlaces) { + const int kk = length + k; // 10^(kk-1) <= v < 10^kk + + if (0 <= k && kk <= 21) { + // 1234e7 -> 12340000000 + for (int i = length; i < kk; i++) + buffer[i] = '0'; + buffer[kk] = '.'; + buffer[kk + 1] = '0'; + return &buffer[kk + 2]; + } + else if (0 < kk && kk <= 21) { + // 1234e-2 -> 12.34 + std::memmove(&buffer[kk + 1], &buffer[kk], static_cast(length - kk)); + buffer[kk] = '.'; + if (0 > k + maxDecimalPlaces) { + // When maxDecimalPlaces = 2, 1.2345 -> 1.23, 1.102 -> 1.1 + // Remove extra trailing zeros (at least one) after truncation. + for (int i = kk + maxDecimalPlaces; i > kk + 1; i--) + if (buffer[i] != '0') + return &buffer[i + 1]; + return &buffer[kk + 2]; // Reserve one zero + } + else + return &buffer[length + 1]; + } + else if (-6 < kk && kk <= 0) { + // 1234e-6 -> 0.001234 + const int offset = 2 - kk; + std::memmove(&buffer[offset], &buffer[0], static_cast(length)); + buffer[0] = '0'; + buffer[1] = '.'; + for (int i = 2; i < offset; i++) + buffer[i] = '0'; + if (length - kk > maxDecimalPlaces) { + // When maxDecimalPlaces = 2, 0.123 -> 0.12, 0.102 -> 0.1 + // Remove extra trailing zeros (at least one) after truncation. + for (int i = maxDecimalPlaces + 1; i > 2; i--) + if (buffer[i] != '0') + return &buffer[i + 1]; + return &buffer[3]; // Reserve one zero + } + else + return &buffer[length + offset]; + } + else if (kk < -maxDecimalPlaces) { + // Truncate to zero + buffer[0] = '0'; + buffer[1] = '.'; + buffer[2] = '0'; + return &buffer[3]; + } + else if (length == 1) { + // 1e30 + buffer[1] = 'e'; + return WriteExponent(kk - 1, &buffer[2]); + } + else { + // 1234e30 -> 1.234e33 + std::memmove(&buffer[2], &buffer[1], static_cast(length - 1)); + buffer[1] = '.'; + buffer[length + 1] = 'e'; + return WriteExponent(kk - 1, &buffer[0 + length + 2]); + } +} + +inline char* dtoa(double value, char* buffer, int maxDecimalPlaces = 324) { + RAPIDJSON_ASSERT(maxDecimalPlaces >= 1); + Double d(value); + if (d.IsZero()) { + if (d.Sign()) + *buffer++ = '-'; // -0.0, Issue #289 + buffer[0] = '0'; + buffer[1] = '.'; + buffer[2] = '0'; + return &buffer[3]; + } + else { + if (value < 0) { + *buffer++ = '-'; + value = -value; + } + int length, K; + Grisu2(value, buffer, &length, &K); + return Prettify(buffer, length, K, maxDecimalPlaces); + } +} + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_DTOA_ diff --git a/Source/Dependencies/rapidjson/include/rapidjson/internal/ieee754.h b/Source/Dependencies/rapidjson/include/rapidjson/internal/ieee754.h new file mode 100644 index 000000000..82bb0b99e --- /dev/null +++ b/Source/Dependencies/rapidjson/include/rapidjson/internal/ieee754.h @@ -0,0 +1,78 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_IEEE754_ +#define RAPIDJSON_IEEE754_ + +#include "../rapidjson.h" + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +class Double { +public: + Double() {} + Double(double d) : d_(d) {} + Double(uint64_t u) : u_(u) {} + + double Value() const { return d_; } + uint64_t Uint64Value() const { return u_; } + + double NextPositiveDouble() const { + RAPIDJSON_ASSERT(!Sign()); + return Double(u_ + 1).Value(); + } + + bool Sign() const { return (u_ & kSignMask) != 0; } + uint64_t Significand() const { return u_ & kSignificandMask; } + int Exponent() const { return static_cast(((u_ & kExponentMask) >> kSignificandSize) - kExponentBias); } + + bool IsNan() const { return (u_ & kExponentMask) == kExponentMask && Significand() != 0; } + bool IsInf() const { return (u_ & kExponentMask) == kExponentMask && Significand() == 0; } + bool IsNanOrInf() const { return (u_ & kExponentMask) == kExponentMask; } + bool IsNormal() const { return (u_ & kExponentMask) != 0 || Significand() == 0; } + bool IsZero() const { return (u_ & (kExponentMask | kSignificandMask)) == 0; } + + uint64_t IntegerSignificand() const { return IsNormal() ? Significand() | kHiddenBit : Significand(); } + int IntegerExponent() const { return (IsNormal() ? Exponent() : kDenormalExponent) - kSignificandSize; } + uint64_t ToBias() const { return (u_ & kSignMask) ? ~u_ + 1 : u_ | kSignMask; } + + static unsigned EffectiveSignificandSize(int order) { + if (order >= -1021) + return 53; + else if (order <= -1074) + return 0; + else + return static_cast(order) + 1074; + } + +private: + static const int kSignificandSize = 52; + static const int kExponentBias = 0x3FF; + static const int kDenormalExponent = 1 - kExponentBias; + static const uint64_t kSignMask = RAPIDJSON_UINT64_C2(0x80000000, 0x00000000); + static const uint64_t kExponentMask = RAPIDJSON_UINT64_C2(0x7FF00000, 0x00000000); + static const uint64_t kSignificandMask = RAPIDJSON_UINT64_C2(0x000FFFFF, 0xFFFFFFFF); + static const uint64_t kHiddenBit = RAPIDJSON_UINT64_C2(0x00100000, 0x00000000); + + union { + double d_; + uint64_t u_; + }; +}; + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_IEEE754_ diff --git a/Source/Dependencies/rapidjson/include/rapidjson/internal/itoa.h b/Source/Dependencies/rapidjson/include/rapidjson/internal/itoa.h new file mode 100644 index 000000000..01a4e7e72 --- /dev/null +++ b/Source/Dependencies/rapidjson/include/rapidjson/internal/itoa.h @@ -0,0 +1,304 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ITOA_ +#define RAPIDJSON_ITOA_ + +#include "../rapidjson.h" + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +inline const char* GetDigitsLut() { + static const char cDigitsLut[200] = { + '0','0','0','1','0','2','0','3','0','4','0','5','0','6','0','7','0','8','0','9', + '1','0','1','1','1','2','1','3','1','4','1','5','1','6','1','7','1','8','1','9', + '2','0','2','1','2','2','2','3','2','4','2','5','2','6','2','7','2','8','2','9', + '3','0','3','1','3','2','3','3','3','4','3','5','3','6','3','7','3','8','3','9', + '4','0','4','1','4','2','4','3','4','4','4','5','4','6','4','7','4','8','4','9', + '5','0','5','1','5','2','5','3','5','4','5','5','5','6','5','7','5','8','5','9', + '6','0','6','1','6','2','6','3','6','4','6','5','6','6','6','7','6','8','6','9', + '7','0','7','1','7','2','7','3','7','4','7','5','7','6','7','7','7','8','7','9', + '8','0','8','1','8','2','8','3','8','4','8','5','8','6','8','7','8','8','8','9', + '9','0','9','1','9','2','9','3','9','4','9','5','9','6','9','7','9','8','9','9' + }; + return cDigitsLut; +} + +inline char* u32toa(uint32_t value, char* buffer) { + const char* cDigitsLut = GetDigitsLut(); + + if (value < 10000) { + const uint32_t d1 = (value / 100) << 1; + const uint32_t d2 = (value % 100) << 1; + + if (value >= 1000) + *buffer++ = cDigitsLut[d1]; + if (value >= 100) + *buffer++ = cDigitsLut[d1 + 1]; + if (value >= 10) + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + } + else if (value < 100000000) { + // value = bbbbcccc + const uint32_t b = value / 10000; + const uint32_t c = value % 10000; + + const uint32_t d1 = (b / 100) << 1; + const uint32_t d2 = (b % 100) << 1; + + const uint32_t d3 = (c / 100) << 1; + const uint32_t d4 = (c % 100) << 1; + + if (value >= 10000000) + *buffer++ = cDigitsLut[d1]; + if (value >= 1000000) + *buffer++ = cDigitsLut[d1 + 1]; + if (value >= 100000) + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + + *buffer++ = cDigitsLut[d3]; + *buffer++ = cDigitsLut[d3 + 1]; + *buffer++ = cDigitsLut[d4]; + *buffer++ = cDigitsLut[d4 + 1]; + } + else { + // value = aabbbbcccc in decimal + + const uint32_t a = value / 100000000; // 1 to 42 + value %= 100000000; + + if (a >= 10) { + const unsigned i = a << 1; + *buffer++ = cDigitsLut[i]; + *buffer++ = cDigitsLut[i + 1]; + } + else + *buffer++ = static_cast('0' + static_cast(a)); + + const uint32_t b = value / 10000; // 0 to 9999 + const uint32_t c = value % 10000; // 0 to 9999 + + const uint32_t d1 = (b / 100) << 1; + const uint32_t d2 = (b % 100) << 1; + + const uint32_t d3 = (c / 100) << 1; + const uint32_t d4 = (c % 100) << 1; + + *buffer++ = cDigitsLut[d1]; + *buffer++ = cDigitsLut[d1 + 1]; + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + *buffer++ = cDigitsLut[d3]; + *buffer++ = cDigitsLut[d3 + 1]; + *buffer++ = cDigitsLut[d4]; + *buffer++ = cDigitsLut[d4 + 1]; + } + return buffer; +} + +inline char* i32toa(int32_t value, char* buffer) { + uint32_t u = static_cast(value); + if (value < 0) { + *buffer++ = '-'; + u = ~u + 1; + } + + return u32toa(u, buffer); +} + +inline char* u64toa(uint64_t value, char* buffer) { + const char* cDigitsLut = GetDigitsLut(); + const uint64_t kTen8 = 100000000; + const uint64_t kTen9 = kTen8 * 10; + const uint64_t kTen10 = kTen8 * 100; + const uint64_t kTen11 = kTen8 * 1000; + const uint64_t kTen12 = kTen8 * 10000; + const uint64_t kTen13 = kTen8 * 100000; + const uint64_t kTen14 = kTen8 * 1000000; + const uint64_t kTen15 = kTen8 * 10000000; + const uint64_t kTen16 = kTen8 * kTen8; + + if (value < kTen8) { + uint32_t v = static_cast(value); + if (v < 10000) { + const uint32_t d1 = (v / 100) << 1; + const uint32_t d2 = (v % 100) << 1; + + if (v >= 1000) + *buffer++ = cDigitsLut[d1]; + if (v >= 100) + *buffer++ = cDigitsLut[d1 + 1]; + if (v >= 10) + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + } + else { + // value = bbbbcccc + const uint32_t b = v / 10000; + const uint32_t c = v % 10000; + + const uint32_t d1 = (b / 100) << 1; + const uint32_t d2 = (b % 100) << 1; + + const uint32_t d3 = (c / 100) << 1; + const uint32_t d4 = (c % 100) << 1; + + if (value >= 10000000) + *buffer++ = cDigitsLut[d1]; + if (value >= 1000000) + *buffer++ = cDigitsLut[d1 + 1]; + if (value >= 100000) + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + + *buffer++ = cDigitsLut[d3]; + *buffer++ = cDigitsLut[d3 + 1]; + *buffer++ = cDigitsLut[d4]; + *buffer++ = cDigitsLut[d4 + 1]; + } + } + else if (value < kTen16) { + const uint32_t v0 = static_cast(value / kTen8); + const uint32_t v1 = static_cast(value % kTen8); + + const uint32_t b0 = v0 / 10000; + const uint32_t c0 = v0 % 10000; + + const uint32_t d1 = (b0 / 100) << 1; + const uint32_t d2 = (b0 % 100) << 1; + + const uint32_t d3 = (c0 / 100) << 1; + const uint32_t d4 = (c0 % 100) << 1; + + const uint32_t b1 = v1 / 10000; + const uint32_t c1 = v1 % 10000; + + const uint32_t d5 = (b1 / 100) << 1; + const uint32_t d6 = (b1 % 100) << 1; + + const uint32_t d7 = (c1 / 100) << 1; + const uint32_t d8 = (c1 % 100) << 1; + + if (value >= kTen15) + *buffer++ = cDigitsLut[d1]; + if (value >= kTen14) + *buffer++ = cDigitsLut[d1 + 1]; + if (value >= kTen13) + *buffer++ = cDigitsLut[d2]; + if (value >= kTen12) + *buffer++ = cDigitsLut[d2 + 1]; + if (value >= kTen11) + *buffer++ = cDigitsLut[d3]; + if (value >= kTen10) + *buffer++ = cDigitsLut[d3 + 1]; + if (value >= kTen9) + *buffer++ = cDigitsLut[d4]; + if (value >= kTen8) + *buffer++ = cDigitsLut[d4 + 1]; + + *buffer++ = cDigitsLut[d5]; + *buffer++ = cDigitsLut[d5 + 1]; + *buffer++ = cDigitsLut[d6]; + *buffer++ = cDigitsLut[d6 + 1]; + *buffer++ = cDigitsLut[d7]; + *buffer++ = cDigitsLut[d7 + 1]; + *buffer++ = cDigitsLut[d8]; + *buffer++ = cDigitsLut[d8 + 1]; + } + else { + const uint32_t a = static_cast(value / kTen16); // 1 to 1844 + value %= kTen16; + + if (a < 10) + *buffer++ = static_cast('0' + static_cast(a)); + else if (a < 100) { + const uint32_t i = a << 1; + *buffer++ = cDigitsLut[i]; + *buffer++ = cDigitsLut[i + 1]; + } + else if (a < 1000) { + *buffer++ = static_cast('0' + static_cast(a / 100)); + + const uint32_t i = (a % 100) << 1; + *buffer++ = cDigitsLut[i]; + *buffer++ = cDigitsLut[i + 1]; + } + else { + const uint32_t i = (a / 100) << 1; + const uint32_t j = (a % 100) << 1; + *buffer++ = cDigitsLut[i]; + *buffer++ = cDigitsLut[i + 1]; + *buffer++ = cDigitsLut[j]; + *buffer++ = cDigitsLut[j + 1]; + } + + const uint32_t v0 = static_cast(value / kTen8); + const uint32_t v1 = static_cast(value % kTen8); + + const uint32_t b0 = v0 / 10000; + const uint32_t c0 = v0 % 10000; + + const uint32_t d1 = (b0 / 100) << 1; + const uint32_t d2 = (b0 % 100) << 1; + + const uint32_t d3 = (c0 / 100) << 1; + const uint32_t d4 = (c0 % 100) << 1; + + const uint32_t b1 = v1 / 10000; + const uint32_t c1 = v1 % 10000; + + const uint32_t d5 = (b1 / 100) << 1; + const uint32_t d6 = (b1 % 100) << 1; + + const uint32_t d7 = (c1 / 100) << 1; + const uint32_t d8 = (c1 % 100) << 1; + + *buffer++ = cDigitsLut[d1]; + *buffer++ = cDigitsLut[d1 + 1]; + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + *buffer++ = cDigitsLut[d3]; + *buffer++ = cDigitsLut[d3 + 1]; + *buffer++ = cDigitsLut[d4]; + *buffer++ = cDigitsLut[d4 + 1]; + *buffer++ = cDigitsLut[d5]; + *buffer++ = cDigitsLut[d5 + 1]; + *buffer++ = cDigitsLut[d6]; + *buffer++ = cDigitsLut[d6 + 1]; + *buffer++ = cDigitsLut[d7]; + *buffer++ = cDigitsLut[d7 + 1]; + *buffer++ = cDigitsLut[d8]; + *buffer++ = cDigitsLut[d8 + 1]; + } + + return buffer; +} + +inline char* i64toa(int64_t value, char* buffer) { + uint64_t u = static_cast(value); + if (value < 0) { + *buffer++ = '-'; + u = ~u + 1; + } + + return u64toa(u, buffer); +} + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_ITOA_ diff --git a/Source/Dependencies/rapidjson/include/rapidjson/internal/meta.h b/Source/Dependencies/rapidjson/include/rapidjson/internal/meta.h new file mode 100644 index 000000000..5a9aaa428 --- /dev/null +++ b/Source/Dependencies/rapidjson/include/rapidjson/internal/meta.h @@ -0,0 +1,181 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_INTERNAL_META_H_ +#define RAPIDJSON_INTERNAL_META_H_ + +#include "../rapidjson.h" + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif +#if defined(_MSC_VER) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(6334) +#endif + +#if RAPIDJSON_HAS_CXX11_TYPETRAITS +#include +#endif + +//@cond RAPIDJSON_INTERNAL +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +// Helper to wrap/convert arbitrary types to void, useful for arbitrary type matching +template struct Void { typedef void Type; }; + +/////////////////////////////////////////////////////////////////////////////// +// BoolType, TrueType, FalseType +// +template struct BoolType { + static const bool Value = Cond; + typedef BoolType Type; +}; +typedef BoolType TrueType; +typedef BoolType FalseType; + + +/////////////////////////////////////////////////////////////////////////////// +// SelectIf, BoolExpr, NotExpr, AndExpr, OrExpr +// + +template struct SelectIfImpl { template struct Apply { typedef T1 Type; }; }; +template <> struct SelectIfImpl { template struct Apply { typedef T2 Type; }; }; +template struct SelectIfCond : SelectIfImpl::template Apply {}; +template struct SelectIf : SelectIfCond {}; + +template struct AndExprCond : FalseType {}; +template <> struct AndExprCond : TrueType {}; +template struct OrExprCond : TrueType {}; +template <> struct OrExprCond : FalseType {}; + +template struct BoolExpr : SelectIf::Type {}; +template struct NotExpr : SelectIf::Type {}; +template struct AndExpr : AndExprCond::Type {}; +template struct OrExpr : OrExprCond::Type {}; + + +/////////////////////////////////////////////////////////////////////////////// +// AddConst, MaybeAddConst, RemoveConst +template struct AddConst { typedef const T Type; }; +template struct MaybeAddConst : SelectIfCond {}; +template struct RemoveConst { typedef T Type; }; +template struct RemoveConst { typedef T Type; }; + + +/////////////////////////////////////////////////////////////////////////////// +// IsSame, IsConst, IsMoreConst, IsPointer +// +template struct IsSame : FalseType {}; +template struct IsSame : TrueType {}; + +template struct IsConst : FalseType {}; +template struct IsConst : TrueType {}; + +template +struct IsMoreConst + : AndExpr::Type, typename RemoveConst::Type>, + BoolType::Value >= IsConst::Value> >::Type {}; + +template struct IsPointer : FalseType {}; +template struct IsPointer : TrueType {}; + +/////////////////////////////////////////////////////////////////////////////// +// IsBaseOf +// +#if RAPIDJSON_HAS_CXX11_TYPETRAITS + +template struct IsBaseOf + : BoolType< ::std::is_base_of::value> {}; + +#else // simplified version adopted from Boost + +template struct IsBaseOfImpl { + RAPIDJSON_STATIC_ASSERT(sizeof(B) != 0); + RAPIDJSON_STATIC_ASSERT(sizeof(D) != 0); + + typedef char (&Yes)[1]; + typedef char (&No) [2]; + + template + static Yes Check(const D*, T); + static No Check(const B*, int); + + struct Host { + operator const B*() const; + operator const D*(); + }; + + enum { Value = (sizeof(Check(Host(), 0)) == sizeof(Yes)) }; +}; + +template struct IsBaseOf + : OrExpr, BoolExpr > >::Type {}; + +#endif // RAPIDJSON_HAS_CXX11_TYPETRAITS + + +////////////////////////////////////////////////////////////////////////// +// EnableIf / DisableIf +// +template struct EnableIfCond { typedef T Type; }; +template struct EnableIfCond { /* empty */ }; + +template struct DisableIfCond { typedef T Type; }; +template struct DisableIfCond { /* empty */ }; + +template +struct EnableIf : EnableIfCond {}; + +template +struct DisableIf : DisableIfCond {}; + +// SFINAE helpers +struct SfinaeTag {}; +template struct RemoveSfinaeTag; +template struct RemoveSfinaeTag { typedef T Type; }; + +#define RAPIDJSON_REMOVEFPTR_(type) \ + typename ::RAPIDJSON_NAMESPACE::internal::RemoveSfinaeTag \ + < ::RAPIDJSON_NAMESPACE::internal::SfinaeTag&(*) type>::Type + +#define RAPIDJSON_ENABLEIF(cond) \ + typename ::RAPIDJSON_NAMESPACE::internal::EnableIf \ + ::Type * = NULL + +#define RAPIDJSON_DISABLEIF(cond) \ + typename ::RAPIDJSON_NAMESPACE::internal::DisableIf \ + ::Type * = NULL + +#define RAPIDJSON_ENABLEIF_RETURN(cond,returntype) \ + typename ::RAPIDJSON_NAMESPACE::internal::EnableIf \ + ::Type + +#define RAPIDJSON_DISABLEIF_RETURN(cond,returntype) \ + typename ::RAPIDJSON_NAMESPACE::internal::DisableIf \ + ::Type + +} // namespace internal +RAPIDJSON_NAMESPACE_END +//@endcond + +#if defined(__GNUC__) || defined(_MSC_VER) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_INTERNAL_META_H_ diff --git a/Source/Dependencies/rapidjson/include/rapidjson/internal/pow10.h b/Source/Dependencies/rapidjson/include/rapidjson/internal/pow10.h new file mode 100644 index 000000000..02f475d70 --- /dev/null +++ b/Source/Dependencies/rapidjson/include/rapidjson/internal/pow10.h @@ -0,0 +1,55 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_POW10_ +#define RAPIDJSON_POW10_ + +#include "../rapidjson.h" + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +//! Computes integer powers of 10 in double (10.0^n). +/*! This function uses lookup table for fast and accurate results. + \param n non-negative exponent. Must <= 308. + \return 10.0^n +*/ +inline double Pow10(int n) { + static const double e[] = { // 1e-0...1e308: 309 * 8 bytes = 2472 bytes + 1e+0, + 1e+1, 1e+2, 1e+3, 1e+4, 1e+5, 1e+6, 1e+7, 1e+8, 1e+9, 1e+10, 1e+11, 1e+12, 1e+13, 1e+14, 1e+15, 1e+16, 1e+17, 1e+18, 1e+19, 1e+20, + 1e+21, 1e+22, 1e+23, 1e+24, 1e+25, 1e+26, 1e+27, 1e+28, 1e+29, 1e+30, 1e+31, 1e+32, 1e+33, 1e+34, 1e+35, 1e+36, 1e+37, 1e+38, 1e+39, 1e+40, + 1e+41, 1e+42, 1e+43, 1e+44, 1e+45, 1e+46, 1e+47, 1e+48, 1e+49, 1e+50, 1e+51, 1e+52, 1e+53, 1e+54, 1e+55, 1e+56, 1e+57, 1e+58, 1e+59, 1e+60, + 1e+61, 1e+62, 1e+63, 1e+64, 1e+65, 1e+66, 1e+67, 1e+68, 1e+69, 1e+70, 1e+71, 1e+72, 1e+73, 1e+74, 1e+75, 1e+76, 1e+77, 1e+78, 1e+79, 1e+80, + 1e+81, 1e+82, 1e+83, 1e+84, 1e+85, 1e+86, 1e+87, 1e+88, 1e+89, 1e+90, 1e+91, 1e+92, 1e+93, 1e+94, 1e+95, 1e+96, 1e+97, 1e+98, 1e+99, 1e+100, + 1e+101,1e+102,1e+103,1e+104,1e+105,1e+106,1e+107,1e+108,1e+109,1e+110,1e+111,1e+112,1e+113,1e+114,1e+115,1e+116,1e+117,1e+118,1e+119,1e+120, + 1e+121,1e+122,1e+123,1e+124,1e+125,1e+126,1e+127,1e+128,1e+129,1e+130,1e+131,1e+132,1e+133,1e+134,1e+135,1e+136,1e+137,1e+138,1e+139,1e+140, + 1e+141,1e+142,1e+143,1e+144,1e+145,1e+146,1e+147,1e+148,1e+149,1e+150,1e+151,1e+152,1e+153,1e+154,1e+155,1e+156,1e+157,1e+158,1e+159,1e+160, + 1e+161,1e+162,1e+163,1e+164,1e+165,1e+166,1e+167,1e+168,1e+169,1e+170,1e+171,1e+172,1e+173,1e+174,1e+175,1e+176,1e+177,1e+178,1e+179,1e+180, + 1e+181,1e+182,1e+183,1e+184,1e+185,1e+186,1e+187,1e+188,1e+189,1e+190,1e+191,1e+192,1e+193,1e+194,1e+195,1e+196,1e+197,1e+198,1e+199,1e+200, + 1e+201,1e+202,1e+203,1e+204,1e+205,1e+206,1e+207,1e+208,1e+209,1e+210,1e+211,1e+212,1e+213,1e+214,1e+215,1e+216,1e+217,1e+218,1e+219,1e+220, + 1e+221,1e+222,1e+223,1e+224,1e+225,1e+226,1e+227,1e+228,1e+229,1e+230,1e+231,1e+232,1e+233,1e+234,1e+235,1e+236,1e+237,1e+238,1e+239,1e+240, + 1e+241,1e+242,1e+243,1e+244,1e+245,1e+246,1e+247,1e+248,1e+249,1e+250,1e+251,1e+252,1e+253,1e+254,1e+255,1e+256,1e+257,1e+258,1e+259,1e+260, + 1e+261,1e+262,1e+263,1e+264,1e+265,1e+266,1e+267,1e+268,1e+269,1e+270,1e+271,1e+272,1e+273,1e+274,1e+275,1e+276,1e+277,1e+278,1e+279,1e+280, + 1e+281,1e+282,1e+283,1e+284,1e+285,1e+286,1e+287,1e+288,1e+289,1e+290,1e+291,1e+292,1e+293,1e+294,1e+295,1e+296,1e+297,1e+298,1e+299,1e+300, + 1e+301,1e+302,1e+303,1e+304,1e+305,1e+306,1e+307,1e+308 + }; + RAPIDJSON_ASSERT(n >= 0 && n <= 308); + return e[n]; +} + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_POW10_ diff --git a/Source/Dependencies/rapidjson/include/rapidjson/internal/regex.h b/Source/Dependencies/rapidjson/include/rapidjson/internal/regex.h new file mode 100644 index 000000000..422a5240b --- /dev/null +++ b/Source/Dependencies/rapidjson/include/rapidjson/internal/regex.h @@ -0,0 +1,701 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_INTERNAL_REGEX_H_ +#define RAPIDJSON_INTERNAL_REGEX_H_ + +#include "../allocators.h" +#include "../stream.h" +#include "stack.h" + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +RAPIDJSON_DIAG_OFF(switch-enum) +RAPIDJSON_DIAG_OFF(implicit-fallthrough) +#endif + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +#ifdef _MSC_VER +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4512) // assignment operator could not be generated +#endif + +#ifndef RAPIDJSON_REGEX_VERBOSE +#define RAPIDJSON_REGEX_VERBOSE 0 +#endif + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +/////////////////////////////////////////////////////////////////////////////// +// GenericRegex + +static const SizeType kRegexInvalidState = ~SizeType(0); //!< Represents an invalid index in GenericRegex::State::out, out1 +static const SizeType kRegexInvalidRange = ~SizeType(0); + +//! Regular expression engine with subset of ECMAscript grammar. +/*! + Supported regular expression syntax: + - \c ab Concatenation + - \c a|b Alternation + - \c a? Zero or one + - \c a* Zero or more + - \c a+ One or more + - \c a{3} Exactly 3 times + - \c a{3,} At least 3 times + - \c a{3,5} 3 to 5 times + - \c (ab) Grouping + - \c ^a At the beginning + - \c a$ At the end + - \c . Any character + - \c [abc] Character classes + - \c [a-c] Character class range + - \c [a-z0-9_] Character class combination + - \c [^abc] Negated character classes + - \c [^a-c] Negated character class range + - \c [\b] Backspace (U+0008) + - \c \\| \\\\ ... Escape characters + - \c \\f Form feed (U+000C) + - \c \\n Line feed (U+000A) + - \c \\r Carriage return (U+000D) + - \c \\t Tab (U+0009) + - \c \\v Vertical tab (U+000B) + + \note This is a Thompson NFA engine, implemented with reference to + Cox, Russ. "Regular Expression Matching Can Be Simple And Fast (but is slow in Java, Perl, PHP, Python, Ruby,...).", + https://swtch.com/~rsc/regexp/regexp1.html +*/ +template +class GenericRegex { +public: + typedef typename Encoding::Ch Ch; + + GenericRegex(const Ch* source, Allocator* allocator = 0) : + states_(allocator, 256), ranges_(allocator, 256), root_(kRegexInvalidState), stateCount_(), rangeCount_(), + stateSet_(), state0_(allocator, 0), state1_(allocator, 0), anchorBegin_(), anchorEnd_() + { + GenericStringStream ss(source); + DecodedStream > ds(ss); + Parse(ds); + } + + ~GenericRegex() { + Allocator::Free(stateSet_); + } + + bool IsValid() const { + return root_ != kRegexInvalidState; + } + + template + bool Match(InputStream& is) const { + return SearchWithAnchoring(is, true, true); + } + + bool Match(const Ch* s) const { + GenericStringStream is(s); + return Match(is); + } + + template + bool Search(InputStream& is) const { + return SearchWithAnchoring(is, anchorBegin_, anchorEnd_); + } + + bool Search(const Ch* s) const { + GenericStringStream is(s); + return Search(is); + } + +private: + enum Operator { + kZeroOrOne, + kZeroOrMore, + kOneOrMore, + kConcatenation, + kAlternation, + kLeftParenthesis + }; + + static const unsigned kAnyCharacterClass = 0xFFFFFFFF; //!< For '.' + static const unsigned kRangeCharacterClass = 0xFFFFFFFE; + static const unsigned kRangeNegationFlag = 0x80000000; + + struct Range { + unsigned start; // + unsigned end; + SizeType next; + }; + + struct State { + SizeType out; //!< Equals to kInvalid for matching state + SizeType out1; //!< Equals to non-kInvalid for split + SizeType rangeStart; + unsigned codepoint; + }; + + struct Frag { + Frag(SizeType s, SizeType o, SizeType m) : start(s), out(o), minIndex(m) {} + SizeType start; + SizeType out; //!< link-list of all output states + SizeType minIndex; + }; + + template + class DecodedStream { + public: + DecodedStream(SourceStream& ss) : ss_(ss), codepoint_() { Decode(); } + unsigned Peek() { return codepoint_; } + unsigned Take() { + unsigned c = codepoint_; + if (c) // No further decoding when '\0' + Decode(); + return c; + } + + private: + void Decode() { + if (!Encoding::Decode(ss_, &codepoint_)) + codepoint_ = 0; + } + + SourceStream& ss_; + unsigned codepoint_; + }; + + State& GetState(SizeType index) { + RAPIDJSON_ASSERT(index < stateCount_); + return states_.template Bottom()[index]; + } + + const State& GetState(SizeType index) const { + RAPIDJSON_ASSERT(index < stateCount_); + return states_.template Bottom()[index]; + } + + Range& GetRange(SizeType index) { + RAPIDJSON_ASSERT(index < rangeCount_); + return ranges_.template Bottom()[index]; + } + + const Range& GetRange(SizeType index) const { + RAPIDJSON_ASSERT(index < rangeCount_); + return ranges_.template Bottom()[index]; + } + + template + void Parse(DecodedStream& ds) { + Allocator allocator; + Stack operandStack(&allocator, 256); // Frag + Stack operatorStack(&allocator, 256); // Operator + Stack atomCountStack(&allocator, 256); // unsigned (Atom per parenthesis) + + *atomCountStack.template Push() = 0; + + unsigned codepoint; + while (ds.Peek() != 0) { + switch (codepoint = ds.Take()) { + case '^': + anchorBegin_ = true; + break; + + case '$': + anchorEnd_ = true; + break; + + case '|': + while (!operatorStack.Empty() && *operatorStack.template Top() < kAlternation) + if (!Eval(operandStack, *operatorStack.template Pop(1))) + return; + *operatorStack.template Push() = kAlternation; + *atomCountStack.template Top() = 0; + break; + + case '(': + *operatorStack.template Push() = kLeftParenthesis; + *atomCountStack.template Push() = 0; + break; + + case ')': + while (!operatorStack.Empty() && *operatorStack.template Top() != kLeftParenthesis) + if (!Eval(operandStack, *operatorStack.template Pop(1))) + return; + if (operatorStack.Empty()) + return; + operatorStack.template Pop(1); + atomCountStack.template Pop(1); + ImplicitConcatenation(atomCountStack, operatorStack); + break; + + case '?': + if (!Eval(operandStack, kZeroOrOne)) + return; + break; + + case '*': + if (!Eval(operandStack, kZeroOrMore)) + return; + break; + + case '+': + if (!Eval(operandStack, kOneOrMore)) + return; + break; + + case '{': + { + unsigned n, m; + if (!ParseUnsigned(ds, &n)) + return; + + if (ds.Peek() == ',') { + ds.Take(); + if (ds.Peek() == '}') + m = kInfinityQuantifier; + else if (!ParseUnsigned(ds, &m) || m < n) + return; + } + else + m = n; + + if (!EvalQuantifier(operandStack, n, m) || ds.Peek() != '}') + return; + ds.Take(); + } + break; + + case '.': + PushOperand(operandStack, kAnyCharacterClass); + ImplicitConcatenation(atomCountStack, operatorStack); + break; + + case '[': + { + SizeType range; + if (!ParseRange(ds, &range)) + return; + SizeType s = NewState(kRegexInvalidState, kRegexInvalidState, kRangeCharacterClass); + GetState(s).rangeStart = range; + *operandStack.template Push() = Frag(s, s, s); + } + ImplicitConcatenation(atomCountStack, operatorStack); + break; + + case '\\': // Escape character + if (!CharacterEscape(ds, &codepoint)) + return; // Unsupported escape character + // fall through to default + + default: // Pattern character + PushOperand(operandStack, codepoint); + ImplicitConcatenation(atomCountStack, operatorStack); + } + } + + while (!operatorStack.Empty()) + if (!Eval(operandStack, *operatorStack.template Pop(1))) + return; + + // Link the operand to matching state. + if (operandStack.GetSize() == sizeof(Frag)) { + Frag* e = operandStack.template Pop(1); + Patch(e->out, NewState(kRegexInvalidState, kRegexInvalidState, 0)); + root_ = e->start; + +#if RAPIDJSON_REGEX_VERBOSE + printf("root: %d\n", root_); + for (SizeType i = 0; i < stateCount_ ; i++) { + State& s = GetState(i); + printf("[%2d] out: %2d out1: %2d c: '%c'\n", i, s.out, s.out1, (char)s.codepoint); + } + printf("\n"); +#endif + } + + // Preallocate buffer for SearchWithAnchoring() + RAPIDJSON_ASSERT(stateSet_ == 0); + if (stateCount_ > 0) { + stateSet_ = static_cast(states_.GetAllocator().Malloc(GetStateSetSize())); + state0_.template Reserve(stateCount_); + state1_.template Reserve(stateCount_); + } + } + + SizeType NewState(SizeType out, SizeType out1, unsigned codepoint) { + State* s = states_.template Push(); + s->out = out; + s->out1 = out1; + s->codepoint = codepoint; + s->rangeStart = kRegexInvalidRange; + return stateCount_++; + } + + void PushOperand(Stack& operandStack, unsigned codepoint) { + SizeType s = NewState(kRegexInvalidState, kRegexInvalidState, codepoint); + *operandStack.template Push() = Frag(s, s, s); + } + + void ImplicitConcatenation(Stack& atomCountStack, Stack& operatorStack) { + if (*atomCountStack.template Top()) + *operatorStack.template Push() = kConcatenation; + (*atomCountStack.template Top())++; + } + + SizeType Append(SizeType l1, SizeType l2) { + SizeType old = l1; + while (GetState(l1).out != kRegexInvalidState) + l1 = GetState(l1).out; + GetState(l1).out = l2; + return old; + } + + void Patch(SizeType l, SizeType s) { + for (SizeType next; l != kRegexInvalidState; l = next) { + next = GetState(l).out; + GetState(l).out = s; + } + } + + bool Eval(Stack& operandStack, Operator op) { + switch (op) { + case kConcatenation: + RAPIDJSON_ASSERT(operandStack.GetSize() >= sizeof(Frag) * 2); + { + Frag e2 = *operandStack.template Pop(1); + Frag e1 = *operandStack.template Pop(1); + Patch(e1.out, e2.start); + *operandStack.template Push() = Frag(e1.start, e2.out, Min(e1.minIndex, e2.minIndex)); + } + return true; + + case kAlternation: + if (operandStack.GetSize() >= sizeof(Frag) * 2) { + Frag e2 = *operandStack.template Pop(1); + Frag e1 = *operandStack.template Pop(1); + SizeType s = NewState(e1.start, e2.start, 0); + *operandStack.template Push() = Frag(s, Append(e1.out, e2.out), Min(e1.minIndex, e2.minIndex)); + return true; + } + return false; + + case kZeroOrOne: + if (operandStack.GetSize() >= sizeof(Frag)) { + Frag e = *operandStack.template Pop(1); + SizeType s = NewState(kRegexInvalidState, e.start, 0); + *operandStack.template Push() = Frag(s, Append(e.out, s), e.minIndex); + return true; + } + return false; + + case kZeroOrMore: + if (operandStack.GetSize() >= sizeof(Frag)) { + Frag e = *operandStack.template Pop(1); + SizeType s = NewState(kRegexInvalidState, e.start, 0); + Patch(e.out, s); + *operandStack.template Push() = Frag(s, s, e.minIndex); + return true; + } + return false; + + default: + RAPIDJSON_ASSERT(op == kOneOrMore); + if (operandStack.GetSize() >= sizeof(Frag)) { + Frag e = *operandStack.template Pop(1); + SizeType s = NewState(kRegexInvalidState, e.start, 0); + Patch(e.out, s); + *operandStack.template Push() = Frag(e.start, s, e.minIndex); + return true; + } + return false; + } + } + + bool EvalQuantifier(Stack& operandStack, unsigned n, unsigned m) { + RAPIDJSON_ASSERT(n <= m); + RAPIDJSON_ASSERT(operandStack.GetSize() >= sizeof(Frag)); + + if (n == 0) { + if (m == 0) // a{0} not support + return false; + else if (m == kInfinityQuantifier) + Eval(operandStack, kZeroOrMore); // a{0,} -> a* + else { + Eval(operandStack, kZeroOrOne); // a{0,5} -> a? + for (unsigned i = 0; i < m - 1; i++) + CloneTopOperand(operandStack); // a{0,5} -> a? a? a? a? a? + for (unsigned i = 0; i < m - 1; i++) + Eval(operandStack, kConcatenation); // a{0,5} -> a?a?a?a?a? + } + return true; + } + + for (unsigned i = 0; i < n - 1; i++) // a{3} -> a a a + CloneTopOperand(operandStack); + + if (m == kInfinityQuantifier) + Eval(operandStack, kOneOrMore); // a{3,} -> a a a+ + else if (m > n) { + CloneTopOperand(operandStack); // a{3,5} -> a a a a + Eval(operandStack, kZeroOrOne); // a{3,5} -> a a a a? + for (unsigned i = n; i < m - 1; i++) + CloneTopOperand(operandStack); // a{3,5} -> a a a a? a? + for (unsigned i = n; i < m; i++) + Eval(operandStack, kConcatenation); // a{3,5} -> a a aa?a? + } + + for (unsigned i = 0; i < n - 1; i++) + Eval(operandStack, kConcatenation); // a{3} -> aaa, a{3,} -> aaa+, a{3.5} -> aaaa?a? + + return true; + } + + static SizeType Min(SizeType a, SizeType b) { return a < b ? a : b; } + + void CloneTopOperand(Stack& operandStack) { + const Frag src = *operandStack.template Top(); // Copy constructor to prevent invalidation + SizeType count = stateCount_ - src.minIndex; // Assumes top operand contains states in [src->minIndex, stateCount_) + State* s = states_.template Push(count); + memcpy(s, &GetState(src.minIndex), count * sizeof(State)); + for (SizeType j = 0; j < count; j++) { + if (s[j].out != kRegexInvalidState) + s[j].out += count; + if (s[j].out1 != kRegexInvalidState) + s[j].out1 += count; + } + *operandStack.template Push() = Frag(src.start + count, src.out + count, src.minIndex + count); + stateCount_ += count; + } + + template + bool ParseUnsigned(DecodedStream& ds, unsigned* u) { + unsigned r = 0; + if (ds.Peek() < '0' || ds.Peek() > '9') + return false; + while (ds.Peek() >= '0' && ds.Peek() <= '9') { + if (r >= 429496729 && ds.Peek() > '5') // 2^32 - 1 = 4294967295 + return false; // overflow + r = r * 10 + (ds.Take() - '0'); + } + *u = r; + return true; + } + + template + bool ParseRange(DecodedStream& ds, SizeType* range) { + bool isBegin = true; + bool negate = false; + int step = 0; + SizeType start = kRegexInvalidRange; + SizeType current = kRegexInvalidRange; + unsigned codepoint; + while ((codepoint = ds.Take()) != 0) { + if (isBegin) { + isBegin = false; + if (codepoint == '^') { + negate = true; + continue; + } + } + + switch (codepoint) { + case ']': + if (start == kRegexInvalidRange) + return false; // Error: nothing inside [] + if (step == 2) { // Add trailing '-' + SizeType r = NewRange('-'); + RAPIDJSON_ASSERT(current != kRegexInvalidRange); + GetRange(current).next = r; + } + if (negate) + GetRange(start).start |= kRangeNegationFlag; + *range = start; + return true; + + case '\\': + if (ds.Peek() == 'b') { + ds.Take(); + codepoint = 0x0008; // Escape backspace character + } + else if (!CharacterEscape(ds, &codepoint)) + return false; + // fall through to default + + default: + switch (step) { + case 1: + if (codepoint == '-') { + step++; + break; + } + // fall through to step 0 for other characters + + case 0: + { + SizeType r = NewRange(codepoint); + if (current != kRegexInvalidRange) + GetRange(current).next = r; + if (start == kRegexInvalidRange) + start = r; + current = r; + } + step = 1; + break; + + default: + RAPIDJSON_ASSERT(step == 2); + GetRange(current).end = codepoint; + step = 0; + } + } + } + return false; + } + + SizeType NewRange(unsigned codepoint) { + Range* r = ranges_.template Push(); + r->start = r->end = codepoint; + r->next = kRegexInvalidRange; + return rangeCount_++; + } + + template + bool CharacterEscape(DecodedStream& ds, unsigned* escapedCodepoint) { + unsigned codepoint; + switch (codepoint = ds.Take()) { + case '^': + case '$': + case '|': + case '(': + case ')': + case '?': + case '*': + case '+': + case '.': + case '[': + case ']': + case '{': + case '}': + case '\\': + *escapedCodepoint = codepoint; return true; + case 'f': *escapedCodepoint = 0x000C; return true; + case 'n': *escapedCodepoint = 0x000A; return true; + case 'r': *escapedCodepoint = 0x000D; return true; + case 't': *escapedCodepoint = 0x0009; return true; + case 'v': *escapedCodepoint = 0x000B; return true; + default: + return false; // Unsupported escape character + } + } + + template + bool SearchWithAnchoring(InputStream& is, bool anchorBegin, bool anchorEnd) const { + RAPIDJSON_ASSERT(IsValid()); + DecodedStream ds(is); + + state0_.Clear(); + Stack *current = &state0_, *next = &state1_; + const size_t stateSetSize = GetStateSetSize(); + std::memset(stateSet_, 0, stateSetSize); + + bool matched = AddState(*current, root_); + unsigned codepoint; + while (!current->Empty() && (codepoint = ds.Take()) != 0) { + std::memset(stateSet_, 0, stateSetSize); + next->Clear(); + matched = false; + for (const SizeType* s = current->template Bottom(); s != current->template End(); ++s) { + const State& sr = GetState(*s); + if (sr.codepoint == codepoint || + sr.codepoint == kAnyCharacterClass || + (sr.codepoint == kRangeCharacterClass && MatchRange(sr.rangeStart, codepoint))) + { + matched = AddState(*next, sr.out) || matched; + if (!anchorEnd && matched) + return true; + } + if (!anchorBegin) + AddState(*next, root_); + } + internal::Swap(current, next); + } + + return matched; + } + + size_t GetStateSetSize() const { + return (stateCount_ + 31) / 32 * 4; + } + + // Return whether the added states is a match state + bool AddState(Stack& l, SizeType index) const { + RAPIDJSON_ASSERT(index != kRegexInvalidState); + + const State& s = GetState(index); + if (s.out1 != kRegexInvalidState) { // Split + bool matched = AddState(l, s.out); + return AddState(l, s.out1) || matched; + } + else if (!(stateSet_[index >> 5] & (1 << (index & 31)))) { + stateSet_[index >> 5] |= (1 << (index & 31)); + *l.template PushUnsafe() = index; + } + return s.out == kRegexInvalidState; // by using PushUnsafe() above, we can ensure s is not validated due to reallocation. + } + + bool MatchRange(SizeType rangeIndex, unsigned codepoint) const { + bool yes = (GetRange(rangeIndex).start & kRangeNegationFlag) == 0; + while (rangeIndex != kRegexInvalidRange) { + const Range& r = GetRange(rangeIndex); + if (codepoint >= (r.start & ~kRangeNegationFlag) && codepoint <= r.end) + return yes; + rangeIndex = r.next; + } + return !yes; + } + + Stack states_; + Stack ranges_; + SizeType root_; + SizeType stateCount_; + SizeType rangeCount_; + + static const unsigned kInfinityQuantifier = ~0u; + + // For SearchWithAnchoring() + uint32_t* stateSet_; // allocated by states_.GetAllocator() + mutable Stack state0_; + mutable Stack state1_; + bool anchorBegin_; + bool anchorEnd_; +}; + +typedef GenericRegex > Regex; + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#ifdef _MSC_VER +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_INTERNAL_REGEX_H_ diff --git a/Source/Dependencies/rapidjson/include/rapidjson/internal/stack.h b/Source/Dependencies/rapidjson/include/rapidjson/internal/stack.h new file mode 100644 index 000000000..022c9aab4 --- /dev/null +++ b/Source/Dependencies/rapidjson/include/rapidjson/internal/stack.h @@ -0,0 +1,230 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_INTERNAL_STACK_H_ +#define RAPIDJSON_INTERNAL_STACK_H_ + +#include "../allocators.h" +#include "swap.h" + +#if defined(__clang__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(c++98-compat) +#endif + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +/////////////////////////////////////////////////////////////////////////////// +// Stack + +//! A type-unsafe stack for storing different types of data. +/*! \tparam Allocator Allocator for allocating stack memory. +*/ +template +class Stack { +public: + // Optimization note: Do not allocate memory for stack_ in constructor. + // Do it lazily when first Push() -> Expand() -> Resize(). + Stack(Allocator* allocator, size_t stackCapacity) : allocator_(allocator), ownAllocator_(0), stack_(0), stackTop_(0), stackEnd_(0), initialCapacity_(stackCapacity) { + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + Stack(Stack&& rhs) + : allocator_(rhs.allocator_), + ownAllocator_(rhs.ownAllocator_), + stack_(rhs.stack_), + stackTop_(rhs.stackTop_), + stackEnd_(rhs.stackEnd_), + initialCapacity_(rhs.initialCapacity_) + { + rhs.allocator_ = 0; + rhs.ownAllocator_ = 0; + rhs.stack_ = 0; + rhs.stackTop_ = 0; + rhs.stackEnd_ = 0; + rhs.initialCapacity_ = 0; + } +#endif + + ~Stack() { + Destroy(); + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + Stack& operator=(Stack&& rhs) { + if (&rhs != this) + { + Destroy(); + + allocator_ = rhs.allocator_; + ownAllocator_ = rhs.ownAllocator_; + stack_ = rhs.stack_; + stackTop_ = rhs.stackTop_; + stackEnd_ = rhs.stackEnd_; + initialCapacity_ = rhs.initialCapacity_; + + rhs.allocator_ = 0; + rhs.ownAllocator_ = 0; + rhs.stack_ = 0; + rhs.stackTop_ = 0; + rhs.stackEnd_ = 0; + rhs.initialCapacity_ = 0; + } + return *this; + } +#endif + + void Swap(Stack& rhs) RAPIDJSON_NOEXCEPT { + internal::Swap(allocator_, rhs.allocator_); + internal::Swap(ownAllocator_, rhs.ownAllocator_); + internal::Swap(stack_, rhs.stack_); + internal::Swap(stackTop_, rhs.stackTop_); + internal::Swap(stackEnd_, rhs.stackEnd_); + internal::Swap(initialCapacity_, rhs.initialCapacity_); + } + + void Clear() { stackTop_ = stack_; } + + void ShrinkToFit() { + if (Empty()) { + // If the stack is empty, completely deallocate the memory. + Allocator::Free(stack_); + stack_ = 0; + stackTop_ = 0; + stackEnd_ = 0; + } + else + Resize(GetSize()); + } + + // Optimization note: try to minimize the size of this function for force inline. + // Expansion is run very infrequently, so it is moved to another (probably non-inline) function. + template + RAPIDJSON_FORCEINLINE void Reserve(size_t count = 1) { + // Expand the stack if needed + if (RAPIDJSON_UNLIKELY(stackTop_ + sizeof(T) * count > stackEnd_)) + Expand(count); + } + + template + RAPIDJSON_FORCEINLINE T* Push(size_t count = 1) { + Reserve(count); + return PushUnsafe(count); + } + + template + RAPIDJSON_FORCEINLINE T* PushUnsafe(size_t count = 1) { + RAPIDJSON_ASSERT(stackTop_ + sizeof(T) * count <= stackEnd_); + T* ret = reinterpret_cast(stackTop_); + stackTop_ += sizeof(T) * count; + return ret; + } + + template + T* Pop(size_t count) { + RAPIDJSON_ASSERT(GetSize() >= count * sizeof(T)); + stackTop_ -= count * sizeof(T); + return reinterpret_cast(stackTop_); + } + + template + T* Top() { + RAPIDJSON_ASSERT(GetSize() >= sizeof(T)); + return reinterpret_cast(stackTop_ - sizeof(T)); + } + + template + const T* Top() const { + RAPIDJSON_ASSERT(GetSize() >= sizeof(T)); + return reinterpret_cast(stackTop_ - sizeof(T)); + } + + template + T* End() { return reinterpret_cast(stackTop_); } + + template + const T* End() const { return reinterpret_cast(stackTop_); } + + template + T* Bottom() { return reinterpret_cast(stack_); } + + template + const T* Bottom() const { return reinterpret_cast(stack_); } + + bool HasAllocator() const { + return allocator_ != 0; + } + + Allocator& GetAllocator() { + RAPIDJSON_ASSERT(allocator_); + return *allocator_; + } + + bool Empty() const { return stackTop_ == stack_; } + size_t GetSize() const { return static_cast(stackTop_ - stack_); } + size_t GetCapacity() const { return static_cast(stackEnd_ - stack_); } + +private: + template + void Expand(size_t count) { + // Only expand the capacity if the current stack exists. Otherwise just create a stack with initial capacity. + size_t newCapacity; + if (stack_ == 0) { + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator()); + newCapacity = initialCapacity_; + } else { + newCapacity = GetCapacity(); + newCapacity += (newCapacity + 1) / 2; + } + size_t newSize = GetSize() + sizeof(T) * count; + if (newCapacity < newSize) + newCapacity = newSize; + + Resize(newCapacity); + } + + void Resize(size_t newCapacity) { + const size_t size = GetSize(); // Backup the current size + stack_ = static_cast(allocator_->Realloc(stack_, GetCapacity(), newCapacity)); + stackTop_ = stack_ + size; + stackEnd_ = stack_ + newCapacity; + } + + void Destroy() { + Allocator::Free(stack_); + RAPIDJSON_DELETE(ownAllocator_); // Only delete if it is owned by the stack + } + + // Prohibit copy constructor & assignment operator. + Stack(const Stack&); + Stack& operator=(const Stack&); + + Allocator* allocator_; + Allocator* ownAllocator_; + char *stack_; + char *stackTop_; + char *stackEnd_; + size_t initialCapacity_; +}; + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#if defined(__clang__) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_STACK_H_ diff --git a/Source/Dependencies/rapidjson/include/rapidjson/internal/strfunc.h b/Source/Dependencies/rapidjson/include/rapidjson/internal/strfunc.h new file mode 100644 index 000000000..2edfae526 --- /dev/null +++ b/Source/Dependencies/rapidjson/include/rapidjson/internal/strfunc.h @@ -0,0 +1,55 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_INTERNAL_STRFUNC_H_ +#define RAPIDJSON_INTERNAL_STRFUNC_H_ + +#include "../stream.h" + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +//! Custom strlen() which works on different character types. +/*! \tparam Ch Character type (e.g. char, wchar_t, short) + \param s Null-terminated input string. + \return Number of characters in the string. + \note This has the same semantics as strlen(), the return value is not number of Unicode codepoints. +*/ +template +inline SizeType StrLen(const Ch* s) { + const Ch* p = s; + while (*p) ++p; + return SizeType(p - s); +} + +//! Returns number of code points in a encoded string. +template +bool CountStringCodePoint(const typename Encoding::Ch* s, SizeType length, SizeType* outCount) { + GenericStringStream is(s); + const typename Encoding::Ch* end = s + length; + SizeType count = 0; + while (is.src_ < end) { + unsigned codepoint; + if (!Encoding::Decode(is, &codepoint)) + return false; + count++; + } + *outCount = count; + return true; +} + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_INTERNAL_STRFUNC_H_ diff --git a/Source/Dependencies/rapidjson/include/rapidjson/internal/strtod.h b/Source/Dependencies/rapidjson/include/rapidjson/internal/strtod.h new file mode 100644 index 000000000..289c413b0 --- /dev/null +++ b/Source/Dependencies/rapidjson/include/rapidjson/internal/strtod.h @@ -0,0 +1,269 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_STRTOD_ +#define RAPIDJSON_STRTOD_ + +#include "ieee754.h" +#include "biginteger.h" +#include "diyfp.h" +#include "pow10.h" + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +inline double FastPath(double significand, int exp) { + if (exp < -308) + return 0.0; + else if (exp >= 0) + return significand * internal::Pow10(exp); + else + return significand / internal::Pow10(-exp); +} + +inline double StrtodNormalPrecision(double d, int p) { + if (p < -308) { + // Prevent expSum < -308, making Pow10(p) = 0 + d = FastPath(d, -308); + d = FastPath(d, p + 308); + } + else + d = FastPath(d, p); + return d; +} + +template +inline T Min3(T a, T b, T c) { + T m = a; + if (m > b) m = b; + if (m > c) m = c; + return m; +} + +inline int CheckWithinHalfULP(double b, const BigInteger& d, int dExp) { + const Double db(b); + const uint64_t bInt = db.IntegerSignificand(); + const int bExp = db.IntegerExponent(); + const int hExp = bExp - 1; + + int dS_Exp2 = 0, dS_Exp5 = 0, bS_Exp2 = 0, bS_Exp5 = 0, hS_Exp2 = 0, hS_Exp5 = 0; + + // Adjust for decimal exponent + if (dExp >= 0) { + dS_Exp2 += dExp; + dS_Exp5 += dExp; + } + else { + bS_Exp2 -= dExp; + bS_Exp5 -= dExp; + hS_Exp2 -= dExp; + hS_Exp5 -= dExp; + } + + // Adjust for binary exponent + if (bExp >= 0) + bS_Exp2 += bExp; + else { + dS_Exp2 -= bExp; + hS_Exp2 -= bExp; + } + + // Adjust for half ulp exponent + if (hExp >= 0) + hS_Exp2 += hExp; + else { + dS_Exp2 -= hExp; + bS_Exp2 -= hExp; + } + + // Remove common power of two factor from all three scaled values + int common_Exp2 = Min3(dS_Exp2, bS_Exp2, hS_Exp2); + dS_Exp2 -= common_Exp2; + bS_Exp2 -= common_Exp2; + hS_Exp2 -= common_Exp2; + + BigInteger dS = d; + dS.MultiplyPow5(static_cast(dS_Exp5)) <<= static_cast(dS_Exp2); + + BigInteger bS(bInt); + bS.MultiplyPow5(static_cast(bS_Exp5)) <<= static_cast(bS_Exp2); + + BigInteger hS(1); + hS.MultiplyPow5(static_cast(hS_Exp5)) <<= static_cast(hS_Exp2); + + BigInteger delta(0); + dS.Difference(bS, &delta); + + return delta.Compare(hS); +} + +inline bool StrtodFast(double d, int p, double* result) { + // Use fast path for string-to-double conversion if possible + // see http://www.exploringbinary.com/fast-path-decimal-to-floating-point-conversion/ + if (p > 22 && p < 22 + 16) { + // Fast Path Cases In Disguise + d *= internal::Pow10(p - 22); + p = 22; + } + + if (p >= -22 && p <= 22 && d <= 9007199254740991.0) { // 2^53 - 1 + *result = FastPath(d, p); + return true; + } + else + return false; +} + +// Compute an approximation and see if it is within 1/2 ULP +inline bool StrtodDiyFp(const char* decimals, size_t length, size_t decimalPosition, int exp, double* result) { + uint64_t significand = 0; + size_t i = 0; // 2^64 - 1 = 18446744073709551615, 1844674407370955161 = 0x1999999999999999 + for (; i < length; i++) { + if (significand > RAPIDJSON_UINT64_C2(0x19999999, 0x99999999) || + (significand == RAPIDJSON_UINT64_C2(0x19999999, 0x99999999) && decimals[i] > '5')) + break; + significand = significand * 10u + static_cast(decimals[i] - '0'); + } + + if (i < length && decimals[i] >= '5') // Rounding + significand++; + + size_t remaining = length - i; + const unsigned kUlpShift = 3; + const unsigned kUlp = 1 << kUlpShift; + int64_t error = (remaining == 0) ? 0 : kUlp / 2; + + DiyFp v(significand, 0); + v = v.Normalize(); + error <<= -v.e; + + const int dExp = static_cast(decimalPosition) - static_cast(i) + exp; + + int actualExp; + DiyFp cachedPower = GetCachedPower10(dExp, &actualExp); + if (actualExp != dExp) { + static const DiyFp kPow10[] = { + DiyFp(RAPIDJSON_UINT64_C2(0xa0000000, 00000000), -60), // 10^1 + DiyFp(RAPIDJSON_UINT64_C2(0xc8000000, 00000000), -57), // 10^2 + DiyFp(RAPIDJSON_UINT64_C2(0xfa000000, 00000000), -54), // 10^3 + DiyFp(RAPIDJSON_UINT64_C2(0x9c400000, 00000000), -50), // 10^4 + DiyFp(RAPIDJSON_UINT64_C2(0xc3500000, 00000000), -47), // 10^5 + DiyFp(RAPIDJSON_UINT64_C2(0xf4240000, 00000000), -44), // 10^6 + DiyFp(RAPIDJSON_UINT64_C2(0x98968000, 00000000), -40) // 10^7 + }; + int adjustment = dExp - actualExp - 1; + RAPIDJSON_ASSERT(adjustment >= 0 && adjustment < 7); + v = v * kPow10[adjustment]; + if (length + static_cast(adjustment)> 19u) // has more digits than decimal digits in 64-bit + error += kUlp / 2; + } + + v = v * cachedPower; + + error += kUlp + (error == 0 ? 0 : 1); + + const int oldExp = v.e; + v = v.Normalize(); + error <<= oldExp - v.e; + + const unsigned effectiveSignificandSize = Double::EffectiveSignificandSize(64 + v.e); + unsigned precisionSize = 64 - effectiveSignificandSize; + if (precisionSize + kUlpShift >= 64) { + unsigned scaleExp = (precisionSize + kUlpShift) - 63; + v.f >>= scaleExp; + v.e += scaleExp; + error = (error >> scaleExp) + 1 + static_cast(kUlp); + precisionSize -= scaleExp; + } + + DiyFp rounded(v.f >> precisionSize, v.e + static_cast(precisionSize)); + const uint64_t precisionBits = (v.f & ((uint64_t(1) << precisionSize) - 1)) * kUlp; + const uint64_t halfWay = (uint64_t(1) << (precisionSize - 1)) * kUlp; + if (precisionBits >= halfWay + static_cast(error)) { + rounded.f++; + if (rounded.f & (DiyFp::kDpHiddenBit << 1)) { // rounding overflows mantissa (issue #340) + rounded.f >>= 1; + rounded.e++; + } + } + + *result = rounded.ToDouble(); + + return halfWay - static_cast(error) >= precisionBits || precisionBits >= halfWay + static_cast(error); +} + +inline double StrtodBigInteger(double approx, const char* decimals, size_t length, size_t decimalPosition, int exp) { + const BigInteger dInt(decimals, length); + const int dExp = static_cast(decimalPosition) - static_cast(length) + exp; + Double a(approx); + int cmp = CheckWithinHalfULP(a.Value(), dInt, dExp); + if (cmp < 0) + return a.Value(); // within half ULP + else if (cmp == 0) { + // Round towards even + if (a.Significand() & 1) + return a.NextPositiveDouble(); + else + return a.Value(); + } + else // adjustment + return a.NextPositiveDouble(); +} + +inline double StrtodFullPrecision(double d, int p, const char* decimals, size_t length, size_t decimalPosition, int exp) { + RAPIDJSON_ASSERT(d >= 0.0); + RAPIDJSON_ASSERT(length >= 1); + + double result; + if (StrtodFast(d, p, &result)) + return result; + + // Trim leading zeros + while (*decimals == '0' && length > 1) { + length--; + decimals++; + decimalPosition--; + } + + // Trim trailing zeros + while (decimals[length - 1] == '0' && length > 1) { + length--; + decimalPosition--; + exp++; + } + + // Trim right-most digits + const int kMaxDecimalDigit = 780; + if (static_cast(length) > kMaxDecimalDigit) { + int delta = (static_cast(length) - kMaxDecimalDigit); + exp += delta; + decimalPosition -= static_cast(delta); + length = kMaxDecimalDigit; + } + + // If too small, underflow to zero + if (int(length) + exp < -324) + return 0.0; + + if (StrtodDiyFp(decimals, length, decimalPosition, exp, &result)) + return result; + + // Use approximation from StrtodDiyFp and make adjustment with BigInteger comparison + return StrtodBigInteger(result, decimals, length, decimalPosition, exp); +} + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_STRTOD_ diff --git a/Source/Dependencies/rapidjson/include/rapidjson/internal/swap.h b/Source/Dependencies/rapidjson/include/rapidjson/internal/swap.h new file mode 100644 index 000000000..666e49f97 --- /dev/null +++ b/Source/Dependencies/rapidjson/include/rapidjson/internal/swap.h @@ -0,0 +1,46 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_INTERNAL_SWAP_H_ +#define RAPIDJSON_INTERNAL_SWAP_H_ + +#include "../rapidjson.h" + +#if defined(__clang__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(c++98-compat) +#endif + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +//! Custom swap() to avoid dependency on C++ header +/*! \tparam T Type of the arguments to swap, should be instantiated with primitive C++ types only. + \note This has the same semantics as std::swap(). +*/ +template +inline void Swap(T& a, T& b) RAPIDJSON_NOEXCEPT { + T tmp = a; + a = b; + b = tmp; +} + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#if defined(__clang__) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_INTERNAL_SWAP_H_ diff --git a/Source/Dependencies/rapidjson/include/rapidjson/istreamwrapper.h b/Source/Dependencies/rapidjson/include/rapidjson/istreamwrapper.h new file mode 100644 index 000000000..f5fe28977 --- /dev/null +++ b/Source/Dependencies/rapidjson/include/rapidjson/istreamwrapper.h @@ -0,0 +1,115 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ISTREAMWRAPPER_H_ +#define RAPIDJSON_ISTREAMWRAPPER_H_ + +#include "stream.h" +#include + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +#endif + +#ifdef _MSC_VER +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4351) // new behavior: elements of array 'array' will be default initialized +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Wrapper of \c std::basic_istream into RapidJSON's Stream concept. +/*! + The classes can be wrapped including but not limited to: + + - \c std::istringstream + - \c std::stringstream + - \c std::wistringstream + - \c std::wstringstream + - \c std::ifstream + - \c std::fstream + - \c std::wifstream + - \c std::wfstream + + \tparam StreamType Class derived from \c std::basic_istream. +*/ + +template +class BasicIStreamWrapper { +public: + typedef typename StreamType::char_type Ch; + BasicIStreamWrapper(StreamType& stream) : stream_(stream), count_(), peekBuffer_() {} + + Ch Peek() const { + typename StreamType::int_type c = stream_.peek(); + return RAPIDJSON_LIKELY(c != StreamType::traits_type::eof()) ? static_cast(c) : '\0'; + } + + Ch Take() { + typename StreamType::int_type c = stream_.get(); + if (RAPIDJSON_LIKELY(c != StreamType::traits_type::eof())) { + count_++; + return static_cast(c); + } + else + return '\0'; + } + + // tellg() may return -1 when failed. So we count by ourself. + size_t Tell() const { return count_; } + + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + + // For encoding detection only. + const Ch* Peek4() const { + RAPIDJSON_ASSERT(sizeof(Ch) == 1); // Only usable for byte stream. + int i; + bool hasError = false; + for (i = 0; i < 4; ++i) { + typename StreamType::int_type c = stream_.get(); + if (c == StreamType::traits_type::eof()) { + hasError = true; + stream_.clear(); + break; + } + peekBuffer_[i] = static_cast(c); + } + for (--i; i >= 0; --i) + stream_.putback(peekBuffer_[i]); + return !hasError ? peekBuffer_ : 0; + } + +private: + BasicIStreamWrapper(const BasicIStreamWrapper&); + BasicIStreamWrapper& operator=(const BasicIStreamWrapper&); + + StreamType& stream_; + size_t count_; //!< Number of characters read. Note: + mutable Ch peekBuffer_[4]; +}; + +typedef BasicIStreamWrapper IStreamWrapper; +typedef BasicIStreamWrapper WIStreamWrapper; + +#if defined(__clang__) || defined(_MSC_VER) +RAPIDJSON_DIAG_POP +#endif + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_ISTREAMWRAPPER_H_ diff --git a/Source/Dependencies/rapidjson/include/rapidjson/memorybuffer.h b/Source/Dependencies/rapidjson/include/rapidjson/memorybuffer.h new file mode 100644 index 000000000..39bee1dec --- /dev/null +++ b/Source/Dependencies/rapidjson/include/rapidjson/memorybuffer.h @@ -0,0 +1,70 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_MEMORYBUFFER_H_ +#define RAPIDJSON_MEMORYBUFFER_H_ + +#include "stream.h" +#include "internal/stack.h" + +RAPIDJSON_NAMESPACE_BEGIN + +//! Represents an in-memory output byte stream. +/*! + This class is mainly for being wrapped by EncodedOutputStream or AutoUTFOutputStream. + + It is similar to FileWriteBuffer but the destination is an in-memory buffer instead of a file. + + Differences between MemoryBuffer and StringBuffer: + 1. StringBuffer has Encoding but MemoryBuffer is only a byte buffer. + 2. StringBuffer::GetString() returns a null-terminated string. MemoryBuffer::GetBuffer() returns a buffer without terminator. + + \tparam Allocator type for allocating memory buffer. + \note implements Stream concept +*/ +template +struct GenericMemoryBuffer { + typedef char Ch; // byte + + GenericMemoryBuffer(Allocator* allocator = 0, size_t capacity = kDefaultCapacity) : stack_(allocator, capacity) {} + + void Put(Ch c) { *stack_.template Push() = c; } + void Flush() {} + + void Clear() { stack_.Clear(); } + void ShrinkToFit() { stack_.ShrinkToFit(); } + Ch* Push(size_t count) { return stack_.template Push(count); } + void Pop(size_t count) { stack_.template Pop(count); } + + const Ch* GetBuffer() const { + return stack_.template Bottom(); + } + + size_t GetSize() const { return stack_.GetSize(); } + + static const size_t kDefaultCapacity = 256; + mutable internal::Stack stack_; +}; + +typedef GenericMemoryBuffer<> MemoryBuffer; + +//! Implement specialized version of PutN() with memset() for better performance. +template<> +inline void PutN(MemoryBuffer& memoryBuffer, char c, size_t n) { + std::memset(memoryBuffer.stack_.Push(n), c, n * sizeof(c)); +} + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_MEMORYBUFFER_H_ diff --git a/Source/Dependencies/rapidjson/include/rapidjson/memorystream.h b/Source/Dependencies/rapidjson/include/rapidjson/memorystream.h new file mode 100644 index 000000000..1d71d8a4f --- /dev/null +++ b/Source/Dependencies/rapidjson/include/rapidjson/memorystream.h @@ -0,0 +1,71 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_MEMORYSTREAM_H_ +#define RAPIDJSON_MEMORYSTREAM_H_ + +#include "stream.h" + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(unreachable-code) +RAPIDJSON_DIAG_OFF(missing-noreturn) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Represents an in-memory input byte stream. +/*! + This class is mainly for being wrapped by EncodedInputStream or AutoUTFInputStream. + + It is similar to FileReadBuffer but the source is an in-memory buffer instead of a file. + + Differences between MemoryStream and StringStream: + 1. StringStream has encoding but MemoryStream is a byte stream. + 2. MemoryStream needs size of the source buffer and the buffer don't need to be null terminated. StringStream assume null-terminated string as source. + 3. MemoryStream supports Peek4() for encoding detection. StringStream is specified with an encoding so it should not have Peek4(). + \note implements Stream concept +*/ +struct MemoryStream { + typedef char Ch; // byte + + MemoryStream(const Ch *src, size_t size) : src_(src), begin_(src), end_(src + size), size_(size) {} + + Ch Peek() const { return RAPIDJSON_UNLIKELY(src_ == end_) ? '\0' : *src_; } + Ch Take() { return RAPIDJSON_UNLIKELY(src_ == end_) ? '\0' : *src_++; } + size_t Tell() const { return static_cast(src_ - begin_); } + + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + + // For encoding detection only. + const Ch* Peek4() const { + return Tell() + 4 <= size_ ? src_ : 0; + } + + const Ch* src_; //!< Current read position. + const Ch* begin_; //!< Original head of the string. + const Ch* end_; //!< End of stream. + size_t size_; //!< Size of the stream. +}; + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_MEMORYBUFFER_H_ diff --git a/Source/Dependencies/rapidjson/include/rapidjson/msinttypes/inttypes.h b/Source/Dependencies/rapidjson/include/rapidjson/msinttypes/inttypes.h new file mode 100644 index 000000000..18111286b --- /dev/null +++ b/Source/Dependencies/rapidjson/include/rapidjson/msinttypes/inttypes.h @@ -0,0 +1,316 @@ +// ISO C9x compliant inttypes.h for Microsoft Visual Studio +// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 +// +// Copyright (c) 2006-2013 Alexander Chemeris +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the product nor the names of its contributors may +// be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +/////////////////////////////////////////////////////////////////////////////// + +// The above software in this distribution may have been modified by +// THL A29 Limited ("Tencent Modifications"). +// All Tencent Modifications are Copyright (C) 2015 THL A29 Limited. + +#ifndef _MSC_VER // [ +#error "Use this header only with Microsoft Visual C++ compilers!" +#endif // _MSC_VER ] + +#ifndef _MSC_INTTYPES_H_ // [ +#define _MSC_INTTYPES_H_ + +#if _MSC_VER > 1000 +#pragma once +#endif + +#include "stdint.h" + +// miloyip: VC supports inttypes.h since VC2013 +#if _MSC_VER >= 1800 +#include +#else + +// 7.8 Format conversion of integer types + +typedef struct { + intmax_t quot; + intmax_t rem; +} imaxdiv_t; + +// 7.8.1 Macros for format specifiers + +#if !defined(__cplusplus) || defined(__STDC_FORMAT_MACROS) // [ See footnote 185 at page 198 + +// The fprintf macros for signed integers are: +#define PRId8 "d" +#define PRIi8 "i" +#define PRIdLEAST8 "d" +#define PRIiLEAST8 "i" +#define PRIdFAST8 "d" +#define PRIiFAST8 "i" + +#define PRId16 "hd" +#define PRIi16 "hi" +#define PRIdLEAST16 "hd" +#define PRIiLEAST16 "hi" +#define PRIdFAST16 "hd" +#define PRIiFAST16 "hi" + +#define PRId32 "I32d" +#define PRIi32 "I32i" +#define PRIdLEAST32 "I32d" +#define PRIiLEAST32 "I32i" +#define PRIdFAST32 "I32d" +#define PRIiFAST32 "I32i" + +#define PRId64 "I64d" +#define PRIi64 "I64i" +#define PRIdLEAST64 "I64d" +#define PRIiLEAST64 "I64i" +#define PRIdFAST64 "I64d" +#define PRIiFAST64 "I64i" + +#define PRIdMAX "I64d" +#define PRIiMAX "I64i" + +#define PRIdPTR "Id" +#define PRIiPTR "Ii" + +// The fprintf macros for unsigned integers are: +#define PRIo8 "o" +#define PRIu8 "u" +#define PRIx8 "x" +#define PRIX8 "X" +#define PRIoLEAST8 "o" +#define PRIuLEAST8 "u" +#define PRIxLEAST8 "x" +#define PRIXLEAST8 "X" +#define PRIoFAST8 "o" +#define PRIuFAST8 "u" +#define PRIxFAST8 "x" +#define PRIXFAST8 "X" + +#define PRIo16 "ho" +#define PRIu16 "hu" +#define PRIx16 "hx" +#define PRIX16 "hX" +#define PRIoLEAST16 "ho" +#define PRIuLEAST16 "hu" +#define PRIxLEAST16 "hx" +#define PRIXLEAST16 "hX" +#define PRIoFAST16 "ho" +#define PRIuFAST16 "hu" +#define PRIxFAST16 "hx" +#define PRIXFAST16 "hX" + +#define PRIo32 "I32o" +#define PRIu32 "I32u" +#define PRIx32 "I32x" +#define PRIX32 "I32X" +#define PRIoLEAST32 "I32o" +#define PRIuLEAST32 "I32u" +#define PRIxLEAST32 "I32x" +#define PRIXLEAST32 "I32X" +#define PRIoFAST32 "I32o" +#define PRIuFAST32 "I32u" +#define PRIxFAST32 "I32x" +#define PRIXFAST32 "I32X" + +#define PRIo64 "I64o" +#define PRIu64 "I64u" +#define PRIx64 "I64x" +#define PRIX64 "I64X" +#define PRIoLEAST64 "I64o" +#define PRIuLEAST64 "I64u" +#define PRIxLEAST64 "I64x" +#define PRIXLEAST64 "I64X" +#define PRIoFAST64 "I64o" +#define PRIuFAST64 "I64u" +#define PRIxFAST64 "I64x" +#define PRIXFAST64 "I64X" + +#define PRIoMAX "I64o" +#define PRIuMAX "I64u" +#define PRIxMAX "I64x" +#define PRIXMAX "I64X" + +#define PRIoPTR "Io" +#define PRIuPTR "Iu" +#define PRIxPTR "Ix" +#define PRIXPTR "IX" + +// The fscanf macros for signed integers are: +#define SCNd8 "d" +#define SCNi8 "i" +#define SCNdLEAST8 "d" +#define SCNiLEAST8 "i" +#define SCNdFAST8 "d" +#define SCNiFAST8 "i" + +#define SCNd16 "hd" +#define SCNi16 "hi" +#define SCNdLEAST16 "hd" +#define SCNiLEAST16 "hi" +#define SCNdFAST16 "hd" +#define SCNiFAST16 "hi" + +#define SCNd32 "ld" +#define SCNi32 "li" +#define SCNdLEAST32 "ld" +#define SCNiLEAST32 "li" +#define SCNdFAST32 "ld" +#define SCNiFAST32 "li" + +#define SCNd64 "I64d" +#define SCNi64 "I64i" +#define SCNdLEAST64 "I64d" +#define SCNiLEAST64 "I64i" +#define SCNdFAST64 "I64d" +#define SCNiFAST64 "I64i" + +#define SCNdMAX "I64d" +#define SCNiMAX "I64i" + +#ifdef _WIN64 // [ +# define SCNdPTR "I64d" +# define SCNiPTR "I64i" +#else // _WIN64 ][ +# define SCNdPTR "ld" +# define SCNiPTR "li" +#endif // _WIN64 ] + +// The fscanf macros for unsigned integers are: +#define SCNo8 "o" +#define SCNu8 "u" +#define SCNx8 "x" +#define SCNX8 "X" +#define SCNoLEAST8 "o" +#define SCNuLEAST8 "u" +#define SCNxLEAST8 "x" +#define SCNXLEAST8 "X" +#define SCNoFAST8 "o" +#define SCNuFAST8 "u" +#define SCNxFAST8 "x" +#define SCNXFAST8 "X" + +#define SCNo16 "ho" +#define SCNu16 "hu" +#define SCNx16 "hx" +#define SCNX16 "hX" +#define SCNoLEAST16 "ho" +#define SCNuLEAST16 "hu" +#define SCNxLEAST16 "hx" +#define SCNXLEAST16 "hX" +#define SCNoFAST16 "ho" +#define SCNuFAST16 "hu" +#define SCNxFAST16 "hx" +#define SCNXFAST16 "hX" + +#define SCNo32 "lo" +#define SCNu32 "lu" +#define SCNx32 "lx" +#define SCNX32 "lX" +#define SCNoLEAST32 "lo" +#define SCNuLEAST32 "lu" +#define SCNxLEAST32 "lx" +#define SCNXLEAST32 "lX" +#define SCNoFAST32 "lo" +#define SCNuFAST32 "lu" +#define SCNxFAST32 "lx" +#define SCNXFAST32 "lX" + +#define SCNo64 "I64o" +#define SCNu64 "I64u" +#define SCNx64 "I64x" +#define SCNX64 "I64X" +#define SCNoLEAST64 "I64o" +#define SCNuLEAST64 "I64u" +#define SCNxLEAST64 "I64x" +#define SCNXLEAST64 "I64X" +#define SCNoFAST64 "I64o" +#define SCNuFAST64 "I64u" +#define SCNxFAST64 "I64x" +#define SCNXFAST64 "I64X" + +#define SCNoMAX "I64o" +#define SCNuMAX "I64u" +#define SCNxMAX "I64x" +#define SCNXMAX "I64X" + +#ifdef _WIN64 // [ +# define SCNoPTR "I64o" +# define SCNuPTR "I64u" +# define SCNxPTR "I64x" +# define SCNXPTR "I64X" +#else // _WIN64 ][ +# define SCNoPTR "lo" +# define SCNuPTR "lu" +# define SCNxPTR "lx" +# define SCNXPTR "lX" +#endif // _WIN64 ] + +#endif // __STDC_FORMAT_MACROS ] + +// 7.8.2 Functions for greatest-width integer types + +// 7.8.2.1 The imaxabs function +#define imaxabs _abs64 + +// 7.8.2.2 The imaxdiv function + +// This is modified version of div() function from Microsoft's div.c found +// in %MSVC.NET%\crt\src\div.c +#ifdef STATIC_IMAXDIV // [ +static +#else // STATIC_IMAXDIV ][ +_inline +#endif // STATIC_IMAXDIV ] +imaxdiv_t __cdecl imaxdiv(intmax_t numer, intmax_t denom) +{ + imaxdiv_t result; + + result.quot = numer / denom; + result.rem = numer % denom; + + if (numer < 0 && result.rem > 0) { + // did division wrong; must fix up + ++result.quot; + result.rem -= denom; + } + + return result; +} + +// 7.8.2.3 The strtoimax and strtoumax functions +#define strtoimax _strtoi64 +#define strtoumax _strtoui64 + +// 7.8.2.4 The wcstoimax and wcstoumax functions +#define wcstoimax _wcstoi64 +#define wcstoumax _wcstoui64 + +#endif // _MSC_VER >= 1800 + +#endif // _MSC_INTTYPES_H_ ] diff --git a/Source/Dependencies/rapidjson/include/rapidjson/msinttypes/stdint.h b/Source/Dependencies/rapidjson/include/rapidjson/msinttypes/stdint.h new file mode 100644 index 000000000..3d4477b9a --- /dev/null +++ b/Source/Dependencies/rapidjson/include/rapidjson/msinttypes/stdint.h @@ -0,0 +1,300 @@ +// ISO C9x compliant stdint.h for Microsoft Visual Studio +// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 +// +// Copyright (c) 2006-2013 Alexander Chemeris +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the product nor the names of its contributors may +// be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +/////////////////////////////////////////////////////////////////////////////// + +// The above software in this distribution may have been modified by +// THL A29 Limited ("Tencent Modifications"). +// All Tencent Modifications are Copyright (C) 2015 THL A29 Limited. + +#ifndef _MSC_VER // [ +#error "Use this header only with Microsoft Visual C++ compilers!" +#endif // _MSC_VER ] + +#ifndef _MSC_STDINT_H_ // [ +#define _MSC_STDINT_H_ + +#if _MSC_VER > 1000 +#pragma once +#endif + +// miloyip: Originally Visual Studio 2010 uses its own stdint.h. However it generates warning with INT64_C(), so change to use this file for vs2010. +#if _MSC_VER >= 1600 // [ +#include + +#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260 + +#undef INT8_C +#undef INT16_C +#undef INT32_C +#undef INT64_C +#undef UINT8_C +#undef UINT16_C +#undef UINT32_C +#undef UINT64_C + +// 7.18.4.1 Macros for minimum-width integer constants + +#define INT8_C(val) val##i8 +#define INT16_C(val) val##i16 +#define INT32_C(val) val##i32 +#define INT64_C(val) val##i64 + +#define UINT8_C(val) val##ui8 +#define UINT16_C(val) val##ui16 +#define UINT32_C(val) val##ui32 +#define UINT64_C(val) val##ui64 + +// 7.18.4.2 Macros for greatest-width integer constants +// These #ifndef's are needed to prevent collisions with . +// Check out Issue 9 for the details. +#ifndef INTMAX_C // [ +# define INTMAX_C INT64_C +#endif // INTMAX_C ] +#ifndef UINTMAX_C // [ +# define UINTMAX_C UINT64_C +#endif // UINTMAX_C ] + +#endif // __STDC_CONSTANT_MACROS ] + +#else // ] _MSC_VER >= 1700 [ + +#include + +// For Visual Studio 6 in C++ mode and for many Visual Studio versions when +// compiling for ARM we have to wrap include with 'extern "C++" {}' +// or compiler would give many errors like this: +// error C2733: second C linkage of overloaded function 'wmemchr' not allowed +#if defined(__cplusplus) && !defined(_M_ARM) +extern "C" { +#endif +# include +#if defined(__cplusplus) && !defined(_M_ARM) +} +#endif + +// Define _W64 macros to mark types changing their size, like intptr_t. +#ifndef _W64 +# if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300 +# define _W64 __w64 +# else +# define _W64 +# endif +#endif + + +// 7.18.1 Integer types + +// 7.18.1.1 Exact-width integer types + +// Visual Studio 6 and Embedded Visual C++ 4 doesn't +// realize that, e.g. char has the same size as __int8 +// so we give up on __intX for them. +#if (_MSC_VER < 1300) + typedef signed char int8_t; + typedef signed short int16_t; + typedef signed int int32_t; + typedef unsigned char uint8_t; + typedef unsigned short uint16_t; + typedef unsigned int uint32_t; +#else + typedef signed __int8 int8_t; + typedef signed __int16 int16_t; + typedef signed __int32 int32_t; + typedef unsigned __int8 uint8_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int32 uint32_t; +#endif +typedef signed __int64 int64_t; +typedef unsigned __int64 uint64_t; + + +// 7.18.1.2 Minimum-width integer types +typedef int8_t int_least8_t; +typedef int16_t int_least16_t; +typedef int32_t int_least32_t; +typedef int64_t int_least64_t; +typedef uint8_t uint_least8_t; +typedef uint16_t uint_least16_t; +typedef uint32_t uint_least32_t; +typedef uint64_t uint_least64_t; + +// 7.18.1.3 Fastest minimum-width integer types +typedef int8_t int_fast8_t; +typedef int16_t int_fast16_t; +typedef int32_t int_fast32_t; +typedef int64_t int_fast64_t; +typedef uint8_t uint_fast8_t; +typedef uint16_t uint_fast16_t; +typedef uint32_t uint_fast32_t; +typedef uint64_t uint_fast64_t; + +// 7.18.1.4 Integer types capable of holding object pointers +#ifdef _WIN64 // [ + typedef signed __int64 intptr_t; + typedef unsigned __int64 uintptr_t; +#else // _WIN64 ][ + typedef _W64 signed int intptr_t; + typedef _W64 unsigned int uintptr_t; +#endif // _WIN64 ] + +// 7.18.1.5 Greatest-width integer types +typedef int64_t intmax_t; +typedef uint64_t uintmax_t; + + +// 7.18.2 Limits of specified-width integer types + +#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [ See footnote 220 at page 257 and footnote 221 at page 259 + +// 7.18.2.1 Limits of exact-width integer types +#define INT8_MIN ((int8_t)_I8_MIN) +#define INT8_MAX _I8_MAX +#define INT16_MIN ((int16_t)_I16_MIN) +#define INT16_MAX _I16_MAX +#define INT32_MIN ((int32_t)_I32_MIN) +#define INT32_MAX _I32_MAX +#define INT64_MIN ((int64_t)_I64_MIN) +#define INT64_MAX _I64_MAX +#define UINT8_MAX _UI8_MAX +#define UINT16_MAX _UI16_MAX +#define UINT32_MAX _UI32_MAX +#define UINT64_MAX _UI64_MAX + +// 7.18.2.2 Limits of minimum-width integer types +#define INT_LEAST8_MIN INT8_MIN +#define INT_LEAST8_MAX INT8_MAX +#define INT_LEAST16_MIN INT16_MIN +#define INT_LEAST16_MAX INT16_MAX +#define INT_LEAST32_MIN INT32_MIN +#define INT_LEAST32_MAX INT32_MAX +#define INT_LEAST64_MIN INT64_MIN +#define INT_LEAST64_MAX INT64_MAX +#define UINT_LEAST8_MAX UINT8_MAX +#define UINT_LEAST16_MAX UINT16_MAX +#define UINT_LEAST32_MAX UINT32_MAX +#define UINT_LEAST64_MAX UINT64_MAX + +// 7.18.2.3 Limits of fastest minimum-width integer types +#define INT_FAST8_MIN INT8_MIN +#define INT_FAST8_MAX INT8_MAX +#define INT_FAST16_MIN INT16_MIN +#define INT_FAST16_MAX INT16_MAX +#define INT_FAST32_MIN INT32_MIN +#define INT_FAST32_MAX INT32_MAX +#define INT_FAST64_MIN INT64_MIN +#define INT_FAST64_MAX INT64_MAX +#define UINT_FAST8_MAX UINT8_MAX +#define UINT_FAST16_MAX UINT16_MAX +#define UINT_FAST32_MAX UINT32_MAX +#define UINT_FAST64_MAX UINT64_MAX + +// 7.18.2.4 Limits of integer types capable of holding object pointers +#ifdef _WIN64 // [ +# define INTPTR_MIN INT64_MIN +# define INTPTR_MAX INT64_MAX +# define UINTPTR_MAX UINT64_MAX +#else // _WIN64 ][ +# define INTPTR_MIN INT32_MIN +# define INTPTR_MAX INT32_MAX +# define UINTPTR_MAX UINT32_MAX +#endif // _WIN64 ] + +// 7.18.2.5 Limits of greatest-width integer types +#define INTMAX_MIN INT64_MIN +#define INTMAX_MAX INT64_MAX +#define UINTMAX_MAX UINT64_MAX + +// 7.18.3 Limits of other integer types + +#ifdef _WIN64 // [ +# define PTRDIFF_MIN _I64_MIN +# define PTRDIFF_MAX _I64_MAX +#else // _WIN64 ][ +# define PTRDIFF_MIN _I32_MIN +# define PTRDIFF_MAX _I32_MAX +#endif // _WIN64 ] + +#define SIG_ATOMIC_MIN INT_MIN +#define SIG_ATOMIC_MAX INT_MAX + +#ifndef SIZE_MAX // [ +# ifdef _WIN64 // [ +# define SIZE_MAX _UI64_MAX +# else // _WIN64 ][ +# define SIZE_MAX _UI32_MAX +# endif // _WIN64 ] +#endif // SIZE_MAX ] + +// WCHAR_MIN and WCHAR_MAX are also defined in +#ifndef WCHAR_MIN // [ +# define WCHAR_MIN 0 +#endif // WCHAR_MIN ] +#ifndef WCHAR_MAX // [ +# define WCHAR_MAX _UI16_MAX +#endif // WCHAR_MAX ] + +#define WINT_MIN 0 +#define WINT_MAX _UI16_MAX + +#endif // __STDC_LIMIT_MACROS ] + + +// 7.18.4 Limits of other integer types + +#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260 + +// 7.18.4.1 Macros for minimum-width integer constants + +#define INT8_C(val) val##i8 +#define INT16_C(val) val##i16 +#define INT32_C(val) val##i32 +#define INT64_C(val) val##i64 + +#define UINT8_C(val) val##ui8 +#define UINT16_C(val) val##ui16 +#define UINT32_C(val) val##ui32 +#define UINT64_C(val) val##ui64 + +// 7.18.4.2 Macros for greatest-width integer constants +// These #ifndef's are needed to prevent collisions with . +// Check out Issue 9 for the details. +#ifndef INTMAX_C // [ +# define INTMAX_C INT64_C +#endif // INTMAX_C ] +#ifndef UINTMAX_C // [ +# define UINTMAX_C UINT64_C +#endif // UINTMAX_C ] + +#endif // __STDC_CONSTANT_MACROS ] + +#endif // _MSC_VER >= 1600 ] + +#endif // _MSC_STDINT_H_ ] diff --git a/Source/Dependencies/rapidjson/include/rapidjson/ostreamwrapper.h b/Source/Dependencies/rapidjson/include/rapidjson/ostreamwrapper.h new file mode 100644 index 000000000..6f4667c08 --- /dev/null +++ b/Source/Dependencies/rapidjson/include/rapidjson/ostreamwrapper.h @@ -0,0 +1,81 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_OSTREAMWRAPPER_H_ +#define RAPIDJSON_OSTREAMWRAPPER_H_ + +#include "stream.h" +#include + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Wrapper of \c std::basic_ostream into RapidJSON's Stream concept. +/*! + The classes can be wrapped including but not limited to: + + - \c std::ostringstream + - \c std::stringstream + - \c std::wpstringstream + - \c std::wstringstream + - \c std::ifstream + - \c std::fstream + - \c std::wofstream + - \c std::wfstream + + \tparam StreamType Class derived from \c std::basic_ostream. +*/ + +template +class BasicOStreamWrapper { +public: + typedef typename StreamType::char_type Ch; + BasicOStreamWrapper(StreamType& stream) : stream_(stream) {} + + void Put(Ch c) { + stream_.put(c); + } + + void Flush() { + stream_.flush(); + } + + // Not implemented + char Peek() const { RAPIDJSON_ASSERT(false); return 0; } + char Take() { RAPIDJSON_ASSERT(false); return 0; } + size_t Tell() const { RAPIDJSON_ASSERT(false); return 0; } + char* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(char*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + BasicOStreamWrapper(const BasicOStreamWrapper&); + BasicOStreamWrapper& operator=(const BasicOStreamWrapper&); + + StreamType& stream_; +}; + +typedef BasicOStreamWrapper OStreamWrapper; +typedef BasicOStreamWrapper WOStreamWrapper; + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_OSTREAMWRAPPER_H_ diff --git a/Source/Dependencies/rapidjson/include/rapidjson/pointer.h b/Source/Dependencies/rapidjson/include/rapidjson/pointer.h new file mode 100644 index 000000000..0206ac1c8 --- /dev/null +++ b/Source/Dependencies/rapidjson/include/rapidjson/pointer.h @@ -0,0 +1,1358 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_POINTER_H_ +#define RAPIDJSON_POINTER_H_ + +#include "document.h" +#include "internal/itoa.h" + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(switch-enum) +#endif + +#ifdef _MSC_VER +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4512) // assignment operator could not be generated +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +static const SizeType kPointerInvalidIndex = ~SizeType(0); //!< Represents an invalid index in GenericPointer::Token + +//! Error code of parsing. +/*! \ingroup RAPIDJSON_ERRORS + \see GenericPointer::GenericPointer, GenericPointer::GetParseErrorCode +*/ +enum PointerParseErrorCode { + kPointerParseErrorNone = 0, //!< The parse is successful + + kPointerParseErrorTokenMustBeginWithSolidus, //!< A token must begin with a '/' + kPointerParseErrorInvalidEscape, //!< Invalid escape + kPointerParseErrorInvalidPercentEncoding, //!< Invalid percent encoding in URI fragment + kPointerParseErrorCharacterMustPercentEncode //!< A character must percent encoded in URI fragment +}; + +/////////////////////////////////////////////////////////////////////////////// +// GenericPointer + +//! Represents a JSON Pointer. Use Pointer for UTF8 encoding and default allocator. +/*! + This class implements RFC 6901 "JavaScript Object Notation (JSON) Pointer" + (https://tools.ietf.org/html/rfc6901). + + A JSON pointer is for identifying a specific value in a JSON document + (GenericDocument). It can simplify coding of DOM tree manipulation, because it + can access multiple-level depth of DOM tree with single API call. + + After it parses a string representation (e.g. "/foo/0" or URI fragment + representation (e.g. "#/foo/0") into its internal representation (tokens), + it can be used to resolve a specific value in multiple documents, or sub-tree + of documents. + + Contrary to GenericValue, Pointer can be copy constructed and copy assigned. + Apart from assignment, a Pointer cannot be modified after construction. + + Although Pointer is very convenient, please aware that constructing Pointer + involves parsing and dynamic memory allocation. A special constructor with user- + supplied tokens eliminates these. + + GenericPointer depends on GenericDocument and GenericValue. + + \tparam ValueType The value type of the DOM tree. E.g. GenericValue > + \tparam Allocator The allocator type for allocating memory for internal representation. + + \note GenericPointer uses same encoding of ValueType. + However, Allocator of GenericPointer is independent of Allocator of Value. +*/ +template +class GenericPointer { +public: + typedef typename ValueType::EncodingType EncodingType; //!< Encoding type from Value + typedef typename ValueType::Ch Ch; //!< Character type from Value + + //! A token is the basic units of internal representation. + /*! + A JSON pointer string representation "/foo/123" is parsed to two tokens: + "foo" and 123. 123 will be represented in both numeric form and string form. + They are resolved according to the actual value type (object or array). + + For token that are not numbers, or the numeric value is out of bound + (greater than limits of SizeType), they are only treated as string form + (i.e. the token's index will be equal to kPointerInvalidIndex). + + This struct is public so that user can create a Pointer without parsing and + allocation, using a special constructor. + */ + struct Token { + const Ch* name; //!< Name of the token. It has null character at the end but it can contain null character. + SizeType length; //!< Length of the name. + SizeType index; //!< A valid array index, if it is not equal to kPointerInvalidIndex. + }; + + //!@name Constructors and destructor. + //@{ + + //! Default constructor. + GenericPointer(Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) {} + + //! Constructor that parses a string or URI fragment representation. + /*! + \param source A null-terminated, string or URI fragment representation of JSON pointer. + \param allocator User supplied allocator for this pointer. If no allocator is provided, it creates a self-owned one. + */ + explicit GenericPointer(const Ch* source, Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { + Parse(source, internal::StrLen(source)); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Constructor that parses a string or URI fragment representation. + /*! + \param source A string or URI fragment representation of JSON pointer. + \param allocator User supplied allocator for this pointer. If no allocator is provided, it creates a self-owned one. + \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. + */ + explicit GenericPointer(const std::basic_string& source, Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { + Parse(source.c_str(), source.size()); + } +#endif + + //! Constructor that parses a string or URI fragment representation, with length of the source string. + /*! + \param source A string or URI fragment representation of JSON pointer. + \param length Length of source. + \param allocator User supplied allocator for this pointer. If no allocator is provided, it creates a self-owned one. + \note Slightly faster than the overload without length. + */ + GenericPointer(const Ch* source, size_t length, Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { + Parse(source, length); + } + + //! Constructor with user-supplied tokens. + /*! + This constructor let user supplies const array of tokens. + This prevents the parsing process and eliminates allocation. + This is preferred for memory constrained environments. + + \param tokens An constant array of tokens representing the JSON pointer. + \param tokenCount Number of tokens. + + \b Example + \code + #define NAME(s) { s, sizeof(s) / sizeof(s[0]) - 1, kPointerInvalidIndex } + #define INDEX(i) { #i, sizeof(#i) - 1, i } + + static const Pointer::Token kTokens[] = { NAME("foo"), INDEX(123) }; + static const Pointer p(kTokens, sizeof(kTokens) / sizeof(kTokens[0])); + // Equivalent to static const Pointer p("/foo/123"); + + #undef NAME + #undef INDEX + \endcode + */ + GenericPointer(const Token* tokens, size_t tokenCount) : allocator_(), ownAllocator_(), nameBuffer_(), tokens_(const_cast(tokens)), tokenCount_(tokenCount), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) {} + + //! Copy constructor. + GenericPointer(const GenericPointer& rhs, Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { + *this = rhs; + } + + //! Destructor. + ~GenericPointer() { + if (nameBuffer_) // If user-supplied tokens constructor is used, nameBuffer_ is nullptr and tokens_ are not deallocated. + Allocator::Free(tokens_); + RAPIDJSON_DELETE(ownAllocator_); + } + + //! Assignment operator. + GenericPointer& operator=(const GenericPointer& rhs) { + if (this != &rhs) { + // Do not delete ownAllcator + if (nameBuffer_) + Allocator::Free(tokens_); + + tokenCount_ = rhs.tokenCount_; + parseErrorOffset_ = rhs.parseErrorOffset_; + parseErrorCode_ = rhs.parseErrorCode_; + + if (rhs.nameBuffer_) + CopyFromRaw(rhs); // Normally parsed tokens. + else { + tokens_ = rhs.tokens_; // User supplied const tokens. + nameBuffer_ = 0; + } + } + return *this; + } + + //@} + + //!@name Append token + //@{ + + //! Append a token and return a new Pointer + /*! + \param token Token to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(const Token& token, Allocator* allocator = 0) const { + GenericPointer r; + r.allocator_ = allocator; + Ch *p = r.CopyFromRaw(*this, 1, token.length + 1); + std::memcpy(p, token.name, (token.length + 1) * sizeof(Ch)); + r.tokens_[tokenCount_].name = p; + r.tokens_[tokenCount_].length = token.length; + r.tokens_[tokenCount_].index = token.index; + return r; + } + + //! Append a name token with length, and return a new Pointer + /*! + \param name Name to be appended. + \param length Length of name. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(const Ch* name, SizeType length, Allocator* allocator = 0) const { + Token token = { name, length, kPointerInvalidIndex }; + return Append(token, allocator); + } + + //! Append a name token without length, and return a new Pointer + /*! + \param name Name (const Ch*) to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::NotExpr::Type, Ch> >), (GenericPointer)) + Append(T* name, Allocator* allocator = 0) const { + return Append(name, StrLen(name), allocator); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Append a name token, and return a new Pointer + /*! + \param name Name to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(const std::basic_string& name, Allocator* allocator = 0) const { + return Append(name.c_str(), static_cast(name.size()), allocator); + } +#endif + + //! Append a index token, and return a new Pointer + /*! + \param index Index to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(SizeType index, Allocator* allocator = 0) const { + char buffer[21]; + char* end = sizeof(SizeType) == 4 ? internal::u32toa(index, buffer) : internal::u64toa(index, buffer); + SizeType length = static_cast(end - buffer); + buffer[length] = '\0'; + + if (sizeof(Ch) == 1) { + Token token = { reinterpret_cast(buffer), length, index }; + return Append(token, allocator); + } + else { + Ch name[21]; + for (size_t i = 0; i <= length; i++) + name[i] = buffer[i]; + Token token = { name, length, index }; + return Append(token, allocator); + } + } + + //! Append a token by value, and return a new Pointer + /*! + \param token token to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(const ValueType& token, Allocator* allocator = 0) const { + if (token.IsString()) + return Append(token.GetString(), token.GetStringLength(), allocator); + else { + RAPIDJSON_ASSERT(token.IsUint64()); + RAPIDJSON_ASSERT(token.GetUint64() <= SizeType(~0)); + return Append(static_cast(token.GetUint64()), allocator); + } + } + + //!@name Handling Parse Error + //@{ + + //! Check whether this is a valid pointer. + bool IsValid() const { return parseErrorCode_ == kPointerParseErrorNone; } + + //! Get the parsing error offset in code unit. + size_t GetParseErrorOffset() const { return parseErrorOffset_; } + + //! Get the parsing error code. + PointerParseErrorCode GetParseErrorCode() const { return parseErrorCode_; } + + //@} + + //! Get the allocator of this pointer. + Allocator& GetAllocator() { return *allocator_; } + + //!@name Tokens + //@{ + + //! Get the token array (const version only). + const Token* GetTokens() const { return tokens_; } + + //! Get the number of tokens. + size_t GetTokenCount() const { return tokenCount_; } + + //@} + + //!@name Equality/inequality operators + //@{ + + //! Equality operator. + /*! + \note When any pointers are invalid, always returns false. + */ + bool operator==(const GenericPointer& rhs) const { + if (!IsValid() || !rhs.IsValid() || tokenCount_ != rhs.tokenCount_) + return false; + + for (size_t i = 0; i < tokenCount_; i++) { + if (tokens_[i].index != rhs.tokens_[i].index || + tokens_[i].length != rhs.tokens_[i].length || + (tokens_[i].length != 0 && std::memcmp(tokens_[i].name, rhs.tokens_[i].name, sizeof(Ch)* tokens_[i].length) != 0)) + { + return false; + } + } + + return true; + } + + //! Inequality operator. + /*! + \note When any pointers are invalid, always returns true. + */ + bool operator!=(const GenericPointer& rhs) const { return !(*this == rhs); } + + //@} + + //!@name Stringify + //@{ + + //! Stringify the pointer into string representation. + /*! + \tparam OutputStream Type of output stream. + \param os The output stream. + */ + template + bool Stringify(OutputStream& os) const { + return Stringify(os); + } + + //! Stringify the pointer into URI fragment representation. + /*! + \tparam OutputStream Type of output stream. + \param os The output stream. + */ + template + bool StringifyUriFragment(OutputStream& os) const { + return Stringify(os); + } + + //@} + + //!@name Create value + //@{ + + //! Create a value in a subtree. + /*! + If the value is not exist, it creates all parent values and a JSON Null value. + So it always succeed and return the newly created or existing value. + + Remind that it may change types of parents according to tokens, so it + potentially removes previously stored values. For example, if a document + was an array, and "/foo" is used to create a value, then the document + will be changed to an object, and all existing array elements are lost. + + \param root Root value of a DOM subtree to be resolved. It can be any value other than document root. + \param allocator Allocator for creating the values if the specified value or its parents are not exist. + \param alreadyExist If non-null, it stores whether the resolved value is already exist. + \return The resolved newly created (a JSON Null value), or already exists value. + */ + ValueType& Create(ValueType& root, typename ValueType::AllocatorType& allocator, bool* alreadyExist = 0) const { + RAPIDJSON_ASSERT(IsValid()); + ValueType* v = &root; + bool exist = true; + for (const Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { + if (v->IsArray() && t->name[0] == '-' && t->length == 1) { + v->PushBack(ValueType().Move(), allocator); + v = &((*v)[v->Size() - 1]); + exist = false; + } + else { + if (t->index == kPointerInvalidIndex) { // must be object name + if (!v->IsObject()) + v->SetObject(); // Change to Object + } + else { // object name or array index + if (!v->IsArray() && !v->IsObject()) + v->SetArray(); // Change to Array + } + + if (v->IsArray()) { + if (t->index >= v->Size()) { + v->Reserve(t->index + 1, allocator); + while (t->index >= v->Size()) + v->PushBack(ValueType().Move(), allocator); + exist = false; + } + v = &((*v)[t->index]); + } + else { + typename ValueType::MemberIterator m = v->FindMember(GenericStringRef(t->name, t->length)); + if (m == v->MemberEnd()) { + v->AddMember(ValueType(t->name, t->length, allocator).Move(), ValueType().Move(), allocator); + v = &(--v->MemberEnd())->value; // Assumes AddMember() appends at the end + exist = false; + } + else + v = &m->value; + } + } + } + + if (alreadyExist) + *alreadyExist = exist; + + return *v; + } + + //! Creates a value in a document. + /*! + \param document A document to be resolved. + \param alreadyExist If non-null, it stores whether the resolved value is already exist. + \return The resolved newly created, or already exists value. + */ + template + ValueType& Create(GenericDocument& document, bool* alreadyExist = 0) const { + return Create(document, document.GetAllocator(), alreadyExist); + } + + //@} + + //!@name Query value + //@{ + + //! Query a value in a subtree. + /*! + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \param unresolvedTokenIndex If the pointer cannot resolve a token in the pointer, this parameter can obtain the index of unresolved token. + \return Pointer to the value if it can be resolved. Otherwise null. + + \note + There are only 3 situations when a value cannot be resolved: + 1. A value in the path is not an array nor object. + 2. An object value does not contain the token. + 3. A token is out of range of an array value. + + Use unresolvedTokenIndex to retrieve the token index. + */ + ValueType* Get(ValueType& root, size_t* unresolvedTokenIndex = 0) const { + RAPIDJSON_ASSERT(IsValid()); + ValueType* v = &root; + for (const Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { + switch (v->GetType()) { + case kObjectType: + { + typename ValueType::MemberIterator m = v->FindMember(GenericStringRef(t->name, t->length)); + if (m == v->MemberEnd()) + break; + v = &m->value; + } + continue; + case kArrayType: + if (t->index == kPointerInvalidIndex || t->index >= v->Size()) + break; + v = &((*v)[t->index]); + continue; + default: + break; + } + + // Error: unresolved token + if (unresolvedTokenIndex) + *unresolvedTokenIndex = static_cast(t - tokens_); + return 0; + } + return v; + } + + //! Query a const value in a const subtree. + /*! + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \return Pointer to the value if it can be resolved. Otherwise null. + */ + const ValueType* Get(const ValueType& root, size_t* unresolvedTokenIndex = 0) const { + return Get(const_cast(root), unresolvedTokenIndex); + } + + //@} + + //!@name Query a value with default + //@{ + + //! Query a value in a subtree with default value. + /*! + Similar to Get(), but if the specified value do not exists, it creates all parents and clone the default value. + So that this function always succeed. + + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \param defaultValue Default value to be cloned if the value was not exists. + \param allocator Allocator for creating the values if the specified value or its parents are not exist. + \see Create() + */ + ValueType& GetWithDefault(ValueType& root, const ValueType& defaultValue, typename ValueType::AllocatorType& allocator) const { + bool alreadyExist; + Value& v = Create(root, allocator, &alreadyExist); + return alreadyExist ? v : v.CopyFrom(defaultValue, allocator); + } + + //! Query a value in a subtree with default null-terminated string. + ValueType& GetWithDefault(ValueType& root, const Ch* defaultValue, typename ValueType::AllocatorType& allocator) const { + bool alreadyExist; + Value& v = Create(root, allocator, &alreadyExist); + return alreadyExist ? v : v.SetString(defaultValue, allocator); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Query a value in a subtree with default std::basic_string. + ValueType& GetWithDefault(ValueType& root, const std::basic_string& defaultValue, typename ValueType::AllocatorType& allocator) const { + bool alreadyExist; + Value& v = Create(root, allocator, &alreadyExist); + return alreadyExist ? v : v.SetString(defaultValue, allocator); + } +#endif + + //! Query a value in a subtree with default primitive value. + /*! + \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c bool + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) + GetWithDefault(ValueType& root, T defaultValue, typename ValueType::AllocatorType& allocator) const { + return GetWithDefault(root, ValueType(defaultValue).Move(), allocator); + } + + //! Query a value in a document with default value. + template + ValueType& GetWithDefault(GenericDocument& document, const ValueType& defaultValue) const { + return GetWithDefault(document, defaultValue, document.GetAllocator()); + } + + //! Query a value in a document with default null-terminated string. + template + ValueType& GetWithDefault(GenericDocument& document, const Ch* defaultValue) const { + return GetWithDefault(document, defaultValue, document.GetAllocator()); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Query a value in a document with default std::basic_string. + template + ValueType& GetWithDefault(GenericDocument& document, const std::basic_string& defaultValue) const { + return GetWithDefault(document, defaultValue, document.GetAllocator()); + } +#endif + + //! Query a value in a document with default primitive value. + /*! + \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c bool + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) + GetWithDefault(GenericDocument& document, T defaultValue) const { + return GetWithDefault(document, defaultValue, document.GetAllocator()); + } + + //@} + + //!@name Set a value + //@{ + + //! Set a value in a subtree, with move semantics. + /*! + It creates all parents if they are not exist or types are different to the tokens. + So this function always succeeds but potentially remove existing values. + + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \param value Value to be set. + \param allocator Allocator for creating the values if the specified value or its parents are not exist. + \see Create() + */ + ValueType& Set(ValueType& root, ValueType& value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator) = value; + } + + //! Set a value in a subtree, with copy semantics. + ValueType& Set(ValueType& root, const ValueType& value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator).CopyFrom(value, allocator); + } + + //! Set a null-terminated string in a subtree. + ValueType& Set(ValueType& root, const Ch* value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator) = ValueType(value, allocator).Move(); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Set a std::basic_string in a subtree. + ValueType& Set(ValueType& root, const std::basic_string& value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator) = ValueType(value, allocator).Move(); + } +#endif + + //! Set a primitive value in a subtree. + /*! + \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c bool + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) + Set(ValueType& root, T value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator) = ValueType(value).Move(); + } + + //! Set a value in a document, with move semantics. + template + ValueType& Set(GenericDocument& document, ValueType& value) const { + return Create(document) = value; + } + + //! Set a value in a document, with copy semantics. + template + ValueType& Set(GenericDocument& document, const ValueType& value) const { + return Create(document).CopyFrom(value, document.GetAllocator()); + } + + //! Set a null-terminated string in a document. + template + ValueType& Set(GenericDocument& document, const Ch* value) const { + return Create(document) = ValueType(value, document.GetAllocator()).Move(); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Sets a std::basic_string in a document. + template + ValueType& Set(GenericDocument& document, const std::basic_string& value) const { + return Create(document) = ValueType(value, document.GetAllocator()).Move(); + } +#endif + + //! Set a primitive value in a document. + /*! + \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c bool + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) + Set(GenericDocument& document, T value) const { + return Create(document) = value; + } + + //@} + + //!@name Swap a value + //@{ + + //! Swap a value with a value in a subtree. + /*! + It creates all parents if they are not exist or types are different to the tokens. + So this function always succeeds but potentially remove existing values. + + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \param value Value to be swapped. + \param allocator Allocator for creating the values if the specified value or its parents are not exist. + \see Create() + */ + ValueType& Swap(ValueType& root, ValueType& value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator).Swap(value); + } + + //! Swap a value with a value in a document. + template + ValueType& Swap(GenericDocument& document, ValueType& value) const { + return Create(document).Swap(value); + } + + //@} + + //! Erase a value in a subtree. + /*! + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \return Whether the resolved value is found and erased. + + \note Erasing with an empty pointer \c Pointer(""), i.e. the root, always fail and return false. + */ + bool Erase(ValueType& root) const { + RAPIDJSON_ASSERT(IsValid()); + if (tokenCount_ == 0) // Cannot erase the root + return false; + + ValueType* v = &root; + const Token* last = tokens_ + (tokenCount_ - 1); + for (const Token *t = tokens_; t != last; ++t) { + switch (v->GetType()) { + case kObjectType: + { + typename ValueType::MemberIterator m = v->FindMember(GenericStringRef(t->name, t->length)); + if (m == v->MemberEnd()) + return false; + v = &m->value; + } + break; + case kArrayType: + if (t->index == kPointerInvalidIndex || t->index >= v->Size()) + return false; + v = &((*v)[t->index]); + break; + default: + return false; + } + } + + switch (v->GetType()) { + case kObjectType: + return v->EraseMember(GenericStringRef(last->name, last->length)); + case kArrayType: + if (last->index == kPointerInvalidIndex || last->index >= v->Size()) + return false; + v->Erase(v->Begin() + last->index); + return true; + default: + return false; + } + } + +private: + //! Clone the content from rhs to this. + /*! + \param rhs Source pointer. + \param extraToken Extra tokens to be allocated. + \param extraNameBufferSize Extra name buffer size (in number of Ch) to be allocated. + \return Start of non-occupied name buffer, for storing extra names. + */ + Ch* CopyFromRaw(const GenericPointer& rhs, size_t extraToken = 0, size_t extraNameBufferSize = 0) { + if (!allocator_) // allocator is independently owned. + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator()); + + size_t nameBufferSize = rhs.tokenCount_; // null terminators for tokens + for (Token *t = rhs.tokens_; t != rhs.tokens_ + rhs.tokenCount_; ++t) + nameBufferSize += t->length; + + tokenCount_ = rhs.tokenCount_ + extraToken; + tokens_ = static_cast(allocator_->Malloc(tokenCount_ * sizeof(Token) + (nameBufferSize + extraNameBufferSize) * sizeof(Ch))); + nameBuffer_ = reinterpret_cast(tokens_ + tokenCount_); + if (rhs.tokenCount_ > 0) { + std::memcpy(tokens_, rhs.tokens_, rhs.tokenCount_ * sizeof(Token)); + } + if (nameBufferSize > 0) { + std::memcpy(nameBuffer_, rhs.nameBuffer_, nameBufferSize * sizeof(Ch)); + } + + // Adjust pointers to name buffer + std::ptrdiff_t diff = nameBuffer_ - rhs.nameBuffer_; + for (Token *t = tokens_; t != tokens_ + rhs.tokenCount_; ++t) + t->name += diff; + + return nameBuffer_ + nameBufferSize; + } + + //! Check whether a character should be percent-encoded. + /*! + According to RFC 3986 2.3 Unreserved Characters. + \param c The character (code unit) to be tested. + */ + bool NeedPercentEncode(Ch c) const { + return !((c >= '0' && c <= '9') || (c >= 'A' && c <='Z') || (c >= 'a' && c <= 'z') || c == '-' || c == '.' || c == '_' || c =='~'); + } + + //! Parse a JSON String or its URI fragment representation into tokens. +#ifndef __clang__ // -Wdocumentation + /*! + \param source Either a JSON Pointer string, or its URI fragment representation. Not need to be null terminated. + \param length Length of the source string. + \note Source cannot be JSON String Representation of JSON Pointer, e.g. In "/\u0000", \u0000 will not be unescaped. + */ +#endif + void Parse(const Ch* source, size_t length) { + RAPIDJSON_ASSERT(source != NULL); + RAPIDJSON_ASSERT(nameBuffer_ == 0); + RAPIDJSON_ASSERT(tokens_ == 0); + + // Create own allocator if user did not supply. + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator()); + + // Count number of '/' as tokenCount + tokenCount_ = 0; + for (const Ch* s = source; s != source + length; s++) + if (*s == '/') + tokenCount_++; + + Token* token = tokens_ = static_cast(allocator_->Malloc(tokenCount_ * sizeof(Token) + length * sizeof(Ch))); + Ch* name = nameBuffer_ = reinterpret_cast(tokens_ + tokenCount_); + size_t i = 0; + + // Detect if it is a URI fragment + bool uriFragment = false; + if (source[i] == '#') { + uriFragment = true; + i++; + } + + if (i != length && source[i] != '/') { + parseErrorCode_ = kPointerParseErrorTokenMustBeginWithSolidus; + goto error; + } + + while (i < length) { + RAPIDJSON_ASSERT(source[i] == '/'); + i++; // consumes '/' + + token->name = name; + bool isNumber = true; + + while (i < length && source[i] != '/') { + Ch c = source[i]; + if (uriFragment) { + // Decoding percent-encoding for URI fragment + if (c == '%') { + PercentDecodeStream is(&source[i], source + length); + GenericInsituStringStream os(name); + Ch* begin = os.PutBegin(); + if (!Transcoder, EncodingType>().Validate(is, os) || !is.IsValid()) { + parseErrorCode_ = kPointerParseErrorInvalidPercentEncoding; + goto error; + } + size_t len = os.PutEnd(begin); + i += is.Tell() - 1; + if (len == 1) + c = *name; + else { + name += len; + isNumber = false; + i++; + continue; + } + } + else if (NeedPercentEncode(c)) { + parseErrorCode_ = kPointerParseErrorCharacterMustPercentEncode; + goto error; + } + } + + i++; + + // Escaping "~0" -> '~', "~1" -> '/' + if (c == '~') { + if (i < length) { + c = source[i]; + if (c == '0') c = '~'; + else if (c == '1') c = '/'; + else { + parseErrorCode_ = kPointerParseErrorInvalidEscape; + goto error; + } + i++; + } + else { + parseErrorCode_ = kPointerParseErrorInvalidEscape; + goto error; + } + } + + // First check for index: all of characters are digit + if (c < '0' || c > '9') + isNumber = false; + + *name++ = c; + } + token->length = static_cast(name - token->name); + if (token->length == 0) + isNumber = false; + *name++ = '\0'; // Null terminator + + // Second check for index: more than one digit cannot have leading zero + if (isNumber && token->length > 1 && token->name[0] == '0') + isNumber = false; + + // String to SizeType conversion + SizeType n = 0; + if (isNumber) { + for (size_t j = 0; j < token->length; j++) { + SizeType m = n * 10 + static_cast(token->name[j] - '0'); + if (m < n) { // overflow detection + isNumber = false; + break; + } + n = m; + } + } + + token->index = isNumber ? n : kPointerInvalidIndex; + token++; + } + + RAPIDJSON_ASSERT(name <= nameBuffer_ + length); // Should not overflow buffer + parseErrorCode_ = kPointerParseErrorNone; + return; + + error: + Allocator::Free(tokens_); + nameBuffer_ = 0; + tokens_ = 0; + tokenCount_ = 0; + parseErrorOffset_ = i; + return; + } + + //! Stringify to string or URI fragment representation. + /*! + \tparam uriFragment True for stringifying to URI fragment representation. False for string representation. + \tparam OutputStream type of output stream. + \param os The output stream. + */ + template + bool Stringify(OutputStream& os) const { + RAPIDJSON_ASSERT(IsValid()); + + if (uriFragment) + os.Put('#'); + + for (Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { + os.Put('/'); + for (size_t j = 0; j < t->length; j++) { + Ch c = t->name[j]; + if (c == '~') { + os.Put('~'); + os.Put('0'); + } + else if (c == '/') { + os.Put('~'); + os.Put('1'); + } + else if (uriFragment && NeedPercentEncode(c)) { + // Transcode to UTF8 sequence + GenericStringStream source(&t->name[j]); + PercentEncodeStream target(os); + if (!Transcoder >().Validate(source, target)) + return false; + j += source.Tell() - 1; + } + else + os.Put(c); + } + } + return true; + } + + //! A helper stream for decoding a percent-encoded sequence into code unit. + /*! + This stream decodes %XY triplet into code unit (0-255). + If it encounters invalid characters, it sets output code unit as 0 and + mark invalid, and to be checked by IsValid(). + */ + class PercentDecodeStream { + public: + typedef typename ValueType::Ch Ch; + + //! Constructor + /*! + \param source Start of the stream + \param end Past-the-end of the stream. + */ + PercentDecodeStream(const Ch* source, const Ch* end) : src_(source), head_(source), end_(end), valid_(true) {} + + Ch Take() { + if (*src_ != '%' || src_ + 3 > end_) { // %XY triplet + valid_ = false; + return 0; + } + src_++; + Ch c = 0; + for (int j = 0; j < 2; j++) { + c = static_cast(c << 4); + Ch h = *src_; + if (h >= '0' && h <= '9') c = static_cast(c + h - '0'); + else if (h >= 'A' && h <= 'F') c = static_cast(c + h - 'A' + 10); + else if (h >= 'a' && h <= 'f') c = static_cast(c + h - 'a' + 10); + else { + valid_ = false; + return 0; + } + src_++; + } + return c; + } + + size_t Tell() const { return static_cast(src_ - head_); } + bool IsValid() const { return valid_; } + + private: + const Ch* src_; //!< Current read position. + const Ch* head_; //!< Original head of the string. + const Ch* end_; //!< Past-the-end position. + bool valid_; //!< Whether the parsing is valid. + }; + + //! A helper stream to encode character (UTF-8 code unit) into percent-encoded sequence. + template + class PercentEncodeStream { + public: + PercentEncodeStream(OutputStream& os) : os_(os) {} + void Put(char c) { // UTF-8 must be byte + unsigned char u = static_cast(c); + static const char hexDigits[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + os_.Put('%'); + os_.Put(hexDigits[u >> 4]); + os_.Put(hexDigits[u & 15]); + } + private: + OutputStream& os_; + }; + + Allocator* allocator_; //!< The current allocator. It is either user-supplied or equal to ownAllocator_. + Allocator* ownAllocator_; //!< Allocator owned by this Pointer. + Ch* nameBuffer_; //!< A buffer containing all names in tokens. + Token* tokens_; //!< A list of tokens. + size_t tokenCount_; //!< Number of tokens in tokens_. + size_t parseErrorOffset_; //!< Offset in code unit when parsing fail. + PointerParseErrorCode parseErrorCode_; //!< Parsing error code. +}; + +//! GenericPointer for Value (UTF-8, default allocator). +typedef GenericPointer Pointer; + +//!@name Helper functions for GenericPointer +//@{ + +////////////////////////////////////////////////////////////////////////////// + +template +typename T::ValueType& CreateValueByPointer(T& root, const GenericPointer& pointer, typename T::AllocatorType& a) { + return pointer.Create(root, a); +} + +template +typename T::ValueType& CreateValueByPointer(T& root, const CharType(&source)[N], typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Create(root, a); +} + +// No allocator parameter + +template +typename DocumentType::ValueType& CreateValueByPointer(DocumentType& document, const GenericPointer& pointer) { + return pointer.Create(document); +} + +template +typename DocumentType::ValueType& CreateValueByPointer(DocumentType& document, const CharType(&source)[N]) { + return GenericPointer(source, N - 1).Create(document); +} + +////////////////////////////////////////////////////////////////////////////// + +template +typename T::ValueType* GetValueByPointer(T& root, const GenericPointer& pointer, size_t* unresolvedTokenIndex = 0) { + return pointer.Get(root, unresolvedTokenIndex); +} + +template +const typename T::ValueType* GetValueByPointer(const T& root, const GenericPointer& pointer, size_t* unresolvedTokenIndex = 0) { + return pointer.Get(root, unresolvedTokenIndex); +} + +template +typename T::ValueType* GetValueByPointer(T& root, const CharType (&source)[N], size_t* unresolvedTokenIndex = 0) { + return GenericPointer(source, N - 1).Get(root, unresolvedTokenIndex); +} + +template +const typename T::ValueType* GetValueByPointer(const T& root, const CharType(&source)[N], size_t* unresolvedTokenIndex = 0) { + return GenericPointer(source, N - 1).Get(root, unresolvedTokenIndex); +} + +////////////////////////////////////////////////////////////////////////////// + +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, const typename T::ValueType& defaultValue, typename T::AllocatorType& a) { + return pointer.GetWithDefault(root, defaultValue, a); +} + +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, const typename T::Ch* defaultValue, typename T::AllocatorType& a) { + return pointer.GetWithDefault(root, defaultValue, a); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, const std::basic_string& defaultValue, typename T::AllocatorType& a) { + return pointer.GetWithDefault(root, defaultValue, a); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) +GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, T2 defaultValue, typename T::AllocatorType& a) { + return pointer.GetWithDefault(root, defaultValue, a); +} + +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const typename T::ValueType& defaultValue, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue, a); +} + +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const typename T::Ch* defaultValue, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue, a); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const std::basic_string& defaultValue, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue, a); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) +GetValueByPointerWithDefault(T& root, const CharType(&source)[N], T2 defaultValue, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue, a); +} + +// No allocator parameter + +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const GenericPointer& pointer, const typename DocumentType::ValueType& defaultValue) { + return pointer.GetWithDefault(document, defaultValue); +} + +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const GenericPointer& pointer, const typename DocumentType::Ch* defaultValue) { + return pointer.GetWithDefault(document, defaultValue); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const GenericPointer& pointer, const std::basic_string& defaultValue) { + return pointer.GetWithDefault(document, defaultValue); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename DocumentType::ValueType&)) +GetValueByPointerWithDefault(DocumentType& document, const GenericPointer& pointer, T2 defaultValue) { + return pointer.GetWithDefault(document, defaultValue); +} + +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const CharType(&source)[N], const typename DocumentType::ValueType& defaultValue) { + return GenericPointer(source, N - 1).GetWithDefault(document, defaultValue); +} + +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const CharType(&source)[N], const typename DocumentType::Ch* defaultValue) { + return GenericPointer(source, N - 1).GetWithDefault(document, defaultValue); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const CharType(&source)[N], const std::basic_string& defaultValue) { + return GenericPointer(source, N - 1).GetWithDefault(document, defaultValue); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename DocumentType::ValueType&)) +GetValueByPointerWithDefault(DocumentType& document, const CharType(&source)[N], T2 defaultValue) { + return GenericPointer(source, N - 1).GetWithDefault(document, defaultValue); +} + +////////////////////////////////////////////////////////////////////////////// + +template +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, typename T::ValueType& value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, const typename T::ValueType& value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, const typename T::Ch* value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, const std::basic_string& value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) +SetValueByPointer(T& root, const GenericPointer& pointer, T2 value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], typename T::ValueType& value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Set(root, value, a); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const typename T::ValueType& value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Set(root, value, a); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const typename T::Ch* value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Set(root, value, a); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const std::basic_string& value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Set(root, value, a); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) +SetValueByPointer(T& root, const CharType(&source)[N], T2 value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Set(root, value, a); +} + +// No allocator parameter + +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const GenericPointer& pointer, typename DocumentType::ValueType& value) { + return pointer.Set(document, value); +} + +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const GenericPointer& pointer, const typename DocumentType::ValueType& value) { + return pointer.Set(document, value); +} + +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const GenericPointer& pointer, const typename DocumentType::Ch* value) { + return pointer.Set(document, value); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const GenericPointer& pointer, const std::basic_string& value) { + return pointer.Set(document, value); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename DocumentType::ValueType&)) +SetValueByPointer(DocumentType& document, const GenericPointer& pointer, T2 value) { + return pointer.Set(document, value); +} + +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const CharType(&source)[N], typename DocumentType::ValueType& value) { + return GenericPointer(source, N - 1).Set(document, value); +} + +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const CharType(&source)[N], const typename DocumentType::ValueType& value) { + return GenericPointer(source, N - 1).Set(document, value); +} + +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const CharType(&source)[N], const typename DocumentType::Ch* value) { + return GenericPointer(source, N - 1).Set(document, value); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const CharType(&source)[N], const std::basic_string& value) { + return GenericPointer(source, N - 1).Set(document, value); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename DocumentType::ValueType&)) +SetValueByPointer(DocumentType& document, const CharType(&source)[N], T2 value) { + return GenericPointer(source, N - 1).Set(document, value); +} + +////////////////////////////////////////////////////////////////////////////// + +template +typename T::ValueType& SwapValueByPointer(T& root, const GenericPointer& pointer, typename T::ValueType& value, typename T::AllocatorType& a) { + return pointer.Swap(root, value, a); +} + +template +typename T::ValueType& SwapValueByPointer(T& root, const CharType(&source)[N], typename T::ValueType& value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Swap(root, value, a); +} + +template +typename DocumentType::ValueType& SwapValueByPointer(DocumentType& document, const GenericPointer& pointer, typename DocumentType::ValueType& value) { + return pointer.Swap(document, value); +} + +template +typename DocumentType::ValueType& SwapValueByPointer(DocumentType& document, const CharType(&source)[N], typename DocumentType::ValueType& value) { + return GenericPointer(source, N - 1).Swap(document, value); +} + +////////////////////////////////////////////////////////////////////////////// + +template +bool EraseValueByPointer(T& root, const GenericPointer& pointer) { + return pointer.Erase(root); +} + +template +bool EraseValueByPointer(T& root, const CharType(&source)[N]) { + return GenericPointer(source, N - 1).Erase(root); +} + +//@} + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#ifdef _MSC_VER +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_POINTER_H_ diff --git a/Source/Dependencies/rapidjson/include/rapidjson/prettywriter.h b/Source/Dependencies/rapidjson/include/rapidjson/prettywriter.h new file mode 100644 index 000000000..0dcb0fee9 --- /dev/null +++ b/Source/Dependencies/rapidjson/include/rapidjson/prettywriter.h @@ -0,0 +1,255 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_PRETTYWRITER_H_ +#define RAPIDJSON_PRETTYWRITER_H_ + +#include "writer.h" + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Combination of PrettyWriter format flags. +/*! \see PrettyWriter::SetFormatOptions + */ +enum PrettyFormatOptions { + kFormatDefault = 0, //!< Default pretty formatting. + kFormatSingleLineArray = 1 //!< Format arrays on a single line. +}; + +//! Writer with indentation and spacing. +/*! + \tparam OutputStream Type of ouptut os. + \tparam SourceEncoding Encoding of source string. + \tparam TargetEncoding Encoding of output stream. + \tparam StackAllocator Type of allocator for allocating memory of stack. +*/ +template, typename TargetEncoding = UTF8<>, typename StackAllocator = CrtAllocator, unsigned writeFlags = kWriteDefaultFlags> +class PrettyWriter : public Writer { +public: + typedef Writer Base; + typedef typename Base::Ch Ch; + + //! Constructor + /*! \param os Output stream. + \param allocator User supplied allocator. If it is null, it will create a private one. + \param levelDepth Initial capacity of stack. + */ + explicit PrettyWriter(OutputStream& os, StackAllocator* allocator = 0, size_t levelDepth = Base::kDefaultLevelDepth) : + Base(os, allocator, levelDepth), indentChar_(' '), indentCharCount_(4), formatOptions_(kFormatDefault) {} + + + explicit PrettyWriter(StackAllocator* allocator = 0, size_t levelDepth = Base::kDefaultLevelDepth) : + Base(allocator, levelDepth), indentChar_(' '), indentCharCount_(4) {} + + //! Set custom indentation. + /*! \param indentChar Character for indentation. Must be whitespace character (' ', '\\t', '\\n', '\\r'). + \param indentCharCount Number of indent characters for each indentation level. + \note The default indentation is 4 spaces. + */ + PrettyWriter& SetIndent(Ch indentChar, unsigned indentCharCount) { + RAPIDJSON_ASSERT(indentChar == ' ' || indentChar == '\t' || indentChar == '\n' || indentChar == '\r'); + indentChar_ = indentChar; + indentCharCount_ = indentCharCount; + return *this; + } + + //! Set pretty writer formatting options. + /*! \param options Formatting options. + */ + PrettyWriter& SetFormatOptions(PrettyFormatOptions options) { + formatOptions_ = options; + return *this; + } + + /*! @name Implementation of Handler + \see Handler + */ + //@{ + + bool Null() { PrettyPrefix(kNullType); return Base::WriteNull(); } + bool Bool(bool b) { PrettyPrefix(b ? kTrueType : kFalseType); return Base::WriteBool(b); } + bool Int(int i) { PrettyPrefix(kNumberType); return Base::WriteInt(i); } + bool Uint(unsigned u) { PrettyPrefix(kNumberType); return Base::WriteUint(u); } + bool Int64(int64_t i64) { PrettyPrefix(kNumberType); return Base::WriteInt64(i64); } + bool Uint64(uint64_t u64) { PrettyPrefix(kNumberType); return Base::WriteUint64(u64); } + bool Double(double d) { PrettyPrefix(kNumberType); return Base::WriteDouble(d); } + + bool RawNumber(const Ch* str, SizeType length, bool copy = false) { + (void)copy; + PrettyPrefix(kNumberType); + return Base::WriteString(str, length); + } + + bool String(const Ch* str, SizeType length, bool copy = false) { + (void)copy; + PrettyPrefix(kStringType); + return Base::WriteString(str, length); + } + +#if RAPIDJSON_HAS_STDSTRING + bool String(const std::basic_string& str) { + return String(str.data(), SizeType(str.size())); + } +#endif + + bool StartObject() { + PrettyPrefix(kObjectType); + new (Base::level_stack_.template Push()) typename Base::Level(false); + return Base::WriteStartObject(); + } + + bool Key(const Ch* str, SizeType length, bool copy = false) { return String(str, length, copy); } + +#if RAPIDJSON_HAS_STDSTRING + bool Key(const std::basic_string& str) { + return Key(str.data(), SizeType(str.size())); + } +#endif + + bool EndObject(SizeType memberCount = 0) { + (void)memberCount; + RAPIDJSON_ASSERT(Base::level_stack_.GetSize() >= sizeof(typename Base::Level)); + RAPIDJSON_ASSERT(!Base::level_stack_.template Top()->inArray); + bool empty = Base::level_stack_.template Pop(1)->valueCount == 0; + + if (!empty) { + Base::os_->Put('\n'); + WriteIndent(); + } + bool ret = Base::WriteEndObject(); + (void)ret; + RAPIDJSON_ASSERT(ret == true); + if (Base::level_stack_.Empty()) // end of json text + Base::os_->Flush(); + return true; + } + + bool StartArray() { + PrettyPrefix(kArrayType); + new (Base::level_stack_.template Push()) typename Base::Level(true); + return Base::WriteStartArray(); + } + + bool EndArray(SizeType memberCount = 0) { + (void)memberCount; + RAPIDJSON_ASSERT(Base::level_stack_.GetSize() >= sizeof(typename Base::Level)); + RAPIDJSON_ASSERT(Base::level_stack_.template Top()->inArray); + bool empty = Base::level_stack_.template Pop(1)->valueCount == 0; + + if (!empty && !(formatOptions_ & kFormatSingleLineArray)) { + Base::os_->Put('\n'); + WriteIndent(); + } + bool ret = Base::WriteEndArray(); + (void)ret; + RAPIDJSON_ASSERT(ret == true); + if (Base::level_stack_.Empty()) // end of json text + Base::os_->Flush(); + return true; + } + + //@} + + /*! @name Convenience extensions */ + //@{ + + //! Simpler but slower overload. + bool String(const Ch* str) { return String(str, internal::StrLen(str)); } + bool Key(const Ch* str) { return Key(str, internal::StrLen(str)); } + + //@} + + //! Write a raw JSON value. + /*! + For user to write a stringified JSON as a value. + + \param json A well-formed JSON value. It should not contain null character within [0, length - 1] range. + \param length Length of the json. + \param type Type of the root of json. + \note When using PrettyWriter::RawValue(), the result json may not be indented correctly. + */ + bool RawValue(const Ch* json, size_t length, Type type) { PrettyPrefix(type); return Base::WriteRawValue(json, length); } + +protected: + void PrettyPrefix(Type type) { + (void)type; + if (Base::level_stack_.GetSize() != 0) { // this value is not at root + typename Base::Level* level = Base::level_stack_.template Top(); + + if (level->inArray) { + if (level->valueCount > 0) { + Base::os_->Put(','); // add comma if it is not the first element in array + if (formatOptions_ & kFormatSingleLineArray) + Base::os_->Put(' '); + } + + if (!(formatOptions_ & kFormatSingleLineArray)) { + Base::os_->Put('\n'); + WriteIndent(); + } + } + else { // in object + if (level->valueCount > 0) { + if (level->valueCount % 2 == 0) { + Base::os_->Put(','); + Base::os_->Put('\n'); + } + else { + Base::os_->Put(':'); + Base::os_->Put(' '); + } + } + else + Base::os_->Put('\n'); + + if (level->valueCount % 2 == 0) + WriteIndent(); + } + if (!level->inArray && level->valueCount % 2 == 0) + RAPIDJSON_ASSERT(type == kStringType); // if it's in object, then even number should be a name + level->valueCount++; + } + else { + RAPIDJSON_ASSERT(!Base::hasRoot_); // Should only has one and only one root. + Base::hasRoot_ = true; + } + } + + void WriteIndent() { + size_t count = (Base::level_stack_.GetSize() / sizeof(typename Base::Level)) * indentCharCount_; + PutN(*Base::os_, static_cast(indentChar_), count); + } + + Ch indentChar_; + unsigned indentCharCount_; + PrettyFormatOptions formatOptions_; + +private: + // Prohibit copy constructor & assignment operator. + PrettyWriter(const PrettyWriter&); + PrettyWriter& operator=(const PrettyWriter&); +}; + +RAPIDJSON_NAMESPACE_END + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_RAPIDJSON_H_ diff --git a/Source/Dependencies/rapidjson/include/rapidjson/rapidjson.h b/Source/Dependencies/rapidjson/include/rapidjson/rapidjson.h new file mode 100644 index 000000000..053b2ce43 --- /dev/null +++ b/Source/Dependencies/rapidjson/include/rapidjson/rapidjson.h @@ -0,0 +1,615 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_RAPIDJSON_H_ +#define RAPIDJSON_RAPIDJSON_H_ + +/*!\file rapidjson.h + \brief common definitions and configuration + + \see RAPIDJSON_CONFIG + */ + +/*! \defgroup RAPIDJSON_CONFIG RapidJSON configuration + \brief Configuration macros for library features + + Some RapidJSON features are configurable to adapt the library to a wide + variety of platforms, environments and usage scenarios. Most of the + features can be configured in terms of overriden or predefined + preprocessor macros at compile-time. + + Some additional customization is available in the \ref RAPIDJSON_ERRORS APIs. + + \note These macros should be given on the compiler command-line + (where applicable) to avoid inconsistent values when compiling + different translation units of a single application. + */ + +#include // malloc(), realloc(), free(), size_t +#include // memset(), memcpy(), memmove(), memcmp() + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_VERSION_STRING +// +// ALWAYS synchronize the following 3 macros with corresponding variables in /CMakeLists.txt. +// + +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +// token stringification +#define RAPIDJSON_STRINGIFY(x) RAPIDJSON_DO_STRINGIFY(x) +#define RAPIDJSON_DO_STRINGIFY(x) #x +//!@endcond + +/*! \def RAPIDJSON_MAJOR_VERSION + \ingroup RAPIDJSON_CONFIG + \brief Major version of RapidJSON in integer. +*/ +/*! \def RAPIDJSON_MINOR_VERSION + \ingroup RAPIDJSON_CONFIG + \brief Minor version of RapidJSON in integer. +*/ +/*! \def RAPIDJSON_PATCH_VERSION + \ingroup RAPIDJSON_CONFIG + \brief Patch version of RapidJSON in integer. +*/ +/*! \def RAPIDJSON_VERSION_STRING + \ingroup RAPIDJSON_CONFIG + \brief Version of RapidJSON in ".." string format. +*/ +#define RAPIDJSON_MAJOR_VERSION 1 +#define RAPIDJSON_MINOR_VERSION 1 +#define RAPIDJSON_PATCH_VERSION 0 +#define RAPIDJSON_VERSION_STRING \ + RAPIDJSON_STRINGIFY(RAPIDJSON_MAJOR_VERSION.RAPIDJSON_MINOR_VERSION.RAPIDJSON_PATCH_VERSION) + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_NAMESPACE_(BEGIN|END) +/*! \def RAPIDJSON_NAMESPACE + \ingroup RAPIDJSON_CONFIG + \brief provide custom rapidjson namespace + + In order to avoid symbol clashes and/or "One Definition Rule" errors + between multiple inclusions of (different versions of) RapidJSON in + a single binary, users can customize the name of the main RapidJSON + namespace. + + In case of a single nesting level, defining \c RAPIDJSON_NAMESPACE + to a custom name (e.g. \c MyRapidJSON) is sufficient. If multiple + levels are needed, both \ref RAPIDJSON_NAMESPACE_BEGIN and \ref + RAPIDJSON_NAMESPACE_END need to be defined as well: + + \code + // in some .cpp file + #define RAPIDJSON_NAMESPACE my::rapidjson + #define RAPIDJSON_NAMESPACE_BEGIN namespace my { namespace rapidjson { + #define RAPIDJSON_NAMESPACE_END } } + #include "rapidjson/..." + \endcode + + \see rapidjson + */ +/*! \def RAPIDJSON_NAMESPACE_BEGIN + \ingroup RAPIDJSON_CONFIG + \brief provide custom rapidjson namespace (opening expression) + \see RAPIDJSON_NAMESPACE +*/ +/*! \def RAPIDJSON_NAMESPACE_END + \ingroup RAPIDJSON_CONFIG + \brief provide custom rapidjson namespace (closing expression) + \see RAPIDJSON_NAMESPACE +*/ +#ifndef RAPIDJSON_NAMESPACE +#define RAPIDJSON_NAMESPACE rapidjson +#endif +#ifndef RAPIDJSON_NAMESPACE_BEGIN +#define RAPIDJSON_NAMESPACE_BEGIN namespace RAPIDJSON_NAMESPACE { +#endif +#ifndef RAPIDJSON_NAMESPACE_END +#define RAPIDJSON_NAMESPACE_END } +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_HAS_STDSTRING + +#ifndef RAPIDJSON_HAS_STDSTRING +#ifdef RAPIDJSON_DOXYGEN_RUNNING +#define RAPIDJSON_HAS_STDSTRING 1 // force generation of documentation +#else +#define RAPIDJSON_HAS_STDSTRING 0 // no std::string support by default +#endif +/*! \def RAPIDJSON_HAS_STDSTRING + \ingroup RAPIDJSON_CONFIG + \brief Enable RapidJSON support for \c std::string + + By defining this preprocessor symbol to \c 1, several convenience functions for using + \ref rapidjson::GenericValue with \c std::string are enabled, especially + for construction and comparison. + + \hideinitializer +*/ +#endif // !defined(RAPIDJSON_HAS_STDSTRING) + +#if RAPIDJSON_HAS_STDSTRING +#include +#endif // RAPIDJSON_HAS_STDSTRING + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_NO_INT64DEFINE + +/*! \def RAPIDJSON_NO_INT64DEFINE + \ingroup RAPIDJSON_CONFIG + \brief Use external 64-bit integer types. + + RapidJSON requires the 64-bit integer types \c int64_t and \c uint64_t types + to be available at global scope. + + If users have their own definition, define RAPIDJSON_NO_INT64DEFINE to + prevent RapidJSON from defining its own types. +*/ +#ifndef RAPIDJSON_NO_INT64DEFINE +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#if defined(_MSC_VER) && (_MSC_VER < 1800) // Visual Studio 2013 +#include "msinttypes/stdint.h" +#include "msinttypes/inttypes.h" +#else +// Other compilers should have this. +#include +#include +#endif +//!@endcond +#ifdef RAPIDJSON_DOXYGEN_RUNNING +#define RAPIDJSON_NO_INT64DEFINE +#endif +#endif // RAPIDJSON_NO_INT64TYPEDEF + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_FORCEINLINE + +#ifndef RAPIDJSON_FORCEINLINE +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#if defined(_MSC_VER) && defined(NDEBUG) +#define RAPIDJSON_FORCEINLINE __forceinline +#elif defined(__GNUC__) && __GNUC__ >= 4 && defined(NDEBUG) +#define RAPIDJSON_FORCEINLINE __attribute__((always_inline)) +#else +#define RAPIDJSON_FORCEINLINE +#endif +//!@endcond +#endif // RAPIDJSON_FORCEINLINE + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ENDIAN +#define RAPIDJSON_LITTLEENDIAN 0 //!< Little endian machine +#define RAPIDJSON_BIGENDIAN 1 //!< Big endian machine + +//! Endianness of the machine. +/*! + \def RAPIDJSON_ENDIAN + \ingroup RAPIDJSON_CONFIG + + GCC 4.6 provided macro for detecting endianness of the target machine. But other + compilers may not have this. User can define RAPIDJSON_ENDIAN to either + \ref RAPIDJSON_LITTLEENDIAN or \ref RAPIDJSON_BIGENDIAN. + + Default detection implemented with reference to + \li https://gcc.gnu.org/onlinedocs/gcc-4.6.0/cpp/Common-Predefined-Macros.html + \li http://www.boost.org/doc/libs/1_42_0/boost/detail/endian.hpp +*/ +#ifndef RAPIDJSON_ENDIAN +// Detect with GCC 4.6's macro +# ifdef __BYTE_ORDER__ +# if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +# define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN +# elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +# define RAPIDJSON_ENDIAN RAPIDJSON_BIGENDIAN +# else +# error Unknown machine endianess detected. User needs to define RAPIDJSON_ENDIAN. +# endif // __BYTE_ORDER__ +// Detect with GLIBC's endian.h +# elif defined(__GLIBC__) +# include +# if (__BYTE_ORDER == __LITTLE_ENDIAN) +# define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN +# elif (__BYTE_ORDER == __BIG_ENDIAN) +# define RAPIDJSON_ENDIAN RAPIDJSON_BIGENDIAN +# else +# error Unknown machine endianess detected. User needs to define RAPIDJSON_ENDIAN. +# endif // __GLIBC__ +// Detect with _LITTLE_ENDIAN and _BIG_ENDIAN macro +# elif defined(_LITTLE_ENDIAN) && !defined(_BIG_ENDIAN) +# define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN +# elif defined(_BIG_ENDIAN) && !defined(_LITTLE_ENDIAN) +# define RAPIDJSON_ENDIAN RAPIDJSON_BIGENDIAN +// Detect with architecture macros +# elif defined(__sparc) || defined(__sparc__) || defined(_POWER) || defined(__powerpc__) || defined(__ppc__) || defined(__hpux) || defined(__hppa) || defined(_MIPSEB) || defined(_POWER) || defined(__s390__) +# define RAPIDJSON_ENDIAN RAPIDJSON_BIGENDIAN +# elif defined(__i386__) || defined(__alpha__) || defined(__ia64) || defined(__ia64__) || defined(_M_IX86) || defined(_M_IA64) || defined(_M_ALPHA) || defined(__amd64) || defined(__amd64__) || defined(_M_AMD64) || defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || defined(__bfin__) +# define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN +# elif defined(_MSC_VER) && defined(_M_ARM) +# define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN +# elif defined(RAPIDJSON_DOXYGEN_RUNNING) +# define RAPIDJSON_ENDIAN +# else +# error Unknown machine endianess detected. User needs to define RAPIDJSON_ENDIAN. +# endif +#endif // RAPIDJSON_ENDIAN + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_64BIT + +//! Whether using 64-bit architecture +#ifndef RAPIDJSON_64BIT +#if defined(__LP64__) || (defined(__x86_64__) && defined(__ILP32__)) || defined(_WIN64) || defined(__EMSCRIPTEN__) +#define RAPIDJSON_64BIT 1 +#else +#define RAPIDJSON_64BIT 0 +#endif +#endif // RAPIDJSON_64BIT + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ALIGN + +//! Data alignment of the machine. +/*! \ingroup RAPIDJSON_CONFIG + \param x pointer to align + + Some machines require strict data alignment. Currently the default uses 4 bytes + alignment on 32-bit platforms and 8 bytes alignment for 64-bit platforms. + User can customize by defining the RAPIDJSON_ALIGN function macro. +*/ +#ifndef RAPIDJSON_ALIGN +#if RAPIDJSON_64BIT == 1 +#define RAPIDJSON_ALIGN(x) (((x) + static_cast(7u)) & ~static_cast(7u)) +#else +#define RAPIDJSON_ALIGN(x) (((x) + 3u) & ~3u) +#endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_UINT64_C2 + +//! Construct a 64-bit literal by a pair of 32-bit integer. +/*! + 64-bit literal with or without ULL suffix is prone to compiler warnings. + UINT64_C() is C macro which cause compilation problems. + Use this macro to define 64-bit constants by a pair of 32-bit integer. +*/ +#ifndef RAPIDJSON_UINT64_C2 +#define RAPIDJSON_UINT64_C2(high32, low32) ((static_cast(high32) << 32) | static_cast(low32)) +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_48BITPOINTER_OPTIMIZATION + +//! Use only lower 48-bit address for some pointers. +/*! + \ingroup RAPIDJSON_CONFIG + + This optimization uses the fact that current X86-64 architecture only implement lower 48-bit virtual address. + The higher 16-bit can be used for storing other data. + \c GenericValue uses this optimization to reduce its size form 24 bytes to 16 bytes in 64-bit architecture. +*/ +#ifndef RAPIDJSON_48BITPOINTER_OPTIMIZATION +#if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || defined(_M_AMD64) +#define RAPIDJSON_48BITPOINTER_OPTIMIZATION 1 +#else +#define RAPIDJSON_48BITPOINTER_OPTIMIZATION 0 +#endif +#endif // RAPIDJSON_48BITPOINTER_OPTIMIZATION + +#if RAPIDJSON_48BITPOINTER_OPTIMIZATION == 1 +#if RAPIDJSON_64BIT != 1 +#error RAPIDJSON_48BITPOINTER_OPTIMIZATION can only be set to 1 when RAPIDJSON_64BIT=1 +#endif +#define RAPIDJSON_SETPOINTER(type, p, x) (p = reinterpret_cast((reinterpret_cast(p) & static_cast(RAPIDJSON_UINT64_C2(0xFFFF0000, 0x00000000))) | reinterpret_cast(reinterpret_cast(x)))) +#define RAPIDJSON_GETPOINTER(type, p) (reinterpret_cast(reinterpret_cast(p) & static_cast(RAPIDJSON_UINT64_C2(0x0000FFFF, 0xFFFFFFFF)))) +#else +#define RAPIDJSON_SETPOINTER(type, p, x) (p = (x)) +#define RAPIDJSON_GETPOINTER(type, p) (p) +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_SSE2/RAPIDJSON_SSE42/RAPIDJSON_SIMD + +/*! \def RAPIDJSON_SIMD + \ingroup RAPIDJSON_CONFIG + \brief Enable SSE2/SSE4.2 optimization. + + RapidJSON supports optimized implementations for some parsing operations + based on the SSE2 or SSE4.2 SIMD extensions on modern Intel-compatible + processors. + + To enable these optimizations, two different symbols can be defined; + \code + // Enable SSE2 optimization. + #define RAPIDJSON_SSE2 + + // Enable SSE4.2 optimization. + #define RAPIDJSON_SSE42 + \endcode + + \c RAPIDJSON_SSE42 takes precedence, if both are defined. + + If any of these symbols is defined, RapidJSON defines the macro + \c RAPIDJSON_SIMD to indicate the availability of the optimized code. +*/ +#if defined(RAPIDJSON_SSE2) || defined(RAPIDJSON_SSE42) \ + || defined(RAPIDJSON_DOXYGEN_RUNNING) +#define RAPIDJSON_SIMD +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_NO_SIZETYPEDEFINE + +#ifndef RAPIDJSON_NO_SIZETYPEDEFINE +/*! \def RAPIDJSON_NO_SIZETYPEDEFINE + \ingroup RAPIDJSON_CONFIG + \brief User-provided \c SizeType definition. + + In order to avoid using 32-bit size types for indexing strings and arrays, + define this preprocessor symbol and provide the type rapidjson::SizeType + before including RapidJSON: + \code + #define RAPIDJSON_NO_SIZETYPEDEFINE + namespace rapidjson { typedef ::std::size_t SizeType; } + #include "rapidjson/..." + \endcode + + \see rapidjson::SizeType +*/ +#ifdef RAPIDJSON_DOXYGEN_RUNNING +#define RAPIDJSON_NO_SIZETYPEDEFINE +#endif +RAPIDJSON_NAMESPACE_BEGIN +//! Size type (for string lengths, array sizes, etc.) +/*! RapidJSON uses 32-bit array/string indices even on 64-bit platforms, + instead of using \c size_t. Users may override the SizeType by defining + \ref RAPIDJSON_NO_SIZETYPEDEFINE. +*/ +typedef unsigned SizeType; +RAPIDJSON_NAMESPACE_END +#endif + +// always import std::size_t to rapidjson namespace +RAPIDJSON_NAMESPACE_BEGIN +using std::size_t; +RAPIDJSON_NAMESPACE_END + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ASSERT + +//! Assertion. +/*! \ingroup RAPIDJSON_CONFIG + By default, rapidjson uses C \c assert() for internal assertions. + User can override it by defining RAPIDJSON_ASSERT(x) macro. + + \note Parsing errors are handled and can be customized by the + \ref RAPIDJSON_ERRORS APIs. +*/ +#ifndef RAPIDJSON_ASSERT +#include +#define RAPIDJSON_ASSERT(x) assert(x) +#endif // RAPIDJSON_ASSERT + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_STATIC_ASSERT + +// Adopt from boost +#ifndef RAPIDJSON_STATIC_ASSERT +#ifndef __clang__ +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#endif +RAPIDJSON_NAMESPACE_BEGIN +template struct STATIC_ASSERTION_FAILURE; +template <> struct STATIC_ASSERTION_FAILURE { enum { value = 1 }; }; +template struct StaticAssertTest {}; +RAPIDJSON_NAMESPACE_END + +#define RAPIDJSON_JOIN(X, Y) RAPIDJSON_DO_JOIN(X, Y) +#define RAPIDJSON_DO_JOIN(X, Y) RAPIDJSON_DO_JOIN2(X, Y) +#define RAPIDJSON_DO_JOIN2(X, Y) X##Y + +#if defined(__GNUC__) +#define RAPIDJSON_STATIC_ASSERT_UNUSED_ATTRIBUTE __attribute__((unused)) +#else +#define RAPIDJSON_STATIC_ASSERT_UNUSED_ATTRIBUTE +#endif +#ifndef __clang__ +//!@endcond +#endif + +/*! \def RAPIDJSON_STATIC_ASSERT + \brief (Internal) macro to check for conditions at compile-time + \param x compile-time condition + \hideinitializer + */ +#define RAPIDJSON_STATIC_ASSERT(x) \ + typedef ::RAPIDJSON_NAMESPACE::StaticAssertTest< \ + sizeof(::RAPIDJSON_NAMESPACE::STATIC_ASSERTION_FAILURE)> \ + RAPIDJSON_JOIN(StaticAssertTypedef, __LINE__) RAPIDJSON_STATIC_ASSERT_UNUSED_ATTRIBUTE +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_LIKELY, RAPIDJSON_UNLIKELY + +//! Compiler branching hint for expression with high probability to be true. +/*! + \ingroup RAPIDJSON_CONFIG + \param x Boolean expression likely to be true. +*/ +#ifndef RAPIDJSON_LIKELY +#if defined(__GNUC__) || defined(__clang__) +#define RAPIDJSON_LIKELY(x) __builtin_expect(!!(x), 1) +#else +#define RAPIDJSON_LIKELY(x) (x) +#endif +#endif + +//! Compiler branching hint for expression with low probability to be true. +/*! + \ingroup RAPIDJSON_CONFIG + \param x Boolean expression unlikely to be true. +*/ +#ifndef RAPIDJSON_UNLIKELY +#if defined(__GNUC__) || defined(__clang__) +#define RAPIDJSON_UNLIKELY(x) __builtin_expect(!!(x), 0) +#else +#define RAPIDJSON_UNLIKELY(x) (x) +#endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +// Helpers + +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN + +#define RAPIDJSON_MULTILINEMACRO_BEGIN do { +#define RAPIDJSON_MULTILINEMACRO_END \ +} while((void)0, 0) + +// adopted from Boost +#define RAPIDJSON_VERSION_CODE(x,y,z) \ + (((x)*100000) + ((y)*100) + (z)) + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_DIAG_PUSH/POP, RAPIDJSON_DIAG_OFF + +#if defined(__GNUC__) +#define RAPIDJSON_GNUC \ + RAPIDJSON_VERSION_CODE(__GNUC__,__GNUC_MINOR__,__GNUC_PATCHLEVEL__) +#endif + +#if defined(__clang__) || (defined(RAPIDJSON_GNUC) && RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,2,0)) + +#define RAPIDJSON_PRAGMA(x) _Pragma(RAPIDJSON_STRINGIFY(x)) +#define RAPIDJSON_DIAG_PRAGMA(x) RAPIDJSON_PRAGMA(GCC diagnostic x) +#define RAPIDJSON_DIAG_OFF(x) \ + RAPIDJSON_DIAG_PRAGMA(ignored RAPIDJSON_STRINGIFY(RAPIDJSON_JOIN(-W,x))) + +// push/pop support in Clang and GCC>=4.6 +#if defined(__clang__) || (defined(RAPIDJSON_GNUC) && RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,6,0)) +#define RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_PRAGMA(push) +#define RAPIDJSON_DIAG_POP RAPIDJSON_DIAG_PRAGMA(pop) +#else // GCC >= 4.2, < 4.6 +#define RAPIDJSON_DIAG_PUSH /* ignored */ +#define RAPIDJSON_DIAG_POP /* ignored */ +#endif + +#elif defined(_MSC_VER) + +// pragma (MSVC specific) +#define RAPIDJSON_PRAGMA(x) __pragma(x) +#define RAPIDJSON_DIAG_PRAGMA(x) RAPIDJSON_PRAGMA(warning(x)) + +#define RAPIDJSON_DIAG_OFF(x) RAPIDJSON_DIAG_PRAGMA(disable: x) +#define RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_PRAGMA(push) +#define RAPIDJSON_DIAG_POP RAPIDJSON_DIAG_PRAGMA(pop) + +#else + +#define RAPIDJSON_DIAG_OFF(x) /* ignored */ +#define RAPIDJSON_DIAG_PUSH /* ignored */ +#define RAPIDJSON_DIAG_POP /* ignored */ + +#endif // RAPIDJSON_DIAG_* + +/////////////////////////////////////////////////////////////////////////////// +// C++11 features + +#ifndef RAPIDJSON_HAS_CXX11_RVALUE_REFS +#if defined(__clang__) +#if __has_feature(cxx_rvalue_references) && \ + (defined(_LIBCPP_VERSION) || defined(__GLIBCXX__) && __GLIBCXX__ >= 20080306) +#define RAPIDJSON_HAS_CXX11_RVALUE_REFS 1 +#else +#define RAPIDJSON_HAS_CXX11_RVALUE_REFS 0 +#endif +#elif (defined(RAPIDJSON_GNUC) && (RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,3,0)) && defined(__GXX_EXPERIMENTAL_CXX0X__)) || \ + (defined(_MSC_VER) && _MSC_VER >= 1600) + +#define RAPIDJSON_HAS_CXX11_RVALUE_REFS 1 +#else +#define RAPIDJSON_HAS_CXX11_RVALUE_REFS 0 +#endif +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS + +#ifndef RAPIDJSON_HAS_CXX11_NOEXCEPT +#if defined(__clang__) +#define RAPIDJSON_HAS_CXX11_NOEXCEPT __has_feature(cxx_noexcept) +#elif (defined(RAPIDJSON_GNUC) && (RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,6,0)) && defined(__GXX_EXPERIMENTAL_CXX0X__)) +// (defined(_MSC_VER) && _MSC_VER >= ????) // not yet supported +#define RAPIDJSON_HAS_CXX11_NOEXCEPT 1 +#else +#define RAPIDJSON_HAS_CXX11_NOEXCEPT 0 +#endif +#endif +#if RAPIDJSON_HAS_CXX11_NOEXCEPT +#define RAPIDJSON_NOEXCEPT noexcept +#else +#define RAPIDJSON_NOEXCEPT /* noexcept */ +#endif // RAPIDJSON_HAS_CXX11_NOEXCEPT + +// no automatic detection, yet +#ifndef RAPIDJSON_HAS_CXX11_TYPETRAITS +#define RAPIDJSON_HAS_CXX11_TYPETRAITS 0 +#endif + +#ifndef RAPIDJSON_HAS_CXX11_RANGE_FOR +#if defined(__clang__) +#define RAPIDJSON_HAS_CXX11_RANGE_FOR __has_feature(cxx_range_for) +#elif (defined(RAPIDJSON_GNUC) && (RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,3,0)) && defined(__GXX_EXPERIMENTAL_CXX0X__)) || \ + (defined(_MSC_VER) && _MSC_VER >= 1700) +#define RAPIDJSON_HAS_CXX11_RANGE_FOR 1 +#else +#define RAPIDJSON_HAS_CXX11_RANGE_FOR 0 +#endif +#endif // RAPIDJSON_HAS_CXX11_RANGE_FOR + +//!@endcond + +/////////////////////////////////////////////////////////////////////////////// +// new/delete + +#ifndef RAPIDJSON_NEW +///! customization point for global \c new +#define RAPIDJSON_NEW(x) new x +#endif +#ifndef RAPIDJSON_DELETE +///! customization point for global \c delete +#define RAPIDJSON_DELETE(x) delete x +#endif + +/////////////////////////////////////////////////////////////////////////////// +// Type + +/*! \namespace rapidjson + \brief main RapidJSON namespace + \see RAPIDJSON_NAMESPACE +*/ +RAPIDJSON_NAMESPACE_BEGIN + +//! Type of JSON value +enum Type { + kNullType = 0, //!< null + kFalseType = 1, //!< false + kTrueType = 2, //!< true + kObjectType = 3, //!< object + kArrayType = 4, //!< array + kStringType = 5, //!< string + kNumberType = 6 //!< number +}; + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_RAPIDJSON_H_ diff --git a/Source/Dependencies/rapidjson/include/rapidjson/reader.h b/Source/Dependencies/rapidjson/include/rapidjson/reader.h new file mode 100644 index 000000000..19f8849b1 --- /dev/null +++ b/Source/Dependencies/rapidjson/include/rapidjson/reader.h @@ -0,0 +1,1879 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_READER_H_ +#define RAPIDJSON_READER_H_ + +/*! \file reader.h */ + +#include "allocators.h" +#include "stream.h" +#include "encodedstream.h" +#include "internal/meta.h" +#include "internal/stack.h" +#include "internal/strtod.h" +#include + +#if defined(RAPIDJSON_SIMD) && defined(_MSC_VER) +#include +#pragma intrinsic(_BitScanForward) +#endif +#ifdef RAPIDJSON_SSE42 +#include +#elif defined(RAPIDJSON_SSE2) +#include +#endif + +#ifdef _MSC_VER +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4127) // conditional expression is constant +RAPIDJSON_DIAG_OFF(4702) // unreachable code +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(old-style-cast) +RAPIDJSON_DIAG_OFF(padded) +RAPIDJSON_DIAG_OFF(switch-enum) +#endif + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#define RAPIDJSON_NOTHING /* deliberately empty */ +#ifndef RAPIDJSON_PARSE_ERROR_EARLY_RETURN +#define RAPIDJSON_PARSE_ERROR_EARLY_RETURN(value) \ + RAPIDJSON_MULTILINEMACRO_BEGIN \ + if (RAPIDJSON_UNLIKELY(HasParseError())) { return value; } \ + RAPIDJSON_MULTILINEMACRO_END +#endif +#define RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID \ + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(RAPIDJSON_NOTHING) +//!@endcond + +/*! \def RAPIDJSON_PARSE_ERROR_NORETURN + \ingroup RAPIDJSON_ERRORS + \brief Macro to indicate a parse error. + \param parseErrorCode \ref rapidjson::ParseErrorCode of the error + \param offset position of the error in JSON input (\c size_t) + + This macros can be used as a customization point for the internal + error handling mechanism of RapidJSON. + + A common usage model is to throw an exception instead of requiring the + caller to explicitly check the \ref rapidjson::GenericReader::Parse's + return value: + + \code + #define RAPIDJSON_PARSE_ERROR_NORETURN(parseErrorCode,offset) \ + throw ParseException(parseErrorCode, #parseErrorCode, offset) + + #include // std::runtime_error + #include "rapidjson/error/error.h" // rapidjson::ParseResult + + struct ParseException : std::runtime_error, rapidjson::ParseResult { + ParseException(rapidjson::ParseErrorCode code, const char* msg, size_t offset) + : std::runtime_error(msg), ParseResult(code, offset) {} + }; + + #include "rapidjson/reader.h" + \endcode + + \see RAPIDJSON_PARSE_ERROR, rapidjson::GenericReader::Parse + */ +#ifndef RAPIDJSON_PARSE_ERROR_NORETURN +#define RAPIDJSON_PARSE_ERROR_NORETURN(parseErrorCode, offset) \ + RAPIDJSON_MULTILINEMACRO_BEGIN \ + RAPIDJSON_ASSERT(!HasParseError()); /* Error can only be assigned once */ \ + SetParseError(parseErrorCode, offset); \ + RAPIDJSON_MULTILINEMACRO_END +#endif + +/*! \def RAPIDJSON_PARSE_ERROR + \ingroup RAPIDJSON_ERRORS + \brief (Internal) macro to indicate and handle a parse error. + \param parseErrorCode \ref rapidjson::ParseErrorCode of the error + \param offset position of the error in JSON input (\c size_t) + + Invokes RAPIDJSON_PARSE_ERROR_NORETURN and stops the parsing. + + \see RAPIDJSON_PARSE_ERROR_NORETURN + \hideinitializer + */ +#ifndef RAPIDJSON_PARSE_ERROR +#define RAPIDJSON_PARSE_ERROR(parseErrorCode, offset) \ + RAPIDJSON_MULTILINEMACRO_BEGIN \ + RAPIDJSON_PARSE_ERROR_NORETURN(parseErrorCode, offset); \ + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; \ + RAPIDJSON_MULTILINEMACRO_END +#endif + +#include "error/error.h" // ParseErrorCode, ParseResult + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// ParseFlag + +/*! \def RAPIDJSON_PARSE_DEFAULT_FLAGS + \ingroup RAPIDJSON_CONFIG + \brief User-defined kParseDefaultFlags definition. + + User can define this as any \c ParseFlag combinations. +*/ +#ifndef RAPIDJSON_PARSE_DEFAULT_FLAGS +#define RAPIDJSON_PARSE_DEFAULT_FLAGS kParseNoFlags +#endif + +//! Combination of parseFlags +/*! \see Reader::Parse, Document::Parse, Document::ParseInsitu, Document::ParseStream + */ +enum ParseFlag { + kParseNoFlags = 0, //!< No flags are set. + kParseInsituFlag = 1, //!< In-situ(destructive) parsing. + kParseValidateEncodingFlag = 2, //!< Validate encoding of JSON strings. + kParseIterativeFlag = 4, //!< Iterative(constant complexity in terms of function call stack size) parsing. + kParseStopWhenDoneFlag = 8, //!< After parsing a complete JSON root from stream, stop further processing the rest of stream. When this flag is used, parser will not generate kParseErrorDocumentRootNotSingular error. + kParseFullPrecisionFlag = 16, //!< Parse number in full precision (but slower). + kParseCommentsFlag = 32, //!< Allow one-line (//) and multi-line (/**/) comments. + kParseNumbersAsStringsFlag = 64, //!< Parse all numbers (ints/doubles) as strings. + kParseTrailingCommasFlag = 128, //!< Allow trailing commas at the end of objects and arrays. + kParseNanAndInfFlag = 256, //!< Allow parsing NaN, Inf, Infinity, -Inf and -Infinity as doubles. + kParseDefaultFlags = RAPIDJSON_PARSE_DEFAULT_FLAGS //!< Default parse flags. Can be customized by defining RAPIDJSON_PARSE_DEFAULT_FLAGS +}; + +/////////////////////////////////////////////////////////////////////////////// +// Handler + +/*! \class rapidjson::Handler + \brief Concept for receiving events from GenericReader upon parsing. + The functions return true if no error occurs. If they return false, + the event publisher should terminate the process. +\code +concept Handler { + typename Ch; + + bool Null(); + bool Bool(bool b); + bool Int(int i); + bool Uint(unsigned i); + bool Int64(int64_t i); + bool Uint64(uint64_t i); + bool Double(double d); + /// enabled via kParseNumbersAsStringsFlag, string is not null-terminated (use length) + bool RawNumber(const Ch* str, SizeType length, bool copy); + bool String(const Ch* str, SizeType length, bool copy); + bool StartObject(); + bool Key(const Ch* str, SizeType length, bool copy); + bool EndObject(SizeType memberCount); + bool StartArray(); + bool EndArray(SizeType elementCount); +}; +\endcode +*/ +/////////////////////////////////////////////////////////////////////////////// +// BaseReaderHandler + +//! Default implementation of Handler. +/*! This can be used as base class of any reader handler. + \note implements Handler concept +*/ +template, typename Derived = void> +struct BaseReaderHandler { + typedef typename Encoding::Ch Ch; + + typedef typename internal::SelectIf, BaseReaderHandler, Derived>::Type Override; + + bool Default() { return true; } + bool Null() { return static_cast(*this).Default(); } + bool Bool(bool) { return static_cast(*this).Default(); } + bool Int(int) { return static_cast(*this).Default(); } + bool Uint(unsigned) { return static_cast(*this).Default(); } + bool Int64(int64_t) { return static_cast(*this).Default(); } + bool Uint64(uint64_t) { return static_cast(*this).Default(); } + bool Double(double) { return static_cast(*this).Default(); } + /// enabled via kParseNumbersAsStringsFlag, string is not null-terminated (use length) + bool RawNumber(const Ch* str, SizeType len, bool copy) { return static_cast(*this).String(str, len, copy); } + bool String(const Ch*, SizeType, bool) { return static_cast(*this).Default(); } + bool StartObject() { return static_cast(*this).Default(); } + bool Key(const Ch* str, SizeType len, bool copy) { return static_cast(*this).String(str, len, copy); } + bool EndObject(SizeType) { return static_cast(*this).Default(); } + bool StartArray() { return static_cast(*this).Default(); } + bool EndArray(SizeType) { return static_cast(*this).Default(); } +}; + +/////////////////////////////////////////////////////////////////////////////// +// StreamLocalCopy + +namespace internal { + +template::copyOptimization> +class StreamLocalCopy; + +//! Do copy optimization. +template +class StreamLocalCopy { +public: + StreamLocalCopy(Stream& original) : s(original), original_(original) {} + ~StreamLocalCopy() { original_ = s; } + + Stream s; + +private: + StreamLocalCopy& operator=(const StreamLocalCopy&) /* = delete */; + + Stream& original_; +}; + +//! Keep reference. +template +class StreamLocalCopy { +public: + StreamLocalCopy(Stream& original) : s(original) {} + + Stream& s; + +private: + StreamLocalCopy& operator=(const StreamLocalCopy&) /* = delete */; +}; + +} // namespace internal + +/////////////////////////////////////////////////////////////////////////////// +// SkipWhitespace + +//! Skip the JSON white spaces in a stream. +/*! \param is A input stream for skipping white spaces. + \note This function has SSE2/SSE4.2 specialization. +*/ +template +void SkipWhitespace(InputStream& is) { + internal::StreamLocalCopy copy(is); + InputStream& s(copy.s); + + typename InputStream::Ch c; + while ((c = s.Peek()) == ' ' || c == '\n' || c == '\r' || c == '\t') + s.Take(); +} + +inline const char* SkipWhitespace(const char* p, const char* end) { + while (p != end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) + ++p; + return p; +} + +#ifdef RAPIDJSON_SSE42 +//! Skip whitespace with SSE 4.2 pcmpistrm instruction, testing 16 8-byte characters at once. +inline const char *SkipWhitespace_SIMD(const char* p) { + // Fast return for single non-whitespace + if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') + ++p; + else + return p; + + // 16-byte align to the next boundary + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + while (p != nextAligned) + if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') + ++p; + else + return p; + + // The rest of string using SIMD + static const char whitespace[16] = " \n\r\t"; + const __m128i w = _mm_loadu_si128(reinterpret_cast(&whitespace[0])); + + for (;; p += 16) { + const __m128i s = _mm_load_si128(reinterpret_cast(p)); + const int r = _mm_cvtsi128_si32(_mm_cmpistrm(w, s, _SIDD_UBYTE_OPS | _SIDD_CMP_EQUAL_ANY | _SIDD_BIT_MASK | _SIDD_NEGATIVE_POLARITY)); + if (r != 0) { // some of characters is non-whitespace +#ifdef _MSC_VER // Find the index of first non-whitespace + unsigned long offset; + _BitScanForward(&offset, r); + return p + offset; +#else + return p + __builtin_ffs(r) - 1; +#endif + } + } +} + +inline const char *SkipWhitespace_SIMD(const char* p, const char* end) { + // Fast return for single non-whitespace + if (p != end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) + ++p; + else + return p; + + // The middle of string using SIMD + static const char whitespace[16] = " \n\r\t"; + const __m128i w = _mm_loadu_si128(reinterpret_cast(&whitespace[0])); + + for (; p <= end - 16; p += 16) { + const __m128i s = _mm_loadu_si128(reinterpret_cast(p)); + const int r = _mm_cvtsi128_si32(_mm_cmpistrm(w, s, _SIDD_UBYTE_OPS | _SIDD_CMP_EQUAL_ANY | _SIDD_BIT_MASK | _SIDD_NEGATIVE_POLARITY)); + if (r != 0) { // some of characters is non-whitespace +#ifdef _MSC_VER // Find the index of first non-whitespace + unsigned long offset; + _BitScanForward(&offset, r); + return p + offset; +#else + return p + __builtin_ffs(r) - 1; +#endif + } + } + + return SkipWhitespace(p, end); +} + +#elif defined(RAPIDJSON_SSE2) + +//! Skip whitespace with SSE2 instructions, testing 16 8-byte characters at once. +inline const char *SkipWhitespace_SIMD(const char* p) { + // Fast return for single non-whitespace + if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') + ++p; + else + return p; + + // 16-byte align to the next boundary + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + while (p != nextAligned) + if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') + ++p; + else + return p; + + // The rest of string + #define C16(c) { c, c, c, c, c, c, c, c, c, c, c, c, c, c, c, c } + static const char whitespaces[4][16] = { C16(' '), C16('\n'), C16('\r'), C16('\t') }; + #undef C16 + + const __m128i w0 = _mm_loadu_si128(reinterpret_cast(&whitespaces[0][0])); + const __m128i w1 = _mm_loadu_si128(reinterpret_cast(&whitespaces[1][0])); + const __m128i w2 = _mm_loadu_si128(reinterpret_cast(&whitespaces[2][0])); + const __m128i w3 = _mm_loadu_si128(reinterpret_cast(&whitespaces[3][0])); + + for (;; p += 16) { + const __m128i s = _mm_load_si128(reinterpret_cast(p)); + __m128i x = _mm_cmpeq_epi8(s, w0); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w1)); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w2)); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w3)); + unsigned short r = static_cast(~_mm_movemask_epi8(x)); + if (r != 0) { // some of characters may be non-whitespace +#ifdef _MSC_VER // Find the index of first non-whitespace + unsigned long offset; + _BitScanForward(&offset, r); + return p + offset; +#else + return p + __builtin_ffs(r) - 1; +#endif + } + } +} + +inline const char *SkipWhitespace_SIMD(const char* p, const char* end) { + // Fast return for single non-whitespace + if (p != end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) + ++p; + else + return p; + + // The rest of string + #define C16(c) { c, c, c, c, c, c, c, c, c, c, c, c, c, c, c, c } + static const char whitespaces[4][16] = { C16(' '), C16('\n'), C16('\r'), C16('\t') }; + #undef C16 + + const __m128i w0 = _mm_loadu_si128(reinterpret_cast(&whitespaces[0][0])); + const __m128i w1 = _mm_loadu_si128(reinterpret_cast(&whitespaces[1][0])); + const __m128i w2 = _mm_loadu_si128(reinterpret_cast(&whitespaces[2][0])); + const __m128i w3 = _mm_loadu_si128(reinterpret_cast(&whitespaces[3][0])); + + for (; p <= end - 16; p += 16) { + const __m128i s = _mm_loadu_si128(reinterpret_cast(p)); + __m128i x = _mm_cmpeq_epi8(s, w0); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w1)); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w2)); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w3)); + unsigned short r = static_cast(~_mm_movemask_epi8(x)); + if (r != 0) { // some of characters may be non-whitespace +#ifdef _MSC_VER // Find the index of first non-whitespace + unsigned long offset; + _BitScanForward(&offset, r); + return p + offset; +#else + return p + __builtin_ffs(r) - 1; +#endif + } + } + + return SkipWhitespace(p, end); +} + +#endif // RAPIDJSON_SSE2 + +#ifdef RAPIDJSON_SIMD +//! Template function specialization for InsituStringStream +template<> inline void SkipWhitespace(InsituStringStream& is) { + is.src_ = const_cast(SkipWhitespace_SIMD(is.src_)); +} + +//! Template function specialization for StringStream +template<> inline void SkipWhitespace(StringStream& is) { + is.src_ = SkipWhitespace_SIMD(is.src_); +} + +template<> inline void SkipWhitespace(EncodedInputStream, MemoryStream>& is) { + is.is_.src_ = SkipWhitespace_SIMD(is.is_.src_, is.is_.end_); +} +#endif // RAPIDJSON_SIMD + +/////////////////////////////////////////////////////////////////////////////// +// GenericReader + +//! SAX-style JSON parser. Use \ref Reader for UTF8 encoding and default allocator. +/*! GenericReader parses JSON text from a stream, and send events synchronously to an + object implementing Handler concept. + + It needs to allocate a stack for storing a single decoded string during + non-destructive parsing. + + For in-situ parsing, the decoded string is directly written to the source + text string, no temporary buffer is required. + + A GenericReader object can be reused for parsing multiple JSON text. + + \tparam SourceEncoding Encoding of the input stream. + \tparam TargetEncoding Encoding of the parse output. + \tparam StackAllocator Allocator type for stack. +*/ +template +class GenericReader { +public: + typedef typename SourceEncoding::Ch Ch; //!< SourceEncoding character type + + //! Constructor. + /*! \param stackAllocator Optional allocator for allocating stack memory. (Only use for non-destructive parsing) + \param stackCapacity stack capacity in bytes for storing a single decoded string. (Only use for non-destructive parsing) + */ + GenericReader(StackAllocator* stackAllocator = 0, size_t stackCapacity = kDefaultStackCapacity) : stack_(stackAllocator, stackCapacity), parseResult_() {} + + //! Parse JSON text. + /*! \tparam parseFlags Combination of \ref ParseFlag. + \tparam InputStream Type of input stream, implementing Stream concept. + \tparam Handler Type of handler, implementing Handler concept. + \param is Input stream to be parsed. + \param handler The handler to receive events. + \return Whether the parsing is successful. + */ + template + ParseResult Parse(InputStream& is, Handler& handler) { + if (parseFlags & kParseIterativeFlag) + return IterativeParse(is, handler); + + parseResult_.Clear(); + + ClearStackOnExit scope(*this); + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + + if (RAPIDJSON_UNLIKELY(is.Peek() == '\0')) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorDocumentEmpty, is.Tell()); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + } + else { + ParseValue(is, handler); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + + if (!(parseFlags & kParseStopWhenDoneFlag)) { + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + + if (RAPIDJSON_UNLIKELY(is.Peek() != '\0')) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorDocumentRootNotSingular, is.Tell()); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + } + } + } + + return parseResult_; + } + + //! Parse JSON text (with \ref kParseDefaultFlags) + /*! \tparam InputStream Type of input stream, implementing Stream concept + \tparam Handler Type of handler, implementing Handler concept. + \param is Input stream to be parsed. + \param handler The handler to receive events. + \return Whether the parsing is successful. + */ + template + ParseResult Parse(InputStream& is, Handler& handler) { + return Parse(is, handler); + } + + //! Whether a parse error has occured in the last parsing. + bool HasParseError() const { return parseResult_.IsError(); } + + //! Get the \ref ParseErrorCode of last parsing. + ParseErrorCode GetParseErrorCode() const { return parseResult_.Code(); } + + //! Get the position of last parsing error in input, 0 otherwise. + size_t GetErrorOffset() const { return parseResult_.Offset(); } + +protected: + void SetParseError(ParseErrorCode code, size_t offset) { parseResult_.Set(code, offset); } + +private: + // Prohibit copy constructor & assignment operator. + GenericReader(const GenericReader&); + GenericReader& operator=(const GenericReader&); + + void ClearStack() { stack_.Clear(); } + + // clear stack on any exit from ParseStream, e.g. due to exception + struct ClearStackOnExit { + explicit ClearStackOnExit(GenericReader& r) : r_(r) {} + ~ClearStackOnExit() { r_.ClearStack(); } + private: + GenericReader& r_; + ClearStackOnExit(const ClearStackOnExit&); + ClearStackOnExit& operator=(const ClearStackOnExit&); + }; + + template + void SkipWhitespaceAndComments(InputStream& is) { + SkipWhitespace(is); + + if (parseFlags & kParseCommentsFlag) { + while (RAPIDJSON_UNLIKELY(Consume(is, '/'))) { + if (Consume(is, '*')) { + while (true) { + if (RAPIDJSON_UNLIKELY(is.Peek() == '\0')) + RAPIDJSON_PARSE_ERROR(kParseErrorUnspecificSyntaxError, is.Tell()); + else if (Consume(is, '*')) { + if (Consume(is, '/')) + break; + } + else + is.Take(); + } + } + else if (RAPIDJSON_LIKELY(Consume(is, '/'))) + while (is.Peek() != '\0' && is.Take() != '\n'); + else + RAPIDJSON_PARSE_ERROR(kParseErrorUnspecificSyntaxError, is.Tell()); + + SkipWhitespace(is); + } + } + } + + // Parse object: { string : value, ... } + template + void ParseObject(InputStream& is, Handler& handler) { + RAPIDJSON_ASSERT(is.Peek() == '{'); + is.Take(); // Skip '{' + + if (RAPIDJSON_UNLIKELY(!handler.StartObject())) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + if (Consume(is, '}')) { + if (RAPIDJSON_UNLIKELY(!handler.EndObject(0))) // empty object + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + return; + } + + for (SizeType memberCount = 0;;) { + if (RAPIDJSON_UNLIKELY(is.Peek() != '"')) + RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissName, is.Tell()); + + ParseString(is, handler, true); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + if (RAPIDJSON_UNLIKELY(!Consume(is, ':'))) + RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissColon, is.Tell()); + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + ParseValue(is, handler); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + ++memberCount; + + switch (is.Peek()) { + case ',': + is.Take(); + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + break; + case '}': + is.Take(); + if (RAPIDJSON_UNLIKELY(!handler.EndObject(memberCount))) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + return; + default: + RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissCommaOrCurlyBracket, is.Tell()); break; // This useless break is only for making warning and coverage happy + } + + if (parseFlags & kParseTrailingCommasFlag) { + if (is.Peek() == '}') { + if (RAPIDJSON_UNLIKELY(!handler.EndObject(memberCount))) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + is.Take(); + return; + } + } + } + } + + // Parse array: [ value, ... ] + template + void ParseArray(InputStream& is, Handler& handler) { + RAPIDJSON_ASSERT(is.Peek() == '['); + is.Take(); // Skip '[' + + if (RAPIDJSON_UNLIKELY(!handler.StartArray())) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + if (Consume(is, ']')) { + if (RAPIDJSON_UNLIKELY(!handler.EndArray(0))) // empty array + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + return; + } + + for (SizeType elementCount = 0;;) { + ParseValue(is, handler); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + ++elementCount; + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + if (Consume(is, ',')) { + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + } + else if (Consume(is, ']')) { + if (RAPIDJSON_UNLIKELY(!handler.EndArray(elementCount))) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + return; + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorArrayMissCommaOrSquareBracket, is.Tell()); + + if (parseFlags & kParseTrailingCommasFlag) { + if (is.Peek() == ']') { + if (RAPIDJSON_UNLIKELY(!handler.EndArray(elementCount))) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + is.Take(); + return; + } + } + } + } + + template + void ParseNull(InputStream& is, Handler& handler) { + RAPIDJSON_ASSERT(is.Peek() == 'n'); + is.Take(); + + if (RAPIDJSON_LIKELY(Consume(is, 'u') && Consume(is, 'l') && Consume(is, 'l'))) { + if (RAPIDJSON_UNLIKELY(!handler.Null())) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, is.Tell()); + } + + template + void ParseTrue(InputStream& is, Handler& handler) { + RAPIDJSON_ASSERT(is.Peek() == 't'); + is.Take(); + + if (RAPIDJSON_LIKELY(Consume(is, 'r') && Consume(is, 'u') && Consume(is, 'e'))) { + if (RAPIDJSON_UNLIKELY(!handler.Bool(true))) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, is.Tell()); + } + + template + void ParseFalse(InputStream& is, Handler& handler) { + RAPIDJSON_ASSERT(is.Peek() == 'f'); + is.Take(); + + if (RAPIDJSON_LIKELY(Consume(is, 'a') && Consume(is, 'l') && Consume(is, 's') && Consume(is, 'e'))) { + if (RAPIDJSON_UNLIKELY(!handler.Bool(false))) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, is.Tell()); + } + + template + RAPIDJSON_FORCEINLINE static bool Consume(InputStream& is, typename InputStream::Ch expect) { + if (RAPIDJSON_LIKELY(is.Peek() == expect)) { + is.Take(); + return true; + } + else + return false; + } + + // Helper function to parse four hexidecimal digits in \uXXXX in ParseString(). + template + unsigned ParseHex4(InputStream& is, size_t escapeOffset) { + unsigned codepoint = 0; + for (int i = 0; i < 4; i++) { + Ch c = is.Peek(); + codepoint <<= 4; + codepoint += static_cast(c); + if (c >= '0' && c <= '9') + codepoint -= '0'; + else if (c >= 'A' && c <= 'F') + codepoint -= 'A' - 10; + else if (c >= 'a' && c <= 'f') + codepoint -= 'a' - 10; + else { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorStringUnicodeEscapeInvalidHex, escapeOffset); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(0); + } + is.Take(); + } + return codepoint; + } + + template + class StackStream { + public: + typedef CharType Ch; + + StackStream(internal::Stack& stack) : stack_(stack), length_(0) {} + RAPIDJSON_FORCEINLINE void Put(Ch c) { + *stack_.template Push() = c; + ++length_; + } + + RAPIDJSON_FORCEINLINE void* Push(SizeType count) { + length_ += count; + return stack_.template Push(count); + } + + size_t Length() const { return length_; } + + Ch* Pop() { + return stack_.template Pop(length_); + } + + private: + StackStream(const StackStream&); + StackStream& operator=(const StackStream&); + + internal::Stack& stack_; + SizeType length_; + }; + + // Parse string and generate String event. Different code paths for kParseInsituFlag. + template + void ParseString(InputStream& is, Handler& handler, bool isKey = false) { + internal::StreamLocalCopy copy(is); + InputStream& s(copy.s); + + RAPIDJSON_ASSERT(s.Peek() == '\"'); + s.Take(); // Skip '\"' + + bool success = false; + if (parseFlags & kParseInsituFlag) { + typename InputStream::Ch *head = s.PutBegin(); + ParseStringToStream(s, s); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + size_t length = s.PutEnd(head) - 1; + RAPIDJSON_ASSERT(length <= 0xFFFFFFFF); + const typename TargetEncoding::Ch* const str = reinterpret_cast(head); + success = (isKey ? handler.Key(str, SizeType(length), false) : handler.String(str, SizeType(length), false)); + } + else { + StackStream stackStream(stack_); + ParseStringToStream(s, stackStream); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + SizeType length = static_cast(stackStream.Length()) - 1; + const typename TargetEncoding::Ch* const str = stackStream.Pop(); + success = (isKey ? handler.Key(str, length, true) : handler.String(str, length, true)); + } + if (RAPIDJSON_UNLIKELY(!success)) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, s.Tell()); + } + + // Parse string to an output is + // This function handles the prefix/suffix double quotes, escaping, and optional encoding validation. + template + RAPIDJSON_FORCEINLINE void ParseStringToStream(InputStream& is, OutputStream& os) { +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#define Z16 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + static const char escape[256] = { + Z16, Z16, 0, 0,'\"', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'/', + Z16, Z16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'\\', 0, 0, 0, + 0, 0,'\b', 0, 0, 0,'\f', 0, 0, 0, 0, 0, 0, 0,'\n', 0, + 0, 0,'\r', 0,'\t', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16 + }; +#undef Z16 +//!@endcond + + for (;;) { + // Scan and copy string before "\\\"" or < 0x20. This is an optional optimzation. + if (!(parseFlags & kParseValidateEncodingFlag)) + ScanCopyUnescapedString(is, os); + + Ch c = is.Peek(); + if (RAPIDJSON_UNLIKELY(c == '\\')) { // Escape + size_t escapeOffset = is.Tell(); // For invalid escaping, report the inital '\\' as error offset + is.Take(); + Ch e = is.Peek(); + if ((sizeof(Ch) == 1 || unsigned(e) < 256) && RAPIDJSON_LIKELY(escape[static_cast(e)])) { + is.Take(); + os.Put(static_cast(escape[static_cast(e)])); + } + else if (RAPIDJSON_LIKELY(e == 'u')) { // Unicode + is.Take(); + unsigned codepoint = ParseHex4(is, escapeOffset); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + if (RAPIDJSON_UNLIKELY(codepoint >= 0xD800 && codepoint <= 0xDBFF)) { + // Handle UTF-16 surrogate pair + if (RAPIDJSON_UNLIKELY(!Consume(is, '\\') || !Consume(is, 'u'))) + RAPIDJSON_PARSE_ERROR(kParseErrorStringUnicodeSurrogateInvalid, escapeOffset); + unsigned codepoint2 = ParseHex4(is, escapeOffset); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + if (RAPIDJSON_UNLIKELY(codepoint2 < 0xDC00 || codepoint2 > 0xDFFF)) + RAPIDJSON_PARSE_ERROR(kParseErrorStringUnicodeSurrogateInvalid, escapeOffset); + codepoint = (((codepoint - 0xD800) << 10) | (codepoint2 - 0xDC00)) + 0x10000; + } + TEncoding::Encode(os, codepoint); + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorStringEscapeInvalid, escapeOffset); + } + else if (RAPIDJSON_UNLIKELY(c == '"')) { // Closing double quote + is.Take(); + os.Put('\0'); // null-terminate the string + return; + } + else if (RAPIDJSON_UNLIKELY(static_cast(c) < 0x20)) { // RFC 4627: unescaped = %x20-21 / %x23-5B / %x5D-10FFFF + if (c == '\0') + RAPIDJSON_PARSE_ERROR(kParseErrorStringMissQuotationMark, is.Tell()); + else + RAPIDJSON_PARSE_ERROR(kParseErrorStringEscapeInvalid, is.Tell()); + } + else { + size_t offset = is.Tell(); + if (RAPIDJSON_UNLIKELY((parseFlags & kParseValidateEncodingFlag ? + !Transcoder::Validate(is, os) : + !Transcoder::Transcode(is, os)))) + RAPIDJSON_PARSE_ERROR(kParseErrorStringInvalidEncoding, offset); + } + } + } + + template + static RAPIDJSON_FORCEINLINE void ScanCopyUnescapedString(InputStream&, OutputStream&) { + // Do nothing for generic version + } + +#if defined(RAPIDJSON_SSE2) || defined(RAPIDJSON_SSE42) + // StringStream -> StackStream + static RAPIDJSON_FORCEINLINE void ScanCopyUnescapedString(StringStream& is, StackStream& os) { + const char* p = is.src_; + + // Scan one by one until alignment (unaligned load may cross page boundary and cause crash) + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + while (p != nextAligned) + if (RAPIDJSON_UNLIKELY(*p == '\"') || RAPIDJSON_UNLIKELY(*p == '\\') || RAPIDJSON_UNLIKELY(static_cast(*p) < 0x20)) { + is.src_ = p; + return; + } + else + os.Put(*p++); + + // The rest of string using SIMD + static const char dquote[16] = { '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"' }; + static const char bslash[16] = { '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\' }; + static const char space[16] = { 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19 }; + const __m128i dq = _mm_loadu_si128(reinterpret_cast(&dquote[0])); + const __m128i bs = _mm_loadu_si128(reinterpret_cast(&bslash[0])); + const __m128i sp = _mm_loadu_si128(reinterpret_cast(&space[0])); + + for (;; p += 16) { + const __m128i s = _mm_load_si128(reinterpret_cast(p)); + const __m128i t1 = _mm_cmpeq_epi8(s, dq); + const __m128i t2 = _mm_cmpeq_epi8(s, bs); + const __m128i t3 = _mm_cmpeq_epi8(_mm_max_epu8(s, sp), sp); // s < 0x20 <=> max(s, 0x19) == 0x19 + const __m128i x = _mm_or_si128(_mm_or_si128(t1, t2), t3); + unsigned short r = static_cast(_mm_movemask_epi8(x)); + if (RAPIDJSON_UNLIKELY(r != 0)) { // some of characters is escaped + SizeType length; + #ifdef _MSC_VER // Find the index of first escaped + unsigned long offset; + _BitScanForward(&offset, r); + length = offset; + #else + length = static_cast(__builtin_ffs(r) - 1); + #endif + char* q = reinterpret_cast(os.Push(length)); + for (size_t i = 0; i < length; i++) + q[i] = p[i]; + + p += length; + break; + } + _mm_storeu_si128(reinterpret_cast<__m128i *>(os.Push(16)), s); + } + + is.src_ = p; + } + + // InsituStringStream -> InsituStringStream + static RAPIDJSON_FORCEINLINE void ScanCopyUnescapedString(InsituStringStream& is, InsituStringStream& os) { + RAPIDJSON_ASSERT(&is == &os); + (void)os; + + if (is.src_ == is.dst_) { + SkipUnescapedString(is); + return; + } + + char* p = is.src_; + char *q = is.dst_; + + // Scan one by one until alignment (unaligned load may cross page boundary and cause crash) + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + while (p != nextAligned) + if (RAPIDJSON_UNLIKELY(*p == '\"') || RAPIDJSON_UNLIKELY(*p == '\\') || RAPIDJSON_UNLIKELY(static_cast(*p) < 0x20)) { + is.src_ = p; + is.dst_ = q; + return; + } + else + *q++ = *p++; + + // The rest of string using SIMD + static const char dquote[16] = { '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"' }; + static const char bslash[16] = { '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\' }; + static const char space[16] = { 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19 }; + const __m128i dq = _mm_loadu_si128(reinterpret_cast(&dquote[0])); + const __m128i bs = _mm_loadu_si128(reinterpret_cast(&bslash[0])); + const __m128i sp = _mm_loadu_si128(reinterpret_cast(&space[0])); + + for (;; p += 16, q += 16) { + const __m128i s = _mm_load_si128(reinterpret_cast(p)); + const __m128i t1 = _mm_cmpeq_epi8(s, dq); + const __m128i t2 = _mm_cmpeq_epi8(s, bs); + const __m128i t3 = _mm_cmpeq_epi8(_mm_max_epu8(s, sp), sp); // s < 0x20 <=> max(s, 0x19) == 0x19 + const __m128i x = _mm_or_si128(_mm_or_si128(t1, t2), t3); + unsigned short r = static_cast(_mm_movemask_epi8(x)); + if (RAPIDJSON_UNLIKELY(r != 0)) { // some of characters is escaped + size_t length; +#ifdef _MSC_VER // Find the index of first escaped + unsigned long offset; + _BitScanForward(&offset, r); + length = offset; +#else + length = static_cast(__builtin_ffs(r) - 1); +#endif + for (const char* pend = p + length; p != pend; ) + *q++ = *p++; + break; + } + _mm_storeu_si128(reinterpret_cast<__m128i *>(q), s); + } + + is.src_ = p; + is.dst_ = q; + } + + // When read/write pointers are the same for insitu stream, just skip unescaped characters + static RAPIDJSON_FORCEINLINE void SkipUnescapedString(InsituStringStream& is) { + RAPIDJSON_ASSERT(is.src_ == is.dst_); + char* p = is.src_; + + // Scan one by one until alignment (unaligned load may cross page boundary and cause crash) + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + for (; p != nextAligned; p++) + if (RAPIDJSON_UNLIKELY(*p == '\"') || RAPIDJSON_UNLIKELY(*p == '\\') || RAPIDJSON_UNLIKELY(static_cast(*p) < 0x20)) { + is.src_ = is.dst_ = p; + return; + } + + // The rest of string using SIMD + static const char dquote[16] = { '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"' }; + static const char bslash[16] = { '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\' }; + static const char space[16] = { 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19 }; + const __m128i dq = _mm_loadu_si128(reinterpret_cast(&dquote[0])); + const __m128i bs = _mm_loadu_si128(reinterpret_cast(&bslash[0])); + const __m128i sp = _mm_loadu_si128(reinterpret_cast(&space[0])); + + for (;; p += 16) { + const __m128i s = _mm_load_si128(reinterpret_cast(p)); + const __m128i t1 = _mm_cmpeq_epi8(s, dq); + const __m128i t2 = _mm_cmpeq_epi8(s, bs); + const __m128i t3 = _mm_cmpeq_epi8(_mm_max_epu8(s, sp), sp); // s < 0x20 <=> max(s, 0x19) == 0x19 + const __m128i x = _mm_or_si128(_mm_or_si128(t1, t2), t3); + unsigned short r = static_cast(_mm_movemask_epi8(x)); + if (RAPIDJSON_UNLIKELY(r != 0)) { // some of characters is escaped + size_t length; +#ifdef _MSC_VER // Find the index of first escaped + unsigned long offset; + _BitScanForward(&offset, r); + length = offset; +#else + length = static_cast(__builtin_ffs(r) - 1); +#endif + p += length; + break; + } + } + + is.src_ = is.dst_ = p; + } +#endif + + template + class NumberStream; + + template + class NumberStream { + public: + typedef typename InputStream::Ch Ch; + + NumberStream(GenericReader& reader, InputStream& s) : is(s) { (void)reader; } + ~NumberStream() {} + + RAPIDJSON_FORCEINLINE Ch Peek() const { return is.Peek(); } + RAPIDJSON_FORCEINLINE Ch TakePush() { return is.Take(); } + RAPIDJSON_FORCEINLINE Ch Take() { return is.Take(); } + RAPIDJSON_FORCEINLINE void Push(char) {} + + size_t Tell() { return is.Tell(); } + size_t Length() { return 0; } + const char* Pop() { return 0; } + + protected: + NumberStream& operator=(const NumberStream&); + + InputStream& is; + }; + + template + class NumberStream : public NumberStream { + typedef NumberStream Base; + public: + NumberStream(GenericReader& reader, InputStream& is) : Base(reader, is), stackStream(reader.stack_) {} + ~NumberStream() {} + + RAPIDJSON_FORCEINLINE Ch TakePush() { + stackStream.Put(static_cast(Base::is.Peek())); + return Base::is.Take(); + } + + RAPIDJSON_FORCEINLINE void Push(char c) { + stackStream.Put(c); + } + + size_t Length() { return stackStream.Length(); } + + const char* Pop() { + stackStream.Put('\0'); + return stackStream.Pop(); + } + + private: + StackStream stackStream; + }; + + template + class NumberStream : public NumberStream { + typedef NumberStream Base; + public: + NumberStream(GenericReader& reader, InputStream& is) : Base(reader, is) {} + ~NumberStream() {} + + RAPIDJSON_FORCEINLINE Ch Take() { return Base::TakePush(); } + }; + + template + void ParseNumber(InputStream& is, Handler& handler) { + internal::StreamLocalCopy copy(is); + NumberStream s(*this, copy.s); + + size_t startOffset = s.Tell(); + double d = 0.0; + bool useNanOrInf = false; + + // Parse minus + bool minus = Consume(s, '-'); + + // Parse int: zero / ( digit1-9 *DIGIT ) + unsigned i = 0; + uint64_t i64 = 0; + bool use64bit = false; + int significandDigit = 0; + if (RAPIDJSON_UNLIKELY(s.Peek() == '0')) { + i = 0; + s.TakePush(); + } + else if (RAPIDJSON_LIKELY(s.Peek() >= '1' && s.Peek() <= '9')) { + i = static_cast(s.TakePush() - '0'); + + if (minus) + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (RAPIDJSON_UNLIKELY(i >= 214748364)) { // 2^31 = 2147483648 + if (RAPIDJSON_LIKELY(i != 214748364 || s.Peek() > '8')) { + i64 = i; + use64bit = true; + break; + } + } + i = i * 10 + static_cast(s.TakePush() - '0'); + significandDigit++; + } + else + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (RAPIDJSON_UNLIKELY(i >= 429496729)) { // 2^32 - 1 = 4294967295 + if (RAPIDJSON_LIKELY(i != 429496729 || s.Peek() > '5')) { + i64 = i; + use64bit = true; + break; + } + } + i = i * 10 + static_cast(s.TakePush() - '0'); + significandDigit++; + } + } + // Parse NaN or Infinity here + else if ((parseFlags & kParseNanAndInfFlag) && RAPIDJSON_LIKELY((s.Peek() == 'I' || s.Peek() == 'N'))) { + useNanOrInf = true; + if (RAPIDJSON_LIKELY(Consume(s, 'N') && Consume(s, 'a') && Consume(s, 'N'))) { + d = std::numeric_limits::quiet_NaN(); + } + else if (RAPIDJSON_LIKELY(Consume(s, 'I') && Consume(s, 'n') && Consume(s, 'f'))) { + d = (minus ? -std::numeric_limits::infinity() : std::numeric_limits::infinity()); + if (RAPIDJSON_UNLIKELY(s.Peek() == 'i' && !(Consume(s, 'i') && Consume(s, 'n') + && Consume(s, 'i') && Consume(s, 't') && Consume(s, 'y')))) + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, s.Tell()); + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, s.Tell()); + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, s.Tell()); + + // Parse 64bit int + bool useDouble = false; + if (use64bit) { + if (minus) + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (RAPIDJSON_UNLIKELY(i64 >= RAPIDJSON_UINT64_C2(0x0CCCCCCC, 0xCCCCCCCC))) // 2^63 = 9223372036854775808 + if (RAPIDJSON_LIKELY(i64 != RAPIDJSON_UINT64_C2(0x0CCCCCCC, 0xCCCCCCCC) || s.Peek() > '8')) { + d = static_cast(i64); + useDouble = true; + break; + } + i64 = i64 * 10 + static_cast(s.TakePush() - '0'); + significandDigit++; + } + else + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (RAPIDJSON_UNLIKELY(i64 >= RAPIDJSON_UINT64_C2(0x19999999, 0x99999999))) // 2^64 - 1 = 18446744073709551615 + if (RAPIDJSON_LIKELY(i64 != RAPIDJSON_UINT64_C2(0x19999999, 0x99999999) || s.Peek() > '5')) { + d = static_cast(i64); + useDouble = true; + break; + } + i64 = i64 * 10 + static_cast(s.TakePush() - '0'); + significandDigit++; + } + } + + // Force double for big integer + if (useDouble) { + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (RAPIDJSON_UNLIKELY(d >= 1.7976931348623157e307)) // DBL_MAX / 10.0 + RAPIDJSON_PARSE_ERROR(kParseErrorNumberTooBig, startOffset); + d = d * 10 + (s.TakePush() - '0'); + } + } + + // Parse frac = decimal-point 1*DIGIT + int expFrac = 0; + size_t decimalPosition; + if (Consume(s, '.')) { + decimalPosition = s.Length(); + + if (RAPIDJSON_UNLIKELY(!(s.Peek() >= '0' && s.Peek() <= '9'))) + RAPIDJSON_PARSE_ERROR(kParseErrorNumberMissFraction, s.Tell()); + + if (!useDouble) { +#if RAPIDJSON_64BIT + // Use i64 to store significand in 64-bit architecture + if (!use64bit) + i64 = i; + + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (i64 > RAPIDJSON_UINT64_C2(0x1FFFFF, 0xFFFFFFFF)) // 2^53 - 1 for fast path + break; + else { + i64 = i64 * 10 + static_cast(s.TakePush() - '0'); + --expFrac; + if (i64 != 0) + significandDigit++; + } + } + + d = static_cast(i64); +#else + // Use double to store significand in 32-bit architecture + d = static_cast(use64bit ? i64 : i); +#endif + useDouble = true; + } + + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (significandDigit < 17) { + d = d * 10.0 + (s.TakePush() - '0'); + --expFrac; + if (RAPIDJSON_LIKELY(d > 0.0)) + significandDigit++; + } + else + s.TakePush(); + } + } + else + decimalPosition = s.Length(); // decimal position at the end of integer. + + // Parse exp = e [ minus / plus ] 1*DIGIT + int exp = 0; + if (Consume(s, 'e') || Consume(s, 'E')) { + if (!useDouble) { + d = static_cast(use64bit ? i64 : i); + useDouble = true; + } + + bool expMinus = false; + if (Consume(s, '+')) + ; + else if (Consume(s, '-')) + expMinus = true; + + if (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + exp = static_cast(s.Take() - '0'); + if (expMinus) { + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + exp = exp * 10 + static_cast(s.Take() - '0'); + if (exp >= 214748364) { // Issue #313: prevent overflow exponent + while (RAPIDJSON_UNLIKELY(s.Peek() >= '0' && s.Peek() <= '9')) // Consume the rest of exponent + s.Take(); + } + } + } + else { // positive exp + int maxExp = 308 - expFrac; + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + exp = exp * 10 + static_cast(s.Take() - '0'); + if (RAPIDJSON_UNLIKELY(exp > maxExp)) + RAPIDJSON_PARSE_ERROR(kParseErrorNumberTooBig, startOffset); + } + } + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorNumberMissExponent, s.Tell()); + + if (expMinus) + exp = -exp; + } + + // Finish parsing, call event according to the type of number. + bool cont = true; + + if (parseFlags & kParseNumbersAsStringsFlag) { + if (parseFlags & kParseInsituFlag) { + s.Pop(); // Pop stack no matter if it will be used or not. + typename InputStream::Ch* head = is.PutBegin(); + const size_t length = s.Tell() - startOffset; + RAPIDJSON_ASSERT(length <= 0xFFFFFFFF); + // unable to insert the \0 character here, it will erase the comma after this number + const typename TargetEncoding::Ch* const str = reinterpret_cast(head); + cont = handler.RawNumber(str, SizeType(length), false); + } + else { + SizeType numCharsToCopy = static_cast(s.Length()); + StringStream srcStream(s.Pop()); + StackStream dstStream(stack_); + while (numCharsToCopy--) { + Transcoder, TargetEncoding>::Transcode(srcStream, dstStream); + } + dstStream.Put('\0'); + const typename TargetEncoding::Ch* str = dstStream.Pop(); + const SizeType length = static_cast(dstStream.Length()) - 1; + cont = handler.RawNumber(str, SizeType(length), true); + } + } + else { + size_t length = s.Length(); + const char* decimal = s.Pop(); // Pop stack no matter if it will be used or not. + + if (useDouble) { + int p = exp + expFrac; + if (parseFlags & kParseFullPrecisionFlag) + d = internal::StrtodFullPrecision(d, p, decimal, length, decimalPosition, exp); + else + d = internal::StrtodNormalPrecision(d, p); + + cont = handler.Double(minus ? -d : d); + } + else if (useNanOrInf) { + cont = handler.Double(d); + } + else { + if (use64bit) { + if (minus) + cont = handler.Int64(static_cast(~i64 + 1)); + else + cont = handler.Uint64(i64); + } + else { + if (minus) + cont = handler.Int(static_cast(~i + 1)); + else + cont = handler.Uint(i); + } + } + } + if (RAPIDJSON_UNLIKELY(!cont)) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, startOffset); + } + + // Parse any JSON value + template + void ParseValue(InputStream& is, Handler& handler) { + switch (is.Peek()) { + case 'n': ParseNull (is, handler); break; + case 't': ParseTrue (is, handler); break; + case 'f': ParseFalse (is, handler); break; + case '"': ParseString(is, handler); break; + case '{': ParseObject(is, handler); break; + case '[': ParseArray (is, handler); break; + default : + ParseNumber(is, handler); + break; + + } + } + + // Iterative Parsing + + // States + enum IterativeParsingState { + IterativeParsingStartState = 0, + IterativeParsingFinishState, + IterativeParsingErrorState, + + // Object states + IterativeParsingObjectInitialState, + IterativeParsingMemberKeyState, + IterativeParsingKeyValueDelimiterState, + IterativeParsingMemberValueState, + IterativeParsingMemberDelimiterState, + IterativeParsingObjectFinishState, + + // Array states + IterativeParsingArrayInitialState, + IterativeParsingElementState, + IterativeParsingElementDelimiterState, + IterativeParsingArrayFinishState, + + // Single value state + IterativeParsingValueState + }; + + enum { cIterativeParsingStateCount = IterativeParsingValueState + 1 }; + + // Tokens + enum Token { + LeftBracketToken = 0, + RightBracketToken, + + LeftCurlyBracketToken, + RightCurlyBracketToken, + + CommaToken, + ColonToken, + + StringToken, + FalseToken, + TrueToken, + NullToken, + NumberToken, + + kTokenCount + }; + + RAPIDJSON_FORCEINLINE Token Tokenize(Ch c) { + +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#define N NumberToken +#define N16 N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N + // Maps from ASCII to Token + static const unsigned char tokenMap[256] = { + N16, // 00~0F + N16, // 10~1F + N, N, StringToken, N, N, N, N, N, N, N, N, N, CommaToken, N, N, N, // 20~2F + N, N, N, N, N, N, N, N, N, N, ColonToken, N, N, N, N, N, // 30~3F + N16, // 40~4F + N, N, N, N, N, N, N, N, N, N, N, LeftBracketToken, N, RightBracketToken, N, N, // 50~5F + N, N, N, N, N, N, FalseToken, N, N, N, N, N, N, N, NullToken, N, // 60~6F + N, N, N, N, TrueToken, N, N, N, N, N, N, LeftCurlyBracketToken, N, RightCurlyBracketToken, N, N, // 70~7F + N16, N16, N16, N16, N16, N16, N16, N16 // 80~FF + }; +#undef N +#undef N16 +//!@endcond + + if (sizeof(Ch) == 1 || static_cast(c) < 256) + return static_cast(tokenMap[static_cast(c)]); + else + return NumberToken; + } + + RAPIDJSON_FORCEINLINE IterativeParsingState Predict(IterativeParsingState state, Token token) { + // current state x one lookahead token -> new state + static const char G[cIterativeParsingStateCount][kTokenCount] = { + // Start + { + IterativeParsingArrayInitialState, // Left bracket + IterativeParsingErrorState, // Right bracket + IterativeParsingObjectInitialState, // Left curly bracket + IterativeParsingErrorState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingValueState, // String + IterativeParsingValueState, // False + IterativeParsingValueState, // True + IterativeParsingValueState, // Null + IterativeParsingValueState // Number + }, + // Finish(sink state) + { + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState + }, + // Error(sink state) + { + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState + }, + // ObjectInitial + { + IterativeParsingErrorState, // Left bracket + IterativeParsingErrorState, // Right bracket + IterativeParsingErrorState, // Left curly bracket + IterativeParsingObjectFinishState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingMemberKeyState, // String + IterativeParsingErrorState, // False + IterativeParsingErrorState, // True + IterativeParsingErrorState, // Null + IterativeParsingErrorState // Number + }, + // MemberKey + { + IterativeParsingErrorState, // Left bracket + IterativeParsingErrorState, // Right bracket + IterativeParsingErrorState, // Left curly bracket + IterativeParsingErrorState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingKeyValueDelimiterState, // Colon + IterativeParsingErrorState, // String + IterativeParsingErrorState, // False + IterativeParsingErrorState, // True + IterativeParsingErrorState, // Null + IterativeParsingErrorState // Number + }, + // KeyValueDelimiter + { + IterativeParsingArrayInitialState, // Left bracket(push MemberValue state) + IterativeParsingErrorState, // Right bracket + IterativeParsingObjectInitialState, // Left curly bracket(push MemberValue state) + IterativeParsingErrorState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingMemberValueState, // String + IterativeParsingMemberValueState, // False + IterativeParsingMemberValueState, // True + IterativeParsingMemberValueState, // Null + IterativeParsingMemberValueState // Number + }, + // MemberValue + { + IterativeParsingErrorState, // Left bracket + IterativeParsingErrorState, // Right bracket + IterativeParsingErrorState, // Left curly bracket + IterativeParsingObjectFinishState, // Right curly bracket + IterativeParsingMemberDelimiterState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingErrorState, // String + IterativeParsingErrorState, // False + IterativeParsingErrorState, // True + IterativeParsingErrorState, // Null + IterativeParsingErrorState // Number + }, + // MemberDelimiter + { + IterativeParsingErrorState, // Left bracket + IterativeParsingErrorState, // Right bracket + IterativeParsingErrorState, // Left curly bracket + IterativeParsingObjectFinishState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingMemberKeyState, // String + IterativeParsingErrorState, // False + IterativeParsingErrorState, // True + IterativeParsingErrorState, // Null + IterativeParsingErrorState // Number + }, + // ObjectFinish(sink state) + { + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState + }, + // ArrayInitial + { + IterativeParsingArrayInitialState, // Left bracket(push Element state) + IterativeParsingArrayFinishState, // Right bracket + IterativeParsingObjectInitialState, // Left curly bracket(push Element state) + IterativeParsingErrorState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingElementState, // String + IterativeParsingElementState, // False + IterativeParsingElementState, // True + IterativeParsingElementState, // Null + IterativeParsingElementState // Number + }, + // Element + { + IterativeParsingErrorState, // Left bracket + IterativeParsingArrayFinishState, // Right bracket + IterativeParsingErrorState, // Left curly bracket + IterativeParsingErrorState, // Right curly bracket + IterativeParsingElementDelimiterState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingErrorState, // String + IterativeParsingErrorState, // False + IterativeParsingErrorState, // True + IterativeParsingErrorState, // Null + IterativeParsingErrorState // Number + }, + // ElementDelimiter + { + IterativeParsingArrayInitialState, // Left bracket(push Element state) + IterativeParsingArrayFinishState, // Right bracket + IterativeParsingObjectInitialState, // Left curly bracket(push Element state) + IterativeParsingErrorState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingElementState, // String + IterativeParsingElementState, // False + IterativeParsingElementState, // True + IterativeParsingElementState, // Null + IterativeParsingElementState // Number + }, + // ArrayFinish(sink state) + { + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState + }, + // Single Value (sink state) + { + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState + } + }; // End of G + + return static_cast(G[state][token]); + } + + // Make an advance in the token stream and state based on the candidate destination state which was returned by Transit(). + // May return a new state on state pop. + template + RAPIDJSON_FORCEINLINE IterativeParsingState Transit(IterativeParsingState src, Token token, IterativeParsingState dst, InputStream& is, Handler& handler) { + (void)token; + + switch (dst) { + case IterativeParsingErrorState: + return dst; + + case IterativeParsingObjectInitialState: + case IterativeParsingArrayInitialState: + { + // Push the state(Element or MemeberValue) if we are nested in another array or value of member. + // In this way we can get the correct state on ObjectFinish or ArrayFinish by frame pop. + IterativeParsingState n = src; + if (src == IterativeParsingArrayInitialState || src == IterativeParsingElementDelimiterState) + n = IterativeParsingElementState; + else if (src == IterativeParsingKeyValueDelimiterState) + n = IterativeParsingMemberValueState; + // Push current state. + *stack_.template Push(1) = n; + // Initialize and push the member/element count. + *stack_.template Push(1) = 0; + // Call handler + bool hr = (dst == IterativeParsingObjectInitialState) ? handler.StartObject() : handler.StartArray(); + // On handler short circuits the parsing. + if (!hr) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorTermination, is.Tell()); + return IterativeParsingErrorState; + } + else { + is.Take(); + return dst; + } + } + + case IterativeParsingMemberKeyState: + ParseString(is, handler, true); + if (HasParseError()) + return IterativeParsingErrorState; + else + return dst; + + case IterativeParsingKeyValueDelimiterState: + RAPIDJSON_ASSERT(token == ColonToken); + is.Take(); + return dst; + + case IterativeParsingMemberValueState: + // Must be non-compound value. Or it would be ObjectInitial or ArrayInitial state. + ParseValue(is, handler); + if (HasParseError()) { + return IterativeParsingErrorState; + } + return dst; + + case IterativeParsingElementState: + // Must be non-compound value. Or it would be ObjectInitial or ArrayInitial state. + ParseValue(is, handler); + if (HasParseError()) { + return IterativeParsingErrorState; + } + return dst; + + case IterativeParsingMemberDelimiterState: + case IterativeParsingElementDelimiterState: + is.Take(); + // Update member/element count. + *stack_.template Top() = *stack_.template Top() + 1; + return dst; + + case IterativeParsingObjectFinishState: + { + // Transit from delimiter is only allowed when trailing commas are enabled + if (!(parseFlags & kParseTrailingCommasFlag) && src == IterativeParsingMemberDelimiterState) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorObjectMissName, is.Tell()); + return IterativeParsingErrorState; + } + // Get member count. + SizeType c = *stack_.template Pop(1); + // If the object is not empty, count the last member. + if (src == IterativeParsingMemberValueState) + ++c; + // Restore the state. + IterativeParsingState n = static_cast(*stack_.template Pop(1)); + // Transit to Finish state if this is the topmost scope. + if (n == IterativeParsingStartState) + n = IterativeParsingFinishState; + // Call handler + bool hr = handler.EndObject(c); + // On handler short circuits the parsing. + if (!hr) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorTermination, is.Tell()); + return IterativeParsingErrorState; + } + else { + is.Take(); + return n; + } + } + + case IterativeParsingArrayFinishState: + { + // Transit from delimiter is only allowed when trailing commas are enabled + if (!(parseFlags & kParseTrailingCommasFlag) && src == IterativeParsingElementDelimiterState) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorValueInvalid, is.Tell()); + return IterativeParsingErrorState; + } + // Get element count. + SizeType c = *stack_.template Pop(1); + // If the array is not empty, count the last element. + if (src == IterativeParsingElementState) + ++c; + // Restore the state. + IterativeParsingState n = static_cast(*stack_.template Pop(1)); + // Transit to Finish state if this is the topmost scope. + if (n == IterativeParsingStartState) + n = IterativeParsingFinishState; + // Call handler + bool hr = handler.EndArray(c); + // On handler short circuits the parsing. + if (!hr) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorTermination, is.Tell()); + return IterativeParsingErrorState; + } + else { + is.Take(); + return n; + } + } + + default: + // This branch is for IterativeParsingValueState actually. + // Use `default:` rather than + // `case IterativeParsingValueState:` is for code coverage. + + // The IterativeParsingStartState is not enumerated in this switch-case. + // It is impossible for that case. And it can be caught by following assertion. + + // The IterativeParsingFinishState is not enumerated in this switch-case either. + // It is a "derivative" state which cannot triggered from Predict() directly. + // Therefore it cannot happen here. And it can be caught by following assertion. + RAPIDJSON_ASSERT(dst == IterativeParsingValueState); + + // Must be non-compound value. Or it would be ObjectInitial or ArrayInitial state. + ParseValue(is, handler); + if (HasParseError()) { + return IterativeParsingErrorState; + } + return IterativeParsingFinishState; + } + } + + template + void HandleError(IterativeParsingState src, InputStream& is) { + if (HasParseError()) { + // Error flag has been set. + return; + } + + switch (src) { + case IterativeParsingStartState: RAPIDJSON_PARSE_ERROR(kParseErrorDocumentEmpty, is.Tell()); return; + case IterativeParsingFinishState: RAPIDJSON_PARSE_ERROR(kParseErrorDocumentRootNotSingular, is.Tell()); return; + case IterativeParsingObjectInitialState: + case IterativeParsingMemberDelimiterState: RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissName, is.Tell()); return; + case IterativeParsingMemberKeyState: RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissColon, is.Tell()); return; + case IterativeParsingMemberValueState: RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissCommaOrCurlyBracket, is.Tell()); return; + case IterativeParsingKeyValueDelimiterState: + case IterativeParsingArrayInitialState: + case IterativeParsingElementDelimiterState: RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, is.Tell()); return; + default: RAPIDJSON_ASSERT(src == IterativeParsingElementState); RAPIDJSON_PARSE_ERROR(kParseErrorArrayMissCommaOrSquareBracket, is.Tell()); return; + } + } + + template + ParseResult IterativeParse(InputStream& is, Handler& handler) { + parseResult_.Clear(); + ClearStackOnExit scope(*this); + IterativeParsingState state = IterativeParsingStartState; + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + while (is.Peek() != '\0') { + Token t = Tokenize(is.Peek()); + IterativeParsingState n = Predict(state, t); + IterativeParsingState d = Transit(state, t, n, is, handler); + + if (d == IterativeParsingErrorState) { + HandleError(state, is); + break; + } + + state = d; + + // Do not further consume streams if a root JSON has been parsed. + if ((parseFlags & kParseStopWhenDoneFlag) && state == IterativeParsingFinishState) + break; + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + } + + // Handle the end of file. + if (state != IterativeParsingFinishState) + HandleError(state, is); + + return parseResult_; + } + + static const size_t kDefaultStackCapacity = 256; //!< Default stack capacity in bytes for storing a single decoded string. + internal::Stack stack_; //!< A stack for storing decoded string temporarily during non-destructive parsing. + ParseResult parseResult_; +}; // class GenericReader + +//! Reader with UTF8 encoding and default allocator. +typedef GenericReader, UTF8<> > Reader; + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +#ifdef _MSC_VER +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_READER_H_ diff --git a/Source/Dependencies/rapidjson/include/rapidjson/schema.h b/Source/Dependencies/rapidjson/include/rapidjson/schema.h new file mode 100644 index 000000000..b182aa27f --- /dev/null +++ b/Source/Dependencies/rapidjson/include/rapidjson/schema.h @@ -0,0 +1,2006 @@ +// Tencent is pleased to support the open source community by making RapidJSON available-> +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip-> All rights reserved-> +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License-> You may obtain a copy of the License at +// +// http://opensource->org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied-> See the License for the +// specific language governing permissions and limitations under the License-> + +#ifndef RAPIDJSON_SCHEMA_H_ +#define RAPIDJSON_SCHEMA_H_ + +#include "document.h" +#include "pointer.h" +#include // abs, floor + +#if !defined(RAPIDJSON_SCHEMA_USE_INTERNALREGEX) +#define RAPIDJSON_SCHEMA_USE_INTERNALREGEX 1 +#else +#define RAPIDJSON_SCHEMA_USE_INTERNALREGEX 0 +#endif + +#if !RAPIDJSON_SCHEMA_USE_INTERNALREGEX && !defined(RAPIDJSON_SCHEMA_USE_STDREGEX) && (__cplusplus >=201103L || (defined(_MSC_VER) && _MSC_VER >= 1800)) +#define RAPIDJSON_SCHEMA_USE_STDREGEX 1 +#else +#define RAPIDJSON_SCHEMA_USE_STDREGEX 0 +#endif + +#if RAPIDJSON_SCHEMA_USE_INTERNALREGEX +#include "internal/regex.h" +#elif RAPIDJSON_SCHEMA_USE_STDREGEX +#include +#endif + +#if RAPIDJSON_SCHEMA_USE_INTERNALREGEX || RAPIDJSON_SCHEMA_USE_STDREGEX +#define RAPIDJSON_SCHEMA_HAS_REGEX 1 +#else +#define RAPIDJSON_SCHEMA_HAS_REGEX 0 +#endif + +#ifndef RAPIDJSON_SCHEMA_VERBOSE +#define RAPIDJSON_SCHEMA_VERBOSE 0 +#endif + +#if RAPIDJSON_SCHEMA_VERBOSE +#include "stringbuffer.h" +#endif + +RAPIDJSON_DIAG_PUSH + +#if defined(__GNUC__) +RAPIDJSON_DIAG_OFF(effc++) +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_OFF(weak-vtables) +RAPIDJSON_DIAG_OFF(exit-time-destructors) +RAPIDJSON_DIAG_OFF(c++98-compat-pedantic) +RAPIDJSON_DIAG_OFF(variadic-macros) +#endif + +#ifdef _MSC_VER +RAPIDJSON_DIAG_OFF(4512) // assignment operator could not be generated +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// Verbose Utilities + +#if RAPIDJSON_SCHEMA_VERBOSE + +namespace internal { + +inline void PrintInvalidKeyword(const char* keyword) { + printf("Fail keyword: %s\n", keyword); +} + +inline void PrintInvalidKeyword(const wchar_t* keyword) { + wprintf(L"Fail keyword: %ls\n", keyword); +} + +inline void PrintInvalidDocument(const char* document) { + printf("Fail document: %s\n\n", document); +} + +inline void PrintInvalidDocument(const wchar_t* document) { + wprintf(L"Fail document: %ls\n\n", document); +} + +inline void PrintValidatorPointers(unsigned depth, const char* s, const char* d) { + printf("S: %*s%s\nD: %*s%s\n\n", depth * 4, " ", s, depth * 4, " ", d); +} + +inline void PrintValidatorPointers(unsigned depth, const wchar_t* s, const wchar_t* d) { + wprintf(L"S: %*ls%ls\nD: %*ls%ls\n\n", depth * 4, L" ", s, depth * 4, L" ", d); +} + +} // namespace internal + +#endif // RAPIDJSON_SCHEMA_VERBOSE + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_INVALID_KEYWORD_RETURN + +#if RAPIDJSON_SCHEMA_VERBOSE +#define RAPIDJSON_INVALID_KEYWORD_VERBOSE(keyword) internal::PrintInvalidKeyword(keyword) +#else +#define RAPIDJSON_INVALID_KEYWORD_VERBOSE(keyword) +#endif + +#define RAPIDJSON_INVALID_KEYWORD_RETURN(keyword)\ +RAPIDJSON_MULTILINEMACRO_BEGIN\ + context.invalidKeyword = keyword.GetString();\ + RAPIDJSON_INVALID_KEYWORD_VERBOSE(keyword.GetString());\ + return false;\ +RAPIDJSON_MULTILINEMACRO_END + +/////////////////////////////////////////////////////////////////////////////// +// Forward declarations + +template +class GenericSchemaDocument; + +namespace internal { + +template +class Schema; + +/////////////////////////////////////////////////////////////////////////////// +// ISchemaValidator + +class ISchemaValidator { +public: + virtual ~ISchemaValidator() {} + virtual bool IsValid() const = 0; +}; + +/////////////////////////////////////////////////////////////////////////////// +// ISchemaStateFactory + +template +class ISchemaStateFactory { +public: + virtual ~ISchemaStateFactory() {} + virtual ISchemaValidator* CreateSchemaValidator(const SchemaType&) = 0; + virtual void DestroySchemaValidator(ISchemaValidator* validator) = 0; + virtual void* CreateHasher() = 0; + virtual uint64_t GetHashCode(void* hasher) = 0; + virtual void DestroryHasher(void* hasher) = 0; + virtual void* MallocState(size_t size) = 0; + virtual void FreeState(void* p) = 0; +}; + +/////////////////////////////////////////////////////////////////////////////// +// Hasher + +// For comparison of compound value +template +class Hasher { +public: + typedef typename Encoding::Ch Ch; + + Hasher(Allocator* allocator = 0, size_t stackCapacity = kDefaultSize) : stack_(allocator, stackCapacity) {} + + bool Null() { return WriteType(kNullType); } + bool Bool(bool b) { return WriteType(b ? kTrueType : kFalseType); } + bool Int(int i) { Number n; n.u.i = i; n.d = static_cast(i); return WriteNumber(n); } + bool Uint(unsigned u) { Number n; n.u.u = u; n.d = static_cast(u); return WriteNumber(n); } + bool Int64(int64_t i) { Number n; n.u.i = i; n.d = static_cast(i); return WriteNumber(n); } + bool Uint64(uint64_t u) { Number n; n.u.u = u; n.d = static_cast(u); return WriteNumber(n); } + bool Double(double d) { + Number n; + if (d < 0) n.u.i = static_cast(d); + else n.u.u = static_cast(d); + n.d = d; + return WriteNumber(n); + } + + bool RawNumber(const Ch* str, SizeType len, bool) { + WriteBuffer(kNumberType, str, len * sizeof(Ch)); + return true; + } + + bool String(const Ch* str, SizeType len, bool) { + WriteBuffer(kStringType, str, len * sizeof(Ch)); + return true; + } + + bool StartObject() { return true; } + bool Key(const Ch* str, SizeType len, bool copy) { return String(str, len, copy); } + bool EndObject(SizeType memberCount) { + uint64_t h = Hash(0, kObjectType); + uint64_t* kv = stack_.template Pop(memberCount * 2); + for (SizeType i = 0; i < memberCount; i++) + h ^= Hash(kv[i * 2], kv[i * 2 + 1]); // Use xor to achieve member order insensitive + *stack_.template Push() = h; + return true; + } + + bool StartArray() { return true; } + bool EndArray(SizeType elementCount) { + uint64_t h = Hash(0, kArrayType); + uint64_t* e = stack_.template Pop(elementCount); + for (SizeType i = 0; i < elementCount; i++) + h = Hash(h, e[i]); // Use hash to achieve element order sensitive + *stack_.template Push() = h; + return true; + } + + bool IsValid() const { return stack_.GetSize() == sizeof(uint64_t); } + + uint64_t GetHashCode() const { + RAPIDJSON_ASSERT(IsValid()); + return *stack_.template Top(); + } + +private: + static const size_t kDefaultSize = 256; + struct Number { + union U { + uint64_t u; + int64_t i; + }u; + double d; + }; + + bool WriteType(Type type) { return WriteBuffer(type, 0, 0); } + + bool WriteNumber(const Number& n) { return WriteBuffer(kNumberType, &n, sizeof(n)); } + + bool WriteBuffer(Type type, const void* data, size_t len) { + // FNV-1a from http://isthe.com/chongo/tech/comp/fnv/ + uint64_t h = Hash(RAPIDJSON_UINT64_C2(0x84222325, 0xcbf29ce4), type); + const unsigned char* d = static_cast(data); + for (size_t i = 0; i < len; i++) + h = Hash(h, d[i]); + *stack_.template Push() = h; + return true; + } + + static uint64_t Hash(uint64_t h, uint64_t d) { + static const uint64_t kPrime = RAPIDJSON_UINT64_C2(0x00000100, 0x000001b3); + h ^= d; + h *= kPrime; + return h; + } + + Stack stack_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// SchemaValidationContext + +template +struct SchemaValidationContext { + typedef Schema SchemaType; + typedef ISchemaStateFactory SchemaValidatorFactoryType; + typedef typename SchemaType::ValueType ValueType; + typedef typename ValueType::Ch Ch; + + enum PatternValidatorType { + kPatternValidatorOnly, + kPatternValidatorWithProperty, + kPatternValidatorWithAdditionalProperty + }; + + SchemaValidationContext(SchemaValidatorFactoryType& f, const SchemaType* s) : + factory(f), + schema(s), + valueSchema(), + invalidKeyword(), + hasher(), + arrayElementHashCodes(), + validators(), + validatorCount(), + patternPropertiesValidators(), + patternPropertiesValidatorCount(), + patternPropertiesSchemas(), + patternPropertiesSchemaCount(), + valuePatternValidatorType(kPatternValidatorOnly), + propertyExist(), + inArray(false), + valueUniqueness(false), + arrayUniqueness(false) + { + } + + ~SchemaValidationContext() { + if (hasher) + factory.DestroryHasher(hasher); + if (validators) { + for (SizeType i = 0; i < validatorCount; i++) + factory.DestroySchemaValidator(validators[i]); + factory.FreeState(validators); + } + if (patternPropertiesValidators) { + for (SizeType i = 0; i < patternPropertiesValidatorCount; i++) + factory.DestroySchemaValidator(patternPropertiesValidators[i]); + factory.FreeState(patternPropertiesValidators); + } + if (patternPropertiesSchemas) + factory.FreeState(patternPropertiesSchemas); + if (propertyExist) + factory.FreeState(propertyExist); + } + + SchemaValidatorFactoryType& factory; + const SchemaType* schema; + const SchemaType* valueSchema; + const Ch* invalidKeyword; + void* hasher; // Only validator access + void* arrayElementHashCodes; // Only validator access this + ISchemaValidator** validators; + SizeType validatorCount; + ISchemaValidator** patternPropertiesValidators; + SizeType patternPropertiesValidatorCount; + const SchemaType** patternPropertiesSchemas; + SizeType patternPropertiesSchemaCount; + PatternValidatorType valuePatternValidatorType; + PatternValidatorType objectPatternValidatorType; + SizeType arrayElementIndex; + bool* propertyExist; + bool inArray; + bool valueUniqueness; + bool arrayUniqueness; +}; + +/////////////////////////////////////////////////////////////////////////////// +// Schema + +template +class Schema { +public: + typedef typename SchemaDocumentType::ValueType ValueType; + typedef typename SchemaDocumentType::AllocatorType AllocatorType; + typedef typename SchemaDocumentType::PointerType PointerType; + typedef typename ValueType::EncodingType EncodingType; + typedef typename EncodingType::Ch Ch; + typedef SchemaValidationContext Context; + typedef Schema SchemaType; + typedef GenericValue SValue; + friend class GenericSchemaDocument; + + Schema(SchemaDocumentType* schemaDocument, const PointerType& p, const ValueType& value, const ValueType& document, AllocatorType* allocator) : + allocator_(allocator), + enum_(), + enumCount_(), + not_(), + type_((1 << kTotalSchemaType) - 1), // typeless + validatorCount_(), + properties_(), + additionalPropertiesSchema_(), + patternProperties_(), + patternPropertyCount_(), + propertyCount_(), + minProperties_(), + maxProperties_(SizeType(~0)), + additionalProperties_(true), + hasDependencies_(), + hasRequired_(), + hasSchemaDependencies_(), + additionalItemsSchema_(), + itemsList_(), + itemsTuple_(), + itemsTupleCount_(), + minItems_(), + maxItems_(SizeType(~0)), + additionalItems_(true), + uniqueItems_(false), + pattern_(), + minLength_(0), + maxLength_(~SizeType(0)), + exclusiveMinimum_(false), + exclusiveMaximum_(false) + { + typedef typename SchemaDocumentType::ValueType ValueType; + typedef typename ValueType::ConstValueIterator ConstValueIterator; + typedef typename ValueType::ConstMemberIterator ConstMemberIterator; + + if (!value.IsObject()) + return; + + if (const ValueType* v = GetMember(value, GetTypeString())) { + type_ = 0; + if (v->IsString()) + AddType(*v); + else if (v->IsArray()) + for (ConstValueIterator itr = v->Begin(); itr != v->End(); ++itr) + AddType(*itr); + } + + if (const ValueType* v = GetMember(value, GetEnumString())) + if (v->IsArray() && v->Size() > 0) { + enum_ = static_cast(allocator_->Malloc(sizeof(uint64_t) * v->Size())); + for (ConstValueIterator itr = v->Begin(); itr != v->End(); ++itr) { + typedef Hasher > EnumHasherType; + char buffer[256 + 24]; + MemoryPoolAllocator<> hasherAllocator(buffer, sizeof(buffer)); + EnumHasherType h(&hasherAllocator, 256); + itr->Accept(h); + enum_[enumCount_++] = h.GetHashCode(); + } + } + + if (schemaDocument) { + AssignIfExist(allOf_, *schemaDocument, p, value, GetAllOfString(), document); + AssignIfExist(anyOf_, *schemaDocument, p, value, GetAnyOfString(), document); + AssignIfExist(oneOf_, *schemaDocument, p, value, GetOneOfString(), document); + } + + if (const ValueType* v = GetMember(value, GetNotString())) { + schemaDocument->CreateSchema(¬_, p.Append(GetNotString(), allocator_), *v, document); + notValidatorIndex_ = validatorCount_; + validatorCount_++; + } + + // Object + + const ValueType* properties = GetMember(value, GetPropertiesString()); + const ValueType* required = GetMember(value, GetRequiredString()); + const ValueType* dependencies = GetMember(value, GetDependenciesString()); + { + // Gather properties from properties/required/dependencies + SValue allProperties(kArrayType); + + if (properties && properties->IsObject()) + for (ConstMemberIterator itr = properties->MemberBegin(); itr != properties->MemberEnd(); ++itr) + AddUniqueElement(allProperties, itr->name); + + if (required && required->IsArray()) + for (ConstValueIterator itr = required->Begin(); itr != required->End(); ++itr) + if (itr->IsString()) + AddUniqueElement(allProperties, *itr); + + if (dependencies && dependencies->IsObject()) + for (ConstMemberIterator itr = dependencies->MemberBegin(); itr != dependencies->MemberEnd(); ++itr) { + AddUniqueElement(allProperties, itr->name); + if (itr->value.IsArray()) + for (ConstValueIterator i = itr->value.Begin(); i != itr->value.End(); ++i) + if (i->IsString()) + AddUniqueElement(allProperties, *i); + } + + if (allProperties.Size() > 0) { + propertyCount_ = allProperties.Size(); + properties_ = static_cast(allocator_->Malloc(sizeof(Property) * propertyCount_)); + for (SizeType i = 0; i < propertyCount_; i++) { + new (&properties_[i]) Property(); + properties_[i].name = allProperties[i]; + properties_[i].schema = GetTypeless(); + } + } + } + + if (properties && properties->IsObject()) { + PointerType q = p.Append(GetPropertiesString(), allocator_); + for (ConstMemberIterator itr = properties->MemberBegin(); itr != properties->MemberEnd(); ++itr) { + SizeType index; + if (FindPropertyIndex(itr->name, &index)) + schemaDocument->CreateSchema(&properties_[index].schema, q.Append(itr->name, allocator_), itr->value, document); + } + } + + if (const ValueType* v = GetMember(value, GetPatternPropertiesString())) { + PointerType q = p.Append(GetPatternPropertiesString(), allocator_); + patternProperties_ = static_cast(allocator_->Malloc(sizeof(PatternProperty) * v->MemberCount())); + patternPropertyCount_ = 0; + + for (ConstMemberIterator itr = v->MemberBegin(); itr != v->MemberEnd(); ++itr) { + new (&patternProperties_[patternPropertyCount_]) PatternProperty(); + patternProperties_[patternPropertyCount_].pattern = CreatePattern(itr->name); + schemaDocument->CreateSchema(&patternProperties_[patternPropertyCount_].schema, q.Append(itr->name, allocator_), itr->value, document); + patternPropertyCount_++; + } + } + + if (required && required->IsArray()) + for (ConstValueIterator itr = required->Begin(); itr != required->End(); ++itr) + if (itr->IsString()) { + SizeType index; + if (FindPropertyIndex(*itr, &index)) { + properties_[index].required = true; + hasRequired_ = true; + } + } + + if (dependencies && dependencies->IsObject()) { + PointerType q = p.Append(GetDependenciesString(), allocator_); + hasDependencies_ = true; + for (ConstMemberIterator itr = dependencies->MemberBegin(); itr != dependencies->MemberEnd(); ++itr) { + SizeType sourceIndex; + if (FindPropertyIndex(itr->name, &sourceIndex)) { + if (itr->value.IsArray()) { + properties_[sourceIndex].dependencies = static_cast(allocator_->Malloc(sizeof(bool) * propertyCount_)); + std::memset(properties_[sourceIndex].dependencies, 0, sizeof(bool)* propertyCount_); + for (ConstValueIterator targetItr = itr->value.Begin(); targetItr != itr->value.End(); ++targetItr) { + SizeType targetIndex; + if (FindPropertyIndex(*targetItr, &targetIndex)) + properties_[sourceIndex].dependencies[targetIndex] = true; + } + } + else if (itr->value.IsObject()) { + hasSchemaDependencies_ = true; + schemaDocument->CreateSchema(&properties_[sourceIndex].dependenciesSchema, q.Append(itr->name, allocator_), itr->value, document); + properties_[sourceIndex].dependenciesValidatorIndex = validatorCount_; + validatorCount_++; + } + } + } + } + + if (const ValueType* v = GetMember(value, GetAdditionalPropertiesString())) { + if (v->IsBool()) + additionalProperties_ = v->GetBool(); + else if (v->IsObject()) + schemaDocument->CreateSchema(&additionalPropertiesSchema_, p.Append(GetAdditionalPropertiesString(), allocator_), *v, document); + } + + AssignIfExist(minProperties_, value, GetMinPropertiesString()); + AssignIfExist(maxProperties_, value, GetMaxPropertiesString()); + + // Array + if (const ValueType* v = GetMember(value, GetItemsString())) { + PointerType q = p.Append(GetItemsString(), allocator_); + if (v->IsObject()) // List validation + schemaDocument->CreateSchema(&itemsList_, q, *v, document); + else if (v->IsArray()) { // Tuple validation + itemsTuple_ = static_cast(allocator_->Malloc(sizeof(const Schema*) * v->Size())); + SizeType index = 0; + for (ConstValueIterator itr = v->Begin(); itr != v->End(); ++itr, index++) + schemaDocument->CreateSchema(&itemsTuple_[itemsTupleCount_++], q.Append(index, allocator_), *itr, document); + } + } + + AssignIfExist(minItems_, value, GetMinItemsString()); + AssignIfExist(maxItems_, value, GetMaxItemsString()); + + if (const ValueType* v = GetMember(value, GetAdditionalItemsString())) { + if (v->IsBool()) + additionalItems_ = v->GetBool(); + else if (v->IsObject()) + schemaDocument->CreateSchema(&additionalItemsSchema_, p.Append(GetAdditionalItemsString(), allocator_), *v, document); + } + + AssignIfExist(uniqueItems_, value, GetUniqueItemsString()); + + // String + AssignIfExist(minLength_, value, GetMinLengthString()); + AssignIfExist(maxLength_, value, GetMaxLengthString()); + + if (const ValueType* v = GetMember(value, GetPatternString())) + pattern_ = CreatePattern(*v); + + // Number + if (const ValueType* v = GetMember(value, GetMinimumString())) + if (v->IsNumber()) + minimum_.CopyFrom(*v, *allocator_); + + if (const ValueType* v = GetMember(value, GetMaximumString())) + if (v->IsNumber()) + maximum_.CopyFrom(*v, *allocator_); + + AssignIfExist(exclusiveMinimum_, value, GetExclusiveMinimumString()); + AssignIfExist(exclusiveMaximum_, value, GetExclusiveMaximumString()); + + if (const ValueType* v = GetMember(value, GetMultipleOfString())) + if (v->IsNumber() && v->GetDouble() > 0.0) + multipleOf_.CopyFrom(*v, *allocator_); + } + + ~Schema() { + if (allocator_) { + allocator_->Free(enum_); + } + if (properties_) { + for (SizeType i = 0; i < propertyCount_; i++) + properties_[i].~Property(); + AllocatorType::Free(properties_); + } + if (patternProperties_) { + for (SizeType i = 0; i < patternPropertyCount_; i++) + patternProperties_[i].~PatternProperty(); + AllocatorType::Free(patternProperties_); + } + AllocatorType::Free(itemsTuple_); +#if RAPIDJSON_SCHEMA_HAS_REGEX + if (pattern_) { + pattern_->~RegexType(); + allocator_->Free(pattern_); + } +#endif + } + + bool BeginValue(Context& context) const { + if (context.inArray) { + if (uniqueItems_) + context.valueUniqueness = true; + + if (itemsList_) + context.valueSchema = itemsList_; + else if (itemsTuple_) { + if (context.arrayElementIndex < itemsTupleCount_) + context.valueSchema = itemsTuple_[context.arrayElementIndex]; + else if (additionalItemsSchema_) + context.valueSchema = additionalItemsSchema_; + else if (additionalItems_) + context.valueSchema = GetTypeless(); + else + RAPIDJSON_INVALID_KEYWORD_RETURN(GetItemsString()); + } + else + context.valueSchema = GetTypeless(); + + context.arrayElementIndex++; + } + return true; + } + + RAPIDJSON_FORCEINLINE bool EndValue(Context& context) const { + if (context.patternPropertiesValidatorCount > 0) { + bool otherValid = false; + SizeType count = context.patternPropertiesValidatorCount; + if (context.objectPatternValidatorType != Context::kPatternValidatorOnly) + otherValid = context.patternPropertiesValidators[--count]->IsValid(); + + bool patternValid = true; + for (SizeType i = 0; i < count; i++) + if (!context.patternPropertiesValidators[i]->IsValid()) { + patternValid = false; + break; + } + + if (context.objectPatternValidatorType == Context::kPatternValidatorOnly) { + if (!patternValid) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternPropertiesString()); + } + else if (context.objectPatternValidatorType == Context::kPatternValidatorWithProperty) { + if (!patternValid || !otherValid) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternPropertiesString()); + } + else if (!patternValid && !otherValid) // kPatternValidatorWithAdditionalProperty) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternPropertiesString()); + } + + if (enum_) { + const uint64_t h = context.factory.GetHashCode(context.hasher); + for (SizeType i = 0; i < enumCount_; i++) + if (enum_[i] == h) + goto foundEnum; + RAPIDJSON_INVALID_KEYWORD_RETURN(GetEnumString()); + foundEnum:; + } + + if (allOf_.schemas) + for (SizeType i = allOf_.begin; i < allOf_.begin + allOf_.count; i++) + if (!context.validators[i]->IsValid()) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetAllOfString()); + + if (anyOf_.schemas) { + for (SizeType i = anyOf_.begin; i < anyOf_.begin + anyOf_.count; i++) + if (context.validators[i]->IsValid()) + goto foundAny; + RAPIDJSON_INVALID_KEYWORD_RETURN(GetAnyOfString()); + foundAny:; + } + + if (oneOf_.schemas) { + bool oneValid = false; + for (SizeType i = oneOf_.begin; i < oneOf_.begin + oneOf_.count; i++) + if (context.validators[i]->IsValid()) { + if (oneValid) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetOneOfString()); + else + oneValid = true; + } + if (!oneValid) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetOneOfString()); + } + + if (not_ && context.validators[notValidatorIndex_]->IsValid()) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetNotString()); + + return true; + } + + bool Null(Context& context) const { + if (!(type_ & (1 << kNullSchemaType))) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + return CreateParallelValidator(context); + } + + bool Bool(Context& context, bool) const { + if (!(type_ & (1 << kBooleanSchemaType))) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + return CreateParallelValidator(context); + } + + bool Int(Context& context, int i) const { + if (!CheckInt(context, i)) + return false; + return CreateParallelValidator(context); + } + + bool Uint(Context& context, unsigned u) const { + if (!CheckUint(context, u)) + return false; + return CreateParallelValidator(context); + } + + bool Int64(Context& context, int64_t i) const { + if (!CheckInt(context, i)) + return false; + return CreateParallelValidator(context); + } + + bool Uint64(Context& context, uint64_t u) const { + if (!CheckUint(context, u)) + return false; + return CreateParallelValidator(context); + } + + bool Double(Context& context, double d) const { + if (!(type_ & (1 << kNumberSchemaType))) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + + if (!minimum_.IsNull() && !CheckDoubleMinimum(context, d)) + return false; + + if (!maximum_.IsNull() && !CheckDoubleMaximum(context, d)) + return false; + + if (!multipleOf_.IsNull() && !CheckDoubleMultipleOf(context, d)) + return false; + + return CreateParallelValidator(context); + } + + bool String(Context& context, const Ch* str, SizeType length, bool) const { + if (!(type_ & (1 << kStringSchemaType))) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + + if (minLength_ != 0 || maxLength_ != SizeType(~0)) { + SizeType count; + if (internal::CountStringCodePoint(str, length, &count)) { + if (count < minLength_) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinLengthString()); + if (count > maxLength_) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaxLengthString()); + } + } + + if (pattern_ && !IsPatternMatch(pattern_, str, length)) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternString()); + + return CreateParallelValidator(context); + } + + bool StartObject(Context& context) const { + if (!(type_ & (1 << kObjectSchemaType))) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + + if (hasDependencies_ || hasRequired_) { + context.propertyExist = static_cast(context.factory.MallocState(sizeof(bool) * propertyCount_)); + std::memset(context.propertyExist, 0, sizeof(bool) * propertyCount_); + } + + if (patternProperties_) { // pre-allocate schema array + SizeType count = patternPropertyCount_ + 1; // extra for valuePatternValidatorType + context.patternPropertiesSchemas = static_cast(context.factory.MallocState(sizeof(const SchemaType*) * count)); + context.patternPropertiesSchemaCount = 0; + std::memset(context.patternPropertiesSchemas, 0, sizeof(SchemaType*) * count); + } + + return CreateParallelValidator(context); + } + + bool Key(Context& context, const Ch* str, SizeType len, bool) const { + if (patternProperties_) { + context.patternPropertiesSchemaCount = 0; + for (SizeType i = 0; i < patternPropertyCount_; i++) + if (patternProperties_[i].pattern && IsPatternMatch(patternProperties_[i].pattern, str, len)) + context.patternPropertiesSchemas[context.patternPropertiesSchemaCount++] = patternProperties_[i].schema; + } + + SizeType index; + if (FindPropertyIndex(ValueType(str, len).Move(), &index)) { + if (context.patternPropertiesSchemaCount > 0) { + context.patternPropertiesSchemas[context.patternPropertiesSchemaCount++] = properties_[index].schema; + context.valueSchema = GetTypeless(); + context.valuePatternValidatorType = Context::kPatternValidatorWithProperty; + } + else + context.valueSchema = properties_[index].schema; + + if (context.propertyExist) + context.propertyExist[index] = true; + + return true; + } + + if (additionalPropertiesSchema_) { + if (additionalPropertiesSchema_ && context.patternPropertiesSchemaCount > 0) { + context.patternPropertiesSchemas[context.patternPropertiesSchemaCount++] = additionalPropertiesSchema_; + context.valueSchema = GetTypeless(); + context.valuePatternValidatorType = Context::kPatternValidatorWithAdditionalProperty; + } + else + context.valueSchema = additionalPropertiesSchema_; + return true; + } + else if (additionalProperties_) { + context.valueSchema = GetTypeless(); + return true; + } + + if (context.patternPropertiesSchemaCount == 0) // patternProperties are not additional properties + RAPIDJSON_INVALID_KEYWORD_RETURN(GetAdditionalPropertiesString()); + + return true; + } + + bool EndObject(Context& context, SizeType memberCount) const { + if (hasRequired_) + for (SizeType index = 0; index < propertyCount_; index++) + if (properties_[index].required) + if (!context.propertyExist[index]) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetRequiredString()); + + if (memberCount < minProperties_) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinPropertiesString()); + + if (memberCount > maxProperties_) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaxPropertiesString()); + + if (hasDependencies_) { + for (SizeType sourceIndex = 0; sourceIndex < propertyCount_; sourceIndex++) + if (context.propertyExist[sourceIndex]) { + if (properties_[sourceIndex].dependencies) { + for (SizeType targetIndex = 0; targetIndex < propertyCount_; targetIndex++) + if (properties_[sourceIndex].dependencies[targetIndex] && !context.propertyExist[targetIndex]) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetDependenciesString()); + } + else if (properties_[sourceIndex].dependenciesSchema) + if (!context.validators[properties_[sourceIndex].dependenciesValidatorIndex]->IsValid()) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetDependenciesString()); + } + } + + return true; + } + + bool StartArray(Context& context) const { + if (!(type_ & (1 << kArraySchemaType))) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + + context.arrayElementIndex = 0; + context.inArray = true; + + return CreateParallelValidator(context); + } + + bool EndArray(Context& context, SizeType elementCount) const { + context.inArray = false; + + if (elementCount < minItems_) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinItemsString()); + + if (elementCount > maxItems_) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaxItemsString()); + + return true; + } + + // Generate functions for string literal according to Ch +#define RAPIDJSON_STRING_(name, ...) \ + static const ValueType& Get##name##String() {\ + static const Ch s[] = { __VA_ARGS__, '\0' };\ + static const ValueType v(s, sizeof(s) / sizeof(Ch) - 1);\ + return v;\ + } + + RAPIDJSON_STRING_(Null, 'n', 'u', 'l', 'l') + RAPIDJSON_STRING_(Boolean, 'b', 'o', 'o', 'l', 'e', 'a', 'n') + RAPIDJSON_STRING_(Object, 'o', 'b', 'j', 'e', 'c', 't') + RAPIDJSON_STRING_(Array, 'a', 'r', 'r', 'a', 'y') + RAPIDJSON_STRING_(String, 's', 't', 'r', 'i', 'n', 'g') + RAPIDJSON_STRING_(Number, 'n', 'u', 'm', 'b', 'e', 'r') + RAPIDJSON_STRING_(Integer, 'i', 'n', 't', 'e', 'g', 'e', 'r') + RAPIDJSON_STRING_(Type, 't', 'y', 'p', 'e') + RAPIDJSON_STRING_(Enum, 'e', 'n', 'u', 'm') + RAPIDJSON_STRING_(AllOf, 'a', 'l', 'l', 'O', 'f') + RAPIDJSON_STRING_(AnyOf, 'a', 'n', 'y', 'O', 'f') + RAPIDJSON_STRING_(OneOf, 'o', 'n', 'e', 'O', 'f') + RAPIDJSON_STRING_(Not, 'n', 'o', 't') + RAPIDJSON_STRING_(Properties, 'p', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') + RAPIDJSON_STRING_(Required, 'r', 'e', 'q', 'u', 'i', 'r', 'e', 'd') + RAPIDJSON_STRING_(Dependencies, 'd', 'e', 'p', 'e', 'n', 'd', 'e', 'n', 'c', 'i', 'e', 's') + RAPIDJSON_STRING_(PatternProperties, 'p', 'a', 't', 't', 'e', 'r', 'n', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') + RAPIDJSON_STRING_(AdditionalProperties, 'a', 'd', 'd', 'i', 't', 'i', 'o', 'n', 'a', 'l', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') + RAPIDJSON_STRING_(MinProperties, 'm', 'i', 'n', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') + RAPIDJSON_STRING_(MaxProperties, 'm', 'a', 'x', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') + RAPIDJSON_STRING_(Items, 'i', 't', 'e', 'm', 's') + RAPIDJSON_STRING_(MinItems, 'm', 'i', 'n', 'I', 't', 'e', 'm', 's') + RAPIDJSON_STRING_(MaxItems, 'm', 'a', 'x', 'I', 't', 'e', 'm', 's') + RAPIDJSON_STRING_(AdditionalItems, 'a', 'd', 'd', 'i', 't', 'i', 'o', 'n', 'a', 'l', 'I', 't', 'e', 'm', 's') + RAPIDJSON_STRING_(UniqueItems, 'u', 'n', 'i', 'q', 'u', 'e', 'I', 't', 'e', 'm', 's') + RAPIDJSON_STRING_(MinLength, 'm', 'i', 'n', 'L', 'e', 'n', 'g', 't', 'h') + RAPIDJSON_STRING_(MaxLength, 'm', 'a', 'x', 'L', 'e', 'n', 'g', 't', 'h') + RAPIDJSON_STRING_(Pattern, 'p', 'a', 't', 't', 'e', 'r', 'n') + RAPIDJSON_STRING_(Minimum, 'm', 'i', 'n', 'i', 'm', 'u', 'm') + RAPIDJSON_STRING_(Maximum, 'm', 'a', 'x', 'i', 'm', 'u', 'm') + RAPIDJSON_STRING_(ExclusiveMinimum, 'e', 'x', 'c', 'l', 'u', 's', 'i', 'v', 'e', 'M', 'i', 'n', 'i', 'm', 'u', 'm') + RAPIDJSON_STRING_(ExclusiveMaximum, 'e', 'x', 'c', 'l', 'u', 's', 'i', 'v', 'e', 'M', 'a', 'x', 'i', 'm', 'u', 'm') + RAPIDJSON_STRING_(MultipleOf, 'm', 'u', 'l', 't', 'i', 'p', 'l', 'e', 'O', 'f') + +#undef RAPIDJSON_STRING_ + +private: + enum SchemaValueType { + kNullSchemaType, + kBooleanSchemaType, + kObjectSchemaType, + kArraySchemaType, + kStringSchemaType, + kNumberSchemaType, + kIntegerSchemaType, + kTotalSchemaType + }; + +#if RAPIDJSON_SCHEMA_USE_INTERNALREGEX + typedef internal::GenericRegex RegexType; +#elif RAPIDJSON_SCHEMA_USE_STDREGEX + typedef std::basic_regex RegexType; +#else + typedef char RegexType; +#endif + + struct SchemaArray { + SchemaArray() : schemas(), count() {} + ~SchemaArray() { AllocatorType::Free(schemas); } + const SchemaType** schemas; + SizeType begin; // begin index of context.validators + SizeType count; + }; + + static const SchemaType* GetTypeless() { + static SchemaType typeless(0, PointerType(), ValueType(kObjectType).Move(), ValueType(kObjectType).Move(), 0); + return &typeless; + } + + template + void AddUniqueElement(V1& a, const V2& v) { + for (typename V1::ConstValueIterator itr = a.Begin(); itr != a.End(); ++itr) + if (*itr == v) + return; + V1 c(v, *allocator_); + a.PushBack(c, *allocator_); + } + + static const ValueType* GetMember(const ValueType& value, const ValueType& name) { + typename ValueType::ConstMemberIterator itr = value.FindMember(name); + return itr != value.MemberEnd() ? &(itr->value) : 0; + } + + static void AssignIfExist(bool& out, const ValueType& value, const ValueType& name) { + if (const ValueType* v = GetMember(value, name)) + if (v->IsBool()) + out = v->GetBool(); + } + + static void AssignIfExist(SizeType& out, const ValueType& value, const ValueType& name) { + if (const ValueType* v = GetMember(value, name)) + if (v->IsUint64() && v->GetUint64() <= SizeType(~0)) + out = static_cast(v->GetUint64()); + } + + void AssignIfExist(SchemaArray& out, SchemaDocumentType& schemaDocument, const PointerType& p, const ValueType& value, const ValueType& name, const ValueType& document) { + if (const ValueType* v = GetMember(value, name)) { + if (v->IsArray() && v->Size() > 0) { + PointerType q = p.Append(name, allocator_); + out.count = v->Size(); + out.schemas = static_cast(allocator_->Malloc(out.count * sizeof(const Schema*))); + memset(out.schemas, 0, sizeof(Schema*)* out.count); + for (SizeType i = 0; i < out.count; i++) + schemaDocument.CreateSchema(&out.schemas[i], q.Append(i, allocator_), (*v)[i], document); + out.begin = validatorCount_; + validatorCount_ += out.count; + } + } + } + +#if RAPIDJSON_SCHEMA_USE_INTERNALREGEX + template + RegexType* CreatePattern(const ValueType& value) { + if (value.IsString()) { + RegexType* r = new (allocator_->Malloc(sizeof(RegexType))) RegexType(value.GetString()); + if (!r->IsValid()) { + r->~RegexType(); + AllocatorType::Free(r); + r = 0; + } + return r; + } + return 0; + } + + static bool IsPatternMatch(const RegexType* pattern, const Ch *str, SizeType) { + return pattern->Search(str); + } +#elif RAPIDJSON_SCHEMA_USE_STDREGEX + template + RegexType* CreatePattern(const ValueType& value) { + if (value.IsString()) + try { + return new (allocator_->Malloc(sizeof(RegexType))) RegexType(value.GetString(), std::size_t(value.GetStringLength()), std::regex_constants::ECMAScript); + } + catch (const std::regex_error&) { + } + return 0; + } + + static bool IsPatternMatch(const RegexType* pattern, const Ch *str, SizeType length) { + std::match_results r; + return std::regex_search(str, str + length, r, *pattern); + } +#else + template + RegexType* CreatePattern(const ValueType&) { return 0; } + + static bool IsPatternMatch(const RegexType*, const Ch *, SizeType) { return true; } +#endif // RAPIDJSON_SCHEMA_USE_STDREGEX + + void AddType(const ValueType& type) { + if (type == GetNullString() ) type_ |= 1 << kNullSchemaType; + else if (type == GetBooleanString()) type_ |= 1 << kBooleanSchemaType; + else if (type == GetObjectString() ) type_ |= 1 << kObjectSchemaType; + else if (type == GetArrayString() ) type_ |= 1 << kArraySchemaType; + else if (type == GetStringString() ) type_ |= 1 << kStringSchemaType; + else if (type == GetIntegerString()) type_ |= 1 << kIntegerSchemaType; + else if (type == GetNumberString() ) type_ |= (1 << kNumberSchemaType) | (1 << kIntegerSchemaType); + } + + bool CreateParallelValidator(Context& context) const { + if (enum_ || context.arrayUniqueness) + context.hasher = context.factory.CreateHasher(); + + if (validatorCount_) { + RAPIDJSON_ASSERT(context.validators == 0); + context.validators = static_cast(context.factory.MallocState(sizeof(ISchemaValidator*) * validatorCount_)); + context.validatorCount = validatorCount_; + + if (allOf_.schemas) + CreateSchemaValidators(context, allOf_); + + if (anyOf_.schemas) + CreateSchemaValidators(context, anyOf_); + + if (oneOf_.schemas) + CreateSchemaValidators(context, oneOf_); + + if (not_) + context.validators[notValidatorIndex_] = context.factory.CreateSchemaValidator(*not_); + + if (hasSchemaDependencies_) { + for (SizeType i = 0; i < propertyCount_; i++) + if (properties_[i].dependenciesSchema) + context.validators[properties_[i].dependenciesValidatorIndex] = context.factory.CreateSchemaValidator(*properties_[i].dependenciesSchema); + } + } + + return true; + } + + void CreateSchemaValidators(Context& context, const SchemaArray& schemas) const { + for (SizeType i = 0; i < schemas.count; i++) + context.validators[schemas.begin + i] = context.factory.CreateSchemaValidator(*schemas.schemas[i]); + } + + // O(n) + bool FindPropertyIndex(const ValueType& name, SizeType* outIndex) const { + SizeType len = name.GetStringLength(); + const Ch* str = name.GetString(); + for (SizeType index = 0; index < propertyCount_; index++) + if (properties_[index].name.GetStringLength() == len && + (std::memcmp(properties_[index].name.GetString(), str, sizeof(Ch) * len) == 0)) + { + *outIndex = index; + return true; + } + return false; + } + + bool CheckInt(Context& context, int64_t i) const { + if (!(type_ & ((1 << kIntegerSchemaType) | (1 << kNumberSchemaType)))) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + + if (!minimum_.IsNull()) { + if (minimum_.IsInt64()) { + if (exclusiveMinimum_ ? i <= minimum_.GetInt64() : i < minimum_.GetInt64()) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString()); + } + else if (minimum_.IsUint64()) { + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString()); // i <= max(int64_t) < minimum.GetUint64() + } + else if (!CheckDoubleMinimum(context, static_cast(i))) + return false; + } + + if (!maximum_.IsNull()) { + if (maximum_.IsInt64()) { + if (exclusiveMaximum_ ? i >= maximum_.GetInt64() : i > maximum_.GetInt64()) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString()); + } + else if (maximum_.IsUint64()) + /* do nothing */; // i <= max(int64_t) < maximum_.GetUint64() + else if (!CheckDoubleMaximum(context, static_cast(i))) + return false; + } + + if (!multipleOf_.IsNull()) { + if (multipleOf_.IsUint64()) { + if (static_cast(i >= 0 ? i : -i) % multipleOf_.GetUint64() != 0) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMultipleOfString()); + } + else if (!CheckDoubleMultipleOf(context, static_cast(i))) + return false; + } + + return true; + } + + bool CheckUint(Context& context, uint64_t i) const { + if (!(type_ & ((1 << kIntegerSchemaType) | (1 << kNumberSchemaType)))) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + + if (!minimum_.IsNull()) { + if (minimum_.IsUint64()) { + if (exclusiveMinimum_ ? i <= minimum_.GetUint64() : i < minimum_.GetUint64()) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString()); + } + else if (minimum_.IsInt64()) + /* do nothing */; // i >= 0 > minimum.Getint64() + else if (!CheckDoubleMinimum(context, static_cast(i))) + return false; + } + + if (!maximum_.IsNull()) { + if (maximum_.IsUint64()) { + if (exclusiveMaximum_ ? i >= maximum_.GetUint64() : i > maximum_.GetUint64()) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString()); + } + else if (maximum_.IsInt64()) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString()); // i >= 0 > maximum_ + else if (!CheckDoubleMaximum(context, static_cast(i))) + return false; + } + + if (!multipleOf_.IsNull()) { + if (multipleOf_.IsUint64()) { + if (i % multipleOf_.GetUint64() != 0) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMultipleOfString()); + } + else if (!CheckDoubleMultipleOf(context, static_cast(i))) + return false; + } + + return true; + } + + bool CheckDoubleMinimum(Context& context, double d) const { + if (exclusiveMinimum_ ? d <= minimum_.GetDouble() : d < minimum_.GetDouble()) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString()); + return true; + } + + bool CheckDoubleMaximum(Context& context, double d) const { + if (exclusiveMaximum_ ? d >= maximum_.GetDouble() : d > maximum_.GetDouble()) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString()); + return true; + } + + bool CheckDoubleMultipleOf(Context& context, double d) const { + double a = std::abs(d), b = std::abs(multipleOf_.GetDouble()); + double q = std::floor(a / b); + double r = a - q * b; + if (r > 0.0) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMultipleOfString()); + return true; + } + + struct Property { + Property() : schema(), dependenciesSchema(), dependenciesValidatorIndex(), dependencies(), required(false) {} + ~Property() { AllocatorType::Free(dependencies); } + SValue name; + const SchemaType* schema; + const SchemaType* dependenciesSchema; + SizeType dependenciesValidatorIndex; + bool* dependencies; + bool required; + }; + + struct PatternProperty { + PatternProperty() : schema(), pattern() {} + ~PatternProperty() { + if (pattern) { + pattern->~RegexType(); + AllocatorType::Free(pattern); + } + } + const SchemaType* schema; + RegexType* pattern; + }; + + AllocatorType* allocator_; + uint64_t* enum_; + SizeType enumCount_; + SchemaArray allOf_; + SchemaArray anyOf_; + SchemaArray oneOf_; + const SchemaType* not_; + unsigned type_; // bitmask of kSchemaType + SizeType validatorCount_; + SizeType notValidatorIndex_; + + Property* properties_; + const SchemaType* additionalPropertiesSchema_; + PatternProperty* patternProperties_; + SizeType patternPropertyCount_; + SizeType propertyCount_; + SizeType minProperties_; + SizeType maxProperties_; + bool additionalProperties_; + bool hasDependencies_; + bool hasRequired_; + bool hasSchemaDependencies_; + + const SchemaType* additionalItemsSchema_; + const SchemaType* itemsList_; + const SchemaType** itemsTuple_; + SizeType itemsTupleCount_; + SizeType minItems_; + SizeType maxItems_; + bool additionalItems_; + bool uniqueItems_; + + RegexType* pattern_; + SizeType minLength_; + SizeType maxLength_; + + SValue minimum_; + SValue maximum_; + SValue multipleOf_; + bool exclusiveMinimum_; + bool exclusiveMaximum_; +}; + +template +struct TokenHelper { + RAPIDJSON_FORCEINLINE static void AppendIndexToken(Stack& documentStack, SizeType index) { + *documentStack.template Push() = '/'; + char buffer[21]; + size_t length = static_cast((sizeof(SizeType) == 4 ? u32toa(index, buffer) : u64toa(index, buffer)) - buffer); + for (size_t i = 0; i < length; i++) + *documentStack.template Push() = buffer[i]; + } +}; + +// Partial specialized version for char to prevent buffer copying. +template +struct TokenHelper { + RAPIDJSON_FORCEINLINE static void AppendIndexToken(Stack& documentStack, SizeType index) { + if (sizeof(SizeType) == 4) { + char *buffer = documentStack.template Push(1 + 10); // '/' + uint + *buffer++ = '/'; + const char* end = internal::u32toa(index, buffer); + documentStack.template Pop(static_cast(10 - (end - buffer))); + } + else { + char *buffer = documentStack.template Push(1 + 20); // '/' + uint64 + *buffer++ = '/'; + const char* end = internal::u64toa(index, buffer); + documentStack.template Pop(static_cast(20 - (end - buffer))); + } + } +}; + +} // namespace internal + +/////////////////////////////////////////////////////////////////////////////// +// IGenericRemoteSchemaDocumentProvider + +template +class IGenericRemoteSchemaDocumentProvider { +public: + typedef typename SchemaDocumentType::Ch Ch; + + virtual ~IGenericRemoteSchemaDocumentProvider() {} + virtual const SchemaDocumentType* GetRemoteDocument(const Ch* uri, SizeType length) = 0; +}; + +/////////////////////////////////////////////////////////////////////////////// +// GenericSchemaDocument + +//! JSON schema document. +/*! + A JSON schema document is a compiled version of a JSON schema. + It is basically a tree of internal::Schema. + + \note This is an immutable class (i.e. its instance cannot be modified after construction). + \tparam ValueT Type of JSON value (e.g. \c Value ), which also determine the encoding. + \tparam Allocator Allocator type for allocating memory of this document. +*/ +template +class GenericSchemaDocument { +public: + typedef ValueT ValueType; + typedef IGenericRemoteSchemaDocumentProvider IRemoteSchemaDocumentProviderType; + typedef Allocator AllocatorType; + typedef typename ValueType::EncodingType EncodingType; + typedef typename EncodingType::Ch Ch; + typedef internal::Schema SchemaType; + typedef GenericPointer PointerType; + friend class internal::Schema; + template + friend class GenericSchemaValidator; + + //! Constructor. + /*! + Compile a JSON document into schema document. + + \param document A JSON document as source. + \param remoteProvider An optional remote schema document provider for resolving remote reference. Can be null. + \param allocator An optional allocator instance for allocating memory. Can be null. + */ + explicit GenericSchemaDocument(const ValueType& document, IRemoteSchemaDocumentProviderType* remoteProvider = 0, Allocator* allocator = 0) : + remoteProvider_(remoteProvider), + allocator_(allocator), + ownAllocator_(), + root_(), + schemaMap_(allocator, kInitialSchemaMapSize), + schemaRef_(allocator, kInitialSchemaRefSize) + { + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator()); + + // Generate root schema, it will call CreateSchema() to create sub-schemas, + // And call AddRefSchema() if there are $ref. + CreateSchemaRecursive(&root_, PointerType(), document, document); + + // Resolve $ref + while (!schemaRef_.Empty()) { + SchemaRefEntry* refEntry = schemaRef_.template Pop(1); + if (const SchemaType* s = GetSchema(refEntry->target)) { + if (refEntry->schema) + *refEntry->schema = s; + + // Create entry in map if not exist + if (!GetSchema(refEntry->source)) { + new (schemaMap_.template Push()) SchemaEntry(refEntry->source, const_cast(s), false, allocator_); + } + } + refEntry->~SchemaRefEntry(); + } + + RAPIDJSON_ASSERT(root_ != 0); + + schemaRef_.ShrinkToFit(); // Deallocate all memory for ref + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Move constructor in C++11 + GenericSchemaDocument(GenericSchemaDocument&& rhs) RAPIDJSON_NOEXCEPT : + remoteProvider_(rhs.remoteProvider_), + allocator_(rhs.allocator_), + ownAllocator_(rhs.ownAllocator_), + root_(rhs.root_), + schemaMap_(std::move(rhs.schemaMap_)), + schemaRef_(std::move(rhs.schemaRef_)) + { + rhs.remoteProvider_ = 0; + rhs.allocator_ = 0; + rhs.ownAllocator_ = 0; + } +#endif + + //! Destructor + ~GenericSchemaDocument() { + while (!schemaMap_.Empty()) + schemaMap_.template Pop(1)->~SchemaEntry(); + + RAPIDJSON_DELETE(ownAllocator_); + } + + //! Get the root schema. + const SchemaType& GetRoot() const { return *root_; } + +private: + //! Prohibit copying + GenericSchemaDocument(const GenericSchemaDocument&); + //! Prohibit assignment + GenericSchemaDocument& operator=(const GenericSchemaDocument&); + + struct SchemaRefEntry { + SchemaRefEntry(const PointerType& s, const PointerType& t, const SchemaType** outSchema, Allocator *allocator) : source(s, allocator), target(t, allocator), schema(outSchema) {} + PointerType source; + PointerType target; + const SchemaType** schema; + }; + + struct SchemaEntry { + SchemaEntry(const PointerType& p, SchemaType* s, bool o, Allocator* allocator) : pointer(p, allocator), schema(s), owned(o) {} + ~SchemaEntry() { + if (owned) { + schema->~SchemaType(); + Allocator::Free(schema); + } + } + PointerType pointer; + SchemaType* schema; + bool owned; + }; + + void CreateSchemaRecursive(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document) { + if (schema) + *schema = SchemaType::GetTypeless(); + + if (v.GetType() == kObjectType) { + const SchemaType* s = GetSchema(pointer); + if (!s) + CreateSchema(schema, pointer, v, document); + + for (typename ValueType::ConstMemberIterator itr = v.MemberBegin(); itr != v.MemberEnd(); ++itr) + CreateSchemaRecursive(0, pointer.Append(itr->name, allocator_), itr->value, document); + } + else if (v.GetType() == kArrayType) + for (SizeType i = 0; i < v.Size(); i++) + CreateSchemaRecursive(0, pointer.Append(i, allocator_), v[i], document); + } + + void CreateSchema(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document) { + RAPIDJSON_ASSERT(pointer.IsValid()); + if (v.IsObject()) { + if (!HandleRefSchema(pointer, schema, v, document)) { + SchemaType* s = new (allocator_->Malloc(sizeof(SchemaType))) SchemaType(this, pointer, v, document, allocator_); + new (schemaMap_.template Push()) SchemaEntry(pointer, s, true, allocator_); + if (schema) + *schema = s; + } + } + } + + bool HandleRefSchema(const PointerType& source, const SchemaType** schema, const ValueType& v, const ValueType& document) { + static const Ch kRefString[] = { '$', 'r', 'e', 'f', '\0' }; + static const ValueType kRefValue(kRefString, 4); + + typename ValueType::ConstMemberIterator itr = v.FindMember(kRefValue); + if (itr == v.MemberEnd()) + return false; + + if (itr->value.IsString()) { + SizeType len = itr->value.GetStringLength(); + if (len > 0) { + const Ch* s = itr->value.GetString(); + SizeType i = 0; + while (i < len && s[i] != '#') // Find the first # + i++; + + if (i > 0) { // Remote reference, resolve immediately + if (remoteProvider_) { + if (const GenericSchemaDocument* remoteDocument = remoteProvider_->GetRemoteDocument(s, i - 1)) { + PointerType pointer(&s[i], len - i, allocator_); + if (pointer.IsValid()) { + if (const SchemaType* sc = remoteDocument->GetSchema(pointer)) { + if (schema) + *schema = sc; + return true; + } + } + } + } + } + else if (s[i] == '#') { // Local reference, defer resolution + PointerType pointer(&s[i], len - i, allocator_); + if (pointer.IsValid()) { + if (const ValueType* nv = pointer.Get(document)) + if (HandleRefSchema(source, schema, *nv, document)) + return true; + + new (schemaRef_.template Push()) SchemaRefEntry(source, pointer, schema, allocator_); + return true; + } + } + } + } + return false; + } + + const SchemaType* GetSchema(const PointerType& pointer) const { + for (const SchemaEntry* target = schemaMap_.template Bottom(); target != schemaMap_.template End(); ++target) + if (pointer == target->pointer) + return target->schema; + return 0; + } + + PointerType GetPointer(const SchemaType* schema) const { + for (const SchemaEntry* target = schemaMap_.template Bottom(); target != schemaMap_.template End(); ++target) + if (schema == target->schema) + return target->pointer; + return PointerType(); + } + + static const size_t kInitialSchemaMapSize = 64; + static const size_t kInitialSchemaRefSize = 64; + + IRemoteSchemaDocumentProviderType* remoteProvider_; + Allocator *allocator_; + Allocator *ownAllocator_; + const SchemaType* root_; //!< Root schema. + internal::Stack schemaMap_; // Stores created Pointer -> Schemas + internal::Stack schemaRef_; // Stores Pointer from $ref and schema which holds the $ref +}; + +//! GenericSchemaDocument using Value type. +typedef GenericSchemaDocument SchemaDocument; +//! IGenericRemoteSchemaDocumentProvider using SchemaDocument. +typedef IGenericRemoteSchemaDocumentProvider IRemoteSchemaDocumentProvider; + +/////////////////////////////////////////////////////////////////////////////// +// GenericSchemaValidator + +//! JSON Schema Validator. +/*! + A SAX style JSON schema validator. + It uses a \c GenericSchemaDocument to validate SAX events. + It delegates the incoming SAX events to an output handler. + The default output handler does nothing. + It can be reused multiple times by calling \c Reset(). + + \tparam SchemaDocumentType Type of schema document. + \tparam OutputHandler Type of output handler. Default handler does nothing. + \tparam StateAllocator Allocator for storing the internal validation states. +*/ +template < + typename SchemaDocumentType, + typename OutputHandler = BaseReaderHandler, + typename StateAllocator = CrtAllocator> +class GenericSchemaValidator : + public internal::ISchemaStateFactory, + public internal::ISchemaValidator +{ +public: + typedef typename SchemaDocumentType::SchemaType SchemaType; + typedef typename SchemaDocumentType::PointerType PointerType; + typedef typename SchemaType::EncodingType EncodingType; + typedef typename EncodingType::Ch Ch; + + //! Constructor without output handler. + /*! + \param schemaDocument The schema document to conform to. + \param allocator Optional allocator for storing internal validation states. + \param schemaStackCapacity Optional initial capacity of schema path stack. + \param documentStackCapacity Optional initial capacity of document path stack. + */ + GenericSchemaValidator( + const SchemaDocumentType& schemaDocument, + StateAllocator* allocator = 0, + size_t schemaStackCapacity = kDefaultSchemaStackCapacity, + size_t documentStackCapacity = kDefaultDocumentStackCapacity) + : + schemaDocument_(&schemaDocument), + root_(schemaDocument.GetRoot()), + outputHandler_(GetNullHandler()), + stateAllocator_(allocator), + ownStateAllocator_(0), + schemaStack_(allocator, schemaStackCapacity), + documentStack_(allocator, documentStackCapacity), + valid_(true) +#if RAPIDJSON_SCHEMA_VERBOSE + , depth_(0) +#endif + { + } + + //! Constructor with output handler. + /*! + \param schemaDocument The schema document to conform to. + \param allocator Optional allocator for storing internal validation states. + \param schemaStackCapacity Optional initial capacity of schema path stack. + \param documentStackCapacity Optional initial capacity of document path stack. + */ + GenericSchemaValidator( + const SchemaDocumentType& schemaDocument, + OutputHandler& outputHandler, + StateAllocator* allocator = 0, + size_t schemaStackCapacity = kDefaultSchemaStackCapacity, + size_t documentStackCapacity = kDefaultDocumentStackCapacity) + : + schemaDocument_(&schemaDocument), + root_(schemaDocument.GetRoot()), + outputHandler_(outputHandler), + stateAllocator_(allocator), + ownStateAllocator_(0), + schemaStack_(allocator, schemaStackCapacity), + documentStack_(allocator, documentStackCapacity), + valid_(true) +#if RAPIDJSON_SCHEMA_VERBOSE + , depth_(0) +#endif + { + } + + //! Destructor. + ~GenericSchemaValidator() { + Reset(); + RAPIDJSON_DELETE(ownStateAllocator_); + } + + //! Reset the internal states. + void Reset() { + while (!schemaStack_.Empty()) + PopSchema(); + documentStack_.Clear(); + valid_ = true; + } + + //! Checks whether the current state is valid. + // Implementation of ISchemaValidator + virtual bool IsValid() const { return valid_; } + + //! Gets the JSON pointer pointed to the invalid schema. + PointerType GetInvalidSchemaPointer() const { + return schemaStack_.Empty() ? PointerType() : schemaDocument_->GetPointer(&CurrentSchema()); + } + + //! Gets the keyword of invalid schema. + const Ch* GetInvalidSchemaKeyword() const { + return schemaStack_.Empty() ? 0 : CurrentContext().invalidKeyword; + } + + //! Gets the JSON pointer pointed to the invalid value. + PointerType GetInvalidDocumentPointer() const { + return documentStack_.Empty() ? PointerType() : PointerType(documentStack_.template Bottom(), documentStack_.GetSize() / sizeof(Ch)); + } + +#if RAPIDJSON_SCHEMA_VERBOSE +#define RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_() \ +RAPIDJSON_MULTILINEMACRO_BEGIN\ + *documentStack_.template Push() = '\0';\ + documentStack_.template Pop(1);\ + internal::PrintInvalidDocument(documentStack_.template Bottom());\ +RAPIDJSON_MULTILINEMACRO_END +#else +#define RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_() +#endif + +#define RAPIDJSON_SCHEMA_HANDLE_BEGIN_(method, arg1)\ + if (!valid_) return false; \ + if (!BeginValue() || !CurrentSchema().method arg1) {\ + RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_();\ + return valid_ = false;\ + } + +#define RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(method, arg2)\ + for (Context* context = schemaStack_.template Bottom(); context != schemaStack_.template End(); context++) {\ + if (context->hasher)\ + static_cast(context->hasher)->method arg2;\ + if (context->validators)\ + for (SizeType i_ = 0; i_ < context->validatorCount; i_++)\ + static_cast(context->validators[i_])->method arg2;\ + if (context->patternPropertiesValidators)\ + for (SizeType i_ = 0; i_ < context->patternPropertiesValidatorCount; i_++)\ + static_cast(context->patternPropertiesValidators[i_])->method arg2;\ + } + +#define RAPIDJSON_SCHEMA_HANDLE_END_(method, arg2)\ + return valid_ = EndValue() && outputHandler_.method arg2 + +#define RAPIDJSON_SCHEMA_HANDLE_VALUE_(method, arg1, arg2) \ + RAPIDJSON_SCHEMA_HANDLE_BEGIN_ (method, arg1);\ + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(method, arg2);\ + RAPIDJSON_SCHEMA_HANDLE_END_ (method, arg2) + + bool Null() { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Null, (CurrentContext() ), ( )); } + bool Bool(bool b) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Bool, (CurrentContext(), b), (b)); } + bool Int(int i) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Int, (CurrentContext(), i), (i)); } + bool Uint(unsigned u) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Uint, (CurrentContext(), u), (u)); } + bool Int64(int64_t i) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Int64, (CurrentContext(), i), (i)); } + bool Uint64(uint64_t u) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Uint64, (CurrentContext(), u), (u)); } + bool Double(double d) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Double, (CurrentContext(), d), (d)); } + bool RawNumber(const Ch* str, SizeType length, bool copy) + { RAPIDJSON_SCHEMA_HANDLE_VALUE_(String, (CurrentContext(), str, length, copy), (str, length, copy)); } + bool String(const Ch* str, SizeType length, bool copy) + { RAPIDJSON_SCHEMA_HANDLE_VALUE_(String, (CurrentContext(), str, length, copy), (str, length, copy)); } + + bool StartObject() { + RAPIDJSON_SCHEMA_HANDLE_BEGIN_(StartObject, (CurrentContext())); + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(StartObject, ()); + return valid_ = outputHandler_.StartObject(); + } + + bool Key(const Ch* str, SizeType len, bool copy) { + if (!valid_) return false; + AppendToken(str, len); + if (!CurrentSchema().Key(CurrentContext(), str, len, copy)) return valid_ = false; + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(Key, (str, len, copy)); + return valid_ = outputHandler_.Key(str, len, copy); + } + + bool EndObject(SizeType memberCount) { + if (!valid_) return false; + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(EndObject, (memberCount)); + if (!CurrentSchema().EndObject(CurrentContext(), memberCount)) return valid_ = false; + RAPIDJSON_SCHEMA_HANDLE_END_(EndObject, (memberCount)); + } + + bool StartArray() { + RAPIDJSON_SCHEMA_HANDLE_BEGIN_(StartArray, (CurrentContext())); + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(StartArray, ()); + return valid_ = outputHandler_.StartArray(); + } + + bool EndArray(SizeType elementCount) { + if (!valid_) return false; + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(EndArray, (elementCount)); + if (!CurrentSchema().EndArray(CurrentContext(), elementCount)) return valid_ = false; + RAPIDJSON_SCHEMA_HANDLE_END_(EndArray, (elementCount)); + } + +#undef RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_ +#undef RAPIDJSON_SCHEMA_HANDLE_BEGIN_ +#undef RAPIDJSON_SCHEMA_HANDLE_PARALLEL_ +#undef RAPIDJSON_SCHEMA_HANDLE_VALUE_ + + // Implementation of ISchemaStateFactory + virtual ISchemaValidator* CreateSchemaValidator(const SchemaType& root) { + return new (GetStateAllocator().Malloc(sizeof(GenericSchemaValidator))) GenericSchemaValidator(*schemaDocument_, root, +#if RAPIDJSON_SCHEMA_VERBOSE + depth_ + 1, +#endif + &GetStateAllocator()); + } + + virtual void DestroySchemaValidator(ISchemaValidator* validator) { + GenericSchemaValidator* v = static_cast(validator); + v->~GenericSchemaValidator(); + StateAllocator::Free(v); + } + + virtual void* CreateHasher() { + return new (GetStateAllocator().Malloc(sizeof(HasherType))) HasherType(&GetStateAllocator()); + } + + virtual uint64_t GetHashCode(void* hasher) { + return static_cast(hasher)->GetHashCode(); + } + + virtual void DestroryHasher(void* hasher) { + HasherType* h = static_cast(hasher); + h->~HasherType(); + StateAllocator::Free(h); + } + + virtual void* MallocState(size_t size) { + return GetStateAllocator().Malloc(size); + } + + virtual void FreeState(void* p) { + return StateAllocator::Free(p); + } + +private: + typedef typename SchemaType::Context Context; + typedef GenericValue, StateAllocator> HashCodeArray; + typedef internal::Hasher HasherType; + + GenericSchemaValidator( + const SchemaDocumentType& schemaDocument, + const SchemaType& root, +#if RAPIDJSON_SCHEMA_VERBOSE + unsigned depth, +#endif + StateAllocator* allocator = 0, + size_t schemaStackCapacity = kDefaultSchemaStackCapacity, + size_t documentStackCapacity = kDefaultDocumentStackCapacity) + : + schemaDocument_(&schemaDocument), + root_(root), + outputHandler_(GetNullHandler()), + stateAllocator_(allocator), + ownStateAllocator_(0), + schemaStack_(allocator, schemaStackCapacity), + documentStack_(allocator, documentStackCapacity), + valid_(true) +#if RAPIDJSON_SCHEMA_VERBOSE + , depth_(depth) +#endif + { + } + + StateAllocator& GetStateAllocator() { + if (!stateAllocator_) + stateAllocator_ = ownStateAllocator_ = RAPIDJSON_NEW(StateAllocator()); + return *stateAllocator_; + } + + bool BeginValue() { + if (schemaStack_.Empty()) + PushSchema(root_); + else { + if (CurrentContext().inArray) + internal::TokenHelper, Ch>::AppendIndexToken(documentStack_, CurrentContext().arrayElementIndex); + + if (!CurrentSchema().BeginValue(CurrentContext())) + return false; + + SizeType count = CurrentContext().patternPropertiesSchemaCount; + const SchemaType** sa = CurrentContext().patternPropertiesSchemas; + typename Context::PatternValidatorType patternValidatorType = CurrentContext().valuePatternValidatorType; + bool valueUniqueness = CurrentContext().valueUniqueness; + if (CurrentContext().valueSchema) + PushSchema(*CurrentContext().valueSchema); + + if (count > 0) { + CurrentContext().objectPatternValidatorType = patternValidatorType; + ISchemaValidator**& va = CurrentContext().patternPropertiesValidators; + SizeType& validatorCount = CurrentContext().patternPropertiesValidatorCount; + va = static_cast(MallocState(sizeof(ISchemaValidator*) * count)); + for (SizeType i = 0; i < count; i++) + va[validatorCount++] = CreateSchemaValidator(*sa[i]); + } + + CurrentContext().arrayUniqueness = valueUniqueness; + } + return true; + } + + bool EndValue() { + if (!CurrentSchema().EndValue(CurrentContext())) + return false; + +#if RAPIDJSON_SCHEMA_VERBOSE + GenericStringBuffer sb; + schemaDocument_->GetPointer(&CurrentSchema()).Stringify(sb); + + *documentStack_.template Push() = '\0'; + documentStack_.template Pop(1); + internal::PrintValidatorPointers(depth_, sb.GetString(), documentStack_.template Bottom()); +#endif + + uint64_t h = CurrentContext().arrayUniqueness ? static_cast(CurrentContext().hasher)->GetHashCode() : 0; + + PopSchema(); + + if (!schemaStack_.Empty()) { + Context& context = CurrentContext(); + if (context.valueUniqueness) { + HashCodeArray* a = static_cast(context.arrayElementHashCodes); + if (!a) + CurrentContext().arrayElementHashCodes = a = new (GetStateAllocator().Malloc(sizeof(HashCodeArray))) HashCodeArray(kArrayType); + for (typename HashCodeArray::ConstValueIterator itr = a->Begin(); itr != a->End(); ++itr) + if (itr->GetUint64() == h) + RAPIDJSON_INVALID_KEYWORD_RETURN(SchemaType::GetUniqueItemsString()); + a->PushBack(h, GetStateAllocator()); + } + } + + // Remove the last token of document pointer + while (!documentStack_.Empty() && *documentStack_.template Pop(1) != '/') + ; + + return true; + } + + void AppendToken(const Ch* str, SizeType len) { + documentStack_.template Reserve(1 + len * 2); // worst case all characters are escaped as two characters + *documentStack_.template PushUnsafe() = '/'; + for (SizeType i = 0; i < len; i++) { + if (str[i] == '~') { + *documentStack_.template PushUnsafe() = '~'; + *documentStack_.template PushUnsafe() = '0'; + } + else if (str[i] == '/') { + *documentStack_.template PushUnsafe() = '~'; + *documentStack_.template PushUnsafe() = '1'; + } + else + *documentStack_.template PushUnsafe() = str[i]; + } + } + + RAPIDJSON_FORCEINLINE void PushSchema(const SchemaType& schema) { new (schemaStack_.template Push()) Context(*this, &schema); } + + RAPIDJSON_FORCEINLINE void PopSchema() { + Context* c = schemaStack_.template Pop(1); + if (HashCodeArray* a = static_cast(c->arrayElementHashCodes)) { + a->~HashCodeArray(); + StateAllocator::Free(a); + } + c->~Context(); + } + + const SchemaType& CurrentSchema() const { return *schemaStack_.template Top()->schema; } + Context& CurrentContext() { return *schemaStack_.template Top(); } + const Context& CurrentContext() const { return *schemaStack_.template Top(); } + + static OutputHandler& GetNullHandler() { + static OutputHandler nullHandler; + return nullHandler; + } + + static const size_t kDefaultSchemaStackCapacity = 1024; + static const size_t kDefaultDocumentStackCapacity = 256; + const SchemaDocumentType* schemaDocument_; + const SchemaType& root_; + OutputHandler& outputHandler_; + StateAllocator* stateAllocator_; + StateAllocator* ownStateAllocator_; + internal::Stack schemaStack_; //!< stack to store the current path of schema (BaseSchemaType *) + internal::Stack documentStack_; //!< stack to store the current path of validating document (Ch) + bool valid_; +#if RAPIDJSON_SCHEMA_VERBOSE + unsigned depth_; +#endif +}; + +typedef GenericSchemaValidator SchemaValidator; + +/////////////////////////////////////////////////////////////////////////////// +// SchemaValidatingReader + +//! A helper class for parsing with validation. +/*! + This helper class is a functor, designed as a parameter of \ref GenericDocument::Populate(). + + \tparam parseFlags Combination of \ref ParseFlag. + \tparam InputStream Type of input stream, implementing Stream concept. + \tparam SourceEncoding Encoding of the input stream. + \tparam SchemaDocumentType Type of schema document. + \tparam StackAllocator Allocator type for stack. +*/ +template < + unsigned parseFlags, + typename InputStream, + typename SourceEncoding, + typename SchemaDocumentType = SchemaDocument, + typename StackAllocator = CrtAllocator> +class SchemaValidatingReader { +public: + typedef typename SchemaDocumentType::PointerType PointerType; + typedef typename InputStream::Ch Ch; + + //! Constructor + /*! + \param is Input stream. + \param sd Schema document. + */ + SchemaValidatingReader(InputStream& is, const SchemaDocumentType& sd) : is_(is), sd_(sd), invalidSchemaKeyword_(), isValid_(true) {} + + template + bool operator()(Handler& handler) { + GenericReader reader; + GenericSchemaValidator validator(sd_, handler); + parseResult_ = reader.template Parse(is_, validator); + + isValid_ = validator.IsValid(); + if (isValid_) { + invalidSchemaPointer_ = PointerType(); + invalidSchemaKeyword_ = 0; + invalidDocumentPointer_ = PointerType(); + } + else { + invalidSchemaPointer_ = validator.GetInvalidSchemaPointer(); + invalidSchemaKeyword_ = validator.GetInvalidSchemaKeyword(); + invalidDocumentPointer_ = validator.GetInvalidDocumentPointer(); + } + + return parseResult_; + } + + const ParseResult& GetParseResult() const { return parseResult_; } + bool IsValid() const { return isValid_; } + const PointerType& GetInvalidSchemaPointer() const { return invalidSchemaPointer_; } + const Ch* GetInvalidSchemaKeyword() const { return invalidSchemaKeyword_; } + const PointerType& GetInvalidDocumentPointer() const { return invalidDocumentPointer_; } + +private: + InputStream& is_; + const SchemaDocumentType& sd_; + + ParseResult parseResult_; + PointerType invalidSchemaPointer_; + const Ch* invalidSchemaKeyword_; + PointerType invalidDocumentPointer_; + bool isValid_; +}; + +RAPIDJSON_NAMESPACE_END +RAPIDJSON_DIAG_POP + +#endif // RAPIDJSON_SCHEMA_H_ diff --git a/Source/Dependencies/rapidjson/include/rapidjson/stream.h b/Source/Dependencies/rapidjson/include/rapidjson/stream.h new file mode 100644 index 000000000..fef82c252 --- /dev/null +++ b/Source/Dependencies/rapidjson/include/rapidjson/stream.h @@ -0,0 +1,179 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "rapidjson.h" + +#ifndef RAPIDJSON_STREAM_H_ +#define RAPIDJSON_STREAM_H_ + +#include "encodings.h" + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// Stream + +/*! \class rapidjson::Stream + \brief Concept for reading and writing characters. + + For read-only stream, no need to implement PutBegin(), Put(), Flush() and PutEnd(). + + For write-only stream, only need to implement Put() and Flush(). + +\code +concept Stream { + typename Ch; //!< Character type of the stream. + + //! Read the current character from stream without moving the read cursor. + Ch Peek() const; + + //! Read the current character from stream and moving the read cursor to next character. + Ch Take(); + + //! Get the current read cursor. + //! \return Number of characters read from start. + size_t Tell(); + + //! Begin writing operation at the current read pointer. + //! \return The begin writer pointer. + Ch* PutBegin(); + + //! Write a character. + void Put(Ch c); + + //! Flush the buffer. + void Flush(); + + //! End the writing operation. + //! \param begin The begin write pointer returned by PutBegin(). + //! \return Number of characters written. + size_t PutEnd(Ch* begin); +} +\endcode +*/ + +//! Provides additional information for stream. +/*! + By using traits pattern, this type provides a default configuration for stream. + For custom stream, this type can be specialized for other configuration. + See TEST(Reader, CustomStringStream) in readertest.cpp for example. +*/ +template +struct StreamTraits { + //! Whether to make local copy of stream for optimization during parsing. + /*! + By default, for safety, streams do not use local copy optimization. + Stream that can be copied fast should specialize this, like StreamTraits. + */ + enum { copyOptimization = 0 }; +}; + +//! Reserve n characters for writing to a stream. +template +inline void PutReserve(Stream& stream, size_t count) { + (void)stream; + (void)count; +} + +//! Write character to a stream, presuming buffer is reserved. +template +inline void PutUnsafe(Stream& stream, typename Stream::Ch c) { + stream.Put(c); +} + +//! Put N copies of a character to a stream. +template +inline void PutN(Stream& stream, Ch c, size_t n) { + PutReserve(stream, n); + for (size_t i = 0; i < n; i++) + PutUnsafe(stream, c); +} + +/////////////////////////////////////////////////////////////////////////////// +// StringStream + +//! Read-only string stream. +/*! \note implements Stream concept +*/ +template +struct GenericStringStream { + typedef typename Encoding::Ch Ch; + + GenericStringStream(const Ch *src) : src_(src), head_(src) {} + + Ch Peek() const { return *src_; } + Ch Take() { return *src_++; } + size_t Tell() const { return static_cast(src_ - head_); } + + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + + const Ch* src_; //!< Current read position. + const Ch* head_; //!< Original head of the string. +}; + +template +struct StreamTraits > { + enum { copyOptimization = 1 }; +}; + +//! String stream with UTF8 encoding. +typedef GenericStringStream > StringStream; + +/////////////////////////////////////////////////////////////////////////////// +// InsituStringStream + +//! A read-write string stream. +/*! This string stream is particularly designed for in-situ parsing. + \note implements Stream concept +*/ +template +struct GenericInsituStringStream { + typedef typename Encoding::Ch Ch; + + GenericInsituStringStream(Ch *src) : src_(src), dst_(0), head_(src) {} + + // Read + Ch Peek() { return *src_; } + Ch Take() { return *src_++; } + size_t Tell() { return static_cast(src_ - head_); } + + // Write + void Put(Ch c) { RAPIDJSON_ASSERT(dst_ != 0); *dst_++ = c; } + + Ch* PutBegin() { return dst_ = src_; } + size_t PutEnd(Ch* begin) { return static_cast(dst_ - begin); } + void Flush() {} + + Ch* Push(size_t count) { Ch* begin = dst_; dst_ += count; return begin; } + void Pop(size_t count) { dst_ -= count; } + + Ch* src_; + Ch* dst_; + Ch* head_; +}; + +template +struct StreamTraits > { + enum { copyOptimization = 1 }; +}; + +//! Insitu string stream with UTF8 encoding. +typedef GenericInsituStringStream > InsituStringStream; + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_STREAM_H_ diff --git a/Source/Dependencies/rapidjson/include/rapidjson/stringbuffer.h b/Source/Dependencies/rapidjson/include/rapidjson/stringbuffer.h new file mode 100644 index 000000000..78f34d209 --- /dev/null +++ b/Source/Dependencies/rapidjson/include/rapidjson/stringbuffer.h @@ -0,0 +1,117 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_STRINGBUFFER_H_ +#define RAPIDJSON_STRINGBUFFER_H_ + +#include "stream.h" +#include "internal/stack.h" + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS +#include // std::move +#endif + +#include "internal/stack.h" + +#if defined(__clang__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(c++98-compat) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Represents an in-memory output stream. +/*! + \tparam Encoding Encoding of the stream. + \tparam Allocator type for allocating memory buffer. + \note implements Stream concept +*/ +template +class GenericStringBuffer { +public: + typedef typename Encoding::Ch Ch; + + GenericStringBuffer(Allocator* allocator = 0, size_t capacity = kDefaultCapacity) : stack_(allocator, capacity) {} + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericStringBuffer(GenericStringBuffer&& rhs) : stack_(std::move(rhs.stack_)) {} + GenericStringBuffer& operator=(GenericStringBuffer&& rhs) { + if (&rhs != this) + stack_ = std::move(rhs.stack_); + return *this; + } +#endif + + void Put(Ch c) { *stack_.template Push() = c; } + void PutUnsafe(Ch c) { *stack_.template PushUnsafe() = c; } + void Flush() {} + + void Clear() { stack_.Clear(); } + void ShrinkToFit() { + // Push and pop a null terminator. This is safe. + *stack_.template Push() = '\0'; + stack_.ShrinkToFit(); + stack_.template Pop(1); + } + + void Reserve(size_t count) { stack_.template Reserve(count); } + Ch* Push(size_t count) { return stack_.template Push(count); } + Ch* PushUnsafe(size_t count) { return stack_.template PushUnsafe(count); } + void Pop(size_t count) { stack_.template Pop(count); } + + const Ch* GetString() const { + // Push and pop a null terminator. This is safe. + *stack_.template Push() = '\0'; + stack_.template Pop(1); + + return stack_.template Bottom(); + } + + size_t GetSize() const { return stack_.GetSize(); } + + static const size_t kDefaultCapacity = 256; + mutable internal::Stack stack_; + +private: + // Prohibit copy constructor & assignment operator. + GenericStringBuffer(const GenericStringBuffer&); + GenericStringBuffer& operator=(const GenericStringBuffer&); +}; + +//! String buffer with UTF8 encoding +typedef GenericStringBuffer > StringBuffer; + +template +inline void PutReserve(GenericStringBuffer& stream, size_t count) { + stream.Reserve(count); +} + +template +inline void PutUnsafe(GenericStringBuffer& stream, typename Encoding::Ch c) { + stream.PutUnsafe(c); +} + +//! Implement specialized version of PutN() with memset() for better performance. +template<> +inline void PutN(GenericStringBuffer >& stream, char c, size_t n) { + std::memset(stream.stack_.Push(n), c, n * sizeof(c)); +} + +RAPIDJSON_NAMESPACE_END + +#if defined(__clang__) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_STRINGBUFFER_H_ diff --git a/Source/Dependencies/rapidjson/include/rapidjson/writer.h b/Source/Dependencies/rapidjson/include/rapidjson/writer.h new file mode 100644 index 000000000..94f22dd5f --- /dev/null +++ b/Source/Dependencies/rapidjson/include/rapidjson/writer.h @@ -0,0 +1,610 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_WRITER_H_ +#define RAPIDJSON_WRITER_H_ + +#include "stream.h" +#include "internal/stack.h" +#include "internal/strfunc.h" +#include "internal/dtoa.h" +#include "internal/itoa.h" +#include "stringbuffer.h" +#include // placement new + +#if defined(RAPIDJSON_SIMD) && defined(_MSC_VER) +#include +#pragma intrinsic(_BitScanForward) +#endif +#ifdef RAPIDJSON_SSE42 +#include +#elif defined(RAPIDJSON_SSE2) +#include +#endif + +#ifdef _MSC_VER +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4127) // conditional expression is constant +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +RAPIDJSON_DIAG_OFF(unreachable-code) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// WriteFlag + +/*! \def RAPIDJSON_WRITE_DEFAULT_FLAGS + \ingroup RAPIDJSON_CONFIG + \brief User-defined kWriteDefaultFlags definition. + + User can define this as any \c WriteFlag combinations. +*/ +#ifndef RAPIDJSON_WRITE_DEFAULT_FLAGS +#define RAPIDJSON_WRITE_DEFAULT_FLAGS kWriteNoFlags +#endif + +//! Combination of writeFlags +enum WriteFlag { + kWriteNoFlags = 0, //!< No flags are set. + kWriteValidateEncodingFlag = 1, //!< Validate encoding of JSON strings. + kWriteNanAndInfFlag = 2, //!< Allow writing of Infinity, -Infinity and NaN. + kWriteDefaultFlags = RAPIDJSON_WRITE_DEFAULT_FLAGS //!< Default write flags. Can be customized by defining RAPIDJSON_WRITE_DEFAULT_FLAGS +}; + +//! JSON writer +/*! Writer implements the concept Handler. + It generates JSON text by events to an output os. + + User may programmatically calls the functions of a writer to generate JSON text. + + On the other side, a writer can also be passed to objects that generates events, + + for example Reader::Parse() and Document::Accept(). + + \tparam OutputStream Type of output stream. + \tparam SourceEncoding Encoding of source string. + \tparam TargetEncoding Encoding of output stream. + \tparam StackAllocator Type of allocator for allocating memory of stack. + \note implements Handler concept +*/ +template, typename TargetEncoding = UTF8<>, typename StackAllocator = CrtAllocator, unsigned writeFlags = kWriteDefaultFlags> +class Writer { +public: + typedef typename SourceEncoding::Ch Ch; + + static const int kDefaultMaxDecimalPlaces = 324; + + //! Constructor + /*! \param os Output stream. + \param stackAllocator User supplied allocator. If it is null, it will create a private one. + \param levelDepth Initial capacity of stack. + */ + explicit + Writer(OutputStream& os, StackAllocator* stackAllocator = 0, size_t levelDepth = kDefaultLevelDepth) : + os_(&os), level_stack_(stackAllocator, levelDepth * sizeof(Level)), maxDecimalPlaces_(kDefaultMaxDecimalPlaces), hasRoot_(false) {} + + explicit + Writer(StackAllocator* allocator = 0, size_t levelDepth = kDefaultLevelDepth) : + os_(0), level_stack_(allocator, levelDepth * sizeof(Level)), maxDecimalPlaces_(kDefaultMaxDecimalPlaces), hasRoot_(false) {} + + //! Reset the writer with a new stream. + /*! + This function reset the writer with a new stream and default settings, + in order to make a Writer object reusable for output multiple JSONs. + + \param os New output stream. + \code + Writer writer(os1); + writer.StartObject(); + // ... + writer.EndObject(); + + writer.Reset(os2); + writer.StartObject(); + // ... + writer.EndObject(); + \endcode + */ + void Reset(OutputStream& os) { + os_ = &os; + hasRoot_ = false; + level_stack_.Clear(); + } + + //! Checks whether the output is a complete JSON. + /*! + A complete JSON has a complete root object or array. + */ + bool IsComplete() const { + return hasRoot_ && level_stack_.Empty(); + } + + int GetMaxDecimalPlaces() const { + return maxDecimalPlaces_; + } + + //! Sets the maximum number of decimal places for double output. + /*! + This setting truncates the output with specified number of decimal places. + + For example, + + \code + writer.SetMaxDecimalPlaces(3); + writer.StartArray(); + writer.Double(0.12345); // "0.123" + writer.Double(0.0001); // "0.0" + writer.Double(1.234567890123456e30); // "1.234567890123456e30" (do not truncate significand for positive exponent) + writer.Double(1.23e-4); // "0.0" (do truncate significand for negative exponent) + writer.EndArray(); + \endcode + + The default setting does not truncate any decimal places. You can restore to this setting by calling + \code + writer.SetMaxDecimalPlaces(Writer::kDefaultMaxDecimalPlaces); + \endcode + */ + void SetMaxDecimalPlaces(int maxDecimalPlaces) { + maxDecimalPlaces_ = maxDecimalPlaces; + } + + /*!@name Implementation of Handler + \see Handler + */ + //@{ + + bool Null() { Prefix(kNullType); return EndValue(WriteNull()); } + bool Bool(bool b) { Prefix(b ? kTrueType : kFalseType); return EndValue(WriteBool(b)); } + bool Int(int i) { Prefix(kNumberType); return EndValue(WriteInt(i)); } + bool Uint(unsigned u) { Prefix(kNumberType); return EndValue(WriteUint(u)); } + bool Int64(int64_t i64) { Prefix(kNumberType); return EndValue(WriteInt64(i64)); } + bool Uint64(uint64_t u64) { Prefix(kNumberType); return EndValue(WriteUint64(u64)); } + + //! Writes the given \c double value to the stream + /*! + \param d The value to be written. + \return Whether it is succeed. + */ + bool Double(double d) { Prefix(kNumberType); return EndValue(WriteDouble(d)); } + + bool RawNumber(const Ch* str, SizeType length, bool copy = false) { + (void)copy; + Prefix(kNumberType); + return EndValue(WriteString(str, length)); + } + + bool String(const Ch* str, SizeType length, bool copy = false) { + (void)copy; + Prefix(kStringType); + return EndValue(WriteString(str, length)); + } + +#if RAPIDJSON_HAS_STDSTRING + bool String(const std::basic_string& str) { + return String(str.data(), SizeType(str.size())); + } +#endif + + bool StartObject() { + Prefix(kObjectType); + new (level_stack_.template Push()) Level(false); + return WriteStartObject(); + } + + bool Key(const Ch* str, SizeType length, bool copy = false) { return String(str, length, copy); } + + bool EndObject(SizeType memberCount = 0) { + (void)memberCount; + RAPIDJSON_ASSERT(level_stack_.GetSize() >= sizeof(Level)); + RAPIDJSON_ASSERT(!level_stack_.template Top()->inArray); + level_stack_.template Pop(1); + return EndValue(WriteEndObject()); + } + + bool StartArray() { + Prefix(kArrayType); + new (level_stack_.template Push()) Level(true); + return WriteStartArray(); + } + + bool EndArray(SizeType elementCount = 0) { + (void)elementCount; + RAPIDJSON_ASSERT(level_stack_.GetSize() >= sizeof(Level)); + RAPIDJSON_ASSERT(level_stack_.template Top()->inArray); + level_stack_.template Pop(1); + return EndValue(WriteEndArray()); + } + //@} + + /*! @name Convenience extensions */ + //@{ + + //! Simpler but slower overload. + bool String(const Ch* str) { return String(str, internal::StrLen(str)); } + bool Key(const Ch* str) { return Key(str, internal::StrLen(str)); } + + //@} + + //! Write a raw JSON value. + /*! + For user to write a stringified JSON as a value. + + \param json A well-formed JSON value. It should not contain null character within [0, length - 1] range. + \param length Length of the json. + \param type Type of the root of json. + */ + bool RawValue(const Ch* json, size_t length, Type type) { Prefix(type); return EndValue(WriteRawValue(json, length)); } + +protected: + //! Information for each nested level + struct Level { + Level(bool inArray_) : valueCount(0), inArray(inArray_) {} + size_t valueCount; //!< number of values in this level + bool inArray; //!< true if in array, otherwise in object + }; + + static const size_t kDefaultLevelDepth = 32; + + bool WriteNull() { + PutReserve(*os_, 4); + PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'u'); PutUnsafe(*os_, 'l'); PutUnsafe(*os_, 'l'); return true; + } + + bool WriteBool(bool b) { + if (b) { + PutReserve(*os_, 4); + PutUnsafe(*os_, 't'); PutUnsafe(*os_, 'r'); PutUnsafe(*os_, 'u'); PutUnsafe(*os_, 'e'); + } + else { + PutReserve(*os_, 5); + PutUnsafe(*os_, 'f'); PutUnsafe(*os_, 'a'); PutUnsafe(*os_, 'l'); PutUnsafe(*os_, 's'); PutUnsafe(*os_, 'e'); + } + return true; + } + + bool WriteInt(int i) { + char buffer[11]; + const char* end = internal::i32toa(i, buffer); + PutReserve(*os_, static_cast(end - buffer)); + for (const char* p = buffer; p != end; ++p) + PutUnsafe(*os_, static_cast(*p)); + return true; + } + + bool WriteUint(unsigned u) { + char buffer[10]; + const char* end = internal::u32toa(u, buffer); + PutReserve(*os_, static_cast(end - buffer)); + for (const char* p = buffer; p != end; ++p) + PutUnsafe(*os_, static_cast(*p)); + return true; + } + + bool WriteInt64(int64_t i64) { + char buffer[21]; + const char* end = internal::i64toa(i64, buffer); + PutReserve(*os_, static_cast(end - buffer)); + for (const char* p = buffer; p != end; ++p) + PutUnsafe(*os_, static_cast(*p)); + return true; + } + + bool WriteUint64(uint64_t u64) { + char buffer[20]; + char* end = internal::u64toa(u64, buffer); + PutReserve(*os_, static_cast(end - buffer)); + for (char* p = buffer; p != end; ++p) + PutUnsafe(*os_, static_cast(*p)); + return true; + } + + bool WriteDouble(double d) { + if (internal::Double(d).IsNanOrInf()) { + if (!(writeFlags & kWriteNanAndInfFlag)) + return false; + if (internal::Double(d).IsNan()) { + PutReserve(*os_, 3); + PutUnsafe(*os_, 'N'); PutUnsafe(*os_, 'a'); PutUnsafe(*os_, 'N'); + return true; + } + if (internal::Double(d).Sign()) { + PutReserve(*os_, 9); + PutUnsafe(*os_, '-'); + } + else + PutReserve(*os_, 8); + PutUnsafe(*os_, 'I'); PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'f'); + PutUnsafe(*os_, 'i'); PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'i'); PutUnsafe(*os_, 't'); PutUnsafe(*os_, 'y'); + return true; + } + + char buffer[25]; + char* end = internal::dtoa(d, buffer, maxDecimalPlaces_); + PutReserve(*os_, static_cast(end - buffer)); + for (char* p = buffer; p != end; ++p) + PutUnsafe(*os_, static_cast(*p)); + return true; + } + + bool WriteString(const Ch* str, SizeType length) { + static const typename TargetEncoding::Ch hexDigits[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + static const char escape[256] = { +#define Z16 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + //0 1 2 3 4 5 6 7 8 9 A B C D E F + 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'b', 't', 'n', 'u', 'f', 'r', 'u', 'u', // 00 + 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', // 10 + 0, 0, '"', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20 + Z16, Z16, // 30~4F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'\\', 0, 0, 0, // 50 + Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16 // 60~FF +#undef Z16 + }; + + if (TargetEncoding::supportUnicode) + PutReserve(*os_, 2 + length * 6); // "\uxxxx..." + else + PutReserve(*os_, 2 + length * 12); // "\uxxxx\uyyyy..." + + PutUnsafe(*os_, '\"'); + GenericStringStream is(str); + while (ScanWriteUnescapedString(is, length)) { + const Ch c = is.Peek(); + if (!TargetEncoding::supportUnicode && static_cast(c) >= 0x80) { + // Unicode escaping + unsigned codepoint; + if (RAPIDJSON_UNLIKELY(!SourceEncoding::Decode(is, &codepoint))) + return false; + PutUnsafe(*os_, '\\'); + PutUnsafe(*os_, 'u'); + if (codepoint <= 0xD7FF || (codepoint >= 0xE000 && codepoint <= 0xFFFF)) { + PutUnsafe(*os_, hexDigits[(codepoint >> 12) & 15]); + PutUnsafe(*os_, hexDigits[(codepoint >> 8) & 15]); + PutUnsafe(*os_, hexDigits[(codepoint >> 4) & 15]); + PutUnsafe(*os_, hexDigits[(codepoint ) & 15]); + } + else { + RAPIDJSON_ASSERT(codepoint >= 0x010000 && codepoint <= 0x10FFFF); + // Surrogate pair + unsigned s = codepoint - 0x010000; + unsigned lead = (s >> 10) + 0xD800; + unsigned trail = (s & 0x3FF) + 0xDC00; + PutUnsafe(*os_, hexDigits[(lead >> 12) & 15]); + PutUnsafe(*os_, hexDigits[(lead >> 8) & 15]); + PutUnsafe(*os_, hexDigits[(lead >> 4) & 15]); + PutUnsafe(*os_, hexDigits[(lead ) & 15]); + PutUnsafe(*os_, '\\'); + PutUnsafe(*os_, 'u'); + PutUnsafe(*os_, hexDigits[(trail >> 12) & 15]); + PutUnsafe(*os_, hexDigits[(trail >> 8) & 15]); + PutUnsafe(*os_, hexDigits[(trail >> 4) & 15]); + PutUnsafe(*os_, hexDigits[(trail ) & 15]); + } + } + else if ((sizeof(Ch) == 1 || static_cast(c) < 256) && RAPIDJSON_UNLIKELY(escape[static_cast(c)])) { + is.Take(); + PutUnsafe(*os_, '\\'); + PutUnsafe(*os_, static_cast(escape[static_cast(c)])); + if (escape[static_cast(c)] == 'u') { + PutUnsafe(*os_, '0'); + PutUnsafe(*os_, '0'); + PutUnsafe(*os_, hexDigits[static_cast(c) >> 4]); + PutUnsafe(*os_, hexDigits[static_cast(c) & 0xF]); + } + } + else if (RAPIDJSON_UNLIKELY(!(writeFlags & kWriteValidateEncodingFlag ? + Transcoder::Validate(is, *os_) : + Transcoder::TranscodeUnsafe(is, *os_)))) + return false; + } + PutUnsafe(*os_, '\"'); + return true; + } + + bool ScanWriteUnescapedString(GenericStringStream& is, size_t length) { + return RAPIDJSON_LIKELY(is.Tell() < length); + } + + bool WriteStartObject() { os_->Put('{'); return true; } + bool WriteEndObject() { os_->Put('}'); return true; } + bool WriteStartArray() { os_->Put('['); return true; } + bool WriteEndArray() { os_->Put(']'); return true; } + + bool WriteRawValue(const Ch* json, size_t length) { + PutReserve(*os_, length); + for (size_t i = 0; i < length; i++) { + RAPIDJSON_ASSERT(json[i] != '\0'); + PutUnsafe(*os_, json[i]); + } + return true; + } + + void Prefix(Type type) { + (void)type; + if (RAPIDJSON_LIKELY(level_stack_.GetSize() != 0)) { // this value is not at root + Level* level = level_stack_.template Top(); + if (level->valueCount > 0) { + if (level->inArray) + os_->Put(','); // add comma if it is not the first element in array + else // in object + os_->Put((level->valueCount % 2 == 0) ? ',' : ':'); + } + if (!level->inArray && level->valueCount % 2 == 0) + RAPIDJSON_ASSERT(type == kStringType); // if it's in object, then even number should be a name + level->valueCount++; + } + else { + RAPIDJSON_ASSERT(!hasRoot_); // Should only has one and only one root. + hasRoot_ = true; + } + } + + // Flush the value if it is the top level one. + bool EndValue(bool ret) { + if (RAPIDJSON_UNLIKELY(level_stack_.Empty())) // end of json text + os_->Flush(); + return ret; + } + + OutputStream* os_; + internal::Stack level_stack_; + int maxDecimalPlaces_; + bool hasRoot_; + +private: + // Prohibit copy constructor & assignment operator. + Writer(const Writer&); + Writer& operator=(const Writer&); +}; + +// Full specialization for StringStream to prevent memory copying + +template<> +inline bool Writer::WriteInt(int i) { + char *buffer = os_->Push(11); + const char* end = internal::i32toa(i, buffer); + os_->Pop(static_cast(11 - (end - buffer))); + return true; +} + +template<> +inline bool Writer::WriteUint(unsigned u) { + char *buffer = os_->Push(10); + const char* end = internal::u32toa(u, buffer); + os_->Pop(static_cast(10 - (end - buffer))); + return true; +} + +template<> +inline bool Writer::WriteInt64(int64_t i64) { + char *buffer = os_->Push(21); + const char* end = internal::i64toa(i64, buffer); + os_->Pop(static_cast(21 - (end - buffer))); + return true; +} + +template<> +inline bool Writer::WriteUint64(uint64_t u) { + char *buffer = os_->Push(20); + const char* end = internal::u64toa(u, buffer); + os_->Pop(static_cast(20 - (end - buffer))); + return true; +} + +template<> +inline bool Writer::WriteDouble(double d) { + if (internal::Double(d).IsNanOrInf()) { + // Note: This code path can only be reached if (RAPIDJSON_WRITE_DEFAULT_FLAGS & kWriteNanAndInfFlag). + if (!(kWriteDefaultFlags & kWriteNanAndInfFlag)) + return false; + if (internal::Double(d).IsNan()) { + PutReserve(*os_, 3); + PutUnsafe(*os_, 'N'); PutUnsafe(*os_, 'a'); PutUnsafe(*os_, 'N'); + return true; + } + if (internal::Double(d).Sign()) { + PutReserve(*os_, 9); + PutUnsafe(*os_, '-'); + } + else + PutReserve(*os_, 8); + PutUnsafe(*os_, 'I'); PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'f'); + PutUnsafe(*os_, 'i'); PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'i'); PutUnsafe(*os_, 't'); PutUnsafe(*os_, 'y'); + return true; + } + + char *buffer = os_->Push(25); + char* end = internal::dtoa(d, buffer, maxDecimalPlaces_); + os_->Pop(static_cast(25 - (end - buffer))); + return true; +} + +#if defined(RAPIDJSON_SSE2) || defined(RAPIDJSON_SSE42) +template<> +inline bool Writer::ScanWriteUnescapedString(StringStream& is, size_t length) { + if (length < 16) + return RAPIDJSON_LIKELY(is.Tell() < length); + + if (!RAPIDJSON_LIKELY(is.Tell() < length)) + return false; + + const char* p = is.src_; + const char* end = is.head_ + length; + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + const char* endAligned = reinterpret_cast(reinterpret_cast(end) & static_cast(~15)); + if (nextAligned > end) + return true; + + while (p != nextAligned) + if (*p < 0x20 || *p == '\"' || *p == '\\') { + is.src_ = p; + return RAPIDJSON_LIKELY(is.Tell() < length); + } + else + os_->PutUnsafe(*p++); + + // The rest of string using SIMD + static const char dquote[16] = { '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"' }; + static const char bslash[16] = { '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\' }; + static const char space[16] = { 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19 }; + const __m128i dq = _mm_loadu_si128(reinterpret_cast(&dquote[0])); + const __m128i bs = _mm_loadu_si128(reinterpret_cast(&bslash[0])); + const __m128i sp = _mm_loadu_si128(reinterpret_cast(&space[0])); + + for (; p != endAligned; p += 16) { + const __m128i s = _mm_load_si128(reinterpret_cast(p)); + const __m128i t1 = _mm_cmpeq_epi8(s, dq); + const __m128i t2 = _mm_cmpeq_epi8(s, bs); + const __m128i t3 = _mm_cmpeq_epi8(_mm_max_epu8(s, sp), sp); // s < 0x20 <=> max(s, 0x19) == 0x19 + const __m128i x = _mm_or_si128(_mm_or_si128(t1, t2), t3); + unsigned short r = static_cast(_mm_movemask_epi8(x)); + if (RAPIDJSON_UNLIKELY(r != 0)) { // some of characters is escaped + SizeType len; +#ifdef _MSC_VER // Find the index of first escaped + unsigned long offset; + _BitScanForward(&offset, r); + len = offset; +#else + len = static_cast(__builtin_ffs(r) - 1); +#endif + char* q = reinterpret_cast(os_->PushUnsafe(len)); + for (size_t i = 0; i < len; i++) + q[i] = p[i]; + + p += len; + break; + } + _mm_storeu_si128(reinterpret_cast<__m128i *>(os_->PushUnsafe(16)), s); + } + + is.src_ = p; + return RAPIDJSON_LIKELY(is.Tell() < length); +} +#endif // defined(RAPIDJSON_SSE2) || defined(RAPIDJSON_SSE42) + +RAPIDJSON_NAMESPACE_END + +#ifdef _MSC_VER +RAPIDJSON_DIAG_POP +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_RAPIDJSON_H_ diff --git a/Source/Engine.sln b/Source/Engine.sln index 348033a8c..93174f2f9 100644 --- a/Source/Engine.sln +++ b/Source/Engine.sln @@ -11,8 +11,8 @@ Global Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {EBCCD73A-A6B7-458D-8C04-9E787251DA0F}.Debug|x64.ActiveCfg = Release|x64 - {EBCCD73A-A6B7-458D-8C04-9E787251DA0F}.Debug|x64.Build.0 = Release|x64 + {EBCCD73A-A6B7-458D-8C04-9E787251DA0F}.Debug|x64.ActiveCfg = Debug|x64 + {EBCCD73A-A6B7-458D-8C04-9E787251DA0F}.Debug|x64.Build.0 = Debug|x64 {EBCCD73A-A6B7-458D-8C04-9E787251DA0F}.Release|x64.ActiveCfg = Release|x64 {EBCCD73A-A6B7-458D-8C04-9E787251DA0F}.Release|x64.Build.0 = Release|x64 EndGlobalSection diff --git a/Source/Engine.vcxproj b/Source/Engine.vcxproj index 28fb53a38..29f54d9a3 100644 --- a/Source/Engine.vcxproj +++ b/Source/Engine.vcxproj @@ -1,4 +1,4 @@ - + @@ -96,16 +96,17 @@ Level3 - true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions);_CRT_SECURE_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS;LOGGING_SUPPORT_DISABLED - true - Dependencies\SDL\include;Dependencies\glew-2.1.0\include\GL;Dependencies\imgui;Dependencies\MathGeoLib;Dependencies\DevIL\include\IL;Dependencies\DebugDraw;Dependencies\assimp\include + false + _CRT_SECURE_NO_WARNINGS;_MBCS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions);_CRT_SECURE_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS;LOGGING_SUPPORT_DISABLED + Default + Dependencies\SDL\include;Dependencies\glew-2.1.0\include\GL;Dependencies\imgui;Dependencies\MathGeoLib;Dependencies\DevIL\include\IL;Dependencies\DebugDraw;Dependencies\assimp\include;Dependencies\ImGuiFileDialog-0.6.3;Dependencies\ImGuizmo;Dependencies\rapidjson\include;Dependencies\optick\include;Dependencies\PhysicsFS + Default Windows true - Dependencies\glew-2.1.0\lib;Dependencies\DevIL\lib;Dependencies\SDL\lib;Dependencies\assimp\lib - assimp-vc142-mtd.lib;DevIL.lib;ILU.lib;ILUT.lib;glew32.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + Dependencies\glew-2.1.0\lib;Dependencies\DevIL\lib;Dependencies\SDL\lib;Dependencies\assimp\lib;Dependencies\optick\lib + assimp-vc141-mt.lib;DevIL.lib;ILU.lib;ILUT.lib;glew32.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;OptickCore.lib;%(AdditionalDependencies) @@ -129,23 +130,73 @@ Level3 true true - true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions);_CRT_SECURE_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS;LOGGING_SUPPORT_DISABLED - true - Dependencies\SDL\include;Dependencies\glew-2.1.0\include\GL;Dependencies\imgui;Dependencies\MathGeoLib;Dependencies\DevIL\include\IL;Dependencies\DebugDraw;Dependencies\assimp\include + false + _CRT_SECURE_NO_WARNINGS;_MBCS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);_CRT_SECURE_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS;LOGGING_SUPPORT_DISABLED + Default + Dependencies\SDL\include;Dependencies\glew-2.1.0\include\GL;Dependencies\imgui;Dependencies\MathGeoLib;Dependencies\DevIL\include\IL;Dependencies\DebugDraw;Dependencies\assimp\include;Dependencies\ImGuiFileDialog-0.6.3;Dependencies\ImGuizmo;Dependencies\rapidjson\include;Dependencies\optick\include;Dependencies\PhysicsFS + Default Windows true true true - Dependencies\glew-2.1.0\lib;Dependencies\DevIL\lib;Dependencies\SDL\lib;Dependencies\assimp\lib - assimp-vc142-mtd.lib;DevIL.lib;ILU.lib;ILUT.lib;glew32.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + Dependencies\glew-2.1.0\lib;Dependencies\DevIL\lib;Dependencies\SDL\lib;Dependencies\assimp\lib;Dependencies\optick\lib + assimp-vc141-mt.lib;DevIL.lib;ILU.lib;ILUT.lib;glew32.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;OptickCore.lib;%(AdditionalDependencies) - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -172,9 +223,9 @@ - - - + + + @@ -198,25 +249,73 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -258,9 +357,9 @@ - - - + + + @@ -304,24 +403,32 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/GameObject.cpp b/Source/GameObject.cpp deleted file mode 100644 index 91b5ca816..000000000 --- a/Source/GameObject.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "GameObject.h" diff --git a/Source/GameObject.h b/Source/GameObject.h deleted file mode 100644 index b4123b93a..000000000 --- a/Source/GameObject.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include "Math.h" - -#include ; -using namespace std; - -class GameObject -{ - public: - GameObject(); - ~GameObject(); - - GameObject* parent = nullptr; - std::vector childs; -}; - diff --git a/Source/Globals.h b/Source/Globals.h deleted file mode 100644 index 2e0f9d2b6..000000000 --- a/Source/Globals.h +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef __GLOBALS_H__ -#define __GLOBALS_H__ - -#include -#include -#include "ImGuiComponents/AppLog.h" - -#define LOG(format, ...) Logger->AddLog(__FILE__, __LINE__, format, __VA_ARGS__); - -#define DEGTORAD 0.0174532925199432957f - -enum update_status -{ - UPDATE_CONTINUE = 1, - UPDATE_STOP, - UPDATE_ERROR -}; - -// Deletes a buffer -#define RELEASE( x )\ - {\ - if( x != nullptr )\ - {\ - delete x;\ - x = nullptr;\ - }\ - } - -// Array buffer deletes -#define RELEASE_ARRAY( x )\ - {\ - if( x != nullptr )\ - {\ - delete[] x;\ - x = nullptr;\ - }\ - } - -// Configuration ----------- -#define SCREEN_WIDTH 1080 -#define SCREEN_HEIGHT 720 -#define FULLSCREEN false -#define RELIZABLE true -#define VSYNC true -#define TITLE "ALTAR BOOY" -#define ENGINE_VERSION "0.0.1" - -#define FPS_LOG_SIZE 100 - -#endif // __GLOBALS_H__ \ No newline at end of file diff --git a/Source/Math.h b/Source/Math.h deleted file mode 100644 index 0a71012e9..000000000 --- a/Source/Math.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef __MATH_H__ -#define __MATH_H__ - -#include "MathBuildConfig.h" -#include "MathGeoLib.h" - -#endif // __MATH_H__ \ No newline at end of file diff --git a/Source/Mesh.cpp b/Source/Mesh.cpp deleted file mode 100644 index c82397f07..000000000 --- a/Source/Mesh.cpp +++ /dev/null @@ -1,113 +0,0 @@ -#include "Mesh.h" - -#include "Application.h" -#include "ModuleProgram.h" -#include "ModuleRender.h" -#include "ModuleCamera.h" - -#include "glew.h" -#include "assimp/cimport.h" -#include "assimp/postprocess.h" -#include "MathGeoLib.h" - - -Mesh::Mesh(const aiMesh* mesh): mesh(mesh), texture_index(mesh->mMaterialIndex) -{ - LoadVBO(); - LoadEBO(); - CreateAABB(); - CreateVAO(); -} - -Mesh::~Mesh() -{ - glDeleteBuffers(1, &VBO); - glDeleteBuffers(1, &EBO); - glDeleteBuffers(1, &VAO); -} - -void Mesh::DrawImGui() -{ - float3 mesh_center = aabb.CenterPoint(); - ImGui::Text("Center point: %f, %f, %f", mesh_center.x, mesh_center.y, mesh_center.z); -} - -void Mesh::CreateAABB() -{ - aabb.SetFrom((float3*)&mesh->mVertices[0], mesh->mNumVertices); -} - -void Mesh::LoadVBO() -{ - num_vertices = mesh->mNumVertices; - glGenBuffers(1, &VBO); - glBindBuffer(GL_ARRAY_BUFFER, VBO); - - unsigned vertex_size = (sizeof(float) * 3 + sizeof(float) * 2); - unsigned buffer_size = vertex_size * mesh->mNumVertices; - unsigned position_size = sizeof(float) * 3 * mesh->mNumVertices; - unsigned uv_offset = position_size; - unsigned uv_size = sizeof(float) * 2 * mesh->mNumVertices; - - glBufferData(GL_ARRAY_BUFFER, buffer_size, nullptr, GL_STATIC_DRAW); - glBufferSubData(GL_ARRAY_BUFFER, 0, position_size, mesh->mVertices); - - float2* uvs = (float2*)(glMapBufferRange(GL_ARRAY_BUFFER, uv_offset, uv_size, GL_MAP_WRITE_BIT)); - for (unsigned i = 0; i < mesh->mNumVertices; ++i) - { - uvs[i] = float2(mesh->mTextureCoords[0][i].x, mesh->mTextureCoords[0][i].y); - } - glUnmapBuffer(GL_ARRAY_BUFFER); -} - -void Mesh::LoadEBO() -{ - num_indices = mesh->mNumFaces * 3; - - glGenBuffers(1, &EBO); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); - - unsigned index_size = sizeof(unsigned) * mesh->mNumFaces * 3; - - glBufferData(GL_ELEMENT_ARRAY_BUFFER, index_size, nullptr, GL_STATIC_DRAW); - unsigned* indices = (unsigned*)glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY); - for (unsigned i = 0; i < mesh->mNumFaces; ++i) - { - assert(mesh->mFaces[i].mNumIndices == 3); - *(indices++) = mesh->mFaces[i].mIndices[0]; - *(indices++) = mesh->mFaces[i].mIndices[1]; - *(indices++) = mesh->mFaces[i].mIndices[2]; - } - glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER); -} - -void Mesh::CreateVAO() -{ - glGenVertexArrays(1, &VAO); - glBindVertexArray(VAO); - glBindBuffer(GL_ARRAY_BUFFER, VBO); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); - - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0); - glEnableVertexAttribArray(1); - glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, (void*)(sizeof(float) * 3 * num_vertices)); -} - -void Mesh::Draw(const std::vector& model_textures, float4x4& model) -{ - unsigned program_id = App->program->GetProgramID(); - - glUseProgram(program_id); - glUniformMatrix4fv(glGetUniformLocation(program_id, "model"), 1, GL_TRUE, &model[0][0]); - glUniformMatrix4fv(glGetUniformLocation(program_id, "view"), 1, GL_FALSE, &App->camera->GetGLView()[0][0]); - glUniformMatrix4fv(glGetUniformLocation(program_id, "proj"), 1, GL_FALSE, &App->camera->GetGLProjection()[0][0]); - - glEnable(GL_TEXTURE_2D); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, model_textures[texture_index]->getID()); - - glBindVertexArray(VAO); - glDrawElements(GL_TRIANGLES, num_indices, GL_UNSIGNED_INT, nullptr); - glDisable(GL_TEXTURE_2D); -} \ No newline at end of file diff --git a/Source/Mesh.h b/Source/Mesh.h deleted file mode 100644 index 17911a52e..000000000 --- a/Source/Mesh.h +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -#include "ModuleTexture.h" - -#include "assimp/scene.h" -#include -#include "Math.h" - - -class Mesh -{ - public: - - Mesh(const aiMesh* mesh); - ~Mesh(); - - void Draw(const std::vector& model_textures, float4x4& model); - - unsigned int GetNumVertices() const { - return num_vertices; - }; - unsigned int GetNumIndices() const { - return num_indices; - }; - - AABB getAABB() { - return aabb; - } - - void DrawImGui(); - - private: - const aiMesh* mesh; - - void CreateAABB(); - void LoadVBO(); - void LoadEBO(); - void CreateVAO(); - - unsigned VBO, EBO, VAO; - unsigned num_vertices, num_indices, texture_index; - - AABB aabb; -}; - diff --git a/Source/Model.cpp b/Source/Model.cpp deleted file mode 100644 index 8fa7ec5c2..000000000 --- a/Source/Model.cpp +++ /dev/null @@ -1,200 +0,0 @@ -#include "Globals.h" -#include "Model.h" -#include "Application.h" - -#include "ModuleTexture.h" - -#include "glew.h" -#include "assimp/cimport.h" -#include "assimp/postprocess.h" -#include "assimp/Importer.hpp" - -#include "il.h" -#include "ilu.h" - -Model::Model(const string& model_path) -{ - Assimp::Importer importer; - - path = model_path.substr(0, model_path.find_last_of("/\\") + 1); - - if (path.size() > 0) { - LOG("[Model] Loading model from this path: %s", path.c_str()); - } - - file_name = model_path.substr(model_path.find_last_of("/\\") + 1); - name = file_name.substr(0, std::string::size_type(file_name.find_last_of('.'))); - - LOG("[Model] Loading model: %s", name.c_str()); - - m_model = float4x4::identity; - local_pos = float3(0.0f, 0.0f, 0.0f); - local_rot_euler = float3(0.0f, 0.0f, 0.0f); - local_scale = float3(1.0f, 1.0f, 1.0f); - m_rotation = Quat::identity; - - const aiScene* scene = importer.ReadFile(model_path, aiProcess_Triangulate); - if (scene) - { - LoadTextures(scene); - LoadMeshes(scene); - - for (unsigned int i = 0; i < meshes.size(); ++i) - { - num_triangles += meshes[i]->GetNumIndices() / 3; - num_vertices += meshes[i]->GetNumVertices(); - } - } - else - { - LOG("[Model] Error loading %s: %s", model_path, aiGetErrorString()); - } -} - -Model::~Model() -{ - for (int i = 0; i < textures.size(); ++i) - delete textures[i]; - for (int i = 0; i < meshes.size(); ++i) - delete meshes[i]; -} - -void Model::Draw() -{ - for (unsigned int i = 0; i < meshes.size(); ++i) { - meshes[i]->Draw(textures, m_model); - } -} - -void Model::LoadTextures(const aiScene* scene) -{ - LOG("[Model] Texture loading for model..."); - - aiString file; - textures = vector(scene->mNumMaterials); - - for (unsigned i = 0; i < scene->mNumMaterials; ++i) - { - if (scene->mMaterials[i]->GetTexture(aiTextureType_DIFFUSE, 0, &file) == AI_SUCCESS) - { - textures[i] = new Texture(file.data, path.c_str()); - } - else { - LOG("[Model] Failed loading texture %s:", aiGetErrorString()); - } - } -} - -void Model::LoadMeshes(const aiScene* scene) -{ - LOG("[Model] Loading meshes"); - - m_aabb.SetNegativeInfinity(); - meshes = vector(scene->mNumMeshes); - - for (unsigned i = 0; i < scene->mNumMeshes; i++) - { - if (scene->mMeshes[i]->HasPositions()) { - meshes[i] = new Mesh(scene->mMeshes[i]); - m_aabb.Enclose(meshes[i]->getAABB()); - } - else { - LOG("[Model] Failed loading mesh: %d: %s", i, aiGetErrorString()); - } - } -} - -void Model::DrawGui() -{ - ImGui::Indent(); - - char go_name[50]; - strcpy_s(go_name, 50, name.c_str()); - ImGuiInputTextFlags name_input_flags = ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue; - ImGui::InputText("###", go_name, 50, name_input_flags); - ImGui::SameLine(); - - ImGui::Unindent(); - - ImGui::Separator(); - - ImGui::Text("Vertices: %i", num_vertices); - ImGui::Text("Triangles: %i", num_triangles); - - ImGui::Separator(); - - DrawTransform(); -} - -void Model::DrawTransform() -{ - ImGui::Indent(); - - if (ImGui::CollapsingHeader("Transform")) { - - if (ImGui::Button("Reset")) - { - m_model = float4x4::identity; - } - - if (ImGui::SliderFloat3("Position", &local_pos[0], -10.0f, 10.0f)) - { - m_model = float4x4::FromTRS(local_pos, m_rotation, local_scale); - } - if (ImGui::SliderFloat3("Rotation", &local_rot_euler[0], 0.0f, 360.0f)) - { - m_rotation = Quat::FromEulerXYZ(local_rot_euler.x * DEGTORAD, local_rot_euler.y * DEGTORAD, local_rot_euler.z * DEGTORAD); - m_model = float4x4::FromTRS(local_pos, m_rotation, local_scale); - } - if (ImGui::SliderFloat3("Scale", &local_scale[0], 0, 10.0f)) - { - m_model = float4x4::FromTRS(local_pos, m_rotation, local_scale); - } - ImGui::Separator(); - ImGui::Separator(); - - ImGui::Text("Local Matrix"); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 0.0f, 1.0f)); - for (int i = 0; i < 4; ++i) - { - for (int j = 0; j < 4; j++) - { - ImGui::Text("%f", m_model[j][i]); - if (j < 3) ImGui::SameLine(); - } - } - ImGui::PopStyleColor(); - ImGui::Separator(); - } - - if (ImGui::CollapsingHeader("Meshes")) - { - for (unsigned int i = 0; i < meshes.size(); ++i) - { - ImGui::Text("Mesh %i:", i); - ImGui::Indent(); - meshes[i]->DrawImGui(); - ImGui::Unindent(); - } - } - - if (ImGui::CollapsingHeader("Textures")) - { - for (unsigned int i = 0; i < textures.size(); ++i) - { - textures[i]->DrawImGui(); - } - } - -} - -float3 Model::GetCenter() -{ - return m_aabb.CenterPoint(); -} - -vec Model::GetDiameter() -{ - return m_aabb.Size(); -} - diff --git a/Source/Model.h b/Source/Model.h deleted file mode 100644 index 51ebbf90e..000000000 --- a/Source/Model.h +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once - -#include "ModuleTexture.h" -#include "Texture.h" - -#include "Mesh.h" -#include "assimp/scene.h" -#include -#include "Math.h" - -#include -using namespace std; - -class Model -{ - public: - Model(const std::string& model_path); - ~Model(); - void Draw(); - void DrawGui(); - - void DrawTransform(); - - float3 GetCenter(); - vec GetDiameter(); - - float3 GetLocalPosition() { return local_pos; } - - private: - void LoadTextures(const aiScene* scene); - void LoadMeshes(const aiScene* scene); - - vector textures; - vector meshes; - - unsigned int num_vertices = 0; - unsigned int num_triangles = 0; - - string name; - string path; - string file_name; - - float4x4 m_model; - float3 local_pos, local_rot_euler, local_scale; - Quat m_rotation; - - AABB m_aabb; -}; - diff --git a/Source/Module.h b/Source/Module.h deleted file mode 100644 index 4e631f986..000000000 --- a/Source/Module.h +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - -#include "Globals.h" - -class Application; - -class Module -{ -public: - - Module() - { - } - - ~Module() {}; - - virtual bool Init() - { - return true; - } - - virtual update_status PreUpdate() - { - return UPDATE_CONTINUE; - } - - virtual update_status Update() - { - return UPDATE_CONTINUE; - } - - virtual update_status PostUpdate() - { - return UPDATE_CONTINUE; - } - - virtual bool CleanUp() - { - return true; - } - - virtual void DrawDebug() - {} - - virtual void DrawGui() {} -}; diff --git a/Source/ModuleCamera.cpp b/Source/ModuleCamera.cpp deleted file mode 100644 index cc55efc42..000000000 --- a/Source/ModuleCamera.cpp +++ /dev/null @@ -1,201 +0,0 @@ -#include "ModuleCamera.h" - -#include "Application.h" -#include "ModuleWindow.h" -#include "ModuleCamera.h" -#include "ModuleWindow.h" -#include "ModuleInput.h" -#include "ModuleScene.h" - -#include "glew.h" -#include "Math/MathConstants.h" - -ModuleCamera::ModuleCamera() -{ -} - -ModuleCamera::~ModuleCamera() -{ -} - -bool ModuleCamera::Init() -{ - frustum.SetKind(FrustumSpaceGL, FrustumRightHanded); - frustum.SetViewPlaneDistances(0.1f, 1000.0f); - - auto screen_surface = App->window->getScreenSurface(); - SetAspectRatio(screen_surface->w, screen_surface->h); - - frustum.SetVerticalFovAndAspectRatio(vertical_fov, aspect_ratio); - - position = float3(0.0f, 8.0f, 10.0f); - rotation_matrix = float3x3::FromEulerXYZ(DEGTORAD * -30.0f, DEGTORAD * 180.0f, 0.0f); - - frustum.SetPos(position); - frustum.SetFront(rotation_matrix.WorldZ()); - frustum.SetUp(rotation_matrix.WorldY()); - - RefreshFov(); - - return true; -} - -update_status ModuleCamera::Update() -{ - CheckCameraControl(); - return UPDATE_CONTINUE; -} - -bool ModuleCamera::CleanUp() -{ - return false; -} - -void ModuleCamera::DrawGui() -{ - ImGui::Text("Variables"); - if (ImGui::DragFloat("Vertical FOV", &initial_vertical_fov, 1.0f, 10.0f, 160.0f, "%.2f")) { - if (aspect_ratio >= 1) - vertical_fov = DEGTORAD * initial_vertical_fov; - else - vertical_fov = math::Atan(math::Tan(DEGTORAD * initial_vertical_fov) / aspect_ratio); - } - - ImGui::Text("Set position"); - ImGui::DragFloat("position-X", &position.x, 1.0f, -25.0f, 25.0f, "%.2f"); - ImGui::DragFloat("position-Y", &position.y, 1.0f, -25.0f, 25.0f, "%.2f"); - ImGui::DragFloat("position-Z", &position.z, 1.0f, -25.0f, 25.0f, "%.2f"); - ImGui::Separator(); - - UpdateCamera(); -} - -void ModuleCamera::SetAspectRatio(unsigned int screen_width, unsigned int screen_height) -{ - aspect_ratio = (float)screen_width / (float)screen_height; -} - -void ModuleCamera::UpdateCamera() -{ - RefreshFov(); - - frustum.SetPos(position); - frustum.SetFront(rotation_matrix.WorldZ()); - frustum.SetUp(rotation_matrix.WorldY()); -} - -float4x4 ModuleCamera::GetGLView() const -{ - return float4x4(frustum.ViewMatrix()).Transposed(); -} - -float4x4 ModuleCamera::GetView() const -{ - return float4x4(frustum.ViewMatrix()); -} - -float4x4 ModuleCamera::GetGLProjection() const -{ - return frustum.ProjectionMatrix().Transposed(); -} - -float4x4 ModuleCamera::GetProjection() const -{ - return frustum.ProjectionMatrix(); -} - -void ModuleCamera::CheckCameraControl() -{ - if (App->input->GetMouseButton(SDL_BUTTON_RIGHT)) - { - int deltaX, deltaY; - App->input->GetMouseMotion(deltaX, deltaY); - - if (App->input->GetKey(SDL_SCANCODE_LALT)) - { - position += rotation_matrix.WorldZ() * deltaY * App->GetDeltaTime() * mouse_speed_rotation; - } - else { - static float delta_time = App->GetDeltaTime(); - - rotation_matrix = float3x3::RotateAxisAngle(float3::unitY, -deltaX * delta_time * mouse_speed_rotation * DEGTORAD) * - rotation_matrix * float3x3::RotateAxisAngle(float3::unitX, deltaY * delta_time * mouse_speed_rotation * DEGTORAD); - - float multiplier = 1.0f; - if (App->input->GetKey(SDL_SCANCODE_LSHIFT)) - multiplier = 3.0f; - - if (App->input->GetKey(SDL_SCANCODE_W)) - position += rotation_matrix.WorldZ() * delta_time * camera_speed * multiplier; - if (App->input->GetKey(SDL_SCANCODE_S)) - position -= rotation_matrix.WorldZ() * delta_time * camera_speed * multiplier; - if (App->input->GetKey(SDL_SCANCODE_A)) - position += rotation_matrix.WorldX() * delta_time * camera_speed * multiplier; - if (App->input->GetKey(SDL_SCANCODE_D)) - position -= rotation_matrix.WorldX() * delta_time * camera_speed * multiplier; - if (App->input->GetKey(SDL_SCANCODE_Q)) - position += rotation_matrix.WorldY() * delta_time * camera_speed * multiplier; - if (App->input->GetKey(SDL_SCANCODE_E)) - position -= rotation_matrix.WorldY() * delta_time * camera_speed * multiplier; - } - } - if (App->input->GetMouseButton(SDL_BUTTON_MIDDLE)) - { - int deltaX, deltaY; - App->input->GetMouseMotion(deltaX, deltaY); - - position += rotation_matrix.WorldX() * deltaX * App->GetDeltaTime() * mouse_speed_movement + - rotation_matrix.WorldY() * deltaY * App->GetDeltaTime() * mouse_speed_movement; - - } - if (App->input->MouseWheel()) - { - int wheelX, wheelY; - App->input->GetMouseWheel(wheelX, wheelY); - - position += rotation_matrix.WorldZ() * wheelY * App->GetDeltaTime() * camera_speed; - - } - if (App->input->GetKey(SDL_SCANCODE_LALT)) { - int deltaX, deltaY; - App->input->GetMouseMotion(deltaX, deltaY); - - OrbitCamera(deltaX * 1.5f, deltaY * 1.5f); - } - - if (App->input->GetKey(SDL_SCANCODE_F)) { - FocusOnModel(App->scene->scene_model); - } - - UpdateCamera(); -} - -void ModuleCamera::RefreshFov() -{ - frustum.SetVerticalFovAndAspectRatio(vertical_fov, aspect_ratio); -} - -void ModuleCamera::OrbitCamera(float motion_x, float motion_y) -{ - float3 vector = position - referencePoint; - - Quat quat_y(rotation_matrix.WorldY(), motion_x * 0.003f); - Quat quat_x(rotation_matrix.WorldX().Neg(), motion_y * 0.003f); - - vector = quat_x.Transform(vector); - vector = quat_y.Transform(vector); - - position = vector + referencePoint; - - float3 front = (float3(referencePoint.x, referencePoint.y, referencePoint.z) - position).Normalized(); - float3 right = Cross(float3::unitY, front).Normalized(); - float3 up = Cross(front, right).Normalized(); - - rotation_matrix = float3x3(right, up, front); -} - -void ModuleCamera::FocusOnModel(Model* model) -{ - position = model->GetCenter() - rotation_matrix.WorldZ().Normalized() * model->GetDiameter().Length(); - UpdateCamera(); -} \ No newline at end of file diff --git a/Source/ModuleCamera.h b/Source/ModuleCamera.h deleted file mode 100644 index 2156e25e0..000000000 --- a/Source/ModuleCamera.h +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once -#include "Module.h" -#include "Globals.h" -#include "Model.h" - -#include "MathGeoLib.h" - - -class ModuleCamera : public Module -{ -public: - ModuleCamera(); - ~ModuleCamera(); - - bool Init(); - update_status Update(); - bool CleanUp(); - - void DrawGui(); - - void SetAspectRatio(unsigned int screen_width, unsigned int screen_height); - void CheckCameraControl(); - - // TODO: Make GL standard - float4x4 GetGLView() const; - float4x4 GetView() const; - - float4x4 GetGLProjection() const; - float4x4 GetProjection() const; - - void RefreshFov(); - - void OrbitCamera(float motion_x, float motion_y); - - void FocusOnModel(Model* model); - -private: - - void UpdateCamera(); - - float3 referencePoint = float3::zero; - float3x3 rotation_matrix; - - float initial_vertical_fov = 45.0f; - float vertical_fov = DEGTORAD * initial_vertical_fov; - - Frustum frustum; - float aspect_ratio; - float3 position; - - float camera_speed = 10.0f; - float mouse_speed_rotation = 5.0f; - float mouse_speed_movement = 2.5f; -}; - diff --git a/Source/ModuleDebugDraw.cpp b/Source/ModuleDebugDraw.cpp deleted file mode 100644 index 65d740f56..000000000 --- a/Source/ModuleDebugDraw.cpp +++ /dev/null @@ -1,625 +0,0 @@ -#include "Globals.h" -#include "ModuleDebugDraw.h" - -#define DEBUG_DRAW_IMPLEMENTATION -#include "DebugDraw.h" // Debug Draw API. Notice that we need the DEBUG_DRAW_IMPLEMENTATION macro here! - -#include "glew.h" - -class DDRenderInterfaceCoreGL final - : public dd::RenderInterface -{ -public: - - // - // dd::RenderInterface overrides: - // - - void drawPointList(const dd::DrawVertex * points, int count, bool depthEnabled) override - { - assert(points != nullptr); - assert(count > 0 && count <= DEBUG_DRAW_VERTEX_BUFFER_SIZE); - - glBindVertexArray(linePointVAO); - glUseProgram(linePointProgram); - - glUniformMatrix4fv(linePointProgram_MvpMatrixLocation, - 1, GL_TRUE, reinterpret_cast(&mvpMatrix)); - - bool already = glIsEnabled(GL_DEPTH_TEST); - - if (depthEnabled) - { - glEnable(GL_DEPTH_TEST); - } - else - { - glDisable(GL_DEPTH_TEST); - } - - // NOTE: Could also use glBufferData to take advantage of the buffer orphaning trick... - glBindBuffer(GL_ARRAY_BUFFER, linePointVBO); - glBufferSubData(GL_ARRAY_BUFFER, 0, count * sizeof(dd::DrawVertex), points); - - // Issue the draw call: - glDrawArrays(GL_POINTS, 0, count); - - glUseProgram(0); - glBindVertexArray(0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - checkGLError(__FILE__, __LINE__); - - if (already) - { - glEnable(GL_DEPTH_TEST); - } - else - { - glDisable(GL_DEPTH_TEST); - } - - } - - void drawLineList(const dd::DrawVertex * lines, int count, bool depthEnabled) override - { - assert(lines != nullptr); - assert(count > 0 && count <= DEBUG_DRAW_VERTEX_BUFFER_SIZE); - - glBindVertexArray(linePointVAO); - glUseProgram(linePointProgram); - - glUniformMatrix4fv(linePointProgram_MvpMatrixLocation, - 1, GL_TRUE, reinterpret_cast(&mvpMatrix)); - - bool already = glIsEnabled(GL_DEPTH_TEST); - - if (depthEnabled) - { - glEnable(GL_DEPTH_TEST); - } - else - { - glDisable(GL_DEPTH_TEST); - } - - // NOTE: Could also use glBufferData to take advantage of the buffer orphaning trick... - glBindBuffer(GL_ARRAY_BUFFER, linePointVBO); - glBufferSubData(GL_ARRAY_BUFFER, 0, count * sizeof(dd::DrawVertex), lines); - - // Issue the draw call: - glDrawArrays(GL_LINES, 0, count); - - glUseProgram(0); - glBindVertexArray(0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - checkGLError(__FILE__, __LINE__); - - if (already) - { - glEnable(GL_DEPTH_TEST); - } - else - { - glDisable(GL_DEPTH_TEST); - } - - } - - void drawGlyphList(const dd::DrawVertex * glyphs, int count, dd::GlyphTextureHandle glyphTex) override - { - assert(glyphs != nullptr); - assert(count > 0 && count <= DEBUG_DRAW_VERTEX_BUFFER_SIZE); - - glBindVertexArray(textVAO); - glUseProgram(textProgram); - - // These doesn't have to be reset every draw call, I'm just being lazy ;) - glUniform1i(textProgram_GlyphTextureLocation, 0); - glUniform2f(textProgram_ScreenDimensions, - static_cast(width), - static_cast(height)); - - if (glyphTex != nullptr) - { - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, handleToGL(glyphTex)); - } - - bool already_blend = glIsEnabled(GL_BLEND); - - if(!already_blend) - { - glEnable(GL_BLEND); - } - - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - bool already = glIsEnabled(GL_DEPTH_TEST); - glDisable(GL_DEPTH_TEST); - - glBindBuffer(GL_ARRAY_BUFFER, textVBO); - glBufferSubData(GL_ARRAY_BUFFER, 0, count * sizeof(dd::DrawVertex), glyphs); - - glDrawArrays(GL_TRIANGLES, 0, count); // Issue the draw call - - if(!already_blend) - { - glDisable(GL_BLEND); - } - - glUseProgram(0); - glBindVertexArray(0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindTexture(GL_TEXTURE_2D, 0); - checkGLError(__FILE__, __LINE__); - - if (already) - { - glEnable(GL_DEPTH_TEST); - } - } - - dd::GlyphTextureHandle createGlyphTexture(int width, int height, const void * pixels) override - { - assert(width > 0 && height > 0); - assert(pixels != nullptr); - - GLuint textureId = 0; - glGenTextures(1, &textureId); - glBindTexture(GL_TEXTURE_2D, textureId); - - glPixelStorei(GL_PACK_ALIGNMENT, 1); - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - - glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, width, height, 0, GL_RED, GL_UNSIGNED_BYTE, pixels); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - - glBindTexture(GL_TEXTURE_2D, 0); - checkGLError(__FILE__, __LINE__); - - return GLToHandle(textureId); - } - - void destroyGlyphTexture(dd::GlyphTextureHandle glyphTex) override - { - if (glyphTex == nullptr) - { - return; - } - - const GLuint textureId = handleToGL(glyphTex); - glBindTexture(GL_TEXTURE_2D, 0); - glDeleteTextures(1, &textureId); - } - - // These two can also be implemented to perform GL render - // state setup/cleanup, but we don't use them in this sample. - //void beginDraw() override { } - //void endDraw() override { } - - // - // Local methods: - // - - DDRenderInterfaceCoreGL() - : mvpMatrix() - , width(0) - , height(0) - , linePointProgram(0) - , linePointProgram_MvpMatrixLocation(-1) - , textProgram(0) - , textProgram_GlyphTextureLocation(-1) - , textProgram_ScreenDimensions(-1) - , linePointVAO(0) - , linePointVBO(0) - , textVAO(0) - , textVBO(0) - { - //std::printf("\n"); - //std::printf("GL_VENDOR : %s\n", glGetString(GL_VENDOR)); - //std::printf("GL_RENDERER : %s\n", glGetString(GL_RENDERER)); - //std::printf("GL_VERSION : %s\n", glGetString(GL_VERSION)); - //std::printf("GLSL_VERSION : %s\n\n", glGetString(GL_SHADING_LANGUAGE_VERSION)); - //std::printf("DDRenderInterfaceCoreGL initializing ...\n"); - - // Default OpenGL states: - glEnable(GL_CULL_FACE); - glEnable(GL_DEPTH_TEST); - glDisable(GL_BLEND); - - // This has to be enabled since the point drawing shader will use gl_PointSize. - glEnable(GL_PROGRAM_POINT_SIZE); - - setupShaderPrograms(); - setupVertexBuffers(); - - //std::printf("DDRenderInterfaceCoreGL ready!\n\n"); - } - - ~DDRenderInterfaceCoreGL() - { - glDeleteProgram(linePointProgram); - glDeleteProgram(textProgram); - - glDeleteVertexArrays(1, &linePointVAO); - glDeleteBuffers(1, &linePointVBO); - - glDeleteVertexArrays(1, &textVAO); - glDeleteBuffers(1, &textVBO); - } - - void setupShaderPrograms() - { - //std::printf("> DDRenderInterfaceCoreGL::setupShaderPrograms()\n"); - - // - // Line/point drawing shader: - // - { - GLuint linePointVS = glCreateShader(GL_VERTEX_SHADER); - glShaderSource(linePointVS, 1, &linePointVertShaderSrc, nullptr); - compileShader(linePointVS); - - GLint linePointFS = glCreateShader(GL_FRAGMENT_SHADER); - glShaderSource(linePointFS, 1, &linePointFragShaderSrc, nullptr); - compileShader(linePointFS); - - linePointProgram = glCreateProgram(); - glAttachShader(linePointProgram, linePointVS); - glAttachShader(linePointProgram, linePointFS); - - glBindAttribLocation(linePointProgram, 0, "in_Position"); - glBindAttribLocation(linePointProgram, 1, "in_ColorPointSize"); - linkProgram(linePointProgram); - - linePointProgram_MvpMatrixLocation = glGetUniformLocation(linePointProgram, "u_MvpMatrix"); - if (linePointProgram_MvpMatrixLocation < 0) - { - //errorF("Unable to get u_MvpMatrix uniform location!"); - } - checkGLError(__FILE__, __LINE__); - } - - // - // Text rendering shader: - // - { - GLuint textVS = glCreateShader(GL_VERTEX_SHADER); - glShaderSource(textVS, 1, &textVertShaderSrc, nullptr); - compileShader(textVS); - - GLint textFS = glCreateShader(GL_FRAGMENT_SHADER); - glShaderSource(textFS, 1, &textFragShaderSrc, nullptr); - compileShader(textFS); - - textProgram = glCreateProgram(); - glAttachShader(textProgram, textVS); - glAttachShader(textProgram, textFS); - - glBindAttribLocation(textProgram, 0, "in_Position"); - glBindAttribLocation(textProgram, 1, "in_TexCoords"); - glBindAttribLocation(textProgram, 2, "in_Color"); - linkProgram(textProgram); - - textProgram_GlyphTextureLocation = glGetUniformLocation(textProgram, "u_glyphTexture"); - if (textProgram_GlyphTextureLocation < 0) - { - //errorF("Unable to get u_glyphTexture uniform location!"); - } - - textProgram_ScreenDimensions = glGetUniformLocation(textProgram, "u_screenDimensions"); - if (textProgram_ScreenDimensions < 0) - { - //errorF("Unable to get u_screenDimensions uniform location!"); - } - - checkGLError(__FILE__, __LINE__); - } - } - - void setupVertexBuffers() - { - //std::printf("> DDRenderInterfaceCoreGL::setupVertexBuffers()\n"); - - // - // Lines/points vertex buffer: - // - { - glGenVertexArrays(1, &linePointVAO); - glGenBuffers(1, &linePointVBO); - checkGLError(__FILE__, __LINE__); - - glBindVertexArray(linePointVAO); - glBindBuffer(GL_ARRAY_BUFFER, linePointVBO); - - // RenderInterface will never be called with a batch larger than - // DEBUG_DRAW_VERTEX_BUFFER_SIZE vertexes, so we can allocate the same amount here. - glBufferData(GL_ARRAY_BUFFER, DEBUG_DRAW_VERTEX_BUFFER_SIZE * sizeof(dd::DrawVertex), nullptr, GL_STREAM_DRAW); - checkGLError(__FILE__, __LINE__); - - // Set the vertex format expected by 3D points and lines: - std::size_t offset = 0; - - glEnableVertexAttribArray(0); // in_Position (vec3) - glVertexAttribPointer( - /* index = */ 0, - /* size = */ 3, - /* type = */ GL_FLOAT, - /* normalize = */ GL_FALSE, - /* stride = */ sizeof(dd::DrawVertex), - /* offset = */ reinterpret_cast(offset)); - offset += sizeof(float) * 3; - - glEnableVertexAttribArray(1); // in_ColorPointSize (vec4) - glVertexAttribPointer( - /* index = */ 1, - /* size = */ 4, - /* type = */ GL_FLOAT, - /* normalize = */ GL_FALSE, - /* stride = */ sizeof(dd::DrawVertex), - /* offset = */ reinterpret_cast(offset)); - - checkGLError(__FILE__, __LINE__); - - // VAOs can be a pain in the neck if left enabled... - glBindVertexArray(0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - } - - // - // Text rendering vertex buffer: - // - { - glGenVertexArrays(1, &textVAO); - glGenBuffers(1, &textVBO); - checkGLError(__FILE__, __LINE__); - - glBindVertexArray(textVAO); - glBindBuffer(GL_ARRAY_BUFFER, textVBO); - - // NOTE: A more optimized implementation might consider combining - // both the lines/points and text buffers to save some memory! - glBufferData(GL_ARRAY_BUFFER, DEBUG_DRAW_VERTEX_BUFFER_SIZE * sizeof(dd::DrawVertex), nullptr, GL_STREAM_DRAW); - checkGLError(__FILE__, __LINE__); - - // Set the vertex format expected by the 2D text: - std::size_t offset = 0; - - glEnableVertexAttribArray(0); // in_Position (vec2) - glVertexAttribPointer( - /* index = */ 0, - /* size = */ 2, - /* type = */ GL_FLOAT, - /* normalize = */ GL_FALSE, - /* stride = */ sizeof(dd::DrawVertex), - /* offset = */ reinterpret_cast(offset)); - offset += sizeof(float) * 2; - - glEnableVertexAttribArray(1); // in_TexCoords (vec2) - glVertexAttribPointer( - /* index = */ 1, - /* size = */ 2, - /* type = */ GL_FLOAT, - /* normalize = */ GL_FALSE, - /* stride = */ sizeof(dd::DrawVertex), - /* offset = */ reinterpret_cast(offset)); - offset += sizeof(float) * 2; - - glEnableVertexAttribArray(2); // in_Color (vec4) - glVertexAttribPointer( - /* index = */ 2, - /* size = */ 4, - /* type = */ GL_FLOAT, - /* normalize = */ GL_FALSE, - /* stride = */ sizeof(dd::DrawVertex), - /* offset = */ reinterpret_cast(offset)); - - checkGLError(__FILE__, __LINE__); - - // Ditto. - glBindVertexArray(0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - } - } - - static GLuint handleToGL(dd::GlyphTextureHandle handle) - { - const std::size_t temp = reinterpret_cast(handle); - return static_cast(temp); - } - - static dd::GlyphTextureHandle GLToHandle(const GLuint id) - { - const std::size_t temp = static_cast(id); - return reinterpret_cast(temp); - } - - static void checkGLError(const char * file, const int line) - { - GLenum err; - while ((err = glGetError()) != GL_NO_ERROR) - { - //errorF("%s(%d) : GL_CORE_ERROR=0x%X - %s", file, line, err, errorToString(err)); - } - } - - static void compileShader(const GLuint shader) - { - glCompileShader(shader); - checkGLError(__FILE__, __LINE__); - - GLint status; - glGetShaderiv(shader, GL_COMPILE_STATUS, &status); - checkGLError(__FILE__, __LINE__); - - if (status == GL_FALSE) - { - GLchar strInfoLog[1024] = {0}; - glGetShaderInfoLog(shader, sizeof(strInfoLog) - 1, nullptr, strInfoLog); - //errorF("\n>>> Shader compiler errors:\n%s", strInfoLog); - } - } - - static void linkProgram(const GLuint program) - { - glLinkProgram(program); - checkGLError(__FILE__, __LINE__); - - GLint status; - glGetProgramiv(program, GL_LINK_STATUS, &status); - checkGLError(__FILE__, __LINE__); - - if (status == GL_FALSE) - { - GLchar strInfoLog[1024] = {0}; - glGetProgramInfoLog(program, sizeof(strInfoLog) - 1, nullptr, strInfoLog); - //errorF("\n>>> Program linker errors:\n%s", strInfoLog); - } - } - - // The "model-view-projection" matrix for the scene. - // In this demo, it consists of the camera's view and projection matrices only. - math::float4x4 mvpMatrix; - unsigned width, height; - -private: - - GLuint linePointProgram; - GLint linePointProgram_MvpMatrixLocation; - - GLuint textProgram; - GLint textProgram_GlyphTextureLocation; - GLint textProgram_ScreenDimensions; - - GLuint linePointVAO; - GLuint linePointVBO; - - GLuint textVAO; - GLuint textVBO; - - static const char * linePointVertShaderSrc; - static const char * linePointFragShaderSrc; - - static const char * textVertShaderSrc; - static const char * textFragShaderSrc; - - -}; // class DDRenderInterfaceCoreGL - -// ======================================================== -// Minimal shaders we need for the debug primitives: -// ======================================================== - -const char * DDRenderInterfaceCoreGL::linePointVertShaderSrc = "\n" - "#version 150\n" - "\n" - "in vec3 in_Position;\n" - "in vec4 in_ColorPointSize;\n" - "\n" - "out vec4 v_Color;\n" - "uniform mat4 u_MvpMatrix;\n" - "\n" - "void main()\n" - "{\n" - " gl_Position = u_MvpMatrix * vec4(in_Position, 1.0);\n" - " gl_PointSize = in_ColorPointSize.w;\n" - " v_Color = vec4(in_ColorPointSize.xyz, 1.0);\n" - "}\n"; - -const char * DDRenderInterfaceCoreGL::linePointFragShaderSrc = "\n" - "#version 150\n" - "\n" - "in vec4 v_Color;\n" - "out vec4 out_FragColor;\n" - "\n" - "void main()\n" - "{\n" - " out_FragColor = v_Color;\n" - "}\n"; - -const char * DDRenderInterfaceCoreGL::textVertShaderSrc = "\n" - "#version 150\n" - "\n" - "in vec2 in_Position;\n" - "in vec2 in_TexCoords;\n" - "in vec3 in_Color;\n" - "\n" - "uniform vec2 u_screenDimensions;\n" - "\n" - "out vec2 v_TexCoords;\n" - "out vec4 v_Color;\n" - "\n" - "void main()\n" - "{\n" - " // Map to normalized clip coordinates:\n" - " float x = ((2.0 * (in_Position.x - 0.5)) / u_screenDimensions.x) - 1.0;\n" - " float y = 1.0 - ((2.0 * (in_Position.y - 0.5)) / u_screenDimensions.y);\n" - "\n" - " gl_Position = vec4(x, y, 0.0, 1.0);\n" - " v_TexCoords = in_TexCoords;\n" - " v_Color = vec4(in_Color, 1.0);\n" - "}\n"; - -const char * DDRenderInterfaceCoreGL::textFragShaderSrc = "\n" - "#version 150\n" - "\n" - "in vec2 v_TexCoords;\n" - "in vec4 v_Color;\n" - "\n" - "uniform sampler2D u_glyphTexture;\n" - "out vec4 out_FragColor;\n" - "\n" - "void main()\n" - "{\n" - " out_FragColor = v_Color;\n" - " out_FragColor.a = texture(u_glyphTexture, v_TexCoords).r;\n" - "}\n"; - -DDRenderInterfaceCoreGL* ModuleDebugDraw::implementation = 0; - -ModuleDebugDraw::ModuleDebugDraw() -{ -} - -ModuleDebugDraw::~ModuleDebugDraw() -{ -} - -bool ModuleDebugDraw::Init() -{ - implementation = new DDRenderInterfaceCoreGL; - dd::initialize(implementation); - return true; -} - - -bool ModuleDebugDraw::CleanUp() -{ - dd::shutdown(); - - delete implementation; - implementation = 0; - - return true; -} - -update_status ModuleDebugDraw::Update() -{ - dd::axisTriad(float4x4::identity, 0.5f, 5.0f); - dd::xzSquareGrid(-30, 30, -0.1f, 1.0f, dd::colors::Gray); - return UPDATE_CONTINUE; -} - -void ModuleDebugDraw::Draw(const float4x4& view, const float4x4& proj, unsigned width, unsigned height) -{ - implementation->width = width; - implementation->height = height; - implementation->mvpMatrix = proj * view; - - dd::flush(); -} - - diff --git a/Source/ModuleDebugDraw.h b/Source/ModuleDebugDraw.h deleted file mode 100644 index 82fd9ec6f..000000000 --- a/Source/ModuleDebugDraw.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef _MODULE_DEBUGDRAW_H_ -#define _MODULE_DEBUGDRAW_H_ - -#include "Module.h" - -#include "Math/float4x4.h" - -class DDRenderInterfaceCoreGL; -class Camera; - -class ModuleDebugDraw : public Module -{ - -public: - - ModuleDebugDraw(); - ~ModuleDebugDraw(); - - bool Init(); - update_status Update(); - bool CleanUp(); - - void Draw(const float4x4& view, const float4x4& proj, unsigned width, unsigned height); -private: - - static DDRenderInterfaceCoreGL* implementation; -}; - -#endif /* _MODULE_DEBUGDRAW_H_ */ diff --git a/Source/ModuleEditor.cpp b/Source/ModuleEditor.cpp deleted file mode 100644 index 67ca44585..000000000 --- a/Source/ModuleEditor.cpp +++ /dev/null @@ -1,246 +0,0 @@ -#include "Globals.h" -#include "Application.h" -#include "ModuleEditor.h" -#include "ModuleWindow.h" -#include "ModuleRender.h" -#include "ModuleCamera.h" -#include "ModuleTexture.h" -#include "ModuleHardware.h" -#include "ModuleScene.h" -#include "Model.h" -#include "SDL.h" - -#include "ImGuiComponents/AppLog.h" - -#include "glew.h" -#include "imgui.h" -#include "imgui_impl_sdl.h" -#include "imgui_impl_opengl3.h" - - -ModuleEditor::~ModuleEditor() -{ -} - -// 006 - Creation Context -bool ModuleEditor::Init() -{ - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); - ImGuiIO& io = ImGui::GetIO(); (void)io; - - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard | ImGuiConfigFlags_NavEnableSetMousePos | ImGuiConfigFlags_DockingEnable; // Enable Keyboard Controls - io.WantSetMousePos = true; - - ImGui_ImplSDL2_InitForOpenGL(App->window->getWindow(), App->renderer->context); - ImGui_ImplOpenGL3_Init(); - - Logger->setShowConsole(!Logger->getShowConsole()); - - // Setup style - ImGui::StyleColorsDark(); - - io.IniFilename = "imgui.ini"; - - return true; -} - -update_status ModuleEditor::PreUpdate() -{ - ImGui_ImplOpenGL3_NewFrame(); - ImGui_ImplSDL2_NewFrame(); - ImGui::NewFrame(); - - constexpr ImGuiWindowFlags dockspace_flags = ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoTitleBar | - ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus | - ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_MenuBar; - - const ImGuiViewport* viewport = ImGui::GetMainViewport(); - ImGui::SetNextWindowPos(viewport->Pos); - ImGui::SetNextWindowSize(viewport->Size); - ImGui::SetNextWindowViewport(viewport->ID); - - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); - ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); - - ImGui::Begin("Dockspace", nullptr, dockspace_flags); - ImGui::PopStyleVar(3); - - const ImGuiID dockSpaceId = ImGui::GetID("DockspaceID"); - - ImGui::DockSpace(dockSpaceId, ImVec2(0.0f, 0.0f), ImGuiDockNodeFlags_PassthruCentralNode); - - if (ImGui::BeginMainMenuBar()) - { - if (ImGui::BeginMenu("Help")) - { - if (ImGui::MenuItem("Documentation")) - App->RequestBrowser("https://github.com/Erick9Thor/Engine3D/wiki"); - - if (ImGui::MenuItem("Download latest")) - App->RequestBrowser("https://github.com/Erick9Thor/Engine3D/releases"); - - if (ImGui::MenuItem("Report a bug")) - App->RequestBrowser("https://github.com/Erick9Thor/Engine3D/issues"); - - if (ImGui::MenuItem("Download source!")) - App->RequestBrowser("https://www.youtube.com/watch?v=dQw4w9WgXcQ"); - - if (ImGui::MenuItem("About")) - show_abaout = !show_abaout; - - if (ImGui::MenuItem("Quit")) - return UPDATE_STOP; - - ImGui::EndMenu(); - } - - if (ImGui::BeginMenu("Tools")) - { - if (ImGui::MenuItem("Console")) - { - Logger->setShowConsole(!Logger->getShowConsole()); - } - if (ImGui::MenuItem("Camera")) - { - show_camera_window = !show_camera_window; - } - if (ImGui::MenuItem("FPS counter")) - { - show_fps_counter = !show_fps_counter; - } - ImGui::EndMenu(); - } - } - - ImGui::EndMainMenuBar(); - - ImGui::End(); - - return UPDATE_CONTINUE; -} - -update_status ModuleEditor::Update() -{ - showMenu(); - Draw(); - return UPDATE_CONTINUE; -} - -update_status ModuleEditor::PostUpdate() -{ - return UPDATE_CONTINUE; -} - -void ModuleEditor::Draw() -{ - ImGui::Render(); - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - SDL_GL_MakeCurrent(App->window->getWindow(), App->renderer->context); -} - -bool ModuleEditor::CleanUp() -{ - ImGui_ImplOpenGL3_Shutdown(); - ImGui_ImplSDL2_Shutdown(); - ImGui::DestroyContext(); - - return true; -} - -void ModuleEditor::showMenu() -{ - if (show_camera_window) - { - if (ImGui::Begin("Camera", &show_camera_window)) - { - App->camera->DrawGui(); - } - ImGui::End(); - } - - if (show_fps_counter) - { - if (ImGui::Begin("FPS counter", &show_fps_counter)) - { - showFPSGraph(); - } - ImGui::End(); - } - - if (show_model_window) - { - if (ImGui::Begin("Model", &show_model_window)) - { - App->scene->DrawGui(); - } - ImGui::End(); - } - - if (Logger->getShowConsole()) Logger->Draw(); - - if (show_abaout) { - if (ImGui::Begin("Aboout Window", &show_abaout)) - { - showAbaoutInfo(); - } - ImGui::End(); - } -} - -void ModuleEditor::showFPSGraph() { - ImGui::Text("Limit Framerate:"); - ImGui::SameLine(); - ImGui::TextColored(ImVec4(1.f, 1.f, 0.f, 1.f), "%i", App->GetFramerateLimit()); - - char title[25]; - sprintf_s(title, 25, "Framerate %.1f", fps_log[fps_log.size() - 1]); - ImGui::PlotHistogram("##framerate", &fps_log[0], fps_log.size(), 0, title, 0.0f, 100.0f, ImVec2(310, 100)); - sprintf_s(title, 25, "Milliseconds %0.1f", ms_log[ms_log.size() - 1]); - ImGui::PlotHistogram("##milliseconds", &ms_log[0], ms_log.size(), 0, title, 0.0f, 40.0f, ImVec2(310, 100)); -} - -void ModuleEditor::AddFPS(float fps, float ms) -{ - static int count = 0; - - if (count == FPS_LOG_SIZE) - { - for (int i = 0; i < FPS_LOG_SIZE - 1; ++i) - { - fps_log[i] = fps_log[i + 1]; - ms_log[i] = ms_log[i + 1]; - } - } - else - ++count; - - fps_log[count - 1] = fps; - ms_log[count - 1] = ms; -} - -void ModuleEditor::showAbaoutInfo() -{ - ImGui::Text("Engine name: %s", TITLE); - ImGui::Text("Version: %s", ENGINE_VERSION); - ImGui::Separator(); - ImGui::Text("My Engine for C++ UPC MASTER!"); - ImGui::Text("Eric Torres Perramon"); - ImGui::Separator(); - ImGui::Text("SDL Version: %d.%d.%d", App->hw->GetInfo().sdl_version.major, - App->hw->GetInfo().sdl_version.minor, App->hw->GetInfo().sdl_version.patch); - ImGui::Separator(); - ImGui::Text("CPUs: %d", App->hw->GetInfo().n_cpu); - ImGui::Text("System RAM: %.1f Gb", App->hw->GetInfo().ram_gb); - ImGui::Separator(); - ImGui::Text("GPU: %s", App->hw->GetInfo().gpu); - ImGui::Text("Brand: %s", App->hw->GetInfo().gpu_brand); - ImGui::Text("VRAM Budget: %.1f Mb", (float) App->hw->GetInfo().vram_mb_budget); - ImGui::Text("Vram Avaliable: %.1f Mb", (float) App->hw->GetInfo().vram_mb_free); - ImGui::Text("Vram Usage: %.1f Mb", (float) (App->hw->GetInfo().vram_mb_budget - App->hw->GetInfo().vram_mb_free)); - ImGui::Separator(); - ImGui::Text("DevIL lib version %d", IL_VERSION); -} - diff --git a/Source/ModuleEditor.h b/Source/ModuleEditor.h deleted file mode 100644 index 8fee899e3..000000000 --- a/Source/ModuleEditor.h +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include "Globals.h" -#include "Application.h" -#include "ModuleRender.h" -#include "ModuleWindow.h" -#include "SDL.h" -#include "glew.h" - -#include "ImGuiComponents/AppLog.h" - -class ModuleEditor : public Module -{ -public: - ModuleEditor() :fps_log(100), ms_log(100) {}; - ~ModuleEditor(); - - bool Init(); - update_status PreUpdate(); - update_status Update(); - update_status PostUpdate(); - bool CleanUp(); - - void Draw(); - - void showMenu(); - - void showFPSGraph(); - void AddFPS(float fps, float ms); - - void showAbaoutInfo(); - -private: - std::vector ms_log; - std::vector fps_log; - - bool show_model_window = true; - bool show_abaout = false; - bool show_camera_window = false; - bool show_fps_counter = false; -}; diff --git a/Source/ModuleInput.cpp b/Source/ModuleInput.cpp deleted file mode 100644 index b36ebd2a4..000000000 --- a/Source/ModuleInput.cpp +++ /dev/null @@ -1,115 +0,0 @@ -#include "Globals.h" -#include "ModuleInput.h" -#include "ModuleRender.h" -#include "ModuleCamera.h" -#include "ModuleWindow.h" -#include "SDL.h" - -#include "Application.h" -#include "ModuleScene.h" - -#include "imgui_impl_sdl.h" - -ModuleInput::ModuleInput() -{} - -// Destructor -ModuleInput::~ModuleInput() -{} - -// Called before render is available -bool ModuleInput::Init() -{ - LOG("[M_INPUT] Init SDL input event system"); - bool ret = true; - SDL_Init(0); - - if(SDL_InitSubSystem(SDL_INIT_EVENTS) < 0) - { - LOG("[M_INPUT] SDL_EVENTS could not initialize! SDL_Error: %s\n", SDL_GetError()); - ret = false; - } - - keyboard = SDL_GetKeyboardState(NULL); - - for (int i = 0; i < NUM_MOUSE_BUTTONS; i++) - mouse_buttons[i] = false; - - return ret; -} - -// Called every draw update -update_status ModuleInput::PreUpdate() -{ - SDL_PumpEvents(); - - SDL_Event event; - - mouse_wheel = false; - mouse_motion_x = mouse_motion_y = 0; - mouse_wheel_x = mouse_wheel_y = 0; - - while (SDL_PollEvent(&event) != 0) - { - ImGui_ImplSDL2_ProcessEvent(&event); - switch (event.type) - { - case SDL_QUIT: - return UPDATE_STOP; - case SDL_WINDOWEVENT: - if (event.window.event == SDL_WINDOWEVENT_RESIZED || event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) - App->window->WindowResized(); - break; - case SDL_MOUSEBUTTONDOWN: - mouse_buttons[event.button.button - 1] = true; - break; - case SDL_MOUSEBUTTONUP: - mouse_buttons[event.button.button - 1] = false; - break; - case SDL_MOUSEMOTION: - mouse_motion_x = event.motion.xrel; - mouse_motion_y = event.motion.yrel; - break; - case SDL_MOUSEWHEEL: - mouse_wheel = true; - mouse_wheel_x = event.wheel.x; - mouse_wheel_y = event.wheel.y; - break; - case SDL_DROPFILE: - { - LOG("[M_INPUT] Dropped file: %s", event.drop.file); - - App->scene->LoadModel(event.drop.file); - - SDL_free(event.drop.file); - break; - } - } - } - - return UPDATE_CONTINUE; -} - -// Called before quitting -bool ModuleInput::CleanUp() -{ - LOG("[M_INPUT] Quitting SDL input event subsystem"); - SDL_QuitSubSystem(SDL_INIT_EVENTS); - return true; -} - -const Uint8 ModuleInput::GetKey(SDL_Scancode key) const -{ - return keyboard[key]; -} - -const bool ModuleInput::GetMouseButton(unsigned int key) const -{ - return mouse_buttons[key - 1]; -} - -const void ModuleInput::GetMouseMotion(int& x, int& y) const -{ - x = mouse_motion_x; - y = mouse_motion_y; -} \ No newline at end of file diff --git a/Source/ModuleInput.h b/Source/ModuleInput.h deleted file mode 100644 index 5b8335e0c..000000000 --- a/Source/ModuleInput.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include "Module.h" -#include "Globals.h" -#include "SDL.h" - -typedef unsigned __int8 Uint8; - -#define NUM_MOUSE_BUTTONS 5 - -class ModuleInput : public Module -{ -public: - - ModuleInput(); - ~ModuleInput(); - - bool Init(); - update_status PreUpdate() override; - bool CleanUp() override; - - const Uint8 GetKey(SDL_Scancode key) const; - const bool GetMouseButton(unsigned int key) const; - const void GetMouseMotion(int& x, int& y) const; - - const bool MouseWheel() const { return mouse_wheel; }; - const void GetMouseWheel(int& x, int& y) const { x = mouse_wheel_x; y = mouse_wheel_y; }; - -private: - const Uint8* keyboard = NULL; - bool mouse_buttons[NUM_MOUSE_BUTTONS]; - int mouse_motion_x; - int mouse_motion_y; - bool mouse_wheel; - int mouse_wheel_x, mouse_wheel_y; -}; diff --git a/Source/ModuleProgram.cpp b/Source/ModuleProgram.cpp deleted file mode 100644 index 1a8051419..000000000 --- a/Source/ModuleProgram.cpp +++ /dev/null @@ -1,95 +0,0 @@ -#include "ModuleProgram.h" - -#include "glew.h" - -#include "MathGeoLib.h" - -ModuleProgram::ModuleProgram() -{ - -} - -bool ModuleProgram::Init() -{ - static const bool transpose = GL_TRUE; - vertex_shader_id = CompileShader(GL_VERTEX_SHADER, LoadShaderSource("vertex.glsl")); - fragment_shader_id = CompileShader(GL_FRAGMENT_SHADER, LoadShaderSource("fragment.glsl")); - program_id = CreateProgram(vertex_shader_id, fragment_shader_id); - - glUseProgram(program_id); - - return true; -} - -char* ModuleProgram::LoadShaderSource(const char* shader_file_name) -{ - char* data = nullptr; - FILE* file = nullptr; - fopen_s(&file, shader_file_name, "rb"); - if (file) - { - fseek(file, 0, SEEK_END); - int size = ftell(file); - data = (char*)malloc(size + 1); - fseek(file, 0, SEEK_SET); - fread(data, 1, size, file); - data[size] = 0; - fclose(file); - } - return data; -} - -unsigned int ModuleProgram::CompileShader(unsigned type, const char* source) -{ - unsigned shader_id = glCreateShader(type); - glShaderSource(shader_id, 1, &source, 0); - glCompileShader(shader_id); - int res = GL_FALSE; - glGetShaderiv(shader_id, GL_COMPILE_STATUS, &res); - if (res == GL_FALSE) - { - int len = 0; - glGetShaderiv(shader_id, GL_INFO_LOG_LENGTH, &len); - if (len > 0) - { - int written = 0; - char* info = (char*)malloc(len); - glGetShaderInfoLog(shader_id, len, &written, info); - LOG("Log Info: %s", info); - free(info); - } - } - return shader_id; -} - -unsigned ModuleProgram::CreateProgram(unsigned vtx_shader, unsigned frg_shader) -{ - unsigned program_id = glCreateProgram(); - glAttachShader(program_id, vtx_shader); - glAttachShader(program_id, frg_shader); - glLinkProgram(program_id); - int res; - glGetProgramiv(program_id, GL_LINK_STATUS, &res); - if (res == GL_FALSE) - { - int len = 0; - glGetProgramiv(program_id, GL_INFO_LOG_LENGTH, &len); - if (len > 0) - { - int written = 0; - char* info = (char*)malloc(len); - glGetProgramInfoLog(program_id, len, &written, info); - LOG("Program Log Info: %s", info); - free(info); - } - } - glDeleteShader(vtx_shader); - glDeleteShader(frg_shader); - return program_id; -} - -bool ModuleProgram::CleanUp() -{ - glDeleteProgram(program_id); - return true; -} \ No newline at end of file diff --git a/Source/ModuleProgram.h b/Source/ModuleProgram.h deleted file mode 100644 index 102c0e567..000000000 --- a/Source/ModuleProgram.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once -#include "Module.h" - -#include "MathGeoLib.h" - -class ModuleProgram : - public Module -{ - public: - ModuleProgram(); - - bool Init(); - char* LoadShaderSource(const char* shader_file_name); - unsigned int CompileShader(unsigned type, const char* source); - unsigned CreateProgram(unsigned vtx_shader, unsigned frg_shader); - - bool CleanUp(); - - unsigned int GetProgramID() { return program_id; } - - private: - unsigned int vertex_shader_id; - unsigned int fragment_shader_id; - unsigned int program_id; -}; - diff --git a/Source/ModuleRender.cpp b/Source/ModuleRender.cpp deleted file mode 100644 index 21f6eb550..000000000 --- a/Source/ModuleRender.cpp +++ /dev/null @@ -1,183 +0,0 @@ -#include "Globals.h" -#include "Application.h" -#include "ModuleRender.h" -#include "ModuleWindow.h" -#include "ModuleProgram.h" -#include "ModuleCamera.h" -#include "ModuleDebugDraw.h" - -#include "SDL.h" -#include "glew.h" - -#include "MathGeoLib.h" -#include "il.h" -#include "ilu.h" - -void __stdcall DebugMessageGL(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) -{ - if (id == 131185 || id == 131204) return; - - char tmp_string[4096]; - - const char* tmp_source = "", * tmp_type = ""; - - switch (source) - { - case GL_DEBUG_SOURCE_API: tmp_source = "API"; break; - case GL_DEBUG_SOURCE_WINDOW_SYSTEM: tmp_source = "Window System"; break; - case GL_DEBUG_SOURCE_SHADER_COMPILER: tmp_source = "Shader Compiler"; break; - case GL_DEBUG_SOURCE_THIRD_PARTY: tmp_source = "Third Party"; break; - case GL_DEBUG_SOURCE_APPLICATION: tmp_source = "Application"; break; - case GL_DEBUG_SOURCE_OTHER: tmp_source = "Other"; break; - }; - - switch (type) - { - case GL_DEBUG_TYPE_ERROR: tmp_type = "Error"; break; - case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: tmp_type = "Deprecated Behaviour"; break; - case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: tmp_type = "Undefined Behaviour"; break; - case GL_DEBUG_TYPE_PORTABILITY: tmp_type = "Portability"; break; - case GL_DEBUG_TYPE_PERFORMANCE: tmp_type = "Performance"; break; - case GL_DEBUG_TYPE_MARKER: tmp_type = "Marker"; break; - case GL_DEBUG_TYPE_PUSH_GROUP: tmp_type = "Push Group"; break; - case GL_DEBUG_TYPE_POP_GROUP: tmp_type = "Pop Group"; break; - case GL_DEBUG_TYPE_OTHER: tmp_type = "Other"; break; - }; - - - const char* tmp_severity = ""; - switch (severity) - { - case GL_DEBUG_SEVERITY_HIGH: tmp_severity = "high"; break; - case GL_DEBUG_SEVERITY_MEDIUM: tmp_severity = "medium"; break; - case GL_DEBUG_SEVERITY_LOW: tmp_severity = "low"; break; - case GL_DEBUG_SEVERITY_NOTIFICATION: tmp_severity = "notification"; break; - }; - - sprintf_s(tmp_string, 4095, " \n", tmp_source, tmp_type, tmp_severity, id, message); - OutputDebugString(tmp_string); -} - -ModuleRender::ModuleRender() -{ -} - -ModuleRender::~ModuleRender() -{ - SDL_GL_DeleteContext(context); -} - -bool ModuleRender::initializeOpenGLviaSDL() -{ - - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); // desired version - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 6); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY); - - SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); // we want a double buffer - SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); // we want to have a depth buffer with 24 bits - SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); // we want to have a stencil buffer with 8 bits - SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG); // enable context debug - - LOG("[M_Render] Creating Renderer context"); - context = SDL_GL_CreateContext(App->window->getWindow()); - - if (context == nullptr) - { - LOG("[M_Render] OpenGL context could not be created! SDL_Error: %s\n", SDL_GetError()); - return false; - } - - return true; -} - -bool ModuleRender::initGlew() -{ - GLenum err = glewInit(); - - if (err != GLEW_OK) - { - LOG("[M_Render] Glew library could not init %s\n", glewGetErrorString(err)); - return false; - } - else - LOG("[M_Render] Using Glew %s", glewGetString(GLEW_VERSION)); - - LOG("[M_Render] Vendor: %s", glGetString(GL_VENDOR)); - LOG("[M_Render] Renderer: %s", glGetString(GL_RENDERER)); - LOG("[M_Render] OpenGL version supported %s", glGetString(GL_VERSION)); - LOG("[M_Render] GLSL: %s\n", glGetString(GL_SHADING_LANGUAGE_VERSION)); - - return true; -} - -bool ModuleRender::Init() -{ - LOG("[M_Render] Creating 3D Renderer context"); - bool ret = true; - - ret = initializeOpenGLviaSDL(); - - if (ret != true) return false; - - ret = initGlew(); - - if (ret == true) { - - glEnable(GL_DEPTH_TEST); // Enable depth test - glEnable(GL_CULL_FACE); // Enable cull backward faces - glFrontFace(GL_CCW); // Front faces will be counter clockwise - - glEnable(GL_DEBUG_OUTPUT); // Enable output callback - glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); - glDebugMessageCallback(&DebugMessageGL, nullptr); // Set the callback - glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, true); // Filter notifications - - int w, h; - SDL_GetWindowSize(App->window->getWindow(), &w, &h); - glViewport(0, 0, w, h); - } - - return true; -} - -update_status ModuleRender::PreUpdate() -{ - glClearColor(0.1f, 0.1f, 0.1f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - return UPDATE_CONTINUE; -} - -// Called every draw update -update_status ModuleRender::Update() -{ - App->debug_draw->Draw(App->camera->GetView(), App->camera->GetProjection(), App->window->getScreenSurface()->w, App->window->getScreenSurface()->h); - - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - return UPDATE_CONTINUE; -} - -update_status ModuleRender::PostUpdate() -{ - SDL_GL_SwapWindow(App->window->getWindow()); - - return UPDATE_CONTINUE; -} - -// Called before quitting -bool ModuleRender::CleanUp() -{ - LOG("[M_Render] Destroying renderer"); - SDL_GL_DeleteContext(context); - return true; -} - -void ModuleRender::WindowResized(unsigned int width, unsigned int height) -{ - SDL_GetWindowSize(App->window->getWindow(), (int*)&width, (int*)&height); - glViewport(0, 0, width, height); -} - diff --git a/Source/ModuleRender.h b/Source/ModuleRender.h deleted file mode 100644 index 1375d54c4..000000000 --- a/Source/ModuleRender.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once -#include "Module.h" -#include "Globals.h" -#include "Model.h" - -struct SDL_Texture; -struct SDL_Renderer; -struct SDL_Rect; - -class ModuleRender : public Module -{ - public: - ModuleRender(); - ~ModuleRender(); - - bool Init(); - update_status PreUpdate(); - update_status Update(); - update_status PostUpdate(); - bool CleanUp(); - void WindowResized(unsigned int width, unsigned int height); - - bool initGlew(); - bool initializeOpenGLviaSDL(); - - void* context; - - Model* house = nullptr; - - private: - unsigned int texture_id; -}; diff --git a/Source/ModuleScene.cpp b/Source/ModuleScene.cpp deleted file mode 100644 index c1abc8f37..000000000 --- a/Source/ModuleScene.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include "ModuleScene.h" -#include "Application.h" -#include "ModuleCamera.h" -#include "ModuleTexture.h" -#include "ModuleWindow.h" -#include "Model.h" - -bool ModuleScene::Init() -{ - LOG("[Scene] Creating Model"); - scene_model = new Model("BakerHouse.fbx"); - - return true; -} - -update_status ModuleScene::PreUpdate() -{ - return UPDATE_CONTINUE; -} - -update_status ModuleScene::Update() -{ - scene_model->Draw(); - return UPDATE_CONTINUE; -} - -update_status ModuleScene::PostUpdate() -{ - return UPDATE_CONTINUE; -} - -bool ModuleScene::CleanUp() -{ - return false; -} - -void ModuleScene::LoadModel(const char* _fileName) -{ - delete scene_model; - scene_model = new Model(_fileName); - App->camera->FocusOnModel(scene_model); -} - -void ModuleScene::DrawGui() -{ - scene_model->DrawGui(); -} diff --git a/Source/ModuleScene.h b/Source/ModuleScene.h deleted file mode 100644 index 8b6b47da0..000000000 --- a/Source/ModuleScene.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once -#include "Module.h" -#include "Math.h" - - -class Model; - -class ModuleScene : - public Module -{ - public: - bool Init(); - update_status PreUpdate(); - update_status Update(); - update_status PostUpdate(); - bool CleanUp(); - - void LoadModel(const char* _fileName); - - void DrawGui(); - - Model* scene_model; - - private: - unsigned int progam_id; -}; - diff --git a/Source/ModuleTexture.cpp b/Source/ModuleTexture.cpp deleted file mode 100644 index 286436169..000000000 --- a/Source/ModuleTexture.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#include "ModuleTexture.h" - -#include "glew.h" - -#include "il.h" -#include "ilu.h" - -ModuleTexture::ModuleTexture() -{ -} - -ModuleTexture::~ModuleTexture() -{ -} - -bool ModuleTexture::Init() -{ - LOG("[M_Texture] Init Image library using DevIL lib version %d", IL_VERSION); - ilInit(); - return true; -} - -bool ModuleTexture::CleanUp() -{ - ilShutDown(); - return true; -} - -unsigned int ModuleTexture::GetTextureData(const char* path) -{ - - ILuint texture_id; - ilGenImages(1, &texture_id); - ilBindImage(texture_id); - - if (!ilLoadImage(path)) { - LOG("[M_Texture] Error loading texture from: %s", path); - return 0; - } - - ilConvertImage(IL_RGBA, IL_UNSIGNED_BYTE); - - ILinfo textureInfo; - iluGetImageInfo(&textureInfo); - if (textureInfo.Origin == IL_ORIGIN_UPPER_LEFT) - iluFlipImage(); - - return texture_id; -} - -void ModuleTexture::DrawGui() { - -} \ No newline at end of file diff --git a/Source/ModuleTexture.h b/Source/ModuleTexture.h deleted file mode 100644 index 1f4d1300a..000000000 --- a/Source/ModuleTexture.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once -#include "Module.h" -#include "Texture.h" - -#include "il.h" -#include "ilu.h" - -#include - -class ModuleTexture : public Module -{ - public: - ModuleTexture(); - ~ModuleTexture(); - - bool Init(); - bool CleanUp(); - - unsigned int GetTextureData(const char* path); - void CleanTexture(unsigned int& texture); - - void DrawGui(); - - ILuint texture_id; -}; \ No newline at end of file diff --git a/Source/ModuleWindow.cpp b/Source/ModuleWindow.cpp deleted file mode 100644 index 7fde660b3..000000000 --- a/Source/ModuleWindow.cpp +++ /dev/null @@ -1,112 +0,0 @@ -#include "Globals.h" -#include "Application.h" -#include "ModuleRender.h" -#include "ModuleWindow.h" -#include "ModuleCamera.h" - -ModuleWindow::ModuleWindow() -{ -} - -ModuleWindow::~ModuleWindow() -{ -} - -bool ModuleWindow::Init() -{ - LOG("[M_Window] Init SDL window & surface"); - bool ret = true; - - if(SDL_Init(SDL_INIT_VIDEO) < 0) - { - LOG("[M_Window] SDL_VIDEO could not initialize! SDL_Error: %s\n", SDL_GetError()); - ret = false; - } - else - { - SDL_DisplayMode DM; - SDL_GetCurrentDisplayMode(0, &DM); - - width = DM.w * 0.75f; - height = DM.h * 0.75f; - - Uint32 flags = SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL; - - if (FULLSCREEN == true) - flags |= SDL_WINDOW_FULLSCREEN; - - if (RELIZABLE == true) - flags |= SDL_WINDOW_RESIZABLE; - - - window = SDL_CreateWindow(TITLE, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, flags); - - if(window == NULL) - { - LOG("[M_Window] Window could not be created! SDL_Error: %s\n", SDL_GetError()); - ret = false; - } - else - { - //Get window surface - screen_surface = SDL_GetWindowSurface(window); - } - } - - return ret; -} - -// Called before quitting -bool ModuleWindow::CleanUp() -{ - LOG("[M_Window] Destroying SDL window and quitting all SDL systems"); - - if(window != NULL) - { - SDL_DestroyWindow(window); - } - - SDL_Quit(); - return true; -} - -void ModuleWindow::SetFullscreen(bool set) { - if (set != fullscreen) - { - fullscreen = set; - if (fullscreen == true) - { - if (SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN) != 0) - LOG("[M_Window] Could not switch to fullscreen: %s\n", SDL_GetError()); - fullscreen_desktop = false; - } - else - { - if (SDL_SetWindowFullscreen(window, 0) != 0) - LOG("[M_Window] Could not switch to windowed: %s\n", SDL_GetError()); - } - } -} - -bool ModuleWindow::IsFullscreen() const -{ - return fullscreen; -} - -bool ModuleWindow::IsResizable() const -{ - return resizable; -} - -void ModuleWindow::WindowResized() -{ - SDL_UpdateWindowSurface(window); - screen_surface = SDL_GetWindowSurface(window); - App->renderer->WindowResized(screen_surface->w, screen_surface->h); - App->camera->SetAspectRatio(screen_surface->w, screen_surface->h); -} - -void ModuleWindow::SetResizable(bool set) { - resizable = set; -} - diff --git a/Source/ModuleWindow.h b/Source/ModuleWindow.h deleted file mode 100644 index c374c24f7..000000000 --- a/Source/ModuleWindow.h +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include "Module.h" -#include "SDL.h" - -class Application; - -class ModuleWindow : public Module -{ - public: - - ModuleWindow(); - virtual ~ModuleWindow(); - - bool Init(); - bool CleanUp(); - - void SetFullscreen(bool set); - void SetResizable(bool set); - - bool IsFullscreen() const; - bool IsResizable() const; - - void WindowResized(); - - inline SDL_Surface* getScreenSurface() { return screen_surface; } - inline SDL_Window* getWindow() { return window; } - - private: - SDL_Window* window = NULL; - SDL_Surface* screen_surface = NULL; - - bool fullscreen = false; - bool resizable = false; - - bool fullscreen_desktop = false; - - int width; - int height; -}; - diff --git a/Source/Texture.cpp b/Source/Texture.cpp deleted file mode 100644 index 9f0e6f297..000000000 --- a/Source/Texture.cpp +++ /dev/null @@ -1,54 +0,0 @@ -#include "Texture.h" -#include "Application.h" -#include "ModuleTexture.h" - -#include "glew.h" - -#include "imgui.h" -#include "imgui_impl_sdl.h" -#include "imgui_impl_opengl3.h" - -Texture::Texture(const char* file_data, const char* path) -{ - LOG("[Texture] Loading texture"); - bool texture_loaded = false; - - string model_texture_path(file_data); - string texture_file = model_texture_path.substr(model_texture_path.find_last_of("/\\") + 1); - - string default_path("Textures\\"); - - if (texture_id = App->texture->GetTextureData((path + texture_file).c_str()) != 0) { - LOG("[Texture] Texture found on: %s", path); - texture_loaded = true; - } - else if (texture_id = App->texture->GetTextureData((default_path + texture_file).c_str()) != 0) { - LOG("[Texture] Texture found on: %s", (default_path + texture_file).c_str()); - texture_loaded = true; - } - - if (texture_loaded) { - glGenTextures(1, &texture_id); - glBindTexture(GL_TEXTURE_2D, texture_id); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - - glTexImage2D(GL_TEXTURE_2D, 0, ilGetInteger(IL_IMAGE_BPP), ilGetInteger(IL_IMAGE_WIDTH), - ilGetInteger(IL_IMAGE_HEIGHT), 0, ilGetInteger(IL_IMAGE_FORMAT), GL_UNSIGNED_BYTE, - ilGetData()); - - glGenerateTextureMipmap(texture_id); - } - - ilDeleteImages(1, &texture_id); -} - -void Texture::DrawImGui() -{ - ImGui::Image((void*)(intptr_t)texture_id, ImVec2(150, 150)); -} - -Texture::~Texture() { - LOG("[Texture] Cleaning textures"); - ilDeleteImages(1, &texture_id); -} \ No newline at end of file diff --git a/Source/Texture.h b/Source/Texture.h deleted file mode 100644 index a77bfdaf7..000000000 --- a/Source/Texture.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include -using namespace std; - -class Texture -{ - public: - Texture(const char* file_data, const char* path); - ~Texture(); - - inline unsigned int getID() { return texture_id; } - - void DrawImGui(); - - private: - unsigned int texture_id; - string texture_path; -}; - diff --git a/Source/Timer.cpp b/Source/Timer.cpp deleted file mode 100644 index dad09fa7b..000000000 --- a/Source/Timer.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include "Timer.h" - -Timer::Timer() -{ - Start(); -} - -void Timer::Start() -{ - running = true; - start_time = SDL_GetTicks(); -} - -void Timer::Stop() -{ - running = false; - last_time = SDL_GetTicks(); -} - -int Timer::Read() -{ - if (running == true) - { - return SDL_GetTicks() - start_time; - } - else - { - return last_time - start_time; - } -} diff --git a/Source/Timer.h b/Source/Timer.h deleted file mode 100644 index a4eb6edb0..000000000 --- a/Source/Timer.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include "SDL.h" - - -class Timer -{ - public: - - Timer(); - - void Start(); - void Stop(); - - int Read(); - - private: - bool running; - float start_time; - float last_time = 0.0f; -}; - diff --git a/Source/src/Application.cpp b/Source/src/Application.cpp new file mode 100644 index 000000000..c571be01b --- /dev/null +++ b/Source/src/Application.cpp @@ -0,0 +1,87 @@ +#pragma once + +#include "Application.h" +#include "Utils/Timer.h" + +#include "Modules/ModuleWindow.h" +#include "Modules/ModuleRender.h" +#include "Modules/ModuleInput.h" +#include "Modules/ModuleEditor.h" +#include "Modules/ModuleCamera.h" +#include "Modules/ModuleTexture.h" +#include "Modules/ModuleProgram.h" +#include "Modules/ModuleHardware.h" +#include "Modules/ModuleSceneManager.h" +#include "Modules/ModuleDebugDraw.h" +#include "Modules/ModuleFileSystem.h" + +using namespace std; + +Application::Application() +{ + modules.push_back(hw = new ModuleHardware()); + modules.push_back(file_sys = new ModuleFileSystem()); + + modules.push_back(window = new ModuleWindow()); + modules.push_back(input = new ModuleInput()); + modules.push_back(texture = new ModuleTexture()); + modules.push_back(renderer = new ModuleRender()); + modules.push_back(camera = new ModuleCamera()); + modules.push_back(scene_manager = new ModuleSceneManager()); + modules.push_back(program = new ModuleProgram()); + modules.push_back(debug_draw = new ModuleDebugDraw()); + modules.push_back(editor = new ModuleEditor()); +} + +Application::~Application() +{ + for (vector::iterator it = modules.begin(); it != modules.end(); ++it) + delete *it; +} + +bool Application::Init() +{ + bool ret = true; + + for (vector::iterator it = modules.begin(); it != modules.end() && ret; ++it) + ret = (*it)->Init(); + + delta = 0.f; + EngineTimer::Start(); + return ret; +} + +update_status Application::Update() +{ + + delta = EngineTimer::Update(); + GameTimer::Update(); + + update_status ret = UPDATE_CONTINUE; + + for (vector::iterator it = modules.begin(); it != modules.end() && ret == UPDATE_CONTINUE; ++it) + ret = (*it)->PreUpdate((float) delta); + + for (vector::iterator it = modules.begin(); it != modules.end() && ret == UPDATE_CONTINUE; ++it) + ret = (*it)->Update((float) delta); + + for (vector::iterator it = modules.begin(); it != modules.end() && ret == UPDATE_CONTINUE; ++it) + ret = (*it)->PostUpdate((float) delta); + + return ret; +} + +bool Application::CleanUp() +{ + bool ret = true; + + for (vector::reverse_iterator it = modules.rbegin(); it != modules.rend() && ret; ++it) + ret = (*it)->CleanUp(); + + return ret; +} + +void Application::RequestBrowser(const char* url) const +{ + ShellExecuteA(NULL, "open", url, NULL, NULL, SW_SHOWNORMAL); +} diff --git a/Source/src/Application.h b/Source/src/Application.h new file mode 100644 index 000000000..3a559ec3f --- /dev/null +++ b/Source/src/Application.h @@ -0,0 +1,63 @@ +#pragma once + +#include "Globals.h" +#include "Modules/Module.h" +#include "Utils/Timer.h" + +#include +#include + +using namespace std; + +class ModuleRender; +class ModuleWindow; +class ModuleTextures; +class ModuleInput; +class ModuleRenderExercise; +class ModuleEditor; +class ModuleDebugDraw; +class ModuleCamera; +class ModuleTexture; +class ModuleSceneManager; +class ModuleProgram; +class ModuleHardware; +class ModuleFileSystem; + +class Program; +class AppLog; + +class Application +{ +public: + Application(); + ~Application(); + + bool Init(); + update_status Update(); + + bool CleanUp(); + + void RequestBrowser(const char* url) const; + +public: + ModuleRender* renderer = nullptr; + ModuleWindow* window = nullptr; + ModuleInput* input = nullptr; + ModuleEditor* editor = nullptr; + ModuleDebugDraw* debug_draw = nullptr; + ModuleCamera* camera = nullptr; + ModuleTexture* texture = nullptr; + ModuleSceneManager* scene_manager = nullptr; + ModuleProgram* program = nullptr; + ModuleHardware* hw = nullptr; + ModuleFileSystem* file_sys = nullptr; + +private: + PerformanceTimer timer; + + double delta = 0; + double prev_tick_time = 0; + std::vector modules; +}; + +extern Application* App; diff --git a/Source/src/Components/Component.h b/Source/src/Components/Component.h new file mode 100644 index 000000000..cc7bbb814 --- /dev/null +++ b/Source/src/Components/Component.h @@ -0,0 +1,59 @@ +#pragma once + +#include "../Utils/UID.h" +#include "../Utils/JsonFormaterValue.h" +#include "../UI/ImGuiUtils.h" + +class GameObject; +class ComponentCamera; +class Program; + +class Component +{ +public: + enum Type + { + NONE, + TRANSFORM, + MESH, + MATERIAL, + CAMERA, + DIRLIGHT, + POINTLIGHT, + SPOTLIGHT, + UNKNOWN + }; + + Component(Type type, GameObject* container) + : type(type) + , game_object(container) {} + + virtual ~Component() {}; + + virtual void Update() {}; + virtual void OnTransformUpdated() {}; + + inline Type GetType() const { return type; }; + UID GetID() const { return uid; } + + const GameObject* GetGameObject() const { return game_object; } + GameObject* GetGameObject() { return game_object; } + + void SetGameObject(GameObject* container) { game_object = container; } + + virtual void DrawGui() {}; + + virtual void Draw(ComponentCamera* camera, Program* program) {}; + virtual void DebugDraw() {}; + + virtual void Save(JsonFormaterValue j_component) const {}; + virtual void Load(JsonFormaterValue j_component) {}; + +protected: + GameObject* game_object = nullptr; + bool active = true; + Type type = Type::UNKNOWN; + +private: + UID uid = 0; +}; diff --git a/Source/src/Components/ComponentCamera.cpp b/Source/src/Components/ComponentCamera.cpp new file mode 100644 index 000000000..19124c7c6 --- /dev/null +++ b/Source/src/Components/ComponentCamera.cpp @@ -0,0 +1,187 @@ +#include "../Application.h" + +#include "../UI/ImGuiUtils.h" + +#include "../Modules/ModuleWindow.h" +#include "../Modules/ModuleRender.h" +#include "../Modules/ModuleSceneManager.h" +#include "../Modules/ModuleCamera.h" +#include "../Utils/Logger.h" + +#include "ComponentCamera.h" +#include "ComponentTransform.h" +#include "debugdraw.h" + +#include "../Scene.h" + +#include +#include + +ComponentCamera::ComponentCamera(GameObject* container) + : Component(Component::Type::CAMERA, container) +{ + frustum.SetKind(FrustumSpaceGL, FrustumRightHanded); + frustum.SetViewPlaneDistances(0.1f, 1000.0f); + + horizontal_fov = 65.0f; + frustum.SetHorizontalFovAndAspectRatio(DegToRad(horizontal_fov), (float) DEFAULT_CAMERA_WIDTH / (float) DEFAULT_CAMERA_HEIGHT); + + frustum.SetPos(float3(0.0f, 0.0f, 0.0f)); + frustum.SetFront(float3x3::identity.WorldZ()); + frustum.SetUp(float3x3::identity.WorldY()); + + frustum.GetPlanes(planes); + + OnTransformUpdated(); +} + +ComponentCamera::~ComponentCamera() +{ + Scene* scene = App->scene_manager->GetActiveScene(); + if (scene->GetCullingCamera() == this) + scene->SetCullingCamera(App->camera->GetMainCamera()); +} + +void ComponentCamera::DebugDraw() +{ + if (draw_frustum) { + float4x4 inverse_clip; + float4x4 matrix = GetProjectionMatrix() * GetViewMatrix(); + matrix.Inverse(); + dd::frustum(matrix, dd::colors::Yellow); + } +} + +void ComponentCamera::SetNearPlane(float distance) +{ + SetPlaneDistances(distance, frustum.FarPlaneDistance()); +} + +void ComponentCamera::SetFarPlane(float distance) +{ + SetPlaneDistances(frustum.NearPlaneDistance(), distance); +} + +void ComponentCamera::SetPlaneDistances(const float near_distance, const float far_distance) +{ + frustum.SetViewPlaneDistances(near_distance, far_distance); + frustum.GetPlanes(planes); +} + +void ComponentCamera::SetHorizontalFov(float fov_deg) +{ + horizontal_fov = fov_deg; + frustum.SetHorizontalFovAndAspectRatio(DegToRad(horizontal_fov), frustum.AspectRatio()); + frustum.GetPlanes(planes); +} + +float4x4 ComponentCamera::GetViewMatrix(const bool transpose) const +{ + if (transpose) + return float4x4(frustum.ViewMatrix()).Transposed(); + return float4x4(frustum.ViewMatrix()); +} + +float4x4 ComponentCamera::GetProjectionMatrix(const bool transpose) const +{ + if (transpose) + return frustum.ProjectionMatrix().Transposed(); + return frustum.ProjectionMatrix(); +} + +void ComponentCamera::OnTransformUpdated() +{ + ComponentTransform* transform = game_object->GetComponent(); + + frustum.SetFrame(transform->GetPosition(), transform->GetFwd(), transform->GetUp()); + frustum.GetPlanes(planes); +} + +void ComponentCamera::SetResolution(unsigned width, unsigned height) +{ + if (resolution_x != width || resolution_y != height) + { + resolution_x = width; + resolution_y = height; + frustum.SetHorizontalFovAndAspectRatio(DegToRad(horizontal_fov), ((float) width / (float) height)); + } +} + +void ComponentCamera::GetResolution(unsigned& width, unsigned& height) const +{ + width = resolution_x; + height = resolution_y; +} + +LineSegment ComponentCamera::RayCast(float x, float y) +{ + return frustum.UnProjectLineSegment(x, y); +} + +void ComponentCamera::Save(JsonFormaterValue j_component) const +{ + JsonFormaterValue j_frustum = j_component["Frustrum"]; + j_frustum["NearDistance"] = frustum.NearPlaneDistance(); + j_frustum["FarDistance"] = frustum.FarPlaneDistance(); + j_frustum["Fov"] = horizontal_fov; + + JsonFormaterValue j_pos = j_frustum["Pos"]; + j_pos[0] = frustum.Pos().x; + j_pos[1] = frustum.Pos().y; + j_pos[2] = frustum.Pos().z; + + JsonFormaterValue j_front = j_frustum["Front"]; + j_front[0] = frustum.Front().x; + j_front[1] = frustum.Front().y; + j_front[2] = frustum.Front().z; + + JsonFormaterValue j_up = j_frustum["Up"]; + j_up[0] = frustum.Up().x; + j_up[1] = frustum.Up().y; + j_up[2] = frustum.Up().z; +} + +void ComponentCamera::Load(JsonFormaterValue j_component) +{ + JsonFormaterValue j_frustum = j_component["Frustrum"]; + + SetNearPlane(j_frustum["NearDistance"]); + SetFarPlane(j_frustum["FarDistance"]); + SetHorizontalFov(j_frustum["Fov"]); + + JsonFormaterValue j_pos = j_frustum["Pos"]; + JsonFormaterValue j_front = j_frustum["Front"]; + JsonFormaterValue j_up = j_frustum["Up"]; + + frustum.SetPos(float3(j_pos[0], j_pos[1], j_pos[2])); + frustum.SetFront(float3(j_front[0], j_front[1], j_front[2])); + frustum.SetUp(float3(j_up[0], j_up[1], j_up[2])); +} + +void ComponentCamera::DrawGui() +{ + ImGui::PushID(this); + static bool debug_data = false; + + if (ImGuiUtils::CollapsingHeader(game_object, this, "Camera")) { + ImGui::Checkbox("Draw Frustum", &draw_frustum); + + float planes[2] = {frustum.NearPlaneDistance(), frustum.FarPlaneDistance()}; + if (ImGui::InputFloat2("N & F", &planes[0])) + SetPlaneDistances(planes[0], planes[1]); + if (ImGui::SliderFloat("H. Fov", &horizontal_fov, 30.f, 180.f)) + SetHorizontalFov(horizontal_fov); + + if (ImGui::Button("Culling Camera")) + App->scene_manager->GetActiveScene()->SetCullingCamera(this); + + ImGui::Checkbox("Debug", &debug_data); + if (debug_data) + { + ImGui::Separator(); + ImGui::Text("Fov (H, V): %.2f, %.2f", RadToDeg(frustum.HorizontalFov()), RadToDeg(frustum.VerticalFov())); + ImGui::Text("Aspect Ratio: %.2f", frustum.AspectRatio()); + } + } + ImGui::PopID(); +} \ No newline at end of file diff --git a/Source/src/Components/ComponentCamera.h b/Source/src/Components/ComponentCamera.h new file mode 100644 index 000000000..fc0c48bfc --- /dev/null +++ b/Source/src/Components/ComponentCamera.h @@ -0,0 +1,55 @@ +#pragma once +#include "Component.h" +#include "MathGeoLib.h" + +#define DEFAULT_CAMERA_WIDTH 1920 +#define DEFAULT_CAMERA_HEIGHT 1080 + +class GameObject; + +class ComponentCamera : public Component +{ +public: + ComponentCamera(GameObject* conatiner); + ~ComponentCamera() override; + + void OnTransformUpdated() override; + + static inline Type GetType() { return Type::CAMERA; }; + + float4x4 GetViewMatrix(const bool transpose = false) const; + float4x4 GetProjectionMatrix(const bool transpose = false) const; + + Frustum* ComponentCamera::GetFrustum() + { + return &frustum; + } + + void SetNearPlane(float distance); + void SetFarPlane(float distance); + void SetPlaneDistances(const float near_distance, const float far_distance); + void SetHorizontalFov(float fov_deg); + + void SetResolution(unsigned width, unsigned height); + void GetResolution(unsigned& width, unsigned& height) const; + + void Save(JsonFormaterValue j_component) const override; + void Load(JsonFormaterValue j_component) override; + + void DrawGui() override; + + void DebugDraw() override; + + LineSegment RayCast(float x, float y); + + Plane planes[6]; + float3 reference_point = float3::zero; + bool draw_frustum = false; + +private: + float horizontal_fov; + Frustum frustum; + + unsigned resolution_x = DEFAULT_CAMERA_WIDTH; + unsigned resolution_y = DEFAULT_CAMERA_HEIGHT; +}; diff --git a/Source/src/Components/ComponentDirLight.cpp b/Source/src/Components/ComponentDirLight.cpp new file mode 100644 index 000000000..921c349d9 --- /dev/null +++ b/Source/src/Components/ComponentDirLight.cpp @@ -0,0 +1,84 @@ +#include "ComponentDirLight.h" + +#include "../UI/ImGuiUtils.h" + +#include "ComponentTransform.h" + +#include "../Scene.h" +#include "debugdraw.h" + +#include "imgui.h" + +#include + +ComponentDirLight::ComponentDirLight(GameObject* conatiner) + : Component(Component::Type::DIRLIGHT, conatiner) +{ + if (game_object->scene_owner) + game_object->scene_owner->dir_lights.push_back((ComponentDirLight*) this); +} + +ComponentDirLight::~ComponentDirLight() +{ + if (game_object->scene_owner) + { + auto& lights = game_object->scene_owner->dir_lights; + lights.erase(std::remove(lights.begin(), lights.end(), this), lights.end()); + } +} + +void ComponentDirLight::DebugDraw() +{ + if (draw_direction) + { + ComponentTransform* transform = game_object->GetComponent(); + if (transform) + { + dd::arrow(transform->GetPosition(), transform->GetFwd().Mul(3.f) + transform->GetPosition(), dd::colors::Blue, 1.f); + } + } +} + +float3 ComponentDirLight::GetDirection() const +{ + ComponentTransform* transform = game_object->GetComponent(); + return transform->GetFwd(); +} + +void ComponentDirLight::Save(JsonFormaterValue j_component) const +{ + j_component["LightType"] = (int) Component::Type::DIRLIGHT; + + JsonFormaterValue j_color = j_component["Color"]; + j_color[0] = color.x; + j_color[1] = color.y; + j_color[2] = color.z; + j_color[3] = color.w; + + j_component["intensity"] = intensity; + j_component["active"] = active; + j_component["drawDirection"] = draw_direction; +} + +void ComponentDirLight::Load(JsonFormaterValue j_component) +{ + JsonFormaterValue j_color = j_component["Color"]; + color = float4(j_color[0], j_color[1], j_color[2], j_color[3]); + + intensity = j_component["intensity"]; + draw_direction = j_component["drawDirection"]; +} + +void ComponentDirLight::DrawGui() +{ + ImGui::PushID(this); + if (ImGuiUtils::CollapsingHeader(game_object, this, "Dir Light")) { + ImGui::Checkbox("D.Active", &active); + ImGui::Checkbox("Draw Direction", &draw_direction); + ImGui::PushItemWidth(100.0f); + ImGui::InputFloat("D.Intensity", &intensity); + ImGui::PopItemWidth(); + ImGuiUtils::CompactColorPicker("Dir Color", &color[0]); + } + ImGui::PopID(); +} \ No newline at end of file diff --git a/Source/src/Components/ComponentDirLight.h b/Source/src/Components/ComponentDirLight.h new file mode 100644 index 000000000..ad92823f0 --- /dev/null +++ b/Source/src/Components/ComponentDirLight.h @@ -0,0 +1,33 @@ +#pragma once + +#include "Component.h" + +#include "MathGeoLib.h" + +class ComponentDirLight : public Component +{ +public: + // TODO: add more light types + ComponentDirLight(GameObject* conatiner); + ~ComponentDirLight() override; + static inline Type GetType() { return Type::DIRLIGHT; }; + + void DebugDraw() override; + + bool IsActive() const { return active; } + void SetActive(bool is_active) { active = is_active; } + + float3 GetDirection() const; + + void Save(JsonFormaterValue j_component) const override; + void Load(JsonFormaterValue j_component) override; + + void DrawGui() override; + + float4 color = float4(1.0f, 1.0f, 1.0f, 1.0f); + float intensity = 1.0f; + +private: + bool active = true; + bool draw_direction = false; +}; diff --git a/Source/src/Components/ComponentMaterial.cpp b/Source/src/Components/ComponentMaterial.cpp new file mode 100644 index 000000000..c2d5572f6 --- /dev/null +++ b/Source/src/Components/ComponentMaterial.cpp @@ -0,0 +1,84 @@ +#include "ComponentMaterial.h" + +#include "../UI/ImGuiUtils.h" + +#include "../Application.h" +#include "../Scene.h" +#include "../Modules/ModuleTexture.h" +#include "../Modules/ModuleSceneManager.h" + +#include "../GameObject.h" + +#include "../Importers/MaterialImporter.h" + +#include + +ComponentMaterial::ComponentMaterial(GameObject* conatiner) + : Component(Component::Type::MATERIAL, conatiner) +{ +} + +ComponentMaterial::~ComponentMaterial() +{ + RELEASE(material); +} + +void ComponentMaterial::Import(aiMaterial* assimp_material, const std::string& model_path, const std::string& model_name) +{ + material = MaterialImporter::Material::Import(assimp_material, model_path, model_name); + use_diffuse_texture = material->diffuse.loaded; + use_specular_texture = material->specular.loaded; +} + +void ComponentMaterial::Save(JsonFormaterValue j_component) const +{ + MaterialImporter::Material::Save(material, j_component); +} + +void ComponentMaterial::Load(JsonFormaterValue j_component) +{ + material = MaterialImporter::Material::Load(j_component); + use_diffuse_texture = material->diffuse.loaded; + use_specular_texture = material->specular.loaded; +} + +void ComponentMaterial::DrawGui() +{ + ImGui::PushID(this); + if (ImGuiUtils::CollapsingHeader(game_object, this, "Material")) { + if (material) + { + Texture& diffuse = material->diffuse; + if (diffuse.loaded) + { + ImGui::Checkbox("Diff Texture", &use_diffuse_texture); + if (use_diffuse_texture) + { + ImGui::Text("Diffuse: %dx%d %s", diffuse.width, diffuse.height, diffuse.path.c_str()); + ImGui::Image((void*) (intptr_t) diffuse.id, ImVec2(150, 150)); + } + } + if (!use_diffuse_texture) + ImGuiUtils::CompactColorPicker("Diffuse Color", &material->diffuse_color[0]); + + Texture& specular = material->specular; + ImGui::InputFloat("Shininess", &material->shininess); + if (specular.loaded) + { + ImGui::Checkbox("Spec Texture", &use_specular_texture); + if (use_specular_texture) + { + ImGui::Text("Specular: %dx%d %s", specular.width, specular.height, specular.path.c_str()); + ImGui::Image((void*) (intptr_t) specular.id, ImVec2(150, 150)); + } + } + if (!use_specular_texture) + ImGuiUtils::CompactColorPicker("Specular Color", &material->specular_color[0]); + } + else + { + ImGui::Text("No material resource"); + } + } + ImGui::PopID(); +} \ No newline at end of file diff --git a/Source/src/Components/ComponentMaterial.h b/Source/src/Components/ComponentMaterial.h new file mode 100644 index 000000000..432169fbc --- /dev/null +++ b/Source/src/Components/ComponentMaterial.h @@ -0,0 +1,29 @@ +#pragma once + +#include "Component.h" +#include "../Modules/ModuleTexture.h" +#include "../Resources/ResourceMaterial.h" + +#include "assimp/scene.h" + +class ComponentMaterial : public Component +{ +public: + ComponentMaterial(GameObject* conatiner); + ~ComponentMaterial() override; + static inline Type GetType() { return Type::MATERIAL; }; + + const ResourceMaterial* GetMaterial() const { return material; } + + void DrawGui() override; + + void Import(aiMaterial* assimp_material, const std::string& model_path, const std::string& model_name); + void Save(JsonFormaterValue j_component) const override; + void Load(JsonFormaterValue j_component) override; + + bool use_diffuse_texture = false; + bool use_specular_texture = false; + +private: + ResourceMaterial* material; +}; diff --git a/Source/src/Components/ComponentMesh.cpp b/Source/src/Components/ComponentMesh.cpp new file mode 100644 index 000000000..c11de8ed6 --- /dev/null +++ b/Source/src/Components/ComponentMesh.cpp @@ -0,0 +1,70 @@ +#include "ComponentMesh.h" +#include "ComponentCamera.h" +#include "ComponentTransform.h" +#include "ComponentMaterial.h" + +#include "../Application.h" +#include "../Modules/ModuleProgram.h" +#include "../Modules/ModuleRender.h" +#include "../Modules/ModuleCamera.h" +#include "../Program.h" + +#include "../Importers/MeshImporter.h" + +#include "glew.h" +#include + +ComponentMesh::ComponentMesh(GameObject* conatiner) + : Component(Component::Type::MESH, conatiner) +{ +} + +ComponentMesh::~ComponentMesh() +{ + RELEASE(resource); +} + +void ComponentMesh::Import(const aiMesh* mesh) +{ + resource = MeshImporter::Import(mesh); +} + +void ComponentMesh::Draw(ComponentCamera* camera, Program* program) +{ + assert(resource->loaded == true); + program->BindUniformFloat4x4("model", &game_object->GetComponent()->GetTransform()[0][0]); + + ComponentMaterial* material = game_object->GetComponent(); + App->program->UpdateMaterial(material); + + glBindVertexArray(resource->vao); + glDrawElements(GL_TRIANGLES, resource->buffer_sizes[ResourceMesh::Buffers::b_indices], GL_UNSIGNED_INT, nullptr); +} + +void ComponentMesh::DrawStencil(ComponentCamera* camera, Program* program) +{ + assert(resource->loaded == true); + program->BindUniformFloat4x4("model", &game_object->GetComponent()->GetTransform()[0][0]); + + glBindVertexArray(resource->vao); + glDrawElements(GL_TRIANGLES, resource->buffer_sizes[ResourceMesh::Buffers::b_indices], GL_UNSIGNED_INT, nullptr); +} + +void ComponentMesh::Save(JsonFormaterValue j_component) const +{ + MeshImporter::Save(resource, game_object->getUID()); +} + +void ComponentMesh::Load(JsonFormaterValue j_component) +{ + resource = MeshImporter::Load(game_object->getUID()); +} + +void ComponentMesh::DrawGui() +{ + if (ImGui::CollapsingHeader("Mesh", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::Text("%d Triangles\n%d vertices\n%d indices", resource->buffer_sizes[ResourceMesh::Buffers::b_indices] / 3, resource->buffer_sizes[ResourceMesh::Buffers::b_vertices] / 3, resource->buffer_sizes[ResourceMesh::Buffers::b_indices]); + ImGui::Checkbox("Visible", &visible); + } +} \ No newline at end of file diff --git a/Source/src/Components/ComponentMesh.h b/Source/src/Components/ComponentMesh.h new file mode 100644 index 000000000..f9ec3f704 --- /dev/null +++ b/Source/src/Components/ComponentMesh.h @@ -0,0 +1,48 @@ +#pragma once +#include "Component.h" +#include "../Modules/ModuleTexture.h" + +#include "../Resources/ResourceMesh.h" + +#include "assimp/scene.h" +#include "MathGeoLib.h" + +#include + +class GameObject; +class ComponentCamera; +class Program; + +class ComponentMesh : public Component +{ +public: + ComponentMesh(GameObject* conatiner); + ~ComponentMesh() override; + + void Draw(ComponentCamera* camera, Program* program) override; + void DrawStencil(ComponentCamera* camera, Program* program); + + static inline Type GetType() { return Type::MESH; }; + + void CleanUp(); + + inline bool IsLoaded() const { return resource->loaded; } + bool IsVisible() const { return visible; } + + AABB GetAABB() const { return resource->bounding_box; } + inline unsigned GetBufferSize(ResourceMesh::Buffers buffer) const { return resource->buffer_sizes[buffer]; } + inline unsigned GetBufferId(ResourceMesh::Buffers buffer) const { return resource->buffer_ids[buffer]; } + const float* GetVertices() const { return resource->vertices; } + const unsigned* GetIndices() const { return resource->indices; } + + void Import(const aiMesh* mesh); + + void Save(JsonFormaterValue j_component) const override; + void Load(JsonFormaterValue j_component) override; + + void DrawGui() override; + +private: + bool visible = true; + ResourceMesh* resource = nullptr; +}; diff --git a/Source/src/Components/ComponentPointLight.cpp b/Source/src/Components/ComponentPointLight.cpp new file mode 100644 index 000000000..b5f770f98 --- /dev/null +++ b/Source/src/Components/ComponentPointLight.cpp @@ -0,0 +1,90 @@ +#include "ComponentPointLight.h" + +#include "../UI/ImGuiUtils.h" + +#include "ComponentTransform.h" +#include "../Scene.h" +#include "debugdraw.h" + +#include + +#include "imgui.h" + +#include + +ComponentPointLight::ComponentPointLight(GameObject* conatiner) + : Component(Component::Type::POINTLIGHT, conatiner) +{ + if (game_object->scene_owner) + game_object->scene_owner->point_lights.push_back((ComponentPointLight*) this); +} + +ComponentPointLight::~ComponentPointLight() +{ + if (game_object->scene_owner) + { + auto& lights = game_object->scene_owner->point_lights; + lights.erase(std::remove(lights.begin(), lights.end(), this), lights.end()); + } +} + +void ComponentPointLight::DebugDraw() +{ + if (draw_sphere) + { + ComponentTransform* transform = game_object->GetComponent(); + if (transform) + { + dd::sphere(transform->GetPosition(), dd::colors::Blue, radius); + } + } +} + +float3 ComponentPointLight::GetPosition() +{ + ComponentTransform* transform = game_object->GetComponent(); + return transform->GetPosition(); +} + +void ComponentPointLight::Save(JsonFormaterValue j_component) const +{ + j_component["LightType"] = (int) Component::Type::POINTLIGHT; + + JsonFormaterValue j_color = j_component["Color"]; + j_color[0] = color.x; + j_color[1] = color.y; + j_color[2] = color.z; + j_color[3] = color.w; + + j_component["intensity"] = intensity; + j_component["radius"] = radius; + j_component["active"] = active; + j_component["drawSphere"] = draw_sphere; +} + +void ComponentPointLight::Load(JsonFormaterValue j_component) +{ + JsonFormaterValue j_color = j_component["Color"]; + color = float4(j_color[0], j_color[1], j_color[2], j_color[3]); + + intensity = j_component["intensity"]; + radius = j_component["radius"]; + active = j_component["active"]; + draw_sphere = j_component["drawSphere"]; +} + +void ComponentPointLight::DrawGui() +{ + ImGui::PushID(this); + if (ImGuiUtils::CollapsingHeader(game_object, this, "Point Light")) { + ImGui::Checkbox("P.Active", &active); + ImGui::Checkbox("Draw Sphere", &draw_sphere); + ImGui::PushItemWidth(100.0f); + ImGui::InputFloat("P.Intensity", &intensity); + ImGui::InputFloat("P.Radius", &radius); + ImGui::PopItemWidth(); + ImGuiUtils::CompactColorPicker("Point Color", &color[0]); + } + ImGui::PopID(); +} + diff --git a/Source/src/Components/ComponentPointLight.h b/Source/src/Components/ComponentPointLight.h new file mode 100644 index 000000000..287767407 --- /dev/null +++ b/Source/src/Components/ComponentPointLight.h @@ -0,0 +1,34 @@ +#pragma once + +#include "Component.h" + +#include "MathGeoLib.h" + +class ComponentPointLight : public Component +{ +public: + // TODO: add more light types + ComponentPointLight(GameObject* conatiner); + ~ComponentPointLight() override; + static inline Type GetType() { return Type::POINTLIGHT; }; + + void DebugDraw() override; + + bool IsActive() const { return active; } + void SetActive(bool is_active) { active = is_active; } + + float3 GetPosition(); + + void Save(JsonFormaterValue j_component) const override; + void Load(JsonFormaterValue j_component) override; + + void DrawGui() override; + + float4 color = float4(1.0f, 1.0f, 1.0f, 1.0f); + float intensity = 1.0f; + float radius = 250.0f; + +private: + bool active = true; + bool draw_sphere = false; +}; diff --git a/Source/src/Components/ComponentSpotLight.cpp b/Source/src/Components/ComponentSpotLight.cpp new file mode 100644 index 000000000..951deefb8 --- /dev/null +++ b/Source/src/Components/ComponentSpotLight.cpp @@ -0,0 +1,104 @@ +#include "ComponentSpotLight.h" + +#include "../UI/ImGuiUtils.h" + +#include "ComponentTransform.h" +#include "../Scene.h" +#include "debugdraw.h" + +#include "imgui.h" +#include + +ComponentSpotLight::ComponentSpotLight(GameObject* conatiner) + : Component(Component::Type::SPOTLIGHT, conatiner) +{ + if (game_object->scene_owner) + game_object->scene_owner->spot_lights.push_back((ComponentSpotLight*) this); +} + +ComponentSpotLight::~ComponentSpotLight() +{ + if (game_object->scene_owner) + { + auto& lights = game_object->scene_owner->spot_lights; + lights.erase(std::remove(lights.begin(), lights.end(), this), lights.end()); + } +} + +void ComponentSpotLight::DebugDraw() +{ + if (draw_cone) + { + ComponentTransform* transform = game_object->GetComponent(); + if (transform) + { + dd::cone(transform->GetPosition(), transform->GetFwd().Mul(radius), dd::colors::Blue, inner, 0.f); + dd::cone(transform->GetPosition(), transform->GetFwd().Mul(radius), dd::colors::Green, outer, 0.f); + } + } +} + +float3 ComponentSpotLight::GetPosition() +{ + ComponentTransform* transform = game_object->GetComponent(); + return transform->GetPosition(); +} + +float3 ComponentSpotLight::GetDirection() +{ + ComponentTransform* transform = game_object->GetComponent(); + return transform->GetFwd(); +} + +void ComponentSpotLight::Save(JsonFormaterValue j_component) const +{ + j_component["LightType"] = (int) Component::Type::SPOTLIGHT; + + JsonFormaterValue j_color = j_component["Color"]; + j_color[0] = color.x; + j_color[1] = color.y; + j_color[2] = color.z; + j_color[3] = color.w; + + j_component["inner"] = inner; + j_component["outer"] = outer; + + j_component["intensity"] = intensity; + j_component["radius"] = radius; + + j_component["active"] = active; + j_component["drawCone"] = draw_cone; +} + +void ComponentSpotLight::Load(JsonFormaterValue j_component) +{ + JsonFormaterValue j_color = j_component["Color"]; + color = float4(j_color[0], j_color[1], j_color[2], j_color[3]); + + inner = j_component["inner"]; + outer = j_component["outer"]; + + intensity = j_component["intensity"]; + radius = j_component["radius"]; + + active = j_component["active"]; + draw_cone = j_component["drawSphere"]; +} + +void ComponentSpotLight::DrawGui() +{ + ImGui::PushID(this); + if (ImGuiUtils::CollapsingHeader(game_object, this, "Spot Light")) + { + ImGui::PushItemWidth(100.0f); + ImGui::Checkbox("S.Active", &active); + ImGui::Checkbox("Draw Cone", &draw_cone); + ImGui::InputFloat("S.Intensity", &intensity); + ImGui::InputFloat("S.Radius", &radius); + ImGui::InputFloat("Inner Angle", &inner); + ImGui::InputFloat("Outer Angle", &outer); + ImGui::PopItemWidth(); + ImGuiUtils::CompactColorPicker("Spot Color", &color[0]); + } + ImGui::PopID(); +} \ No newline at end of file diff --git a/Source/src/Components/ComponentSpotLight.h b/Source/src/Components/ComponentSpotLight.h new file mode 100644 index 000000000..10298be95 --- /dev/null +++ b/Source/src/Components/ComponentSpotLight.h @@ -0,0 +1,37 @@ +#pragma once + +#include "Component.h" + +#include "MathGeoLib.h" + +class ComponentSpotLight : public Component +{ +public: + // TODO: add more light types + ComponentSpotLight(GameObject* conatiner); + ~ComponentSpotLight() override; + static inline Type GetType() { return Type::SPOTLIGHT; }; + + void DebugDraw() override; + + bool IsActive() const { return active; } + void SetActive(bool is_active) { active = is_active; } + + float3 GetPosition(); + float3 GetDirection(); + + void Save(JsonFormaterValue j_component) const override; + void Load(JsonFormaterValue j_component) override; + + void DrawGui() override; + + float4 color = float4(1.0f, 1.0f, 1.0f, 1.0f); + float inner = 15.f; + float outer = 30.f; + float intensity = 1.0f; + float radius = 250.0f; + +private: + bool active = true; + bool draw_cone = false; +}; diff --git a/Source/src/Components/ComponentTransform.cpp b/Source/src/Components/ComponentTransform.cpp new file mode 100644 index 000000000..e38a92b0a --- /dev/null +++ b/Source/src/Components/ComponentTransform.cpp @@ -0,0 +1,187 @@ +#include "ComponentTransform.h" +#include "Component.h" + +#include "../GameObject.h" +#include "../Globals.h" +#include + +ComponentTransform::ComponentTransform(GameObject* new_object, float3 position, Quat rotation, float3 scale) + : Component(Component::Type::TRANSFORM, new_object) + , local_position(position) + , local_rotation(rotation) + , local_scale(scale) +{ + SetLocalTransform(position, rotation, scale); +} + +ComponentTransform::ComponentTransform(GameObject* new_object, const float4x4& new_transform) + : Component(Component::Type::TRANSFORM, new_object) +{ + SetLocalTransform(new_transform); +} + +ComponentTransform::~ComponentTransform() +{ +} + +void ComponentTransform::LookAt(float3 target, float3 world_up) +{ + float3 direction = target - GetPosition(); + + float3 fwd = direction.Normalized(); + float3 right = world_up.Cross(fwd).Normalized(); + float3 up = fwd.Cross(right).Normalized(); + + SetRotationAxis(right, up, fwd); +} + +void ComponentTransform::SetRotationAxis(float3 x, float3 y, float3 z) +{ + transform.SetCol3(0, x); + transform.SetCol3(1, y); + transform.SetCol3(2, z); + + SetGlobalTransform(transform); +} + +inline void ComponentTransform::SetLocalTransform(float3 position, Quat rotation, float3 scale) +{ + float4x4 new_transform = float4x4::FromTRS(position, rotation, scale); + SetLocalTransform(new_transform); +} + +inline void ComponentTransform::SetLocalPosition(float3 new_position) +{ + SetLocalTransform(new_position, local_rotation, local_scale); +} + +inline void ComponentTransform::SetLocalScale(float3 new_scale) +{ + SetLocalTransform(local_position, local_rotation, new_scale); +} + +inline void ComponentTransform::SetLocalRotation(Quat new_rotation) +{ + SetLocalTransform(local_position, new_rotation, local_scale); +} + +void ComponentTransform::SetLocalRotation(float3 rotation_angles) +{ + float3 rotation = DegToRad(rotation_angles); + SetLocalTransform(local_position, Quat::FromEulerXYZ(rotation.x, rotation.y, rotation.z).Normalized(), local_scale); +} +void ComponentTransform::SetLocalTransform(float4x4 new_transform) +{ + local_transform = new_transform; + local_transform.Decompose(local_position, local_rotation, local_scale); + local_rotation_euler = RadToDeg(local_rotation.ToEulerXYZ()); + + UpdateGlobalTransformHierarchy(); +} + +void ComponentTransform::SetGlobalTransform(float4x4 transform) +{ + // Use parent global transform to get local transform + + if (game_object->parent) + { + float4x4 parent_transform = game_object->parent->GetComponent()->GetTransform(); + parent_transform.Inverse(); + SetLocalTransform(parent_transform * transform); + } + else + SetLocalTransform(transform); +} + +void ComponentTransform::UpdateGlobalTransformHierarchy() +{ + // Updates current transform based on parent and calls to update the children + if (game_object->parent) + transform = game_object->parent->GetComponent()->GetTransform() * local_transform; + else + transform = local_transform; + + // Set transform as changed + changed = true; + + // Update children + for (GameObject* child : game_object->childs) + child->GetComponent()->UpdateGlobalTransformHierarchy(); +} + +void ComponentTransform::SetPosition(float3 new_transform) +{ + transform.SetTranslatePart(new_transform); + SetGlobalTransform(transform); +} + +void ComponentTransform::Save(JsonFormaterValue j_component) const +{ + JsonFormaterValue j_position = j_component["Position"]; + + j_position[0] = local_position.x; + j_position[1] = local_position.y; + j_position[2] = local_position.z; + + JsonFormaterValue j_rotation = j_component["Rotation"]; + + j_rotation[0] = local_rotation.x; + j_rotation[1] = local_rotation.y; + j_rotation[2] = local_rotation.z; + j_rotation[3] = local_rotation.w; + + JsonFormaterValue j_scale = j_component["Scale"]; + + j_scale[0] = local_scale.x; + j_scale[1] = local_scale.y; + j_scale[2] = local_scale.z; +} + +void ComponentTransform::Load(JsonFormaterValue j_component) +{ + JsonFormaterValue j_position = j_component["Position"]; + JsonFormaterValue j_rotation = j_component["Rotation"]; + JsonFormaterValue j_scale = j_component["Scale"]; + + local_position = float3(j_position[0], j_position[1], j_position[2]); + local_rotation = Quat(j_rotation[0], j_rotation[1], j_rotation[2], j_rotation[3]); + local_scale = float3(j_scale[0], j_scale[1], j_scale[2]); + + SetLocalTransform(local_position, local_rotation, local_scale); +} + +void ComponentTransform::DrawGui() +{ + static bool locked_scale = true; + static bool debug_transforms = false; + if (ImGui::CollapsingHeader("Local Transform", ImGuiTreeNodeFlags_DefaultOpen)) + { + float3 position = local_position; + float3 scale = local_scale; + float3 rotation = local_rotation_euler; + if (ImGui::InputFloat3("position", &position[0])) SetLocalPosition(position); + if (ImGui::InputFloat3("scale", &scale[0])) SetLocalScale(scale); + if (ImGui::InputFloat3("rotation", &rotation[0])) SetLocalRotation(rotation); + + ImGui::Checkbox("Debug Transforms", &debug_transforms); + if (debug_transforms) + { + ImGui::Separator(); + ImGui::Text("Local"); + for (int r = 0; r < 4; ++r) + { + auto row = local_transform.Row(r); + ImGui::Text("%.2f, %.2f, %.2f, %.2f", row[0], row[1], row[2], row[3]); + } + + ImGui::Separator(); + ImGui::Text("Global"); + for (int r = 0; r < 4; ++r) + { + auto row = transform.Row(r); + ImGui::Text("%.2f, %.2f, %.2f, %.2f", row[0], row[1], row[2], row[3]); + } + } + + } +} \ No newline at end of file diff --git a/Source/src/Components/ComponentTransform.h b/Source/src/Components/ComponentTransform.h new file mode 100644 index 000000000..e4f7b109f --- /dev/null +++ b/Source/src/Components/ComponentTransform.h @@ -0,0 +1,56 @@ +#pragma once +#include "Component.h" +#include "../GameObject.h" + +#include "MathGeoLib.h" + +class GameObject; + +class ComponentTransform : public Component +{ +public: + ComponentTransform(GameObject* new_object, float3 position = float3::zero, Quat rotation = Quat::identity, float3 scale = float3::one); + ComponentTransform(GameObject* new_object, const float4x4& transform = float4x4::identity); + ~ComponentTransform(); + static inline Type GetType() { return Type::TRANSFORM; }; + + void SetPosition(float3 new_position); + void SetRotationAxis(float3 x, float3 y, float3 z); + void LookAt(float3 target, float3 world_up = float3::unitY); + + void SetLocalTransform(float4x4 new_transform); + void SetLocalTransform(float3 position, Quat rotation, float3 scale); + + inline void SetLocalPosition(float3 new_position); + inline void SetLocalScale(float3 new_scale); + inline void SetLocalRotation(Quat new_rotation); + inline void SetLocalRotation(float3 rotation_angles); + + void SetGlobalTransform(float4x4 transform); + void UpdateGlobalTransformHierarchy(); + void OnTransformUpdated() override { changed = false; } + inline bool HasChanged() const { return changed; } + + inline float4x4 GetTransform() const { return transform; } + + inline float3 GetPosition() const { return transform.TranslatePart(); } + inline float3 GetFwd() const { return transform.WorldZ(); } + inline float3 GetUp() const { return transform.WorldY(); } + inline float3 GetRight() const { return transform.WorldX(); } + + void Save(JsonFormaterValue j_component) const override; + void Load(JsonFormaterValue j_component) override; + + void DrawGui() override; + +private: + bool changed = true; + + float4x4 transform; + + float4x4 local_transform; + float3 local_position; + float3 local_scale; + Quat local_rotation; + float3 local_rotation_euler; +}; diff --git a/Source/src/GameObject.cpp b/Source/src/GameObject.cpp new file mode 100644 index 000000000..396d71438 --- /dev/null +++ b/Source/src/GameObject.cpp @@ -0,0 +1,321 @@ +#include "GameObject.h" + +#include "Globals.h" +#include "Utils/Logger.h" + +#include "Application.h" +#include "Scene.h" +#include "Quadtree.h" +#include "Program.h" +#include "Components/ComponentTransform.h" +#include "Components/ComponentCamera.h" +#include "Components/ComponentMesh.h" +#include "Components/ComponentMaterial.h" +#include "Components/ComponentDirLight.h" +#include "Components/ComponentPointLight.h" +#include "Components/ComponentSpotLight.h" + +#include + + +GameObject::GameObject(const char* name) + : name(name) +{ + AddComponent(new ComponentTransform(this, float3::zero, Quat::identity, float3::one)); +} + +GameObject::GameObject(GameObject* parent, const float4x4& transform, const char* name, UID uid) + : name(name) + , uid(uid) +{ + SetNewParent(parent); + AddComponent(new ComponentTransform(this, transform)); +} + +GameObject::GameObject(GameObject* parent, const char* name, UID uid, const float3& translation, const Quat& rotation, const float3& scale) + : name(name) + , uid(uid) +{ + SetNewParent(parent); + AddComponent(new ComponentTransform(this, translation, rotation, scale)); +} + +GameObject::~GameObject() +{ + if (parent) + { + parent->RemoveChild(this); + } + + if (scene_owner) + scene_owner->DestroyGameObject(this); + + for (GameObject* child : childs) + { + child->parent = nullptr; + RELEASE(child); + } + for (Component* component : components) + { + RELEASE(component); + } +} + +void GameObject::RemoveChild(GameObject* game_object) +{ + childs.erase(std::remove(childs.begin(), childs.end(), game_object), childs.end()); +} + +void GameObject::SetNewParent(GameObject* new_parent) +{ + if (new_parent == parent) + return; + + if (parent) + parent->RemoveChild(this); + + if (new_parent) + new_parent->childs.push_back(this); + + parent = new_parent; +} + +void GameObject::AddComponent(Component* component) +{ + switch (component->GetType()) + { + case (Component::Type::TRANSFORM): + { + components.push_back(component); + transform = (ComponentTransform*) component; + component->SetGameObject(this); + break; + } + case (Component::Type::CAMERA): + { + components.push_back((Component*) component); + component->SetGameObject(this); + break; + } + } +} + +Component* GameObject::CreateComponent(Component::Type type) +{ + Component* new_component = nullptr; + switch (type) + { + case (Component::Type::TRANSFORM): + return transform; + break; + case (Component::Type::CAMERA): + new_component = new ComponentCamera(this); + break; + case (Component::Type::MESH): + new_component = new ComponentMesh(this); + break; + case (Component::Type::MATERIAL): + new_component = new ComponentMaterial(this); + break; + case (Component::Type::DIRLIGHT): + new_component = new ComponentDirLight(this); + break; + case (Component::Type::POINTLIGHT): + new_component = new ComponentPointLight(this); + break; + case (Component::Type::SPOTLIGHT): + new_component = new ComponentSpotLight(this); + break; + } + + if (new_component != nullptr) + components.push_back(new_component); + else + LOG("Falied to create component"); + return new_component; +} + +void GameObject::Update() +{ + if (transform->HasChanged()) + { + OnTransformUpdated(); + } + + for (Component* component : components) + { + component->Update(); + } + + for (GameObject* child : childs) + { + if (child->IsActive()) + child->Update(); + } +} + +void GameObject::DrawAll(ComponentCamera* camera, Program* program) +{ + // Draw yourself + Draw(camera, program); + // Draw children recursively + for (GameObject* child : childs) + { + child->DrawAll(camera, program); + } +} + +void GameObject::Draw(ComponentCamera* camera, Program* program) +{ + // Call draw on all components + for (Component* component : components) + { + component->Draw(camera, program); + } +} + +void GameObject::DrawStencil(ComponentCamera* camera, Program* program) +{ + ComponentMesh* mesh = GetComponent(); + if (mesh) + mesh->DrawStencil(camera, program); +} + +void GameObject::OnTransformUpdated() +{ + // Update components + for (Component* component : components) + component->OnTransformUpdated(); + + // Update children + for (GameObject* child : childs) + child->OnTransformUpdated(); + + UpdateBoundingBoxes(); +} + +void GameObject::DebugDrawAll() +{ + // Draw yourself + DebugDraw(); + // Draw children recursively + for (GameObject* child : childs) + { + child->DebugDrawAll(); + } +} + +void GameObject::DebugDraw() +{ + DrawBoundingBox(); + for (Component* component : components) + { + component->DebugDraw(); + } +} + +void GameObject::DrawBoundingBox() +{ + ddVec3 p[8]; + // This order was pure trial and error, i dont know how to really do it + // Using center and points does not show the rotation + static const int order[8] = {0, 1, 5, 4, 2, 3, 7, 6}; + for (int i = 0; i < 8; ++i) + p[i] = obb.CornerPoint(order[i]); + + dd::box(p, dd::colors::White); +} + +void GameObject::UpdateBoundingBoxes() +{ + constexpr float default_bounding_size = 1.0f; + ComponentMesh* mesh = GetComponent(); + if (mesh != nullptr) + { + obb = mesh->GetAABB(); + obb.Transform(transform->GetTransform()); + // Enclose is accumulative, reset the box + aabb.SetNegativeInfinity(); + aabb.Enclose(obb); + } + else + { + // If there is no mesh generate a default size + aabb.SetNegativeInfinity(); + aabb.SetFromCenterAndSize(transform->GetPosition(), float3(default_bounding_size)); + obb = aabb; + } + + // Without the check main camera crashes bcs there is no quadtree + if (scene_owner) { + Quadtree* quadtree = scene_owner->GetQuadtree(); + quadtree->Remove(this); + quadtree->Insert(this); + } +} + +void GameObject::RemoveComponent(Component* component) +{ + //TODO: Should I delete the component? + components.erase(std::remove(components.begin(), components.end(), component)); +} + +void GameObject::Save(JsonFormaterValue j_gameObject) const +{ + j_gameObject["Uid"] = uid; + j_gameObject["GOName"] = name.c_str(); + j_gameObject["Active"] = active; + j_gameObject["ParentId"] = parent != nullptr ? parent->uid : 0; + + JsonFormaterValue j_components = j_gameObject["Components"]; + for (unsigned i = 0; i < components.size(); ++i) + { + JsonFormaterValue j_component = j_components[i]; + Component* component = components[i]; + + j_component["ComponentID"] = component->GetID(); + j_component["ComponentType"] = component->GetType(); + component->Save(j_component); + } + + JsonFormaterValue j_children = j_gameObject["GOChildrens"]; + for (unsigned i = 0; i < childs.size(); ++i) + { + JsonFormaterValue j_child = j_children[i]; + GameObject* child = childs[i]; + child->Save(j_child); + } +} + +void GameObject::Load(JsonFormaterValue j_gameObject) +{ + JsonFormaterValue j_components = j_gameObject["Components"]; + for (unsigned i = 0; i < j_components.Size(); ++i) + { + JsonFormaterValue j_component = j_components[i]; + + UID c_uid = j_component["ComponentID"]; + int enum_type = j_component["ComponentType"]; + bool active = j_component["Active"]; + + Component::Type type = Component::Type(enum_type); + + Component* component = CreateComponent(type); + + component->Load(j_component); + } + + JsonFormaterValue j_childrens = j_gameObject["GOChildrens"]; + for (unsigned i = 0; i < j_childrens.Size(); ++i) + { + JsonFormaterValue j_child = j_childrens[i]; + + std::string child_name = j_child["GOName"]; + GameObject* child = new GameObject(this, child_name.c_str(), j_child["Uid"]); + child->scene_owner = scene_owner; + child->Load(j_child); + + } +} + + diff --git a/Source/src/GameObject.h b/Source/src/GameObject.h new file mode 100644 index 000000000..1086d136f --- /dev/null +++ b/Source/src/GameObject.h @@ -0,0 +1,84 @@ +#pragma once +#include "MathGeoLib.h" + +#include "Utils/UID.h" +#include "Components/Component.h" + +#include +using namespace std; + +class ComponentTransform; +class ComponentCamera; +class Program; +class Scene; + +class GameObject +{ + friend class Component; + +public: + GameObject(const char* name = "Unnamed"); + GameObject(GameObject* parent, const float4x4& transform, const char* name = "Unnamed", UID uid = GenerateUID()); + GameObject(GameObject* parent, const char* name = "Unnamed", UID uid = GenerateUID(), const float3& translation = float3::zero, const Quat& rotation = Quat::identity, const float3& scale = float3::one); + virtual ~GameObject(); + + void SetNewParent(GameObject* new_parent); + + void AddComponent(Component* component); + void RemoveComponent(Component* component); + + Component* CreateComponent(Component::Type type); + void RemoveChild(GameObject* gameObject); + + //void Destroy(); + void Update(); + void DrawAll(ComponentCamera* camera, Program* program); + void Draw(ComponentCamera* camera, Program* program); + void DrawStencil(ComponentCamera* camera, Program* program); + + inline bool IsActive() { return active; } + + void OnTransformUpdated(); + + void DebugDrawAll(); + void DebugDraw(); + void DrawBoundingBox(); + void UpdateBoundingBoxes(); + + UID getUID() { return uid; } + void Save(JsonFormaterValue j_gameObject) const; + void Load(JsonFormaterValue j_gameObject); + + const OBB& GetOBB() const { return obb; } + const AABB& GetAABB() { return aabb; } + + const std::vector& GetComponents() const { return components; } + + template + RetComponent* GetComponent() + { + Component::Type type = RetComponent::GetType(); + for (unsigned int i = 0; i < components.size(); i++) + { + if (components[i]->GetType() == type) + return ((RetComponent*) (components[i])); + } + return nullptr; + } + + std::string name; + Scene* scene_owner = nullptr; + GameObject* parent = nullptr; + std::vector childs; + + bool active = true; + +private: + std::vector components; + ComponentTransform* transform = nullptr; + + AABB aabb; + OBB obb; + + UID uid = 0; +}; \ No newline at end of file diff --git a/Source/src/Globals.h b/Source/src/Globals.h new file mode 100644 index 000000000..59cfaeed5 --- /dev/null +++ b/Source/src/Globals.h @@ -0,0 +1,89 @@ +#pragma once + +#include +#include + +#define M_PI 3.14159265358979323846 +constexpr float to_rad = (float) M_PI / 180.0f; +constexpr float to_deg = 180.0f / (float) M_PI; + +enum update_status +{ + UPDATE_CONTINUE = 1, + UPDATE_STOP, + UPDATE_ERROR, +}; + +// Deletes a buffer +#define RELEASE(x) \ + { \ + if (x != nullptr) \ + { \ + delete x; \ + x = nullptr; \ + } \ + } + +// Array buffer deletes +#define RELEASE_ARRAY(x) \ + { \ + if (x != nullptr) \ + { \ + delete[] x; \ + x = nullptr; \ + } \ + } + +// Configuration ----------- +#define WINDOWED_RATIO 0.75f +#define FULLSCREEN false +#define RESIZABLE true +#define TITLE "ALTAR BOOY" +#define ENGINE_VERSION "0.0.1" + +#define FPS_LOG_SIZE 100 + +// File System config ----------- +#define VERSION "0.1" +#define ASSETS_FOLDER "Assets" +#define ASSETS_FOLDER_SCENES "Assets/Scenes" + +#define SETTINGS_FOLDER "Settings" +#define LIBRARY_FOLDER "Library" +#define LIBRARY_TEXTURES_FOLDER "Library/Textures" +#define LIBRARY_MESH_FOLDER "Library/Meshes" +#define LIBRARY_SCENE_FOLDER "Library/Scenes" +#define LIBRARY_MATERIAL_FOLDER "Library/Materials" +#define LIBRARY_SHADERS_FOLDER "Library/Shaders" +#define LIBRARY_FONTS_FOLDER "Library/Fonts" + +#define JPG_TEXTURE_EXTENSION ".jpg" +#define PNG_TEXTURE_EXTENSION ".png" + +#define MATERIAL_EXTENSION ".mat" +#define SCENE_EXTENSION ".scene" +#define PREFAB_EXTENSION ".prefab" +#define META_EXTENSION ".meta" + +#define FRAGMENT_SHADER_EXTENSION ".frag" +#define VERTEX_SHADER_EXTENSION ".vert" +#define DEFAULT_SHADER_EXTENSION ".glsl" + +#define MODEL_EXTENSION ".fbx" + +#define SKYBOX_EXTENSION ".hdr" + +// Defering for file system https://stackoverflow.com/questions/32432450/what-is-standard-defer-finalizer-implementation-in-c +#ifndef defer +struct defer_dummy +{}; +template struct deferrer +{ + F f; + ~deferrer() { f(); } +}; +template deferrer operator*(defer_dummy, F f) { return {f}; } +#define DEFER_(LINE) zz_defer##LINE +#define DEFER(LINE) DEFER_(LINE) +#define defer auto DEFER(__LINE__) = defer_dummy {}* [&]() +#endif // defer \ No newline at end of file diff --git a/Source/src/Importers/MaterialImporter.cpp b/Source/src/Importers/MaterialImporter.cpp new file mode 100644 index 000000000..5a358410a --- /dev/null +++ b/Source/src/Importers/MaterialImporter.cpp @@ -0,0 +1,116 @@ +#include "MaterialImporter.h" + +#include "../Application.h" +#include "../Modules/ModuleTexture.h" +#include "../Resources/ResourceMaterial.h" + +void ColorCopy(const aiColor4D& assimp_color, float4& color) +{ + color.x = assimp_color.r; + color.y = assimp_color.g; + color.z = assimp_color.b; + color.w = assimp_color.a; +} + +ResourceMaterial* MaterialImporter::Material::Import(const aiMaterial* assimp_material, const std::string& model_path, const std::string& model_name) +{ + static const int index = 0; + static const std::string default_path(ASSETS_FOLDER "\\Textures\\"); + aiString file; + + ResourceMaterial* r_material = new ResourceMaterial(0); + + aiColor4D color; + if (assimp_material->Get(AI_MATKEY_COLOR_DIFFUSE, color) == AI_SUCCESS) + { + ColorCopy(color, r_material->diffuse_color); + } + if (assimp_material->Get(AI_MATKEY_COLOR_SPECULAR, color) == AI_SUCCESS) + { + ColorCopy(color, r_material->specular_color); + } + + assimp_material->Get(AI_MATKEY_SHININESS, r_material->shininess); + + std::vector search_paths; + if (assimp_material->GetTexture(aiTextureType_DIFFUSE, index, &file) == AI_SUCCESS) + { + std::string model_texture_path(file.data); + std::string texture_file = model_texture_path.substr(model_texture_path.find_last_of("/\\") + 1); + search_paths.push_back(file.data); + search_paths.push_back(model_path + texture_file); + search_paths.push_back(default_path + texture_file); + } + search_paths.push_back(model_path + model_name + "Diffuse.png"); + search_paths.push_back(default_path + model_name + "Diffuse.png"); + + for (std::string path : search_paths) + { + r_material->diffuse = App->texture->Load(path.c_str()); + if (r_material->diffuse.loaded) + break; + } + + search_paths.clear(); + if (assimp_material->GetTexture(aiTextureType_SPECULAR, index, &file) == AI_SUCCESS) + { + std::string model_texture_path(file.data); + std::string texture_file = model_texture_path.substr(model_texture_path.find_last_of("/\\") + 1); + search_paths.push_back(file.data); + search_paths.push_back(model_path + texture_file); + search_paths.push_back(default_path + texture_file); + } + search_paths.push_back(model_path + model_name + "Specular.tif"); + search_paths.push_back(default_path + model_name + "Specular.tif"); + + for (std::string path : search_paths) + { + // Not flip specular because devil seems to auto flip tif images already + constexpr bool flip = false; + r_material->specular = App->texture->Load(path.c_str(), flip); + if (r_material->specular.loaded) + break; + } + return r_material; +} + +bool MaterialImporter::Material::Save(const ResourceMaterial* material, JsonFormaterValue j_material) +{ + j_material["HasDiffuse"] = material->diffuse.loaded; + j_material["HasSpecular"] = material->specular.loaded; + j_material["DiffusePath"] = material->diffuse.path.c_str(); + j_material["SpecularPath"] = material->specular.path.c_str(); + j_material["Shininess"] = material->shininess; + + JsonFormaterValue j_diffuse_color = j_material["DiffuseColor"]; + j_diffuse_color[0] = material->diffuse_color.x; + j_diffuse_color[1] = material->diffuse_color.y; + j_diffuse_color[2] = material->diffuse_color.z; + j_diffuse_color[3] = material->diffuse_color.w; + + JsonFormaterValue j_specular_color = j_material["SpecularColor"]; + j_specular_color[0] = material->specular_color.x; + j_specular_color[1] = material->specular_color.y; + j_specular_color[2] = material->specular_color.z; + j_specular_color[3] = material->specular_color.w; + return true; +} + +ResourceMaterial* MaterialImporter::Material::Load(JsonFormaterValue j_material) +{ + // TODO: Manage uid properly + ResourceMaterial* material = new ResourceMaterial(1); + JsonFormaterValue j_diffuse_color = j_material["DiffuseColor"]; + JsonFormaterValue j_specular_color = j_material["SpecularColor"]; + material->diffuse_color = float4(j_diffuse_color[0], j_diffuse_color[1], j_diffuse_color[2], j_diffuse_color[3]); + material->specular_color = float4(j_specular_color[0], j_specular_color[1], j_specular_color[2], j_specular_color[3]); + + //r_material->diffuse = App->texture->Load(path.c_str()) + constexpr bool flip = true; + std::string diffuse_path = j_material["DiffusePath"]; + material->diffuse = App->texture->Load(diffuse_path.c_str()); + std::string specular_path = j_material["SpecularPath"]; + material->specular = App->texture->Load(specular_path.c_str(), !flip); + material->shininess = j_material["Shininess"]; + return material; +} diff --git a/Source/src/Importers/MaterialImporter.h b/Source/src/Importers/MaterialImporter.h new file mode 100644 index 000000000..adbb362a2 --- /dev/null +++ b/Source/src/Importers/MaterialImporter.h @@ -0,0 +1,18 @@ +#pragma once + +#include "../Utils/JsonFormaterValue.h" +#include "../Utils/UID.h" + +#include "assimp/scene.h" + +class ResourceMaterial; + +namespace MaterialImporter +{ + namespace Material + { + ResourceMaterial* Import(const aiMaterial* assimp_material, const std::string& model_path, const std::string& model_name); + bool Save(const ResourceMaterial* material, JsonFormaterValue j_material); + ResourceMaterial* Load(JsonFormaterValue j_material); + } +} \ No newline at end of file diff --git a/Source/src/Importers/MeshImporter.cpp b/Source/src/Importers/MeshImporter.cpp new file mode 100644 index 000000000..99432c131 --- /dev/null +++ b/Source/src/Importers/MeshImporter.cpp @@ -0,0 +1,135 @@ +#include "MeshImporter.h" +#include "../Utils/UID.h" + +#include "../Application.h" +#include "../Modules/ModuleFileSystem.h" + +#include "../Resources/ResourceMesh.h" +#include "assimp/scene.h" + +ResourceMesh* MeshImporter::Import(const aiMesh* ai_mesh) +{ + ResourceMesh* r_mesh = new ResourceMesh(GenerateUID()); + r_mesh->buffer_sizes[ResourceMesh::Buffers::b_vertices] = ai_mesh->mNumVertices * 3; + r_mesh->vertices = new float[r_mesh->buffer_sizes[ResourceMesh::Buffers::b_vertices]]; + memcpy(r_mesh->vertices, ai_mesh->mVertices, r_mesh->buffer_sizes[ResourceMesh::Buffers::b_vertices] * sizeof(float)); + + // TODO: use mesh->HasFaces(), mesh->HasNormals() and mesh->HasTextureCoords() to load if exists + r_mesh->buffer_sizes[ResourceMesh::Buffers::b_normals] = ai_mesh->mNumVertices * 3; + r_mesh->normals = new float[r_mesh->buffer_sizes[ResourceMesh::Buffers::b_normals]]; + memcpy(r_mesh->normals, ai_mesh->mNormals, r_mesh->buffer_sizes[ResourceMesh::Buffers::b_normals] * sizeof(float)); + + r_mesh->buffer_sizes[ResourceMesh::Buffers::b_tex_coords] = ai_mesh->mNumVertices * 2; + r_mesh->tex_coords = new float[r_mesh->buffer_sizes[ResourceMesh::Buffers::b_tex_coords]]; + for (unsigned i = 0; i < ai_mesh->mNumVertices; i++) + memcpy(&r_mesh->tex_coords[i * 2], &ai_mesh->mTextureCoords[0][i], 2 * sizeof(unsigned)); + + r_mesh->buffer_sizes[ResourceMesh::Buffers::b_indices] = ai_mesh->mNumFaces * 3; + r_mesh->indices = new unsigned[r_mesh->buffer_sizes[ResourceMesh::Buffers::b_indices]]; + for (unsigned i = 0; i < ai_mesh->mNumFaces; ++i) + memcpy(&r_mesh->indices[i * 3], ai_mesh->mFaces[i].mIndices, 3 * sizeof(unsigned)); + + r_mesh->GenerateBuffers(); + r_mesh->GenerateAABB(); + r_mesh->loaded = true; + + // Save resource + + return r_mesh; +} + +void MeshImporter::Save(ResourceMesh* r_mesh, UID uid) +{ + std::string file_path = std::string(LIBRARY_MESH_FOLDER "/") + std::to_string(uid) + ".mesh"; + + unsigned* sizes = r_mesh->buffer_sizes; + unsigned header[ResourceMesh::Buffers::n_buffers] = { + sizes[ResourceMesh::Buffers::b_indices], + sizes[ResourceMesh::Buffers::b_vertices], + sizes[ResourceMesh::Buffers::b_normals], + sizes[ResourceMesh::Buffers::b_tex_coords], + }; + + unsigned file_size = 0; + file_size += sizeof(header); + file_size += sizeof(unsigned) * sizes[ResourceMesh::Buffers::b_indices]; + file_size += sizeof(float) * sizes[ResourceMesh::Buffers::b_vertices]; + file_size += sizeof(float) * sizes[ResourceMesh::Buffers::b_normals]; + file_size += sizeof(float) * sizes[ResourceMesh::Buffers::b_tex_coords]; + + char* file_buffer = new char[file_size]; + char* cursor = file_buffer; + unsigned size_bytes = 0; + + size_bytes = sizeof(header); + memcpy(cursor, sizes, size_bytes); + cursor += size_bytes; + + size_bytes = sizeof(unsigned) * sizes[ResourceMesh::Buffers::b_indices]; + memcpy(cursor, r_mesh->indices, size_bytes); + cursor += size_bytes; + + size_bytes = sizeof(float) * sizes[ResourceMesh::Buffers::b_vertices]; + memcpy(cursor, r_mesh->vertices, size_bytes); + cursor += size_bytes; + + size_bytes = sizeof(float) * sizes[ResourceMesh::Buffers::b_normals]; + memcpy(cursor, r_mesh->normals, size_bytes); + cursor += size_bytes; + + size_bytes = sizeof(float) * sizes[ResourceMesh::Buffers::b_tex_coords]; + memcpy(cursor, r_mesh->tex_coords, size_bytes); + cursor += size_bytes; + + + App->file_sys->Save(file_path.c_str(), file_buffer, file_size); + delete[] file_buffer; +} + +ResourceMesh* MeshImporter::Load(UID uid) +{ + std::string file_path = std::string(LIBRARY_MESH_FOLDER "/") + std::to_string(uid) + ".mesh"; + char* file_buffer = App->file_sys->Load(file_path.c_str()); + char* cursor = file_buffer; + unsigned size_bytes = 0; + + // TODO: Manage uid properly + ResourceMesh* r_mesh = new ResourceMesh(1); + + unsigned header[ResourceMesh::Buffers::n_buffers]; + size_bytes = sizeof(header); + memcpy(header, cursor, size_bytes); + cursor += size_bytes; + + unsigned* sizes = r_mesh->buffer_sizes; + sizes[ResourceMesh::Buffers::b_indices] = header[0]; + sizes[ResourceMesh::Buffers::b_vertices] = header[1]; + sizes[ResourceMesh::Buffers::b_normals] = header[2]; + sizes[ResourceMesh::Buffers::b_tex_coords] = header[3]; + + size_bytes = sizeof(unsigned) * sizes[ResourceMesh::Buffers::b_indices]; + r_mesh->indices = new unsigned[sizes[ResourceMesh::Buffers::b_indices]]; + memcpy(r_mesh->indices, cursor, size_bytes); + cursor += size_bytes; + + r_mesh->vertices = new float[sizes[ResourceMesh::Buffers::b_vertices]]; + size_bytes = sizeof(float) * sizes[ResourceMesh::Buffers::b_vertices]; + memcpy(r_mesh->vertices, cursor, size_bytes); + cursor += size_bytes; + + r_mesh->normals = new float[sizes[ResourceMesh::Buffers::b_normals]]; + size_bytes = sizeof(float) * sizes[ResourceMesh::Buffers::b_normals]; + memcpy(r_mesh->normals, cursor, size_bytes); + cursor += size_bytes; + + r_mesh->tex_coords = new float[sizes[ResourceMesh::Buffers::b_tex_coords]]; + size_bytes = sizeof(float) * sizes[ResourceMesh::Buffers::b_tex_coords]; + memcpy(r_mesh->tex_coords, cursor, size_bytes); + cursor += size_bytes; + + r_mesh->GenerateBuffers(); + r_mesh->GenerateAABB(); + r_mesh->loaded = true; + + return r_mesh; +} diff --git a/Source/src/Importers/MeshImporter.h b/Source/src/Importers/MeshImporter.h new file mode 100644 index 000000000..0d59508d8 --- /dev/null +++ b/Source/src/Importers/MeshImporter.h @@ -0,0 +1,13 @@ +#pragma once + +#include "../Utils/UID.h" + +class ResourceMesh; +struct aiMesh; + +namespace MeshImporter +{ + ResourceMesh* Import(const aiMesh* ai_mesh); + void Save(ResourceMesh* r_mesh, UID uid); + ResourceMesh* Load(UID uid); +}; diff --git a/Source/src/Importers/SceneImporter.cpp b/Source/src/Importers/SceneImporter.cpp new file mode 100644 index 000000000..0c5f92485 --- /dev/null +++ b/Source/src/Importers/SceneImporter.cpp @@ -0,0 +1,64 @@ +#include "SceneImporter.h" + +#include "../GameObject.h" +#include "../Application.h" +#include "../Scene.h" + +#include "../Modules/ModuleFileSystem.h" + +#include "rapidjson/document.h" +#include "rapidjson/prettywriter.h" +#include "rapidjson/error/en.h" + +#include "../Utils/Logger.h" + +#include + +Scene* SceneImporter::LoadScene(const char* file_path) +{ + const char* buffer = App->file_sys->Load(file_path); + + if (sizeof(buffer) == 0) { + return nullptr; + } + + // Parse document from file + rapidjson::Document document; + document.Parse(buffer); + if (document.HasParseError()) + { + LOG("Error parsing JSON: %s (offset: %u)", rapidjson::GetParseError_En(document.GetParseError()), document.GetErrorOffset()); + return nullptr; + } + + JsonFormaterValue j_scene(document, document); + + Scene* scene = new Scene(); + scene->Load(j_scene); + return scene; +} + +bool SceneImporter::SaveScene(Scene* scene, const char* file_path) +{ + rapidjson::Document document; + document.SetObject(); + JsonFormaterValue j_Scene(document, document); + + scene->Save(j_Scene); + + // Write document to buffer + rapidjson::StringBuffer string_buffer; + + rapidjson::PrettyWriter< + /*typename OutputStream */ rapidjson::StringBuffer, + /*typename SourceEncoding*/ rapidjson::UTF8<>, + /*typename TargetEncoding*/ rapidjson::UTF8<>, + /*typename StackAllocator*/ rapidjson::CrtAllocator, + 0 + > writer(string_buffer); + + document.Accept(writer); + + // Save to file + return App->file_sys->Save(file_path, string_buffer.GetString(), string_buffer.GetSize()); +} diff --git a/Source/src/Importers/SceneImporter.h b/Source/src/Importers/SceneImporter.h new file mode 100644 index 000000000..8d048c73b --- /dev/null +++ b/Source/src/Importers/SceneImporter.h @@ -0,0 +1,11 @@ +#pragma once + +#include "../Utils/JsonFormaterValue.h" + +class Scene; + +namespace SceneImporter +{ + Scene* LoadScene(const char* file_path); + bool SaveScene(Scene* scene, const char* file_path); +} \ No newline at end of file diff --git a/Source/Main.cpp b/Source/src/Main.cpp similarity index 70% rename from Source/Main.cpp rename to Source/src/Main.cpp index f39dc77a1..840624319 100644 --- a/Source/Main.cpp +++ b/Source/src/Main.cpp @@ -1,18 +1,33 @@ -#include -#include "Application.h" -#include "ModuleRender.h" #include "Globals.h" -#include "glew.h" +#include "Utils/Logger.h" +#include "Application.h" +#include "Modules/ModuleRender.h" + +#include "glew.h" +#include #include "SDL.h" -#pragma comment( lib, "SDL2.lib" ) -#pragma comment( lib, "SDL2main.lib" ) +#pragma comment(lib, "SDL2.lib") +#pragma comment(lib, "SDL2main.lib") -#include "ImGuiComponents/AppLog.h" +#ifdef _DEBUG +#define DEBUG_NEW new (_NORMAL_BLOCK, __FILE__, __LINE__) +#define new DEBUG_NEW +#define _CRTDBG_MAP_ALLOC +#include +#include +#endif + +#include "optick.h" + +void DumpLeaks(void) +{ + _CrtDumpMemoryLeaks(); // Show leaks with file and line where allocation was made +} enum main_states { - MAIN_CREATION, + MAIN_CREATION, MAIN_START, MAIN_UPDATE, MAIN_FINISH, @@ -20,11 +35,10 @@ enum main_states }; Application* App = NULL; -AppLog* Logger = NULL; -int main(int argc, char ** argv) +int main(int argc, char** argv) { - Logger = new AppLog(); + atexit(DumpLeaks); int main_return = EXIT_FAILURE; main_states state = MAIN_CREATION; @@ -59,6 +73,7 @@ int main(int argc, char ** argv) case MAIN_UPDATE: { int update_return = App->Update(); + OPTICK_FRAME("MainThread"); if (update_return == UPDATE_ERROR) { @@ -69,7 +84,7 @@ int main(int argc, char ** argv) if (update_return == UPDATE_STOP) state = MAIN_FINISH; } - break; + break; case MAIN_FINISH: @@ -84,9 +99,7 @@ int main(int argc, char ** argv) state = MAIN_EXIT; break; - } - } delete App; diff --git a/Source/src/Modules/Module.h b/Source/src/Modules/Module.h new file mode 100644 index 000000000..e4186ad08 --- /dev/null +++ b/Source/src/Modules/Module.h @@ -0,0 +1,20 @@ +#pragma once + +#include "../Globals.h" + +class Application; + +class Module +{ +public: + Module(){} + virtual ~Module() {}; + + virtual bool Init() { return true; } + + virtual update_status PreUpdate(const float delta) { return UPDATE_CONTINUE; } + virtual update_status Update(const float delta) { return UPDATE_CONTINUE; } + virtual update_status PostUpdate(const float delta) { return UPDATE_CONTINUE; } + + virtual bool CleanUp() { return true; } +}; diff --git a/Source/src/Modules/ModuleCamera.cpp b/Source/src/Modules/ModuleCamera.cpp new file mode 100644 index 000000000..8cd97424e --- /dev/null +++ b/Source/src/Modules/ModuleCamera.cpp @@ -0,0 +1,173 @@ +#include "../GameObject.h" +#include "../Application.h" + +#include "ModuleWindow.h" +#include "ModuleCamera.h" +#include "ModuleCamera.h" +#include "ModuleWindow.h" +#include "ModuleInput.h" +#include "ModuleEditor.h" +#include "ModuleSceneManager.h" + +#include "../Components/ComponentTransform.h" +#include "../Components/ComponentCamera.h" + +#include "../UI/WindowScene.h" + +#include "glew.h" +#include "Math/MathConstants.h" + +#include "ImGuizmo.h" + +#include "../Utils/Logger.h" + +ModuleCamera::ModuleCamera() {} + +ModuleCamera::~ModuleCamera() {} + +bool ModuleCamera::Init() +{ + LOG("Creating main camera"); + + GameObject* cameraGameObject = new GameObject("Main Camera"); + main_camera = (ComponentCamera*) cameraGameObject->CreateComponent(Component::CAMERA); + + cameraGameObject->GetComponent()->SetPosition(float3(0.0f, 8.0f, 10.0f)); + cameraGameObject->GetComponent()->LookAt(float3::zero); + cameraGameObject->Update(); + + ImGuizmo::Enable(true); + + return true; +} + +update_status ModuleCamera::Update(const float delta) +{ + Controller(delta); + return UPDATE_CONTINUE; +} + +void ModuleCamera::OnResize(unsigned int screen_width, unsigned int screen_height) +{ + main_camera->SetResolution(screen_width, screen_height); +} + +void ModuleCamera::Controller(const float delta) +{ + static const float zoom_speed = 3.0f; + static const float rot_speed = 2.0f; + + if (!App->editor->GetSceneWindow()->IsHovering()) + return; + + // Keyboard movement --------------- + if (App->input->GetMouseButton(SDL_BUTTON_RIGHT)) + { + int moved_x, moved_y; + App->input->GetMouseDelta(moved_x, moved_y); + + Rotate(-(float) moved_x * delta * rot_speed, (float) moved_y * delta * rot_speed); + MovementController(delta); + } + + // Mouse ---------------------------- + int scrolled_y = App->input->GetScrollDelta(); + if (scrolled_y != 0) + Zoom(-(float) scrolled_y * zoom_speed); + + if (App->input->GetKey(SDL_SCANCODE_LALT)) + { + int moved_x, moved_y; + App->input->GetMouseDelta(moved_x, moved_y); + Orbit(moved_x * 1.5f, moved_y * 1.5f); + } + if (App->input->GetKey(SDL_SCANCODE_F)) + { + float distance = (main_camera->reference_point - main_camera->GetGameObject()->GetComponent()->GetPosition()).Length(); + GameObject* go = App->editor->GetSelectedGO(); + FocusOnModel(go->GetComponent()->GetPosition(), distance); + } +} + +void ModuleCamera::Zoom(float zoom) +{ + ComponentTransform* transform = main_camera->GetGameObject()->GetComponent(); + float distance = main_camera->reference_point.Distance(transform->GetPosition()); + vec newPos = transform->GetPosition() + float(zoom) * transform->GetFwd() * distance * 0.05f; + + transform->SetPosition(newPos); + main_camera->GetGameObject()->Update(); +} + +void ModuleCamera::Orbit(float motion_x, float motion_y) +{ + ComponentTransform* transform = main_camera->GetGameObject()->GetComponent(); + float3 vector = transform->GetPosition() - main_camera->reference_point; + + vector = Quat(transform->GetUp(), motion_x * 0.003f).Transform(vector); + vector = Quat(transform->GetRight().Neg(), motion_y * 0.003f).Transform(vector); + + transform->SetPosition(vector + main_camera->reference_point); + + main_camera->GetGameObject()->GetComponent()->LookAt(main_camera->reference_point); + main_camera->GetGameObject()->Update(); +} + +void ModuleCamera::MovementController(const float delta) +{ + static const float move_speed = 15.0f; + static const float speed_modifier = 2.0f; + + float effective_speed = move_speed; + if (App->input->GetKeyMod(KMOD_SHIFT)) + effective_speed *= speed_modifier; + + ComponentTransform* transform = main_camera->GetGameObject()->GetComponent(); + float3 deltaRight = float3::zero, deltaUp = float3::zero, deltaFwd = float3::zero; + + if (App->input->GetKey(SDL_SCANCODE_W) == KEY_REPEAT) + deltaFwd += transform->GetFwd() * delta * effective_speed; + if (App->input->GetKey(SDL_SCANCODE_S) == KEY_REPEAT) + deltaFwd -= transform->GetFwd() * delta * effective_speed; + if (App->input->GetKey(SDL_SCANCODE_A) == KEY_REPEAT) + deltaRight += transform->GetRight() * delta * effective_speed; + if (App->input->GetKey(SDL_SCANCODE_D) == KEY_REPEAT) + deltaRight -= transform->GetRight() * delta * effective_speed; + if (App->input->GetKey(SDL_SCANCODE_Q) == KEY_REPEAT) + deltaUp += float3::unitY * delta * effective_speed; + if (App->input->GetKey(SDL_SCANCODE_E) == KEY_REPEAT) + deltaUp -= float3::unitY * delta * effective_speed; + + transform->SetPosition(transform->GetPosition() + deltaFwd + deltaRight + deltaUp); + main_camera->GetGameObject()->Update(); + main_camera->reference_point += deltaRight + deltaUp; +} + +void ModuleCamera::FocusOnModel(const float3& target, float distance) +{ + ComponentTransform* transform = main_camera->GetGameObject()->GetComponent(); + float3 v = transform->GetFwd().Neg(); + + transform->SetPosition(target + (v * distance)); + main_camera->GetGameObject()->Update(); + + main_camera->reference_point = target; +} + +void ModuleCamera::Rotate(float motion_x, float motion_y) +{ + ComponentTransform* transform = main_camera->GetGameObject()->GetComponent(); + + Quat yaw_quat = Quat::RotateY(motion_x); + float3 newRight = yaw_quat * transform->GetRight(); + + Quat pitch_quat = Quat::RotateAxisAngle(newRight, motion_y); + float3 newUp = pitch_quat * yaw_quat * transform->GetUp(); + float3 newFwd = pitch_quat * yaw_quat * transform->GetFwd(); + + transform->SetRotationAxis(newRight, newUp, newFwd); + main_camera->GetGameObject()->Update(); + + float distancetoReference = (main_camera->reference_point - transform->GetPosition()).Length(); + main_camera->reference_point = transform->GetPosition() + newFwd * distancetoReference; +} \ No newline at end of file diff --git a/Source/src/Modules/ModuleCamera.h b/Source/src/Modules/ModuleCamera.h new file mode 100644 index 000000000..c11785475 --- /dev/null +++ b/Source/src/Modules/ModuleCamera.h @@ -0,0 +1,33 @@ +#pragma once +#include "Module.h" +#include "../Globals.h" + +#include "MathGeoLib.h" + +class ComponentCamera; + +class ModuleCamera : public Module +{ +public: + ModuleCamera(); + ~ModuleCamera() override; + + bool Init() override; + update_status Update(const float delta) override; + + void OnResize(unsigned int screen_width, unsigned int screen_height); + + void Controller(const float delta); + void MovementController(const float delta); + + ComponentCamera* GetMainCamera() { return main_camera; } + + // Camera actions + void Zoom(float zoom); + void Orbit(float motion_x, float motion_y); + void FocusOnModel(const float3& target, float distance); + void Rotate(float motion_x, float motion_y); + +private: + ComponentCamera* main_camera = nullptr; +}; diff --git a/Source/src/Modules/ModuleDebugDraw.cpp b/Source/src/Modules/ModuleDebugDraw.cpp new file mode 100644 index 000000000..b1c7ee4ff --- /dev/null +++ b/Source/src/Modules/ModuleDebugDraw.cpp @@ -0,0 +1,629 @@ +#include "../Globals.h" +#include "ModuleDebugDraw.h" + +#include "../Application.h" +#include "ModuleSceneManager.h" +#include "../Scene.h" +#include "../GameObject.h" +#include "../Quadtree.h" +#define DEBUG_DRAW_IMPLEMENTATION +#include "DebugDraw.h" // Debug Draw API. Notice that we need the DEBUG_DRAW_IMPLEMENTATION macro here! + +#include "glew.h" + +class DDRenderInterfaceCoreGL final + : public dd::RenderInterface +{ +public: + // + // dd::RenderInterface overrides: + // + + void drawPointList(const dd::DrawVertex* points, int count, bool depthEnabled) override + { + assert(points != nullptr); + assert(count > 0 && count <= DEBUG_DRAW_VERTEX_BUFFER_SIZE); + + glBindVertexArray(linePointVAO); + glUseProgram(linePointProgram); + + glUniformMatrix4fv(linePointProgram_MvpMatrixLocation, 1, GL_TRUE, reinterpret_cast(&mvpMatrix)); + + bool already = glIsEnabled(GL_DEPTH_TEST); + + if (depthEnabled) + { + glEnable(GL_DEPTH_TEST); + } + else + { + glDisable(GL_DEPTH_TEST); + } + + // NOTE: Could also use glBufferData to take advantage of the buffer orphaning trick... + glBindBuffer(GL_ARRAY_BUFFER, linePointVBO); + glBufferSubData(GL_ARRAY_BUFFER, 0, count * sizeof(dd::DrawVertex), points); + + // Issue the draw call: + glDrawArrays(GL_POINTS, 0, count); + + glUseProgram(0); + glBindVertexArray(0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + checkGLError(__FILE__, __LINE__); + + if (already) + { + glEnable(GL_DEPTH_TEST); + } + else + { + glDisable(GL_DEPTH_TEST); + } + } + + void drawLineList(const dd::DrawVertex* lines, int count, bool depthEnabled) override + { + assert(lines != nullptr); + assert(count > 0 && count <= DEBUG_DRAW_VERTEX_BUFFER_SIZE); + + glBindVertexArray(linePointVAO); + glUseProgram(linePointProgram); + + glUniformMatrix4fv(linePointProgram_MvpMatrixLocation, 1, GL_TRUE, reinterpret_cast(&mvpMatrix)); + + bool already = glIsEnabled(GL_DEPTH_TEST); + + if (depthEnabled) + { + glEnable(GL_DEPTH_TEST); + } + else + { + glDisable(GL_DEPTH_TEST); + } + + // NOTE: Could also use glBufferData to take advantage of the buffer orphaning trick... + glBindBuffer(GL_ARRAY_BUFFER, linePointVBO); + glBufferSubData(GL_ARRAY_BUFFER, 0, count * sizeof(dd::DrawVertex), lines); + + // Issue the draw call: + glDrawArrays(GL_LINES, 0, count); + + glUseProgram(0); + glBindVertexArray(0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + checkGLError(__FILE__, __LINE__); + + if (already) + { + glEnable(GL_DEPTH_TEST); + } + else + { + glDisable(GL_DEPTH_TEST); + } + } + + void drawGlyphList(const dd::DrawVertex* glyphs, int count, dd::GlyphTextureHandle glyphTex) override + { + assert(glyphs != nullptr); + assert(count > 0 && count <= DEBUG_DRAW_VERTEX_BUFFER_SIZE); + + glBindVertexArray(textVAO); + glUseProgram(textProgram); + + // These doesn't have to be reset every draw call, I'm just being lazy ;) + glUniform1i(textProgram_GlyphTextureLocation, 0); + glUniform2f(textProgram_ScreenDimensions, static_cast(width), static_cast(height)); + + if (glyphTex != nullptr) + { + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, handleToGL(glyphTex)); + } + + bool already_blend = glIsEnabled(GL_BLEND); + + if (!already_blend) + { + glEnable(GL_BLEND); + } + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + bool already = glIsEnabled(GL_DEPTH_TEST); + glDisable(GL_DEPTH_TEST); + + glBindBuffer(GL_ARRAY_BUFFER, textVBO); + glBufferSubData(GL_ARRAY_BUFFER, 0, count * sizeof(dd::DrawVertex), glyphs); + + glDrawArrays(GL_TRIANGLES, 0, count); // Issue the draw call + + if (!already_blend) + { + glDisable(GL_BLEND); + } + + glUseProgram(0); + glBindVertexArray(0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindTexture(GL_TEXTURE_2D, 0); + checkGLError(__FILE__, __LINE__); + + if (already) + { + glEnable(GL_DEPTH_TEST); + } + } + + dd::GlyphTextureHandle createGlyphTexture(int width, int height, const void* pixels) override + { + assert(width > 0 && height > 0); + assert(pixels != nullptr); + + GLuint textureId = 0; + glGenTextures(1, &textureId); + glBindTexture(GL_TEXTURE_2D, textureId); + + glPixelStorei(GL_PACK_ALIGNMENT, 1); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, width, height, 0, GL_RED, GL_UNSIGNED_BYTE, pixels); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + glBindTexture(GL_TEXTURE_2D, 0); + checkGLError(__FILE__, __LINE__); + + return GLToHandle(textureId); + } + + void destroyGlyphTexture(dd::GlyphTextureHandle glyphTex) override + { + if (glyphTex == nullptr) + { + return; + } + + const GLuint textureId = handleToGL(glyphTex); + glBindTexture(GL_TEXTURE_2D, 0); + glDeleteTextures(1, &textureId); + } + + // These two can also be implemented to perform GL render + // state setup/cleanup, but we don't use them in this sample. + //void beginDraw() override { } + //void endDraw() override { } + + // + // Local methods: + // + + DDRenderInterfaceCoreGL() + : mvpMatrix() + , width(0) + , height(0) + , linePointProgram(0) + , linePointProgram_MvpMatrixLocation(-1) + , textProgram(0) + , textProgram_GlyphTextureLocation(-1) + , textProgram_ScreenDimensions(-1) + , linePointVAO(0) + , linePointVBO(0) + , textVAO(0) + , textVBO(0) + { + //std::printf("\n"); + //std::printf("GL_VENDOR : %s\n", glGetString(GL_VENDOR)); + //std::printf("GL_RENDERER : %s\n", glGetString(GL_RENDERER)); + //std::printf("GL_VERSION : %s\n", glGetString(GL_VERSION)); + //std::printf("GLSL_VERSION : %s\n\n", glGetString(GL_SHADING_LANGUAGE_VERSION)); + //std::printf("DDRenderInterfaceCoreGL initializing ...\n"); + + // Default OpenGL states: + glEnable(GL_CULL_FACE); + glEnable(GL_DEPTH_TEST); + glDisable(GL_BLEND); + + // This has to be enabled since the point drawing shader will use gl_PointSize. + glEnable(GL_PROGRAM_POINT_SIZE); + + setupShaderPrograms(); + setupVertexBuffers(); + + //std::printf("DDRenderInterfaceCoreGL ready!\n\n"); + } + + ~DDRenderInterfaceCoreGL() + { + glDeleteProgram(linePointProgram); + glDeleteProgram(textProgram); + + glDeleteVertexArrays(1, &linePointVAO); + glDeleteBuffers(1, &linePointVBO); + + glDeleteVertexArrays(1, &textVAO); + glDeleteBuffers(1, &textVBO); + } + + void setupShaderPrograms() + { + //std::printf("> DDRenderInterfaceCoreGL::setupShaderPrograms()\n"); + + // + // Line/point drawing shader: + // + { + GLuint linePointVS = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(linePointVS, 1, &linePointVertShaderSrc, nullptr); + compileShader(linePointVS); + + GLint linePointFS = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(linePointFS, 1, &linePointFragShaderSrc, nullptr); + compileShader(linePointFS); + + linePointProgram = glCreateProgram(); + glAttachShader(linePointProgram, linePointVS); + glAttachShader(linePointProgram, linePointFS); + + glBindAttribLocation(linePointProgram, 0, "in_Position"); + glBindAttribLocation(linePointProgram, 1, "in_ColorPointSize"); + linkProgram(linePointProgram); + + linePointProgram_MvpMatrixLocation = glGetUniformLocation(linePointProgram, "u_MvpMatrix"); + if (linePointProgram_MvpMatrixLocation < 0) + { + //errorF("Unable to get u_MvpMatrix uniform location!"); + } + checkGLError(__FILE__, __LINE__); + } + + // + // Text rendering shader: + // + { + GLuint textVS = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(textVS, 1, &textVertShaderSrc, nullptr); + compileShader(textVS); + + GLint textFS = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(textFS, 1, &textFragShaderSrc, nullptr); + compileShader(textFS); + + textProgram = glCreateProgram(); + glAttachShader(textProgram, textVS); + glAttachShader(textProgram, textFS); + + glBindAttribLocation(textProgram, 0, "in_Position"); + glBindAttribLocation(textProgram, 1, "in_TexCoords"); + glBindAttribLocation(textProgram, 2, "in_Color"); + linkProgram(textProgram); + + textProgram_GlyphTextureLocation = glGetUniformLocation(textProgram, "u_glyphTexture"); + if (textProgram_GlyphTextureLocation < 0) + { + //errorF("Unable to get u_glyphTexture uniform location!"); + } + + textProgram_ScreenDimensions = glGetUniformLocation(textProgram, "u_screenDimensions"); + if (textProgram_ScreenDimensions < 0) + { + //errorF("Unable to get u_screenDimensions uniform location!"); + } + + checkGLError(__FILE__, __LINE__); + } + } + + void setupVertexBuffers() + { + //std::printf("> DDRenderInterfaceCoreGL::setupVertexBuffers()\n"); + + // + // Lines/points vertex buffer: + // + { + glGenVertexArrays(1, &linePointVAO); + glGenBuffers(1, &linePointVBO); + checkGLError(__FILE__, __LINE__); + + glBindVertexArray(linePointVAO); + glBindBuffer(GL_ARRAY_BUFFER, linePointVBO); + + // RenderInterface will never be called with a batch larger than + // DEBUG_DRAW_VERTEX_BUFFER_SIZE vertexes, so we can allocate the same amount here. + glBufferData(GL_ARRAY_BUFFER, DEBUG_DRAW_VERTEX_BUFFER_SIZE * sizeof(dd::DrawVertex), nullptr, GL_STREAM_DRAW); + checkGLError(__FILE__, __LINE__); + + // Set the vertex format expected by 3D points and lines: + std::size_t offset = 0; + + glEnableVertexAttribArray(0); // in_Position (vec3) + glVertexAttribPointer( + /* index = */ 0, + /* size = */ 3, + /* type = */ GL_FLOAT, + /* normalize = */ GL_FALSE, + /* stride = */ sizeof(dd::DrawVertex), + /* offset = */ reinterpret_cast(offset)); + offset += sizeof(float) * 3; + + glEnableVertexAttribArray(1); // in_ColorPointSize (vec4) + glVertexAttribPointer( + /* index = */ 1, + /* size = */ 4, + /* type = */ GL_FLOAT, + /* normalize = */ GL_FALSE, + /* stride = */ sizeof(dd::DrawVertex), + /* offset = */ reinterpret_cast(offset)); + + checkGLError(__FILE__, __LINE__); + + // VAOs can be a pain in the neck if left enabled... + glBindVertexArray(0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + } + + // + // Text rendering vertex buffer: + // + { + glGenVertexArrays(1, &textVAO); + glGenBuffers(1, &textVBO); + checkGLError(__FILE__, __LINE__); + + glBindVertexArray(textVAO); + glBindBuffer(GL_ARRAY_BUFFER, textVBO); + + // NOTE: A more optimized implementation might consider combining + // both the lines/points and text buffers to save some memory! + glBufferData(GL_ARRAY_BUFFER, DEBUG_DRAW_VERTEX_BUFFER_SIZE * sizeof(dd::DrawVertex), nullptr, GL_STREAM_DRAW); + checkGLError(__FILE__, __LINE__); + + // Set the vertex format expected by the 2D text: + std::size_t offset = 0; + + glEnableVertexAttribArray(0); // in_Position (vec2) + glVertexAttribPointer( + /* index = */ 0, + /* size = */ 2, + /* type = */ GL_FLOAT, + /* normalize = */ GL_FALSE, + /* stride = */ sizeof(dd::DrawVertex), + /* offset = */ reinterpret_cast(offset)); + offset += sizeof(float) * 2; + + glEnableVertexAttribArray(1); // in_TexCoords (vec2) + glVertexAttribPointer( + /* index = */ 1, + /* size = */ 2, + /* type = */ GL_FLOAT, + /* normalize = */ GL_FALSE, + /* stride = */ sizeof(dd::DrawVertex), + /* offset = */ reinterpret_cast(offset)); + offset += sizeof(float) * 2; + + glEnableVertexAttribArray(2); // in_Color (vec4) + glVertexAttribPointer( + /* index = */ 2, + /* size = */ 4, + /* type = */ GL_FLOAT, + /* normalize = */ GL_FALSE, + /* stride = */ sizeof(dd::DrawVertex), + /* offset = */ reinterpret_cast(offset)); + + checkGLError(__FILE__, __LINE__); + + // Ditto. + glBindVertexArray(0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + } + } + + static GLuint handleToGL(dd::GlyphTextureHandle handle) + { + const std::size_t temp = reinterpret_cast(handle); + return static_cast(temp); + } + + static dd::GlyphTextureHandle GLToHandle(const GLuint id) + { + const std::size_t temp = static_cast(id); + return reinterpret_cast(temp); + } + + static void checkGLError(const char* file, const int line) + { + GLenum err; + while ((err = glGetError()) != GL_NO_ERROR) + { + //errorF("%s(%d) : GL_CORE_ERROR=0x%X - %s", file, line, err, errorToString(err)); + } + } + + static void compileShader(const GLuint shader) + { + glCompileShader(shader); + checkGLError(__FILE__, __LINE__); + + GLint status; + glGetShaderiv(shader, GL_COMPILE_STATUS, &status); + checkGLError(__FILE__, __LINE__); + + if (status == GL_FALSE) + { + GLchar strInfoLog[1024] = {0}; + glGetShaderInfoLog(shader, sizeof(strInfoLog) - 1, nullptr, strInfoLog); + //errorF("\n>>> Shader compiler errors:\n%s", strInfoLog); + } + } + + static void linkProgram(const GLuint program) + { + glLinkProgram(program); + checkGLError(__FILE__, __LINE__); + + GLint status; + glGetProgramiv(program, GL_LINK_STATUS, &status); + checkGLError(__FILE__, __LINE__); + + if (status == GL_FALSE) + { + GLchar strInfoLog[1024] = {0}; + glGetProgramInfoLog(program, sizeof(strInfoLog) - 1, nullptr, strInfoLog); + //errorF("\n>>> Program linker errors:\n%s", strInfoLog); + } + } + + // The "model-view-projection" matrix for the scene. + // In this demo, it consists of the camera's view and projection matrices only. + math::float4x4 mvpMatrix; + unsigned width, height; + +private: + GLuint linePointProgram; + GLint linePointProgram_MvpMatrixLocation; + + GLuint textProgram; + GLint textProgram_GlyphTextureLocation; + GLint textProgram_ScreenDimensions; + + GLuint linePointVAO; + GLuint linePointVBO; + + GLuint textVAO; + GLuint textVBO; + + static const char* linePointVertShaderSrc; + static const char* linePointFragShaderSrc; + + static const char* textVertShaderSrc; + static const char* textFragShaderSrc; + +}; // class DDRenderInterfaceCoreGL + +// ======================================================== +// Minimal shaders we need for the debug primitives: +// ======================================================== + +const char* DDRenderInterfaceCoreGL::linePointVertShaderSrc = "\n" + "#version 150\n" + "\n" + "in vec3 in_Position;\n" + "in vec4 in_ColorPointSize;\n" + "\n" + "out vec4 v_Color;\n" + "uniform mat4 u_MvpMatrix;\n" + "\n" + "void main()\n" + "{\n" + " gl_Position = u_MvpMatrix * vec4(in_Position, 1.0);\n" + " gl_PointSize = in_ColorPointSize.w;\n" + " v_Color = vec4(in_ColorPointSize.xyz, 1.0);\n" + "}\n"; + +const char* DDRenderInterfaceCoreGL::linePointFragShaderSrc = "\n" + "#version 150\n" + "\n" + "in vec4 v_Color;\n" + "out vec4 out_FragColor;\n" + "\n" + "void main()\n" + "{\n" + " out_FragColor = v_Color;\n" + "}\n"; + +const char* DDRenderInterfaceCoreGL::textVertShaderSrc = "\n" + "#version 150\n" + "\n" + "in vec2 in_Position;\n" + "in vec2 in_TexCoords;\n" + "in vec3 in_Color;\n" + "\n" + "uniform vec2 u_screenDimensions;\n" + "\n" + "out vec2 v_TexCoords;\n" + "out vec4 v_Color;\n" + "\n" + "void main()\n" + "{\n" + " // Map to normalized clip coordinates:\n" + " float x = ((2.0 * (in_Position.x - 0.5)) / u_screenDimensions.x) - 1.0;\n" + " float y = 1.0 - ((2.0 * (in_Position.y - 0.5)) / u_screenDimensions.y);\n" + "\n" + " gl_Position = vec4(x, y, 0.0, 1.0);\n" + " v_TexCoords = in_TexCoords;\n" + " v_Color = vec4(in_Color, 1.0);\n" + "}\n"; + +const char* DDRenderInterfaceCoreGL::textFragShaderSrc = "\n" + "#version 150\n" + "\n" + "in vec2 v_TexCoords;\n" + "in vec4 v_Color;\n" + "\n" + "uniform sampler2D u_glyphTexture;\n" + "out vec4 out_FragColor;\n" + "\n" + "void main()\n" + "{\n" + " out_FragColor = v_Color;\n" + " out_FragColor.a = texture(u_glyphTexture, v_TexCoords).r;\n" + "}\n"; + +DDRenderInterfaceCoreGL* ModuleDebugDraw::implementation = 0; + +ModuleDebugDraw::ModuleDebugDraw() +{ +} + +ModuleDebugDraw::~ModuleDebugDraw() +{ +} + +bool ModuleDebugDraw::Init() +{ + implementation = new DDRenderInterfaceCoreGL; + dd::initialize(implementation); + return true; +} + +bool ModuleDebugDraw::CleanUp() +{ + dd::shutdown(); + + delete implementation; + implementation = 0; + + return true; +} + +update_status ModuleDebugDraw::Update(const float delta) +{ + dd::xzSquareGrid(-30, 30, -0.1f, 1.0f, dd::colors::Gray); + + if (debug_draw) + { + Scene* scene = App->scene_manager->GetActiveScene(); + if (scene) + { + scene->GetRoot()->DebugDrawAll(); + if (draw_quadtree) + scene->GetQuadtree()->DebugDraw(); + } + } + + return UPDATE_CONTINUE; +} + +void ModuleDebugDraw::Draw(const float4x4& view, const float4x4& proj, unsigned width, unsigned height) +{ + implementation->width = width; + implementation->height = height; + implementation->mvpMatrix = proj * view; + + dd::flush(); +} diff --git a/Source/src/Modules/ModuleDebugDraw.h b/Source/src/Modules/ModuleDebugDraw.h new file mode 100644 index 000000000..70b4a78d5 --- /dev/null +++ b/Source/src/Modules/ModuleDebugDraw.h @@ -0,0 +1,27 @@ +#pragma once + +#include "Module.h" +#include "Math/float4x4.h" + +class DDRenderInterfaceCoreGL; +class Camera; + +class ModuleDebugDraw : public Module +{ +public: + ModuleDebugDraw(); + ~ModuleDebugDraw() override; + + bool Init() override; + update_status Update(const float delta) override; + bool CleanUp() override; + + void Draw(const float4x4& view, const float4x4& proj, unsigned width, unsigned height); + void SetDebugDraw(bool v) { debug_draw = v; } + + bool debug_draw = true; + bool draw_quadtree = false; + +private: + static DDRenderInterfaceCoreGL* implementation; +}; diff --git a/Source/src/Modules/ModuleEditor.cpp b/Source/src/Modules/ModuleEditor.cpp new file mode 100644 index 000000000..09a25be62 --- /dev/null +++ b/Source/src/Modules/ModuleEditor.cpp @@ -0,0 +1,352 @@ +#include "../Globals.h" +#include "../Application.h" +#include "../Utils/Timer.h" + +#include "ModuleEditor.h" +#include "ModuleFileSystem.h" +#include "ModuleWindow.h" +#include "ModuleRender.h" +#include "ModuleCamera.h" +#include "ModuleTexture.h" +#include "ModuleHardware.h" +#include "ModuleInput.h" +#include "ModuleSceneManager.h" +#include "SDL.h" + +#include "../GameObject.h" +#include "../Scene.h" +#include "../Components/ComponentCamera.h" + +#include "../Importers/SceneImporter.h" + +#include "ImGuizmo.h" + +#include "glew.h" +#include "imgui.h" +#include "imgui_impl_sdl.h" +#include "imgui_impl_opengl3.h" +#include +#include +#include "../Utils/Logger.h" + +#include "ImGuiFileDialog.h" + +ModuleEditor::ModuleEditor() { + LOG("Creating windows"); + + windows.push_back(&w_configuration); + windows.push_back(&w_hierarchy); + windows.push_back(&w_scene); + windows.push_back(&w_inspector); + windows.push_back(&w_about); + windows.push_back(&w_console); + // windows.push_back(&w_resource); + windows.push_back(&w_project); + windows.push_back(&w_timers); +} + +bool ModuleEditor::Init() +{ + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + + ImGuiIO& io = ImGui::GetIO(); + (void) io; + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; + io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; + io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange; + io.ConfigWindowsMoveFromTitleBarOnly = true; + + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + { + io.ConfigViewportsNoAutoMerge = false; + io.ConfigViewportsNoTaskBarIcon = true; + } + + if (io.ConfigFlags & ImGuiConfigFlags_DockingEnable) + { + io.ConfigDockingTransparentPayload = true; + } + + //TODO: ADD resource loader for fonts and icons + io.Fonts->AddFontDefault(); + static const ImWchar icons_ranges[] = {ICON_MIN_FA, ICON_MAX_FA, 0}; + ImFontConfig icons_config; + icons_config.MergeMode = true; + icons_config.PixelSnapH = true; + m_big_icon_font = io.Fonts->AddFontFromFileTTF(FONT_ICON_FILE_NAME_FAS, 10.0f, &icons_config, icons_ranges); + m_small_icon_font = io.Fonts->AddFontFromFileTTF(FONT_ICON_FILE_NAME_FAR, 10.0f, &icons_config, icons_ranges); + + // Setup style + ImGui::StyleColorsDark(); + + ImGui_ImplSDL2_InitForOpenGL(App->window->GetWindow(), App->renderer->GetGLContext()); + ImGui_ImplOpenGL3_Init(); + + LOG("Init windows"); + for (Window* panel : windows) + { + if (panel->active) + panel->Init(); + } + + return true; +} + +update_status ModuleEditor::PreUpdate(const float delta) +{ + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplSDL2_NewFrame(App->window->GetWindow()); + ImGui::NewFrame(); + ImGuizmo::BeginFrame(); + + return UPDATE_CONTINUE; +} + +update_status ModuleEditor::Update(const float delta) +{ + ImGui::CaptureMouseFromApp(true); + ImGui::CaptureKeyboardFromApp(true); + + update_status retval = MainMenuBar(); + + if (ImGuiFileDialog::Instance()->Display("LoadScene")) + { + if (ImGuiFileDialog::Instance()->IsOk()) + { + std::string file_path_name = ImGuiFileDialog::Instance()->GetFilePathName(); + + //TODO: Make a function inside file sys to get relative path Assets/Scenes/X.scene + std::string file_name_extension = App->file_sys->GetFileNameAndExtension(file_path_name.c_str()); + + std::string file_path = std::string(ASSETS_FOLDER_SCENES) + "/" + file_name_extension; + + LOG("Loading scene: %s", file_path_name.c_str()); + App->scene_manager->LoadScene(file_path.c_str()); + } + + ImGuiFileDialog::Instance()->Close(); + } + + GenerateDockingSpace(); + + for (Window* panel : windows) + { + if (panel->active) + panel->Update(); + } + + RenderGui(); + return retval; +} + +update_status ModuleEditor::MainMenuBar() +{ + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 4)); + if (ImGui::BeginMainMenuBar()) + { + FileMenu(); + EditMenu(); + GoMenu(); + ViewMenu(); + + if (ImGui::BeginMenu("Help")) + { + if (ImGui::MenuItem("Documentation")) + App->RequestBrowser("https://github.com/Erick9Thor/Engine3D/wiki"); + + if (ImGui::MenuItem("Download latest")) + App->RequestBrowser("https://github.com/Erick9Thor/Engine3D/releases"); + + if (ImGui::MenuItem("Report a bug")) + App->RequestBrowser("https://github.com/Erick9Thor/Engine3D/issues"); + + if (ImGui::MenuItem("Download source!")) + App->RequestBrowser("https://www.youtube.com/watch?v=dQw4w9WgXcQ"); + + if (ImGui::MenuItem("Quit")) + return UPDATE_STOP; + + ImGui::EndMenu(); + } + + ImGui::PopStyleVar(2); + ImGui::EndMainMenuBar(); + } + + return UPDATE_CONTINUE; +} + +void ModuleEditor::GenerateDockingSpace() +{ + ImGuiViewport* viewport = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(viewport->WorkPos); + ImGui::SetNextWindowSize(viewport->WorkSize); + ImGui::SetNextWindowViewport(viewport->ID); + + ImGuiDockNodeFlags dockspace_flags = ImGuiDockNodeFlags_PassthruCentralNode; + ImGuiWindowFlags host_window_flags = 0; + host_window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDocking; + host_window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus; + if (dockspace_flags & ImGuiDockNodeFlags_PassthruCentralNode) + host_window_flags |= ImGuiWindowFlags_NoBackground; + + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + ImGui::Begin("DockSpace Window", nullptr, host_window_flags); + ImGui::PopStyleVar(3); + + ImGuiID dockspace_id = ImGui::GetID("DockSpace"); + + if (!ImGui::DockBuilderGetNode(dockspace_id)) + { + ImGui::DockBuilderAddNode(dockspace_id); + ImGui::DockBuilderSetNodeSize(dockspace_id, viewport->WorkSize); + + dock_main_id = dockspace_id; + dock_up_id = ImGui::DockBuilderSplitNode(dock_main_id, ImGuiDir_Up, 0.2f, nullptr, &dock_main_id); + ImGui::DockBuilderSetNodeSize(dock_up_id, ImVec2(viewport->WorkSize.x, 40)); + dock_right_id = ImGui::DockBuilderSplitNode(dock_main_id, ImGuiDir_Right, 0.2f, nullptr, &dock_main_id); + dock_down_id = ImGui::DockBuilderSplitNode(dock_main_id, ImGuiDir_Down, 0.3f, nullptr, &dock_main_id); + dock_left_id = ImGui::DockBuilderSplitNode(dock_main_id, ImGuiDir_Left, 0.25f, nullptr, &dock_main_id); + } + + ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags, nullptr); + ImGui::End(); +} + +void ModuleEditor::FileMenu() +{ + // TODO: shortcuts + Scene* current_scene = App->scene_manager->GetActiveScene(); + + if (!ImGui::BeginMenu("File")) return; + if (ImGui::MenuItem(ICON_FA_PLUS "New")) + { + App->scene_manager->CreateEmptyScene(); + } + if (ImGui::MenuItem(ICON_FA_SAVE "Save", nullptr, false, true)) // TODO: Use internal timer to disable/enable + { + std::string temp_scene_file_path = std::string(ASSETS_FOLDER_SCENES) + "/" + "untitled" + SCENE_EXTENSION; + App->scene_manager->SaveScene(temp_scene_file_path.c_str()); + } + if (ImGui::MenuItem("Save as", nullptr, false, true)) // TODO: Use internal timer + { + ImGui::OpenPopup("Save scene"); + } + ImGui::Separator(); + if (ImGui::MenuItem("Load")) + { + ImGuiFileDialog::Instance()->OpenDialog("LoadScene", "Load Scene", ".scene", ASSETS_FOLDER_SCENES); + } + + ImGui::EndMenu(); + + char file_name_buffer[32] = {'\0'}; + if (ImGui::BeginPopupModal("Save scene")) + { + ImGui::SetItemDefaultFocus(); + ImGui::InputText("File name", file_name_buffer, sizeof(file_name_buffer)); + if (ImGui::Button("Save")) + { + LOG("Saving Scene"); + std::string temp_scene_file_path = std::string(ASSETS_FOLDER_SCENES) + "/" + file_name_buffer + SCENE_EXTENSION; + App->scene_manager->SaveScene(temp_scene_file_path.c_str()); + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("Cancel")) + { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } +} + +void ModuleEditor::ViewMenu() +{ + if (ImGui::BeginMenu("View")) + { + ImGui::MenuItem(w_scene.name, "", &w_scene.active); + ImGui::MenuItem(w_inspector.name, "", &w_inspector.active); + ImGui::MenuItem(w_hierarchy.name, "", &w_hierarchy.active); + ImGui::MenuItem(w_configuration.name, "", &w_configuration.active); + ImGui::MenuItem(w_about.name, "", &w_about.active); + ImGui::MenuItem(w_resource.name, "", &w_resource.active); + ImGui::MenuItem(w_project.name, "", &w_project.active); + ImGui::MenuItem(w_timers.name, "", &w_timers.active); + ImGui::EndMenu(); + } +} + +void ModuleEditor::EditMenu() +{ + // TODO: shortcuts + const bool is_go_selected = GetSelectedGO() != nullptr; + + if (!ImGui::BeginMenu("Edit")) return; + + if (ImGui::MenuItem(ICON_FA_UNDO "Undo", nullptr, false, CanUndo())) + { + //TODO: + } + if (ImGui::MenuItem(ICON_FA_REDO "Redo", nullptr, false, CanRedo())) + { + //TODO: + } + ImGui::Separator(); + if (ImGui::MenuItem(ICON_FA_CLIPBOARD "Copy", nullptr, false, is_go_selected)) + { + //TODO: + } + if (ImGui::MenuItem(ICON_FA_PASTE "Paste", nullptr, false, CanPaste())) + { + //TODO: + } + if (ImGui::MenuItem(ICON_FA_CLONE "Duplicate", nullptr, false, is_go_selected)) + { + //TODO: + } + ImGui::EndMenu(); +} + +void ModuleEditor::GoMenu() +{ + if (!ImGui::BeginMenu("GameObject")) return; + + if (ImGui::MenuItem("Add GameObject")) + { + App->scene_manager->GetActiveScene()->CreateNewGameObject("GameObject", App->scene_manager->GetActiveScene()->GetRoot()); + } + + ImGui::EndMenu(); +} + +void ModuleEditor::RenderGui() +{ + ImGui::Render(); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + + ImGui::UpdatePlatformWindows(); + ImGui::RenderPlatformWindowsDefault(); + SDL_GL_MakeCurrent(App->window->GetWindow(), App->renderer->GetGLContext()); +} + +bool ModuleEditor::CleanUp() +{ + LOG("Cleaning windows"); + + for (Window* panel : windows) + { + if (panel->active) + panel->CleanUp(); + } + + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplSDL2_Shutdown(); + ImGui::DestroyContext(); + + return true; +} \ No newline at end of file diff --git a/Source/src/Modules/ModuleEditor.h b/Source/src/Modules/ModuleEditor.h new file mode 100644 index 000000000..6eef13a9c --- /dev/null +++ b/Source/src/Modules/ModuleEditor.h @@ -0,0 +1,79 @@ +#pragma once + +#include "../Globals.h" +#include "../Application.h" +#include "../GameObject.h" + +#include "../UI/Window.h" +#include "../UI/WindowAbout.h" +#include "../UI/WindowConfiguration.h" +#include "../UI/WindowHierarchy.h" +#include "../UI/WindowInspector.h" +#include "../UI/WindowScene.h" +#include "../UI/WindowResource.h" +#include "../UI/WindowProject.h" +#include "../UI/WindowConsole.h" +#include "../UI/WindowTimers.h" + +#include "ModuleRender.h" +#include "ModuleWindow.h" +#include "SDL.h" +#include "glew.h" + +class ModuleEditor : public Module +{ +public: + + ModuleEditor(); + + bool Init() override; + update_status PreUpdate(const float delta) override; + update_status Update(const float delta) override; + bool CleanUp() override; + + //Edit actions + bool CanUndo() { return false; } + bool CanRedo() { return false; } + bool CanPaste() { return false; } + + void SetSelectedGO(GameObject* go) { selected_go = go; } + GameObject* GetSelectedGO() { return selected_go; } + const GameObject* GetSelectedGO() const { return selected_go; } + + const WindowScene* GetSceneWindow() const { return &w_scene; } + + ImFont* m_big_icon_font = nullptr; + ImFont* m_small_icon_font = nullptr; + + // TODO: Save the info imgui.ini + unsigned dock_up_id = 0; + unsigned dock_main_id = 0; + unsigned dock_left_id = 0; + unsigned dock_right_id = 0; + unsigned dock_down_id = 0; + +private: + void RenderGui(); + void GenerateDockingSpace(); + + // Main menu bar + update_status MainMenuBar(); + void FileMenu(); + void EditMenu(); + void GoMenu(); + void ViewMenu(); + + GameObject* selected_go = nullptr; + + std::vector windows; + + WindowHierarchy w_hierarchy; + WindowScene w_scene; + WindowInspector w_inspector; + WindowConfiguration w_configuration; + WindowAbout w_about; + WindowConsole w_console; + WindowResource w_resource; + WindowProject w_project; + WindowTimers w_timers; +}; diff --git a/Source/src/Modules/ModuleFileSystem.cpp b/Source/src/Modules/ModuleFileSystem.cpp new file mode 100644 index 000000000..ddc27c0f4 --- /dev/null +++ b/Source/src/Modules/ModuleFileSystem.cpp @@ -0,0 +1,325 @@ +#include "ModuleFileSystem.h" + +#include "../Globals.h" +#include "../Utils/Logger.h" + +#include +#include "physfs.h" +#include +#include +#include + +ModuleFileSystem::ModuleFileSystem() +{ + LOG("Creating virtual file system"); + // Create base path to be ready for other modules + char* base_path = SDL_GetBasePath(); + PHYSFS_init(base_path); + SDL_free(base_path); + + PHYSFS_mount(".", nullptr, 0); + + PHYSFS_setWriteDir("."); + + // Make sure standard paths exist + const char* dirs[] = { + SETTINGS_FOLDER, + ASSETS_FOLDER, + LIBRARY_FOLDER, + LIBRARY_MESH_FOLDER, + LIBRARY_MATERIAL_FOLDER, + LIBRARY_SCENE_FOLDER, + LIBRARY_SHADERS_FOLDER, + LIBRARY_TEXTURES_FOLDER, + }; + + for (unsigned int i = 0; i < sizeof(dirs) / sizeof(const char*); ++i) + { + if (PHYSFS_exists(dirs[i]) == 0) + PHYSFS_mkdir(dirs[i]); + } +} + +ModuleFileSystem::~ModuleFileSystem() +{ + PHYSFS_deinit(); +} + +bool ModuleFileSystem::Init() +{ + LOG("Init virtual file system"); + + CreateContext(); + + char* write_path = SDL_GetPrefPath("AltarCO", "AltarBoy"); + SDL_free(write_path); + + return true; +} + +void ModuleFileSystem::CreateContext() +{ + char engine_path[MAX_PATH]; + GetCurrentDirectory(MAX_PATH, engine_path); + working_directory = engine_path; + + LOG("Engine context: %s", working_directory.c_str()); +} + +char* ModuleFileSystem::Load(const char* file_path) const +{ + char* buffer = nullptr; + + PHYSFS_file* fs_file = PHYSFS_openRead(file_path); + + if (!fs_file) + { + LOG("Error opening file %s (%s).\n", fs_file, PHYSFS_getLastError()); + return buffer; + } + defer + { + PHYSFS_close(fs_file); + }; + + PHYSFS_sint32 size = (PHYSFS_sint32) PHYSFS_fileLength(fs_file); + + if (size < 0) + { + LOG("File size couldn't be determined for %s (%s).\n", fs_file, PHYSFS_getLastError()); + return buffer; + } + + buffer = new char[size]; + PHYSFS_sint64 readed = PHYSFS_read(fs_file, buffer, 1, size); + + if (readed < size) + { + LOG("Error reading file %s (%s).\n", fs_file, PHYSFS_getLastError()); + return buffer; + } + + return buffer; +} + +bool ModuleFileSystem::Save(const char* file_path, const void* buffer, unsigned int size, bool append) const +{ + bool overwrite = PHYSFS_exists(file_path) != 0; + PHYSFS_File* file = append ? PHYSFS_openAppend(file_path) : PHYSFS_openWrite(file_path); + + if (!file) + { + LOG("Error saving file %s (%s).\n", file_path, strerror(errno)); + return false; + } + defer + { + PHYSFS_close(file); + }; + + PHYSFS_sint64 written = PHYSFS_write(file, (const void*) buffer, 1, size); + if (written != size) + { + LOG("File System error while writing to file %s: %s", file, PHYSFS_getLastError()); + return false; + } + else + { + if (append == true) { + LOG("Added %u data to [%s%s]", size, PHYSFS_getWriteDir(), file); + } + else if (overwrite == false) + { + LOG("New file created [%s%s] of %u bytes", PHYSFS_getWriteDir(), file, size); + } + return true; + } + + return false; +} + +bool ModuleFileSystem::Exists(const char* file_path) const +{ + return PHYSFS_exists(file_path) != 0; +} + +bool ModuleFileSystem::IsDirectory(const char* directory_path) const +{ + return PHYSFS_isDirectory(directory_path) != 0; +} + +void ModuleFileSystem::CreateDir(const char* directory_path) { + if (!PHYSFS_mkdir(directory_path)) LOG(PHYSFS_getLastError()); +} + +void ModuleFileSystem::Copy(const char* source_file_path, const char* destination_file_path) +{ + CopyFile(source_file_path, destination_file_path, false); +} + +void ModuleFileSystem::Delete(const char* file_path) +{ + if (!PHYSFS_delete(file_path)) + { + LOG("Can't erase file %s. (%s)\n", file_path, PHYSFS_getLastError()); + } +} + +std::string ModuleFileSystem::NormalizePath(const char* full_path) const +{ + std::string newPath(full_path); + for (int i = 0; i < newPath.size(); ++i) + { + if (newPath[i] == '\\') + newPath[i] = '/'; + } + return newPath; +} + +std::string ModuleFileSystem::GetFileNameAndExtension(const char* file_path) const +{ + const char* last_slash = strrchr(file_path, '/'); + const char* last_backslash = strrchr(file_path, '\\'); + const char* last_separator = last_slash >= last_backslash ? last_slash : last_backslash; + + if (last_separator == nullptr) + { + return file_path; + } + + const char* name_extension = last_separator + 1; + return name_extension; +} + +std::string ModuleFileSystem::GetFileExtension(const char* file_path) const +{ + const char* last_slash = strrchr(file_path, '/'); + const char* last_backslash = strrchr(file_path, '\\'); + const char* last_separator = last_slash >= last_backslash ? last_slash : last_backslash; + + const char* lastDot = strrchr(file_path, '.'); + + std::string extension = std::string(lastDot); + std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower); + + return extension; +} + + +PathNode ModuleFileSystem::GetAllFiles(const char* directory, std::vector* filter_ext, std::vector* ignore_ext) const +{ + PathNode root; + if (Exists(directory)) + { + root.path = directory; + SplitFilePath(directory, nullptr, &root.localPath); + if (root.localPath == "") + root.localPath = directory; + + std::vector file_list, dir_list; + DiscoverFiles(directory, file_list, dir_list); + + //Adding all child directories + for (unsigned int i = 0; i < dir_list.size(); i++) + { + std::string str = directory; + str.append("/").append(dir_list[i]); + root.children.push_back(GetAllFiles(str.c_str(), filter_ext, ignore_ext)); + } + //Adding all child files + for (unsigned int i = 0; i < file_list.size(); i++) + { + //Filtering extensions + bool filter = true, discard = false; + if (filter_ext != nullptr) + { + filter = HasExtension(file_list[i].c_str(), *filter_ext); + } + if (ignore_ext != nullptr) + { + discard = HasExtension(file_list[i].c_str(), *ignore_ext); + } + if (filter == true && discard == false) + { + std::string str = directory; + str.append("/").append(file_list[i]); + root.children.push_back(GetAllFiles(str.c_str(), filter_ext, ignore_ext)); + } + } + root.isFile = HasExtension(root.path.c_str()); + root.isLeaf = root.children.empty() == true; + } + return root; +} + +void ModuleFileSystem::SplitFilePath(const char* full_path, std::string* path, std::string* file, std::string* extension) const +{ + if (full_path != nullptr) + { + std::string full(full_path); + size_t pos_separator = full.find_last_of("\\/"); + size_t pos_dot = full.find_last_of("."); + + if (path != nullptr) + { + if (pos_separator < full.length()) + *path = full.substr(0, pos_separator + 1); + else + path->clear(); + } + + if (file != nullptr) + { + if (pos_separator < full.length()) + *file = full.substr(pos_separator + 1, pos_dot - pos_separator - 1); + else + *file = full.substr(0, pos_dot); + } + + if (extension != nullptr) + { + if (pos_dot < full.length()) + *extension = full.substr(pos_dot + 1); + else + extension->clear(); + } + } +} + +void ModuleFileSystem::DiscoverFiles(const char* directory, std::vector& file_list, std::vector& dir_list) const +{ + char** rc = PHYSFS_enumerateFiles(directory); + char** i; + + for (i = rc; *i != nullptr; i++) + { + std::string str = std::string(directory) + std::string("/") + std::string(*i); + if (IsDirectory(str.c_str())) + dir_list.push_back(*i); + else + file_list.push_back(*i); + } + + PHYSFS_freeList(rc); +} + +bool ModuleFileSystem::HasExtension(const char* path) const +{ + std::string ext = ""; + SplitFilePath(path, nullptr, nullptr, &ext); + return ext != ""; +} + +bool ModuleFileSystem::HasExtension(const char* path, std::vector extensions) const +{ + std::string ext = ""; + SplitFilePath(path, nullptr, nullptr, &ext); + if (ext == "") + return true; + for (unsigned int i = 0; i < extensions.size(); i++) + { + if (extensions[i] == ext) + return true; + } + return false; +} \ No newline at end of file diff --git a/Source/src/Modules/ModuleFileSystem.h b/Source/src/Modules/ModuleFileSystem.h new file mode 100644 index 000000000..ffc7ff7b8 --- /dev/null +++ b/Source/src/Modules/ModuleFileSystem.h @@ -0,0 +1,47 @@ +#pragma once +#include "Module.h" + +#include "../Utils/PathNode.h" + +#include +#include + +class ModuleFileSystem : public Module +{ +public: + + ModuleFileSystem(); + ~ModuleFileSystem(); + + bool Init() override; + + void CreateContext(); + + // --- CRUD --- // + void CreateDir(const char* directory_path); + char* Load(const char* file_path) const; + bool Save(const char* file_path, const void* buffer, unsigned int size, bool append = false) const; + void Delete(const char* file_path); + + // --- Folder utils --- // + void Copy(const char* source_file_path, const char* destination_file_path); + std::string NormalizePath(const char* full_path) const; + bool Exists(const char* file_path) const; + bool IsDirectory(const char* directory_path) const; + + // --- File utils --- // + void SplitFilePath(const char* full_path, std::string* path, std::string* file = nullptr, std::string* extension = nullptr) const; + std::string GetFileNameAndExtension(const char* file_path) const; + std::string GetFileExtension(const char* file_path) const; + bool HasExtension(const char* path) const; + bool HasExtension(const char* path, std::vector extensions) const; + + // --- Path utils --- // + PathNode GetAllFiles(const char* directory, std::vector* filter_ext, std::vector* ignore_ext) const; + void DiscoverFiles(const char* directory, std::vector& file_list, std::vector& dir_list) const; + + std::string GetWorkingDirectory() { return working_directory; } + +private: + std::string working_directory = ""; +}; diff --git a/Source/ModuleHardware.cpp b/Source/src/Modules/ModuleHardware.cpp similarity index 64% rename from Source/ModuleHardware.cpp rename to Source/src/Modules/ModuleHardware.cpp index 4494f43ab..50ea85ba2 100644 --- a/Source/ModuleHardware.cpp +++ b/Source/src/Modules/ModuleHardware.cpp @@ -1,4 +1,4 @@ -#include "Globals.h" +#include "../Globals.h" #include "ModuleHardware.h" #include "SDL.h" @@ -7,8 +7,9 @@ #include "imgui_impl_sdl.h" #include "imgui_impl_opengl3.h" -ModuleHardware::ModuleHardware() -{} +ModuleHardware::ModuleHardware() {} + +ModuleHardware::~ModuleHardware() {} bool ModuleHardware::Init() { @@ -17,18 +18,14 @@ bool ModuleHardware::Init() info.n_cpu = SDL_GetCPUCount(); info.ram_gb = SDL_GetSystemRAM() / 1024.0f; - info.gpu = (unsigned char*)glGetString(GL_RENDERER); - info.gpu_brand = (unsigned char*)glGetString(GL_VENDOR); + info.gpu = (unsigned char*) glGetString(GL_RENDERER); + info.gpu_brand = (unsigned char*) glGetString(GL_VENDOR); glGetIntegerv(GL_GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX, &info.vram_mb_budget); glGetIntegerv(GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX, &info.vram_mb_free); - info.vram_mb_budget = info.vram_mb_budget / 1024.0f; + info.vram_mb_budget = info.vram_mb_budget / 1024; glGetIntegerv(GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX, &info.vram_mb_free); - info.vram_mb_free = info.vram_mb_free / 1024.0f; + info.vram_mb_free = info.vram_mb_free / 1024; return true; } - -ModuleHardware::~ModuleHardware() -{} - diff --git a/Source/ModuleHardware.h b/Source/src/Modules/ModuleHardware.h similarity index 83% rename from Source/ModuleHardware.h rename to Source/src/Modules/ModuleHardware.h index 572da286c..20afb16df 100644 --- a/Source/ModuleHardware.h +++ b/Source/src/Modules/ModuleHardware.h @@ -5,8 +5,8 @@ class ModuleHardware : public Module { public: - - struct hw_info { + struct hw_info + { SDL_version sdl_version; int n_cpu; float ram_gb; @@ -17,15 +17,13 @@ class ModuleHardware : public Module }; public: - ModuleHardware(); - ~ModuleHardware(); + ~ModuleHardware() override; - bool Init(); + bool Init() override; inline hw_info GetInfo() { return info; }; private: - mutable hw_info info; }; diff --git a/Source/src/Modules/ModuleInput.cpp b/Source/src/Modules/ModuleInput.cpp new file mode 100644 index 000000000..195253842 --- /dev/null +++ b/Source/src/Modules/ModuleInput.cpp @@ -0,0 +1,107 @@ +#include "ModuleInput.h" + +#include "../Globals.h" +#include "../Utils/Logger.h" + +#include "../Application.h" +#include "ModuleWindow.h" +#include "ModuleRender.h" +#include "ModuleCamera.h" +#include "ModuleSceneManager.h" + +#include "imgui_impl_sdl.h" + +#include + +#define MAX_KEYS 300 + +ModuleInput::ModuleInput() { + keyboard = new KeyState[MAX_KEYS]; + memset(keyboard, KEY_IDLE, sizeof(KeyState) * MAX_KEYS); +} + +ModuleInput::~ModuleInput() { + RELEASE_ARRAY(keyboard); +} + +// Called before render is available +bool ModuleInput::Init() +{ + LOG("Init SDL input event system"); + bool ret = true; + SDL_Init(0); + + if (SDL_InitSubSystem(SDL_INIT_EVENTS) < 0) + { + LOG("SDL_EVENTS could not initialize! SDL_Error: %s\n", SDL_GetError()); + ret = false; + } + + return ret; +} + +update_status ModuleInput::PreUpdate(const float delta) +{ + SDL_Event event; + mouse_delta_x = 0; + mouse_delta_y = 0; + scroll_delta = 0; + while (SDL_PollEvent(&event) != 0) + { + ImGui_ImplSDL2_ProcessEvent(&event); + switch (event.type) + { + case SDL_QUIT: + return UPDATE_STOP; + case SDL_WINDOWEVENT: + if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) + App->window->WindowResized(); + break; + case SDL_MOUSEMOTION: + mouse_delta_x = event.motion.xrel; + mouse_delta_y = event.motion.yrel; + break; + case SDL_MOUSEWHEEL: + scroll_delta = event.wheel.y; + break; + case SDL_DROPFILE: + LOG("Dropped file: %s", event.drop.file); + App->scene_manager->LoadModel(event.drop.file); + SDL_free(event.drop.file); + break; + } + } + UpdateInputMaps(); + + return UPDATE_CONTINUE; +} + +void ModuleInput::UpdateInputMaps() +{ + const Uint8* keys_state = SDL_GetKeyboardState(0); + for (int i = 0; i < MAX_KEYS; ++i) { + if (keys_state[i] == 1) + { + if (keyboard[i] == KEY_IDLE) + keyboard[i] = KEY_DOWN; + else if (keyboard[i] != KEY_REPEAT) + keyboard[i] = KEY_REPEAT; + } + else + { + if (keyboard[i] == KEY_REPEAT || keyboard[i] == KEY_DOWN) + keyboard[i] = KEY_UP; + else + keyboard[i] = KEY_IDLE; + } + } + keymods = SDL_GetModState(); + mouse = SDL_GetMouseState(&mouse_x, &mouse_y); +} + +bool ModuleInput::CleanUp() +{ + LOG("Quitting SDL input event subsystem"); + SDL_QuitSubSystem(SDL_INIT_EVENTS); + return true; +} diff --git a/Source/src/Modules/ModuleInput.h b/Source/src/Modules/ModuleInput.h new file mode 100644 index 000000000..42ee7b969 --- /dev/null +++ b/Source/src/Modules/ModuleInput.h @@ -0,0 +1,53 @@ +#pragma once +#include "Module.h" +#include "../Globals.h" + +#include "SDL.h" + +typedef unsigned __int8 Uint8; + +enum KeyState +{ + KEY_IDLE = 0, + KEY_DOWN, + KEY_REPEAT, + KEY_UP +}; + +class ModuleInput : public Module +{ +public: + ModuleInput(); + ~ModuleInput() override; + + bool Init() override; + update_status PreUpdate(const float delta) override; + bool CleanUp() override; + + KeyState GetKey(SDL_Scancode key) const { return keyboard[key]; } + const bool GetKeyMod(SDL_Keymod modifier) const { return (keymods & modifier); } + const bool GetMouseButton(int button) const { return (mouse & SDL_BUTTON(button)); } + int GetScrollDelta() const { return scroll_delta; } + void GetMouseDelta(int& x, int& y) const + { + x = mouse_delta_x; + y = mouse_delta_y; + } + + void GetMousePosition(int& x, int& y) const + { + x = mouse_x; + y = mouse_y; + } + +private: + void UpdateInputMaps(); + + KeyState* keyboard = nullptr; + Uint32 mouse; + SDL_Keymod keymods; + + int mouse_x, mouse_y; + int mouse_delta_x, mouse_delta_y; + int scroll_delta; +}; \ No newline at end of file diff --git a/Source/src/Modules/ModuleProgram.cpp b/Source/src/Modules/ModuleProgram.cpp new file mode 100644 index 000000000..ff879cd5c --- /dev/null +++ b/Source/src/Modules/ModuleProgram.cpp @@ -0,0 +1,266 @@ +#include "ModuleProgram.h" + +#include "../Application.h" +#include "../Components/ComponentCamera.h" +#include "../Components/ComponentDirLight.h" +#include "../Components/ComponentPointLight.h" +#include "../Components/ComponentSpotLight.h" +#include "../Components/ComponentMaterial.h" +#include "../Resources/ResourceMaterial.h" + +#include "../Utils/Logger.h" +#include "../Globals.h" + +#include "glew.h" +#include "MathGeoLib.h" +#include "imgui.h" + +ModuleProgram::ModuleProgram() {} + +ModuleProgram::~ModuleProgram() {} + +bool ModuleProgram::Init() +{ + CreateMainProgram(); + CreateSkyboxProgram(); + CreateStencilProgram(); + if (!main_program || !skybox_program || !stencil_program) + return false; + + CreateCameraUBO(); + CreateMaterialUBO(); + CreateLightsUBO(); + return true; +} + +char* ModuleProgram::LoadShaderSource(const char* shader_file_name) +{ + char* data = nullptr; + FILE* file = nullptr; + fopen_s(&file, shader_file_name, "rb"); + if (file) + { + fseek(file, 0, SEEK_END); + int size = ftell(file); + data = (char*) malloc(size + 1); + fseek(file, 0, SEEK_SET); + fread(data, 1, size, file); + data[size] = 0; + fclose(file); + } + return data; +} + +unsigned int ModuleProgram::CompileShader(unsigned type, const char* source) +{ + unsigned shader_id = glCreateShader(type); + glShaderSource(shader_id, 1, &source, 0); + glCompileShader(shader_id); + int res = GL_FALSE; + glGetShaderiv(shader_id, GL_COMPILE_STATUS, &res); + if (res == GL_FALSE) + { + int len = 0; + glGetShaderiv(shader_id, GL_INFO_LOG_LENGTH, &len); + if (len > 0) + { + int written = 0; + char* info = (char*) malloc(len); + glGetShaderInfoLog(shader_id, len, &written, info); + LOG("Log Info: %s", info); + free(info); + } + return 0; + } + return shader_id; +} + +void ModuleProgram::CompileShaders(const char* vtx_shader_path, const char* frg_shader_path, unsigned& vtx_shader, unsigned& frg_shader) +{ + const char* vertex_source = LoadShaderSource(vtx_shader_path); + const char* fragment_source = LoadShaderSource(frg_shader_path); + + vtx_shader = CompileShader(GL_VERTEX_SHADER, vertex_source); + frg_shader = CompileShader(GL_FRAGMENT_SHADER, fragment_source); + + delete vertex_source; + delete fragment_source; +} + +Program* ModuleProgram::CreateProgram(const char* vtx_shader_path, const char* frg_shader_path) +{ + unsigned fragment_shader_id; + unsigned vertex_shader_id; + CompileShaders(vtx_shader_path, frg_shader_path, vertex_shader_id, fragment_shader_id); + + if (vertex_shader_id == 0 || fragment_shader_id == 0) + return nullptr; + + Program* program = new Program(vertex_shader_id, fragment_shader_id); + + if (program->GetId() == 0) + { + delete program; + return nullptr; + } + + glDeleteShader(vertex_shader_id); + glDeleteShader(fragment_shader_id); + + return program; +} + +Program* ModuleProgram::CreateMainProgram() +{ + main_program = CreateProgram(ASSETS_FOLDER "/Shaders/vertex.glsl", ASSETS_FOLDER "/Shaders/fragment.glsl"); + return main_program; +} + +Program* ModuleProgram::CreateSkyboxProgram() +{ + skybox_program = CreateProgram(ASSETS_FOLDER "/Shaders/vertex_skybox.glsl", ASSETS_FOLDER "/Shaders/fragment_skybox.glsl"); + return skybox_program; +} + +Program* ModuleProgram::CreateStencilProgram() +{ + stencil_program = CreateProgram(ASSETS_FOLDER "/Shaders/vertex_stencil.glsl", ASSETS_FOLDER "/Shaders/fragment_stencil.glsl"); + return stencil_program; +} + +void ModuleProgram::CreateUBO(UBOPoints binding_point, unsigned size) +{ + glGenBuffers(1, &ubos[binding_point]); + glBindBuffer(GL_UNIFORM_BUFFER, ubos[binding_point]); + glBufferData(GL_UNIFORM_BUFFER, size, NULL, GL_DYNAMIC_DRAW); + glBindBufferBase(GL_UNIFORM_BUFFER, binding_point, ubos[binding_point]); + glBindBuffer(GL_UNIFORM_BUFFER, 0); +} + +void ModuleProgram::UpdateUBO(UBOPoints binding_point, unsigned size, void* data, unsigned offset) +{ + glBindBuffer(GL_UNIFORM_BUFFER, ubos[binding_point]); + glBufferSubData(GL_UNIFORM_BUFFER, offset, size, data); + glBindBuffer(GL_UNIFORM_BUFFER, 0); +} + +void ModuleProgram::CreateCameraUBO() +{ + CreateUBO(UBOPoints::p_camera, sizeof(Camera)); +} + +void ModuleProgram::CreateMaterialUBO() +{ + CreateUBO(UBOPoints::p_material, sizeof(MaterialData)); +} + +void ModuleProgram::CreateLightsUBO() +{ + CreateUBO(UBOPoints::p_lights, sizeof(Lights)); +} + +bool ModuleProgram::CleanUp() +{ + main_program->CleanUp(); + delete main_program; + skybox_program->CleanUp(); + delete skybox_program; + stencil_program->CleanUp(); + delete stencil_program; + return true; +} + +void ModuleProgram::UpdateCamera(const ComponentCamera* camera) +{ + Camera camera_data; + camera_data.view = camera->GetViewMatrix(); + camera_data.proj = camera->GetProjectionMatrix(); + // TODO: Understand why camera_data.view.TranslatePart() does not give the position + camera_data.pos = camera_data.view.RotatePart().Transposed().Transform(-camera_data.view.TranslatePart()); + + UpdateUBO(UBOPoints::p_camera, sizeof(Camera), &camera_data); +} + +void ModuleProgram::UpdateMaterial(const ComponentMaterial* material_comp) +{ + static int texture_slots[n_texture_slots] = {t_diffuse, t_specular}; + main_program->BindUniformInts("textures", n_texture_slots, &texture_slots[0]); + + const ResourceMaterial* material = material_comp->GetMaterial(); + + MaterialData material_data; + material_data.diffuse_color = material->diffuse_color; + material_data.diffuse_flag = material_comp->use_diffuse_texture; + material_data.specular_color = material->specular_color; + material_data.specular_flag = material_comp->use_specular_texture; + material_data.shininess = material->shininess; + + if (material_comp->use_diffuse_texture) + App->texture->Bind(material->GetDiffuseId(), ModuleProgram::TextureSlots::t_diffuse); + if (material_comp->use_specular_texture) + App->texture->Bind(material->GetSpecularId(), ModuleProgram::TextureSlots::t_specular); + + UpdateUBO(UBOPoints::p_material, sizeof(MaterialData), &material_data); +} + +void ModuleProgram::UpdateLights(const ComponentDirLight* dir_light, const std::vector& point_lights, const std::vector& spot_lights) +{ + Lights lights_data; + // Ambient + lights_data.ambient = ambient_light; + // DirectionalS + if (dir_light && dir_light->IsActive()) + { + lights_data.directional.direction = float4(dir_light->GetDirection(), 0.0f); + lights_data.directional.color = dir_light->color; + lights_data.directional.intensity = dir_light->intensity; + } + else + { + lights_data.directional.intensity = 0.0f; + } + + // Point + lights_data.n_points = 0; + for (unsigned i = 0; i < point_lights.size(); ++i) + { + if (point_lights[i]->IsActive()) + { + lights_data.points[lights_data.n_points].position = float4(point_lights[i]->GetPosition(), 0.0f); + lights_data.points[lights_data.n_points].color = point_lights[i]->color; + lights_data.points[lights_data.n_points].intensity = point_lights[i]->intensity; + lights_data.points[lights_data.n_points].radius = point_lights[i]->radius; + ++lights_data.n_points; + if (lights_data.n_points == MAX_POINT_LIGHTS) + break; + } + } + // Spot + lights_data.n_spots = 0; + for (unsigned i = 0; i < spot_lights.size(); ++i) + { + if (spot_lights[i]->IsActive()) + { + lights_data.spots[lights_data.n_spots].position = float4(spot_lights[i]->GetPosition(), 0.0f); + lights_data.spots[lights_data.n_spots].direction = float4(spot_lights[i]->GetDirection(), 0.0f); + lights_data.spots[lights_data.n_spots].color = spot_lights[i]->color; + lights_data.spots[lights_data.n_spots].inner = DegToRad(spot_lights[i]->inner); + lights_data.spots[lights_data.n_spots].outer = DegToRad(spot_lights[i]->outer); + lights_data.spots[lights_data.n_spots].intensity = spot_lights[i]->intensity; + lights_data.spots[lights_data.n_spots].radius = spot_lights[i]->radius; + ++lights_data.n_spots; + if (lights_data.n_spots == MAX_SPOT_LIGHTS) + break; + } + } + UpdateUBO(UBOPoints::p_lights, sizeof(Lights), &lights_data); +} + +void ModuleProgram::OptionsMenu() +{ + ImGui::PushItemWidth(100.0f); + ImGui::Text("Ambient Light"); + ImGui::InputFloat("Intensity", &ambient_light.intensity); + ImGuiUtils::CompactColorPicker("Color", &ambient_light.color[0]); + ImGui::PopItemWidth(); +} \ No newline at end of file diff --git a/Source/src/Modules/ModuleProgram.h b/Source/src/Modules/ModuleProgram.h new file mode 100644 index 000000000..88ab8ca79 --- /dev/null +++ b/Source/src/Modules/ModuleProgram.h @@ -0,0 +1,142 @@ +#pragma once +#include "Module.h" + +#include "../Program.h" + +#include "MathGeoLib.h" +#include +#include "../UI/ImGuiUtils.h" + +#define MAX_POINT_LIGHTS 4 +#define MAX_SPOT_LIGHTS 4 + +class ComponentCamera; +class ComponentDirLight; +class ComponentPointLight; +class ComponentSpotLight; +class ComponentMaterial; + +class ModuleProgram : public Module +{ +public: + + enum UBOPoints + { + p_camera = 0, + p_material, + p_lights, + n_ubo_points, + }; + + enum TextureSlots + { + t_diffuse = 0, + t_specular, + n_texture_slots, + }; + + ModuleProgram(); + ~ModuleProgram() override; + + bool Init() override; + bool CleanUp() override; + + Program* GetMainProgram() const { return main_program; } + Program* GetSkyboxProgram() const { return skybox_program; } + Program* GetStencilProgram() const { return stencil_program; } + + void UpdateCamera(const ComponentCamera* camera); + void UpdateMaterial(const ComponentMaterial* material_comp); + void UpdateLights(const ComponentDirLight* dir_light, const std::vector& point_lights, const std::vector& spot_lights); + + void OptionsMenu(); + +private: + char* LoadShaderSource(const char* shader_file_name); + unsigned int CompileShader(unsigned type, const char* source); + void CompileShaders(const char* vtx_shader_path, const char* frg_shader_path, unsigned& vtx_shader, unsigned& frg_shader); + Program* CreateProgram(const char* vtx_shader_path, const char* frg_shader_path); + + Program* CreateMainProgram(); + Program* CreateSkyboxProgram(); + Program* CreateStencilProgram(); + + Program* main_program; + Program* skybox_program; + Program* stencil_program; + + // Assume the shader already manages its binding points + void CreateUBO(UBOPoints binding_point, unsigned size); + void UpdateUBO(UBOPoints binding_point, unsigned size, void* data, unsigned offset = 0); + void CreateCameraUBO(); + void CreateMaterialUBO(); + void CreateLightsUBO(); + + unsigned ubos[UBOPoints::n_ubo_points]; + + struct Camera + { + float4x4 view = float4x4::identity; + float4x4 proj = float4x4::identity; + float3 pos = float3::zero; + }; + + struct MaterialData + { + float4 diffuse_color; + float4 specular_color; + unsigned diffuse_flag; + unsigned specular_flag; + float shininess; + }; + + + // Use float4 to prevent padding + struct AmbientLight + { + float4 color = float4::one; + float intensity = 0.05f; + float padding[3]; + }; + + struct DirLight + { + float4 direction = float4::zero; + float4 color = float4::zero; + float intensity; + float padding[3]; + }; + + struct PointLight + { + float4 position = float4::zero; + float4 color = float4::zero; + float intensity; + float radius; + float padding[2]; + }; + + struct SpotLight + { + float4 position = float4::zero; + float4 direction = float4::zero; + float4 color = float4::zero; + float inner; + float outer; + float intensity; + float radius; + }; + + struct Lights + { + AmbientLight ambient; + DirLight directional; + PointLight points[MAX_POINT_LIGHTS]; + SpotLight spots[MAX_SPOT_LIGHTS]; + unsigned int n_points; + unsigned int n_spots; + }; + + AmbientLight ambient_light; + float ambient_strength = 0.05f; +}; diff --git a/Source/src/Modules/ModuleRender.cpp b/Source/src/Modules/ModuleRender.cpp new file mode 100644 index 000000000..d4cd84987 --- /dev/null +++ b/Source/src/Modules/ModuleRender.cpp @@ -0,0 +1,410 @@ +#include "../Globals.h" +#include "../Application.h" +#include "../Utils/Logger.h" + +#include "ModuleRender.h" +#include "ModuleWindow.h" +#include "ModuleProgram.h" +#include "ModuleCamera.h" +#include "ModuleDebugDraw.h" +#include "ModuleSceneManager.h" +#include "ModuleEditor.h" +#include "../Scene.h" +#include "../Skybox.h" +#include "../Quadtree.h" +#include "../GameObject.h" + +#include "../Components/ComponentCamera.h" + +#include "SDL.h" +#include "glew.h" + +#include "MathGeoLib.h" +#include "il.h" +#include "ilu.h" + +#include "imgui.h" +#include "imgui_impl_sdl.h" +#include "imgui_impl_opengl3.h" + +ModuleRender::ModuleRender() +{ +} + +ModuleRender::~ModuleRender() +{ +} + +void __stdcall OurOpenGLErrorFunction(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam); + +bool ModuleRender::Init() +{ + LOG("Init Module render"); + + CreateContext(); + + RetrieveGpuInfo(); + RetrieveLibVersions(); + + SetGLOptions(); + GenerateFrameBuffer(); + +#ifdef _DEBUG + glEnable(GL_DEBUG_OUTPUT); // Enable output callback + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + glDebugMessageCallback(&OurOpenGLErrorFunction, nullptr); // Set the callback + glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, true); // Filter notifications +#endif + + fps_log = std::vector(n_bins); + ms_log = std::vector(n_bins); + + return true; +} + +void ModuleRender::GenerateFrameBuffer() +{ + glGenFramebuffers(1, &frame_buffer); + glBindFramebuffer(GL_FRAMEBUFFER, frame_buffer); + + glGenTextures(1, &fb_texture); + glBindTexture(GL_TEXTURE_2D, fb_texture); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 800, 600, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); + glBindTexture(GL_TEXTURE_2D, 0); + + //Depth and stencil buffer + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb_texture, 0); + glGenRenderbuffers(1, &depth_stencil_buffer); + glBindRenderbuffer(GL_RENDERBUFFER, depth_stencil_buffer); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depth_stencil_buffer); + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + { + LOG("Error creating frame buffer"); + } + + glBindRenderbuffer(GL_RENDERBUFFER, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); +} + +void ModuleRender::ResizeFrameBuffer(int heigth, int width) +{ + glBindTexture(GL_TEXTURE_2D, fb_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, heigth, width, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + glBindTexture(GL_TEXTURE_2D, 0); + + glBindRenderbuffer(GL_RENDERBUFFER, depth_stencil_buffer); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, heigth, width); + glBindRenderbuffer(GL_RENDERBUFFER, 0); +} + +void ModuleRender::ManageResolution(ComponentCamera* camera) +{ + unsigned res_x, res_y; + camera->GetResolution(res_x, res_y); + if (res_x != fb_height || res_y != fb_width) { + ResizeFrameBuffer(res_x, res_y); + glViewport(0, 0, res_x, res_y); + fb_height = res_x; + fb_width = res_y; + } +} + +void ModuleRender::CreateContext() +{ + LOG("Creating Renderer context"); + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); // desired version + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 6); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); // we want a double buffer + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); // we want to have a depth buffer with 24 bits + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); // we want to have a stencil buffer with 8 bits + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG); // enable context debug + + context = SDL_GL_CreateContext(App->window->GetWindow()); + GLenum err = glewInit(); + + clear_color = float4(0.1f, 0.1f, 0.1f, 1.0f); +} + +void ModuleRender::SetGLOptions() +{ + glEnable(GL_DEPTH_TEST); // Enable depth test + glEnable(GL_CULL_FACE); // Enable cull backward faces + glFrontFace(GL_CCW); // Front faces will be counter clockwise + glEnable(GL_STENCIL_TEST); // Enable stencil test + glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); // Only replace stencil value if stencil and depth tests pass +} + +update_status ModuleRender::Update(const float delta) +{ + ComponentCamera* camera = App->camera->GetMainCamera(); + + // Using debug camera to test culling + + App->program->UpdateCamera(camera); + Scene* active_scene = App->scene_manager->GetActiveScene(); + ComponentCamera* culling = active_scene->GetCullingCamera(); + + ComponentDirLight* dir_light = nullptr; + if (active_scene->dir_lights.size() > 0) + dir_light = active_scene->dir_lights[0]; + + App->program->UpdateLights(dir_light, active_scene->point_lights, active_scene->spot_lights); + + Draw(App->scene_manager->GetActiveScene(), camera, culling); + return UPDATE_CONTINUE; +} + +void ModuleRender::Draw(Scene* scene, ComponentCamera* camera, ComponentCamera* culling) +{ + glBindFramebuffer(GL_FRAMEBUFFER, frame_buffer); + + ManageResolution(camera); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + glStencilFunc(GL_ALWAYS, 1, 0XFF); + glStencilMask(0x00); // Prevent background from filling stencil + + if (draw_skybox) + scene->GetSkybox()->Draw(camera); + else + glClearColor(clear_color[0], clear_color[1], clear_color[2], clear_color[3]); + + float4x4 view = camera->GetViewMatrix(); + float4x4 proj = camera->GetProjectionMatrix(); + + glStencilMask(0XFF); + + App->debug_draw->Draw(view, proj, fb_height, fb_width); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + //Draw alternatives 0 optiomization, no quadtree + // GameObject* root = scene->GetRoot(); + //root->DrawAll(camera); + //render_list.Update(culling, root); + + render_list.Update(culling, scene->GetQuadtree()->GetRoot()); + Program* program = App->program->GetMainProgram(); + program->Activate(); + + GameObject* selected_go = App->editor->GetSelectedGO(); + RenderTarget* outline_target = nullptr; + for (RenderTarget& target : render_list.GetNodes()) + { + target.game_object->Draw(camera, program); + if (selected_go && target.game_object == selected_go) + { + outline_target = ⌖ + } + } + program->Deactivate(); + + if (outline_selection && outline_target) + { + glStencilFunc(GL_NOTEQUAL, 1, 0XFF); + glStencilMask(0X00); + glDisable(GL_DEPTH_TEST); + + Program* outline_program = App->program->GetStencilProgram(); + outline_program->Activate(); + outline_target->game_object->DrawStencil(camera, outline_program); + outline_program->Deactivate(); + + glStencilMask(0XFF); + glStencilFunc(GL_ALWAYS, 0, 0xFF); + glEnable(GL_DEPTH_TEST); + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); +} + + +update_status ModuleRender::PostUpdate(const float delta) +{ + SDL_GL_SwapWindow(App->window->GetWindow()); + AddFrame(delta); + + return UPDATE_CONTINUE; +} + +void GLOptionCheck(GLenum option, bool enable) +{ + if (enable) + glEnable(option); + else + glDisable(option); +} + +void ModuleRender::OptionsMenu() +{ + ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "Draw Options"); + ImGui::Checkbox("Debug Draw", &App->debug_draw->debug_draw); + ImGui::Checkbox("Quadtree", &App->debug_draw->draw_quadtree); + + ImGui::Checkbox("Skybox", &draw_skybox); + if (!draw_skybox) + ImGuiUtils::CompactColorPicker("Background Color", &clear_color[0]); +} + +void ModuleRender::PerformanceMenu() +{ + glGetIntegerv(GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX, &vram_free); + float vram_free_mb = vram_free / 1024.0f; + float vram_usage_mb = gpu.vram_budget_mb - vram_free_mb; + ImGui::Text("VRAM Budget: %.1f Mb", gpu.vram_budget_mb); + ImGui::Text("Vram Usage: %.1f Mb", vram_usage_mb); + ImGui::Text("Vram Avaliable: %.1f Mb", vram_free_mb); + ImGui::Separator(); + FpsGraph(); +} + +void ModuleRender::FpsGraph() +{ + ImGui::Text("Fps: %.1f", current_fps); + + char title[25]; + sprintf_s(title, 25, "Framerate %.1f", current_fps); + ImGui::PlotHistogram("##framerate", &fps_log[0], (int) fps_log.size(), 0, title, 0.0f, 1000.f, ImVec2(310, 100)); + sprintf_s(title, 25, "Milliseconds %0.1f", current_ms); + ImGui::PlotHistogram("##milliseconds", &ms_log[0], (int) ms_log.size(), 0, title, 0.0f, 20.0f, ImVec2(310, 100)); +} + +void ModuleRender::AddFrame(const float delta) +{ + static const float update_frequency_seconds = 0.5f; + static int filled_bins = 0; + static int frames = 0; + static float time = 0; + + ++frames; + time += delta; + + if (time >= update_frequency_seconds) + { + if (filled_bins == n_bins) + { + for (int i = 0; i < n_bins - 1; ++i) + { + fps_log[i] = fps_log[i + 1]; + ms_log[i] = ms_log[i + 1]; + } + } + else + { + ++filled_bins; + } + fps_log[filled_bins - 1] = float(frames) / time; + current_fps = fps_log[filled_bins - 1]; + ms_log[filled_bins - 1] = time * 1000.0f / float(frames); + current_ms = ms_log[filled_bins - 1]; + time = 0; + frames = 0; + } +} + +void ModuleRender::RetrieveLibVersions() +{ + gl.glew = (unsigned char*) glewGetString(GLEW_VERSION); + gl.opengl = (unsigned char*) glGetString(GL_VERSION); + gl.glsl = (unsigned char*) glGetString(GL_SHADING_LANGUAGE_VERSION); + + LOG("Using Glew %s", gl.glew); + LOG("OpenGL version supported %s", gl.opengl); + LOG("GLSL: %s", gl.glsl); +} + +void ModuleRender::RetrieveGpuInfo() +{ + gpu.name = (unsigned char*) glGetString(GL_RENDERER); + gpu.brand = (unsigned char*) glGetString(GL_VENDOR); + + int vram_budget; + glGetIntegerv(GL_GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX, &vram_budget); + gpu.vram_budget_mb = (float) vram_budget / 1024.0f; +} + +bool ModuleRender::CleanUp() +{ + //LOG("Destroying renderer"); + SDL_GL_DeleteContext(context); + + return true; +} + +void __stdcall OurOpenGLErrorFunction(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) +{ + const char *tmp_source = "", *tmp_type = "", *tmp_severity = ""; + switch (source) + { + case GL_DEBUG_SOURCE_API: + tmp_source = "API"; + break; + case GL_DEBUG_SOURCE_WINDOW_SYSTEM: + tmp_source = "Window System"; + break; + case GL_DEBUG_SOURCE_SHADER_COMPILER: + tmp_source = "Shader Compiler"; + break; + case GL_DEBUG_SOURCE_THIRD_PARTY: + tmp_source = "Third Party"; + break; + case GL_DEBUG_SOURCE_APPLICATION: + tmp_source = "Application"; + break; + case GL_DEBUG_SOURCE_OTHER: + tmp_source = "Other"; + break; + }; + switch (type) + { + case GL_DEBUG_TYPE_ERROR: + tmp_type = "Error"; + break; + case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: + tmp_type = "Deprecated Behaviour"; + break; + case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: + tmp_type = "Undefined Behaviour"; + break; + case GL_DEBUG_TYPE_PORTABILITY: + tmp_type = "Portability"; + break; + case GL_DEBUG_TYPE_PERFORMANCE: + tmp_type = "Performance"; + break; + case GL_DEBUG_TYPE_MARKER: + tmp_type = "Marker"; + break; + case GL_DEBUG_TYPE_PUSH_GROUP: + tmp_type = "Push Group"; + break; + case GL_DEBUG_TYPE_POP_GROUP: + tmp_type = "Pop Group"; + break; + case GL_DEBUG_TYPE_OTHER: + tmp_type = "Other"; + break; + }; + switch (severity) + { + case GL_DEBUG_SEVERITY_HIGH: + tmp_severity = "high"; + break; + case GL_DEBUG_SEVERITY_MEDIUM: + tmp_severity = "medium"; + break; + case GL_DEBUG_SEVERITY_LOW: + return; + // case GL_DEBUG_SEVERITY_NOTIFICATION: tmp_severity = "notification"; break; + default: + return; + }; + LOG(" \n", tmp_source, tmp_type, tmp_severity, id, message); +} \ No newline at end of file diff --git a/Source/src/Modules/ModuleRender.h b/Source/src/Modules/ModuleRender.h new file mode 100644 index 000000000..fc3334b32 --- /dev/null +++ b/Source/src/Modules/ModuleRender.h @@ -0,0 +1,89 @@ +#pragma once +#include "Module.h" +#include "../Globals.h" +#include "../UI/ImGuiUtils.h" + +#include "MathGeoLib.h" + +#include "../RenderList.h" + +#include + +struct SDL_Texture; +struct SDL_Renderer; +struct SDL_Rect; +class ComponentCamera; +class Scene; + +struct GpuData +{ + unsigned char* name; + unsigned char* brand; + float vram_budget_mb; +}; + +struct GlVersion +{ + unsigned char* glew; + unsigned char* opengl; + unsigned char* glsl; +}; + +class ModuleRender : public Module +{ +public: + ModuleRender(); + ~ModuleRender() override; + + bool Init() override; + update_status Update(const float delta) override; + update_status PostUpdate(const float delta) override; + bool CleanUp() override; + + inline unsigned int GetFrameBuffer() const { return frame_buffer; } + inline unsigned int GetTextureId() const { return fb_texture; } + + void OptionsMenu(); + void PerformanceMenu(); + void FpsGraph(); + void AddFrame(const float delta); + + inline void* GetGLContext() const { return context; } + inline const GpuData GetGpuData() const { return gpu; } + inline const GlVersion GetGlVersion() { return gl; } + +private: + void GenerateFrameBuffer(); + void ResizeFrameBuffer(int heigth, int width); + void ManageResolution(ComponentCamera* camera); + void Draw(Scene* scene, ComponentCamera* camera, ComponentCamera* culling); + + void CreateContext(); + void SetGLOptions(); + void RetrieveLibVersions(); + void RetrieveGpuInfo(); + + void* context; + + RenderList render_list; + + unsigned frame_buffer = 0; + unsigned depth_stencil_buffer = 0; + unsigned fb_texture = 0; + unsigned fb_height = 0; + unsigned fb_width = 0; + + float4 clear_color; + bool draw_skybox = true; + bool outline_selection = true; + + GpuData gpu; + GlVersion gl; + int vram_free; + + static const unsigned n_bins = 50; + std::vector fps_log; + std::vector ms_log; + float current_fps = 0.0f; + float current_ms = 0.0f; +}; diff --git a/Source/src/Modules/ModuleSceneManager.cpp b/Source/src/Modules/ModuleSceneManager.cpp new file mode 100644 index 000000000..a3030862f --- /dev/null +++ b/Source/src/Modules/ModuleSceneManager.cpp @@ -0,0 +1,58 @@ +#include "ModuleSceneManager.h" + +#include "../Importers/SceneImporter.h" +#include "../Utils/Logger.h" + +ModuleSceneManager::ModuleSceneManager() {} + +ModuleSceneManager::~ModuleSceneManager() {} + +bool ModuleSceneManager::Init() +{ + LOG("Creating Empty scene"); + + // main_scene = new Scene(); + + main_scene = SceneImporter::LoadScene(ASSETS_FOLDER "/Scenes/lights_delivery.scene"); + + //LoadModel(ASSETS_FOLDER "\\Models\\BakerHouse.fbx"); //TODO: Remove this when importen will be created + + // CreateEmptyScene(); + // LoadScene(LIBRARY_SCENE_FOLDER "/survival_shooter.scene"); + return true; +} + +update_status ModuleSceneManager::Update(const float delta) +{ + main_scene->Update(); + return UPDATE_CONTINUE; +} + +bool ModuleSceneManager::CleanUp() +{ + RELEASE(main_scene); + return true; +} + +void ModuleSceneManager::LoadModel(const char* model_path) +{ + // delete scene_model; + main_scene->LoadFBX(model_path); +} + +void ModuleSceneManager::CreateEmptyScene() +{ + delete main_scene; + main_scene = new Scene(); +} + +void ModuleSceneManager::LoadScene(const char* file_path) +{ + delete main_scene; + main_scene = SceneImporter::LoadScene(file_path); +} + +void ModuleSceneManager::SaveScene(const char* file_path) +{ + SceneImporter::SaveScene(main_scene, file_path); +} diff --git a/Source/src/Modules/ModuleSceneManager.h b/Source/src/Modules/ModuleSceneManager.h new file mode 100644 index 000000000..41406d532 --- /dev/null +++ b/Source/src/Modules/ModuleSceneManager.h @@ -0,0 +1,36 @@ +#pragma once +#include "Module.h" +#include "Math.h" + +#include "../Scene.h" + +class GameObject; +class ComponentCamera; + +class ModuleSceneManager : public Module +{ +public: + ModuleSceneManager(); + ~ModuleSceneManager() override; + + // --- Life cycle --- // + bool Init() override; + update_status Update(const float delta) override; + bool CleanUp() override; + + // --- Scene management --- // + void LoadModel(const char* model_path); // TODO: delete Change to load scene and load al gameObjects for path + + GameObject* GetRoot() { return main_scene->root; } + const GameObject* GetRoot() const { return main_scene->root; } + Scene* GetActiveScene() { return main_scene; } + const Scene* GetActiveScene() const { return main_scene; } + + void CreateEmptyScene(); + + void LoadScene(const char* file_path); + void SaveScene(const char* file_path); + +private: + Scene* main_scene = nullptr; +}; diff --git a/Source/src/Modules/ModuleTexture.cpp b/Source/src/Modules/ModuleTexture.cpp new file mode 100644 index 000000000..f12cc86b6 --- /dev/null +++ b/Source/src/Modules/ModuleTexture.cpp @@ -0,0 +1,137 @@ +#include "ModuleTexture.h" + +#include "ilu.h" +#include "imgui.h" + +ModuleTexture::ModuleTexture() {} + +ModuleTexture::~ModuleTexture() {} + +bool ModuleTexture::Init() +{ + ilInit(); + return true; +} + +bool ModuleTexture::CleanUp() +{ + ilShutDown(); + return true; +} + +Texture ModuleTexture::Load(const char* path, bool flip) +{ + Texture texture; + texture.path = path; + unsigned int img_id = LoadImg(path, flip); + + if (img_id != 0) + { + glGenTextures(1, &texture.id); + glBindTexture(GL_TEXTURE_2D, texture.id); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + + glTexImage2D(GL_TEXTURE_2D, 0, ilGetInteger(IL_IMAGE_BPP), texture.width = ilGetInteger(IL_IMAGE_WIDTH), texture.height = ilGetInteger(IL_IMAGE_HEIGHT), 0, ilGetInteger(IL_IMAGE_FORMAT), GL_UNSIGNED_BYTE, ilGetData()); + glGenerateMipmap(GL_TEXTURE_2D); + + glBindTexture(GL_TEXTURE_2D, 0); + DeleteImg(img_id); + texture.loaded = true; // False by default + } + return texture; +} + +void ModuleTexture::Unload(Texture& texture) +{ + if (texture.loaded) + { + glDeleteTextures(1, &texture.id); + texture.loaded = false; + } + +} + +TextureCube ModuleTexture::LoadCubeMap(const char* paths[6]) +{ + constexpr bool flip = true; + TextureCube cube; + cube.loaded = true; + + glGenTextures(1, &cube.id); + glBindTexture(GL_TEXTURE_CUBE_MAP, cube.id); + + // Expected file order x, -x, y, -y, z, -z + for (int i = 0; i < 6; ++i) + { + unsigned int img_id = LoadImg(paths[i], flip); + iluFlipImage(); + if (img_id == 0) + { + cube.loaded = false; + continue; // Try loading the other parts despite failing + } + // Take advantage of opengl enum with index + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, ilGetInteger(IL_IMAGE_BPP), cube.widths[i] = ilGetInteger(IL_IMAGE_WIDTH), cube.heighths[i] = ilGetInteger(IL_IMAGE_HEIGHT), 0, ilGetInteger(IL_IMAGE_FORMAT), GL_UNSIGNED_BYTE, ilGetData()); + } + + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + + return cube; +} + +void ModuleTexture::Bind(unsigned id, unsigned slot) +{ + glEnable(GL_TEXTURE_2D); + glActiveTexture(GL_TEXTURE0 + slot); + glBindTexture(GL_TEXTURE_2D, id); +} + +void ModuleTexture::Unbind(unsigned slot) +{ + glActiveTexture(GL_TEXTURE0 + slot); + glBindTexture(GL_TEXTURE_2D, 0); +} + +void SetOption(unsigned option, unsigned value) +{ + glTexParameteri(GL_TEXTURE_2D, option, value); +} + +void ModuleTexture::OptionsMenu() +{ + const char* labels_mag[] = {"Linear", "Nearest"}; + const unsigned values_mag[] = {GL_LINEAR, GL_NEAREST}; + const char* labels_min[] = {"Nearest", "Linear", "Nearest Mipmaps Nearest Criteria", "Nearest Mipmap Linear Criteria", "Linear Mipmaps (Two Closest) Nearest Criteria", "Linear Mipmaps (Two Closest) Linear Criteria"}; + const unsigned values_min[] = {GL_NEAREST, GL_LINEAR, GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR_MIPMAP_LINEAR}; + static int mag_filter = 0; // Default is GL_LINEAR + static int min_filter = 3; // Default is GL_NEAREST_MIPMAP_LINEAR + + if (ImGui::Combo("Mag Filter", &mag_filter, labels_mag, IM_ARRAYSIZE(labels_mag))) + SetOption(GL_TEXTURE_MAG_FILTER, values_mag[mag_filter]); + if (ImGui::Combo("Min Filter", &min_filter, labels_min, IM_ARRAYSIZE(labels_min))) + SetOption(GL_TEXTURE_MIN_FILTER, values_min[min_filter]); +} + +unsigned int ModuleTexture::LoadImg(const char* path, bool flip) +{ + ILuint img_id; // The image name to return. + ilGenImages(1, &img_id); // Grab a new image name. + ilBindImage(img_id); + if (!ilLoadImage(path)) + return 0; + ilConvertImage(IL_RGBA, IL_UNSIGNED_BYTE); + if (flip) + iluFlipImage(); + return img_id; +} + +void ModuleTexture::DeleteImg(unsigned& img_id) +{ + ilDeleteImages(1, &img_id); +} diff --git a/Source/src/Modules/ModuleTexture.h b/Source/src/Modules/ModuleTexture.h new file mode 100644 index 000000000..5618b4b13 --- /dev/null +++ b/Source/src/Modules/ModuleTexture.h @@ -0,0 +1,51 @@ +#pragma once +#include "Module.h" + +#include "il.h" +#include "glew.h" + +#include + +struct Texture +{ + bool loaded = false; + unsigned id = 0; + std::string path; + unsigned width = 0; + unsigned height = 0; +}; + +struct TextureCube +{ + bool loaded = false; + unsigned id; + unsigned widths[6]; + unsigned heighths[6]; +}; + + +class ModuleTexture : public Module +{ + +public: + ModuleTexture(); + ~ModuleTexture() override; + + bool Init() override; + bool CleanUp() override; + + Texture Load(const char* path, bool flip = true); + void Unload(Texture& texture); + TextureCube LoadCubeMap(const char* paths[6]); + void Bind(unsigned id, unsigned slot); + void Unbind(unsigned slot); + + inline const short GetDevilVersion() const { return devil_version; } + + void OptionsMenu(); + +private: + const short devil_version = IL_VERSION; + unsigned int LoadImg(const char* path, bool flip = true); + void DeleteImg(unsigned& img_id); +}; diff --git a/Source/src/Modules/ModuleWindow.cpp b/Source/src/Modules/ModuleWindow.cpp new file mode 100644 index 000000000..33cda3a79 --- /dev/null +++ b/Source/src/Modules/ModuleWindow.cpp @@ -0,0 +1,135 @@ +#include "../Globals.h" +#include "../Application.h" +#include "../Utils/Logger.h" + +#include "ModuleWindow.h" +#include "ModuleRender.h" +#include "ModuleCamera.h" + +#include "wtypes.h" +#include + +ModuleWindow::ModuleWindow() {} + +ModuleWindow::~ModuleWindow() {} + +bool ModuleWindow::Init() +{ + GetMonitorResolution(max_width, max_height); + width = (int) (max_width * WINDOWED_RATIO); + height = (int) (max_height * WINDOWED_RATIO); + + fullscreen = FULLSCREEN; + resizable = RESIZABLE; + vsync = true; + + LOG("Init SDL window & surface"); + bool ret = true; + + if (SDL_Init(SDL_INIT_VIDEO) < 0) + { + LOG("SDL_VIDEO could not initialize! SDL_Error: %s\n", SDL_GetError()); + ret = false; + } + else + { + Uint32 flags = SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL; + + if (fullscreen) + flags |= SDL_WINDOW_FULLSCREEN; + if (resizable) + flags |= SDL_WINDOW_RESIZABLE; + + window = SDL_CreateWindow(TITLE, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, flags); + + if (window == NULL) + { + LOG("Window could not be created! SDL_Error: %s\n", SDL_GetError()); + ret = false; + } + else + { + // Get window surface, it must be updated on resize + screen_surface = SDL_GetWindowSurface(window); + } + SDL_DisplayMode mode; + SDL_GetDisplayMode(0, 0, &mode); + refresh_rate = mode.refresh_rate; + } + + return ret; +} + +// Called before quitting +bool ModuleWindow::CleanUp() +{ + LOG("Destroying SDL window and quitting all SDL systems"); + + //Destroy window + if (window != NULL) + { + SDL_DestroyWindow(window); + } + + //Quit SDL subsystems + SDL_Quit(); + return true; +} + +void ModuleWindow::WindowResized() +{ + // Update window surface so it is correct + SDL_UpdateWindowSurface(window); + screen_surface = SDL_GetWindowSurface(window); +} + +void ModuleWindow::SetFullScreen(bool fullscreen) +{ + // Method returns negative error code on failure + if (fullscreen) + SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP); + else + SDL_SetWindowFullscreen(window, 0); +} + +void ModuleWindow::SetResizable(bool resizable) +{ + SDL_SetWindowResizable(window, (SDL_bool) resizable); +} + +void ModuleWindow::SetSize(int w, int h) +{ + SDL_SetWindowSize(window, w, h); +} + +void ModuleWindow::SetVsync(bool vsync) +{ + SDL_GL_SetSwapInterval((int) vsync); +} + +void ModuleWindow::OptionsMenu() +{ + if (ImGui::Checkbox("Fullscreen", &fullscreen)) + App->window->SetFullScreen(fullscreen); + + ImGui::SameLine(); + if (ImGui::Checkbox("Vsync", &vsync)) + SetVsync(vsync); + + if (!fullscreen) + { + if (ImGui::Checkbox("Resizable", &resizable)) + SetResizable(resizable); + } + ImGui::Text("Monitor Refresh Rate: %d", refresh_rate); +} + +void ModuleWindow::GetMonitorResolution(int& width, int& height) +{ + RECT monitor; + const HWND desktop_handle = GetDesktopWindow(); + // Get the size of screen to the variable desktop + GetWindowRect(desktop_handle, &monitor); + width = monitor.right; + height = monitor.bottom; +} diff --git a/Source/src/Modules/ModuleWindow.h b/Source/src/Modules/ModuleWindow.h new file mode 100644 index 000000000..fa70f643e --- /dev/null +++ b/Source/src/Modules/ModuleWindow.h @@ -0,0 +1,45 @@ +#pragma once + +#include "Module.h" +#include "SDL.h" + +class Application; + +class ModuleWindow : public Module +{ +public: + ModuleWindow(); + ~ModuleWindow() override; + + bool Init() override; + bool CleanUp() override; + + void WindowResized(); + void SetFullScreen(bool fullscreen); + void SetResizable(bool resizable); + void SetSize(int w, int h); + void SetVsync(bool vsync); + + inline int GetWidth() const { return width; } + inline int GetHeight() const { return height; } + + void OptionsMenu(); + + inline SDL_Surface* GetScreenSurface() const { return screen_surface; } + inline SDL_Window* GetWindow() const { return window; } + +private: + void GetMonitorResolution(int& width, int& height); + + SDL_Window* window = NULL; + SDL_Surface* screen_surface = NULL; + + bool fullscreen; + bool resizable; + int width; + int height; + int max_width; + int max_height; + int refresh_rate; + bool vsync; +}; diff --git a/Source/src/Program.cpp b/Source/src/Program.cpp new file mode 100644 index 000000000..28a9387c0 --- /dev/null +++ b/Source/src/Program.cpp @@ -0,0 +1,70 @@ +#include "Program.h" + +#include "glew.h" + +#include + +Program::Program(unsigned vtx_shader, unsigned frg_shader) +{ + id = glCreateProgram(); + glAttachShader(id, vtx_shader); + glAttachShader(id, frg_shader); + glLinkProgram(id); + int res; + glGetProgramiv(id, GL_LINK_STATUS, &res); + if (res == GL_FALSE) + { + int len = 0; + glGetProgramiv(id, GL_INFO_LOG_LENGTH, &len); + if (len > 0) + { + int written = 0; + char* info = (char*) malloc(len); + glGetProgramInfoLog(id, len, &written, info); + // LOG("Program Log Info: %s", info); + free(info); + } + // Set id to 0 to indicate that it failed + id = 0; + } +} + +void Program::Activate() +{ + glUseProgram(id); +} + +void Program::Deactivate() +{ + glUseProgram(0); +} + +void Program::CleanUp() +{ + glDeleteProgram(id); +} + +void Program::BindUniformFloat4x4(const char* name, const float* data, bool transpose) +{ + glUniformMatrix4fv(glGetUniformLocation(id, name), 1, transpose, data); +} + +void Program::BindUniformFloat3(const char* name, const float* data) +{ + glUniform3fv(glGetUniformLocation(id, name), 1, data); +} + +void Program::BindUniformFloat(const char* name, const float* data) +{ + glUniform1fv(glGetUniformLocation(id, name), 1, data); +} + +void Program::BindUniformBool(const char* name, bool value) +{ + glUniform1i(glGetUniformLocation(id, name), value); +} + +void Program::BindUniformInts(const char* name, unsigned size, const int* data) +{ + glUniform1iv(glGetUniformLocation(id, name), size, data); +} diff --git a/Source/src/Program.h b/Source/src/Program.h new file mode 100644 index 000000000..131ca2f16 --- /dev/null +++ b/Source/src/Program.h @@ -0,0 +1,22 @@ +#pragma once + +class Program +{ +public: + Program(unsigned vtx_shader, unsigned frg_shader); + + void Activate(); + void Deactivate(); + void CleanUp(); + + void BindUniformFloat4x4(const char* name, const float* data, bool transpose = true); + void BindUniformFloat3(const char* name, const float* data); + void BindUniformFloat(const char* name, const float* data); + void BindUniformBool(const char* name, bool value); + void BindUniformInts(const char* name, unsigned size, const int* data); + + inline int GetId() const{ return id; } + +private: + unsigned id = 0; +}; diff --git a/Source/src/Quadtree.cpp b/Source/src/Quadtree.cpp new file mode 100644 index 000000000..b02969d3a --- /dev/null +++ b/Source/src/Quadtree.cpp @@ -0,0 +1,169 @@ +#include "Quadtree.h" + +#include "GameObject.h" +#include "debugdraw.h" + +QuadtreeNode::QuadtreeNode(const AABB& box, QuadtreeNode* parent) + : box(box) + , parent(parent) +{ + childs[Quadrants::NW] = childs[Quadrants::NE] = childs[Quadrants::SE] = childs[Quadrants::SW] = nullptr; +} + +QuadtreeNode::~QuadtreeNode() +{ + RELEASE(childs[Quadrants::NW]); + RELEASE(childs[Quadrants::NE]); + RELEASE(childs[Quadrants::SE]); + RELEASE(childs[Quadrants::SW]); +} + +void QuadtreeNode::Insert(GameObject* game_object) +{ + // No split due to not enough objects + if (IsLeaf() && objects.size() < QUADTREE_MAX_ITEMS) + { + objects.push_back(game_object); + return; + } + // No split due to minimum size + if (box.HalfSize().LengthSq() <= QUADTREE_MIN_SIZE * QUADTREE_MIN_SIZE) + { + objects.push_back(game_object); + return; + } + // Rearrange childs + if (IsLeaf()) + CreateChildren(); + + objects.push_back(game_object); + RearangeChildren(); + +} + +void QuadtreeNode::Remove(GameObject* game_object) +{ + // TODO: Reduce size when no longer necessary? + std::list::iterator find_iter = std::find(objects.begin(), objects.end(), game_object); + if (find_iter != objects.end()) + objects.erase(find_iter); + + if (!IsLeaf()) { + for (int i = 0; i < Quadrants::NUM_QUADRANTS; ++i) + childs[i]->Remove(game_object); + } +} + +void QuadtreeNode::CreateChildren() +{ + // Subdivide current box + float3 size = float3(box.Size()); + float3 center = box.CenterPoint(); + // Do not operate y, 2d + float3 child_size = float3(0.5f * size.x, size.y, 0.5f * size.z); + // Recalculate for each child + float3 child_center(center); + AABB child_box; + + // NW + child_center.x = center.x - size.x * 0.25f; + child_center.z = center.z + size.z * 0.25f; + child_box.SetFromCenterAndSize(child_center, child_size); + childs[Quadrants::NW] = new QuadtreeNode(child_box, this); + + // NE + child_center.x = center.x + size.x * 0.25f; + child_center.z = center.z + size.z * 0.25f; + child_box.SetFromCenterAndSize(child_center, child_size); + childs[Quadrants::NE] = new QuadtreeNode(child_box, this); + + // SE + child_center.x = center.x + size.x * 0.25f; + child_center.z = center.z - size.z * 0.25f; + child_box.SetFromCenterAndSize(child_center, child_size); + childs[Quadrants::SE] = new QuadtreeNode(child_box, this); + + // SW + child_center.x = center.x - size.x * 0.25f; + child_center.z = center.z - size.z * 0.25f; + child_box.SetFromCenterAndSize(child_center, child_size); + childs[Quadrants::SW] = new QuadtreeNode(child_box, this); +} + +void QuadtreeNode::RearangeChildren() +{ + for (std::list::iterator it = objects.begin(); it != objects.end();) + { + GameObject* game_object = *it; + bool intersects[Quadrants::NUM_QUADRANTS]; + for (int i = 0; i < Quadrants::NUM_QUADRANTS; ++i) + intersects[i] = childs[i]->box.Intersects(game_object->GetAABB()); + + // If it intersects all there is no point in moving downwards + if (intersects[Quadrants::NW] && intersects[Quadrants::NE] && intersects[Quadrants::SE] && intersects[Quadrants::SW]) + { + ++it; + continue; + } + + for (int i = 0; i < Quadrants::NUM_QUADRANTS; ++i) { + if (intersects[i]) + childs[i]->Insert(game_object); + } + it = objects.erase(it); + } + auto a = 1; +} + +void QuadtreeNode::DebugDraw() +{ + static const int order[8] = {0, 1, 5, 4, 2, 3, 7, 6}; + if (IsLeaf()) + { + ddVec3 p[8]; + + for (int i = 0; i < 8; ++i) + p[i] = box.CornerPoint(order[i]); + + dd::box(p, dd::colors::LawnGreen); + return; + } + + for (QuadtreeNode* child : childs) + child->DebugDraw(); +} + +Quadtree::Quadtree() +{ +} + +Quadtree::~Quadtree() +{ + Clear(); +} + +void Quadtree::Clear() +{ + RELEASE(root); +} + +void Quadtree::SetBox(const AABB& box) +{ + Clear(); + root = new QuadtreeNode(box, nullptr); +} + +void Quadtree::Insert(GameObject* game_object) +{ + if (root) root->Insert(game_object); +} + +void Quadtree::Remove(GameObject* game_object) +{ + if (root) root->Remove(game_object); +} + +void Quadtree::DebugDraw() +{ + if (root) root->DebugDraw(); +} diff --git a/Source/src/Quadtree.h b/Source/src/Quadtree.h new file mode 100644 index 000000000..470e388ce --- /dev/null +++ b/Source/src/Quadtree.h @@ -0,0 +1,92 @@ +#pragma once + +#include "Globals.h" +#include "MathGeoLib.h" + +#include "GameObject.h" + +#include +#include + +#define QUADTREE_MAX_ITEMS 8 +#define QUADTREE_MIN_SIZE 10.0f + +enum Quadrants +{ + NW = 0, + NE, + SE, + SW, + NUM_QUADRANTS +}; + +class QuadtreeNode +{ +public: + QuadtreeNode(const AABB& box, QuadtreeNode* parent); + ~QuadtreeNode(); + + void Insert(GameObject* game_object); + void Remove(GameObject* game_object); + void CreateChildren(); + void RearangeChildren(); + + bool IsLeaf() const { return childs[0] == nullptr; } + + const AABB& GetBox() const { return box; } + const std::list& GetObjects() const { return objects; } + + template + void GetIntersections(std::map& objects, const T& primitive) const; + + QuadtreeNode* childs[Quadrants::NUM_QUADRANTS]; + + void DebugDraw(); + +private: + AABB box; + QuadtreeNode* parent; + std::list objects; + +}; + +class Quadtree +{ +public: + Quadtree(); + ~Quadtree(); + + void Clear(); + void SetBox(const AABB& box); + + void Insert(GameObject* game_object); + void Remove(GameObject* game_object); + + inline QuadtreeNode* GetRoot() { return root; } + + void DebugDraw(); + +private: + QuadtreeNode* root = nullptr; +}; + + +template +void QuadtreeNode::GetIntersections(std::map& intersected, const T& primitive) const +{ + if (primitive.Intersects(box)) + { + float near_hit, far_hit; + for (GameObject* object : objects) + { + if (primitive.Intersects(object->GetOBB())) + intersected[near_hit] = object; + } + + // If it has one child all exist + if (childs[0] != nullptr) { + for (int i = 0; i < 4; ++i) + childs[i]->GetIntersections(intersected, primitive); + } + } +} \ No newline at end of file diff --git a/Source/src/RenderList.cpp b/Source/src/RenderList.cpp new file mode 100644 index 000000000..1232e0cfa --- /dev/null +++ b/Source/src/RenderList.cpp @@ -0,0 +1,79 @@ +#include "RenderList.h" + +#include "GameObject.h" +#include "Components/ComponentCamera.h" +#include "Components/ComponentMesh.h" +#include "Quadtree.h" + +void RenderList::Update(ComponentCamera* camera, GameObject* game_object) +{ + nodes.clear(); + Frustum* frustum = camera->GetFrustum(); + float3 camera_pos = frustum->WorldMatrix().TranslatePart(); + CollectObjects(camera, camera_pos, game_object); +} + +void RenderList::Update(ComponentCamera* camera, QuadtreeNode* quadtree) +{ + nodes.clear(); + Frustum* frustum = camera->GetFrustum(); + float3 camera_pos = frustum->WorldMatrix().TranslatePart(); + CollectObjects(camera, camera_pos, quadtree); +} + +void RenderList::CollectObjects(ComponentCamera* camera, const float3& camera_pos, GameObject* game_object) +{ + bool inside = camera->GetFrustum()->Intersects(game_object->GetOBB()); + if (inside) { + + CollectMesh(camera_pos, game_object); + } + + for (GameObject* child : game_object->childs) + CollectObjects(camera, camera_pos, child); +} + +void RenderList::CollectObjects(ComponentCamera* camera, const float3& camera_pos, QuadtreeNode* quadtree) +{ + Frustum* frustum = camera->GetFrustum(); + bool quad_inside = frustum->Intersects(quadtree->GetBox()); + // Check if node intersects camera + if (quad_inside) { + // Check any gameobjects intersect + for (GameObject* game_object : quadtree->GetObjects()) { + bool object_inside = frustum->Intersects(game_object->GetOBB()); + + if (object_inside) { + CollectMesh(camera_pos, game_object); + } + } + // Call for all childs (What to do if it is duplicated when collecting)? + if (!quadtree->IsLeaf()) { + for (QuadtreeNode* child : quadtree->childs) + { + CollectObjects(camera, camera_pos, child); + } + } + } +} + +void RenderList::CollectMesh(const float3& camera_pos, GameObject* game_object) +{ + ComponentMesh* mesh = game_object->GetComponent(); + if (mesh && mesh->IsVisible()) + { + RenderTarget target; + target.name = game_object->name.c_str(); + target.game_object = game_object; + target.mesh = mesh; + target.distance = (game_object->GetOBB().CenterPoint() - camera_pos).LengthSq(); + + // Get first element which distance is not less than current target one + std::vector::iterator it = std::lower_bound(nodes.begin(), nodes.end(), target, + [](const RenderTarget& it_target, const RenderTarget& new_target) + { + return it_target.distance < new_target.distance; + }); + nodes.insert(it, target); + } +} diff --git a/Source/src/RenderList.h b/Source/src/RenderList.h new file mode 100644 index 000000000..a7cddeb52 --- /dev/null +++ b/Source/src/RenderList.h @@ -0,0 +1,36 @@ +#pragma once + +#include "MathGeoLib.h" + +#include + +class GameObject; +class ComponentMesh; +class ComponentCamera; +class QuadtreeNode; + +struct RenderTarget +{ + const char* name = nullptr; + GameObject* game_object = nullptr; + ComponentMesh* mesh = nullptr; + float distance = 0.0f; +}; + +class RenderList +{ +public: + void Update(ComponentCamera* camera, GameObject* game_object); + void Update(ComponentCamera* camera, QuadtreeNode* quadtree); + + std::vector& GetNodes() { return nodes; } + const std::vector& GetNodes() const { return nodes; } + +private: + void CollectObjects(ComponentCamera* camera, const float3& camera_pos, GameObject* game_object); + void CollectObjects(ComponentCamera* camera, const float3& camera_pos, QuadtreeNode* quadtree); + void CollectMesh(const float3& camera_pos, GameObject* game_object); + + std::vector nodes; + +}; diff --git a/Source/src/Resources/Resource.cpp b/Source/src/Resources/Resource.cpp new file mode 100644 index 000000000..ecbb46871 --- /dev/null +++ b/Source/src/Resources/Resource.cpp @@ -0,0 +1,11 @@ +#include "Resource.h" + +Resource::Resource(UID id, ResourceType type) + : id(id) + , type(type) +{ +} + +Resource::~Resource() +{ +} diff --git a/Source/src/Resources/Resource.h b/Source/src/Resources/Resource.h new file mode 100644 index 000000000..5d28fd1ff --- /dev/null +++ b/Source/src/Resources/Resource.h @@ -0,0 +1,35 @@ +#pragma once + +#include "../Utils/UID.h" + + +enum class ResourceType +{ + UNKNOWN, + MATERIAL, + TEXTURE, + MODEL, + MESH, + SCENE, + SHADER +}; + +class Resource +{ +public: + Resource(UID id, ResourceType type); + virtual ~Resource(); + + ResourceType GetType() const + { + return type; + } + + UID GetID() const { return id; } + + unsigned int instances = 0; + +private: + UID id = 0; + ResourceType type = ResourceType::UNKNOWN; +}; diff --git a/Source/src/Resources/ResourceMaterial.cpp b/Source/src/Resources/ResourceMaterial.cpp new file mode 100644 index 000000000..64544c78a --- /dev/null +++ b/Source/src/Resources/ResourceMaterial.cpp @@ -0,0 +1,13 @@ +#include "ResourceMaterial.h" +#include "../Application.h" + +ResourceMaterial::ResourceMaterial(UID uid) + : Resource(uid, ResourceType::MATERIAL) +{ +} + +ResourceMaterial::~ResourceMaterial() +{ + App->texture->Unload(diffuse); + App->texture->Unload(specular); +} \ No newline at end of file diff --git a/Source/src/Resources/ResourceMaterial.h b/Source/src/Resources/ResourceMaterial.h new file mode 100644 index 000000000..50cc4595f --- /dev/null +++ b/Source/src/Resources/ResourceMaterial.h @@ -0,0 +1,23 @@ +#pragma once +#include "Resource.h" + +#include "../Modules/ModuleTexture.h" +#include "MathGeoLib.h" +#include "assimp/scene.h" + +class ResourceMaterial : public Resource +{ +public: + ResourceMaterial(UID uid); + ~ResourceMaterial() override; + + inline unsigned GetDiffuseId() const { return diffuse.id; } + inline unsigned GetSpecularId() const { return specular.id; } + + + Texture diffuse; + Texture specular; + float4 diffuse_color = float4::one; + float4 specular_color = float4::one / 10.0f; + float shininess = 50.0f; +}; diff --git a/Source/src/Resources/ResourceMesh.cpp b/Source/src/Resources/ResourceMesh.cpp new file mode 100644 index 000000000..59390071e --- /dev/null +++ b/Source/src/Resources/ResourceMesh.cpp @@ -0,0 +1,70 @@ +#include "ResourceMesh.h" + +#include "glew.h" + + +ResourceMesh::ResourceMesh(UID id) + : Resource(id, ResourceType::MESH) +{ +} + +void ResourceMesh::GenerateBuffers() +{ + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + + // Positions (3 values per coord) + glGenBuffers(1, &buffer_ids[Buffers::b_vertices]); + glBindBuffer(GL_ARRAY_BUFFER, buffer_ids[Buffers::b_vertices]); + glBufferData(GL_ARRAY_BUFFER, buffer_sizes[Buffers::b_vertices] * sizeof(float), vertices, GL_STATIC_DRAW); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*) 0); + glEnableVertexAttribArray(0); + + // Normals (3 values per coord) + glGenBuffers(1, &buffer_ids[Buffers::b_normals]); + glBindBuffer(GL_ARRAY_BUFFER, buffer_ids[Buffers::b_normals]); + glBufferData(GL_ARRAY_BUFFER, buffer_sizes[Buffers::b_normals] * sizeof(float), normals, GL_STATIC_DRAW); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*) 0); + glEnableVertexAttribArray(1); + + // Texture Coords (2 values per coord) + glGenBuffers(1, &buffer_ids[Buffers::b_tex_coords]); + glBindBuffer(GL_ARRAY_BUFFER, buffer_ids[Buffers::b_tex_coords]); + glBufferData(GL_ARRAY_BUFFER, buffer_sizes[Buffers::b_tex_coords] * sizeof(float), tex_coords, GL_STATIC_DRAW); + glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*) 0); + glEnableVertexAttribArray(2); + + // Indices (1 value) + glGenBuffers(1, &buffer_ids[Buffers::b_indices]); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer_ids[Buffers::b_indices]); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, buffer_sizes[Buffers::b_indices] * sizeof(unsigned), indices, GL_STATIC_DRAW); + + glBindVertexArray(0); +} + +void ResourceMesh::GenerateAABB() +{ + bounding_box.SetFrom((float3*) vertices, buffer_sizes[Buffers::b_vertices] / 3); +} + +void ResourceMesh::CleanUp() +{ + if (loaded) + { + glDeleteBuffers(1, &buffer_ids[Buffers::b_indices]); + glDeleteBuffers(1, &buffer_ids[Buffers::b_vertices]); + glDeleteBuffers(1, &buffer_ids[Buffers::b_normals]); + glDeleteBuffers(1, &buffer_ids[Buffers::b_tex_coords]); + + delete[] indices; + delete[] vertices; + delete[] normals; + delete[] tex_coords; + + buffer_sizes[Buffers::b_indices] = 0; + buffer_sizes[Buffers::b_vertices] = 0; + buffer_sizes[Buffers::b_normals] = 0; + buffer_sizes[Buffers::b_tex_coords] = 0; + } + loaded = false; +} diff --git a/Source/src/Resources/ResourceMesh.h b/Source/src/Resources/ResourceMesh.h new file mode 100644 index 000000000..47ecb33b4 --- /dev/null +++ b/Source/src/Resources/ResourceMesh.h @@ -0,0 +1,43 @@ +#pragma once +#include "Resource.h" + +#include "MathGeoLib.h" + + +class ResourceMesh : public Resource +{ +public: + + enum Buffers + { + b_indices = 0, + b_vertices, + b_normals, + b_tex_coords, + n_buffers, + }; + + ResourceMesh(UID id); + void GenerateBuffers(); + void GenerateAABB(); + + void CleanUp(); + + bool loaded = false; + + AABB bounding_box; + + // Store the ids of buffers in opengl + unsigned vbo, ebo; + unsigned vao; + unsigned buffer_ids[n_buffers]; + + // Track buffer sizes + unsigned buffer_sizes[n_buffers]; + + // In memory buffers + unsigned* indices; + float* vertices; + float* normals; + float* tex_coords; +}; diff --git a/Source/src/Scene.cpp b/Source/src/Scene.cpp new file mode 100644 index 000000000..1ae9ebd18 --- /dev/null +++ b/Source/src/Scene.cpp @@ -0,0 +1,232 @@ +#include "Scene.h" + +#include "Globals.h" + +#include "Utils/Logger.h" + +#include "Application.h" +#include "Modules/ModuleDebugDraw.h" + +#include "GameObject.h" +#include "Components/Component.h" +#include "Components/ComponentTransform.h" +#include "Components/ComponentCamera.h" +#include "Components/ComponentMesh.h" +#include "Components/ComponentMaterial.h" +#include "Components/ComponentPointLight.h" +#include "Components/ComponentSpotLight.h" + +#include "Modules/ModuleTexture.h" +#include "Modules/ModuleProgram.h" +#include "Modules/ModuleEditor.h" +#include "Modules/ModuleCamera.h" + +#include "Resources/Resource.h" +#include "Importers/SceneImporter.h" + +#include "Skybox.h" +#include "Quadtree.h" + +#include "assimp/cimport.h" +#include "assimp/postprocess.h" +#include "assimp/Importer.hpp" + +#include + +Scene::Scene() +{ + quadtree = new Quadtree(); + skybox = new Skybox(); + + quadtree->SetBox(AABB(float3(-500, 0, -500), float3(500, 250, 500))); + root = new GameObject(nullptr, float4x4::identity, "Root"); + culling_camera = App->camera->GetMainCamera(); +} + +Scene::~Scene() +{ + CleanScene(); +} + +void Scene::CleanScene() +{ + App->editor->SetSelectedGO(nullptr); + delete root; + delete skybox; + delete quadtree; +} + +void Scene::DestroyGameObject(GameObject* game_object) +{ + if (App->editor->GetSelectedGO() == game_object) + App->editor->SetSelectedGO(nullptr); + quadtree->Remove(game_object); +} + +void Scene::AddGameObject(GameObject* new_object, GameObject* parent) +{ + GameObject* new_parent = parent ? parent : root; + new_parent->childs.push_back(new_object); + quadtree->Insert(new_object); +} + +GameObject* Scene::CreateNewGameObject(const char* name, GameObject* parent) +{ + // It will insert itself into quadtree on first bounding box update + GameObject* foo = new GameObject(parent ? parent : root, name); + foo->scene_owner = this; + return foo; +} + +GameObject* Scene::LoadFBX(const std::string& path) +{ + // TODO: Set name from filename + auto model_path = path.substr(0, path.find_last_of("/\\") + 1); + auto file_name = path.substr(path.find_last_of("/\\") + 1); + auto name = file_name.substr(0, std::string::size_type(file_name.find_last_of('.'))); + + GameObject* model = nullptr; + Assimp::Importer importer; + const aiScene* scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_GlobalScale); + if (scene) + { + //std::vector materials = LoadMaterials(scene, model_path, name); + model = CreateNewGameObject(name.c_str(), root); + LoadNode(scene, scene->mRootNode, model, model_path); + //materials.clear(); + } + else + { + LOG("Error loading file %s: %s", model_path.c_str(), aiGetErrorString()); + } + importer.FreeScene(); + return model; +} + +void Scene::Save(JsonFormaterValue j_scene) const +{ + // GameObjects + JsonFormaterValue j_root = j_scene["GORoot"]; + root->Save(j_root); + + //TODO: Save camera position +} + +void Scene::Load(JsonFormaterValue j_scene) +{ + //CleanScene(); + delete root; + + // Load GameObjects + JsonFormaterValue j_root = j_scene["GORoot"]; + root = new GameObject(nullptr, "Root", j_root["Uid"]); + root->scene_owner = this; + root->Load(j_root); +} + +GameObject* Scene::RayCast(const LineSegment& segment) const +{ + GameObject* selected = nullptr; + float closest_hit_distance = inf; + + map game_objects; + quadtree->GetRoot()->GetIntersections(game_objects, segment); + + for (auto game_object_pair: game_objects) + { + GameObject* game_object = game_object_pair.second; + ComponentMesh* mesh = game_object->GetComponent(); + if (mesh) + { + // Transform ray to mesh space, more efficient + LineSegment local_segment(segment); + local_segment.Transform(game_object->GetComponent()->GetTransform().Inverted()); + + const float* vertices = mesh->GetVertices(); + const unsigned* indices = mesh->GetIndices(); + for (unsigned i = 0; i < mesh->GetBufferSize(ResourceMesh::Buffers::b_indices); i += 3) + { + Triangle triangle; + triangle.a = vec(&vertices[indices[i] * 3]); + triangle.b = vec(&vertices[indices[i + 1] * 3]); + triangle.c = vec(&vertices[indices[i + 2] * 3]); + + float hit_distance; + float3 hit_point; + if (local_segment.Intersects(triangle, &hit_distance, &hit_point)) + { + if (hit_distance < closest_hit_distance) + { + closest_hit_distance = hit_distance; + selected = game_object; + } + } + + } + } + } + return selected; +} + +void Scene::CreateLights() +{ + GameObject* sun = CreateNewGameObject("Sun", root); + sun->GetComponent()->SetLocalPosition(float3(1, 1, -1)); + sun->GetComponent()->LookAt(float3(0, 0, 0)); + sun->CreateComponent(Component::Type::DIRLIGHT); + + GameObject* spot = CreateNewGameObject("Spot Light", root); + spot->GetComponent()->SetLocalPosition(float3(-1, 1, -1)); + + spot->CreateComponent(Component::Type::SPOTLIGHT); + + GameObject* point = CreateNewGameObject("Point Light", root); + point->GetComponent()->SetLocalPosition(float3(0, 1, -1)); + point->CreateComponent(Component::Type::POINTLIGHT); +} + +void Scene::LoadNode(const aiScene* scene, const aiNode* node, GameObject* parent, const std::string& model_path) +{ + for (unsigned int i = 0; i < node->mNumMeshes; i++) + { + aiMesh* mesh = scene->mMeshes[node->mMeshes[i]]; + GameObject* model_part = CreateNewGameObject(node->mName.C_Str(), parent); + model_part->CreateComponent(Component::Type::MESH); + model_part->GetComponent()->Import(mesh); + model_part->CreateComponent(Component::Type::MATERIAL); + model_part->GetComponent()->Import(scene->mMaterials[mesh->mMaterialIndex], model_path, node->mName.C_Str()); + + aiVector3D aiTranslation, aiScale; + aiQuaternion aiRotation; + node->mTransformation.Decompose(aiScale, aiRotation, aiTranslation); + model_part->GetComponent()->SetLocalTransform( + float3(aiTranslation.x, aiTranslation.y, aiTranslation.z), + Quat(aiRotation.x, aiRotation.y, aiRotation.z, aiRotation.w), + float3(aiScale.x, aiScale.y, aiScale.z)); + } + + // then do the same for each of its children + for (unsigned int i = 0; i < node->mNumChildren; i++) + { + LoadNode(scene, node->mChildren[i], parent, model_path); + } +} + +GameObject* Scene::CreateDebugCamera() +{ + GameObject* camera = CreateNewGameObject("Debug Camera", root); + camera->GetComponent()->SetLocalPosition(float3(5, 5, 0)); + camera->CreateComponent(Component::Type::CAMERA); + camera->GetComponent()->LookAt(float3(0, 5, 0)); + + ComponentCamera* debug_camera = camera->GetComponent(); + debug_camera->SetFarPlane(100.0f); + debug_camera->draw_frustum = true; + + return camera; +} + +void Scene::Update() +{ + root->Update(); +} \ No newline at end of file diff --git a/Source/src/Scene.h b/Source/src/Scene.h new file mode 100644 index 000000000..603d511a2 --- /dev/null +++ b/Source/src/Scene.h @@ -0,0 +1,78 @@ +#pragma once + +#include "Geometry/LineSegment.h" +#include "Modules/ModuleTexture.h" +#include "Components/Component.h" +#include "Resources/ResourceMaterial.h" + +#include "assimp/scene.h" + +#include +#include + +class ModuleSceneManager; +class GameObject; +class ComponentTransform; +class ComponentCamera; +class ComponentDirLight; +class ComponentPointLight; +class ComponentSpotLight; +class Skybox; +class Quadtree; + +class Scene +{ + friend class ModuleSceneManager; + +public: + Scene(); + ~Scene(); + + void CleanScene(); + + // --- Life cycle Scene --- // + void Update(); + void Save(JsonFormaterValue j_scene) const; + void Load(JsonFormaterValue j_scene); + + // --- GameObject Management --- // + void AddGameObject(GameObject* new_object, GameObject* parent = nullptr); + void DestroyGameObject(GameObject* game_object); + GameObject* CreateNewGameObject(const char* name, GameObject* parent = nullptr); + GameObject* GetRoot() const { return root; } + ComponentCamera* GetCullingCamera() const { return culling_camera; } + void SetCullingCamera(ComponentCamera* v) { culling_camera =v; } + + // --- Quadtree --- // + Quadtree* GetQuadtree() const { return quadtree; } + + // --- Debug --- // + GameObject* CreateDebugCamera(); + + // --- Skybox --- // + Skybox* GetSkybox() const { return skybox; } + + GameObject* RayCast(const LineSegment& segment) const; + + void CreateLights(); + std::vector dir_lights; + std::vector point_lights; + std::vector spot_lights; + + // --- Importer --- // TODO: Move to importer + GameObject* LoadFBX(const std::string& path); + +private: + // TODO: Fix leaks from resource material pointers when doing import system + void LoadNode(const aiScene* scene, const aiNode* node, GameObject* parent, const std::string& model_path); + //std::vector LoadMaterials(const aiScene* scene, const std::string& model_path, const std::string& model_name); + +private: + GameObject* root = nullptr; + ComponentCamera* culling_camera = nullptr; + + Skybox* skybox; + Quadtree* quadtree = nullptr; + + bool draw_all_bounding_boxes = false; +}; diff --git a/Source/src/Skybox.cpp b/Source/src/Skybox.cpp new file mode 100644 index 000000000..4be7e2296 --- /dev/null +++ b/Source/src/Skybox.cpp @@ -0,0 +1,98 @@ +#include "Skybox.h" + +#include "Application.h" +#include "Program.h" + +#include "Modules/ModuleProgram.h" +#include "Components/ComponentCamera.h" + + +Skybox::Skybox() +{ + const char* paths[6] = { + "Assets/Skybox/right.jpg", + "Assets/Skybox/left.jpg", + "Assets/Skybox/top.jpg", + "Assets/Skybox/bottom.jpg", + "Assets/Skybox/front.jpg", + "Assets/Skybox/back.jpg" + }; + texture = App->texture->LoadCubeMap(paths); + + float vertices[] = { + // positions + -1.0f, 1.0f, -1.0f, + -1.0f, -1.0f, -1.0f, + 1.0f, -1.0f, -1.0f, + 1.0f, -1.0f, -1.0f, + 1.0f, 1.0f, -1.0f, + -1.0f, 1.0f, -1.0f, + + -1.0f, -1.0f, 1.0f, + -1.0f, -1.0f, -1.0f, + -1.0f, 1.0f, -1.0f, + -1.0f, 1.0f, -1.0f, + -1.0f, 1.0f, 1.0f, + -1.0f, -1.0f, 1.0f, + + 1.0f, -1.0f, -1.0f, + 1.0f, -1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, -1.0f, + 1.0f, -1.0f, -1.0f, + + -1.0f, -1.0f, 1.0f, + -1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 1.0f, + -1.0f, -1.0f, 1.0f, + + -1.0f, 1.0f, -1.0f, + 1.0f, 1.0f, -1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, -1.0f, + + -1.0f, -1.0f, -1.0f, + -1.0f, -1.0f, 1.0f, + 1.0f, -1.0f, -1.0f, + 1.0f, -1.0f, -1.0f, + -1.0f, -1.0f, 1.0f, + 1.0f, -1.0f, 1.0f + }; + + glGenVertexArrays(1, &vao); + glGenBuffers(1, &vbo); + glBindVertexArray(vao); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), &vertices, GL_STATIC_DRAW); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*) 0); +} + +Skybox::~Skybox() +{ + glDeleteBuffers(1, &vbo); + glDeleteTextures(1, &texture.id); + texture.loaded = false; +} + +void Skybox::Draw(ComponentCamera* camera) +{ + // Use for optimized version (draw at the end) glDepthFunc(GL_LEQUAL); + + glDepthFunc(GL_ALWAYS); + Program* program = App->program->GetSkyboxProgram(); + program->Activate(); + // Draw skybox + glBindVertexArray(vao); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_CUBE_MAP, texture.id); + glDrawArrays(GL_TRIANGLES, 0, 36); + glBindVertexArray(0); + program->Deactivate(); + glDepthFunc(GL_LESS); +} diff --git a/Source/src/Skybox.h b/Source/src/Skybox.h new file mode 100644 index 000000000..a1325dc7b --- /dev/null +++ b/Source/src/Skybox.h @@ -0,0 +1,20 @@ +#pragma once + +#include "Modules/ModuleTexture.h" + +#include "MathGeoLib.h" + +class ComponentCamera; + +class Skybox +{ +public: + Skybox(); + ~Skybox(); + void Draw(ComponentCamera* camera); + +private: + TextureCube texture; + unsigned vao; + unsigned vbo; +}; diff --git a/Source/src/UI/ImGuiUtils.h b/Source/src/UI/ImGuiUtils.h new file mode 100644 index 000000000..9623f9330 --- /dev/null +++ b/Source/src/UI/ImGuiUtils.h @@ -0,0 +1,20 @@ +#pragma once + +#include "imgui.h" +#include "imgui_internal.h" + +class GameObject; +class Component; + +namespace ImGuiUtils +{ + bool IconButton(const char* icon, const char* tooltip); + + void VSplitter(const char* str_id, ImVec2* size); + + void Rect(float w, float h, ImU32 color); + + bool CompactColorPicker(const char* name, float* color); + + bool CollapsingHeader(GameObject* game_object, Component* component, const char* header_name); +}; diff --git a/Source/src/UI/ImguiUtils.cpp b/Source/src/UI/ImguiUtils.cpp new file mode 100644 index 000000000..773bae53e --- /dev/null +++ b/Source/src/UI/ImguiUtils.cpp @@ -0,0 +1,77 @@ +#include "ImGuiUtils.h" +#include + +#include "../GameObject.h" +#include "../Components/Component.h" + +using namespace ImGui; +using namespace std; + +bool ImGuiUtils::IconButton(const char* icon, const char* tooltip) +{ + PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + PushStyleColor(ImGuiCol_Button, GetStyle().Colors[ImGuiCol_WindowBg]); + bool res = SmallButton(icon); + if (IsItemHovered()) + { + SetTooltip("%s", tooltip); + } + PopStyleColor(); + PopStyleVar(); + return res; +} + +void ImGuiUtils::VSplitter(const char* str_id, ImVec2* size) +{ + ImVec2 screen_pos = GetCursorScreenPos(); + InvisibleButton(str_id, ImVec2(3, -1)); + ImVec2 end_pos = screen_pos + GetItemRectSize(); + ImGuiWindow* win = GetCurrentWindow(); + ImVec4* colors = GetStyle().Colors; + ImU32 color = GetColorU32(ImGui::IsItemActive() || IsItemHovered() ? colors[ImGuiCol_ButtonActive] : colors[ImGuiCol_Button]); + win->DrawList->AddRectFilled(screen_pos, end_pos, color); + if (IsItemActive()) + { + size->x = ImMax(1.0f, GetIO().MouseDelta.x + size->x); + } +} + +void ImGuiUtils::Rect(float w, float h, ImU32 color) +{ + ImGuiWindow* win = GetCurrentWindow(); + ImVec2 screen_pos = GetCursorScreenPos(); + ImVec2 end_pos = screen_pos + ImVec2(w, h); + ImRect total_bb(screen_pos, end_pos); + ItemSize(total_bb); + if (!ItemAdd(total_bb, 0)) return; + win->DrawList->AddRectFilled(screen_pos, end_pos, color); +} + +bool ImGuiUtils::CompactColorPicker(const char* name, float* color) +{ + constexpr ImGuiColorEditFlags flags = ImGuiColorEditFlags_DisplayRGB |ImGuiColorEditFlags_NoSidePreview | ImGuiColorEditFlags_NoLabel; + PushItemWidth(150.0f); + bool ret = ColorPicker4(name, color, flags); + PopItemWidth(); + return ret; +} + +bool ImGuiUtils::CollapsingHeader(GameObject* game_object, Component* component, const char* header_name) +{ + bool open = ImGui::CollapsingHeader(header_name, ImGuiTreeNodeFlags_AllowItemOverlap | ImGuiTreeNodeFlags_DefaultOpen); + + ImGui::SameLine(); + if (ImGui::GetWindowWidth() > 170) ImGui::Indent(ImGui::GetWindowWidth() - 43); + if (ImGui::Button(std::string(ICON_FA_TIMES).c_str())) ImGui::OpenPopup(header_name); + if (ImGui::GetWindowWidth() > 170) ImGui::Unindent(ImGui::GetWindowWidth() - 43); + + if (ImGui::BeginPopup(header_name)) + { + game_object->RemoveComponent(component); + ImGui::EndPopup(); + } + + return open; +} + + diff --git a/Source/src/UI/Window.cpp b/Source/src/UI/Window.cpp new file mode 100644 index 000000000..1610408d4 --- /dev/null +++ b/Source/src/UI/Window.cpp @@ -0,0 +1,10 @@ +#include "Window.h" + +Window::Window(const char* name, bool active) + : name(name), active(active) +{ +} + +Window::~Window() +{ +} diff --git a/Source/src/UI/Window.h b/Source/src/UI/Window.h new file mode 100644 index 000000000..8e4bb48aa --- /dev/null +++ b/Source/src/UI/Window.h @@ -0,0 +1,19 @@ +#pragma once +#include +using namespace std; + +class Window +{ +public: + Window(const char* name, bool active); + virtual ~Window(); + + // --- Life cycle --- // + virtual void Init() {}; + virtual void Update() {}; + virtual void CleanUp() {}; + +public: + const char* name; + bool active = true; +}; diff --git a/Source/src/UI/WindowAbout.cpp b/Source/src/UI/WindowAbout.cpp new file mode 100644 index 000000000..17c0b741f --- /dev/null +++ b/Source/src/UI/WindowAbout.cpp @@ -0,0 +1,50 @@ +#include "WindowAbout.h" + +#include "../Application.h" +#include "../Modules/ModuleHardware.h" + +#include + +#include "imgui.h" +#include "../Globals.h" +#include + +WindowAbout::WindowAbout() + : Window("About", false) +{ +} + +WindowAbout::~WindowAbout() +{ +} + +void WindowAbout::Update() +{ + ImGui::SetNextWindowSize(ImVec2(400.0f, 200.0f), ImGuiCond_FirstUseEver); + if (ImGui::Begin(name, &active, ImGuiWindowFlags_AlwaysAutoResize)) + { + ImGui::Text("Engine name: %s", TITLE); + ImGui::Text("Version: %s", ENGINE_VERSION); + ImGui::Separator(); + ImGui::Text("Eric Torres Perramon & Bernat UWU"); + ImGui::Text("Master in Advanced Programming for AAA Video Games UPC."); + ImGui::Text("Engine licensed under the MIT License."); + ImGui::Separator(); + + ImVec2 child_size = ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 6); + + ImGui::Text("SDL Version: %d.%d.%d", App->hw->GetInfo().sdl_version.major, App->hw->GetInfo().sdl_version.minor, App->hw->GetInfo().sdl_version.patch); + ImGui::Separator(); + ImGui::Text("CPUs: %d", App->hw->GetInfo().n_cpu); + ImGui::Text("System RAM: %.1f Gb", App->hw->GetInfo().ram_gb); + ImGui::Separator(); + ImGui::Text("GPU: %s", App->hw->GetInfo().gpu); + ImGui::Text("Brand: %s", App->hw->GetInfo().gpu_brand); + ImGui::Text("VRAM Budget: %.1f Mb", (float) App->hw->GetInfo().vram_mb_budget); + ImGui::Text("Vram Avaliable: %.1f Mb", (float) App->hw->GetInfo().vram_mb_free); + ImGui::Text("Vram Usage: %.1f Mb", (float) (App->hw->GetInfo().vram_mb_budget - App->hw->GetInfo().vram_mb_free)); + ImGui::Separator(); + ImGui::Text("DevIL lib version %d", IL_VERSION); + } + ImGui::End(); +} diff --git a/Source/src/UI/WindowAbout.h b/Source/src/UI/WindowAbout.h new file mode 100644 index 000000000..f9c3fcbb9 --- /dev/null +++ b/Source/src/UI/WindowAbout.h @@ -0,0 +1,12 @@ +#pragma once + +#include "Window.h" + +class WindowAbout : public Window +{ +public: + WindowAbout(); + + ~WindowAbout() override; + void Update() override; +}; diff --git a/Source/src/UI/WindowConfiguration.cpp b/Source/src/UI/WindowConfiguration.cpp new file mode 100644 index 000000000..64ed20e4f --- /dev/null +++ b/Source/src/UI/WindowConfiguration.cpp @@ -0,0 +1,46 @@ +#include "WindowConfiguration.h" + +#include "../Application.h" +#include "../Scene.h" +#include "../Modules/ModuleEditor.h" +#include "../Modules/ModuleSceneManager.h" +#include "../Modules/ModuleProgram.h" +#include "../Modules/ModuleCamera.h" +#include "../Components/ComponentCamera.h" + +#include +#include + +WindowConfiguration::WindowConfiguration() + : Window("Configuration", true) +{ +} + +WindowConfiguration::~WindowConfiguration() +{ +} + +void WindowConfiguration::Update() +{ + ImGui::SetNextWindowDockID(App->editor->dock_right_id, ImGuiCond_FirstUseEver); + if (ImGui::Begin((std::string(ICON_FA_COG " ") + name).c_str(), &active)) + { + if (ImGui::CollapsingHeader("Scene")) + { + App->renderer->OptionsMenu(); + ImGui::Separator(); + ImGui::Text("Shader Options"); + App->program->OptionsMenu(); + } + + //It is already a collapsing header + App->camera->GetMainCamera()->DrawGui(); + + if (ImGui::CollapsingHeader("Engine")) + { + App->window->OptionsMenu(); + App->renderer->PerformanceMenu(); + } + } + ImGui::End(); +} diff --git a/Source/src/UI/WindowConfiguration.h b/Source/src/UI/WindowConfiguration.h new file mode 100644 index 000000000..442619366 --- /dev/null +++ b/Source/src/UI/WindowConfiguration.h @@ -0,0 +1,11 @@ +#pragma once +#include "Window.h" + +class WindowConfiguration : public Window +{ +public: + WindowConfiguration(); + + ~WindowConfiguration() override; + void Update() override; +}; diff --git a/Source/ImGuiComponents/AppLog.h b/Source/src/UI/WindowConsole.cpp similarity index 54% rename from Source/ImGuiComponents/AppLog.h rename to Source/src/UI/WindowConsole.cpp index b93e6cbec..c4569ae03 100644 --- a/Source/ImGuiComponents/AppLog.h +++ b/Source/src/UI/WindowConsole.cpp @@ -1,56 +1,28 @@ -#pragma once +#include "WindowConsole.h" -#include +#include "../Application.h" -#include -#include -#include +#include "../Modules/ModuleEditor.h" +#include -class AppLog -{ -public: - void Clear() { - buff.clear(); - line_offsets.clear(); - line_offsets.push_back(0); - } +#include "../Utils/Logger.h" - // Apply printf-style warnings to our formatting functions. - void AddLog(const char file[], int line, const char* format, ...) IM_FMTARGS(2) - { - static char tmp_string[4096]; - static char tmp_string2[4096]; - static char tmp_string3[4096]; - - int old_size = buff.size(); - va_list args; - va_start(args, format); - vsprintf_s(tmp_string, 4096, format, args); - va_end(args); - sprintf_s(tmp_string3, 4096, "\n%s", tmp_string); - sprintf_s(tmp_string2, 4096, "\n%s(%d) : %s", file, line, tmp_string); - - buff.append(tmp_string3); - - for (int new_size = buff.size(); old_size < new_size; old_size++) - if (buff[old_size] == '\n') - line_offsets.push_back(old_size + 1); - OutputDebugString(tmp_string2); - } +#include "imgui.h" - AppLog() { - autoscroll = true; - Clear(); - } - ~AppLog() { - Clear(); - } +WindowConsole::WindowConsole() + : Window("Console", true) +{} - void Draw() { - ImGui::SetNextWindowSize(ImVec2(1100, 170), ImGuiCond_FirstUseEver); +WindowConsole::~WindowConsole() +{ +} - ImGui::Begin("Console", &show_another_window); +void WindowConsole::Update() +{ + ImGui::SetNextWindowDockID(App->editor->dock_down_id, ImGuiCond_FirstUseEver); + if (ImGui::Begin((std::string(ICON_FA_TERMINAL " ") + name).c_str(), &active)) + { if (ImGui::BeginPopup("Options")) { ImGui::Checkbox("Auto-scroll", &autoscroll); @@ -69,7 +41,7 @@ class AppLog ImGui::Separator(); ImGui::BeginChild("scrolling", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar); - + if (clear) Clear(); if (copy) @@ -104,29 +76,26 @@ class AppLog } clipper.End(); } + ImGui::PopStyleVar(); if (autoscroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) ImGui::SetScrollHereY(1.0f); ImGui::EndChild(); - ImGui::End(); - } - - void setShowConsole(bool s) { - show_another_window = s; } + ImGui::End(); +} - bool getShowConsole() { - return show_another_window; - } - -private: - ImGuiTextBuffer buff; - ImGuiTextFilter filter; - ImVector line_offsets; - bool autoscroll; - bool show_another_window = false; -}; +void WindowConsole::Clear() +{ + buff.clear(); + line_offsets.clear(); + line_offsets.push_back(0); +} -extern AppLog* Logger; \ No newline at end of file +void WindowConsole::CleanUp() +{ + Clear(); + filter.Clear(); +} diff --git a/Source/src/UI/WindowConsole.h b/Source/src/UI/WindowConsole.h new file mode 100644 index 000000000..af2a0b566 --- /dev/null +++ b/Source/src/UI/WindowConsole.h @@ -0,0 +1,20 @@ +#pragma once +#include "Window.h" + +#include + + +class WindowConsole : public Window +{ +public: + WindowConsole(); + ~WindowConsole() override; + void Update() override; + void Clear(); + + void CleanUp() override; + +private: + ImGuiTextFilter filter = nullptr; + bool autoscroll = true; +}; diff --git a/Source/src/UI/WindowHierarchy.cpp b/Source/src/UI/WindowHierarchy.cpp new file mode 100644 index 000000000..a1347c3ae --- /dev/null +++ b/Source/src/UI/WindowHierarchy.cpp @@ -0,0 +1,128 @@ +#include "WindowHierarchy.h" +#include +#include + +#include "../Modules/ModuleSceneManager.h" +#include "../Modules/ModuleInput.h" +#include "../Modules/ModuleEditor.h" + +#include "../Application.h" +#include "../GameObject.h" +#include "../Scene.h" + +#include "../Utils/Logger.h" + +WindowHierarchy::WindowHierarchy() + : Window("Hierarchy", true) +{ +} + +WindowHierarchy::~WindowHierarchy() +{ +} + +void WindowHierarchy::Update() +{ + ImGui::SetNextWindowDockID(App->editor->dock_left_id, ImGuiCond_FirstUseEver); + if (ImGui::Begin((std::string(ICON_FA_SITEMAP " ") + name).c_str(), &active)) + { + DrawHierarchyTree(App->scene_manager->GetRoot()); + } + ImGui::End(); +} + +void WindowHierarchy::CleanUp() +{ + dragged_object = nullptr; + delete dragged_object; +} + +void WindowHierarchy::DrawHierarchyTree(GameObject* root) +{ + DrawGOChilds(root); + if (!App->input->GetMouseButton(SDL_BUTTON_LEFT)) + dragged_object = nullptr; +} + +void WindowHierarchy::DrawGOChilds(GameObject* root) +{ + for (GameObject* game_object : root->childs) + { + ImGui::PushID(game_object); + DrawGameObject(game_object); + ImGui::PopID(); + } +} + +void WindowHierarchy::DrawGameObject(GameObject* game_object) +{ + ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow; + flags |= ImGuiTreeNodeFlags_DefaultOpen; + + if (game_object->childs.empty()) + flags |= ImGuiTreeNodeFlags_Leaf; + + if (game_object == App->editor->GetSelectedGO()) + flags |= ImGuiTreeNodeFlags_Selected; + + ImVec4 node_color(1.0f, 1.0f, 1.0f, 1.0f); + if (!game_object->IsActive()) + node_color = ImVec4(1.f, 0.f, 0.f, 1.f); + + ImGui::PushStyleColor(ImGuiCol_Text, node_color); + + bool node_open = ImGui::TreeNodeEx(game_object, flags, game_object->name.c_str()); + + if (ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly)) + { + if (dragged_object && dragged_object != game_object) + { + ImGui::BeginTooltip(); + ImGui::Text("% s->% s ", dragged_object->name.c_str(), game_object->name.c_str()); + ImGui::EndTooltip(); + } + + if (ImGui::IsMouseClicked(0)) + { + App->editor->SetSelectedGO(game_object); + dragged_object = game_object; + } + if (ImGui::IsMouseClicked(1)) + { + ImGui::OpenPopup(game_object->name.c_str()); + } + + if (dragged_object && ImGui::IsMouseReleased(0) && dragged_object != game_object) + { + dragged_object->SetNewParent(game_object); + dragged_object = nullptr; + } + } + + if (!game_object->childs.empty()) + DrawGOChilds(game_object); + + // TODO: Make robust to repeted game object names + if (ImGui::BeginPopup(game_object->name.c_str())) + { + // Alternativs: ImGui::Selectable, ImGuiHelper::ValueSelection + // TODO: Open options to create/destroy new object or move up down in the list of childs + if (ImGui::MenuItem("Add Gameojbect")) + { + App->scene_manager->GetActiveScene()->CreateNewGameObject("Test", game_object); + ImGui::CloseCurrentPopup(); + } + if (ImGui::MenuItem("Delete Gameojbect")) + { + RELEASE(game_object); + dragged_object = nullptr; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + + if (node_open) + ImGui::TreePop(); + + ImGui::PopStyleColor(); +} \ No newline at end of file diff --git a/Source/src/UI/WindowHierarchy.h b/Source/src/UI/WindowHierarchy.h new file mode 100644 index 000000000..b56b1ae02 --- /dev/null +++ b/Source/src/UI/WindowHierarchy.h @@ -0,0 +1,21 @@ +#pragma once +#include "Window.h" + +class GameObject; + +class WindowHierarchy : public Window +{ +public: + WindowHierarchy(); + ~WindowHierarchy() override; + void Update() override; + + void CleanUp() override; + +private: + void DrawHierarchyTree(GameObject* root); + void DrawGOChilds(GameObject* root); + void DrawGameObject(GameObject* go); + + GameObject* dragged_object = nullptr; +}; diff --git a/Source/src/UI/WindowInspector.cpp b/Source/src/UI/WindowInspector.cpp new file mode 100644 index 000000000..fc0203965 --- /dev/null +++ b/Source/src/UI/WindowInspector.cpp @@ -0,0 +1,79 @@ +#include "WindowInspector.h" + +#include "../Application.h" +#include "../GameObject.h" + +#include "../Modules/ModuleEditor.h" + +#include "imgui.h" +#include + +WindowInspector::WindowInspector() + : Window("Inspector", true) {} + +WindowInspector::~WindowInspector() +{ +} + +void WindowInspector::Update() +{ + ImGui::SetNextWindowDockID(App->editor->dock_right_id, ImGuiCond_FirstUseEver); + if (ImGui::Begin((std::string(ICON_FA_EYE " ") + name).c_str(), &active)) + { + DrawGameObject(App->editor->GetSelectedGO()); + } + ImGui::End(); +} + +void WindowInspector::DrawGameObject(GameObject* game_object) +{ + if (game_object != nullptr) + { + char go_name[50]; + strcpy_s(go_name, 50, game_object->name.c_str()); + ImGuiInputTextFlags name_input_flags = ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue; + if (ImGui::InputText("###", go_name, 50, name_input_flags)) + game_object->name = go_name; + + ImGui::SameLine(); + ImGui::Checkbox("Active", &game_object->active); + + std::vector go_components = game_object->GetComponents(); + for (vector::iterator it = go_components.begin(); it != go_components.end(); ++it) + (*it)->DrawGui(); + + ImGui::Separator(); + const float x = (ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize((std::string(ICON_FA_PLUS " ") + "Add component").c_str()).x - ImGui::GetStyle().FramePadding.x * 2) * 0.5f; + ImGui::SetCursorPosX(x); + if (ImGui::Button((std::string(ICON_FA_PLUS " ") + "Add component").c_str())) + { + ImGui::OpenPopup("AddComponentPopup"); + } + + if (ImGui::BeginPopup("AddComponentPopup")) + { + + if (ImGui::MenuItem("Camera")) + { + game_object->CreateComponent(Component::Type::CAMERA); + ImGui::CloseCurrentPopup(); + } + if (ImGui::MenuItem("Point Light")) + { + game_object->CreateComponent(Component::Type::POINTLIGHT); + ImGui::CloseCurrentPopup(); + } + if (ImGui::MenuItem("Spot Light")) + { + game_object->CreateComponent(Component::Type::SPOTLIGHT); + ImGui::CloseCurrentPopup(); + } + if (ImGui::MenuItem("Dir Light")) + { + game_object->CreateComponent(Component::Type::DIRLIGHT); + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + } +} diff --git a/Source/src/UI/WindowInspector.h b/Source/src/UI/WindowInspector.h new file mode 100644 index 000000000..ba54930e9 --- /dev/null +++ b/Source/src/UI/WindowInspector.h @@ -0,0 +1,15 @@ +#pragma once +#include "Window.h" + +class GameObject; + +class WindowInspector : public Window +{ +public: + WindowInspector(); + ~WindowInspector() override; + void Update() override; + +private: + void DrawGameObject(GameObject* go); +}; diff --git a/Source/src/UI/WindowProject.cpp b/Source/src/UI/WindowProject.cpp new file mode 100644 index 000000000..506c7bd55 --- /dev/null +++ b/Source/src/UI/WindowProject.cpp @@ -0,0 +1,313 @@ +#include "WindowProject.h" + +#include "../Application.h" +#include "../Globals.h" +#include "../Modules/ModuleFileSystem.h" +#include "../Modules/ModuleEditor.h" + +#include "ImGuiUtils.h" +#include + +WindowProject::WindowProject() + : Window("Project", true) +{ +} + +WindowProject::~WindowProject() +{ +} + +void WindowProject::Init() +{ + m_filter[0] = '\0'; + GetAssets(); +} + +void WindowProject::Update() +{ + ImGui::SetNextWindowDockID(App->editor->dock_down_id, ImGuiCond_FirstUseEver); + if (ImGui::Begin((std::string(ICON_FA_IMAGES " ") + name).c_str(), &active)) + { + ImGui::PushItemWidth(100); + if (ImGui::InputTextWithHint("##filter", "Filter", m_filter, sizeof(m_filter))) DoFilter(); + ImGui::PopItemWidth(); + ImGui::SameLine(); + if (ImGuiUtils::IconButton(ICON_FA_TIMES, "Reset filter")) + { + m_filter[0] = '\0'; + DoFilter(); + } + + ImGui::SameLine(); + CreateBreadCrumps(); + ImGui::Separator(); + + float content_w = ImGui::GetContentRegionAvail().x; + ImVec2 left_size(m_left_column_width, 0); + if (left_size.x < 10) left_size.x = 10; + if (left_size.x > content_w - 10) left_size.x = content_w - 10; + + ShowDir(all_assets); + + ImGui::SameLine(); + ImGuiUtils::VSplitter("vsplit1", &left_size); + if (left_size.x >= 120) + { + m_left_column_width = left_size.x; + } + ImGui::SameLine(); + + ShowFilesOnFolder(); + } + ImGui::End(); +} + +void WindowProject::CleanUp() +{} + +//TODO +void WindowProject::DoFilter() +{ +} + +void WindowProject::CreateBreadCrumps() +{ + const char* c = all_assets.path.c_str(); + char tmp[260]; + + while (*c) + { + char* c_out = tmp; + while (*c && *c != '/') + { + *c_out = *c; + ++c_out; + ++c; + } + *c_out = '\0'; + if (*c == '/') ++c; + if (ImGui::Button(tmp)) + { + if (tmp != all_assets.localPath) { + all_assets = App->file_sys->GetAllFiles(tmp, nullptr, nullptr); + } + } + ImGui::SameLine(); + ImGui::TextUnformatted("/"); + ImGui::SameLine(); + } + ImGui::NewLine(); +} + +//TODO +void WindowProject::Thumbnail(PathNode& node, float size, bool selected) +{ + ImGui::BeginGroup(); + ImVec2 img_size(size, size); + + ImGuiUtils::Rect(img_size.x, img_size.y, 0xffffFFFF); + /* TODO */ + + ImVec2 text_size = ImGui::CalcTextSize(node.localPath.c_str()); + ImVec2 pos = ImGui::GetCursorPos(); + pos.x += (size - text_size.x) * 0.5f; + ImGui::SetCursorPos(pos); + ImGui::Text("%s", node.localPath.c_str()); + ImGui::EndGroup(); + if (selected) + { + ImDrawList* dl = ImGui::GetWindowDrawList(); + const unsigned int color = ImGui::ColorConvertFloat4ToU32(ImGui::GetStyle().Colors[ImGuiCol_ButtonActive]); + dl->AddRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), color, 0, 0, 3.f); + } +} + +void WindowProject::ShowDir(PathNode& node) +{ + ImVec2 size(max(120.f, m_left_column_width), 0); + ImGui::BeginChild("left_col", size); + ImGui::PushItemWidth(120); + + PathNode new_root; + bool selected_item = false; + if ((node.path != ASSETS_FOLDER) && ImGui::Selectable("..", &selected_item)) + { + const char* tmp = node.path.c_str(); + std::string previous_path; + std::string full(tmp); + size_t pos_last_separator = full.find_last_of("\\/"); + + previous_path = full.substr(0, pos_last_separator); + + all_assets = App->file_sys->GetAllFiles(previous_path.c_str(), nullptr, nullptr); + + ImGui::PopItemWidth(); + ImGui::EndChild(); + + return; + } + + for (auto& subdir : node.children) + { + if (!subdir.isFile) + { + if (ImGui::Selectable(subdir.localPath.c_str(), &selected_item)) + { + new_root = subdir; + } + } + } + + if (selected_item) + { + all_assets = new_root; + } + + ImGui::PopItemWidth(); + ImGui::EndChild(); +} + +void WindowProject::ShowFilesOnFolder() +{ + ImGui::BeginChild("main_col"); + + float w = ImGui::GetContentRegionAvail().x; + int columns = (int) w / int(96 * 1.f); + columns = max(columns, 1); + int tile_count = all_assets.children.empty() ? all_assets.children.size() : 50; + int row_count = 1; + + auto callbacks = [this](PathNode& node, int idx) { + if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", node.path.c_str()); + if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID)) + { + ImGui::Text("%s", (const char*) node.path.c_str()); + ImGui::SetDragDropPayload("path", node.path.c_str(), strlen(node.path.c_str()) + 1, ImGuiCond_Once); + ImGui::EndDragDropSource(); + } + else if (ImGui::IsItemHovered()) + { + if (ImGui::IsMouseReleased(0)) + { + // selected_resource = find on list resource module resource + } + else if (ImGui::IsMouseReleased(1)) + { + ImGui::OpenPopup("item_ctx"); + } + else if (ImGui::IsMouseDoubleClicked(0)) + { + // instanciate on current level; + } + } + }; + + ImGuiListClipper clipper; + clipper.Begin(row_count); + while (clipper.Step()) + { + for (int j = clipper.DisplayStart; j < clipper.DisplayEnd; ++j) + { + for (int i = 0; i < columns; ++i) + { + if (i > 0) ImGui::SameLine(); + int idx = GetThumbnailIndex(i, j, columns); + if (idx < 0) + { + ImGui::NewLine(); + break; + } + PathNode& node = all_assets.children[idx]; + bool is_resource = node.children.size() == 0 && App->file_sys->HasExtension(node.path.c_str()); + if (is_resource) { + // bool selected = find on resource and search match + Thumbnail(node, m_thumbnail_size * 70, false); + callbacks(node, idx); + } + } + } + } + + bool open_delete_popup = false; + static char tmp[260] = ""; + auto common_popup = [&]() { + const char* base_path = nullptr; + + // TODO + /*if (ImGui::BeginMenu("Create directory")) + { + /*ImGui::InputTextWithHint("##dirname", "New directory name", tmp, sizeof(tmp)); + ImGui::SameLine(); + if (ImGui::Button("Create")) + { + const char* new_dir = /* curent dir path + new dir path nullptr; + App->file_sys->ModuleFileSystem::CreateDir(new_dir); + ImGui::CloseCurrentPopup(); + } + ImGui::EndMenu(); + } + // TODO + if (ImGui::MenuItem("Select all")) + { + + ImGui::EndMenu(); + }*/ + }; + if (ImGui::BeginPopup("item_ctx")) + { + ImGui::Separator(); + + open_delete_popup = ImGui::MenuItem("Delete"); + ImGui::Separator(); + common_popup(); + ImGui::EndPopup(); + } + else if (ImGui::BeginPopupContextWindow("context")) + { + common_popup(); + ImGui::EndPopup(); + } + + if (open_delete_popup) ImGui::OpenPopup("Delete file"); + + if (ImGui::BeginPopupModal("Delete file", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) + { + ImGui::Text("Are you sure? This can not be undone."); + if (ImGui::Button("Yes, delete", ImVec2(100, 0))) + { + //TODO Delete resource no fucking idea how but i guess we need to destroy from library first + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(ImGui::GetWindowWidth() - 100 - ImGui::GetStyle().WindowPadding.x); + if (ImGui::Button("Cancel", ImVec2(100, 0))) + { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + + ImGui::EndChild(); +} + +void WindowProject::GetAssets() +{ + std::vector ignore_ext; + ignore_ext.push_back("meta"); + all_assets = App->file_sys->GetAllFiles(ASSETS_FOLDER, nullptr, &ignore_ext); +} + +int WindowProject::GetThumbnailIndex(int i, int j, int columns) const +{ + int idx = j * columns + i; + if (!all_assets.children.empty()) + { + if (idx >= all_assets.children.size()) return -1; + return idx; + } + if (idx >= all_assets.children.size()) + { + return -1; + } + return idx; +} + diff --git a/Source/src/UI/WindowProject.h b/Source/src/UI/WindowProject.h new file mode 100644 index 000000000..a61b00c80 --- /dev/null +++ b/Source/src/UI/WindowProject.h @@ -0,0 +1,41 @@ +#pragma once +#include "Window.h" + +#include "../Utils/PathNode.h" + +#include + +using namespace std; + +class WindowProject : public Window +{ +public: + WindowProject(); + ~WindowProject() override; + + void Init() override; + void Update() override; + + void CleanUp() override; + +private: + void DoFilter(); + void CreateBreadCrumps(); + void Thumbnail(PathNode& node, float size, bool selected); + + void ShowDir(PathNode& node); + void ShowFilesOnFolder(); + + void GetAssets(); + +private: + + int GetThumbnailIndex(int i, int j, int columns) const; + + char m_filter[128]; + float m_left_column_width = 120; + + float m_thumbnail_size = 1.f; + + PathNode all_assets; +}; diff --git a/Source/src/UI/WindowResource.cpp b/Source/src/UI/WindowResource.cpp new file mode 100644 index 000000000..e5abf9a8e --- /dev/null +++ b/Source/src/UI/WindowResource.cpp @@ -0,0 +1,20 @@ +#include "WindowResource.h" + +#include "imgui.h" +#include + +WindowResource::WindowResource() + : Window("Resources", true) {} + +WindowResource::~WindowResource() +{ +} + + +void WindowResource::Update() +{ + if (ImGui::Begin(ICON_FA_FILE "Resource", &active)) + { + } + ImGui::End(); +} diff --git a/Source/src/UI/WindowResource.h b/Source/src/UI/WindowResource.h new file mode 100644 index 000000000..dea871f4d --- /dev/null +++ b/Source/src/UI/WindowResource.h @@ -0,0 +1,10 @@ +#pragma once +#include "Window.h" + +class WindowResource : public Window +{ +public: + WindowResource(); + ~WindowResource() override; + void Update() override; +}; diff --git a/Source/src/UI/WindowScene.cpp b/Source/src/UI/WindowScene.cpp new file mode 100644 index 000000000..6fe810364 --- /dev/null +++ b/Source/src/UI/WindowScene.cpp @@ -0,0 +1,242 @@ +#include "WindowScene.h" + +#include "../Application.h" +#include "../Utils/Timer.h" +#include "../GameObject.h" + +#include "../Modules/ModuleInput.h" +#include "../Modules/ModuleEditor.h" +#include "../Modules/ModuleCamera.h" +#include "../Modules/ModuleRender.h" +#include "../Modules/ModuleSceneManager.h" + +#include "../Components/ComponentCamera.h" +#include "../Components/ComponentTransform.h" + +#include +#include + +WindowScene::WindowScene() + : Window("Scene", true) +{ +} + +WindowScene::~WindowScene() +{ +} + +void WindowScene::Update() +{ + if (!App->input->GetMouseButton(SDL_BUTTON_RIGHT)) + { + if (App->input->GetKey(SDL_SCANCODE_W) == KEY_REPEAT) guizmo_operation = ImGuizmo::TRANSLATE; + if (App->input->GetKey(SDL_SCANCODE_E) == KEY_REPEAT) guizmo_operation = ImGuizmo::ROTATE; + if (App->input->GetKey(SDL_SCANCODE_R) == KEY_REPEAT) guizmo_operation = ImGuizmo::SCALE; + } + ImGui::SetNextWindowDockID(App->editor->dock_main_id, ImGuiCond_FirstUseEver); + if (ImGui::Begin((std::string(ICON_FA_GLOBE " ") + name).c_str(), &active)) + { + focused = ImGui::IsWindowFocused(); + GuizmoOptionsController(); + ImGui::SameLine(); + ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); + ImGui::SameLine(); + ToolbarMenu(); + DrawScene(); + Controller(); + ImGui::End(); + }; + +} + +void WindowScene::CleanUp() +{ +} + +void WindowScene::GuizmoOptionsController() +{ + if (ToolbarButton(App->editor->m_big_icon_font, ICON_FA_ARROWS_ALT, guizmo_operation == ImGuizmo::TRANSLATE)) guizmo_operation = ImGuizmo::TRANSLATE; + if (ToolbarButton(App->editor->m_big_icon_font, ICON_FA_UNDO, guizmo_operation == ImGuizmo::ROTATE)) guizmo_operation = ImGuizmo::ROTATE; + if (ToolbarButton(App->editor->m_big_icon_font, ICON_FA_EXPAND_ALT, guizmo_operation == ImGuizmo::SCALE)) guizmo_operation = ImGuizmo::SCALE; + + ImGui::SameLine(); + ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); + ImGui::SameLine(); + + if (ToolbarButton(App->editor->m_big_icon_font, ICON_FA_HOME, guizmo_mode == ImGuizmo::LOCAL)) guizmo_mode = ImGuizmo::LOCAL; + if (ToolbarButton(App->editor->m_big_icon_font, ICON_FA_GLOBE, guizmo_mode == ImGuizmo::WORLD)) guizmo_mode = ImGuizmo::WORLD; +} + +void WindowScene::ToolbarMenu() +{ + float w = (ImGui::GetWindowContentRegionMax().x - ImGui::GetWindowContentRegionMin().x) * 0.5f - 30 - ImGui::GetCursorPosX(); + ImGui::Dummy(ImVec2(w, ImGui::GetTextLineHeight())); + + bool playing = !GameTimer::paused && GameTimer::running; + + if (ToolbarButton(App->editor->m_big_icon_font, ICON_FA_PAUSE, GameTimer::paused && GameTimer::running)) + { + if (!GameTimer::paused) + { + GameTimer::Pause(); + } + } + + if (ToolbarButton(App->editor->m_big_icon_font, ICON_FA_PLAY, !GameTimer::paused && GameTimer::running)) + { + if (!GameTimer::running) + { + App->scene_manager->SaveScene("tmp_scene.scene"); + GameTimer::Start(); + } + else if (GameTimer::paused) + GameTimer::Resume(); + } + + if (ToolbarButton(App->editor->m_big_icon_font, ICON_FA_STOP, !GameTimer::running)) + { + if (GameTimer::running) + { + GameTimer::Stop(); + App->scene_manager->LoadScene("tmp_scene.scene"); + } + + } + + ImGui::Separator(); +} + +void WindowScene::DrawScene() +{ + ImVec2 size = ImGui::GetContentRegionAvail(); + if (size.x != texture_size.x || size.y != texture_size.y) { + texture_size = { + size.x, + size.y, + }; + App->camera->OnResize((unsigned) texture_size.x, (unsigned) texture_size.y); + } + + ComponentCamera* camera = App->camera->GetMainCamera(); + + // Top left corner of the image in imgui coords, top is y = 0 + guizmo_rect_origin = ImGui::GetCursorScreenPos(); + // Bottom left corner in opengl coordinates, bottom is y = 0 + texture_position = float2(guizmo_rect_origin.x, float(App->window->GetHeight()) - guizmo_rect_origin.y - texture_size.y); + + ImGui::Image((void*) (intptr_t) App->renderer->GetTextureId(), size, ImVec2 {0, 1}, ImVec2 {1, 0}); + + // TODO: Move to other section to take the input + if (ImGui::BeginDragDropTarget()) + { + if (auto* payload = ImGui::AcceptDragDropPayload("path")) + { + const char* path = (const char*) payload->Data; + App->scene_manager->LoadModel(path); + } + ImGui::EndDragDropTarget(); + } + + hovering = ImGui::IsWindowHovered(); + + // TO AVOID Camera orbit + if (ImGui::IsWindowFocused()) + { + bool guizmo_enabled = !(App->input->GetMouseButton(SDL_BUTTON_RIGHT) || App->input->GetKey(SDL_SCANCODE_LALT)); + ImGuizmo::Enable(guizmo_enabled); + } + + constexpr bool transposed = true; + float4x4 view = camera->GetViewMatrix(transposed); + + //TODO ADD look at when manipulating cube + ImGuizmo::ViewManipulate(view.ptr(), 4, ImVec2(guizmo_rect_origin.x + texture_size.x - imguizmo_size.x, + guizmo_rect_origin.y + texture_size.y - imguizmo_size.x), imguizmo_size, 0x10101010); + + if (ImGui::IsWindowFocused()) + { + float4x4 new_camera_view = view.InverseTransposed(); + App->camera->GetMainCamera()->GetFrustum()->SetFrame(new_camera_view.Col(3).xyz(), -new_camera_view.Col(2).xyz(), new_camera_view.Col(1).xyz()); + } + + GameObject* selected_object = App->editor->GetSelectedGO(); + if (!selected_object) return; + + if (selected_object) { + // Using GL format which means transposing them + view = camera->GetViewMatrix(transposed); + float4x4 projection = camera->GetProjectionMatrix(transposed); + float4x4 model = selected_object->GetComponent()->GetTransform().Transposed(); + float4x4 delta; + + ImGuizmo::SetRect(guizmo_rect_origin.x, guizmo_rect_origin.y, texture_size.x, texture_size.y); + ImGuizmo::SetDrawlist(); + + ImGuizmo::Manipulate(view.ptr(), projection.ptr(), guizmo_operation, guizmo_mode, model.ptr(), delta.ptr()); + + using_guizmo = ImGuizmo::IsUsing(); + if (using_guizmo && !delta.IsIdentity()) + { + // Transpose back again to store in our format + model.Transpose(); + selected_object->GetComponent()->SetGlobalTransform(model); + } + } +} + +void WindowScene::Controller() +{ + if (!using_guizmo && focused &&App->input->GetMouseButton(SDL_BUTTON_LEFT) == KEY_DOWN) + { + Scene* scene = App->scene_manager->GetActiveScene(); + GameObject* picked = SelectObject(App->camera->GetMainCamera(), scene); + if (picked) + App->editor->SetSelectedGO(picked); + } +} + +GameObject* WindowScene::SelectObject(ComponentCamera* camera, Scene* scene) +{ + // sdl -> (0, 0) top left, (w, h) bottom right + // // imgui -> (0,0) botton left + // plane -> (1,1) top right , (-1, -1) bottom left + + + ImVec2 mouse = ImGui::GetMousePos(); + float2 mouse_viewport_pos = float2(mouse.x - guizmo_rect_origin.x, mouse.y - guizmo_rect_origin.y); + + // Fit in range and coordinate direction + float x_normalized = mouse_viewport_pos.x / texture_size.x * 2.f - 1.f; + float y_normalized = -(mouse_viewport_pos.y / texture_size.y * 2.f - 1.f); + + LineSegment line = camera->RayCast(x_normalized, y_normalized); + GameObject* selected = scene->RayCast(line); + + return selected; +} + +//TODO: Move to utils gui and add tooltips +bool WindowScene::ToolbarButton(ImFont* font, const char* font_icon, bool active) +{ + const ImVec4 col_active = ImGui::GetStyle().Colors[ImGuiCol_ButtonActive]; + const ImVec4 bg_color = active ? col_active : ImGui::GetStyle().Colors[ImGuiCol_Text]; + + ImGui::SameLine(); + auto frame_padding = ImGui::GetStyle().FramePadding; + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0, 0, 0, 0)); + ImGui::PushStyleColor(ImGuiCol_Text, bg_color); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0, 0, 0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, ImGui::GetStyle().FramePadding.y)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, frame_padding); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0); + + ImGui::PushFont(font); + active = ImGui::Button(font_icon); + + ImGui::PopFont(); + ImGui::PopStyleColor(4); + ImGui::PopStyleVar(3); + + return active; +} diff --git a/Source/src/UI/WindowScene.h b/Source/src/UI/WindowScene.h new file mode 100644 index 000000000..d3b6818a9 --- /dev/null +++ b/Source/src/UI/WindowScene.h @@ -0,0 +1,46 @@ +#pragma once +#include "Window.h" +#include +#include + +#include "../Utils/Timer.h" +#include "MathGeoLib.h" + +class GameObject; +class ComponentCamera; +class Scene; + +class WindowScene : public Window +{ +public: + WindowScene(); + ~WindowScene() override; + void Update() override; + + void CleanUp() override; + + bool ToolbarButton(ImFont* font, const char* font_icon, bool active); + bool IsHovering() const { return hovering; } + +private: + void GuizmoOptionsController(); + void ToolbarMenu(); + void DrawScene(); + void Controller(); + + GameObject* SelectObject(ComponentCamera* camera, Scene* scene); + + ImVec2 imguizmo_size = {100.0f, 100.0f}; + bool using_guizmo = false; + bool focused = false; + bool hovering = false; + + Timer* scene_timer = nullptr; + + ImGuizmo::OPERATION guizmo_operation = ImGuizmo::TRANSLATE; + ImGuizmo::MODE guizmo_mode = ImGuizmo::WORLD; + + ImVec2 guizmo_rect_origin = {0.0f, 0.0f}; + float2 texture_position = float2(0.0f); + float2 texture_size = {0.0f, 0.0f}; +}; diff --git a/Source/src/UI/WindowTimers.cpp b/Source/src/UI/WindowTimers.cpp new file mode 100644 index 000000000..a0738fd0e --- /dev/null +++ b/Source/src/UI/WindowTimers.cpp @@ -0,0 +1,62 @@ +#include "WindowTimers.h" + +#include "imgui.h" + +WindowTimers::WindowTimers() + : Window("Timers", false) +{ + timer.Start(); +} + +WindowTimers::~WindowTimers() +{ +} + +void ClockDisplay(bool running, bool paused, double time) +{ + ImVec4 color(0.0f, 1.0f, 0.0f, 1.0f); + const char* state = "Running"; + if (!running) + { + color = ImVec4(1.0f, 0.0f, 0.0f, 1.0f); + state = "Stopped"; + } + else if (paused) + { + color = ImVec4(0.5f, 0.0f, 1.0f, 1.0f); + state = "Paused"; + } + ImGui::TextColored(color, "(%s): %.2f", state, time); +} + +void WindowTimers::Update() +{ + if (ImGui::Begin(name, &active, ImGuiWindowFlags_AlwaysAutoResize)) + { + ImGui::Text("Game Timer"); + ImGui::SameLine(); + ClockDisplay(GameTimer::running, GameTimer::paused, GameTimer::total_time); + + ImGui::Separator(); + ImGui::Text("Engine Timer"); + ImGui::SameLine(); + ClockDisplay(EngineTimer::running, false, EngineTimer::total_time); + + ImGui::Separator(); + ImGui::Text("Test Timer"); + ImGui::SameLine(); + ClockDisplay(timer.IsRunning(), false, timer.Read()); + + if (ImGui::Button("Start")) + timer.Start(); + ImGui::SameLine(); + if (ImGui::Button("Stop")) + timer.Stop(); + ImGui::SameLine(); + if (ImGui::Button("Resume")) + timer.Resume(); + + } + + ImGui::End(); +} diff --git a/Source/src/UI/WindowTimers.h b/Source/src/UI/WindowTimers.h new file mode 100644 index 000000000..af2f50bd4 --- /dev/null +++ b/Source/src/UI/WindowTimers.h @@ -0,0 +1,16 @@ +#pragma once + +#include "Window.h" +#include "../Utils/Timer.h" + +class WindowTimers : public Window +{ +public: + WindowTimers(); + ~WindowTimers() override; + + void Update() override; + +private: + Timer timer; +}; diff --git a/Source/src/Utils/JsonFormaterValue.cpp b/Source/src/Utils/JsonFormaterValue.cpp new file mode 100644 index 000000000..57d0883a9 --- /dev/null +++ b/Source/src/Utils/JsonFormaterValue.cpp @@ -0,0 +1,106 @@ +#include "JsonFormaterValue.h" + + +JsonFormaterValue::JsonFormaterValue(rapidjson::Document& document_, rapidjson::Value& value_) + : document(document_) + , value(value_) {} + +size_t JsonFormaterValue::Size() const +{ + return value.IsArray() ? value.Size() : 0; +} + +JsonFormaterValue JsonFormaterValue::operator[](unsigned index) const +{ + if (!value.IsArray()) + { + value.SetArray(); + } + + while (index >= value.Size()) + { + value.PushBack(rapidjson::Value(), document.GetAllocator()); + } + + return JsonFormaterValue(document, value[index]); +} + +void JsonFormaterValue::operator=(bool x) +{ + value = x; +} + +void JsonFormaterValue::operator=(int x) +{ + value = x; +} + +void JsonFormaterValue::operator=(unsigned x) +{ + value = x; +} + +void JsonFormaterValue::operator=(long long x) +{ + value = x; +} + +void JsonFormaterValue::operator=(unsigned long long x) +{ + value = x; +} + +void JsonFormaterValue::operator=(float x) +{ + value = x; +} + +void JsonFormaterValue::operator=(double x) +{ + value = x; +} + +void JsonFormaterValue::operator=(const char* x) +{ + value.SetString(x, document.GetAllocator()); +} + +JsonFormaterValue::operator bool() const +{ + return value.IsBool() ? value.GetBool() : false; +} + +JsonFormaterValue::operator int() const +{ + return value.IsInt() ? value.GetInt() : 0; +} + +JsonFormaterValue::operator unsigned() const +{ + return value.IsUint() ? value.GetUint() : 0; +} + +JsonFormaterValue::operator long long() const +{ + return value.IsInt64() ? value.GetInt64() : 0; +} + +JsonFormaterValue::operator unsigned long long() const +{ + return value.IsUint64() ? value.GetUint64() : 0; +} + +JsonFormaterValue::operator float() const +{ + return value.IsDouble() ? value.GetFloat() : 0; +} + +JsonFormaterValue::operator double() const +{ + return value.IsDouble() ? value.GetDouble() : 0; +} + +JsonFormaterValue::operator std::string() const +{ + return value.IsString() ? value.GetString() : ""; +} diff --git a/Source/src/Utils/JsonFormaterValue.h b/Source/src/Utils/JsonFormaterValue.h new file mode 100644 index 000000000..0c5e8a0ee --- /dev/null +++ b/Source/src/Utils/JsonFormaterValue.h @@ -0,0 +1,60 @@ +#pragma once + +//TODO Improve using this https://github.com/systelab/cpp-rapidjson-json-adapter/blob/master/src/RapidJSONAdapter/JSONValue.h + +#include "rapidjson/document.h" + +#include + +class JsonFormaterValue +{ +public: + JsonFormaterValue(rapidjson::Document& document, rapidjson::Value& value); + + // Size of the array. Returns 0 if the value is not an array. + size_t Size() const; + + // Object/array access (Creates new members if they don't exist) + template JsonFormaterValue operator[](T* key) const; + JsonFormaterValue operator[](unsigned index) const; + + // Assignment operators to modify the value + void operator=(bool x); + void operator=(int x); + void operator=(unsigned x); + void operator=(long long x); + void operator=(unsigned long long x); + void operator=(float x); + void operator=(double x); + void operator=(const char* x); + + // Conversion operators for easy access + operator bool() const; + operator int() const; + operator unsigned() const; + operator long long() const; + operator unsigned long long() const; + operator float() const; + operator double() const; + operator std::string() const; + +private: + rapidjson::Document& document; + rapidjson::Value& value; +}; + +template +inline JsonFormaterValue JsonFormaterValue::operator[](T* key) const +{ + if (!value.IsObject()) + { + value.SetObject(); + } + + if (!value.HasMember(key)) + { + value.AddMember(rapidjson::StringRef(key), rapidjson::Value(), document.GetAllocator()); + } + + return JsonFormaterValue(document, value[key]); +} \ No newline at end of file diff --git a/Source/src/Utils/Logger.cpp b/Source/src/Utils/Logger.cpp new file mode 100644 index 000000000..adeb193b7 --- /dev/null +++ b/Source/src/Utils/Logger.cpp @@ -0,0 +1,29 @@ +#include "Logger.h" + +#include +#include +#include + +void log(const char file[], int line, const char* format, ...) +{ + static char tmp_string[4096]; + static char tmp_string2[4096]; + static char tmp_string3[4096]; + + int old_size = buff.size(); + va_list args; + va_start(args, format); + vsprintf_s(tmp_string, 4096, format, args); + va_end(args); + sprintf_s(tmp_string2, 4096, "\n%s(%d) : %s", file, line, tmp_string); + + buff.append(tmp_string2); + + for (int new_size = buff.size(); old_size < new_size; old_size++) + if (buff[old_size] == '\n') + line_offsets.push_back(old_size + 1); + OutputDebugString(tmp_string2); +} + +ImVector line_offsets; +ImGuiTextBuffer buff; diff --git a/Source/src/Utils/Logger.h b/Source/src/Utils/Logger.h new file mode 100644 index 000000000..1602f2277 --- /dev/null +++ b/Source/src/Utils/Logger.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +#define __FILENAME__ (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__) + +#define LOG(format, ...) log(__FILENAME__, __LINE__, format, __VA_ARGS__); + +void log(const char file[], int line, const char* format, ...); + +extern ImVector line_offsets; +extern ImGuiTextBuffer buff; diff --git a/Source/src/Utils/PathNode.h b/Source/src/Utils/PathNode.h new file mode 100644 index 000000000..73af5a7c0 --- /dev/null +++ b/Source/src/Utils/PathNode.h @@ -0,0 +1,54 @@ +#ifndef __PATHNODE_H__ +#define __PATHNODE_H__ + +#include +#include + +struct PathNode +{ + PathNode() + : path("") {}; + PathNode(const PathNode& a) + : path(a.path) + , localPath(a.localPath) + , children(a.children) + , isLeaf(a.isLeaf) + , isFile(a.isFile) + {} + + std::string path; + std::string localPath; + + std::vector children; + + bool isLeaf = true; + bool isFile = true; + + bool IsLastFolder() const + { + for (unsigned int i = 0; i < children.size(); i++) + if (children[i].isFile == false) + return false; + return true; + } + + bool operator==(const PathNode node) const + { + return path == node.path; + } + + PathNode& operator=(const PathNode& a) + { + path = a.path; + localPath = a.localPath; + + children = std::vector(a.children); + + isLeaf = a.isLeaf; + isFile = a.isFile; + + return *this; + } +}; + +#endif diff --git a/Source/src/Utils/Timer.cpp b/Source/src/Utils/Timer.cpp new file mode 100644 index 000000000..b88ddf583 --- /dev/null +++ b/Source/src/Utils/Timer.cpp @@ -0,0 +1,156 @@ +#include "Timer.h" + +#include "SDL.h" + +void Timer::Start() +{ + running = true; + start_time = SDL_GetTicks(); +} + +void Timer::Resume() +{ + if (!running) + { + running = true; + start_time = SDL_GetTicks() - (stop_time - start_time); + } +} + +double Timer::Read() +{ + unsigned int now; + if (running) + now = SDL_GetTicks(); + else + now = stop_time; + + current_time = (double) (now - start_time); // ms + return current_time; +} + +double Timer::Stop() +{ + if (running) + { + running = false; + stop_time = SDL_GetTicks(); + return Read(); + } + return (double) stop_time; +} + + +void PerformanceTimer::Start() +{ + running = true; + start_time = SDL_GetPerformanceCounter(); +} + +void PerformanceTimer::Resume() +{ + if (!running) + { + running = true; + start_time = SDL_GetPerformanceCounter() - (stop_time - start_time); + } +} + +double PerformanceTimer::Read() +{ + static const double frequency = (double) SDL_GetPerformanceFrequency(); + + unsigned long long now; + if (running) + now = SDL_GetPerformanceCounter(); + else + now = stop_time; + + current_time = (double) ((now - start_time) * 1000.0 / frequency); + return current_time; +} + +double PerformanceTimer::Stop() +{ + if (running) + { + running = false; + stop_time = SDL_GetPerformanceCounter(); + return Read(); + } + return (double) stop_time; +} + +double GameTimer::delta_time = 0.; +double GameTimer::total_time = 0.; +double GameTimer::prev_tick_time = 0.; +bool GameTimer::running = false; +bool GameTimer::paused = false; +PerformanceTimer GameTimer::timer; + +void GameTimer::Start() +{ + total_time = 0.; + running = true; + paused = false; + timer.Start(); + prev_tick_time = timer.Read(); +} + +double GameTimer::Update() +{ + double tick_time = timer.Read(); + delta_time = (tick_time - prev_tick_time) / 1000.0; + prev_tick_time = tick_time; + if (running) + total_time = timer.Read(); + return delta_time; +} + +void GameTimer::Play() +{ + running = true; +} + +void GameTimer::Pause() +{ + paused = true; + timer.Stop(); +} + +void GameTimer::Resume() +{ + paused = false; + timer.Resume(); +} + +void GameTimer::Stop() +{ + running = false; + timer.Stop(); +} + +PerformanceTimer EngineTimer::timer; +double EngineTimer::delta_time = 0.; +double EngineTimer::total_time = 0.; +double EngineTimer::prev_tick_time = 0.; +bool EngineTimer::running = false; + + +void EngineTimer::Start() +{ + total_time = 0.; + running = true; + timer.Start(); + prev_tick_time = timer.Read(); +} + +double EngineTimer::Update() +{ + double tick_time = timer.Read(); + delta_time = (tick_time - prev_tick_time) / 1000.0; + prev_tick_time = tick_time; + if (running) + total_time = timer.Read(); + return delta_time; +} diff --git a/Source/src/Utils/Timer.h b/Source/src/Utils/Timer.h new file mode 100644 index 000000000..b6ad0fc2e --- /dev/null +++ b/Source/src/Utils/Timer.h @@ -0,0 +1,69 @@ +#pragma once + +class Timer +{ +public: + void Start(); + void Resume(); + double Read(); + double Stop(); + bool IsRunning() const { return running; } + +private: + bool running = false; + unsigned long long start_time; + unsigned long long stop_time; + double current_time; +}; + +class PerformanceTimer +{ +public: + void Start(); + void Resume(); + double Read(); + double Stop(); + bool IsRunning() const { return running; } + +private: + bool running = false; + unsigned long long start_time; + unsigned long long stop_time; + double current_time; +}; + + +class GameTimer +{ +public: + + static void Start(); + static double Update(); + + static void Play(); + static void Pause(); + static void Resume(); + static void Stop(); + + static PerformanceTimer timer; + + static double delta_time; + static double total_time; + static double prev_tick_time; + + static bool running; + static bool paused; +}; + +class EngineTimer +{ +public: + static void Start(); + static double Update(); + + static PerformanceTimer timer; + static double delta_time; + static double total_time; + static double prev_tick_time; + static bool running; +}; diff --git a/Source/src/Utils/UID.cpp b/Source/src/Utils/UID.cpp new file mode 100644 index 000000000..a3cdd3902 --- /dev/null +++ b/Source/src/Utils/UID.cpp @@ -0,0 +1,60 @@ +#include "UID.h" + +#include +#include +#include + +// USED this algorithm to generate uid https://stackoverflow.com/questions/24365331/how-can-i-generate-uuid-in-c-without-using-boost-library + +static std::random_device rd; +static std::mt19937 gen(rd()); +static std::uniform_int_distribution dis; +static std::uniform_int_distribution distribution_hex(0, 15); +static std::uniform_int_distribution distribution_hex_2(8, 11); + +UID GenerateUID() +{ + return dis(gen); +} + +std::string GenerateUID128() +{ + std::stringstream ss; + + ss << std::hex; + ss << std::uppercase; + + for (int i = 0; i < 8; i++) + { + ss << distribution_hex(gen); + } + + ss << "-"; + for (int i = 0; i < 4; i++) + { + ss << distribution_hex(gen); + } + + ss << "-4"; + for (int i = 0; i < 3; i++) + { + ss << distribution_hex(gen); + } + + ss << "-"; + ss << distribution_hex_2(gen); + + for (int i = 0; i < 3; i++) + { + ss << distribution_hex(gen); + } + + ss << "-"; + + for (int i = 0; i < 12; i++) + { + ss << distribution_hex(gen); + }; + + return ss.str(); +} \ No newline at end of file diff --git a/Source/src/Utils/UID.h b/Source/src/Utils/UID.h new file mode 100644 index 000000000..18a8c0e5b --- /dev/null +++ b/Source/src/Utils/UID.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +typedef unsigned long long UID; + +UID GenerateUID(); + +std::string GenerateUID128(); \ No newline at end of file diff --git a/Source/x64/Release/BakerHouse.fbx b/Source/x64/Release/BakerHouse.fbx deleted file mode 100644 index b6e672144..000000000 Binary files a/Source/x64/Release/BakerHouse.fbx and /dev/null differ diff --git a/Source/x64/Release/Engine.exe.recipe b/Source/x64/Release/Engine.exe.recipe index 14256ecb6..ae325449c 100644 --- a/Source/x64/Release/Engine.exe.recipe +++ b/Source/x64/Release/Engine.exe.recipe @@ -2,7 +2,7 @@ - C:\Users\erict\OneDrive\Escritorio\AltarBoy\Engine3D\Source\x64\Release\Engine.exe + C:\Users\erict\OneDrive\Escritorio\AltarBoy\Source\x64\Release\Engine.exe diff --git a/Source/x64/Release/Engine.exp b/Source/x64/Release/Engine.exp new file mode 100644 index 000000000..99762910c Binary files /dev/null and b/Source/x64/Release/Engine.exp differ diff --git a/Source/x64/Release/Engine.tlog/CL.command.1.tlog b/Source/x64/Release/Engine.tlog/CL.command.1.tlog index fe7a4e836..09c7f18f0 100644 Binary files a/Source/x64/Release/Engine.tlog/CL.command.1.tlog and b/Source/x64/Release/Engine.tlog/CL.command.1.tlog differ diff --git a/Source/x64/Release/Engine.tlog/CL.read.1.tlog b/Source/x64/Release/Engine.tlog/CL.read.1.tlog index a14ccf55c..7811fc94c 100644 Binary files a/Source/x64/Release/Engine.tlog/CL.read.1.tlog and b/Source/x64/Release/Engine.tlog/CL.read.1.tlog differ diff --git a/Source/x64/Release/Engine.tlog/CL.write.1.tlog b/Source/x64/Release/Engine.tlog/CL.write.1.tlog index cc5643a22..d293289ee 100644 Binary files a/Source/x64/Release/Engine.tlog/CL.write.1.tlog and b/Source/x64/Release/Engine.tlog/CL.write.1.tlog differ diff --git a/Source/x64/Release/Engine.tlog/Engine.lastbuildstate b/Source/x64/Release/Engine.tlog/Engine.lastbuildstate index 23dc0847c..e3ab9e964 100644 --- a/Source/x64/Release/Engine.tlog/Engine.lastbuildstate +++ b/Source/x64/Release/Engine.tlog/Engine.lastbuildstate @@ -1,2 +1,2 @@ PlatformToolSet=v142:VCToolArchitecture=Native32Bit:VCToolsVersion=14.28.29910:TargetPlatformVersion=10.0.19041.0: -Release|x64|C:\Users\erict\OneDrive\Escritorio\AltarBoy\Engine3D\Source\| +Release|x64|C:\Users\erict\OneDrive\Escritorio\AltarBoy\Source\| diff --git a/Source/x64/Release/Engine.tlog/Engine.write.1u.tlog b/Source/x64/Release/Engine.tlog/Engine.write.1u.tlog index a74037912..0e5751d6c 100644 Binary files a/Source/x64/Release/Engine.tlog/Engine.write.1u.tlog and b/Source/x64/Release/Engine.tlog/Engine.write.1u.tlog differ diff --git a/Source/x64/Release/Engine.tlog/link.command.1.tlog b/Source/x64/Release/Engine.tlog/link.command.1.tlog index 041c980cd..7eef6750d 100644 Binary files a/Source/x64/Release/Engine.tlog/link.command.1.tlog and b/Source/x64/Release/Engine.tlog/link.command.1.tlog differ diff --git a/Source/x64/Release/Engine.tlog/link.read.1.tlog b/Source/x64/Release/Engine.tlog/link.read.1.tlog index 789065714..4f9542cd8 100644 Binary files a/Source/x64/Release/Engine.tlog/link.read.1.tlog and b/Source/x64/Release/Engine.tlog/link.read.1.tlog differ diff --git a/Source/x64/Release/Engine.tlog/link.write.1.tlog b/Source/x64/Release/Engine.tlog/link.write.1.tlog index 5be164fae..ba3052004 100644 Binary files a/Source/x64/Release/Engine.tlog/link.write.1.tlog and b/Source/x64/Release/Engine.tlog/link.write.1.tlog differ diff --git a/Source/x64/Release/Engine.vcxproj.FileListAbsolute.txt b/Source/x64/Release/Engine.vcxproj.FileListAbsolute.txt index 1c55b56dc..6262c278b 100644 --- a/Source/x64/Release/Engine.vcxproj.FileListAbsolute.txt +++ b/Source/x64/Release/Engine.vcxproj.FileListAbsolute.txt @@ -1 +1 @@ -C:\Users\erict\OneDrive\Escritorio\AltarBoy\Engine3D\Source\x64\Release\Engine.exe +C:\Users\erict\OneDrive\Escritorio\AltarBoy\Source\x64\Release\Engine.exe diff --git a/Source/x64/Release/Textures/Baker_house.png b/Source/x64/Release/Textures/Baker_house.png deleted file mode 100644 index 415732b77..000000000 Binary files a/Source/x64/Release/Textures/Baker_house.png and /dev/null differ diff --git a/Source/x64/Release/fragment.glsl b/Source/x64/Release/fragment.glsl deleted file mode 100644 index cd019016d..000000000 --- a/Source/x64/Release/fragment.glsl +++ /dev/null @@ -1,11 +0,0 @@ -# version 330 -out vec4 color; - -in vec2 tex_coord; - -uniform sampler2D texture; - -void main() -{ - color = texture2D(texture, tex_coord); -} diff --git a/Source/x64/Release/vertex.glsl b/Source/x64/Release/vertex.glsl deleted file mode 100644 index 7242fca85..000000000 --- a/Source/x64/Release/vertex.glsl +++ /dev/null @@ -1,15 +0,0 @@ -# version 330 -layout(location=0) in vec3 position; -layout(location=1) in vec2 in_tex_coord; - -uniform mat4 model; -uniform mat4 view; -uniform mat4 proj; - -out vec2 tex_coord; - -void main() -{ - gl_Position = proj * view * model * vec4(position, 1.0); - tex_coord = vec2(in_tex_coord.x, in_tex_coord.y); -} \ No newline at end of file