├── Reference ├── README.md ├── UndertaleModLib.dll └── UndertaleModTool.dll ├── ico.ico ├── ModShardLauncherTest ├── GlobalUsings.cs └── ModShardLauncherTest.csproj ├── Resources ├── zpix.ttf ├── Codes │ ├── LoggerModule │ │ ├── o_msl_timer_Create_0.gml │ │ ├── o_msl_timer_Step_0.gml │ │ ├── scr_msl_log_save.gml │ │ ├── o_msl_log_Create_0.gml │ │ └── scr_msl_log.gml │ └── LootModule │ │ ├── scr_msl_resolve_guaranteed_items.gml │ │ ├── scr_msl_resolve_items.gml │ │ ├── scr_msl_resolve_loot_table.gml │ │ ├── scr_msl_resolve_random_items.gml │ │ └── scr_msl_resolve_refence_table.gml ├── SSFont.ttf ├── Mystery Font.ttf └── Sprites │ ├── icon.gif │ ├── menu.png │ ├── min.png │ ├── mod.png │ ├── open.png │ ├── save.png │ ├── SSicon.png │ ├── close.png │ ├── server.png │ ├── source.png │ ├── arrow_up.png │ ├── leftPanel.png │ ├── menu_down.png │ ├── menu_over.png │ ├── min_down.png │ ├── min_over.png │ ├── mod_down.png │ ├── mod_over.png │ ├── open_down.png │ ├── open_over.png │ ├── save_down.png │ ├── save_over.png │ ├── settings.png │ ├── splitter.png │ ├── ModInfosBG.png │ ├── arrow_down.png │ ├── checkbox_0.png │ ├── checkbox_1.png │ ├── checkbox_2.png │ ├── close_down.png │ ├── close_over.png │ ├── icon_default.png │ ├── patch_icon.png │ ├── refresh_icon.png │ ├── server_down.png │ ├── server_over.png │ ├── source_down.png │ ├── source_over.png │ ├── arrow_up_down.png │ ├── arrow_up_over.png │ ├── enable_button.png │ ├── mod_icon_fore.png │ ├── settings_down.png │ ├── settings_over.png │ ├── arrow_down_down.png │ ├── arrow_down_over.png │ ├── settings_button.png │ ├── enable_button_down.png │ ├── enable_button_over.png │ ├── scrollbar_vertical.png │ ├── settings_button_down.png │ ├── settings_button_over.png │ └── scrollbar_vertical_over.png ├── docs ├── img │ ├── mod_0.en.png │ ├── mod_0.zh.png │ ├── mod_1.en.png │ ├── mod_1.zh.png │ ├── mod_2.en.png │ ├── mod_2.zh.png │ ├── mod_3.en.png │ ├── mod_3.zh.png │ ├── Stoneshard.ico │ ├── class_0.en.png │ ├── class_0.zh.png │ ├── class_1.en.png │ ├── tool_UI.en.png │ ├── tool_UI2.en.png │ ├── tool_UI3.en.png │ ├── tool_UI4.en.png │ ├── tool_UI5.en.png │ ├── tool_UI6.en.png │ ├── weapon_0.en.png │ ├── weapon_0.zh.png │ ├── weapon_1.en.png │ ├── weapon_2.en.png │ ├── compile_0.en.png │ ├── compile_1.en.png │ ├── linux_bottle_1.png │ ├── modding_codes.en.png │ ├── msl_template.en.png │ ├── create_project_0.en.png │ ├── create_project_0.zh.png │ ├── create_project_1.en.png │ ├── create_project_1.zh.png │ ├── create_project_2.en.png │ ├── create_project_2.zh.png │ └── msl_template_location.en.png ├── guides │ ├── how-to-play-mod.zh.md │ ├── start-modding-with-vsc.zh.md │ ├── introduction.zh.md │ ├── introduction.en.md │ ├── api.zh.md │ ├── how-to-play-mod.en.md │ └── start-modding.zh.md ├── index.zh.md └── index.en.md ├── ModReference ├── System.Linq.dll ├── netstandard.dll ├── System.Runtime.dll ├── System.Collections.dll └── System.ObjectModel.dll ├── FodyWeavers.xml ├── Mods ├── ModHooks.cs ├── DisassemblyEditor.cs └── Mod.cs ├── App.xaml.cs ├── .github ├── ISSUE_TEMPLATE │ ├── feature-request.md │ └── bug-report.md └── workflows │ ├── build-test.yml │ ├── ci.yml │ └── publish.yaml ├── AssemblyInfo.cs ├── Controls ├── MainPage.xaml.cs ├── LoadingDialog.xaml.cs ├── ModSourceInfos.xaml.cs ├── Settings.xaml.cs ├── LoadingDialog.xaml ├── ScriptEnginePage.xaml.cs ├── MyItemsControl.xaml ├── SourceBar.xaml.cs ├── MyItemsControl.xaml.cs ├── ModBar.xaml.cs ├── MyToggleButton.xaml.cs ├── ModSourceInfos.xaml ├── ScriptEnginePage.xaml ├── MainPage.xaml ├── GeneralPage.xaml.cs ├── ModInfos.xaml.cs ├── ModInfos.xaml ├── MyToggleButton.xaml ├── Settings.xaml ├── SourceBar.xaml └── ModBar.xaml ├── ModUtils ├── TableUtils │ ├── TableUtils.cs │ ├── Backers.cs │ ├── PotionsStats.cs │ ├── RecipesCraft.cs │ ├── SurfaceSpawn.cs │ ├── ContractsStats.cs │ ├── DungeonsSpawn.cs │ ├── RecipesCook.cs │ ├── Drops.cs │ └── SkillsStats.cs ├── VariableUtils.cs ├── LogUtils.cs ├── HookUtils.cs ├── WeaponUtils.cs ├── TextureUtils.cs ├── LootUtils.cs └── ObjectUtils.cs ├── future_workflow.md ├── FilePacker.cs ├── Loader ├── ChecksumChecker.cs └── Exporter.cs ├── CodeResources.cs ├── Converters └── CompareNumbersConverter.cs ├── Language ├── ru-ru.xaml ├── zh-cn.xaml └── en-us.xaml ├── .gitattributes ├── Core ├── Errors │ └── MSLDiagnostic.cs └── UI │ └── ErrorMessageDialog.cs ├── README.md ├── ModShardLauncher.sln ├── mkdocs.yml └── ModShardLauncher.csproj /Reference/README.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ico.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/ico.ico -------------------------------------------------------------------------------- /ModShardLauncherTest/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using Xunit; 2 | global using ModShardLauncher; -------------------------------------------------------------------------------- /Resources/zpix.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/zpix.ttf -------------------------------------------------------------------------------- /Resources/Codes/LoggerModule/o_msl_timer_Create_0.gml: -------------------------------------------------------------------------------- 1 | func = -4; 2 | end_time = 0; 3 | cumulative_time = 0; -------------------------------------------------------------------------------- /Resources/SSFont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/SSFont.ttf -------------------------------------------------------------------------------- /docs/img/mod_0.en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/docs/img/mod_0.en.png -------------------------------------------------------------------------------- /docs/img/mod_0.zh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/docs/img/mod_0.zh.png -------------------------------------------------------------------------------- /docs/img/mod_1.en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/docs/img/mod_1.en.png -------------------------------------------------------------------------------- /docs/img/mod_1.zh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/docs/img/mod_1.zh.png -------------------------------------------------------------------------------- /docs/img/mod_2.en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/docs/img/mod_2.en.png -------------------------------------------------------------------------------- /docs/img/mod_2.zh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/docs/img/mod_2.zh.png -------------------------------------------------------------------------------- /docs/img/mod_3.en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/docs/img/mod_3.en.png -------------------------------------------------------------------------------- /docs/img/mod_3.zh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/docs/img/mod_3.zh.png -------------------------------------------------------------------------------- /docs/img/Stoneshard.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/docs/img/Stoneshard.ico -------------------------------------------------------------------------------- /docs/img/class_0.en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/docs/img/class_0.en.png -------------------------------------------------------------------------------- /docs/img/class_0.zh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/docs/img/class_0.zh.png -------------------------------------------------------------------------------- /docs/img/class_1.en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/docs/img/class_1.en.png -------------------------------------------------------------------------------- /docs/img/tool_UI.en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/docs/img/tool_UI.en.png -------------------------------------------------------------------------------- /docs/img/tool_UI2.en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/docs/img/tool_UI2.en.png -------------------------------------------------------------------------------- /docs/img/tool_UI3.en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/docs/img/tool_UI3.en.png -------------------------------------------------------------------------------- /docs/img/tool_UI4.en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/docs/img/tool_UI4.en.png -------------------------------------------------------------------------------- /docs/img/tool_UI5.en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/docs/img/tool_UI5.en.png -------------------------------------------------------------------------------- /docs/img/tool_UI6.en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/docs/img/tool_UI6.en.png -------------------------------------------------------------------------------- /docs/img/weapon_0.en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/docs/img/weapon_0.en.png -------------------------------------------------------------------------------- /docs/img/weapon_0.zh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/docs/img/weapon_0.zh.png -------------------------------------------------------------------------------- /docs/img/weapon_1.en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/docs/img/weapon_1.en.png -------------------------------------------------------------------------------- /docs/img/weapon_2.en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/docs/img/weapon_2.en.png -------------------------------------------------------------------------------- /Resources/Mystery Font.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Mystery Font.ttf -------------------------------------------------------------------------------- /Resources/Sprites/icon.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/icon.gif -------------------------------------------------------------------------------- /Resources/Sprites/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/menu.png -------------------------------------------------------------------------------- /Resources/Sprites/min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/min.png -------------------------------------------------------------------------------- /Resources/Sprites/mod.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/mod.png -------------------------------------------------------------------------------- /Resources/Sprites/open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/open.png -------------------------------------------------------------------------------- /Resources/Sprites/save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/save.png -------------------------------------------------------------------------------- /docs/img/compile_0.en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/docs/img/compile_0.en.png -------------------------------------------------------------------------------- /docs/img/compile_1.en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/docs/img/compile_1.en.png -------------------------------------------------------------------------------- /ModReference/System.Linq.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/ModReference/System.Linq.dll -------------------------------------------------------------------------------- /ModReference/netstandard.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/ModReference/netstandard.dll -------------------------------------------------------------------------------- /Reference/UndertaleModLib.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Reference/UndertaleModLib.dll -------------------------------------------------------------------------------- /Resources/Sprites/SSicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/SSicon.png -------------------------------------------------------------------------------- /Resources/Sprites/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/close.png -------------------------------------------------------------------------------- /Resources/Sprites/server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/server.png -------------------------------------------------------------------------------- /Resources/Sprites/source.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/source.png -------------------------------------------------------------------------------- /docs/img/linux_bottle_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/docs/img/linux_bottle_1.png -------------------------------------------------------------------------------- /docs/img/modding_codes.en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/docs/img/modding_codes.en.png -------------------------------------------------------------------------------- /docs/img/msl_template.en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/docs/img/msl_template.en.png -------------------------------------------------------------------------------- /ModReference/System.Runtime.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/ModReference/System.Runtime.dll -------------------------------------------------------------------------------- /Reference/UndertaleModTool.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Reference/UndertaleModTool.dll -------------------------------------------------------------------------------- /Resources/Sprites/arrow_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/arrow_up.png -------------------------------------------------------------------------------- /Resources/Sprites/leftPanel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/leftPanel.png -------------------------------------------------------------------------------- /Resources/Sprites/menu_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/menu_down.png -------------------------------------------------------------------------------- /Resources/Sprites/menu_over.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/menu_over.png -------------------------------------------------------------------------------- /Resources/Sprites/min_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/min_down.png -------------------------------------------------------------------------------- /Resources/Sprites/min_over.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/min_over.png -------------------------------------------------------------------------------- /Resources/Sprites/mod_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/mod_down.png -------------------------------------------------------------------------------- /Resources/Sprites/mod_over.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/mod_over.png -------------------------------------------------------------------------------- /Resources/Sprites/open_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/open_down.png -------------------------------------------------------------------------------- /Resources/Sprites/open_over.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/open_over.png -------------------------------------------------------------------------------- /Resources/Sprites/save_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/save_down.png -------------------------------------------------------------------------------- /Resources/Sprites/save_over.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/save_over.png -------------------------------------------------------------------------------- /Resources/Sprites/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/settings.png -------------------------------------------------------------------------------- /Resources/Sprites/splitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/splitter.png -------------------------------------------------------------------------------- /docs/guides/how-to-play-mod.zh.md: -------------------------------------------------------------------------------- 1 | !!! warning "TODO" 2 | 3 |
![](../img/tool_UI.png){: style="width:50%"}
-------------------------------------------------------------------------------- /Resources/Sprites/ModInfosBG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/ModInfosBG.png -------------------------------------------------------------------------------- /Resources/Sprites/arrow_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/arrow_down.png -------------------------------------------------------------------------------- /Resources/Sprites/checkbox_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/checkbox_0.png -------------------------------------------------------------------------------- /Resources/Sprites/checkbox_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/checkbox_1.png -------------------------------------------------------------------------------- /Resources/Sprites/checkbox_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/checkbox_2.png -------------------------------------------------------------------------------- /Resources/Sprites/close_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/close_down.png -------------------------------------------------------------------------------- /Resources/Sprites/close_over.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/close_over.png -------------------------------------------------------------------------------- /Resources/Sprites/icon_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/icon_default.png -------------------------------------------------------------------------------- /Resources/Sprites/patch_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/patch_icon.png -------------------------------------------------------------------------------- /Resources/Sprites/refresh_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/refresh_icon.png -------------------------------------------------------------------------------- /Resources/Sprites/server_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/server_down.png -------------------------------------------------------------------------------- /Resources/Sprites/server_over.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/server_over.png -------------------------------------------------------------------------------- /Resources/Sprites/source_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/source_down.png -------------------------------------------------------------------------------- /Resources/Sprites/source_over.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/source_over.png -------------------------------------------------------------------------------- /docs/img/create_project_0.en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/docs/img/create_project_0.en.png -------------------------------------------------------------------------------- /docs/img/create_project_0.zh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/docs/img/create_project_0.zh.png -------------------------------------------------------------------------------- /docs/img/create_project_1.en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/docs/img/create_project_1.en.png -------------------------------------------------------------------------------- /docs/img/create_project_1.zh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/docs/img/create_project_1.zh.png -------------------------------------------------------------------------------- /docs/img/create_project_2.en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/docs/img/create_project_2.en.png -------------------------------------------------------------------------------- /docs/img/create_project_2.zh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/docs/img/create_project_2.zh.png -------------------------------------------------------------------------------- /ModReference/System.Collections.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/ModReference/System.Collections.dll -------------------------------------------------------------------------------- /ModReference/System.ObjectModel.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/ModReference/System.ObjectModel.dll -------------------------------------------------------------------------------- /Resources/Sprites/arrow_up_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/arrow_up_down.png -------------------------------------------------------------------------------- /Resources/Sprites/arrow_up_over.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/arrow_up_over.png -------------------------------------------------------------------------------- /Resources/Sprites/enable_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/enable_button.png -------------------------------------------------------------------------------- /Resources/Sprites/mod_icon_fore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/mod_icon_fore.png -------------------------------------------------------------------------------- /Resources/Sprites/settings_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/settings_down.png -------------------------------------------------------------------------------- /Resources/Sprites/settings_over.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/settings_over.png -------------------------------------------------------------------------------- /Resources/Sprites/arrow_down_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/arrow_down_down.png -------------------------------------------------------------------------------- /Resources/Sprites/arrow_down_over.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/arrow_down_over.png -------------------------------------------------------------------------------- /Resources/Sprites/settings_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/settings_button.png -------------------------------------------------------------------------------- /docs/img/msl_template_location.en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/docs/img/msl_template_location.en.png -------------------------------------------------------------------------------- /Resources/Sprites/enable_button_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/enable_button_down.png -------------------------------------------------------------------------------- /Resources/Sprites/enable_button_over.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/enable_button_over.png -------------------------------------------------------------------------------- /Resources/Sprites/scrollbar_vertical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/scrollbar_vertical.png -------------------------------------------------------------------------------- /Resources/Sprites/settings_button_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/settings_button_down.png -------------------------------------------------------------------------------- /Resources/Sprites/settings_button_over.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/settings_button_over.png -------------------------------------------------------------------------------- /Resources/Sprites/scrollbar_vertical_over.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModShardTeam/ModShardLauncher/HEAD/Resources/Sprites/scrollbar_vertical_over.png -------------------------------------------------------------------------------- /FodyWeavers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /docs/index.zh.md: -------------------------------------------------------------------------------- 1 | # ModShardLauncher Modding Documentation 2 | 3 | 欢迎访问 **ModShardLauncher 文档** ! 4 | 5 | !!! info "多种语言" 6 | 本文档提供多种语言版本。
7 | 您可以点击页面顶部的语言按钮更改语言。

8 | 某些页面可能不支持所有语言,请随时提供您的意见! -------------------------------------------------------------------------------- /Resources/Codes/LoggerModule/o_msl_timer_Step_0.gml: -------------------------------------------------------------------------------- 1 | cumulative_time += delta_time / 1000000; 2 | if (cumulative_time > end_time) 3 | { 4 | if (func != noone) 5 | { 6 | script_execute(func) 7 | } 8 | instance_destroy(); 9 | } -------------------------------------------------------------------------------- /Mods/ModHooks.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ModShardLauncher.Mods 8 | { 9 | public class ModHooks 10 | { 11 | public virtual void OnGameStart(object[] obj) { } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Data; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | 9 | namespace ModShardLauncher 10 | { 11 | /// 12 | /// Interaction logic for App.xaml 13 | /// 14 | public partial class App : Application 15 | { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /docs/index.en.md: -------------------------------------------------------------------------------- 1 | # ModShardLauncher Modding Documentation 2 | 3 | Welcome to the **ModShardLauncher Modding Documentation** ! 4 | 5 | !!! info "Multiple languages" 6 | This documentation is available in **multiple languages**.
7 | You can change the language by clicking the **language button** at the **top** of the page.

8 | Some pages may not be available in all languages, so feel free to contribute! -------------------------------------------------------------------------------- /docs/guides/start-modding-with-vsc.zh.md: -------------------------------------------------------------------------------- 1 | # 使用Visual Studio Code进行开发 2 | 3 | [TOC] 4 | 5 | ## 前言 6 | 7 | 为什么要把这个教程单独拿出来呢? 首先一个原因是考虑到想做mod的各位对于编程可能一无所知, 而下载Visual Studio的步骤与某些编程知识是相关的, 因此可能会造成一些误解或让各位在前期做无用功。 8 | 9 | Visual Studio Code 是作者很喜欢的一款文本编辑器, 因此关于这方面的内容我们都围绕VSCode来讲. 10 | 11 | ## 下载工具 12 | 13 | [在这里下载](https://code.visualstudio.com/) 14 | 15 | 网上也有很多关于下载VSCode的教程, 你可以参考这些教程来进行下载. 基本上一路按继续就可以了. 16 | 17 | ## 下载插件 18 | 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for this project 4 | title: "[ENHANCEMENT] Feature Request" 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## **Describe the problem which requires a new feature.** 11 | ... 12 | 13 | ## **Describe the solution you'd like** 14 | ... 15 | 16 | ## **Describe alternatives you've considered if any** 17 | ... 18 | 19 | ## **Additional context** 20 | ... 21 | -------------------------------------------------------------------------------- /Resources/Codes/LoggerModule/scr_msl_log_save.gml: -------------------------------------------------------------------------------- 1 | function scr_msl_log_save() 2 | { 3 | if (global._msl_log.save_in_progress) return; 4 | 5 | global._msl_log.save_in_progress = true; 6 | var nfile_name = global._msl_log.name + "_" + string(global._msl_log.nfile) + ".txt"; 7 | buffer_save_async(global._msl_log.buf, nfile_name, 0, global._msl_log.cur_size); 8 | 9 | global._msl_log.save_in_progress = false; 10 | instance_destroy(global._msl_log.timer); 11 | } -------------------------------------------------------------------------------- /Resources/Codes/LoggerModule/o_msl_log_Create_0.gml: -------------------------------------------------------------------------------- 1 | size = 1000000 2 | buf = buffer_create(size, buffer_wrap, 1); 3 | cur_size = 0 4 | nfile = 0 5 | save_in_progress = false 6 | 7 | var curr_time = date_current_datetime(); 8 | var format_time = string_format(date_get_year(curr_time), 2, 0) + string_format(date_get_month(curr_time), 2, 0) + string_format(date_get_day(curr_time), 2, 0) + "_" + string_format(date_get_hour(curr_time), 2, 0) + string_format(date_get_minute(curr_time), 2, 0); 9 | name = "Logs/msl_log_" + string_replace_all(format_time, " ", "0"); 10 | 11 | timer = -4 -------------------------------------------------------------------------------- /AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | [assembly: ThemeInfo( 4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 5 | //(used if a resource is not found in the page, 6 | // or application resource dictionaries) 7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 8 | //(used if a resource is not found in the page, 9 | // app, or any theme specific resource dictionaries) 10 | )] 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Create a report to help us improve 4 | title: "[BUG] Bug report" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## **Describe the Bug :** 11 | ... 12 | 13 | ## **Steps To Reproduce :** 14 | ... 15 | 16 | ## **Expected Behavior :** 17 | ... 18 | 19 | ## **Screenshots if applicable :** 20 | _You can directly paste screenshots here, no need for external hosting._ 21 | ... 22 | 23 | --- 24 | 25 | ## **Additional Information :** 26 | - Stoneshard Version : **X.X.X.X** 27 | - MSL Version : **X.X.X.X** 28 | 29 | ## **Additional Context :** 30 | _Add any other context about the problem here._ 31 | ... 32 | -------------------------------------------------------------------------------- /.github/workflows/build-test.yml: -------------------------------------------------------------------------------- 1 | name: Build + Test 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | branches: [ main ] 7 | push: 8 | branches: [ main ] 9 | 10 | jobs: 11 | build: 12 | 13 | runs-on: windows-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: Setup .NET 21 | uses: actions/setup-dotnet@v4 22 | with: 23 | dotnet-version: 6.0.x 24 | 25 | - name: Restore dependencies 26 | run: dotnet restore 27 | 28 | - name: Build 29 | run: dotnet build -c Release --no-restore 30 | 31 | - name: Test 32 | run: dotnet test -c Release --no-build --verbosity normal 33 | -------------------------------------------------------------------------------- /docs/guides/introduction.zh.md: -------------------------------------------------------------------------------- 1 | # ModShardLauncher 2 | 3 | ## 什么是**ModShardLauncher**? 4 | 5 | **ModShardLauncher**是一个用于给StoneShard(紫色晶石)这款游戏加载mod的工具。 6 | 7 | 在我开发这款工具之前, mod作者们都是使用**UTMT**, 即UndertaleModTool这款工具来开发mod并保存的. 这样开发的难点在于, 作者们只能制作源码mod, 也就是说, 不同的mod不能一起加载, 除非你自己把他们的内容整合到一起. 而且UTMT使用较为繁琐, 并不适合用于开发StoneShard的mod. 一旦作者进行更新, 所有mod将不再适用, 源文件需要重新被编辑和保存. 8 | 9 | 为了解决这些痛苦的问题, 我打算开发一款工具, 也就是**ModShardLauncher**. 10 | 11 | ## **ModShardLauncher**是如何工作的? 12 | 13 | 实际上, **UTMT**是用C#这门语言开发的. 因此, 引用它的源码就可以很方便的读取和保存data.win中的数据. 并且在C#强大的反射功能支持下, mod作者们可以通过该工具内置的打包器将所有mod代码以及贴图打包成 `.sml` 文件, 然后工具内置的读取器可以读取这种格式的文件, 并将其中的数据打包进新的data.win文件. 以达成多mod共存, 便捷开发的目的. 14 | 15 | ## 太棒辣 我现在就想开发一个自己的mod! 16 | 17 | [那就从这里开始吧!](../guides/start-modding.md) -------------------------------------------------------------------------------- /Controls/MainPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using System.Windows.Controls; 8 | using System.Windows.Data; 9 | using System.Windows.Documents; 10 | using System.Windows.Input; 11 | using System.Windows.Media; 12 | using System.Windows.Media.Imaging; 13 | using System.Windows.Navigation; 14 | using System.Windows.Shapes; 15 | 16 | namespace ModShardLauncher.Controls 17 | { 18 | /// 19 | /// MainPage.xaml 的交互逻辑 20 | /// 21 | public partial class MainPage : UserControl 22 | { 23 | public MainPage() 24 | { 25 | InitializeComponent(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ModUtils/TableUtils/TableUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | using System.Runtime.Serialization; 5 | 6 | namespace ModShardLauncher 7 | { 8 | public partial class Msl 9 | { 10 | private static string? GetEnumMemberValue(this T value) 11 | where T : Enum 12 | { 13 | return typeof(T) 14 | .GetTypeInfo() 15 | .DeclaredMembers 16 | .SingleOrDefault(x => x.Name == value.ToString())? 17 | .GetCustomAttribute(false)? 18 | .Value ?? value.ToString(); 19 | } 20 | 21 | // Tables left to do : 22 | // - ai 23 | // - supply / demand if necessary ? 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | paths: 9 | - 'docs/**' 10 | permissions: 11 | contents: write 12 | jobs: 13 | deploy: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | with: 18 | fetch-depth: 0 19 | - uses: actions/setup-python@v4 20 | with: 21 | python-version: 3.x 22 | - uses: actions/cache@v2 23 | with: 24 | key: ${{ github.ref }} 25 | path: .cache 26 | - run: pip install mkdocs-material mkdocs-table-reader-plugin mkdocs-glightbox mkdocs-static-i18n[material] mkdocs-git-authors-plugin mkdocs-git-revision-date-plugin 27 | - run: mkdocs gh-deploy --force 28 | -------------------------------------------------------------------------------- /Controls/LoadingDialog.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | using System.Windows.Controls; 9 | using System.Windows.Data; 10 | using System.Windows.Documents; 11 | using System.Windows.Input; 12 | using System.Windows.Media; 13 | using System.Windows.Media.Imaging; 14 | using System.Windows.Shapes; 15 | using System.Windows.Shell; 16 | 17 | namespace ModShardLauncher 18 | { 19 | /// 20 | /// LoadingDialog.xaml 的交互逻辑 21 | /// 22 | public partial class LoadingDialog : Window 23 | { 24 | public LoadingDialog() 25 | { 26 | InitializeComponent(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ModUtils/TableUtils/Backers.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Serilog; 3 | 4 | namespace ModShardLauncher; 5 | 6 | public partial class Msl 7 | { 8 | public static void InjectTableBackers(string? name = null, string? nickname = null) 9 | { 10 | // Table filename 11 | const string tableName = "gml_GlobalScript_table_backers"; 12 | 13 | // Load table if it exists 14 | List table = ThrowIfNull(ModLoader.GetTable(tableName)); 15 | 16 | // Prepare line 17 | string newline = $"{name};{nickname};"; 18 | 19 | // Add line to table 20 | table.Add(newline); 21 | ModLoader.SetTable(table, tableName); 22 | Log.Information("Injected {0}:{1} into {2} table.", name, nickname, tableName); 23 | } 24 | } -------------------------------------------------------------------------------- /ModUtils/VariableUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Serilog; 4 | using UndertaleModLib.Models; 5 | 6 | namespace ModShardLauncher 7 | { 8 | public static partial class Msl 9 | { 10 | public static UndertaleVariable GetVariable(string name) 11 | { 12 | UndertaleVariable variable = ModLoader.Data.Variables.First(t => t.Name?.Content == name); 13 | Log.Information("Found variable: {0}", variable); 14 | 15 | return variable; 16 | } 17 | public static UndertaleString GetString(string name) 18 | { 19 | UndertaleString variable = ModLoader.Data.Strings.First(t => t.Content == name); 20 | Log.Information("Found string: {0}", variable); 21 | 22 | return variable; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /Controls/ModSourceInfos.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Windows; 4 | using System.Windows.Controls; 5 | using Serilog; 6 | 7 | namespace ModShardLauncher.Controls 8 | { 9 | /// 10 | /// ModSourceInfos.xaml 的交互逻辑 11 | /// 12 | public partial class ModSourceInfos : UserControl 13 | { 14 | public static ModSourceInfos Instance; 15 | public List ModSources { get; set; } = new(); 16 | public ModSourceInfos() 17 | { 18 | InitializeComponent(); 19 | Instance = this; 20 | } 21 | private async void Open_Click(object sender, EventArgs e) 22 | { 23 | await DataLoader.DoOpenDialog(); 24 | Main.Instance.Refresh(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /future_workflow.md: -------------------------------------------------------------------------------- 1 | # RELEASE RECIPE 2 | 3 | - `git pull` in main repo 4 | - Edit version in ModShardLauncher.csproj 5 | - `git tag vX.XX.X.X` 6 | - `git push origin vX.XX.X.X` last modification 7 | - `git log` to check for properly tagged commit 8 | - `git pull` for safety 9 | - dotnet publish -r win-x64 -c Release /p:PublishSingleFile=true /p:IncludeAllContentForSelfExtract=true /p:PublishReadyToRun=true --self-contained 10 | - locate `publish` folder in `bin/release/net6.0-windows\win-x64\` 11 | - Move required dlls (everything in `ModReference` + `Reference` folders) in the folder 12 | - Run `ModShardLauncher.exe` once 13 | - Verify there is no unexpected file / folder / mods 14 | - Archive to msl.zip 15 | - Test in sandbox environment (no need to install .NET, use a vanilla.win + a mod you **__KNOW__** works) and check proper injection with UTMTCE 16 | - Add archive to release 17 | - Publish release -------------------------------------------------------------------------------- /FilePacker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Serilog; 4 | using System.Diagnostics; 5 | using ModShardPackerReference; 6 | 7 | namespace ModShardLauncher 8 | { 9 | public static class UtilsPacker 10 | { 11 | /// 12 | /// Pack a mod located in using the packing method from . 13 | /// 14 | /// 15 | /// 16 | public static void Pack(string path) 17 | { 18 | FilePacker.Pack( 19 | null, 20 | path, 21 | ModLoader.ModPath, 22 | path, 23 | Main.Instance.mslVersion, 24 | new Type[2] {typeof(ModShardLauncher.Mods.Mod), typeof(UndertaleModLib.Models.UndertaleCode)} 25 | ); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Controls/Settings.xaml.cs: -------------------------------------------------------------------------------- 1 | using ModShardLauncher.Controls; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | using System.Windows.Controls; 9 | using System.Windows.Controls.Primitives; 10 | 11 | namespace ModShardLauncher.Controls 12 | { 13 | /// 14 | /// Settings.xaml 的交互逻辑 15 | /// 16 | public partial class Settings : UserControl 17 | { 18 | public Settings() 19 | { 20 | InitializeComponent(); 21 | } 22 | public List List { get; set; } = new List(); 23 | public GeneralPage GeneralPage = new GeneralPage(); 24 | 25 | private void GeneralSettings_Click(object sender, RoutedEventArgs e) 26 | { 27 | bool isChecked = Msl.ThrowIfNull(((ToggleButton)sender).IsChecked); 28 | if (isChecked) Viewer.Content = GeneralPage; 29 | else Viewer.Content = null; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Resources/Codes/LootModule/scr_msl_resolve_guaranteed_items.gml: -------------------------------------------------------------------------------- 1 | function scr_msl_resolve_guaranteed_items(argument0, argument1, argument2) 2 | { 3 | if (!variable_struct_exists(argument0, "ListItems") 4 | || !variable_struct_exists(argument0, "ListRarity") 5 | || !variable_struct_exists(argument0, "ListDurability")) 6 | { 7 | scr_msl_log("no ItemsTable data"); 8 | return 0; 9 | } 10 | 11 | var items = variable_struct_get(argument0, "ListItems"); 12 | var rarity = variable_struct_get(argument0, "ListRarity"); 13 | var durability = variable_struct_get(argument0, "ListDurability"); 14 | 15 | var size_array = array_length(items); 16 | 17 | if (size_array != array_length(rarity) || 18 | size_array != array_length(durability)) 19 | { 20 | scr_msl_log("List with incorrect size"); 21 | return 0; 22 | } 23 | 24 | for(var _i = 0; _i < size_array; _i++) 25 | { 26 | scr_msl_resolve_items(items[_i], rarity[_i], durability[_i], argument1, argument2); 27 | } 28 | 29 | return 1; 30 | } -------------------------------------------------------------------------------- /ModUtils/LogUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Serilog; 4 | using Newtonsoft.Json; 5 | using System.IO; 6 | using UndertaleModLib.Models; 7 | 8 | namespace ModShardLauncher; 9 | 10 | public static class LogUtils 11 | { 12 | public static void InjectLog() 13 | { 14 | UndertaleGameObject timer = Msl.AddObject("o_msl_timer", isPersistent: true); 15 | UndertaleGameObject log = Msl.AddObject("o_msl_log", isPersistent: true); 16 | 17 | Msl.LoadGML(Msl.EventName("o_gameLoader", EventType.Create, 0)) 18 | .MatchAll() 19 | .InsertBelow("global._msl_log = instance_create_depth(0, 0, -100, o_msl_log);") 20 | .Save(); 21 | 22 | Msl.AddNewEvent(timer, Msl.GetCodeRes("o_msl_timer_Create_0"), EventType.Create, 0); 23 | Msl.AddNewEvent(timer, Msl.GetCodeRes("o_msl_timer_Step_0"), EventType.Step, 0); 24 | Msl.AddNewEvent(log, Msl.GetCodeRes("o_msl_log_Create_0"), EventType.Create, 0); 25 | 26 | Msl.AddInnerFunction("scr_msl_log_save"); 27 | Msl.AddInnerFunction("scr_msl_log"); 28 | } 29 | } -------------------------------------------------------------------------------- /docs/guides/introduction.en.md: -------------------------------------------------------------------------------- 1 | # ModShardLauncher 2 | 3 | ## What is **ModShardLauncher**? 4 | 5 | --- 6 | 7 | **ModShardLauncher** is a tool to patch **mods** into StoneShard original data files.
8 | 9 | In the past, we modders used a tool called **UndertaleModTool (UTMT)** to edit the source code and save it.
10 | But if the game received even the most insignificant of updates, all mods would break.
11 | Additionally, multiple mods couldn't work together unless you actually combined them by hand.
12 | 13 | To deal with these issues and limitations, I wanted to make a tool.
14 | That's what **ModShardLauncher** is. 15 | 16 | ## How does **ModShardLauncher** work? 17 | 18 | --- 19 | 20 | Did you know that **UTMT** was made in C# ?
21 | 22 | Using **UTMT**'s source code, **ModShardLauncher** can load data files.
23 | And with C#'s reflection, **ModShardLauncher** can load .dll files as mods and patch the '**modthings**' into data files, then save them. 24 | 25 | ## I want to start modding now! 26 | 27 | --- 28 | 29 | [Just check out the guides here !](../guides/start-modding.md) -------------------------------------------------------------------------------- /docs/guides/api.zh.md: -------------------------------------------------------------------------------- 1 | # API 2 | 3 | 该页面介绍了本工具可以使用的API. 4 | 5 | [TOC] 6 | 7 | ## ModLoader 8 | 9 | ### ModLoader.AddObject(string name) 10 | 11 | 根据给出的名称, 向数据文件中添加一个Game Object, 并返回这个Object. 12 | 13 | ### ModLoader.GetObject(string name) 14 | 15 | 根据给出的名称, 从数据文件中查找一个Game Object, 并返回这个Object. 如果找不到则返回null. 16 | 17 | ### ModLoader.SetObject(string name, UndertaleGameObject o) 18 | 19 | 根据给出的名称和Object, 给数据文件中对应的Game Object赋值. 20 | 21 | ### ModLoader.AddCode(string Code, string name) 22 | 23 | 根据给出的名称和Code, 向数据文件中添加一个Code, 并返回这个Code. 24 | !!! notice "" 25 | 注意不要使用AddCode来添加新的function, 若想添加新的function, 请使用AddFunction. 26 | 27 | ### ModLoader.AddFunction(string Code, string name) 28 | 29 | 根据给出的名称和Code, 向数据文件中添加一个包含Function的Code, 并返回这个Code 30 | !!! notice "" 31 | 请使用本方法添加Function, 否则数据文件将损坏. 32 | 33 | ### ModLoader.GetTable(string name) 34 | 35 | 根据给出的名称, 从数据文件中查找一个Table, 将其转换为List并返回这个List. 36 | 37 | ### GetDecompiledCode(string name) 38 | 39 | 根据给出的名称, 从数据文件中查找一个Code, 并返回这个Code的Decompile版本代码. 40 | 41 | ### GetDisassemblydCode(string name) 42 | 43 | 根据给出的名称, 从数据文件中查找一个Code, 并返回这个Code的Disassembly版本代码.(说实话, 我并不知道这个方法有什么卵用) -------------------------------------------------------------------------------- /Controls/LoadingDialog.xaml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 18 | -------------------------------------------------------------------------------- /Controls/ScriptEnginePage.xaml.cs: -------------------------------------------------------------------------------- 1 | using ModShardLauncher.Mods; 2 | using System; 3 | using System.Windows; 4 | using System.Windows.Input; 5 | using System.Windows.Threading; 6 | 7 | namespace ModShardLauncher.Controls 8 | { 9 | /// 10 | /// ScriptEnginePage.xaml 的交互逻辑 11 | /// 12 | public partial class ScriptEnginePage : Window 13 | { 14 | public static ScriptEnginePage? Instance; 15 | 16 | public DispatcherTimer Timer; 17 | 18 | public ScriptEnginePage() 19 | { 20 | InitializeComponent(); 21 | Instance = this; 22 | Timer = new DispatcherTimer(); 23 | } 24 | 25 | private void ScriptBox_KeyDown(object sender, KeyEventArgs e) 26 | { 27 | if (e.Key == Key.Enter) 28 | { 29 | if (ScriptBox.Text.Length == 0) return; 30 | ModInterfaceServer.SendScript(ScriptBox.Text); 31 | ScriptBox.Text = ""; 32 | } 33 | } 34 | 35 | private void Window_Closed(object sender, EventArgs e) 36 | { 37 | ModInterfaceServer.Server.Close(); 38 | ModInterfaceServer.Server = null; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Loader/ChecksumChecker.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System; 3 | using System.Linq; 4 | using System.Security.Cryptography; 5 | 6 | namespace ModShardLauncher.Loader 7 | { 8 | static public class ChecksumChecker 9 | { 10 | private static string ComputeChecksum(FileStream stream) 11 | { 12 | using MD5 md5 = MD5.Create(); 13 | return Convert.ToHexString(md5.ComputeHash(stream)); 14 | } 15 | /// 16 | /// Return True if the MD5 checksum of a file is equal a valid precomputed checksum. 17 | /// 18 | /// 19 | /// 20 | public static bool CompareChecksum(FileStream stream) 21 | { 22 | string hash = ComputeChecksum(stream); 23 | 24 | string[] checksums = 25 | { 26 | "5F91989CA7E2A2B1234B2CD2A6AF9821", // Steam 0.9.1.16-vm 27 | "2BD331F728428746FA337D6C7B67040A", // Steam 0.9.1.17-vm 28 | "6F9F1E29275EEF60E3A725ECA1033DF8", // Steam 0.9.1.18-vm 29 | "47282D0C650216D88AE25FA99615F9CB", // Steam 0.9.3.9-vm 30 | }; 31 | return checksums.Contains(hash); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /ModUtils/HookUtils.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace ModShardLauncher 9 | { 10 | public static partial class Msl 11 | { 12 | /// 13 | /// Add a hook with the name to . 14 | /// Havent done yet. 15 | /// 16 | /// 17 | /// 18 | /// All the things you want to get. 19 | public static void HookFunction(string functionName, string hookName, params string[] paramNames) 20 | { 21 | Log.Information("Trying add hook in: {0}", functionName); 22 | 23 | List? originalCode = GetStringGMLFromFile(functionName).Split("\n").ToList(); 24 | originalCode.Append($"var {hookName} = createHookObj({paramNames.Length}, {string.Join(", ", paramNames)})"); 25 | originalCode.Append($"SendMsg(\"HOK\", \"{hookName}\" + {hookName}, false)"); 26 | SetStringGMLInFile(string.Join("\n", originalCode), functionName); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Mods/DisassemblyEditor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using UndertaleModLib; 5 | using UndertaleModLib.Decompiler; 6 | using UndertaleModLib.Models; 7 | 8 | namespace ModShardLauncher.Mods 9 | { 10 | public class DisassemblyEditor 11 | { 12 | public UndertaleData Data => DataLoader.data; 13 | public UndertaleCode Code; 14 | public List CodeContents; 15 | public int Index { get; private set; } = 0; 16 | public DisassemblyEditor(UndertaleCode code) 17 | { 18 | Code = code; 19 | CodeContents = code.Disassemble(Data.Variables, Data.CodeLocals.For(Code)).Split("\n").ToList(); 20 | } 21 | public bool TryGotoNext(Func predicate) 22 | { 23 | if (CodeContents.FirstOrDefault(predicate) == default) return false; 24 | else 25 | { 26 | Index = CodeContents.IndexOf(CodeContents.First(predicate)); 27 | return true; 28 | } 29 | } 30 | public void Emit(OpCodes o) 31 | { 32 | 33 | } 34 | } 35 | public enum OpCodes 36 | { 37 | Conv, 38 | Mul, 39 | Div, 40 | Rem, 41 | Mod, 42 | Add, 43 | Sub 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /ModShardLauncherTest/ModShardLauncherTest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0-windows 5 | enable 6 | enable 7 | false 8 | false 9 | True 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | all 22 | 23 | 24 | runtime; build; native; contentfiles; analyzers; buildtransitive 25 | all 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /Mods/Mod.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace ModShardLauncher.Mods 10 | { 11 | public class Mod 12 | { 13 | public override string ToString() 14 | { 15 | return Name; 16 | } 17 | public virtual string Name { get => GetType().Name; } 18 | public virtual string Author { get => "未知"; } 19 | public virtual string Description { get => "未知"; } 20 | public virtual string ShortDesc { get => "未知"; } 21 | public virtual string Version { get => "v0.0.0.0"; } 22 | public virtual string TargetVersion { get => "v0.0.0.0"; } 23 | public List ModWeapons = new(); 24 | public ModFile ModFiles = new(); 25 | public Mod() { } 26 | public virtual void LoadAssembly() 27 | { 28 | 29 | } 30 | public virtual void PatchMod() 31 | { 32 | 33 | } 34 | } 35 | public enum ModLanguage 36 | { 37 | Russian, 38 | English, 39 | Chinese, 40 | German, 41 | Spanish, 42 | French, 43 | Italian, 44 | Portuguese, 45 | Polish, 46 | Turkish, 47 | Japanese, 48 | Korean 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Controls/MyItemsControl.xaml: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Resources/Codes/LootModule/scr_msl_resolve_items.gml: -------------------------------------------------------------------------------- 1 | function scr_msl_resolve_items(argument0, argument1, argument2, argument3, argument4) 2 | { 3 | if (argument1 == -1) 4 | { 5 | var objectName = argument0; 6 | var obj = ""; 7 | if (argument3 == 0) 8 | { 9 | obj = asset_get_index("o_inv_" + objectName) 10 | if (obj > -1) 11 | { 12 | scr_inventory_add_item(obj); 13 | } 14 | else 15 | { 16 | scr_msl_log("invalid object " + string(objectName)); 17 | } 18 | } 19 | else 20 | { 21 | obj = asset_get_index("o_loot_" + objectName) 22 | if (obj > -1) 23 | { 24 | scr_loot_drop(argument4.x, argument4.y, obj) 25 | } 26 | else 27 | { 28 | scr_msl_log("invalid object " + string(objectName)); 29 | } 30 | } 31 | } 32 | else 33 | { 34 | if (argument3 == 0) 35 | { 36 | with (scr_inventory_add_weapon(argument0, argument1)) 37 | { 38 | scr_inv_atr_set("Duration", argument2); 39 | } 40 | } 41 | else 42 | { 43 | with (scr_weapon_loot(argument0, argument4.x, argument4.y, 100, argument1)) 44 | { 45 | scr_inv_atr_set("Duration", argument2) 46 | } 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /Controls/SourceBar.xaml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32; 2 | using Serilog; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using System.Windows; 9 | using System.Windows.Controls; 10 | using System.Windows.Data; 11 | using System.Windows.Documents; 12 | using System.Windows.Input; 13 | using System.Windows.Media; 14 | using System.Windows.Media.Imaging; 15 | using System.Windows.Navigation; 16 | using System.Windows.Shapes; 17 | 18 | namespace ModShardLauncher.Controls 19 | { 20 | /// 21 | /// SourceBar.xaml 的交互逻辑 22 | /// 23 | public partial class SourceBar : UserControl 24 | { 25 | public SourceBar() 26 | { 27 | InitializeComponent(); 28 | } 29 | 30 | private void CompileButton_Click(object sender, RoutedEventArgs e) 31 | { 32 | try 33 | { 34 | UtilsPacker.Pack(Msl.ThrowIfNull(DataContext as ModSource).Path); 35 | } 36 | catch(Exception ex) 37 | { 38 | Log.Error(ex, "Something went wrong"); 39 | } 40 | 41 | Msl.ThrowIfNull((UserControl)Main.Instance.Viewer.Content).UpdateLayout(); 42 | Main.Instance.Refresh(); 43 | } 44 | 45 | private void OpenButton_Click(object sender, RoutedEventArgs e) 46 | { 47 | System.Diagnostics.Process.Start("explorer.exe", Msl.ThrowIfNull(DataContext as ModSource).Path); 48 | 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /CodeResources.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | using Serilog; 7 | 8 | namespace ModShardLauncher 9 | { 10 | internal static class CodeResources 11 | { 12 | private static readonly Lazy> _scripts = new(LoadAllScripts); 13 | public static string GetGML(string name) 14 | { 15 | if (_scripts.Value.TryGetValue(name, out var script)) return script; 16 | 17 | throw new ArgumentException($"GML script '{name}' not found"); 18 | } 19 | private static Dictionary LoadAllScripts() 20 | { 21 | Dictionary scripts = new(); 22 | Assembly assembly = Assembly.GetExecutingAssembly(); 23 | 24 | // Load all .gml resources 25 | IEnumerable resourceNames = assembly.GetManifestResourceNames() 26 | .Where(name => name.EndsWith(".gml")); 27 | foreach (string resourceName in resourceNames) 28 | { 29 | string? scriptName = Path.GetFileNameWithoutExtension(resourceName); 30 | 31 | using Stream? stream = 32 | assembly.GetManifestResourceStream(resourceName) ?? 33 | throw new FileNotFoundException($"GML script '{scriptName}' not found"); 34 | using StreamReader reader = new(stream); 35 | scripts[scriptName.Split('.').Last()] = reader.ReadToEnd(); 36 | } 37 | 38 | return scripts; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Converters/CompareNumbersConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | using System.Windows.Data; 9 | 10 | namespace ModShardLauncher.Converters 11 | { 12 | public class CompareNumbersConverter : IMultiValueConverter 13 | { 14 | // these could be overridden on declaration 15 | public object TrueValue { get; set; } = Visibility.Visible; 16 | public object FalseValue { get; set; } = Visibility.Collapsed; 17 | 18 | public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) 19 | { 20 | double a, b; 21 | try 22 | { 23 | a = (double)values[0]; 24 | b = (double)values[1]; 25 | } 26 | catch 27 | { 28 | return null; 29 | } 30 | 31 | if (parameter is string par) 32 | { 33 | int r; 34 | if (par == ">") // greater than 35 | r = 1; 36 | else if (par == "<") // less than 37 | r = -1; 38 | else 39 | return null; 40 | 41 | bool res = a.CompareTo(b) == r; 42 | return res ? TrueValue : FalseValue; 43 | } 44 | 45 | return null; 46 | } 47 | 48 | public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) 49 | { 50 | throw new NotImplementedException(); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Controls/MyItemsControl.xaml.cs: -------------------------------------------------------------------------------- 1 | using ModShardLauncher.Mods; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | using System.Windows.Controls; 9 | using System.Windows.Data; 10 | using System.Windows.Documents; 11 | using System.Windows.Input; 12 | using System.Windows.Media; 13 | using System.Windows.Media.Imaging; 14 | using System.Windows.Navigation; 15 | using System.Windows.Shapes; 16 | 17 | namespace ModShardLauncher.Controls 18 | { 19 | /// 20 | /// MyScrollViewer.xaml 的交互逻辑 21 | /// 22 | public partial class MyItemsControl : UserControl 23 | { 24 | public MyItemsControl() 25 | { 26 | InitializeComponent(); 27 | } 28 | public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register( 29 | "ItemsSource", 30 | typeof(object), 31 | typeof(MyItemsControl), 32 | new PropertyMetadata(default(object), OnItemsPropertyChanged)); 33 | private static void OnItemsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 34 | { 35 | } 36 | public List ItemsSource { get; set; } 37 | } 38 | public class TempSelector : DataTemplateSelector 39 | { 40 | public override DataTemplate SelectTemplate(object item, DependencyObject container) 41 | { 42 | if (item is ModFile) 43 | return Application.Current.FindResource("mod") as DataTemplate; 44 | else return Application.Current.FindResource("source") as DataTemplate; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Controls/ModBar.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using System.Windows; 9 | using System.Windows.Controls; 10 | using System.Windows.Data; 11 | using System.Windows.Documents; 12 | using System.Windows.Input; 13 | using System.Windows.Media; 14 | using System.Windows.Media.Imaging; 15 | using System.Windows.Navigation; 16 | using System.Windows.Shapes; 17 | 18 | namespace ModShardLauncher.Controls 19 | { 20 | /// 21 | /// ModBar.xaml 的交互逻辑 22 | /// 23 | public partial class ModBar : UserControl 24 | { 25 | public ImageSource? Icon = null; 26 | public ModBar() 27 | { 28 | InitializeComponent(); 29 | } 30 | } 31 | public class ByteArrayToImageSourceConverter : IValueConverter 32 | { 33 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 34 | { 35 | byte[] imageData = (byte[])value; 36 | if (imageData.Length == 0) return new BitmapImage(new Uri("/Resources/Sprites/icon_default.png", UriKind.Relative)); 37 | 38 | BitmapImage biImg = new(); 39 | MemoryStream ms = new(imageData); 40 | biImg.BeginInit(); 41 | biImg.StreamSource = ms; 42 | biImg.EndInit(); 43 | biImg.Freeze(); 44 | 45 | return biImg; 46 | } 47 | 48 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 49 | { 50 | throw new NotImplementedException(); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ModUtils/TableUtils/PotionsStats.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Serilog; 4 | 5 | namespace ModShardLauncher; 6 | 7 | public partial class Msl 8 | { 9 | public static void InjectTablePotionsStats(string name, string effectScript, params string[] blockedEffects) 10 | { 11 | // Weird table. It has no header, and seems to work like this: 12 | // Name of the potion | script with actual effect | any number of incompatible effects that cannot coexist with the actual effect 13 | // 14 | // Example: 15 | // MyPotion;myEffectScript;blockedEffectScript1;blockedEffectScript2;blockedEffectScript3;blockedEffectScript4... 16 | // 17 | // This seems to be used when rolling potions in the game, to ensure that the potion doesn't have conflicting effects. 18 | // Needs more investigation to figure out how many blocked effects are allowed, max vanilla potion has 26. 19 | 20 | // Table filename 21 | const string tableName = "gml_GlobalScript_table_potions_stats"; 22 | 23 | // Load table if it exists 24 | List table = ThrowIfNull(ModLoader.GetTable(tableName)); 25 | 26 | // Prepare line 27 | string newline = $"{name};{effectScript};{string.Join(";", blockedEffects)};"; 28 | 29 | // Adding potentially missing ; at the end of the line. Could be unnecessary ? 30 | while (newline.Count(t => t == ';') < 28) 31 | newline += ';'; 32 | 33 | // Add line to end of table 34 | table.Add(newline); 35 | ModLoader.SetTable(table, tableName); 36 | Log.Information("Injected {0} into {1} table.", name, tableName); 37 | } 38 | } -------------------------------------------------------------------------------- /Controls/MyToggleButton.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | using System.Windows.Controls; 9 | using System.Windows.Data; 10 | using System.Windows.Documents; 11 | using System.Windows.Input; 12 | using System.Windows.Media; 13 | using System.Windows.Media.Imaging; 14 | using System.Windows.Navigation; 15 | using System.Windows.Shapes; 16 | 17 | namespace ModShardLauncher.Controls 18 | { 19 | /// 20 | /// MyToggleButton.xaml 的交互逻辑 21 | /// 22 | public partial class MyToggleButton : UserControl 23 | { 24 | public ImageSource ImageSource { get; set; } 25 | public string Text { get; set; } 26 | [Category("Behavior")] 27 | public event EventHandler Checked; 28 | public event EventHandler Click; 29 | public static readonly DependencyProperty TextProperty = DependencyProperty.Register( 30 | "Text", 31 | typeof(string), 32 | typeof(MyToggleButton), 33 | new PropertyMetadata(default(string), OnTextChanged) 34 | ); 35 | public MyToggleButton() 36 | { 37 | InitializeComponent(); 38 | } 39 | private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 40 | { 41 | 42 | } 43 | private void MyButton_Checked(object sender, RoutedEventArgs e) 44 | { 45 | Checked?.Invoke(this, e); 46 | } 47 | 48 | private void MyButton_Click(object sender, RoutedEventArgs e) 49 | { 50 | Click?.Invoke(this, e); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Resources/Codes/LoggerModule/scr_msl_log.gml: -------------------------------------------------------------------------------- 1 | function scr_msl_log(argument0) 2 | { 3 | var time = date_datetime_string(date_current_datetime()); 4 | if (global._msl_log != noone) 5 | { 6 | if (global._msl_log.buf != noone) 7 | { 8 | var string_log = "[" + time + "]: " + argument0 + "\n"; 9 | var len_log = string_byte_length(string_log); 10 | 11 | if (len_log > global._msl_log.size - 1) 12 | { 13 | var msg_space = global._msl_log.size - string_byte_length("[" + time + "]: \n") - 1; 14 | argument0 = string_copy(argument0, 1, msg_space); 15 | string_log = "[" + time + "]: " + argument0 + "\n"; 16 | len_log = string_byte_length(string_log); 17 | } 18 | 19 | if (len_log + global._msl_log.cur_size > global._msl_log.size) 20 | { 21 | scr_msl_log_save(); 22 | global._msl_log.nfile += 1; 23 | global._msl_log.cur_size = 0; 24 | } 25 | 26 | buffer_write(global._msl_log.buf, buffer_text, string_log); 27 | global._msl_log.cur_size += len_log; 28 | 29 | if (global._msl_log.timer == noone || !instance_exists(global._msl_log.timer)) 30 | { 31 | var t = instance_create_depth(0, 0, -100, o_msl_timer); 32 | t.end_time = 5; 33 | t.func = gml_Script_scr_msl_log_save; 34 | 35 | global._msl_log.timer = t.id; 36 | } 37 | } 38 | else 39 | { 40 | scr_actionsLogUpdate("msl log buff does not exist. Please report that bug to the MSL devs."); 41 | } 42 | } 43 | else 44 | { 45 | scr_actionsLogUpdate("msl log does not exist. Please report that bug to the MSL devs."); 46 | } 47 | } -------------------------------------------------------------------------------- /Controls/ModSourceInfos.xaml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Controls/ScriptEnginePage.xaml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Language/ru-ru.xaml: -------------------------------------------------------------------------------- 1 | 4 | Файл 5 | Настройки 6 | Открыть 7 | Язык 8 | Обновить 9 | Загрузить 10 | Поиск по названию мода... 11 | Добро пожаловать 12 | Добро пожаловать в ModShardLauncher! 13 | Если вы хотите обновить моды, просто вставьте их в папку /Mods и нажмите кнопку "Обновить" слева. 14 | Сначала нажмите кнопку "Файл" и откройте исходный файл данных, затем выберите моды, которые вы хотите включить. 15 | Затем нажмите кнопку "Загрузить", чтобы загрузить моды в файл данных. 16 | Название мода 17 | Автор мода 18 | Описание мода 19 | Включить мод 20 | Версия мода 21 | Версия игры 22 | Компилировать 23 | Пожалуйста, загрузите файл данных игры сначала! 24 | Версия мода отличается от версии игры 25 | Версия мода отличается от версии игры, возможно, это вызовет сбой. Вы всё равно хотите загрузить его? 26 | Файл мода отсутствует 27 | -------------------------------------------------------------------------------- /Controls/MainPage.xaml: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 15 | 16 | 18 | 20 | 23 | 24 | 26 | 28 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Controls/GeneralPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using ModShardLauncher.Mods; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | using System.Windows.Controls; 9 | using System.Windows.Controls.Primitives; 10 | using System.Windows.Media; 11 | using UndertaleModTool; 12 | 13 | namespace ModShardLauncher.Controls 14 | { 15 | /// 16 | /// GeneralPage.xaml 的交互逻辑 17 | /// 18 | public partial class GeneralPage : UserControl 19 | { 20 | public GeneralPage() 21 | { 22 | InitializeComponent(); 23 | 24 | // add Languages 25 | Languages.Add("中文"); 26 | Languages.Add("English"); 27 | //Languages.Add("Русский"); 28 | 29 | switch (Main.Settings.Language) 30 | { 31 | case "Chinese": 32 | LangSelector.SelectedIndex = 0; 33 | UserSettings.ChangeLanguage(0); 34 | break; 35 | case "English": 36 | LangSelector.SelectedIndex = 1; 37 | UserSettings.ChangeLanguage(1); 38 | break; 39 | case "Russian": 40 | LangSelector.SelectedIndex = 2; 41 | UserSettings.ChangeLanguage(2); 42 | break; 43 | } 44 | } 45 | public int selectIndex { get; set; } = 1; 46 | public List Languages { get; set; } = new List(); 47 | public int selection = -1; 48 | 49 | private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) 50 | { 51 | if (selection == -1) 52 | { 53 | selection = LangSelector.SelectedIndex; 54 | return; 55 | } 56 | ComboBox combo = Msl.ThrowIfNull(sender as ComboBox); 57 | UserSettings.ChangeLanguage(combo.SelectedIndex); 58 | selection = combo.SelectedIndex; 59 | LangSelector.SelectedIndex = selection; 60 | } 61 | 62 | private void Logger_Checked(object sender, RoutedEventArgs e) 63 | { 64 | Main.Settings.EnableLogger = Msl.ThrowIfNull(Logger.IsChecked); 65 | UserSettings.CheckLog(Main.Settings.EnableLogger); 66 | Main.Settings.SaveSettings(); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /ModUtils/TableUtils/RecipesCraft.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Linq; 5 | using System.Runtime.Serialization; 6 | using Serilog; 7 | 8 | namespace ModShardLauncher; 9 | 10 | [SuppressMessage("ReSharper", "InconsistentNaming")] 11 | public partial class Msl 12 | { 13 | public enum RecipesCraftHook 14 | { 15 | BASIC, 16 | ADVANCED, 17 | OTHER 18 | } 19 | public enum RecipesCraftCategory 20 | { 21 | basic, 22 | advanced, 23 | other 24 | } 25 | 26 | public enum RecipesCraftSource 27 | { 28 | Trade, 29 | [EnumMember(Value = "Base Recipe")] 30 | BaseRecipe, 31 | [EnumMember(Value = "Random Find")] 32 | RandomFind, 33 | [EnumMember(Value = "Pol Find")] 34 | PolFind 35 | } 36 | 37 | public static void InjectTableRecipesCraft( 38 | RecipesCraftHook hook, 39 | string NAME, 40 | RecipesCraftCategory CAT, 41 | ushort XP, 42 | ushort AMOUNT, 43 | RecipesCraftSource SOURCE, 44 | string Recipe1, 45 | string? Recipe2 = null, 46 | string? Recipe3 = null, 47 | string? Recipe4 = null, 48 | string? Recipe5 = null, 49 | string? Recipe6 = null 50 | ) 51 | { 52 | // Table filename 53 | const string tableName = "gml_GlobalScript_table_recipes_craft"; 54 | 55 | // Load table if it exists 56 | List table = ThrowIfNull(ModLoader.GetTable(tableName)); 57 | 58 | // Prepare new line 59 | string newline = $"{NAME};{CAT};{Recipe1};{Recipe2 ?? "-"};{Recipe3 ?? "-"};{Recipe4 ?? "-"};{Recipe5 ?? "-"};{Recipe6 ?? "-"};{AMOUNT};{XP};{GetEnumMemberValue(SOURCE)}"; 60 | 61 | // Find hook 62 | (int ind, string? foundLine) = table.Enumerate().FirstOrDefault(x => x.Item2.Contains(hook.ToString())); 63 | 64 | // Add line to table 65 | if (foundLine != null) 66 | { 67 | table.Insert(ind + 1, newline); 68 | ModLoader.SetTable(table, tableName); 69 | Log.Information("Injected craft recipe {0} into {1} under {2}", NAME, tableName, hook); 70 | } 71 | else 72 | { 73 | Log.Error("Cannot find hook {0} in table {1}", hook, tableName); 74 | throw new Exception($"Hook {hook} not found in table {tableName}"); 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /Resources/Codes/LootModule/scr_msl_resolve_loot_table.gml: -------------------------------------------------------------------------------- 1 | function scr_msl_resolve_loot_table(argument0, argument1) 2 | { 3 | var objectName = object_get_name(argument0.object_index); 4 | scr_msl_log("instance: " + string(argument0.id) + " of " + objectName); 5 | 6 | var referenceLootTable = scr_msl_resolve_refence_table(argument0); 7 | if (referenceLootTable == noone) 8 | { 9 | scr_msl_log("Reference Table resolution failed"); 10 | return 0; 11 | } 12 | 13 | var file = file_text_open_read("loot_table.json"); 14 | var json = file_text_read_string(file); 15 | var data = json_parse(json); 16 | 17 | if (!variable_struct_exists(data, referenceLootTable)) 18 | { 19 | scr_msl_log("cant find ref " + referenceLootTable); 20 | file_text_close(file); 21 | return 0; 22 | } 23 | var lootStruct = variable_struct_get(data, referenceLootTable); 24 | 25 | if (!variable_struct_exists(lootStruct, "GuaranteedItems")) 26 | { 27 | scr_msl_log("no guaranteedItems"); 28 | file_text_close("loot_table.json"); 29 | return 0; 30 | } 31 | var guaranteedItems = variable_struct_get(lootStruct, "GuaranteedItems"); 32 | 33 | if (!scr_msl_resolve_guaranteed_items(guaranteedItems, argument1, argument0)) 34 | { 35 | scr_msl_log("Guaranteed Items resolution failed"); 36 | file_text_close("loot_table.json"); 37 | return 0; 38 | } 39 | 40 | if (!variable_struct_exists(lootStruct, "RandomLootMin") || !variable_struct_exists(lootStruct, "RandomLootMax") || !variable_struct_exists(lootStruct, "EmptyWeight")) 41 | { 42 | scr_msl_log("no int"); 43 | file_text_close("loot_table.json"); 44 | return 0; 45 | } 46 | 47 | var randomLootMin = variable_struct_get(lootStruct, "RandomLootMin"); 48 | var randomLootMax = variable_struct_get(lootStruct, "RandomLootMax"); 49 | var emptyWeight = variable_struct_get(lootStruct, "EmptyWeight"); 50 | 51 | var iteration = randomLootMin + irandom(randomLootMax - randomLootMin); 52 | scr_msl_log("iteration " + string(iteration)); 53 | 54 | if (!variable_struct_exists(lootStruct, "RandomItemsTable")) 55 | { 56 | scr_msl_log("no RandomItemsTable"); 57 | file_text_close("loot_table.json"); 58 | return 0; 59 | } 60 | 61 | var randomItemsTable = variable_struct_get(lootStruct, "RandomItemsTable"); 62 | 63 | scr_msl_resolve_random_items(randomItemsTable, argument1, argument0, iteration, emptyWeight); 64 | 65 | file_text_close(file); 66 | 67 | return 1; 68 | } -------------------------------------------------------------------------------- /Resources/Codes/LootModule/scr_msl_resolve_random_items.gml: -------------------------------------------------------------------------------- 1 | function scr_msl_resolve_random_items(argument0, argument1, argument2, argument3, argument4) 2 | { 3 | if (!variable_struct_exists(argument0, "ItemsTable") 4 | || !variable_struct_exists(argument0, "ListWeight")) 5 | { 6 | scr_msl_log("no randomLoot data"); 7 | return 0; 8 | } 9 | 10 | var itemsTable = variable_struct_get(argument0, "ItemsTable"); 11 | var weight = variable_struct_get(argument0, "ListWeight"); 12 | 13 | if (!variable_struct_exists(itemsTable, "ListItems") 14 | || !variable_struct_exists(itemsTable, "ListRarity") 15 | || !variable_struct_exists(itemsTable, "ListDurability")) 16 | { 17 | scr_msl_log("no randomLoot data"); 18 | return 0; 19 | } 20 | 21 | var items = variable_struct_get(itemsTable, "ListItems"); 22 | var rarity = variable_struct_get(itemsTable, "ListRarity"); 23 | var durability = variable_struct_get(itemsTable, "ListDurability"); 24 | 25 | var sizeItems = array_length(items); 26 | var tableItemsSpecialLootAlready = array_create(sizeItems, 0); 27 | 28 | for (var _j = 0; _j < argument3; _j++) 29 | { 30 | var totalWeight = argument4; 31 | for (var _i = 0; _i < sizeItems; _i++) 32 | { 33 | if (ds_list_find_index(scr_atr("specialItemsPool"), items[_i]) != -1) 34 | { 35 | tableItemsSpecialLootAlready[_i] = 1; 36 | } 37 | else 38 | { 39 | totalWeight += weight[_i]; 40 | } 41 | } 42 | scr_msl_log("totalWeight " + string(totalWeight)); 43 | 44 | var randomWeight = irandom(totalWeight - 1); 45 | scr_msl_log("randomWeight " + string(randomWeight)); 46 | var cumulativeWeight = 0; 47 | var index = -1; 48 | 49 | for (var _i = 0; _i < sizeItems; _i++) 50 | { 51 | if (tableItemsSpecialLootAlready[_i] == 1) 52 | { 53 | continue; 54 | } 55 | cumulativeWeight += weight[_i] 56 | if (randomWeight < cumulativeWeight) 57 | { 58 | index = _i; 59 | break; 60 | } 61 | } 62 | 63 | if (index != -1) 64 | { 65 | scr_msl_log("found " + string(index)); 66 | scr_msl_resolve_items(items[index], rarity[index], durability[index], argument1, argument2); 67 | } 68 | else 69 | { 70 | scr_msl_log("found empty"); 71 | } 72 | } 73 | 74 | return 1; 75 | } -------------------------------------------------------------------------------- /Controls/ModInfos.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | using System.Windows.Controls; 9 | using ModShardLauncher.Core.Errors; 10 | using ModShardLauncher.Core.UI; 11 | using ModShardLauncher.Mods; 12 | using Serilog; 13 | 14 | namespace ModShardLauncher.Controls 15 | { 16 | /// 17 | /// ModInfos.xaml 的交互逻辑 18 | /// 19 | public partial class ModInfos : UserControl 20 | { 21 | public static ModInfos Instance; 22 | public List Mods { get; set; } = new(); 23 | public ModInfos() 24 | { 25 | InitializeComponent(); 26 | Instance = this; 27 | } 28 | private async void Open_Click(object sender, EventArgs e) 29 | { 30 | await DataLoader.DoOpenDialog(); 31 | Main.Instance.Refresh(); 32 | } 33 | private async void Save_Click(object sender, EventArgs e) 34 | { 35 | if (DataLoader.data.FORM == null) 36 | { 37 | MessageBox.Show(Application.Current.FindResource("LoadDataWarning").ToString()); 38 | return; 39 | } 40 | 41 | MSLDiagnostic? diag = ModLoader.PatchFile(); 42 | 43 | if (diag is null) 44 | { 45 | Main.Instance.LogModListStatus(); 46 | Log.Information("Successfully patch vanilla"); 47 | 48 | Task save = DataLoader.DoSaveDialog(); 49 | await save; 50 | if (save.Result) 51 | { 52 | LootUtils.SaveLootTables(Msl.ThrowIfNull(Path.GetDirectoryName(DataLoader.savedDataPath))); 53 | } 54 | else 55 | { 56 | Log.Information("Saved cancelled."); 57 | } 58 | } 59 | else 60 | { 61 | Main.Instance.LogModListStatus(); 62 | Log.Information("Failed patching vanilla"); 63 | ErrorMessageDialog.Show(diag.Title(), diag.MessageDialog(), Main.Instance.logPath); 64 | } 65 | 66 | // reload the data 67 | await DataLoader.LoadFile(DataLoader.dataPath); 68 | Main.Instance.Refresh(); 69 | } 70 | 71 | private void Server_Click(object sender, EventArgs e) 72 | { 73 | ModInterfaceServer.StartServer(1333); 74 | Main.Instance.Refresh(); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Language/zh-cn.xaml: -------------------------------------------------------------------------------- 1 | 4 | 一款用于StoneShard的模组工具. 5 | 模组之旅从这里启程. 6 | 文件 7 | 设置 8 | 打开 9 | 语言 10 | 启用Logger 11 | 刷新 12 | 加载 13 | 以模组名称搜索... 14 | 欢迎 15 | 启用 16 | 禁用 17 | 欢迎使用ModShardLauncher! 18 | 若想重新加载mod 请在启动器目录下的Mods文件夹内放入你想加载的Mod 并点击左上角的刷新按钮 19 | Mod名称 20 | Mod作者 21 | Mod描述 22 | 是否启用 23 | Mod版本 24 | 游戏版本 25 | 打包错误, 未能保存文件 26 | 编译 27 | 请先加载游戏文件! 28 | Mod版本与游戏版本不一致 29 | Mod版本与游戏版本不一致, 如果执意加载可能会崩溃.仍然要加载吗? 30 | Mod文件丢失 31 | 编译错误: 32 | 将此路径保存为默认加载路径? 33 | 将此路径保存为默认保存路径? 34 | 常规 35 | 模组 36 | 模组源码 37 | 设置 38 | 菜单 39 | 关闭 40 | 最小化 41 | 加载中... 42 | 模组交互服务器已在{0}端口上启动. 43 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /ModUtils/WeaponUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using ModShardLauncher.Mods; 5 | using Serilog; 6 | 7 | namespace ModShardLauncher 8 | { 9 | public static partial class Msl 10 | { 11 | public static Weapon GetWeapon(string id) 12 | { 13 | string weaponsName = ModLoader.Weapons.First(t => t.StartsWith(id)); 14 | 15 | // for a lazy evaluation to avoid going through all the WeaponDescriptions list 16 | IEnumerator weaponDescriptionEnumerator = ModLoader.WeaponDescriptions.Where(t => t.StartsWith(id)).GetEnumerator(); 17 | 18 | // getting the first element - the localization name 19 | weaponDescriptionEnumerator.MoveNext(); 20 | List localizationNames = weaponDescriptionEnumerator.Current.Split(";").ToList(); 21 | localizationNames.Remove(""); 22 | localizationNames.RemoveAt(0); 23 | 24 | // getting the second element - the description 25 | weaponDescriptionEnumerator.MoveNext(); 26 | List weaponDescription = weaponDescriptionEnumerator.Current.Split(";").ToList(); 27 | weaponDescription.Remove(""); 28 | weaponDescription.RemoveAt(0); 29 | 30 | Log.Information("Found weapon: {0}", weaponsName); 31 | return new(weaponsName, weaponDescription, localizationNames); 32 | } 33 | public static void SetWeapon(string id, Weapon weapon) 34 | { 35 | string targetName = ModLoader.Weapons.First(t => t.StartsWith(id)); 36 | int indexTargetName = ModLoader.Weapons.IndexOf(targetName); 37 | 38 | // for a lazy evaluation to avoid going through all the WeaponDescriptions list 39 | IEnumerator<(int, string)> weaponDescriptionEnumerator = ModLoader.WeaponDescriptions.Where(t => t.StartsWith(id)).Enumerate().GetEnumerator(); 40 | 41 | // getting the first element - the localization name 42 | weaponDescriptionEnumerator.MoveNext(); 43 | (int indexLocalizationName, _) = weaponDescriptionEnumerator.Current; 44 | 45 | // getting the first element - the description 46 | weaponDescriptionEnumerator.MoveNext(); 47 | (int indexDescription, _) = weaponDescriptionEnumerator.Current; 48 | 49 | (string, string, string) w2s = Weapon.Weapon2String(weapon); 50 | ModLoader.Weapons[indexTargetName] = w2s.Item1; 51 | ModLoader.WeaponDescriptions[indexDescription] = w2s.Item2; 52 | ModLoader.WeaponDescriptions[indexLocalizationName] = w2s.Item3; 53 | 54 | Log.Information("Successfully set weapon: {0}", targetName); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /ModUtils/TableUtils/SurfaceSpawn.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Linq; 5 | using System.Runtime.Serialization; 6 | using Serilog; 7 | 8 | namespace ModShardLauncher; 9 | 10 | [SuppressMessage("ReSharper", "InconsistentNaming")] 11 | [SuppressMessage("ReSharper", "IdentifierTypo")] 12 | public partial class Msl 13 | { 14 | public enum SurfaceSpawnSlot 15 | { 16 | Hostile, 17 | Neutral 18 | } 19 | 20 | public enum SurfaceSpawnTier 21 | { 22 | [EnumMember(Value = "1")] 23 | Tier1, 24 | [EnumMember(Value = "2")] 25 | Tier2, 26 | [EnumMember(Value = "3")] 27 | Tier3, 28 | [EnumMember(Value = "4")] 29 | Tier4, 30 | [EnumMember(Value = "5")] 31 | Tier5 32 | } 33 | 34 | // Bitmask enum, use like this: `SurfaceSpawnBiome Biome = SurfaceSpawnBiome.Forest | SurfaceSpawnBiome.Steppe` to have multiple choices 35 | [Flags] 36 | public enum SurfaceSpawnBiome 37 | { 38 | Any = 1, 39 | Forest = 2, 40 | Pineforest = 4, 41 | Field = 8, 42 | Pinefield = 16, 43 | Steppe = 32 44 | } 45 | 46 | public enum SurfaceSpawnFaction 47 | { 48 | Brigand, 49 | Beast, 50 | // Anything below is not confirmed, just assuming it would work 51 | Undead, 52 | Vampire, 53 | GrandMagistrate, 54 | RottenWillow, 55 | Mercenary, 56 | Neutral 57 | } 58 | 59 | public static void InjectTableSurfaceSpawn( 60 | SurfaceSpawnSlot Slot, 61 | // SurfaceSpawnTier Tier, 62 | HashSet Tier, 63 | SurfaceSpawnBiome Biome, 64 | SurfaceSpawnFaction Faction, 65 | byte Spawn_Chance, 66 | string Enemy1, 67 | string? Enemy2 = null, 68 | string? Enemy3 = null, 69 | string? Enemy4 = null, 70 | string? Enemy5 = null, 71 | string? Enemy6 = null 72 | ) 73 | { 74 | // Table filename 75 | const string tableName = "gml_GlobalScript_table_surface_spawn"; 76 | 77 | // Load table if it exists 78 | List table = ThrowIfNull(ModLoader.GetTable(tableName)); 79 | 80 | // Parse tier 81 | string tierStr = ""; 82 | foreach (SurfaceSpawnTier tier in Tier) 83 | tierStr += $"{GetEnumMemberValue(tier)}, "; 84 | tierStr = tierStr.TrimEnd(',', ' '); 85 | 86 | // Prepare line 87 | string newline = $"{Slot};{tierStr};{Biome};{Faction};{Spawn_Chance};{Enemy1};{Enemy2};{Enemy3};{Enemy4};{Enemy5};{Enemy6};"; 88 | 89 | // Add line to table 90 | table.Add(newline); 91 | ModLoader.SetTable(table, tableName); 92 | Log.Information("Injected a Spawn into table {0}", tableName); 93 | } 94 | } -------------------------------------------------------------------------------- /Controls/ModInfos.xaml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 24 | 25 | 26 | 27 | 28 | 29 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Resources/Codes/LootModule/scr_msl_resolve_refence_table.gml: -------------------------------------------------------------------------------- 1 | function scr_msl_resolve_refence_table(argument0) 2 | { 3 | var objectName = object_get_name(argument0.object_index); 4 | var refFile = file_text_open_read("reference_table.json"); 5 | var refJson = file_text_read_string(refFile); 6 | var refData = json_parse(refJson); 7 | 8 | var min_lvl = scr_globaltile_dungeon_get("mob_lvl_min"); 9 | var max_lvl = scr_globaltile_dungeon_get("mob_lvl_max"); 10 | var tier = floor(((max_lvl + min_lvl) / 2)); 11 | scr_msl_log("current tier: " + string(tier)); 12 | 13 | if (!variable_struct_exists(refData, objectName)) 14 | { 15 | scr_msl_log("cant find object " + objectName); 16 | file_text_close(refFile); 17 | return -4; 18 | } 19 | var refStruct = variable_struct_get(refData, objectName); 20 | var referenceLootTableIndex = -1; 21 | 22 | if (!variable_struct_exists(refStruct, "DefaultTable")) 23 | { 24 | scr_msl_log("cant find DefaultTable"); 25 | file_text_close(refFile); 26 | return -4; 27 | } 28 | var defaultTable = variable_struct_get(refStruct, "DefaultTable"); 29 | 30 | if (!variable_struct_exists(refStruct, "Ids")) 31 | { 32 | scr_msl_log("cant find Ids"); 33 | file_text_close(refFile); 34 | return -4; 35 | } 36 | var idsStruct = variable_struct_get(refStruct, "Ids"); 37 | 38 | var _ids = variable_struct_get_names(idsStruct); 39 | for (var i = 0; i < array_length(_ids); i++;) 40 | { 41 | if (real(_ids[i]) == argument0.id) 42 | { 43 | var referenceLootTable = variable_struct_get(idsStruct, _ids[i]); 44 | scr_msl_log("ref with id: " + referenceLootTable); 45 | file_text_close(refFile); 46 | return referenceLootTable; 47 | } 48 | } 49 | 50 | if (!variable_struct_exists(refStruct, "Tiers")) 51 | { 52 | scr_msl_log("cant find Tiers"); 53 | file_text_close(refFile); 54 | return -4; 55 | } 56 | var tiersStruct = variable_struct_get(refStruct, "Tiers"); 57 | var tiers = variable_struct_get_names(tiersStruct); 58 | var indexTier = -1; 59 | 60 | for (var i = 0; i < array_length(tiers); i++;) 61 | { 62 | if (tier < real(tiers[i])) 63 | { 64 | indexTier = i - 1; 65 | break; 66 | } 67 | else 68 | { 69 | indexTier = i; 70 | } 71 | } 72 | 73 | if (indexTier == -1) 74 | { 75 | var referenceLootTable = defaultTable; 76 | scr_msl_log("ref with default: " + referenceLootTable); 77 | } 78 | else 79 | { 80 | var referenceLootTable = variable_struct_get(tiersStruct, tiers[indexTier]); 81 | scr_msl_log("ref with tier: " + referenceLootTable); 82 | } 83 | 84 | file_text_close(refFile); 85 | return referenceLootTable; 86 | } -------------------------------------------------------------------------------- /ModUtils/TableUtils/ContractsStats.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Linq; 5 | using Serilog; 6 | 7 | namespace ModShardLauncher; 8 | 9 | [SuppressMessage("ReSharper", "InconsistentNaming")] 10 | [SuppressMessage("ReSharper", "IdentifierTypo")] 11 | 12 | public partial class Msl 13 | { 14 | public enum ContractsStatsCategory 15 | { 16 | kill, 17 | destroy, 18 | clear, 19 | interact, 20 | find 21 | } 22 | 23 | public enum ContractsStatsDungeonType 24 | { 25 | Crypt, 26 | Catacombs, 27 | Bastion 28 | } 29 | 30 | public static void InjectTableContractsStats( 31 | string id, 32 | byte Amount, 33 | ContractsStatsCategory Category, 34 | string Faction, // Could be an enum ? Leaving as string for modded factions for now 35 | string Village_Type, // Could be an enum ? Leaving as string for modded villages and multiple choices 36 | ContractsStatsDungeonType DungeonType, 37 | string Script, 38 | byte Contract_Deadline, 39 | byte Contract_Expiration, 40 | short Contract_Reward, 41 | short Contract_Reputation, 42 | short Contract_Reputation_Loss, 43 | byte Village_Aftermath, 44 | string Dungeon_Aftermath, // Could be an enum ? Leaving as string for multiple choices 45 | string Contract_NPC, 46 | byte Contract_Respawn, // Unknown type, assuming byte for now 47 | byte Mod_time, // Unknown type, assuming byte for now 48 | bool Can_Generate, 49 | float k 50 | ) 51 | { 52 | // Table filename 53 | const string tableName = "gml_GlobalScript_table_contracts_stats"; 54 | 55 | // Load table if it exists 56 | List table = ThrowIfNull(ModLoader.GetTable(tableName)); 57 | 58 | // Prepare line 59 | string newline = $"{id};{Amount};{Category};{Faction};{Village_Type};{DungeonType};{Script};{Contract_Deadline};{Contract_Expiration};{Contract_Reward};{Contract_Reputation};{Contract_Reputation_Loss};{Village_Aftermath};{Dungeon_Aftermath};{Contract_NPC};{Contract_Respawn};{Mod_time};{Can_Generate};{k};"; 60 | 61 | // Find hook 62 | (int ind, string? foundLine) = table.Enumerate().FirstOrDefault(x => x.Item2.Contains("bastion_HostageRottenWillow")); 63 | 64 | // Add line to table 65 | if (foundLine != null) 66 | { 67 | table.Insert(ind + 1, newline); 68 | ModLoader.SetTable(table, tableName); 69 | Log.Information("Injected contract {0} into {1}", id, tableName); 70 | } 71 | else 72 | { 73 | Log.Error("Hook not found in {0}. {1} was not injected.", tableName, id); 74 | throw new Exception($"Hook not found in {tableName}. {id} was not injected."); 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /Core/Errors/MSLDiagnostic.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Windows.Forms; 4 | using Serilog; 5 | 6 | namespace ModShardLauncher.Core.Errors 7 | { 8 | public class MSLDiagnostic 9 | { 10 | public string ModName; 11 | public string? FileName; 12 | public string? PatchingMethod; 13 | public Exception exception; 14 | public MSLDiagnostic(Exception ex, ModFile modFile) 15 | { 16 | if (ex.Data.Contains("fileName")) 17 | { 18 | object? fileName = ex.Data["fileName"]; 19 | FileName = $"{fileName}"; 20 | } 21 | if (ex.Data.Contains("patchingWay")) 22 | { 23 | object? patchingWay = ex.Data["patchingWay"]; 24 | PatchingMethod = $"{patchingWay}"; 25 | } 26 | ModName = modFile.Name; 27 | exception = ex; 28 | } 29 | public void ToLog() 30 | { 31 | string extraInformation = " for {{{0}}}"; 32 | if (FileName is not null) 33 | { 34 | extraInformation += " in file {{{1}}}"; 35 | } 36 | if (PatchingMethod is not null) 37 | { 38 | extraInformation += " while patching by {{{2}}}"; 39 | } 40 | Log.Error(exception, "Something went wrong" + extraInformation, ModName, FileName, PatchingMethod); 41 | } 42 | public string Title() 43 | { 44 | return $"Patching {ModName} failed"; 45 | } 46 | public string MessageDialog() 47 | { 48 | RichTextBox message = new(); 49 | 50 | message.SelectionColor = Color.Black; 51 | message.AppendText("An error was encountered while patching the mod "); 52 | message.SelectionColor = Color.Blue; 53 | message.AppendText(ModName); 54 | message.SelectionColor = Color.Black; 55 | message.AppendText(":\n"); 56 | 57 | if (FileName is not null) 58 | { 59 | message.SelectionColor = Color.Black; 60 | message.AppendText("In file "); 61 | message.SelectionColor = Color.Blue; 62 | message.AppendText(FileName); 63 | } 64 | if (PatchingMethod is not null) 65 | { 66 | message.SelectionColor = Color.Black; 67 | message.AppendText(" by "); 68 | message.SelectionColor = Color.Blue; 69 | message.AppendText(PatchingMethod); 70 | message.SelectionColor = Color.Black; 71 | message.AppendText(":\n"); 72 | } 73 | 74 | message.SelectionColor = Color.Black; 75 | message.AppendText("\n"); 76 | message.SelectionFont = new Font(message.Font, FontStyle.Bold); 77 | message.AppendText(exception.ToString()); 78 | return message.Rtf; 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /ModUtils/TableUtils/DungeonsSpawn.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Linq; 5 | using System.Runtime.Serialization; 6 | using Serilog; 7 | 8 | namespace ModShardLauncher; 9 | 10 | [SuppressMessage("ReSharper", "InconsistentNaming")] 11 | public partial class Msl 12 | { 13 | public enum DungeonsSpawnHook 14 | { 15 | GENERIC, 16 | UNDEAD 17 | } 18 | 19 | public enum DungeonsSpawnTemplateType 20 | { 21 | Normal, 22 | Advanced, 23 | Special 24 | } 25 | 26 | public enum DungeonsSpawnTier 27 | { 28 | [EnumMember(Value = "1")] 29 | Tier1, 30 | [EnumMember(Value = "2")] 31 | Tier2, 32 | [EnumMember(Value = "3")] 33 | Tier3, 34 | [EnumMember(Value = "4")] 35 | Tier4, 36 | [EnumMember(Value = "5")] 37 | Tier5 38 | } 39 | 40 | [Flags] 41 | public enum DungeonsSpawnFaction 42 | { 43 | Undead = 1, 44 | Vampire = 2, 45 | Brigand = 4 46 | } 47 | 48 | public static void InjectTableDungeonsSpawn( 49 | DungeonsSpawnHook hook, 50 | string id, // Could be byte, not sure how it's used 51 | DungeonsSpawnTemplateType Template_Type, 52 | HashSet Dungeon_Tier, 53 | DungeonsSpawnFaction Faction, 54 | string Enemy1, 55 | string? Enemy2 = null, 56 | string? Enemy3 = null, 57 | string? Enemy4 = null, 58 | string? Enemy5 = null, 59 | string? Enemy6 = null 60 | ) 61 | { 62 | // Table filename 63 | const string tableName = "gml_GlobalScript_table_dungeons_spawn"; 64 | 65 | // Load table if it exists 66 | List table = ThrowIfNull(ModLoader.GetTable(tableName)); 67 | 68 | // Parse Dungeon_Tier 69 | string dungeonTierStr = ""; 70 | foreach (DungeonsSpawnTier tier in Dungeon_Tier) 71 | dungeonTierStr += $"{GetEnumMemberValue(tier)}, "; 72 | dungeonTierStr = dungeonTierStr.TrimEnd(',', ' '); 73 | 74 | // Prepare new line 75 | string newline = $"{id};{Template_Type};{dungeonTierStr};{Faction};{Enemy1};{Enemy2};{Enemy3};{Enemy4};{Enemy5};{Enemy6};"; 76 | 77 | // Find hook 78 | string hookStr = "// " + GetEnumMemberValue(hook); 79 | (int ind, string? foundLine) = table.Enumerate().FirstOrDefault(x => x.Item2.Contains(hookStr)); 80 | 81 | // Add line to table 82 | if (foundLine != null) 83 | { 84 | table.Insert(ind + 1, newline); 85 | ModLoader.SetTable(table, tableName); 86 | Log.Information("Injected Dungeon Spawn {0} into {1} under {2}", id, tableName, hook); 87 | } 88 | else 89 | { 90 | Log.Error("Cannot find Hook {0} in table {1}", hook, tableName); 91 | throw new Exception($"Hook {hook} not found in table {tableName}"); 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ModShardLauncher 2 | 3 | 4 | ## Description 5 | 6 | ModShardLauncher is a **powerful** and **user-friendly** mod loader for the game Stoneshard.
7 | It's designed to let players easily **install** and **manage** a wide variety of **mods**, as well as **playing with multiple mods at a time** !
8 | 9 | **But that's not it** :
10 | It also lets modders **create mods** through **C#** instead of **UndertaleModTool**, which improves **compatibility** and prevents updates from systematically **breaking** mods. 11 | 12 | With **ModShardLoader**, you can **customize** your Stoneshard gameplay to suit your **preferences**.
13 | Whether you want to tweak the **game mechanics**, add **new content**, or **anything else**, **ModShardLoader** makes it possible. 14 | 15 | ## Documentation 16 | 17 | - [Documentation](https://modshardteam.github.io/ModShardLauncher/guides/introduction.html) 18 | - [这里是文档](https://modshardteam.github.io/ModShardLauncher/zh/guides/introduction.html) 19 | 20 | ## Roadmap 21 | 22 | See roadmap [here](https://github.com/ModShardTeam/ModShardLauncher/milestones). 23 | 24 | ## Usage 25 | ### For Players 26 | [How to install mods](https://modshardteam.github.io/ModShardLauncher/guides/how-to-play-mod.html) 27 | ### For Modders 28 | [How to create mods with MSL](https://modshardteam.github.io/ModShardLauncher/guides/start-modding.html)
29 | #### Mod Template :
30 | - [Source Code](https://github.com/ModShardTeam/ModShardLauncherTemplate) 31 | - [NuGet Package](https://www.nuget.org/packages/ModShardLauncher.Templates) 32 | #### Mod Examples : 33 | - [Quicksave](https://github.com/Nylux/Stoneshard-Quicksave) 34 | - [SpeedManager](https://github.com/Nylux/Stoneshard-SpeedManager) 35 | - [NeoConsole](https://github.com/Nylux/Stoneshard-NeoConsole) 36 | - [ExamplesForLog](https://github.com/remyCases/ExamplesForLogMod) 37 | 38 | ## Features 39 | 40 | ### For Players 41 | 42 | | **Name** | **Status** | 43 | | :------ | :--------: | 44 | | Installing Mods | :heavy_check_mark: | 45 | | Version Checking | :x: | 46 | | Auto-updates | :x: | 47 | | Conflict Checking | :x: | 48 | | Mod Settings | :heavy_check_mark: | 49 | 50 | ### For Modders 51 | 52 | | Name | Status | 53 | | :------ | :--------: | 54 | | Mod Settings | :heavy_check_mark: | 55 | | GML Editing | :heavy_check_mark: | 56 | | Assembly Editing | :heavy_check_mark: | 57 | | GameObjects | :heavy_check_mark: | 58 | | Events | :heavy_check_mark: | 59 | | Functions | :heavy_check_mark: | 60 | | Tables | :heavy_check_mark: | 61 | | Weapons | :heavy_check_mark: | 62 | | Rooms | :heavy_check_mark: | 63 | | Sounds | :x: | 64 | | Sprites | :heavy_check_mark: | 65 | | Shaders | :x: | 66 | | Fonts | :x: | 67 | | DE2 Dialogues | :x: | 68 | | Extensions | :heavy_check_mark: | 69 | 70 | ## Contributing 71 | ### Building From Source 72 | Requires the [.NET 6.0 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/6.0) 73 | 74 | `git clone git@github.com:ModShardTeam/ModShardLauncher.git`
75 | `cd ModShardLauncher`
76 | `dotnet build` 77 | 78 | ### Editing The Documentation 79 | **TODO** 80 | -------------------------------------------------------------------------------- /ModShardLauncher.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.7.34024.191 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ModShardLauncher", "ModShardLauncher.csproj", "{381707F0-2C79-4B36-BD23-2114A0EF55D1}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModShardLauncherTest", "ModShardLauncherTest\ModShardLauncherTest.csproj", "{9083D03A-A393-4EFD-80EB-17ADF18F4C82}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Debug|x64 = Debug|x64 14 | Debug|x86 = Debug|x86 15 | Release|Any CPU = Release|Any CPU 16 | Release|x64 = Release|x64 17 | Release|x86 = Release|x86 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {381707F0-2C79-4B36-BD23-2114A0EF55D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {381707F0-2C79-4B36-BD23-2114A0EF55D1}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {381707F0-2C79-4B36-BD23-2114A0EF55D1}.Debug|x64.ActiveCfg = Debug|Any CPU 23 | {381707F0-2C79-4B36-BD23-2114A0EF55D1}.Debug|x64.Build.0 = Debug|Any CPU 24 | {381707F0-2C79-4B36-BD23-2114A0EF55D1}.Debug|x86.ActiveCfg = Debug|Any CPU 25 | {381707F0-2C79-4B36-BD23-2114A0EF55D1}.Debug|x86.Build.0 = Debug|Any CPU 26 | {381707F0-2C79-4B36-BD23-2114A0EF55D1}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {381707F0-2C79-4B36-BD23-2114A0EF55D1}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {381707F0-2C79-4B36-BD23-2114A0EF55D1}.Release|x64.ActiveCfg = Release|Any CPU 29 | {381707F0-2C79-4B36-BD23-2114A0EF55D1}.Release|x64.Build.0 = Release|Any CPU 30 | {381707F0-2C79-4B36-BD23-2114A0EF55D1}.Release|x86.ActiveCfg = Release|Any CPU 31 | {381707F0-2C79-4B36-BD23-2114A0EF55D1}.Release|x86.Build.0 = Release|Any CPU 32 | {9083D03A-A393-4EFD-80EB-17ADF18F4C82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {9083D03A-A393-4EFD-80EB-17ADF18F4C82}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {9083D03A-A393-4EFD-80EB-17ADF18F4C82}.Debug|x64.ActiveCfg = Debug|Any CPU 35 | {9083D03A-A393-4EFD-80EB-17ADF18F4C82}.Debug|x64.Build.0 = Debug|Any CPU 36 | {9083D03A-A393-4EFD-80EB-17ADF18F4C82}.Debug|x86.ActiveCfg = Debug|Any CPU 37 | {9083D03A-A393-4EFD-80EB-17ADF18F4C82}.Debug|x86.Build.0 = Debug|Any CPU 38 | {9083D03A-A393-4EFD-80EB-17ADF18F4C82}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {9083D03A-A393-4EFD-80EB-17ADF18F4C82}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {9083D03A-A393-4EFD-80EB-17ADF18F4C82}.Release|x64.ActiveCfg = Release|Any CPU 41 | {9083D03A-A393-4EFD-80EB-17ADF18F4C82}.Release|x64.Build.0 = Release|Any CPU 42 | {9083D03A-A393-4EFD-80EB-17ADF18F4C82}.Release|x86.ActiveCfg = Release|Any CPU 43 | {9083D03A-A393-4EFD-80EB-17ADF18F4C82}.Release|x86.Build.0 = Release|Any CPU 44 | EndGlobalSection 45 | GlobalSection(SolutionProperties) = preSolution 46 | HideSolutionNode = FALSE 47 | EndGlobalSection 48 | GlobalSection(ExtensibilityGlobals) = postSolution 49 | SolutionGuid = {52146A09-AD74-4763-84E8-8C333FE660A8} 50 | EndGlobalSection 51 | EndGlobal 52 | -------------------------------------------------------------------------------- /Language/en-us.xaml: -------------------------------------------------------------------------------- 1 | 4 | A tool for StoneShard modding. 5 | Start your first mod from here. 6 | File 7 | Settings 8 | Open 9 | Languages 10 | Enable logger 11 | Refresh 12 | Patch 13 | Search by mod's name... 14 | Welcome! 15 | Enable 16 | Disable 17 | Welcome to use ModShardLauncher! 18 | if you want to refresh mods, just paste them into the /Mods path and click the refresh button on the left 19 | First, click File button and open a original data file then select Mods you want to enable. 20 | Then click patch button to patch mod into data file. 21 | Mod Name 22 | Mod Author 23 | Mod Description 24 | Enable 25 | ModVersion 26 | MSL Version 27 | Compile 28 | Patching failed, cant save 29 | Please load a game data file first! 30 | Mod version is different from game version 31 | Mod version is different from game version, maybe it will crash if you load it.Still want to load? 32 | Mod file lost. 33 | Compile Error: 34 | Save this path as default LoadPath? 35 | Save this path as default SavePath? 36 | General 37 | Mods 38 | ModSources 39 | Settings 40 | Menu 41 | Close 42 | Minimize 43 | Loading... 44 | Mod Interface Server starts at port {0}. 45 | -------------------------------------------------------------------------------- /ModUtils/TableUtils/RecipesCook.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Linq; 5 | using System.Runtime.Serialization; 6 | using Serilog; 7 | 8 | namespace ModShardLauncher; 9 | 10 | [SuppressMessage("ReSharper", "InconsistentNaming")] 11 | [SuppressMessage("ReSharper", "IdentifierTypo")] 12 | public partial class Msl 13 | { 14 | public enum RecipesCookHook 15 | { 16 | [EnumMember(Value = "FRYING & SALTING")] 17 | FryingAndSalting, 18 | SOUPS, 19 | [EnumMember(Value = "MAIN COURSES")] 20 | MainCourses, 21 | SALADS, 22 | DESSERTS 23 | } 24 | 25 | public enum RecipesCookCategory 26 | { 27 | other, 28 | soup, 29 | main, 30 | side, 31 | dessert 32 | } 33 | 34 | public enum RecipesCookAdditive 35 | { 36 | X, 37 | V, 38 | butter 39 | } 40 | 41 | public enum RecipesCookSource 42 | { 43 | [EnumMember(Value = "Base Recipe")] 44 | BaseRecipe, 45 | Trade, 46 | [EnumMember(Value = "Random Find")] 47 | RandomFind, 48 | [EnumMember(Value = "Reputation Reward")] 49 | ReputationReward 50 | } 51 | 52 | public static void InjectTableRecipesCook( 53 | RecipesCookHook hook, 54 | string NAME, 55 | RecipesCookCategory CAT, 56 | string? Recipe1 = null, 57 | string? Recipe2 = null, 58 | string? Recipe3 = null, 59 | string? Recipe4 = null, 60 | string? Recipe5 = null, 61 | RecipesCookAdditive ADDITIVES = RecipesCookAdditive.X, 62 | bool COOKPOT = false, 63 | bool PLATE = false, 64 | bool DEEP_PLATE = false, 65 | bool SATIETY = false, 66 | short? MORALE = null, 67 | short? SANITY = null, 68 | RecipesCookSource SOURCE = RecipesCookSource.BaseRecipe 69 | ) 70 | { 71 | // Table filename 72 | const string tableName = "gml_GlobalScript_table_recipes_cook"; 73 | 74 | // Load table if it exists 75 | List table = ThrowIfNull(ModLoader.GetTable(tableName)); 76 | 77 | // Prepare line 78 | string newline = $"{NAME};{CAT};{Recipe1 ?? "-"};{Recipe2 ?? "-"};{Recipe3 ?? "-"};{Recipe4 ?? "-"};{Recipe5 ?? "-"};{GetEnumMemberValue(ADDITIVES)};{(COOKPOT ? "V" : "X")};{(PLATE ? "V" : "X")};{(DEEP_PLATE ? "V" : "X")};{(SATIETY ? "V" : "X")};{MORALE};{SANITY};{GetEnumMemberValue(SOURCE)};"; 79 | 80 | // Find hook 81 | string hookStr = "// " + GetEnumMemberValue(hook); 82 | (int ind, string? foundLine) = table.Enumerate().FirstOrDefault(x => x.Item2.Contains(hookStr)); 83 | 84 | // Add line to table 85 | if (foundLine != null) 86 | { 87 | table.Insert(ind + 1, newline); 88 | ModLoader.SetTable(table, tableName); 89 | Log.Information("Injected Armor {0} into table {1} under {2}", NAME, tableName, hook); 90 | } 91 | else 92 | { 93 | Log.Error("Cannot find hook {0} in table {1}", hook, tableName); 94 | throw new Exception($"Cannot find hook {hook} in table {tableName}"); 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json 2 | 3 | name: publish 4 | on: 5 | workflow_dispatch: # Allow running the workflow manually from the GitHub UI 6 | push: 7 | branches: 8 | - 'main' # Run the workflow when pushing to the main branch (test only) 9 | tags: 10 | - '*' # Run the workflow with a tag 11 | 12 | env: 13 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 14 | DOTNET_NOLOGO: true 15 | NuGetDirectory: ${{ github.workspace }}/publish 16 | 17 | jobs: 18 | set_version: 19 | if: startsWith(github.ref, 'refs/tags/') 20 | runs-on: ubuntu-latest 21 | outputs: 22 | sha: ${{ steps.sha.outputs.sha }} 23 | steps: 24 | - name: Checkout 25 | uses: actions/checkout@v4 26 | with: 27 | fetch-depth: 0 28 | ref: main 29 | 30 | - name: Set Version 31 | id: package_version 32 | uses: KageKirin/set-csproj-version@v0 33 | with: 34 | file: ./ModShardLauncher.csproj 35 | version: ${{ github.ref_name }} 36 | 37 | - name: Set user info 38 | run: | 39 | git config user.name github-actions 40 | git config user.email github-actions@github.com 41 | 42 | - name: Check if there are any changes 43 | id: verify_diff 44 | run: | 45 | git diff --quiet . || echo "changed=true" >> $GITHUB_OUTPUT 46 | 47 | - name: Commit Files & Pull 48 | if: steps.verify_diff.outputs.changed == 'true' 49 | run: | 50 | git commit -a -m "Updated version with CI." 51 | git pull origin main --rebase 52 | 53 | - name: Push Changes 54 | if: steps.verify_diff.outputs.changed == 'true' 55 | uses: ad-m/github-push-action@master 56 | with: 57 | github_token: ${{ secrets.GITHUB_TOKEN }} 58 | branch: main 59 | 60 | - name: Get SHA 61 | id: sha 62 | run: | 63 | echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT 64 | echo ${{ steps.sha.outputs.sha }} 65 | 66 | create_msl: 67 | runs-on: ubuntu-latest 68 | needs: [ set_version ] 69 | steps: 70 | - uses: actions/checkout@v4 71 | with: 72 | fetch-depth: 0 # Get all history to allow automatic versioning using MinVer 73 | ref: ${{ needs.set_version.outputs.sha }} 74 | 75 | # Install the .NET SDK indicated in the global.json file 76 | - name: Setup .NET 77 | uses: actions/setup-dotnet@v4 78 | 79 | # Build the project in the folder from the environment variable NuGetDirectory 80 | - run: dotnet publish -r win-x64 -c Release /p:PublishSingleFile=true /p:IncludeAllContentForSelfExtract=true /p:PublishReadyToRun=true --output ${{ env.NuGetDirectory }} 81 | 82 | # Zip the folder 83 | - name: Zip 84 | run: zip -r msl.zip ${{ env.NuGetDirectory }} 85 | 86 | # Publish the NuGet package as an artifact, so they can be used in the following jobs 87 | - uses: actions/upload-artifact@v3 88 | with: 89 | name: msl 90 | if-no-files-found: error 91 | retention-days: 7 92 | path: msl.zip 93 | 94 | create_release: 95 | runs-on: ubuntu-latest 96 | needs: [ create_msl ] 97 | steps: 98 | - uses: actions/checkout@v4 99 | 100 | - name: Create Release 101 | run: gh release create ${{ github.ref_name }} --generate-notes --prerelease 102 | env: 103 | GITHUB_TOKEN: ${{ github.TOKEN }} 104 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: ModShardLauncher Modding Documentation 2 | repo_url: https://github.com/ModShardTeam/ModShardLauncher 3 | repo_name: ModShardTeam/ModShardLauncher 4 | edit_uri: edit/main/docs/ 5 | use_directory_urls: false 6 | copyright: All rights belong to Ink Stains Games.
This is a community 7 | project and is not affiliated with Ink Stains Games. 8 | 9 | 10 | theme: 11 | name: material 12 | favicon: img/Stoneshard.ico 13 | features: 14 | - navigation.path 15 | - navigation.indexes 16 | - header.autohide 17 | - toc.integrate 18 | - navigation.top 19 | - search.suggest 20 | - search.highlight 21 | - content.tabs.link 22 | - content.code.annotate 23 | - content.code.copy 24 | - content.action.edit 25 | - content.action.view 26 | language: en 27 | locale: en 28 | palette: 29 | - scheme: default 30 | toggle: 31 | icon: material/brightness-7 32 | name: Switch to Dark Mode 33 | - scheme: slate 34 | toggle: 35 | icon: material/brightness-4 36 | name: Switch to Light Mode 37 | 38 | extra: 39 | social: 40 | - icon: fontawesome/brands/discord 41 | link: https://discord.gg/YxfRKYUuht 42 | name: Stoneshard Mod Hub Discord 43 | - icon: fontawesome/brands/github 44 | link: https://github.com/DDDDDragon/ModShardLauncher 45 | name: GitHub Repository 46 | - icon: fontawesome/brands/steam 47 | link: https://store.steampowered.com/app/625960/Stoneshard/ 48 | name: Stoneshard on Steam 49 | - icon: simple/gogdotcom 50 | link: https://www.gog.com/game/stoneshard 51 | name: Stoneshard on GOG 52 | 53 | markdown_extensions: 54 | - attr_list 55 | - admonition 56 | - pymdownx.details 57 | - pymdownx.superfences 58 | - pymdownx.highlight: 59 | anchor_linenums: true 60 | line_spans: __span 61 | pygments_lang_class: true 62 | - pymdownx.inlinehilite 63 | - pymdownx.snippets 64 | - pymdownx.critic 65 | - pymdownx.caret 66 | - pymdownx.keys 67 | - pymdownx.mark 68 | - pymdownx.tilde 69 | - pymdownx.tabbed: 70 | alternate_style: true 71 | - pymdownx.emoji: 72 | emoji_index: !!python/name:material.extensions.emoji.twemoji 73 | emoji_generator: !!python/name:material.extensions.emoji.to_svg 74 | 75 | 76 | plugins: 77 | - search 78 | - table-reader 79 | - glightbox 80 | - git-authors 81 | - git-revision-date 82 | - i18n: 83 | docs_structure: suffix 84 | reconfigure_material: true 85 | reconfigure_search: true 86 | languages: 87 | - locale: en 88 | default: true 89 | name: English 90 | build: true 91 | - locale: zh 92 | name: 中文 93 | build: true 94 | nav_translations: 95 | Index: 首页 96 | Welcome: 欢迎 97 | Guides: 指南 98 | Introduction: 简介 99 | Creating your First Mod: 开始 100 | Installing Mods: 如何使用MOD 101 | API: API 102 | Modding: MOD制作 103 | # You can remove the nav_translations above and remove comments here if you want to have custom ZH nav. 104 | # This would avoid setting up empty 'TODO' pages. 105 | # nav: 106 | # - 首页: "index.md" 107 | # - 指南: 108 | # - "简介": "guides/introduction.md" 109 | # - ... : ... 110 | 111 | 112 | nav: 113 | - Index: 114 | - "Index": "index.md" 115 | - Guides: 116 | - "Introduction": "guides/introduction.md" 117 | - "Installing Mods": "guides/how-to-play-mod.md" 118 | - Modding : 119 | - "API": "guides/api.md" 120 | - "Creating your First Mod": "guides/start-modding.md" -------------------------------------------------------------------------------- /ModShardLauncher.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ModShardLauncher.App 5 | WinExe 6 | net6.0-windows 7 | enable 8 | true 9 | ico.ico 10 | false 11 | zh-Hans 12 | 0.13.2.0 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | runtime; build; native; contentfiles; analyzers; buildtransitive 48 | all 49 | 50 | 51 | 52 | 53 | 54 | Reference\UndertaleModLib.dll 55 | 56 | 57 | Reference\UndertaleModTool.dll 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /Loader/Exporter.cs: -------------------------------------------------------------------------------- 1 | using UndertaleModLib; 2 | using System.IO; 3 | using System; 4 | using System.Linq; 5 | using System.Collections.Generic; 6 | using Newtonsoft.Json; 7 | using Serilog; 8 | using UndertaleModLib.Decompiler; 9 | 10 | namespace ModShardLauncher.Loader 11 | { 12 | public enum DataType 13 | { 14 | Codes, 15 | Items, 16 | DungeonPresets, 17 | } 18 | static public class ExporterExtensions 19 | { 20 | /// 21 | /// Export all code, variables and rooms name in json. 22 | /// 23 | private static void ExportCodes(UndertaleData data) 24 | { 25 | File.WriteAllText("json_dump_code.json", JsonConvert.SerializeObject(data.Code.Select(t => t.Name.Content))); 26 | File.WriteAllText("json_dump_variables.json", JsonConvert.SerializeObject(data.Variables.Select(t => t.Name.Content))); 27 | File.WriteAllText("json_dump_rooms.json", JsonConvert.SerializeObject(data.Rooms.Select(t => t.Name.Content))); 28 | } 29 | /// 30 | /// Export all items, weapons and armors in csv. 31 | /// 32 | private static void ExportItems(UndertaleData data) 33 | { 34 | DirectoryInfo dir = new("export"); 35 | if (dir.Exists) dir.Delete(true); 36 | dir.Create(); 37 | 38 | List? weapons = ModLoader.GetTable("gml_GlobalScript_table_weapons"); 39 | List? armor = ModLoader.GetTable("gml_GlobalScript_table_armor"); 40 | 41 | File.WriteAllLines( 42 | Path.Join(dir.FullName, Path.DirectorySeparatorChar.ToString(), "_all_items.csv"), 43 | data.GameObjects.Select(t => t.Name.Content).Where(x => x.Contains("o_inv_")).Select(x => x.Replace("o_inv_", "")) 44 | ); 45 | 46 | if (weapons != null) 47 | { 48 | File.WriteAllLines( 49 | Path.Join(dir.FullName, Path.DirectorySeparatorChar.ToString(), "_all_weapons.csv"), 50 | weapons.Select(x => string.Join(';', x.Split(';').Take(4))) 51 | ); 52 | } 53 | 54 | if (armor != null) 55 | { 56 | File.WriteAllLines( 57 | Path.Join(dir.FullName, Path.DirectorySeparatorChar.ToString(), "_all_armors.csv"), 58 | armor.Select(x => string.Join(';', x.Split(';').Take(6))) 59 | ); 60 | } 61 | } 62 | /// 63 | /// Export all preset data for all dungeons in json. 64 | /// 65 | private static void ExportPresets(UndertaleData data) 66 | { 67 | GlobalDecompileContext context = new(ModLoader.Data, false); 68 | File.WriteAllText("json_preset_bastion.json", Decompiler.Decompile(data.Code.First(t => t.Name.Content.Contains("scr_preset_bastion_1")), context)); 69 | File.WriteAllText("json_preset_catacombs.json", Decompiler.Decompile(data.Code.First(t => t.Name.Content.Contains("scr_preset_catacombs")), context)); 70 | File.WriteAllText("json_preset_crypt.json", Decompiler.Decompile(data.Code.First(t => t.Name.Content.Contains("scr_preset_crypt_1")), context)); 71 | } 72 | /// 73 | /// Export data for code exploration in json or csv. 74 | /// 75 | public static void Export(this UndertaleData data, DataType dataType) 76 | { 77 | try 78 | { 79 | switch (dataType) 80 | { 81 | case DataType.Codes: 82 | ExportCodes(data); 83 | break; 84 | case DataType.Items: 85 | ExportItems(data); 86 | break; 87 | case DataType.DungeonPresets: 88 | ExportPresets(data); 89 | break; 90 | } 91 | } 92 | catch (Exception ex) 93 | { 94 | Log.Error(ex, "Something went wrong while exporting {{{0}}}", dataType); 95 | } 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /Controls/MyToggleButton.xaml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 32 | 33 | 34 | 35 | 36 | 41 | 42 | 48 | 49 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /docs/guides/how-to-play-mod.en.md: -------------------------------------------------------------------------------- 1 | # Installing Mods 2 | 3 | ## Downloading ModShardLauncher 4 | 5 | In order to install mods, you will require the **ModShardLauncher**.
6 | You can grab it on **Github** by clicking the button below.
7 | 8 | 9 | [ModShardLauncher :octicons-link-external-16:](https://github.com/DDDDDragon/ModShardLauncher/releases){ .md-button .md-button--primary}  10 | 11 | Make sure you download the `msl.zip` file, and ^^**not the source code**^^.
12 |
13 | Alternatively, you can build ModShardLauncher from source, but it's out of this guide's scope. 14 |
15 | 16 | If launching MSL opens a console and closes immediately, you may require the **.NET Desktop Runtime 6.0** 17 | 18 | [.NET Desktop Runtime 6.0 :octicons-link-external-16:](https://dotnet.microsoft.com/en-us/download/dotnet/6.0){ .md-button .md-button--primary}  19 | 20 | --- 21 | 22 | ## Linux setup instructions 23 | 24 | ### Pre-requisites 25 | 26 | - ***Bottles*** - https://usebottles.com/ 27 | - ***Stoneshard*** - The Windows version of Stoneshard must be used as the native Linux version is bundled differently 28 | 29 | ### Steps 30 | 1. Create a Bottle environment for gaming 31 | 32 | ![linux_bottle_1.png](../img/linux_bottle_1.png) 33 | 2. Open the newly created bottle and click "Add Shortcut" and browse to then select the `ModShardLauncher.exe` 34 | 3. Modify the newly added item by clicking the three dot menu and going to "Change Launch Options". 35 | The working directory must be updated to the directory that `ModShardLauncher.exe` resides in. 36 | 4. The bottle settings need to be updated to support the .NET environment. This can be found below the "Add Shortcut" 37 | button, select "Settings" and then "Environment Variables". A new variable needs to be added to handle the 38 | extraction location `DOTNET_BUNDLE_EXTRACT_BASE_DIR=Z:\home\{user}\dotnet_bundle_extract`, update this to a path of your choosing. 39 | 40 | *If you run into file permission issues with Bottles this is due to it running in a flatpak. 41 | I suggest using [Flatseal](https://flathub.org/apps/com.github.tchx84.Flatseal) to give Bottles access to the required paths.* 42 | --- 43 | 44 | ## ModShardLauncher Setup 45 | 46 | ???+ warning "IMPORTANT - Installing after RtR" 47 | Make sure you are on the `modbranch` beta of Stoneshard on Steam before proceeding below.
48 | When launching the game, the version at the top right should say `X.X.X.X-vm`.
49 | 50 | Here's how to do it :
51 | - Right click on Stoneshard in your library on steam.
52 | - Click `Properties`.
53 | - Go in the `Betas` tab.
54 | - Select the `modbranch` beta.
55 | - Steam will now update your game files, when it's done, proceed below. 56 | 57 | - **Extract** `ModShardLauncher.zip` anywhere. 58 | - **Rename** `data.win` to `vanilla.win` in your Stoneshard folder. 59 | - **Run** `ModShardLauncher.exe`. 60 | 61 | --- 62 | 63 | ## UI Tour 64 | 65 | Let's take a quick tour of the UI to better know MSL.

66 | 67 |
![Main Menu](../img/tool_UI.png){: style="width:50%"}
68 | 69 |
70 | The button on the top left of the window shows the button's name.

71 | The `Anvil` button below that leads to the `Mods` menu. (***^^This is the only menu you will need^^***.)

72 | The `C#` button leads to the `Mod Sources` menu. (*It's used by modders to create their mods*.)

73 | The `Cog` button leads to the `Settings` menu. (*It's used for some settings you probably won't need*.)

74 |
75 | 76 | --- 77 | 78 | ## Installing a Mod 79 | 80 | Now that we've got MSL running, let's install some mods.
81 | Here's an [example video showing the installation](https://www.youtube.com/watch?v=_J0oJYGi38E&t=13s) of [NeoConsole](https://github.com/Nylux/Stoneshard-NeoConsole/releases). (*enable the subtitles !*)

82 | Note: It's a bit outdated, but the process is pretty much the same, a new video will be made at some point.
83 | 84 | ???+ warning "MAKE SURE YOU ARE ON THE MODBRANCH BETA OF STONESHARD ON STEAM" 85 | 86 | - Close MSL. 87 | - Download your mod's `.sml` file. 88 | - Place the `.sml` file in MSL's `Mods` folder. 89 | - Start MSL. 90 | - Click on the **anvil button** to open the `Mods` menu. 91 | - At the top left of the window, click on the **folder button**. 92 | - Select the `vanilla.win` file in the `Stoneshard` folder 93 | - In the `Mods` menu, **tick the box** next to the mods you wish to enable. 94 | - Click on the **save button** and save under any name (eg. `modded.win`) 95 | - Close MSL. 96 | - Start **Stoneshard**. It should ask you to select a `.win` file. 97 | - Select the `modded.win` file and enjoy your mods ! 98 | 99 |

100 | -------------------------------------------------------------------------------- /Controls/Settings.xaml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /ModUtils/TableUtils/Drops.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Linq; 5 | using System.Runtime.Serialization; 6 | using Serilog; 7 | using Serilog.Debugging; 8 | 9 | namespace ModShardLauncher; 10 | 11 | [SuppressMessage("ReSharper", "InconsistentNaming")] 12 | public partial class Msl 13 | { 14 | private static string EqTypeParser(HashSet eqTypes) 15 | { 16 | string eqTypesStr = ""; 17 | foreach (DropsEqType t in eqTypes) 18 | eqTypesStr += $"{GetEnumMemberValue(t)}, "; 19 | eqTypesStr = eqTypesStr.TrimEnd(',', ' '); 20 | return eqTypesStr; 21 | } 22 | 23 | public enum DropsHook 24 | { 25 | CRYPTS, 26 | CATACOMBS, 27 | BASTIONS, 28 | OTHER 29 | } 30 | 31 | public enum DropsTier 32 | { 33 | [EnumMember(Value = "1")] 34 | Tier1, 35 | [EnumMember(Value = "2")] 36 | Tier2, 37 | [EnumMember(Value = "3")] 38 | Tier3, 39 | [EnumMember(Value = "4")] 40 | Tier4, 41 | [EnumMember(Value = "5")] 42 | Tier5 43 | } 44 | 45 | [Flags] 46 | public enum DropsSlotTags 47 | { 48 | raw = 1, 49 | common = 2 50 | } 51 | 52 | public enum DropsEqType 53 | { 54 | weapon, 55 | dagger, 56 | [EnumMember(Value = "2HStaff")] 57 | TwoHandedStaff, 58 | Chest, 59 | Legs, 60 | Waist, 61 | Arms, 62 | Head, 63 | jewelry, 64 | sword, 65 | mace, 66 | axe, 67 | spear, 68 | crossbow 69 | } 70 | 71 | [Flags] 72 | public enum DropsEqTags 73 | { 74 | aldor = 1, 75 | magic = 2, 76 | elven = 4, 77 | unique = 8 78 | } 79 | 80 | [Flags] 81 | public enum DropsEqRarity 82 | { 83 | common = 1, 84 | uncommon = 2, 85 | rare = 4, 86 | unique = 8 87 | } 88 | 89 | public record DropsSlot(string id, DropsSlotTags? tags, string count, byte chance); 90 | public record DropsEq(HashSet types, DropsEqTags tags, DropsEqRarity? rarity, string dur, byte chance); 91 | 92 | public static void InjectTableDrops( 93 | DropsHook hook, 94 | string id, 95 | DropsTier tier, 96 | HashSet? tierMod = null, 97 | DropsSlot? slot_1 = null, 98 | DropsSlot? slot_2 = null, 99 | DropsSlot? slot_3 = null, 100 | DropsSlot? slot_4 = null, 101 | DropsSlot? slot_5 = null, 102 | DropsSlot? slot_6 = null, 103 | DropsSlot? slot_7 = null, 104 | DropsSlot? slot_8 = null, 105 | DropsSlot? slot_9 = null, 106 | DropsEq? eq_1 = null, 107 | DropsEq? eq_2 = null, 108 | DropsEq? eq_3 = null, 109 | DropsEq? eq_4 = null, 110 | DropsEq? eq_5 = null 111 | ) 112 | { 113 | // Table filename 114 | const string tableName = "gml_GlobalScript_table_drops"; 115 | 116 | // Load table if it exists 117 | List table = ThrowIfNull(ModLoader.GetTable(tableName)); 118 | 119 | // Parse tier 120 | string tierModStr = ""; 121 | if (tierMod != null) 122 | { 123 | foreach (SurfaceSpawnTier t in tierMod) 124 | tierModStr += $"{GetEnumMemberValue(t)}, "; 125 | tierModStr = tierModStr.TrimEnd(',', ' '); 126 | } 127 | 128 | // Prepare line 129 | string newline = $"{id};{GetEnumMemberValue(tier)};{tierModStr};{slot_1?.id};{slot_1?.tags};{slot_1?.count};{slot_1?.chance};;{slot_2?.id};{slot_2?.tags};{slot_2?.count};{slot_2?.chance};;{slot_3?.id};{slot_3?.tags};{slot_3?.count};{slot_3?.chance};;{slot_4?.id};{slot_4?.tags};{slot_4?.count};{slot_4?.chance};;{slot_5?.id};{slot_5?.tags};{slot_5?.count};{slot_5?.chance};;{slot_6?.id};{slot_6?.tags};{slot_6?.count};{slot_6?.chance};;{slot_7?.id};{slot_7?.tags};{slot_7?.count};{slot_7?.chance};;{slot_8?.id};{slot_8?.tags};{slot_8?.count};{slot_8?.chance};;{slot_9?.id};{slot_9?.tags};{slot_9?.count};{slot_9?.chance};;{(eq_1 != null ? EqTypeParser(eq_1.types) : "")};{eq_1?.tags};{eq_1?.rarity};{eq_1?.dur};{eq_1?.chance};;{(eq_2 != null ? EqTypeParser(eq_2.types) : "")};{eq_2?.rarity};{eq_2?.dur};{eq_2?.chance};;{(eq_3 != null ? EqTypeParser(eq_3.types) : "")};{eq_3?.rarity};{eq_3?.dur};{eq_3?.chance};;{(eq_4 != null ? EqTypeParser(eq_4.types) : "")};{eq_4?.rarity};{eq_4?.dur};{eq_4?.chance};;{(eq_5 != null ? EqTypeParser(eq_5.types) : "")};{eq_5?.rarity};{eq_5?.dur};{eq_5?.chance};"; 130 | 131 | // Find hook 132 | string hookStr = "// " + hook; 133 | (int ind, string? foundLine) = table.Enumerate().FirstOrDefault(x => x.Item2.Contains(hookStr)); 134 | 135 | // Add line to table 136 | if (foundLine != null) 137 | { 138 | table.Insert(ind + 1, newline); 139 | ModLoader.SetTable(table, tableName); 140 | Log.Information("Injected Drop {0} into table {1} under {2}", id, tableName, hook); 141 | } 142 | else 143 | { 144 | Log.Error("Cannot find hook {0} in table {1}", hook, tableName); 145 | throw new Exception($"Cannot find hook {hook} in table {tableName}"); 146 | } 147 | } 148 | } -------------------------------------------------------------------------------- /Core/UI/ErrorMessageDialog.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Windows.Forms; 4 | using Serilog; 5 | 6 | namespace ModShardLauncher.Core.UI 7 | { 8 | public class ErrorMessageDialog : Form 9 | { 10 | private readonly RichTextBox messageTextBox; 11 | public DialogResult Result { get; private set; } 12 | private readonly TableLayoutPanel panel; 13 | private readonly Button okButton; 14 | private readonly Button cpyButton; 15 | private readonly Button logButton; 16 | public ErrorMessageDialog(string title, string message, string? logPath = null) 17 | { 18 | panel = new(); 19 | messageTextBox = new RichTextBox(); 20 | okButton = new Button(); 21 | cpyButton = new Button(); 22 | logButton = new Button(); 23 | 24 | InitializeComponent(); 25 | SetupDialog(title, message, logPath); 26 | } 27 | private void InitializeComponent() 28 | { 29 | SuspendLayout(); 30 | 31 | Text = string.Empty; 32 | Size = new Size(450, 200); 33 | StartPosition = FormStartPosition.CenterParent; 34 | FormBorderStyle = FormBorderStyle.FixedDialog; 35 | MaximizeBox = false; 36 | MinimizeBox = false; 37 | ShowIcon = false; 38 | ShowInTaskbar = false; 39 | Dock = DockStyle.Fill; 40 | 41 | // panel 42 | panel.Anchor = AnchorStyles.Top; 43 | panel.Size = new Size(300, 150); 44 | panel.BorderStyle = BorderStyle.None; 45 | panel.ColumnCount = 3; 46 | panel.RowCount = 2; 47 | panel.RowStyles.Add(new RowStyle(SizeType.AutoSize)); 48 | panel.RowStyles.Add(new RowStyle(SizeType.Absolute, 44)); 49 | panel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100)); 50 | panel.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize)); 51 | panel.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize)); 52 | panel.Dock = DockStyle.Fill; 53 | 54 | // Message TextBox 55 | messageTextBox.ReadOnly = true; 56 | messageTextBox.BorderStyle = BorderStyle.None; 57 | messageTextBox.BackColor = BackColor; 58 | messageTextBox.ScrollBars = RichTextBoxScrollBars.Vertical; 59 | messageTextBox.TabStop = false; 60 | 61 | // ok button 62 | okButton.Text = "OK"; 63 | okButton.Size = new Size(75, 23); 64 | okButton.DialogResult = DialogResult.OK; 65 | okButton.Click += (s, e) => { Result = DialogResult.OK; Close(); }; 66 | 67 | // copy button 68 | cpyButton.Text = "Copy error"; 69 | cpyButton.Size = new Size(100, 23); 70 | cpyButton.Click += CopyButton_Click; 71 | 72 | // log button 73 | logButton.Text = "Open Log Folder"; 74 | logButton.Size = new Size(120, 23); 75 | logButton.Click += LogButton_Click; 76 | 77 | panel.Controls.Add(messageTextBox, 0, 0); 78 | panel.SetColumnSpan(messageTextBox, 3); 79 | panel.Controls.Add(okButton, 0, 1); 80 | panel.Controls.Add(cpyButton, 1, 1); 81 | panel.Controls.Add(logButton, 2, 1); 82 | 83 | Controls.Add(panel); 84 | 85 | // Set default button and cancel button 86 | AcceptButton = okButton; 87 | CancelButton = okButton; 88 | 89 | ResumeLayout(); 90 | } 91 | private void SetupDialog(string title, string message, string? logPath = null) 92 | { 93 | Text = title; 94 | messageTextBox.Rtf = message; 95 | messageTextBox.AutoSize = true; 96 | 97 | Size size = messageTextBox.GetPreferredSize(new Size(800, 0)) + new Size(0, 50); 98 | int newHeight = size.Height + 100; 99 | int newWidth = Math.Max(450, size.Width + 50); 100 | 101 | messageTextBox.Size = new Size(size.Width, size.Height); 102 | Size = new Size(newWidth, newHeight); 103 | 104 | // Hide log button if no path provided 105 | if (string.IsNullOrEmpty(logPath)) 106 | { 107 | logButton.Visible = false; 108 | okButton.Location = new Point(340, 125); // Center the OK button 109 | } 110 | else 111 | { 112 | logButton.Tag = logPath; // Store the log path 113 | } 114 | } 115 | private void LogButton_Click(object? sender, EventArgs e) 116 | { 117 | try 118 | { 119 | string? logPath = logButton.Tag?.ToString(); 120 | if (!string.IsNullOrEmpty(logPath)) 121 | { 122 | System.Diagnostics.Process.Start("explorer.exe", logPath); 123 | } 124 | } 125 | catch (Exception ex) 126 | { 127 | MessageBox.Show($"Could not open log folder: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Warning); 128 | } 129 | 130 | // nifty trick: use Retry to indicate log button was clicked 131 | Result = DialogResult.Retry; 132 | Close(); 133 | } 134 | private void CopyButton_Click(object? sender, EventArgs e) 135 | { 136 | Clipboard.SetText(messageTextBox.Text); 137 | } 138 | public static DialogResult Show(string title, string message, string? logPath = null) 139 | { 140 | using var dialog = new ErrorMessageDialog(title, message, logPath); 141 | dialog.ShowDialog(); 142 | return dialog.Result; 143 | } 144 | } 145 | } -------------------------------------------------------------------------------- /docs/guides/start-modding.zh.md: -------------------------------------------------------------------------------- 1 | # 开始吧! 2 | 3 | [TOC] 4 | 5 | ## C#基础 6 | 7 | 如果你想要制作mod, 那请确保你有一定的C#基础, 不然你可能会看不懂工具的API是怎么用的. 8 | 9 | ## 下载工具 10 | 11 | 要想制作mod, 你首先需要一个写代码的工具. 12 | 13 | 比如**Visual Studio**, 这是一款很专业的开发工具, 你可以用它方便地开发mod. 14 | 15 | [在这里下载](https://visualstudio.microsoft.com/) 16 | 17 | 在下载时, 确保你选择了 `.NET Desktop Development` 工作负载. 其他的负载可以不用下载. 18 | 19 | 最后, 到微软官网查找 `.NET 6.0 SDK` 来下载安装包. 然后重启你的电脑来应用这些更改. 20 | 21 | 另一个选择是**Visual Studio Code**, 在我看来其实用VSCode就足以制作StoneShard的mod了. 22 | 23 | 对于**Visual Studio Code**的教程, 请移步[这里](../guides/start-modding-with-vsc.md) 24 | 25 | ## 创建你的第一个mod 26 | 27 | 首先, 你需要启动 **ModShardLauncher.exe** , 启动之后它会在根目录下创建两个文件夹, 28 | 一个是Mods, 另一个是ModSources. 29 | 30 | 然后在ModSources文件夹中创建一个新的文件夹, 你的第一个mod就从这里开始了. 31 | 32 | ### 创建mod! 33 | 34 | 创建mod的方式有很多, 我们先来拿**Visual Studio**举例. 35 | 首先运行VS, 点击创建新项目, 搜索类库并单击下一步. 如图: 36 |
![](../img/create_project_0.png){: style="width:50%"}
37 | 38 | 然后输入你mod的名字, 选择路径为刚才 **ModShardLauncher.exe** 创建的 Mods 文件夹. 39 |
![](../img/create_project_1.png){: style="width:50%"}
40 | 41 | 最后选择 .Net 6.0即可 42 |
![](../img/create_project_2.png){: style="width:50%"}
43 | 44 | ### 程序集引用! 45 | 46 | 首先我们需要引用工具的程序集, 即Dll文件. 47 | 48 | 先打开解决方案资源管理器. 49 |
![](../img/mod_0.png){: style="width:50%"}
50 | 51 | 右键依赖项, 并单击添加项目引用. 52 |
![](../img/mod_1.png){: style="width:50%"}
53 | 54 | 点击浏览. 55 |
![](../img/mod_2.png){: style="width:50%"}
56 | 57 | 最后选择ModShardLauncher.dll并点击添加. 58 |
![](../img/mod_3.png){: style="width:50%"}
59 | 60 | ### Mod主类! 61 | 62 | 如果你有C#基础, 那你一定对 **类**(Class) 有了解.我们接下来就要创建一个Mod的主类. 63 | 64 | 创建项目时, VS应该已经为我们创建了一个类, 名字叫做Class1. 我们要做的就是先添加对 `ModShardLauncher` 和 `ModShardLauncher.Mods` 这两个命名空间的引用. 然后将代码改成如下这样: 65 |
![](../img/class_0.png){: style="width:50%"}
66 | 67 | 68 | 可以看到我们先是把类的访问级别从 `internal` 改为了 `public` , 这样一来Mod加载时就可以读取到这个类. 然后我们把类名改成了 `MyFirstMod` , 并让这个类继承 `Mod` 类. 69 | 70 | ### Mod信息! 71 | 72 | 接下来我们给Mod添加基础的信息. 73 | 74 | 在 `MyFirstMod` 类中添加如下代码: 75 | ```C# 76 | public override string Name => "MyFirstMod"; 77 | public override string Author => "Mantodea"; 78 | public override string Description => "我的第一个mod"; 79 | ``` 80 | 这样我们就设置了mod的名称, 作者与描述信息. 81 | 82 | ### 编译Mod! 83 | 84 | 接下来我们启动**ModShardLauncher**. 可以看到我们的Mod源码已经被加载出来了. 85 |
![](../img/compile_0.png){: style="width:50%"}
86 | 87 | 在编译mod之前, 我们需要先点击模组界面左上角的文件夹按钮, 并选择 **原版** 的data.win文件进行加载. 88 | 89 | ??? reason "为什么要使用原版文件?" 90 | 91 | 这个工具是基于data.win文件内的各种信息的名称来工作的, 如果你已经加入了源码mod, 很可能会出现各种崩溃的情况. 92 | 93 | ??? reason "为什么要加载游戏文件才能编译?" 94 | 95 | 为了获取游戏版本, 防止极小可能出现的版本不同而崩溃现象(确信) 96 | 97 | 然后我们就可以点击 `MyFirstMod` 栏位右下角的编译辣! (UI现在嘎嘎好看是不是) 98 | 99 | 编译成功后的结果: 100 |
![](../img/compile_1.png){: style="width:50%"}
101 | 102 | ## 创建你的第一把武器 103 | 104 | 就在刚刚, 你的第一个mod已经成功编译了! 接下来, 让我们为它添加一些看得到的东西吧. 105 | 106 | ### 创建武器类! 107 | 108 | 首先点击右侧的解决方案资源管理器, 然后右键你的项目, 点击添加, 最后点击新建项, 如图: 109 |
![](../img/weapon_0.png){: style="width:50%"}
110 | 111 | 名字就输入这把武器的名称即可, 这里我们使用 `MyFirstWeapon` 作为它的名字. 112 | 113 | 接下来进入代码界面, 还是一样的操作, 将 `internal` 改为 `public` , 以便Mod加载时可以加载到这把武器的信息. 然后添加对 `ModShardLauncher` 和 `ModShardLauncher.Mods` 这两个命名空间的引用. 并让武器类继承 `Weapon` 类. 114 | 115 | ### 修改武器的信息! 116 | 117 | 玩过紫晶的人都知道, 紫晶里的武器属性很多. 挨个设置不仅麻烦, 还会很痛苦, 而且还有可能落下某些属性, 导致mod没法正常加载. 118 | 119 | ??? why "你知道的太多了" 120 | ~~实际上是因为毛子写的代码很傻逼.~~ 121 | 122 | 那么有没有一种办法可以让我们简单快捷的设置一把武器的属性呢? 123 | 124 | 首先我们添加如下代码来重写 `SetDefaults` 方法. 125 | ```C# 126 | public override void SetDefaults() 127 | { 128 | 129 | } 130 | ``` 131 | ??? why "你知道的太多了" 132 | ~~有种tModLoader的风格, 我已经被荼毒了.~~ 133 | 134 | 看英文可以知道, 这个方法用于在加载武器时设定它的属性. 135 | 136 | 接下来我们隆重介绍---- **`CloneDefaults`** 方法! 137 | 138 | 没错, 为了防止玩家累死(不是) 我们modder开发时经常忘记各种属性, 我提供了一个方法来让当前这把武器的除 `Name` 与 `ID` 两个属性之外的所有属性全部照抄另外一把原版武器----**`CloneDefaults`**! 因此, 只需把代码改成这样: 139 | ```C# 140 | public override void SetDefaults() 141 | { 142 | CloneDefaults("Homemade Blade"); 143 | Name = "MyFirstWeapon"; 144 | ID = "MyFirstMod1"; 145 | } 146 | ``` 147 | 148 | 这样一来, 这把武器就变成了一把除了名字不一样其他全部一样的换皮土刀(有种NTR的感觉) 149 | 150 | 但是需要注意的是, `CloneDefaults` 并不会对武器的各种语言名称和介绍进行赋值, 这些仍然需要你手动修改. 因此我们再加上两行: 151 | 152 | ```C# 153 | public override void SetDefaults() 154 | { 155 | CloneDefaults("Homemade Blade"); 156 | Name = "MyFirstWeapon"; 157 | ID = "MyFirstMod1"; 158 | Description[ModLanguage.Chinese] = "这是我的第一把武器"; 159 | NameList[ModLanguage.Chinese] = "我的神剑咖喱棒"; 160 | } 161 | ``` 162 | 163 | 这样一来我们的第一把武器算是初步完成了. 164 | 165 | ### 武器贴图! 166 | 167 | StoneShard做mod最痛苦的一部分就是这里了. 贴图, 一把最基础的武器竟然需要六张贴图, 这无疑增加了modder的工作量. 168 | 169 | 如果你没得贴图, 你可以使用UTMT对原版的贴图进行导出, 然后放在Mod目录除 `.vs, bin 和 obj` 的任何地方, 打包时会自动将他们打包进data.win的. 170 |
![](../img/weapon_1.png){: style="width:50%"}
171 | 172 | 如图所示, 从上到下依次是: 人物右手拿武器, 人物左手拿武器, 背包中的武器(有三张的原因是紫晶的武器有破损系统, 如果你不想画, 可以把完整版的武器复制三遍), 掉落的武器. 173 | 174 | ??? why "注意" 175 | 武器在背包中的贴图长宽必须是27的倍数, 这是因为紫晶的背包一格为27*27 176 | 177 | 以上这张图只针对单手武器, 如果是双手武器, 则只需要char而不需要char_left, 因为双手武器只有一种拿取方式. 178 | 179 | 准备好所有这些东西后, 你就可以再次打开 **ModShardLauncher.exe** 重复之前的编译步骤. 180 | 181 | ### 加载Mod! 182 | 183 | !!! notice "**注意!!!**" 184 | 有一点很重要, 你需要把工具目录下一个叫做 `ModShard.dll` 的文件移动到游戏的根目录, 他是该工具内置的一个游戏插件. 否则你将无法启动游戏! 185 | 186 | 最后一步, 也就是加载Mod了, 在你编译完mod之后, 你会发现上方的模组界面中多出了你的mod, 选择它右下角的启用. 最后点击左上角的保存按钮, 就可以把mod数据打包进你刚才加载的那个原版data.win了. 把打包好的数据随便存在什么地方, 把原版的data.win挪走, 再进入游戏, 就会提示你选择数据文件. 选择我们刚才保存的那个数据文件即可. 187 | 188 | ### 进入游戏! 189 | 190 | 打开游戏后, 你会发现打开了一个除游戏之外的窗口, 那是刚才的插件打开的, 它的作用是类似于一个控制台, 可以在游戏过程中运行一些内置函数.(如果没打开记得联系我 一定是哪里出问题了) 191 | 192 | 正常进入游戏, 插件内置了一个give函数, 你可以在插件窗口的下方 `Script` 文本框中输入 193 | ``` 194 | give MyFirstWeapon 195 | ``` 196 | 来获取刚才那把mod武器. 197 | 198 | 如果你想用这个功能获取其他武器, 请注意把武器名中的空格改为 `_` , 如: 199 | ``` 200 | give Homemade_Blade 201 | ``` 202 | 203 | 执行了give函数后, 可以发现背包中就多了一把mod武器了. 204 |
![](../img/weapon_2.png){: style="width:50%"}
-------------------------------------------------------------------------------- /ModUtils/TextureUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Serilog; 5 | using UndertaleModLib; 6 | using UndertaleModLib.Models; 7 | 8 | namespace ModShardLauncher 9 | { 10 | public class RectTexture 11 | { 12 | public ushort X; 13 | public ushort Y; 14 | public ushort Width; 15 | public ushort Height; 16 | 17 | public RectTexture(ushort x, ushort y, ushort width, ushort height) 18 | { 19 | X = x; 20 | Y = y; 21 | Width = width; 22 | Height = height; 23 | } 24 | } 25 | public class MarginData 26 | { 27 | public int Top; 28 | public int Bottom; 29 | public int Left; 30 | public int Right; 31 | 32 | public MarginData(int top, int bottom, int left, int right) 33 | { 34 | Top = top; 35 | Bottom = bottom; 36 | Left = left; 37 | Right = right; 38 | } 39 | } 40 | public class BoundingData 41 | { 42 | public T Width; 43 | public T Height; 44 | 45 | public BoundingData(T width, T height) 46 | { 47 | Width = width; 48 | Height = height; 49 | } 50 | } 51 | public class OriginData 52 | { 53 | public int X; 54 | public int Y; 55 | 56 | public OriginData(int x, int y) 57 | { 58 | X = x; 59 | Y = y; 60 | } 61 | } 62 | public static partial class Msl 63 | { 64 | public static UndertaleTexturePageItem CreateTexureItem(UndertaleEmbeddedTexture texture, RectTexture source, RectTexture target, BoundingData bounding) 65 | { 66 | return new() 67 | { 68 | Name = ModLoader.Data.Strings.MakeString("PageItem " + ModLoader.Data.TexturePageItems.Count), 69 | 70 | SourceX = source.X, 71 | SourceY = source.Y, 72 | SourceHeight = source.Height, 73 | SourceWidth = source.Width, 74 | 75 | TargetX = target.X, 76 | TargetY = target.Y, 77 | TargetHeight = target.Height, 78 | TargetWidth = target.Width, 79 | 80 | BoundingHeight = bounding.Height, 81 | BoundingWidth = bounding.Width, 82 | TexturePage = texture 83 | }; 84 | } 85 | public static UndertaleSprite CreateSpriteNoCollisionMasks(string spriteName, MarginData margin, OriginData origin, BoundingData bounding) 86 | { 87 | UndertaleSprite newSprite = new() 88 | { 89 | Name = ModLoader.Data.Strings.MakeString(spriteName), 90 | Width = bounding.Width, 91 | Height = bounding.Height, 92 | MarginLeft = margin.Left, 93 | MarginRight = margin.Right, 94 | MarginTop = margin.Top, 95 | MarginBottom = margin.Bottom, 96 | OriginX = origin.X, 97 | OriginY = origin.Y, 98 | }; 99 | 100 | return newSprite; 101 | } 102 | public static UndertaleSprite GetSprite(string name) 103 | { 104 | UndertaleSprite sprite = ModLoader.Data.Sprites.First(t => t.Name.Content == name); 105 | Log.Information("Found sprite: {0}", name); 106 | return sprite; 107 | } 108 | public static UndertaleEmbeddedTexture GetEmbeddedTexture(string name) 109 | { 110 | UndertaleEmbeddedTexture embeddedTexture = ModLoader.Data.EmbeddedTextures.First(t => t.Name.Content == name); 111 | Log.Information("Found embedded texture: {0}", name); 112 | return embeddedTexture; 113 | } 114 | public static UndertaleTexturePageItem GetTexturePageItem(string name) 115 | { 116 | UndertaleTexturePageItem texturePageItem = ModLoader.Data.TexturePageItems.First(t => t.Name.Content == name); 117 | Log.Information("Found texture page item: {0}", name); 118 | return texturePageItem; 119 | } 120 | public static string AddNewTexturePageItem(string embeddedTextureName, RectTexture source, RectTexture target, BoundingData bounding) 121 | { 122 | UndertaleEmbeddedTexture embeddedTexture = GetEmbeddedTexture(embeddedTextureName); 123 | 124 | UndertaleTexturePageItem texturePageItem = CreateTexureItem( 125 | embeddedTexture, 126 | source, 127 | target, 128 | bounding 129 | ); 130 | ModLoader.Data.TexturePageItems.Add(texturePageItem); 131 | Log.Information("Successfully added a new texture from: {0}", embeddedTextureName); 132 | return texturePageItem.Name.Content; 133 | } 134 | public static string AddNewSprite(string spriteName, List texturePageItemNames, MarginData margin, OriginData origin, BoundingData bounding) 135 | { 136 | UndertaleSprite newSprite = CreateSpriteNoCollisionMasks( 137 | spriteName, 138 | margin, 139 | origin, 140 | bounding 141 | ); 142 | 143 | IEnumerable texturePageItems = texturePageItemNames 144 | .Select(x => GetTexturePageItem(x)) 145 | .Select(x => new UndertaleSprite.TextureEntry(){ Texture = x }); 146 | 147 | foreach(UndertaleSprite.TextureEntry texturePageItem in texturePageItems) 148 | { 149 | newSprite.Textures.Add(texturePageItem); 150 | } 151 | 152 | ModLoader.Data.Sprites.Add(newSprite); 153 | 154 | Log.Information("Successfully added new sprite: {0}", newSprite.Name.Content); 155 | return newSprite.Name.Content; 156 | } 157 | } 158 | } -------------------------------------------------------------------------------- /ModUtils/TableUtils/SkillsStats.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Linq; 5 | using System.Runtime.Serialization; 6 | using Serilog; 7 | 8 | namespace ModShardLauncher; 9 | 10 | [SuppressMessage("ReSharper", "InconsistentNaming")] 11 | [SuppressMessage("ReSharper", "IdentifierTypo")] 12 | public partial class Msl 13 | { 14 | public enum SkillsStatsHook 15 | { 16 | BASIC, 17 | RANGED, 18 | SWORDS, 19 | [EnumMember(Value = "2H SWORDS")] 20 | TWOHANDEDSWORDS, 21 | [EnumMember(Value = "2H MACES")] 22 | TWOHANDEDMACES, 23 | [EnumMember(Value = "2H AXES")] 24 | TWOHANDEDAXES, 25 | AXES, 26 | MACES, 27 | STAVES, 28 | SHIELDS, 29 | DAGGERS, 30 | [EnumMember(Value = "DUAL WIELDING")] 31 | DUALWIELDING, 32 | SPEARS, 33 | COMBAT, 34 | ATHLETICS, 35 | SURVIVAL, 36 | PYRO, 37 | GEO, 38 | ELECTRO, 39 | ARMOR, 40 | [EnumMember(Value = "MAGIC MASTERY")] 41 | MAGICMASTERY, 42 | UNDEAD, 43 | [EnumMember(Value = "PROLOGUE STATUES")] 44 | PROLOGUESTATUES, 45 | [EnumMember(Value = "PROLOGUE ARCHON")] 46 | PROLOGUEARCHON, 47 | PROSELYTES, 48 | BRIGANDS, 49 | [EnumMember(Value = "ANCIENT TROLL")] 50 | ANCIENTTROLL, 51 | BEASTS, 52 | MANTICORE 53 | } 54 | 55 | public enum SkillsStatsTarget 56 | { 57 | [EnumMember(Value = "No Target")] 58 | NoTarget, 59 | [EnumMember(Value = "Target Object")] 60 | TargetObject, 61 | [EnumMember(Value = "Target Point")] 62 | TargetPoint, 63 | [EnumMember(Value = "Target Area")] 64 | TargetArea, 65 | [EnumMember(Value = "Target Ally")] 66 | TargetAlly 67 | } 68 | 69 | public enum SkillsStatsPattern 70 | { 71 | normal, 72 | five, 73 | line, 74 | circle, 75 | pyramid, 76 | pyramid_shift 77 | } 78 | 79 | public enum SkillsStatsValidator 80 | { 81 | [EnumMember(Value = "")] 82 | none, 83 | AVOID_TILEMARKS, 84 | DASH 85 | } 86 | 87 | public enum SkillsStatsClass 88 | { 89 | skill, 90 | spell, 91 | attack, 92 | } 93 | 94 | public enum SkillsStatsBranch 95 | { 96 | none, // For some reason the string none has to be written, rather than leaving the field empty. Inconsistent but it is what it is. 97 | ranged, 98 | sword, 99 | [EnumMember(Value = "2hsword")] 100 | two_handed_sword, 101 | [EnumMember(Value = "2hmace")] 102 | two_handed_mace, 103 | [EnumMember(Value = "2haxe")] 104 | two_handed_axe, 105 | axe, 106 | mace, 107 | staff, 108 | shield, 109 | dagger, 110 | dual, 111 | spear, 112 | combat, 113 | athletic, 114 | pyromancy, 115 | geomancy, 116 | electromancy, 117 | armor, 118 | magic_mastery, 119 | necromancy, 120 | sanguimancy 121 | } 122 | 123 | public enum SkillsStatsMetacategory 124 | { 125 | [EnumMember(Value = "")] 126 | none, 127 | weapon, 128 | utility 129 | } 130 | 131 | public static void InjectTableSkillsStats( 132 | SkillsStatsHook hook, 133 | string id, 134 | string? Object = null, 135 | SkillsStatsTarget Target = SkillsStatsTarget.NoTarget, 136 | string Range = "0", 137 | ushort KD = 0, 138 | ushort MP = 0, 139 | ushort Reserv = 0, 140 | ushort Duration = 0, 141 | byte AOE_Lenght = 0, 142 | byte AOE_Width = 0, 143 | bool is_movement = false, 144 | SkillsStatsPattern Pattern = SkillsStatsPattern.normal, 145 | SkillsStatsValidator Validators = SkillsStatsValidator.none, 146 | SkillsStatsClass Class = SkillsStatsClass.skill, 147 | bool Bonus_Range = false, // could be byte ? Not sure as only values are 0 and 1 148 | string? Starcast = null, 149 | SkillsStatsBranch Branch = SkillsStatsBranch.none, 150 | bool is_knockback = false, 151 | bool Crime = false, 152 | SkillsStatsMetacategory metacategory = SkillsStatsMetacategory.none, 153 | short FMB = 0, 154 | string AP = "x", 155 | bool Attack = false, 156 | bool Stance = false, 157 | bool Charge = false, 158 | bool Maneuver = false, 159 | bool Spell = false 160 | ) 161 | { 162 | // Table filename 163 | const string tableName = "gml_GlobalScript_table_skills_stats"; 164 | 165 | // Load table if it exists 166 | List table = ThrowIfNull(ModLoader.GetTable(tableName)); 167 | 168 | // Prepare line 169 | string newline = $"{id};{Object};{GetEnumMemberValue(Target)};{Range};{KD};{MP};{Reserv};{Duration};{AOE_Lenght};{AOE_Width};{(is_movement ? "1" : "0")};{Pattern};{GetEnumMemberValue(Validators)};{Class};{(Bonus_Range ? "1" : "0")};{Starcast};{GetEnumMemberValue(Branch)};{(is_knockback ? "1" : "0")};{(Crime ? "1" : "")};{GetEnumMemberValue(metacategory)};{FMB};{AP};{(Attack ? "1" : "")};{(Stance ? "1" : "")};{(Charge ? "1" : "")};{(Maneuver ? "1" : "")};{(Spell ? "1" : "")};"; 170 | 171 | // Find Hook 172 | string hookStr = "// " + GetEnumMemberValue(hook); 173 | (int ind, string? foundLine) = table.Enumerate().FirstOrDefault(x => x.Item2.Contains(hookStr)); 174 | 175 | // Add line to table 176 | if (foundLine != null) 177 | { 178 | table.Insert(ind + 1, newline); 179 | ModLoader.SetTable(table, tableName); 180 | Log.Information("Injected Skill Stat {0} into {1} under {2}", id, tableName, hook); 181 | } 182 | else 183 | { 184 | Log.Error("Cannot find Hook {0} in table {1}", hook, tableName); 185 | throw new Exception($"Hook {hook} not found in table {tableName}"); 186 | } 187 | } 188 | } -------------------------------------------------------------------------------- /Controls/SourceBar.xaml: -------------------------------------------------------------------------------- 1 | 13 | 14 | 16 | 19 | 20 | 24 | 64 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /ModUtils/LootUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Serilog; 4 | using Newtonsoft.Json; 5 | using System.IO; 6 | 7 | namespace ModShardLauncher 8 | { 9 | public class ItemsTable 10 | { 11 | public string[] ListItems { get; } 12 | public int[] ListRarity { get; } 13 | public int[] ListDurability { get; } 14 | public ItemsTable() 15 | { 16 | ListItems = Array.Empty(); 17 | ListRarity = Array.Empty(); 18 | ListDurability = Array.Empty(); 19 | } 20 | public ItemsTable(string[] listItems, int[] listRarity, int[] listDurability) 21 | { 22 | if (listItems.Length != listRarity.Length || listItems.Length != listDurability.Length) 23 | { 24 | throw new ArgumentException($"Error in ItemsTable constructor, {listItems}, {listRarity} and {listDurability} must have the same length."); 25 | } 26 | ListItems = listItems; 27 | ListRarity = listRarity; 28 | ListDurability = listDurability; 29 | } 30 | } 31 | public class RandomItemsTable 32 | { 33 | public int[] ListWeight { get; } 34 | public ItemsTable ItemsTable { get; } 35 | public RandomItemsTable(int[] listWeight, ItemsTable itemsTable) 36 | { 37 | if (listWeight.Length != itemsTable.ListItems.Length) 38 | { 39 | throw new ArgumentException($"Error in RandomItemsTable constructor, {listWeight}, and {itemsTable} elements must have the same length."); 40 | } 41 | ListWeight = listWeight; 42 | ItemsTable = itemsTable; 43 | } 44 | public RandomItemsTable(int[] listWeight, string[] listItems, int[] listRarity, int[] listDurability): this(listWeight, new ItemsTable(listItems, listRarity, listDurability)) { } 45 | } 46 | public class ReferenceTable 47 | { 48 | public string DefaultTable { get; } 49 | public Dictionary Ids { get; } 50 | public Dictionary Tiers { get; } 51 | public ReferenceTable(string defaultTable, Dictionary ids, Dictionary tiers) 52 | { 53 | DefaultTable = defaultTable; 54 | Ids = ids; 55 | Tiers = tiers; 56 | } 57 | } 58 | public class LootTable 59 | { 60 | public ItemsTable GuaranteedItems { get; } 61 | public int RandomLootMin { get; } 62 | public int RandomLootMax { get; } 63 | public int EmptyWeight { get; } 64 | public RandomItemsTable RandomItemsTable { get; } 65 | public LootTable(ItemsTable guaranteedItems, int randomLootMin, int randomLootMax, int emptyWeight, RandomItemsTable randomItemsTable) 66 | { 67 | GuaranteedItems = guaranteedItems; 68 | RandomLootMin = randomLootMin; 69 | RandomLootMax = randomLootMax; 70 | EmptyWeight = emptyWeight; 71 | RandomItemsTable = randomItemsTable; 72 | } 73 | } 74 | public static class LootUtils 75 | { 76 | internal static Dictionary ReferenceTables = new(); 77 | internal static Dictionary LootTables = new(); 78 | public static void ResetLootTables() 79 | { 80 | ReferenceTables.Clear(); 81 | LootTables.Clear(); 82 | } 83 | public static void SaveLootTables(string DirPath) 84 | { 85 | try 86 | { 87 | if (LootTables.Count > 0) 88 | { 89 | File.WriteAllText(Path.Combine(DirPath, "loot_table.json"), JsonConvert.SerializeObject(LootTables)); 90 | Log.Information("Successfully saving the loot table json."); 91 | } 92 | if (ReferenceTables.Count > 0) 93 | { 94 | File.WriteAllText(Path.Combine(DirPath, "reference_table.json"), JsonConvert.SerializeObject(ReferenceTables)); 95 | Log.Information("Successfully saving the reference table json."); 96 | } 97 | } 98 | catch (Exception ex) 99 | { 100 | Log.Error(ex, "Saving Loot table failed for {0}", DirPath); 101 | } 102 | } 103 | public static void InjectLootScripts() 104 | { 105 | if (LootTables.Count == 0 && ReferenceTables.Count == 0) return; 106 | 107 | Msl.AddInnerFunction("scr_msl_resolve_items"); 108 | Msl.AddInnerFunction("scr_msl_resolve_refence_table"); 109 | Msl.AddInnerFunction("scr_msl_resolve_guaranteed_items"); 110 | Msl.AddInnerFunction("scr_msl_resolve_random_items"); 111 | Msl.AddInnerFunction("scr_msl_resolve_loot_table"); 112 | 113 | Msl.LoadGML("gml_Object_c_container_Other_10") 114 | .MatchFrom("script_execute") 115 | .InsertBelow("scr_msl_resolve_loot_table(other, 0)") 116 | .Save(); 117 | 118 | Msl.LoadGML("gml_Object_o_unit_Destroy_0") 119 | .MatchAll() 120 | .InsertBelow("scr_msl_resolve_loot_table(self, 1)") 121 | .Save(); 122 | } 123 | } 124 | public static partial class Msl 125 | { 126 | public static void AddLootTable(string lootTableID, ItemsTable guaranteedItems, int randomLootMin, int randomLootMax, int emptyWeight, RandomItemsTable randomItemsTable) 127 | { 128 | LootTable lootTable = new(guaranteedItems, randomLootMin, randomLootMax, emptyWeight, randomItemsTable); 129 | LootUtils.LootTables.Add(lootTableID, lootTable); 130 | Log.Information("Adding LootTable {0}", lootTableID); 131 | } 132 | public static void AddReferenceTable(string nameObject, string table) 133 | { 134 | LootUtils.ReferenceTables.Add(nameObject, new ReferenceTable(table, new Dictionary(), new Dictionary())); 135 | Log.Information("Adding ReferenceTable {0} for {1}", table, nameObject); 136 | } 137 | public static void AddReferenceTable(string nameObject, string table, Dictionary? ids, Dictionary? tiers) 138 | { 139 | LootUtils.ReferenceTables.Add(nameObject, new ReferenceTable(table, ids ?? new Dictionary(), tiers ?? new Dictionary())); 140 | Log.Information("Adding ReferenceTable {0} for {1}", table, nameObject); 141 | } 142 | public static void AddReferenceTableForMultipleObjects(string table, params string[] nameObjects) 143 | { 144 | foreach(string nameObject in nameObjects) 145 | { 146 | Msl.AddReferenceTable(nameObject, table); 147 | } 148 | } 149 | } 150 | } -------------------------------------------------------------------------------- /Controls/ModBar.xaml: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 24 | 27 | 28 | 30 | 31 | 32 | 34 | 37 | 38 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /ModUtils/ObjectUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Serilog; 4 | using UndertaleModLib; 5 | using UndertaleModLib.Models; 6 | 7 | namespace ModShardLauncher 8 | { 9 | public static class GameObjectUtils 10 | { 11 | /// 12 | /// Extension method to apply several to a simultaneously. It is expected that all MslEvent.Code contain the path of their code. 13 | /// For example: 14 | /// 15 | /// gameObject.ApplyEvent(ModFiles, 16 | /// new MslEvent(fileName0, EventType.Create, 0), 17 | /// new MslEvent(fileName1, EventType.Other, 10), 18 | /// new MslEvent(fileName2, EventType.Other, 11) 19 | /// ); 20 | /// 21 | /// 22 | /// 23 | /// 24 | /// 25 | /// 26 | static public void ApplyEvent(this UndertaleGameObject gameObject, ModFile modFile, params MslEvent[] mslEvents) 27 | { 28 | foreach (MslEvent mslEvent in mslEvents) 29 | { 30 | mslEvent.Apply(gameObject, modFile); 31 | } 32 | } 33 | /// 34 | /// Extension method to apply several to a simultaneously. It is expected that all MslEvent.Code contain their code directly. 35 | /// For example: 36 | /// 37 | /// gameObject.ApplyEvent( 38 | /// new MslEvent(code0, EventType.Create, 0), 39 | /// new MslEvent(code1, EventType.Other, 10), 40 | /// new MslEvent(code2, EventType.Other, 11) 41 | /// ); 42 | /// 43 | /// 44 | /// 45 | /// 46 | /// 47 | static public void ApplyEvent(this UndertaleGameObject gameObject, params MslEvent[] mslEvents) 48 | { 49 | foreach (MslEvent mslEvent in mslEvents) 50 | { 51 | mslEvent.Apply(gameObject); 52 | } 53 | } 54 | } 55 | public static partial class Msl 56 | { 57 | /// 58 | /// Add and return a new named to the data.win if this name is not used already. 59 | /// Else return the existing . 60 | /// This methods does not allow any parametrization when creating this . 61 | /// 62 | /// 63 | /// 64 | public static UndertaleGameObject AddObject(string name) 65 | { 66 | return AddObject( 67 | name, 68 | spriteName:"", 69 | parentName: "", 70 | isVisible: false, 71 | isPersistent: false, 72 | isAwake: false, 73 | collisionShapeFlags: CollisionShapeFlags.Circle); 74 | } 75 | /// 76 | /// Add and return a new named to the data.win if this name is not used already. 77 | /// Else return the existing . 78 | /// A lot of parametrization is possible when creating this . 79 | /// 80 | /// 81 | /// 82 | /// 83 | /// 84 | /// 85 | /// 86 | /// 87 | /// 88 | /// 89 | public static UndertaleGameObject AddObject( 90 | string name, 91 | string spriteName = "", 92 | string parentName = "", 93 | bool isVisible = false, 94 | bool isPersistent = false, 95 | bool isAwake = false, 96 | CollisionShapeFlags collisionShapeFlags = CollisionShapeFlags.Circle) 97 | { 98 | try 99 | { 100 | // check if the object exists already 101 | UndertaleGameObject? existingObj = ModLoader.Data.GameObjects.FirstOrDefault(t => t.Name.Content == name); 102 | if(existingObj != null) 103 | { 104 | Log.Information("Cannot create the GameObject since it already exists: {0}", name); 105 | return existingObj; 106 | } 107 | 108 | // retrieve possible parent and sprite 109 | UndertaleSprite? sprite = null; 110 | if (spriteName != "") sprite = GetSprite(spriteName); 111 | UndertaleGameObject? parent = null; 112 | if (parentName != "") parent = GetObject(parentName); 113 | 114 | // doesnt exist so it can be added 115 | UndertaleGameObject obj = new() 116 | { 117 | Name = ModLoader.Data.Strings.MakeString(name), 118 | Sprite = sprite, 119 | ParentId = parent, 120 | Visible = isVisible, 121 | Persistent = isPersistent, 122 | CollisionShape = collisionShapeFlags, 123 | Awake = isAwake, 124 | }; 125 | ModLoader.Data.GameObjects.Add(obj); 126 | Log.Information("Successfully created gameObject: {0}", name); 127 | return obj; 128 | } 129 | catch 130 | { 131 | throw; 132 | } 133 | } 134 | /// 135 | /// Return the named if it exists. Else raise an exception. 136 | /// 137 | /// 138 | /// 139 | public static UndertaleGameObject GetObject(string name) 140 | { 141 | UndertaleGameObject gameObject = ModLoader.Data.GameObjects.First(t => t.Name.Content == name); 142 | Log.Information("Found gameObject: {0}", name); 143 | return gameObject; 144 | } 145 | /// 146 | /// Replace the named by . 147 | /// Raise an exception if the named does not exist. 148 | /// 149 | /// 150 | /// 151 | public static void SetObject(string name, UndertaleGameObject o) 152 | { 153 | (int indexObj, _) = ModLoader.Data.GameObjects.Enumerate().First(t => t.Item2.Name.Content == name); 154 | ModLoader.Data.GameObjects[indexObj] = o; 155 | Log.Information("Successfully replaced gameObject: {0}", name); 156 | } 157 | } 158 | } --------------------------------------------------------------------------------