├── .editorconfig
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ └── question.md
└── workflows
│ └── build.yml
├── .gitignore
├── FreeMote.Plugins.x64
├── FreeMote.Plugins.x64.csproj
├── Images
│ └── TlgFormatter.cs
├── Properties
│ └── AssemblyInfo.cs
└── Shells
│ └── MflShell.cs
├── FreeMote.Plugins
├── Audio
│ ├── At9Formatter.cs
│ ├── NxAdpcmFormatter.cs
│ ├── OpusFormatter.cs
│ ├── VagFile.cs
│ ├── VagFormatter.cs
│ └── XwmaFormatter.cs
├── FreeMote.Plugins.csproj
├── FreeMote.Plugins.csproj.DotSettings
├── Images
│ ├── AstcFormatter.cs
│ └── Bc7Formatter.cs
├── Properties
│ ├── AssemblyInfo.cs
│ ├── Resources.Designer.cs
│ └── Resources.resx
└── Shells
│ ├── Lz4Shell.cs
│ ├── MdfShell.cs
│ ├── MxbShell.cs
│ ├── MzsShell.cs
│ ├── PsdShell.cs
│ ├── PspShell.cs
│ └── PszShell.cs
├── FreeMote.PsBuild
├── Converters
│ ├── Common2KrkrConverter.cs
│ ├── CommonWinConverter.cs
│ ├── ISpecConverter.cs
│ └── Krkr2CommonConverter.cs
├── FreeMote.PsBuild.csproj
├── MmoBuilder.cs
├── MmoCompiler.cs
├── MmoTypes.cs
├── Properties
│ ├── AssemblyInfo.cs
│ ├── Resources.Designer.cs
│ └── Resources.resx
├── PsBuildHelper.cs
├── PsbCompiler.cs
├── PsbDecompiler.cs
├── PsbJsonConverter.cs
├── PsbResourceJson.cs
└── PsbSpecConverter.cs
├── FreeMote.Psb
├── EmtPainter.cs
├── FreeMote.Psb.csproj
├── FreeMote.Psb.csproj.DotSettings
├── IPsbType.cs
├── IResourceMetadata.cs
├── MtnPainter.cs
├── Plugins
│ ├── AudioFileFormatter.cs
│ ├── FreeMount.cs
│ ├── FreeMountContext.cs
│ ├── IPsbAudioFormatter.cs
│ ├── IPsbImageFormatter.cs
│ ├── IPsbKeyProvider.cs
│ ├── IPsbPluginInfo.cs
│ ├── IPsbShell.cs
│ ├── IPsbSpecialType.cs
│ ├── ManagedTlgFormatter.cs
│ └── WavFormatter.cs
├── Properties
│ └── AssemblyInfo.cs
├── Psb.cs
├── PsbExtension.cs
├── PsbResHelper.cs
├── PsbValues.cs
├── Resources
│ ├── ArchDatas.cs
│ ├── AudioMetadata.cs
│ ├── FlattenArrayMetadata.cs
│ ├── IArchData.cs
│ └── ImageMetadata.cs
├── SprPainter.cs
├── Textures
│ ├── TextureCombiner.cs
│ ├── TexturePacker.cs
│ └── TextureSpliter.cs
└── Types
│ ├── ArchiveType.cs
│ ├── BaseImageType.cs
│ ├── FontType.cs
│ ├── ImageType.cs
│ ├── M2Types.cs
│ ├── MapType.cs
│ ├── MmoType.cs
│ ├── MotionType.cs
│ ├── PimgType.cs
│ ├── ScnType.cs
│ └── SoundArchiveType.cs
├── FreeMote.Purify
└── readme.md
├── FreeMote.Tests
├── App.config
├── FreeMote.Tests.csproj
├── FreeMoteTest.cs
├── MmoTest.cs
├── Properties
│ └── AssemblyInfo.cs
├── PsBuildTest.cs
└── PsbTest.cs
├── FreeMote.Tools.EmtConvert
├── App.config
├── FreeMote.Tools.EmtConvert.csproj
├── Program.cs
└── Properties
│ └── AssemblyInfo.cs
├── FreeMote.Tools.EmtMake
├── App.config
├── FreeMote.Tools.EmtMake.csproj
├── Program.cs
└── Properties
│ └── AssemblyInfo.cs
├── FreeMote.Tools.PsBuild
├── App.config
├── FreeMote.Tools.PsBuild.csproj
├── Program.cs
└── Properties
│ └── AssemblyInfo.cs
├── FreeMote.Tools.PsbDecompile
├── App.config
├── FreeMote.Tools.PsbDecompile.csproj
├── Program.cs
└── Properties
│ └── AssemblyInfo.cs
├── FreeMote.Tools.Viewer
├── App.xaml
├── App.xaml.cs
├── FreeMote.Tools.Viewer.csproj
├── MainWindow.xaml
├── MainWindow.xaml.cs
├── PreciseTimer.cs
├── Properties
│ ├── AssemblyInfo.cs
│ ├── Resources.Designer.cs
│ ├── Resources.resx
│ ├── Settings.Designer.cs
│ └── Settings.settings
└── app.config
├── FreeMote.sln
├── FreeMote.sln.DotSettings
├── FreeMote
├── Adler32.cs
├── AstcDecoder.cs
├── AstcFile.cs
├── Bc7Decoder.cs
├── BitmapExtension.cs
├── BitmapHelper.cs
├── Consts.cs
├── DxtCodec.cs
├── DxtUtil.cs
├── FreeMote.LICENSE.txt
├── FreeMote.csproj
├── FreeMoteExtension.cs
├── Logger.cs
├── MPack.cs
├── Motion
│ └── MmoEnums.cs
├── PathHelper.cs
├── PostProcessing.cs
├── PrefixTree.cs
├── Properties
│ └── AssemblyInfo.cs
├── PsbEnums.cs
├── PsbFile.cs
├── PsbHeader.cs
├── PsbStreamContext.cs
├── RL.cs
├── RleCompress.cs
├── TlgImageConverter.cs
└── ZlibCompress.cs
├── LICENSE
└── README.md
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.cs]
2 |
3 | # CS1591: 缺少对公共可见类型或成员的 XML 注释
4 | dotnet_diagnostic.cs1591.severity = none
5 |
6 | # Microsoft .NET properties
7 | csharp_new_line_before_members_in_object_initializers = false
8 | csharp_preferred_modifier_order = public, private, protected, internal, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async:suggestion
9 | csharp_space_after_cast = true
10 | csharp_style_var_elsewhere = true:suggestion
11 | csharp_style_var_for_built_in_types = true:suggestion
12 | csharp_style_var_when_type_is_apparent = true:suggestion
13 | dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none
14 | dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:none
15 | dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none
16 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
17 | dotnet_style_predefined_type_for_member_access = true:suggestion
18 | dotnet_style_qualification_for_event = false:suggestion
19 | dotnet_style_qualification_for_field = false:suggestion
20 | dotnet_style_qualification_for_method = false:suggestion
21 | dotnet_style_qualification_for_property = false:suggestion
22 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
23 |
24 | # ReSharper properties
25 | resharper_csharp_max_line_length = 140
26 | resharper_space_within_single_line_array_initializer_braces = false
27 |
28 | # ReSharper inspection severities
29 | resharper_arrange_redundant_parentheses_highlighting = hint
30 | resharper_arrange_this_qualifier_highlighting = hint
31 | resharper_arrange_type_member_modifiers_highlighting = hint
32 | resharper_arrange_type_modifiers_highlighting = hint
33 | resharper_built_in_type_reference_style_for_member_access_highlighting = hint
34 | resharper_built_in_type_reference_style_highlighting = hint
35 | resharper_redundant_base_qualifier_highlighting = warning
36 | resharper_suggest_var_or_type_built_in_types_highlighting = hint
37 | resharper_suggest_var_or_type_elsewhere_highlighting = hint
38 | resharper_suggest_var_or_type_simple_types_highlighting = hint
39 |
40 | [*.{appxmanifest,axml,build,c,c++,cc,cginc,compute,config,cp,cpp,csproj,cu,cuh,cxx,dbml,discomap,dtd,h,hh,hlsl,hlsli,hlslinc,hpp,hxx,inc,inl,ino,ipp,jsproj,lsproj,mpp,mq4,mq5,mqh,njsproj,nuspec,proj,props,proto,resw,resx,StyleCop,targets,tasks,tpp,usf,ush,vbproj,xml,xsd}]
41 | indent_style = tab
42 | indent_size = tab
43 | tab_width = 4
44 |
45 | [*.{asax,ascx,aspx,axaml,cs,cshtml,css,htm,html,js,jsx,master,paml,razor,skin,ts,tsx,vb,xaml,xamlx,xoml}]
46 | indent_style = space
47 | indent_size = 4
48 | tab_width = 4
49 |
50 | [*.{json,resjson}]
51 | indent_style = space
52 | indent_size = 2
53 | tab_width = 2
54 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: Ulysses
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/question.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Question
3 | about: Provide samples (e.g. PSB file) or your issue will be ignored!
4 | title: "[Question]"
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 | * Read [wiki](https://github.com/UlyssesWu/FreeMote/wiki)
10 | * Provide game name and platform (PC/Android/PSP etc.)
11 | * Provide PSB file samples
12 | * Program output, or screenshots
13 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: beta
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 | branches:
9 | - master
10 |
11 | jobs:
12 | build:
13 | runs-on: windows-2019
14 |
15 | steps:
16 | - name: Checkout code
17 | uses: actions/checkout@v4
18 |
19 | - name: Add NuGet source
20 | run: nuget sources add -Name MonarchSolutions -Source https://www.myget.org/F/monarchsolutions/api/v3/index.json
21 |
22 | - name: Restore NuGet packages
23 | run: nuget restore FreeMote.sln
24 |
25 | - name: Build solution
26 | run: dotnet build -c Release FreeMote.sln
27 |
28 | - name: Run post-build script
29 | shell: pwsh
30 | run: |
31 | cd $env:GITHUB_WORKSPACE
32 | mkdir FreeMoteToolkit -Force
33 | Copy-Item FreeMote.Tools.EmtConvert/bin/Release/net48/* FreeMoteToolkit -Recurse -Force
34 | Copy-Item FreeMote.Tools.PsbDecompile/bin/Release/net48/* FreeMoteToolkit -Recurse -Force
35 | Copy-Item FreeMote.Tools.PsBuild/bin/Release/net48/* FreeMoteToolkit -Recurse -Force
36 | Copy-Item FreeMote.Tools.EmtMake/bin/Release/net48/* FreeMoteToolkit -Recurse -Force
37 | Copy-Item FreeMote.Tools.Viewer/bin/Release/net48/* FreeMoteToolkit -Recurse -Force
38 | del FreeMoteToolkit\*.pdb
39 | del FreeMoteToolkit\*.xml
40 | mkdir FreeMoteToolkit\lib -Force
41 | Move-Item FreeMoteToolkit/*.dll FreeMoteToolkit/lib
42 | Move-Item FreeMoteToolkit/x86 FreeMoteToolkit/lib/
43 | Move-Item FreeMoteToolkit/x64 FreeMoteToolkit/lib/
44 |
45 | - name: Upload artifact
46 | uses: actions/upload-artifact@v4
47 | with:
48 | name: FreeMote-${{ github.workflow }}-${{ github.run_number }}
49 | path: FreeMoteToolkit
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 | [Ll]og/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | artifacts/
46 |
47 | *_i.c
48 | *_p.c
49 | *_i.h
50 | *.ilk
51 | *.meta
52 | *.obj
53 | *.pch
54 | *.pdb
55 | *.pgc
56 | *.pgd
57 | *.rsp
58 | *.sbr
59 | *.tlb
60 | *.tli
61 | *.tlh
62 | *.tmp
63 | *.tmp_proj
64 | *.log
65 | *.vspscc
66 | *.vssscc
67 | .builds
68 | *.pidb
69 | *.svclog
70 | *.scc
71 |
72 | # Chutzpah Test files
73 | _Chutzpah*
74 |
75 | # Visual C++ cache files
76 | ipch/
77 | *.aps
78 | *.ncb
79 | *.opendb
80 | *.opensdf
81 | *.sdf
82 | *.cachefile
83 | *.VC.db
84 | *.VC.VC.opendb
85 |
86 | # Visual Studio profiler
87 | *.psess
88 | *.vsp
89 | *.vspx
90 | *.sap
91 |
92 | # TFS 2012 Local Workspace
93 | $tf/
94 |
95 | # Guidance Automation Toolkit
96 | *.gpState
97 |
98 | # ReSharper is a .NET coding add-in
99 | _ReSharper*/
100 | *.[Rr]e[Ss]harper
101 | *.DotSettings.user
102 |
103 | # JustCode is a .NET coding add-in
104 | .JustCode
105 |
106 | # TeamCity is a build add-in
107 | _TeamCity*
108 |
109 | # DotCover is a Code Coverage Tool
110 | *.dotCover
111 |
112 | # NCrunch
113 | _NCrunch_*
114 | .*crunch*.local.xml
115 | nCrunchTemp_*
116 |
117 | # MightyMoose
118 | *.mm.*
119 | AutoTest.Net/
120 |
121 | # Web workbench (sass)
122 | .sass-cache/
123 |
124 | # Installshield output folder
125 | [Ee]xpress/
126 |
127 | # DocProject is a documentation generator add-in
128 | DocProject/buildhelp/
129 | DocProject/Help/*.HxT
130 | DocProject/Help/*.HxC
131 | DocProject/Help/*.hhc
132 | DocProject/Help/*.hhk
133 | DocProject/Help/*.hhp
134 | DocProject/Help/Html2
135 | DocProject/Help/html
136 |
137 | # Click-Once directory
138 | publish/
139 |
140 | # Publish Web Output
141 | *.[Pp]ublish.xml
142 | *.azurePubxml
143 | # TODO: Comment the next line if you want to checkin your web deploy settings
144 | # but database connection strings (with potential passwords) will be unencrypted
145 | *.pubxml
146 | *.publishproj
147 |
148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
149 | # checkin your Azure Web App publish settings, but sensitive information contained
150 | # in these scripts will be unencrypted
151 | PublishScripts/
152 |
153 | # NuGet Packages
154 | *.nupkg
155 | # The packages folder can be ignored because of Package Restore
156 | **/packages/*
157 | # except build/, which is used as an MSBuild target.
158 | !**/packages/build/
159 | # Uncomment if necessary however generally it will be regenerated when needed
160 | #!**/packages/repositories.config
161 | # NuGet v3's project.json files produces more ignoreable files
162 | *.nuget.props
163 | *.nuget.targets
164 |
165 | # Microsoft Azure Build Output
166 | csx/
167 | *.build.csdef
168 |
169 | # Microsoft Azure Emulator
170 | ecf/
171 | rcf/
172 |
173 | # Windows Store app package directories and files
174 | AppPackages/
175 | BundleArtifacts/
176 | Package.StoreAssociation.xml
177 | _pkginfo.txt
178 |
179 | # Visual Studio cache files
180 | # files ending in .cache can be ignored
181 | *.[Cc]ache
182 | # but keep track of directories ending in .cache
183 | !*.[Cc]ache/
184 |
185 | # Others
186 | ClientBin/
187 | ~$*
188 | *~
189 | *.dbmdl
190 | *.dbproj.schemaview
191 | *.pfx
192 | *.publishsettings
193 | node_modules/
194 | orleans.codegen.cs
195 |
196 | # Since there are multiple workflows, uncomment next line to ignore bower_components
197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
198 | #bower_components/
199 |
200 | # RIA/Silverlight projects
201 | Generated_Code/
202 |
203 | # Backup & report files from converting an old project file
204 | # to a newer Visual Studio version. Backup files are not needed,
205 | # because we have git ;-)
206 | _UpgradeReport_Files/
207 | Backup*/
208 | UpgradeLog*.XML
209 | UpgradeLog*.htm
210 |
211 | # SQL Server files
212 | *.mdf
213 | *.ldf
214 |
215 | # Business Intelligence projects
216 | *.rdl.data
217 | *.bim.layout
218 | *.bim_*.settings
219 |
220 | # Microsoft Fakes
221 | FakesAssemblies/
222 |
223 | # GhostDoc plugin setting file
224 | *.GhostDoc.xml
225 |
226 | # Node.js Tools for Visual Studio
227 | .ntvs_analysis.dat
228 |
229 | # Visual Studio 6 build log
230 | *.plg
231 |
232 | # Visual Studio 6 workspace options file
233 | *.opt
234 |
235 | # Visual Studio LightSwitch build output
236 | **/*.HTMLClient/GeneratedArtifacts
237 | **/*.DesktopClient/GeneratedArtifacts
238 | **/*.DesktopClient/ModelManifest.xml
239 | **/*.Server/GeneratedArtifacts
240 | **/*.Server/ModelManifest.xml
241 | _Pvt_Extensions
242 |
243 | # Paket dependency manager
244 | .paket/paket.exe
245 | paket-files/
246 |
247 | # FAKE - F# Make
248 | .fake/
249 |
250 | # JetBrains Rider
251 | .idea/
252 | *.sln.iml
253 | /FreeMote.Purify
254 | /FreeMote.Tests/Res
255 | /FreeMote.Tests/PurifyTest.cs
256 |
257 | /MigrationBackup
258 | /bak
259 | /.vscode
260 | launchSettings.json
261 |
--------------------------------------------------------------------------------
/FreeMote.Plugins.x64/FreeMote.Plugins.x64.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net48
4 | Library
5 | FreeMote.Plugins
6 | false
7 | x64
8 |
9 |
10 |
11 | 1.1.0
12 |
13 |
14 | 0.2.0
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | echo https://github.com/dotnet/project-system/issues/1569#issuecomment-289939906
27 | xcopy "$(TargetDir.TrimEnd('\'))" "$(SolutionDir)FreeMote.Tools.PsbDecompile\$(OutDir)" /E /Y
28 | xcopy "$(TargetDir.TrimEnd('\'))" "$(SolutionDir)FreeMote.Tools.PsBuild\$(OutDir)" /E /Y
29 | xcopy "$(TargetDir.TrimEnd('\'))" "$(SolutionDir)FreeMote.Tools.EmtConvert\$(OutDir)" /E /Y
30 | xcopy "$(TargetDir.TrimEnd('\'))" "$(SolutionDir)FreeMote.Tools.EmtMake\$(OutDir)" /E /Y
31 |
32 |
--------------------------------------------------------------------------------
/FreeMote.Plugins.x64/Images/TlgFormatter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.Composition;
4 | using System.Drawing;
5 | using System.IO;
6 | using FreeMote.Tlg;
7 |
8 | namespace FreeMote.Plugins.Images
9 | {
10 | [Export(typeof(IPsbImageFormatter))]
11 | [ExportMetadata("Name", "FreeMote.Tlg")]
12 | [ExportMetadata("Author", "Ulysses")]
13 | [ExportMetadata("Comment", "TLG support via TlgLib.")]
14 | public class TlgFormatter : IPsbImageFormatter
15 | {
16 | private const string TlgVersion = "TlgVersion";
17 |
18 | ///
19 | /// Is TLG encode plugin enabled
20 | ///
21 | //public static bool CanSaveTlg => TlgNativePlugin.IsEnabled;
22 |
23 | private static TlgImageConverter _managedConverter = null;
24 |
25 | ///
26 | /// Load TLG
27 | ///
28 | ///
29 | /// TLG version, can be 0(unknown),5,6
30 | ///
31 | public static Bitmap LoadTlg(byte[] tlgData, out int version)
32 | {
33 | if (!Consts.PreferManaged)
34 | {
35 | try
36 | {
37 | return TlgNative.ToBitmap(tlgData, out version);
38 | }
39 | catch (Exception)
40 | {
41 | //ignored, fallback to managed decoder
42 | }
43 | }
44 |
45 | if (_managedConverter == null)
46 | {
47 | _managedConverter = new TlgImageConverter();
48 | }
49 |
50 | using (var ms = new MemoryStream(tlgData))
51 | {
52 | using (var br = new BinaryReader(ms))
53 | {
54 | var bmp = _managedConverter.ReadAndGetMetaData(br, out var md);
55 | version = md.Version;
56 | return bmp;
57 | }
58 | }
59 | }
60 |
61 | ///
62 | /// Save TLG
63 | ///
64 | ///
65 | /// true: Save as TLG6; false: Save as TLG5
66 | ///
67 | public static byte[] SaveTlg(Bitmap bmp, bool tlg6 = false)
68 | {
69 | return tlg6 ? bmp.ToTlg6() : bmp.ToTlg5();
70 | }
71 |
72 | public List Extensions { get; } = new List { ".tlg", ".tlg5", ".tlg6" };
73 | public bool CanToBitmap(in byte[] data, Dictionary context = null)
74 | {
75 | return true;
76 | }
77 |
78 | public bool CanToBytes(Bitmap bitmap, Dictionary context = null)
79 | {
80 | var bpp = Image.GetPixelFormatSize(bitmap.PixelFormat);
81 | return bpp == 32 || bpp == 24 || bpp == 8;
82 | }
83 |
84 | public Bitmap ToBitmap(in byte[] data, int width, int height, PsbSpec platform, Dictionary context = null)
85 | {
86 | var bmp = LoadTlg(data, out var v);
87 | if (v > 5 && context != null)
88 | {
89 | context[TlgVersion] = v;
90 | }
91 |
92 | return bmp;
93 | }
94 |
95 | public byte[] ToBytes(Bitmap bitmap, PsbSpec platform, Dictionary context = null)
96 | {
97 | return SaveTlg(bitmap,
98 | context != null && context.ContainsKey(TlgVersion) && context[TlgVersion] is int v && v == 6);
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/FreeMote.Plugins.x64/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | [assembly: AssemblyTitle("FreeMote.Plugins.x64")]
5 | [assembly: AssemblyDescription("FreeMote official plugins (x64)")]
6 | [assembly: AssemblyConfiguration("")]
7 | [assembly: AssemblyCompany("Ulysses")]
8 | [assembly: AssemblyProduct("FreeMote")]
9 | [assembly: AssemblyCopyright("Copyright © Ulysses 2020-2025")]
10 | [assembly: AssemblyTrademark("wdwxy12345@gmail.com")]
11 | [assembly: AssemblyCulture("")]
12 |
13 | [assembly: ComVisible(false)]
14 |
15 | [assembly: Guid("7b5bae81-6779-4404-9e6e-512be4471ab9")]
16 |
17 | // [assembly: AssemblyVersion("1.0.*")]
18 | [assembly: AssemblyVersion("4.0.0.0")]
19 | [assembly: AssemblyFileVersion("4.0.0.0")]
20 |
--------------------------------------------------------------------------------
/FreeMote.Plugins.x64/Shells/MflShell.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.Composition;
4 | using System.IO;
5 | using System.Linq;
6 | using FreeMote.FastLz;
7 | using FreeMote.Psb;
8 | using static FreeMote.Consts;
9 |
10 | namespace FreeMote.Plugins.Shells
11 | {
12 | [Export(typeof(IPsbShell))]
13 | [ExportMetadata("Name", "FreeMote.Mfl")]
14 | [ExportMetadata("Author", "Ulysses")]
15 | [ExportMetadata("Comment", "MFL (FastLZ) support.")]
16 | class MflShell : IPsbShell
17 | {
18 | public string Name => "MFL";
19 |
20 | public byte[] Signature => new byte[] {(byte) 'm', (byte) 'f', (byte) 'l', 0};
21 |
22 | public bool IsInShell(Stream stream, Dictionary context = null)
23 | {
24 | var header = new byte[4];
25 | var pos = stream.Position;
26 | stream.Read(header, 0, 4);
27 | stream.Position = pos;
28 | if (header.SequenceEqual(Signature))
29 | {
30 | if (context != null)
31 | {
32 | context[Context_PsbShellType] = Name;
33 | }
34 |
35 | return true;
36 | }
37 |
38 | return false;
39 | }
40 |
41 | public MemoryStream ToPsb(Stream stream, Dictionary context = null)
42 | {
43 | if (context != null)
44 | {
45 | if (context.ContainsKey(Context_MdfKey))
46 | {
47 | int? keyLength = context.ContainsKey(Context_MdfKeyLength)
48 | ? Convert.ToInt32(context[Context_MdfKeyLength])
49 | : (int?) null;
50 |
51 | stream = PsbExtension.EncodeMdf(stream, (string) context[Context_MdfKey], keyLength, true);
52 | stream.Position = 0; //A new MemoryStream
53 | }
54 | }
55 |
56 | stream.Seek(4, SeekOrigin.Current);
57 | var bytes = new byte[4];
58 | stream.Read(bytes, 0, 4);
59 | var unzippedSize = BitConverter.ToInt32(bytes, 0);
60 |
61 | byte[] input = new byte[stream.Length - 8];
62 | stream.Read(input, 0, input.Length);
63 |
64 | var output = FastLzNative.Decompress(input, unzippedSize);
65 | if (output == null)
66 | {
67 | throw new InvalidDataException("Fast LZ decompress failed");
68 | }
69 |
70 | return new MemoryStream(output);
71 | }
72 |
73 |
74 | public MemoryStream ToShell(Stream stream, Dictionary context = null)
75 | {
76 | byte[] input;
77 | if (stream is MemoryStream inputMs)
78 | {
79 | input = inputMs.ToArray();
80 | }
81 | else
82 | {
83 | input = new byte[stream.Length];
84 | stream.Read(input, 0, input.Length);
85 | }
86 |
87 | var unzipLength = input.Length;
88 |
89 | var output = FastLzNative.Compress(input);
90 | var ms = new MemoryStream(output);
91 |
92 | if (context != null && context.ContainsKey(Context_MdfKey))
93 | {
94 | int? keyLength;
95 | if (context.ContainsKey(Context_MdfKeyLength))
96 | {
97 | keyLength = Convert.ToInt32(context[Context_MdfKeyLength]);
98 | }
99 | else
100 | {
101 | keyLength = (int?) null;
102 | }
103 |
104 | var mms = PsbExtension.EncodeMdf(ms, (string) context[Context_MdfKey], keyLength, false);
105 | ms?.Dispose(); //ms disposed
106 | ms = mms;
107 | }
108 |
109 | ms.Seek(0, SeekOrigin.Begin);
110 | var shellMs = new MemoryStream((int) (ms.Length + 8));
111 | shellMs.Write(Signature, 0, 4);
112 | shellMs.Write(BitConverter.GetBytes(unzipLength), 0, 4);
113 | ms.CopyTo(shellMs);
114 | ms.Dispose();
115 | shellMs.Seek(0, SeekOrigin.Begin);
116 | return shellMs;
117 | }
118 | }
119 | }
--------------------------------------------------------------------------------
/FreeMote.Plugins/Audio/NxAdpcmFormatter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.Composition;
4 | using System.IO;
5 | using System.Linq;
6 | using FreeMote.Psb;
7 | using VGAudio.Containers.Dsp;
8 | using VGAudio.Containers.Wave;
9 | using VGAudio.Utilities;
10 |
11 | //REF: https://www.metroid2002.com/retromodding/wiki/DSP_(File_Format)
12 |
13 | namespace FreeMote.Plugins.Audio
14 | {
15 | [Export(typeof(IPsbAudioFormatter))]
16 | [ExportMetadata("Name", "FreeMote.NxAdpcm")]
17 | [ExportMetadata("Author", "Ulysses")]
18 | [ExportMetadata("Comment", "NX ADPCM support via VGAudio.")]
19 | class NxAdpcmFormatter : IPsbAudioFormatter
20 | {
21 | public List Extensions { get; } = new List { ".adpcm" };
22 | public bool CanToWave(IArchData archData, Dictionary context = null)
23 | {
24 | return archData is AdpcmArchData;
25 | }
26 |
27 | public bool CanToArchData(byte[] wave, Dictionary context = null)
28 | {
29 | return wave != null;
30 | }
31 |
32 | public byte[] ToWave(AudioMetadata md, IArchData archData, string fileName = null, Dictionary context = null)
33 | {
34 | DspReader reader = new DspReader();
35 | var data = reader.Read(archData.Data.Data);
36 | using MemoryStream oms = new MemoryStream();
37 | WaveWriter writer = new WaveWriter();
38 | writer.WriteToStream(data, oms, new WaveConfiguration { Codec = WaveCodec.Pcm16Bit }); //only 16Bit supported
39 | return oms.ToArray();
40 | }
41 |
42 | public bool ToArchData(AudioMetadata md, IArchData archData, in byte[] wave, string fileName, string waveExt, Dictionary context = null)
43 | {
44 | if (archData is not AdpcmArchData data)
45 | {
46 | return false;
47 | }
48 | WaveReader reader = new WaveReader();
49 | var rawData = reader.Read(wave);
50 | using MemoryStream oms = new MemoryStream();
51 | DspWriter writer = new DspWriter();
52 | writer.WriteToStream(rawData, oms, new DspConfiguration {Endianness = Endianness.LittleEndian});
53 | data.Data = new PsbResource {Data = oms.ToArray()};
54 | var format = rawData.GetAllFormats().FirstOrDefault();
55 | if (format != null)
56 | {
57 | data.SampRate = format.SampleRate;
58 | }
59 |
60 | return true;
61 | }
62 |
63 | public bool TryGetArchData(AudioMetadata md, PsbDictionary channel, out IArchData data, Dictionary context = null)
64 | {
65 | data = null;
66 | //if (psb.Platform != PsbSpec.nx)
67 | //{
68 | // return false;
69 | //}
70 | if (!(channel["archData"] is PsbDictionary archDic))
71 | {
72 | return false;
73 | }
74 |
75 | if (!archDic.ContainsKey("body") &&
76 | archDic["data"] is PsbResource aData
77 | && archDic["ext"] is PsbString ext && ext.Value == Extensions[0] &&
78 | archDic["samprate"] is PsbNumber sampRate)
79 | {
80 | var newData = new AdpcmArchData
81 | {
82 | Data = aData,
83 | SampRate = sampRate.AsInt,
84 | Format = PsbAudioFormat.ADPCM
85 | };
86 |
87 | ExtractPan(channel, newData);
88 |
89 | data = newData;
90 | return true;
91 | }
92 |
93 | return false;
94 | }
95 |
96 | private static void ExtractPan(PsbDictionary channel, AdpcmArchData newData)
97 | {
98 | if (channel["pan"] is PsbList panList)
99 | {
100 | newData.Pan = panList;
101 |
102 | if (panList.Count == 2)
103 | {
104 | var left = panList[0].GetFloat();
105 | var right = panList[1].GetFloat();
106 | if (left == 1.0f && right == 0.0f)
107 | {
108 | newData.ChannelPan = PsbAudioPan.Left;
109 | }
110 | else if (left == 0.0f && right == 1.0f)
111 | {
112 | newData.ChannelPan = PsbAudioPan.Right;
113 | }
114 | }
115 | }
116 | }
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/FreeMote.Plugins/Audio/VagFormatter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.Composition;
4 | using System.Diagnostics;
5 | using System.IO;
6 | using System.Reflection;
7 | using FreeMote.Psb;
8 |
9 | namespace FreeMote.Plugins.Audio
10 | {
11 | [Export(typeof(IPsbAudioFormatter))]
12 | [ExportMetadata("Name", "FreeMote.Vag")]
13 | [ExportMetadata("Author", "Ulysses")]
14 | [ExportMetadata("Comment", "VAG support.")]
15 | class VagFormatter : IPsbAudioFormatter
16 | {
17 | public List Extensions { get; } = new List { ".vag" };
18 |
19 | private const string EncoderTool = "vagconv2.exe";
20 | private const string DecoderTool = "vgmstream.exe";
21 |
22 | public string ToolPath { get; set; } = null;
23 |
24 | public VagFormatter()
25 | {
26 | var toolPath = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location) ?? "", "Tools", EncoderTool);
27 | if (File.Exists(toolPath))
28 | {
29 | ToolPath = toolPath;
30 | }
31 | }
32 | public bool CanToWave(IArchData archData, Dictionary context = null)
33 | {
34 | if (archData is PsArchData psArch)
35 | {
36 | if (psArch.Data?.Data != null && psArch.Data.Data.Length > 4)
37 | {
38 | if (psArch.Data.Data.AsciiEqual("VAGp"))
39 | {
40 | return true;
41 | }
42 | }
43 | }
44 |
45 | return false;
46 | }
47 |
48 | public bool CanToArchData(byte[] wave, Dictionary context = null)
49 | {
50 | if (!File.Exists(ToolPath))
51 | {
52 | Logger.LogWarn($"[WARN] External tool missing: {EncoderTool}");
53 | return false;
54 | }
55 |
56 | return true;
57 | }
58 |
59 | public byte[] ToWave(AudioMetadata md, IArchData archData, string fileName = null, Dictionary context = null)
60 | {
61 | //bool hasTool = File.Exists(ToolPath);
62 |
63 | VagFile vag = new VagFile();
64 | if (vag.LoadFromStream(new MemoryStream(archData.Data.Data)))
65 | {
66 | return vag.ToWave().ToArray();
67 | }
68 |
69 | return null;
70 | }
71 |
72 | public bool ToArchData(AudioMetadata md, IArchData archData, in byte[] wave, string fileName, string waveExt, Dictionary context = null)
73 | {
74 | if (!File.Exists(ToolPath))
75 | {
76 | return false;
77 | }
78 |
79 | if (archData is not PsArchData data)
80 | {
81 | return false;
82 | }
83 |
84 | var tempPath = Path.GetTempPath();
85 | var tempFile = Path.Combine(tempPath, fileName);
86 | if (tempFile.EndsWith(".vag"))
87 | {
88 | tempFile = Path.ChangeExtension(tempFile, "");
89 | }
90 | File.WriteAllBytes(tempFile, wave);
91 | var tempOutFile = Path.ChangeExtension(tempFile, ".vag");
92 |
93 | byte[] outBytes = null;
94 | try
95 | {
96 | ProcessStartInfo info = new ProcessStartInfo(ToolPath, $"\"{tempFile}\"")
97 | {
98 | UseShellExecute = false,
99 | WindowStyle = ProcessWindowStyle.Hidden,
100 | CreateNoWindow = true
101 | };
102 | Process process = Process.Start(info);
103 | process?.WaitForExit();
104 | if (!File.Exists(tempOutFile) || process?.ExitCode != 0)
105 | {
106 | Logger.LogError("[ERROR] VAG convert failed.");
107 | return false;
108 | }
109 |
110 | outBytes = File.ReadAllBytes(tempOutFile);
111 | File.Delete(tempFile);
112 | File.Delete(tempOutFile);
113 | }
114 | catch (Exception e)
115 | {
116 | Logger.Log(e);
117 | }
118 |
119 | if (data.Data == null)
120 | {
121 | data.Data = new PsbResource { Data = outBytes };
122 | }
123 | else
124 | {
125 | data.Data.Data = outBytes;
126 | }
127 | data.Format = PsbAudioFormat.VAG;
128 |
129 | return true;
130 | }
131 |
132 | public bool TryGetArchData(AudioMetadata md, PsbDictionary channel, out IArchData data, Dictionary context = null)
133 | {
134 | data = null;
135 | if (md.Spec == PsbSpec.ps4 || md.Spec == PsbSpec.vita)
136 | {
137 | if (channel.Count == 1 && channel["archData"] is PsbResource res)
138 | {
139 | if (res.Data != null && res.Data.Length > 0) //res data exists
140 | {
141 | if (res.Data.AsciiEqual("VAGp"))
142 | {
143 | data = new PsArchData
144 | {
145 | Data = res,
146 | Format = PsbAudioFormat.VAG
147 | };
148 | return true;
149 | }
150 |
151 | //...but the data is other format (at9)
152 | return false;
153 | }
154 |
155 | //res data is null, maybe linking
156 | data = new PsArchData
157 | {
158 | Data = res
159 | };
160 | return true;
161 | }
162 |
163 | return false;
164 | }
165 |
166 | return false;
167 | }
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/FreeMote.Plugins/Audio/XwmaFormatter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.Composition;
4 | using System.Diagnostics;
5 | using System.IO;
6 | using System.Reflection;
7 | using FreeMote.Psb;
8 |
9 | //REF: https://wiki.multimedia.cx/index.php/Microsoft_xWMA
10 |
11 | namespace FreeMote.Plugins.Audio
12 | {
13 | [Export(typeof(IPsbAudioFormatter))]
14 | [ExportMetadata("Name", "FreeMote.Xwma")]
15 | [ExportMetadata("Author", "Ulysses")]
16 | [ExportMetadata("Comment", "XWMA support.")]
17 | class XwmaFormatter : IPsbAudioFormatter
18 | {
19 | public List Extensions { get; } = new List {".xwma", ".xwm"};
20 |
21 | private const string EncoderTool = "xWMAEncode.exe";
22 |
23 | public string ToolPath { get; set; } = null;
24 |
25 | public XwmaFormatter()
26 | {
27 | var toolPath = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location) ?? "", "Tools", EncoderTool);
28 | if (File.Exists(toolPath))
29 | {
30 | ToolPath = toolPath;
31 | }
32 | }
33 |
34 | public bool CanToWave(IArchData archData, Dictionary context = null)
35 | {
36 | return archData is XwmaArchData;
37 | }
38 |
39 | public bool CanToArchData(byte[] wave, Dictionary context = null)
40 | {
41 | if (!File.Exists(ToolPath))
42 | {
43 | Logger.LogWarn($"[WARN] External tool missing: {EncoderTool}");
44 | return false;
45 | }
46 |
47 | return wave != null;
48 | }
49 |
50 | public byte[] ToWave(AudioMetadata md, IArchData archData, string fileName = null, Dictionary context = null)
51 | {
52 | if (string.IsNullOrEmpty(ToolPath))
53 | {
54 | archData.WaveExtension = Extensions[0];
55 | return ((XwmaArchData)archData).ToXwma();
56 | }
57 |
58 | archData.WaveExtension = ".wav";
59 | var xwmaBytes = ((XwmaArchData) archData).ToXwma();
60 | var tempFile = Path.GetTempFileName();
61 | File.WriteAllBytes(tempFile, xwmaBytes);
62 | var tempOutFile = Path.GetTempFileName();
63 |
64 | byte[] outBytes = null;
65 | try
66 | {
67 | ProcessStartInfo info = new ProcessStartInfo(ToolPath, $"\"{tempFile}\" \"{tempOutFile}\"")
68 | {
69 | WindowStyle = ProcessWindowStyle.Hidden, CreateNoWindow = true
70 | };
71 | Process process = Process.Start(info);
72 | process?.WaitForExit();
73 |
74 | outBytes = File.ReadAllBytes(tempOutFile);
75 | File.Delete(tempFile);
76 | File.Delete(tempOutFile);
77 | }
78 | catch (Exception e)
79 | {
80 | Logger.Log(e);
81 | }
82 |
83 |
84 | return outBytes;
85 | }
86 |
87 | public bool ToArchData(AudioMetadata md, IArchData archData, in byte[] wave, string fileName, string waveExt, Dictionary context = null)
88 | {
89 | if (!File.Exists(ToolPath))
90 | {
91 | return false;
92 | }
93 |
94 | if (archData is not XwmaArchData xwma)
95 | {
96 | return false;
97 | }
98 |
99 | var tempFile = Path.GetTempFileName();
100 | File.WriteAllBytes(tempFile, wave);
101 | var tempOutFile = Path.GetTempFileName();
102 | MemoryStream oms = null;
103 | try
104 | {
105 | ProcessStartInfo info = new ProcessStartInfo(ToolPath, $"\"{tempFile}\" \"{tempOutFile}\"")
106 | {
107 | WindowStyle = ProcessWindowStyle.Hidden,
108 | CreateNoWindow = true
109 | };
110 | Process process = Process.Start(info);
111 | process?.WaitForExit();
112 |
113 | var fs = File.OpenRead(tempOutFile);
114 | oms = new MemoryStream((int)fs.Length);
115 | fs.CopyTo(oms);
116 | oms.Position = 0;
117 |
118 | File.Delete(tempFile);
119 | File.Delete(tempOutFile);
120 | }
121 | catch (Exception e)
122 | {
123 | Logger.Log(e);
124 | }
125 |
126 | if (oms == null)
127 | {
128 | return false;
129 | }
130 |
131 | xwma.ReadFromXwma(oms);
132 | oms.Dispose();
133 |
134 | return true;
135 | }
136 |
137 | public bool TryGetArchData(AudioMetadata md, PsbDictionary channel, out IArchData data, Dictionary context = null)
138 | {
139 | data = null;
140 | if (md.Spec == PsbSpec.win)
141 | {
142 | if (channel.Count == 1 && channel["archData"] is PsbDictionary archDic && archDic["data"] is PsbResource aData && archDic["dpds"] is PsbResource aDpds && archDic["fmt"] is PsbResource aFmt && archDic["wav"] is PsbString aWav)
143 | {
144 | data = new XwmaArchData()
145 | {
146 | Data = aData,
147 | Fmt = aFmt,
148 | Dpds = aDpds,
149 | Wav = aWav.Value
150 | };
151 |
152 | return true;
153 | }
154 |
155 | return false;
156 | }
157 |
158 | return false;
159 | }
160 | }
161 | }
--------------------------------------------------------------------------------
/FreeMote.Plugins/FreeMote.Plugins.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net48
4 | Library
5 | false
6 | default
7 |
8 |
9 |
10 | True
11 | True
12 | Resources.resx
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | 2.1.0-CI00002
22 |
23 |
24 | 2.5.1.16
25 |
26 |
27 | 1.0.0.6
28 |
29 |
30 | 1.3.8
31 |
32 |
33 |
34 |
35 |
36 | 2.2.1-CI00002
37 |
38 |
39 | 1.4.5
40 |
41 |
42 |
43 |
44 | ResXFileCodeGenerator
45 | Resources.Designer.cs
46 | Designer
47 |
48 |
49 |
50 |
51 | echo There is a bug that some nuget package dll may not have edit date so it won't be copied. If it happens, remove /D and try again.
52 | xcopy "$(TargetDir.TrimEnd('\'))" "$(SolutionDir)FreeMote.Tools.PsbDecompile\$(OutDir)" /E /Y
53 | xcopy "$(TargetDir.TrimEnd('\'))" "$(SolutionDir)FreeMote.Tools.PsBuild\$(OutDir)" /E /Y
54 | xcopy "$(TargetDir.TrimEnd('\'))" "$(SolutionDir)FreeMote.Tools.EmtConvert\$(OutDir)" /E /Y
55 | xcopy "$(TargetDir.TrimEnd('\'))" "$(SolutionDir)FreeMote.Tools.EmtMake\$(OutDir)" /E /Y
56 | xcopy "$(TargetDir.TrimEnd('\'))" "$(SolutionDir)FreeMote.Tools.Viewer\$(OutDir)" /E /Y
57 | xcopy "$(TargetDir.TrimEnd('\'))" "$(SolutionDir)FreeMote.Tests\$(OutDir)" /E /Y
58 |
59 |
--------------------------------------------------------------------------------
/FreeMote.Plugins/FreeMote.Plugins.csproj.DotSettings:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/FreeMote.Plugins/Images/AstcFormatter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.Composition;
4 | using System.Diagnostics;
5 | using System.Drawing;
6 | using System.IO;
7 | using System.Linq;
8 | using System.Reflection;
9 |
10 | namespace FreeMote.Plugins.Images
11 | {
12 | [Export(typeof(IPsbImageFormatter))]
13 | [ExportMetadata("Name", "FreeMote.Astc")]
14 | [ExportMetadata("Author", "Ulysses")]
15 | [ExportMetadata("Comment", "ASTC support.")]
16 | class AstcFormatter : IPsbImageFormatter
17 | {
18 | public const string AstcKeepHeader = "AstcKeepHeader";
19 |
20 | public AstcFormatter()
21 | {
22 | var toolPath = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location) ?? "", "Tools");
23 | foreach (var encoderTool in EncoderTools)
24 | {
25 | var tool = Path.Combine(toolPath, encoderTool);
26 | if (File.Exists(tool))
27 | {
28 | ToolPath = tool;
29 | return;
30 | }
31 | }
32 | }
33 |
34 | public string ToolPath { get; set; } = null;
35 |
36 | public List Extensions { get; } = new() {".astc"};
37 |
38 | public static List EncoderTools { get; } = new()
39 | {"astcenc.exe", "astcenc-sse2.exe", "astcenc-sse4.1.exe", "astcenc-avx2.exe"};
40 |
41 | public bool CanToBitmap(in byte[] data, Dictionary context = null)
42 | {
43 | if (data.Take(4).SequenceEqual(AstcHeader.Magic))
44 | {
45 | return true;
46 | }
47 |
48 | return false;
49 | }
50 |
51 | public bool CanToBytes(Bitmap bitmap, Dictionary context = null)
52 | {
53 | if (string.IsNullOrEmpty(ToolPath))
54 | {
55 | return false;
56 | }
57 |
58 | return true;
59 | }
60 |
61 | public Bitmap ToBitmap(in byte[] data, int width, int height, PsbSpec platform, Dictionary context = null)
62 | {
63 | if (AstcFile.IsAstcHeader(data))
64 | {
65 | var header = AstcFile.ParseAstcHeader(data);
66 | return RL.ConvertToImage(data.AsSpan(AstcHeader.Length).ToArray(), header.Width, header.Height, PsbPixelFormat.ASTC_8BPP);
67 | }
68 |
69 | return null;
70 | }
71 |
72 | public byte[] ToBytes(Bitmap bitmap, PsbSpec platform, Dictionary context = null)
73 | {
74 | var tempFile = Path.GetTempFileName();
75 | bitmap.Save(tempFile);
76 | var tempOutFile = Path.ChangeExtension(tempFile, ".astc");
77 |
78 | byte[] outBytes = null;
79 | try
80 | {
81 | ProcessStartInfo info = new ProcessStartInfo(ToolPath, $"-cl \"{tempFile}\" \"{tempOutFile}\" 4x4 -thorough")
82 | {
83 | UseShellExecute = false,
84 | WindowStyle = ProcessWindowStyle.Hidden,
85 | CreateNoWindow = true
86 | };
87 | Process process = Process.Start(info);
88 | process?.WaitForExit();
89 |
90 | if (!File.Exists(tempOutFile))
91 | {
92 | Logger.LogError("[ERROR] ASTC convert failed.");
93 | File.Delete(tempFile);
94 | return null;
95 | }
96 |
97 | outBytes = File.ReadAllBytes(tempOutFile);
98 | if (outBytes.Length == 0)
99 | {
100 | Logger.LogWarn("[WARN] ASTC encoder output length is 0");
101 | }
102 | File.Delete(tempFile);
103 | File.Delete(tempOutFile);
104 | }
105 | catch (Exception e)
106 | {
107 | Logger.Log(e);
108 | }
109 |
110 | if (context != null)
111 | {
112 | if (context.ContainsKey(AstcKeepHeader) && context[AstcKeepHeader] is true)
113 | {
114 | return outBytes;
115 | }
116 | }
117 |
118 | return AstcFile.CutHeader(outBytes);
119 | }
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/FreeMote.Plugins/Images/Bc7Formatter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.Composition;
4 | using System.Diagnostics;
5 | using System.Drawing;
6 | using System.Runtime.InteropServices;
7 | using BCnEncoder.Decoder;
8 | using BCnEncoder.Encoder;
9 | using BCnEncoder.Shared;
10 | using PixelFormat = System.Drawing.Imaging.PixelFormat;
11 |
12 | namespace FreeMote.Plugins.Images
13 | {
14 | [Export(typeof(IPsbImageFormatter))]
15 | [ExportMetadata("Name", "FreeMote.Bc7")]
16 | [ExportMetadata("Author", "Ulysses")]
17 | [ExportMetadata("Comment", "BC7 support via BCnEncoder.NET.")]
18 | class Bc7Formatter : IPsbImageFormatter
19 | {
20 | public List Extensions { get; } = new() {".bc7", ".dds"};
21 |
22 | public bool CanToBitmap(in byte[] data, Dictionary context = null)
23 | {
24 | return true;
25 | }
26 |
27 | public bool CanToBytes(Bitmap bitmap, Dictionary context = null)
28 | {
29 | if (bitmap != null && bitmap.PixelFormat == PixelFormat.Format32bppArgb)
30 | return true;
31 | return false;
32 | }
33 |
34 | public Bitmap ToBitmap(in byte[] data, int width, int height, PsbSpec platform, Dictionary context = null)
35 | {
36 | var decoder = new BcDecoder();
37 | //var bufferSize = decoder.GetBlockSize(CompressionFormat.Bc7) * width * height;
38 | //Debug.WriteLine($"size: expect: {bufferSize} ; actual: {buffer.Length}");
39 |
40 | var pixels = decoder.DecodeRaw(data, width, height, CompressionFormat.Bc7);
41 | var pixelBytes = MemoryMarshal.Cast(pixels);
42 | return RL.ConvertToImage(pixelBytes.ToArray(), width, height, PsbPixelFormat.BeRGBA8);
43 | }
44 |
45 | public byte[] ToBytes(Bitmap bitmap, PsbSpec platform, Dictionary context = null)
46 | {
47 | var encoder = new BcEncoder(CompressionFormat.Bc7)
48 | {OutputOptions = {GenerateMipMaps = false, Quality = CompressionQuality.Fast}};
49 | var pixelBytes = RL.GetPixelBytesFromImage(bitmap, PsbPixelFormat.BeRGBA8);
50 | var pixels = encoder.EncodeToRawBytes(pixelBytes, bitmap.Width, bitmap.Height, BCnEncoder.Encoder.PixelFormat.Rgba32);
51 | return pixels[0]; //other = mipmap
52 | }
53 | }
54 | }
--------------------------------------------------------------------------------
/FreeMote.Plugins/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | [assembly: AssemblyTitle("FreeMote.Plugins")]
6 | [assembly: AssemblyDescription("FreeMote official plugins.")]
7 | [assembly: AssemblyConfiguration("")]
8 | [assembly: AssemblyCompany("Ulysses")]
9 | [assembly: AssemblyProduct("FreeMote")]
10 | [assembly: AssemblyCopyright("Copyright © Ulysses 2018-2025")]
11 | [assembly: AssemblyTrademark("wdwxy12345@gmail.com")]
12 | [assembly: AssemblyCulture("")]
13 |
14 | [assembly: ComVisible(false)]
15 | [assembly: InternalsVisibleTo("FreeMote.Tests")]
16 |
17 | [assembly: Guid("f37472b9-6501-440e-8898-7774304f7fec")]
18 |
19 | // [assembly: AssemblyVersion("1.0.*")]
20 | [assembly: AssemblyVersion("4.0.0.0")]
21 | [assembly: AssemblyFileVersion("4.0.0.0")]
22 |
--------------------------------------------------------------------------------
/FreeMote.Plugins/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // 此代码由工具生成。
4 | // 运行时版本:4.0.30319.42000
5 | //
6 | // 对此文件的更改可能会导致不正确的行为,并且如果
7 | // 重新生成代码,这些更改将会丢失。
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace FreeMote.Plugins.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// 一个强类型的资源类,用于查找本地化的字符串等。
17 | ///
18 | // 此类是由 StronglyTypedResourceBuilder
19 | // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
20 | // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
21 | // (以 /str 作为命令选项),或重新生成 VS 项目。
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// 返回此类使用的缓存的 ResourceManager 实例。
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("FreeMote.Plugins.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// 重写当前线程的 CurrentUICulture 属性,对
51 | /// 使用此强类型资源类的所有资源查找执行重写。
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 |
63 | ///
64 | /// 查找类似 <?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
65 | ///<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 6.1.10">
66 | /// <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
67 | /// <rdf:Description rdf:about=""
68 | /// xmlns:xmp="http://ns.adobe.com/xap/1.0/"
69 | /// xmlns:dc="http://purl.org/dc/elements/1.1/"
70 | /// xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/"
71 | /// xmlns:xmpRights="http://ns.adobe.com/xap/1.0/rights/"
72 | /// xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/"
73 | /// [字符串的其余部分被截断]"; 的本地化字符串。
74 | ///
75 | internal static string Xmp {
76 | get {
77 | return ResourceManager.GetString("Xmp", resourceCulture);
78 | }
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/FreeMote.Plugins/Shells/Lz4Shell.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.Composition;
4 | using System.IO;
5 | using K4os.Compression.LZ4.Streams;
6 |
7 | namespace FreeMote.Plugins.Shells
8 | {
9 | [Export(typeof(IPsbShell))]
10 | [ExportMetadata("Name", "FreeMote.Lz4")]
11 | [ExportMetadata("Author", "Ulysses")]
12 | [ExportMetadata("Comment", "LZ4 support.")]
13 | class Lz4Shell : IPsbShell
14 | {
15 | ///
16 | /// LZ4 Frame Header Signature
17 | ///
18 | public const int MAGIC = 0x184D2204;
19 |
20 | public string Name => "LZ4";
21 | public bool IsInShell(Stream stream, Dictionary context = null)
22 | {
23 | var header = new byte[4];
24 | var pos = stream.Position;
25 | stream.Read(header, 0, 4);
26 | stream.Position = pos;
27 | if (BitConverter.ToInt32(header, 0) == MAGIC)
28 | {
29 | if (context != null)
30 | {
31 | context[Consts.Context_PsbShellType] = Name;
32 | }
33 | return true;
34 | }
35 |
36 | return false;
37 | }
38 |
39 | public MemoryStream ToPsb(Stream stream, Dictionary context = null)
40 | {
41 | var ms = new MemoryStream();
42 | using (var decode = LZ4Stream.Decode(stream, leaveOpen:true))
43 | {
44 | decode.CopyTo(ms);
45 | }
46 |
47 | ms.Position = 0;
48 | return ms;
49 | }
50 |
51 | public MemoryStream ToShell(Stream stream, Dictionary context = null)
52 | {
53 | var ms = new MemoryStream();
54 | using (var encode = LZ4Stream.Encode(ms, leaveOpen:true))
55 | {
56 | stream.CopyTo(encode);
57 | }
58 | ms.Position = 0;
59 | return ms;
60 | }
61 |
62 | public byte[] Signature => BitConverter.GetBytes(MAGIC);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/FreeMote.Plugins/Shells/MdfShell.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.Composition;
4 | using System.IO;
5 | using System.Linq;
6 | using FreeMote.Psb;
7 | using static FreeMote.Consts;
8 |
9 | namespace FreeMote.Plugins.Shells
10 | {
11 | [Export(typeof(IPsbShell))]
12 | [ExportMetadata("Name", "FreeMote.Mdf")]
13 | [ExportMetadata("Author", "Ulysses")]
14 | [ExportMetadata("Comment", "MDF (ZLIB) support.")]
15 | class MdfShell : IPsbShell
16 | {
17 | public string Name => "MDF";
18 |
19 | public byte[] Signature => new byte[] {(byte) 'm', (byte) 'd', (byte) 'f', 0};
20 |
21 | public bool IsInShell(Stream stream, Dictionary context = null)
22 | {
23 | var header = new byte[4];
24 | var pos = stream.Position;
25 | _ = stream.Read(header, 0, 4);
26 | stream.Position = pos;
27 | if (header.SequenceEqual(Signature))
28 | {
29 | if (context != null)
30 | {
31 | context[Context_PsbShellType] = Name;
32 | }
33 |
34 | return true;
35 | }
36 |
37 | return false;
38 | }
39 |
40 | public MemoryStream ToPsb(Stream stream, Dictionary context = null)
41 | {
42 | int size = 0;
43 | if (context != null)
44 | {
45 | if (context.TryGetValue(Context_MdfKey, out var mdfKey))
46 | {
47 | int? keyLength = context.TryGetValue(Context_MdfKeyLength, out var kl)
48 | ? Convert.ToInt32(kl)
49 | : (int?) null;
50 |
51 | stream = PsbExtension.EncodeMdf(stream, (string) mdfKey, keyLength, true);
52 | stream.Position = 0; //A new MemoryStream
53 | //File.WriteAllBytes("test.mdf", ((MemoryStream) stream).ToArray());
54 | //stream.Position = 0;
55 | }
56 |
57 | var pos = stream.Position;
58 | stream.Seek(4, SeekOrigin.Current);
59 | var bytes = new byte[4];
60 | stream.Read(bytes, 0, 4);
61 | if (FastMode)
62 | {
63 | size = BitConverter.ToInt32(bytes, 0);
64 | }
65 | stream.Seek(1, SeekOrigin.Current);
66 | context[Context_PsbZlibFastCompress] = stream.ReadByte() == (byte) 0x9C;
67 | stream.Position = pos;
68 | }
69 |
70 | return MPack.MdfDecompressToStream(stream, size) as MemoryStream;
71 | }
72 |
73 |
74 | public MemoryStream ToShell(Stream stream, Dictionary context = null)
75 | {
76 | bool fast = true; //mdf use fast mode by default
77 | if (context != null && context.TryGetValue(Context_PsbZlibFastCompress, out var fastCompress))
78 | {
79 | fast = (bool) fastCompress;
80 | }
81 |
82 | var ms = MPack.CompressPsbToMdfStream(stream, fast); //this will prepend MDF header
83 |
84 | if (context != null && context.TryGetValue(Context_MdfKey, out var mdfKey))
85 | {
86 | int? keyLength;
87 | if (context.TryGetValue(Context_MdfKeyLength, out var kl))
88 | {
89 | keyLength = Convert.ToInt32(kl);
90 | }
91 | else
92 | {
93 | keyLength = (int?) null;
94 | }
95 |
96 | var mms = PsbExtension.EncodeMdf(ms, (string)mdfKey, keyLength, true);
97 | ms?.Dispose(); //ms disposed
98 | ms = mms;
99 | }
100 |
101 | return ms;
102 | }
103 | }
104 | }
--------------------------------------------------------------------------------
/FreeMote.Plugins/Shells/MxbShell.cs:
--------------------------------------------------------------------------------
1 | using FreeMote.Psb;
2 | using System;
3 | using System.Buffers.Binary;
4 | using System.Collections.Generic;
5 | using System.ComponentModel.Composition;
6 | using System.IO;
7 | using System.Linq;
8 | using System.Runtime.InteropServices;
9 | using XMemCompress;
10 | using static FreeMote.Consts;
11 |
12 | namespace FreeMote.Plugins.Shells
13 | {
14 | [Export(typeof(IPsbShell))]
15 | [ExportMetadata("Name", "FreeMote.Mxb")]
16 | [ExportMetadata("Author", "Ulysses")]
17 | [ExportMetadata("Comment", "MXB (XMemCompress) support.")]
18 | internal class MxbShell : IPsbShell
19 | {
20 | public string Name => "MXB";
21 | public byte[] Signature { get; } = { (byte) 'm', (byte) 'x', (byte) 'b', 0 };
22 |
23 | public bool IsInShell(Stream stream, Dictionary context = null)
24 | {
25 | var header = new byte[4];
26 | var pos = stream.Position;
27 | _ = stream.Read(header, 0, 4);
28 | stream.Position = pos;
29 | if (header.SequenceEqual(Signature))
30 | {
31 | if (context != null)
32 | {
33 | context[Context_PsbShellType] = Name;
34 | }
35 |
36 | return true;
37 | }
38 |
39 | return false;
40 | }
41 |
42 | public MemoryStream ToPsb(Stream stream, Dictionary context = null)
43 | {
44 | if (context != null)
45 | {
46 | if (context.TryGetValue(Context_MdfKey, out var mdfKey))
47 | {
48 | int? keyLength = context.TryGetValue(Context_MdfKeyLength, out var kl)
49 | ? Convert.ToInt32(kl)
50 | : (int?) null;
51 |
52 | stream = PsbExtension.EncodeMdf(stream, (string) mdfKey, keyLength, true);
53 | stream.Position = 0; //A new MemoryStream
54 | }
55 | }
56 |
57 | stream.Seek(4, SeekOrigin.Current);
58 | var bytes = new byte[4];
59 | _ = stream.Read(bytes, 0, 4);
60 | var unzippedSize = BitConverter.ToInt32(bytes, 0);
61 |
62 | var ms = new MemoryStream(unzippedSize);
63 | XCompressFile.DecompressStream(stream, ms);
64 | ms.Seek(0, SeekOrigin.Begin);
65 | return ms;
66 | }
67 |
68 | public MemoryStream ToShell(Stream stream, Dictionary context = null)
69 | {
70 | var unzipLength = (int)stream.Length;
71 | var ms = XCompressFile.CompressStream(stream);
72 | if (context != null && context.TryGetValue(Context_MdfKey, out var mdfKey))
73 | {
74 | int? keyLength;
75 | if (context.TryGetValue(Context_MdfKeyLength, out var kl))
76 | {
77 | keyLength = Convert.ToInt32(kl);
78 | }
79 | else
80 | {
81 | keyLength = (int?) null;
82 | }
83 |
84 | var mms = PsbExtension.EncodeMdf(ms, (string) mdfKey, keyLength, false);
85 | ms?.Dispose(); //ms disposed
86 | ms = mms;
87 | }
88 |
89 | ms.Seek(0, SeekOrigin.Begin);
90 | var shellMs = new MemoryStream((int) (ms.Length + 8));
91 | shellMs.Write(Signature, 0, 4);
92 | shellMs.Write(BitConverter.GetBytes(unzipLength), 0, 4);
93 | ms.CopyTo(shellMs);
94 | ms.Dispose();
95 | shellMs.Seek(0, SeekOrigin.Begin);
96 | return shellMs;
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/FreeMote.Plugins/Shells/MzsShell.cs:
--------------------------------------------------------------------------------
1 | //Actually I don't even have a sample of mzs. This is inspired by https://gitlab.com/modmyclassic/sega-mega-drive-mini/marchive-batch-tool/-/blob/master/MArchiveBatchTool/MArchive/ZStandardCodec.cs
2 |
3 | using System;
4 | using System.Collections.Generic;
5 | using System.ComponentModel.Composition;
6 | using System.IO;
7 | using System.Linq;
8 | using FreeMote.Psb;
9 | using ZstdNet;
10 | using static FreeMote.Consts;
11 |
12 | namespace FreeMote.Plugins.Shells
13 | {
14 | [Export(typeof(IPsbShell))]
15 | [ExportMetadata("Name", "FreeMote.Mzs")]
16 | [ExportMetadata("Author", "Ulysses")]
17 | [ExportMetadata("Comment", "MZS (ZStandard) support.")]
18 | class MzsShell : IPsbShell
19 | {
20 | public string Name => "MZS";
21 |
22 | public byte[] Signature => new byte[] { (byte)'m', (byte)'z', (byte)'s', 0 };
23 |
24 | public bool IsInShell(Stream stream, Dictionary context = null)
25 | {
26 | var header = new byte[4];
27 | var pos = stream.Position;
28 | _ = stream.Read(header, 0, 4);
29 | stream.Position = pos;
30 | if (header.SequenceEqual(Signature))
31 | {
32 | if (context != null)
33 | {
34 | context[Context_PsbShellType] = Name;
35 | }
36 |
37 | return true;
38 | }
39 |
40 | return false;
41 | }
42 |
43 | public MemoryStream ToPsb(Stream stream, Dictionary context = null)
44 | {
45 | if (context != null)
46 | {
47 | if (context.TryGetValue(Context_MdfKey, out var mdfKey))
48 | {
49 | int? keyLength = context.TryGetValue(Context_MdfKeyLength, out var kl)
50 | ? Convert.ToInt32(kl)
51 | : (int?)null;
52 |
53 | stream = PsbExtension.EncodeMdf(stream, (string)mdfKey, keyLength, true);
54 | stream.Position = 0; //A new MemoryStream
55 | }
56 | }
57 |
58 | stream.Seek(4, SeekOrigin.Current);
59 | var bytes = new byte[4];
60 | stream.Read(bytes, 0, 4);
61 | var unzippedSize = BitConverter.ToInt32(bytes, 0);
62 |
63 | byte[] input = new byte[stream.Length - 8];
64 | stream.Read(input, 0, input.Length);
65 |
66 | using var decompress = new Decompressor();
67 | var output = decompress.Unwrap(input, unzippedSize);
68 |
69 | return new MemoryStream(output);
70 | }
71 |
72 |
73 | public MemoryStream ToShell(Stream stream, Dictionary context = null)
74 | {
75 | var unzipLength = (int) stream.Length;
76 | int? compressLevel = null;
77 | if (context != null && context.TryGetValue(Context_PsbZStdCompressLevel, out var cl))
78 | {
79 | compressLevel = (int) cl;
80 | if (compressLevel > CompressionOptions.MaxCompressionLevel)
81 | {
82 | compressLevel = CompressionOptions.MaxCompressionLevel;
83 | }
84 | else if (compressLevel < CompressionOptions.MinCompressionLevel)
85 | {
86 | compressLevel = CompressionOptions.MinCompressionLevel;
87 | }
88 | }
89 |
90 | byte[] input;
91 | if (stream is MemoryStream inputMs)
92 | {
93 | input = inputMs.ToArray();
94 | }
95 | else
96 | {
97 | input = new byte[stream.Length];
98 | stream.Read(input, 0, input.Length);
99 | }
100 |
101 | using var compress = compressLevel == null ? new Compressor() : new Compressor(new CompressionOptions(compressLevel.Value));
102 | var output = compress.Wrap(input);
103 | var ms = new MemoryStream(output);
104 |
105 | if (context != null && context.TryGetValue(Context_MdfKey, out var mdfKey))
106 | {
107 | int? keyLength;
108 | if (context.TryGetValue(Context_MdfKeyLength, out var kl))
109 | {
110 | keyLength = Convert.ToInt32(kl);
111 | }
112 | else
113 | {
114 | keyLength = (int?)null;
115 | }
116 |
117 | var mms = PsbExtension.EncodeMdf(ms, (string)mdfKey, keyLength, false);
118 | ms?.Dispose(); //ms disposed
119 | ms = mms;
120 | }
121 |
122 | var shellMs = new MemoryStream((int) (ms.Length + 8));
123 | shellMs.Write(Signature, 0, 4);
124 | shellMs.Write(BitConverter.GetBytes(unzipLength), 0, 4);
125 | ms.CopyTo(shellMs);
126 | ms.Dispose();
127 | shellMs.Seek(0, SeekOrigin.Begin);
128 | return shellMs;
129 | }
130 |
131 | }
132 | }
--------------------------------------------------------------------------------
/FreeMote.Plugins/Shells/PspShell.cs:
--------------------------------------------------------------------------------
1 | // LZSS decompression by morkt (https://github.com/morkt/GARbro) LICENSE: MIT
2 |
3 | using System;
4 | using System.Collections.Generic;
5 | using System.ComponentModel.Composition;
6 | using System.IO;
7 | using System.Linq;
8 | using System.Text;
9 |
10 | namespace FreeMote.Plugins.Shells
11 | {
12 | [Export(typeof(IPsbShell))]
13 | [ExportMetadata("Name", "FreeMote.Psp")]
14 | [ExportMetadata("Author", "Ulysses & morkt")]
15 | [ExportMetadata("Comment", "PSP (LZSS) unpack support.")]
16 | class PspShell : IPsbShell
17 | {
18 | public string Name => "PSP";
19 |
20 | //40 C0 A6 01 FF | 50 53 42 (P S B)
21 | public static byte[] MAGIC => new[] {(byte) 'P', (byte) 'S', (byte) 'B'};
22 |
23 | public bool IsInShell(Stream stream, Dictionary context = null)
24 | {
25 | var header = new byte[3];
26 | var pos = stream.Position;
27 | stream.Seek(5, SeekOrigin.Current);
28 | _ = stream.Read(header, 0, 3);
29 | stream.Position = pos;
30 | if (header.SequenceEqual(MAGIC))
31 | {
32 | return true;
33 | }
34 |
35 | return false;
36 | }
37 |
38 | public MemoryStream ToPsb(Stream stream, Dictionary context = null)
39 | {
40 | MemoryStream ms = null;
41 | using (BinaryReader br = new BinaryReader(stream))
42 | {
43 | int unpackedSize = br.ReadInt32();
44 | ms = new MemoryStream(unpackedSize);
45 | using (BinaryWriter bw = new BinaryWriter(ms, Encoding.UTF8, true))
46 | {
47 | //var output = new byte[unpackedSize];
48 | //int dst = 0;
49 | var frame = new byte[0x1000];
50 | int framePos = 1;
51 | while (ms.Length < unpackedSize)
52 | {
53 | int ctl = br.ReadByte();
54 | for (int bit = 1; ms.Length < unpackedSize && bit != 0x100; bit <<= 1)
55 | {
56 | if (0 != (ctl & bit))
57 | {
58 | byte b = br.ReadByte();
59 | bw.Write(frame[framePos++ & 0xFFF] = b);
60 | }
61 | else
62 | {
63 | int hi = br.ReadByte();
64 | int lo = br.ReadByte();
65 | int offset = hi << 4 | lo >> 4;
66 | for (int count = 2 + (lo & 0xF); count != 0; --count)
67 | {
68 | byte v = frame[offset++ & 0xFFF];
69 | bw.Write(frame[framePos++ & 0xFFF] = v);
70 | }
71 | }
72 | }
73 | }
74 | }
75 | }
76 |
77 | ms.Position = 0;
78 | return ms;
79 | }
80 |
81 | public MemoryStream ToShell(Stream stream, Dictionary context = null)
82 | {
83 | //TODO:
84 | Logger.Log("PSP compression is not supported.");
85 | return null;
86 | //throw new NotImplementedException("PSP compression is not supported.");
87 | }
88 |
89 | public byte[] Signature => null;
90 | }
91 | }
--------------------------------------------------------------------------------
/FreeMote.Plugins/Shells/PszShell.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.ComponentModel.Composition;
3 | using System.IO;
4 | using System.Text;
5 |
6 | namespace FreeMote.Plugins.Shells
7 | {
8 | [Export(typeof(IPsbShell))]
9 | [ExportMetadata("Name", "FreeMote.Psz")]
10 | [ExportMetadata("Author", "Ulysses")]
11 | [ExportMetadata("Comment", "PSZ (ZLIB) support.")]
12 | class PszShell : IPsbShell
13 | {
14 | public string Name => "PSZ";
15 |
16 | public bool IsInShell(Stream stream, Dictionary context = null)
17 | {
18 | var header = new byte[4];
19 | var pos = stream.Position;
20 | _ = stream.Read(header, 0, 4);
21 | stream.Position = pos;
22 | if (header[0] == Name[0] && header[1] == Name[1] && header[2] == Name[2] && header[3] == 0)
23 | {
24 | if (context != null)
25 | {
26 | context[Consts.Context_PsbShellType] = Name;
27 | }
28 | return true;
29 | }
30 |
31 | return false;
32 | }
33 |
34 | public MemoryStream ToPsb(Stream stream, Dictionary context = null)
35 | {
36 | using (var br = new BinaryReader(stream))
37 | {
38 | br.ReadBytes(4); //PSZ
39 | var zippedLen = br.ReadInt32();
40 | var oriLen = br.ReadInt32();
41 | br.ReadInt32(); //0
42 | br.ReadByte(); //0x78
43 | var config = br.ReadByte(); //0x9C: fast; 0xDA: compact
44 | if (context != null)
45 | {
46 | context[Consts.Context_PsbZlibFastCompress] = config == (byte)0x9C;
47 | }
48 |
49 | return ZlibCompress.DecompressToStream(stream) as MemoryStream;
50 | }
51 | }
52 |
53 | public MemoryStream ToShell(Stream stream, Dictionary context = null)
54 | {
55 | bool fast = false;
56 | if (context != null && context.TryGetValue(Consts.Context_PsbZlibFastCompress, out var fastCompress))
57 | {
58 | fast = (bool)fastCompress;
59 | }
60 |
61 | var oriLen = (int)stream.Length;
62 | var pos = stream.Position;
63 | var compressedStream = ZlibCompress.CompressToStream(stream, fast);
64 | MemoryStream ms = new MemoryStream(16 + (int)compressedStream.Length);
65 | using (var bw = new BinaryWriter(ms, Encoding.UTF8, true))
66 | {
67 | stream.Position = pos;
68 | var adler32 = new Adler32();
69 | adler32.Update(stream);
70 | var checksum = (uint)adler32.Checksum;
71 |
72 | bw.Write(Signature);
73 | bw.Write((int)compressedStream.Length + 4);
74 | bw.Write(oriLen);
75 | bw.Write((int)0);
76 | compressedStream.CopyTo(ms);
77 | bw.WriteBE(checksum);
78 | compressedStream.Dispose();
79 | }
80 |
81 | ms.Position = 0;
82 | return ms;
83 | }
84 |
85 | public byte[] Signature { get; } = { (byte)'P', (byte)'S', (byte)'Z', 0 };
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/FreeMote.PsBuild/Converters/CommonWinConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using FreeMote.Psb;
5 |
6 | namespace FreeMote.PsBuild.Converters
7 | {
8 | ///
9 | /// Common/Ems-Win Converter
10 | ///
11 | class CommonWinConverter : ISpecConverter
12 | {
13 | ///
14 | /// Won't be used in this conversion
15 | ///
16 | public SpecConvertOption ConvertOption { get; set; }
17 | ///
18 | /// Won't be used in this conversion
19 | ///
20 | public PsbPixelFormat TargetPixelFormat { get; set; }
21 | public bool UseRL { get; set; } = false;
22 | ///
23 | /// If true, it is an EmsWinConverter
24 | ///
25 | public bool EmsAsCommon { get; set; } = false;
26 | public IList FromSpec { get; } = new List { PsbSpec.win, PsbSpec.common, PsbSpec.ems };
27 | public IList ToSpec { get; } = new List { PsbSpec.common, PsbSpec.win, PsbSpec.ems };
28 | public void Convert(PSB psb)
29 | {
30 | if (!FromSpec.Contains(psb.Platform))
31 | {
32 | throw new FormatException("Can not convert Spec for this PSB");
33 | }
34 |
35 | var asSpec = EmsAsCommon ? PsbSpec.ems : PsbSpec.common;
36 | var toSpec = psb.Platform == PsbSpec.win ? asSpec : PsbSpec.win;
37 | var toPixelFormat = toSpec == asSpec ? PsbPixelFormat.BeRGBA8 : PsbPixelFormat.LeRGBA8;
38 | var resList = psb.CollectResources(false);
39 | foreach (var resMd in resList)
40 | {
41 | var resourceData = resMd.Resource.Data;
42 | if (resourceData == null)
43 | {
44 | continue;
45 | }
46 | if (resMd.Compress == PsbCompressType.RL)
47 | {
48 | resourceData = RL.Decompress(resourceData);
49 | }
50 | else if (resMd.PixelFormat == PsbPixelFormat.DXT5)
51 | {
52 | resourceData = RL.GetPixelBytesFromImage(
53 | DxtUtil.Dxt5Decode(resourceData, resMd.Width, resMd.Height), toPixelFormat);
54 | resMd.TypeString.Value = toPixelFormat.ToStringForPsb();
55 | }
56 | else if (resMd.PixelFormat == PsbPixelFormat.DXT1)
57 | {
58 | resourceData = RL.GetPixelBytesFromImage(
59 | DxtUtil.Dxt1Decode(resourceData, resMd.Width, resMd.Height), toPixelFormat);
60 | resMd.TypeString.Value = toPixelFormat.ToStringForPsb();
61 | }
62 | else
63 | {
64 | RL.Switch_0_2(ref resourceData);
65 | if (UseRL)
66 | {
67 | resourceData = RL.Compress(resourceData);
68 | }
69 | }
70 | resMd.Resource.Data = resourceData;
71 | }
72 | psb.Platform = toSpec;
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/FreeMote.PsBuild/Converters/ISpecConverter.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using FreeMote.Psb;
3 |
4 | namespace FreeMote.PsBuild.Converters
5 | {
6 | ///
7 | /// Spec convert strategy
8 | ///
9 | public enum SpecConvertOption
10 | {
11 | ///
12 | /// Best success rate
13 | ///
14 | Default,
15 | ///
16 | /// Remove unnecessary info
17 | ///
18 | Minimum,
19 | ///
20 | /// Keep unnecessary info
21 | ///
22 | Maximum,
23 | }
24 |
25 | ///
26 | /// Convert among
27 | ///
28 | public interface ISpecConverter
29 | {
30 | ///
31 | /// Convert a PSB to target
32 | ///
33 | ///
34 | void Convert(PSB psb);
35 |
36 | SpecConvertOption ConvertOption { get; set; }
37 |
38 | ///
39 | /// Select if supported
40 | ///
41 | PsbPixelFormat TargetPixelFormat { get; set; }
42 |
43 | ///
44 | /// Use RL Compress
45 | ///
46 | bool UseRL { get; set; }
47 |
48 | IList FromSpec { get; }
49 | IList ToSpec { get; }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/FreeMote.PsBuild/FreeMote.PsBuild.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net48
4 | Library
5 | false
6 | 5118e00b-0de7-4935-9013-b6ebe83c8397
7 |
8 |
9 | default
10 |
11 |
12 | default
13 | bin\Release\FreeMote.PsBuild.xml
14 |
15 |
16 |
17 | True
18 | True
19 | Resources.resx
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | 13.0.3
29 |
30 |
31 | 4.5.0
32 |
33 |
34 |
35 |
36 | PublicResXFileCodeGenerator
37 | Resources.Designer.cs
38 |
39 |
40 |
--------------------------------------------------------------------------------
/FreeMote.PsBuild/MmoCompiler.cs:
--------------------------------------------------------------------------------
1 | namespace FreeMote.PsBuild
2 | {
3 | ///
4 | /// TODO: Managed Open-Source MMO->PSB Compiler
5 | /// When implemented, you can use furi Editor for editing and use this to get PSB
6 | ///
7 | class MmoCompiler
8 | {
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/FreeMote.PsBuild/MmoTypes.cs:
--------------------------------------------------------------------------------
1 | //This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
2 |
3 | using System;
4 | using System.Diagnostics;
5 | using FreeMote.Psb;
6 |
7 | // ReSharper disable InconsistentNaming
8 |
9 | namespace FreeMote.PsBuild
10 | {
11 | internal static class MmoExtensions
12 | {
13 | ///
14 | /// Convert number to shape
15 | ///
16 | ///
17 | ///
18 | public static string ToShapeString(this PsbNumber shape)
19 | {
20 | if (Enum.IsDefined(typeof(MmoShape), shape.IntValue))
21 | {
22 | return ((MmoShape) shape.IntValue).ToString();
23 | }
24 |
25 | Debug.WriteLine($"{shape.IntValue} is not a valid {nameof(MmoShape)}");
26 | return MmoShape.point.ToString();
27 | }
28 | }
29 |
30 | /*particle in frameList/content
31 | * "prt": {
32 | "amax": 5,
33 | "amin": 4,
34 | "fmax": 10.0,
35 | "fmin": 1.0,
36 | "mask": 63,
37 | "range": 8,
38 | "trigger": 1,
39 | "vmax": 0.05,
40 | "vmin": 0.0333333351,
41 | "zmax": 7,
42 | "zmin": 6
43 | },
44 | */
45 |
46 | internal enum MmoShape
47 | {
48 | point = 0,
49 | circle = 1,
50 | rect = 2,
51 | quad = 3,
52 | }
53 |
54 | [Flags]
55 | internal enum MmoFrameMaskEx
56 | {
57 | None = 0,
58 | CoordXY = 0b1,
59 | CoordZ = 0b10,
60 | SrcSrc = 0b100,
61 | SrcMotion = 0b1000,
62 | SrcShape = 0b10000,
63 | SrcParticle = 0b100000,
64 | }
65 |
66 | public enum MmoMarkerColor
67 | {
68 | ///
69 | /// なし
70 | ///
71 | None = 0,
72 |
73 | ///
74 | /// 赤
75 | ///
76 | Red = 1,
77 |
78 | ///
79 | /// 绿
80 | ///
81 | Green = 2,
82 |
83 | ///
84 | /// 青
85 | ///
86 | Blue = 3,
87 |
88 | ///
89 | /// 橙
90 | ///
91 | Orange = 4,
92 |
93 | ///
94 | /// 紫
95 | ///
96 | Purple = 5,
97 |
98 | ///
99 | /// 桃
100 | ///
101 | Pink = 6,
102 |
103 | ///
104 | /// 灰
105 | ///
106 | Gray = 7,
107 | }
108 |
109 | public enum MmoItemClass
110 | {
111 | ObjLayerItem = 0, //CharaItem, MotionItem
112 |
113 | //"objClipping": 0,
114 | //"objMaskThresholdOpacity": 64,
115 | //"objTriPriority": 2,
116 | //TextLayer is always hold by a ObjLayer with "#text00000" label and "src/#font00000/#text00000" frameList/content/src
117 | ShapeLayerItem = 1,
118 |
119 | //"shape": "point" (psb: 0) | "circle" (psb: 1) | "rect" (psb: 2) | "quad" (psb: 3)
120 | LayoutLayerItem = 2,
121 | MotionLayerItem = 3,
122 |
123 | /*
124 | "motionClipping": 0,
125 | "motionIndependentLayerInherit": 0,
126 | "motionMaskThresholdOpacity": 64,
127 | */
128 | ParticleLayerItem = 4, //global.LAYER_TYPE_PARTICLE = (int)4
129 | //"particle": "point" (psb: 0) | "ellipse" (psb: 1) | "quad" (psb: 2)
130 | /*
131 | "particleAccelRatio": 1.0,
132 | "particleApplyZoomToVelocity": 0,
133 | "particleDeleteOutsideScreen": 0,
134 | "particleFlyDirection": 0,
135 | "particleInheritAngle": 0,
136 | "particleInheritOpacity": 1,
137 | "particleInheritVelocity": 0,
138 | "particleMaxNum": 20,
139 | "particleMotionList": [],
140 | "particleTriVolume": 0,
141 | */
142 |
143 | CameraLayerItem = 5,
144 | ModelLayerItem = 6,
145 | ClipLayerItem = 7, //nothing special
146 | TextLayerItem = 8,
147 | AnchorLayerItem = 9,
148 | FeedbackLayerItem = 10,
149 | /*
150 | "fontParams": {
151 | "antiAlias": 1,
152 | "bold": 0,
153 | "brushColor1": -16777216,
154 | "brushColor2": -16777216,
155 | "depth": 1,
156 | "name": "MS ゴシック",
157 | "penColor": -16777216,
158 | "penSize": 0,
159 | "rev": 1,
160 | "size": 16
161 | },
162 | "textParams": {
163 | "alignment": 0,
164 | "colSpace": 0,
165 | "defaultVertexColor": -1,
166 | "originAlignment": 1,
167 | "rasterlize": 2,
168 | "rowSpace": 0,
169 | "text": "Built by FreeMote"
170 | },
171 | */
172 | MeshLayerItem = 11, //nothing special, take care of meshXXX
173 | StencilLayerItem = 12,
174 | /*
175 | "stencilCompositeMaskLayerList": [],
176 | "stencilMaskThresholdOpacity": 64,
177 | "stencilType": 1,
178 | */
179 | }
180 |
181 | ///
182 | /// MMO Meta Format for PSD
183 | ///
184 | public class MmoPsdMetadata
185 | {
186 | public string SourceLabel { get; set; }
187 | public string PsdComment { get; set; }
188 | public string PsdFrameLabel { get; set; }
189 | public string PsdGroup { get; set; }
190 |
191 | ///
192 | /// Category
193 | /// WARNING: Category can not be set as `Expression`
194 | ///
195 | public string Category { get; set; }
196 |
197 | public string Label { get; set; }
198 | }
199 |
200 | public partial class MmoBuilder
201 | {
202 | }
203 | }
--------------------------------------------------------------------------------
/FreeMote.PsBuild/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | [assembly: AssemblyTitle("FreeMote.PsBuild")]
6 | [assembly: AssemblyDescription("Managed PSB Compiler & Decompiler")]
7 | [assembly: AssemblyConfiguration("")]
8 | [assembly: AssemblyCompany("Ulysses")]
9 | [assembly: AssemblyProduct("FreeMote")]
10 | [assembly: AssemblyCopyright("Copyright © Ulysses 2017-2025")]
11 | [assembly: AssemblyTrademark("wdwxy12345@gmail.com")]
12 | [assembly: AssemblyCulture("")]
13 |
14 | [assembly: ComVisible(false)]
15 |
16 | [assembly: Guid("717a63de-e599-4134-92ab-40a17c326da3")]
17 | [assembly: InternalsVisibleTo("FreeMote.Tests")]
18 | [assembly: InternalsVisibleTo("FreeMote.Editor")]
19 |
20 | // [assembly: AssemblyVersion("1.0.*")]
21 | [assembly: AssemblyVersion("4.0.1.0")]
22 | [assembly: AssemblyFileVersion("4.0.1.0")]
--------------------------------------------------------------------------------
/FreeMote.PsBuild/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // 此代码由工具生成。
4 | // 运行时版本:4.0.30319.42000
5 | //
6 | // 对此文件的更改可能会导致不正确的行为,并且如果
7 | // 重新生成代码,这些更改将会丢失。
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace FreeMote.PsBuild.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// 一个强类型的资源类,用于查找本地化的字符串等。
17 | ///
18 | // 此类是由 StronglyTypedResourceBuilder
19 | // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
20 | // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
21 | // (以 /str 作为命令选项),或重新生成 VS 项目。
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | public class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// 返回此类使用的缓存的 ResourceManager 实例。
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | public static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("FreeMote.PsBuild.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// 重写当前线程的 CurrentUICulture 属性,对
51 | /// 使用此强类型资源类的所有资源查找执行重写。
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | public static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 |
63 | ///
64 | /// 查找类似 {
65 | /// "bustControlParameterDefinitionList": [
66 | /// {
67 | /// "comment": "",
68 | /// "friction": 0.125,
69 | /// "gravity": 0.3,
70 | /// "label": "ふらり",
71 | /// "scale_x": 1.0,
72 | /// "scale_y": 1.0,
73 | /// "spring": 0.015625
74 | /// },
75 | /// {
76 | /// "comment": "",
77 | /// "friction": 0.125,
78 | /// "gravity": 0.3,
79 | /// "label": "標準",
80 | /// "scale_x": 1.0,
81 | /// "scale_y": 2.0,
82 | /// "spring": 0.03125
83 | /// },
84 | /// {
85 | /// "comment" [字符串的其余部分被截断]"; 的本地化字符串。
86 | ///
87 | public static string Mmo {
88 | get {
89 | return ResourceManager.GetString("Mmo", resourceCulture);
90 | }
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/FreeMote.PsBuild/PsBuildHelper.cs:
--------------------------------------------------------------------------------
1 | using FreeMote.Psb;
2 |
3 | namespace FreeMote.PsBuild
4 | {
5 | public static class PsBuildHelper
6 | {
7 | public static PsbNumber ToPsbNumber(this MmoMarkerColor color) => ((int)color).ToPsbNumber();
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/FreeMote.PsBuild/PsbResourceJson.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Text;
5 | using FreeMote.Psb;
6 | using Newtonsoft.Json;
7 | using Newtonsoft.Json.Converters;
8 |
9 | namespace FreeMote.PsBuild
10 | {
11 | ///
12 | /// Advanced resource json (.resx.json)
13 | ///
14 | /// JsonConvert.SerializeObject(MyObject, new Newtonsoft.Json.Converters.StringEnumConverter());
15 | [Serializable]
16 | public class PsbResourceJson
17 | {
18 | ///
19 | /// PSB version
20 | ///
21 | public ushort? PsbVersion { get; set; } = 3;
22 |
23 | ///
24 | /// PSB Type
25 | ///
26 | [JsonConverter(typeof(StringEnumConverter))]
27 | public PsbType? PsbType { get; set; } = null;
28 |
29 | ///
30 | /// PSB Spec (only for EMT)
31 | ///
32 | [JsonConverter(typeof(StringEnumConverter))]
33 | public PsbSpec? Platform { get; set; } = null;
34 |
35 | ///
36 | /// Key
37 | ///
38 | public uint? CryptKey { get; set; } = null;
39 |
40 | ///
41 | /// Whether to use external textures
42 | ///
43 | public bool ExternalTextures { get; set; } = false;
44 |
45 | ///
46 | /// Setting Context (mainly for plugins)
47 | ///
48 | [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
49 | public Dictionary Context { get; set; } = new();
50 |
51 | ///
52 | /// Resources
53 | ///
54 | public Dictionary Resources { get; set; }
55 |
56 | ///
57 | /// ExtraResources
58 | ///
59 | [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
60 | public Dictionary ExtraResources { get; set; }
61 |
62 | ///
63 | /// ExtraResources
64 | ///
65 | [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
66 | public Dictionary ExtraFlattenArrays { get; set; }
67 |
68 | [JsonIgnore]
69 | public bool HasExtraResources => ExtraFlattenArrays is {Count: > 0} ||
70 | ExtraResources is {Count: > 0};
71 |
72 | [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
73 | public int? Encoding { get; set; }
74 |
75 | public PsbResourceJson()
76 | {
77 | }
78 |
79 | public PsbResourceJson(PSB psb, Dictionary context = null)
80 | {
81 | PsbVersion = psb.Header.Version;
82 | PsbType = psb.Type;
83 | Platform = psb.Platform;
84 | //ExternalTextures = psb.Resources.Count <= 0;
85 |
86 | if (context != null)
87 | {
88 | CryptKey = context.ContainsKey(Consts.Context_CryptKey)
89 | ? (uint?) context[Consts.Context_CryptKey]
90 | : null;
91 | Context = context;
92 | }
93 | }
94 |
95 | public void AppliedToPsb(PSB psb)
96 | {
97 | if (PsbType != null)
98 | {
99 | psb.Type = PsbType.Value;
100 | }
101 |
102 | if (PsbVersion != null)
103 | {
104 | psb.Header.Version = PsbVersion.Value;
105 | }
106 |
107 | if (Platform != null && psb.Platform != PsbSpec.none)
108 | {
109 | psb.Platform = Platform.Value;
110 | }
111 | }
112 |
113 | public string SerializeToJson()
114 | {
115 | return JsonConvert.SerializeObject(this, Formatting.Indented);
116 | }
117 |
118 | ///
119 | /// Load resx.json using psb.json path
120 | ///
121 | /// psb.json path
122 | ///
123 | public static PsbResourceJson LoadByPsbJsonPath(string jsonPath)
124 | {
125 | var inputResPath = Path.ChangeExtension(jsonPath, ".resx.json");
126 | if (inputResPath != null && File.Exists(inputResPath))
127 | {
128 | return JsonConvert.DeserializeObject(File.ReadAllText(inputResPath));
129 | }
130 |
131 | throw new FileNotFoundException($"Can not find {inputResPath}");
132 | }
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/FreeMote.PsBuild/PsbSpecConverter.cs:
--------------------------------------------------------------------------------
1 | using FreeMote.Psb;
2 | using FreeMote.PsBuild.Converters;
3 |
4 | namespace FreeMote.PsBuild
5 | {
6 | public static class PsbSpecConverter
7 | {
8 | ///
9 | /// Enable resolution support (may scaling images, quality is not guaranteed)
10 | ///
11 | public static bool EnableResolution = false;
12 |
13 | ///
14 | /// Try to switch Spec
15 | ///
16 | ///
17 | ///
18 | ///
19 | public static void SwitchSpec(this PSB psb, PsbSpec targetSpec,
20 | PsbPixelFormat pixelFormat = PsbPixelFormat.None)
21 | {
22 | if (targetSpec is PsbSpec.other or PsbSpec.none)
23 | {
24 | return;
25 | }
26 |
27 | //Alternative //TODO: Alternative table?
28 | bool isAlternative = false;
29 | var realTargetSpec = PsbSpec.common;
30 |
31 | var original = psb.Platform;
32 | if (original == PsbSpec.ems)
33 | {
34 | original = PsbSpec.common;
35 | }
36 |
37 | if (targetSpec == PsbSpec.ems)
38 | {
39 | isAlternative = true;
40 | realTargetSpec = targetSpec;
41 | targetSpec = PsbSpec.common;
42 | }
43 |
44 | if (targetSpec == PsbSpec.krkr) //krkr can not select pixel format
45 | {
46 | switch (original)
47 | {
48 | case PsbSpec.win:
49 | {
50 | Common2KrkrConverter winKrkr = new Common2KrkrConverter();
51 | winKrkr.Convert(psb);
52 | break;
53 | }
54 |
55 | case PsbSpec.common:
56 | {
57 | Common2KrkrConverter commonKrkr = new Common2KrkrConverter();
58 | commonKrkr.Convert(psb);
59 | break;
60 | }
61 |
62 | default:
63 | psb.Platform = targetSpec;
64 | break;
65 | }
66 | }
67 |
68 | else if (targetSpec == PsbSpec.win)
69 | {
70 | switch (original)
71 | {
72 | case PsbSpec.krkr:
73 | Krkr2CommonConverter krkr2Win = new Krkr2CommonConverter(true) {EnableResolution = EnableResolution};
74 | krkr2Win.Convert(psb);
75 | break;
76 | case PsbSpec.common:
77 | CommonWinConverter winCommon = new CommonWinConverter();
78 | winCommon.Convert(psb);
79 | break;
80 | default:
81 | psb.Platform = targetSpec;
82 | break;
83 | }
84 | }
85 |
86 | else if (targetSpec is PsbSpec.common or PsbSpec.ems)
87 | {
88 | switch (original)
89 | {
90 | case PsbSpec.krkr:
91 | Krkr2CommonConverter krkr2Common = new Krkr2CommonConverter() {EnableResolution = EnableResolution};
92 | krkr2Common.Convert(psb);
93 | break;
94 | case PsbSpec.win:
95 | CommonWinConverter commonWin = new CommonWinConverter();
96 | commonWin.Convert(psb);
97 | break;
98 | case PsbSpec.common:
99 | case PsbSpec.ems:
100 | psb.Platform = targetSpec;
101 | break;
102 | default:
103 | psb.Platform = targetSpec;
104 | break;
105 | }
106 | }
107 |
108 | else
109 | {
110 | psb.Platform = targetSpec;
111 | }
112 |
113 | if (isAlternative)
114 | {
115 | psb.Platform = realTargetSpec;
116 | }
117 | }
118 | }
119 | }
--------------------------------------------------------------------------------
/FreeMote.Psb/FreeMote.Psb.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net48
4 | Library
5 | false
6 |
7 |
8 | default
9 | true
10 |
11 |
12 | default
13 | true
14 | bin\Release\FreeMote.Psb.xml
15 |
16 |
17 |
18 |
19 |
20 |
21 | 2.1.0
22 |
23 |
24 | 2024.1.21.12
25 |
26 |
27 | 4.5.1
28 |
29 |
30 |
31 | 4.5.5
32 |
33 |
34 | 6.0.0
35 |
36 |
37 | 4.5.0
38 |
39 |
40 |
41 |
42 | .editorconfig
43 |
44 |
45 |
--------------------------------------------------------------------------------
/FreeMote.Psb/FreeMote.Psb.csproj.DotSettings:
--------------------------------------------------------------------------------
1 |
2 | True
--------------------------------------------------------------------------------
/FreeMote.Psb/IPsbType.cs:
--------------------------------------------------------------------------------
1 | // ReSharper disable InconsistentNaming
2 |
3 | using System.Collections.Generic;
4 | using FreeMote.Plugins;
5 | using FreeMote.Psb.Types;
6 |
7 | namespace FreeMote.Psb
8 | {
9 | public interface IPsbType
10 | {
11 | ///
12 | /// PSB type
13 | ///
14 | PsbType PsbType { get; }
15 |
16 | ///
17 | /// Check if is this type
18 | ///
19 | ///
20 | ///
21 | bool IsThisType(PSB psb);
22 |
23 | ///
24 | /// Collect Resources
25 | ///
26 | ///
27 | ///
28 | ///
29 | List CollectResources(PSB psb, bool deDuplication = true) where T: class, IResourceMetadata;
30 |
31 | ///
32 | /// Link
33 | ///
34 | ///
35 | ///
36 | /// (legacy) res.json style resource list
37 | ///
38 | ///
39 | void Link(PSB psb, FreeMountContext context, IList resPaths, string baseDir = null, PsbLinkOrderBy order = PsbLinkOrderBy.Convention);
40 |
41 | ///
42 | /// Link
43 | ///
44 | ///
45 | ///
46 | ///
47 | ///
48 | void Link(PSB psb, FreeMountContext context, IDictionary resPaths, string baseDir = null);
49 |
50 | ///
51 | /// Unlink to file
52 | ///
53 | ///
54 | ///
55 | /// resource folder name, could be PSB name itself, or with `-resource` suffix
56 | /// resource folder path
57 | /// whether to save the PSB without texture
58 | ///
59 | /// unlinked PSB path
60 | void UnlinkToFile(PSB psb, FreeMountContext context, string name, string dirPath, bool outputUnlinkedPsb = true, PsbLinkOrderBy order = PsbLinkOrderBy.Name);
61 |
62 | ///
63 | /// Output resources
64 | ///
65 | ///
66 | ///
67 | /// resource folder name, could be PSB name itself, or with `-resource` suffix
68 | /// resource folder path
69 | ///
70 | /// (FileName, RelativePath)
71 | Dictionary OutputResources(PSB psb, FreeMountContext context, string name, string dirPath, PsbExtractOption extractOption = PsbExtractOption.Original);
72 | }
73 |
74 | public partial class PSB
75 | {
76 | internal static readonly Dictionary TypeHandlers = new()
77 | {
78 | {PsbType.Motion, new MotionType()},
79 | {PsbType.Scn, new ScnType()},
80 | {PsbType.Tachie, new ImageType()},
81 | {PsbType.Pimg, new PimgType()},
82 | {PsbType.Mmo, new MmoType()},
83 | {PsbType.ArchiveInfo, new ArchiveType()},
84 | {PsbType.SoundArchive, new SoundArchiveType()},
85 | {PsbType.BmpFont, new FontType()},
86 | {PsbType.Map, new MapType()},
87 | {PsbType.ClutImg, new ClutType()},
88 | {PsbType.SprBlock, new SprBlockType()},
89 | {PsbType.SprData, new SprDataType()},
90 | {PsbType.Chip, new ChipType()},
91 | {PsbType.PSB, new MotionType()}, //assume as motion type by default, must put this after Motion
92 | };
93 |
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/FreeMote.Psb/IResourceMetadata.cs:
--------------------------------------------------------------------------------
1 | using FreeMote.Plugins;
2 |
3 | namespace FreeMote.Psb
4 | {
5 | public interface IResourceMetadata
6 | {
7 | string Name { get; set; }
8 | public uint Index { get; }
9 | PsbSpec Spec { get; set; }
10 | public PsbType PsbType { get; set; }
11 | void Link(string fullPath, FreeMountContext context);
12 | }
13 |
14 | ///
15 | /// Texture Link Order
16 | ///
17 | public enum PsbLinkOrderBy
18 | {
19 | ///
20 | /// The image name should be FreeMote style: {part}-{name}.{ext}
21 | ///
22 | Convention = 0,
23 |
24 | ///
25 | /// The image name should be EMT Editor style: {name}_tex#{no:D3}.{ext}
26 | ///
27 | Name = 1,
28 |
29 | ///
30 | /// The order in list matters
31 | ///
32 | Order = 2,
33 | }
34 |
35 | ///
36 | /// Compression in PSB
37 | ///
38 | public enum PsbCompressType
39 | {
40 | ///
41 | /// Normal
42 | ///
43 | None,
44 |
45 | ///
46 | /// RLE
47 | ///
48 | RL,
49 |
50 | ///
51 | /// Raw Bitmap
52 | ///
53 | Bmp,
54 |
55 | ///
56 | /// KRKR TLG
57 | ///
58 | Tlg,
59 |
60 | /////
61 | ///// ASTC
62 | /////
63 | //Astc,
64 |
65 | /////
66 | ///// BC7
67 | /////
68 | //Bc7,
69 |
70 | ///
71 | /// By extension
72 | ///
73 | ByName,
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/FreeMote.Psb/Plugins/AudioFileFormatter.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.ComponentModel.Composition;
3 | using FreeMote.Psb;
4 |
5 | namespace FreeMote.Plugins
6 | {
7 | [Export(typeof(IPsbAudioFormatter))]
8 | [ExportMetadata("Name", "FreeMote.Audio.File")]
9 | [ExportMetadata("Author", "Ulysses")]
10 | [ExportMetadata("Comment", "Export/Import audio file.")]
11 | internal class AudioFileFormatter : IPsbAudioFormatter
12 | {
13 | public List Extensions { get; } = new() { ".wav", ".ogg" };
14 | public bool CanToWave(IArchData archData, Dictionary context = null)
15 | {
16 | return archData is AudioFileArchData;
17 | }
18 |
19 | public bool CanToArchData(byte[] wave, Dictionary context = null)
20 | {
21 | if (wave == null || wave.Length < 4)
22 | {
23 | return false;
24 | }
25 |
26 | if (wave[0] == 'O' && wave[1] == 'g' && wave[2] == 'g') return true;
27 | if (wave[0] == 'R' && wave[1] == 'I' && wave[2] == 'F' && wave[3] == 'F') return true;
28 |
29 | return false;
30 | }
31 |
32 | public byte[] ToWave(AudioMetadata md, IArchData archData, string fileName = null, Dictionary context = null)
33 | {
34 | var arch = archData as AudioFileArchData;
35 | return arch?.Data?.Data;
36 | }
37 |
38 | public bool ToArchData(AudioMetadata md, IArchData archData, in byte[] wave, string fileName, string waveExt, Dictionary context = null)
39 | {
40 | if (archData is not AudioFileArchData fileArchData)
41 | {
42 | return false;
43 | }
44 |
45 | if (wave[0] == 'O' && wave[1] == 'g' && wave[2] == 'g')
46 | {
47 | fileArchData.Format = PsbAudioFormat.OGG;
48 | }
49 | else //if (wave[0] == 'R' && wave[1] == 'I' && wave[2] == 'F' && wave[3] == 'F')
50 | {
51 | fileArchData.Format = PsbAudioFormat.WAV;
52 | }
53 |
54 | if (fileArchData.Data == null)
55 | {
56 | fileArchData.Data = new PsbResource { Data = wave };
57 | }
58 | else
59 | {
60 | fileArchData.Data.Data = wave;
61 | }
62 | return true;
63 | }
64 |
65 | public bool TryGetArchData(AudioMetadata md, PsbDictionary channel, out IArchData data, Dictionary context = null)
66 | {
67 | data = null;
68 | if (channel.Count == 1 && channel["archData"] is PsbDictionary archDic && archDic.TryGetPsbValue("background", out var background) && archDic.TryGetPsbValue("filetype", out var fileType) && archDic.TryGetPsbValue("ext", out var ext) && archDic.TryGetPsbValue("data", out var aData))
69 | {
70 | var newData = new AudioFileArchData
71 | {
72 | Data = aData,
73 | Background = background,
74 | Format = ext == ".ogg" ? PsbAudioFormat.OGG : PsbAudioFormat.WAV
75 | };
76 |
77 | if (archDic["loop"] is PsbList aLoop)
78 | {
79 | newData.Loop = aLoop;
80 | }
81 |
82 | data = newData;
83 | return true;
84 | }
85 |
86 | return false;
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/FreeMote.Psb/Plugins/IPsbAudioFormatter.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using FreeMote.Psb;
3 |
4 | namespace FreeMote.Plugins
5 | {
6 | ///
7 | /// Handle audio conversion
8 | ///
9 | public interface IPsbAudioFormatter : IPsbPlugin
10 | {
11 | ///
12 | /// Target Extension (if have) e.g. ".wav"
13 | ///
14 | List Extensions { get; }
15 |
16 | ///
17 | /// Check if is available
18 | ///
19 | ///
20 | ///
21 | ///
22 | bool CanToWave(IArchData archData, Dictionary context = null);
23 |
24 | ///
25 | /// Check if is available
26 | ///
27 | ///
28 | ///
29 | ///
30 | bool CanToArchData(byte[] wave, Dictionary context = null);
31 |
32 | ///
33 | /// Convert to wave bytes
34 | ///
35 | ///
36 | ///
37 | ///
38 | ///
39 | ///
40 | byte[] ToWave(AudioMetadata md, IArchData archData, string fileName = null, Dictionary context = null);
41 |
42 | ///
43 | /// Convert wave bytes to
44 | ///
45 | ///
46 | /// the archData to be filled
47 | ///
48 | ///
49 | ///
50 | ///
51 | /// true if filled.
52 | bool ToArchData(AudioMetadata md, IArchData archData, in byte[] wave, string fileName, string waveExt, Dictionary context = null);
53 |
54 | ///
55 | /// Used when collecting audio resource, the data could be null when compiling
56 | ///
57 | /// is still null at this time
58 | /// an object in [channelList]
59 | ///
60 | ///
61 | ///
62 | bool TryGetArchData(AudioMetadata md, PsbDictionary channel, out IArchData data, Dictionary context = null);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/FreeMote.Psb/Plugins/IPsbImageFormatter.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Drawing;
3 |
4 | namespace FreeMote.Plugins
5 | {
6 | public interface IPsbImageFormatter : IPsbPlugin
7 | {
8 | ///
9 | /// Target Extension (if have) e.g. ".png"
10 | ///
11 | List Extensions { get; }
12 |
13 | ///
14 | /// Check if is available
15 | ///
16 | ///
17 | ///
18 | ///
19 | bool CanToBitmap(in byte[] data, Dictionary context = null);
20 |
21 | ///
22 | /// Check if is available
23 | ///
24 | ///
25 | ///
26 | ///
27 | bool CanToBytes(Bitmap bitmap, Dictionary context = null);
28 |
29 | ///
30 | /// Convert image bytes to
31 | ///
32 | ///
33 | ///
34 | ///
35 | ///
36 | ///
37 | ///
38 | Bitmap ToBitmap(in byte[] data, int width, int height, PsbSpec platform, Dictionary context = null);
39 |
40 | ///
41 | /// Convert to image bytes
42 | ///
43 | ///
44 | ///
45 | ///
46 | ///
47 | byte[] ToBytes(Bitmap bitmap, PsbSpec platform, Dictionary context = null);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/FreeMote.Psb/Plugins/IPsbKeyProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.IO;
3 |
4 | namespace FreeMote.Plugins
5 | {
6 | ///
7 | /// Get PSB CryptKey
8 | ///
9 | public interface IPsbKeyProvider : IPsbPlugin
10 | {
11 | ///
12 | /// Try to get PSB key, do not lift the stream position
13 | ///
14 | ///
15 | ///
16 | /// null if no key detected; otherwise give the key
17 | uint? GetKey(Stream stream, Dictionary context = null);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/FreeMote.Psb/Plugins/IPsbPluginInfo.cs:
--------------------------------------------------------------------------------
1 | namespace FreeMote.Plugins
2 | {
3 | public interface IPsbPlugin
4 | {
5 | }
6 | public interface IPsbPluginInfo
7 | {
8 | ///
9 | /// Plugin Name
10 | ///
11 | string Name { get; }
12 | ///
13 | /// Plugin Author
14 | ///
15 | string Author { get; }
16 | ///
17 | /// Comment
18 | ///
19 | string Comment { get; }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/FreeMote.Psb/Plugins/IPsbShell.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.IO;
3 |
4 | namespace FreeMote.Plugins
5 | {
6 | public interface IPsbShell : IPsbPlugin
7 | {
8 | string Name { get; }
9 | bool IsInShell(Stream stream, Dictionary context = null);
10 | MemoryStream ToPsb(Stream stream, Dictionary context = null);
11 | MemoryStream ToShell(Stream stream, Dictionary context = null);
12 | ///
13 | /// If no signature, should set it to null, use instead
14 | ///
15 | byte[] Signature { get; }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/FreeMote.Psb/Plugins/IPsbSpecialType.cs:
--------------------------------------------------------------------------------
1 | using FreeMote.Psb;
2 |
3 | namespace FreeMote.Plugins
4 | {
5 | public interface IPsbSpecialType : IPsbType, IPsbPlugin
6 | {
7 | string TypeId { get; }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/FreeMote.Psb/Plugins/ManagedTlgFormatter.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Drawing;
3 | using System.IO;
4 |
5 | namespace FreeMote.Plugins
6 | {
7 | class ManagedTlgFormatter : IPsbImageFormatter
8 | {
9 | private const string TlgVersion = "TlgVersion";
10 |
11 | private static TlgImageConverter _managedConverter = null;
12 |
13 | ///
14 | /// Load TLG
15 | ///
16 | ///
17 | /// TLG version, can be 0(unknown),5,6
18 | ///
19 | public static Bitmap LoadTlg(byte[] tlgData, out int version)
20 | {
21 | if (_managedConverter == null)
22 | {
23 | _managedConverter = new TlgImageConverter();
24 | }
25 |
26 | using (var ms = new MemoryStream(tlgData))
27 | {
28 | using (var br = new BinaryReader(ms))
29 | {
30 | var bmp = _managedConverter.ReadAndGetMetaData(br, out var md);
31 | version = md.Version;
32 | return bmp;
33 | }
34 | }
35 | }
36 |
37 | ///
38 | /// Save TLG
39 | ///
40 | ///
41 | /// true: Save as TLG6; false: Save as TLG5
42 | ///
43 | public static byte[] SaveTlg(Bitmap bmp, bool tlg6 = false)
44 | {
45 | return null;
46 | }
47 |
48 | public List Extensions { get; } = new List { ".tlg", ".tlg5", ".tlg6" };
49 | public bool CanToBitmap(in byte[] data, Dictionary context = null)
50 | {
51 | return true;
52 | }
53 |
54 | public bool CanToBytes(Bitmap bitmap, Dictionary context = null)
55 | {
56 | return false;
57 | }
58 |
59 | public Bitmap ToBitmap(in byte[] data, int width, int height, PsbSpec platform, Dictionary context = null)
60 | {
61 | var bmp = LoadTlg(data, out var v);
62 | if (v > 5 && context != null)
63 | {
64 | context[TlgVersion] = v;
65 | }
66 |
67 | return bmp;
68 | }
69 |
70 | public byte[] ToBytes(Bitmap bitmap, PsbSpec platform, Dictionary context = null)
71 | {
72 | return null;
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/FreeMote.Psb/Plugins/WavFormatter.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.ComponentModel.Composition;
3 | using System.IO;
4 | using FreeMote.Psb;
5 | // ReSharper disable CheckNamespace
6 |
7 | namespace FreeMote.Plugins
8 | {
9 | [Export(typeof(IPsbAudioFormatter))]
10 | [ExportMetadata("Name", "FreeMote.Wav")]
11 | [ExportMetadata("Author", "Ulysses")]
12 | [ExportMetadata("Comment", "Wav/P16 support.")]
13 | class WavFormatter : IPsbAudioFormatter
14 | {
15 | public List Extensions { get; } = new List { ".wav", ".p16" };
16 | public bool CanToWave(IArchData archData, Dictionary context = null)
17 | {
18 | return archData is WavArchData || archData is P16ArchData;
19 | }
20 |
21 | public bool CanToArchData(byte[] wave, Dictionary context = null)
22 | {
23 | return wave != null;
24 | }
25 |
26 | public byte[] ToWave(AudioMetadata md, IArchData archData, string fileName = null, Dictionary context = null)
27 | {
28 | if (archData is WavArchData arch)
29 | {
30 | return arch.ToWav();
31 | }
32 |
33 | if (archData is P16ArchData p16)
34 | {
35 | return p16.ToWav();
36 | }
37 |
38 | return null;
39 | }
40 |
41 | public bool ToArchData(AudioMetadata md, IArchData archData, in byte[] wave, string fileName, string waveExt, Dictionary context = null)
42 | {
43 | if (archData is WavArchData data)
44 | {
45 | WavArchData arch = data;
46 |
47 | using var oms = new MemoryStream(wave);
48 | arch.ReadFromWav(oms);
49 | if (md != null && md.LoopStr != null)
50 | {
51 | arch.Loop = PsbResHelper.ParseLoopStr(md.LoopStr.Value);
52 | }
53 |
54 | return true;
55 | }
56 |
57 | if (archData is P16ArchData p16)
58 | {
59 | P16ArchData arch = p16;
60 |
61 | using var oms = new MemoryStream(wave);
62 | arch.ReadFromWav(oms);
63 |
64 | return true;
65 |
66 | }
67 |
68 | return false;
69 | }
70 |
71 | public bool TryGetArchData(AudioMetadata md, PsbDictionary channel, out IArchData data, Dictionary context = null)
72 | {
73 | data = null;
74 | if (md.Spec == PsbSpec.win)
75 | {
76 | if (channel.Count == 1 && channel["archData"] is PsbDictionary archDic && !archDic.ContainsKey("dpds") && archDic["data"] is PsbResource aData && archDic["fmt"] is PsbResource aFmt && archDic["wav"] is PsbString aWav)
77 | {
78 | var newData = new WavArchData
79 | {
80 | Data = aData,
81 | Fmt = aFmt,
82 | Wav = aWav.Value
83 | };
84 |
85 | if (archDic["loop"] is PsbList aLoop)
86 | {
87 | newData.Loop = aLoop;
88 | }
89 |
90 | data = newData;
91 | return true;
92 | }
93 | }
94 |
95 | {
96 | if (channel.Count == 1 && channel["archData"] is PsbDictionary p16Arch && p16Arch["filetype"] is PsbString fileType && fileType == "p16" && p16Arch["data"] is PsbResource p16Data && p16Arch["samprate"] is PsbNumber sampRate)
97 | {
98 | var newData = new P16ArchData
99 | {
100 | Data = p16Data,
101 | SampleRate = sampRate.AsInt
102 | };
103 | data = newData;
104 | return true;
105 | }
106 | }
107 |
108 | // android 2ch P16
109 | {
110 | if (channel["archData"] is PsbDictionary p16Arch && p16Arch["ext"] is PsbString ext && ext == ".p16" && p16Arch["data"] is PsbResource p16Data && p16Arch["samprate"] is PsbNumber sampRate)
111 | {
112 | var newData = new P16ArchData
113 | {
114 | Data = p16Data,
115 | SampleRate = sampRate.AsInt
116 | };
117 |
118 | ExtractPan(channel, newData);
119 | data = newData;
120 | return true;
121 | }
122 | }
123 |
124 | return false;
125 | }
126 |
127 | private static void ExtractPan(PsbDictionary channel, P16ArchData newData)
128 | {
129 | if (channel["pan"] is PsbList panList)
130 | {
131 | newData.Pan = panList;
132 |
133 | if (panList.Count == 2)
134 | {
135 | var left = panList[0].GetFloat();
136 | var right = panList[1].GetFloat();
137 | if (left == 1.0f && right == 0.0f)
138 | {
139 | newData.ChannelPan = PsbAudioPan.Left;
140 | }
141 | else if (left == 0.0f && right == 1.0f)
142 | {
143 | newData.ChannelPan = PsbAudioPan.Right;
144 | }
145 | }
146 | }
147 | }
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/FreeMote.Psb/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | [assembly: AssemblyTitle("FreeMote.Psb")]
6 | [assembly: AssemblyDescription("PSB Parser & Painter")]
7 | [assembly: AssemblyConfiguration("")]
8 | [assembly: AssemblyCompany("Ulysses")]
9 | [assembly: AssemblyProduct("FreeMote")]
10 | [assembly: AssemblyCopyright("Copyright © Ulysses 2017-2025")]
11 | [assembly: AssemblyTrademark("wdwxy12345@gmail.com")]
12 | [assembly: AssemblyCulture("")]
13 |
14 | [assembly: ComVisible(false)]
15 |
16 | [assembly: Guid("c0b2c2ff-d8f4-497e-8312-c2af1bb6e7f7")]
17 | [assembly: InternalsVisibleTo("FreeMote.PsBuild")]
18 | [assembly: InternalsVisibleTo("FreeMote.Purify")]
19 | [assembly: InternalsVisibleTo("FreeMote.Plugins")]
20 | [assembly: InternalsVisibleTo("FreeMote.Tests")]
21 | [assembly: InternalsVisibleTo("FreeMote.Editor")]
22 |
23 | // [assembly: AssemblyVersion("1.0.*")]
24 | [assembly: AssemblyVersion("4.0.0.0")]
25 | [assembly: AssemblyFileVersion("4.0.0.0")]
26 |
--------------------------------------------------------------------------------
/FreeMote.Psb/Resources/FlattenArrayMetadata.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Runtime.InteropServices;
4 | using FreeMote.Plugins;
5 |
6 | namespace FreeMote.Psb
7 | {
8 | ///
9 | /// Information about FlattenArray such as rawMeshList
10 | ///
11 | public class FlattenArrayMetadata : IResourceMetadata
12 | {
13 | public string Name { get; set; }
14 | public PsbResource Resource { get; set; }
15 |
16 | public uint Index
17 | {
18 | get => Resource.Index ?? uint.MaxValue;
19 | set
20 | {
21 | if (Resource != null)
22 | {
23 | Resource.Index = value;
24 | }
25 | }
26 | }
27 |
28 | public byte[] Data
29 | {
30 | get => Resource?.Data;
31 |
32 | internal set
33 | {
34 | if (Resource == null)
35 | {
36 | throw new NullReferenceException("Resource is null");
37 | }
38 |
39 | Resource.Data = value;
40 | }
41 | }
42 |
43 | public Span FloatValues => MemoryMarshal.Cast(Data.AsSpan());
44 |
45 | public PsbSpec Spec { get; set; }
46 | public PsbType PsbType { get; set; }
47 |
48 | public void Link(string fullPath, FreeMountContext context)
49 | {
50 | Data = File.ReadAllBytes(fullPath);
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/FreeMote.Psb/Resources/IArchData.cs:
--------------------------------------------------------------------------------
1 | // ArchData in PSB is a typical bad design. It does not describe the true audio format,
2 | // and different formats (such as AT9 and VAG) may share exactly same ArchData structure.
3 | // We have to keep this info in resx.json for re-compile.
4 | // An ArchData may have multiple channels, a channel may have multiple resources,
5 | // making it difficult to re-compile.
6 |
7 | using System.Collections.Generic;
8 |
9 | namespace FreeMote.Psb
10 | {
11 | ///
12 | /// Audio Arch Data (usually a Channel)
13 | ///
14 | public interface IArchData
15 | {
16 | uint Index { get; }
17 | ///
18 | /// Unusual audio extension, such as .vag (must start with dot)
19 | ///
20 | string Extension { get; }
21 | ///
22 | /// Common wave type after decode, usually .wav (must start with dot)
23 | ///
24 | string WaveExtension { get; set; }
25 | PsbAudioFormat Format { get; }
26 | PsbAudioPan ChannelPan { get; }
27 | ///
28 | /// key "data" in PSB
29 | ///
30 | PsbResource Data { get; }
31 | IList DataList { get; }
32 |
33 | ///
34 | /// PSB object "archData" value
35 | ///
36 | PsbDictionary PsbArchData { get; set; }
37 |
38 | ///
39 | /// Generate PSB object "archData" value
40 | ///
41 | ///
42 | IPsbValue ToPsbArchData();
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/FreeMote.Psb/Types/ArchiveType.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using FreeMote.Plugins;
3 |
4 | namespace FreeMote.Psb.Types
5 | {
6 | class ArchiveType : IPsbType
7 | {
8 | public PsbType PsbType => PsbType.ArchiveInfo;
9 | public bool IsThisType(PSB psb)
10 | {
11 | return psb.TypeId == "archive" || (psb.TypeId == "scenario" && psb.Objects.ContainsKey("file_info")); //&& psb.Objects.ContainsKey("file_info")
12 | }
13 |
14 | public List CollectResources(PSB psb, bool deDuplication = true) where T : class, IResourceMetadata
15 | {
16 | return new List();
17 | }
18 |
19 | public void Link(PSB psb, FreeMountContext context, IList resPaths, string baseDir = null, PsbLinkOrderBy order = PsbLinkOrderBy.Convention)
20 | {
21 | }
22 |
23 | public void Link(PSB psb, FreeMountContext context, IDictionary resPaths, string baseDir = null)
24 | {
25 | }
26 |
27 | public void UnlinkToFile(PSB psb, FreeMountContext context, string name, string dirPath, bool outputUnlinkedPsb = true,
28 | PsbLinkOrderBy order = PsbLinkOrderBy.Name)
29 | {
30 | }
31 |
32 | public Dictionary OutputResources(PSB psb, FreeMountContext context, string name, string dirPath,
33 | PsbExtractOption extractOption = PsbExtractOption.Original)
34 | {
35 | return null;
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/FreeMote.Psb/Types/BaseImageType.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Drawing;
3 | using FreeMote.Plugins;
4 |
5 | namespace FreeMote.Psb.Types
6 | {
7 | public abstract class BaseImageType
8 | {
9 | public virtual void Link(PSB psb, FreeMountContext context, IList resPaths, string baseDir = null, PsbLinkOrderBy order = PsbLinkOrderBy.Convention)
10 | {
11 | PsbResHelper.LinkImages(psb, context, resPaths, baseDir, order);
12 | }
13 |
14 | public virtual void Link(PSB psb, FreeMountContext context, IDictionary resPaths, string baseDir = null)
15 | {
16 | PsbResHelper.LinkImages(psb, context, resPaths, baseDir);
17 | }
18 |
19 | public virtual List Unlink(PSB psb, PsbLinkOrderBy order = PsbLinkOrderBy.Name, bool disposeResInPsb = true)
20 | {
21 | return PsbResHelper.UnlinkImages(psb, order, disposeResInPsb);
22 | }
23 |
24 | public virtual void UnlinkToFile(PSB psb, FreeMountContext context, string name, string dirPath, bool outputUnlinkedPsb = true,
25 | PsbLinkOrderBy order = PsbLinkOrderBy.Name)
26 | {
27 | PsbResHelper.UnlinkImagesToFile(psb, context, name, dirPath, outputUnlinkedPsb, order);
28 | }
29 |
30 | public virtual Dictionary OutputResources(PSB psb, FreeMountContext context, string name, string dirPath,
31 | PsbExtractOption extractOption = PsbExtractOption.Original)
32 | {
33 | return PsbResHelper.OutputImageResources(psb, context, name, dirPath, extractOption);
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/FreeMote.Psb/Types/FontType.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 |
4 | namespace FreeMote.Psb.Types
5 | {
6 | class FontType : BaseImageType, IPsbType
7 | {
8 | public const string Source = "source";
9 | public PsbType PsbType => PsbType.BmpFont;
10 | public bool IsThisType(PSB psb)
11 | {
12 | return psb.TypeId == "font"; //&& psb.Objects.ContainsKey("code");
13 | }
14 |
15 | public List CollectResources(PSB psb, bool deDuplication = true) where T : class, IResourceMetadata
16 | {
17 | //List resourceList = psb.Resources == null
18 | // ? new List()
19 | // : new List(psb.Resources.Count);
20 |
21 | //if (psb.Resources != null)
22 | // resourceList.AddRange(psb.Resources.Select(r => new ImageMetadata { Resource = r }).Cast());
23 |
24 | var resourceList = FindFontResources(psb, deDuplication).Cast().ToList();
25 |
26 | return resourceList;
27 | }
28 |
29 | public List FindFontResources(PSB psb, bool deDuplication = true)
30 | {
31 | List resList = new List(psb.Resources.Count);
32 |
33 | if (psb.Objects == null || !psb.Objects.ContainsKey(Source) || !(psb.Objects[Source] is PsbList list))
34 | {
35 | return resList;
36 | }
37 |
38 | foreach (var item in list)
39 | {
40 | if (item is not PsbDictionary obj)
41 | {
42 | continue;
43 | }
44 |
45 | //TODO: deDuplication for resource (besides pal)
46 | var md = PsbResHelper.GenerateImageMetadata(obj, null);
47 | md.PsbType = PsbType.BmpFont;
48 | md.Spec = psb.Platform;
49 | resList.Add(md);
50 | }
51 |
52 | return resList;
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/FreeMote.Psb/Types/MapType.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 |
4 | namespace FreeMote.Psb.Types
5 | {
6 | ///
7 | /// Tile Map
8 | ///
9 | class MapType : BaseImageType, IPsbType
10 | {
11 | public const string Source = "layer";
12 | public PsbType PsbType => PsbType.Map;
13 | public bool IsThisType(PSB psb)
14 | {
15 | return psb.TypeId == "map";
16 | }
17 |
18 | public List CollectResources(PSB psb, bool deDuplication = true) where T : class, IResourceMetadata
19 | {
20 | var resourceList = FindTileResources(psb, deDuplication).Cast().ToList();
21 |
22 | return resourceList;
23 | }
24 |
25 | private List FindTileResources(PSB psb, bool deDuplication)
26 | {
27 | List resList = new List(psb.Resources.Count);
28 |
29 | if (psb.Objects == null || !psb.Objects.ContainsKey(Source) || psb.Objects[Source] is not PsbList list)
30 | {
31 | return resList;
32 | }
33 |
34 | foreach (var item in list)
35 | {
36 | if (item is not PsbDictionary obj || !obj.ContainsKey("image") || obj["image"] is not PsbDictionary image)
37 | {
38 | continue;
39 | }
40 |
41 | var md = PsbResHelper.GenerateImageMetadata(image, null);
42 | if (obj.ContainsKey("label") && (string.IsNullOrEmpty(md.Name) || md.Name == "image"))
43 | {
44 | md.Name = obj["label"].ToString();
45 | }
46 | md.PsbType = PsbType.Map;
47 | md.Spec = psb.Platform;
48 | resList.Add(md);
49 | }
50 |
51 | return resList;
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/FreeMote.Psb/Types/MmoType.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 |
4 | namespace FreeMote.Psb.Types
5 | {
6 | class MmoType : BaseImageType, IPsbType
7 | {
8 | public const string MmoSourceKey = "sourceChildren";
9 | public const string MmoBgSourceKey = "bgChildren";
10 |
11 | public PsbType PsbType => PsbType.Mmo;
12 | public bool IsThisType(PSB psb)
13 | {
14 | if (psb.Objects == null)
15 | {
16 | return false;
17 | }
18 | return psb.Objects.ContainsKey("objectChildren") && psb.Objects.ContainsKey("sourceChildren");
19 | }
20 |
21 | public List CollectResources(PSB psb, bool deDuplication = true) where T : class, IResourceMetadata
22 | {
23 | List resourceList = psb.Resources == null
24 | ? new List()
25 | : new List(psb.Resources.Count);
26 |
27 | FindMmoResources(resourceList, psb.Objects[MmoBgSourceKey], MmoBgSourceKey, deDuplication);
28 | FindMmoResources(resourceList, psb.Objects[MmoSourceKey], MmoSourceKey, deDuplication);
29 |
30 | resourceList.ForEach(r =>
31 | {
32 | r.PsbType = PsbType.Mmo;
33 | r.Spec = psb.Platform;
34 | });
35 | return resourceList;
36 | }
37 |
38 | private static void FindMmoResources(List list, IPsbValue obj, in string defaultPartname = "",
39 | bool deDuplication = true) where T: IResourceMetadata
40 | {
41 | switch (obj)
42 | {
43 | case PsbList c:
44 | foreach (var o in c) FindMmoResources(list, o, defaultPartname, deDuplication);
45 | break;
46 | case PsbDictionary d:
47 | if (d[Consts.ResourceKey] is PsbResource r)
48 | {
49 | if (!deDuplication)
50 | {
51 | list.Add((T)(IResourceMetadata)GenerateMmoResMetadata(d, defaultPartname, r));
52 | }
53 | else if (r.Index == null || list.FirstOrDefault(md => md.Index == r.Index.Value) == null)
54 | {
55 | list.Add((T)(IResourceMetadata)GenerateMmoResMetadata(d, defaultPartname, r));
56 | }
57 | }
58 |
59 | foreach (var o in d.Values)
60 | {
61 | FindMmoResources(list, o, defaultPartname, deDuplication);
62 | }
63 |
64 | break;
65 | }
66 | }
67 |
68 | private static ImageMetadata GenerateMmoResMetadata(PsbDictionary d, string defaultPartName = "",
69 | PsbResource r = null)
70 | {
71 | if (r == null)
72 | {
73 | r = d.Values.FirstOrDefault(v => v is PsbResource) as PsbResource;
74 | }
75 |
76 | var dd = d.Parent.Parent as PsbDictionary ?? d;
77 |
78 | string name = "";
79 | string part = defaultPartName;
80 | if ((dd["label"]) is PsbString lbl)
81 | {
82 | name = lbl.Value;
83 | }
84 |
85 | //if (dd.Parent.Parent["className"] is PsbString className)
86 | //{
87 | // part = className;
88 | //}
89 |
90 | bool is2D = false;
91 | var compress = PsbCompressType.None;
92 | if (d["compress"] is PsbString sc)
93 | {
94 | is2D = true;
95 | if (sc.Value.ToUpperInvariant() == "RL")
96 | {
97 | compress = PsbCompressType.RL;
98 | }
99 | }
100 |
101 | int width = 1, height = 1;
102 | float originX = 0, originY = 0;
103 | if ((d["width"] ?? dd["width"]) is PsbNumber nw)
104 | {
105 | is2D = true;
106 | width = (int)nw;
107 | }
108 |
109 | if ((d["height"] ?? dd["height"]) is PsbNumber nh)
110 | {
111 | is2D = true;
112 | height = (int)nh;
113 | }
114 |
115 | if ((dd["originX"] ?? d["originX"]) is PsbNumber nx)
116 | {
117 | is2D = true;
118 | originX = (float)nx;
119 | }
120 |
121 | if ((dd["originY"] ?? d["originY"]) is PsbNumber ny)
122 | {
123 | is2D = true;
124 | originY = (float)ny;
125 | }
126 |
127 | var md = new ImageMetadata()
128 | {
129 | Is2D = is2D,
130 | Compress = compress,
131 | OriginX = originX,
132 | OriginY = originY,
133 | Width = width,
134 | Height = height,
135 | Name = name,
136 | Part = part,
137 | Resource = r,
138 | };
139 | return md;
140 | }
141 |
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/FreeMote.Psb/Types/MotionType.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using FreeMote.Plugins;
4 | using static FreeMote.Psb.PsbResHelper;
5 |
6 | namespace FreeMote.Psb.Types
7 | {
8 | class MotionType : BaseImageType, IPsbType
9 | {
10 | public const string MotionSourceKey = "source";
11 |
12 | public PsbType PsbType => PsbType.Motion;
13 | public bool IsThisType(PSB psb)
14 | {
15 | return psb.TypeId == "motion";
16 | }
17 |
18 | public List CollectResources(PSB psb, bool deDuplication = true) where T : class, IResourceMetadata
19 | {
20 | List resourceList = psb.Resources == null
21 | ? new List()
22 | : new List(psb.Resources.Count);
23 |
24 | if (psb.Objects != null && psb.Objects.ContainsKey(MotionSourceKey))
25 | {
26 | FindMotionResources(resourceList, psb.Objects[MotionSourceKey], deDuplication);
27 | }
28 |
29 | resourceList.ForEach(r =>
30 | {
31 | r.PsbType = psb.Type;
32 | r.Spec = psb.Platform;
33 | });
34 |
35 | return resourceList;
36 | }
37 |
38 | public override void Link(PSB psb, FreeMountContext context, IList resPaths, string baseDir = null,
39 | PsbLinkOrderBy order = PsbLinkOrderBy.Convention)
40 | {
41 | LinkImages(psb, context, resPaths, baseDir, order, true);
42 | }
43 |
44 | private static void FindMotionResources(List list, IPsbValue obj, bool deDuplication = true) where T: IResourceMetadata
45 | {
46 | switch (obj)
47 | {
48 | case PsbList c:
49 | c.ForEach(o => FindMotionResources(list, o, deDuplication));
50 | break;
51 | case PsbDictionary d:
52 | if (d[Consts.ResourceKey] is PsbResource r)
53 | {
54 | if (!deDuplication)
55 | {
56 | list.Add((T)(IResourceMetadata)GenerateImageMetadata(d, r));
57 | }
58 | else if (r.Index == null)
59 | {
60 | list.Add((T)(IResourceMetadata)GenerateImageMetadata(d, r));
61 | }
62 | else
63 | {
64 | var imd = GenerateImageMetadata(d, r);
65 | if (imd.Palette == null)
66 | {
67 | if (list.FirstOrDefault(md => md.Index == r.Index.Value) == null)
68 | {
69 | list.Add((T) (IResourceMetadata) imd);
70 | }
71 | }
72 | else
73 | {
74 | if (list.FirstOrDefault(md => md is ImageMetadata i && md.Index == r.Index.Value && i.Palette == imd.Palette) == null)
75 | {
76 | list.Add((T) (IResourceMetadata) imd);
77 | }
78 | }
79 | }
80 | }
81 |
82 | foreach (var o in d.Values)
83 | {
84 | FindMotionResources(list, o, deDuplication);
85 | }
86 |
87 | break;
88 | }
89 | }
90 |
91 | ///
92 | /// Add stub to this PSB
93 | ///
94 | ///
95 | internal static List MotionResourceInstrument(PSB psb)
96 | {
97 | if (!psb.Objects.ContainsKey(MotionSourceKey))
98 | {
99 | return null;
100 | }
101 |
102 | var resources = new List();
103 | GenerateMotionResourceStubs(resources, psb.Objects[MotionSourceKey]);
104 | return resources;
105 | }
106 |
107 | ///
108 | /// Add stubs ( with null Data) into a Motion PSB. A stub must be linked with a texture, or it will be null after
109 | ///
110 | ///
111 | ///
112 | private static void GenerateMotionResourceStubs(List resources, IPsbValue obj)
113 | {
114 | switch (obj)
115 | {
116 | case PsbList c:
117 | c.ForEach(o => GenerateMotionResourceStubs(resources, o));
118 | break;
119 | case PsbDictionary d:
120 | if (d.ContainsKey(Consts.ResourceKey) && (d[Consts.ResourceKey] == null || d[Consts.ResourceKey] is PsbNull))
121 | {
122 | if (d.ContainsKey("width") && d.ContainsKey("height"))
123 | {
124 | //confirmed, add stub
125 | PsbResource res = new PsbResource();
126 | resources.Add(res);
127 | res.Index = (uint)resources.IndexOf(res);
128 | d[Consts.ResourceKey] = res;
129 | }
130 | }
131 |
132 | foreach (var o in d.Values)
133 | {
134 | GenerateMotionResourceStubs(resources, o);
135 | }
136 |
137 | break;
138 | }
139 | }
140 |
141 | public override Dictionary OutputResources(PSB psb, FreeMountContext context, string name, string dirPath,
142 | PsbExtractOption extractOption = PsbExtractOption.Original)
143 | {
144 | return base.OutputResources(psb, context, name, dirPath, extractOption);
145 | }
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/FreeMote.Psb/Types/PimgType.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace FreeMote.Psb.Types
6 | {
7 | class PimgType : BaseImageType, IPsbType
8 | {
9 | public const string PimgSourceKey = "layers";
10 |
11 | public PsbType PsbType => PsbType.Pimg;
12 | public bool IsThisType(PSB psb)
13 | {
14 | if (psb.Objects == null)
15 | {
16 | return false;
17 | }
18 |
19 | if (psb.Objects.ContainsKey("layers") && psb.Objects.ContainsKey("height") && psb.Objects.ContainsKey("width"))
20 | {
21 | return true;
22 | }
23 |
24 | if (psb.Objects.Any(k => k.Key.Contains(".") && k.Value is PsbResource))
25 | {
26 | return true;
27 | }
28 |
29 | return false;
30 | }
31 |
32 | public List CollectResources(PSB psb, bool deDuplication = true) where T : class, IResourceMetadata
33 | {
34 | List resourceList = psb.Resources == null
35 | ? new List()
36 | : new List(psb.Resources.Count);
37 |
38 | resourceList.AddRange(psb.Objects.Where(k => k.Value is PsbResource).Select(k =>
39 | new ImageMetadata()
40 | {
41 | Name = k.Key,
42 | Resource = k.Value as PsbResource,
43 | Compress = k.Key.EndsWith(".tlg", true, null) ? PsbCompressType.Tlg : PsbCompressType.ByName,
44 | PsbType = PsbType.Pimg,
45 | Spec = psb.Platform
46 | }).Cast());
47 | FindPimgResources(resourceList, psb.Objects[PimgSourceKey], deDuplication);
48 |
49 | return resourceList;
50 | }
51 |
52 | private static void FindPimgResources(List list, IPsbValue obj, bool deDuplication = true) where T: IResourceMetadata
53 | {
54 | if (obj is PsbList c)
55 | {
56 | foreach (var o in c)
57 | {
58 | if (!(o is PsbDictionary dic)) continue;
59 | if (dic.ContainsKey("layer_id"))
60 | {
61 | int layerId = 0;
62 | if (dic["layer_id"] is PsbString sLayerId && int.TryParse(sLayerId.Value, out var id))
63 | {
64 | layerId = id;
65 | }
66 | else if (dic["layer_id"] is PsbNumber nLayerId)
67 | {
68 | layerId = nLayerId.IntValue;
69 | }
70 | else
71 | {
72 | Logger.LogWarn($"[WARN] layer_id {dic["layer_id"]} is wrong.");
73 | continue;
74 | }
75 |
76 | var res =
77 | (ImageMetadata) (IResourceMetadata) (list.FirstOrDefault(k => k.Name.StartsWith($"{layerId}.", true, null)) ??
78 | list.FirstOrDefault(k => k.Name.StartsWith($"{layerId}", true, null)));
79 | if (res == null)
80 | {
81 | continue;
82 | }
83 |
84 | res.Index = (uint)layerId;
85 | //res.Part = layerId.ToString();
86 |
87 | if (dic["width"] is PsbNumber nw)
88 | {
89 | res.Width = GetIntValue(nw, res.Width);
90 | }
91 |
92 | if (dic["height"] is PsbNumber nh)
93 | {
94 | res.Height = GetIntValue(nh, res.Height);
95 | }
96 |
97 | if (dic["top"] is PsbNumber nt)
98 | {
99 | res.Top = GetIntValue(nt, res.Top);
100 | }
101 |
102 | if (dic["left"] is PsbNumber nl)
103 | {
104 | res.Left = GetIntValue(nl, res.Left);
105 | }
106 |
107 | if (dic["opacity"] is PsbNumber no)
108 | {
109 | res.Opacity = GetIntValue(no, res.Opacity);
110 | }
111 |
112 | if (dic["group_layer_id"] is PsbNumber gLayerId)
113 | {
114 | res.Part = gLayerId.IntValue.ToString();
115 | }
116 |
117 | if (dic["visible"] is PsbNumber nv)
118 | {
119 | res.Visible = nv.IntValue != 0;
120 | }
121 |
122 | if (dic["name"] is PsbString nn)
123 | {
124 | res.Label = nn.Value;
125 | }
126 |
127 | if (dic["layer_type"] is PsbNumber lt)
128 | {
129 | res.LayerType = lt.IntValue;
130 | }
131 | }
132 | }
133 | }
134 |
135 | int GetIntValue(PsbNumber num, int ori) => deDuplication ? Math.Max((int)num, ori) : (int)num;
136 | }
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/FreeMote.Psb/Types/ScnType.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 |
4 | namespace FreeMote.Psb.Types
5 | {
6 | class ScnType : BaseImageType, IPsbType
7 | {
8 | public PsbType PsbType => PsbType.Scn;
9 | public bool IsThisType(PSB psb)
10 | {
11 | if (psb.Objects == null)
12 | {
13 | return false;
14 | }
15 |
16 | if (psb.Objects.ContainsKey("scenes") && psb.Objects.ContainsKey("name"))
17 | {
18 | return true;
19 | }
20 |
21 | if (psb.Objects.ContainsKey("list") && psb.Objects.ContainsKey("map") && psb.Resources?.Count == 0)
22 | {
23 | return true;
24 | }
25 |
26 | return false;
27 | }
28 |
29 | public List CollectResources(PSB psb, bool deDuplication = true) where T : class, IResourceMetadata
30 | {
31 | List resourceList = psb.Resources == null
32 | ? new List()
33 | : new List(psb.Resources.Count);
34 |
35 | resourceList.AddRange(psb.Objects.Where(k => k.Value is PsbResource).Select(k =>
36 | new ImageMetadata()
37 | {
38 | Name = k.Key,
39 | Resource = k.Value as PsbResource,
40 | Compress = k.Key.EndsWith(".tlg", true, null) ? PsbCompressType.Tlg : PsbCompressType.ByName,
41 | Spec = psb.Platform,
42 | PsbType = PsbType.Scn
43 | }).Cast());
44 |
45 | return resourceList;
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/FreeMote.Purify/readme.md:
--------------------------------------------------------------------------------
1 | # FreeMote.Purify
2 |
3 | A tool lib to *infer* the key of an EMT PSB file. It doesn't need any other file(e.g. DLL), and can do inference without brute force. It can get the key very fast and is universal for most EMT PSB files. It's NOT based on already known keys.
4 |
5 | # Feature
6 | * Support EMT PSB v2-v4 (only works with *EMT* PSB, not for normal PSB)
7 | * Very fast because it's all from inference rather than brute force
8 | * Can handle both header & body(string) encryption
9 |
10 | It's unreleased (in a standalone repo) for now. It implements `IPsbKeyProvider` and can be used as a FreeMote plugin.
11 |
12 | ---
13 | by Ulysses, wdwxy12345@gmail.com
14 |
15 |
16 |
--------------------------------------------------------------------------------
/FreeMote.Tests/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/FreeMote.Tests/FreeMote.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net48
4 | Library
5 | false
6 | default
7 |
8 |
9 | bin\x86\Debug\
10 | default
11 | MinimumRecommendedRules.ruleset
12 |
13 |
14 | bin\x86\Release\
15 | default
16 | MinimumRecommendedRules.ruleset
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | 2.1.0-CI00002
27 |
28 |
29 | 1.1.0
30 |
31 |
32 |
33 | 3.6.0
34 |
35 |
36 | 3.6.0
37 |
38 |
39 | 13.0.3
40 |
41 |
42 | 6.0.0
43 |
44 |
45 | 2.2.1-CI00002
46 |
47 |
48 |
--------------------------------------------------------------------------------
/FreeMote.Tests/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | [assembly: AssemblyTitle("FreeMote.Tests")]
5 | [assembly: AssemblyDescription("Tests for FreeMote.")]
6 | [assembly: AssemblyConfiguration("")]
7 | [assembly: AssemblyCompany("Ulysses")]
8 | [assembly: AssemblyProduct("FreeMote")]
9 | [assembly: AssemblyCopyright("Copyright © Ulysses 2017-2024")]
10 | [assembly: AssemblyTrademark("wdwxy12345@gmail.com")]
11 | [assembly: AssemblyCulture("")]
12 |
13 | [assembly: ComVisible(false)]
14 |
15 | [assembly: Guid("c4077584-00a5-44ce-88a0-1eaf216031ee")]
16 |
17 | // [assembly: AssemblyVersion("1.0.*")]
18 | [assembly: AssemblyVersion("1.0.0.0")]
19 | [assembly: AssemblyFileVersion("1.0.0.0")]
20 |
--------------------------------------------------------------------------------
/FreeMote.Tools.EmtConvert/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/FreeMote.Tools.EmtConvert/FreeMote.Tools.EmtConvert.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net48
4 | Exe
5 | EmtConvert
6 | false
7 | default
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | 3.0.0
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/FreeMote.Tools.EmtConvert/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | [assembly: AssemblyTitle("FreeMote.Tools.EmtConvert")]
5 | [assembly: AssemblyDescription("Convert/Compress/Decompress EMT PSBs and images")]
6 | [assembly: AssemblyConfiguration("")]
7 | [assembly: AssemblyCompany("Ulysses")]
8 | [assembly: AssemblyProduct("FreeMote")]
9 | [assembly: AssemblyCopyright("Copyright © Ulysses 2017-2025")]
10 | [assembly: AssemblyTrademark("wdwxy12345@gmail.com")]
11 | [assembly: AssemblyCulture("")]
12 |
13 | [assembly: ComVisible(false)]
14 |
15 | [assembly: Guid("44270712-4acb-447d-835e-9982bd515dbb")]
16 |
17 | [assembly: AssemblyVersion("4.0.0.0")]
18 | [assembly: AssemblyFileVersion("4.0.0.0")]
19 |
--------------------------------------------------------------------------------
/FreeMote.Tools.EmtMake/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/FreeMote.Tools.EmtMake/FreeMote.Tools.EmtMake.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net48
4 | Exe
5 | EmtMake
6 | false
7 | default
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | 2.3.2
18 |
19 |
20 |
21 | 6.0.0
22 |
23 |
24 |
--------------------------------------------------------------------------------
/FreeMote.Tools.EmtMake/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using FreeMote.Plugins;
4 | using FreeMote.Psb;
5 | using FreeMote.PsBuild;
6 |
7 | namespace FreeMote.Tools.EmtMake
8 | {
9 | class Program
10 | {
11 | static void Main(string[] args)
12 | {
13 | Console.WriteLine("FreeMote MMO Decompiler (Preview)");
14 | Console.WriteLine("by Ulysses, wdwxy12345@gmail.com");
15 | Logger.InitConsole();
16 | FreeMount.Init();
17 | Console.WriteLine();
18 | Console.WriteLine("This is a preview version. If it crashes, send the PSB to me.");
19 | Console.WriteLine("All output files from this tool should follow [CC BY-NC-SA 4.0] license. Agree this license by pressing Enter:");
20 | Console.ReadLine();
21 | if (args.Length < 1 || !File.Exists(args[0]))
22 | {
23 | PrintHelp();
24 | return;
25 | }
26 |
27 | PSB psb = null;
28 | try
29 | {
30 | psb = new PSB(args[0]);
31 | }
32 | catch (Exception e)
33 | {
34 | Logger.LogError($"Input PSB is invalid: {e.Message}");
35 | }
36 |
37 | if (psb != null)
38 | {
39 | psb.FixMotionMetadata(); //Fix for partial exported PSB
40 | if (psb.Platform != PsbSpec.krkr)
41 | {
42 | if (psb.Platform == PsbSpec.common || psb.Platform == PsbSpec.win)
43 | {
44 | psb.SwitchSpec(PsbSpec.krkr);
45 | psb.Merge();
46 | }
47 | else
48 | {
49 | Console.WriteLine(
50 | $"EmtMake do not support {psb.Platform} PSB. Please use pure krkr PSB.");
51 | goto END;
52 | }
53 | }
54 | #if !DEBUG
55 | try
56 | #endif
57 | {
58 | MmoBuilder builder = new MmoBuilder();
59 | var output = builder.Build(psb);
60 | output.Merge();
61 | File.WriteAllBytes(Path.ChangeExtension(args[0], ".FreeMote.mmo"), output.Build());
62 | }
63 | #if !DEBUG
64 | catch (Exception e)
65 | {
66 | Console.WriteLine(e);
67 | }
68 | #endif
69 | }
70 |
71 | END:
72 | Console.WriteLine("Done.");
73 | Console.ReadLine();
74 | }
75 |
76 | private static void PrintHelp()
77 | {
78 | Console.WriteLine("Usage: .exe ");
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/FreeMote.Tools.EmtMake/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | [assembly: AssemblyTitle("FreeMote.Tools.EmtMake")]
5 | [assembly: AssemblyDescription("MMO/Emtproj Maker")]
6 | [assembly: AssemblyConfiguration("")]
7 | [assembly: AssemblyCompany("Ulysses")]
8 | [assembly: AssemblyProduct("FreeMote")]
9 | [assembly: AssemblyCopyright("Copyright © Ulysses 2018-2025")]
10 | [assembly: AssemblyTrademark("wdwxy12345@gmail.com")]
11 | [assembly: AssemblyCulture("")]
12 |
13 | [assembly: ComVisible(false)]
14 |
15 | [assembly: Guid("bd8855cf-0b99-43b2-8b92-4013e9a0c41c")]
16 |
17 | // [assembly: AssemblyVersion("1.0.*")]
18 | [assembly: AssemblyVersion("4.0.0.0")]
19 | [assembly: AssemblyFileVersion("4.0.0.0")]
--------------------------------------------------------------------------------
/FreeMote.Tools.PsBuild/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/FreeMote.Tools.PsBuild/FreeMote.Tools.PsBuild.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net48
4 | Exe
5 | PsBuild
6 | false
7 | default
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | 3.0.0
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/FreeMote.Tools.PsBuild/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | [assembly: AssemblyTitle("FreeMote.Tools.PsBuild")]
5 | [assembly: AssemblyDescription("PSB Compiler")]
6 | [assembly: AssemblyConfiguration("")]
7 | [assembly: AssemblyCompany("Ulysses")]
8 | [assembly: AssemblyProduct("FreeMote")]
9 | [assembly: AssemblyCopyright("Copyright © Ulysses 2017-2025")]
10 | [assembly: AssemblyTrademark("wdwxy12345@gmail.com")]
11 | [assembly: AssemblyCulture("")]
12 |
13 | [assembly: ComVisible(false)]
14 |
15 | [assembly: Guid("d877dbdb-41d7-4c6f-9a46-957cecdaefa8")]
16 |
17 | [assembly: AssemblyVersion("4.0.2.0")]
18 | [assembly: AssemblyFileVersion("4.0.2.0")]
19 |
--------------------------------------------------------------------------------
/FreeMote.Tools.PsbDecompile/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/FreeMote.Tools.PsbDecompile/FreeMote.Tools.PsbDecompile.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net48
4 | Exe
5 | PsbDecompile
6 | false
7 | default
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | 3.0.0
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/FreeMote.Tools.PsbDecompile/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | [assembly: AssemblyTitle("FreeMote.Tools.PsbDecompile")]
5 | [assembly: AssemblyDescription("PSB Decompiler")]
6 | [assembly: AssemblyConfiguration("")]
7 | [assembly: AssemblyCompany("Ulysses")]
8 | [assembly: AssemblyProduct("FreeMote")]
9 | [assembly: AssemblyCopyright("Copyright © Ulysses 2017-2025")]
10 | [assembly: AssemblyTrademark("wdwxy12345@gmail.com")]
11 | [assembly: AssemblyCulture("")]
12 |
13 | [assembly: ComVisible(false)]
14 |
15 | [assembly: Guid("943fe440-32bb-4caf-a1da-5f38de7f9b92")]
16 |
17 | [assembly: AssemblyVersion("4.0.2.0")]
18 | [assembly: AssemblyFileVersion("4.0.2.0")]
19 |
--------------------------------------------------------------------------------
/FreeMote.Tools.Viewer/App.xaml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/FreeMote.Tools.Viewer/FreeMote.Tools.Viewer.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net48
4 | WinExe
5 | FreeMoteViewer
6 | false
7 | true
8 | true
9 | default
10 | true
11 |
12 |
13 | true
14 |
15 |
16 |
17 |
18 | FreeMote.Tools.Viewer.Program
19 | x64
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | 3.0.0
28 |
29 |
30 |
31 | 6.0.0
32 |
33 |
34 | 4.5.0
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/FreeMote.Tools.Viewer/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | Powered by FreeMote
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/FreeMote.Tools.Viewer/PreciseTimer.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.InteropServices;
2 |
3 | namespace FreeMote.Tools.Viewer
4 | {
5 | ///
6 | /// 精确计时器类
7 | ///
8 | public class PreciseTimer
9 | {
10 | [System.Security.SuppressUnmanagedCodeSecurity]
11 | [DllImport("kernel32")]
12 | private static extern bool QueryPerformanceFrequency(ref long PerformanceFrequency);
13 | [System.Security.SuppressUnmanagedCodeSecurity]
14 | [DllImport("kernel32")]
15 | private static extern bool QueryPerformanceCounter(ref long PerformanceCount);
16 | long _ticksPerSecond = 0;
17 | long _previousElapsedTime = 0;
18 | public PreciseTimer()
19 | {
20 | QueryPerformanceFrequency(ref _ticksPerSecond);
21 | GetElaspedTime();//Get rid of first rubbish result
22 | }
23 |
24 | public double GetElaspedTime()
25 | {
26 | long time = 0;
27 | QueryPerformanceCounter(ref time);
28 | double elapsedTime = (double)(time - _previousElapsedTime) / (double)_ticksPerSecond;
29 | _previousElapsedTime = time;
30 | return elapsedTime;
31 | }
32 | //QueryPerformanceFrequency用于获取高分辨率性能计时器的频率。
33 | //QueryPerformanceCounter用于获取计时器的当前值。
34 | //结合起来确定最后一帧用了多长时间。
35 | //GetElaspedTime应该每帧调用一次,它会记录帧之间经过的时间。
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/FreeMote.Tools.Viewer/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 | using System.Windows;
4 |
5 | [assembly: AssemblyTitle("FreeMote Viewer")]
6 | [assembly: AssemblyDescription("EMT PSB models Viewer")]
7 | [assembly: AssemblyConfiguration("")]
8 | [assembly: AssemblyCompany("Ulysses")]
9 | [assembly: AssemblyProduct("FreeMote")]
10 | [assembly: AssemblyCopyright("Copyright © Ulysses 2015-2025")]
11 | [assembly: AssemblyTrademark("wdwxy12345@gmail.com")]
12 | [assembly: AssemblyCulture("")]
13 |
14 | // 将 ComVisible 设置为 false 使此程序集中的类型
15 | // 对 COM 组件不可见。 如果需要从 COM 访问此程序集中的类型,
16 | // 则将该类型上的 ComVisible 特性设置为 true。
17 | [assembly: ComVisible(false)]
18 |
19 | //若要开始生成可本地化的应用程序,请在
20 | // 中的 .csproj 文件中
21 | //设置 CultureYouAreCodingWith。 例如,如果您在源文件中
22 | //使用的是美国英语,请将 设置为 en-US。 然后取消
23 | //对以下 NeutralResourceLanguage 特性的注释。 更新
24 | //以下行中的“en-US”以匹配项目文件中的 UICulture 设置。
25 |
26 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
27 |
28 |
29 | [assembly: ThemeInfo(
30 | ResourceDictionaryLocation.None, //主题特定资源词典所处位置
31 | //(在页面或应用程序资源词典中
32 | // 未找到某个资源的情况下使用)
33 | ResourceDictionaryLocation.SourceAssembly //常规资源词典所处位置
34 | //(在页面、应用程序或任何主题特定资源词典中
35 | // 未找到某个资源的情况下使用)
36 | )]
37 |
38 |
39 | // 程序集的版本信息由下面四个值组成:
40 | //
41 | // 主版本
42 | // 次版本
43 | // 生成号
44 | // 修订号
45 | //
46 | // 可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值,
47 | // 方法是按如下所示使用“*”:
48 | // [assembly: AssemblyVersion("1.0.*")]
49 | [assembly: AssemblyVersion("4.0.0.0")]
50 | [assembly: AssemblyFileVersion("4.0.0.0")]
51 |
--------------------------------------------------------------------------------
/FreeMote.Tools.Viewer/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // 此代码由工具生成。
4 | // 运行时版本:4.0.30319.42000
5 | //
6 | // 对此文件的更改可能会导致不正确的行为,并且如果
7 | // 重新生成代码,这些更改将会丢失。
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace FreeMote.Tools.Viewer.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// 一个强类型的资源类,用于查找本地化的字符串等。
17 | ///
18 | // 此类是由 StronglyTypedResourceBuilder
19 | // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
20 | // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
21 | // (以 /str 作为命令选项),或重新生成 VS 项目。
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// 返回此类使用的缓存的 ResourceManager 实例。
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("FreeMote.Tools.Viewer.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// 重写当前线程的 CurrentUICulture 属性,对
51 | /// 使用此强类型资源类的所有资源查找执行重写。
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/FreeMote.Tools.Viewer/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | text/microsoft-resx
107 |
108 |
109 | 2.0
110 |
111 |
112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
113 |
114 |
115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
--------------------------------------------------------------------------------
/FreeMote.Tools.Viewer/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // 此代码由工具生成。
4 | // 运行时版本:4.0.30319.42000
5 | //
6 | // 对此文件的更改可能会导致不正确的行为,并且如果
7 | // 重新生成代码,这些更改将会丢失。
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace FreeMote.Tools.Viewer.Properties {
12 |
13 |
14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.2.0.0")]
16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
17 |
18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
19 |
20 | public static Settings Default {
21 | get {
22 | return defaultInstance;
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/FreeMote.Tools.Viewer/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/FreeMote.Tools.Viewer/app.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/FreeMote.sln.DotSettings:
--------------------------------------------------------------------------------
1 |
2 | OK
3 | RL
4 | UTF
5 | True
6 | True
7 | True
8 | True
9 | True
10 | True
11 | True
12 | True
13 | True
14 | True
15 | True
16 | True
17 | True
18 | True
19 | True
20 | True
21 | True
22 | True
23 | True
24 | True
25 | True
26 | True
27 | True
28 | True
29 | True
30 | True
31 | True
32 | True
33 | True
34 | True
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/FreeMote/AstcFile.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 |
5 | namespace FreeMote
6 | {
7 | ///
8 | /// AstcHeader - 16 bytes
9 | ///
10 | public class AstcHeader
11 | {
12 | public static readonly byte[] Magic = {0x13, 0xAB, 0xA1, 0x5C};
13 | public const int Length = 16;
14 |
15 | public byte[] Data { get; }
16 |
17 | public AstcHeader()
18 | {
19 | Data = new byte[16];
20 | }
21 |
22 | public AstcHeader(byte[] data)
23 | {
24 | Data = data;
25 | }
26 |
27 | public Span Signature
28 | {
29 | get => Data.AsSpan(0, 4);
30 | set => value.CopyTo(Data);
31 | }
32 |
33 | public byte BlockX
34 | {
35 | get => Data[4];
36 | set => Data[4] = value;
37 | }
38 |
39 | public byte BlockY
40 | {
41 | get => Data[5];
42 | set => Data[5] = value;
43 | }
44 |
45 | public byte BlockZ
46 | {
47 | get => Data[6];
48 | set => Data[6] = value;
49 | }
50 |
51 | public Span DimX
52 | {
53 | get => Data.AsSpan().Slice(7, 3);
54 | set => value.CopyTo(Data.AsSpan(7, 3));
55 | }
56 |
57 | public Span DimY {
58 | get => Data.AsSpan().Slice(10, 3);
59 | set => value.CopyTo(Data.AsSpan(10, 3));
60 | }
61 | public Span DimZ {
62 | get => Data.AsSpan().Slice(13, 3);
63 | set => value.CopyTo(Data.AsSpan(13, 3));
64 | }
65 |
66 | public int Width
67 | {
68 | get => DimX[0] << 16 | DimX[1] << 8 | DimX[2];
69 | set => BitConverter.GetBytes(value).AsSpan(0, 3).CopyTo(Data.AsSpan(7, 3));
70 | }
71 |
72 | public int Height
73 | {
74 | get => DimY[0] << 16 | DimY[1] << 8 | DimY[2];
75 | set => BitConverter.GetBytes(value).AsSpan(0, 3).CopyTo(Data.AsSpan(10, 3));
76 | }
77 |
78 | public int Depth
79 | {
80 | get => DimZ[0] << 16 | DimZ[1] << 8 | DimZ[2];
81 | set => BitConverter.GetBytes(value).AsSpan(0, 3).CopyTo(Data.AsSpan(13, 3));
82 | }
83 | }
84 |
85 | public static class AstcFile
86 | {
87 | public static AstcHeader ParseAstcHeader(Stream stream)
88 | {
89 | var pos = stream.Position;
90 | if (stream.Length - stream.Position < 16)
91 | {
92 | return null;
93 | }
94 |
95 | byte[] bytes = new byte[16];
96 | stream.Read(bytes, 0, 16);
97 | stream.Position = pos;
98 |
99 | if (bytes[0] == AstcHeader.Magic[0] && bytes[1] == AstcHeader.Magic[1] && bytes[2] == AstcHeader.Magic[2] && bytes[3] == AstcHeader.Magic[3])
100 | {
101 | return new AstcHeader(bytes);
102 | }
103 |
104 | return null;
105 | }
106 |
107 | public static AstcHeader ParseAstcHeader(BinaryReader br)
108 | {
109 | var stream = br.BaseStream;
110 | var pos = stream.Position;
111 | if (stream.Length - stream.Position < 16)
112 | {
113 | return null;
114 | }
115 |
116 | var bytes = br.ReadBytes(16);
117 | stream.Position = pos;
118 |
119 | if (bytes[0] == AstcHeader.Magic[0] && bytes[1] == AstcHeader.Magic[1] && bytes[2] == AstcHeader.Magic[2] && bytes[3] == AstcHeader.Magic[3])
120 | {
121 | return new AstcHeader(bytes);
122 | }
123 |
124 | return null;
125 | }
126 |
127 | public static AstcHeader ParseAstcHeader(byte[] bytes)
128 | {
129 | if (IsAstcHeader(bytes))
130 | {
131 | return new AstcHeader(bytes.Take(16).ToArray());
132 | }
133 |
134 | return null;
135 | }
136 |
137 | public static bool IsAstcHeader(in byte[] bytes)
138 | {
139 | if (bytes.Length > 16 && bytes.Take(4).SequenceEqual(AstcHeader.Magic))
140 | {
141 | return true;
142 | }
143 |
144 | return false;
145 | }
146 |
147 | public static bool IsAstcHeader(BinaryReader br)
148 | {
149 | var stream = br.BaseStream;
150 | var pos = stream.Position;
151 | if (stream.Length - stream.Position < 16)
152 | {
153 | return false;
154 | }
155 |
156 | var bytes = br.ReadBytes(4);
157 | stream.Position = pos;
158 |
159 | return bytes.SequenceEqual(AstcHeader.Magic);
160 | }
161 |
162 | public static bool IsAstcHeader(Stream stream)
163 | {
164 | var pos = stream.Position;
165 | if (stream.Length - stream.Position < 16)
166 | {
167 | return false;
168 | }
169 |
170 | byte[] bytes = new byte[4];
171 | stream.Read(bytes, 0, 4);
172 | stream.Position = pos;
173 |
174 | return bytes.SequenceEqual(AstcHeader.Magic);
175 | }
176 |
177 | public static byte[] CutHeader(byte[] bts)
178 | {
179 | return IsAstcHeader(bts) ? bts.AsSpan(AstcHeader.Length).ToArray() : bts;
180 | }
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/FreeMote/BitmapExtension.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Drawing;
3 | using System.Drawing.Drawing2D;
4 | using System.Drawing.Imaging;
5 |
6 | namespace FreeMote
7 | {
8 | public static class BitmapExtension
9 | {
10 | ///
11 | /// Copies a region of the source bitmap into this fast bitmap
12 | ///
13 | /// The image which applies to
14 | /// The source image to copy
15 | /// The region on the source bitmap that will be copied over
16 | /// The region on this fast bitmap that will be changed
17 | /// The provided source bitmap is the same bitmap locked in this FastBitmap
18 | public static void CopyRegion(this Bitmap target, Bitmap source, Rectangle srcRect, Rectangle destRect)
19 | {
20 | if (srcRect.Width != destRect.Width || srcRect.Height != destRect.Height)
21 | {
22 | throw new ArgumentException("Source and destination rectangles must have the same dimensions.");
23 | }
24 |
25 | var srcData = source.LockBits(srcRect, ImageLockMode.ReadOnly, source.PixelFormat);
26 | var destData = target.LockBits(destRect, ImageLockMode.WriteOnly, target.PixelFormat);
27 |
28 | try
29 | {
30 | unsafe
31 | {
32 | byte* srcPtr = (byte*)srcData.Scan0;
33 | byte* destPtr = (byte*)destData.Scan0;
34 |
35 | int bytesPerPixel = Image.GetPixelFormatSize(source.PixelFormat) / 8;
36 | int stride = srcData.Stride;
37 | int height = srcRect.Height;
38 | int widthInBytes = srcRect.Width * bytesPerPixel;
39 |
40 | for (int y = 0; y < height; y++)
41 | {
42 | byte* srcRow = srcPtr + (y * stride);
43 | byte* destRow = destPtr + (y * destData.Stride);
44 |
45 | for (int x = 0; x < widthInBytes; x++)
46 | {
47 | destRow[x] = srcRow[x];
48 | }
49 | }
50 | }
51 | }
52 | finally
53 | {
54 | source.UnlockBits(srcData);
55 | target.UnlockBits(destData);
56 | }
57 | }
58 |
59 | ///
60 | /// Resize the image to the specified width and height.
61 | ///
62 | /// The image to resize.
63 | /// The width to resize to.
64 | /// The height to resize to.
65 | /// The resized image.
66 | /// https://stackoverflow.com/a/24199315/4374462
67 | public static Bitmap ResizeImage(this Image image, int width, int height)
68 | {
69 | var destRect = new Rectangle(0, 0, width, height);
70 | var destImage = new Bitmap(width, height);
71 |
72 | destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
73 |
74 | using (var graphics = Graphics.FromImage(destImage))
75 | {
76 | graphics.CompositingMode = CompositingMode.SourceCopy;
77 | graphics.CompositingQuality = CompositingQuality.HighQuality;
78 | graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
79 | graphics.SmoothingMode = SmoothingMode.HighQuality; // = SmoothingMode.AntiAlias
80 | graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
81 |
82 | using (var wrapMode = new ImageAttributes())
83 | {
84 | wrapMode.SetWrapMode(WrapMode.TileFlipXY);
85 | graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);
86 | }
87 | }
88 |
89 | return destImage;
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/FreeMote/FreeMote.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net48
4 | Library
5 | false
6 |
7 |
8 | true
9 | default
10 |
11 |
12 |
13 | true
14 | default
15 | bin\Release\FreeMote.xml
16 | 1591
17 |
18 |
19 |
20 |
21 |
22 |
23 | PreserveNewest
24 |
25 |
26 |
27 |
28 | 2.3.2
29 |
30 |
31 | 4.5.5
32 |
33 |
34 | 6.0.0
35 |
36 |
37 |
--------------------------------------------------------------------------------
/FreeMote/Motion/MmoEnums.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace FreeMote.Motion
4 | {
5 | [Flags]
6 | public enum TexAttribute: ushort
7 | {
8 | None = 0,
9 | Translucent = 1,
10 | Opaque = 2,
11 | NoDiffuse = 4
12 | }
13 |
14 | [Flags]
15 | public enum MeshSyncChild : ushort
16 | {
17 | None = 0,
18 | Coord = 1,
19 | Angle = 2,
20 | Zoom = 4,
21 | Shape = 8
22 | }
23 |
24 | public enum MeshTransform : short
25 | {
26 | None = 0,
27 | BezierPatch = 1,
28 | Composite = 2
29 | }
30 |
31 | //it's a god-damn krkr enum
32 | internal enum TimelineFrameType: short
33 | {
34 | Null = 0,
35 | Single = 1,
36 | Continuous = 2,
37 | Tween = 3
38 | }
39 |
40 |
41 | // "///^(透過表示|ビュー|レイアウト|レイアウト角度|角度|XY座標|XY座標角度|Z座標|メッシュ|パーティクル|削除|ブレンドモード)\\((.+)\\)$"
42 | // "///^(Transparent|View|Layout|Layout角度|角度|XY座標|XY座標角度|Z座標|Mesh|Particle|Remove|BlendMode)\\((.+)\\)$"
43 | // Some parameters are compile-time constants (BlendMode, Remove etc), it's impossible to recover them if they were default value when compiling
44 | [Flags]
45 | public enum MmoFrameMask
46 | {
47 | //fx,fy, (flip) sx, sy, scc (slant), ccc (coord)
48 | //Low to High:
49 | //0: ox,oy
50 | //1: coord
51 | //2,3: fx, fy (must set both)
52 | //4: angle
53 | //5,6: zx,zy
54 | //7,8: sx,xy
55 | //9: color
56 | //10: opa
57 | //11: ccc
58 | //12: acc (angle)
59 | //13: zcc
60 | //14: scc
61 | //15: occ
62 | //16: ???
63 | //17: bm
64 | //19: motion/timeOffset?
65 | //20: particle
66 | //25: mesh
67 | None = 0,
68 |
69 | ///
70 | /// ox, oy
71 | ///
72 | Origin = 1,
73 |
74 | ///
75 | /// coord
76 | ///
77 | Coord = 0b10,
78 |
79 | ///
80 | /// angle
81 | ///
82 | Angle = 0b10000,
83 |
84 | ///
85 | /// zx
86 | ///
87 | ZoomX = 0b100000,
88 |
89 | ///
90 | /// zy
91 | ///
92 | ZoomY = 0b1000000,
93 |
94 | ///
95 | /// color
96 | ///
97 | Color = 0b1000000000,
98 |
99 | ///
100 | /// opa
101 | ///
102 | Opacity = 0b10000000000,
103 |
104 | ///
105 | /// ccc
106 | ///
107 | CoordCurve = 0b10_0000000000,
108 |
109 | ///
110 | /// acc
111 | ///
112 | AngleCurve = 0b100_0000000000,
113 |
114 | ///
115 | /// zcc
116 | ///
117 | ZoomCurve = 0b1000_0000000000,
118 |
119 | ///
120 | /// scc
121 | ///
122 | SlantCurve = 0b10000_0000000000,
123 |
124 | ///
125 | /// occ (color cc)
126 | ///
127 | OpacityCurve = 0b100000_0000000000,
128 |
129 | ///
130 | /// bm
131 | ///
132 | BlendMode = 0b10000000_0000000000,
133 |
134 | ///
135 | /// motion
136 | ///
137 | Motion = 0b1000000000_0000000000,
138 |
139 | ///
140 | /// prt
141 | ///
142 | Particle = 0b1_0000000000_0000000000,
143 |
144 | ///
145 | /// mesh
146 | ///
147 | Mesh = 0b100000_0000000000_0000000000,
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/FreeMote/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | [assembly: AssemblyTitle("FreeMote")]
6 | [assembly: AssemblyDescription("Managed EMT/PSB Tool Library")]
7 | [assembly: AssemblyConfiguration("")]
8 | [assembly: AssemblyCompany("Ulysses")]
9 | [assembly: AssemblyProduct("FreeMote")]
10 | [assembly: AssemblyCopyright("Copyright © Ulysses 2017-2025")]
11 | [assembly: AssemblyTrademark("wdwxy12345@gmail.com")]
12 | [assembly: AssemblyCulture("")]
13 |
14 | [assembly: ComVisible(false)]
15 |
16 | [assembly: Guid("d43ca425-6476-4ae3-a3d8-bbcac0f0383c")]
17 | [assembly: InternalsVisibleTo("FreeMote.PsBuild")]
18 | [assembly: InternalsVisibleTo("FreeMote.Purify")]
19 | [assembly: InternalsVisibleTo("FreeMote.Psb")]
20 | [assembly: InternalsVisibleTo("FreeMote.Plugins")]
21 | [assembly: InternalsVisibleTo("FreeMote.Tests")]
22 | [assembly: InternalsVisibleTo("PsbDecompile")]
23 | [assembly: InternalsVisibleTo("PsBuild")]
24 | [assembly: InternalsVisibleTo("EmtConvert")]
25 | [assembly: InternalsVisibleTo("EmtMake")]
26 |
27 | // [assembly: AssemblyVersion("1.0.*")]
28 | [assembly: AssemblyVersion("4.0.0.0")]
29 | [assembly: AssemblyFileVersion("4.0.0.0")]
30 |
--------------------------------------------------------------------------------
/FreeMote/PsbStreamContext.cs:
--------------------------------------------------------------------------------
1 | namespace FreeMote
2 | {
3 | ///
4 | /// PSB Stream Cipher Context (XorShift128)
5 | ///
6 | public class PsbStreamContext
7 | {
8 | ///
9 | /// Key 1
10 | /// Usually you shouldn't modify it.
11 | ///
12 | public uint Key1 { get; set; }
13 | ///
14 | /// Key 2
15 | /// Usually you shouldn't modify it.
16 | ///
17 | public uint Key2 { get; set; }
18 | ///
19 | /// Key 3
20 | /// Usually you shouldn't modify it.
21 | ///
22 | public uint Key3 { get; set; }
23 | ///
24 | /// Key 4
25 | /// This is the key which differs among versions.
26 | ///
27 | public uint Key4 { get; set; }
28 |
29 | ///
30 | /// Current Key
31 | ///
32 | public uint CurrentKey { get; internal set; } = 0;
33 | ///
34 | /// Stream Round
35 | ///
36 | public uint Round { get; internal set; }
37 | ///
38 | /// Round Count in byte
39 | ///
40 | public ulong ByteCount { get; internal set; }
41 |
42 | public PsbStreamContext()
43 | {
44 | Init();
45 | }
46 |
47 | public PsbStreamContext(uint key)
48 | {
49 | Init();
50 | Key4 = key;
51 | }
52 |
53 | public void Init()
54 | {
55 | Round = 0;
56 | ByteCount = 0;
57 | Key1 = Consts.Key1;
58 | Key2 = Consts.Key2;
59 | Key3 = Consts.Key3;
60 | CurrentKey = 0;
61 | }
62 |
63 | ///
64 | /// Encode bytes using stream cipher.
65 | /// Every time called, the key might be modified.
66 | ///
67 | ///
68 | ///
69 | public ref byte[] Encode(ref byte[] input)
70 | {
71 | for (int i = 0; i < input.Length; i++)
72 | {
73 | if (CurrentKey == 0)
74 | {
75 | var a = Key1 ^ (Key1 << 11);
76 | var b = Key4;
77 | var c = a ^ b ^ ((a ^ (b >> 11)) >> 8);
78 | Key1 = Key2;
79 | Key2 = Key3;
80 | Key3 = b;
81 | Key4 = c;
82 | CurrentKey = c;
83 | Round++;
84 | }
85 | input[i] ^= (byte) CurrentKey;
86 | CurrentKey = CurrentKey >> 8;
87 | ByteCount++;
88 | }
89 | return ref input;
90 | }
91 |
92 | ///
93 | /// Encode bytes using stream cipher.
94 | /// Every time called, the key might be modified.
95 | ///
96 | ///
97 | ///
98 | public byte[] Encode(byte[] input)
99 | {
100 | for (int i = 0; i < input.Length; i++)
101 | {
102 | if (CurrentKey == 0)
103 | {
104 | var a = Key1 ^ (Key1 << 11);
105 | var b = Key4;
106 | var c = a ^ b ^ ((a ^ (b >> 11)) >> 8);
107 | Key1 = Key2;
108 | Key2 = Key3;
109 | Key3 = b;
110 | Key4 = c;
111 | CurrentKey = c;
112 | Round++;
113 | }
114 | input[i] ^= (byte)CurrentKey;
115 | CurrentKey = CurrentKey >> 8;
116 | ByteCount++;
117 | }
118 | return input;
119 | }
120 |
121 | ///
122 | /// Skip some bytes
123 | ///
124 | ///
125 | public void FastForward(uint byteLength)
126 | {
127 | for (int i = 0; i < byteLength; i++)
128 | {
129 | if (CurrentKey == 0)
130 | {
131 | var a = Key1 ^ (Key1 << 11);
132 | var b = Key4;
133 | var c = a ^ b ^ ((a ^ (b >> 11)) >> 8);
134 | Key1 = Key2;
135 | Key2 = Key3;
136 | Key3 = b;
137 | Key4 = c;
138 | CurrentKey = c;
139 | Round++;
140 | }
141 | CurrentKey = CurrentKey >> 1;
142 | ByteCount++;
143 | }
144 | }
145 |
146 | ///
147 | /// Skip to next round
148 | ///
149 | public uint NextRound()
150 | {
151 | while (CurrentKey != 0)
152 | {
153 | CurrentKey = CurrentKey >> 1;
154 | ByteCount++;
155 | }
156 | var a = Key1 ^ (Key1 << 11);
157 | var b = Key4;
158 | var c = a ^ b ^ ((a ^ (b >> 11)) >> 8);
159 | Key1 = Key2;
160 | Key2 = Key3;
161 | Key3 = b;
162 | Key4 = c;
163 | CurrentKey = c;
164 | Round++;
165 | return CurrentKey;
166 | }
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/FreeMote/ZlibCompress.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.IO.Compression;
3 |
4 | namespace FreeMote
5 | {
6 | public static class ZlibCompress
7 | {
8 | public static byte[] Decompress(Stream input)
9 | {
10 | using (var ms = Consts.MsManager.GetStream())
11 | {
12 | using (DeflateStream deflateStream = new DeflateStream(input, CompressionMode.Decompress))
13 | {
14 | deflateStream.CopyTo(ms);
15 | }
16 |
17 | //input.Dispose();
18 | return ms.ToArray();
19 | }
20 | }
21 |
22 | ///
23 | /// [RequireUsing]
24 | ///
25 | ///
26 | ///
27 | ///
28 | public static Stream DecompressToStream(Stream input, int size = 0)
29 | {
30 | MemoryStream ms = size <= 0
31 | ? Consts.MsManager.GetStream()
32 | : Consts.MsManager.GetStream("DecompressToStream", size);
33 | //var ms = Consts.MsManager.GetStream();
34 | using (DeflateStream deflateStream = new DeflateStream(input, CompressionMode.Decompress))
35 | {
36 | deflateStream.CopyTo(ms);
37 | }
38 |
39 | //input.Dispose();
40 | ms.Seek(0, SeekOrigin.Begin);
41 | return ms;
42 | }
43 |
44 | public static byte[] Compress(Stream input, bool fast = false)
45 | {
46 | using var ms = new MemoryStream();
47 | ms.WriteByte(0x78);
48 | ms.WriteByte((byte) (fast ? 0x9C : 0xDA));
49 | using (DeflateStream deflateStream =
50 | new DeflateStream(ms, Consts.ForceCompressionLevel != null ? Consts.ForceCompressionLevel.Value :
51 | fast ? CompressionLevel.Fastest : CompressionLevel.Optimal))
52 | {
53 | input.CopyTo(deflateStream);
54 | }
55 |
56 | //input.Dispose();
57 | return ms.GetBuffer();
58 | }
59 |
60 | public static void CompressToBinaryWriter(BinaryWriter bw, Stream input, bool fast = false)
61 | {
62 | using var output = CompressToStream(input, fast);
63 | output.CopyTo(bw.BaseStream);
64 | }
65 |
66 | ///
67 | /// Will NOT dispose input stream
68 | ///
69 | ///
70 | ///
71 | ///
72 | public static MemoryStream CompressToStream(Stream input, bool fast = false)
73 | {
74 | MemoryStream ms = new MemoryStream();
75 | ms.WriteByte(0x78);
76 | ms.WriteByte((byte) (fast ? 0x9C : 0xDA));
77 | using (DeflateStream deflateStream =
78 | new DeflateStream(ms,
79 | Consts.ForceCompressionLevel != null ? Consts.ForceCompressionLevel.Value :
80 | fast ? CompressionLevel.Fastest : CompressionLevel.Optimal, true))
81 | {
82 | input.CopyTo(deflateStream);
83 | }
84 |
85 | //input.Dispose(); //DO NOT dispose
86 | ms.Position = 0;
87 | return ms;
88 | }
89 | }
90 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
--------------------------------------------------------------------------------