feat:移除了弹窗,服务器添加sls

This commit is contained in:
2026-09-08 22:39:45 +08:00
commit 6a295f9a7a
4082 changed files with 1322534 additions and 0 deletions

View File

@ -0,0 +1,158 @@
CREATE TABLE settings (
id INTEGER NOT NULL CHECK (id = 0),
max_concurrent_downloads INTEGER NOT NULL DEFAULT 10,
max_concurrent_writes INTEGER NOT NULL DEFAULT 10,
theme TEXT NOT NULL DEFAULT 'dark',
default_page TEXT NOT NULL DEFAULT 'home',
collapsed_navigation INTEGER NOT NULL DEFAULT TRUE,
advanced_rendering INTEGER NOT NULL DEFAULT TRUE,
native_decorations INTEGER NOT NULL DEFAULT FALSE,
telemetry INTEGER NOT NULL DEFAULT FALSE,
discord_rpc INTEGER NOT NULL DEFAULT TRUE,
developer_mode INTEGER NOT NULL DEFAULT FALSE,
onboarded INTEGER NOT NULL DEFAULT FALSE,
-- array of strings
extra_launch_args JSONB NOT NULL,
-- array of (string, string)
custom_env_vars JSONB NOT NULL,
mc_memory_max INTEGER NOT NULL DEFAULT 2048,
mc_force_fullscreen INTEGER NOT NULL DEFAULT FALSE,
mc_game_resolution_x INTEGER NOT NULL DEFAULT 854,
mc_game_resolution_y INTEGER NOT NULL DEFAULT 480,
hide_on_process_start INTEGER NOT NULL DEFAULT FALSE,
hook_pre_launch TEXT NULL,
hook_wrapper TEXT NULL,
hook_post_exit TEXT NULL,
custom_dir TEXT NULL,
prev_custom_dir TEXT NULL,
migrated INTEGER NOT NULL DEFAULT FALSE,
PRIMARY KEY (id)
);
INSERT INTO settings (id, extra_launch_args, custom_env_vars) VALUES (0, jsonb_array(), jsonb_array());
CREATE TABLE java_versions (
major_version INTEGER NOT NULL,
full_version TEXT NOT NULL,
architecture TEXT NOT NULL,
path TEXT NOT NULL,
PRIMARY KEY (major_version)
);
CREATE TABLE minecraft_users (
uuid TEXT NOT NULL,
active INTEGER NOT NULL DEFAULT FALSE,
username TEXT NOT NULL,
access_token TEXT NOT NULL,
refresh_token TEXT NOT NULL,
expires INTEGER NOT NULL,
PRIMARY KEY (uuid)
);
CREATE UNIQUE INDEX minecraft_users_active ON minecraft_users(active);
CREATE TABLE minecraft_device_tokens (
id INTEGER NOT NULL CHECK (id = 0),
uuid TEXT NOT NULL,
private_key TEXT NOT NULL,
x TEXT NOT NULL,
y TEXT NOT NULL,
issue_instant INTEGER NOT NULL,
not_after INTEGER NOT NULL,
token TEXT NOT NULL,
display_claims JSONB NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE modrinth_users (
id TEXT NOT NULL,
active INTEGER NOT NULL DEFAULT FALSE,
session_id TEXT NOT NULL,
expires INTEGER NOT NULL,
PRIMARY KEY (id)
);
CREATE UNIQUE INDEX modrinth_users_active ON modrinth_users(active);
CREATE TABLE cache (
id TEXT NOT NULL,
data_type TEXT NOT NULL,
alias TEXT NULL,
data JSONB NULL,
expires INTEGER NOT NULL,
UNIQUE (data_type, alias),
PRIMARY KEY (id, data_type)
);
CREATE TABLE profiles (
path TEXT NOT NULL,
install_stage TEXT NOT NULL,
name TEXT NOT NULL,
icon_path TEXT NULL,
game_version TEXT NOT NULL,
mod_loader TEXT NOT NULL,
mod_loader_version TEXT NULL,
-- array of strings
groups JSONB NOT NULL,
linked_project_id TEXT NULL,
linked_version_id TEXT NULL,
locked INTEGER NULL,
created INTEGER NOT NULL,
modified INTEGER NOT NULL,
last_played INTEGER NULL,
submitted_time_played INTEGER NOT NULL DEFAULT 0,
recent_time_played INTEGER NOT NULL DEFAULT 0,
override_java_path TEXT NULL,
-- array of strings
override_extra_launch_args JSONB NOT NULL,
-- array of (string, string)
override_custom_env_vars JSONB NOT NULL,
override_mc_memory_max INTEGER NULL,
override_mc_force_fullscreen INTEGER NULL,
override_mc_game_resolution_x INTEGER NULL,
override_mc_game_resolution_y INTEGER NULL,
override_hook_pre_launch TEXT NULL,
override_hook_wrapper TEXT NULL,
override_hook_post_exit TEXT NULL,
PRIMARY KEY (path)
);
CREATE TABLE processes (
pid INTEGER NOT NULL,
start_time INTEGER NOT NULL,
name TEXT NOT NULL,
executable TEXT NOT NULL,
profile_path TEXT NOT NULL,
post_exit_command TEXT NULL,
UNIQUE (pid),
PRIMARY KEY (pid),
FOREIGN KEY (profile_path) REFERENCES profiles(path)
);
CREATE INDEX processes_profile_path ON processes(profile_path);

View File

@ -0,0 +1,2 @@
DROP INDEX modrinth_users_active;
DROP INDEX minecraft_users_active;

View File

@ -0,0 +1 @@
ALTER TABLE settings ADD COLUMN personalized_ads INTEGER NOT NULL DEFAULT TRUE;

View File

@ -0,0 +1,2 @@
ALTER TABLE settings ADD COLUMN toggle_sidebar INTEGER NOT NULL DEFAULT FALSE;
ALTER TABLE settings ADD COLUMN feature_flags JSONB NOT NULL default '{}';

View File

@ -0,0 +1 @@
ALTER TABLE profiles ADD COLUMN protocol_version INTEGER NULL

View File

@ -0,0 +1,10 @@
CREATE TABLE join_log (
profile_path TEXT NOT NULL,
host TEXT NOT NULL,
port INTEGER NOT NULL,
join_time INTEGER NOT NULL,
PRIMARY KEY (profile_path, host, port),
FOREIGN KEY (profile_path) REFERENCES profiles(path) ON DELETE CASCADE
);
CREATE INDEX join_log_profile_path ON join_log(profile_path);

View File

@ -0,0 +1,80 @@
CREATE TABLE default_minecraft_capes (
minecraft_user_uuid TEXT NOT NULL,
id TEXT NOT NULL,
PRIMARY KEY (minecraft_user_uuid, id)
);
-- Emulate a ON UPDATE CASCADE foreign key constraint for the user UUID on the default_minecraft_capes table,
-- but allowing deletion of the user UUID in the minecraft_users table. This allows the application to temporarily
-- keep skin state around for logged-out users, allowing them to retain their skins under the right conditions
CREATE TRIGGER default_minecraft_capes_user_uuid_insert_check
BEFORE INSERT ON default_minecraft_capes FOR EACH ROW
BEGIN
SELECT CASE WHEN NOT EXISTS (
SELECT 1 FROM minecraft_users WHERE uuid = NEW.minecraft_user_uuid
) THEN RAISE(ABORT, 'Cannot add a default cape for an unknown Minecraft user UUID') END;
END;
CREATE TRIGGER default_minecraft_capes_user_uuid_update_check
BEFORE UPDATE ON default_minecraft_capes FOR EACH ROW
BEGIN
SELECT CASE WHEN NOT EXISTS (
SELECT 1 FROM minecraft_users WHERE uuid = NEW.minecraft_user_uuid
) THEN RAISE(ABORT, 'Cannot change a default cape to refer to an unknown Minecraft user UUID') END;
END;
CREATE TRIGGER default_minecraft_capes_user_uuid_update_cascade
AFTER UPDATE OF uuid ON minecraft_users FOR EACH ROW
BEGIN
UPDATE default_minecraft_capes SET minecraft_user_uuid = NEW.uuid WHERE minecraft_user_uuid = OLD.uuid;
END;
CREATE TABLE custom_minecraft_skins (
minecraft_user_uuid TEXT NOT NULL,
texture_key TEXT NOT NULL,
variant TEXT NOT NULL CHECK (variant IN ('CLASSIC', 'SLIM', 'UNKNOWN')),
cape_id TEXT,
PRIMARY KEY (minecraft_user_uuid, texture_key, variant, cape_id),
FOREIGN KEY (texture_key) REFERENCES custom_minecraft_skin_textures(texture_key)
ON DELETE CASCADE ON UPDATE CASCADE
);
-- Similar partial foreign key emulation as above
CREATE TRIGGER custom_minecraft_skins_user_uuid_insert_check
BEFORE INSERT ON custom_minecraft_skins FOR EACH ROW
BEGIN
SELECT CASE WHEN NOT EXISTS (
SELECT 1 FROM minecraft_users WHERE uuid = NEW.minecraft_user_uuid
) THEN RAISE(ABORT, 'Cannot add a custom skin for an unknown Minecraft user UUID') END;
END;
CREATE TRIGGER custom_minecraft_skins_user_uuid_update_check
BEFORE UPDATE ON custom_minecraft_skins FOR EACH ROW
BEGIN
SELECT CASE WHEN NOT EXISTS (
SELECT 1 FROM minecraft_users WHERE uuid = NEW.minecraft_user_uuid
) THEN RAISE(ABORT, 'Cannot change a custom skin to refer to an unknown Minecraft user UUID') END;
END;
CREATE TRIGGER custom_minecraft_skins_user_uuid_update_cascade
AFTER UPDATE OF uuid ON minecraft_users FOR EACH ROW
BEGIN
UPDATE custom_minecraft_skins SET minecraft_user_uuid = NEW.uuid WHERE minecraft_user_uuid = OLD.uuid;
END;
CREATE TABLE custom_minecraft_skin_textures (
texture_key TEXT NOT NULL,
texture PNG BLOB NOT NULL,
PRIMARY KEY (texture_key)
);
CREATE TRIGGER custom_minecraft_skin_texture_delete_cleanup
AFTER DELETE ON custom_minecraft_skins FOR EACH ROW
BEGIN
DELETE FROM custom_minecraft_skin_textures WHERE texture_key NOT IN (
SELECT texture_key FROM custom_minecraft_skins
);
END;

View File

@ -0,0 +1 @@
ALTER TABLE profiles ADD COLUMN launcher_feature_version TEXT NOT NULL DEFAULT 'none'

View File

@ -0,0 +1,10 @@
CREATE TABLE attached_world_data (
profile_path TEXT NOT NULL,
world_type TEXT CHECK ( world_type in ('singleplayer', 'server') ) NOT NULL,
world_id TEXT NOT NULL,
display_status TEXT NOT NULL DEFAULT 'normal',
PRIMARY KEY (profile_path, world_type, world_id),
FOREIGN KEY (profile_path) REFERENCES profiles(path) ON DELETE CASCADE
);
CREATE INDEX attached_world_data_profile_path ON attached_world_data(profile_path);

View File

@ -0,0 +1 @@
ALTER TABLE settings ADD COLUMN hide_nametag_skins_page INTEGER NOT NULL DEFAULT 0 CHECK (hide_nametag_skins_page IN (0, 1));

View File

@ -0,0 +1,6 @@
ALTER TABLE settings
ADD COLUMN skipped_update TEXT NULL;
ALTER TABLE settings
ADD COLUMN pending_update_toast_for_version TEXT NULL;
ALTER TABLE settings
ADD COLUMN auto_download_updates INT NULL;

View File

@ -0,0 +1 @@
ALTER TABLE settings ADD COLUMN version INTEGER NOT NULL DEFAULT 1;

View File

@ -0,0 +1,2 @@
-- Add migration script here
ALTER TABLE settings ADD COLUMN locale TEXT NOT NULL DEFAULT '';

View File

@ -0,0 +1,2 @@
ALTER TABLE attached_world_data ADD COLUMN project_id TEXT;
ALTER TABLE attached_world_data ADD COLUMN content_kind TEXT;

View File

@ -0,0 +1,13 @@
DROP TABLE IF EXISTS default_minecraft_capes;
-- Keep only one saved skin per Minecraft account and texture.
-- variant and cape_id are settings on that saved skin, not part of the skin identity.
DELETE FROM custom_minecraft_skins
WHERE rowid NOT IN (
SELECT MAX(rowid)
FROM custom_minecraft_skins
GROUP BY minecraft_user_uuid, texture_key
);
CREATE UNIQUE INDEX custom_minecraft_skins_one_per_texture
ON custom_minecraft_skins (minecraft_user_uuid, texture_key);

View File

@ -0,0 +1,3 @@
DROP TRIGGER IF EXISTS default_minecraft_capes_user_uuid_insert_check;
DROP TRIGGER IF EXISTS default_minecraft_capes_user_uuid_update_check;
DROP TRIGGER IF EXISTS default_minecraft_capes_user_uuid_update_cascade;

View File

@ -0,0 +1,2 @@
ALTER TABLE profiles
ADD COLUMN preferred_update_channel TEXT NOT NULL DEFAULT 'release';

View File

@ -0,0 +1,13 @@
ALTER TABLE custom_minecraft_skins
ADD COLUMN display_order INTEGER NOT NULL DEFAULT 0;
UPDATE custom_minecraft_skins
SET display_order = (
SELECT COUNT(*)
FROM custom_minecraft_skins AS previous
WHERE previous.minecraft_user_uuid = custom_minecraft_skins.minecraft_user_uuid
AND previous.rowid <= custom_minecraft_skins.rowid
) - 1;
CREATE INDEX custom_minecraft_skins_user_display_order
ON custom_minecraft_skins (minecraft_user_uuid, display_order);

View File

@ -0,0 +1,376 @@
CREATE TABLE instances (
id TEXT NOT NULL,
path TEXT NOT NULL,
applied_content_set_id TEXT NULL,
install_stage TEXT NOT NULL,
launcher_feature_version TEXT NOT NULL,
update_channel TEXT NOT NULL DEFAULT 'release',
name TEXT NOT NULL,
icon_path TEXT NULL,
created INTEGER NOT NULL,
modified INTEGER NOT NULL,
last_played INTEGER NULL,
submitted_time_played INTEGER NOT NULL DEFAULT 0,
recent_time_played INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (id),
UNIQUE (path)
);
CREATE INDEX instances_path ON instances(path);
CREATE INDEX instances_applied_content_set_id
ON instances(applied_content_set_id);
CREATE TABLE instance_links (
instance_id TEXT NOT NULL,
link_kind TEXT NOT NULL,
modrinth_project_id TEXT NULL,
modrinth_version_id TEXT NULL,
server_project_id TEXT NULL,
content_project_id TEXT NULL,
content_version_id TEXT NULL,
hosting_server_id TEXT NULL,
hosting_instance_ids JSONB NULL,
hosting_active_instance_id TEXT NULL,
shared_instance_id TEXT NULL,
PRIMARY KEY (instance_id),
FOREIGN KEY (instance_id) REFERENCES instances(id) ON DELETE CASCADE
);
CREATE INDEX instance_links_link_kind ON instance_links(link_kind);
CREATE INDEX instance_links_modrinth_project_id
ON instance_links(modrinth_project_id);
CREATE INDEX instance_links_server_project_id
ON instance_links(server_project_id);
CREATE INDEX instance_links_hosting_server_id
ON instance_links(hosting_server_id);
CREATE INDEX instance_links_hosting_active_instance_id
ON instance_links(hosting_active_instance_id);
CREATE INDEX instance_links_shared_instance_id
ON instance_links(shared_instance_id);
CREATE TABLE instance_groups (
instance_id TEXT NOT NULL,
group_name TEXT NOT NULL,
PRIMARY KEY (instance_id, group_name),
FOREIGN KEY (instance_id) REFERENCES instances(id) ON DELETE CASCADE
);
CREATE INDEX instance_groups_group_name ON instance_groups(group_name);
CREATE TABLE instance_launch_overrides (
instance_id TEXT NOT NULL,
overrides JSONB NOT NULL,
PRIMARY KEY (instance_id),
FOREIGN KEY (instance_id) REFERENCES instances(id) ON DELETE CASCADE
);
CREATE TABLE instance_content_sets (
id TEXT NOT NULL,
instance_id TEXT NOT NULL,
name TEXT NOT NULL,
source_kind TEXT NOT NULL,
status TEXT NOT NULL,
game_version TEXT NOT NULL,
protocol_version INTEGER NULL,
loader TEXT NOT NULL,
loader_version TEXT NULL,
created INTEGER NOT NULL,
modified INTEGER NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (instance_id) REFERENCES instances(id) ON DELETE CASCADE
);
CREATE INDEX instance_content_sets_instance_id
ON instance_content_sets(instance_id);
CREATE TABLE instance_content_set_remote_refs (
content_set_id TEXT NOT NULL,
ref_type TEXT NOT NULL,
ref_id TEXT NOT NULL,
PRIMARY KEY (content_set_id, ref_type),
FOREIGN KEY (content_set_id)
REFERENCES instance_content_sets(id)
ON DELETE CASCADE
);
CREATE INDEX instance_content_set_remote_refs_ref
ON instance_content_set_remote_refs(ref_type, ref_id);
CREATE TABLE instance_content_set_sync_state (
content_set_id TEXT NOT NULL,
provider TEXT NOT NULL,
applied_update_id TEXT NULL,
latest_available_update_id TEXT NULL,
checked_at INTEGER NULL,
status TEXT NOT NULL,
PRIMARY KEY (content_set_id),
FOREIGN KEY (content_set_id)
REFERENCES instance_content_sets(id)
ON DELETE CASCADE
);
CREATE INDEX instance_content_set_sync_state_provider
ON instance_content_set_sync_state(provider);
CREATE INDEX instance_content_set_sync_state_latest_available_update_id
ON instance_content_set_sync_state(latest_available_update_id);
CREATE TABLE instance_files (
id TEXT NOT NULL,
instance_id TEXT NOT NULL,
relative_path TEXT NOT NULL,
file_name TEXT NOT NULL,
enabled INTEGER NOT NULL,
sha1 TEXT NOT NULL,
size INTEGER NOT NULL,
missing INTEGER NOT NULL DEFAULT 0,
added_at INTEGER NOT NULL,
modified_at INTEGER NOT NULL,
PRIMARY KEY (id),
UNIQUE (instance_id, relative_path),
FOREIGN KEY (instance_id) REFERENCES instances(id) ON DELETE CASCADE
);
CREATE INDEX instance_files_instance_id ON instance_files(instance_id);
CREATE INDEX instance_files_sha1 ON instance_files(sha1);
CREATE INDEX instance_files_missing ON instance_files(missing);
CREATE TABLE instance_content_entries (
id TEXT NOT NULL,
instance_id TEXT NOT NULL,
content_set_id TEXT NOT NULL,
file_id TEXT NULL,
project_type TEXT NOT NULL,
project_id TEXT NULL,
version_id TEXT NULL,
source_kind TEXT NOT NULL,
server_requirement TEXT NOT NULL,
client_requirement TEXT NOT NULL,
enabled INTEGER NOT NULL DEFAULT 1,
added_at INTEGER NOT NULL,
modified_at INTEGER NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (instance_id) REFERENCES instances(id) ON DELETE CASCADE,
FOREIGN KEY (content_set_id)
REFERENCES instance_content_sets(id)
ON DELETE CASCADE,
FOREIGN KEY (file_id) REFERENCES instance_files(id) ON DELETE SET NULL
);
CREATE INDEX instance_content_entries_instance_id
ON instance_content_entries(instance_id);
CREATE INDEX instance_content_entries_content_set_id
ON instance_content_entries(content_set_id);
CREATE INDEX instance_content_entries_file_id
ON instance_content_entries(file_id);
CREATE INDEX instance_content_entries_project_id
ON instance_content_entries(project_id);
CREATE INDEX instance_content_entries_version_id
ON instance_content_entries(version_id);
CREATE INDEX instance_content_entries_source_kind
ON instance_content_entries(source_kind);
CREATE TABLE instance_content_update_checks (
content_entry_id TEXT NOT NULL,
update_channel TEXT NOT NULL,
update_version_id TEXT NULL,
checked_at INTEGER NOT NULL,
PRIMARY KEY (content_entry_id),
FOREIGN KEY (content_entry_id)
REFERENCES instance_content_entries(id)
ON DELETE CASCADE
);
CREATE INDEX instance_content_update_checks_update_version_id
ON instance_content_update_checks(update_version_id);
INSERT INTO instances (
id,
path,
applied_content_set_id,
install_stage,
launcher_feature_version,
update_channel,
name,
icon_path,
created,
modified,
last_played,
submitted_time_played,
recent_time_played
)
SELECT
'legacy:' || path,
path,
'legacy:' || path || ':default',
install_stage,
launcher_feature_version,
preferred_update_channel,
name,
icon_path,
created,
modified,
last_played,
submitted_time_played,
recent_time_played
FROM profiles;
INSERT INTO instance_content_sets (
id,
instance_id,
name,
source_kind,
status,
game_version,
protocol_version,
loader,
loader_version,
created,
modified
)
SELECT
'legacy:' || path || ':default',
'legacy:' || path,
'Default',
CASE
WHEN linked_project_id IS NOT NULL
AND linked_version_id IS NOT NULL
AND linked_version_id != ''
THEN 'modrinth_modpack'
WHEN linked_project_id IS NOT NULL
AND (linked_version_id IS NULL OR linked_version_id = '')
THEN 'server_project'
ELSE 'local'
END,
'available',
game_version,
protocol_version,
mod_loader,
mod_loader_version,
created,
modified
FROM profiles;
INSERT INTO instance_links (
instance_id,
link_kind,
modrinth_project_id,
modrinth_version_id,
server_project_id,
content_project_id,
content_version_id,
hosting_server_id,
hosting_instance_ids,
hosting_active_instance_id,
shared_instance_id
)
SELECT
'legacy:' || path,
CASE
WHEN linked_project_id IS NULL
THEN 'unmanaged'
WHEN linked_version_id IS NULL OR linked_version_id = ''
THEN 'server_project'
ELSE 'modrinth_modpack'
END,
CASE
WHEN linked_version_id IS NOT NULL AND linked_version_id != ''
THEN linked_project_id
ELSE NULL
END,
CASE
WHEN linked_version_id IS NOT NULL AND linked_version_id != ''
THEN linked_version_id
ELSE NULL
END,
CASE
WHEN linked_project_id IS NOT NULL
AND (linked_version_id IS NULL OR linked_version_id = '')
THEN linked_project_id
ELSE NULL
END,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
FROM profiles;
INSERT OR IGNORE INTO instance_groups (instance_id, group_name)
SELECT
'legacy:' || profiles.path,
json_each.value
FROM profiles, json_each(profiles.groups);
INSERT INTO instance_launch_overrides (
instance_id,
overrides
)
SELECT
'legacy:' || path,
jsonb(json_object(
'java_path', override_java_path,
'extra_launch_args', json(CASE
WHEN json_type(override_extra_launch_args) = 'null' THEN 'null'
ELSE json(override_extra_launch_args)
END),
'custom_env_vars', json(CASE
WHEN json_type(override_custom_env_vars) = 'null' THEN 'null'
ELSE json(override_custom_env_vars)
END),
'memory', json(CASE
WHEN override_mc_memory_max IS NULL THEN 'null'
ELSE json_object('maximum', override_mc_memory_max)
END),
'force_fullscreen', json(CASE
WHEN override_mc_force_fullscreen IS NULL THEN 'null'
WHEN override_mc_force_fullscreen = 0 THEN 'false'
ELSE 'true'
END),
'game_resolution', json(CASE
WHEN override_mc_game_resolution_x IS NULL
OR override_mc_game_resolution_y IS NULL
THEN 'null'
ELSE json_array(
override_mc_game_resolution_x,
override_mc_game_resolution_y
)
END),
'hooks', json_object(
'pre_launch', COALESCE(override_hook_pre_launch, ''),
'wrapper', COALESCE(override_hook_wrapper, ''),
'post_exit', COALESCE(override_hook_post_exit, '')
)
))
FROM profiles;

View File

@ -0,0 +1,129 @@
CREATE TEMP TABLE finish_instance_profile_migration_validation (
error_count INTEGER NOT NULL CHECK (error_count = 0)
);
INSERT INTO finish_instance_profile_migration_validation (error_count)
SELECT COUNT(*)
FROM profiles
LEFT JOIN instances ON instances.path = profiles.path
WHERE instances.id IS NULL;
INSERT INTO finish_instance_profile_migration_validation (error_count)
SELECT COUNT(*)
FROM instances
LEFT JOIN instance_content_sets
ON instance_content_sets.id = instances.applied_content_set_id
AND instance_content_sets.instance_id = instances.id
WHERE instances.applied_content_set_id IS NULL
OR instance_content_sets.id IS NULL;
DROP TABLE finish_instance_profile_migration_validation;
DROP INDEX processes_profile_path;
ALTER TABLE processes RENAME TO processes_old;
CREATE TABLE processes (
pid INTEGER NOT NULL,
start_time INTEGER NOT NULL,
name TEXT NOT NULL,
executable TEXT NOT NULL,
instance_id TEXT NOT NULL,
post_exit_command TEXT NULL,
UNIQUE (pid),
PRIMARY KEY (pid),
FOREIGN KEY (instance_id) REFERENCES instances(id) ON DELETE CASCADE
);
INSERT INTO processes (
pid,
start_time,
name,
executable,
instance_id,
post_exit_command
)
SELECT
processes_old.pid,
processes_old.start_time,
processes_old.name,
processes_old.executable,
instances.id,
processes_old.post_exit_command
FROM processes_old
JOIN instances ON instances.path = processes_old.profile_path;
DROP TABLE processes_old;
CREATE INDEX processes_instance_id ON processes(instance_id);
DROP INDEX join_log_profile_path;
ALTER TABLE join_log RENAME TO join_log_old;
CREATE TABLE join_log (
instance_id TEXT NOT NULL,
host TEXT NOT NULL,
port INTEGER NOT NULL,
join_time INTEGER NOT NULL,
PRIMARY KEY (instance_id, host, port),
FOREIGN KEY (instance_id) REFERENCES instances(id) ON DELETE CASCADE
);
INSERT INTO join_log (
instance_id,
host,
port,
join_time
)
SELECT
instances.id,
join_log_old.host,
join_log_old.port,
join_log_old.join_time
FROM join_log_old
JOIN instances ON instances.path = join_log_old.profile_path;
DROP TABLE join_log_old;
CREATE INDEX join_log_instance_id ON join_log(instance_id);
DROP INDEX attached_world_data_profile_path;
ALTER TABLE attached_world_data RENAME TO attached_world_data_old;
CREATE TABLE attached_world_data (
instance_id TEXT NOT NULL,
world_type TEXT CHECK ( world_type in ('singleplayer', 'server') ) NOT NULL,
world_id TEXT NOT NULL,
display_status TEXT NOT NULL DEFAULT 'normal',
project_id TEXT NULL,
content_kind TEXT NULL,
PRIMARY KEY (instance_id, world_type, world_id),
FOREIGN KEY (instance_id) REFERENCES instances(id) ON DELETE CASCADE
);
INSERT INTO attached_world_data (
instance_id,
world_type,
world_id,
display_status,
project_id,
content_kind
)
SELECT
instances.id,
attached_world_data_old.world_type,
attached_world_data_old.world_id,
attached_world_data_old.display_status,
attached_world_data_old.project_id,
attached_world_data_old.content_kind
FROM attached_world_data_old
JOIN instances ON instances.path = attached_world_data_old.profile_path;
DROP TABLE attached_world_data_old;
CREATE INDEX attached_world_data_instance_id
ON attached_world_data(instance_id);
DROP TABLE profiles;

View File

@ -0,0 +1,7 @@
CREATE TABLE app_metadata (
key TEXT NOT NULL,
value TEXT NOT NULL,
updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
PRIMARY KEY (key)
);

View File

@ -0,0 +1,19 @@
CREATE TABLE install_jobs (
id TEXT NOT NULL PRIMARY KEY,
instance_id TEXT NULL,
kind TEXT NOT NULL,
status TEXT NOT NULL,
state JSONB NOT NULL,
created INTEGER NOT NULL,
modified INTEGER NOT NULL,
finished INTEGER NULL,
dismissed INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY (instance_id) REFERENCES instances(id) ON DELETE SET NULL
);
CREATE INDEX install_jobs_instance_id ON install_jobs(instance_id);
CREATE INDEX install_jobs_status ON install_jobs(status);
CREATE INDEX install_jobs_finished ON install_jobs(finished);

View File

@ -0,0 +1,21 @@
ALTER TABLE instance_links ADD COLUMN imported_name TEXT NULL;
ALTER TABLE instance_links ADD COLUMN imported_version_number TEXT NULL;
ALTER TABLE instance_links ADD COLUMN imported_filename TEXT NULL;
UPDATE instance_links
SET
link_kind = 'imported_modpack',
imported_name = (
SELECT instances.name
FROM instances
WHERE instances.id = instance_links.instance_id
)
WHERE
link_kind = 'unmanaged'
AND EXISTS (
SELECT 1
FROM instance_content_entries
WHERE
instance_content_entries.instance_id = instance_links.instance_id
AND instance_content_entries.source_kind = 'imported_modpack'
);

View File

@ -0,0 +1,12 @@
UPDATE instances
SET
submitted_time_played = CASE
WHEN submitted_time_played < 0 THEN 0
ELSE submitted_time_played
END,
recent_time_played = CASE
WHEN recent_time_played < 0 THEN 0
ELSE recent_time_played
END
WHERE submitted_time_played < 0
OR recent_time_played < 0;

View File

@ -0,0 +1,3 @@
UPDATE settings
SET telemetry = FALSE,
personalized_ads = FALSE;

View File

@ -0,0 +1 @@
ALTER TABLE settings ADD COLUMN accent_color TEXT NOT NULL DEFAULT 'pink';

View File

@ -0,0 +1,3 @@
ALTER TABLE minecraft_users
ADD COLUMN account_type TEXT NOT NULL DEFAULT 'microsoft'
CHECK (account_type IN ('microsoft', 'offline'));

View File

@ -0,0 +1,33 @@
CREATE TABLE offline_minecraft_skins (
minecraft_user_uuid TEXT NOT NULL,
texture_key TEXT NOT NULL,
variant TEXT NOT NULL CHECK (variant IN ('CLASSIC', 'SLIM', 'UNKNOWN')),
PRIMARY KEY (minecraft_user_uuid)
);
-- Keep offline skin selections when an account is temporarily removed, matching
-- the saved-skin tables, while still rejecting selections for unknown users.
CREATE TRIGGER offline_minecraft_skins_user_uuid_insert_check
BEFORE INSERT ON offline_minecraft_skins FOR EACH ROW
BEGIN
SELECT CASE WHEN NOT EXISTS (
SELECT 1 FROM minecraft_users WHERE uuid = NEW.minecraft_user_uuid
) THEN RAISE(ABORT, 'Cannot add an offline skin for an unknown Minecraft user UUID') END;
END;
CREATE TRIGGER offline_minecraft_skins_user_uuid_update_check
BEFORE UPDATE ON offline_minecraft_skins FOR EACH ROW
BEGIN
SELECT CASE WHEN NOT EXISTS (
SELECT 1 FROM minecraft_users WHERE uuid = NEW.minecraft_user_uuid
) THEN RAISE(ABORT, 'Cannot change an offline skin to refer to an unknown Minecraft user UUID') END;
END;
CREATE TRIGGER offline_minecraft_skins_user_uuid_update_cascade
AFTER UPDATE OF uuid ON minecraft_users FOR EACH ROW
BEGIN
UPDATE offline_minecraft_skins
SET minecraft_user_uuid = NEW.uuid
WHERE minecraft_user_uuid = OLD.uuid;
END;

View File

@ -0,0 +1,3 @@
ALTER TABLE settings ADD COLUMN custom_background_path TEXT NULL;
ALTER TABLE settings ADD COLUMN custom_background_blur INTEGER NOT NULL DEFAULT 12 CHECK (custom_background_blur BETWEEN 0 AND 40);
ALTER TABLE settings ADD COLUMN custom_background_opacity INTEGER NOT NULL DEFAULT 65 CHECK (custom_background_opacity BETWEEN 10 AND 100);

View File

@ -0,0 +1,24 @@
CREATE TABLE translation_settings (
id INTEGER NOT NULL CHECK (id = 0),
provider TEXT NOT NULL DEFAULT 'microsoft',
target_language TEXT NOT NULL DEFAULT '',
mode TEXT NOT NULL DEFAULT 'bilingual',
auto_translate INTEGER NOT NULL DEFAULT FALSE,
style TEXT NOT NULL DEFAULT 'weakened',
openai_base_url TEXT NOT NULL DEFAULT 'https://api.openai.com/v1',
openai_model TEXT NOT NULL DEFAULT 'gpt-4o-mini',
openai_api_key TEXT NULL,
deeplx_base_url TEXT NOT NULL DEFAULT 'https://api.deeplx.org/{{apiKey}}/translate',
deeplx_api_key TEXT NULL,
PRIMARY KEY (id)
);
INSERT INTO translation_settings (id) VALUES (0);
CREATE TABLE translation_cache (
key TEXT NOT NULL PRIMARY KEY,
translation TEXT NOT NULL,
created_at INTEGER NOT NULL
);
CREATE INDEX translation_cache_created_at ON translation_cache(created_at);

View File

@ -0,0 +1,49 @@
ALTER TABLE instance_links
ADD COLUMN curseforge_project_id INTEGER NULL;
ALTER TABLE instance_links
ADD COLUMN curseforge_file_id INTEGER NULL;
CREATE INDEX instance_links_curseforge_project_id
ON instance_links(curseforge_project_id);
CREATE TABLE instance_content_provider_refs (
content_entry_id TEXT NOT NULL,
provider TEXT NOT NULL,
project_id TEXT NOT NULL,
version_id TEXT NULL,
primary_ref INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (content_entry_id, provider),
FOREIGN KEY (content_entry_id)
REFERENCES instance_content_entries(id)
ON DELETE CASCADE,
CHECK (provider IN ('modrinth', 'curseforge')),
CHECK (primary_ref IN (0, 1))
);
CREATE INDEX instance_content_provider_refs_project
ON instance_content_provider_refs(provider, project_id);
CREATE INDEX instance_content_provider_refs_version
ON instance_content_provider_refs(provider, version_id);
CREATE UNIQUE INDEX instance_content_provider_refs_primary
ON instance_content_provider_refs(content_entry_id)
WHERE primary_ref = 1;
INSERT OR IGNORE INTO instance_content_provider_refs (
content_entry_id,
provider,
project_id,
version_id,
primary_ref
)
SELECT
id,
'modrinth',
project_id,
version_id,
1
FROM instance_content_entries
WHERE project_id IS NOT NULL;

View File

@ -0,0 +1,30 @@
ALTER TABLE install_jobs ADD COLUMN provider TEXT NULL;
ALTER TABLE install_jobs ADD COLUMN retry_of TEXT NULL;
ALTER TABLE install_jobs ADD COLUMN files_total INTEGER NULL;
ALTER TABLE install_jobs ADD COLUMN files_completed INTEGER NOT NULL DEFAULT 0;
ALTER TABLE install_jobs ADD COLUMN bytes_total INTEGER NULL;
ALTER TABLE install_jobs ADD COLUMN bytes_downloaded INTEGER NOT NULL DEFAULT 0;
CREATE TABLE install_job_items (
id TEXT NOT NULL,
job_id TEXT NOT NULL,
name TEXT NOT NULL,
project_id TEXT NULL,
version_id TEXT NULL,
item_type TEXT NULL,
status TEXT NOT NULL,
url TEXT NULL,
bytes_total INTEGER NULL,
bytes_downloaded INTEGER NOT NULL DEFAULT 0,
error TEXT NULL,
manual_url TEXT NULL,
created INTEGER NOT NULL,
modified INTEGER NOT NULL,
finished INTEGER NULL,
PRIMARY KEY (job_id, id),
FOREIGN KEY (job_id) REFERENCES install_jobs(id) ON DELETE CASCADE
);
CREATE INDEX install_jobs_provider ON install_jobs(provider);
CREATE INDEX install_job_items_job_id ON install_job_items(job_id);
CREATE INDEX install_job_items_status ON install_job_items(status);

View File

@ -0,0 +1,81 @@
DROP TRIGGER custom_minecraft_skins_user_uuid_insert_check;
DROP TRIGGER custom_minecraft_skins_user_uuid_update_check;
DROP TRIGGER custom_minecraft_skins_user_uuid_update_cascade;
DROP TRIGGER offline_minecraft_skins_user_uuid_insert_check;
DROP TRIGGER offline_minecraft_skins_user_uuid_update_check;
DROP TRIGGER offline_minecraft_skins_user_uuid_update_cascade;
CREATE TABLE minecraft_users_yggdrasil (
uuid TEXT NOT NULL,
active INTEGER NOT NULL DEFAULT FALSE,
username TEXT NOT NULL,
access_token TEXT NOT NULL,
refresh_token TEXT NOT NULL,
expires INTEGER NOT NULL,
account_type TEXT NOT NULL DEFAULT 'microsoft'
CHECK (account_type IN ('microsoft', 'offline', 'yggdrasil')),
yggdrasil_api_root TEXT NOT NULL DEFAULT '',
yggdrasil_server_name TEXT NOT NULL DEFAULT '',
yggdrasil_login TEXT NOT NULL DEFAULT '',
yggdrasil_client_token TEXT NOT NULL DEFAULT '',
PRIMARY KEY (uuid)
);
INSERT INTO minecraft_users_yggdrasil (
uuid, active, username, access_token, refresh_token, expires, account_type
)
SELECT
uuid, active, username, access_token, refresh_token, expires, account_type
FROM minecraft_users;
DROP TABLE minecraft_users;
ALTER TABLE minecraft_users_yggdrasil RENAME TO minecraft_users;
CREATE TRIGGER custom_minecraft_skins_user_uuid_insert_check
BEFORE INSERT ON custom_minecraft_skins FOR EACH ROW
BEGIN
SELECT CASE WHEN NOT EXISTS (
SELECT 1 FROM minecraft_users WHERE uuid = NEW.minecraft_user_uuid
) THEN RAISE(ABORT, 'Cannot add a custom skin for an unknown Minecraft user UUID') END;
END;
CREATE TRIGGER custom_minecraft_skins_user_uuid_update_check
BEFORE UPDATE ON custom_minecraft_skins FOR EACH ROW
BEGIN
SELECT CASE WHEN NOT EXISTS (
SELECT 1 FROM minecraft_users WHERE uuid = NEW.minecraft_user_uuid
) THEN RAISE(ABORT, 'Cannot change a custom skin to refer to an unknown Minecraft user UUID') END;
END;
CREATE TRIGGER custom_minecraft_skins_user_uuid_update_cascade
AFTER UPDATE OF uuid ON minecraft_users FOR EACH ROW
BEGIN
UPDATE custom_minecraft_skins
SET minecraft_user_uuid = NEW.uuid
WHERE minecraft_user_uuid = OLD.uuid;
END;
CREATE TRIGGER offline_minecraft_skins_user_uuid_insert_check
BEFORE INSERT ON offline_minecraft_skins FOR EACH ROW
BEGIN
SELECT CASE WHEN NOT EXISTS (
SELECT 1 FROM minecraft_users WHERE uuid = NEW.minecraft_user_uuid
) THEN RAISE(ABORT, 'Cannot add an offline skin for an unknown Minecraft user UUID') END;
END;
CREATE TRIGGER offline_minecraft_skins_user_uuid_update_check
BEFORE UPDATE ON offline_minecraft_skins FOR EACH ROW
BEGIN
SELECT CASE WHEN NOT EXISTS (
SELECT 1 FROM minecraft_users WHERE uuid = NEW.minecraft_user_uuid
) THEN RAISE(ABORT, 'Cannot change an offline skin to refer to an unknown Minecraft user UUID') END;
END;
CREATE TRIGGER offline_minecraft_skins_user_uuid_update_cascade
AFTER UPDATE OF uuid ON minecraft_users FOR EACH ROW
BEGIN
UPDATE offline_minecraft_skins
SET minecraft_user_uuid = NEW.uuid
WHERE minecraft_user_uuid = OLD.uuid;
END;

View File

@ -0,0 +1,3 @@
ALTER TABLE settings ADD COLUMN use_minecraft_mirror INTEGER NOT NULL DEFAULT FALSE;
ALTER TABLE settings ADD COLUMN use_modrinth_mirror INTEGER NOT NULL DEFAULT FALSE;
ALTER TABLE settings ADD COLUMN use_curseforge_mirror INTEGER NOT NULL DEFAULT TRUE;

View File

@ -0,0 +1,3 @@
UPDATE settings
SET use_modrinth_mirror = TRUE
WHERE use_modrinth_mirror = FALSE;

View File

@ -0,0 +1,50 @@
ALTER TABLE settings ADD COLUMN auto_concurrent_downloads INTEGER NOT NULL DEFAULT TRUE;
ALTER TABLE settings
ADD COLUMN minecraft_metadata_source TEXT NOT NULL DEFAULT 'auto' CHECK (
minecraft_metadata_source IN ('auto', 'official_only', 'mirror_preferred')
);
ALTER TABLE settings
ADD COLUMN minecraft_file_source TEXT NOT NULL DEFAULT 'auto' CHECK (
minecraft_file_source IN ('auto', 'official_only', 'mirror_preferred')
);
ALTER TABLE settings
ADD COLUMN modrinth_source TEXT NOT NULL DEFAULT 'auto' CHECK (
modrinth_source IN ('auto', 'official_only', 'mirror_preferred')
);
ALTER TABLE settings
ADD COLUMN curseforge_source TEXT NOT NULL DEFAULT 'auto' CHECK (
curseforge_source IN ('auto', 'official_only', 'mirror_preferred')
);
UPDATE settings
SET
auto_concurrent_downloads = TRUE,
max_concurrent_downloads = MIN(MAX(max_concurrent_downloads, 1), 64),
minecraft_metadata_source = CASE
WHEN use_minecraft_mirror = FALSE
AND use_modrinth_mirror = TRUE
AND use_curseforge_mirror = TRUE THEN 'auto'
WHEN use_minecraft_mirror = TRUE THEN 'mirror_preferred'
ELSE 'official_only'
END,
minecraft_file_source = CASE
WHEN use_minecraft_mirror = FALSE
AND use_modrinth_mirror = TRUE
AND use_curseforge_mirror = TRUE THEN 'auto'
WHEN use_minecraft_mirror = TRUE THEN 'mirror_preferred'
ELSE 'official_only'
END,
modrinth_source = CASE
WHEN use_minecraft_mirror = FALSE
AND use_modrinth_mirror = TRUE
AND use_curseforge_mirror = TRUE THEN 'auto'
WHEN use_modrinth_mirror = TRUE THEN 'mirror_preferred'
ELSE 'official_only'
END,
curseforge_source = CASE
WHEN use_minecraft_mirror = FALSE
AND use_modrinth_mirror = TRUE
AND use_curseforge_mirror = TRUE THEN 'auto'
WHEN use_curseforge_mirror = TRUE THEN 'mirror_preferred'
ELSE 'official_only'
END;

View File

@ -0,0 +1,2 @@
ALTER TABLE install_job_items ADD COLUMN attempt INTEGER NULL;
ALTER TABLE install_job_items ADD COLUMN max_attempts INTEGER NULL;

View File

@ -0,0 +1,6 @@
UPDATE settings
SET
minecraft_metadata_source = 'auto',
minecraft_file_source = 'auto',
modrinth_source = 'auto',
curseforge_source = 'auto';

View File

@ -0,0 +1,2 @@
UPDATE settings
SET max_concurrent_downloads = MIN(MAX(max_concurrent_downloads, 1), 256);

View File

@ -0,0 +1,5 @@
ALTER TABLE settings ADD COLUMN onboarding_version INTEGER NOT NULL DEFAULT 0;
ALTER TABLE settings ADD COLUMN onboarding_instance_tour_completed INTEGER NOT NULL DEFAULT TRUE;
UPDATE settings
SET onboarding_instance_tour_completed = CASE WHEN onboarded = 1 THEN TRUE ELSE FALSE END;

View File

@ -0,0 +1,10 @@
CREATE TABLE discovered_javas (
path TEXT NOT NULL PRIMARY KEY,
major_version INTEGER NOT NULL,
full_version TEXT NOT NULL,
architecture TEXT NOT NULL,
file_size INTEGER NOT NULL,
file_mtime_ms INTEGER NOT NULL
);
CREATE INDEX discovered_javas_major_version ON discovered_javas (major_version);

View File

@ -0,0 +1,2 @@
ALTER TABLE instances
ADD COLUMN symlink_target TEXT;

View File

@ -0,0 +1 @@
ALTER TABLE settings ADD COLUMN sidebar_instance_count INTEGER NOT NULL DEFAULT 0;

View File

@ -0,0 +1,6 @@
-- Add local_mod_data column to instance_files for storing manually
-- extracted mod metadata (fabric.mod.json / quilt.mod.json / mods.toml
-- / neoforge.mods.toml / mcmod.info) when Modrinth hash lookup fails.
ALTER TABLE instance_files
ADD COLUMN local_mod_data TEXT NULL;

View File

@ -0,0 +1 @@
ALTER TABLE settings ADD COLUMN mc_memory_auto INTEGER NOT NULL DEFAULT FALSE;

View File

@ -0,0 +1 @@
ALTER TABLE settings ADD COLUMN mc_memory_optimize INTEGER NOT NULL DEFAULT FALSE;

View File

@ -0,0 +1 @@
ALTER TABLE translation_settings ADD COLUMN openai_system_prompt TEXT NOT NULL DEFAULT '';

View File

@ -0,0 +1 @@
ALTER TABLE settings ADD COLUMN auto_set_java_high_performance_mode INTEGER NOT NULL DEFAULT TRUE;

View File

@ -0,0 +1,2 @@
ALTER TABLE settings ADD COLUMN transparent_background INTEGER NOT NULL DEFAULT FALSE;
ALTER TABLE settings ADD COLUMN transparent_background_opacity INTEGER NOT NULL DEFAULT 55;

View File

@ -0,0 +1 @@
ALTER TABLE settings ADD COLUMN transparent_background_blur INTEGER NOT NULL DEFAULT FALSE;

View File

@ -0,0 +1,16 @@
CREATE TABLE IF NOT EXISTS java_versions_new (
major_version INTEGER NOT NULL,
full_version TEXT NOT NULL,
architecture TEXT NOT NULL,
path TEXT NOT NULL PRIMARY KEY,
distribution TEXT
);
INSERT OR REPLACE INTO java_versions_new (major_version, full_version, architecture, path, distribution)
SELECT major_version, full_version, architecture, path, NULL FROM java_versions
GROUP BY path;
DROP TABLE IF EXISTS java_versions;
ALTER TABLE java_versions_new RENAME TO java_versions;
CREATE INDEX IF NOT EXISTS idx_java_versions_major_version ON java_versions(major_version);

View File

@ -0,0 +1 @@
ALTER TABLE discovered_javas ADD COLUMN distribution TEXT;

View File

@ -0,0 +1,15 @@
ALTER TABLE instances
ADD COLUMN pinned_at INTEGER NULL;
CREATE TABLE instance_daily_playtime (
played_on TEXT NOT NULL,
instance_id TEXT NOT NULL,
instance_name TEXT NOT NULL,
played_seconds INTEGER NOT NULL DEFAULT 0,
session_count INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (played_on, instance_id)
);
CREATE INDEX instance_daily_playtime_played_on
ON instance_daily_playtime(played_on);

View File

@ -0,0 +1,7 @@
ALTER TABLE settings
ADD COLUMN home_layout TEXT NOT NULL DEFAULT 'standard'
CHECK (home_layout IN ('standard', 'minimal'));
ALTER TABLE settings
ADD COLUMN minimal_home_instance_id TEXT NULL
REFERENCES instances(id) ON DELETE SET NULL;

View File

@ -0,0 +1 @@
ALTER TABLE settings ADD COLUMN auto_hide_downloads_button INTEGER NOT NULL DEFAULT FALSE;

View File

@ -0,0 +1,242 @@
ALTER TABLE instance_content_provider_refs
RENAME TO instance_content_provider_refs_legacy;
ALTER TABLE instance_content_update_checks
RENAME TO instance_content_update_checks_legacy;
ALTER TABLE instance_content_entries
RENAME TO instance_content_entries_legacy;
-- SQLite keeps index names unchanged when a table is renamed. Drop the
-- legacy indexes before creating their provider-qualified replacements;
-- otherwise the new CREATE INDEX statements fail with "already exists".
DROP INDEX IF EXISTS instance_content_entries_instance_id;
DROP INDEX IF EXISTS instance_content_entries_content_set_id;
DROP INDEX IF EXISTS instance_content_entries_file_id;
DROP INDEX IF EXISTS instance_content_entries_project_id;
DROP INDEX IF EXISTS instance_content_entries_version_id;
DROP INDEX IF EXISTS instance_content_entries_source_kind;
DROP INDEX IF EXISTS instance_content_update_checks_update_version_id;
DROP INDEX IF EXISTS instance_content_provider_refs_project;
DROP INDEX IF EXISTS instance_content_provider_refs_version;
DROP INDEX IF EXISTS instance_content_provider_refs_primary;
CREATE TABLE instance_content_entries (
id TEXT NOT NULL,
instance_id TEXT NOT NULL,
content_set_id TEXT NOT NULL,
file_id TEXT NULL,
project_type TEXT NOT NULL,
source_kind TEXT NOT NULL,
server_requirement TEXT NOT NULL,
client_requirement TEXT NOT NULL,
enabled INTEGER NOT NULL DEFAULT 1,
added_at INTEGER NOT NULL,
modified_at INTEGER NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (instance_id) REFERENCES instances(id) ON DELETE CASCADE,
FOREIGN KEY (content_set_id)
REFERENCES instance_content_sets(id)
ON DELETE CASCADE,
FOREIGN KEY (file_id) REFERENCES instance_files(id) ON DELETE SET NULL
);
CREATE INDEX IF NOT EXISTS instance_content_entries_instance_id
ON instance_content_entries(instance_id);
CREATE INDEX IF NOT EXISTS instance_content_entries_content_set_id
ON instance_content_entries(content_set_id);
CREATE INDEX IF NOT EXISTS instance_content_entries_file_id
ON instance_content_entries(file_id);
CREATE INDEX IF NOT EXISTS instance_content_entries_source_kind
ON instance_content_entries(source_kind);
INSERT INTO instance_content_entries (
id,
instance_id,
content_set_id,
file_id,
project_type,
source_kind,
server_requirement,
client_requirement,
enabled,
added_at,
modified_at
)
SELECT
id,
instance_id,
content_set_id,
file_id,
project_type,
source_kind,
server_requirement,
client_requirement,
enabled,
added_at,
modified_at
FROM instance_content_entries_legacy;
CREATE TABLE instance_content_provider_refs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
content_entry_id TEXT NOT NULL,
provider TEXT NOT NULL,
provider_project_id TEXT NOT NULL,
provider_release_id TEXT NULL,
is_origin INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY (content_entry_id)
REFERENCES instance_content_entries(id)
ON DELETE CASCADE,
CHECK (provider IN ('modrinth', 'curseforge')),
CHECK (is_origin IN (0, 1))
);
CREATE INDEX IF NOT EXISTS instance_content_provider_refs_project
ON instance_content_provider_refs(provider, provider_project_id);
CREATE INDEX IF NOT EXISTS instance_content_provider_refs_release
ON instance_content_provider_refs(provider, provider_release_id);
CREATE UNIQUE INDEX IF NOT EXISTS instance_content_provider_refs_identity
ON instance_content_provider_refs(
content_entry_id,
provider,
provider_project_id,
COALESCE(provider_release_id, '')
);
CREATE UNIQUE INDEX IF NOT EXISTS instance_content_provider_refs_origin
ON instance_content_provider_refs(content_entry_id)
WHERE is_origin = 1;
INSERT OR IGNORE INTO instance_content_provider_refs (
content_entry_id,
provider,
provider_project_id,
provider_release_id,
is_origin
)
SELECT
content_entry_id,
provider,
project_id,
version_id,
primary_ref
FROM instance_content_provider_refs_legacy
WHERE length(trim(project_id)) > 0
AND (
provider = 'modrinth'
OR (
provider = 'curseforge'
AND project_id NOT GLOB '*[^0-9]*'
AND CAST(project_id AS INTEGER) > 0
AND (
version_id IS NULL
OR (
version_id NOT GLOB '*[^0-9]*'
AND CAST(version_id AS INTEGER) > 0
)
)
)
);
INSERT OR IGNORE INTO instance_content_provider_refs (
content_entry_id,
provider,
provider_project_id,
provider_release_id,
is_origin
)
SELECT
legacy.id,
'curseforge',
legacy.project_id,
legacy.version_id,
1
FROM instance_content_entries_legacy legacy
WHERE legacy.source_kind = 'curseforge'
AND legacy.project_id IS NOT NULL
AND legacy.project_id NOT GLOB '*[^0-9]*'
AND CAST(legacy.project_id AS INTEGER) > 0
AND NOT EXISTS (
SELECT 1
FROM instance_content_provider_refs ref
WHERE ref.content_entry_id = legacy.id
AND ref.provider = 'curseforge'
);
INSERT OR IGNORE INTO instance_content_provider_refs (
content_entry_id,
provider,
provider_project_id,
provider_release_id,
is_origin
)
SELECT
legacy.id,
'modrinth',
json_extract(cache.data, '$.project_id'),
json_extract(cache.data, '$.version_id'),
CASE
WHEN legacy.source_kind IN ('modrinth_modpack', 'modrinth_hosting')
THEN 1
ELSE 0
END
FROM instance_content_entries_legacy legacy
INNER JOIN instance_files file ON file.id = legacy.file_id
INNER JOIN cache
ON cache.data_type = 'file_hash'
AND json_extract(cache.data, '$.path') =
legacy.instance_id || '/' || file.relative_path
WHERE json_extract(cache.data, '$.project_id') IS NOT NULL
AND json_extract(cache.data, '$.version_id') IS NOT NULL;
CREATE TABLE instance_content_update_checks (
content_entry_id TEXT NOT NULL,
update_channel TEXT NOT NULL,
provider TEXT NULL,
provider_project_id TEXT NULL,
provider_release_id TEXT NULL,
checked_at INTEGER NOT NULL,
PRIMARY KEY (content_entry_id),
FOREIGN KEY (content_entry_id)
REFERENCES instance_content_entries(id)
ON DELETE CASCADE,
CHECK (provider IS NULL OR provider IN ('modrinth', 'curseforge'))
);
CREATE INDEX IF NOT EXISTS instance_content_update_checks_release
ON instance_content_update_checks(provider, provider_release_id);
INSERT INTO instance_content_update_checks (
content_entry_id,
update_channel,
provider,
provider_project_id,
provider_release_id,
checked_at
)
SELECT
legacy.content_entry_id,
legacy.update_channel,
ref.provider,
ref.provider_project_id,
CASE
WHEN legacy.update_version_id IS NULL THEN NULL
ELSE legacy.update_version_id
END,
legacy.checked_at
FROM instance_content_update_checks_legacy legacy
LEFT JOIN instance_content_provider_refs ref
ON ref.content_entry_id = legacy.content_entry_id
AND ref.is_origin = 1
WHERE ref.provider = 'modrinth';
DROP TABLE instance_content_provider_refs_legacy;
DROP TABLE instance_content_update_checks_legacy;
DROP TABLE instance_content_entries_legacy;

