Fix exported player data folder validation
This commit is contained in:
parent
c0860fac03
commit
b3880c9e2b
8 changed files with 219 additions and 18 deletions
|
|
@ -9,7 +9,7 @@ custom_features=""
|
|||
export_filter="all_resources"
|
||||
include_filter=""
|
||||
exclude_filter="builds/*,playtest/*,scripts/*"
|
||||
export_path="builds/v0.3.0-prealpha.2/windows-x86_64/NETfishing.exe"
|
||||
export_path="builds/v0.3.0-prealpha.3/windows-x86_64/NETfishing.exe"
|
||||
patches=PackedStringArray()
|
||||
encryption_include_filters=""
|
||||
encryption_exclude_filters=""
|
||||
|
|
@ -35,11 +35,11 @@ application/modify_resources=true
|
|||
application/icon=""
|
||||
application/console_wrapper_icon=""
|
||||
application/icon_interpolation=4
|
||||
application/file_version="0.3.0.2"
|
||||
application/product_version="0.3.0.2"
|
||||
application/file_version="0.3.0.3"
|
||||
application/product_version="0.3.0.3"
|
||||
application/company_name=""
|
||||
application/product_name="NETfishing"
|
||||
application/file_description="NETfishing v0.3.0-prealpha.2"
|
||||
application/file_description="NETfishing v0.3.0-prealpha.3"
|
||||
application/copyright=""
|
||||
application/trademarks=""
|
||||
application/export_angle=0
|
||||
|
|
@ -62,7 +62,7 @@ custom_features=""
|
|||
export_filter="all_resources"
|
||||
include_filter=""
|
||||
exclude_filter="builds/*,playtest/*,scripts/*"
|
||||
export_path="builds/v0.3.0-prealpha.2/linux-x86_64/NETfishing.x86_64"
|
||||
export_path="builds/v0.3.0-prealpha.3/linux-x86_64/NETfishing.x86_64"
|
||||
patches=PackedStringArray()
|
||||
encryption_include_filters=""
|
||||
encryption_exclude_filters=""
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ const README_FILENAME := "README.txt"
|
|||
const ENVIRONMENT_VARIABLE := "NETFISHING_DATA_DIR"
|
||||
const APPLICATION_ID := "netfishing"
|
||||
const APP_DATA_PORTABLE_PATH := "user://portable-data"
|
||||
const PATH_ALLOWED: StringName = &"allowed"
|
||||
const PATH_SOURCE_PROJECT: StringName = &"source_project"
|
||||
const PATH_INSTALLATION: StringName = &"installation"
|
||||
|
||||
enum Mode {
|
||||
UNRESOLVED,
|
||||
|
|
@ -226,15 +229,77 @@ func _activate_existing(path: String, expected_id: String, permit_creation: bool
|
|||
|
||||
|
||||
func _validate_candidate(path: String, create: bool) -> bool:
|
||||
if path.is_empty() or not path.is_absolute_path():
|
||||
var normalized: String = _normalize(path)
|
||||
if normalized.is_empty() or not normalized.is_absolute_path():
|
||||
return _fail("Choose an absolute filesystem folder.")
|
||||
var project: String = ProjectSettings.globalize_path("res://").trim_suffix("/")
|
||||
if path == project or path.begins_with(project + "/"):
|
||||
var classification: StringName = classify_candidate_path(
|
||||
normalized,
|
||||
ProjectSettings.globalize_path("res://"),
|
||||
OS.get_executable_path().get_base_dir(),
|
||||
)
|
||||
if classification == PATH_SOURCE_PROJECT:
|
||||
return _fail("The project folder cannot be used as the player data folder.")
|
||||
if not DirAccess.dir_exists_absolute(path):
|
||||
if not create or DirAccess.make_dir_recursive_absolute(path) != OK:
|
||||
if classification == PATH_INSTALLATION:
|
||||
return _fail(
|
||||
"The application installation folder cannot be used as the player data folder."
|
||||
)
|
||||
if not DirAccess.dir_exists_absolute(normalized):
|
||||
if not create or DirAccess.make_dir_recursive_absolute(normalized) != OK:
|
||||
return _fail("The selected data folder is unavailable.")
|
||||
return _test_writable(path)
|
||||
return _test_writable(normalized)
|
||||
|
||||
|
||||
static func classify_candidate_path(
|
||||
candidate_path: String,
|
||||
project_reference: String,
|
||||
installation_reference: String,
|
||||
case_insensitive: bool = OS.get_name() == "Windows",
|
||||
) -> StringName:
|
||||
var candidate: String = _normalize_comparison_path(
|
||||
candidate_path, case_insensitive
|
||||
)
|
||||
if candidate.is_empty() or not candidate.is_absolute_path():
|
||||
return PATH_ALLOWED
|
||||
var project: String = _normalize_reference_path(
|
||||
project_reference, case_insensitive
|
||||
)
|
||||
if not project.is_empty() and _is_same_or_child_path(candidate, project):
|
||||
return PATH_SOURCE_PROJECT
|
||||
var installation: String = _normalize_reference_path(
|
||||
installation_reference, case_insensitive
|
||||
)
|
||||
if (
|
||||
not installation.is_empty()
|
||||
and _is_same_or_child_path(candidate, installation)
|
||||
):
|
||||
return PATH_INSTALLATION
|
||||
return PATH_ALLOWED
|
||||
|
||||
|
||||
static func _normalize_reference_path(path: String, case_insensitive: bool) -> String:
|
||||
var normalized: String = _normalize_comparison_path(path, case_insensitive)
|
||||
if normalized.is_empty() or not normalized.is_absolute_path():
|
||||
return ""
|
||||
if normalized == "/" or _is_windows_drive_root(normalized):
|
||||
return ""
|
||||
return normalized
|
||||
|
||||
|
||||
static func _normalize_comparison_path(path: String, case_insensitive: bool) -> String:
|
||||
var normalized: String = path.strip_edges().replace("\\", "/").simplify_path()
|
||||
while normalized.length() > 1 and normalized.ends_with("/"):
|
||||
if _is_windows_drive_root(normalized):
|
||||
break
|
||||
normalized = normalized.left(-1)
|
||||
return normalized.to_lower() if case_insensitive else normalized
|
||||
|
||||
|
||||
static func _is_windows_drive_root(path: String) -> bool:
|
||||
return path.length() == 3 and path[1] == ":" and path[2] == "/"
|
||||
|
||||
|
||||
static func _is_same_or_child_path(candidate: String, reference: String) -> bool:
|
||||
return candidate == reference or candidate.begins_with(reference + "/")
|
||||
|
||||
|
||||
func _test_writable(path: String) -> bool:
|
||||
|
|
@ -340,7 +405,12 @@ func _directory_is_empty(path: String) -> bool:
|
|||
|
||||
|
||||
func _normalize(path: String) -> String:
|
||||
return path.simplify_path().trim_suffix("/")
|
||||
var normalized: String = path.replace("\\", "/").simplify_path()
|
||||
while normalized.length() > 1 and normalized.ends_with("/"):
|
||||
if _is_windows_drive_root(normalized):
|
||||
break
|
||||
normalized = normalized.left(-1)
|
||||
return normalized
|
||||
|
||||
|
||||
func _read_json(path: String, maximum := 1024 * 1024) -> Dictionary:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
NETfishing
|
||||
v0.3.0-prealpha.2
|
||||
v0.3.0-prealpha.3
|
||||
Pre-Alpha 0.3
|
||||
|
||||
Thank you for trying this early private playtest.
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ config_version=5
|
|||
[application]
|
||||
|
||||
config/name="NETFISHING"
|
||||
config/version="0.3.0-prealpha.2"
|
||||
config/version="0.3.0-prealpha.3"
|
||||
run/main_scene="res://main/main.tscn"
|
||||
config/features=PackedStringArray("4.7", "GL Compatibility")
|
||||
config/icon="res://icon.svg"
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ set -euo pipefail
|
|||
|
||||
readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
readonly PROJECT_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)"
|
||||
readonly BUILD_ROOT="${PROJECT_ROOT}/builds/v0.3.0-prealpha.2"
|
||||
readonly BUILD_ROOT="${PROJECT_ROOT}/builds/v0.3.0-prealpha.3"
|
||||
readonly WINDOWS_DIR="${BUILD_ROOT}/windows-x86_64"
|
||||
readonly LINUX_DIR="${BUILD_ROOT}/linux-x86_64"
|
||||
readonly README_SOURCE="${PROJECT_ROOT}/playtest/README-PLAYTEST.txt"
|
||||
readonly WINDOWS_ZIP="${BUILD_ROOT}/NETfishing-v0.3.0-prealpha.2-windows-x86_64.zip"
|
||||
readonly LINUX_ZIP="${BUILD_ROOT}/NETfishing-v0.3.0-prealpha.2-linux-x86_64.zip"
|
||||
readonly WINDOWS_ZIP="${BUILD_ROOT}/NETfishing-v0.3.0-prealpha.3-windows-x86_64.zip"
|
||||
readonly LINUX_ZIP="${BUILD_ROOT}/NETfishing-v0.3.0-prealpha.3-linux-x86_64.zip"
|
||||
readonly GODOT_BIN="${GODOT_BIN:-godot}"
|
||||
|
||||
if [[ ! -f "${PROJECT_ROOT}/project.godot" ]]; then
|
||||
|
|
|
|||
130
scripts/test_player_data_root_paths.gd
Normal file
130
scripts/test_player_data_root_paths.gd
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
extends SceneTree
|
||||
|
||||
const PROJECT_PATH := "/tmp/netfishing"
|
||||
const INSTALL_PATH := "/opt/NETfishing"
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
var root_to_create: String = OS.get_environment("NETFISHING_TEST_CREATE_ROOT")
|
||||
if not root_to_create.is_empty():
|
||||
_create_validation_root(root_to_create)
|
||||
return
|
||||
var failures: PackedStringArray = []
|
||||
_expect(
|
||||
"external temporary directory",
|
||||
"/tmp/NETfishing-test-data",
|
||||
PlayerDataRoot.PATH_ALLOWED,
|
||||
failures,
|
||||
)
|
||||
_expect(
|
||||
"external trailing slash",
|
||||
"/tmp/NETfishing-test-data/",
|
||||
PlayerDataRoot.PATH_ALLOWED,
|
||||
failures,
|
||||
)
|
||||
_expect(
|
||||
"similar project-name sibling",
|
||||
"/tmp/netfishing-data",
|
||||
PlayerDataRoot.PATH_ALLOWED,
|
||||
failures,
|
||||
)
|
||||
_expect(
|
||||
"documents directory",
|
||||
"/home/player/Documents/NETfishing",
|
||||
PlayerDataRoot.PATH_ALLOWED,
|
||||
failures,
|
||||
)
|
||||
_expect("source project", PROJECT_PATH, PlayerDataRoot.PATH_SOURCE_PROJECT, failures)
|
||||
_expect(
|
||||
"source project child",
|
||||
PROJECT_PATH.path_join("player-data"),
|
||||
PlayerDataRoot.PATH_SOURCE_PROJECT,
|
||||
failures,
|
||||
)
|
||||
_expect(
|
||||
"installation directory",
|
||||
INSTALL_PATH,
|
||||
PlayerDataRoot.PATH_INSTALLATION,
|
||||
failures,
|
||||
)
|
||||
_expect(
|
||||
"installation child",
|
||||
INSTALL_PATH.path_join("portable-data"),
|
||||
PlayerDataRoot.PATH_INSTALLATION,
|
||||
failures,
|
||||
)
|
||||
_expect_with_references(
|
||||
"empty project reference",
|
||||
"/tmp/NETfishing-test-data",
|
||||
"",
|
||||
INSTALL_PATH,
|
||||
PlayerDataRoot.PATH_ALLOWED,
|
||||
failures,
|
||||
)
|
||||
_expect_with_references(
|
||||
"root project reference",
|
||||
"/tmp/NETfishing-test-data",
|
||||
"/",
|
||||
INSTALL_PATH,
|
||||
PlayerDataRoot.PATH_ALLOWED,
|
||||
failures,
|
||||
)
|
||||
_expect_with_references(
|
||||
"Windows component and case normalization",
|
||||
"C:\\Games\\NETfishing Data",
|
||||
"C:\\Games\\NETfishing",
|
||||
"C:\\Program Files\\NETfishing",
|
||||
PlayerDataRoot.PATH_ALLOWED,
|
||||
failures,
|
||||
true,
|
||||
)
|
||||
if failures.is_empty():
|
||||
print("PlayerDataRoot path validation: PASS")
|
||||
quit(0)
|
||||
return
|
||||
for failure: String in failures:
|
||||
push_error(failure)
|
||||
quit(1)
|
||||
|
||||
|
||||
func _create_validation_root(path: String) -> void:
|
||||
var data_root := PlayerDataRoot.new()
|
||||
get_root().add_child(data_root)
|
||||
if data_root.select_new_root(path):
|
||||
print("PlayerDataRoot validation root created: ", data_root.root_path)
|
||||
quit(0)
|
||||
return
|
||||
push_error(data_root.error_message)
|
||||
quit(1)
|
||||
|
||||
|
||||
func _expect(
|
||||
label: String,
|
||||
candidate: String,
|
||||
expected: StringName,
|
||||
failures: PackedStringArray,
|
||||
) -> void:
|
||||
_expect_with_references(
|
||||
label,
|
||||
candidate,
|
||||
PROJECT_PATH,
|
||||
INSTALL_PATH,
|
||||
expected,
|
||||
failures,
|
||||
)
|
||||
|
||||
|
||||
func _expect_with_references(
|
||||
label: String,
|
||||
candidate: String,
|
||||
project: String,
|
||||
installation: String,
|
||||
expected: StringName,
|
||||
failures: PackedStringArray,
|
||||
case_insensitive: bool = false,
|
||||
) -> void:
|
||||
var actual: StringName = PlayerDataRoot.classify_candidate_path(
|
||||
candidate, project, installation, case_insensitive
|
||||
)
|
||||
if actual != expected:
|
||||
failures.append("%s: expected %s, got %s" % [label, expected, actual])
|
||||
1
scripts/test_player_data_root_paths.gd.uid
Normal file
1
scripts/test_player_data_root_paths.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://e8glhlm205au
|
||||
|
|
@ -206,7 +206,7 @@ unique_name_in_owner = true
|
|||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.682, 0.733, 0.761, 1)
|
||||
theme_override_font_sizes/font_size = 22
|
||||
text = "v0.3.0-prealpha.2"
|
||||
text = "v0.3.0-prealpha.3"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="Spacer" type="Control" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/Center/MainContent"]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue