GithubHelp home page GithubHelp logo

kingcom / armips Goto Github PK

View Code? Open in Web Editor NEW
356.0 356.0 75.0 2.03 MB

An assembler for various ARM and MIPS platforms. Builds available at http://buildbot.orphis.net/armips/

License: MIT License

C++ 92.31% C 0.82% CMake 1.04% Assembly 5.25% Makefile 0.22% Shell 0.35%

armips's People

Contributors

aliaspider avatar archshift avatar bentley avatar bigpet avatar blade2187 avatar dfuchsgruber avatar fothsid avatar germanaizek avatar hrydgard avatar jeffman avatar joeydumont avatar kabochi avatar kingcom avatar kwyxz avatar lemonboy avatar lioncash avatar nekopsykose avatar normmatt avatar onlymx13 avatar peterlemon avatar prof9 avatar queueram avatar radimerry avatar ryandwyer avatar sp1187 avatar stradama avatar thar0 avatar unknownbrackets avatar warmenhoven avatar zeturic avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

armips's Issues

filesize() returns 0 if the file is created earlier in the script

I'm not sure if this is by design or not but it has been an issue. Files created earlier in the script return 0 size if checked later in the file after it is closed.
Example:

.nds
.create "a.bin",0
.byte 1,1,1,1
.close

.notice fileexists("a.bin")
.notice filesize("a.bin")

The expectation here is after the .close the file is created and the notices should be
1 and 4. Instead the notices are 0 and 0.
Simply running the script again gives the expected result.

What are the equivalents from arm-none-eabi-as

Hi,
I've 2 questions regarding some features that arm-none-eabi-as allow me to use that i couldn't find any equivalent for arm-ips.
With arm-none-eabi-as it's possible to do something like:

.thumb
ldr r0, my_label
ldr r1, my_label

.align 2
my_label:
        .word 0x89ABCDEF

But armips give me an error: THUMB parameter failure
I couldn't find how to do the same with armips, other than doing this:

ldr r0, =0x89ABCDEF
ldr r1, =0x89ABCDEF
.pool

But in some case I would prefer to use label instead, is there any way to do so?

I also can't load half-word using that way: ldrh r0, =0xABCD, and i don't get why.

MIPS break instruction discrepancy

I'm seeing a discrepancy between the way armips and gas encode the break instruction. The way I read the instruction set (least significant 6-bits=0xD, most significant 6-bits=0, middle 20-bits=immediate 'code'), armips seems correct, but both binutils and llvm do it another way. Can anyone shed some light on this?

armips:

$ echo '.n64 :: .create "break.bin", 0 :: break 7 :: .close' > break.s
$ armips break.s
$ mips64-elf-objdump -b binary -EB -mmips -D break.bin
Disassembly of section .data:

00000000 <.data>:
   0:   000001cd        break   0x0,0x7

# not just binutils!:
$ rasm2 -a mips -D -e -Bf break.bin
0x00000000   4                 000001cd  break 0, 7

gas:

$ echo "break 7" | mips64-elf-as -o break.o -
$ mips64-elf-objdump -d break.o
Disassembly of section .text:

00000000 <.text>:
   0:   0007000d        break   0x7

Generated symfile is corrupt

Using a built of the latest commit on GitHub. The following .asm file:

.gba
.open "input.gba","output.gba",0x08000000

.close

ran with:

armips.exe src.asm -sym output.sym

generates the following symfile:

00000000 0
08400000 test
FFFFFFFFFFFFFFFF .thumb

which No$gba complains is corrupt and refuses to load:

Symbolic Info File Corrupt, Line 3

It looks like the FFFFFFFFFFFFFFFF .thumb line is the culprit; replacing it with FFFFFFFF .thumb will fix the problem. Though I'm not sure what it's doing there in the first place...

EDIT: I've tried with -sym2 as well, same result.

Feature request: big endian !

I love armips and I use it for everything but now I'm working on a big endian arm platform and it seems armips doesn't support big endian. any chance that will change in the future ?

thanks for your great work !

.open in false block can cause an error if file does not exist

.if 0==1
.open "a.bin", "aa.bin", 0x08000000
.elseif 1==1
.open "b.bin", "bb.bin", 0x08000000
.endif
.close

If a.bin doesn't exist but b.bin does armips gives an error that a.bin is not found. If a.bin exists there is no error. I would expect armips not to care if a.bin exists if it's in a false block.

No error thrown on non-terminated macro

Armips happily accepts the following input, but it would be helpful if an error was thrown instead. No output "test.bin" file is created. Using Armips 07853e5.

.macro test
dw 0x12345678

.gba
.create "test.bin",0x08000000

test

.close

Command line arguments -equ and -strequ case weirdness

For example, if test.asm contains just:

.warning STRING

And you invoke it with armips test.asm -strequ STRING something, you get an error complaining that the label string does not exist. It works if you invoke it with armips test.asm -strequ string something instead.

It seems like the command line argument parser needs to lowercase the names (like is done in other contexts, such as a literal EQU in source) to make it properly case-insensitive, as it is elsewhere.

This applies to both -equ and -strequ, as the title implies.

Compiler warning: unsequenced modification

Archs\MIPS\PsxRelocator.cpp:53:26:

                          skip += input[pos+skip++];

Having skip++ in an expression assigned to skip itself is not safe. Would be nice to get it cleaned up to avoid getting a warning when building.

align requires an argument

Readme.md suggests that the argument to .align is optional, and this seems to be the case in my old copy of armips.

.align [num]

Writes zeroes into the output file until the output
position is divisible by num. If num is not given,
4 will be used by default. num has to be a power of two.

But if I try it in git master, armips gives an error. E.g.,

.create "blah.bin",0
.psx
.align  
.close

$ armips foo.s
ARMIPS64 Assembler v0.8.0 (Apr 12 2016 03:22:58) by Kingcom
/tmp/foo.s(3) fatal error: Invalid alignment
/tmp/foo.s(3) error: Undefined label ".close"
Aborting.

If I replace the “align” with “align 4”, it works:

$ armips foo.s
ARMIPS64 Assembler v0.8.0 (Apr 12 2016 03:22:58) by Kingcom
Done.

LDR-relative problems.

{ "ldr",	"d,[r\xF]",			0x4800,	THUMB_TYPE6,	2,	0 },
{ "ldr",	"d,[r\xF,/#i\x08]",	0x4800,	THUMB_TYPE6,	2,	THUMB_IMMEDIATE },
{ "ldr",	"d,[/#I\x20]",		0x4800,	THUMB_TYPE6,	2,	THUMB_IMMEDIATE|THUMB_PCR },
{ "ldr",	"d,=/#I\x20",		0x4800,	THUMB_TYPE6,	2,	THUMB_IMMEDIATE|THUMB_POOL },
  1. 2nd one - Immediate Value
    The second one ... well, I was thinking that should go from 0x0 - 0x3FC, but since the THUMB_WORD isn't a flag, >> 2, never happens. (Already tried ldr r#, [pc, 0x100] = Causes error. ; Unless maybe it was intended to be some type of word-index value instead of relative offset? (I was doubting it... especially since usually any numbers in [,] are supposed to be added together... , But hard to know if this was an actual design decision or not b/c the number must get from a 32-bit aligned address? So maybe this "issue" was accidentally posted. Haha... Or maybe not... Since you can /still/ add code to check the alignment anyway... Hmm...))

  2. 3rd one - "Stuck in infinite validation loop"
    No problem with immediate value? Since THUMB_PCR does the >> 2.
    Is ldr r#, [label] unusable? I get Stuck in infinite validation loop if a label is used, but not if an address number itself is used.. I probably did something wrong, though. Either case, the error is definitely not informative enough? (But I suppose it was more of a catch for unknown bugs.) v0.7d does not seem to give Stuck in infinite validation loop error? (But also seems contradictory to the newer version with the "PC relative address must be word-aligned" error..) I suppose I shall check again a bit later... Make sure I am 100% using the latest version, and see if I can figure out what's going on.

Is blh as a sole instruction supported? / Any plans to add it if not?

I know that the list of instructions at https://github.com/Kingcom/armips/blob/master/Archs/ARM/ThumbOpcodes.cpp shows:

{ "bl",		"/#I\x16",			0xF800,	THUMB_TYPE19,	4,	THUMB_IMMEDIATE|THUMB_BRANCH|THUMB_LONG },

However seeing as it says bl instead of blh ... and that the mask still has x16 in it, I figured maybe it went with the original bl instruction.

The reason why I ask, is, I know that games can use blh as a sole instruction as seen by Golden Sun 1/2 's code... Where they manually put a RAM address into LR, and do blh 0x0000 to call it. (The map code stuff...)

We know that in a normal bl instruction, It's basically:
1st instruction = LR = PC+4+(nn SHL 12)
2nd instruction = PC = LR + (nn SHL 1), and LR = PC+2 OR 1
...just to point out that blh (0xF800) works the same way regardless of whether it has its counterpart or not.

I know that there are alternatives that I could do like using other instructions, or even doing .halfword 0xF800 for worst case scenario.


Edit again: Gbatek seems to refer to it as "BL LR+imm" ... -

.open [single file] doesn't always work with .relativeinclude on

> tools/armips.exe src/boot-patches.asm
ARMIPS Assembler v0.8.0 (Feb 20 2016 13:31:51) by Kingcom
src/boot-patches.asm(4) fatal error: Could not open file ../workdir/BOOT.BIN.patched
Aborting.

The contents of the asm file are:

.psp
.relativeinclude off
.open "../workdir/BOOT.BIN.patched", 0x08803F60
...

Note that neither the source, nor the target file are in current working directory.
Works fine when 2 different paths in .open are used, or when switched to .relativeinclude off

.definelabel in false block creates label

In code like

.if 0==0
     .definelabel label1, 0
.else
     .definelabel label1, 1
.endif

Will give the error that label is already defined in the line that's inside the false block. This .definelabel is in a false block so should not be defining the label.

Recursive equ memory leak

The following .asm file causes armips to spin out of control:

input equ input-input

.gba
.open input-input.gba,0x08000000

.close

Probably safe to say the reason for this is that input-input.gba gets expanded to input-input-input-input.gba, then to input-input-input-input-input-input-input-input.gba, and so on.

Replaceall

For example, replace any occurrences (with proper alignment) of 0x01234567 in the currently open file with 0x89ABCDEF.

I'm repointing something in a gba rom with armips; the problem is that there are ~200 pointers to the thing I'm repointing. Currently, I just found all of the addresses with a hex editor, and have a huge sequence of .org/.word pairs to update them all, but that's unwieldy. A directive that would automatically scan the open file and do it for me would be very useful.

Allow omission of address offset in MIPS cache instruction

Feature request to allow the address offset of the cache instruction to be omitted if its value is 0, similar to load and store instructions. i.e., make these two equivalent:

 cache 1, ($t0)
 cache 1, 0($t0)

Currently, armips issues a MIPS parameter failure in the former case. gas allows both.

Can't compile on linux

link.txt is setup like it push the compiled file as armips in a dir where a armips dir exist,
you could setup a build folder in root dir and link.txt output as "-o build/armips -rdynamic"

Feature request: $ and math operations in notice/errors/warnings

First of all - thank you very much for this invaluable tool, it is really a masterpiece in romhacking world I would say :)