View File

@ -0,0 +1,187 @@
ALTER TABLE instance_content_provider_refs
RENAME TO instance_content_provider_refs_single_legacy;
DROP INDEX IF EXISTS instance_content_provider_refs_project;
DROP INDEX IF EXISTS instance_content_provider_refs_release;
DROP INDEX IF EXISTS instance_content_provider_refs_origin;
DROP INDEX IF EXISTS instance_content_provider_refs_identity;
CREATE TABLE instance_content_provider_refs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
content_entry_id TEXT NOT NULL,
provider TEXT NOT NULL,
provider_project_id TEXT NOT NULL,
provider_release_id TEXT NULL,
is_origin INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY (content_entry_id)
REFERENCES instance_content_entries(id)
ON DELETE CASCADE,
CHECK (provider IN ('modrinth', 'curseforge')),
CHECK (is_origin IN (0, 1))
);
CREATE INDEX instance_content_provider_refs_project
ON instance_content_provider_refs(provider, provider_project_id);
CREATE INDEX instance_content_provider_refs_release
ON instance_content_provider_refs(provider, provider_release_id);
CREATE UNIQUE INDEX instance_content_provider_refs_identity
ON instance_content_provider_refs(
content_entry_id,
provider,
provider_project_id,
COALESCE(provider_release_id, '')
);
CREATE UNIQUE INDEX instance_content_provider_refs_origin
ON instance_content_provider_refs(content_entry_id)
WHERE is_origin = 1;
INSERT OR IGNORE INTO instance_content_provider_refs (
content_entry_id,
provider,
provider_project_id,
provider_release_id,
is_origin
)
SELECT
content_entry_id,
provider,
provider_project_id,
provider_release_id,
is_origin
FROM instance_content_provider_refs_single_legacy
WHERE length(trim(provider_project_id)) > 0
AND (
(
provider = 'modrinth'
AND (
provider_release_id IS NULL
OR length(trim(provider_release_id)) > 0
)
)
OR (
provider = 'curseforge'
AND provider_project_id NOT GLOB '*[^0-9]*'
AND CAST(provider_project_id AS INTEGER) > 0
AND (
provider_release_id IS NULL
OR (
provider_release_id NOT GLOB '*[^0-9]*'
AND CAST(provider_release_id AS INTEGER) > 0
)
)
)
);
UPDATE instance_content_provider_refs
SET is_origin = 0
WHERE content_entry_id IN (
SELECT id
FROM instance_content_entries
WHERE source_kind = 'curseforge'
);
INSERT OR IGNORE INTO instance_content_provider_refs (
content_entry_id,
provider,
provider_project_id,
provider_release_id,
is_origin
)
SELECT
ref.content_entry_id,
'curseforge',
ref.provider_project_id,
ref.provider_release_id,
0
FROM instance_content_provider_refs ref
INNER JOIN instance_content_entries entry
ON entry.id = ref.content_entry_id
WHERE entry.source_kind = 'curseforge'
AND ref.provider = 'modrinth'
AND ref.provider_project_id NOT GLOB '*[^0-9]*'
AND CAST(ref.provider_project_id AS INTEGER) > 0
AND (
ref.provider_release_id IS NULL
OR (
ref.provider_release_id NOT GLOB '*[^0-9]*'
AND CAST(ref.provider_release_id AS INTEGER) > 0
)
)
AND NOT EXISTS (
SELECT 1
FROM instance_content_provider_refs existing
WHERE existing.content_entry_id = ref.content_entry_id
AND existing.provider = 'curseforge'
)
AND ref.id = (
SELECT MIN(candidate.id)
FROM instance_content_provider_refs candidate
WHERE candidate.content_entry_id = ref.content_entry_id
AND candidate.provider = 'modrinth'
AND candidate.provider_project_id NOT GLOB '*[^0-9]*'
AND CAST(candidate.provider_project_id AS INTEGER) > 0
AND (
candidate.provider_release_id IS NULL
OR (
candidate.provider_release_id NOT GLOB '*[^0-9]*'
AND CAST(candidate.provider_release_id AS INTEGER) > 0
)
)
);
UPDATE instance_content_provider_refs
SET is_origin = 1
WHERE id IN (
SELECT MIN(ref.id)
FROM instance_content_provider_refs ref
INNER JOIN instance_content_entries entry
ON entry.id = ref.content_entry_id
WHERE entry.source_kind = 'curseforge'
AND ref.provider = 'curseforge'
GROUP BY ref.content_entry_id
);
DELETE FROM instance_content_provider_refs
WHERE provider = 'modrinth'
AND content_entry_id IN (
SELECT id
FROM instance_content_entries
WHERE source_kind = 'curseforge'
)
AND NOT EXISTS (
SELECT 1
FROM instance_content_entries entry
INNER JOIN instance_files file ON file.id = entry.file_id
INNER JOIN cache
ON cache.data_type = 'file_hash'
AND json_extract(
CASE WHEN json_valid(cache.data) THEN cache.data ELSE '{}' END,
'$.path'
) =
entry.instance_id || '/' || file.relative_path
WHERE entry.id = instance_content_provider_refs.content_entry_id
AND json_extract(
CASE WHEN json_valid(cache.data) THEN cache.data ELSE '{}' END,
'$.project_id'
) =
instance_content_provider_refs.provider_project_id
AND json_extract(
CASE WHEN json_valid(cache.data) THEN cache.data ELSE '{}' END,
'$.version_id'
) IS
instance_content_provider_refs.provider_release_id
);
DELETE FROM instance_content_update_checks
WHERE provider = 'modrinth'
AND content_entry_id IN (
SELECT id
FROM instance_content_entries
WHERE source_kind = 'curseforge'
);
DROP TABLE instance_content_provider_refs_single_legacy;

