├── DarkModeFix.ini ├── GI_LoadingScreen.exe ├── GI_LoadingScreen.py ├── LSMod.ini ├── README.md ├── README_Pictures ├── Example.png └── Example2.png ├── build.bat └── config.txt /DarkModeFix.ini: -------------------------------------------------------------------------------- 1 | [ShaderRegexLoadingScreenDarkMode] 2 | shader_model = ps_4_0 ps_5_0 3 | temps = stereo tmp1 4 | 5 | [ShaderRegexLoadingScreenDarkMode.Pattern] 6 | dcl_temps 1\n 7 | sample_indexable\(texture2d\)\(float,float,float,float\) r0\.xyzw, v2\.xyxx, t0\.xyzw, s0\n 8 | add r0\.xyzw, r0\.xyzw, cb0\[(?P[0-9]+)\]\.xyzw\n 9 | mul o0\.xyzw, r0\.xyzw, v1\.xyzw\n 10 | ret\n 11 | 12 | [ShaderRegexLoadingScreenDarkMode.Pattern.Replace] 13 | dcl_temps 5\n 14 | 15 | ld_indexable(texture1d)(float,float,float,float) r0.xyxw, l(151, 0, 0, 0), t120.xyzw\n 16 | ge r1.x, r0.x, l(0.5)\n 17 | lt r2.x, r0.y, l(0.5)\n 18 | 19 | and r4.x, r1.x, r2.x\n 20 | if_nz r4.x\n 21 | mov o0.xyzw, l(0,0,0,0)\n 22 | ret\n 23 | endif\n 24 | 25 | movc r3.xyzw, r1.xxxx, l(1.0,1.0,1.0,1.0), v1.xyzw\n 26 | sample_indexable(texture2d)(float,float,float,float) r0.xyzw, v2.xyxx, t0.xyzw, s0\n 27 | add r0.xyzw, r0.xyzw, cb0[${cb0_index}].xyzw\n 28 | mul o0.xyzw, r0.xyzw, r3.xyzw\n 29 | ret\n 30 | 31 | [ShaderRegexLoadingScreenDarkMode.InsertDeclarations] 32 | dcl_resource_texture1d (float,float,float,float) t120 -------------------------------------------------------------------------------- /GI_LoadingScreen.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arsinia/GI_LoadscreenRandomizer/5407d864cc962f890273ee63aeb74c3ae7dc9a7d/GI_LoadingScreen.exe -------------------------------------------------------------------------------- /GI_LoadingScreen.py: -------------------------------------------------------------------------------- 1 | import os 2 | import glob 3 | import subprocess 4 | from PIL import Image, ImageDraw 5 | from PIL import ImageFilter 6 | from PIL import ImageEnhance 7 | from typing import Tuple 8 | import numpy as np 9 | 10 | def apply_effect(image: Image, resolution: Tuple[int, int]) -> Image: 11 | image = image.transpose(Image.FLIP_TOP_BOTTOM) 12 | width, height = image.size 13 | target_width, target_height = resolution 14 | aspect_ratio = width / height 15 | target_aspect_ratio = target_width / target_height 16 | if aspect_ratio > target_aspect_ratio: 17 | # Image is wider than target resolution 18 | new_height = int(target_width / aspect_ratio) 19 | image = image.resize((target_width, new_height)) 20 | offset = (0, (target_height - new_height) // 2) 21 | else: 22 | # Image is taller than target resolution 23 | new_width = int(target_height * aspect_ratio) 24 | image = image.resize((new_width, target_height)) 25 | offset = ((target_width - new_width) // 2, 0) 26 | 27 | # image.show() 28 | # Add black bars 29 | background = Image.new("RGBA", resolution, (0,0,0,1)) 30 | background.paste(image, offset) 31 | # Create Gaussian Blur 32 | background = background.filter(ImageFilter.GaussianBlur(radius=min(width,height)/8)) 33 | # alpha = background.split()[3] 34 | # # Normalize the alpha values 35 | # alpha = Image.eval(alpha, lambda a: 255 * a / 128) 36 | # # Merge the alpha band back into the image 37 | # background.putalpha(alpha) 38 | background.paste(image, offset) 39 | background = background.convert('RGBA') 40 | color_layer = Image.new("RGBA", resolution, color_avg(image)) 41 | composite = Image.alpha_composite(color_layer, background) 42 | composite_array = composite.load() 43 | # for y in range(4): 44 | # for x in range(4): 45 | # composite_array[x, y] = (composite_array[x, y][0], composite_array[x, y][1], composite_array[x, y][2], 127) 46 | return composite 47 | 48 | def color_avg(image: Image) -> Tuple[int, int, int]: 49 | pixels = np.array(image) 50 | return tuple(map(int, np.average(pixels, axis=(0, 1)))) 51 | 52 | # Define the configuration file 53 | config_file = "config.txt" 54 | 55 | # Read the configuration file 56 | with open(config_file, 'r') as f: 57 | lines = f.read().splitlines() 58 | target_resolution = tuple(map(int, lines[0].split(','))) 59 | input_dir = lines[1] 60 | output_dir = lines[2] 61 | ini_file = lines[3] 62 | 63 | # Create the output directory if it doesn't exist 64 | if not os.path.exists(output_dir): 65 | os.makedirs(output_dir) 66 | 67 | # Get a list of all images in the input directory 68 | input_files = all_files = glob.glob(input_dir + '/**/*.*', recursive=True) 69 | input_files = [file for file in input_files if '\\-' not in file] 70 | 71 | output_files_dds = [] 72 | 73 | # Iterate over the input files 74 | for input_file in input_files: 75 | 76 | should_refresh = True 77 | 78 | # Define the output file path 79 | output_file, ext = os.path.splitext(input_file) 80 | output_file = os.path.join(output_dir, os.path.basename(output_file)) 81 | output_file_png = output_file + ".png" 82 | output_file_dds = output_file + ".dds" 83 | 84 | output_files_dds.append(output_file_dds) 85 | 86 | # Check if the output file already exists 87 | if os.path.isfile(output_file_dds): 88 | # Check if the input file has been modified since the output file was created 89 | input_mtime = os.path.getmtime(input_file) 90 | output_mtime = os.path.getmtime(output_file_dds) 91 | 92 | should_refresh = input_mtime >= output_mtime 93 | 94 | 95 | if should_refresh: 96 | # Open the image using PIL 97 | im = Image.open(input_file) 98 | 99 | if (False): 100 | 101 | # Crop and scale the image to fit the target resolution 102 | # Ugh why isn't there a function to do this garbage for me 103 | x_coeff = target_resolution[0] / im.width 104 | y_coeff = target_resolution[1] / im.height 105 | coeff = x_coeff if x_coeff > y_coeff else y_coeff 106 | im = im.resize((round(im.width * coeff), round(im.height * coeff)), Image.BICUBIC) 107 | left = (im.width - target_resolution[0]) / 2 108 | top = (im.height - target_resolution[1]) / 2 109 | right = (im.width + target_resolution[0]) / 2 110 | bottom = (im.height + target_resolution[1]) / 2 111 | im = im.crop((left, top, right, bottom)) 112 | im = im.transpose(Image.FLIP_TOP_BOTTOM) 113 | 114 | else: 115 | im = apply_effect(image=im, resolution=target_resolution) 116 | 117 | # Save the output file 118 | im.save(output_file_png, 'PNG', srgb=False) 119 | subprocess.run(["texconv.exe", "-f", "BC7_UNORM", "-y", "-sepalpha", "-srgb", "-m", "1", "-o", output_dir, output_file_png]) 120 | os.remove(output_file_png) 121 | 122 | 123 | with open(ini_file, "r") as f: 124 | ini_lines = f.readlines() 125 | with open(ini_file, "w") as f: 126 | for line in ini_lines: 127 | if line.startswith("global $n_imgs"): 128 | line = f"global $n_imgs = {len(input_files)}\n" 129 | f.write(line) 130 | if line.startswith(";BEGIN_SCRIPT_GENERATED_SECTION"): 131 | break 132 | 133 | for i in range(len(input_files)): 134 | f.write(f"else if $is_load_prev && $curr_img == {i}\n") 135 | f.write(f" this = ResourceLS.{i}\n") 136 | f.write("endif\n") 137 | f.write("endif\n") 138 | for i, output_file_dds in enumerate(output_files_dds): 139 | f.write(f"[ResourceLS.{i}]\n") 140 | output_file_dds_windows = output_file_dds.replace('/', '\\') 141 | f.write(f"filename = {output_file_dds_windows}\n") 142 | -------------------------------------------------------------------------------- /LSMod.ini: -------------------------------------------------------------------------------- 1 | ;Concept for loading screen mod by DiXiao 2 | ;Loading screen randomizer by Arsinia 3 | [Constants] 4 | global $skip_ui = 1 5 | 6 | global $n_imgs = 4 7 | global $curr_img = 0 8 | global persist $init_seed 9 | $curr_img = 0 10 | global $is_load = 1 11 | global $is_load_prev = 1 12 | global $is_init = 1 13 | global $do_once = 1 14 | global $not_load = 0 15 | 16 | 17 | [Present] 18 | $is_load_prev = ($is_load && !$not_load) || $is_init 19 | $not_load = 0 20 | $is_load = 0 21 | if $do_once 22 | $curr_img = ($init_seed + $n_imgs // 7) % $n_imgs 23 | $do_once = 0 24 | endif 25 | 26 | ;--- 27 | 28 | [TextureOverrideLSLoad] 29 | hash = 77fe5250 30 | run = CommandListLS_Bar 31 | 32 | [TextureOverrideLSThinBar] 33 | hash = 3da809e7 34 | run = CommandListLS_Bar_Thin 35 | 36 | [TextureOverrideNotLoadingScreenUI] 37 | hash = b5b2d5b6 38 | $not_load = 1 39 | 40 | [TextureOverrideLSLoadBarBiggerHydro] 41 | hash = 29feba14 42 | run = CommandListLS_Bar_Big 43 | [TextureOverrideLSLoadBarBiggerCryo] 44 | hash = 19f48cd6 45 | run = CommandListLS_Bar_Big 46 | [TextureOverrideLSLoadBarBiggerPyro] 47 | hash = b891661d 48 | run = CommandListLS_Bar_Big 49 | [TextureOverrideLSLoadBarBiggerDendro] 50 | hash = b53d4fd0 51 | run = CommandListLS_Bar_Big 52 | [TextureOverrideLSLoadBarBiggerGeo] 53 | hash = 91f2d7cc 54 | run = CommandListLS_Bar_Big 55 | [TextureOverrideLSLoadBarBiggerAnemo] 56 | hash = 0f078b00 57 | run = CommandListLS_Bar_Big 58 | [TextureOverrideLSLoadBarBiggerElectro] 59 | hash = 59c10306 60 | run = CommandListLS_Bar_Big 61 | 62 | [TextureOverrideText] 63 | hash = 45544863 64 | run = CommandListLS_Text 65 | 66 | [TextureOverrideLS] 67 | hash = b7ff7a6e 68 | run = CommandListLS 69 | 70 | [CommandListLS_Bar] 71 | $is_init = 0 72 | $is_load = 1 73 | if $is_load_prev == 0 74 | ; The // operator is floor division so it can cast float to int 75 | $curr_img = ((time * 1000) // 1) % $n_imgs 76 | $init_seed = $curr_img 77 | endif 78 | if $is_load_prev == 1 && $skip_ui 79 | handling = skip 80 | endif 81 | if $skip_ui 82 | y151 = 0.0 83 | endif 84 | 85 | [CommandListLS_Bar_Big] 86 | $is_init = 0 87 | $is_load = 1 88 | if $is_load_prev == 0 89 | ; The // operator is floor division so it can cast float to int 90 | $curr_img = ((time * 1000) // 1) % $n_imgs 91 | $init_seed = $curr_img 92 | endif 93 | if $is_load_prev == 1 && $skip_ui 94 | handling = skip 95 | endif 96 | if $skip_ui 97 | y151 = 0.0 98 | endif 99 | 100 | [CommandListLS_Bar_Thin] 101 | if $is_load_prev == 1 && $skip_ui 102 | handling = skip 103 | endif 104 | if $skip_ui 105 | y151 = 0.0 106 | endif 107 | 108 | [ShaderOverrideOverworldOnly] 109 | hash = 3ba1b9b792857b2a 110 | $is_init = 0 111 | 112 | [CommandListLS] 113 | if $is_load_prev && !$is_init 114 | x151 = 1.0 115 | else 116 | x151 = 0.0 117 | endif 118 | y151 = 1.0 119 | if $is_load_prev 120 | if 0 121 | ;BEGIN_SCRIPT_GENERATED_SECTION 122 | else if $is_load_prev && $curr_img == 0 123 | this = ResourceLS.0 124 | else if $is_load_prev && $curr_img == 1 125 | this = ResourceLS.1 126 | else if $is_load_prev && $curr_img == 2 127 | this = ResourceLS.2 128 | else if $is_load_prev && $curr_img == 3 129 | this = ResourceLS.3 130 | endif 131 | endif 132 | [ResourceLS.0] 133 | filename = .\OutputLoadingScreens\100645647_p13 - Copy - Copy.dds 134 | [ResourceLS.1] 135 | filename = .\OutputLoadingScreens\100645647_p13 - Copy.dds 136 | [ResourceLS.2] 137 | filename = .\OutputLoadingScreens\98174547_p0.dds 138 | [ResourceLS.3] 139 | filename = .\OutputLoadingScreens\24785.dds 140 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GI Loading Screen Randomizer 2 | 3 | # What does it do? 4 | This tool lets you take any images and prep them to be used as loading screens in a certain game. It also sets up the .ini file to be able to load these loading screens in game. This mod also replaces the annoying white flash when the game opens. 5 | 6 | This is what it looks like in game by default 7 | ![alt text](https://github.com/Arsinia/GI_LoadscreenRandomizer/blob/main/README_Pictures/Example.png?raw=true) 8 | 9 | # How do I use it? 10 | #### Basic instructions 11 | Just put the pictures you want to see in a folder called "InputLoadingScreens" and run GI_LoadingScreen.exe or GI_LoadingScreen.py. Then just run the game and it'll work. If you change the images while the game is running, press F10 to reload. 12 | 13 | #### Toggling Subfolders 14 | You can have multiple folders inside of "InputLoadingScreens" and running the program will search through all images in these folders. If you want to disable a folder, rename it to start with a hyphen "-" and rerun the program. This will prevent any images in this folder from being used as a loading screen. 15 | 16 | #### Options 17 | You can configure the resolution of the pictures by editing config.txt. Set the first two numbers to the width and height of your monitor and it'll resize the outputs automatically for you. When you change the resolution, you will need to delete all files in "OutputLoadingScreens" to force it to use the new size. 18 | 19 | You can toggle showing the loading UI in the LSMod.ini file. Open it in a text editor and set the first 3 options to whatever you want (0 to show, 1 to skip). 20 | 21 | #### Required Files 22 | Most mods you download will have all of the files needed present. Also if you download from the releases, all of the files will be present. If you want to make sure, you need [texconv.exe](https://github.com/Microsoft/DirectXTex/wiki/Texconv), the LSMod.ini file, and config.txt in the same folder as the main executable. 23 | 24 | #### I don't want to run it as an EXE 25 | EXE files can be a huge security risk if you don't trust where they came from. If you want to use this without an EXE, you can just run the Python file instead. Mods using this should provide the Python file for anyone who doesn't want to use EXEs. To run the Python file, you will need to install Python and the package Pillow. Then you should be able to run it just like the EXE. You can also build the EXE yourself using Pyinstaller and running the build.bat file. 26 | 27 | # Other stuff 28 | Feel free to use this project for whatever you want, just make sure you credit DiXiao for figuring out how to make loading screen mods. 90% of the Python code was just forcing ChatGPT to code it for me. 29 | 30 | ## Special Thanks 31 | [DiXiao](https://gamebanana.com/members/2182818) for figuring out how to make loading screen mods 32 | 33 | The [Anime Game Modding Group Discord](https://discord.gg/gR2Ts6ApP7) for helping me mod a certain game 34 | -------------------------------------------------------------------------------- /README_Pictures/Example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arsinia/GI_LoadscreenRandomizer/5407d864cc962f890273ee63aeb74c3ae7dc9a7d/README_Pictures/Example.png -------------------------------------------------------------------------------- /README_Pictures/Example2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arsinia/GI_LoadscreenRandomizer/5407d864cc962f890273ee63aeb74c3ae7dc9a7d/README_Pictures/Example2.png -------------------------------------------------------------------------------- /build.bat: -------------------------------------------------------------------------------- 1 | pyinstaller --onefile -y GI_LoadingScreen.py -------------------------------------------------------------------------------- /config.txt: -------------------------------------------------------------------------------- 1 | 1920, 1080 2 | ./InputLoadingScreens 3 | ./OutputLoadingScreens 4 | LSMod.ini --------------------------------------------------------------------------------