From 0644716d15d6d792e59a09b6ab08f478944712b7 Mon Sep 17 00:00:00 2001 From: Aurelien Labate Date: Thu, 26 Dec 2019 12:07:17 +0100 Subject: [PATCH] Initial commit --- DOCUMENTATION.md | 275 +++++++++++++++++++++++++++++++++++++++++ LICENSE | 21 ++++ README.md | 15 +++ _getKeyState.asm | 284 +++++++++++++++++++++++++++++++++++++++++++ index.php | 62 ++++++++++ system/converter.php | 278 ++++++++++++++++++++++++++++++++++++++++++ system/functions.php | 188 ++++++++++++++++++++++++++++ system/style.css | 47 +++++++ 8 files changed, 1170 insertions(+) create mode 100644 DOCUMENTATION.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 _getKeyState.asm create mode 100644 index.php create mode 100644 system/converter.php create mode 100644 system/functions.php create mode 100644 system/style.css diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md new file mode 100644 index 0000000..c49ab49 --- /dev/null +++ b/DOCUMENTATION.md @@ -0,0 +1,275 @@ +# Problem solved by this tool + +## sh3/sh4 compatible input function: `_GetKeyState` +Most of the problem solved by this tool are keyboard input related. So the first step was to create an sh3/sh4 compatible function to get keyboard key state. This function is injected at the end of the program and because of that we also need to rewrite the header, but that's easy thanks to Simon Lothar and his documentation. + +You can find the full commented assembly source of the function into [_getKeyState.asm](./_getKeyState.asm). + +This function is faster than most input functions (except KeyDown). So using the following code, I tested the speed of each functions first: + +```c++ +int AddIn_main(int isAppli, unsigned short OptionNum) +{ + unsigned int timeBegin; + unsigned int duration; + char string[9]; + int i; + while(1) + { + timeBegin = RTC_GetTicks();//RTC_GetTicks is a syscall documented in FxReverse + for(i=0;i<5000;i++) + { + key_down(K_EXE);//Change this function here + } + duration = RTC_GetTicks()-timeBegin; + intToHex(duration, string); + + Bdisp_AllClr_DDVRAM(); + locate(1,1); + Print((unsigned char*)string); + Bdisp_PutDisp_DD(); + } + + return 1; +} + +void intToHex(unsigned int in, char* string) +{ + string[0] = nibbleToHex((unsigned char)in>>28); + string[1] = nibbleToHex((unsigned char)(in>>24)&0xF); + string[2] = nibbleToHex((unsigned char)(in>>20)&0xF); + string[3] = nibbleToHex((unsigned char)(in>>16)&0xF); + string[4] = nibbleToHex((unsigned char)(in>>12)&0xF); + string[5] = nibbleToHex((unsigned char)(in>>8)&0xF); + string[6] = nibbleToHex((unsigned char)(in>>4)&0xF); + string[7] = nibbleToHex((unsigned char)in&0xF); + string[8] = 0; +} + + +char nibbleToHex(unsigned char in) +{ + char out; + if(in <= 9) + out = 0x30 + (unsigned int)in; + else + { + switch(in-10) + { + case 0 : out = 0x61; break; + case 1 : out = 0x62; break; + case 2 : out = 0x63; break; + case 3 : out = 0x64; break; + case 4 : out = 0x65; break; + case 5 : out = 0x66; break; + } + } + return out; +} +``` + +And here are the result: + +It shows the number of ticks taken to execute the function. + +| | Original | _GetKeyState SH3 | _GetKeyState SH4 | +|--------------------------------------|----------|------------------|------------------| +| IsKeyDown | 0xb2 | 0x17(miss 155) | 0x11(miss 161) | +| IsKeyDown with 0x1000 waitloop | --- | 0x17b | 0x1cd | +| IsKeyDown ; sh3:0x12D1F ; sh4:0xF1A8 | --- | 0xb2 | 0xb2 | +| IsKeyUp | 0x1a43 | 0x17(miss 6700) | 0x11(miss 6706) | +| IsKeyUp ; sh3:0x12D1F ; sh4:0xF1A8 | --- | 0x1a42 | 0x1a4a | +| KeyDown | 0x9 | 0x11 | 0xd | + + * 0x1000 loop takes 0x164 ticks to be executed on SH3 => 1024/89 loop/ticks + * 0x1000 loop takes 0x1BC ticks to be executed on SH4 => 1024/111 loop/ticks + * KeyDown is faster than `_GetKeyState` + + +## IsKeyDown function from FxLib +The IsKeyDown Function read directly the keyboard input state without using a syscall. But with power graphic 2 the keyboard connections to the CPU changed and it's not possible anymore to read the keyboard the same way. + +As Casimo showed us with his solution in C, Power Graphic 2 calculators have a specific keyboard register that we can read easily. The solution used by this tool is it to lookup for the binary code of the IsKeyDown function, and replacing it with a new one that jump to `_GetKeyState`. + +Firstly, we look for the `isKeyDown` function: + +``` +4f227ffc63f3bee52f367f0463f0633c43118b0384f1600c401189037f044f26000be000bf5664f37f044f26000b0009 +``` + +Then we replace its content starting at offset `0xc` of the function. (The first bytes replaced are `0x63f0`). + +``` +_IsKeyDownReplacement + ; before this injection, there is call of _KeyCodeConvert that put an array of two byte that respectivly contain the col and the line in the stack + ; So we put them into registers to use them letter in the _GetKeyState function that we will call + mov r15,r4 ; first param of the _GetKeyState function + mov #1,r5 ; set slowmode of the _GetKeyState function + ; Jump to _GetKeyState + mov.l GetKeyStateAddress,r0 + jsr @r0 ;call _GetKeyState + nop + nop + ; after _GetKeyState + ; return to the calling position + add #4,r15 + lds.l @r15+,pr + rts + nop +GetKeyStateAddress: + .data.l h'xxxxxxxx ; addres of the compatible _GetKeyState function +``` + +## IsKeyUp function from FxLib +This function doesn't works at all like IsKeyDown. This one use the syscall `0x24C` called "Chattering" by the fxLib. + +The prototype seem to be : + +```C++ +int Chattering(unsigned char* coord); +``` + +with key coordinates in an array of two cells, the first is the cols, the seconds is the row of the key. This syscall return 1 if the key is pressed. + + +Syscall are writed in the OS code, so when there is an OS update, syscall are generally updated to works on the new hardware (it's the case of a lot of usefull syscall). But this one seem to not work on SH4 calc, it allays return 0. So maybe Casio choosed to disable this syscall. + +Anyway, no matter the original implementation, we will also use `_GetKeyState` and just put a "not" at the end. + +```asm +_IsKeyUpReplacement + ; before this injection, there is call of _KeyCodeConvert that put an array of two byte that respectivly contain the col and the line in the stack + ; So we put them into registers to use them letter in the _GetKeyState function that we will call + mov r15,r4 ; first param of the _GetKeyState function + mov #2,r5 ; set slowmode of the _GetKeyState function + ; Jump to _GetKeyState + mov.l GetKeyStateAddress2,r0 + jsr @r0 ;call _GetKeyState + nop + nop + ; after _GetKeyState + ; Negate _GetKeyState output + not r0,r0 + and #1,r0 + ; return to the calling position + add #4,r15 + lds.l @r15+,pr + rts + nop +GetKeyStateAddress2: + .data.l h'xxxxxxxx ; addres of the compatible _GetKeyState function +``` + +## KeyDown function + +This is the same problem as the IsKeyDown function, the I/O register change so we can not read the keyboard input on the SH4 cpu. To identify this function, this was a little harder, because this function is not precompilated like the FxLib. So I found 2 binary implementation of this function in asm code: + +* when the first line offset is equal to 0 modulo 4 +* when it's equal to 2 modulo 4. + +Some asm code work only when it's on a mod4=0, like + +``` +mov.l @(h'4,pc),r0 +``` + +be cause it can read a longword (4byte) only at an offset mod4=0 and the number in parameter need to be divisible by 4. That explains the difference between the two implementations. + +So we replace the KeyDown function with this code + +```asm +_KeyDownReplacement ; put this at beginning of the KeyDown function. (the first four byte replaced : 2FE6634C) + ;before : keycode in r4 ; keycode=col<<4 + row + sts.l pr,@-r15 + mov.l r1,@-r15 + mov.l r5,@-r15 + ; add #-2,r15 ;r15 need to always contain a number divisible by 4 (because when we put a longword of 4byte in the stack, we can only put it on adress multiple of 4) + ;get the col + mov #-4,r0 + mov r4,r1 + shld r0,r1 + ;get the row + mov r4,r0 + and #h'f,r0 + ; mov.b r0,@-r15 + ; mov.b r1,@-r15 + ;prepartion of the array content + shll8 r1 + add r0,r1 + shll16 r1 + mov.l r1,@-r15 + ;prepare _GetKeyState call + mov r15,r4 ; get array address + mov #3,r5 ; set slowmode of _GetKeyState function + mov.l GetKeyStateAddress3,r0 + jsr @r0 ;call _GetKeyState + nop + ;after _GetKeyState + add #4,r15 + mov.l @r15+,r5 + mov.l @r15+,r1 + lds.l @r15+,pr + rts + nop +GetKeyStateAddress3: + .data.l h'xxxxxxxx ; addres of the compatible _GetKeyState function +``` +## monochromeLib and syscall call method +Monochromelib call OS syscall with this C++ code + +```c++ +static int SysCallCode[] = {0xD201422B,0x60F20000,0x80010070}; +static int (*SysCall)( int R4, int R5, int R6, int R7, int FNo ) = (void*)&SysCallCode; +char* ML_vram_adress() +{ + return (char*)((*SysCall)(0, 0, 0, 0, 309)); +} +``` + +The array SysCallCode is writed in the memory at the address > `0x0810000`. Once this array is writen into this memory, it jump on it. The content of this array is a binary code to run syscall. That's mean that this array need to be stored in a memory that can be read, writen and executed. This was possible in SH3, but on the SH4 CPU, this memory cannot be executed. + +An easy solution is to avoid writting this array on this memory by making it a "const". As const, it will stay in the programm "instruction list", and will not be copied anywhere. And the programm instruction list is obviously still readable and executable. + +```C++ +static const int SysCallCode[] = {0xD201422B,0x60F20000,0x80010070}; +static int (*SysCall)( int R4, int R5, int R6, int R7, int FNo ) = (void*)&SysCallCode; +char* ML_vram_adress() +{ + return (char*)((*SysCall)(0, 0, 0, 0, 309)); +} +``` + +The code above is not a solution for us cause we cannot edit the C without sources. +Another problem is that there is many binary implementation for this solution, because it depends of the parameters. And it's too small to look it up like we do on `isKeyDown`. + +Here is an assembly that can be generated from the C++ code + +```asm +mov.l @(H'114,pc),r3 ; It get the address where the address of the array is written, here it's 0x08100014 +mov.l @(H'10c,pc),r2 ; It get the syscall number from the parameter, here it's 0x135 +mov.l @r3,r0 ; It get the address of the array, here it's 0x08100008 +jsr @r0 ; Jump to the array +mov.l r2,@-r15 ; This is executed just before to jump : it put the syscall number in the stack +``` + +So to solve the problem, I put this code at the end of the file + +```asm +_SyscallFix + mov.l #h'80010070,r2 ; the syscall table (where we jump to execute a syscall) + jmp @r2 ; Jump to the syscall table + mov.l @r15,r0 ; Just before to jump, put the value in the stack to the register r0 (the value in the stack is the syscall number) +``` + +And with our original code, i edit it a little : + +```asm +mov.l @(H'114,pc),r3 ; I change the value pointed to be the address of my function SyscallFix (added at the end of the file) +mov.l @(H'10c,pc),r2 +mov.l r3,r0 ; change to put the address get at the first line in r0 +jsr @r0 ; Jump to the my added code +mov.l r2,@-r15 +``` +And it work well with this solution. + +The hardest part is how we find the original implementation, because it changes everytime. All the line that are here everytime, but sometime separated by other instructions. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..35627ac --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2013-2019 Aurélien Labate + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b977cb7 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# SH4 Compatibility tool + +**SH4 Compatibility tool** takes binaries (aka addins) for old SH3 Casio Calculators and make them compatible with newer sh4 calculators (power graphic 2). + +This tool has originally been created by Ziqumu (aka Alabate) during summer of 2013 for [Planète Casio](https://www.planet-casio.com/Fr/forums/topic12292-1-SH4-compatibility-tool.html). + +Documentation about problem solved by this tool can be found [here on DOCUMENTATION.md](./DOCUMENTATION.md) + +## Thanks to + +* [Casimo](https://www.planet-casio.com/Fr/compte/voir_profil.php?membre=Casimo) to have created the first keyboard input function compatible with SH4 +* Simon Lothar and Andreas Bertheussen for their documentations on Casio syscalls +* [Kristaba](https://www.planet-casio.com/Fr/compte/voir_profil.php?membre=Kristaba) pour [ce topic](http://www.planet-casio.com/Fr/forums/lecture_sujet.php?id=9216&page=1) +* [Alphacreator](https://www.planet-casio.com/Fr/compte/voir_profil.php?membre=Alphacreator) and [Maliafo](https://www.planet-casio.com/Fr/compte/voir_profil.php?membre=Maliafo) to have tried programs generated by this tool +* Ayfer-Marie who gave me access to her SH4 calculator. \ No newline at end of file diff --git a/_getKeyState.asm b/_getKeyState.asm new file mode 100644 index 0000000..18055e1 --- /dev/null +++ b/_getKeyState.asm @@ -0,0 +1,284 @@ +;param 1 (in r4) : Adress of an array of two unsigned char, with in the first cell the col, and in the second the row +;param 2 (in r5) : slowMode : this determine the time this function will wait to emulate the olds functions +; -n : number of loop +; 0 : fatest as possible +; 1 : = duration of IsKeyDown function +; 2 : = duration of IsKeyUp function +; 3 : = duration of KeyDown function +;return (in r0) 1 if the key is pressed. +_GetKeyState + ;First put current register values in the stack + sts.l pr,@-r15 + mov.l r1,@-r15 + mov.l r2,@-r15 + mov.l r3,@-r15 + mov.l r6,@-r15 + mov.l r7,@-r15 + mov.l r8,@-r15 + mov.l r9,@-r15 + mov r4,r8 ; first param + mov r5,r9 ; second param + +;check the os version with the syscall 0x0015 | if I use only 1 byte for chars, and 2 for short, it crash on all calc but not on emulator. But these type are valid because the syscall only edit the correct number of byte. + add #-4,r15 ; main version : unsigned char + mov r15,r4 + add #-4,r15 ; minor version : unsigned char + mov r15,r5 + add #-4,r15 ; release : unsigned short + mov r15,r6 + add #-4,r15 ; build : unsigned short + mov r15,r7 + ;call syscall + mov.l #h'80010070,r2 + jsr @r2 + mov #h'15,r0 + ;put os version into r6 + add #8,r15 + mov.b @r15,r6 ; minor version + add #4,r15 + mov.b @r15,r0 ; main version + add #4,r15 + shll8 r0 ; r0 = r0<<8 + add r0,r6 +;reserved registers : + ;r9 second param + ;r6 OS version +; read and checks coords + mov.b @r8,r7 ; r7 = Key's column + mov.b @(1,r8),r0 + mov r0,r8 ; r8 = Key's row + ;verify the row value : 0 ≤ row ≤ 9 + mov #0, r0 + cmp/gt r8,r0 ; if r0 > r8 ⇒ if 0 > row + bt NegativeEnd + mov #9,r1 + cmp/gt r1,r8 ; if r8 > r1 ⇒ if row > 9 + bt NegativeEnd + ;verify the column value : 0 ≤ row ≤ 6 + cmp/gt r7,r0 ; if r0 > r7 ⇒ if 0 > column + bt NegativeEnd + mov #6,r1 + cmp/gt r1,r7 ; if r7 > r1 ⇒ if column > 6 + bt NegativeEnd +;check if os is > 2.02 + mov.w #h'0202,r0 + cmp/ge r0,r6 ; r0 ≤ r6 + bt SH4 +;reserved registers : + ;r9 second param + ;r8 Key's row + ;r7 Key's col +;SH3 part + ;r6 = smask = 0x0003 << (( row %8)*2); + mov r8,r0 ; row->r0 + and #7,r0 ; %8 + add r0,r0 ; *2 + mov #3,r6 + shld r0,r6 ; 3<< + ;r5 = cmask = ~( 1 << ( row %8) ); + mov r8,r0 ; row->r0 + and #7,r0 ; %8 + mov #1,r5 + shld r0,r5 ; 1<< + not r5,r5 ; ~ +;reserved registers : + ;r9 second param + ;r8 Key's row + ;r7 Key's col + ;r6 smask + ;r5 cmask +;Preparation of the gbr register + mov.l #h'A4000100,r0 + ldc r0,gbr +;RowCond : if(row <8) + mov #8,r0 + cmp/gt r8,r0 ; if r0>r8 ; row>=8 + bf rowCond_Else +;rowCond_begin + ;*PORTB_CTRL = 0xAAAA ^ smask; + mov r6,r0 + mov.w #h'AAAA,r1 + xor r1,r0 + mov.w r0,@(h'02,gbr) + ;*PORTM_CTRL = (*PORTM_CTRL & 0xFF00 ) | 0x00AA; + mov.w @(h'18,gbr),r0 ; *PORTM_CTRL->r0 + mov.w #h'FF00,r1 + and r1,r0 ; *PORTM_CTRL & 0xFF00 + or #h'AA,r0 ; | 0x00AA; + mov.w r0,@(h'18,gbr) + ;delay() + bsr delay + mov #-10,r4 + ;*PORTB = cmask; + mov r5,r0 + mov.b r0,@(h'22,gbr) ;PORTB = cmask + ;*PORTM = (*PORTM & 0xF0 ) | 0x0F; + mov.b @(h'38,gbr),r0 ; *PORTM->r0 + and #h'F0,r0 ; *PORTM & 0xF0 + or #h'0F,r0 ; | 0x0F; + mov.b r0,@(h'38,gbr) + bra rowCond_End + nop +rowCond_Else: + ; *PORTB_CTRL = 0xAAAA; + mov.w #h'AAAA,r0 + mov.w r0,@(h'02,gbr) + ; *PORTM_CTRL = ((*PORTM_CTRL & 0xFF00 ) | 0x00AA) ^ smask; + mov.w @(h'18,gbr),r0 + mov.w #h'FF00,r1 + and r1,r0 ; *PORTM_CTRL & 0xFF00 + or #h'AA,r0 ; | 0x00AA; + xor r6,r0 ; ^ smask; + mov.b r0,@(h'18,gbr) + ;delay() + bsr delay + mov #-10,r4 ;In the begin this was 5, but as the delay function is faster, i need to put more + ;*PORTB = 0xFF; + mov.b #h'ff,r0 + mov.b r0,@(h'22,gbr) ;PORTB = 0xFF + ;*PORTM = (*PORTM & 0xF0 ) | cmask; + mov.b @(h'38,gbr),r0 + and #h'F0,r0 ; *PORTM & 0xF0 + or r5,r0 ; | cmask; + mov.b r0,@(h'38,gbr) +rowCond_End: + ;reserved registers : + ;r9 second param + ;r8 Key's row + ;r7 Key's col + ;delay() + bsr delay + mov #-10,r4 + ;result = (~(*PORTA))>>column & 1; + mov.b @(h'20,gbr),r0 + not r0,r6 ; r6 = ~r0 + neg r7,r0 ; r0 = -column + shld r0,r6 ; r6 = r6>>column + mov.b #1,r0 + and r0,r6 + ;reserved registers : + ;r9 second param + ;r8 Key's row + ;r7 Key's col + ;r6 result + ;delay() + bsr delay + mov #-10,r4 + ; *PORTB_CTRL = 0xAAAA; + mov.w #h'AAAA,r0 + mov.w r0,@(h'02,gbr) + ;*PORTM_CTRL = (*PORTM_CTRL & 0xFF00 ) | 0x00AA; + mov.w @(h'18,gbr),r0 + mov.w #h'FF00,r1 + and r1,r0 ; *PORTM_CTRL & 0xFF00 + or #h'AA,r0 ; | 0x00AA; + mov.w r0,@(h'18,gbr) + ;delay() + bsr delay + mov #-10,r4 + ; *PORTB_CTRL = 0x5555; + mov.w #h'5555,r0 + mov.w r0,@(h'02,gbr) + ;*PORTM_CTRL = (*PORTM_CTRL & 0xFF00 ) | 0x0055; + mov.w @(h'18,gbr),r0 + mov.w #h'FF00,r1 + and r1,r0 ; *PORTM_CTRL & 0xFF00 + or #h'55,r0 ; | 0x0055; + mov.w r0,@(h'18,gbr) + ;delay() + bsr delay + mov #-10,r4 + ;End of SH3 part + bra AllEnd + nop +SH4: + ;Add 3 to the second param (if >0)to select the right wait time + mov #0,r0 + cmp/gt r0,r9 + bf negatif2ndParam + add #3,r9 +negatif2ndParam: + ;get the main keyboard regsiter address+1 + mov.l #H'A44B0001,r1 + mov r8,r0 + tst #1,r0 ;if row is even T=1 else T=0 + add r8,r1 + bt row_even ; Jump if T=1 + add #-2,r1 +row_even: + mov.b @r1,r0 ; The byte that contain the row data is now in R0 + mov #1,r1 + shld r7,r1 ; R9 now contain 1< + + + + + SH4 Compatibility Tool + + + + + + '; + $_SESSION['msg_text']=''; + } + ?> + +
+
+

Make your g1a compatible for casio SH4 (Power Graphic 2)

+
+
+
+ Warning! This tool can cause damage on your calculator! Use it at your own risk. Planet-Casio, Casiopeia and me will not be responsible for any damage. +
+
+ + + + + diff --git a/system/converter.php b/system/converter.php new file mode 100644 index 0000000..1e6dc09 --- /dev/null +++ b/system/converter.php @@ -0,0 +1,278 @@ +'; + // require_once('functions.php'); + + //Get the g1a + $InFile = file_get_contents($_FILES['file']['tmp_name']); + $InFileSize = strlen($InFile); + $InFile = bin2hex($InFile); + //Check if the file is a g1a + if(strtolower($InFile[16].$InFile[17]) != '0c') + { + $_SESSION['msg_text']='The file is not recognized as a .g1a'; + $_SESSION['msg_color']='red'; + header("Location: ../"); + exit; + } + //Check if the is not already edited by this tool + if(strtolower(substr($InFile,-60,52)) == '000053483420436f6d7061746962696c69747920546f6f6c2076') + { + $_SESSION['msg_text']='This file has already been modified by this tool. (version '.hex2bin(substr($InFile,-8)).')'; + $_SESSION['msg_color']='red'; + header("Location: ../"); + exit; + } + //Verify if the file length is a divisible by 4 (cause the code that i add to the end is done only for this) + if($InFileSize%4 != 0) + { + $OutFile = str_pad($InFile, ($InFileSize+4-($InFileSize%4))*2, '0', STR_PAD_RIGHT); + $InFileSize = $InFileSize+4-($InFileSize%4); + } + else + $OutFile = $InFile; + //Input functions fix + $inputFix = false; + $GetKeyStateAdress = str_pad(dechex($InFileSize+0x300000), 8, '0', STR_PAD_LEFT); + //IsKeyDown + $isKeyDownCode = '4f227ffc63f3bee52f367f0463f0633c43118b0384f1600c401189037f044f26000be000bf5664f37f044f26000b0009'; + $isKeyDownPosition = strpos($OutFile,$isKeyDownCode); + if($isKeyDownPosition !== false) + { + //Test if there is more than one function + if(strpos($OutFile,$isKeyDownCode,$isKeyDownPosition+1) !== false) + { + $_SESSION['msg_text']='Error : we found more than one IsKeyDown function !'; + $_SESSION['msg_color']='red'; + header("Location: ../"); + exit; + } + //Replacement + $inputFix = true; + if(!isset($_POST['slow']))$slowcode = '00'; //fastmode + else $slowcode = '01'; //IsKeyDown Slowmode + $OutFile = substr($OutFile,0,$isKeyDownPosition+(0xc*2)) . '64F3E5'.$slowcode.'D003400B000900097F044F26000B0009' . $GetKeyStateAdress . substr($OutFile,$isKeyDownPosition+(0xc+20+4)*2); + } + // echo (strlen($OutFile)/2).'->0
'; + //IsKeyUp + $isKeyUpCode = '4f227ffc63f3becd2f367f0463f0633c43118b0384f1600c401189037f044f26000be000d208420b64f3200800297f044f26000b00095555a40001000000aaaa0000ff00a4000120'; + $isKeyUpPosition = strpos($OutFile,$isKeyUpCode); + if($isKeyUpPosition !== false) + { + //Test if there is more than one function + if(strpos($OutFile,$isKeyUpCode,$isKeyUpPosition+1) !== false) + { + $_SESSION['msg_text']='Error : we found more than one IsKeyUp function !'; + $_SESSION['msg_color']='red'; + header("Location: ../"); + exit; + } + //Replacement + $inputFix = true; + if(!isset($_POST['slow']))$slowcode = '00'; //fastmode + else $slowcode = '02'; //IsKeyUp Slowmode + $OutFile = substr($OutFile,0,$isKeyUpPosition+(0xc*2)) . '64F3E5'.$slowcode.'D004400B000900096007C9017F044F26000B0009' . $GetKeyStateAdress . substr($OutFile,$isKeyUpPosition+(0xc+24+4)*2); + } + // echo (strlen($OutFile)/2).'->1
'; + //KeyDown : pc%4=0 + $keydownfix=false; + $KeyDownCode = '2fe6634c2fd6e50f2fc643092fb625492fa643092f96665c4f2260637ffc40112f308b01a005c90760077001c907600770016403'; + $KeyDownPosition = strpos($OutFile,$KeyDownCode); + if($KeyDownPosition !== false) + { + //Test if there is more than one function + if(strpos($OutFile,$KeyDownCode,$KeyDownPosition+1) !== false) + { + $_SESSION['msg_text']='Error 1 : we found more than one KeyDown function !'; + $_SESSION['msg_color']='red'; + header("Location: ../"); + exit; + } + $inputFix = true; + $keydownfix=true; + } + //KeyDown : pc%4=2 + $KeyDown2Code = '634c2fe6e50f2fd643092fc625492fb643092fa6665c2f9660634f2240117ffc2f308b01a005c90760077001c90760077001'; + $KeyDown2Position = strpos($OutFile,$KeyDown2Code); + if($KeyDown2Position !== false) + { + //Test if there is more than one function + if(strpos($OutFile,$KeyDown2Code,$KeyDown2Position+1) !== false || $keydownfix) + { + $_SESSION['msg_text']='Error 2 : we found more than one KeyDown function !'; + $_SESSION['msg_color']='red'; + header("Location: ../"); + exit; + } + $inputFix = true; + $keydownfix=true; + //Decallage pour avoir pc%4=0 + $OutFile = substr($OutFile,0,$KeyDown2Position) . '0009'. substr($OutFile,$KeyDown2Position+4); + $KeyDownPosition = $KeyDown2Position+4; + } + //KeyDown replacement + if($keydownfix) + { + if(!isset($_POST['slow']))$slowcode = '00'; //fastmode + else $slowcode = '03'; //IsKeyDown Slowmode + $OutFile = substr($OutFile,0,$KeyDownPosition) . '4F222F162F56E0FC6143410D6043C90F4118310C41282F1664F3E5'.$slowcode.'D004400B00097F0465F661F64F26000B00090009' . $GetKeyStateAdress . substr($OutFile,$KeyDownPosition+54+2+40+8); + } + //Part to add at the end for IsKeyDown, IsKeyUp, and KeyDown + if($inputFix) + { + $delayLoop = 'F6'; //-10 + $OutFile .='4F222F162F262F362F662F762F862F96684369537FFC64F37FFC65F37FFC66F37FFC67F3D21F420BE0157F0866f07F0460f07F044018360C678084816803E0003087897BE1093817897830778976E106371789739023360389596083C907300CE603460D6083C907E501450D6557D00E401EE00830878B1960639111201AC101C50C910E2019CBAAC10CB065E4'.$delayLoop.'6053C022C438C9F0CB0FC038A01700090202AAAAFF0080010070A4000100902CC101C50C912A2019CBAA206AC10CB04CE4'.$delayLoop.'E0FFC022C438C9F0205BC038B044E4'.$delayLoop.'C4206607607B460DE0012609B03CE4'.$delayLoop.'9012C101C50C91102019CBAAC10CB033E4'.$delayLoop.'900BC101C50C91072019CB55C10CB02AE4'.$delayLoop.'A01B0009AAAAFF005555E00039078B007903D1086083C801318C890071FE6010E101417D201800296007C9016603A00400090000A44B0001E600B00B6493606369F668F667F666F663F662F661F64F26000B0009E00034038B14344CC702344C304C6102A00F0009'. + // Number of loop to emulate old functions + '00000001'. //fastest (can't be under 1) + '000006F7'. //IsKeyDown SH3 + '00012D1F'. //IsKeyUp SH3 + '00000001'. //KeyDown SH3 + '000005CD'. //IsKeyDown SH4 + '0000F1A8'. //IsKeyUp SH4 + '00000001'. //KeyDown SH4 + '614B41108BFD000B00090009'; + } + //Syscall bug fix + $position = 0; + $SyscallFix = false; + $syscallCodeAddress = str_pad(dechex((strlen($OutFile)/2)+0x300000), 8, '0', STR_PAD_LEFT); + while(isset($OutFile[$position+3])) + { + if($OutFile[$position]=='4' && strtolower($OutFile[$position+1])=='f' && $OutFile[$position+2]=='2' &&$OutFile[$position+3]=='2') + { + //The instruction of the actual line is 4F22: sts.l pr,@-15 + //The $position+4 or $position+8 instruction is d3xx:mov.l @(h'xx,pc),r3 // and @(h'xx,pc) contain the an address >= 0x08100000 (and we will suppose < 0x08200000) + $pc=0; + $valide = true; + if(strtolower($OutFile[$position+4]=='d') && $OutFile[$position+5]=='3') + { + $disp = ord(hex2bin($OutFile[$position+6].$OutFile[$position+7])); + $pc = ($position/2)+4; + $subOffset = 4; + } + elseif(strtolower($OutFile[$position+8]=='d') && $OutFile[$position+9]=='3') + { + $disp = ord(hex2bin($OutFile[$position+10].$OutFile[$position+11])); + $pc = ($position/2)+4+4; + $subOffset = 8; + } + else + $valide=false; + //Check if the address >= 0x08100000 and <0x08200000 + if($valide) + { + $ToChange_Address = (($pc&0xFFFFFFFC)+($disp<<2))*2; + if(substr($OutFile,$ToChange_Address,3) != '081') + $valide=false; + } + //Search the row 92XX: mov.w @(h'xx,pc),r2 // This is the instruction that get the syscall number, so if I want it, I can. (just do as above) + if($valide) + { + $valide=false; + for($i=0;$i<=4;$i++) + { + if($OutFile[$position+$subOffset+($i*4)] == 'd' && $OutFile[$position+$subOffset+($i*4)+1] == '3') + { + $subOffset+=$i*4; + $valide = true; + break; + } + } + } + //Search the row 6032: mov @R3,r0 // I need to edit this line to : mov r3,r0 + if($valide) + { + $valide=false; + for($i=0;$i<=4;$i++) + { + if(substr($OutFile,$position+$subOffset+($i*4),4) == '6032') + { + $ToChange_Instruction = $position+$subOffset+($i*4); + $subOffset+=$i*4; + $valide = true; + break; + } + } + } + //Search the row 400b: jsr @r0 //jump instruction + if($valide) + { + $valide=false; + for($i=0;$i<=4;$i++) + { + if(substr($OutFile,$position+$subOffset+($i*4),4) == '400b') + { + $subOffset+=$i*4; + $valide = true; + break; + } + } + } + //Fixing + if($valide) + { + $SyscallFix = true; + //Change the mov instruction + $OutFile = substr($OutFile,0,$ToChange_Instruction) . '6033' . substr($OutFile,$ToChange_Instruction+4); + //Change the value moved (to the address of the code, to let him jump to it) + $OutFile = substr($OutFile,0,$ToChange_Address) . $syscallCodeAddress . substr($OutFile,$ToChange_Address+8); + } + } + $position+=4; + } + //Add the code for syscall if needed + if($SyscallFix) + { + $OutFile .= 'D201422B60F2000080010070'; + } + //Add the version mark + $OutFile .= '0000000053483420436F6D7061746962696C69747920546F6F6C2076'.$version; //SH4 Compatibility Tool v$version + //Recalcul header + $OutFileSize = strlen($OutFile)/2; + if($InFileSize != $OutFileSize) + { + //OFFSET 0xE : last filesize byte + 65(0x41) and NOT (inverted) + $offset0xE = $OutFileSize + 0x41; + $offset0xE = 0xFFFFFFFF - $offset0xE; + $offset0xE = dechex($offset0xE); + $offset0xE = str_pad($offset0xE, 2, '0', STR_PAD_LEFT); + $offset0xE = substr($offset0xE, -2); // take the last byte + //OFFSET 0x10 : filesize NOT (inverted) + $offset0x10 = 0xFFFFFFFF - $OutFileSize; + $offset0x10 = dechex($offset0x10);// to hex + $offset0x10 = str_pad($offset0x10, 8, '0', STR_PAD_LEFT); + //OFFSET 0x14 : last filesize byte + 184(0xB8) and NOT (inverted) + $offset0x14 = $OutFileSize + 0xB8; + $offset0x14 = 0xFFFFFFFF - $offset0x14; + $offset0x14 = dechex($offset0x14); + $offset0x14 = str_pad($offset0x14, 2, '0', STR_PAD_LEFT); + $offset0x14 = substr($offset0x14, -2); + //OFFSET 0x1f0 : filesize + $offset0x1f0 = $OutFileSize; + $offset0x1f0 = dechex($offset0x1f0);// to hex + $offset0x1f0 = str_pad($offset0x1f0, 8, '0', STR_PAD_LEFT); + //Modifs + $OutFile = substr($OutFile,0,0xE*2). $offset0xE . substr($OutFile,0xf*2,2). + $offset0x10 . substr($OutFile,0x14*2,0x1dc*2) . $offset0x1f0 . substr($OutFile,0x1f4*2); + // echo $InFileSize . ' - ' .$OutFileSize;exit; + } + //making + $fileContent = pack('H*',$OutFile); + //sending + header('Content-Type: application/octet-stream'); + header('Content-Disposition: attachment;filename="'.substr($_FILES['file']['name'],0,-4).'.g1a"'); + header('Cache-Control: max-age=0'); + $fh = fopen('php://output', 'wb'); + fwrite($fh, $fileContent); + fclose($fh); + +} +else +{ + header("Location: ../"); +} +?> \ No newline at end of file diff --git a/system/functions.php b/system/functions.php new file mode 100644 index 0000000..1172584 --- /dev/null +++ b/system/functions.php @@ -0,0 +1,188 @@ + "18", 254 => "19", 253 => "1a", 252 => "1b", 251 => "1c", + 250 => "1d", 249 => "1e", 248 => "1f", 247 => "10", 246 => "11", + 245 => "12", 244 => "13", 243 => "14", 242 => "15", 241 => "16", + 240 => "17", 239 => "08", 238 => "09", 237 => "0a", 236 => "0b", + 235 => "0c", 234 => "0d", 233 => "0e", 232 => "0f", 231 => "00", + 230 => "01", 229 => "02", 228 => "03", 227 => "04", 226 => "05", + 225 => "06", 224 => "07", 223 => "38", 222 => "39", 221 => "3a", + 220 => "3b", 219 => "3c", 218 => "3d", 217 => "3e", 216 => "3f", + 215 => "30", 214 => "31", 213 => "32", 212 => "33", 211 => "34", + 210 => "35", 209 => "36", 208 => "37", 207 => "28", 206 => "29", + 205 => "2a", 204 => "2b", 203 => "2c", 202 => "2d", 201 => "2e", + 200 => "2f", 199 => "20", 198 => "21", 197 => "22", 196 => "23", + 195 => "24", 194 => "25", 193 => "26", 192 => "27", 191 => "58", + 190 => "59", 189 => "5a", 188 => "5b", 187 => "5c", 186 => "5d", + 185 => "5e", 184 => "5f", 183 => "50", 182 => "51", 181 => "52", + 180 => "53", 179 => "54", 178 => "55", 177 => "56", 176 => "57", + 175 => "48", 174 => "49", 173 => "4a", 172 => "4b", 171 => "4c", + 170 => "4d", 169 => "4e", 168 => "4f", 167 => "40", 166 => "41", + 165 => "42", 164 => "43", 163 => "44", 162 => "45", 161 => "46", + 160 => "47", 159 => "78", 158 => "79", 157 => "7a", 156 => "7b", + 155 => "7c", 154 => "7d", 153 => "7e", 152 => "7f", 151 => "70", + 150 => "71", 149 => "72", 148 => "73", 147 => "74", 146 => "75", + 145 => "76", 144 => "77", 143 => "68", 142 => "69", 141 => "6a", + 140 => "6b", 139 => "6c", 138 => "6d", 137 => "6e", 136 => "6f", + 135 => "60", 134 => "61", 133 => "62", 132 => "63", 131 => "64", + 130 => "65", 129 => "66", 128 => "67", 127 => "98", 126 => "99", + 125 => "9a", 124 => "9b", 123 => "9c", 122 => "9d", 121 => "9e", + 120 => "9f", 119 => "90", 118 => "91", 117 => "92", 116 => "93", + 115 => "94", 114 => "95", 113 => "96", 112 => "97", 111 => "88", + 110 => "89", 109 => "8a", 108 => "8b", 107 => "8c", 106 => "8d", + 105 => "8e", 104 => "8f", 103 => "80", 102 => "81", 101 => "82", + 100 => "83", 99 => "84", 98 => "85", 97 => "86", 96 => "87", + 95 => "b8", 94 => "b9", 93 => "ba", 92 => "bb", 91 => "bc", + 90 => "bd", 89 => "be", 88 => "bf", 87 => "b0", 86 => "b1", + 85 => "b2", 84 => "b3", 83 => "b4", 82 => "b5", 81 => "b6", + 80 => "b7", 79 => "a8", 78 => "a9", 77 => "aa", 76 => "ab", + 75 => "ac", 74 => "ad", 73 => "ae", 72 => "af", 71 => "a0", + 70 => "a1", 69 => "a2", 68 => "a3", 67 => "a4", 66 => "a5", + 65 => "a6", 64 => "a7", 63 => "d8", 62 => "d9", 61 => "da", + 60 => "db", 59 => "dc", 58 => "dd", 57 => "de", 56 => "df", + 55 => "d0", 54 => "d1", 53 => "d2", 52 => "d3", 51 => "d4", + 50 => "d5", 49 => "d6", 48 => "d7", 47 => "c8", 46 => "c9", + 45 => "ca", 44 => "cb", 43 => "cc", 42 => "cd", 41 => "ce", + 40 => "cf", 39 => "c0", 38 => "c1", 37 => "c2", 36 => "c3", + 35 => "c4", 34 => "c5", 33 => "c6", 32 => "c7", 31 => "f8", + 30 => "f9", 29 => "fa", 28 => "fb", 27 => "fc", 26 => "fd", + 25 => "fe", 24 => "ff", 23 => "f0", 22 => "f1", 21 => "f2", + 20 => "f3", 19 => "f4", 18 => "f5", 17 => "f6", 16 => "f7", + 15 => "e8", 14 => "e9", 13 => "ea", 12 => "eb", 11 => "ec", + 10 => "ed", 9 => "ee", 8 => "ef", 7 => "e0", 6 => "e1", + 5 => "e2", 4 => "e3", 3 => "e4", 2 => "e5", 1 => "e6", + 0 => "e7", + ); + + + function fxiDecode($text) + { + global $Fxi2RawArray; + $fileSize = strlen($text); + $Return=""; + for($i = 0 ; $i < $fileSize; $i++) + { + if(isset($Fxi2RawArray[ord($text[$i])])) + { + $Return.= $Fxi2RawArray[ord($text[$i])]; + } + else + { + echo "Erreur : caractere non reconnu (erreur impossible)";exit; + } + } + return $Return; + } + + function getFxiPic($text) //transform fxi pic an array of 4 array reprent all 4 colors. array of colors are coded like a g1m sheet (natural organisation : one bit represente one pixel) ; color order : Blue, orange, green, white + { + $textSize = strlen($text); + $actuelColor = 0; + $x = 15; + $y = 63; + for($i = 8 ; $i < $textSize; $i+=2) + { + //changement de couleur + if($i >=($actuelColor*2056+2056) ) + { + $actuelColor++; + $i+=8; + $x = 15; + $y = 63; + } + //changement de colonne + if($y<0) + { + $y=63; + $x--; + } + //enregistrement de la case + $Return[$actuelColor][$y*16+$x] = hexdec($text[$i].$text[$i+1]); + $y--; + } + + return $Return; + } + + function getG1mPic($text) //transform G1m pic an array of 4 array reprent all 2 colors. array of colors are coded like a g1m sheet (natural organisation : one bit represente one pixel) ; color order : Blue, orange, green, white + { + $textSize = strlen($text); + $actuelColor = 0; + $i = 0; + while($actuelColor < 2 && $actuelColor*2048+$i<$textSize) + { + if($i >= 2048 ) + { + $i = 0; + $actuelColor++; + } + $Return[$actuelColor][$i/2] = hexdec($text[$actuelColor*2048+$i].$text[$actuelColor*2048+$i+1]); + $i+=2; + } + // print_r($Return);exit; + return $Return; + } + //color=array(array(255,0,0),array(0, 0, 255),array(0, 128, 0),array(66, 174, 9)) + + + function imageResizeAlpha($src, $coef) + { + $w = imagesx($src)*$coef; + $h = imagesy($src)*$coef; + $temp = imagecreatetruecolor($w, $h); + imagealphablending($temp, false); + imagesavealpha($temp, true); + $trans_layer_overlay = imagecolorallocatealpha($temp, 0, 0, 0, 127); + imagefill($temp, 0, 0, $trans_layer_overlay); + imagecopyresized($temp, $src, 0, 0, 0, 0, $w, $h, imagesx($src), imagesy($src)); + return $temp; + } + + //=array(array(255,0,0),array(0, 0, 255),array(0, 128, 0),array(66, 174, 9)) + // $actualPic = 1; + //0 = rouge + //1 = bleu + //2 = vert + //3 = white + function writePics($pics, $colors, $size=3) + { + $nbrPics = count($pics); + header("Content-type: image/png"); + $image = imagecreate(128,64); + $fond = imagecolorallocate($image, 255,255,255); + imagecolortransparent($image,$fond); + for($actualPic = 0 ; $actualPic < $nbrPics; $actualPic++) + { + // echo $actualPic; + $AColor = imagecolorallocate($image, $colors[$actualPic][0], $colors[$actualPic][1], $colors[$actualPic][2]); + for($xy = 0 ; $xy < 1024; $xy++) + { + $y = (int)($xy/16); + $x = ($xy%16)*8; + if($pics[$actualPic][$xy]&1)//00000001 + ImageSetPixel($image, $x+7, $y, $AColor); + if(($pics[$actualPic][$xy]&2)>>1)//00000010 + ImageSetPixel($image, $x+6, $y, $AColor); + if(($pics[$actualPic][$xy]&4)>>2)//00000100 + ImageSetPixel($image, $x+5, $y, $AColor); + if(($pics[$actualPic][$xy]&8)>>3)//00001000 + ImageSetPixel($image, $x+4, $y, $AColor); + if(($pics[$actualPic][$xy]&16)>>4)//00010000 + ImageSetPixel($image, $x+3, $y, $AColor); + if(($pics[$actualPic][$xy]&32)>>5)//00100000 + ImageSetPixel($image, $x+2, $y, $AColor); + if(($pics[$actualPic][$xy]&64)>>6)//01000000 + ImageSetPixel($image, $x+1, $y, $AColor); + if(($pics[$actualPic][$xy]&128)>>7)//10000000 + ImageSetPixel($image, $x, $y, $AColor); + } + } + //resizing + if($size >=2) + { + $image = imageResizeAlpha($image,$size); + } + //senging + imagepng($image); + } +?> \ No newline at end of file diff --git a/system/style.css b/system/style.css new file mode 100644 index 0000000..d309c0c --- /dev/null +++ b/system/style.css @@ -0,0 +1,47 @@ +body{margin:0;padding:0;background-color:#f6f6f6;font-family:sans-serif;min-width:950px;} +/* header bar : title and links */ +#headerBar{text-align:center;background-color:#ffffff;border:solid #a7d7f9 1px;width:95%;margin:15px auto;height:40px; box-shadow: 2px 2px 15px #d0d0d0;} + #headerBar div{margin:0;padding:0;color:red;} + #headerBar .Cell{height:40px;display:inline-block} + #headerBar .CellR{border-left:solid #a7d7f9 1px;float:right;} + #headerBar .CellL{border-right:solid #a7d7f9 1px;float:left;} + #headerBar a{color:#000;text-decoration:none;} + #headerBar a:hover{background-color:#ecfbfe;color:#120447; } + #headerBar .PClink:hover{color:#be1717;background-color:#fce9e9;border:solid #be1717 1px;margin:-1px -1px 0 0;} + #headerBar .casiopeiaLink:hover{color:#4283c6;background-color:#e2eef6;border:solid #4283c6 1px;margin:-1px -1px 0 0;} + #headerBar h1{font-weight:normal;font-family:sans-serif;font-size:26px;margin:5px 10px 0 10px;} + #headerBar strong{display:inline-block;font-weight:normal;font-size:18px;margin:9px 10px 0 10px;} +/* loadbox to get file to load */ + #loadBox{display:block;width:40%;margin: 0 auto;padding:10px;box-shadow: 2px 2px 15px #d0d0d0;background-color:white;border:solid #a7d7f9 1px;text-align:center;} + #loadBox a{text-decoration:none;color:#0d90d4;margin-top:5px;display:inline-block;} + #loadBox h4{margin:0;padding:0;} + #loadBox a:hover{text-decoration:underline;} +/* errorBox error msg box */ + #errorBox{overflow:hidden;display:none;width:90%;margin: 30px auto;padding:10px;box-shadow: 2px 2px 15px #d0d0d0;background-color:white;border:solid #a7d7f9 1px;text-align:center;} + #errorBox h3{margin:0;padding:0;font-size:1.2em;color:red;font-weight:normal;} + #errorBox a{text-decoration:none;color:#0d90d4;margin-top:5px;display:block;} + #errorBox a:hover{text-decoration:underline;} +/* settings Bar : name format and eventual password */ +#settingsBar{width:95%;height:25px;margin:auto;text-align:center;margin-bottom:15px;} + #settingsBar .title{float:left;} + #settingsBar .format{float:right;} + #settingsBar .centralInput{display:none;} +/* #eactmaker the textarea and menu in top and in bottom */ +#eactmaker{width:95%;margin: 0 auto;padding:0;box-shadow: 2px 2px 15px #d0d0d0;} + #eactmaker .buttons{background-color:white;height:33px;width:100%;border:solid #a7d7f9 1px;margin:0 auto;padding:0;} + #eactmaker .buttons.top{display:none;} + #eactmaker .buttons a{float:left;border-right:solid 1px #dddddd;margin:0;height:26px;padding:7px 15px 0 15px;text-decoration:none;color:#7b7b7b;display:none;} + #eactmaker .buttons a.img{padding:0 10px;height:33px;}/*comportement of the link for img */ + #eactmaker .buttons a.img div{display:inline-block;height:33px;} + #eactmaker .buttons a:hover{color:#0d90d4;background-color:#ecfbfe;} + #eactmaker .buttons .right{border-left:1px solid #dddddd;border-right:0;float:right;} + #eactmaker .buttons input{border-style:solid;border-width:0 1px 0 1;background:none;display:inline-block;margin:0;height:33px;padding:0px 15px 0 15px;color:#7b7b7b;font-size:1.0em;font-weight:bold;cursor:pointer;} + #eactmaker .buttons input:hover{color:#0d90d4;background-color:#ecfbfe;} + #eactmaker #charsTab{background-color:#f3fdfe;width:100%;border:solid #a7d7f9 1px;margin:-1px auto 0 auto;padding:0;display:none;} + #eactmaker #charsTab div{margin:10px auto;position:relative;} + #eactmaker #charsTab a{position:absolute;display:block;width:23px;height:29px;} + #eactmaker #charsTab a:hover{border:solid #a7d7f9 1px;} + #eactmaker textarea{vertical-align: middle;width:100%;font-size:1.3em;border:solid #a7d7f9 1px;padding:0;max-width:100%;min-width:100%;min-height:30px;margin:-1px 0 -1px 0;} +#footer{font-size:10px;color:grey;float:right;width:100%;text-align:center;margin-top:15px;} + #footer a{color:grey;} + #footer a:hover{text-decoration:none;} \ No newline at end of file