├── .gitignore
├── LICENSE.txt
├── LibTextPet
├── CustomDictionary.xml
├── General
│ ├── ByteSequenceEqualityComparer.cs
│ ├── GameInfo.cs
│ ├── IDefined.cs
│ ├── IDefinition.cs
│ ├── INameable.cs
│ ├── LZ77.cs
│ ├── LookupTree.cs
│ ├── LookupTreeNode.cs
│ ├── LookupTreePath.cs
│ ├── MaskedByte.cs
│ ├── NamedCollection.cs
│ ├── NumberParser.cs
│ ├── ReadOnlyKeyedCollection.cs
│ └── ReadOnlyNamedCollection.cs
├── GlobalSuppressions.cs
├── IO
│ ├── FileIndexEntry.cs
│ ├── FileIndexEntryCollection.cs
│ ├── FileIndexReader.cs
│ ├── FileIndexWriter.cs
│ ├── IPatcher.cs
│ ├── IReader.cs
│ ├── IWriter.cs
│ ├── MSG
│ │ ├── BinaryCommandReader.cs
│ │ ├── BinaryCommandWriter.cs
│ │ ├── BinaryScriptReader.cs
│ │ ├── BinaryScriptWriter.cs
│ │ ├── BinaryTextArchiveReader.cs
│ │ ├── BinaryTextArchiveWriter.cs
│ │ ├── FileManager.cs
│ │ ├── FileTextArchiveReader.cs
│ │ ├── FileTextArchiveWriter.cs
│ │ ├── FixedSizeScriptReader.cs
│ │ └── ScriptEntry.cs
│ ├── Manager.cs
│ ├── ScriptReader.cs
│ ├── ScriptTextBoxPatcher.cs
│ ├── ScriptWriter.cs
│ ├── SingleManager.cs
│ ├── TPL
│ │ ├── IndentedWriter.cs
│ │ ├── TPLCommandReader.cs
│ │ ├── TPLCommandWriter.cs
│ │ ├── TPLReader.cs
│ │ ├── TPLScriptReader.cs
│ │ ├── TPLScriptWriter.cs
│ │ ├── TPLTextArchiveReader.cs
│ │ ├── TPLTextArchiveWriter.cs
│ │ └── TPLTokenType.cs
│ ├── TextArchiveTextBoxPatcher.cs
│ ├── TextBox
│ │ ├── TextBoxScriptTemplateReader.cs
│ │ ├── TextBoxScriptWriter.cs
│ │ ├── TextBoxTemplateReader.cs
│ │ ├── TextBoxTextArchiveTemplateReader.cs
│ │ ├── TextBoxTextArchiveWriter.cs
│ │ └── TextBoxTokenType.cs
│ ├── Token.cs
│ └── TokenReader.cs
├── LibTextPet.csproj
├── Msg
│ ├── ByteElement.cs
│ ├── Command.cs
│ ├── CommandDatabase.cs
│ ├── CommandDefinition.cs
│ ├── CommandElement.cs
│ ├── CommandElementDefinition.cs
│ ├── DirectiveElement.cs
│ ├── DirectiveType.cs
│ ├── EndType.cs
│ ├── IScriptElement.cs
│ ├── OffsetType.cs
│ ├── Parameter.cs
│ ├── ParameterDefinition.cs
│ ├── Script.cs
│ ├── StringLengthUnit.cs
│ ├── StringSubDefinition.cs
│ ├── TextArchive.cs
│ └── TextElement.cs
├── Plugins
│ ├── BuiltIn
│ │ └── bool.ini
│ ├── CommandDatabaseLoader.cs
│ ├── GameLoader.cs
│ ├── IPlugin.cs
│ ├── IniFile.cs
│ ├── IniLoader.cs
│ ├── IniSection.cs
│ ├── PluginLoader.cs
│ └── TableFileLoader.cs
├── Properties
│ └── AssemblyInfo.cs
├── Text
│ ├── CharacterCodeEncoder.cs
│ ├── ConservativeStreamReader.cs
│ ├── DecoderIgnoreFallback.cs
│ ├── EncoderIgnoreFallback.cs
│ ├── IgnoreFallbackEncoding.cs
│ ├── LookupTableDecoder.cs
│ ├── LookupTableEncoder.cs
│ └── LookupTableEncoding.cs
└── key.snk
├── README.md
├── TextPet.sln
└── TextPet
├── .gitignore
├── CliCommand.cs
├── CommandLineInterface.cs
├── Commands
├── ClearCommand.cs
├── DeleteCommand.cs
├── FloodCommand.cs
├── GameCommand.cs
├── HelpCommand.cs
├── InsertTextBoxesCommand.cs
├── LoadFileCommand.cs
├── LoadFileIndexCommand.cs
├── LoadPluginsCommand.cs
├── ReadTextArchivesCommand.cs
├── RegexCommand.cs
├── RenameCommand.cs
├── RunScriptCommand.cs
├── SearchCommand.cs
├── SetCompressionCommand.cs
├── SilentCommand.cs
├── TestTextArchivesCommand.cs
├── TrimCommand.cs
├── VerboseCommand.cs
├── WriteFileIndexCommand.cs
└── WriteTextArchivesCommand.cs
├── CustomDictionary.xml
├── Events
├── BeginReadWriteEventArgs.cs
├── FileIndexEventArgs.cs
├── GameInfoEventArgs.cs
├── PluginsEventArgs.cs
├── ReadWriteEventArgs.cs
├── TestEventArgs.cs
└── TextArchivesEventArgs.cs
├── GlobalSuppressions.cs
├── MMSF3TextArchiveReader.cs
├── MMSF3TextArchiveWriter.cs
├── Program.cs
├── Properties
└── AssemblyInfo.cs
├── TextPet.csproj
├── TextPetCore.cs
├── app.config
├── icon.ico
├── indexes
├── exe1.tpi
├── exe2-lc.tpi
├── exe2-v10.tpi
├── exe2-v11.tpi
├── exe3-v10.tpi
├── exe3-v11.tpi
├── exe3b-v10.tpi
├── exe3b-v11.tpi
├── exe45.tpi
├── exe4bm-v10.tpi
├── exe4bm-v11.tpi
├── exe4rs-v10.tpi
├── exe4rs-v11.tpi
├── exe5tb.tpi
├── exe5tc.tpi
├── exe6cf.tpi
├── exe6cg.tpi
├── exeoss-demo-arm9-jp.tpi
├── mmbn1-eu.tpi
├── mmbn1-us.tpi
├── mmbn2-eu.tpi
├── mmbn2-us-beta.tpi
├── mmbn2-us.tpi
├── mmbn3b-eu.tpi
├── mmbn3b-us.tpi
├── mmbn3w-eu.tpi
├── mmbn3w-us.tpi
├── mmbn4bm-eu.tpi
├── mmbn4bm-us.tpi
├── mmbn4rs-eu.tpi
├── mmbn4rs-us.tpi
├── mmbn5tc-us.tpi
├── mmbn5tp-us.tpi
├── mmbn6cf-us.tpi
└── mmbn6cg-us.tpi
├── key.snk
├── plugins
├── bn1-utf8.tbl
├── bn2-utf8.tbl
├── bn3-utf8.tbl
├── bn4-utf8.tbl
├── bn5-utf8.tbl
├── bn5dse-used-utf8.tbl
├── bn6-utf8.tbl
├── bnlc-bn1-utf8.tbl
├── bnlc-bn2-utf8.tbl
├── bnlc-bn3-utf8.tbl
├── bnlc-bn4-utf8.tbl
├── bnlc-bn5-utf8.tbl
├── bnlc-bn6-utf8.tbl
├── exe1-utf8.tbl
├── exe2-utf8.tbl
├── exe3-utf8.tbl
├── exe4-utf8.tbl
├── exe45-utf8.tbl
├── exe45t-utf8.tbl
├── exe5-utf8.tbl
├── exe5ds-utf8.tbl
├── exe6-utf8.tbl
├── exeac-exe1-utf8.tbl
├── exeac-exe2-utf8.tbl
├── exeac-exe3-utf8.tbl
├── exeac-exe4-chs-utf8.tbl
├── exeac-exe4-cht-utf8.tbl
├── exeac-exe4-utf8.tbl
├── exeac-exe5-chs-utf8.tbl
├── exeac-exe5-utf8.tbl
├── exeac-exe6-chs-utf8.tbl
├── exeac-exe6-cht-utf8.tbl
├── exeac-exe6-utf8.tbl
├── games.ini
├── mmbn1-lc.ini
├── mmbn1.ini
├── mmbn1s.ini
├── mmbn2-lc.ini
├── mmbn2.ini
├── mmbn2s.ini
├── mmbn3.ini
├── mmbn3s.ini
├── mmbn4-lc.ini
├── mmbn4.ini
├── mmbn45.ini
├── mmbn45s.ini
├── mmbn4s.ini
├── mmbn5-lc.ini
├── mmbn5.ini
├── mmbn5ds.ini
├── mmbn5dse.ini
├── mmbn5dses.ini
├── mmbn5s.ini
├── mmbn6-lc.ini
├── mmbn6.ini
├── mmbn6s.ini
├── mmsf1.ini
├── mmsf2.ini
├── mmsf3.ini
├── rnr1-utf8.tbl
├── rnr2-utf8.tbl
├── rnr3-utf8.tbl
├── sf1-utf8.tbl
├── sf2-utf8.tbl
└── sf3-utf8.tbl
├── script.tps
└── scripts
├── fix-6cf-jp.tpl
├── fix-6cf-us.tpl
├── fix-6cg-jp.tpl
├── fix-6cg-us.tpl
└── normalize-5ds-eu.tps
/.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 |
24 | # Visual Studio 2015 cache/options directory
25 | .vs/
26 | # Uncomment if you have tasks that create the project's static files in wwwroot
27 | #wwwroot/
28 |
29 | # MSTest test Results
30 | [Tt]est[Rr]esult*/
31 | [Bb]uild[Ll]og.*
32 |
33 | # NUNIT
34 | *.VisualState.xml
35 | TestResult.xml
36 |
37 | # Build Results of an ATL Project
38 | [Dd]ebugPS/
39 | [Rr]eleasePS/
40 | dlldata.c
41 |
42 | # DNX
43 | project.lock.json
44 | artifacts/
45 |
46 | *_i.c
47 | *_p.c
48 | *_i.h
49 | *.ilk
50 | *.meta
51 | *.obj
52 | *.pch
53 | *.pdb
54 | *.pgc
55 | *.pgd
56 | *.rsp
57 | *.sbr
58 | *.tlb
59 | *.tli
60 | *.tlh
61 | *.tmp
62 | *.tmp_proj
63 | *.log
64 | *.vspscc
65 | *.vssscc
66 | .builds
67 | *.pidb
68 | *.svclog
69 | *.scc
70 |
71 | # Chutzpah Test files
72 | _Chutzpah*
73 |
74 | # Visual C++ cache files
75 | ipch/
76 | *.aps
77 | *.ncb
78 | *.opendb
79 | *.opensdf
80 | *.sdf
81 | *.cachefile
82 |
83 | # Visual Studio profiler
84 | *.psess
85 | *.vsp
86 | *.vspx
87 | *.sap
88 |
89 | # TFS 2012 Local Workspace
90 | $tf/
91 |
92 | # Guidance Automation Toolkit
93 | *.gpState
94 |
95 | # ReSharper is a .NET coding add-in
96 | _ReSharper*/
97 | *.[Rr]e[Ss]harper
98 | *.DotSettings.user
99 |
100 | # JustCode is a .NET coding add-in
101 | .JustCode
102 |
103 | # TeamCity is a build add-in
104 | _TeamCity*
105 |
106 | # DotCover is a Code Coverage Tool
107 | *.dotCover
108 |
109 | # NCrunch
110 | _NCrunch_*
111 | .*crunch*.local.xml
112 | nCrunchTemp_*
113 |
114 | # MightyMoose
115 | *.mm.*
116 | AutoTest.Net/
117 |
118 | # Web workbench (sass)
119 | .sass-cache/
120 |
121 | # Installshield output folder
122 | [Ee]xpress/
123 |
124 | # DocProject is a documentation generator add-in
125 | DocProject/buildhelp/
126 | DocProject/Help/*.HxT
127 | DocProject/Help/*.HxC
128 | DocProject/Help/*.hhc
129 | DocProject/Help/*.hhk
130 | DocProject/Help/*.hhp
131 | DocProject/Help/Html2
132 | DocProject/Help/html
133 |
134 | # Click-Once directory
135 | publish/
136 |
137 | # Publish Web Output
138 | *.[Pp]ublish.xml
139 | *.azurePubxml
140 | # TODO: Comment the next line if you want to checkin your web deploy settings
141 | # but database connection strings (with potential passwords) will be unencrypted
142 | *.pubxml
143 | *.publishproj
144 |
145 | # NuGet Packages
146 | *.nupkg
147 | # The packages folder can be ignored because of Package Restore
148 | **/packages/*
149 | # except build/, which is used as an MSBuild target.
150 | !**/packages/build/
151 | # Uncomment if necessary however generally it will be regenerated when needed
152 | #!**/packages/repositories.config
153 | # NuGet v3's project.json files produces more ignoreable files
154 | *.nuget.props
155 | *.nuget.targets
156 |
157 | # Microsoft Azure Build Output
158 | csx/
159 | *.build.csdef
160 |
161 | # Microsoft Azure Emulator
162 | ecf/
163 | rcf/
164 |
165 | # Microsoft Azure ApplicationInsights config file
166 | ApplicationInsights.config
167 |
168 | # Windows Store app package directory
169 | AppPackages/
170 | BundleArtifacts/
171 |
172 | # Visual Studio cache files
173 | # files ending in .cache can be ignored
174 | *.[Cc]ache
175 | # but keep track of directories ending in .cache
176 | !*.[Cc]ache/
177 |
178 | # Others
179 | ClientBin/
180 | ~$*
181 | *~
182 | *.dbmdl
183 | *.dbproj.schemaview
184 | *.pfx
185 | *.publishsettings
186 | node_modules/
187 | orleans.codegen.cs
188 |
189 | # RIA/Silverlight projects
190 | Generated_Code/
191 |
192 | # Backup & report files from converting an old project file
193 | # to a newer Visual Studio version. Backup files are not needed,
194 | # because we have git ;-)
195 | _UpgradeReport_Files/
196 | Backup*/
197 | UpgradeLog*.XML
198 | UpgradeLog*.htm
199 |
200 | # SQL Server files
201 | *.mdf
202 | *.ldf
203 |
204 | # Business Intelligence projects
205 | *.rdl.data
206 | *.bim.layout
207 | *.bim_*.settings
208 |
209 | # Microsoft Fakes
210 | FakesAssemblies/
211 |
212 | # GhostDoc plugin setting file
213 | *.GhostDoc.xml
214 |
215 | # Node.js Tools for Visual Studio
216 | .ntvs_analysis.dat
217 |
218 | # Visual Studio 6 build log
219 | *.plg
220 |
221 | # Visual Studio 6 workspace options file
222 | *.opt
223 |
224 | # Visual Studio LightSwitch build output
225 | **/*.HTMLClient/GeneratedArtifacts
226 | **/*.DesktopClient/GeneratedArtifacts
227 | **/*.DesktopClient/ModelManifest.xml
228 | **/*.Server/GeneratedArtifacts
229 | **/*.Server/ModelManifest.xml
230 | _Pvt_Extensions
231 |
232 | # Paket dependency manager
233 | .paket/paket.exe
234 |
235 | # FAKE - F# Make
236 | .fake/
237 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Prof. 9
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/LibTextPet/CustomDictionary.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Def
6 | Defs
7 | Dest
8 | Dict
9 | Elem
10 | Elems
11 | Heredoc
12 | IgnorePointerSyncErrors
13 | Ini
14 | LibTxtPt
15 | Msg
16 | Mugshot
17 | Multi
18 | Patcher
19 | Sync
20 | Tokenizer
21 |
22 |
23 | scriptwriter
24 |
25 |
26 |
27 |
28 | libtxtpt
29 | ROM
30 | TPL
31 |
32 |
33 |
--------------------------------------------------------------------------------
/LibTextPet/General/ByteSequenceEqualityComparer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace LibTextPet.General
5 | {
6 | public class ByteSequenceEqualityComparer : IEqualityComparer> {
7 | // Singleton design pattern
8 | private static ByteSequenceEqualityComparer _instance;
9 | public static ByteSequenceEqualityComparer Instance => _instance ?? (_instance = new ByteSequenceEqualityComparer());
10 |
11 | protected ByteSequenceEqualityComparer() { }
12 |
13 | ///
14 | /// Determines whether the specified byte sequences are equal.
15 | ///
16 | /// The first byte sequence to compare.
17 | /// The second byte sequence to compare.
18 | /// true if the specified byte sequences are equal; otherwise, false.
19 | public bool Equals(IEnumerable x, IEnumerable y) {
20 | // Check if same reference, or both null.
21 | if (x == y) {
22 | return true;
23 | }
24 |
25 | // Check if either is null.
26 | if (x == null || y == null) {
27 | return false;
28 | }
29 |
30 | IEnumerator xe = x.GetEnumerator();
31 | IEnumerator ye = y.GetEnumerator();
32 | bool xHas, yHas;
33 |
34 | // Compare all elements.
35 | while (true) {
36 | xHas = xe.MoveNext();
37 | yHas = ye.MoveNext();
38 |
39 | // End reached.
40 | if (!xHas && !yHas) {
41 | break;
42 | }
43 |
44 | // Length differs.
45 | if (xHas != yHas) {
46 | return false;
47 | }
48 |
49 | // Element differs.
50 | if (xe.Current != ye.Current) {
51 | return false;
52 | }
53 | }
54 |
55 | // All tests passed.
56 | return true;
57 | }
58 |
59 | ///
60 | /// Returns a hash code for the specified byte sequence;
61 | ///
62 | /// The byte sequence for which a hash code is to be returned.
63 | /// A hash code for the specified byte sequence.
64 | public int GetHashCode(IEnumerable obj) {
65 | if (obj == null)
66 | throw new ArgumentNullException(nameof(obj), "The byte sequence cannot be null.");
67 |
68 | // compute FNV-1a hash
69 | int hash = -2128831035;
70 | unchecked {
71 | foreach (byte b in obj) {
72 | hash ^= b;
73 | hash *= 16777619;
74 | }
75 | }
76 | return hash;
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/LibTextPet/General/IDefined.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace LibTextPet.General {
7 | ///
8 | /// An object of which instances can be defined by another object.
9 | ///
10 | /// The definition object type.
11 | internal interface IDefined where T : IDefinition {
12 | ///
13 | /// Gets the definition of this instance.
14 | ///
15 | T Definition { get; }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/LibTextPet/General/IDefinition.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace LibTextPet.General {
7 | ///
8 | /// An object that defines an instance of another object.
9 | ///
10 | internal interface IDefinition {
11 | ///
12 | /// Gets the name of the defined instance.
13 | ///
14 | string Name { get; }
15 |
16 | ///
17 | /// Gets a description of the defined instance.
18 | ///
19 | string Description { get; }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/LibTextPet/General/INameable.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace LibTextPet.General {
7 | ///
8 | /// An object with a name that uniquely identifies it.
9 | ///
10 | public interface INameable {
11 | ///
12 | /// Gets the name of this object.
13 | ///
14 | string Name { get; }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/LibTextPet/General/LookupTreeNode.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace LibTextPet.General {
7 | ///
8 | /// Represents a node in the tree with any number of children, optionally containing a value.
9 | ///
10 | internal class LookupTreeNode {
11 | private TValue _value;
12 | private IEqualityComparer equalityComparer;
13 |
14 | ///
15 | /// Gets the children of this tree node.
16 | ///
17 | private IDictionary> Children { get; set; }
18 |
19 | ///
20 | /// Gets a boolean that indicates whether this tree node has any children.
21 | ///
22 | public bool HasChildren
23 | => this.Children?.Any() ?? false;
24 |
25 | ///
26 | /// Gets a boolean that indicates whether this tree node has a child with the specified key element.
27 | ///
28 | /// The key element.
29 | /// true if there is a child; otherwise, false.
30 | public bool HasChild(TKeyElement keyElement)
31 | => this.Children?.ContainsKey(keyElement) ?? false;
32 |
33 | ///
34 | /// Gets the child of this tree node with the specified key element.
35 | ///
36 | /// The key element.
37 | /// The child.
38 | public LookupTreeNode GetChild(TKeyElement keyElement)
39 | => this.Children[keyElement];
40 |
41 | ///
42 | /// Gets the child of this tree node with the specified key element.
43 | ///
44 | /// The key element.
45 | /// When this method returns, the child, if it exists; otherwise, null.
46 | /// true if there is a child; otherwise, false.
47 | public bool TryGetChild(TKeyElement keyElement, out LookupTreeNode child) {
48 | if (this.Children == null || !this.Children.TryGetValue(keyElement, out child)) {
49 | child = null;
50 | return false;
51 | } else {
52 | return true;
53 | }
54 | }
55 |
56 | ///
57 | /// Gets or sets the value for this tree node.
58 | ///
59 | public TValue Value {
60 | get {
61 | return this._value;
62 | }
63 | set {
64 | this._value = value;
65 | this.HasValue = true;
66 | }
67 | }
68 |
69 | ///
70 | /// Gets a boolean that indicates whether this tree node has a value.
71 | ///
72 | public bool HasValue { get; private set; }
73 |
74 | ///
75 | /// Creates a new tree node with the specified value.
76 | ///
77 | /// The value of this tree node.
78 | public LookupTreeNode(TValue value) {
79 | this.Value = value;
80 | this.HasValue = true;
81 | }
82 |
83 | ///
84 | /// Creates a new tree node with no value.
85 | ///
86 | public LookupTreeNode() {
87 | this.Value = default(TValue);
88 | this.HasValue = false;
89 | }
90 |
91 | ///
92 | /// Creates a new tree node with the specified value.
93 | ///
94 | /// The value of this tree node.
95 | /// The equality comparer to use for the child keys.
96 | public LookupTreeNode(TValue value, IEqualityComparer equalityComparer)
97 | : this(value) {
98 | this.equalityComparer = equalityComparer;
99 | }
100 |
101 | ///
102 | /// Creates a new tree node with no value.
103 | ///
104 | /// The equality comparer to use for the child keys.
105 | public LookupTreeNode(IEqualityComparer equalityComparer)
106 | : this() {
107 | this.equalityComparer = equalityComparer;
108 | }
109 |
110 | ///
111 | /// Adds a child to this tree node.
112 | ///
113 | /// The key element of the child.
114 | /// The child node.
115 | internal void AddChild(TKeyElement keyElement, LookupTreeNode child) {
116 | if (this.HasChild(keyElement))
117 | throw new ArgumentException("A child with this key element already exists.", nameof(keyElement));
118 |
119 | // Create child dictionary if it does not exist.
120 | if (this.Children == null) {
121 | if (this.equalityComparer != null) {
122 | this.Children = new Dictionary>(equalityComparer);
123 | } else {
124 | this.Children = new Dictionary>();
125 | }
126 | }
127 | // Add the child to the dictionary.
128 | this.Children.Add(keyElement, child);
129 | }
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/LibTextPet/General/LookupTreePath.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace LibTextPet.General {
7 | public class LookupTreePath : ICloneable where TKeyElement : IEquatable {
8 | ///
9 | /// Gets the tree.
10 | ///
11 | public LookupTree Tree { get; }
12 |
13 | ///
14 | /// Gets the current depth of the tree path.
15 | ///
16 | public int Depth { get; internal set; }
17 | ///
18 | /// The current node in the tree path.
19 | ///
20 | internal LookupTreeNode CurrentNode { get; set; }
21 | ///
22 | /// Gets a boolean that indicates whether the current node has a value.
23 | ///
24 | public bool AtValue => this.CurrentNode.HasValue;
25 | ///
26 | /// Gets the value of the current node, or the default value of the value type if it has none.
27 | ///
28 | public TValue CurrentValue => this.CurrentNode.Value;
29 | ///
30 | /// Gets or sets a boolean that indicates whether the tree path has hit a dead end.
31 | ///
32 | public bool AtEnd => !this.CurrentNode.HasChildren;
33 |
34 | internal LookupTreePath(LookupTree tree) {
35 | this.Tree = tree;
36 | this.Reset();
37 | }
38 |
39 | ///
40 | /// Resets the path through the tree.
41 | ///
42 | public void Reset() {
43 | this.Depth = 0;
44 | this.CurrentNode = this.Tree.RootNode;
45 | }
46 |
47 | ///
48 | /// Steps through the tree on the specified key element.
49 | ///
50 | /// The key element.
51 | /// true if the step moved to a new node; otherwise, false.
52 | public virtual bool StepNext(TKeyElement keyElement) {
53 | if (this.CurrentNode.TryGetChild(keyElement, out LookupTreeNode child)) {
54 | this.CurrentNode = child;
55 | this.Depth += 1;
56 | return true;
57 | } else {
58 | return false;
59 | }
60 | }
61 |
62 | ///
63 | /// Steps through the tree to the next value.
64 | ///
65 | /// The enumerator for key elements.
66 | /// When this method returns, the next value, if one was found; otherwise, the default value of the value type.
67 | /// true if a value was found; otherwise, false.
68 | internal bool TryStepToValue(IEnumerator keyElementEnumerator, out TValue value) {
69 | if (keyElementEnumerator == null)
70 | throw new ArgumentNullException(nameof(keyElementEnumerator));
71 |
72 | // Root node is empty and should not have a value.
73 | while (keyElementEnumerator.MoveNext() && this.StepNext(keyElementEnumerator.Current)) {
74 | if (this.CurrentNode.HasValue) {
75 | value = this.CurrentNode.Value;
76 | return true;
77 | }
78 | }
79 |
80 | // End reached.
81 | value = default(TValue);
82 | return false;
83 | }
84 |
85 | ///
86 | /// Step through the tree until a dead end is reached.
87 | ///
88 | /// The enumerator for key elements.
89 | /// true if a value was found; otherwise, false.
90 | internal void StepToEnd(IEnumerator keyElementEnumerator) {
91 | // Step to next value until end reached.
92 | while (this.TryStepToValue(keyElementEnumerator, out _)) { }
93 | }
94 |
95 | public LookupTreePath Clone() {
96 | return new LookupTreePath(this.Tree) {
97 | Depth = this.Depth,
98 | CurrentNode = this.CurrentNode
99 | };
100 | }
101 |
102 | object ICloneable.Clone() {
103 | return this.Clone();
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/LibTextPet/General/MaskedByte.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Linq;
5 | using System.Text;
6 |
7 | namespace LibTextPet.General {
8 | ///
9 | /// Represents a byte masked with a bit pattern.
10 | ///
11 | [ImmutableObject(true)]
12 | public struct MaskedByte : IEquatable {
13 | ///
14 | /// Gets the base byte value of this masked byte.
15 | ///
16 | public byte Byte { get; }
17 | ///
18 | /// Gets the mask bit pattern of this masked byte.
19 | ///
20 | public byte Mask { get; }
21 |
22 | ///
23 | /// Creates a new masked byte with the specified base byte value and mask.
24 | ///
25 | /// The base value.
26 | /// The mask.
27 | public MaskedByte(int baseValue, int mask) {
28 | this.Byte = (byte)(baseValue & mask);
29 | this.Mask = (byte)mask;
30 | }
31 |
32 | public override int GetHashCode() {
33 | return this.Byte | (this.Mask << 8);
34 | }
35 |
36 | public override bool Equals(object obj) {
37 | if (obj is MaskedByte mb) {
38 | return this.Equals(mb);
39 | } else {
40 | return false;
41 | }
42 | }
43 |
44 | public bool Equals(MaskedByte other) {
45 | return this.Byte == other.Byte
46 | && this.Mask == other.Mask;
47 | }
48 |
49 | public static bool operator ==(MaskedByte mb1, MaskedByte mb2) {
50 | return mb1.Equals(mb2);
51 | }
52 |
53 | public static bool operator !=(MaskedByte mb1, MaskedByte mb2) {
54 | return !mb1.Equals(mb2);
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/LibTextPet/General/NamedCollection.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.Linq;
5 | using System.Text;
6 |
7 | namespace LibTextPet.General {
8 | ///
9 | /// A collection of values indexed by their names, case insensitive.
10 | ///
11 | public class NamedCollection : KeyedCollection where T : INameable {
12 | ///
13 | /// Constructs an empty collection.
14 | ///
15 | public NamedCollection()
16 | : base(StringComparer.OrdinalIgnoreCase) { }
17 |
18 | ///
19 | /// Constructs a collection containing the given values.
20 | ///
21 | /// The values this collection should contain.
22 | protected NamedCollection(IEnumerable values)
23 | : base(StringComparer.OrdinalIgnoreCase) {
24 | if (values == null)
25 | throw new ArgumentNullException(nameof(values), "The values cannot be null.");
26 |
27 | foreach (T value in values) {
28 | Add(value);
29 | }
30 | }
31 |
32 | ///
33 | /// Extracts the name from the specified element.
34 | ///
35 | /// The element from which to extract the name.
36 | /// The name for the specified element.
37 | protected override string GetKeyForItem(T item) {
38 | if (item == null)
39 | throw new ArgumentNullException(nameof(item), "The item cannot be null.");
40 |
41 | return item.Name;
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/LibTextPet/General/ReadOnlyKeyedCollection.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.Linq;
5 | using System.Text;
6 |
7 | namespace LibTextPet.General {
8 | ///
9 | /// A read-only collection whose keys are embedded in the values.
10 | ///
11 | public abstract class ReadOnlyKeyedCollection : KeyedCollection {
12 | private const string exceptionMessage = "This collection is read-only.";
13 |
14 | internal bool Locked { get; set; }
15 |
16 | ///
17 | /// Constructs a read-only collection containing the given values that uses the specified equality comparer.
18 | ///
19 | /// The values this read-only collection should contain.
20 | /// The equality comparer to use when comparing keys.
21 | protected ReadOnlyKeyedCollection(IEnumerable values, IEqualityComparer comparer)
22 | : base(comparer) {
23 | if (values == null)
24 | throw new ArgumentNullException(nameof(values), "The values cannot be null.");
25 |
26 | this.Locked = false;
27 |
28 | foreach (TValue value in values) {
29 | Add(value);
30 | }
31 |
32 | this.Locked = true;
33 | }
34 |
35 | internal ReadOnlyKeyedCollection(IEqualityComparer comparer)
36 | : base(comparer) {
37 | // Doesn't lock it
38 | }
39 |
40 | ///
41 | /// Constructs a read-only collection containing the given values.
42 | ///
43 | /// The values this read-only collection should contain.
44 | protected ReadOnlyKeyedCollection(IEnumerable values)
45 | : this(values, EqualityComparer.Default) { }
46 |
47 | protected override void InsertItem(int index, TValue item) {
48 | if (Locked)
49 | throw new NotSupportedException(exceptionMessage);
50 | if (item == null)
51 | throw new ArgumentNullException(nameof(item), "The item cannot be null.");
52 |
53 | base.InsertItem(index, item);
54 | }
55 |
56 | protected override void ClearItems() {
57 | if (Locked)
58 | throw new NotSupportedException(exceptionMessage);
59 |
60 | base.ClearItems();
61 | }
62 |
63 | protected override void RemoveItem(int index) {
64 | if (Locked)
65 | throw new NotSupportedException(exceptionMessage);
66 |
67 | base.RemoveItem(index);
68 | }
69 |
70 | protected override void SetItem(int index, TValue item) {
71 | if (Locked)
72 | throw new NotSupportedException(exceptionMessage);
73 |
74 | base.SetItem(index, item);
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/LibTextPet/General/ReadOnlyNamedCollection.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace LibTextPet.General {
7 | ///
8 | /// A read-only collection of values indexed by their names, case insensitive.
9 | ///
10 | public class ReadOnlyNamedCollection : ReadOnlyKeyedCollection where T : INameable {
11 | ///
12 | /// Constructs a new named collection containing the given elements.
13 | ///
14 | /// The elements this named collection should contain.
15 | public ReadOnlyNamedCollection(IEnumerable elements)
16 | : base(elements, StringComparer.OrdinalIgnoreCase) { }
17 |
18 | internal ReadOnlyNamedCollection()
19 | : base(StringComparer.OrdinalIgnoreCase) {
20 | // Doesn't lock it
21 | }
22 |
23 | ///
24 | /// Extracts the name from the specified element.
25 | ///
26 | /// The element from which to extract the name.
27 | /// The name for the specified element.
28 | protected override string GetKeyForItem(T item) {
29 | if (item == null)
30 | throw new ArgumentNullException(nameof(item), "The item cannot be null.");
31 |
32 | return item.Name;
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/LibTextPet/GlobalSuppressions.cs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Prof9/TextPet/4f739d539a3ac328b4c3c5c645a4551469cd3628/LibTextPet/GlobalSuppressions.cs
--------------------------------------------------------------------------------
/LibTextPet/IO/FileIndexEntry.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.Linq;
5 | using System.Text;
6 |
7 | namespace LibTextPet.IO {
8 | ///
9 | /// A file index entry for a single text archive inside a larger file.
10 | ///
11 | public class FileIndexEntry {
12 | ///
13 | /// Gets the offset of the text archive in the file.
14 | ///
15 | public int Offset { get; set; }
16 | ///
17 | /// Gets the (compressed) size of the text archive.
18 | ///
19 | public int Size { get; set; }
20 | ///
21 | /// Gets or sets a boolean that indicates whether the text archive is compressed.
22 | ///
23 | public bool Compressed { get; set; }
24 | ///
25 | /// Gets or sets a boolean that indicates whether the text archive contains a size header.
26 | ///
27 | public bool SizeHeader { get; set; }
28 | ///
29 | /// Gets or sets the offsets of the pointers pointing to the text archive.
30 | ///
31 | public ICollection Pointers { get; }
32 |
33 | ///
34 | /// Creates a new file index entry with the specified offset.
35 | ///
36 | /// The offset of the text archive in the file.
37 | public FileIndexEntry(int offset)
38 | : this(offset, 0, false, false, new int[0]) { }
39 |
40 | ///
41 | /// Creates a new file index entry with the specified offset, size, compression and pointer offsets.
42 | ///
43 | /// The offset of the text archive in the file.
44 | /// The (compressed) size of the text archive.
45 | /// Whether the text archive is compressed.
46 | /// Whether the text archive has a size header.
47 | /// The offsets of the pointers to the text archive.
48 | public FileIndexEntry(int offset, int size, bool compressed, bool sizeHeader, IEnumerable pointers) {
49 | if (offset < 0)
50 | throw new ArgumentOutOfRangeException(nameof(offset), offset, "The file offset cannot be negative.");
51 | if (size < 0)
52 | throw new ArgumentOutOfRangeException(nameof(size), size, "The size cannot be negative.");
53 | if (pointers == null)
54 | throw new ArgumentNullException(nameof(pointers), "The pointer offsets cannot be null.");
55 |
56 | this.Offset = offset;
57 | this.Size = size;
58 | this.Compressed = compressed;
59 | this.SizeHeader = sizeHeader;
60 | this.Pointers = pointers.Distinct().ToList();
61 | }
62 |
63 | ///
64 | /// Checks whether this file index entry overlaps another file index entry.
65 | ///
66 | /// The file index entry to compare against.
67 | /// true if the file entries overlap; otherwise, false.
68 | public bool Overlaps(FileIndexEntry other) {
69 | if (other == null)
70 | throw new ArgumentNullException(nameof(other), "The other file index entry cannot be null.");
71 |
72 | return Math.Max(this.Offset, other.Offset) < Math.Min(this.Offset + this.Size, other.Offset + other.Size);
73 | }
74 |
75 | public override bool Equals(object obj) {
76 | if (obj == null || GetType() != obj.GetType())
77 | return false;
78 |
79 | FileIndexEntry entry = (FileIndexEntry)obj;
80 |
81 | if (!(this.Offset == entry.Offset && this.Size == entry.Size && this.Compressed == entry.Compressed)) {
82 | return false;
83 | }
84 |
85 | if (this.Pointers.Count != entry.Pointers.Count) {
86 | return false;
87 | }
88 |
89 | if (this.Pointers.Union(entry.Pointers).Distinct().Count() != this.Pointers.Count) {
90 | return false;
91 | }
92 |
93 | return true;
94 | }
95 |
96 | public override int GetHashCode() {
97 | return this.Offset.GetHashCode() ^ this.Size.GetHashCode() ^ this.Compressed.GetHashCode() ^ this.Pointers.GetHashCode();
98 | }
99 |
100 | public static bool operator ==(FileIndexEntry entry1, FileIndexEntry entry2) {
101 | if (ReferenceEquals(entry1, entry2)) {
102 | return true;
103 | } else if (entry1 is null || entry2 is null) {
104 | return false;
105 | } else {
106 | return entry1.Equals(entry2);
107 | }
108 | }
109 |
110 | public static bool operator !=(FileIndexEntry token1, FileIndexEntry token2) {
111 | return !(token1 == token2);
112 | }
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/LibTextPet/IO/FileIndexEntryCollection.cs:
--------------------------------------------------------------------------------
1 | using LibTextPet.General;
2 | using LibTextPet.Msg;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Collections.ObjectModel;
6 | using System.Globalization;
7 | using System.Linq;
8 | using System.Text;
9 | using System.Text.RegularExpressions;
10 |
11 | namespace LibTextPet.IO {
12 | ///
13 | /// A collection of file index entries for text archives, indexed by their offsets.
14 | ///
15 | public class FileIndexEntryCollection : KeyedCollection {
16 | private Regex OffsetRegex;
17 |
18 | ///
19 | /// Creates a new empty file index.
20 | ///
21 | public FileIndexEntryCollection()
22 | : base() {
23 | this.OffsetRegex = new Regex("^[0-9A-Fa-f]{6,8}$", RegexOptions.Compiled);
24 | }
25 |
26 | protected override long GetKeyForItem(FileIndexEntry item) {
27 | if (item == null)
28 | throw new ArgumentNullException(nameof(item), "The item cannot be null.");
29 |
30 | // long is used instead of int to not overlap with this[int].
31 | return item.Offset;
32 | }
33 |
34 | ///
35 | /// Gets the file index entry for the specified text archive, or null if no such entry exists.
36 | ///
37 | /// The text archive.
38 | /// The corresponding file index entry, or null if no matching entry was found.
39 | public FileIndexEntry GetEntryForTextArchive(TextArchive textArchive) {
40 | if (textArchive == null)
41 | throw new ArgumentNullException(nameof(textArchive), "The text archive cannot be null.");
42 |
43 | return GetEntryForTextArchive(textArchive.Identifier);
44 | }
45 |
46 | ///
47 | /// Gets the file index entry for the text archive with the specified identifier, or null if no such entry exists.
48 | ///
49 | /// The identifier of the text archive.
50 | /// The corresponding file index entry, or null if no matching entry was found.
51 | public FileIndexEntry GetEntryForTextArchive(string identifier) {
52 | if (identifier == null)
53 | throw new ArgumentNullException(nameof(identifier), "The text archive identifier cannot be null.");
54 | if (String.IsNullOrWhiteSpace(identifier))
55 | throw new ArgumentException("The text archive identifier cannot consist only of whitespace.", nameof(identifier));
56 |
57 | bool parsed = false;
58 | int offset;
59 | if (this.OffsetRegex.IsMatch(identifier)) {
60 | parsed = Int32.TryParse(identifier, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out offset);
61 | } else {
62 | parsed = NumberParser.TryParseInt32(identifier, out offset);
63 | }
64 |
65 | if (parsed && this.Contains(offset)) {
66 | return this[(long)offset];
67 | } else {
68 | // Could not parse identifier or no entry available.
69 | return null;
70 | }
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/LibTextPet/IO/FileIndexReader.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using LibTextPet.Msg;
6 | using System.Text.RegularExpressions;
7 | using System.IO;
8 | using LibTextPet.General;
9 |
10 | namespace LibTextPet.IO {
11 | ///
12 | /// A reader that reads a file index from an input stream.
13 | ///
14 | public class FileIndexReader : Manager, IReader, IDisposable {
15 | ///
16 | /// Gets the text reader that is used to read text from the input stream.
17 | ///
18 | protected TextReader TextReader { get; }
19 |
20 | ///
21 | /// Gets the regex that is used for removing comments, whitespace and carriage returns.
22 | ///
23 | private Regex StripRegex { get; }
24 | ///
25 | /// Gets the regex that is used for matching a single line.
26 | ///
27 | private Regex EntryRegex { get; }
28 |
29 | ///
30 | /// Creates a new file index reader that reads from the specified input stream.
31 | ///
32 | /// The stream to read from.
33 | public FileIndexReader(Stream stream)
34 | : base(stream, false, FileAccess.Read) {
35 | this.TextReader = new StreamReader(stream);
36 |
37 | this.StripRegex = new Regex(
38 | @"[^\S\n]+|(//|;|#)[^\r\n]*?(?=\r?$)|/\*(.|\n)*?(\*/|$(?!\r?\n))",
39 | RegexOptions.Multiline | RegexOptions.Compiled
40 | );
41 | this.EntryRegex = new Regex(
42 | @"^[\dA-Za-z$#]+:&?%?[\dA-Za-z$#]+=([\dA-Za-z$#]+,)*([\dA-Za-z$#]+)?$",
43 | RegexOptions.Compiled
44 | );
45 | }
46 |
47 | ///
48 | /// Reads a file index from the input stream.
49 | ///
50 | /// The file index that was read.
51 | public FileIndexEntryCollection Read() {
52 | string fulltext = this.TextReader.ReadToEnd();
53 | return Read(fulltext);
54 | }
55 |
56 | ///
57 | /// Reads a file index entries from the specified full text.
58 | ///
59 | /// The full text.
60 | /// The file index that was read.
61 | public FileIndexEntryCollection Read(string fullText) {
62 | if (fullText == null)
63 | throw new ArgumentNullException(nameof(fullText), "The full text cannot be null.");
64 |
65 | fullText = this.StripRegex.Replace(fullText, "");
66 | string[] lines = fullText.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
67 |
68 | FileIndexEntryCollection fileIndex = new FileIndexEntryCollection();
69 |
70 | foreach (string line in lines) {
71 | fileIndex.Add(ReadEntry(line));
72 | }
73 |
74 | return fileIndex;
75 | }
76 |
77 | ///
78 | /// Reads a single file index entry from the specified entry string. The entry string must be correctly formatted.
79 | ///
80 | /// The entry string to read from.
81 | /// The file index entry that was read.
82 | protected FileIndexEntry ReadEntry(string entry) {
83 | if (entry == null)
84 | throw new ArgumentNullException(nameof(entry), "The entry string cannot be null.");
85 | if (!this.EntryRegex.IsMatch(entry))
86 | throw new ArgumentException("Could not parse \"" + entry + "\" as a file index entry.", nameof(entry));
87 |
88 | int colonPos = entry.IndexOf(':');
89 | int equalsPos = entry.IndexOf('=');
90 | bool compressed = entry.Contains('&');
91 | bool sizeHeader = entry.Contains('%');
92 |
93 | int sizeStart = colonPos + 1 + (compressed ? 1 : 0) + (sizeHeader ? 1 : 0);
94 |
95 | string offsetString = entry.Substring(0, colonPos);
96 | string sizeString = entry.Substring(sizeStart, equalsPos - sizeStart);
97 | string[] pointerStrings = entry.Substring(equalsPos + 1).Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
98 |
99 | int offset = NumberParser.ParseInt32(offsetString);
100 | int size = NumberParser.ParseInt32(sizeString);
101 | int[] pointers = new int[pointerStrings.Length];
102 | for (int i = 0; i < pointers.Length; i++) {
103 | pointers[i] = NumberParser.ParseInt32(pointerStrings[i]);
104 | }
105 |
106 | return new FileIndexEntry(offset, size, compressed, sizeHeader, pointers);
107 | }
108 |
109 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "k__BackingField")]
110 | protected virtual void Dispose(bool disposing) {
111 | if (disposing) {
112 | this.TextReader.Close();
113 | }
114 | }
115 |
116 | public void Dispose() {
117 | Dispose(true);
118 | GC.SuppressFinalize(this);
119 | }
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/LibTextPet/IO/IPatcher.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace LibTextPet.IO {
7 | ///
8 | /// An inserter that inserts elements from an object into another object.
9 | ///
10 | /// The type of object that is inserted from and to.
11 | public interface IPatcher {
12 | ///
13 | /// Patches the specified patch object into the specified base object.
14 | ///
15 | /// The base object.
16 | /// The patch object.
17 | void Patch(T baseObj, T patchObj);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/LibTextPet/IO/IReader.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace LibTextPet.IO {
7 | ///
8 | /// A reader that reads objects from an input stream.
9 | ///
10 | /// The type of object that is read.
11 | public interface IReader {
12 | ///
13 | /// Reads an object from the input stream.
14 | ///
15 | /// The read object.
16 | T Read();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/LibTextPet/IO/IWriter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace LibTextPet.IO {
7 | ///
8 | /// A writer that writes objects to an output stream.
9 | ///
10 | /// The type of object that is written.
11 | public interface IWriter {
12 | ///
13 | /// Writes an object to the output stream.
14 | ///
15 | /// The object to write.
16 | void Write(T obj);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/LibTextPet/IO/MSG/BinaryScriptReader.cs:
--------------------------------------------------------------------------------
1 | using LibTextPet.General;
2 | using LibTextPet.Msg;
3 | using LibTextPet.Text;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.IO;
7 | using System.Linq;
8 | using System.Text;
9 |
10 | namespace LibTextPet.IO.Msg {
11 | ///
12 | /// A binary script reader that reads a script from an input stream.
13 | ///
14 | public class BinaryScriptReader : ScriptReader {
15 | ///
16 | /// Gets the starting position of the last script read or the script currently being read from the input stream.
17 | ///
18 | public long StartPosition { get; private set; }
19 |
20 | ///
21 | /// Creates a new binary script reader that reads from the specified input stream, using the specified game info.
22 | ///
23 | /// The stream to read from.
24 | /// The game info to use.
25 | public BinaryScriptReader(Stream stream, GameInfo game)
26 | : this(stream, game?.Encoding, game?.Databases.ToArray()) { }
27 |
28 | ///
29 | /// Creates a new binary script reader that reads from the specified input stream, using the specified encoding and command databases.
30 | ///
31 | /// The stream to read from.
32 | /// The encoding to use.
33 | /// The command databases to use, in order of preference.
34 | public BinaryScriptReader(Stream stream, IgnoreFallbackEncoding encoding, params CommandDatabase[] databases)
35 | : base(stream, encoding, CreateCommandReaders(stream, encoding, databases)) { }
36 |
37 | ///
38 | /// Reads a script from the current input stream.
39 | ///
40 | /// The script that was read.
41 | public override Script Read() {
42 | // Save the start position of the current script to keep track of the running length.
43 | this.StartPosition = this.BaseStream.Position;
44 |
45 | return base.Read();
46 | }
47 |
48 | ///
49 | /// Reads the next fallback element from the input stream.
50 | ///
51 | /// A fallback script element.
52 | protected override IScriptElement ReadFallback() {
53 | return new ByteElement((byte)this.BaseStream.ReadByte());
54 | }
55 |
56 | ///
57 | /// Creates command readers with the specified command databases that read from the specified input stream.
58 | ///
59 | /// The stream to read from.
60 | /// The command databases to use.
61 | /// The encoding to use.
62 | /// The resulting command readers.
63 | private static BinaryCommandReader[] CreateCommandReaders(Stream stream, IgnoreFallbackEncoding encoding, params CommandDatabase[] databases) {
64 | if (databases == null)
65 | throw new ArgumentNullException(nameof(databases), "The command databases cannot be null.");
66 |
67 | BinaryCommandReader[] readers = new BinaryCommandReader[databases.Length];
68 | for (int i = 0; i < databases.Length; i++) {
69 | readers[i] = new BinaryCommandReader(stream, databases[i], encoding);
70 | }
71 |
72 | return readers;
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/LibTextPet/IO/MSG/BinaryScriptWriter.cs:
--------------------------------------------------------------------------------
1 | using LibTextPet.Msg;
2 | using LibTextPet.Text;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.IO;
6 | using System.Linq;
7 | using System.Text;
8 |
9 | namespace LibTextPet.IO.Msg {
10 | ///
11 | /// A binary script writer that writes a script to an output stream.
12 | ///
13 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "ScriptWriter")]
14 | public class BinaryScriptWriter : ScriptWriter {
15 | ///
16 | /// Creates a new binary script writer that writes to the specified output stream.
17 | ///
18 | /// The stream to write to.
19 | /// The encoding to use.
20 | /// The command databases to use.
21 | public BinaryScriptWriter(Stream stream, IgnoreFallbackEncoding encoding)
22 | : base(stream, true, true, encoding, new BinaryCommandWriter(stream, encoding)) { }
23 |
24 | ///
25 | /// Writes a fallback element to the output stream.
26 | ///
27 | /// The fallback element to write.
28 | protected override void WriteFallback(IScriptElement element) {
29 | if (element is ByteElement byteElement) {
30 | this.BaseStream.WriteByte(byteElement.Byte);
31 | return;
32 | }
33 |
34 | throw new NotSupportedException("Unsupported fallback element.");
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/LibTextPet/IO/MSG/BinaryTextArchiveWriter.cs:
--------------------------------------------------------------------------------
1 | using LibTextPet.Msg;
2 | using LibTextPet.Text;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.IO;
6 | using System.Linq;
7 | using System.Text;
8 |
9 | namespace LibTextPet.IO.Msg {
10 | ///
11 | /// A binary text archive writer that writes a text archive to an output stream.
12 | ///
13 | public class BinaryTextArchiveWriter : Manager, IWriter {
14 | ///
15 | /// Gets the script writer that is used to write scripts to the output stream.
16 | ///
17 | protected BinaryScriptWriter ScriptWriter { get; private set; }
18 |
19 | ///
20 | /// Creates a new binary text archive writer that writes to the specified output stream, using the specified encoding and command databases.
21 | ///
22 | /// The stream to write to.
23 | /// The encoding to use.
24 | /// The command databases to use.
25 | public BinaryTextArchiveWriter(Stream stream, IgnoreFallbackEncoding encoding)
26 | : base(stream, true, FileAccess.Write, encoding) {
27 | this.ScriptWriter = new BinaryScriptWriter(stream, encoding);
28 | }
29 |
30 | ///
31 | /// Writes the specified text archive to the output stream.
32 | ///
33 | /// The text archive to write.
34 | public virtual void Write(TextArchive obj) {
35 | if (obj == null)
36 | throw new ArgumentNullException(nameof(obj), "The text archive cannot be null.");
37 |
38 | long start = this.BaseStream.Position;
39 |
40 | // Pad the stream where the pointers go.
41 | byte[] buffer = new byte[2];
42 | for (int i = 0; i < obj.Count; i++) {
43 | this.BaseStream.Write(buffer, 0, buffer.Length);
44 | }
45 |
46 | // Keep track of the script offsets.
47 | List offsets = new List(obj.Count);
48 |
49 | // Write all scripts.
50 | for (int i = 0; i < obj.Count; i++) {
51 | // Save the script offset.
52 | offsets.Add(this.BaseStream.Position - start);
53 |
54 | Script script = obj[i];
55 | #if !DEBUG
56 | try {
57 | #endif
58 | this.ScriptWriter.Write(script);
59 | #if !DEBUG
60 | } catch (EncoderFallbackException ex) {
61 | throw new InvalidDataException("Could not encode character " + ex.CharUnknown + " in script " + i + " of text archive " + obj.Identifier + ".", ex);
62 | }
63 | #endif
64 | }
65 |
66 | // Save the end position.
67 | long end = this.BaseStream.Position;
68 |
69 | // Write all offsets.
70 | this.BaseStream.Position = start;
71 | foreach (long offset in offsets) {
72 | if (offset > 0xFFFF) {
73 | throw new ArgumentException("The text archive is too large.", nameof(obj));
74 | }
75 | buffer[0] = (byte)(offset & 0xFF);
76 | buffer[1] = (byte)((offset >> 8) & 0xFF);
77 |
78 | this.BaseStream.Write(buffer, 0, buffer.Length);
79 | }
80 |
81 | // Restore the end position.
82 | this.BaseStream.Position = end;
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/LibTextPet/IO/MSG/FileManager.cs:
--------------------------------------------------------------------------------
1 | using LibTextPet.General;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Text;
7 |
8 | namespace LibTextPet.IO.Msg {
9 | ///
10 | /// A manager that reads from and/or writes to a file.
11 | ///
12 | public class FileManager : Manager, IDisposable {
13 | ///
14 | /// Gets the binary reader that is used to read bytes from the base stream.
15 | ///
16 | protected BinaryReader BinaryReader { get; }
17 |
18 | ///
19 | /// Gets the binary writer that is used to write bytes to the base stream.
20 | ///
21 | protected BinaryWriter BinaryWriter { get; }
22 |
23 | ///
24 | /// Gets the game info for the base stream.
25 | ///
26 | protected GameInfo Game { get; }
27 |
28 | ///
29 | /// Gets the file index for reading/writing text archives from/to the base stream.
30 | ///
31 | public FileIndexEntryCollection FileIndex { get; }
32 |
33 | ///
34 | /// Gets or sets a boolean that indicates whether the currently loaded file index and the identifiers of written text archives will be updated after writing.
35 | ///
36 | public bool UpdateFileIndex { get; set; }
37 |
38 | ///
39 | /// Creates a new file index manager that reads to and/or writes from the specified stream.
40 | ///
41 | /// The stream to read from or write to.
42 | /// The type of access this manager requires.
43 | /// The game info to use.
44 | /// The file index to use.
45 | public FileManager(Stream stream, FileAccess access, GameInfo game, FileIndexEntryCollection fileIndex)
46 | : base(stream, true, access, game) {
47 | if (fileIndex == null)
48 | throw new ArgumentNullException(nameof(fileIndex), "The file index cannot be null.");
49 |
50 | if (access.HasFlag(FileAccess.Read)) {
51 | this.BinaryReader = new BinaryReader(stream);
52 | }
53 | if (access.HasFlag(FileAccess.Write)) {
54 | this.BinaryWriter = new BinaryWriter(stream);
55 | }
56 |
57 | this.Game = game;
58 | this.FileIndex = fileIndex;
59 | this.UpdateFileIndex = true;
60 | }
61 |
62 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "k__BackingField")]
63 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "k__BackingField")]
64 | protected virtual void Dispose(bool disposing) {
65 | if (disposing) {
66 | this.BinaryReader?.Dispose();
67 | this.BinaryWriter?.Dispose();
68 | }
69 | }
70 |
71 | public void Dispose() {
72 | Dispose(true);
73 | GC.SuppressFinalize(this);
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/LibTextPet/IO/MSG/FixedSizeScriptReader.cs:
--------------------------------------------------------------------------------
1 | using LibTextPet.General;
2 | using LibTextPet.Msg;
3 | using LibTextPet.Text;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.IO;
7 | using System.Linq;
8 | using System.Text;
9 |
10 | namespace LibTextPet.IO.Msg {
11 | ///
12 | /// A binary script reader that reads a script from an input stream.
13 | ///
14 | public class FixedSizeScriptReader : BinaryScriptReader {
15 | private long fixedLength;
16 | ///
17 | /// Gets the fixed byte length for a script.
18 | ///
19 | public long FixedLength {
20 | get {
21 | return this.UseFixedLength ? this.fixedLength : long.MaxValue;
22 | }
23 | private set {
24 | if (value < 0)
25 | throw new ArgumentOutOfRangeException(nameof(value), value, "The length must be at least 0.");
26 |
27 | this.fixedLength = value;
28 | this.UseFixedLength = true;
29 | }
30 | }
31 |
32 | ///
33 | /// Gets a boolean that determines whether to use a fixed byte length for scripts.
34 | ///
35 | public bool UseFixedLength {
36 | get; private set;
37 | }
38 |
39 | ///
40 | /// Creates a new fixed size binary script reader that reads from the specified input stream.
41 | ///
42 | /// The stream to read from.
43 | /// The encoding to use.
44 | /// The command databases to use, in order of preference.
45 | public FixedSizeScriptReader(Stream stream, IgnoreFallbackEncoding encoding, params CommandDatabase[] databases)
46 | : base(stream, encoding, databases) {
47 | this.fixedLength = int.MaxValue;
48 | this.UseFixedLength = false;
49 | }
50 |
51 | public override Script Read() {
52 | Script script = base.Read();
53 |
54 | // Check if the fixed length was exceeded.
55 | if (this.BaseStream.Position - this.StartPosition > this.FixedLength) {
56 | script = null;
57 | }
58 |
59 | return script;
60 | }
61 |
62 | ///
63 | /// Enables the use of a maximum byte length for read scripts, and sets the maximum length to the specified value.
64 | ///
65 | /// The fixed byte length.
66 | public void SetFixedLength(long length) {
67 | if (length < 0)
68 | throw new ArgumentOutOfRangeException(nameof(length), length, "The fixed length cannot be negative.");
69 |
70 | this.FixedLength = length;
71 | this.UseFixedLength = true;
72 | }
73 |
74 | ///
75 | /// Disables the use of a maximum byte length for read scripts.
76 | ///
77 | public void ClearFixedLength() {
78 | this.UseFixedLength = false;
79 | }
80 |
81 | ///
82 | /// Reads the next script command from the input stream.
83 | ///
84 | /// The next script command read from the input stream, or null if no script command exists at the current position in the input stream.
85 | protected override Command ReadCommand(IReader commandReader) {
86 | if (commandReader == null)
87 | throw new ArgumentNullException(nameof(commandReader), "The command reader cannot be null.");
88 |
89 | ((BinaryCommandReader)commandReader).BytesLeft = this.FixedLength - (this.BaseStream.Position - this.StartPosition);
90 |
91 | return commandReader.Read();
92 | }
93 |
94 | ///
95 | /// Checks whether the current stream has script elements left.
96 | ///
97 | /// true if there are script elements left; otherwise, false.
98 | protected override bool HasNext() {
99 | if (!this.UseFixedLength) {
100 | return base.HasNext();
101 | }
102 |
103 | // Check if the script length has been reached.
104 | if (this.BaseStream.Position - this.StartPosition >= this.FixedLength) {
105 | return false;
106 | }
107 | return true;
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/LibTextPet/IO/MSG/ScriptEntry.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace LibTextPet.IO.Msg {
4 | ///
5 | /// A script entry that contains the script's number and offset.
6 | ///
7 | public struct ScriptEntry {
8 | ///
9 | /// The script number of this script entry.
10 | ///
11 | public int ScriptNumber { get; set; }
12 |
13 | ///
14 | /// The stream position of this script entry.
15 | ///
16 | public long Position { get; set; }
17 |
18 | private int _size;
19 | ///
20 | /// The fixed size of this script entry, or -1 if there is no fixed size.
21 | ///
22 | public int Size {
23 | get => this._size;
24 | set {
25 | if (value < -1) {
26 | throw new ArgumentOutOfRangeException(nameof(value), value, "Size cannot be less than -1.");
27 | }
28 | this._size = value;
29 | }
30 | }
31 |
32 | ///
33 | /// Creates a new script entry with the specified script number.
34 | ///
35 | /// The script number.
36 | public ScriptEntry(int scriptNumber) {
37 | this.ScriptNumber = scriptNumber;
38 | this.Position = 0;
39 | this._size = -1;
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/LibTextPet/IO/ScriptWriter.cs:
--------------------------------------------------------------------------------
1 | using LibTextPet.IO;
2 | using LibTextPet.Msg;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Collections.ObjectModel;
6 | using System.IO;
7 | using System.Linq;
8 | using System.Text;
9 |
10 | namespace LibTextPet.IO {
11 | ///
12 | /// A script writer that writes a script to an output stream.
13 | ///
14 | public abstract class ScriptWriter : Manager, IWriter