Savegame AIR EditorFlash Builder Project 4.0 by me, updated the above pack: Dune Savegame Editor v2.7z Windows installer for it: DuneSGE.zip
From: http://gotoandlearnforum.com/viewtopic.php?f=35&t=29721 Written by tox: hey folks, ever wanted to edit the savegames of Dune? Well, I wanted to, but I couldn't find any editor... obviously no-one ever made one because the game uses an unusual compression method for the gamedata including the savegames. After a lot of research I found out how it works and made an editor for it. The classes I used follow. Mind you, even though I did a lot of testing I can't guarantee it's bug-free. Have fun and let me know if you do something with this. Have fun! DuneSavegameGives access to the savegames of Dune. package de.webcodesign.dune
{
import flash.events.Event;
/**
* Gives access to the savegames of Dune.
* @author Tox
* @version 1.0
*/
public class DuneSavegame
{
public var sietches:Array2D = new Array2D(0x0B, 0x0C);
public var sietchNames:Array = [];
private var sc:SavegameCompression = new SavegameCompression(); // The compression-algorithm
private var noCompression:Boolean = false; // If set no decompression / compression is used
private var compressedSave:Array = []; // The compressed savegame
private var decompressedSave:Array = []; // The decompresssed savegame
private var saveSequences:Array = SietchSequences.compressed; // sequences used to identify the sietches (compressed, i.e. savegame )
private var exeSequences:Array = SietchSequences.uncompressed; // sequences used to identify the sietches (uncompressed, i.e. exe)
private var offsets:Array2D = new Array2D(0x0B, 0x0C); // offsets of the sietches / fortresses / palaces
private var sietchStartIndex:uint = 0; // beginning offset of the sietch-block
private var sietchEndIndex:uint = 0; // last offset of the sietch-block
public function DuneSavegame() { }
public function sietchExists(region:uint, subregion:uint):Boolean { return (sietches.read(subregion, region) undefined) ? false : true; }
private function getSietchOffsets():void
{
var offsets1:Array = [], offsets2:Array = [];
var i:uint = 0, j:uint = 0, k:uint = 0, l:uint = saveSequences.length, m:int = 0;
var sietchFound:Boolean = false;
// Find all possible offsets and put them into the sOff Array2D
while (m < l)
{
if (m < l - 1) offsets.write(saveSequences[m][1], saveSequences[m][0], findSequence(decompressedSave, saveSequences[m]))
else offsets.write(saveSequences[m - 1][1] + 1, saveSequences[m - 1][0], findSequence(decompressedSave, saveSequences[m]));
m++;
}
// Test whether the found offset is connected to a sietch
l--;
while (i < l)
{
if (i+1 < 70)
{
offsets1 = offsets.read(saveSequences[i][1],saveSequences[i][0]);
offsets2 = offsets.read(saveSequences[i + 1][1], saveSequences[i + 1][0]);
}
else
{
offsets1 = offsets.read(saveSequences[i-1][1]+1,saveSequences[i-1][0]);
offsets2 = offsets.read(0x07, 0x0C);
}
if ((offsets2[0] - offsets1[0]) > 0x20 || (offsets2[0] - offsets1[0]) < 0x00)
{
for (j = 0; j < offsets2.length; j++)
{
for (k = 0; k < offsets1.length; k++)
{
if ((offsets2[j] - offsets1[k] <= 0x20) && (offsets2[j] - offsets1[k] > 0x00))
{
if (i + 1 < 70)
{
offsets.write(saveSequences[i][1], saveSequences[i][0], new Array([offsets1[k]]));
offsets.write(saveSequences[i + 1][1], saveSequences[i + 1][0], new Array([offsets2[j]]));
}
else
{
offsets.write(saveSequences[i - 1][1] + 1, saveSequences[i - 1][0], new Array([offsets1[k]]));
offsets.write(0x07, 0x0C, new Array([offsets2[j]]));
}
sietchFound = true;
}
if (sietchFound) break;
}
if (sietchFound) break;
}
sietchFound = false;
}
i++;
}
}
public function load(savegame:File):void
{
var sietchList:Array = [];
var offsets1:uint = 0;
var offsets2:uint = 0;
var region:uint = 0;
var subregion:uint = 0;
var i:uint = 0;
var l:uint = saveSequences.length-1;
var sietch:Sietch;
var fs:FileStream = new FileStream();
fs.open(savegame, FileMode.READ);
noCompression = (savegame.type.toLowerCase() '.exe') ? true : false;
// Decompressing the savegame
decompressedSave = (noCompression) ? copyFileStreamToArray(fs) : sc.decompress(fs);
fs.close();
// Exchange sietch identification sequences if editing noCompression
if (noCompression) saveSequences = exeSequences;
// Finding all offsets for the sietches
getSietchOffsets();
// Reading begin and end offset of the sietch part
sietchStartIndex = offsets.read(0x01, 0x02)[0];
sietchEndIndex = offsets.read(0x07, 0x0C)[0];
// Getting the sietch part from the decompressed savegame
sietchList = decompressedSave.slice(sietchStartIndex, sietchEndIndex);
sietchList.splice(sietchEndIndex - sietchStartIndex);
if (!noCompression) sietchList = sc.removeF7ControlSequence(sietchList);
// Loading all sietches into a Array2D
while (i < l)
{
// Getting the ID of the sietch's region and subregion
if (i < 70)
{
region = saveSequences[i][0];
subregion = saveSequences[i][1];
}
else
{
region = saveSequences[i-1][0]+1;
subregion = saveSequences[i-1][1];
}
// Setting the offset bounds
offsets1 = i * 0x1C;
offsets2 = (i+1) * 0x1C;
// Creating a new sietch
sietch = new Sietch(sietchList.slice(offsets1, offsets2));
// Adding the sietchname to the list
(subregion >= 0x03) ? sietchNames.push({label:sietch.region+'-'+sietch.subregion, data:[region, subregion]}) : sietchNames.push( { label:sietch.subregion, data:[region, subregion] } );
// Adding the sietch to a Array2D
sietches.write(subregion, region, sietch);
i++;
}
}
public function save():void
{
var startPart:Array = []; // Beginning of save to beginning of sietch part
var sietchPart:Array = []; // Sietch part
var endPart:Array = []; // End of sietch part to save end
var savegame:Array = []; // The complete savegame
var fSave:File = new File(); // The savegame file
// Temporary array for the sietches
var sietchesTemp:Array = [];
var i:uint = 0, l:uint = saveSequences.length - 1, m:uint = 0;
startPart = decompressedSave.slice(0, sietchStartIndex);
endPart = decompressedSave.slice(sietchEndIndex);
for (i = 0; i < l; i++)
{
sietchesTemp = loadSietch(saveSequences[i][0], saveSequences[i][1]).toArray();
for (m = 0; m < sietchesTemp.length; m++) sietchPart.push(sietchesTemp[m]);
}
if (!noCompression)
{
startPart = sc.compressArray(startPart);
sietchPart = sc.insertF7ControlSequence(sietchPart);
sietchPart = sc.compressArray(sietchPart);
endPart = sc.compressArray(endPart);
}
for (i = 0; i < startPart.length; i++) savegame.push(startPart[i]);
for (i = 0; i < sietchPart.length; i++) savegame.push(sietchPart[i]);
for (i = 0; i < endPart.length; i++) savegame.push(endPart[i]);
compressedSave = savegame;
fSave.addEventListener
(
Event.SELECT,
function (e:Event):void
{
var fsSave:FileStream = new FileStream();
var fNew:File = e.target as File;
fsSave.open(fNew, FileMode.WRITE);
var i:uint = 0;
while (fsSave.position < compressedSave.length) fsSave.writeByte(compressedSave[i++]);
fsSave.close();
}
);
fSave.browseForSave('Save savegame...');
}
private function loadSietch(region:uint, subregion:uint):Sietch { return sietches.read(subregion, region); }
private function findSequence(source:Array, sequences:Array):Array
{
var b:Array = [];
var l:uint = source.length-sequences.length, c:uint = 0, j:uint = 0, v:uint = 0, i:uint = 0;
while (i < l)
{
c = source[i];
j = source[i + 1];
v = source[i + 2];
if ((c sequences[0]) && (j sequences[1]) && (v sequences[2]))
{
b.push(i);
i += 3;
}
else i++;
}
return b;
}
private function copyFileStreamToArray(fs:FileStream):Array
{
fs.position = 0;
var l:uint = fs.bytesAvailable;
var a:Array = [];
while (fs.position < l) a.push(fs.readUnsignedByte());
return a;
}
}
}
SavegameCompressionAlgorithm for compressing / decompressing savegames used by the game Dune.
package de.webcodesign.dune
{
/**
* Algorithm for compressing / decompressing savegames used by the game Dune.
* @author Tox
* @version 1.0
*/
public class SavegameCompression
{
public function SavegameCompression() { }
public function decompress(fs:FileStream):Array
{
fs.position = 0;
var a:Array = [], b:Array = [];
var i:uint = 0, l:uint = fs.bytesAvailable;
while (fs.position < l) a.push(fs.readUnsignedByte());
b = decompressArray(a);
return b;
}
public function compress(fs:FileStream):Array
{
fs.position = 0;
var a:Array = [], b:Array = [];
var i:uint = 0, l:uint = fs.bytesAvailable;
while (fs.position < l) a.push(fs.readUnsignedByte);
b = compressArray(a);
return b;
}
public function decompressArray(a:Array):Array
{
var b:Array = [];
var l:uint = a.length, c:uint = 0, i:uint = 0, j:uint = 0, k:uint = 0, v:uint = 0;
while (i < l)
{
c = a[i];
j = a[i + 1];
v = a[i + 2];
if (isControlSequence(c, j, v))
{
b.push(c);
b.push(j);
b.push(v);
i += 3;
}
else if (isReplaceSequence(c, j, v))
{
k = 0;
while (k < j) { b.push(v); k++; }
i += 3;
}
else
{
b.push(c);
i++;
}
}
return b;
}
public function compressArray(a:Array):Array
{
var b:Array = [];
var l:uint = a.length, c:uint = 0, v:uint = 0, count:uint = 0, i:uint = 0, j:uint = 0;
while (i < l)
{
c = a[i];
j = a[i + 1];
v = a[i + 2];
if (isControlSequence(c, j, v))
{
b.push(c);
b.push(j);
b.push(v);
i += 3;
}
else
{
count = 1;
j = i+1;
while (j < l)
{
v = a[j];
(v c) ? count++ : j = l;
j++;
}
if (count >= 0x03)
{
b.push(0xF7);
b.push(count);
b.push(c);
i += count;
}
else
{
b.push(c);
i++;
}
}
}
return b;
}
private function isControlSequence(a:uint, b:uint, c:uint):Boolean { return ((a 0xF7)&&((b 0x01 && c a) || (b 0xFF && c 0x00))); }
private function isReplaceSequence(a:uint, b:uint, c:uint):Boolean { return (a 0xF7 && b > 0x02 && b < 0xFF); }
public function removeF7ControlSequence(a:Array):Array
{
var b:Array = [];
var i:uint = 0, l:uint = a.length, c:uint = 0, j:uint = 0, v:uint = 0;
while (i < l)
{
c = a[i];
j = a[i + 1];
v = a[i + 2];
if ((c 0xF7) && (j 0x01) && (v 0xF7))
{
b.push(c);
i += 3;
}
else
{
b.push(c);
i++;
}
}
return b;
}
public function insertF7ControlSequence(a:Array):Array
{
var b:Array = [];
var i:uint = 0, l:uint = a.length;
while (i < l)
{
if (a[i] 0xF7)
{
b.push(0xF7);
b.push(0x01);
b.push(0xF7);
}
else b.push(a[i]);
i++;
}
return b;
}
}
}
RegionsUse this class to get the names of sietches.
package de.webcodesign.dune
{
/**
* Use this class to get the names of sietches.
* @author Tox
* @version 1.0
*/
public class Regions
{
public static function region(ID:uint):String
{
var res:String = ';
switch (ID)
{
case 0x01: res = 'Arrakeen'; break;
case 0x02: res = 'Carthag'; break;
case 0x03: res = 'Tuono'; break;
case 0x04: res = 'Habbanya'; break;
case 0x05: res = 'Oxtyn'; break;
case 0x06: res = 'Tsympo'; break;
case 0x07: res = 'Bledan'; break;
case 0x08: res = 'Ergsun'; break;
case 0x09: res = 'Haga'; break;
case 0x0A: res = 'Cielago'; break;
case 0x0B: res = 'Sihaya'; break;
case 0x0C: res = 'Celimyn'; break;
}
return res;
}
public static function subregion(ID:uint):String
{
var res:String = ';
switch (ID)
{
case 0x1: res = 'Atreides Palace'; break;
case 0x2: res = 'Harkonnen Palace'; break;
case 0x3: res = 'Tabr'; break;
case 0x4: res = 'Timin'; break;
case 0x5: res = 'Tuek'; break;
case 0x6: res = 'Harg'; break;
case 0x7: res = 'Clam'; break;
case 0x8: res = 'Tsymyn'; break;
case 0x9: res = 'Siet'; break;
case 0xA: res = 'Pyons'; break;
case 0xB: res = 'Pyort'; break;
}
return res;
}
}
}
SietchRepresents a sietch. The Bitfield datatype can be found here: viewtopic.php?f=35&t=29714
package de.webcodesign.dune
{
import de.webcodesign.datatypes.Bitfield;
/**
* Represents a sietch.
* @author Tox
* @version 1.0
*/
public class Sietch
{
private var _region:uint = 0;
private var _subregion:uint = 0;
private var _status:Bitfield = new Bitfield(0);
public var desertAroundSietch:uint = 0;
public var mapPositionX:uint = 0;
public var mapPositionY:uint = 0;
public var unknown1:uint = 0;
public var positionX:uint = 0;
public var positionY:uint = 0;
public var type:uint = 0;
public var troop:uint = 0;
public var unknown2:uint = 0;
public var unknown3:uint = 0;
public var unknown4:uint = 0;
public var unknown5:uint = 0;
public var unknown6:uint = 0;
public var connectedArea:uint = 0;
public var amountSpice:uint = 0;
public var unknown7:uint = 0;
public var unknown8:uint = 0;
public var harvesters:uint = 0;
public var ornis:uint = 0;
public var krys:uint = 0;
public var laserguns:uint = 0;
public var weirdingModules:uint = 0;
public var atomics:uint = 0;
public var bulbs:uint = 0;
public var amountWater:uint = 0;
private var _tmpSietch:Array = [];
public function Sietch(sietch:Array)
{
_tmpSietch = sietch;
_region = getByte(0x00);
_subregion = getByte(0x01);
desertAroundSietch = getByte(0x02);
mapPositionX = getByte(0x03);
mapPositionY = getByte(0x04);
unknown1 = getByte(0x05);
positionX = getByte(0x06);
positionY = getByte(0x07);
type = getByte(0x08);
troop = getByte(0x09);
_status.bitfield = getByte(0x0A);
unknown2 = getByte(0x0B);
unknown3 = getByte(0x0C);
unknown4 = getByte(0x0D);
unknown5 = getByte(0x0E);
unknown6 = getByte(0x0F);
connectedArea = getByte(0x10);
amountSpice = getByte(0x11);
unknown7 = getByte(0x12);
unknown8 = getByte(0x13);
harvesters = getByte(0x14);
ornis = getByte(0x15);
krys = getByte(0x16);
laserguns = getByte(0x17);
weirdingModules = getByte(0x18);
atomics = getByte(0x19);
bulbs = getByte(0x1A);
amountWater = getByte(0x1B);
}
public function toArray():Array
{
setByte(0x00, _region);
setByte(0x01, _subregion);
setByte(0x02, desertAroundSietch);
setByte(0x03, mapPositionX);
setByte(0x04, mapPositionY);
setByte(0x05, unknown1);
setByte(0x06, positionX);
setByte(0x07, positionY);
setByte(0x08, type);
setByte(0x09, troop);
setByte(0x0A, _status.bitfield);
setByte(0x0B, unknown2);
setByte(0x0C, unknown3);
setByte(0x0D, unknown4);
setByte(0x0E, unknown5);
setByte(0x0F, unknown6);
setByte(0x10, connectedArea);
setByte(0x11, amountSpice);
setByte(0x12, unknown7);
setByte(0x13, unknown8);
setByte(0x14, harvesters);
setByte(0x15, ornis);
setByte(0x15, ornis);
setByte(0x16, krys);
setByte(0x17, laserguns);
setByte(0x18, weirdingModules);
setByte(0x19, atomics);
setByte(0x1A, bulbs);
setByte(0x1B, amountWater);
return _tmpSietch;
}
private function getByte(byte:uint):uint { return _tmpSietch[byte]; }
private function setByte(byte:uint, value:uint):void { _tmpSietch[byte] = value; }
public function get length():uint { return _tmpSietch.length; }
public function get region():String { return Regions.region(_region); }
public function get subregion():String { return Regions.subregion(_subregion); }
public function get hasVegetationBegun():Boolean { return _status.getBit(0); }
public function set hasVegetationBegun(value:Boolean):void { _status.setBit(0, value); }
public function get underAttack():Boolean { return _status.getBit(1); }
public function set underAttack(value:Boolean):void { _status.setBit(1, value); }
public function get sietchInfiltrated():Boolean { return _status.getBit(2); }
public function set sietchInfiltrated(value:Boolean):void { _status.setBit(2, value); }
public function get battleWon():Boolean { return _status.getBit(3); }
public function set battleWon(value:Boolean):void { _status.setBit(3, value); }
public function get fremenFound():Boolean { return _status.getBit(4); }
public function set fremenFound(value:Boolean):void { _status.setBit(4, value); }
public function get hasWindtrap():Boolean { return _status.getBit(5); }
public function set hasWindtrap(value:Boolean):void { _status.setBit(5, value); }
public function get spiceLocated():Boolean { return _status.getBit(6); }
public function set spiceLocated(value:Boolean):void { _status.setBit(6, value); }
public function get isHidden():Boolean { return _status.getBit(7); }
public function set isHidden(value:Boolean):void { _status.setBit(7, value); }
}
}
SietchSequencesHolds the sequences used to indentify sietches.
package de.webcodesign.dune
{
/**
* Holds the sequences used to indentify sietches.
* @author Tox
* @version 1.0
*/
public class SietchSequences
{
// use when opening a savegame
public static var compressed:Array = new Array
(
/* Palaces */
[0x02, 0x01, 0x15], [0x01, 0x02, 0xCF],
/* Arrakeen */
[0x01, 0x03, 0xFE], [0x01, 0x04, 0x38], [0x01, 0x05, 0x96], [0x01, 0x06, 0x37],
[0x01, 0x07, 0xD0], [0x01, 0x08, 0x0A], [0x01, 0x09, 0x89], [0x01, 0x0A, 0x6C],
/* Carthag */
[0x02, 0x03, 0x70], [0x02, 0x04, 0x4E], [0x02, 0x05, 0x55], [0x02, 0x06, 0x02],
[0x02, 0x07, 0x5D],
/* Tuono */
[0x03, 0x03, 0x12], [0x03, 0x04, 0x9A], [0x03, 0x05, 0x2F], [0x03, 0x06, 0x55],
[0x03, 0x07, 0x76], [0x03, 0x0A, 0x7E],
/* Habbanya (Harg & Clam are exchanged in the savegame...)*/
[0x04, 0x03, 0xC7], [0x04, 0x04, 0x47], [0x04, 0x05, 0xB1], [0x04, 0x07, 0xA1],
[0x04, 0x06, 0xD1],
/* Oxtyn */
[0x05, 0x03, 0xF7], [0x05, 0x04, 0x52], [0x05, 0x05, 0xA8], [0x05, 0x06, 0xA0],
[0x05, 0x0A, 0xC1],
/* Tsympo */
[0x06, 0x03, 0xF3], [0x06, 0x04, 0xF0], [0x06, 0x05, 0xC5], [0x06, 0x06, 0x27],
[0x06, 0x07, 0x29], [0x06, 0x08, 0x84], [0x06, 0x09, 0x46], [0x06, 0x0A, 0xFA],
[0x06, 0x0B, 0xCC],
/* Bledan */
[0x07, 0x03, 0x00], [0x07, 0x04, 0xE3], [0x07, 0x05, 0x4F], [0x07, 0x06, 0x6C],
/* Ergsun */
[0x08, 0x03, 0x61], [0x08, 0x04, 0x7F], [0x08, 0x05, 0x74], [0x08, 0x06, 0xC6],
[0x08, 0x07, 0x70], [0x08, 0x08, 0xF1],
/* Haga */
[0x09, 0x03, 0xE2], [0x09, 0x04, 0x32], [0x09, 0x05, 0x00], [0x09, 0x06, 0x18],
[0x09, 0x07, 0xE3], [0x09, 0x08, 0x14], [0x09, 0x09, 0xBE], [0x09, 0x0A, 0x03],
/* Cielago */
[0x0A, 0x03, 0xB5], [0x0A, 0x04, 0x95],
/* Sihaya */
[0x0B, 0x03, 0x4C], [0x0B, 0x04, 0xF3], [0x0B, 0x05, 0xDF], [0x0B, 0x06, 0xA2],
[0x0B, 0x07, 0xDE], [0x0B, 0x0A, 0xF0],
/* Celimyn */
[0x0C, 0x03, 0xB6], [0x0C, 0x04, 0x93], [0x0C, 0x05, 0xF6], [0x0C, 0x06, 0x00],
/* End of block delimiter */
[0xFF, 0xFF, 0x01]
);
// use when opening the game executable
public static var uncompressed:Array = new Array
(
/* Palaces */
[0x02, 0x01, 0x15], [0x01, 0x02, 0x98],
/* Arrakeen */
[0x01, 0x03, 0xFE], [0x01, 0x04, 0x1D], [0x01, 0x05, 0xAE], [0x01, 0x06, 0x37],
[0x01, 0x07, 0xD0], [0x01, 0x08, 0x0C], [0x01, 0x09, 0x89], [0x01, 0x0A, 0x19],
/* Carthag */
[0x02, 0x03, 0x48], [0x02, 0x04, 0x4E], [0x02, 0x05, 0x48], [0x02, 0x06, 0x1E],
[0x02, 0x07, 0x30],
/* Tuono */
[0x03, 0x03, 0xC8], [0x03, 0x04, 0x9A], [0x03, 0x05, 0x2F], [0x03, 0x06, 0x54],
[0x03, 0x07, 0x78], [0x03, 0x0A, 0x78],
/* Habbanya (Harg & Clam are exchanged in the savegame...)*/
[0x04, 0x03, 0xC7], [0x04, 0x04, 0x8D], [0x04, 0x05, 0xB1], [0x04, 0x07, 0xA8],
[0x04, 0x06, 0xD1],
/* Oxtyn */
[0x05, 0x03, 0xF7], [0x05, 0x04, 0x52], [0x05, 0x05, 0xCA], [0x05, 0x06, 0x9E],
[0x05, 0x0A, 0x75],
/* Tsympo */
[0x06, 0x03, 0xF3], [0x06, 0x04, 0xF0], [0x06, 0x05, 0xFB], [0x06, 0x06, 0x27],
[0x06, 0x07, 0x29], [0x06, 0x08, 0x7C], [0x06, 0x09, 0x46], [0x06, 0x0A, 0x2F],
[0x06, 0x0B, 0xCC],
/* Bledan */
[0x07, 0x03, 0x00], [0x07, 0x04, 0xE3], [0x07, 0x05, 0x4F], [0x07, 0x06, 0x6C],
/* Ergsun */
[0x08, 0x03, 0x61], [0x08, 0x04, 0x7F], [0x08, 0x05, 0x74], [0x08, 0x06, 0xC6],
[0x08, 0x07, 0x70], [0x08, 0x08, 0xF1],
/* Haga */
[0x09, 0x03, 0xE2], [0x09, 0x04, 0x32], [0x09, 0x05, 0x00], [0x09, 0x06, 0x18],
[0x09, 0x07, 0xE3], [0x09, 0x08, 0x14], [0x09, 0x09, 0xBE], [0x09, 0x0A, 0xF1],
/* Cielago */
[0x0A, 0x03, 0xB5], [0x0A, 0x04, 0x95],
/* Sihaya */
[0x0B, 0x03, 0x4C], [0x0B, 0x04, 0xF3], [0x0B, 0x05, 0xDF], [0x0B, 0x06, 0xB0],
[0x0B, 0x07, 0xDE], [0x0B, 0x0A, 0xAE],
/* Celimyn */
[0x0C, 0x03, 0xB6], [0x0C, 0x04, 0x93], [0x0C, 0x05, 0xF5], [0x0C, 0x06, 0x00],
/* End of block delimiter */
[0xFF, 0xFF, 0x01]
);
}
}
|
Dune Savegame Editor v2.7z Dune Savegame Editor.rar DuneSGE.zip DuneSGE_air.zip |