View File

@ -0,0 +1,16 @@
CREATE UNIQUE INDEX java_versions_major_version_path
ON java_versions(major_version, path);
CREATE TABLE java_default_versions (
major_version INTEGER NOT NULL PRIMARY KEY,
path TEXT NOT NULL,
FOREIGN KEY (major_version, path)
REFERENCES java_versions(major_version, path)
ON DELETE CASCADE
ON UPDATE CASCADE
);
INSERT INTO java_default_versions (major_version, path)
SELECT major_version, MIN(path)
FROM java_versions
GROUP BY major_version;

View File

@ -0,0 +1,5 @@
-- Cache extracted icons for unmatched Mod / ResourcePack files so the
-- content list can show real artwork without reopening archives on every
-- render. NULL = not attempted yet, '' = attempted with no icon found.
ALTER TABLE instance_files
ADD COLUMN icon_path TEXT NULL;

View File

@ -0,0 +1,75 @@
CREATE TABLE settings_with_official_preferred (
id INTEGER NOT NULL CHECK (id = 0),
max_concurrent_downloads INTEGER NOT NULL DEFAULT 10,
max_concurrent_writes INTEGER NOT NULL DEFAULT 10,
theme TEXT NOT NULL DEFAULT 'dark',
default_page TEXT NOT NULL DEFAULT 'home',
collapsed_navigation INTEGER NOT NULL DEFAULT TRUE,
advanced_rendering INTEGER NOT NULL DEFAULT TRUE,
native_decorations INTEGER NOT NULL DEFAULT FALSE,
telemetry INTEGER NOT NULL DEFAULT FALSE,
discord_rpc INTEGER NOT NULL DEFAULT TRUE,
developer_mode INTEGER NOT NULL DEFAULT FALSE,
onboarded INTEGER NOT NULL DEFAULT FALSE,
extra_launch_args JSONB NOT NULL,
custom_env_vars JSONB NOT NULL,
mc_memory_max INTEGER NOT NULL DEFAULT 2048,
mc_force_fullscreen INTEGER NOT NULL DEFAULT FALSE,
mc_game_resolution_x INTEGER NOT NULL DEFAULT 854,
mc_game_resolution_y INTEGER NOT NULL DEFAULT 480,
hide_on_process_start INTEGER NOT NULL DEFAULT FALSE,
hook_pre_launch TEXT NULL,
hook_wrapper TEXT NULL,
hook_post_exit TEXT NULL,
custom_dir TEXT NULL,
prev_custom_dir TEXT NULL,
migrated INTEGER NOT NULL DEFAULT FALSE,
personalized_ads INTEGER NOT NULL DEFAULT TRUE,
toggle_sidebar INTEGER NOT NULL DEFAULT FALSE,
feature_flags JSONB NOT NULL DEFAULT '{}',
hide_nametag_skins_page INTEGER NOT NULL DEFAULT 0 CHECK (hide_nametag_skins_page IN (0, 1)),
skipped_update TEXT NULL,
pending_update_toast_for_version TEXT NULL,
auto_download_updates INT NULL,
version INTEGER NOT NULL DEFAULT 1,
locale TEXT NOT NULL DEFAULT '',
accent_color TEXT NOT NULL DEFAULT 'pink',
custom_background_path TEXT NULL,
custom_background_blur INTEGER NOT NULL DEFAULT 12 CHECK (custom_background_blur BETWEEN 0 AND 40),
custom_background_opacity INTEGER NOT NULL DEFAULT 65 CHECK (custom_background_opacity BETWEEN 10 AND 100),
use_minecraft_mirror INTEGER NOT NULL DEFAULT FALSE,
use_modrinth_mirror INTEGER NOT NULL DEFAULT FALSE,
use_curseforge_mirror INTEGER NOT NULL DEFAULT TRUE,
auto_concurrent_downloads INTEGER NOT NULL DEFAULT TRUE,
minecraft_metadata_source TEXT NOT NULL DEFAULT 'auto' CHECK (
minecraft_metadata_source IN ('auto', 'official_only', 'mirror_preferred', 'official_preferred')
),
minecraft_file_source TEXT NOT NULL DEFAULT 'auto' CHECK (
minecraft_file_source IN ('auto', 'official_only', 'mirror_preferred', 'official_preferred')
),
modrinth_source TEXT NOT NULL DEFAULT 'auto' CHECK (
modrinth_source IN ('auto', 'official_only', 'mirror_preferred', 'official_preferred')
),
curseforge_source TEXT NOT NULL DEFAULT 'auto' CHECK (
curseforge_source IN ('auto', 'official_only', 'mirror_preferred', 'official_preferred')
),
onboarding_version INTEGER NOT NULL DEFAULT 0,
onboarding_instance_tour_completed INTEGER NOT NULL DEFAULT TRUE,
sidebar_instance_count INTEGER NOT NULL DEFAULT 0,
mc_memory_auto INTEGER NOT NULL DEFAULT FALSE,
mc_memory_optimize INTEGER NOT NULL DEFAULT FALSE,
auto_set_java_high_performance_mode INTEGER NOT NULL DEFAULT TRUE,
transparent_background INTEGER NOT NULL DEFAULT FALSE,
transparent_background_opacity INTEGER NOT NULL DEFAULT 55,
transparent_background_blur INTEGER NOT NULL DEFAULT FALSE,
home_layout TEXT NOT NULL DEFAULT 'standard' CHECK (home_layout IN ('standard', 'minimal')),
minimal_home_instance_id TEXT NULL REFERENCES instances(id) ON DELETE SET NULL,
auto_hide_downloads_button INTEGER NOT NULL DEFAULT FALSE,
PRIMARY KEY (id)
);
INSERT INTO settings_with_official_preferred
SELECT * FROM settings;
DROP TABLE settings;
ALTER TABLE settings_with_official_preferred RENAME TO settings;

