Well, here it is.
Operation is fairly simple, when you first run it, type the directory you keep your snes roms in the box on at the upper left and hit the button, a list of the roms (well anything with a .smc, .swc, .fig or .zip extension) in that directory will appear below. When you select one the information in the rom's cht file (a file with the same path and name but .cht extension) will be loaded into the boxes to the right (if one exists, otherwise you will be creating one). You can then edit or add to the data in the boxes, then hit "save CHT file" to save the the changes (or create the file if it doesn't exist). When editing the codes, you need only the enter a PAR or a GG code, and it will fill the other in for you.
While testing it on various CHT files on the web, I realised many of them have 18 byte chunks per code rather than 28, I wasn't aware of these since the code I was reading to figure out the spec seems to only support 28. So for that reason the 18 byte code version won't work with this, but instead will only give you garbage. Any file created with this or ZSNES, however, should work.
Code: Select all
use Tk; #Load the Tk module
#load the last directory used \/
open(LD,'ld');
$rd=<LD>;
close(LD);
$b=chr(0); #Set $b to NULL
#Some hashes to make encoding and decoding gg codes easier \/
%bin=('D'=>'0000','6'=>'1000','F'=>'0001','B'=>'1001',
'4'=>'0010','C'=>'1010','7'=>'0011','8'=>'1011','0'=>'0100',
'A'=>'1100','9'=>'0101','2'=>'1101','1'=>'0110','3'=>'1110',
'5'=>'0111','E'=>'1111');
%bin2=('0'=>'1101','8'=>'0110','1'=>'1111','9'=>'1011',
'2'=>'0100','A'=>'1100','3'=>'0111','B'=>'1000','4'=>'0000',
'C'=>'1010','5'=>'1001','D'=>'0010','6'=>'0001','E'=>'0011'
,'7'=>'0101','F'=>'1110');
%hex=('0000'=>'0','0001'=>'1','0010'=>'2','0011'=>'3','
0100'=>'4','0101'=>'5',
'0110'=>'6','0111'=>'7','1000'=>'8','1001'=>'9','1010'=>'A',
'1011'=>'B','1100'=>'C','1101'=>'D','1110'=>'E','1111'=>'F');
%hex2=('0'=>'0000','1'=>'0001','2'=>'0010','3'=>'0011',
'4'=>'0100','5'=>'0101',
'6'=>'0110','7'=>'0111','8'=>'1000','9'=>'1001','A'=>'1010','
B'=>'1011','C'=>'1100','D'=>'1101','E'=>'1110','F'=>'1111');
#transposition cipher keys for gg codes \/
@pos=(8,9,10,11,16,17,18,19,14,15,0,1,2,3,20,21,22,23,4,5,6,7,12,13);
@pos2=(10,11,12,13,18,19,20,21,0,1,2,3,22,23,8,9,4,5,6,7,14,15,16,17);
#build the GUI \/
$mw=new MainWindow(-title=>'CHT Maker');
$chfr=$mw->Frame()->grid(-column=>0, -row=>0);
$cfr=$mw->Frame()->grid(-column=>1, -row=>0);
$dlab=$chfr->Label(-text=>'Rom directory:')->grid(-column=>0, -row=>0);
$dent=$chfr->Entry(-textvariable=>\$rd)->grid(-column=>1, -row=>0);
$dbut=$chfr->Button(-text=>'Load Roms', -command=>\&lrm)->grid(-column=>3, -row=>0);
$dlist=$chfr->Scrolled(Listbox, -width=>40, -height=>30, -scrollbars=>'se')->grid(-column=>0, -row=>1, -columnspan=>4);
&lrm if $rd; #this autoloads the last used directory into the listbox
#Bindings for the listbox, up and down set the active element, while mouse clicks set the anchor \/
$dlist->bind('<Up>'=>sub{&rdcht($dlist->get('active'))});
$dlist->bind('<Down>'=>sub{&rdcht($dlist->get('active'))});
$dlist->bind('<1>'=>sub{$dlist->focus();&rdcht($dlist->get('anchor'));});
#Loops for put the code entry boxes up \/
for($i=0;$i<5;$i++)
{
$tlab[$i]=$cfr->Label(-text=>'Code'.($i+1).':')->grid(-column=>0, -row=>$i*5);
$namelab[$i]=$cfr->Label(-text=>'Description: ')->grid(-column=>0, -row=>$i*5+1);
$nameent[$i]=$cfr->Entry(-textvariable=>\$name[$i])->grid(-column=>1, -row=>$i*5+1);
$gglab[$i]=$cfr->Label(-text=>'Game Genie Code: ')->grid(-column=>0, -row=>$i*5+2);
$ggent[$i]=$cfr->Entry(-textvariable=>\$gg[$i])->grid(-column=>1, -row=>$i*5+2);
$parlab[$i]=$cfr->Label(-text=>'Pro Action Replay Code: ')->grid(-column=>0, -row=>$i*5+3);
$parent[$i]=$cfr->Entry(-textvariable=>\$par[$i])->grid(-column=>1, -row=>$i*5+3);
$aclab[$i]=$cfr->Label(-text=>'Active: ')->grid(-column=>0, -row=>$i*5+4);
$acchk[$i]=$cfr->Checkbutton(-variable=>\$act[$i])->grid(-column=>1, -row=>$i*5+4);
}
for($i=5;$i<10;$i++)
{
$tlab[$i]=$cfr->Label(-text=>'Code'.($i+1).':')->grid(-column=>2, -row=>($i-5)*5);
$namelab[$i]=$cfr->Label(-text=>'Description: ')->grid(-column=>2, -row=>($i-5)*5+1);
$nameent[$i]=$cfr->Entry(-textvariable=>\$name[$i])->grid(-column=>3, -row=>($i-5)*5+1);
$gglab[$i]=$cfr->Label(-text=>'Game Genie Code: ')->grid(-column=>2, -row=>($i-5)*5+2);
$ggent[$i]=$cfr->Entry(-textvariable=>\$gg[$i])->grid(-column=>3, -row=>($i-5)*5+2);
$parlab[$i]=$cfr->Label(-text=>'Pro Action Replay Code: ')->grid(-column=>2, -row=>($i-5)*5+3);
$parent[$i]=$cfr->Entry(-textvariable=>\$par[$i])->grid(-column=>3, -row=>($i-5)*5+3);
$aclab[$i]=$cfr->Label(-text=>'Active: ')->grid(-column=>2, -row=>($i-5)*5+4);
$acchk[$i]=$cfr->Checkbutton(-variable=>\$act[$i])->grid(-column=>3, -row=>($i-5)*5+4);
}
$cbut=$mw->Button(-text=>'Save CHT file', -command=>\&go)->grid(-row=>1, -column=>1);
MainLoop;
sub go #subroutine that calls the gg codec subs then the sub that generates the CHT file
{
$g=0;
foreach(@gg)
{
if($_)
{
&decode($g++);
}
else
{
next unless $par[$g];
&encode($g++);
}
}
&mkcht;
}
sub decode #this decodes a gg code into par code (address and value)
{
$a=0; #reset $a
$d=$gg[$_[0]]; #I was originally using $_ here and in and the
#encode sub, but it caused all kinds of problems
$d=uc($d); #make it all uppercase
$d=~s/-//; #remove the hyphen
$d=~s/(..)//; #take off the first to characters and put them in $1
$v=$1; #$v is the value, unlike the rest of the code, it only uses the
#substitution cipher, and not the bitwise transposition
$v=~tr/DF4709156BC8A23E/0123456789ABCDEF/; #this decodes
#the value
$d=~s/(.)/$bin{$1}/g; #convert to binart with substition cipher
#already applied
$new[$pos[$a++]]=$_ foreach(split //, $d); #this decodes the
#transposition cipher, it uses the @pos array to put them in the right order
$d=join '', @new; #turn @new into a string
$d=~s/(....)/$hex{$1}/g; #convert back to hex
$par[$_[0]]="$d$v"; #put the address and value together
}
sub mkcht #this generates the CHT file
{
$chtfile=''; #reset $chtfile
$once=0; #reset $once
$g=0; #reset $g
foreach(@par)
{
next unless $_; #this makes sure it only processes entries that
#are filled in
$cht=$act[$g]?chr(0):chr(4); #Conditional operator sets the
#first byte to show whether the code is active
foreach $h (reverse($_=~/(..)/g))
{
$cht.=chr(hex($h)); #The codes are stored basically like
#a par code in reverse
}
$gap=$once ? (chr(0).chr(0)) : (chr(hex('FE')).chr(hex('FC'))); #This sets the value of the bytes between the code and the title, these
#bytes are different for the first code, I don't know why
$cht.=chr(0).$gap.uc($name[$g++]); #put it together
$cht=~/(.{0,28})/s; #chop any excess bytes
$cht.=chr(0) x (28-length($cht)); #pad it as nessicary
$chtfile.=$cht; #assignment operator add this code
$once=1; #ran this once
}
$_=$cur;
s/....$/\.cht/; #swap the extention .cht
open(CHT,">$rd$_"); #open it for write
binmode(CHT); #go to binmode, just in case to bytes form a new line code
print CHT $chtfile;
close CHT;
}
sub rdcht #this parses a cht file
{
$cur=$_[0];
$_=$rd.$_[0]; #make the full path
s/....$/\.cht/; #swap extensions
open(CHT,$_); #open readonly
#this loop resets the values in all the boxes \/
for($i=0;$i<10;$i++)
{
$name[$i]='';
$gg[$i]='';
$par[$i]='';
$act[$i]='';
}
binmode(CHT);
$_=join '', <CHT>; #put the file contents in $_
close(CHT);
$g=0; #reset $g
$chunk=(length()%28) ? 18: 28; #this is how I determine if it the 18
#byte 252 would match both, this will assume it 28 than (there isn't room
#for 14 codes anyway)
$bn=$chunk==18?5:7; #set where to start reading the description
#from
#this loop processes it one 18 or 28 byte chunk at a time
foreach(/(.{$chunk})/gs)
{
s/(.)//; #take off first byte and put it in $1
$act[$g]=!ord($1); #detimines if the code is active from first
#byte
/(.{4})/s; #next 4 bytes go in $1
$code[$#code+1]=ord($_) foreach reverse(split //, $1); #get
#par code by reversing the string in $1
$par[$g]=sprintf('%02X%02X%02X%02X', @code); #fill in par code
/.{$bn}(.*)$b.*/s; #put description in $1 ($b is NULL)
$name[$g]=$1; #fill in the entry
&encode($g++); #encode to gg form
@code=(); #reset @code
}
}
sub encode #turn a par code into gg
{
$a=0; #reset $a
#Next several lines are like decoding, except substituing back \/
$e=$par[$_[0]];
$e=uc($e);
$e=~s/://;
$e=~s/(..)$//;
$v=$1;
$v=~tr/0123456789ABCDEF/DF4709156BC8A23E/;
$e=~s/(.)/$hex2{$1}/g; #into binary, no substitution (this has to be
#done after the transposition)
$new[$pos2[$a++]]=$_ foreach(split //, $e); #transposition, this
#time putting things in the wrong order instead of right
$e=join '', @new;
$e=~s/(....)/$hex{$1}/g;
$e=~tr/0123456789ABCDEF/DF4709156BC8A23E/; #now the
#substitution
$e="$v$e";
$gg[$_[0]]=join '-', $e=~/(....)/g; #fill it in with the hyphen added
}
sub lrm #this loads the directory into the listbox
{
$rd=~/(\/|\\)/; #this determines the direction of slash used
$sdir=$1;
$rd=~/(.)$/;
$rd.=$sdir unless $1 eq $sdir; #add a trailing slash if there isn't one
opendir(DIR,$rd); #open the directory
$dlist->delete('0','end'); #clear the list
foreach(readdir(DIR))
{
$dlist->insert('end', $_) if /\.smc|\.fig|\.swc|\.zip/; #only put
#things files that might be roms in the list
}
close(DIR);
open(LD,'>ld');
print LD $rd; #save the last directory used, to avoid retyping
close(LD);
}