diff --git a/README.md b/README.md index 64b44b3f..dee99a0b 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,10 @@ Includes skating, tricks, grinds, offboard movement, difficulty settings and ## Play Extract the Windows release ZIP and run `skate3rust.exe`. Select your Skate 3 -Xbox 360 ISO. Setup prepares the skater, animations and all disc maps, then +Xbox 360 ISO, or select `default.xex` in an extracted game folder. Keep its +`data` folder alongside it. Setup prepares the skater, animations and all disc maps, then starts University. No Blender, Python or Rust installation is needed. -The first setup needs internet access and can take a while. +ISO extraction needs internet access. The first conversion can take a while. Use an XInput controller to play. Escape opens graphics, difficulty and map settings. Loading another map restarts the game session. diff --git a/docs/installation.md b/docs/installation.md index 7e521fe0..4cec92c6 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -4,7 +4,10 @@ `support` directory beside `skate3rust.exe`. The game links Bevy and the MSVC runtime statically; the setup helper bundles Python, NumPy, Pillow and Tcl/Tk. -On first launch, setup asks for a local Xbox 360 Skate 3 ISO. It downloads +On first launch, setup accepts a local Xbox 360 Skate 3 ISO, `default.xex`, +or an extracted game folder. Selecting `default.xex` uses the surrounding +folder, which must still contain the full game data. Extracted copies skip +ISO extraction and need no tool download. For ISO input, setup downloads the hash-pinned extract-xiso utility from XboxDev. Asset conversion runs in the bundled setup helper. Blender is neither required nor downloaded. No game files are downloaded or included in the package. diff --git a/tools/asset_pipeline/install.py b/tools/asset_pipeline/install.py index 5122602a..2a73c7d4 100644 --- a/tools/asset_pipeline/install.py +++ b/tools/asset_pipeline/install.py @@ -113,6 +113,13 @@ def convert_map(archive,work,maps,stage,game_exe,log,report): def install(iso,base,game_exe,report,game_root=None): + if game_root is None and iso is not None: + selected=iso.resolve() + if selected.is_dir():game_root=selected + elif selected.suffix.lower()=='.xex': + if selected.name.lower()!='default.xex' or not selected.is_file(): + raise RuntimeError('Select default.xex inside your extracted Skate 3 game folder') + game_root=selected.parent base=base.resolve();base.mkdir(parents=True,exist_ok=True) lock=base/'setup.lock' try:fd=os.open(lock,os.O_CREAT|os.O_EXCL|os.O_WRONLY) diff --git a/tools/setup.py b/tools/setup.py index a033502e..913a200e 100644 --- a/tools/setup.py +++ b/tools/setup.py @@ -24,22 +24,24 @@ def main(): from tools.asset_pipeline.install import install window=tk.Tk() window.title('Skate 3 Rust Engine setup') - window.geometry('660x390');window.resizable(False,False) + window.geometry('700x420');window.resizable(False,False) icon=ROOT/'docs/images/skating-crab.ico' if icon.is_file():window.iconbitmap(str(icon)) frame=ttk.Frame(window,padding=24);frame.pack(fill='both',expand=True) ttk.Label(frame,text='Set up Skate 3 Rust Engine',font=('Segoe UI',20)).pack(anchor='w',pady=(0,16)) - ttk.Label(frame,text='Select your Xbox 360 Skate 3 ISO.\nSetup extracts your game and prepares the skater, animations\nand all disc maps automatically. No other apps need installing.\n\nThe first setup needs internet access and free disk space.\nLarge maps can take a while to convert.', + ttk.Label(frame,text='Select your Skate 3 Xbox 360 ISO, or default.xex inside an\nextracted game folder. Keep the game data beside default.xex.\nSetup prepares the skater, animations and all disc maps.\nNo other apps need installing.\n\nISO extraction needs internet access. Allow free disk space\nand time for the first conversion.', font=('Segoe UI',11),justify='left').pack(anchor='w') - status=tk.StringVar(value='Choose your ISO to begin.') + status=tk.StringVar(value='Choose your game to begin.') ttk.Label(frame,textvariable=status,wraplength=600).pack(anchor='w',pady=(18,8)) progress=ttk.Progressbar(frame,mode='indeterminate');progress.pack(fill='x') messages=queue.Queue();running=False;success=False - def start(): + def start(folder=False): nonlocal running - iso=filedialog.askopenfilename(parent=window,title='Select your Skate 3 Xbox 360 ISO',filetypes=[('Xbox 360 ISO','*.iso')]) + iso=(filedialog.askdirectory(parent=window,title='Select the Skate 3 folder containing default.xex') if folder else + filedialog.askopenfilename(parent=window,title='Select your Skate 3 Xbox 360 game', + filetypes=[('Xbox 360 game','*.iso *.xex'),('Xbox 360 ISO','*.iso'),('Xbox executable','*.xex')])) if not iso:return - button.config(state='disabled');running=True;progress.start() + button.config(state='disabled');folder_button.config(state='disabled');running=True;progress.start() def work(): try: install(Path(iso),args.base,args.game_exe,lambda text:messages.put(('progress',text))) @@ -51,9 +53,11 @@ def main(): threading.Thread(target=work,daemon=True).start() def close(): if running: - messagebox.showinfo('Setup running','Wait for the current conversion to finish. Your original ISO is not modified.',parent=window) + messagebox.showinfo('Setup running','Wait for the current conversion to finish. Your source game files are not modified.',parent=window) else:window.destroy() - button=ttk.Button(frame,text='Select Skate 3 ISO',command=start);button.pack(anchor='e',pady=18) + buttons=ttk.Frame(frame);buttons.pack(anchor='e',pady=18) + folder_button=ttk.Button(buttons,text='Select extracted folder',command=lambda:start(True));folder_button.pack(side='left',padx=(0,8)) + button=ttk.Button(buttons,text='Select ISO or default.xex',command=start);button.pack(side='left') def poll(): nonlocal running,success while not messages.empty(): @@ -61,7 +65,7 @@ def main(): if kind=='done': running=False;success=True;progress.stop();window.destroy();return if kind=='error': - running=False;progress.stop();button.config(state='normal') + running=False;progress.stop();button.config(state='normal');folder_button.config(state='normal') messagebox.showerror('Setup could not finish',text+'\n\nDetails: '+str(args.base/'setup-error.log'),parent=window) window.after(100,poll) window.protocol('WM_DELETE_WINDOW',close)