View File

@ -0,0 +1,3 @@
ALTER TABLE settings
ADD COLUMN use_system_proxy INTEGER NOT NULL DEFAULT FALSE
CHECK (use_system_proxy IN (0, 1));

View File

@ -0,0 +1,192 @@
ALTER TABLE instance_content_sets
ADD COLUMN revision INTEGER NOT NULL DEFAULT 0;
ALTER TABLE instance_content_entries
ADD COLUMN ownership_kind TEXT NOT NULL DEFAULT 'user_added'
CHECK (ownership_kind IN ('pack_managed', 'user_added'));
UPDATE instance_content_entries AS entry
SET ownership_kind = 'pack_managed'
WHERE EXISTS (
SELECT 1
FROM instance_links link
WHERE link.instance_id = entry.instance_id
AND (
(link.link_kind IN ('modrinth_modpack', 'server_project_modpack')
AND entry.source_kind = 'modrinth_modpack')
OR (link.link_kind = 'curseforge_modpack'
AND entry.source_kind = 'curseforge')
OR (link.link_kind = 'imported_modpack'
AND entry.source_kind IN (
'imported_modpack',
'modrinth_modpack',
'curseforge'
))
)
);
CREATE INDEX instance_content_entries_ownership_kind
ON instance_content_entries(content_set_id, ownership_kind);
CREATE TABLE instance_pack_members (
id TEXT NOT NULL,
content_set_id TEXT NOT NULL,
content_entry_id TEXT NULL,
member_key TEXT NOT NULL,
project_type TEXT NOT NULL,
expected_relative_path TEXT NOT NULL,
provider TEXT NULL,
provider_project_id TEXT NULL,
provider_release_id TEXT NULL,
required INTEGER NOT NULL DEFAULT 1,
expected_sha1 TEXT NULL,
expected_size INTEGER NULL,
expected_fingerprint INTEGER NULL,
materialization_state TEXT NOT NULL DEFAULT 'present',
override_kind TEXT NOT NULL DEFAULT 'none',
reconciled INTEGER NOT NULL DEFAULT 1,
created_at INTEGER NOT NULL,
modified_at INTEGER NOT NULL,
PRIMARY KEY (id),
UNIQUE (content_set_id, member_key),
FOREIGN KEY (content_set_id)
REFERENCES instance_content_sets(id)
ON DELETE CASCADE,
FOREIGN KEY (content_entry_id)
REFERENCES instance_content_entries(id)
ON DELETE SET NULL,
CHECK (provider IS NULL OR provider IN ('modrinth', 'curseforge')),
CHECK (required IN (0, 1)),
CHECK (materialization_state IN (
'present',
'pending_manual',
'missing',
'removed'
)),
CHECK (override_kind IN ('none', 'disabled', 'removed', 'version')),
CHECK (reconciled IN (0, 1))
);
CREATE INDEX instance_pack_members_content_set
ON instance_pack_members(content_set_id);
CREATE INDEX instance_pack_members_content_entry
ON instance_pack_members(content_entry_id);
CREATE INDEX instance_pack_members_provider
ON instance_pack_members(provider, provider_project_id, provider_release_id);
CREATE INDEX instance_pack_members_state
ON instance_pack_members(content_set_id, materialization_state);
INSERT INTO instance_pack_members (
id,
content_set_id,
content_entry_id,
member_key,
project_type,
expected_relative_path,
provider,
provider_project_id,
provider_release_id,
required,
expected_sha1,
expected_size,
expected_fingerprint,
materialization_state,
override_kind,
reconciled,
created_at,
modified_at
)
SELECT
'pack-member:' || entry.id,
entry.content_set_id,
entry.id,
CASE
WHEN origin.provider IS NOT NULL THEN
origin.provider || ':' || origin.provider_project_id || ':' || entry.project_type
ELSE 'path:' || lower(replace(file.relative_path, '\\', '/'))
END,
entry.project_type,
file.relative_path,
origin.provider,
origin.provider_project_id,
origin.provider_release_id,
CASE
WHEN entry.server_requirement = 'optional'
AND entry.client_requirement = 'optional'
THEN 0
ELSE 1
END,
file.sha1,
file.size,
NULL,
CASE WHEN file.missing = 1 THEN 'missing' ELSE 'present' END,
CASE
WHEN entry.enabled = 0 OR file.enabled = 0 THEN 'disabled'
ELSE 'none'
END,
CASE
WHEN link.link_kind = 'curseforge_modpack' THEN 0
ELSE 1
END,
entry.added_at,
entry.modified_at
FROM instance_content_entries entry
INNER JOIN instance_files file ON file.id = entry.file_id
INNER JOIN instance_links link ON link.instance_id = entry.instance_id
LEFT JOIN instance_content_provider_refs origin
ON origin.content_entry_id = entry.id
AND origin.is_origin = 1
WHERE entry.ownership_kind = 'pack_managed';
CREATE TABLE instance_pending_manual_downloads (
id TEXT NOT NULL,
instance_id TEXT NOT NULL,
pack_member_id TEXT NULL,
content_entry_id TEXT NULL,
operation_kind TEXT NOT NULL,
operation_target_id TEXT NULL,
project_type TEXT NOT NULL,
provider TEXT NOT NULL,
provider_project_id TEXT NOT NULL,
provider_release_id TEXT NOT NULL,
file_name TEXT NOT NULL,
website_url TEXT NULL,
target_relative_path TEXT NOT NULL,
expected_sha1 TEXT NULL,
expected_size INTEGER NULL,
expected_fingerprint INTEGER NULL,
state TEXT NOT NULL DEFAULT 'waiting',
context JSONB NOT NULL DEFAULT '{}',
created_at INTEGER NOT NULL,
modified_at INTEGER NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (instance_id) REFERENCES instances(id) ON DELETE CASCADE,
FOREIGN KEY (pack_member_id)
REFERENCES instance_pack_members(id)
ON DELETE CASCADE,
FOREIGN KEY (content_entry_id)
REFERENCES instance_content_entries(id)
ON DELETE SET NULL,
CHECK (provider IN ('modrinth', 'curseforge')),
CHECK (operation_kind IN (
'pack_install',
'pack_update',
'content_install',
'content_update'
)),
CHECK (state IN ('waiting', 'matched', 'imported', 'error', 'cancelled'))
);
CREATE UNIQUE INDEX instance_pending_manual_download_identity
ON instance_pending_manual_downloads(
instance_id,
operation_kind,
provider,
provider_project_id,
provider_release_id
)
WHERE state IN ('waiting', 'matched');
CREATE INDEX instance_pending_manual_downloads_instance_state
ON instance_pending_manual_downloads(instance_id, state);

