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"
|
export_filter="all_resources"
|
||||||
include_filter=""
|
include_filter=""
|
||||||
exclude_filter="builds/*,playtest/*,scripts/*"
|
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()
|
patches=PackedStringArray()
|
||||||
encryption_include_filters=""
|
encryption_include_filters=""
|
||||||
encryption_exclude_filters=""
|
encryption_exclude_filters=""
|
||||||
|
|
@ -35,11 +35,11 @@ application/modify_resources=true
|
||||||
application/icon=""
|
application/icon=""
|
||||||
application/console_wrapper_icon=""
|
application/console_wrapper_icon=""
|
||||||
application/icon_interpolation=4
|
application/icon_interpolation=4
|
||||||
application/file_version="0.3.0.2"
|
application/file_version="0.3.0.3"
|
||||||
application/product_version="0.3.0.2"
|
application/product_version="0.3.0.3"
|
||||||
application/company_name=""
|
application/company_name=""
|
||||||
application/product_name="NETfishing"
|
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/copyright=""
|
||||||
application/trademarks=""
|
application/trademarks=""
|
||||||
application/export_angle=0
|
application/export_angle=0
|
||||||
|
|
@ -62,7 +62,7 @@ custom_features=""
|
||||||
export_filter="all_resources"
|
export_filter="all_resources"
|
||||||
include_filter=""
|
include_filter=""
|
||||||
exclude_filter="builds/*,playtest/*,scripts/*"
|
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()
|
patches=PackedStringArray()
|
||||||
encryption_include_filters=""
|
encryption_include_filters=""
|
||||||
encryption_exclude_filters=""
|
encryption_exclude_filters=""
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,9 @@ const README_FILENAME := "README.txt"
|
||||||
const ENVIRONMENT_VARIABLE := "NETFISHING_DATA_DIR"
|
const ENVIRONMENT_VARIABLE := "NETFISHING_DATA_DIR"
|
||||||
const APPLICATION_ID := "netfishing"
|
const APPLICATION_ID := "netfishing"
|
||||||
const APP_DATA_PORTABLE_PATH := "user://portable-data"
|
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 {
|
enum Mode {
|
||||||
UNRESOLVED,
|
UNRESOLVED,
|
||||||
|
|
@ -226,15 +229,77 @@ func _activate_existing(path: String, expected_id: String, permit_creation: bool
|
||||||
|
|
||||||
|
|
||||||
func _validate_candidate(path: String, create: bool) -> 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.")
|
return _fail("Choose an absolute filesystem folder.")
|
||||||
var project: String = ProjectSettings.globalize_path("res://").trim_suffix("/")
|
var classification: StringName = classify_candidate_path(
|
||||||
if path == project or path.begins_with(project + "/"):
|
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.")
|
return _fail("The project folder cannot be used as the player data folder.")
|
||||||
if not DirAccess.dir_exists_absolute(path):
|
if classification == PATH_INSTALLATION:
|
||||||
if not create or DirAccess.make_dir_recursive_absolute(path) != OK:
|
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 _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:
|
func _test_writable(path: String) -> bool:
|
||||||
|
|
@ -340,7 +405,12 @@ func _directory_is_empty(path: String) -> bool:
|
||||||
|
|
||||||
|
|
||||||
func _normalize(path: String) -> String:
|
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:
|
func _read_json(path: String, maximum := 1024 * 1024) -> Dictionary:
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
NETfishing
|
NETfishing
|
||||||
v0.3.0-prealpha.2
|
v0.3.0-prealpha.3
|
||||||
Pre-Alpha 0.3
|
Pre-Alpha 0.3
|
||||||
|
|
||||||
Thank you for trying this early private playtest.
|
Thank you for trying this early private playtest.
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ config_version=5
|
||||||
[application]
|
[application]
|
||||||
|
|
||||||
config/name="NETFISHING"
|
config/name="NETFISHING"
|
||||||
config/version="0.3.0-prealpha.2"
|
config/version="0.3.0-prealpha.3"
|
||||||
run/main_scene="res://main/main.tscn"
|
run/main_scene="res://main/main.tscn"
|
||||||
config/features=PackedStringArray("4.7", "GL Compatibility")
|
config/features=PackedStringArray("4.7", "GL Compatibility")
|
||||||
config/icon="res://icon.svg"
|
config/icon="res://icon.svg"
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,12 @@ set -euo pipefail
|
||||||
|
|
||||||
readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
readonly PROJECT_ROOT="$(cd -- "${SCRIPT_DIR}/.." && 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 WINDOWS_DIR="${BUILD_ROOT}/windows-x86_64"
|
||||||
readonly LINUX_DIR="${BUILD_ROOT}/linux-x86_64"
|
readonly LINUX_DIR="${BUILD_ROOT}/linux-x86_64"
|
||||||
readonly README_SOURCE="${PROJECT_ROOT}/playtest/README-PLAYTEST.txt"
|
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 WINDOWS_ZIP="${BUILD_ROOT}/NETfishing-v0.3.0-prealpha.3-windows-x86_64.zip"
|
||||||
readonly LINUX_ZIP="${BUILD_ROOT}/NETfishing-v0.3.0-prealpha.2-linux-x86_64.zip"
|
readonly LINUX_ZIP="${BUILD_ROOT}/NETfishing-v0.3.0-prealpha.3-linux-x86_64.zip"
|
||||||
readonly GODOT_BIN="${GODOT_BIN:-godot}"
|
readonly GODOT_BIN="${GODOT_BIN:-godot}"
|
||||||
|
|
||||||
if [[ ! -f "${PROJECT_ROOT}/project.godot" ]]; then
|
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
|
layout_mode = 2
|
||||||
theme_override_colors/font_color = Color(0.682, 0.733, 0.761, 1)
|
theme_override_colors/font_color = Color(0.682, 0.733, 0.761, 1)
|
||||||
theme_override_font_sizes/font_size = 22
|
theme_override_font_sizes/font_size = 22
|
||||||
text = "v0.3.0-prealpha.2"
|
text = "v0.3.0-prealpha.3"
|
||||||
horizontal_alignment = 1
|
horizontal_alignment = 1
|
||||||
|
|
||||||
[node name="Spacer" type="Control" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/Center/MainContent"]
|
[node name="Spacer" type="Control" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/Center/MainContent"]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue