text:
set tag=%DATE:~6,4%%DATE:~0,2%%DATE:~3,2%.%time:~0,2%%time:~3,2%
gh release create %tag% C:\Users\pvnn\Desktop\ToIR_Patch\toir_patch.zip --title "Tales of Innocence R - English Patch %date% %time%" --notes "Automated Release %date% %time% Story 69%%, Movie Subs 99%%, Skit 68%%, Menu 84%%, NPC 75%%"
pause

repo_name: lifebottle/Tales-of-Innocence-R

repo_language: Python

mime_type: text/plain


text:
# Tales of Innocence R

An attempt to create an English patch for Tales of Innocence R.

![ToIR](https://raw.githubusercontent.com/pnvnd/Tales-of-Innocence-R/main/toir.png)

Discord: https://discord.gg/tmDgBDNPpE

Spreadsheet (Current): https://docs.google.com/spreadsheets/d/1hfZIQXpGyQw6dQtG_oVKI7bkO0teIUG9bXN9kKrANBw/edit?usp=sharing
Spreadsheet (Outdated): https://docs.google.com/spreadsheets/d/1X_-WfAYM5J2JL2uDAK0JPA9P8zGQHe2G3otozGymd1I/edit?usp=sharing

## Hacker Note 1

![ToIR](https://raw.githubusercontent.com/pnvnd/Tales-of-Innocence-R/main/decrypt_toir.gif)

1. Get `pkg2zip` https://github.com/mmozeiko/pkg2zip
2. Also, get `psvpfstools` https://github.com/motoharu-gosuto/psvpfstools
3. With `pkg2zip`, extract the `pkg` `pkg2zip.exe` `innocenceR.pkg`
4. It should produce this zip file `テイルズ オブ イノセンス R [PCSG00009] [JPN].zip`. Extract it.
5. It should extract a folder named app, cut the folder PCSG00009 from it and paste it in the same folder as `psvpfstools`
6. Then run `psvpfsparser.exe -i PCSG00009 -o PCSG00009_dec -z <zRif> -f http://cma.henkaku.xyz`
7. Get `zRif` from somewhere.
8. If everything's correct it will decrypt everything into the `PCSG00009_dec`
9. You still need a Vita to decrypt the `EBOOT.BIN` as it is an FSELF and it's encrypted
10. Download `FAGDec.vpk` https://github.com/CelesteBlue-dev/PSVita-RE-tools/raw/master/FAGDec/build/FAGDec.vpk
11. Install `FAGDec.vpk` from Vita Shell.  A new bubble should be on the home screen.
12. Select the `EBOOT.BIN` file to decrypt and choose `START DECRYPT(ELF)`

![L7CA_header](https://raw.githubusercontent.com/pnvnd/Tales-of-Innocence-R/main/L7CA_decrypted.png)

## Hacker Note 2 (Outdated)

1. Compile `psvita-l7ctool.exe` from the taikotools submodule and run this command: `psvita-l7ctool.exe x toidata_release.l7c`
2. You'll get a message `This archive type is unsupported and most likely won't unpack properly.`
3. Files get extracted anyway, not sure if extracted correctly though: `_Data/Battle/Effect/00.pck`

## Hacker Note 3

1. Skip Hacker Note 2
2. Get `Kuriimu2` from https://github.com/FanTranslatorsInternational/Kuriimu2/releases/tag/1.2.0
3. Install .NET Core 3.1 Runtime
4. Open `Kuriimu2.exe` and go to File > Open with Plugin > plugin_bandai_namco.dll > L7C > OK
5. Select `toidata_release.l7c` (decrypted from Hacker Note 1)
6. Right-click `_Data` and extract all.
7. View files in `UTF-8` to get readable Japanese

## Hacker Note 4
1. ItemDataPack.dat = item name and description
2. ArtsDataPack.dat - Magic
3. EnemyParam = artes names, enemy names
4. BattleBookDataPack.dat seems to be tutorial ( and also end of battle pop ups ?)
5. these DAT files are easy enough that you could just write a simple Python script that extracts/inserts the text, Python can decode/encode UTF-8 natively
6. first a 32-bit word that represents the number of "sub sections" or whatever you want to call them. then, starting from offset 0x10, there is a list of offset/size pairs that describes the sub sections. each sub section then contains a sequence of records. the records contain the text (besides others).
7. the size of a record is always the same within a sub section, but can be different between different sub sections
8. get from virtual address to offset in the file you'd need to subtract `0x80FFF000`
9. you only substract the offset to the text section from the base virtual address
10. `abcde`: if it uses 32-bit math you could try `7F001000` (positive)
11. `0x175AEC` at this offset there's a pointer to some text
12. the pointers where the lower 16 and upper 16 bits are a few instructions apart, because they're embedded into the code, and you need to extract the values from the instruction encoding (which abcde probably can't do)
13. new line `0A`

## Hacker Note #4
1. Change the `eboot.bin` (the one with the 0x1000 SCE header) as follows: offset `0x7B5E` (should contain `01 23`) to `00 23` to fix monospace problem
2. Also, change the `eboot.bin` as follows: offset `0x8630` (should contain `80 18`) to `40 18`
3. If `recompile.py` doesn't work, install python package "sortedcontainers", "click", and "pypng": `pip install sortedcontainers`, `pip install click`, `pip install pypng`
4. Get `Kuriiku2.exe` (CMD version) to batch replace patched files: `Kuriimu2.exe extensions batch-inject orig-dir patch-dir`
```
orig-dir
 data_release.l7c (as a file)

patch-dir
 data_release.l7c (as a directory)
  _data
   announcement
    announcement.anm
 (followed by any number of files in the correct directory relative to the L7c)
```

## Hacker Note #5
![movie_subs](https://raw.githubusercontent.com/pnvnd/Tales-of-Innocence-R/main/moviecaptions.png)
1. Movie subs can be saved to .SRT format

## Hacker Note #6
![kuriimu2](https://raw.githubusercontent.com/pnvnd/Tales-of-Innocence-R/main/kuriimu2_settings.png)
1. Batch inject settings to create patch

## Credits
Thanks to Ethanol for basically everything
Thanks to LT for for basically everything
Thansk to OnePieceFreak for basically everything
`Mine` for Translation Script: https://docs.google.com/document/d/12hoLXugMHRRiQ7YrqrB5nvLvxlpH9tIc7yisB_ZI_Y4/edit
`kkhdigifantasy13` for Translation Guide: https://drive.google.com/file/d/1sbCzce3OMSNVEKuTcYGWufS-NWkJq4Th/view?usp=sharing
`AppleKratue` for Gameplay Guide: https://psnprofiles.com/guide/6120-tales-of-innocence-r-faq (https://twitter.com/talesofkratue)

## Tools
https://gbatemp.net/threads/release-decrypt-and-launch-psn-store-vita-games-without-plugins.548878/
https://github.com/FanTranslatorsInternational/Kuriimu2
https://github.com/onepiecefreak3/taikotools
https://github.com/mariodon/taikotools


text:
import struct

with open('boot.elf', 'rb') as f:
    binary = f.read()

start = 0x175B10
end = 0x175B18

for offset in range(start, end, 4):
    dest, = struct.unpack_from('<L', binary, offset)
    dest -= 0x80FFF000
    end_dest = dest
    while binary[end_dest] != 0:
        end_dest += 1
    print(binary[dest:end_dest].decode('utf-8'))


text:
REM Sheets with <space> in the name should be replaced with %20. Using BAT file, escape % by using %%20 instead.
if not exist "translated" mkdir translated
curl -L "https://docs.google.com/spreadsheets/d/1hfZIQXpGyQw6dQtG_oVKI7bkO0teIUG9bXN9kKrANBw/gviz/tq?tqx=out:csv&sheet=Skits%%205.0&range=A:G" > ./translated/Skit.csv
curl -L "https://docs.google.com/spreadsheets/d/1hfZIQXpGyQw6dQtG_oVKI7bkO0teIUG9bXN9kKrANBw/gviz/tq?tqx=out:csv&sheet=Story%%201.0" > ./translated/Story.csv
curl -L "https://docs.google.com/spreadsheets/d/1hfZIQXpGyQw6dQtG_oVKI7bkO0teIUG9bXN9kKrANBw/gviz/tq?tqx=out:csv&sheet=TESTING-eboot&range=A:C" > ./translated/eboot.csv
curl -L "https://docs.google.com/spreadsheets/d/1hfZIQXpGyQw6dQtG_oVKI7bkO0teIUG9bXN9kKrANBw/gviz/tq?tqx=out:csv&sheet=Done--Artes&range=A:E" > ./translated/ArtsDataPack.csv
curl -L "https://docs.google.com/spreadsheets/d/1hfZIQXpGyQw6dQtG_oVKI7bkO0teIUG9bXN9kKrANBw/gviz/tq?tqx=out:csv&sheet=WIP-Items&range=A:E" > ./translated/ItemDataPack.csv
curl -L "https://docs.google.com/spreadsheets/d/1hfZIQXpGyQw6dQtG_oVKI7bkO0teIUG9bXN9kKrANBw/gviz/tq?tqx=out:csv&sheet=Done--Names&range=A:C" > ./translated/CharaNames.csv
curl -L "https://docs.google.com/spreadsheets/d/1hfZIQXpGyQw6dQtG_oVKI7bkO0teIUG9bXN9kKrANBw/gviz/tq?tqx=out:csv&sheet=Mapdata/NPC%%201.0&range=A:G" > ./translated/MapData.csv
pause


text:
xdelta.exe -f -e -s "PCSG00009_dec\eboot.bin" "patched\eboot.bin" "dec_eboot.xdelta"
xdelta.exe -f -e -s "eboot.bin.elf" "patched\eboot.bin" "fagdec_eboot_elf.xdelta"
xdelta.exe -f -e -s "eboot.bin" "patched\eboot.bin" "fagdec_eboot.xdelta"
xdelta.exe -f -e -s "eboot_raw.bin" "patched\eboot.bin" "raw_eboot.xdelta"
xdelta.exe -f -e -s "PCSG00009_dec\toidata_release.l7c" "orig-dir\toidata_release.l7c" "toidata_release.xdelta"
xdelta.exe -f -e -s "C:\temp\ToIR\PCSG00009\toidata_release.l7c" "orig-dir\toidata_release.l7c" "raw_toidata_release.xdelta"
tar.exe -acf toir_patch.zip dec_eboot.xdelta fagdec_eboot_elf.xdelta fagdec_eboot.xdelta toidata_release.xdelta toir_readme.txt
pause


text:
import sys

def monospace1():
    eboot = open('patched/eboot.bin', 'r+b')
    eboot.seek(0x7B5E)
    eboot.write(b'\x00\x23')
    eboot.close()

def monospace2():
    eboot = open('patched/eboot.bin', 'r+b')
    eboot.seek(0x8630)
    eboot.write(b'\x40\x18')
    eboot.close()

if __name__ == '__main__':
    if sys.argv[1] == '1':
        monospace1()
        monospace2()


text:
if exist "tmp" rmdir tmp /s/q
Kuriimu2.exe extensions batch-inject orig-dir patch-dir
pause


text:
python extract.py PCSG00009_dec extracted
pause


text:
import struct
import csv

def get_captions():
    subs = []
    f = open("C:/Users/pvnn/Desktop/MovieCaption_019.dat", "rb")

    # Check total number of lines in subtitles, in this case 60 lines (Long unsigned integer)
    lines = struct.unpack('<L', f.read(4))[0]

    # Move to 0x10
    f.seek(0x10)

    # Check the 60 lines for time and caption
    while line < lines

    # Read the next 4 bytes to get the time in float
    # read next 252 bytes convert to text
    # store in dictionary or something
    subs.append([struct.unpack('<f', f.read(4))[0], b"something".decode("utf-8")])
    line += 1

    # Write to a CSV file
    f.close()


text:
python recompile.py PCSG00009_dec translated patched
python monospace.py 1
xcopy "patched\toidata_release\_Data" "patch-dir\toidata_release.l7c\_Data" /E/H/C/I/Y
pause


text:
with open('utf-8.tbl', 'w', encoding='utf-8') as f:
    for i in range(0x20, 0x10000):
        try:
            utf8 = chr(i).encode('utf-8')
            for b in utf8:
                f.write(f'{b:02X}')
            f.write(f'={chr(i)}\n')
        except:
            pass


text:
//GAME NAME:        TOI

// Define required TABLE variables and load the corresponding tables
#VAR(Table_0, TABLE)
#ADDTBL("C:\build\romhacking\innocence-r\ascii.tbl", Table_0)

//BLOCK #000 NAME:        test
#ACTIVETBL(Table_0) // Activate this block's starting TABLE

#JMP($162340, $1623AD) // Jump to insertion point
#HDR($-80FFF000) // Difference between ROM and RAM addresses for pointer value calculations

//POINTER #0 @ $175B18 - STRING #0 @ $162340
#W32($175B18)
UseItem[END]
// current address: $162348

//POINTER #1 @ $175B1C - STRING #1 @ $162348
#W32($175B1C)
Weapon[END]
// current address: $16234F

//POINTER #2 @ $175B20 - STRING #2 @ $162350
#W32($175B20)
Armor[END]
// current address: $162356

//POINTER #3 @ $175B24 - STRING #3 @ $162358
#W32($175B24)
Helm[END]
// current address: $16235D

//POINTER #4 @ $175B28 - STRING #4 @ $162360
#W32($175B28)
Accessory[END]
// current address: $16236A

//POINTER #5 @ $175B2C - STRING #5 @ $16236C
#W32($175B2C)
Material[END]
// current address: $162375

//POINTER #6 @ $175B30 - STRING #6 @ $162378
#W32($175B30)
Event[END]
// current address: $16237E

//POINTER #7 @ $175B34 - STRING #7 @ $162380
#W32($175B34)
DLC[END]
// current address: $162384

//POINTER #8 @ $175B38 - STRING #8 @ $162384
#W32($175B38)
New[END]
// current address: $162388

//POINTER #9 @ $175B3C - STRING #9 @ $162388
#W32($175B3C)
CodeName[END]
// current address: $162391

//POINTER #10 @ $175B40 - STRING #10 @ $162394
#W32($175B40)
Recipe[END]
// current address: $16239B

//POINTER #11 @ $175B44 - STRING #11 @ $16239C
#W32($175B44)
Rave[END]
// current address: $1623A1

//POINTER #12 @ $175B48 - STRING #12 @ $1623A4
#W32($175B48)
Operation[END]
// current address: $1623AE


text:
#GAME NAME: TOI
#BLOCK NAME: test
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $175B18
#POINTER TABLE STOP:  $175B4C
#POINTER SIZE: 4
#POINTER SPACE: 0
#BASE POINTER: -$80FFF000
#TABLE: ascii.tbl
#ATLAS PTRS: Yes
#COMMENTS: No
#END BLOCK


text:
/00=[END]
20=
21=!
22="
23=#
24=$
25=%
26=&
27='
28=(
29=)
2A=*
2B=+
2C=,
2D=-
2E=.
2F=/
30=0
31=1
32=2
33=3
34=4
35=5
36=6
37=7
38=8
39=9
3A=:
3B=;
3D==
3F=?
40=@
41=A
42=B
43=C
44=D
45=E
46=F
47=G
48=H
49=I
4A=J
4B=K
4C=L
4D=M
4E=N
4F=O
50=P
51=Q
52=R
53=S
54=T
55=U
56=V
57=W
58=X
59=Y
5A=Z
5B=[
5D=]
5E=^
60=`
61=a
62=b
63=c
64=d
65=e
66=f
67=g
68=h
69=i
6A=j
6B=k
6C=l
6D=m
6E=n
6F=o
70=p
71=q
72=r
73=s
74=t
75=u
76=v
77=w
78=x
79=y
7A=z
7B={
7C=|
7D=}


text:
1
00:00:03,000 --> 00:00:08,000
文字列テスト

2
00:00:15,000 --> 00:00:18,000
@BABCDEFGHIJKLMN
OPQRSTUVWXYZ

3
00:00:18,000 --> 00:00:21,000
@BBAHBNJIAHIO
NUFKSHUFHOHWUIHFUHIOKJGIAGY

4
00:00:21,000 --> 00:00:25,000
@BISJNIIUCIHWUI
IADOI


text:
1
00:00:00,000 --> 00:00:01,799
Don't joke with me, rats!

2
00:00:02,799 --> 00:00:06,000
Urraaaaaaaahhh!!

3
00:00:09,600 --> 00:00:12,199
This despair of mine...

4
00:00:12,199 --> 00:00:14,300
Asura's despair!

5
00:00:14,300 --> 00:00:16,500
I'll hammer it into your very bones!


text:
1
00:00:01,000 --> 00:00:03,000
Hm? What?

2
00:00:03,000 --> 00:00:05,000
A ship's approachin' us.

3
00:00:14,000 --> 00:00:15,000
Whoa!

4
00:00:15,000 --> 00:00:16,000
Damn, what's happening!?

5
00:00:16,000 --> 00:00:21,700
Registration No. 120093
Garam Ship No. 120093

6
00:00:21,700 --> 00:00:23,399
Stop your vessel at once!

7
00:00:23,399 --> 00:00:26,000
Why are we being shot at!?
What happened!?

8
00:00:26,000 --> 00:00:29,000
Garam Ship No. 120093!

9
00:00:29,000 --> 00:00:30,799
This voice!
It's that woman again!

10
00:00:30,799 --> 00:00:35,299
Ship Number 120093.
Stop your vessel at once and surrender!

11
00:00:35,299 --> 00:00:38,000
Should you fail to surrender,
we'll have no choice but to sink your ship!


text:
1
00:00:00,000 --> 00:00:02,000
You bastard...

2
00:00:03,299 --> 00:00:07,780
This pain... this hatred...

3
00:00:07,780 --> 00:00:11,500
I'll never let you forget it...

4
00:00:12,500 --> 00:00:15,750
That's right. It's fine if I never forget.

5
00:00:15,900 --> 00:00:21,200
Mattheus... no, Asura's despair,

6
00:00:22,000 --> 00:00:25,000
the pain that your heart holds...

7
00:00:26,000 --> 00:00:28,000
will always be with me.

8
00:00:28,000 --> 00:00:35,000
"My pain" you say? As if you can...

9
00:00:35,000 --> 00:00:40,000
c-carry... my... pain...

10
00:00:41,000 --> 00:00:44,000
Because, you're also a part of me.

11
00:00:46,599 --> 00:00:50,000
Now, come.

12
00:01:08,000 --> 00:01:13,000
That can't be... why?

13
00:01:13,000 --> 00:01:16,500
No... I don't want this...

14
00:01:16,500 --> 00:01:19,500
No... Lord Asura...

15
00:01:21,000 --> 00:01:24,500
............fufufu... fufufufufu...

16
00:01:24,500 --> 00:01:29,000
Lady Mattheus is gone...

17
00:01:30,000 --> 00:01:36,000
And I will never reach Ruca's heart...

18
00:01:36,699 --> 00:01:45,000
And I... will never... have... Lord Asura...

19
00:01:45,000 --> 00:01:48,000
Noooooooooo!!


text:
1
00:00:00,000 --> 00:00:04,500
Asura!  You bastard!!

2
00:00:04,500 --> 00:00:09,000
You've killed another one of my comrades!

3
00:00:09,800 --> 00:00:15,199
I won't forgive you... I won't forgive you!!

4
00:00:16,000 --> 00:00:17,799
Stay back, Ruca!

5
00:00:17,799 --> 00:00:21,000
Rest thine heart unto thy blade
to become a shield for others!

6
00:00:21,000 --> 00:00:24,250
It's something my gramps used to say to me.

7
00:00:24,500 --> 00:00:26,500
Get back so you don't get hurt.

7
00:00:26,500 --> 00:00:28,000
Here I go!!

8
00:00:30,200 --> 00:00:32,200
...Durandal!


text:
1
00:00:00,000 --> 00:00:02,400
Fuuuuuuuuuu...!

2
00:00:02,400 --> 00:00:03,799
Haaaaaa!

3
00:00:04,699 --> 00:00:07,000
Devaloka!!

4
00:00:07,000 --> 00:00:09,699
Opposition Wave!!


text:
1
00:00:00,000 --> 00:00:03,000
What's wrong?  Too scared to move?

2
00:00:04,000 --> 00:00:05,699
Then, let me help you.

3
00:00:08,000 --> 00:00:09,800
Just run away already!

4
00:00:09,800 --> 00:00:12,500
Who knows what else these guys will do.

5
00:00:17,000 --> 00:00:18,700
Remember...

6
00:00:18,700 --> 00:00:22,500
The other me... the other me...

7
00:00:22,500 --> 00:00:27,200
the other me, the other me, the other me...

8
00:00:28,000 --> 00:00:31,299
I... I can do it too!

9
00:00:31,299 --> 00:00:34,000
Uwaaaaah!!

10
00:00:36,299 --> 00:00:39,000
What, t-this guy is...!


text:
1
00:00:01,200 --> 00:00:05,000
So 'ow 'bout it? Wanna play for a bit?

2
00:00:12,000 --> 00:00:18,000
W-w-what... even you?!
You were a m-m-monster too!

3
00:00:18,000 --> 00:00:22,700
It is because I am a monster that
I shall protect you, master.

4
00:00:22,700 --> 00:00:27,000
Hmph, so yer also a Reincarnator.

5
00:00:27,000 --> 00:00:33,700
No need t' hold back, 'ear?
If ya don', you'll get 'urt real good.

6
00:00:33,700 --> 00:00:37,599
Even if God won't forgive you, I shall!

7
00:00:37,599 --> 00:00:38,500
Hermie!

8
00:00:38,500 --> 00:00:40,000
Teach him how it's done!

9
00:00:40,000 --> 00:00:47,000
Telling me to come at you seriously...
Let me whip some humility onto you.

10
00:00:47,000 --> 00:00:50,299
No turning back now!

11
00:00:50,299 --> 00:00:54,000
I'm not letting ya lay a hand on Big Sis Ange.

12
00:00:54,000 --> 00:00:57,500
I'll deal with ya by mahself.

13
00:00:58,000 --> 00:00:59,200
Let's go!


text:
1
00:00:03,400 --> 00:00:05,800
Hey, seems like we can open this here.
Ruca, lend me a hand.

2
00:00:05,800 --> 00:00:06,699
Sure.

3
00:00:06,699 --> 00:00:08,500
One, two...


text:
1
00:00:01,799 --> 00:00:06,000
You bastard! Let go!
I said, let go of me!

2
00:00:06,800 --> 00:00:10,000
You are under arrest under the edict
of the Deviant Reformation Act!

3
00:00:10,000 --> 00:00:12,000
Now walk, deviant!

4
00:00:13,000 --> 00:00:16,200
Me, a "deviant"!?
I'm just a normal human, goddammit!

5
00:00:16,200 --> 00:00:18,500
Let go of me, you bastard!
Let go!

6
00:00:18,500 --> 00:00:20,200
Hmph! This brat!

7
00:00:20,200 --> 00:00:24,500
I heard that you're from a famous family of knights,
but now you're nothing but a mere delinquent!

8
00:00:24,500 --> 00:00:26,299
Shut up!
That's none of your business!

9
00:00:26,299 --> 00:00:27,299
Let go of me!

10
00:00:27,299 --> 00:00:30,000
For a kid you sure are strong, but...

11
00:00:30,000 --> 00:00:33,500
...it seems that you haven't fully awakened yet.

12
00:00:33,500 --> 00:00:36,000
Now, cut it out and turn yourself in.

13
00:00:36,000 --> 00:00:37,500
Let's go!


text:
1
00:00:01,000 --> 00:00:04,500
You foolish child!


text:
1
00:00:00,200 --> 00:00:04,500
Why do I have to buy food for those two...

2
00:00:14,199 --> 00:00:16,500
H-Hey... what's the matter with you?

3
00:00:16,500 --> 00:00:19,000
Are you feeling sick? Hey!

4
00:00:20,500 --> 00:00:24,000
I can't... make it... anymore.

5
00:00:24,000 --> 00:00:25,000
Eeeehhh!?

6
00:00:25,000 --> 00:00:28,000
Hey, hang in there! Don't die!!


text:
1
00:00:00,000 --> 00:00:02,799
What's that?

2
00:00:02,799 --> 00:00:05,500
Don't let your guard down, everyone...

3
00:00:21,000 --> 00:00:23,299
What's it doing...?

4
00:00:23,299 --> 00:00:26,799
It's taking the corpses into itself...


text:
1
00:00:00,000 --> 00:00:01,200
Wait a minute!

2
00:00:02,000 --> 00:00:03,500
Then then...

3
00:00:03,500 --> 00:00:06,000
What about plan C?

4
00:00:06,000 --> 00:00:09,699
Uhh... what happened to plan B again?

5
00:00:09,699 --> 00:00:12,000
Ah well, whatever.

6
00:00:12,000 --> 00:00:17,000
Now, now, this should be fine first, right?

7
00:00:18,299 --> 00:00:23,200
Before you attack me, I'm gonna let
you in on some classified information.

8
00:00:23,200 --> 00:00:27,500
And you all will change your minds.

9
00:00:27,500 --> 00:00:31,500
Just like a mountain's weather!

10
00:00:32,000 --> 00:00:36,000
Illia, Ricardo. Got your bullets ready?

11
00:00:36,000 --> 00:00:36,799
Locked and loaded.

12
00:00:36,799 --> 00:00:38,000
Naturally!

13
00:00:40,000 --> 00:00:44,000
Don't shoot at someone defenseless!

14
00:00:44,000 --> 00:00:45,299
Wait, hear me out!!

15
00:00:45,299 --> 00:00:51,000
I'll spill the beans now! Okay?

16
00:00:51,000 --> 00:00:54,000
Now, boy?

17
00:00:54,000 --> 00:00:58,700
Lend me your ear for a bit.
I'll pay you back later.

18
00:00:58,700 --> 00:01:00,000
Huh? Me?

19
00:01:00,000 --> 00:01:01,000
Ruca!

20
00:01:01,000 --> 00:01:04,000
Don't listen to this guy!

21
00:01:04,000 --> 00:01:09,000
Now, now, boy.
Listen closely...

22
00:01:09,000 --> 00:01:13,300
This is the sound of a blade tearing through flesh...

23
00:01:25,500 --> 00:01:26,000
Crap!!

24
00:01:26,000 --> 00:01:27,000
YOU BASTARD!!

25
00:01:27,000 --> 00:01:29,000
Ruca, hang in there!!


text:
1
00:00:05,000 --> 00:00:06,500
What!? What's happening!?

2
00:00:09,500 --> 00:00:10,000
What's that?

3
00:00:15,500 --> 00:00:18,000
@BA_KURAMA_TE_DNIa
SE_DOROKO_TAN_NOK[

4
00:00:18,000 --> 00:00:20,000
@BAD_NUREAKE_TIKI_AW_QQ[


text:
1
00:00:05,000 --> 00:00:06,500
What!? What's happening!?

2
00:00:09,500 --> 00:00:10,000
What's that!?

3
00:00:15,500 --> 00:00:18,000
As if I'm going to die here!

4
00:00:18,000 --> 00:00:20,000
QQ is going to live and go home!


text:
from collections import namedtuple
from itertools import groupby, zip_longest
import re, struct, os, sys

rTIME_STAMP = r"(\d+):(\d+):(\d+),(\d+)"
subtitle = namedtuple('Subtitle', 'number start end content')
MAX_SUB_SIZE = 0x100
MAX_LINE_SIZE = 0xFB
HEADER_SIZE = 0x10

def get_file_name_noext(path):
    return os.path.splitext(os.path.basename(path))[0]

def srt_time_to_seconds(time_stamp:str):
    times = [int(s) for s in re.findall(rTIME_STAMP, time_stamp)[0]]
    seconds = float(times[0] * 60) #hours
    seconds += times[1] * 60       #minutes
    seconds += times[2]            #seconds
    seconds += times[3] / 1000     #miliseconds
    return seconds

def get_sub_pos(sub:int):
    return sub * MAX_SUB_SIZE + HEADER_SIZE

def insert_sub_into(buf:bytearray,sub:subtitle,index:int):
    assert len(sub.content) <= MAX_LINE_SIZE, "Subtitle line %02d is too long!" % sub.number
    struct.pack_into("<f%ds" % len(sub.content),buf,get_sub_pos(index),sub.start,sub.content)

def get_subs(filename:str):
# simple srt parser from: https://stackoverflow.com/a/23620587
# "chunk" our input file, delimited by blank lines
    with open(filename, encoding="utf8") as f:
        res = [list(g) for b,g in groupby(f, lambda x: bool(x.strip())) if b]

    subs = []

    for sub,sub_n in zip_longest(res, res[1:]):
        assert len(sub) >= 3, "Invalid subtitle entry in file: %s" % filename
        sub = [x.strip() for x in sub]
        number = sub[0]
        start, end = [srt_time_to_seconds(t) for t in sub[1].split(' --> ')]
        content = str.encode("\r\n".join(sub[2:]))
        subs.append(subtitle(number, start, end, content))

        if sub_n != None:
            assert len(sub_n) >= 3, "Invalid subtitle entry in file: %s" % filename
            sub_n = [x.strip() for x in sub_n]
            start_n, _ = [srt_time_to_seconds(t) for t in sub_n[1].split(' --> ')]
            if end != start_n:
                subs.append(subtitle(-1, end, start_n, b"\x00"))
        else:
            subs.append(subtitle(-1, end, end, b"\x00"))

    return subs

srt_files = []
for file in os.listdir(os.getcwd()):
    if file.endswith(".srt"):
        srt_files.append(file)

print("Found %02d srt files in path: %s" % (len(srt_files), os.getcwd()))
for p_files, file in zip(range(1,len(srt_files)+1),srt_files):
    print("Converting file %02d/%02d..." % (p_files, len(srt_files)), end="\r")
    subs = get_subs(file)

    dat_file = bytearray(len(subs) * MAX_SUB_SIZE + HEADER_SIZE)
    struct.pack_into("<i",dat_file,0,len(subs))

    for i, sub in zip(range(len(subs)),subs):
        insert_sub_into(dat_file,sub,i)

    out_name = get_file_name_noext(file) + ".dat"
    with open(out_name, mode="wb+") as f:
        f.write(bytes(dat_file))

print("Converted %02d/%02d files." % (p_files, len(srt_files)))
print("Done!")


text:
1
00:00:03,299 --> 00:00:06,000
Now my brother can rest in peace...

2
00:00:23,500 --> 00:00:24,500
Brother!!


text:
1
00:00:00,300 --> 00:00:02,000
I'm sorry, but...

2
00:00:02,000 --> 00:00:05,000
...there will not be a "next time".

3
00:00:05,000 --> 00:00:10,500
Huh? Don't lie to me...

4
00:00:11,000 --> 00:00:22,000
Don't you know how many... tiiimes... I've beeeen...
reborn... till now... That's why next time I...


text:
1
00:00:07,199 --> 00:00:09,800
The cause of Cielo's destruction...

2
00:00:09,800 --> 00:00:13,800
...and the one who brought misfortune
upon everyone was me...

3
00:00:14,500 --> 00:00:17,000
I... I...

4
00:00:17,000 --> 00:00:19,500
It's better if I just disappear!

5
00:00:19,500 --> 00:00:22,000
It'd be better!!

6
00:00:30,000 --> 00:00:32,500
Lady Mattheus, the castle is collapsing.

7
00:00:32,500 --> 00:00:34,500
There is still time. Come, Chitose!

8
00:00:36,799 --> 00:00:39,000
Everyone, let's hurry back to the airship.

9
00:00:39,000 --> 00:00:40,000
But Ruca...!

10
00:00:40,000 --> 00:00:42,500
We can't leave him here!

11
00:00:42,500 --> 00:00:43,700
............Can't be helped.

12
00:00:43,700 --> 00:00:46,000
Oi, Kongwai, QQ.

13
00:00:50,799 --> 00:00:53,799
You bastar...

14
00:00:53,799 --> 00:00:57,700
Sorry, but we can't afford to lose you two here.

15
00:00:57,700 --> 00:00:59,500
Ange, Hermie, let's go!

16
00:00:59,500 --> 00:01:03,000
Do you think Ruca would want you to stay here?


text:
1
00:00:08,000 --> 00:00:09,500
Brother...


text:
1
00:00:04,100 --> 00:00:05,740
I had a dream

2
00:00:05,750 --> 00:00:07,600
In a faraway place

3
00:00:07,650 --> 00:00:10,100
In a faraway land

4
00:00:10,400 --> 00:00:12,900
There, I--

5
00:00:15,000 --> 00:00:17,200
Why am I in this world!

6
00:00:17,200 --> 00:00:20,000
Right now, at this time, at this place!

7
00:00:20,000 --> 00:00:23,000
Why was I born!?

8
00:00:23,000 --> 00:00:25,500
And what meaning does that reason hold!?

9
00:00:25,500 --> 00:00:30,000
The answer to that lies in this battle!!

10
00:00:31,299 --> 00:00:33,599
For the unification of Cielo!

11
00:00:36,000 --> 00:00:43,000
From this day forth, we are no longer rebels.
All of Cielo and future generations

12
00:00:43,000 --> 00:00:47,000
Will undoubtedly know us as Sensus!

13
00:00:54,000 --> 00:00:57,799
And soon, we shall obtain the Genesis
the Primordial Wish!

14
00:00:57,799 --> 00:01:00,799
And grant us our ideal world!

15
00:01:00,799 --> 00:01:04,000
ALL TROOPS! CHARGE!

16
00:01:32,199 --> 00:01:34,500
Asura, take care...

17
00:01:35,000 --> 00:01:36,500
Lord Asura...

18
00:01:37,500 --> 00:01:40,199
Lady Inanna, Lady Sakuya.

19
00:01:40,199 --> 00:01:45,500
If it's Lord Asura, then he will surely
do a splendid job conquering that fortress.

20
00:01:51,000 --> 00:01:52,199
Hahahaha!!

21
00:01:52,199 --> 00:01:57,500
Dear mother!
The battle will grow stale lest you hold back！

22
00:02:01,000 --> 00:02:05,000
If you've grown bored,
Then how about I entertain you for the moment?

23
00:02:08,000 --> 00:02:11,000
However it will cost you your life!

24
00:02:11,000 --> 00:02:14,000
Reaper Hypnos... is that what they call you?

25
00:02:14,000 --> 00:02:17,000
I take it your older brother is in good health?

26
00:02:17,000 --> 00:02:21,500
Do not mention my brother...
Be quiet and let me slay your life.

27
00:02:22,000 --> 00:02:28,500
Have you grown tired of stealing souls from
Terro that you've come to steal mine instead?

28
00:02:28,500 --> 00:02:32,000
I'm surprised you were able to break
through the barrier laid by the Senate.

29
00:02:32,000 --> 00:02:35,000
As expected of the Rebels' commander.

30
00:02:35,000 --> 00:02:38,500
"Rebel" you say...?
I've grown tired of hearing that name.

31
00:02:38,500 --> 00:02:40,500
If you're tired of it,
then what shall I call you?

32
00:02:40,500 --> 00:02:43,000
We are called "Sensus!"

33
00:02:43,000 --> 00:02:47,000
With this Holy Sword Durandal,
I shall carve this name upon your body

34
00:02:47,000 --> 00:02:48,699
So you may never forget!

35
00:02:48,699 --> 00:02:53,000
Interesting!
I'd like to see you try!

36
00:03:18,199 --> 00:03:21,000
Not yet... I'm not dead yet...

37
00:03:21,000 --> 00:03:26,000
Without me to gather Terran souls,
Cielo will...

38
00:03:26,000 --> 00:03:30,500
Our ideal... once Cielo's united with Terro,
your worries will be meaningless.

39
00:03:30,500 --> 00:03:36,000
Rest yourself and witness this world's future!

40
00:03:36,000 --> 00:03:38,000
...So be it.

41
00:03:38,000 --> 00:03:40,300
From the day, I reincarnate once again...

42
00:03:40,300 --> 00:03:44,500
I shall judge the consequences
of your deeds myself!

43
00:03:49,500 --> 00:03:53,000
Behold! Hypnos of Ratio has fallen!

44
00:03:53,000 --> 00:03:55,199
Forward, Sensus!

45
00:03:55,199 --> 00:03:57,300
From this point on, Ciella Castello is ours!

46
00:03:57,300 --> 00:04:00,000
Our ideals will be fulfilled,

47
00:04:00,000 --> 00:04:03,000
And the Genesis will be ours!

48
00:04:05,000 --> 00:04:07,199
And soon... soon...

49
00:04:07,199 --> 00:04:11,000
The Primordial Giant's Wish...
and our goal...

50
00:04:11,000 --> 00:04:15,000
As long as I obtain it... yes...
as long as I have it...


text:
1
00:00:09,500 --> 00:00:14,000
How pitiful. It's most likely that the
Church and the Army both...

2
00:00:14,000 --> 00:00:18,000
...have lost sight of what they were
fighting for in the first place.

3
00:00:18,000 --> 00:00:23,000
Look, it's obvious that the Imperial Army
overpowers the Church by a long shot.

4
00:00:23,000 --> 00:00:25,000
They'd win in no time.

5
00:00:25,000 --> 00:00:30,000
No, look over there.
The Church has something in store.


text:
1
00:00:01,600 --> 00:00:04,500
Oi wait a min, who are you guys?

2
00:00:04,500 --> 00:00:05,500
Eh?

3
00:00:08,699 --> 00:00:11,000
Ya don't seem like state people...

4
00:00:11,000 --> 00:00:13,500
How'd y'all know 'bout this place?


text:
1
00:00:03,000 --> 00:00:08,000
文字列テスト

2
00:00:15,000 --> 00:00:18,000
改行メッセージ

3
00:00:18,000 --> 00:00:21,000
あいうえお
かきくけこさしすせそ

4
00:00:21,000 --> 00:00:25,000
たちつてとなにぬねのはひふへほまみむめも
やゆよらりるれろわをん


text:
1
00:00:00,000 --> 00:00:04,500
Bastard! For my brothers of Ratio you've killed...!

2
00:00:04,500 --> 00:00:06,000
I'll have your life!!!

3
00:00:09,500 --> 00:00:11,500
Die!  Asura!!

4
00:00:12,000 --> 00:00:13,000
What the hell is this!?

5
00:00:13,000 --> 00:00:17,299
You... You're not human! Just what...

6
00:00:17,799 --> 00:00:19,000
Ratio's menace!

7
00:00:19,000 --> 00:00:21,000
My brethrens' enemy!

8
00:00:21,000 --> 00:00:23,000
Cielo's adversary!

9
00:00:23,000 --> 00:00:25,700
Asuraaa!!

10
00:00:25,700 --> 00:00:29,000
I'll kill you!!!!

11
00:00:29,000 --> 00:00:30,500
Uwaaaaaa!!


text:
1
00:00:12,300 --> 00:00:17,500
You got guts to attack this camp alone!

2
00:00:19,700 --> 00:00:21,500
We won't let you get away with this!


text:
1
00:00:02,000 --> 00:00:03,799
Stop this at once, Orfiel!

2
00:00:03,799 --> 00:00:06,000
W-What are you trying to do!?

3
00:00:06,000 --> 00:00:10,000
Have you gone mad!?

4
00:00:10,500 --> 00:00:17,000
As long as Cielo stands, wherever
you may choose to reincarnate,

5
00:00:17,000 --> 00:00:19,200
I'll be there.

6
00:00:21,299 --> 00:00:26,799
And, just like how my sweet apprentice was murdered,

7
00:00:26,799 --> 00:00:30,299
I'll make you pay for what you've done.

8
00:00:30,299 --> 00:00:34,000
So that you may regret choosing to live again!

9
00:00:39,000 --> 00:00:41,500
Lord Himmel...

10
00:00:42,000 --> 00:00:43,500
Lord Himmel!


text:
1
00:00:05,300 --> 00:00:07,000
Look, Asura.

2
00:00:07,000 --> 00:00:12,000
From here, you can see the green meadows on Terro...

3
00:00:12,000 --> 00:00:15,000
Look! There's even a small town...

4
00:00:17,600 --> 00:00:19,000
Inanna.

5
00:00:20,000 --> 00:00:22,200
You also know.

6
00:00:22,200 --> 00:00:28,000
Terro was once one with Cielo.

7
00:00:28,000 --> 00:00:37,000
That is why I must unite them once again.


text:
1
00:00:00,000 --> 00:00:02,000
My name is Gardle

2
00:00:02,799 --> 00:00:04,900
Protector of Terro.

3
00:00:05,300 --> 00:00:08,500
This guy's not ordinary...

4
00:00:09,300 --> 00:00:11,130
"For the sake of Terro"...

5
00:00:11,830 --> 00:00:13,199
What do you mean?

6
00:00:13,699 --> 00:00:15,860
Reincarnator scum...

7
00:00:16,400 --> 00:00:19,000
Ask yourselves...

8
00:00:19,000 --> 00:00:22,000
Enemies of Terro...

9
00:00:22,000 --> 00:00:23,000
Here he comes!


text:
FROM mhart/alpine-node:8.6.0 as BUILDER

WORKDIR /app
ADD package.json /app/
RUN npm install

ADD ./src /app/src
ADD ./public /app/public
RUN npm run build

FROM mhart/alpine-node:8.6.0
RUN npm install -g http-server
COPY --from=BUILDER /app/build /app/build

WORKDIR /app/build
EXPOSE 3000
CMD hs -p 3000

repo_name: vitalcode/auth-graphiql

repo_language: JavaScript

mime_type: text/html


text:
Wraps GraphiQL providing support for bearer token and arbitrary URL.
Great for testing GraphQL services secured with OAuth2.0

## Running with Docker

`docker run -it -p3000:3000 vitalcode/auth-graphiql`

The service will now be listening on `http://localhost:3000`. To run on a
different port change the first 3000 in the command, for example
`docker run -it -p3001:3000 vitalcode/auth-graphiql` will listen on port
3001 instead.

## Running locally

### Install dependencies

`npm install`

### Run

`npm start` or `env PORT=3001 npm start` to run on a different port.

### Enjoy

Open `http://localhost:3000`, then provide a URL and token. Information
from the endpoint will be fetched whenever you update the URL.


text:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

it('renders without crashing', () => {
  const div = document.createElement('div');
  ReactDOM.render(<App />, div);
});


text:
import React, {Component} from "react";
import GraphiQL from "graphiql";
import "./App.css";
import "../node_modules/graphiql/graphiql.css";

class App extends Component {
  constructor(props) {
    super(props);

    var search = window.location.search;
    var parameters = {};
    search.substr(1).split('&').forEach(function (entry) {
      var eq = entry.indexOf('=');
      if (eq >= 0) {
        parameters[decodeURIComponent(entry.slice(0, eq))] = decodeURIComponent(entry.slice(eq + 1));
      }
    });

    if (parameters.variables) {
      try {
        parameters.variables = JSON.stringify(JSON.parse(parameters.variables), null, 2);
      } catch (e) {}
    }

    this.state = {
      url: parameters.url,
      token: parameters.token,
      query: parameters.query,
      variables: parameters.variables
    };
    this.updateURL = this.updateURL.bind(this)
  }

  onEditQuery(newQuery) {
    this.setState({query: newQuery}, this.updateURL);
  }

  onEditVariables(newVariables) {
    this.setState({variables: newVariables}, this.updateURL);
  }

  onUrlChange(e) {
    this.setState({url: e.target.value}, this.updateURL);
  }

  onTokenChange(e) {
    this.setState({token: e.target.value}, this.updateURL);
  }

  updateURL() {
    var newSearch = '?' + Object.keys(this.state)
        .map((key) => encodeURIComponent(key) + '=' + encodeURIComponent(this.state[key]))
        .join('&');
    window.history.replaceState(null, null, newSearch);
  }

  fetcherFactory(url, token) {
    if (this.oldFactory && url === this.oldUrl && token === this.oldToken) {
         return this.oldFactory
    }
    this.oldUrl = url
    this.oldToken = token
    let headers = {
          'Accept': 'application/json',
          'Content-Type': 'application/json',
    }
    if (token && token !== 'undefined') {
         headers['Authorization'] = `Bearer ${token}`
    }
    this.oldFactory = function graphQLFetcher(graphQLParams) {
      return fetch(url + '/graphql', {
        method: 'post',
        headers,
        body: JSON.stringify(graphQLParams),
      }).then(response => response.json());
    }
    return this.oldFactory
  }

  render() {
    return (

          url
            <input type="text" id="url-input" onChange={this.onUrlChange.bind(this)} value={this.state.url}/>

          token
            <input type="text" id="token-input" onChange={this.onTokenChange.bind(this)} value={this.state.token}/>

          <GraphiQL fetcher={this.fetcherFactory(this.state.url, this.state.token)}
                    query={this.state.query}
                    variables={this.state.variables}
                    onEditQuery={this.onEditQuery.bind(this)}
                    onEditVariables={this.onEditVariables.bind(this)}/>

    );
  }
}

export default App;


text:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './index.css';

ReactDOM.render(
  <App />,
  document.getElementById('root')
);


text:
.app {
  height: 100vh;
  display: flex;
  flex-direction: column;
}

.header {
  font-size: 14px;
  background-color: #2e6ba6;;
  padding: 5px;
  color: white;
}

.body {
  height: 100%;
  flex: 1;
}

label {
  padding-right: 5px;
}

input {
  width: 300px;
  margin: 0 5px 0 5px;
}


text:
body {
  margin: 0;
  padding: 0;
  font-family: sans-serif;
}


text:
<!doctype html>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">

    <title>React App</title>


text:
[package]
name = "bulkssrf"
version = "0.1.0"
authors = ["Junn <likesgophers@gmail.com>"]
edition = "2018"

[[bin]]
bench = false
path = "src/bin/main.rs"
name = "bssrf"

[dependencies]
clap = "2.33.1"
futures = "0.3.5"
http-types = "2.2.1"
reqwest = { version = "0.10", features = ["json"] }
tokio = { version = "0.2", features = ["full"] }

[target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies]
openssl = { version = "0.10", features = ["vendored"] }

repo_name: junnlikestea/bulkssrf

repo_language: Rust

mime_type: text/x-c


text:
# BulkSSRF (bssrf)
![release](https://github.com/junnlikestea/bulkssrf/workflows/release/badge.svg)
[![Build status](https://github.com/junnlikestea/bulkssrf/workflows/Continuous%20integration/badge.svg)](https://github.com/junnlikestea/bulkssrf/actions)

This is a Rust port of [m4ll0k](https://twitter.com/m4ll0k2) `ssrf.py` script.

### Installation
Precompiled binaries for bssrf are available in the [releases](https://github.com/junnlikestea/bulkssrf/releases) tab. Just pick your platform and extract the archive that contains the binary.

## Building it yourself
If you want to build it yourself you will need to install Rust, you can get the [official installation](https://www.rust-lang.org/tools/install) from the Rust website.

To build BulkSSRF:
```
$ git clone https://github.com/junnlikestea/bulkssrf
$ cd bulkssrf
$ cargo build --release
$ ./target/release/bssrf --version
```

### Usage
With a list of urls in a file:
```
bssrf -f path/to/file.txt -l xyzburpcollaborator.com
```

Using a tool like `gau` to feed bssrf urls:
```
gau hackerone.com | bssrf -l xyzburpcollaborator.com
```

By default the program has timeout set to 5 seconds, but you can alter that with the `-t` flag, you can also add various debugging
info with the `-v` for verbose flag:
```
gau hackerone.com | bssrf -v -t 10 -l xyzburpcollaborator.com
```

If you want to feed the traffic through an http proxy you can use the `-p` or `-replay-proxy` flag.
```
gau hackerone.com | bssrf -l xyzburpcollaborator.com -p http://127.0.0.1:8080
```

### Common error
BulkSSRF uses async concurrent http requests under the hood. If you encounter an error similar to "Too many open files" it means that there isn't enough available file descriptors on your system. You can fix this by increasing the limits available. There are lots of different guides available to increase the limits, [but here is one for linux](https://www.tecmint.com/increase-set-open-file-limits-in-linux/). I actually ran into this problem while writing the code.

### Disclaimer
Developers have/has no responsibility or authority over any kind of:
* Legal or Law infringement by third parties and users.
* Malicious use capable of causing damage to third parties.
* Illegal or unlawful use of bulkssrf.

Thanks to [0xatul](https://twitter.com/atul_hax) for the feedback!


text:
# THIS FILE WAS TAKEN FROM  - https://github.com/BurntSushi/ripgrep
# All credit goes to BurntSushi!

name: release
on:
  push:
    # Enable when testing release infrastructure on a branch.
    # branches:
    # - ag/release
    tags:
    - '[0-9]+.[0-9]+.[0-9]+'
jobs:
  create-release:
    name: create-release
    runs-on: ubuntu-latest
    # env:
      # Set to force version number, e.g., when no tag exists.
      # BULKSSRF_VERSION: TEST-0.0.0
    steps:
      - name: Create artifacts directory
        run: mkdir artifacts

      - name: Get the release version from the tag
        if: env.BULKSSRF_VERSION == ''
        run: |
          # Apparently, this is the right way to get a tag name. Really?
          #
          # See: https://github.community/t5/GitHub-Actions/How-to-get-just-the-tag-name/m-p/32167/highlight/true#M1027
          echo "::set-env name=BULKSSRF_VERSION::${GITHUB_REF#refs/tags/}"
          echo "version is: ${{ env.BULKSSRF_VERSION }}"

      - name: Create GitHub release
        id: release
        uses: actions/create-release@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: ${{ env.BULKSSRF_VERSION }}
          release_name: ${{ env.BULKSSRF_VERSION }}

      - name: Save release upload URL to artifact
        run: echo "${{ steps.release.outputs.upload_url }}" > artifacts/release-upload-url

      - name: Save version number to artifact
        run: echo "${{ env.BULKSSRF_VERSION }}" > artifacts/release-version

      - name: Upload artifacts
        uses: actions/upload-artifact@v1
        with:
          name: artifacts
          path: artifacts

  build-release:
    name: build-release
    needs: ['create-release']
    runs-on: ${{ matrix.os }}
    env:
      # For some builds, we use cross to test on 32-bit and big-endian
      # systems.
      CARGO: cargo
      # When CARGO is set to CROSS, this is set to `--target matrix.target`.
      TARGET_FLAGS:
      # When CARGO is set to CROSS, TARGET_DIR includes matrix.target.
      TARGET_DIR: ./target
      # Emit backtraces on panics.
      RUST_BACKTRACE: 1
      # Build static releases with PCRE2.
      PCRE2_SYS_STATIC: 1
    strategy:
      matrix:
        build: [linux, macos, win-msvc, win-gnu, win32-msvc]
        include:
        - build: linux
          os: ubuntu-18.04
          rust: stable
          target: x86_64-unknown-linux-musl
        - build: macos
          os: macos-latest
          rust: stable
          target: x86_64-apple-darwin
        - build: win-msvc
          os: windows-2019
          rust: stable
          target: x86_64-pc-windows-msvc
        - build: win-gnu
          os: windows-2019
          rust: stable-x86_64-gnu
          target: x86_64-pc-windows-gnu
        - build: win32-msvc
          os: windows-2019
          rust: stable
          target: i686-pc-windows-msvc

    steps:
    - name: Checkout repository
      uses: actions/checkout@v2
      with:
        fetch-depth: 1

    - name: Install Rust
      uses: actions-rs/toolchain@v1
      with:
        toolchain: ${{ matrix.rust }}
        profile: minimal
        override: true
        target: ${{ matrix.target }}

    - name: Use Cross
      # if: matrix.os != 'windows-2019'
      run: |
        # FIXME: to work around bugs in latest cross release, install master.
        # See: https://github.com/rust-embedded/cross/issues/357
        cargo install --git https://github.com/rust-embedded/cross
        echo "::set-env name=CARGO::cross"
        echo "::set-env name=TARGET_FLAGS::--target ${{ matrix.target }}"
        echo "::set-env name=TARGET_DIR::./target/${{ matrix.target }}"

    - name: Show command used for Cargo
      run: |
        echo "cargo command is: ${{ env.CARGO }}"
        echo "target flag is: ${{ env.TARGET_FLAGS }}"
        echo "target dir is: ${{ env.TARGET_DIR }}"

    - name: Get release download URL
      uses: actions/download-artifact@v1
      with:
        name: artifacts
        path: artifacts

    - name: Set release upload URL and release version
      shell: bash
      run: |
        release_upload_url="$(cat artifacts/release-upload-url)"
        echo "::set-env name=RELEASE_UPLOAD_URL::$release_upload_url"
        echo "release upload url: $RELEASE_UPLOAD_URL"
        release_version="$(cat artifacts/release-version)"
        echo "::set-env name=RELEASE_VERSION::$release_version"
        echo "release version: $RELEASE_VERSION"

    - name: Build release binary
      run: ${{ env.CARGO }} build --verbose --release ${{ env.TARGET_FLAGS }}

    - name: Strip release binary (linux and macos)
      if: matrix.build == 'linux' || matrix.build == 'macos'
      run: strip "target/${{ matrix.target }}/release/bssrf"

    - name: Build archive
      shell: bash
      run: |
        staging="bulkssrf-${{ env.RELEASE_VERSION }}-${{ matrix.target }}"
        mkdir -p "$staging"/

        if [ "${{ matrix.os }}" = "windows-2019" ]; then
          cp "target/${{ matrix.target }}/release/bssrf.exe" "$staging/"
          7z a "$staging.zip" "$staging"
          echo "::set-env name=ASSET::$staging.zip"
        else
          cp "target/${{ matrix.target }}/release/bssrf" "$staging/"
          tar czf "$staging.tar.gz" "$staging"
          echo "::set-env name=ASSET::$staging.tar.gz"
        fi

    - name: Upload release archive
      uses: actions/upload-release-asset@v1.0.1
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        upload_url: ${{ env.RELEASE_UPLOAD_URL }}
        asset_path: ${{ env.ASSET }}
        asset_name: ${{ env.ASSET }}
        asset_content_type: application/octet-stream


text:
on: [push, pull_request]

name: Continuous integration

jobs:
  linux:
    name: Linux
    runs-on: ubuntu-latest
    strategy:
      matrix:
        toolchain:
          - stable
        arch:
          - i686-unknown-linux-gnu
          - x86_64-unknown-linux-gnu
    steps:
      - uses: actions/checkout@master
      - uses: actions-rs/toolchain@v1
        with:
          toolchain: ${{ matrix.toolchain }}
          target: ${{ matrix.arch }}
          override: true

      - name: Build
        uses: actions-rs/cargo@v1
        with:
          use-cross: true
          command: build
          args: --target ${{ matrix.arch }}

      - name: Run tests
        uses: actions-rs/cargo@v1
        with:
          use-cross: true
          command: test
          args: --target ${{ matrix.arch }}

  windows:
    name: Windows
    runs-on: windows-latest
    strategy:
      matrix:
        toolchain:
          - stable
        arch:
          - i686-pc-windows-msvc
          - x86_64-pc-windows-msvc
    steps:
      - uses: actions/checkout@master
      - uses: actions-rs/toolchain@v1
        with:
          toolchain: ${{ matrix.toolchain }}
          target: ${{ matrix.arch }}
          override: true

      - name: Build
        uses: actions-rs/cargo@v1
        with:
          use-cross: true
          command: build
          args: --target ${{ matrix.arch }}

      - name: Run tests
        uses: actions-rs/cargo@v1
        with:
          use-cross: true
          command: test
          args: --target ${{ matrix.arch }}

  macos:
    name: macOS
    runs-on: macOS-latest
    strategy:
      matrix:
        toolchain:
          - stable
        arch:
          - x86_64-apple-darwin
    steps:
      - uses: actions/checkout@master
      - uses: actions-rs/toolchain@v1
        with:
          toolchain: ${{ matrix.toolchain }}
          target: ${{ matrix.arch }}
          override: true

      - name: Build
        uses: actions-rs/cargo@v1
        with:
          use-cross: true
          command: build
          args: --target ${{ matrix.arch }}

      - name: Run tests
        uses: actions-rs/cargo@v1
        with:
          use-cross: true
          command: test
          args: --target ${{ matrix.arch }}


text:
use futures::stream::StreamExt;
use std::sync::Arc;
use std::time::Duration;

pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync + 'static>>;

const INJECTABLE_HEADERS: &[&str] = &[
    "Proxy-Host",
    "Request-Uri",
    "X-Forwarded",
    "X-Forwarded-By",
    "X-Forwarded-For",
    "X-Forwarded-For-Original",
    "X-Forwarded-Host",
    "X-Forwarded-Server",
    "X-Forwarder-For",
    "X-Forward-For",
    "Base-Url",
    "Http-Url",
    "Proxy-Url",
    "Redirect",
    "Real-Ip",
    "Referer",
    "Referrer",
    "Refferer",
    "Uri",
    "Url",
    "X-Host",
    "X-Http-Destinationurl",
    "X-Http-Host-Override",
    "X-Original-Remote-Addr",
    "X-Original-Url",
    "X-Proxy-Url",
    "X-Rewrite-Url",
    "X-Real-Ip",
    "X-Remote-Addr",
];

pub async fn run(
    urls: Vec<String>,
    location: String,
    timeout: u64,
    verbose: bool,
    proxy: Option<String>,
) {
    const ACTIVE_REQUESTS: usize = 100;
    let shared_location = Arc::new(location);

    let responses = futures::stream::iter(urls.into_iter().map(|url| {
        let url = Arc::new(url);
        let loc = shared_location.clone();
        let proxy = proxy.clone();
        tokio::spawn(async move { inject_headers(url, loc, timeout, verbose, proxy).await })
    }))
    .buffer_unordered(ACTIVE_REQUESTS)
    .collect::<Vec<_>>();

    responses.await;
}

fn client_builder(proxy: Option<String>) -> Result<reqwest::Client> {
    if let Some(p) = proxy {
        Ok(reqwest::Client::builder()
            .proxy(reqwest::Proxy::http(p.as_str())?)
            .build()?)
    } else {
        Ok(reqwest::Client::builder().build()?)
    }
}

async fn fetch(
    url: Arc<String>,
    header: &str,
    location: Arc<String>,
    timeout: u64,
    verbose: bool,
    proxy: Option<String>,
) -> Result<()> {
    let time = Duration::from_secs(timeout);
    let client = client_builder(proxy)?;
    let resp = client
        .get(url.as_str())
        .timeout(time)
        .header(header, location.as_str())
        .send()
        .await;

    match resp {
        Ok(r) => println!("[{}] -> {}", r.status().as_str(), url),

        Err(e) => {
            if verbose {
                eprintln!("Requested: {} but was unreachable, with error: {}.", url, e)
            }
        }
    }
    Ok(())
}

async fn inject_headers(
    url: Arc<String>,
    location: Arc<String>,
    timeout: u64,
    verbose: bool,
    proxy: Option<String>,
) -> Result<()> {
    let mut tasks = Vec::new();

    for header in INJECTABLE_HEADERS.iter() {
        let url = url.clone();
        let location = location.clone();
        let proxy = proxy.clone();
        tasks.push(tokio::spawn(async move {
            if verbose {
                println!("Injecting:{} into {} -> {}", location, header, url);
            }

            fetch(url, header, location, timeout, verbose, proxy).await
        }))
    }

    for t in tasks {
        t.await?;
    }

    Ok(())
}


text:
extern crate bulkssrf;
use bulkssrf::run;
use bulkssrf::Result;
use clap::{App, Arg};
use std::fs;
use std::io::{self, Read};

#[tokio::main]
async fn main() -> Result<()> {
    let args = create_clap_app("0.1.2");
    let matches = args.get_matches();
    let mut verbose = false;
    let mut proxy = None;
    let mut urls: Vec<String> = Vec::new();
    let location = matches.value_of("location").unwrap();
    let timeout: u64 = matches.value_of("timeout").unwrap().parse()?;

    if matches.is_present("verbose") {
        verbose = true;
    }

    if matches.is_present("file") {
        let input = matches.value_of("input").unwrap();
        let contents = fs::read_to_string(input)?;
        urls = contents.lines().map(|l| l.to_string()).collect();
    } else {
        urls = read_stdin()?;
    }

    if matches.is_present("replay-proxy") {
        proxy = Some(matches.value_of("replay-proxy").unwrap().to_string());
    }

    run(urls, location.into(), timeout, verbose, proxy).await;
    Ok(())
}

fn create_clap_app(version: &str) -> clap::App {
    App::new("bssrf")
        .version(version)
        .about(
            "Test for SSRF by injecting a location into headers.
            This is a Rust port of m4ll0k's python script.",
        )
        .usage("bssrf -f <urls.txt> -l <location.com>")
        .arg(Arg::with_name("input").index(1).required(false))
        .arg(
            Arg::with_name("file")
                .help("bssrf -f <urls.txt> -l <location.com>")
                .short("f")
                .long("file"),
        )
        .arg(
            Arg::with_name("location")
                .help(
                    "The server address you want the ssrf to hit, i.e.
                    xyz.burpcollaborator.com",
                )
                .takes_value(true)
                .required(true)
                .short("l")
                .long("location"),
        )
        .arg(
            Arg::with_name("timeout")
                .help("the connection timeout i.e. the time to wait for a response body.")
                .short("t")
                .long("timeout")
                .default_value("4")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("verbose")
                .help("Adds a bunch of debugging messages to the output")
                .short("v")
                .long("verbose"),
        )
        .arg(
            Arg::with_name("replay-proxy")
                .help("feed the output of bssrf into an http proxy")
                .short("p")
                .long("replay-proxy")
                .takes_value(true),
        )
}

fn read_stdin() -> Result<Vec<String>> {
    let mut buffer = String::new();
    io::stdin().read_to_string(&mut buffer)?;
    Ok(buffer.split_whitespace().map(|l| l.to_string()).collect())
}


text:
#Elliptic curve basics, tools for finding rational points, and ECDSA implementation.
#Brendan Cordy, 2015

from fractions import Fraction
from math import ceil, sqrt
from random import SystemRandom, randrange
from hashlib import sha256
from time import time

#Useful constant. The order of the subgroup defined in the secp256k1 standard.

secp256k1_order = 115792089237316195423570985008687907852837564279074904382605163141518161494337

#Affine Point (+Infinity) on an Elliptic Curve ---------------------------------------------------

class Point(object):
    #Construct a point with two given coordindates.
    def __init__(self, x, y):
        self.x, self.y = x, y
        self.inf = False

    #Construct the point at infinity.
    @classmethod
    def atInfinity(cls):
        P = cls(0, 0)
        P.inf = True
        return P

    #The secp256k1 generator.
    @classmethod
    def secp256k1(cls):
        return cls(55066263022277343669578718895168534326250603453777594175500187360389116729240,
                   32670510020758816978083085130507043184471273380659243275938904335757337482424)

    def __str__(self):
        if self.inf:
            return 'Inf'
        else:
            return '(' + str(self.x) + ',' + str(self.y) + ')'

    def __eq__(self,other):
        if self.inf:
            return other.inf
        elif other.inf:
            return self.inf
        else:
            return self.x == other.x and self.y == other.y

    def is_infinite(self):
        return self.inf

#Elliptic Curves over any Field ------------------------------------------------------------------

class Curve(object):
    #Set attributes of a general Weierstrass cubic y^2 = x^3 + ax^2 + bx + c over any field.
    def __init__(self, a, b, c, char, exp):
        self.a, self.b, self.c = a, b, c
        self.char, self.exp = char, exp
        print(self)

    def __str__(self):
        #Cases for 0, 1, -1, and general coefficients in the x^2 term.
        if self.a == 0:
            aTerm = ''
        elif self.a == 1:
            aTerm = ' + x^2'
        elif self.a == -1:
            aTerm = ' - x^2'
        elif self.a < 0:
            aTerm = " - " + str(-self.a) + 'x^2'
        else:
            aTerm = " + " + str(self.a) + 'x^2'
        #Cases for 0, 1, -1, and general coefficients in the x term.
        if self.b == 0:
            bTerm = ''
        elif self.b == 1:
            bTerm = ' + x'
        elif self.b == -1:
            bTerm = ' - x'
        elif self.b < 0:
            bTerm = " - " + str(-self.b) + 'x'
        else:
            bTerm = " + " + str(self.b) + 'x'
        #Cases for 0, 1, -1, and general coefficients in the constant term.
        if self.c == 0:
            cTerm = ''
        elif self.c < 0:
            cTerm = " - " + str(-self.c)
        else:
            cTerm = " + " + str(self.c)
        #Write out the nicely formatted Weierstrass equation.
        self.eq = 'y^2 = x^3' + aTerm + bTerm + cTerm
        #Print prettily.
        if self.char == 0:
            return self.eq + ' over Q'
        elif self.exp == 1:
            return self.eq + ' over ' + 'F_' + str(self.char)
        else:
            return self.eq + ' over ' + 'F_' + str(self.char) + '^' + str(self.exp)

    #Compute the discriminant.
    def discriminant(self):
        a, b, c = self.a, self.b, self.c
        return -4*a*a*a*c + a*a*b*b + 18*a*b*c - 4*b*b*b - 27*c*c

    #Compute the order of a point on the curve.
    def order(self, P):
        Q = P
        orderP = 1
        #Add P to Q repeatedly until obtaining the identity (point at infinity).
        while not Q.is_infinite():
            Q = self.add(P,Q)
            orderP += 1
        return orderP

    #List all multiples of a point on the curve.
    def generate(self, P):
        Q = P
        orbit = [str(Point.atInfinity())]
        #Repeatedly add P to Q, appending each (pretty printed) result.
        while not Q.is_infinite():
            orbit.append(str(Q))
            Q = self.add(P,Q)
        return orbit

    #Double a point on the curve.
    def double(self, P):
        return self.add(P,P)

    #Add P to itself k times.
    def mult(self, P, k):
        if P.is_infinite():
            return P
        elif k == 0:
            return Point.atInfinity()
        elif k < 0:
            return self.mult(self.invert(P), -k)
        else:
            #Convert k to a bitstring and use peasant multiplication to compute the product quickly.
            b = bin(k)[2:]
            return self.repeat_additions(P, b, 1)

    #Add efficiently by repeatedly doubling the given point, and adding the result to a running
    #total when, after the ith doubling, the ith digit in the bitstring b is a one.
    def repeat_additions(self, P, b, n):
        if b == '0':
            return Point.atInfinity()
        elif b == '1':
            return P
        elif b[-1] == '0':
            return self.repeat_additions(self.double(P), b[:-1], n+1)
        elif b[-1] == '1':
            return self.add(P, self.repeat_additions(self.double(P), b[:-1], n+1))

    #Returns a pretty printed list of points.
    def show_points(self):
        return [str(P) for P in self.get_points()]

#Elliptic Curves over Q --------------------------------------------------------------------------

class CurveOverQ(Curve):
    #Construct a Weierstrass cubic y^2 = x^3 + ax^2 + bx + c over Q.
    def __init__(self, a, b, c):
        Curve.__init__(self, a, b, c, 0, 0)

    def contains(self, P):
        if P.is_infinite():
            return True
        else:
            return P.y*P.y == P.x*P.x*P.x + self.a*P.x*P.x + self.b*P.x + self.c

    def get_points(self):
        #Start with the point at infinity.
        points = [Point.atInfinity()]
        #The only possible y values are divisors of the discriminant.
        for y in divisors(self.discriminant()):
            #Each possible y value yields a monic cubic polynomial in x, whose roots
            #must divide the constant term.
            const_term = self.c - y*y
            if const_term != 0:
                for x in divisors(const_term):
                    P = Point(x,y)
                    if 0 == x*x*x + self.a*x*x + self.b*x + const_term and self.has_finite_order(P):
                        points.append(P)
            #If the constant term is zero, factor out x and look for rational roots
            #of the resulting quadratic polynomial. Any such roots must divide b.
            elif self.b != 0:
                for x in divisors(self.b):
                    P = Point(x,y)
                    if 0 == x*x*x+self.a*x*x+self.b*x+const_term and self.has_finite_order(P):
                        points.append(P)
            #If the constant term and b are both zero, factor out x^2 and look for rational
            #roots of the resulting linear polynomial. Any such roots must divide a.
            elif self.a != 0:
                 for x in divisors(self.a):
                    P = Point(x,y)
                    if 0 == x*x*x+self.a*x*x+self.b*x+const_term and self.has_finite_order(P):
                        points.append(P)
            #If the constant term, b, and a are all zero, we have 0 = x^3 + c - y^2 with
            #const_term = c - y^2 = 0, so (0,y) is a point on the curve.
            else:
                points.append(Point(0,y))
        #Ensure that there are no duplicates in our list of points.
        unique_points = []
        for P in points:
            addP = True
            for Q in unique_points:
                if P == Q:
                    addP = False
            if addP:
                unique_points.append(P)
        return unique_points

    def invert(self, P):
        if P.is_infinite():
            return P
        else:
            return Point(P.x, -P.y)

    def add(self, P_1, P_2):
        #Compute the differences in the coordinates.
        y_diff = P_2.y - P_1.y
        x_diff = P_2.x - P_1.x
        #Cases involving the point at infinity.
        if P_1.is_infinite():
            return P_2
        elif P_2.is_infinite():
            return P_1
        #Case for adding an affine point to its inverse.
        elif x_diff == 0 and y_diff != 0:
            return Point.atInfinity()
        #Case for adding an affine point to itself.
        elif x_diff == 0 and y_diff == 0:
            #If the point is on the x-axis, there's a vertical tangent there (assuming
            #the curve in nonsingular) so we obtain the point at infinity.
            if P_1.y == 0:
                return Point.atInfinity()
            #Otherwise the result is an affine point on the curve we can arrive at by
            #following the tangent line, whose slope is given below.
            else:
                ld = Fraction(3*P_1.x*P_1.x + 2*self.a*P_1.x + self.b, 2*P_1.y)
        #Case for adding two distinct affine points, where we compute the slope of
        #the secant line through the two points.
        else:
            ld = Fraction(y_diff, x_diff)
        #Use the slope of the tangent line or secant line to compute the result.
        nu = P_1.y - ld*P_1.x
        x = ld*ld - self.a  -P_1.x - P_2.x
        y = -ld*x - nu
        return Point(x,y)

    #Use the Nagell-Lutz Theorem and Mazur's Theorem to potentially save time.
    def order(self, P):
        Q = P
        orderP = 1
        #Add P to Q repeatedly until obtaining the point at infinity.
        while not Q.is_infinite():
            Q = self.add(P,Q)
            orderP += 1
            #If we ever obtain non integer coordinates, the point has infinite order.
            if Q.x != int(Q.x) or Q.y != int(Q.y):
                return -1
            #Moreover, all finite order points have order at most 12.
            if orderP > 12:
                return -1
        return orderP

    def has_finite_order(self, P):
            return not self.order(P) == -1

    def torsion_group(self):
        highest_order = 1
        #Find the rational point with the highest order.
        for P in self.get_points():
            if self.order(P) > highest_order:
                highest_order = self.order(P)
        #If this point generates the entire torsion group, the torsion group is cyclic.
        if highest_order == len(self.get_points()):
            print('Z/' + str(highest_order) + 'Z')
        #If not, by Mazur's Theorem the torsion group must be a direct product of Z/2Z
        #with the cyclic group generated by the highest order point.
        else:
            print('Z/2Z x ' + 'Z/' + str(highest_order) + 'Z')
        print(C.show_points())

#Elliptic Curves over Prime Order Fields ---------------------------------------------------------

class CurveOverFp(Curve):
    #Construct a Weierstrass cubic y^2 = x^3 + ax^2 + bx + c over Fp.
    def __init__(self, a, b, c, p):
        Curve.__init__(self, a, b, c, p, 1)

    #The secp256k1 curve.
    @classmethod
    def secp256k1(cls):
        return cls(0, 0, 7, 2**256-2**32-2**9-2**8-2**7-2**6-2**4-1)

    def contains(self, P):
        if P.is_infinite():
            return True
        else:
            return (P.y*P.y) % self.char == (P.x*P.x*P.x + self.a*P.x*P.x + self.b*P.x + self.c) % self.char

    def get_points(self):
        #Start with the point at infinity.
        points = [Point.atInfinity()]

        #Just brute force the rest.
        for x in range(self.char):
                for y in range(self.char):
                    P = Point(x,y)
                    if (y*y) % self.char == (x*x*x + self.a*x*x + self.b*x + self.c) % self.char:
                        points.append(P)
        return points

    def invert(self, P):
        if P.is_infinite():
            return P
        else:
            return Point(P.x, -P.y % self.char)

    def add(self, P_1, P_2):
        #Adding points over Fp and can be done in exactly the same way as adding over Q,
        #but with of the all arithmetic now happening in Fp.
        y_diff = (P_2.y - P_1.y) % self.char
        x_diff = (P_2.x - P_1.x) % self.char
        if P_1.is_infinite():
            return P_2
        elif P_2.is_infinite():
            return P_1
        elif x_diff == 0 and y_diff != 0:
            return Point.atInfinity()
        elif x_diff == 0 and y_diff == 0:
            if P_1.y == 0:
                return Point.atInfinity()
            else:
                ld = ((3*P_1.x*P_1.x + 2*self.a*P_1.x + self.b) * mult_inv(2*P_1.y, self.char)) % self.char
        else:
            ld = (y_diff * mult_inv(x_diff, self.char)) % self.char
        nu = (P_1.y - ld*P_1.x) % self.char
        x = (ld*ld - self.a - P_1.x - P_2.x) % self.char
        y = (-ld*x - nu) % self.char
        return Point(x,y)

#Elliptic Curves over Prime Power Order Fields ---------------------------------------------------

class CurveOverFq(Curve):
    #Construct a Weierstrass cubic y^2 = x^3 + ax^2 + bx + c over Fp^n.
    def __init__(self, a, b, c, p, n, irred_poly):
        self.irred_poly = irred_poly
        Curve.__init__(self, a, b, c, p, n)

    #TODO: Implement it!

#Number Theoretic Functions ----------------------------------------------------------------------

def divisors(n):
    divs = [0]
    for i in range(1, abs(n) + 1):
        if n % i == 0:
            divs.append(i)
            divs.append(-i)
    return divs

#Extended Euclidean algorithm.
def euclid(sml, big):
    #When the smaller value is zero, it's done, gcd = b = 0*sml + 1*big.
    if sml == 0:
        return (big, 0, 1)
    else:
        #Repeat with sml and the remainder, big%sml.
        g, y, x = euclid(big % sml, sml)
        #Backtrack through the calculation, rewriting the gcd as we go. From the values just
        #returned above, we have gcd = y*(big%sml) + x*sml, and rewriting big%sml we obtain
        #gcd = y*(big - (big//sml)*sml) + x*sml = (x - (big//sml)*y)*sml + y*big.
        return (g, x - (big//sml)*y, y)

#Compute the multiplicative inverse mod n of a with 0 < a < n.
def mult_inv(a, n):
    g, x, y = euclid(a, n)
    #If gcd(a,n) is not one, then a has no multiplicative inverse.
    if g != 1:
        raise ValueError('multiplicative inverse does not exist')
    #If gcd(a,n) = 1, and gcd(a,n) = x*a + y*n, x is the multiplicative inverse of a.
    else:
        return x % n

#ECDSA functions ---------------------------------------------------------------------------------

#Use sha256 to hash a message, and return the hash value as an integer.
def hash(message):
    return int(sha256(message.encode('utf-8')).hexdigest(), 16)

#Hash the message and return integer whose binary representation is the the L leftmost bits
#of the hash value, where L is the bit length of n.
def hash_and_truncate(message, n):
    h = hash(message)
    b = bin(h)[2:len(bin(n))]
    return int(b, 2)

#Generate a keypair using the point P of order n on the given curve. The private key is a
#positive integer d smaller than n, and the public key is Q = dP.
def generate_keypair(curve, P, n):
    sysrand = SystemRandom()
    d = sysrand.randrange(1, n)
    Q = curve.mult(P, d)
    print("Priv key: d = " + str(d))
    print("Publ key: Q = " + str(Q))
    return (d, Q)

#Create a digital signature for the string message using a given curve with a distinguished
#point P which generates a prime order subgroup of size n.
def sign(message, curve, P, n, keypair):
    #Extract the private and public keys, and compute z by hashing the message.
    d, Q = keypair
    z = hash_and_truncate(message, n)
    #Choose a randomly selected secret point kP then compute r and s.
    r, s = 0, 0
    while r == 0 or s == 0:
        sysrand = SystemRandom()
        k = sysrand.randrange(1, n)
        R = curve.mult(P, k)
        r = R.x % n
        s = (mult_inv(k, n) * (z + r*d)) % n
    print('ECDSA sig: (Q, r, s) = (' + str(Q) + ', ' + str(r) + ', ' + str(s) + ')')
    return (Q, r, s)

#Verify the string message is authentic, given an ECDSA signature generated using a curve with
#a distinguished point P that generates a prime order subgroup of size n.
def verify(message, curve, P, n, sig):
    Q, r, s = sig
    #Confirm that Q is on the curve.
    if Q.is_infinite() or not curve.contains(Q):
        return False
    #Confirm that Q has order that divides n.
    if not curve.mult(Q,n).is_infinite():
        return False
    #Confirm that r and s are at least in the acceptable range.
    if r > n or s > n:
        return False
    #Compute z in the same manner used in the signing procedure,
    #and verify the message is authentic.
    z = hash_and_truncate(message, n)
    w = mult_inv(s, n) % n
    u_1, u_2 = z * w % n, r * w % n
    C_1, C_2 = curve.mult(P, u_1), curve.mult(Q, u_2)
    C = curve.add(C_1, C_2)
    return r % n == C.x % n

#Key Cracking Functions --------------------------------------------------------------------------

#Find d for which Q = dP by simply trying all possibilities
def crack_brute_force(curve, P, n, Q):
    start_time = time()
    for d in range(n):
        if curve.mult(P,d) == Q:
            end_time = time()
            print("Priv key: d = " + str(d))
            print("Time: " + str(round(end_time - start_time, 3)) + " secs")
            break

#Find d for which Q = dP using the baby-step giant-step algortihm.
def crack_baby_giant(curve, P, n, Q):
    start_time = time()
    m = int(ceil(sqrt(n)))
    #Build a hash table with all bP with 0 < b < m using a dictionary. The dicitonary value
    #stores b so that it can be quickly recovered after a matching giant step hash.
    baby_table = {}
    for b in range(m):
        bP = curve.mult(P,b)
        baby_table[str(bP)] = b
    #Check if Q - gmP is in the hash table for all 0 < g < m. If we get such a matching hash,
    #we have Q - gmP = bP, so extract b from the dictionary, then Q = (b + gm)P.
    for g in range(m):
        R = curve.add(Q, curve.invert(curve.mult(P, g*m)))
        if str(R) in baby_table.keys():
            b = baby_table[str(R)]
            end_time = time()
            print("Priv key: d = " + str((b + g*m) % n))
            print("Time: " + str(round(end_time - start_time, 3)) + " secs")
            break

#Find d for which Q = dP using Pollard's rho algorithm. Assumes subgroup has prime order n.
def crack_rho(curve, P, n, Q, bits):
    start_time = time()
    R_list = []
    #Compute 2^bits randomly selected linear combinations of P and Q, storing them as triples
    #of the form (aP + bQ, a, b) in R_list.
    for i in range(2**bits):
        a, b = randrange(0,n), randrange(0,n)
        R_list.append((curve.add(curve.mult(P,a), curve.mult(Q,b)), a, b))
    #Compute a new random linear combination of P and Q to start the cycle-finding.
    aT, bT = randrange(0,n), randrange(0,n)
    aH, bH = aT, bT
    T = curve.add(curve.mult(P,aT), curve.mult(Q,bT))
    H = curve.add(curve.mult(P,aH), curve.mult(Q,bH))
    while True:
        #Advance the tortoise one step, by adding a point in R_list determined by the last b
        #bits in the binary explansion of the x coordinate of the current position.
        j = int(bin(T.x)[len(bin(T.x)) - bits : len(bin(T.x))], 2)
        T, aT, bT = curve.add(T, R_list[j][0]), (aT + R_list[j][1]) % n, (bT + R_list[j][2]) % n
        #Advance the hare two steps, again by adding points in R_list determined by the last
        #b bits in the binary explansion of the x coordinate of the current position.
        for i in range(2):
            j = int(bin(H.x)[len(bin(H.x)) - bits : len(bin(H.x))], 2)
            H, aH, bH = curve.add(H, R_list[j][0]), (aH + R_list[j][1]) % n, (bH + R_list[j][2]) % n
        #If the tortoise and hare arrive at the same point, a cycle has been found.
        if(T == H):
            break
    #It is possible that the tortoise and hare arrive at exactly the same linear combination.
    if bH == bT:
        end_time = time()
        print("Rho failed with identical linear combinations")
        print(str(end_time - start_time) + " secs")
    else:
        end_time = time()
        print("Priv key: d = " + str((aT - aH) * mult_inv((bH - bT) % n, n) % n))
        print("Time: " + str(round(end_time - start_time, 3)) + " secs")

#Find d from two messages signed with the same nonce k. Assumes subgroup has prime order n.
def crack_from_ECDSA_repeat_k(curve, P, n, m1, sig1, m2, sig2):
    Q1, r1, s1 = sig1
    Q2, r2, s2 = sig2
    #Check that the two messages were signed with the same k. This check may pass even if m1
    #and m2 were signed with distinct k, in which case the value of d computed below will be
    #wrong, but this is a very unlikely scenario.
    if not r1 == r2:
        print("Messages signed with distinct k")
    else:
        z1 = hash_and_truncate(m1, n)
        z2 = hash_and_truncate(m2, n)
        k = (z1 - z2) * mult_inv((s1 - s2) % n, n) % n
        d = mult_inv(r1, n) * ((s1 * k) % n - z1) % n
        print("Priv key: d = " + str(d))


text:
mini_ecdsa
===============

Arithmetic on elliptic curves and introduction to ECDSA in Python.

*Disclaimer*: This module is a tool for learning about elliptic curves and elliptic curve cryptography. It provides a fully functional implementation of ECDSA, but don't use it as anything other than a sandbox. There are many [subtle and important implementation details](http://safecurves.cr.yp.to/index.html) that I haven't thought about.

You can find a really nice introduction to elliptic curve cryptography on [Andrea Corbellini's blog](http://andrea.corbellini.name/2015/05/17/elliptic-curve-cryptography-a-gentle-introduction/).

To use this module as is, instead of importing it for use in another project, let's open a Python console in the directory where it's stored and run the command below.

```
>>> from mini_ecdsa import *
```

To begin, we need to define a nonsingular elliptic curve over a field of prime characteristic, or over the rationals. `CurveOverFp(a,b,c,p)` will define an elliptic curve from the equation y^2 = x^3 + ax^2 + bx + c over F_p. `CurveOverQ(a,b,c)` will define a curve using the same equation over the rationals. This module assumes the coefficients a, b, and c are integers.

```
>>> C = CurveOverFp(2, 0, 1, 7)
y^2 = x^3 + 2x^2 + 1 over F_7
```

To see a list of all finite order rational points on the curve, use `C.show_points()`. This will produce a nicely printed set of points. To return a list of point objects, use `C.get_points()`. *Warning:* These functions are very computationally intensive. Use them only on curves with small discriminant, or when working over a small finite field.

```
>>> C.show_points()
['Inf', '(0,1)', '(0,6)', '(1,2)', '(1,5)', '(3,2)', '(3,5)', '(5,1)', '(5,6)', '(6,3)', '(6,4)']
```

Calling `C.torsion_group()` will classify the group of finite order rational points on a curve defined over Q, with the help of [Mazur's theorem](https://en.wikipedia.org/wiki/Torsion_conjecture#Elliptic_curves). Let's say we want to see the group of torsion points on the curve y^2 = x^3 + x + 2.

```
>>> C = CurveOverQ(0, 1, 2)
y^2 = x^3 + x + 2 over Q
>>> C.torsion_group()
Z/4Z
['Inf', '(-1,0)', '(1,2)', '(1,-2)']
```

In this case, the torsion points form a cyclic group of order four. You can also mess around with arithmetic on the curve by adding points, multiplying them by integers (remember multiplication in this context means repeated addition), and looking at the subgroup generated by a given point.

```
>>> C = CurveOverQ(0, 1, 2)
y^2 = x^3 + x + 2 over Q
>>> P = Point(-1,0)
>>> Q = Point(1,2)
>>> C.contains(P)
True
>>> C.contains(Q)
True
>>> print(C.add(P,Q))
(1,-2)
>>> print(C.mult(Q,4))
Inf
>>> C.generate(Q)
['Inf', '(-1,0)', '(1,2)', '(1,-2)']
```

ECDSA is a digital signature scheme that uses elliptic curves. It's part of SSL/TLS and so you use it every day (click the green lock next to the url in your browser). Another of its best known uses is in Bitcoin, where spending money amounts to generating a valid ECDSA signature.

To use ECDSA, we need to publicly agree on a curve over a finite field (*this module works exclusively with prime characteristic fields*) along with a distinguished point that generates a subgroup of prime order. Why the prime order requirement on the subgroup? As part of the signing process, we'll need to find a multiplicative inverse, and the prime order requirement guarantees this will work.

*Tiny Example*: Consider P = (1341,854) on the curve y^2 = x^3 + x + 1 over the field with 2833 elements.

```
>>> C = CurveOverFp(0, 1, 1, 2833)
y^2 = x^3 + x + 1 over F_2833
>>> P = Point(1341,854)
>>> C.contains(P)
True
>>> C.order(P)
131
```

Thus, P is on the curve, and it generates a subgroup of order 131, which is prime. The curve C, the point P, and the order of P are all public information.

To sign a message, create a private-public keypair by calling `generate_keypair`. This keypair will consist of a randomly generated positive integer d smaller than the order of P, and a point Q = dP. The pair is returned as a tuple and printed. Computing Q given d and P can be done very quickly (this module uses the [double and add method](https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Double-and-add)), but at the moment, no one knows any effective and generally applicable method of computing d given P and Q. This is the [one-way function](https://en.wikipedia.org/wiki/One-way_function) that provides the theoretical security in all elliptic curve based cryptography.

```
>>> key = generate_keypair(C, P, 131)
Priv key: d = 71
Publ key: Q = (1449,1186)
```

Digital signatures generated by ECDSA consist of a public key Q as well as two positive integers, r and s, which are smaller than the order of P. These values are computed using the private key d and a hash of the message. In this implementation sha256 is the hash function being used. The signature is returned as a tuple by `sign` and also printed.

```
>>> msg = 'this is an important message'
>>> sig = sign(msg, C, P, 131, key)
ECDSA sig: (Q, r, s) = ((1449,1186), 105, 8)
```

Another randomly selected positive integer smaller than the order of P is chosen as part of the signature generation process. This value, k, is a [nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce). It is crucially important to generate a new one each time a message is signed. As a consequence, if a fixed message is signed multiple times, a different signature will be produced each time. Well-known attacks on ECDSA have exploited implementations which have problems generating k.

The recipient can verify the authenticity of the message by calling `verify`, which takes a message and signature, as well as the public curve parameters, and checks that the signature is valid. Any modification of the message will, with very high probability, result in verify returning `False`.

```
>>> verify('this is an important message', C, P, 131, sig)
True
>>> verify('thiz is an important mossage', C, P, 131, sig)
False
```

*Big Example*: The curve (over a given finite field with a distinguished point) used to verify Bitcoin transactions is called secp256k1. I've been working with this curve a lot, so the classmethods `CurveOverFp.secp256k1()`, `Point.secp256k1()`, and the constant `secp256k1_order` are provided to save time, but you can also do it the hard way.

```
>>> C = CurveOverFp(0, 0, 7, 2**256-2**32-2**9-2**8-2**7-2**6-2**4-1)
y^2 = x^3 + 7 over F_115792089237316195423570985008687907853269984665640564039457584007908834671663
>>> P = Point(55066263022277343669578718895168534326250603453777594175500187360389116729240,
... 32670510020758816978083085130507043184471273380659243275938904335757337482424)
>>> n = 115792089237316195423570985008687907852837564279074904382605163141518161494337
```

There are few noteworthy things about the order of P for secp256k1. Note that it's given here, not calculated. That makes sense, since in order to find the order of a point (naively) we need to calculate iP for increasing i until we get the point at infinity. If that was actually feasible, we could crack public keys and recover d from Q in the same way, by calculating iP for increasing i until the result is Q. Calling `C.order(P)` will indeed calculate the order of P in this naive way, so don't do it unless you're prepared to wait a long time, and by long I mean universe ending long.

The order of P is also about the same as the size of the field the curve is defined over. If you've studied elliptic curves before and know about the [Hasse bound](https://en.wikipedia.org/wiki/Hasse's_theorem_on_elliptic_curves) along with a little bit of group theory, you should be able to convince yourself that the subgroup generated by P is actually the entire set of points on the curve. This is good, it means the set of private keys (possible values for d) is as large as it can be on this curve.

Generating keypairs, signing, and authenticating are all done exactly as in the tiny example.

```
>>> key = generate_keypair(C, P, n)
Priv key: 50815196737043990229212712040447958865064188767262580693117504952509239687366
Publ key: (60178516215593300273458789571475750050105656844208932820175446762050535381256,92933466624192676140900093650081093228918214155456856436706041935976250501492)
>>> msg = 'this is an important message'
>>> sig = sign(msg, C, P, n, key)
ECDSA sig: (Q, r, s) = ((60178516215593300273458789571475750050105656844208932820175446762050535381256,92933466624192676140900093650081093228918214155456856436706041935976250501492), 67756844576696401107541526159652184771668032529513958275679795576766605561987, 18515072816757752760109761447693938223957409424050002694174985246358775622034)
>>> verify('this is an important message', C, P, n, sig)
True
>>> verify('this is a faked important message', C, P, n, sig)
False
```

When you see these numbers in the wild, they are typically given as a sequence of hex bytes. In the case of Bitcoin, the private key d is stored in a Bitcoin wallet, while the public key Q goes through a [hashing procedure](https://en.bitcoin.it/wiki/Technical_background_of_version_1_Bitcoin_addresses) and gets converted to base 58. That's what a Bitcoin address is, a point on the curve y^2 = x^3 + 7 over a really big finite field after being eaten by hash functions a few times and converted to base 58. Cool.

Also included in this module are a few methods of solving Q = dP for d, i.e. cracking a public key and recovering the private key. The group defined by the curve and point given below is small enough for brute forcing to succeed, but large enough that the process takes a few minutes on a desktop computer, so it's a nice context for comparing these key cracking methods.

```
>>> C = CurveOverFp(0, 1, 7, 729787)
y^2 = x^3 + x + 7 over F_729787
>>> P = Point(1,3)
>>> n = C.order(P)
>>> (d, Q) = generate_keypair(C, P, n)
Priv key: d = 692847
Publ key: Q = (257099,102580)
```

The `crack_brute_force` function will simply try all possible values of d in increasing order until Q = dP.

```
>>> crack_brute_force(C, P, n, Q)
Priv key: d = 692847
Time: 177.963 secs
```

The baby-step giant-step method works by using a hash table to trade space for time. It's particularly easy to implement since python has hash tables nicely built in as dictionaries.

```
>>> crack_baby_giant(C, P, n, Q)
Priv key: d = 692847
Time: 0.356 secs
```

In this example, the baby-step giant-step method performs well, but in larger examples the memory requirements become problematic, and simply constructing the hash table can take an enourmous amount of time. At some point the time-space tradeoff becomes unfeasible however you slice it.

Pollard's rho method manages to acheive the same aymptotic time complexity while eschewing the memory issues completely. It incorporates a clever idea called the [tortoise and hare algorithm](https://en.wikipedia.org/wiki/Cycle_detection#Tortoise_and_hare) to find two distinct linear combinations of P and Q that produce the same point, so aP + bQ = cP + dQ. Isolating Q yields the private key. The last argument to the function, `bits`, is used to create a small list of randomly generated linear combinations of P and Q, of length 2^bits. This list is then used to define an iterating function on the curve (for details, see section 4.1.2 of Menezes, Hankerson, and Vanstone's Guide to Elliptic Curve Cryptography).

```
>>> crack_rho(C, P, n, Q, 4)
Priv key: d = 692847
Time: 0.077 secs
```

Finally, there is a method to recover the private key from a pair of messages signed using the same value of k, called `crack_from_ECDSA_repeat_k`. This is a very quick calculation, using only a few lines of modular arithmetic and no iteration or arithmetic on the curve at all.


text:
#!/usr/bin/env node

var fs = require('fs');

// Read Tags & Pages
var tags = JSON.parse(_clean(fs.readFileSync('-data/tags.json', 'utf8')));
var pages = JSON.parse(_clean(fs.readFileSync('-data/pages.json', 'utf8')));
var template = fs.readFileSync('_template.html', 'utf8');

// Create the pages!
for(var pageID in pages){

	// What page is this? (and is this the index?)
	var isIndex = (pageID=="index");
	var isAll = (pageID=="all");
	var page = pages[pageID];

	////////////////////////////////
	// EDIT THE TEMPLATE ///////////
	////////////////////////////////

	var html = template+""; // clone string?

	// 1. Base?
	html = html.replace("[[base?]]", isIndex ? "" : "<base href='/'>");

	// 2. Title
	var title = "Explorable Explanations";
	if(!isIndex) title = page.title+" | "+title;
	html = html.replace(/\[\[title\]\]/g, title);

	// 3. Index style? If Text is TOO BIG
	if(page.customCSS){
		html = html.replace("[[index_style?]]", "");
	}else{
		html = html.replace("[[index_style?]]", isIndex ?
			""
		: "");
	}

	// 4. Splash BG color
	if(isIndex || isAll){
		html = html.replace("[[splash_color]]", "");
	}else{
		var tag = tags.filter(function(tag){ return tag.id==pageID; })[0];
		html = html.replace("[[splash_color]]", "style='background:"+tag.color+";'");
	}

	// 5. Iframe Src
	html = html.replace("[[iframe_src]]", "/-splash/"+page.splash);

	// 6. Splash title
	html = html.replace("[[splash_title]]", page.splashTitle);

	// 7. Splash home?
	html = html.replace("[[splash_home?]]", !isIndex ? "" : "");

	// 8. Intro Words
	html = html.replace("[[intro]]", page.intro);

	////////////////////////////////
	// WRITE THE FILE //////////////
	////////////////////////////////

	// What's the path?
	var path = "";
	if(!isIndex){
		path = pageID+"/";
		if(!fs.existsSync(pageID)) fs.mkdirSync(pageID); // Create directory if doesn't already exist
	}

	// Create file!
	fs.writeFileSync(path+"index.html", html, 'utf8');

}

// Helper: remove newlines
function _clean(str){
	str = str.replace(/[^\:]\/\/.*\n/g,""); // strip comments
	str = str.replace(/\n|\t/g,""); // strip newlines & tabs
	return str;
};


text:
<!DOCTYPE html>

	<title>Explorable Explanations</title>

	<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
	<meta content="utf-8" http-equiv="encoding">
	<meta charset="utf-8">
	<meta name="viewport" content="width=640">
	<link rel="icon" type="image/png" href="favicon.png">

	<meta itemprop="name" content="Explorable Explanations">
	<meta itemprop="description" content="a hub for learning through play">
	<meta itemprop="image" content="http://explorabl.es/-sharing/index.png">

	<meta name="twitter:card" content="summary_large_image">
	<meta name="twitter:title" content="Explorable Explanations">
	<meta name="twitter:description" content="a hub for learning through play">
	<meta name="twitter:image" content="http://explorabl.es/-sharing/index.png">

	<meta property="og:title" content="Explorable Explanations">
	<meta property="og:type" content="website">
	<meta property="og:url" content="http://explorabl.es/">
	<meta property="og:image" content="http://explorabl.es/-sharing/index.png">
	<meta property="og:description" content="a hub for learning through play">

	<link rel="stylesheet" type="text/css" href="-css/page.css">

		Sorry! Explorable Explanations needs JavaScript.
		Don't worry, there's no tracking code or anything creepy like that,
		the JS is just so I can dynamically load shtuff.
		Thanks!

EXPLORABLE
EXPLANATIONS

Lion cubs play-fight to learn social skills. Rats play to learn emotional skills. Monkeys play to learn cognitive skills. And yet, in the last century, we humans have convinced ourselves that play is useless, and learning is supposed to be boring.

Gosh, no wonder we’re all so miserable.

 Welcome to Explorable Explanations, a hub for learning through play! We’re a disorganized “movement” of artists, coders & educators who want to reunite play and learning.

 Let's get started! Check out these 3 random Explorables:

[loading...]

				Play Explorable Explanations in specific subjects:

				Make your own Explorable Explanations:

				See everything on one page:

				ALL OF IT

				Explorable Explanations is, above all, an experiment.
				None of us know anything, and that's exciting.
				We'd love for you to join us, in playing & making these interactive things!

				There's a lot to explore with Explorable Explanations. ♥

				@explorables
				r/explorables

			this website is open sauce

			get code & see credits!

			<sharing title="Explorable Explanations"
				 text="Explorable Explanations, a hub for learning through play:"
				 link="http://explorabl.es/"></sharing>


text:
<!DOCTYPE html>

	[[base?]]
	<title>[[title]]</title>

	<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
	<meta content="utf-8" http-equiv="encoding">
	<meta charset="utf-8">
	<meta name="viewport" content="width=640">
	<link rel="icon" type="image/png" href="favicon.png">

	<meta itemprop="name" content="[[title]]">
	<meta itemprop="description" content="a hub for learning through play">
	<meta itemprop="image" content="http://explorabl.es/-sharing/index.png">

	<meta name="twitter:card" content="summary_large_image">
	<meta name="twitter:title" content="[[title]]">
	<meta name="twitter:description" content="a hub for learning through play">
	<meta name="twitter:image" content="http://explorabl.es/-sharing/index.png">

	<meta property="og:title" content="[[title]]">
	<meta property="og:type" content="website">
	<meta property="og:url" content="http://explorabl.es/">
	<meta property="og:image" content="http://explorabl.es/-sharing/index.png">
	<meta property="og:description" content="a hub for learning through play">

	<link rel="stylesheet" type="text/css" href="-css/page.css">
	[[index_style?]]

		Sorry! Explorable Explanations needs JavaScript.
		Don't worry, there's no tracking code or anything creepy like that,
		the JS is just so I can dynamically load shtuff.
		Thanks!

[[splash_title]]

		[[splash_home?]]

[[intro]]

[loading...]

				Play Explorable Explanations in specific subjects:

				Make your own Explorable Explanations:

				See everything on one page:

				ALL OF IT

				Explorable Explanations is, above all, an experiment.
				None of us know anything, and that's exciting.
				We'd love for you to join us, in playing & making these interactive things!

				There's a lot to explore with Explorable Explanations. ♥

				@explorables
				r/explorables

			this website is open sauce

			get code & see credits!

			<sharing title="Explorable Explanations"
				 text="Explorable Explanations, a hub for learning through play:"
				 link="http://explorabl.es/"></sharing>


text:
<!DOCTYPE html>

	<base href='/'>
	<title>Physics | Explorable Explanations</title>

	<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
	<meta content="utf-8" http-equiv="encoding">
	<meta charset="utf-8">
	<meta name="viewport" content="width=640">
	<link rel="icon" type="image/png" href="favicon.png">

	<meta itemprop="name" content="Physics | Explorable Explanations">
	<meta itemprop="description" content="a hub for learning through play">
	<meta itemprop="image" content="http://explorabl.es/-sharing/index.png">

	<meta name="twitter:card" content="summary_large_image">
	<meta name="twitter:title" content="Physics | Explorable Explanations">
	<meta name="twitter:description" content="a hub for learning through play">
	<meta name="twitter:image" content="http://explorabl.es/-sharing/index.png">

	<meta property="og:title" content="Physics | Explorable Explanations">
	<meta property="og:type" content="website">
	<meta property="og:url" content="http://explorabl.es/">
	<meta property="og:image" content="http://explorabl.es/-sharing/index.png">
	<meta property="og:description" content="a hub for learning through play">

	<link rel="stylesheet" type="text/css" href="-css/page.css">

		Sorry! Explorable Explanations needs JavaScript.
		Don't worry, there's no tracking code or anything creepy like that,
		the JS is just so I can dynamically load shtuff.
		Thanks!

PHYSICS

Gedankenexperiment. That’s the German word Albert Einstein used to explain what he did. Gedanken = thoughts, experiment = experiment. By playing with thought experiments in his head, good ol’ Al could unlock the secrets of the universe.

Here’s some explorable explanations of physics, so you can learn by playing with ideas, theories, simulations... gedankenexperiments.

[loading...]

				Play Explorable Explanations in specific subjects:

				Make your own Explorable Explanations:

				See everything on one page:

				ALL OF IT

				Explorable Explanations is, above all, an experiment.
				None of us know anything, and that's exciting.
				We'd love for you to join us, in playing & making these interactive things!

				There's a lot to explore with Explorable Explanations. ♥

				@explorables
				r/explorables

			this website is open sauce

			get code & see credits!

			<sharing title="Explorable Explanations"
				 text="Explorable Explanations, a hub for learning through play:"
				 link="http://explorabl.es/"></sharing>


text:
html, body{
	width: 100%;
	height: 100%;
}
html{
	touch-action: none;
}
body{
	margin:0;
}


text:
var _getCenterOfPoints = function(points){
	var center = [0,0];
	for(var i=0;i<points.length;i++){
		var p = points[i];
		_addToVector(center, [p.x,p.y]);
	}
	center[0] /= points.length;
	center[1] /= points.length;
	return center;
};
var _getClosestPoint = function(points, comparison, withinRadius){
	var minDistance = Infinity;
	var minPoint = null;
	for(var i=0; i<points.length; i++){
		var p = points[i];
		var dx = p.x-comparison.x;
		var dy = p.y-comparison.y;
		var d2 = dx*dx+dy*dy;
		if(d2<minDistance){
			minDistance = d2;
			minPoint = p;
		}
	}
	if(minDistance>=withinRadius*withinRadius) return null; // none close enough
	return minPoint;
};
var _tooClose = function(points, comparison, radius){
	var r2 = radius*radius;
	return points.filter(function(p){
		if(p==comparison) return false;
		var dx = p.x-comparison.x;
		var dy = p.y-comparison.y;
		var d2 = dx*dx+dy*dy;
		return (d2<r2);
	});
};
var _ifPointTooClose = function(point, comparison, radius){
	var points = [point];
	return(_tooClose(points, comparison, radius).length>0);
};
var _angleToVector = function(angle){
	return [Math.cos(angle), Math.sin(angle)];
};
var _vectorToAngle = function(vector){
	return Math.atan2(vector[1], vector[0]);
};
var _addToVector = function(a,b){
	a[0] += b[0];
	a[1] += b[1];
	return a;
};
var _normalize = function(vector, newMag){
	newMag = newMag || 1;
	var mag = _magnitude(vector);
	vector[0] = (vector[0]/mag)*newMag;
	vector[1] = (vector[1]/mag)*newMag;
	return vector;
};
var _magnitude = function(vector){
	return Math.sqrt(vector[0]*vector[0] + vector[1]*vector[1]);
}


text:
Math.TAU = Math.PI*2;

var innerCircleSize = 25;
var outerCircleRatio = 1;
var _drawCircle = function(ctx, outerRadius){

	var Mouse = window.Mouse2 ? window.Mouse2 : window.Mouse;

	ctx.save();

	ctx.strokeStyle = "rgba(255,255,255,0.5)";
	ctx.fillStyle = "rgba(255,255,255,0.5)";

	// Inner circle
	var innerCircleSizeGOTO = (Mouse.pressed ? 50 : 20);
	innerCircleSize = innerCircleSize*0.9 + innerCircleSizeGOTO*0.1;
	ctx.beginPath();
	ctx.arc(Mouse.x*2, Mouse.y*2, innerCircleSize, 0, Math.TAU, false);
	ctx.fill();

	// Outer circle
	if(!window.Mouse2 && !window.noOuterCircle){

		var outerCircleRatioGOTO;
		if(window.CONFETTI_HACK){
			outerCircleRatioGOTO = (Mouse.pressed ? 2 : 1);
		}else{
			outerCircleRatioGOTO = (Mouse.pressed ? 0.9 : 1);
		}

		outerCircleRatio = outerCircleRatio*0.9 + outerCircleRatioGOTO*0.1;
		ctx.beginPath();
		ctx.arc(Mouse.x*2, Mouse.y*2, outerRadius*outerCircleRatio, 0, Math.TAU, false);
		ctx.lineWidth = 2;
		ctx.stroke();
	}

	ctx.restore();

}


text:
(function(exports){

	// Singleton
	var Mouse = {
		x: document.body.clientWidth/2,
		y: document.body.clientHeight/2,
		pressed: false
	};
	exports.Mouse = Mouse;

	// Event Handling
	var onMouseMove,onTouchMove;

	document.body.addEventListener("mousedown",function(event){
	    Mouse.pressed = true;
	    onMouseMove(event);
	},false);

	document.body.addEventListener("mouseup",function(event){
	    Mouse.pressed = false;
	},false);

	document.body.addEventListener("mousemove",onMouseMove = function(event){
		Mouse.x = event.pageX;
		Mouse.y = event.pageY - window.pageYOffset;
	},false);

	document.body.addEventListener("touchstart",function(event){
	    Mouse.pressed = true;
	    onTouchMove(event);
	},false);

	document.body.addEventListener("touchend",function(event){
	    Mouse.pressed = false;
	},false);

	document.body.addEventListener("touchmove",onTouchMove = function(event){
		Mouse.x = event.changedTouches[0].clientX;
		Mouse.y = event.changedTouches[0].clientY - window.pageYOffset;
		event.preventDefault();
	},false);

})(window);


text:
<!DOCTYPE html>

<link rel="stylesheet" type="text/css" href="../splash.css">