View File

@ -0,0 +1 @@
ALTER TABLE settings DROP COLUMN use_system_proxy;

View File

@ -0,0 +1,2 @@
ALTER TABLE settings
ADD COLUMN home_widgets JSONB NULL;

View File

@ -0,0 +1,93 @@
CREATE TABLE ai_settings (
id INTEGER NOT NULL CHECK (id = 0),
enabled INTEGER NOT NULL DEFAULT TRUE,
PRIMARY KEY (id)
);
INSERT INTO ai_settings (id) VALUES (0);
CREATE TABLE ai_provider_configs (
provider_id TEXT NOT NULL PRIMARY KEY,
custom_name TEXT NULL,
protocol TEXT NOT NULL DEFAULT 'openai',
enabled INTEGER NOT NULL DEFAULT FALSE,
endpoint TEXT NOT NULL DEFAULT '',
settings TEXT NOT NULL DEFAULT '{}'
);
CREATE TABLE ai_provider_models (
provider_id TEXT NOT NULL,
model_id TEXT NOT NULL,
display_name TEXT NOT NULL DEFAULT '',
enabled INTEGER NOT NULL DEFAULT TRUE,
source TEXT NOT NULL DEFAULT 'custom',
PRIMARY KEY (provider_id, model_id)
);
ALTER TABLE translation_settings ADD COLUMN ai_provider_id TEXT NOT NULL DEFAULT '';
ALTER TABLE translation_settings ADD COLUMN ai_model_id TEXT NOT NULL DEFAULT '';
INSERT INTO ai_provider_configs (
provider_id,
custom_name,
protocol,
enabled,
endpoint
)
SELECT
'openai',
'OpenAI Compatible',
'openai',
TRUE,
openai_base_url
FROM translation_settings
WHERE id = 0
AND trim(openai_model) != ''
AND (
lower(trim(openai_base_url)) LIKE 'http://%'
OR lower(trim(openai_base_url)) LIKE 'https://%'
)
AND (
provider = 'openai-compatible'
OR openai_api_key IS NOT NULL
OR openai_base_url != 'https://api.openai.com/v1'
OR openai_model != 'gpt-4o-mini'
OR openai_system_prompt != ''
);
INSERT INTO ai_provider_models (
provider_id,
model_id,
display_name,
enabled,
source
)
SELECT
'openai',
openai_model,
openai_model,
TRUE,
'custom'
FROM translation_settings
WHERE id = 0
AND EXISTS (
SELECT 1
FROM ai_provider_configs
WHERE provider_id = 'openai'
);
UPDATE translation_settings
SET provider = 'ai',
ai_provider_id = 'openai',
ai_model_id = openai_model
WHERE id = 0
AND provider = 'openai-compatible'
AND trim(openai_model) != ''
AND (
lower(trim(openai_base_url)) LIKE 'http://%'
OR lower(trim(openai_base_url)) LIKE 'https://%'
);
UPDATE translation_settings
SET provider = 'microsoft'
WHERE id = 0 AND provider = 'openai-compatible';