But useful things were not implemented, which could be really useful.

  1. Some way to define current address. Yes, this could be done using label, but very convenient just to put some instruction to define that. In assembers I used this was usually just a $ sign. Works this way: lhu a0,$-4 - put in a0 address of previous command. Really useful when you create some self-modifying code.
  2. Some way to display an equation, based on the assembled data. Now I see only way to show plain text message, so if you for example want to know on what address is "label1" or what is the size of data between label1 and label2 - there is no easy way to display that and immediately see. Of course, it is possible to export symbol file, do manually counting, etc... - but why, if it is possible just to do all with some display command?

Hope these requests are not really complicate.

Linux and macOS Automated Builds

Now that ARMIPS has automated builds for Windows, it would be nice if we had this for other platforms too.

I've written a configuration for Travis CI which deploys to GitHub Releases automatically over here, but it would be better if this could be integrated into the main repository.

I'd be happy to work on a pull request if necessary to get this going, but I'd be unable to set up the actual Travis CI integration.

My repostory also has an AppVeyor configuration to deploy Windows 32-bit and 64-bit builds to BinTray which might also be useful.

Problems with .org and equ

Hello,
First of all, thanks for such an amazing work, your assembler makes assembly really easy to insert

Using it, I wanted to create structures or enums like this:
.org Myloc
start_new_battle_struct equ .
DoubleWordMega equ (.-start_new_battle_struct) :: .word 0, 0
YourWordLocation equ (.-start_new_battle_struct) :: .word 0
simulated_hitmarker equ (.-start_new_battle_struct) :: .word 0
SlowStartLocation equ .-start_new_battle_struct :: .word 0
size_new_battle_struct equ .-start_new_battle_struct

but it seems that all the things defined through these equ take the value 0.
Are these results normal?

Also, I have a suggestion about the equ :
Would it be possible to make the assembler read all the equ before assembling? They can't be defined twice anyway, and in some case it'd be a lot more practical.

Thanks for your time!

Duplicate errors/wrong line numbers in macros

// empty line
.macro .test
	.notice readu32("doesntexist.bin")
.endmacro
// empty line
.test
.test

Produces the following three error messages:

test.asm(7) error: Could not open 001A6948
test.asm(7) error: Could not open 001A6600
test.asm(5) error: Could not open 001A6830

Even though the macro is only being called twice. Additionally, the line number for the error message is wrong; line 5 is an empty line, line 7 is the second .test call which makes sense (although it would be nicer to have the line number inside the macro, imo) but there is no corresponding error message for the first .test call on line 6.

Crash on recursive macro call

ARMIPS crashes with the following input .asm:

.macro .repeat,count
.notice "Test " + count
.if count > 0
	.repeat (count-1)
.endif
.endmacro

.repeat 3

Recursive macro calls in .if .elseif inside macro lead to max recursion

// cached repeat
.macro .crepeat,index,count
	.if count < 0
		// count initialized once
		.crepeat index,3
	.elseif index < count
		.notice "test "+index
		.crepeat (index+1),count
	.endif
.endmacro
.crepeat 0,-1

This macro call should print three test lines; on the first call count < 0, so it's called again with count initialized to 3 (this would be replaced by an 'expensive' operation such as readu32()); then, it's called three times with index incrementing up to 3. However, the macro goes into max recursion instead and produces the following output:

test.asm(8) error: Parse error '.endif'
test.asm(2) error: Directive not terminated
test.asm(10) error: Max include/recursion depth reached

[Feature request] specify working directory from command line

Currently the root path for .open commands can either be the cwd, with .relativeinclude off, or the directory of the script file, with .relativeinclude on.
This is quite limiting if asm files stay separately from binaries and then the project folder is reorganized, directories are moved etc. The paths are hardcoded to the asm file and it's not very flexible.

