Skip to content

Commit

Permalink
Experimental version, can read and write a Ruby Marshal list of class…
Browse files Browse the repository at this point in the history
…es, needs more testing and more ruby token support as we go
  • Loading branch information
optimus-code committed Aug 18, 2024
1 parent 1c571b7 commit 3d3d300
Show file tree
Hide file tree
Showing 18 changed files with 430 additions and 915 deletions.
4 changes: 3 additions & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
MIT License

Copyright (c) 2024 HNIdesu
RmSharp - Copyright (C) 2024 optimus-code

RubyMarshal - Copyright (c) 2024 HNIdesu

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
110 changes: 76 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,76 @@
### All marshal types
| Token | Type |
| --- | --- |
| " | String |
| [ | Array |
| i | Fixnum/long |
| 0 | Nil |
| o | Object |
| : | Symbol |
| ; | Symbol Link |
| I | Instance Variable |
| T | True |
| F | False |
| f | Float |
| { | Hash |
| @ | Object References |
| } | Default Hash |
| / | Regexp |
| l | Bignum |
| S | Struct |
| c | Class |
| m | Module |
| C | User Class |
| e | Extended |
| U | User Marshal |
| d | Data |
| u | User Defined |
| e | Extended |

### Not supported types
| Token | Type |
| --- | --- |
| U | User Marshal |
| d | Data |
# RmSharp

**RmSharp** is a C# .NET class library designed for deserializing and serializing Ruby Marshal files. It enables seamless translation between C# models and Ruby Marshal files, allowing you to work with Ruby data structures directly in your C# applications.

This library is built upon the work done in [HNIdesu's RubyMarshal project](https://github.com/HNIdesu/RubyMarshal). Please note that not all Ruby Marshal tokens are supported in the current version, making it unsuitable for production use at this time.

## Features

- **Deserialize** Ruby Marshal files directly into C# objects.
- **Serialize** C# objects into Ruby Marshal files.
- Support for Arrays, basic types, and objects.
- Attribute-based mapping to maintain Ruby naming conventions for classes and properties.

## Installation

You can install RmSharp via NuGet:

```shell
dotnet add package RmSharp
```

## Usage

### Deserializing Ruby Marshal Files

You can deserialize a Ruby Marshal file into your C# model as shown below:

```csharp
using (var stream = File.OpenRead(file))
{
var instance = RmSerialiser.Deserialise<YourType>(stream);
}
```

### Serializing C# Objects to Ruby Marshal Files

To serialize your C# object into a Ruby Marshal file:

```csharp
using (var stream = File.OpenWrite(file))
{
RmSerialiser.Serialise(stream, instance);
}
```

### Attribute-Based Mapping

To ensure that Ruby naming conventions are maintained in your C# classes, you need to use the `[RmName]` attribute:

```csharp
[RmName("ModuleName::ClassName")]
public class YourClass
{
[RmName("ruby_name")]
public string YourProperty { get; set; }
}
```

This will map your C# class and properties to the corresponding Ruby class and properties during serialization and deserialization.

## Limitations

- **Not Production-Ready**: This library is still in its early stages and is not ready for production use.
- **Limited Token Support**: Currently, only a subset of Ruby Marshal tokens is supported, including arrays, basic types, and objects.

## Credits

This library is based on the work done in the [RubyMarshal project by HNIdesu](https://github.com/HNIdesu/RubyMarshal). We are deeply grateful for their contributions to the Ruby Marshal serialization format.

## Contributing

Contributions are welcome! Feel free to open issues or submit pull requests to improve the library.

## License

RmSharp is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details.
12 changes: 11 additions & 1 deletion RmSharp.Tests/RmSharp.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Nullable>disable</Nullable>

<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
Expand All @@ -24,4 +24,14 @@
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
</ItemGroup>

<ItemGroup>
<Folder Include="Data\" />
</ItemGroup>

<ItemGroup>
<None Update="Data\States.rxdata">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
11 changes: 0 additions & 11 deletions RmSharp/Attributes/AddReferenceAttribute.cs

This file was deleted.

13 changes: 13 additions & 0 deletions RmSharp/Attributes/RmNameAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;

namespace RmSharp.Attributes
{
public class RmNameAttribute( string name ) : Attribute
{
public string Name
{
get;
private set;
} = name;
}
}
106 changes: 106 additions & 0 deletions RmSharp/Converters/ClassConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
using RmSharp.Attributes;
using RmSharp.Exceptions;
using RmSharp.Extensions;
using RmSharp.Tokens;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;

namespace RmSharp.Converters
{
public class ClassConverter : RmTypeConverter
{
private readonly List<string> _readSymbols = new List<string>();
private readonly List<string> _writeSymbols = new List<string>( );
private readonly RmNameAttribute _typeName;
private readonly List<(PropertyInfo, RmNameAttribute)> _instanceVariables = [];
private readonly SymbolConverter _symbolConverter = RmConverterFactory.SymbolConverter;

public ClassConverter( Type type )
: base( type )
{
_typeName = type.GetCustomAttribute<RmNameAttribute>( );

if ( _typeName == null )
throw new RmException( $"No RmName attribute on type '{type.FullName}'." );

var properties = type.GetProperties( BindingFlags.Instance | BindingFlags.Public );

if ( properties == null || !properties.Any() )
throw new RmException( $"No properties accessible on type '{type.FullName}'." );

foreach ( var property in properties )
{
var nameAttribute = property.GetCustomAttribute<RmNameAttribute>( );

if ( nameAttribute == null )
continue;

_instanceVariables.Add((property, nameAttribute));
}
}

public override object Read( BinaryReader reader )
{
return reader.ReadValue( ( token ) =>
{
var className = ( string ) _symbolConverter.Read( reader );

if ( className != _typeName.Name )
throw new RmException( $"Class name '{className}' does not match expected type '{_typeName.Name}'." );

var instance = Activator.CreateInstance( Type );
var count = reader.ReadFixNum<int>( );

for ( var i = 0; i < count; i++ )
{
var symbolName = ( string ) _symbolConverter.Read( reader );

var property = _instanceVariables
.Where( i => symbolName == "@" + i.Item2.Name )
.Select( i => i.Item1 )
.FirstOrDefault( );

if ( property == null )
throw new RmException( $"Instance variable '{symbolName}' not found in type '{Type.FullName}'." );

var typeConverter = RmConverterFactory.GetConverter( property.PropertyType );

if ( typeConverter == null )
throw new RmException( $"No type converter found for '{property.PropertyType.FullName}'." );

object value = typeConverter.Read( reader );
property.SetValue( instance, value );
}

return instance;
}, RubyMarshalToken.Object );
}

public override void Write( BinaryWriter writer, object instance )
{
writer.WriteValue( instance, RubyMarshalToken.Object, ( ) =>
{
_symbolConverter.Write( writer, _typeName.Name );

writer.WriteFixNum( _instanceVariables.Count );

foreach ( var instanceVariable in _instanceVariables )
{
var rubyName = "@" + instanceVariable.Item2.Name;

_symbolConverter.Write( writer, rubyName );

var typeConverter = RmConverterFactory.GetConverter( instanceVariable.Item1.PropertyType );

if ( typeConverter == null )
throw new RmException( $"No type converter found for '{instanceVariable.Item1.PropertyType.FullName}'." );

typeConverter.Write( writer, instanceVariable.Item1.GetValue( instance ) );
}
} );
}
}
}
24 changes: 24 additions & 0 deletions RmSharp/Converters/RmConverterFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ namespace RmSharp.Converters
{
public static class RmConverterFactory
{
public static SymbolConverter SymbolConverter
{
get;
private set;
} = new( );

private static readonly Dictionary<Type, RmTypeConverter> _typeConverters =
new Dictionary<Type, RmTypeConverter>
{
Expand All @@ -26,6 +32,8 @@ public static class RmConverterFactory
{ typeof( ulong ), new UInt64Converter( ) },
};

private static readonly Dictionary<Type, RmTypeConverter> _classConverters = [];

public static RmTypeConverter GetConverter( Type type )
{
if ( _typeConverters.TryGetValue( type, out var converter ) )
Expand All @@ -44,7 +52,23 @@ public static RmTypeConverter GetConverter( Type type )
{
return new DictionaryConverter( type );
}
else if ( type.IsClass )
{
if ( _classConverters.TryGetValue( type, out converter ) )
{
return converter;
}

converter = new ClassConverter( type );
_classConverters.Add( type, converter );
return converter;
}
return null;
}

public static void Reset( )
{
SymbolConverter.Reset( );
}
}
}
67 changes: 67 additions & 0 deletions RmSharp/Converters/SymbolConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using RmSharp.Exceptions;
using RmSharp.Extensions;
using RmSharp.Tokens;
using System.Collections.Generic;
using System.IO;

namespace RmSharp.Converters
{
public class SymbolConverter : RmConverter
{
private readonly List<string> _readSymbols = [];
private readonly List<string> _writeSymbols = [];

public override object Read( BinaryReader reader )
{
var token = reader.ReadToken( RubyMarshalToken.Symbol, RubyMarshalToken.SymbolLink );
var symbol = "";

if ( token == RubyMarshalToken.Symbol )
{
symbol = reader.ReadRubyString( false );
_readSymbols.Add( symbol );
}
else
{
var symbolID = reader.ReadFixNum<int>( );

if ( symbolID >= _readSymbols.Count )
throw new RmException( $"No valid Symbol Link '{symbolID}''." );

symbol = _readSymbols[symbolID];
}

return symbol;
}

public override void Write( BinaryWriter writer, object instance )
{
var symbol = ( string ) instance;

if ( HasWriteSymbol( symbol, out var id ) )
{
writer.Write( RubyMarshalToken.SymbolLink );
writer.WriteFixNum( id );
}
else
{
writer.Write( RubyMarshalToken.Symbol );
writer.WriteRubyString( symbol, false );
_writeSymbols.Add( symbol );
}
}

private bool HasWriteSymbol( string symbol, out int id )
{
id = _writeSymbols.IndexOf( symbol );

return id != -1;
}

public void Reset( )
{
_writeSymbols.Clear( );
_readSymbols.Clear( );
}
}
}
Loading

0 comments on commit 3d3d300

Please sign in to comment.