View File

@ -0,0 +1,111 @@
ALTER TABLE ai_settings
ADD COLUMN legacy_openai_credential_cleanup INTEGER NOT NULL DEFAULT FALSE;
CREATE TEMP TABLE legacy_openai_ai_config (
provider_id TEXT NOT NULL PRIMARY KEY,
model_id TEXT NOT NULL
);
INSERT INTO legacy_openai_ai_config (provider_id, model_id)
SELECT
config.provider_id,
translation.openai_model
FROM translation_settings AS translation
INNER JOIN ai_provider_configs AS config
ON config.provider_id = 'openai'
AND config.custom_name = 'OpenAI Compatible'
AND config.protocol = 'openai'
AND config.enabled = TRUE
AND config.endpoint = translation.openai_base_url
AND json(config.settings) = json('{}')
INNER JOIN ai_provider_models AS model
ON model.provider_id = config.provider_id
AND model.model_id = translation.openai_model
AND model.display_name = translation.openai_model
AND model.enabled = TRUE
AND model.source = 'custom'
WHERE translation.id = 0;
UPDATE ai_settings
SET legacy_openai_credential_cleanup = TRUE
WHERE id = 0
AND (
EXISTS (SELECT 1 FROM legacy_openai_ai_config)
OR EXISTS (
SELECT 1
FROM translation_settings
WHERE id = 0 AND openai_api_key IS NOT NULL
)
);
DELETE FROM ai_provider_models
WHERE EXISTS (
SELECT 1
FROM legacy_openai_ai_config
WHERE legacy_openai_ai_config.provider_id = ai_provider_models.provider_id
AND legacy_openai_ai_config.model_id = ai_provider_models.model_id
);
DELETE FROM ai_provider_configs
WHERE EXISTS (
SELECT 1
FROM legacy_openai_ai_config
WHERE legacy_openai_ai_config.provider_id = ai_provider_configs.provider_id
)
AND NOT EXISTS (
SELECT 1
FROM ai_provider_models
WHERE ai_provider_models.provider_id = ai_provider_configs.provider_id
);
UPDATE translation_settings
SET provider = CASE
WHEN provider = 'ai'
AND ai_provider_id = 'openai'
AND EXISTS (
SELECT 1
FROM legacy_openai_ai_config
WHERE model_id = ai_model_id
)
THEN 'microsoft'
ELSE provider
END,
ai_provider_id = CASE
WHEN provider = 'ai'
AND ai_provider_id = 'openai'
AND EXISTS (
SELECT 1
FROM legacy_openai_ai_config
WHERE model_id = ai_model_id
)
THEN ''
ELSE ai_provider_id
END,
ai_model_id = CASE
WHEN provider = 'ai'
AND ai_provider_id = 'openai'
AND EXISTS (
SELECT 1
FROM legacy_openai_ai_config
WHERE model_id = ai_model_id
)
THEN ''
ELSE ai_model_id
END,
openai_base_url = 'https://api.openai.com/v1',
openai_model = 'gpt-4o-mini',
openai_api_key = NULL,
openai_system_prompt = CASE
WHEN provider = 'ai'
AND NOT EXISTS (
SELECT 1
FROM legacy_openai_ai_config
WHERE provider_id = ai_provider_id
AND model_id = ai_model_id
)
THEN openai_system_prompt
ELSE ''
END
WHERE id = 0;
DROP TABLE legacy_openai_ai_config;

View File

@ -0,0 +1,3 @@
ALTER TABLE settings ADD COLUMN mojang_auth_source TEXT NOT NULL DEFAULT 'auto' CHECK (
mojang_auth_source IN ('auto', 'official_only', 'mirror_preferred', 'official_preferred')
);

View File

@ -0,0 +1,6 @@
CREATE TABLE instance_config_sync_state (
instance_id TEXT NOT NULL PRIMARY KEY,
config_updated_at INTEGER NOT NULL,
generated_at INTEGER NULL,
FOREIGN KEY (instance_id) REFERENCES instances(id) ON DELETE CASCADE
);

View File

@ -0,0 +1,2 @@
ALTER TABLE settings
ADD COLUMN terracotta_public_nodes JSONB NOT NULL DEFAULT '["wss://center.node.1tmc.top"]';

View File

@ -0,0 +1,2 @@
UPDATE translation_settings SET provider = 'google'
WHERE id = 0 AND provider = 'microsoft';

View File

@ -0,0 +1,5 @@
CREATE TABLE IF NOT EXISTS google_translate_ip_cache (
ip TEXT NOT NULL PRIMARY KEY,
latency_ms INTEGER NOT NULL,
created_at INTEGER NOT NULL
);

View File

@ -0,0 +1,32 @@
ALTER TABLE settings
ADD COLUMN telemetry_consent_version INTEGER NOT NULL DEFAULT 0;
CREATE TABLE telemetry_identity (
id INTEGER NOT NULL CHECK (id = 0),
installation_id TEXT NOT NULL,
last_heartbeat_day TEXT NULL,
PRIMARY KEY (id)
);
CREATE TABLE telemetry_outbox (
event_id TEXT NOT NULL,
event_type TEXT NOT NULL CHECK (event_type IN ('heartbeat', 'error')),
payload JSONB NOT NULL,
created_at INTEGER NOT NULL,
next_attempt_at INTEGER NOT NULL,
attempts INTEGER NOT NULL DEFAULT 0,
size_bytes INTEGER NOT NULL,
dedupe_key TEXT NOT NULL,
occurrence_count INTEGER NOT NULL DEFAULT 1,
PRIMARY KEY (event_id),
UNIQUE (dedupe_key)
);
CREATE INDEX telemetry_outbox_retry
ON telemetry_outbox (next_attempt_at, created_at);
CREATE TABLE telemetry_error_daily (
day TEXT NOT NULL,
fingerprint TEXT NOT NULL,
PRIMARY KEY (day, fingerprint)
);

View File

@ -0,0 +1,47 @@
ALTER TABLE instance_content_entries
ADD COLUMN auto_dependency INTEGER NOT NULL DEFAULT 0
CHECK (auto_dependency IN (0, 1));
CREATE INDEX instance_content_entries_auto_dependency
ON instance_content_entries(content_set_id, auto_dependency);
CREATE TABLE instance_content_dependencies (
id TEXT NOT NULL,
content_set_id TEXT NOT NULL,
parent_entry_id TEXT NOT NULL,
child_entry_id TEXT NOT NULL,
provider TEXT NOT NULL,
dependency_kind TEXT NOT NULL,
parent_project_id TEXT NOT NULL,
parent_release_id TEXT NOT NULL,
child_project_id TEXT NOT NULL,
child_release_id TEXT NOT NULL,
created_at INTEGER NOT NULL,
modified_at INTEGER NOT NULL,
PRIMARY KEY (id),
UNIQUE (
content_set_id,
parent_entry_id,
child_entry_id,
dependency_kind
),
FOREIGN KEY (content_set_id)
REFERENCES instance_content_sets(id)
ON DELETE CASCADE,
FOREIGN KEY (parent_entry_id)
REFERENCES instance_content_entries(id)
ON DELETE CASCADE,
FOREIGN KEY (child_entry_id)
REFERENCES instance_content_entries(id)
ON DELETE CASCADE,
CHECK (provider IN ('modrinth', 'curseforge')),
CHECK (dependency_kind IN ('required', 'include'))
);
CREATE INDEX instance_content_dependencies_child
ON instance_content_dependencies(content_set_id, child_entry_id);
CREATE INDEX instance_content_dependencies_parent
ON instance_content_dependencies(content_set_id, parent_entry_id);
CREATE INDEX instance_content_dependencies_child_project
ON instance_content_dependencies(provider, child_project_id);

View File

