Re: Savefile Character / Map Editor
Posted: September 14th, 2010, 9:07 am
Oh, sweet. Really looking forward to this. :)Jedi_Learner wrote:Here are some screenshots of what I'm working on.
Basilisk Games Offical Forums
https://basiliskgames.com/forums/
Oh, sweet. Really looking forward to this. :)Jedi_Learner wrote:Here are some screenshots of what I'm working on.
Huh, weird. I'm actually totally unfamiliar with Parallels; is that some kind of VM type thing? Are you actually running Windows in a separate process, or is it some kind of emulation layer like Cider?laurelt wrote:Unable to execute file:
C:\Program Files\Eschalon Utilities\eschalon_b1_char.exe
CreateProcess failed; code 14001.
This application has failed to start because the application configuration is incorrect. Reinstalling the application may fix this problem.
Code: Select all
add_gold 83510 ; screen_fade_out ; map_port 52 ; move_player 7018 ; screen_fade_in ; areacheck
Code: Select all
screen_fade_out ; map_port (Darkford) ; updatezones ; areacheck ; screen_fade_in
Code: Select all
screen_fade_out ; move_player 5753 ; strip_items ; message (You wake laying upon cold dirt. The smell of death is all around you...) ; screen_fade_in
Code: Select all
# More Unknowns
for i in range(17):
self.unknown.iblock1.append(self.df.readint())
for i in range(5):
self.unknown.ssiblocks1.append(self.df.readstr())
self.unknown.ssiblocks2.append(self.df.readstr())
self.unknown.ssiblocki.append(self.df.readint())
self.unknown.extstr1 = self.df.readstr()
self.unknown.extstr2 = self.df.readstr()
Code: Select all
# More Unknowns
for i in range(16):
self.unknown.iblock1.append(self.df.readint())
for i in range(6):
self.unknown.ssiblocki.append(self.df.readint())
self.unknown.ssiblocks1.append(self.df.readstr())
self.unknown.ssiblocks2.append(self.df.readstr())
Code: Select all
for i in range(4):
self.fxblock.append(self.df.readint())
# An unknown, seems to be a multiple of 256
self.unknown.anotherint = self.df.readint()
# Character profile pic (multiple of 256, for some reason)
self.picid = self.df.readint()
# Disease flag
self.disease = self.df.readint()
# More Unknowns. Apparently there's one 2-byte integer in here, too.
self.unknown.shortval = self.df.readshort()
self.unknown.emptystr = self.df.readstr()
Code: Select all
# 5 byte bitfield.
self.unknown.fxbits = self.df.readint()
# Always 3F?
self.unknown.fx3f = self.df.readbyte()
# 4 unknown ints
for i in range(4):
self.fxblock.append(self.df.readbyte())
# An unknown.
self.unknown.anotherint = self.df.readint()
# Character profile pic.
self.picid = self.df.readint()
# Disease flag
self.disease = self.df.readint()
# More Unknowns. Apparently there's a byte in here, too.
self.unknown.byteval = self.df.readbyte()
self.unknown.emptystr = self.df.readstr()
That is, rather than:
08 52 B8 1E
3F 0F 00 00
00 5F 00 00
00 5A 00 00
00 01 00 00 - int multiple of 256
00 00 00 00 - int profile pic multiple of 256
00 00 00 00 - int disease - is this x256 - see constantsb1.py
00 00 - short
0D 0A - string
To me, it looks like:
08 52 B8 1E - sfx/lighting bitfield?
3F - Always 3F?
0F 00 00 00 - int
5F 00 00 00 - int
5A 00 00 00 - int
01 00 00 00 - int
00 00 00 00 - int profile pic
00 00 00 00 - int disease - need to edit constantsb1.py, see below.
00 - byte
0D 0A - string
That then changes constantsb1.py's code from:
diseasetable = {
0x0200: 'Dungeon Fever',
0x0400: 'Rusty Knuckles',
0x0800: 'Eye Fungus',
0x1000: 'Blister Pox',
0x2000: 'Insanity Fever',
0x4000: 'Fleshrot',
0x8000: 'Cursed'
}
to:
diseasetable = {
0x02: 'Dungeon Fever',
0x04: 'Rusty Knuckles',
0x08: 'Eye Fungus',
0x10: 'Blister Pox',
0x20: 'Insanity Fever',
0x40: 'Fleshrot',
0x80: 'Cursed'
}
Oh, cool! Thanks for the notes; I'll integrate them in with my docs soonish; I've been meaning to implement some things for the next version of the editor but haven't actually taken the time to work on it for awhile now (damn Minecraft, damn Fathamurk...) Anyway, I appreciate it; especially the work you've put into deciphering some of those unknown values. It's been awhile since I've looked at those.DewiMorgan wrote:...
That's largely right - if you expand the thingy next to the items in the main list, you do have the ability to at least copy/paste items, so you can clone items pretty easily and then just modify the single fields that you're interested in. I would be the first to agree that the UI is hardly ideal; I'm a commandline guy at heart, and I would never list UI design amongst my strengths. :)DewiMorgan wrote:I just tried actually installing and running the character editor and noticed that... well... you can't actually use it. At least, not for what I intended, which was to give myself a bunch of skill books and see what changed in the save file when I read one.
Far as I can tell, you can't just select an item type to plonk into a slot: you have to manually edit every field. Bleegh. But I might be missing something in the UI.
Ah, nifty. I've specifically avoided trying to read anything out of the actual game executable, though, and I'm pretty unlikely to do so in the future. I'd have to maintain an understanding of the executable on three different platforms (linux/mac/win), and then Windows of course is bundled by different content distribution networks as well, which could change things around. In the end I'm just not interested in maintaining that kind of thing (and I wouldn't want to distribute an extracted CSV with the util; I'd like to keep the editor as "clean" as possible).In the exe file, beginning around 13EF9C, there's a "master_item_sheet.csv" table of all the default items in the game. Might be worth having your code parse that and use it to give users a picker where they can grab any item prefab they want.
Yeah, though once again, I don't have any interest in my util parsing content in the executable itself, and I don't want to redistribute content embedded in there, either. (Not to mention that actually editing that information would be problematic at best; I'd only really be able to do a readonly view of those things, which doesn't strike me as worth the effort.) For what it's worth, BW has mentioned a few times that he'll probably release his own game editing tools when Book 3 comes out (or at least at some point after Book 3 is out), at which point we'd have a more comprehensive set of tools available to work on things like conversations, etc).If you can extract.... you can maybe embed, too. And that will give your editor control over conversation trees, and book contents, and so much more. Translations. Grammar/spelling fixes. New NPCs. You name it.
Get better! And thanks for the tips re: scripting.5:30am. Off to bed.
Code: Select all
<?php
$filenames = array('book_2.exe', 'eschalon_book_1.exe', 'eschalon_book_1', 'Eschalon Book I');
$fileFound = 0;
foreach ($filenames as $filename) {
if (file_exists($filename)) {
if ($filename == 'book2.exe') {
$folder = 'resources';
}
else {
$folder = 'incbin';
}
@mkdir($folder);
$fileFound = 1;
break;
}
}
if ($fileFound == 0) {
echo "Error: Could not find the executable.\n";
exit;
}
# Every exe I've checked uses a different value for 'constant' :(
# pad [ constant ] [ maxint ] [ stringlength ] [ string ] pad
$unicodeRegex = '/(\x90*[\x00-\xff]{4}\xff\xff\xff\x7f[\x00-\xFF][\x00-\xFF]\x00\x00([\x20-\x7e]\x00)+\x90*)/';
$file = preg_split($unicodeRegex, file_get_contents($filename), -1, PREG_SPLIT_DELIM_CAPTURE);
$unicodeStrings = '';
$fileCount = $stringCount = 0;
for ($i = 0; $i < count($file); $i++) {
if (!preg_match($unicodeRegex, $file[$i])) { continue; }
// Clean unicode to ascii, primitively.
$string = preg_replace('/^\x90*[\x00-\xff]{4}\xff\xff\xff\x7f[\x00-\xFF][\x00-\xFF]|\x00|\x90/', '', $file[$i]);
if (substr($string, 0, strlen($folder)+1) == "$folder/") {
if (preg_match('/\.ttf$/', $string)) { // Fix TTF leading nulls.
$file[$i-1] = substr($file[$i-1], strpos($file[$i-1], "\x00\x01\x00\x00"));
}
file_put_contents($string, $file[$i-1]);
$fileCount++;
}
$unicodeStrings .= $string . "\n";
$stringCount++;
}
file_put_contents("$folder/unicodestrings.txt", $unicodeStrings);
echo "Exported $fileCount files to $folder/\n";
echo "Exported $stringCount strings to $folder/unicodestrings.txt\n";
Code: Select all
int7val : file : times found
06 : 58.tre : x 1
11 : 56.tre : x 1
12 : 56.tre : x 1
14 : 67.tre : x 2
16 : 67.tre : x 1
17 : 63.tre : x 1
20 : 83.tre : x 1
21 : 72.tre : x 1
22 : 73.tre : x 1
25 : 77.tre : x 3
27 : 80.tre : x 3
45 : 85.tre : x 1
46 : 85.tre : x 1
47 : 76.tre : x 1
49 : 86.tre : x 1
50 : 86.tre : x 1
Code: Select all
int7val : file : times found
1:67.tre:x 2
1:72.tre:x 1
1:77.tre:x 2
1:86.tre:x 1
2:67.tre:x 1
2:80.tre:x 3
2:86.tre:x 1
5:56.tre:x 1
9:56.tre:x 1
9:58.tre:x 1
9:73.tre:x 1
9:77.tre:x 1
9:83.tre:x 1
9:85.tre:x 1
12:85.tre:x 1
Code: Select all
[08][02][LL]"I got your thingy [Give Thingy]
[LL]"My thingy!"
[18][01][00][00][00][00][09]
Thingy\r\n
remove_item (Thingy) ; add_gold 10 ; quest 24 9\r\n
\r\n\r\n
Code: Select all
[02][02][LL]"Yes, I'd like to have a look at your inventory."
[LL]"Thanks for your patronage"
[00][00][00][00][00][00][03]
\r\n
init_trade (gunther)\r\n
\r\n\r\n
Code: Select all
[03] - int1=id (ids of [00] are initial greetings, so won't be blank.)
[00] - int2=no category
[00] - int3+str1=zero length PC conversation string
[00] - int4+str2=zero length NPC conversation string
[00] - int5=no quest to check
[00] - int6=no quest stage to check
[00] - int7=unknown
[00] - int8=unknown, reference to another int1?
[00] - int9=unknown, always [00]
[00] - int10=unknown, always [00]
[00] - int11=no subsequent category
\r\n - str3=no item needed
\r\n - str4=no script to run
\r\n\r\n - str5+str6=empty data strings
Code: Select all
Initial greeting: A man waves. "Hi, I'm Joe."
Subsequent greeting: Joe looks up and waves.
Then displays bottom level of this conversation tree.
"Train me!" / (training happens) "Thanks."
- "[Back]" / ""
"Trade with me!" / (trading happens) "Thanks."
- "[Back]" / ""
"Tell me about yourself." / "I'm boring."
- "Nevermind, then." / ""
- "Tell me anyway." / "I'm REALLY boring."
- "Nevermind, then." / ""
- "No really, tell me." / "Once upon a time...."
- "That was gripping!" / ""
(if not already told to get a fish)
"Give me a job!" / "OK, get me a fish."
- "OK, brb" / ""
- end convo.
- "I've got one on me." / "Great."
- "[Back]" / ""
(if told to get one, and has one)
"I've got you a fish." / "Great."
- "[Back]" / ""
"I have to go."
- end convo.
Code: Select all
[00][00]
[LL=00] - empty PC string for greetings.
[LL]A man waves. "Hi, I'm Joe."
[00][00][00][00][00][00][02] - display cat 2.
\r\nquest 63 0\r\n\r\n\r\n - initial greeting zeroes the quest, just in case.
[01][01]
[LL=00]
[LL]Joe looks up and waves.
[00][00][00][00][00][00][02] - display cat 2.
\r\n\r\n\r\n\r\n
[02][02]
[LL]"Train me!"
[LL]"Thanks."[00][00][00][00][00][00][03]
\r\nteach_skill (Joe) 24\r\n\r\n\r\n
[03][03]
[LL][BACK]
[LL=00] - empty NPC string, because we're going back to the greeting.
[00][00][00][00][00][00][-1]
\r\n\r\n\r\n\r\n
[04][02]
[LL]"Trade with me!"
[LL]"Thanks."[00][00][00][00][00][00][04]
\r\ninit_trade joe\r\n\r\n\r\n
[05][04]
[LL][BACK]
[LL=00]
[00][00][00][00][00][00][-1]
\r\n\r\n\r\n\r\n
[06][02]
[LL]"Tell me about yourself."
[LL]"I'm boring."
[00][00][00][00][00][00][05]
\r\n\r\n\r\n\r\n
[07][05]
[LL]"Nevermind, then."
[LL]
[00][00][00][00][00][00][-1]
\r\n\r\n\r\n\r\n
[08][05]
[LL]"Tell me anyway."
[LL]"I'm REALLY boring."
[00][00][00][00][00][00][06]
\r\n\r\n\r\n\r\n
[09][06]
[LL]"Nevermind, then."
[LL]
[00][00][00][00][00][00][-1]
\r\n\r\n\r\n\r\n
[0a][06]
[LL]"No really, tell me."
[LL]"Once upon a time...."
[00][00][00][00][00][00][07]
\r\n\r\n\r\n\r\n
[0b][07]
[LL]"That was gripping!"
[LL]
[00][00][00][00][00][00][-1]
\r\n\r\n\r\n\r\n
[0c][02]
[LL]"Give me a job!"
[LL]"OK, get me a fish."
[00][00][00][00][00][00][08]
\r\nquest 63 1 ; give_item (Fishing Rod)\r\n\r\n\r\n
[0d][08]
[LL]"OK, brb"
[LL]
[00][00][00][00][00][00][0e]
\r\n\r\n\r\n\r\n
[0e][00] - empty "end conversation" entry.
[LL]
[LL]
[00][00][00][00][00][00][00]
\r\n\r\n\r\n\r\n
[0f][08]
[LL]"I've got one on me."
[LL]"Great."
[00][00][00][00][00][00][09]
Fish\r\nquest 63 9 ; add_gold 10 ; remove_item (Fish)\r\n\r\n\r\n
[10][09]
[LL][BACK]
[LL=00]
[00][00][00][00][00][00][-1]
\r\n\r\n\r\n\r\n
[11][02]
[LL]"I've got you a fish."
[LL]"Great."
[3f][01][00][00][00][00][0a] - [3f][01] means check quest 61 is stage 1
Fish\r\nquest 63 9 ; add_gold 10 ; remove_item (Fish)\r\n\r\n\r\n
[12][0a]
[LL][BACK]
[LL=00]
[00][00][00][00][00][00][-1]
\r\n\r\n\r\n\r\n
[13][02]
[LL]"I have to go."
[LL]
[00][00][00][00][00][00][14]
\r\n\r\n\r\n\r\n
[14][00] - empty "end conversation" entry.
[LL]
[LL]
[00][00][00][00][00][00][00]
\r\n\r\n\r\n\r\n
Well, dang. I was considering making a little something to allow conversations to be modded, but while that might've been fun, I feel that it's much more important to respect BW's restrictions on his own work.BasiliskWrangler wrote:As long as no internal game information is being posted (such as charts or item lists) then I don't mind. Most people don't know how to use hex editors and if you want to risk corrupting your saved games to get a few stat boosts, that is your choice.
You cannot use a hex editor on the main executable or any other file that is part of the initial installation. That would be in violation of the license that you agreed to when you installed the game.
Well, I posted that on November 1 last year, so it seems only fair that I should actually follow through on my promise before we get to a whole year's delay. The suggestions from both folks have now been integrated into the scripting docs on my site.xolotl wrote:Oh, cool! Thanks for the notes; I'll integrate them in with my docs soonish... (Oh, and at this point I'd be remiss if I didn't thank SpottedShroom for his additional scripting notes as well, sent to me seemingly ages ago. I'll get those integrated soon too, I swear. :)
Code: Select all
import eschalon
m = eschalon.Map()
m.draw_wall(10, 10, 10, 20, WALL_BRICK)
m.draw_wall(10, 20, 20, 20, WALL_BRICK)
m.draw_wall(20, 20, 20, 10, WALL_BRICK)
m.draw_wall(20, 10, 10, 10, WALL_BRICK)
for i in range(10, 20):
for j in range(10, 20):
m.setFloor(i, j, FLOOR_BRICK)
m.save('somefile.map')