My idea is to allow setting the root working directory for with a command line switch, like -root. So that we can:
armips src/*.asm -root myfiles/
in the build scripts, and the source remains as clean as
.open "mycoolstuff.bin"

Recent change broke mcr and mrc for nds.

A recent change changed the encoding for both the mcr and mrc instructions for the nds.
It changed, for example mrc p15, 0, r0, c2, c0, 0, from 10 0F 12 EE to 10 20 B6 FF. This "new" instruction is seen as undefined in objdump and does not run properly on the target system (3ds).

ELF: Wrong string table used for symbol names

When importing an object file: if the section header string table is stored after the symbol string table, then it will be used as the symbol string table as well. The symbol names will then be totally wrong and cause undefined symbol errors.

Somewhere around line 414 in ElfFile.cpp, there should be a check to see if the current section is the section header string table before assigning section to strTab.

.area directive

If the code/data exceed the size, don't show the error message

An example:
.area 3
db 1,2,3,4
.endarea

Assembler output:
"ARMIPS Assembler....
"m2.asm(10) error:"
"Aborting."

Error in ".str/.strn" with 2 tables

An example:

"test.asm":
.psx
.create "test.bin", 0
.org 0
.table "test1.tbl"
.strn "ABCDE"
.table "test2.tbl"
.strn "ABCDE"
.close

"test1.tbl":
00=0000
41=A
42=B
43=C
44=D
45=E

"test2.tbl":
61=A
62=B
63=C
64=D
65=E

"test.bin" is: 41-42-43-44-45-61-65, miss "63-63-64"

with 00=00 -> 41-42-43-44-45-61-63-64-65, miss "62"
with 00=000 -> 41-42-43-44-45-61-64-65, miss "62-63"

Can not checkout

When I try to build via the RetroPie setup I get the following error:

Submodule 'ext/armips' (https://github.com/Kingcom/armips) registered for path 'ext/armips'
Submodule 'ffmpeg' (https://github.com/libretro/ppsspp-ffmpeg.git) registered for path 'ffmpeg'
Submodule 'lang' (https://github.com/hrydgard/ppsspp-lang.git) registered for path 'lang'
Submodule 'native' (https://github.com/libretro/ppsspp-native.git) registered for path 'native'
Submodule 'pspautotests' (https://github.com/hrydgard/pspautotests.git) registered for path 'pspautotests'
Cloning into 'dx9sdk'...
Submodule path 'dx9sdk': checked out 'ec19e643461c84dbb256f6faaaab02cba61d4edc'
Cloning into 'ext/armips'...
Submodule path 'ext/armips': checked out '8bd93be9ba715d2f5723857edacf936ae904d07c'
Submodule 'ext/tinyformat' (https://github.com/Kingcom/tinyformat) registered for path 'ext/tinyformat'
Cloning into 'ext/tinyformat'...
fatal: reference is not a tree: b7f5a22753c81d834ab5133d655f1fd525280765
Unable to checkout 'b7f5a22753c81d834ab5133d655f1fd525280765' in submodule path 'ext/armips/ext/tinyformat'
Cloning into 'ffmpeg'...
Submodule path 'ffmpeg': checked out 'e3b21c60f9bb80524be2293e6e434d04f6b6b4c3'
Cloning into 'lang'...
Submodule path 'lang': checked out '70f23a30317c6fb3ac8aafcefa2185c7fcf8168a'
Cloning into 'native'...
Submodule path 'native': checked out '9baedbcc2a07b3bccc6d8b8d170498111b990adc'
Cloning into 'pspautotests'...
Submodule path 'pspautotests': checked out '905c3018d01af9dfb511c87e65e07a49257a33ac'
Failed to recurse into submodule path 'ext/armips'
~/RetroPie-Setup
Error running 'git clone --recursive --depth 1 --branch libretro https://github.com/RetroPie/ppsspp.git /home/rasmus/RetroPie-Setup/tmp/build/lr-ppsspp' - returned 1

Something seems to be wrong with your submodule

Tinyformat files missing when downloading as ZIP?

So I downloaded a ZIP from the "Clone or download" button... When I went to compile, I noticed I had build errors telling me that 'ext/tinyformat/tinyformat.h' couldn't be opened/found. So when I went to go check the directory... I went to ext, and saw the tinyformat folder, but guess what? There was nothing in it! (Neither from the directory I extracted nor the ZIP folder.) And... if you look on this Github, you'll see that the tinyformat folder seems to be named "tinyformat @ b7f5a22" or something. (Possible glitch?)... And that folder does have its files. I did try downloading from both Firefox and Google Chrome. (Didn't think it was a browser issue.) Oh, and I use Windows 10, if that means anything. (Doubt it.) The "Size" seems to be 191 KB (196,189 bytes). My guess is that it is a Github glitch. - Anyone else having the problem? If not, then probably not much of a big deal since I should be able to manually copy those files myself...

Statement syntax proposal

My suggestion is that newlines are restored as statement separators, but supporting \ at the end of a line to continue a statement into the next line, similar to how statements work in Python.
This should allow opcodes and directives to have optional first arguments, solving issue #50 and allowing MIPS break and syscall to be used without an argument. It also forbids potentially confusing syntax like instruction1 arg instruction2 arg in the same line.

Example:

addi t1, t2, \
very_long_expression_here

readu32() etc evaluated in false block

Given the following input .asm and an empty test.bin file:

.if 0x12345678 < filesize("test.bin")
	.notice tohex(readu32("test.bin", 0x12345678))
.endif

Produces test.asm(2) error: Invalid expression due to the readu32 offset 0x12345678 being greater than the file size. However, nothing should happen since the readu32() occurs in a false block.

However, if a readu32() in a false block is passed an undefined label as the filename, then it does work. The following compiles without errors:

.if 0
.notice tohex(readu32(undefinedlabel))
.endif

By the way, the error message for a non-existant file doesn't print correctly. .notice tohex(readu32("doesntexist.bin")) produces test.asm(1) error: Could not open 00D22220 where 00D22220 is a random memory address.

Using 91ad61a. Probably similar to #83.

Bad message error in ".open"

1.- A tiny bug, maybe solved in the latest revision, but I can't check it. ".open FILE1, FILE2" show "fatal error: FILE2 not found" if FILE1 do not exist.

2.- Can you include a vcproject file compatible with VC2010? My PC has died and I can't install VC2013 in my old PC with Windows Vista to compile ARMIPS.
Can I find the last EXE somewhere?

CUE

.ascii "foo"0 does not throw an error

Expected behavior: an error is thrown, as "foo"0 is faulty syntax (the comma is missing). Instead, the quotes are stripped and the character 0 is happily included in the string.

ConditionalUnsafe expression using label inside macro causes infinite loop/memory blowup

Here's a fun one.

test.asm

.macro .test,label
.if filesize(label)
.endif
.endmacro

.test label

If you:

  • Create a macro with an argument;
  • Put in an .if block;
  • Have it use a ConditionalUnsafe function using said argument;
  • Then call the macro with a label that has the exact same name as the macro argument name;

ARMIPS will go into an infinite loop and consume all memory. This only happens if the exact same label name is used for the macro argument, the function argument and the macro call. It doesn't matter if the label is actually defined or not.

Using 7f8579a.

EDIT: Changing title since the missing .endif isn't actually part of it.

Some issues

ARMIPS with a nonexistent file only show the first character:

armips test.asm
ARMIPS Assembler v0.7d (Oct 21 2013 19:35:51) by Kingcom
File t not found <-- must be "test.asm" instead "t"

A warning compiling with Visual Studio 2010:

Archs\MIPS\CMipsInstruction.cpp(419): warning C4018: '>' : no coinciden signed/unsigned <--- mismatched signed/unsigned

Small Idea: Adding block comments

I don't see my request as high priority, but some older ASM hacks have lots of block comments for documentation purposes. So, it would be useful to not only have line, but also block comments.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.