@ -0,0 +1,56 @@
-- Allow dependency edges between locally identified files. SQLite cannot
-- alter a CHECK constraint, so the table is rebuilt with the widened
-- provider constraint. Index names are recreated after the old table (and
-- its indexes) are dropped.
CREATE TABLE instance_content_dependencies_local_provider (
id TEXT NOT NULL,
content_set_id TEXT NOT NULL,
parent_entry_id TEXT NOT NULL,
child_entry_id TEXT NOT NULL,
provider TEXT NOT NULL,
dependency_kind TEXT NOT NULL,
parent_project_id TEXT NOT NULL,
parent_release_id TEXT NOT NULL,
child_project_id TEXT NOT NULL,
child_release_id TEXT NOT NULL,
created_at INTEGER NOT NULL,
modified_at INTEGER NOT NULL,
PRIMARY KEY (id),
UNIQUE (
content_set_id,
parent_entry_id,
child_entry_id,
dependency_kind
),
FOREIGN KEY (content_set_id)
REFERENCES instance_content_sets(id)
ON DELETE CASCADE,
FOREIGN KEY (parent_entry_id)
REFERENCES instance_content_entries(id)
ON DELETE CASCADE,
FOREIGN KEY (child_entry_id)
REFERENCES instance_content_entries(id)
ON DELETE CASCADE,
CHECK (provider IN ('modrinth', 'curseforge', 'local')),
CHECK (dependency_kind IN ('required', 'include'))
);
INSERT INTO instance_content_dependencies_local_provider
SELECT id, content_set_id, parent_entry_id, child_entry_id, provider,
dependency_kind, parent_project_id, parent_release_id,
child_project_id, child_release_id, created_at, modified_at
FROM instance_content_dependencies;
DROP TABLE instance_content_dependencies;
ALTER TABLE instance_content_dependencies_local_provider
RENAME TO instance_content_dependencies;
CREATE INDEX instance_content_dependencies_child
ON instance_content_dependencies(content_set_id, child_entry_id);
CREATE INDEX instance_content_dependencies_parent
ON instance_content_dependencies(content_set_id, parent_entry_id);
CREATE INDEX instance_content_dependencies_child_project
ON instance_content_dependencies(provider, child_project_id);

View File

@ -0,0 +1,10 @@
-- Track which content entries already had their CurseForge dependency
-- relations resolved during backfill, so entries without any relations are
-- not refetched from the API on every snapshot.
ALTER TABLE instance_content_entries
ADD COLUMN dependency_backfilled INTEGER NOT NULL DEFAULT 0
CHECK (dependency_backfilled IN (0, 1));
CREATE INDEX instance_content_entries_dependency_backfilled
ON instance_content_entries(content_set_id, dependency_backfilled);

View File

@ -0,0 +1,8 @@
ALTER TABLE settings
ADD COLUMN download_engine TEXT NOT NULL DEFAULT 'xmcl';
UPDATE settings
SET download_engine = CASE
WHEN json_extract(feature_flags, '$.xmcl_download_engine') = 0 THEN 'legacy'
ELSE 'xmcl'
END;

View File

@ -0,0 +1,2 @@
ALTER TABLE settings
ADD COLUMN bypass_curseforge_download_restrictions INTEGER NOT NULL DEFAULT TRUE;

View File

@ -0,0 +1,59 @@
-- A dependency edge can be proven by one provider while each endpoint belongs
-- to a different provider. Rebuild because SQLite cannot tighten the existing
-- provider CHECK constraint in place.
CREATE TABLE instance_content_dependencies_endpoint_providers (
id TEXT NOT NULL,
content_set_id TEXT NOT NULL,
parent_entry_id TEXT NOT NULL,
child_entry_id TEXT NOT NULL,
evidence_provider TEXT NOT NULL,
parent_provider TEXT NOT NULL,
child_provider TEXT NOT NULL,
dependency_kind TEXT NOT NULL,
parent_project_id TEXT NOT NULL,
parent_release_id TEXT NOT NULL,
child_project_id TEXT NOT NULL,
child_release_id TEXT NOT NULL,
created_at INTEGER NOT NULL,
modified_at INTEGER NOT NULL,
PRIMARY KEY (id),
UNIQUE (
content_set_id,
parent_entry_id,
child_entry_id,
dependency_kind
),
FOREIGN KEY (content_set_id)
REFERENCES instance_content_sets(id)
ON DELETE CASCADE,
FOREIGN KEY (parent_entry_id)
REFERENCES instance_content_entries(id)
ON DELETE CASCADE,
FOREIGN KEY (child_entry_id)
REFERENCES instance_content_entries(id)
ON DELETE CASCADE,
CHECK (evidence_provider IN ('modrinth', 'curseforge', 'local')),
CHECK (parent_provider IN ('modrinth', 'curseforge', 'local')),
CHECK (child_provider IN ('modrinth', 'curseforge', 'local')),
CHECK (dependency_kind IN ('required', 'include'))
);
INSERT INTO instance_content_dependencies_endpoint_providers
SELECT id, content_set_id, parent_entry_id, child_entry_id,
provider, provider, provider, dependency_kind,
parent_project_id, parent_release_id,
child_project_id, child_release_id, created_at, modified_at
FROM instance_content_dependencies;
DROP TABLE instance_content_dependencies;
ALTER TABLE instance_content_dependencies_endpoint_providers
RENAME TO instance_content_dependencies;
CREATE INDEX instance_content_dependencies_child
ON instance_content_dependencies(content_set_id, child_entry_id);
CREATE INDEX instance_content_dependencies_parent
ON instance_content_dependencies(content_set_id, parent_entry_id);
CREATE INDEX instance_content_dependencies_child_project
ON instance_content_dependencies(child_provider, child_project_id);

View File

@ -0,0 +1,3 @@
UPDATE settings
SET download_engine = 'legacy'
WHERE id = 0;

View File

@ -0,0 +1,2 @@
UPDATE settings
SET mc_memory_auto = 1;

View File

@ -0,0 +1,3 @@
UPDATE settings
SET download_engine = 'xmcl'
WHERE id = 0;

View File

@ -0,0 +1,84 @@
CREATE TABLE instance_loader_components (
instance_id TEXT NOT NULL,
kind TEXT NOT NULL CHECK(kind IN (
'vanilla',
'forge',
'neoforge',
'fabric',
'quilt',
'cleanroom',
'legacy_fabric',
'optifine',
'lite_loader',
'optifabric'
)),
version TEXT NULL,
role TEXT NOT NULL CHECK(role IN ('primary', 'adjunct')),
provider_metadata TEXT NULL
CHECK(provider_metadata IS NULL OR json_valid(provider_metadata)),
PRIMARY KEY (instance_id, kind),
FOREIGN KEY (instance_id) REFERENCES instances(id) ON DELETE CASCADE
);
CREATE UNIQUE INDEX instance_loader_components_primary
ON instance_loader_components(instance_id)
WHERE role = 'primary';
CREATE INDEX instance_loader_components_kind
ON instance_loader_components(kind);
INSERT INTO instance_loader_components (
instance_id,
kind,
version,
role,
provider_metadata
)
SELECT
i.id,
CASE cs.loader
WHEN 'optifine' THEN 'vanilla'
WHEN 'lite_loader' THEN 'vanilla'
ELSE cs.loader
END,
CASE
WHEN cs.loader IN ('optifine', 'lite_loader') THEN NULL
ELSE cs.loader_version
END,
'primary',
NULL
FROM instances i
INNER JOIN instance_content_sets cs
ON cs.id = i.applied_content_set_id
AND cs.instance_id = i.id
WHERE cs.loader IN (
'vanilla',
'forge',
'neoforge',
'fabric',
'quilt',
'cleanroom',
'legacy_fabric',
'optifine',
'lite_loader'
);
INSERT INTO instance_loader_components (
instance_id,
kind,
version,
role,
provider_metadata
)
SELECT
i.id,
cs.loader,
cs.loader_version,
'adjunct',
NULL
FROM instances i
INNER JOIN instance_content_sets cs
ON cs.id = i.applied_content_set_id
AND cs.instance_id = i.id
WHERE cs.loader IN ('optifine', 'lite_loader');

View File

@ -0,0 +1,5 @@
ALTER TABLE settings ADD COLUMN proxy_mode TEXT NOT NULL DEFAULT 'system'
CHECK (proxy_mode IN ('none', 'system', 'custom'));
ALTER TABLE settings ADD COLUMN proxy_url TEXT NOT NULL DEFAULT '';
ALTER TABLE settings ADD COLUMN proxy_username TEXT NOT NULL DEFAULT '';
ALTER TABLE settings ADD COLUMN proxy_password TEXT NOT NULL DEFAULT '';

View File

@ -0,0 +1,10 @@
CREATE TABLE content_favorites (
provider TEXT NOT NULL CHECK (provider IN ('modrinth', 'curseforge')),
project_id TEXT NOT NULL CHECK (length(project_id) > 0),
content_type TEXT NOT NULL CHECK (content_type IN ('mod', 'resourcepack', 'datapack', 'shader')),
saved_at INTEGER NOT NULL,
PRIMARY KEY (provider, project_id)
);
CREATE INDEX content_favorites_saved_at_idx ON content_favorites (saved_at DESC);

View File

@ -0,0 +1,2 @@
ALTER TABLE translation_settings ADD COLUMN deepl_api_endpoint TEXT NOT NULL DEFAULT 'https://api-free.deepl.com/v2/translate';
ALTER TABLE translation_settings ADD COLUMN deepl_api_key TEXT NULL;

View File

@ -0,0 +1 @@
ALTER TABLE settings ADD COLUMN enter_lightweight_mode_on_game_launch INTEGER NOT NULL DEFAULT FALSE;

View File

@ -0,0 +1,2 @@
UPDATE settings
SET download_engine = 'legacy';

View File

@ -0,0 +1,43 @@
DROP INDEX instance_loader_components_primary;
DROP INDEX instance_loader_components_kind;
ALTER TABLE instance_loader_components RENAME TO instance_loader_components_old;
CREATE TABLE instance_loader_components (
instance_id TEXT NOT NULL,
kind TEXT NOT NULL CHECK(kind IN (
'vanilla',
'forge',
'neoforge',
'fabric',
'quilt',
'cleanroom',
'legacy_fabric',
'babric',
'optifine',
'lite_loader',
'optifabric'
)),
version TEXT NULL,
role TEXT NOT NULL CHECK(role IN ('primary', 'adjunct')),
provider_metadata TEXT NULL
CHECK(provider_metadata IS NULL OR json_valid(provider_metadata)),
PRIMARY KEY (instance_id, kind),
FOREIGN KEY (instance_id) REFERENCES instances(id) ON DELETE CASCADE
);
INSERT INTO instance_loader_components (
instance_id, kind, version, role, provider_metadata
)
SELECT instance_id, kind, version, role, provider_metadata
FROM instance_loader_components_old;
DROP TABLE instance_loader_components_old;
CREATE UNIQUE INDEX instance_loader_components_primary
ON instance_loader_components(instance_id)
WHERE role = 'primary';
CREATE INDEX instance_loader_components_kind
ON instance_loader_components(kind);

View File

