NdsForge.NET 1.0.1
Read, validate, edit, compare, and build Nintendo DS and DSi images from .NET
Loading...
Searching...
No Matches
NdsImageLayoutPlanner.cs
1namespace NdsForge;
2
4internal static class NdsImageLayoutPlanner
5{
11 public static NdsImageBuildLayout Plan(
12 NdsImageBuilder builder,
13 NdsImageBuildContent content,
14 NdsImageBuildOptions options)
15 {
16 bool ndstoolProfile = options.Profile == NdsImageBuildProfile.Ndstool1503;
17 long cursor = options.HeaderSize;
18 NdsRegion arm9 = PlaceProgram(
19 ref cursor,
20 content.Arm9Data.Length,
21 content.Arm9DeclaredLength,
22 options.SectionAlignment);
23 NdsRegion? arm9Footer = content.Arm9TrailingData.IsEmpty
24 ? null
25 : Place(ref cursor, content.Arm9TrailingData.Length, alignment: 1);
26 NdsRegion arm9Overlays = content.Arm9OverlayTable.Length == 0
27 ? default
28 : Place(ref cursor, content.Arm9OverlayTable.Length, ndstoolProfile ? 1 : options.SectionAlignment);
29 NdsRegion arm7 = PlaceProgram(
30 ref cursor,
31 content.Arm7Data.Length,
32 content.Arm7DeclaredLength,
33 options.SectionAlignment);
34 NdsRegion arm7Overlays = content.Arm7OverlayTable.Length == 0
35 ? default
36 : Place(ref cursor, content.Arm7OverlayTable.Length, ndstoolProfile ? 1 : options.SectionAlignment);
37 NdsRegion fnt = Place(ref cursor, content.FileSystem.FileNameTable.Length, options.SectionAlignment);
38 NdsRegion fat = Place(ref cursor, checked(content.Allocations.Length * 8), options.SectionAlignment);
39 NdsRegion? banner = builder.Banner is null
40 ? null
41 : Place(ref cursor, builder.Banner.RawData.Length, options.SectionAlignment);
42 NdsRegion[] files = PlaceAllocations(ref cursor, content, options, ndstoolProfile);
43 return PlanImageEnd(
44 builder,
45 content,
46 options,
47 arm9,
48 arm9Footer,
49 arm9Overlays,
50 arm7,
51 arm7Overlays,
52 fnt,
53 fat,
54 banner,
55 files,
56 cursor,
57 ndstoolProfile);
58 }
59
66 private static NdsRegion[] PlaceAllocations(
67 ref long cursor,
68 NdsImageBuildContent content,
69 NdsImageBuildOptions options,
70 bool ndstoolProfile)
71 {
72 var files = new NdsRegion[content.Allocations.Length];
73 int alignment = ndstoolProfile ? options.SectionAlignment : options.FileAlignment;
74 for (int fileId = 0; fileId < files.Length; fileId++)
75 {
76 files[fileId] = Place(ref cursor, content.Allocations[fileId].Length, alignment);
77 }
78
79 return files;
80 }
81
98 private static NdsImageBuildLayout PlanImageEnd(
99 NdsImageBuilder builder,
100 NdsImageBuildContent content,
101 NdsImageBuildOptions options,
102 NdsRegion arm9,
103 NdsRegion? arm9Footer,
104 NdsRegion arm9Overlays,
105 NdsRegion arm7,
106 NdsRegion arm7Overlays,
107 NdsRegion fnt,
108 NdsRegion fat,
109 NdsRegion? banner,
110 NdsRegion[] files,
111 long commonContentEnd,
112 bool ndstoolProfile)
113 {
114 long cursor = commonContentEnd;
115 NdsRegion? arm9i = null;
116 NdsRegion? arm7i = null;
117 NdsRegion ntrDigest = default;
118 NdsRegion twlDigest = default;
119 NdsRegion sectorHashTable = default;
120 NdsRegion blockHashTable = default;
121 long usedSize;
122 long physicalSize;
123 if (builder.Kind == NdsImageKind.NintendoDs)
124 {
125 physicalSize = Align(commonContentEnd, options.FileAlignment);
126 usedSize = ndstoolProfile ? physicalSize : commonContentEnd;
127 }
128 else
129 {
130 usedSize = Align(commonContentEnd, options.FileAlignment);
131 cursor = usedSize;
132 arm9i = Place(ref cursor, content.Arm9iData.Length, alignment: 0x400);
133 arm7i = Place(ref cursor, content.Arm7iData.Length, options.SectionAlignment);
134 if (builder.DsiMetadata!.Digests is not null)
135 {
136 NdsDsiDigestOptions digestOptions = builder.DsiMetadata.Digests;
137 ntrDigest = new(arm9.Offset, checked(usedSize - arm9.Offset));
138 twlDigest = new(arm9i.Value.Offset, checked(arm7i.Value.End - arm9i.Value.Offset));
139 long sectorCount = checked(
140 DivideRoundUp(ntrDigest.Length, digestOptions.SectorSize) +
141 DivideRoundUp(twlDigest.Length, digestOptions.SectorSize));
142 sectorHashTable = Place(ref cursor, checked(sectorCount * 20), digestOptions.SectorSize);
143 long blockCount = DivideRoundUp(sectorCount, digestOptions.BlockSectorCount);
144 blockHashTable = Place(ref cursor, checked(blockCount * 20), options.SectionAlignment);
145 }
146
147 physicalSize = Align(cursor, options.SectionAlignment);
148 }
149
150 if (physicalSize > uint.MaxValue)
151 {
152 throw new InvalidDataException("The generated image exceeds the DS header's 32-bit offset and size fields.");
153 }
154
155 return new(
156 arm9,
157 arm9Footer,
158 arm9Overlays,
159 arm7,
160 arm7Overlays,
161 fnt,
162 fat,
163 banner,
164 files,
165 arm9i,
166 arm7i,
167 ntrDigest,
168 twlDigest,
169 sectorHashTable,
170 blockHashTable,
171 usedSize,
172 physicalSize);
173 }
174
180 private static NdsRegion Place(ref long cursor, long length, int alignment)
181 {
182 cursor = Align(cursor, alignment);
183 var region = new NdsRegion(cursor, length);
184 cursor = region.End;
185 return region;
186 }
187
194 private static NdsRegion PlaceProgram(
195 ref long cursor,
196 int physicalLength,
197 int declaredLength,
198 int alignment)
199 {
200 if (declaredLength < physicalLength)
201 {
202 throw new InvalidDataException("A Program's declared length cannot exclude physical executable bytes.");
203 }
204
205 cursor = Align(cursor, alignment);
206 var region = new NdsRegion(cursor, declaredLength);
207 cursor = checked(cursor + physicalLength);
208 return region;
209 }
210
215 private static long Align(long value, int alignment) => checked((value + alignment - 1) & -alignment);
216
221 private static long DivideRoundUp(long value, int divisor) => checked((value + divisor - 1) / divisor);
222}
NdsImageKind
Identifies the hardware family targeted by an image.
NdsImageBuildProfile
Selects a documented Layout personality without contaminating the logical Build Recipe.