Skip to content

Commit afdb26a

Browse files
committed
Thanks Krisp for fixing Rvl CI4 tile #139
1 parent 252c46e commit afdb26a

3 files changed

Lines changed: 114 additions & 68 deletions

File tree

FreeMote/PostProcessing.cs

Lines changed: 108 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Untile/Unswizzle by xdaniel. Copyright(c) 2016 xdaniel(Daniel R.) / DigitalZero Domain. License: The MIT License (MIT)
22
// Tile/Swizzle by Ulysses (wdwxy12345@gmail.com). License: same as FreeMote
3+
// UntileTextureRvl/TileTextureRvl was fixed by Krisp (FreeMote#139)
34

45
using System;
56
using System.Drawing;
@@ -9,7 +10,7 @@
910
namespace FreeMote
1011
{
1112
/// <summary>
12-
/// PS Related Post Process
13+
/// Texture Post Process for consoles
1314
/// </summary>
1415
public static class PostProcessing
1516
{
@@ -152,160 +153,219 @@ public static byte[] TileTexture(byte[] pixelData, int width, int height, int bi
152153
/// <summary>
153154
/// UnTile for Revolution (Wii)
154155
/// </summary>
155-
/// <param name="pixelData"></param>
156-
/// <param name="width"></param>
157-
/// <param name="height"></param>
158-
/// <param name="bitDepth"></param>
159-
/// <returns></returns>
156+
/// <param name="pixelData">The source tiled pixel data.</param>
157+
/// <param name="width">Image width.</param>
158+
/// <param name="height">Image height.</param>
159+
/// <param name="bitDepth">Image bit depth (e.g., 4 for CI4, 8 for CI8, 32 for RGBA8).</param>
160+
/// <returns>The untiled (linear) pixel data.</returns>
160161
public static byte[] UntileTextureRvl(byte[] pixelData, int width, int height, int bitDepth = 32)
161162
{
162163
// ref: Revolution SDK Graphics Library (GX)
164+
// Determine tile dimensions based on bit depth.
163165
int xTileSize = 4;
164166
int yTileSize = 4;
165167
if (bitDepth <= 8)
166168
{
167-
xTileSize = 8; // 4x8
169+
xTileSize = 8; // 8x4 for CI8/I8
168170
}
169-
170171
if (bitDepth <= 4)
171172
{
172-
yTileSize = 8; // 8x8
173+
yTileSize = 8; // 8x8 for CI4/I4
173174
}
174-
bool wide = width > height;
175+
175176
var byteSizePerPixel = bitDepth / 8.0f;
176177
var scale = 1;
177178
bool compactMode = false;
178179
int bpp = bitDepth / 8;
179180
byte[] untiledData;
181+
182+
// Handle compact formats where one byte stores multiple pixels (e.g., 4bpp).
180183
if (byteSizePerPixel < 1.0f)
181184
{
182185
compactMode = true;
183-
scale = (int) (1.0f / byteSizePerPixel);
186+
scale = (int) (1.0f / byteSizePerPixel); // For 4bpp, scale is 2.
184187
untiledData = new byte[width * height / scale];
185-
bpp = 1;
186188
}
187189
else
188190
{
189191
untiledData = new byte[width * height * bpp];
190192
}
191193

192-
int dataIndex = 0;
194+
int dataIndex = 0; // Tracks the current read position in the source tiled data.
193195

196+
// Iterate through the image by tile blocks.
194197
for (int yt = 0; yt < height; yt += yTileSize)
195198
{
196199
for (int xt = 0; xt < width; xt += xTileSize)
197200
{
201+
// Iterate through pixels within a tile.
198202
for (int y = yt; y < yt + yTileSize; y++)
199203
{
200204
for (int x = xt; x < xt + xTileSize; x++)
201205
{
202206
if (x >= width || y >= height) continue;
203207

204-
int pixelIndex = ((y * width) + x) * bpp;
205-
206208
if (!compactMode)
207209
{
208-
if (dataIndex >= pixelData.Length)
210+
// Standard copy for formats with 1 or more bytes per pixel.
211+
int pixelIndex = ((y * width) + x) * bpp;
212+
if (dataIndex + bpp <= pixelData.Length && pixelIndex + bpp <= untiledData.Length)
209213
{
210-
break;
214+
Buffer.BlockCopy(pixelData, dataIndex, untiledData, pixelIndex, bpp);
211215
}
212-
Buffer.BlockCopy(pixelData, dataIndex, untiledData, pixelIndex, bpp);
213216
dataIndex += bpp;
214217
}
215218
else
216219
{
217-
var currentPos = dataIndex / scale;
218-
if (currentPos >= pixelData.Length)
220+
// Corrected logic for compact formats (e.g., CI4).
221+
// dataIndex tracks the current 4-bit pixel in the tiled source data.
222+
int srcByteIndex = dataIndex / scale; // Which source byte holds our 4-bit pixel.
223+
if (srcByteIndex >= pixelData.Length) continue;
224+
225+
byte sourceByte = pixelData[srcByteIndex];
226+
227+
// Extract the correct 4-bit nibble from the source byte.
228+
// Even index = high bits, odd index = low bits.
229+
byte pixelValue = (dataIndex % 2 == 0)
230+
? (byte) (sourceByte >> 4) // Get the left nibble.
231+
: (byte) (sourceByte & 0x0F); // Get the right nibble.
232+
233+
// Calculate the destination linear pixel position.
234+
int destPixelLinearIndex = (y * width) + x;
235+
int destByteIndex = destPixelLinearIndex / scale; // Which byte to write to in the destination.
236+
int destNibblePos = destPixelLinearIndex % 2; // Which nibble (0 or 1) to write to.
237+
238+
if (destByteIndex >= untiledData.Length) continue;
239+
240+
// Place the 4-bit pixelValue into the correct nibble of the destination byte,
241+
// preserving the other nibble that might already be there.
242+
if (destNibblePos == 0)
219243
{
220-
return untiledData;
244+
// Even linear position: write to the high bits (left nibble).
245+
untiledData[destByteIndex] = (byte) ((untiledData[destByteIndex] & 0x0F) | (pixelValue << 4));
221246
}
222-
var srcData = pixelData[currentPos];
223-
var dstPosition = pixelIndex / scale;
224-
var dstSubIndex = pixelIndex % scale;
225-
untiledData[dstPosition] = dstSubIndex == 0
226-
? (byte) ((untiledData[dstPosition] & 0xF0) | srcData)
227-
: (byte) ((untiledData[dstPosition] & 0x0F) | (srcData << 4));
247+
else
248+
{
249+
// Odd linear position: write to the low bits (right nibble).
250+
untiledData[destByteIndex] = (byte) ((untiledData[destByteIndex] & 0xF0) | pixelValue);
251+
}
252+
253+
// Move to the next 4-bit pixel in the source tile stream.
228254
dataIndex++;
229255
}
230256
}
231257
}
232258
}
233259
}
234-
235260
return untiledData;
236261
}
237262

263+
/// <summary>
264+
/// Tile for Revolution (Wii)
265+
/// </summary>
266+
/// <param name="pixelData">The source linear pixel data.</param>
267+
/// <param name="width">Image width.</param>
268+
/// <param name="height">Image height.</param>
269+
/// <param name="bitDepth">Image bit depth (e.g., 4 for CI4, 8 for CI8, 32 for RGBA8).</param>
270+
/// <returns>The tiled pixel data.</returns>
238271
public static byte[] TileTextureRvl(byte[] pixelData, int width, int height, int bitDepth = 32)
239272
{
273+
// Determine tile dimensions based on bit depth.
240274
int xTileSize = 4;
241275
int yTileSize = 4;
242276
if (bitDepth <= 8)
243277
{
244-
xTileSize = 8; // 4x8
278+
xTileSize = 8; // 8x4 for CI8/I8
245279
}
246-
247280
if (bitDepth <= 4)
248281
{
249-
yTileSize = 8; // 8x8
282+
yTileSize = 8; // 8x8 for CI4/I4
250283
}
251284

252-
bool wide = width > height;
253285
var byteSizePerPixel = bitDepth / 8.0f;
254286
var scale = 1;
255287
bool compactMode = false;
256288
int bpp = bitDepth / 8;
257289
byte[] tiledData;
290+
291+
// Handle compact formats where one byte stores multiple pixels (e.g., 4bpp).
258292
if (byteSizePerPixel < 1.0f)
259293
{
260294
compactMode = true;
261-
scale = (int) (1.0f / byteSizePerPixel);
295+
scale = (int) (1.0f / byteSizePerPixel); // For 4bpp, scale is 2.
262296
tiledData = new byte[width * height / scale];
263-
bpp = 1;
264297
}
265298
else
266299
{
267300
tiledData = new byte[width * height * bpp];
268301
}
269302

270-
int dataIndex = 0;
303+
int dataIndex = 0; // Tracks the current write position in the destination tiled data.
271304

305+
// Iterate through the image by tile blocks.
272306
for (int yt = 0; yt < height; yt += yTileSize)
273307
{
274308
for (int xt = 0; xt < width; xt += xTileSize)
275309
{
310+
// Iterate through pixels within a tile.
276311
for (int y = yt; y < yt + yTileSize; y++)
277312
{
278313
for (int x = xt; x < xt + xTileSize; x++)
279314
{
280315
if (x >= width || y >= height) continue;
281316

282-
int pixelIndex = ((y * width) + x) * bpp;
283-
284317
if (!compactMode)
285318
{
286-
Buffer.BlockCopy(pixelData, pixelIndex, tiledData, dataIndex, bpp);
319+
// Standard copy for formats with 1 or more bytes per pixel.
320+
int pixelIndex = ((y * width) + x) * bpp;
321+
if (pixelIndex + bpp <= pixelData.Length && dataIndex + bpp <= tiledData.Length)
322+
{
323+
Buffer.BlockCopy(pixelData, pixelIndex, tiledData, dataIndex, bpp);
324+
}
287325
dataIndex += bpp;
288326
}
289327
else
290328
{
291-
var currentPos = pixelIndex / scale;
292-
if (currentPos >= tiledData.Length)
329+
// Corrected logic for tiling compact formats (e.g., CI4).
330+
// 1. Find and extract the 4-bit pixel from the linear source data (pixelData).
331+
int srcPixelLinearIndex = (y * width) + x;
332+
int srcByteIndex = srcPixelLinearIndex / scale; // Which source byte to read from.
333+
int srcNibblePos = srcPixelLinearIndex % 2; // Which nibble (0 or 1) to read.
334+
335+
if (srcByteIndex >= pixelData.Length) continue;
336+
337+
byte sourceByte = pixelData[srcByteIndex];
338+
339+
// Extract the correct 4-bit pixelValue.
340+
byte pixelValue = (srcNibblePos == 0)
341+
? (byte) (sourceByte >> 4) // Get the left nibble.
342+
: (byte) (sourceByte & 0x0F); // Get the right nibble.
343+
344+
// 2. Write this 4-bit pixel to the next available position in the tiled destination.
345+
int destByteIndex = dataIndex / scale; // Which destination byte to write to.
346+
int destNibblePos = dataIndex % 2; // Which nibble in that byte to write to.
347+
348+
if (destByteIndex >= tiledData.Length) continue;
349+
350+
// Place the pixelValue into the correct nibble, preserving the other nibble.
351+
if (destNibblePos == 0)
293352
{
294-
return tiledData;
353+
// Even dataIndex: write to the high bits (left nibble).
354+
tiledData[destByteIndex] = (byte) ((tiledData[destByteIndex] & 0x0F) | (pixelValue << 4));
295355
}
296-
var srcData = pixelData[currentPos];
297-
var dstPosition = dataIndex / scale;
298-
var dstSubIndex = dataIndex % scale;
299-
tiledData[dstPosition] = dstSubIndex == 0
300-
? (byte) ((tiledData[dstPosition] & 0xF0) | srcData)
301-
: (byte) ((tiledData[dstPosition] & 0x0F) | (srcData << 4));
356+
else
357+
{
358+
// Odd dataIndex: write to the low bits (right nibble).
359+
tiledData[destByteIndex] = (byte) ((tiledData[destByteIndex] & 0xF0) | pixelValue);
360+
}
361+
362+
// Move to the next 4-bit pixel position in the destination tile stream.
302363
dataIndex++;
303364
}
304365
}
305366
}
306367
}
307368
}
308-
309369
return tiledData;
310370
}
311371

FreeMote/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,5 @@
2525
[assembly: InternalsVisibleTo("EmtMake")]
2626

2727
// [assembly: AssemblyVersion("1.0.*")]
28-
[assembly: AssemblyVersion("4.2.0.0")]
29-
[assembly: AssemblyFileVersion("4.2.0.0")]
28+
[assembly: AssemblyVersion("4.3.0.0")]
29+
[assembly: AssemblyFileVersion("4.3.0.0")]

FreeMote/PsbFile.cs

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,8 @@ public void ParseHeader()
2828
throw new FileNotFoundException("Can not load file.", Path);
2929
}
3030

31-
using (var fs = File.OpenRead(Path))
32-
{
33-
ParseHeader(fs);
34-
}
31+
using var fs = File.OpenRead(Path);
32+
ParseHeader(fs);
3533
}
3634

3735
private void ParseHeader(Stream stream)
@@ -82,19 +80,7 @@ public bool TestHeaderEncrypted()
8280

8381
return true;
8482
}
85-
86-
[Obsolete("Not Implemented")]
87-
private static bool TestKeyValidForHeader()
88-
{
89-
throw new NotImplementedException();
90-
}
91-
92-
[Obsolete("Not Implemented")]
93-
private static bool TestKeyValidForBody()
94-
{
95-
throw new NotImplementedException();
96-
}
97-
83+
9884
public static bool TestHeaderEncrypted(Stream stream, PsbHeader header)
9985
{
10086
//MARK: Not always works
@@ -502,7 +488,7 @@ void WriteOriginal()
502488
WriteOriginalBody(br, bw);
503489
break;
504490
}
505-
else //Header Clean; Body Encrpyted
491+
else //Header Clean; Body Encrypted
506492
{
507493
bw.Write((ushort) 0); //
508494
if (headerEnc)

0 commit comments

Comments
 (0)