@ -0,0 +1,272 @@
DROP INDEX IF EXISTS instance_content_provider_refs_project;
DROP INDEX IF EXISTS instance_content_provider_refs_release;
DROP INDEX IF EXISTS instance_content_provider_refs_identity;
DROP INDEX IF EXISTS instance_content_provider_refs_origin;
DROP INDEX IF EXISTS instance_content_update_checks_release;
DROP INDEX IF EXISTS instance_pack_members_content_set;
DROP INDEX IF EXISTS instance_pack_members_content_entry;
DROP INDEX IF EXISTS instance_pack_members_provider;
DROP INDEX IF EXISTS instance_pack_members_state;
DROP INDEX IF EXISTS instance_pending_manual_download_identity;
DROP INDEX IF EXISTS instance_pending_manual_downloads_instance_state;
DROP INDEX IF EXISTS instance_content_dependencies_child;
DROP INDEX IF EXISTS instance_content_dependencies_parent;
DROP INDEX IF EXISTS instance_content_dependencies_child_project;
DROP INDEX IF EXISTS content_favorites_saved_at_idx;
ALTER TABLE instance_content_provider_refs RENAME TO instance_content_provider_refs_old;
ALTER TABLE instance_content_update_checks RENAME TO instance_content_update_checks_old;
ALTER TABLE instance_pack_members RENAME TO instance_pack_members_old;
ALTER TABLE instance_pending_manual_downloads RENAME TO instance_pending_manual_downloads_old;
ALTER TABLE instance_content_dependencies RENAME TO instance_content_dependencies_old;
ALTER TABLE content_favorites RENAME TO content_favorites_old;
CREATE TABLE instance_content_provider_refs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
content_entry_id TEXT NOT NULL,
provider TEXT NOT NULL,
provider_project_id TEXT NOT NULL,
provider_release_id TEXT NULL,
is_origin INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY (content_entry_id)
REFERENCES instance_content_entries(id)
ON DELETE CASCADE,
CHECK (provider IN ('modrinth', 'curseforge', 'mcarchive')),
CHECK (is_origin IN (0, 1))
);
CREATE INDEX instance_content_provider_refs_project
ON instance_content_provider_refs(provider, provider_project_id);
CREATE INDEX instance_content_provider_refs_release
ON instance_content_provider_refs(provider, provider_release_id);
CREATE UNIQUE INDEX instance_content_provider_refs_identity
ON instance_content_provider_refs(
content_entry_id,
provider,
provider_project_id,
COALESCE(provider_release_id, '')
);
CREATE UNIQUE INDEX instance_content_provider_refs_origin
ON instance_content_provider_refs(content_entry_id)
WHERE is_origin = 1;
INSERT INTO instance_content_provider_refs (
id, content_entry_id, provider, provider_project_id,
provider_release_id, is_origin
)
SELECT id, content_entry_id, provider, provider_project_id,
provider_release_id, is_origin
FROM instance_content_provider_refs_old;
CREATE TABLE instance_content_update_checks (
content_entry_id TEXT NOT NULL,
update_channel TEXT NOT NULL,
provider TEXT NULL,
provider_project_id TEXT NULL,
provider_release_id TEXT NULL,
checked_at INTEGER NOT NULL,
PRIMARY KEY (content_entry_id),
FOREIGN KEY (content_entry_id)
REFERENCES instance_content_entries(id)
ON DELETE CASCADE,
CHECK (provider IS NULL OR provider IN ('modrinth', 'curseforge', 'mcarchive'))
);
CREATE INDEX instance_content_update_checks_release
ON instance_content_update_checks(provider, provider_release_id);
INSERT INTO instance_content_update_checks (
content_entry_id, update_channel, provider, provider_project_id,
provider_release_id, checked_at
)
SELECT content_entry_id, update_channel, provider, provider_project_id,
provider_release_id, checked_at
FROM instance_content_update_checks_old;
CREATE TABLE instance_pack_members (
id TEXT NOT NULL,
content_set_id TEXT NOT NULL,
content_entry_id TEXT NULL,
member_key TEXT NOT NULL,
project_type TEXT NOT NULL,
expected_relative_path TEXT NOT NULL,
provider TEXT NULL,
provider_project_id TEXT NULL,
provider_release_id TEXT NULL,
required INTEGER NOT NULL DEFAULT 1,
expected_sha1 TEXT NULL,
expected_size INTEGER NULL,
expected_fingerprint INTEGER NULL,
materialization_state TEXT NOT NULL DEFAULT 'present',
override_kind TEXT NOT NULL DEFAULT 'none',
reconciled INTEGER NOT NULL DEFAULT 1,
created_at INTEGER NOT NULL,
modified_at INTEGER NOT NULL,
PRIMARY KEY (id),
UNIQUE (content_set_id, member_key),
FOREIGN KEY (content_set_id)
REFERENCES instance_content_sets(id)
ON DELETE CASCADE,
FOREIGN KEY (content_entry_id)
REFERENCES instance_content_entries(id)
ON DELETE SET NULL,
CHECK (provider IS NULL OR provider IN ('modrinth', 'curseforge', 'mcarchive')),
CHECK (required IN (0, 1)),
CHECK (materialization_state IN ('present', 'pending_manual', 'missing', 'removed')),
CHECK (override_kind IN ('none', 'disabled', 'removed', 'version')),
CHECK (reconciled IN (0, 1))
);
CREATE INDEX instance_pack_members_content_set
ON instance_pack_members(content_set_id);
CREATE INDEX instance_pack_members_content_entry
ON instance_pack_members(content_entry_id);
CREATE INDEX instance_pack_members_provider
ON instance_pack_members(provider, provider_project_id, provider_release_id);
CREATE INDEX instance_pack_members_state
ON instance_pack_members(content_set_id, materialization_state);
INSERT INTO instance_pack_members (
id, content_set_id, content_entry_id, member_key, project_type,
expected_relative_path, provider, provider_project_id, provider_release_id,
required, expected_sha1, expected_size, expected_fingerprint,
materialization_state, override_kind, reconciled, created_at, modified_at
)
SELECT id, content_set_id, content_entry_id, member_key, project_type,
expected_relative_path, provider, provider_project_id, provider_release_id,
required, expected_sha1, expected_size, expected_fingerprint,
materialization_state, override_kind, reconciled, created_at, modified_at
FROM instance_pack_members_old;
CREATE TABLE instance_pending_manual_downloads (
id TEXT NOT NULL,
instance_id TEXT NOT NULL,
pack_member_id TEXT NULL,
content_entry_id TEXT NULL,
operation_kind TEXT NOT NULL,
operation_target_id TEXT NULL,
project_type TEXT NOT NULL,
provider TEXT NOT NULL,
provider_project_id TEXT NOT NULL,
provider_release_id TEXT NOT NULL,
file_name TEXT NOT NULL,
website_url TEXT NULL,
target_relative_path TEXT NOT NULL,
expected_sha1 TEXT NULL,
expected_size INTEGER NULL,
expected_fingerprint INTEGER NULL,
state TEXT NOT NULL DEFAULT 'waiting',
context JSONB NOT NULL DEFAULT '{}',
created_at INTEGER NOT NULL,
modified_at INTEGER NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (instance_id) REFERENCES instances(id) ON DELETE CASCADE,
FOREIGN KEY (pack_member_id)
REFERENCES instance_pack_members(id)
ON DELETE CASCADE,
FOREIGN KEY (content_entry_id)
REFERENCES instance_content_entries(id)
ON DELETE SET NULL,
CHECK (provider IN ('modrinth', 'curseforge', 'mcarchive')),
CHECK (operation_kind IN ('pack_install', 'pack_update', 'content_install', 'content_update')),
CHECK (state IN ('waiting', 'matched', 'imported', 'error', 'cancelled'))
);
CREATE UNIQUE INDEX instance_pending_manual_download_identity
ON instance_pending_manual_downloads(
instance_id, operation_kind, provider, provider_project_id,
provider_release_id
)
WHERE state IN ('waiting', 'matched');
CREATE INDEX instance_pending_manual_downloads_instance_state
ON instance_pending_manual_downloads(instance_id, state);
INSERT INTO instance_pending_manual_downloads (
id, instance_id, pack_member_id, content_entry_id, operation_kind,
operation_target_id, project_type, provider, provider_project_id,
provider_release_id, file_name, website_url, target_relative_path,
expected_sha1, expected_size, expected_fingerprint, state, context,
created_at, modified_at
)
SELECT id, instance_id, pack_member_id, content_entry_id, operation_kind,
operation_target_id, project_type, provider, provider_project_id,
provider_release_id, file_name, website_url, target_relative_path,
expected_sha1, expected_size, expected_fingerprint, state, context,
created_at, modified_at
FROM instance_pending_manual_downloads_old;
CREATE TABLE instance_content_dependencies (
id TEXT NOT NULL,
content_set_id TEXT NOT NULL,
parent_entry_id TEXT NOT NULL,
child_entry_id TEXT NOT NULL,
evidence_provider TEXT NOT NULL,
parent_provider TEXT NOT NULL,
child_provider TEXT NOT NULL,
dependency_kind TEXT NOT NULL,
parent_project_id TEXT NOT NULL,
parent_release_id TEXT NOT NULL,
child_project_id TEXT NOT NULL,
child_release_id TEXT NOT NULL,
created_at INTEGER NOT NULL,
modified_at INTEGER NOT NULL,
PRIMARY KEY (id),
UNIQUE (content_set_id, parent_entry_id, child_entry_id, dependency_kind),
FOREIGN KEY (content_set_id)
REFERENCES instance_content_sets(id)
ON DELETE CASCADE,
FOREIGN KEY (parent_entry_id)
REFERENCES instance_content_entries(id)
ON DELETE CASCADE,
FOREIGN KEY (child_entry_id)
REFERENCES instance_content_entries(id)
ON DELETE CASCADE,
CHECK (evidence_provider IN ('modrinth', 'curseforge', 'mcarchive', 'local')),
CHECK (parent_provider IN ('modrinth', 'curseforge', 'mcarchive', 'local')),
CHECK (child_provider IN ('modrinth', 'curseforge', 'mcarchive', 'local')),
CHECK (dependency_kind IN ('required', 'include'))
);
CREATE INDEX instance_content_dependencies_child
ON instance_content_dependencies(content_set_id, child_entry_id);
CREATE INDEX instance_content_dependencies_parent
ON instance_content_dependencies(content_set_id, parent_entry_id);
CREATE INDEX instance_content_dependencies_child_project
ON instance_content_dependencies(child_provider, child_project_id);
INSERT INTO instance_content_dependencies (
id, content_set_id, parent_entry_id, child_entry_id, evidence_provider,
parent_provider, child_provider, dependency_kind, parent_project_id,
parent_release_id, child_project_id, child_release_id, created_at, modified_at
)
SELECT id, content_set_id, parent_entry_id, child_entry_id, evidence_provider,
parent_provider, child_provider, dependency_kind, parent_project_id,
parent_release_id, child_project_id, child_release_id, created_at, modified_at
FROM instance_content_dependencies_old;
CREATE TABLE content_favorites (
provider TEXT NOT NULL CHECK (provider IN ('modrinth', 'curseforge', 'mcarchive')),
project_id TEXT NOT NULL CHECK (length(project_id) > 0),
content_type TEXT NOT NULL CHECK (content_type IN ('mod', 'resourcepack', 'datapack', 'shader')),
saved_at INTEGER NOT NULL,
PRIMARY KEY (provider, project_id)
);
CREATE INDEX content_favorites_saved_at_idx ON content_favorites (saved_at DESC);
INSERT INTO content_favorites (provider, project_id, content_type, saved_at)
SELECT provider, project_id, content_type, saved_at
FROM content_favorites_old;
DROP TABLE instance_pending_manual_downloads_old;
DROP TABLE instance_pack_members_old;
DROP TABLE instance_content_update_checks_old;
DROP TABLE instance_content_provider_refs_old;
DROP TABLE instance_content_dependencies_old;
DROP TABLE content_favorites_old;

View File

@ -0,0 +1,56 @@
DROP INDEX IF EXISTS instance_content_provider_refs_project;
DROP INDEX IF EXISTS instance_content_provider_refs_release;
DROP INDEX IF EXISTS instance_content_provider_refs_identity;
DROP INDEX IF EXISTS instance_content_provider_refs_origin;
ALTER TABLE instance_content_provider_refs RENAME TO instance_content_provider_refs_old;
CREATE TABLE instance_content_provider_refs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
content_entry_id TEXT NOT NULL,
provider TEXT NOT NULL,
provider_project_id TEXT NOT NULL,
provider_release_id TEXT NULL,
provider_file_id TEXT NULL,
is_origin INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY (content_entry_id)
REFERENCES instance_content_entries(id)
ON DELETE CASCADE,
CHECK (provider IN ('modrinth', 'curseforge', 'mcarchive')),
CHECK (is_origin IN (0, 1))
);
CREATE INDEX instance_content_provider_refs_project
ON instance_content_provider_refs(provider, provider_project_id);
CREATE INDEX instance_content_provider_refs_release
ON instance_content_provider_refs(provider, provider_release_id);
CREATE INDEX instance_content_provider_refs_file
ON instance_content_provider_refs(provider, provider_file_id);
CREATE UNIQUE INDEX instance_content_provider_refs_identity
ON instance_content_provider_refs(
content_entry_id,
provider,
provider_project_id,
COALESCE(provider_release_id, ''),
COALESCE(provider_file_id, '')
);
CREATE UNIQUE INDEX instance_content_provider_refs_origin
ON instance_content_provider_refs(content_entry_id)
WHERE is_origin = 1;
INSERT INTO instance_content_provider_refs (
id, content_entry_id, provider, provider_project_id,
provider_release_id, provider_file_id, is_origin
)
SELECT
id,
content_entry_id,
provider,
provider_project_id,
provider_release_id,
CASE WHEN provider = 'curseforge' THEN provider_release_id ELSE NULL END,
is_origin
FROM instance_content_provider_refs_old;
DROP TABLE instance_content_provider_refs_old;

View File

@ -0,0 +1,9 @@
CREATE TABLE crash_analysis_ai_settings (
id INTEGER NOT NULL CHECK (id = 0),
enabled INTEGER NOT NULL DEFAULT FALSE,
provider_id TEXT NOT NULL DEFAULT '',
model_id TEXT NOT NULL DEFAULT '',
PRIMARY KEY (id)
);
INSERT INTO crash_analysis_ai_settings (id) VALUES (0);

View File

@ -0,0 +1,7 @@
CREATE TABLE crash_analysis_mod_snapshots (
instance_id TEXT NOT NULL,
filename TEXT NOT NULL,
size INTEGER NOT NULL,
sha256 TEXT NOT NULL,
PRIMARY KEY (instance_id, filename)
);

View File

@ -0,0 +1,5 @@
ALTER TABLE crash_analysis_mod_snapshots ADD COLUMN project_id TEXT;
ALTER TABLE crash_analysis_mod_snapshots ADD COLUMN project_title TEXT;
ALTER TABLE crash_analysis_mod_snapshots ADD COLUMN icon_url TEXT;
ALTER TABLE crash_analysis_mod_snapshots ADD COLUMN version_id TEXT;
ALTER TABLE crash_analysis_mod_snapshots ADD COLUMN version_number TEXT;

View File

@ -0,0 +1,9 @@
CREATE TABLE instance_post_upgrade_notices (
instance_id TEXT PRIMARY KEY NOT NULL REFERENCES instances(id) ON DELETE CASCADE,
upgrade_job_id TEXT NOT NULL,
target_game_version TEXT NOT NULL,
consecutive_clean_launches INTEGER NOT NULL DEFAULT 0,
warnings_json TEXT NOT NULL,
created TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
modified TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);

View File

@ -0,0 +1,2 @@
ALTER TABLE instances
ADD COLUMN game_dir_override TEXT;

View File

@ -0,0 +1,3 @@
ALTER TABLE settings ADD COLUMN close_behavior TEXT NOT NULL DEFAULT 'ask' CHECK (
close_behavior IN ('ask', 'close', 'lightweight')
);

View File

@ -0,0 +1,8 @@
-- Direct association ("直接关联") instances point at an externally managed
-- traditional .minecraft layout without copying, symlinking, or reinstalling
-- anything. All columns are NULL for ordinary and pre-migration instances.
ALTER TABLE instances ADD COLUMN linked_launcher TEXT NULL;
ALTER TABLE instances ADD COLUMN linked_launcher_root TEXT NULL;
ALTER TABLE instances ADD COLUMN linked_dot_minecraft TEXT NULL;
ALTER TABLE instances ADD COLUMN linked_version_id TEXT NULL;
ALTER TABLE instances ADD COLUMN linked_version_json_path TEXT NULL;

View File

@ -0,0 +1,5 @@
-- Error telemetry is no longer collected or uploaded. Remove legacy local
-- samples and queued error payloads while retaining the tables for upgrades
-- from older installations.
DELETE FROM telemetry_outbox WHERE event_type <> 'heartbeat';
DELETE FROM telemetry_error_daily;

View File

@ -0,0 +1 @@
ALTER TABLE settings ADD COLUMN mc_maximize_window INTEGER NOT NULL DEFAULT FALSE;

View File

@ -0,0 +1,4 @@
-- The selected external-root layout is recorded per linked instance so it
-- remains stable after Settings closes and across application restarts.
-- NULL keeps the legacy launcher-specific automatic behavior.
ALTER TABLE instances ADD COLUMN linked_game_dir_mode TEXT NULL;